2020-05-20 05:06:13 +00:00
|
|
|
/*
|
|
|
|
* mod_authn_sasl - SASL backend for lighttpd HTTP auth
|
|
|
|
*
|
|
|
|
* Copyright(c) 2017 Glenn Strauss gstrauss()gluelogic.com All rights reserved
|
|
|
|
* License: BSD 3-clause (same as lighttpd)
|
|
|
|
*/
|
2017-11-05 23:52:09 +00:00
|
|
|
#include "first.h"
|
|
|
|
|
|
|
|
/* mod_authn_sasl
|
|
|
|
*
|
|
|
|
* FUTURE POTENTIAL PERFORMANCE ENHANCEMENTS:
|
|
|
|
* - database response is not cached
|
|
|
|
* TODO: db response caching (for limited time) to reduce load on db
|
|
|
|
* (only cache successful logins to prevent cache bloat?)
|
|
|
|
* (or limit number of entries (size) of cache)
|
|
|
|
* (maybe have negative cache (limited size) of names not found in database)
|
|
|
|
* - database query is synchronous and blocks waiting for response
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sasl/sasl.h>
|
|
|
|
|
|
|
|
#include "base.h"
|
|
|
|
#include "http_auth.h"
|
|
|
|
#include "log.h"
|
|
|
|
#include "plugin.h"
|
|
|
|
|
|
|
|
#include <sys/utsname.h>
|
2018-03-25 07:45:05 +00:00
|
|
|
#include <stdlib.h>
|
2017-11-05 23:52:09 +00:00
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
const char *service;
|
|
|
|
const char *fqdn;
|
|
|
|
const buffer *pwcheck_method;
|
|
|
|
const buffer *sasldb_path;
|
|
|
|
} plugin_config;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
PLUGIN_DATA;
|
2019-11-07 00:37:11 +00:00
|
|
|
plugin_config defaults;
|
2017-11-05 23:52:09 +00:00
|
|
|
plugin_config conf;
|
2019-11-07 00:37:11 +00:00
|
|
|
|
2017-11-05 23:52:09 +00:00
|
|
|
int initonce;
|
|
|
|
} plugin_data;
|
|
|
|
|
2020-01-13 02:51:12 +00:00
|
|
|
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);
|
2017-11-05 23:52:09 +00:00
|
|
|
|
|
|
|
INIT_FUNC(mod_authn_sasl_init) {
|
|
|
|
static http_auth_backend_t http_auth_backend_sasl =
|
|
|
|
{ "sasl", mod_authn_sasl_basic, NULL, NULL };
|
|
|
|
plugin_data *p = calloc(1, sizeof(*p));
|
|
|
|
|
|
|
|
/* register http_auth_backend_sasl */
|
|
|
|
http_auth_backend_sasl.p_d = p;
|
|
|
|
http_auth_backend_set(&http_auth_backend_sasl);
|
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
2019-11-19 08:39:40 +00:00
|
|
|
FREE_FUNC(mod_authn_sasl_free) {
|
|
|
|
plugin_data * const p = p_d;
|
|
|
|
if (p->initonce) sasl_done();
|
2019-11-07 00:37:11 +00:00
|
|
|
if (NULL == p->cvlist) return;
|
|
|
|
/* (init i to 0 if global context; to 1 to skip empty global context) */
|
|
|
|
for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
|
|
|
|
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
|
|
|
|
for (; -1 != cpv->k_id; ++cpv) {
|
|
|
|
switch (cpv->k_id) {
|
|
|
|
case 0: /* auth.backend.sasl.opts */
|
|
|
|
if (cpv->vtype == T_CONFIG_LOCAL) free(cpv->v.v);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mod_authn_sasl_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
|
|
|
|
switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
|
|
|
|
case 0: /* auth.backend.sasl.opts */
|
|
|
|
if (cpv->vtype == T_CONFIG_LOCAL)
|
|
|
|
memcpy(pconf, cpv->v.v, sizeof(plugin_config));
|
|
|
|
break;
|
|
|
|
default:/* should not happen */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2017-11-05 23:52:09 +00:00
|
|
|
|
2019-11-07 00:37:11 +00:00
|
|
|
static void mod_authn_sasl_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
|
|
|
|
do {
|
|
|
|
mod_authn_sasl_merge_config_cpv(pconf, cpv);
|
|
|
|
} while ((++cpv)->k_id != -1);
|
|
|
|
}
|
2017-11-05 23:52:09 +00:00
|
|
|
|
2020-01-13 02:51:12 +00:00
|
|
|
static void mod_authn_sasl_patch_config(request_st * const r, plugin_data * const p) {
|
2020-01-11 16:07:43 +00:00
|
|
|
p->conf = p->defaults; /* copy small struct instead of memcpy() */
|
|
|
|
/*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
|
2019-11-07 00:37:11 +00:00
|
|
|
for (int i = 1, used = p->nconfig; i < used; ++i) {
|
2020-01-13 02:51:12 +00:00
|
|
|
if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
|
2019-11-07 00:37:11 +00:00
|
|
|
mod_authn_sasl_merge_config(&p->conf,
|
|
|
|
p->cvlist + p->cvlist[i].v.u2[0]);
|
|
|
|
}
|
|
|
|
}
|
2017-11-05 23:52:09 +00:00
|
|
|
|
2019-11-07 00:37:11 +00:00
|
|
|
static plugin_config * mod_authn_sasl_parse_opts(server *srv, const array * const opts) {
|
|
|
|
const data_string *ds;
|
|
|
|
const char *service = NULL;
|
|
|
|
const char *fqdn = NULL;
|
|
|
|
const buffer *pwcheck_method = NULL;
|
|
|
|
const buffer *sasldb_path = NULL;
|
|
|
|
|
|
|
|
ds = (const data_string *)
|
|
|
|
array_get_element_klen(opts, CONST_STR_LEN("service"));
|
|
|
|
service = (NULL != ds) ? ds->value.ptr : "http";
|
|
|
|
|
|
|
|
ds = (const data_string *)
|
|
|
|
array_get_element_klen(opts, CONST_STR_LEN("fqdn"));
|
|
|
|
if (NULL != ds) fqdn = ds->value.ptr;
|
|
|
|
if (NULL == fqdn) {
|
|
|
|
static struct utsname uts;
|
|
|
|
if (uts.nodename[0] == '\0') {
|
|
|
|
if (0 != uname(&uts)) {
|
|
|
|
log_perror(srv->errh, __FILE__, __LINE__, "uname()");
|
|
|
|
return NULL;
|
2017-11-05 23:52:09 +00:00
|
|
|
}
|
|
|
|
}
|
2019-11-07 00:37:11 +00:00
|
|
|
fqdn = uts.nodename;
|
|
|
|
}
|
2017-11-05 23:52:09 +00:00
|
|
|
|
2019-11-07 00:37:11 +00:00
|
|
|
ds = (const data_string *)
|
|
|
|
array_get_element_klen(opts, CONST_STR_LEN("pwcheck_method"));
|
|
|
|
if (NULL != ds) {
|
|
|
|
pwcheck_method = &ds->value;
|
|
|
|
if (!buffer_is_equal_string(&ds->value, CONST_STR_LEN("saslauthd"))
|
|
|
|
&& !buffer_is_equal_string(&ds->value, CONST_STR_LEN("auxprop"))
|
|
|
|
&& !buffer_is_equal_string(&ds->value, CONST_STR_LEN("sasldb"))){
|
|
|
|
log_error(srv->errh, __FILE__, __LINE__,
|
|
|
|
"sasl pwcheck_method must be one of saslauthd, "
|
|
|
|
"sasldb, or auxprop, not: %s", ds->value.ptr);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
if (buffer_is_equal_string(&ds->value, CONST_STR_LEN("sasldb"))) {
|
|
|
|
/* Cyrus libsasl2 expects "auxprop" instead of "sasldb"
|
|
|
|
* (mod_authn_sasl_cb_getopt auxprop_plugin returns "sasldb") */
|
|
|
|
buffer *b;
|
|
|
|
*(const buffer **)&b = &ds->value;
|
|
|
|
buffer_copy_string_len(b, CONST_STR_LEN("auxprop"));
|
2017-11-05 23:52:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-07 00:37:11 +00:00
|
|
|
ds = (const data_string *)
|
|
|
|
array_get_element_klen(opts, CONST_STR_LEN("sasldb_path"));
|
|
|
|
if (NULL != ds) sasldb_path = &ds->value;
|
|
|
|
|
|
|
|
plugin_config *pconf = malloc(sizeof(plugin_config));
|
|
|
|
force_assert(pconf);
|
|
|
|
pconf->service = service;
|
|
|
|
pconf->fqdn = fqdn;
|
|
|
|
pconf->pwcheck_method = pwcheck_method;
|
|
|
|
pconf->sasldb_path = sasldb_path;
|
|
|
|
return pconf;
|
2017-11-05 23:52:09 +00:00
|
|
|
}
|
|
|
|
|
2019-11-07 00:37:11 +00:00
|
|
|
SETDEFAULTS_FUNC(mod_authn_sasl_set_defaults) {
|
|
|
|
static const config_plugin_keys_t cpk[] = {
|
|
|
|
{ CONST_STR_LEN("auth.backend.sasl.opts"),
|
2019-12-08 00:15:55 +00:00
|
|
|
T_CONFIG_ARRAY_KVSTRING,
|
2019-11-07 00:37:11 +00:00
|
|
|
T_CONFIG_SCOPE_CONNECTION }
|
|
|
|
,{ NULL, 0,
|
|
|
|
T_CONFIG_UNSET,
|
|
|
|
T_CONFIG_SCOPE_UNSET }
|
|
|
|
};
|
|
|
|
|
|
|
|
plugin_data * const p = p_d;
|
|
|
|
if (!config_plugin_values_init(srv, p, cpk, "mod_authn_sasl"))
|
|
|
|
return HANDLER_ERROR;
|
|
|
|
|
|
|
|
/* process and validate config directives
|
|
|
|
* (init i to 0 if global context; to 1 to skip empty global context) */
|
|
|
|
for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
|
|
|
|
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
|
|
|
|
for (; -1 != cpv->k_id; ++cpv) {
|
|
|
|
switch (cpv->k_id) {
|
|
|
|
case 0: /* auth.backend.sasl.opts */
|
|
|
|
if (cpv->v.a->used) {
|
|
|
|
cpv->v.v = mod_authn_sasl_parse_opts(srv, cpv->v.a);
|
|
|
|
if (NULL == cpv->v.v) return HANDLER_ERROR;
|
|
|
|
cpv->vtype = T_CONFIG_LOCAL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:/* should not happen */
|
|
|
|
break;
|
2017-11-05 23:52:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-07 00:37:11 +00:00
|
|
|
/* initialize p->defaults from global config context */
|
|
|
|
if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
|
|
|
|
const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
|
|
|
|
if (-1 != cpv->k_id)
|
|
|
|
mod_authn_sasl_merge_config(&p->defaults, cpv);
|
|
|
|
}
|
|
|
|
|
|
|
|
return HANDLER_GO_ON;
|
2017-11-05 23:52:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int mod_authn_sasl_cb_getopt(void *p_d, const char *plugin_name, const char *opt, const char **res, unsigned *len) {
|
|
|
|
plugin_data *p = (plugin_data *)p_d;
|
|
|
|
size_t sz;
|
|
|
|
|
|
|
|
if (0 == strcmp(opt, "pwcheck_method")) {
|
|
|
|
if (!buffer_string_is_empty(p->conf.pwcheck_method)) {
|
|
|
|
*res = p->conf.pwcheck_method->ptr;
|
|
|
|
sz = buffer_string_length(p->conf.pwcheck_method);
|
|
|
|
}
|
|
|
|
else { /* default */
|
|
|
|
*res = "saslauthd";
|
|
|
|
sz = sizeof("saslauthd")-1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (0 == strcmp(opt, "sasldb_path")
|
|
|
|
&& !buffer_string_is_empty(p->conf.sasldb_path)) {
|
|
|
|
*res = p->conf.sasldb_path->ptr;
|
|
|
|
sz = buffer_string_length(p->conf.sasldb_path);
|
|
|
|
}
|
|
|
|
else if (0 == strcmp(opt, "auxprop_plugin")) {
|
|
|
|
*res = "sasldb";
|
|
|
|
sz = sizeof("sasldb")-1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
UNUSED(plugin_name);
|
|
|
|
return SASL_FAIL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (len) *len = (unsigned int)sz;
|
|
|
|
return SASL_OK;
|
|
|
|
}
|
|
|
|
|
2020-01-13 02:51:12 +00:00
|
|
|
static int mod_authn_sasl_cb_log(void *vreq, int level, const char *message) {
|
2017-11-05 23:52:09 +00:00
|
|
|
switch (level) {
|
|
|
|
#if 0
|
|
|
|
case SASL_LOG_NONE:
|
|
|
|
case SASL_LOG_NOTE:
|
|
|
|
case SASL_LOG_DEBUG:
|
|
|
|
case SASL_LOG_TRACE:
|
|
|
|
case SASL_LOG_PASS:
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
case SASL_LOG_ERR:
|
|
|
|
case SASL_LOG_FAIL:
|
|
|
|
case SASL_LOG_WARN: /* (might omit SASL_LOG_WARN if too noisy in logs) */
|
2020-01-13 02:51:12 +00:00
|
|
|
log_error(((request_st *)vreq)->conf.errh, __FILE__, __LINE__,
|
2019-11-25 06:54:08 +00:00
|
|
|
"%s", message);
|
2017-11-05 23:52:09 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
return SASL_OK;
|
|
|
|
}
|
|
|
|
|
2020-01-13 02:51:12 +00:00
|
|
|
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) {
|
2017-11-05 23:52:09 +00:00
|
|
|
plugin_data *p = (plugin_data *)p_d;
|
|
|
|
sasl_conn_t *sc;
|
|
|
|
sasl_callback_t const cb[] = {
|
|
|
|
{ SASL_CB_GETOPT, (int(*)())mod_authn_sasl_cb_getopt, (void *) p },
|
2020-01-13 02:51:12 +00:00
|
|
|
{ SASL_CB_LOG, (int(*)())mod_authn_sasl_cb_log, (void *) r },
|
2017-11-05 23:52:09 +00:00
|
|
|
{ SASL_CB_LIST_END, NULL, NULL }
|
|
|
|
};
|
|
|
|
int rc;
|
|
|
|
|
2020-01-13 02:51:12 +00:00
|
|
|
mod_authn_sasl_patch_config(r, p);
|
2017-11-05 23:52:09 +00:00
|
|
|
|
|
|
|
if (!p->initonce) {
|
|
|
|
/* must be done once, but after fork() if multiple lighttpd workers */
|
|
|
|
rc = sasl_server_init(cb, NULL);
|
|
|
|
if (SASL_OK != rc) return HANDLER_ERROR;
|
|
|
|
p->initonce = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = sasl_server_new(p->conf.service, p->conf.fqdn,
|
|
|
|
realm, NULL, NULL, cb, 0, &sc);
|
|
|
|
if (SASL_OK == rc) {
|
|
|
|
rc = sasl_checkpass(sc, CONST_BUF_LEN(username), pw, strlen(pw));
|
|
|
|
sasl_dispose(&sc);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (SASL_OK == rc) ? HANDLER_GO_ON : HANDLER_ERROR;
|
|
|
|
}
|
|
|
|
|
2020-01-13 02:51:12 +00:00
|
|
|
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) {
|
2017-11-05 23:52:09 +00:00
|
|
|
char *realm = require->realm->ptr;
|
2020-01-13 02:51:12 +00:00
|
|
|
handler_t rc = mod_authn_sasl_query(r, p_d, username, realm, pw);
|
2017-11-05 23:52:09 +00:00
|
|
|
if (HANDLER_GO_ON != rc) return rc;
|
|
|
|
return http_auth_match_rules(require, username->ptr, NULL, NULL)
|
|
|
|
? HANDLER_GO_ON /* access granted */
|
|
|
|
: HANDLER_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
int mod_authn_sasl_plugin_init(plugin *p);
|
|
|
|
int mod_authn_sasl_plugin_init(plugin *p) {
|
|
|
|
p->version = LIGHTTPD_VERSION_ID;
|
2019-10-19 04:30:54 +00:00
|
|
|
p->name = "authn_sasl";
|
2017-11-05 23:52:09 +00:00
|
|
|
p->init = mod_authn_sasl_init;
|
|
|
|
p->set_defaults= mod_authn_sasl_set_defaults;
|
|
|
|
p->cleanup = mod_authn_sasl_free;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|