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