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 <errno.h> 23 #include <stdlib.h> 24 #include <string.h> 25 26 typedef struct { 27 array *opts; 28 const char *service; 29 const char *fqdn; 30 const buffer *pwcheck_method; 31 const buffer *sasldb_path; 32 } plugin_config; 33 34 typedef struct { 35 PLUGIN_DATA; 36 plugin_config **config_storage; 37 plugin_config conf; 38 buffer *fqdn; 39 int initonce; 40 } plugin_data; 41 42 static handler_t mod_authn_sasl_basic(server *srv, connection *con, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw); 43 44 INIT_FUNC(mod_authn_sasl_init) { 45 static http_auth_backend_t http_auth_backend_sasl = 46 { "sasl", mod_authn_sasl_basic, NULL, NULL }; 47 plugin_data *p = calloc(1, sizeof(*p)); 48 49 /* register http_auth_backend_sasl */ 50 http_auth_backend_sasl.p_d = p; 51 http_auth_backend_set(&http_auth_backend_sasl); 52 53 return p; 54 } 55 56 FREE_FUNC(mod_authn_sasl_free) { 57 plugin_data *p = p_d; 58 if (!p) return HANDLER_GO_ON; 59 60 if (p->initonce) sasl_done(); 61 62 if (p->config_storage) { 63 for (size_t i = 0; i < srv->config_context->used; ++i) { 64 plugin_config *s = p->config_storage[i]; 65 if (NULL == s) continue; 66 array_free(s->opts); 67 free(s); 68 } 69 free(p->config_storage); 70 } 71 buffer_free(p->fqdn); 72 free(p); 73 UNUSED(srv); 74 return HANDLER_GO_ON; 75 } 76 77 SETDEFAULTS_FUNC(mod_authn_sasl_set_defaults) { 78 plugin_data *p = p_d; 79 size_t i; 80 config_values_t cv[] = { 81 { "auth.backend.sasl.opts", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, 82 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } 83 }; 84 85 p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *)); 86 87 for (i = 0; i < srv->config_context->used; i++) { 88 data_config const *config = (data_config const*)srv->config_context->data[i]; 89 data_string *ds; 90 plugin_config *s = calloc(1, sizeof(plugin_config)); 91 s->opts = array_init(); 92 93 cv[0].destination = s->opts; 94 95 p->config_storage[i] = s; 96 97 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { 98 return HANDLER_ERROR; 99 } 100 101 if (0 == s->opts->used) continue; 102 103 ds = (data_string *) 104 array_get_element_klen(s->opts, CONST_STR_LEN("service")); 105 s->service = (NULL != ds) ? ds->value->ptr : "http"; 106 107 ds = (data_string *) 108 array_get_element_klen(s->opts, CONST_STR_LEN("fqdn")); 109 if (NULL != ds) s->fqdn = ds->value->ptr; 110 if (NULL == s->fqdn) { 111 if (NULL == p->fqdn) { 112 struct utsname uts; 113 if (0 != uname(&uts)) { 114 log_error_write(srv, __FILE__, __LINE__, "ss", 115 "uname():", strerror(errno)); 116 return HANDLER_ERROR; 117 } 118 p->fqdn = buffer_init_string(uts.nodename); 119 } 120 s->fqdn = p->fqdn->ptr; 121 } 122 123 ds = (data_string *) 124 array_get_element_klen(s->opts, CONST_STR_LEN("pwcheck_method")); 125 if (NULL != ds) { 126 s->pwcheck_method = ds->value; 127 if (!buffer_is_equal_string(ds->value, CONST_STR_LEN("saslauthd")) 128 && !buffer_is_equal_string(ds->value, CONST_STR_LEN("auxprop")) 129 && !buffer_is_equal_string(ds->value, CONST_STR_LEN("sasldb"))){ 130 log_error_write(srv, __FILE__, __LINE__, "sb", 131 "sasl pwcheck_method must be one of saslauthd, " 132 "sasldb, or auxprop, not:", ds->value); 133 return HANDLER_ERROR; 134 } 135 if (buffer_is_equal_string(ds->value, CONST_STR_LEN("sasldb"))) { 136 /* Cyrus libsasl2 expects "auxprop" instead of "sasldb" 137 * (mod_authn_sasl_cb_getopt auxprop_plugin returns "sasldb") */ 138 buffer_copy_string_len(ds->value, CONST_STR_LEN("auxprop")); 139 } 140 } 141 142 ds = (data_string *) 143 array_get_element_klen(s->opts, CONST_STR_LEN("sasldb_path")); 144 if (NULL != ds) s->sasldb_path = ds->value; 145 } 146 147 return HANDLER_GO_ON; 148 } 149 150 #define PATCH(x) \ 151 p->conf.x = s->x; 152 static int mod_authn_sasl_patch_connection(server *srv, connection *con, plugin_data *p) { 153 plugin_config *s = p->config_storage[0]; 154 PATCH(service); 155 PATCH(fqdn); 156 PATCH(pwcheck_method); 157 PATCH(sasldb_path); 158 159 /* skip the first, the global context */ 160 for (size_t i = 1; i < srv->config_context->used; ++i) { 161 data_config *dc = (data_config *)srv->config_context->data[i]; 162 163 /* condition didn't match */ 164 if (!config_check_cond(srv, con, dc)) continue; 165 166 /* merge config */ 167 s = p->config_storage[i]; 168 for (size_t j = 0; j < dc->value->used; ++j) { 169 data_unset *du = dc->value->data[j]; 170 if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.sasl.opts"))) { 171 PATCH(service); 172 PATCH(fqdn); 173 PATCH(pwcheck_method); 174 PATCH(sasldb_path); 175 } 176 } 177 } 178 179 return 0; 180 } 181 #undef PATCH 182 183 static int mod_authn_sasl_cb_getopt(void *p_d, const char *plugin_name, const char *opt, const char **res, unsigned *len) { 184 plugin_data *p = (plugin_data *)p_d; 185 size_t sz; 186 187 if (0 == strcmp(opt, "pwcheck_method")) { 188 if (!buffer_string_is_empty(p->conf.pwcheck_method)) { 189 *res = p->conf.pwcheck_method->ptr; 190 sz = buffer_string_length(p->conf.pwcheck_method); 191 } 192 else { /* default */ 193 *res = "saslauthd"; 194 sz = sizeof("saslauthd")-1; 195 } 196 } 197 else if (0 == strcmp(opt, "sasldb_path") 198 && !buffer_string_is_empty(p->conf.sasldb_path)) { 199 *res = p->conf.sasldb_path->ptr; 200 sz = buffer_string_length(p->conf.sasldb_path); 201 } 202 else if (0 == strcmp(opt, "auxprop_plugin")) { 203 *res = "sasldb"; 204 sz = sizeof("sasldb")-1; 205 } 206 else { 207 UNUSED(plugin_name); 208 return SASL_FAIL; 209 } 210 211 if (len) *len = (unsigned int)sz; 212 return SASL_OK; 213 } 214 215 static int mod_authn_sasl_cb_log(void *vsrv, int level, const char *message) { 216 switch (level) { 217 #if 0 218 case SASL_LOG_NONE: 219 case SASL_LOG_NOTE: 220 case SASL_LOG_DEBUG: 221 case SASL_LOG_TRACE: 222 case SASL_LOG_PASS: 223 #endif 224 default: 225 break; 226 case SASL_LOG_ERR: 227 case SASL_LOG_FAIL: 228 case SASL_LOG_WARN: /* (might omit SASL_LOG_WARN if too noisy in logs) */ 229 log_error_write((server *)vsrv, __FILE__, __LINE__, "s", message); 230 break; 231 } 232 return SASL_OK; 233 } 234 235 static handler_t mod_authn_sasl_query(server *srv, connection *con, void *p_d, const buffer *username, const char *realm, const char *pw) { 236 plugin_data *p = (plugin_data *)p_d; 237 sasl_conn_t *sc; 238 sasl_callback_t const cb[] = { 239 { SASL_CB_GETOPT, (int(*)())mod_authn_sasl_cb_getopt, (void *) p }, 240 { SASL_CB_LOG, (int(*)())mod_authn_sasl_cb_log, (void *) srv }, 241 { SASL_CB_LIST_END, NULL, NULL } 242 }; 243 int rc; 244 245 mod_authn_sasl_patch_connection(srv, con, p); 246 247 if (!p->initonce) { 248 /* must be done once, but after fork() if multiple lighttpd workers */ 249 rc = sasl_server_init(cb, NULL); 250 if (SASL_OK != rc) return HANDLER_ERROR; 251 p->initonce = 1; 252 } 253 254 rc = sasl_server_new(p->conf.service, p->conf.fqdn, 255 realm, NULL, NULL, cb, 0, &sc); 256 if (SASL_OK == rc) { 257 rc = sasl_checkpass(sc, CONST_BUF_LEN(username), pw, strlen(pw)); 258 sasl_dispose(&sc); 259 } 260 261 return (SASL_OK == rc) ? HANDLER_GO_ON : HANDLER_ERROR; 262 } 263 264 static handler_t mod_authn_sasl_basic(server *srv, connection *con, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw) { 265 char *realm = require->realm->ptr; 266 handler_t rc = mod_authn_sasl_query(srv, con, p_d, username, realm, pw); 267 if (HANDLER_GO_ON != rc) return rc; 268 return http_auth_match_rules(require, username->ptr, NULL, NULL) 269 ? HANDLER_GO_ON /* access granted */ 270 : HANDLER_ERROR; 271 } 272 273 int mod_authn_sasl_plugin_init(plugin *p); 274 int mod_authn_sasl_plugin_init(plugin *p) { 275 p->version = LIGHTTPD_VERSION_ID; 276 p->name = buffer_init_string("authn_sasl"); 277 p->init = mod_authn_sasl_init; 278 p->set_defaults= mod_authn_sasl_set_defaults; 279 p->cleanup = mod_authn_sasl_free; 280 281 p->data = NULL; 282 283 return 0; 284 } 285