diff options
author | Glenn Strauss <gstrauss@gluelogic.com> | 2018-09-18 01:51:45 -0400 |
---|---|---|
committer | Glenn Strauss <gstrauss@gluelogic.com> | 2018-09-23 18:01:58 -0400 |
commit | df4812ec2e14e06509f34e6c886c32ad0b31bd18 (patch) | |
tree | f11b674395a3274f51f5bac68b576e7f6e1aa68b | |
parent | 5c2d52b4acd32719691ef19013da2cd6165d0e98 (diff) | |
download | lighttpd-git-df4812ec2e14e06509f34e6c886c32ad0b31bd18.tar.gz |
[mod_authn_pam] mod_auth PAM support (fixes #688)
x-ref:
"auth via pam"
https://redmine.lighttpd.net/issues/688
-rw-r--r-- | SConstruct | 9 | ||||
-rw-r--r-- | configure.ac | 28 | ||||
-rw-r--r-- | meson_options.txt | 5 | ||||
-rw-r--r-- | src/CMakeLists.txt | 15 | ||||
-rw-r--r-- | src/Makefile.am | 11 | ||||
-rw-r--r-- | src/SConscript | 3 | ||||
-rw-r--r-- | src/meson.build | 21 | ||||
-rw-r--r-- | src/mod_authn_pam.c | 185 | ||||
-rw-r--r-- | src/server.c | 5 |
9 files changed, 282 insertions, 0 deletions
@@ -330,6 +330,7 @@ if 1: LIBLUA = '', LIBMEMCACHED = '', LIBMYSQL = '', + LIBPAM = '', LIBPCRE = '', LIBPGSQL = '', LIBSASL = '', @@ -580,6 +581,14 @@ if 1: LIBCRYPTO = 'crypto', ) + if env['with_pam']: + if not autoconf.CheckLibWithHeader('pam', 'security/pam_appl.h', 'C'): + fail("Couldn't find pam") + autoconf.env.Append( + CPPFLAGS = [ '-DHAVE_PAM' ], + LIBPAM = 'pam', + ) + if env['with_pcre']: pcre_config = autoconf.checkProgram('pcre', 'pcre-config') if not autoconf.CheckParseConfigForLib('LIBPCRE', pcre_config + ' --cflags --libs'): diff --git a/configure.ac b/configure.ac index 039706b9..815b728e 100644 --- a/configure.ac +++ b/configure.ac @@ -425,6 +425,31 @@ if test "$WITH_LDAP" != no; then fi AM_CONDITIONAL([BUILD_WITH_LDAP], [test "$WITH_LDAP" != no]) +dnl Check for PAM +AC_MSG_NOTICE([----------------------------------------]) +AC_MSG_CHECKING(for PAM support) +AC_ARG_WITH([pam], + [AC_HELP_STRING([--with-pam],[enable PAM support])], + [WITH_PAM=$withval], + [WITH_PAM=no] +) +AC_MSG_RESULT([$withval]) + +if test "$WITH_PAM" != "no"; then + AC_CHECK_LIB([pam], [pam_start], + [AC_CHECK_HEADERS([security/pam_appl.h],[ + PAM_LIB=-lpam + AC_DEFINE([HAVE_PAM], [1], [libpam]) + AC_DEFINE([HAVE_SECURITY_PAM_APPL_H], [1]) + ], + [AC_MSG_ERROR([pam headers not found, install them or build without --with-pam])] + )], + [AC_MSG_ERROR([pam library not found, install it or build without --with-pam])] + ) + AC_SUBST(PAM_LIB) +fi +AM_CONDITIONAL([BUILD_WITH_PAM], [test "$WITH_PAM" != no]) + dnl Check for xattr AC_MSG_NOTICE([----------------------------------------]) AC_MSG_CHECKING([for extended attributes support]) @@ -1389,6 +1414,9 @@ lighty_track_feature "kerberos" "mod_authn_gssapi" \ lighty_track_feature "ldap" "mod_authn_ldap mod_vhostdb_ldap" \ 'test "$WITH_LDAP" != no' +lighty_track_feature "pam" "mod_authn_pam" \ + 'test "$WITH_PAM" != no' + lighty_track_feature "network-openssl" "mod_openssl" \ 'test "$WITH_OPENSSL" != no' diff --git a/meson_options.txt b/meson_options.txt index 188f4d33..6ed52002 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -63,6 +63,11 @@ option('with_openssl', value: false, description: 'with openssl-support [default: off]', ) +option('with_pam', + type: 'boolean', + value: false, + description: 'with PAM-support for mod_auth [default: off]', +) option('with_pcre', type: 'boolean', value: true, diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b77f0140..0ba06736 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,6 +25,7 @@ option(WITH_BZIP "with bzip2-support for mod_compress [default: off]") option(WITH_ZLIB "with deflate-support for mod_compress [default: on]" ON) option(WITH_KRB5 "with Kerberos5-support for mod_auth [default: off]") option(WITH_LDAP "with LDAP-support for mod_auth mod_vhostdb_ldap [default: off]") +option(WITH_PAM "with PAM-support for mod_auth [default: off]") option(WITH_LUA "with lua 5.1 for mod_magnet [default: off]") # option(WITH_VALGRIND "with internal support for valgrind [default: off]") option(WITH_FAM "fam/gamin for reducing number of stat() calls [default: off]") @@ -469,6 +470,14 @@ else() unset(HAVE_LIBLBER) endif() +if(WITH_PAM) + check_include_files(security/pam_appl.h HAVE_SECURITY_PAM_APPL_H) + check_library_exists(pam pam_start "" HAVE_PAM) +else() + unset(HAVE_SECURITY_PAM_APPL_H) + unset(HAVE_PAM) +endif() + if(WITH_LUA) pkg_search_module(LUA REQUIRED lua5.3 lua-5.3 lua5.2 lua-5.2 lua5.1 lua-5.1 lua) message(STATUS "found lua at: INCDIR: ${LUA_INCLUDE_DIRS} LIBDIR: ${LUA_LIBRARY_DIRS} LDFLAGS: ${LUA_LDFLAGS} CFLAGS: ${LUA_CFLAGS}") @@ -790,6 +799,12 @@ if(WITH_LDAP) target_link_libraries(mod_vhostdb_ldap ${L_MOD_AUTHN_LDAP}) endif() +if(WITH_PAM) + add_and_install_library(mod_authn_pam "mod_authn_pam.c") + set(L_MOD_AUTHN_PAM ${L_MOD_AUTHN_PAM} pam) + target_link_libraries(mod_authn_pam ${L_MOD_AUTHN_PAM}) +endif() + if(WITH_SASL) add_and_install_library(mod_authn_sasl "mod_authn_sasl.c") set(L_MOD_AUTHN_SASL ${L_MOD_AUTHN_SASL} sasl2) diff --git a/src/Makefile.am b/src/Makefile.am index 1a5845d4..a9d820fe 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -342,6 +342,13 @@ mod_authn_ldap_la_LDFLAGS = $(common_module_ldflags) mod_authn_ldap_la_LIBADD = $(LDAP_LIB) $(LBER_LIB) $(common_libadd) endif +if BUILD_WITH_PAM +lib_LTLIBRARIES += mod_authn_pam.la +mod_authn_pam_la_SOURCES = mod_authn_pam.c +mod_authn_pam_la_LDFLAGS = $(common_module_ldflags) +mod_authn_pam_la_LIBADD = $(PAM_LIB) $(common_libadd) +endif + if BUILD_WITH_MYSQL lib_LTLIBRARIES += mod_authn_mysql.la mod_authn_mysql_la_SOURCES = mod_authn_mysql.c @@ -483,6 +490,10 @@ if BUILD_WITH_LDAP lighttpd_SOURCES += mod_authn_ldap.c mod_vhostdb_ldap.c lighttpd_LDADD += $(LDAP_LIB) $(LBER_LIB) endif +if BUILD_WITH_PAM +lighttpd_SOURCES += mod_authn_pam.c +lighttpd_LDADD += $(PAM_LIB) +endif if BUILD_WITH_MYSQL lighttpd_SOURCES += mod_authn_mysql.c mod_mysql_vhost.c mod_vhostdb_mysql.c lighttpd_CPPFLAGS += $(MYSQL_INCLUDE) diff --git a/src/SConscript b/src/SConscript index 9c497416..957ea10d 100644 --- a/src/SConscript +++ b/src/SConscript @@ -148,6 +148,9 @@ if env['with_lua']: 'lib' : [ env['LIBMEMCACHED'], env['LIBLUA'] ] } +if env['with_pam']: + modules['mod_authn_pam'] = { 'src' : [ 'mod_authn_pam.c' ], 'lib' : [ env['LIBPAM'] ] } + if env['with_pcre'] and (env['with_memcached'] or env['with_gdbm']): modules['mod_trigger_b4_dl'] = { 'src' : [ 'mod_trigger_b4_dl.c' ], 'lib' : [ env['LIBPCRE'], env['LIBMEMCACHED'], env['LIBGDBM'] ] } diff --git a/src/meson.build b/src/meson.build index 91a541c3..a91c33c9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -316,6 +316,21 @@ if get_option('with_ldap') conf_data.set('HAVE_LIBLBER', true) endif +libpam = [] +if get_option('with_pam') + libpam = [ compiler.find_library('pam') ] + if not(compiler.has_function('pam_start', + args: defs, + dependencies: libpam, + prefix: ''' + #include <security/pam_appl.h> + ''' + )) + error('Couldn\'t find security/pam_appl.h or pam_start in lib libpam') + endif + conf_data.set('HAVE_PAM', true) +endif + libev = [] if get_option('with_libev') libev = dependency('ev', required: false) @@ -838,6 +853,12 @@ if get_option('with_openssl') ] endif +if get_option('with_pam') + modules += [ + [ 'mod_authn_pam', [ 'mod_authn_pam.c' ], libpam ], + ] +endif + if get_option('with_sasl') modules += [ [ 'mod_authn_sasl', [ 'mod_authn_sasl.c' ], libsasl2 ], diff --git a/src/mod_authn_pam.c b/src/mod_authn_pam.c new file mode 100644 index 00000000..2439f1df --- /dev/null +++ b/src/mod_authn_pam.c @@ -0,0 +1,185 @@ +#include "first.h" + +/* mod_authn_pam + * + * 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 <security/pam_appl.h> + +#include "base.h" +#include "http_auth.h" +#include "log.h" +#include "plugin.h" + +#include <stdlib.h> +#include <string.h> + +typedef struct { + array *opts; + const char *service; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + plugin_config **config_storage; + plugin_config conf; +} plugin_data; + +static handler_t mod_authn_pam_basic(server *srv, connection *con, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw); + +INIT_FUNC(mod_authn_pam_init) { + static http_auth_backend_t http_auth_backend_pam = + { "pam", mod_authn_pam_basic, NULL, NULL }; + plugin_data *p = calloc(1, sizeof(*p)); + + /* register http_auth_backend_pam */ + http_auth_backend_pam.p_d = p; + http_auth_backend_set(&http_auth_backend_pam); + + return p; +} + +FREE_FUNC(mod_authn_pam_free) { + plugin_data *p = p_d; + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + for (size_t i = 0; i < srv->config_context->used; ++i) { + plugin_config *s = p->config_storage[i]; + if (NULL == s) continue; + array_free(s->opts); + free(s); + } + free(p->config_storage); + } + free(p); + UNUSED(srv); + return HANDLER_GO_ON; +} + +SETDEFAULTS_FUNC(mod_authn_pam_set_defaults) { + plugin_data *p = p_d; + config_values_t cv[] = { + { "auth.backend.pam.opts", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *)); + + for (size_t i = 0; i < srv->config_context->used; ++i) { + data_config const *config = (data_config const*)srv->config_context->data[i]; + data_string *ds; + plugin_config *s = calloc(1, sizeof(plugin_config)); + s->opts = array_init(); + + cv[0].destination = s->opts; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { + return HANDLER_ERROR; + } + + if (0 == s->opts->used) continue; + + ds = (data_string *) + array_get_element_klen(s->opts, CONST_STR_LEN("service")); + s->service = (NULL != ds) ? ds->value->ptr : "http"; + } + + if (p->config_storage[0]->service == NULL) + p->config_storage[0]->service = "http"; + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_authn_pam_patch_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + PATCH(service); + + /* skip the first, the global context */ + for (size_t i = 1; i < srv->config_context->used; ++i) { + data_config *dc = (data_config *)srv->config_context->data[i]; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + s = p->config_storage[i]; + for (size_t j = 0; j < dc->value->used; ++j) { + data_unset *du = dc->value->data[j]; + if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.pam.opts"))) { + PATCH(service); + } + } + } + + return 0; +} +#undef PATCH + +static int mod_authn_pam_fn_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { + const char * const pw = (char *)appdata_ptr; + struct pam_response * const pr = *resp = + (struct pam_response *)malloc(num_msg * sizeof(struct pam_response)); + for (int i = 0; i < num_msg; ++i) { + const int style = msg[i]->msg_style; + pr[i].resp_retcode = 0; + pr[i].resp = (style==PAM_PROMPT_ECHO_OFF || style==PAM_PROMPT_ECHO_ON) + ? strdup(pw) + : NULL; + } + return PAM_SUCCESS; +} + +static handler_t mod_authn_pam_query(server *srv, connection *con, void *p_d, const buffer *username, const char *realm, const char *pw) { + plugin_data *p = (plugin_data *)p_d; + pam_handle_t *pamh = NULL; + struct pam_conv conv = { mod_authn_pam_fn_conv, NULL }; + const int flags = PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK; + int rc; + UNUSED(realm); + *(const char **)&conv.appdata_ptr = pw; /*(cast away const)*/ + + mod_authn_pam_patch_connection(srv, con, p); + + rc = pam_start(p->conf.service, username->ptr, &conv, &pamh); + if (PAM_SUCCESS != rc + || PAM_SUCCESS !=(rc = pam_set_item(pamh,PAM_RHOST,con->dst_addr_buf->ptr)) + || PAM_SUCCESS !=(rc = pam_authenticate(pamh, flags)) + || PAM_SUCCESS !=(rc = pam_acct_mgmt(pamh, flags))) + log_error_write(srv, __FILE__, __LINE__, "ss", + "pam:", pam_strerror(pamh, rc)); + pam_end(pamh, rc); + return (PAM_SUCCESS == rc) ? HANDLER_GO_ON : HANDLER_ERROR; +} + +static handler_t mod_authn_pam_basic(server *srv, connection *con, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw) { + char *realm = require->realm->ptr; + handler_t rc = mod_authn_pam_query(srv, con, 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_pam_plugin_init(plugin *p); +int mod_authn_pam_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("authn_pam"); + p->data = NULL; + p->init = mod_authn_pam_init; + p->cleanup = mod_authn_pam_free; + p->set_defaults= mod_authn_pam_set_defaults; + + return 0; +} diff --git a/src/server.c b/src/server.c index f6409bb1..8c5ff379 100644 --- a/src/server.c +++ b/src/server.c @@ -588,6 +588,11 @@ static void show_features (void) { #else "\t- LDAP support\n" #endif +#ifdef HAVE_PAM + "\t+ PAM support\n" +#else + "\t- PAM support\n" +#endif #ifdef USE_MEMCACHED "\t+ memcached support\n" #else |