xref: /lighttpd1.4/src/mod_authn_sasl.c (revision e11f72b1)
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