xref: /lighttpd1.4/src/mod_authn_sasl.c (revision 9570cc2f)
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 <sasl/sasl.h>
21 
22 #include "base.h"
23 #include "http_auth.h"
24 #include "log.h"
25 #include "plugin.h"
26 
27 #include <sys/utsname.h>
28 #include <stdlib.h>
29 #include <string.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 
152     ds = (const data_string *)
153       array_get_element_klen(opts, CONST_STR_LEN("sasldb_path"));
154     if (NULL != ds) sasldb_path = &ds->value;
155 
156     plugin_config *pconf = malloc(sizeof(plugin_config));
157     force_assert(pconf);
158     pconf->service = service;
159     pconf->fqdn = fqdn;
160     pconf->pwcheck_method = pwcheck_method;
161     pconf->sasldb_path = sasldb_path;
162     return pconf;
163 }
164 
165 SETDEFAULTS_FUNC(mod_authn_sasl_set_defaults) {
166     static const config_plugin_keys_t cpk[] = {
167       { CONST_STR_LEN("auth.backend.sasl.opts"),
168         T_CONFIG_ARRAY_KVSTRING,
169         T_CONFIG_SCOPE_CONNECTION }
170      ,{ NULL, 0,
171         T_CONFIG_UNSET,
172         T_CONFIG_SCOPE_UNSET }
173     };
174 
175     plugin_data * const p = p_d;
176     if (!config_plugin_values_init(srv, p, cpk, "mod_authn_sasl"))
177         return HANDLER_ERROR;
178 
179     /* process and validate config directives
180      * (init i to 0 if global context; to 1 to skip empty global context) */
181     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
182         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
183         for (; -1 != cpv->k_id; ++cpv) {
184             switch (cpv->k_id) {
185               case 0: /* auth.backend.sasl.opts */
186                 if (cpv->v.a->used) {
187                     cpv->v.v = mod_authn_sasl_parse_opts(srv, cpv->v.a);
188                     if (NULL == cpv->v.v) return HANDLER_ERROR;
189                     cpv->vtype = T_CONFIG_LOCAL;
190                 }
191                 break;
192               default:/* should not happen */
193                 break;
194             }
195         }
196     }
197 
198     /* initialize p->defaults from global config context */
199     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
200         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
201         if (-1 != cpv->k_id)
202             mod_authn_sasl_merge_config(&p->defaults, cpv);
203     }
204 
205     return HANDLER_GO_ON;
206 }
207 
208 static int mod_authn_sasl_cb_getopt(void *p_d, const char *plugin_name, const char *opt, const char **res, unsigned *len) {
209     plugin_data *p = (plugin_data *)p_d;
210     size_t sz;
211 
212     if (0 == strcmp(opt, "pwcheck_method")) {
213         if (!buffer_string_is_empty(p->conf.pwcheck_method)) {
214             *res = p->conf.pwcheck_method->ptr;
215             sz = buffer_string_length(p->conf.pwcheck_method);
216         }
217         else { /* default */
218             *res = "saslauthd";
219             sz = sizeof("saslauthd")-1;
220         }
221     }
222     else if (0 == strcmp(opt, "sasldb_path")
223              && !buffer_string_is_empty(p->conf.sasldb_path)) {
224         *res = p->conf.sasldb_path->ptr;
225         sz = buffer_string_length(p->conf.sasldb_path);
226     }
227     else if (0 == strcmp(opt, "auxprop_plugin")) {
228         *res = "sasldb";
229         sz = sizeof("sasldb")-1;
230     }
231     else {
232         UNUSED(plugin_name);
233         return SASL_FAIL;
234     }
235 
236     if (len) *len = (unsigned int)sz;
237     return SASL_OK;
238 }
239 
240 static int mod_authn_sasl_cb_log(void *vreq, int level, const char *message) {
241     switch (level) {
242      #if 0
243       case SASL_LOG_NONE:
244       case SASL_LOG_NOTE:
245       case SASL_LOG_DEBUG:
246       case SASL_LOG_TRACE:
247       case SASL_LOG_PASS:
248      #endif
249       default:
250         break;
251       case SASL_LOG_ERR:
252       case SASL_LOG_FAIL:
253       case SASL_LOG_WARN: /* (might omit SASL_LOG_WARN if too noisy in logs) */
254         log_error(((request_st *)vreq)->conf.errh, __FILE__, __LINE__,
255                   "%s", message);
256         break;
257     }
258     return SASL_OK;
259 }
260 
261 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) {
262     plugin_data *p = (plugin_data *)p_d;
263     sasl_conn_t *sc;
264     sasl_callback_t const cb[] = {
265       { SASL_CB_GETOPT,   (int(*)())mod_authn_sasl_cb_getopt, (void *) p },
266       { SASL_CB_LOG,      (int(*)())mod_authn_sasl_cb_log, (void *) r },
267       { SASL_CB_LIST_END, NULL, NULL }
268     };
269     int rc;
270 
271     mod_authn_sasl_patch_config(r, p);
272 
273     if (!p->initonce) {
274         /* must be done once, but after fork() if multiple lighttpd workers */
275         rc = sasl_server_init(cb, NULL);
276         if (SASL_OK != rc) return HANDLER_ERROR;
277         p->initonce = 1;
278     }
279 
280     rc = sasl_server_new(p->conf.service, p->conf.fqdn,
281                          realm, NULL, NULL, cb, 0, &sc);
282     if (SASL_OK == rc) {
283         rc = sasl_checkpass(sc, CONST_BUF_LEN(username), pw, strlen(pw));
284         sasl_dispose(&sc);
285     }
286 
287     return (SASL_OK == rc) ? HANDLER_GO_ON : HANDLER_ERROR;
288 }
289 
290 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) {
291     char *realm = require->realm->ptr;
292     handler_t rc = mod_authn_sasl_query(r, p_d, username, realm, pw);
293     if (HANDLER_GO_ON != rc) return rc;
294     return http_auth_match_rules(require, username->ptr, NULL, NULL)
295       ? HANDLER_GO_ON  /* access granted */
296       : HANDLER_ERROR;
297 }
298 
299 int mod_authn_sasl_plugin_init(plugin *p);
300 int mod_authn_sasl_plugin_init(plugin *p) {
301     p->version     = LIGHTTPD_VERSION_ID;
302     p->name        = "authn_sasl";
303     p->init        = mod_authn_sasl_init;
304     p->set_defaults= mod_authn_sasl_set_defaults;
305     p->cleanup     = mod_authn_sasl_free;
306 
307     return 0;
308 }
309