1 #include "first.h" 2 3 /* mod_authn_sasl 4 * 5 * FUTURE POTENTIAL PERFORMANCE ENHANCEMENTS: 6 * - database response is not cached 7 * TODO: db response caching (for limited time) to reduce load on db 8 * (only cache successful logins to prevent cache bloat?) 9 * (or limit number of entries (size) of cache) 10 * (maybe have negative cache (limited size) of names not found in database) 11 * - database query is synchronous and blocks waiting for response 12 */ 13 14 #include <sasl/sasl.h> 15 16 #include "base.h" 17 #include "http_auth.h" 18 #include "log.h" 19 #include "plugin.h" 20 21 #include <sys/utsname.h> 22 #include <stdlib.h> 23 #include <string.h> 24 25 typedef struct { 26 const char *service; 27 const char *fqdn; 28 const buffer *pwcheck_method; 29 const buffer *sasldb_path; 30 } plugin_config; 31 32 typedef struct { 33 PLUGIN_DATA; 34 plugin_config defaults; 35 plugin_config conf; 36 37 int initonce; 38 } plugin_data; 39 40 static handler_t mod_authn_sasl_basic(request_st *r, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw); 41 42 INIT_FUNC(mod_authn_sasl_init) { 43 static http_auth_backend_t http_auth_backend_sasl = 44 { "sasl", mod_authn_sasl_basic, NULL, NULL }; 45 plugin_data *p = calloc(1, sizeof(*p)); 46 47 /* register http_auth_backend_sasl */ 48 http_auth_backend_sasl.p_d = p; 49 http_auth_backend_set(&http_auth_backend_sasl); 50 51 return p; 52 } 53 54 FREE_FUNC(mod_authn_sasl_free) { 55 plugin_data * const p = p_d; 56 if (p->initonce) sasl_done(); 57 if (NULL == p->cvlist) return; 58 /* (init i to 0 if global context; to 1 to skip empty global context) */ 59 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) { 60 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 61 for (; -1 != cpv->k_id; ++cpv) { 62 switch (cpv->k_id) { 63 case 0: /* auth.backend.sasl.opts */ 64 if (cpv->vtype == T_CONFIG_LOCAL) free(cpv->v.v); 65 break; 66 default: 67 break; 68 } 69 } 70 } 71 } 72 73 static void mod_authn_sasl_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 74 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 75 case 0: /* auth.backend.sasl.opts */ 76 if (cpv->vtype == T_CONFIG_LOCAL) 77 memcpy(pconf, cpv->v.v, sizeof(plugin_config)); 78 break; 79 default:/* should not happen */ 80 return; 81 } 82 } 83 84 static void mod_authn_sasl_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 85 do { 86 mod_authn_sasl_merge_config_cpv(pconf, cpv); 87 } while ((++cpv)->k_id != -1); 88 } 89 90 static void mod_authn_sasl_patch_config(request_st * const r, plugin_data * const p) { 91 p->conf = p->defaults; /* copy small struct instead of memcpy() */ 92 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/ 93 for (int i = 1, used = p->nconfig; i < used; ++i) { 94 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 95 mod_authn_sasl_merge_config(&p->conf, 96 p->cvlist + p->cvlist[i].v.u2[0]); 97 } 98 } 99 100 static plugin_config * mod_authn_sasl_parse_opts(server *srv, const array * const opts) { 101 const data_string *ds; 102 const char *service = NULL; 103 const char *fqdn = NULL; 104 const buffer *pwcheck_method = NULL; 105 const buffer *sasldb_path = NULL; 106 107 ds = (const data_string *) 108 array_get_element_klen(opts, CONST_STR_LEN("service")); 109 service = (NULL != ds) ? ds->value.ptr : "http"; 110 111 ds = (const data_string *) 112 array_get_element_klen(opts, CONST_STR_LEN("fqdn")); 113 if (NULL != ds) fqdn = ds->value.ptr; 114 if (NULL == fqdn) { 115 static struct utsname uts; 116 if (uts.nodename[0] == '\0') { 117 if (0 != uname(&uts)) { 118 log_perror(srv->errh, __FILE__, __LINE__, "uname()"); 119 return NULL; 120 } 121 } 122 fqdn = uts.nodename; 123 } 124 125 ds = (const data_string *) 126 array_get_element_klen(opts, CONST_STR_LEN("pwcheck_method")); 127 if (NULL != ds) { 128 pwcheck_method = &ds->value; 129 if (!buffer_is_equal_string(&ds->value, CONST_STR_LEN("saslauthd")) 130 && !buffer_is_equal_string(&ds->value, CONST_STR_LEN("auxprop")) 131 && !buffer_is_equal_string(&ds->value, CONST_STR_LEN("sasldb"))){ 132 log_error(srv->errh, __FILE__, __LINE__, 133 "sasl pwcheck_method must be one of saslauthd, " 134 "sasldb, or auxprop, not: %s", ds->value.ptr); 135 return NULL; 136 } 137 if (buffer_is_equal_string(&ds->value, CONST_STR_LEN("sasldb"))) { 138 /* Cyrus libsasl2 expects "auxprop" instead of "sasldb" 139 * (mod_authn_sasl_cb_getopt auxprop_plugin returns "sasldb") */ 140 buffer *b; 141 *(const buffer **)&b = &ds->value; 142 buffer_copy_string_len(b, CONST_STR_LEN("auxprop")); 143 } 144 } 145 146 ds = (const data_string *) 147 array_get_element_klen(opts, CONST_STR_LEN("sasldb_path")); 148 if (NULL != ds) sasldb_path = &ds->value; 149 150 plugin_config *pconf = malloc(sizeof(plugin_config)); 151 force_assert(pconf); 152 pconf->service = service; 153 pconf->fqdn = fqdn; 154 pconf->pwcheck_method = pwcheck_method; 155 pconf->sasldb_path = sasldb_path; 156 return pconf; 157 } 158 159 SETDEFAULTS_FUNC(mod_authn_sasl_set_defaults) { 160 static const config_plugin_keys_t cpk[] = { 161 { CONST_STR_LEN("auth.backend.sasl.opts"), 162 T_CONFIG_ARRAY_KVSTRING, 163 T_CONFIG_SCOPE_CONNECTION } 164 ,{ NULL, 0, 165 T_CONFIG_UNSET, 166 T_CONFIG_SCOPE_UNSET } 167 }; 168 169 plugin_data * const p = p_d; 170 if (!config_plugin_values_init(srv, p, cpk, "mod_authn_sasl")) 171 return HANDLER_ERROR; 172 173 /* process and validate config directives 174 * (init i to 0 if global context; to 1 to skip empty global context) */ 175 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 176 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 177 for (; -1 != cpv->k_id; ++cpv) { 178 switch (cpv->k_id) { 179 case 0: /* auth.backend.sasl.opts */ 180 if (cpv->v.a->used) { 181 cpv->v.v = mod_authn_sasl_parse_opts(srv, cpv->v.a); 182 if (NULL == cpv->v.v) return HANDLER_ERROR; 183 cpv->vtype = T_CONFIG_LOCAL; 184 } 185 break; 186 default:/* should not happen */ 187 break; 188 } 189 } 190 } 191 192 /* initialize p->defaults from global config context */ 193 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 194 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 195 if (-1 != cpv->k_id) 196 mod_authn_sasl_merge_config(&p->defaults, cpv); 197 } 198 199 return HANDLER_GO_ON; 200 } 201 202 static int mod_authn_sasl_cb_getopt(void *p_d, const char *plugin_name, const char *opt, const char **res, unsigned *len) { 203 plugin_data *p = (plugin_data *)p_d; 204 size_t sz; 205 206 if (0 == strcmp(opt, "pwcheck_method")) { 207 if (!buffer_string_is_empty(p->conf.pwcheck_method)) { 208 *res = p->conf.pwcheck_method->ptr; 209 sz = buffer_string_length(p->conf.pwcheck_method); 210 } 211 else { /* default */ 212 *res = "saslauthd"; 213 sz = sizeof("saslauthd")-1; 214 } 215 } 216 else if (0 == strcmp(opt, "sasldb_path") 217 && !buffer_string_is_empty(p->conf.sasldb_path)) { 218 *res = p->conf.sasldb_path->ptr; 219 sz = buffer_string_length(p->conf.sasldb_path); 220 } 221 else if (0 == strcmp(opt, "auxprop_plugin")) { 222 *res = "sasldb"; 223 sz = sizeof("sasldb")-1; 224 } 225 else { 226 UNUSED(plugin_name); 227 return SASL_FAIL; 228 } 229 230 if (len) *len = (unsigned int)sz; 231 return SASL_OK; 232 } 233 234 static int mod_authn_sasl_cb_log(void *vreq, int level, const char *message) { 235 switch (level) { 236 #if 0 237 case SASL_LOG_NONE: 238 case SASL_LOG_NOTE: 239 case SASL_LOG_DEBUG: 240 case SASL_LOG_TRACE: 241 case SASL_LOG_PASS: 242 #endif 243 default: 244 break; 245 case SASL_LOG_ERR: 246 case SASL_LOG_FAIL: 247 case SASL_LOG_WARN: /* (might omit SASL_LOG_WARN if too noisy in logs) */ 248 log_error(((request_st *)vreq)->conf.errh, __FILE__, __LINE__, 249 "%s", message); 250 break; 251 } 252 return SASL_OK; 253 } 254 255 static handler_t mod_authn_sasl_query(request_st * const r, void *p_d, const buffer * const username, const char * const realm, const char * const pw) { 256 plugin_data *p = (plugin_data *)p_d; 257 sasl_conn_t *sc; 258 sasl_callback_t const cb[] = { 259 { SASL_CB_GETOPT, (int(*)())mod_authn_sasl_cb_getopt, (void *) p }, 260 { SASL_CB_LOG, (int(*)())mod_authn_sasl_cb_log, (void *) r }, 261 { SASL_CB_LIST_END, NULL, NULL } 262 }; 263 int rc; 264 265 mod_authn_sasl_patch_config(r, p); 266 267 if (!p->initonce) { 268 /* must be done once, but after fork() if multiple lighttpd workers */ 269 rc = sasl_server_init(cb, NULL); 270 if (SASL_OK != rc) return HANDLER_ERROR; 271 p->initonce = 1; 272 } 273 274 rc = sasl_server_new(p->conf.service, p->conf.fqdn, 275 realm, NULL, NULL, cb, 0, &sc); 276 if (SASL_OK == rc) { 277 rc = sasl_checkpass(sc, CONST_BUF_LEN(username), pw, strlen(pw)); 278 sasl_dispose(&sc); 279 } 280 281 return (SASL_OK == rc) ? HANDLER_GO_ON : HANDLER_ERROR; 282 } 283 284 static handler_t mod_authn_sasl_basic(request_st * const r, void *p_d, const http_auth_require_t * const require, const buffer * const username, const char * const pw) { 285 char *realm = require->realm->ptr; 286 handler_t rc = mod_authn_sasl_query(r, p_d, username, realm, pw); 287 if (HANDLER_GO_ON != rc) return rc; 288 return http_auth_match_rules(require, username->ptr, NULL, NULL) 289 ? HANDLER_GO_ON /* access granted */ 290 : HANDLER_ERROR; 291 } 292 293 int mod_authn_sasl_plugin_init(plugin *p); 294 int mod_authn_sasl_plugin_init(plugin *p) { 295 p->version = LIGHTTPD_VERSION_ID; 296 p->name = "authn_sasl"; 297 p->init = mod_authn_sasl_init; 298 p->set_defaults= mod_authn_sasl_set_defaults; 299 p->cleanup = mod_authn_sasl_free; 300 301 return 0; 302 } 303