lighttpd1.4/src/mod_authn_sasl.c

309 lines
10 KiB
C
Raw Normal View History

/*
* 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)
*/
#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>
#include <stdlib.h>
#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;
plugin_config defaults;
plugin_config conf;
int initonce;
} plugin_data;
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);
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;
}
FREE_FUNC(mod_authn_sasl_free) {
plugin_data * const p = p_d;
if (p->initonce) sasl_done();
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;
}
}
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);
}
static void mod_authn_sasl_patch_config(request_st * const r, plugin_data * const p) {
p->conf = p->defaults; /* copy small struct instead of memcpy() */
/*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
for (int i = 1, used = p->nconfig; i < used; ++i) {
if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
mod_authn_sasl_merge_config(&p->conf,
p->cvlist + p->cvlist[i].v.u2[0]);
}
}
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;
}
}
fqdn = uts.nodename;
}
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"));
}
}
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;
}
SETDEFAULTS_FUNC(mod_authn_sasl_set_defaults) {
static const config_plugin_keys_t cpk[] = {
{ CONST_STR_LEN("auth.backend.sasl.opts"),
T_CONFIG_ARRAY_KVSTRING,
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;
}
}
}
/* 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;
}
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;
}
static int mod_authn_sasl_cb_log(void *vreq, int level, const char *message) {
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) */
log_error(((request_st *)vreq)->conf.errh, __FILE__, __LINE__,
"%s", message);
break;
}
return SASL_OK;
}
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) {
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 },
{ SASL_CB_LOG, (int(*)())mod_authn_sasl_cb_log, (void *) r },
{ SASL_CB_LIST_END, NULL, NULL }
};
int rc;
mod_authn_sasl_patch_config(r, p);
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;
}
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) {
char *realm = require->realm->ptr;
handler_t rc = mod_authn_sasl_query(r, p_d, username, realm, pw);
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;
p->name = "authn_sasl";
p->init = mod_authn_sasl_init;
p->set_defaults= mod_authn_sasl_set_defaults;
p->cleanup = mod_authn_sasl_free;
return 0;
}