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