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