From a7557acb5423ea8e4c6bcce27472918c638d56f8 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Wed, 17 Jun 2009 00:13:58 +0000 Subject: Some coding work. Banged out generally how things will work. Nothing compiles yet. --- .gitignore | 4 + autogen.sh | 1 + configure.in | 36 +- ideas.txt | 19 + module/Makefile.am | 38 +- module/consumer.cc | 243 +++++++ module/consumer.h | 14 + module/mod_auth_singleid.c | 1621 ++++++++++++++++++++++++++++++++++++++++++++ module/storage.c | 200 ++++++ module/storage.h | 32 + 10 files changed, 2180 insertions(+), 28 deletions(-) create mode 100644 ideas.txt create mode 100644 module/consumer.cc create mode 100644 module/consumer.h create mode 100644 module/mod_auth_singleid.c create mode 100644 module/storage.c create mode 100644 module/storage.h diff --git a/.gitignore b/.gitignore index 7753ee0..66a335c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +*.o +depcomp +libtool +ltmain.sh aclocal.m4 autom4te.cache config.* diff --git a/autogen.sh b/autogen.sh index 3b52ffd..2995946 100755 --- a/autogen.sh +++ b/autogen.sh @@ -4,6 +4,7 @@ set -ex aclocal autoheader +libtoolize automake -a autoconf ./configure --enable-maintainer-mode "$@" diff --git a/configure.in b/configure.in index de38bd9..a18d5de 100644 --- a/configure.in +++ b/configure.in @@ -39,17 +39,38 @@ dnl Process this file with autoconf to produce a configure script. AC_INIT(mod_auth_singleid, 0.1, stef@memberwebs.com) AM_INIT_AUTOMAKE(mod_auth_singleid, 0.1) -AC_CONFIG_SRCDIR([module/mod_auth_singleid.c]) +AC_CONFIG_SRCDIR([module/mod_auth_singleid.cc]) AM_CONFIG_HEADER([config.h]) # Checks for programs. -AC_PROG_CC +AC_PROG_CXX +AC_PROG_CXXCPP +AC_LANG_CPLUSPLUS AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET +AM_PROG_LIBTOOL # APACHE Build stuff +AC_ARG_WITH(apr_config, AC_HELP_STRING([[--with-apr-config=FILE]], [Path to apr-config program]), + [ apr_config="$withval" ], + [AC_PATH_PROGS(apr_config, + [apr-config apr-0-config apr-1-config], + [no], + [$PATH:/usr/sbin/:/usr/local/apache2/bin] + )] +) + +if test "$apr_config" = "no"; then + AC_MSG_ERROR(Could not find the apr-config program. You can specify a location with the --with-apr-config=FILE option. It may be named apr-0-config or apr-1-config and can be found in your apache2 bin directory.) +fi + +$apr_config --cppflags &> /dev/null +if test "$?" != "0"; then + AC_MSG_ERROR($apr_config is not a valid apr-config program) +fi + AC_PATH_PROG(APXS, apxs, "failed") if test "$APXS" = "failed"; then AC_PATH_PROG(APXS2, apxs2, "failed") @@ -75,6 +96,17 @@ fi AC_SUBST(APACHECTL) +APACHE_CFLAGS="-I`${APXS} -q INCLUDEDIR` -I`${apr_config} --includedir --cppflags`" +AC_SUBST(APACHE_CFLAGS) + +# ------------------------------------------------------------------------------------ +# OPKELE + +PKG_CHECK_MODULES([OPKELE], [libopkele >= 2.0], , [ + AC_MSG_ERROR([no libopkele library found (version 2.0 or higher). get one from http://kin.klever.net/libopkele/]) +]) + + AC_DEFINE_UNQUOTED(CONF_PREFIX, "`eval echo ${sysconfdir}`", [Installation Prefix] ) # Debug mode diff --git a/ideas.txt b/ideas.txt new file mode 100644 index 0000000..f10cbb5 --- /dev/null +++ b/ideas.txt @@ -0,0 +1,19 @@ + +AuthSingleIdIdentifier https://id.familymembers.com/ +AuthSingleIdCookie openid +AuthSingleIdUserPrefix https://id.familymembers.com/ +AuthSingleIdUserSuffix +AuthSingleIdAttribute url alias count + +AX_TYPE_FNAME=http://example.com/schema/fullname +AX_VALUE_FNAME=John Smith +AX_TYPE_GENDER=http://example.com/schema/gender +AX_COUNT_GENDER=0 +AX_TYPE_FAV_DOG=http://example.com/schema/favourite_dog +AX_VALUE_FAV_DOG=Spot +AX_TYPE_FAV_MOVIE=http://example.com/schema/favourite_movie +AX_COUNT_FAV_MOVIE=2 +AX_VALUE_FAV_MOVIE_1=Movie1 +AX_VALUE_FAV_MOVIE_2=Movie2 +AX_UPDATE_URL=http://idconsumer.com/update?transaction_id=a6b5c41 + diff --git a/module/Makefile.am b/module/Makefile.am index b303c5e..7b05ad4 100644 --- a/module/Makefile.am +++ b/module/Makefile.am @@ -1,30 +1,16 @@ +noinst_LTLIBRARIES = libmodauthsingleid.la +noinst_DATA = mod_auth_singleid.la -EXTRA_DIST = mod_auth_singleid.c +INCLUDES = -I../ -g -O0 ${APACHE_CFLAGS} ${OPKELE_CFLAGS} +AM_LDFLAGS = ${OPKELE_LIBS} -DEF = -INC = -I../ +libmodauthsingleid_la_SOURCES = \ + mod_auth_singleid.c \ + consumer.cc consumer.h \ + storage.c storage.h -all: mod_auth_singleid.so - -mod_auth_singleid.so: mod_auth_singleid.c - @APXS@ -c -Wc,-g -Wc,-O0 -Wc,-Wall $(DEF) $(INC) $(LIB) mod_auth_singleid.c - -# Install the DSO file into the Apache installation and activate it in config -install: all - @APXS@ -i -a -c -Wc,-g -Wc,-O0 $(DEF) $(INC) $(LIB) mod_auth_singleid.c - -# Cleanup -clean: - -rm -f mod_auth_singleid.o mod_auth_singleid.so - -# Reload the module by installing and restarting Apache -reload: install restart - -# The general Apache start/restart/stop procedures -start: - @APACHECTL@ start -restart: - @APACHECTL@ restart -stop: - @APACHECTL@ stop +install-exec-local: + @APXS@ -i -a -n 'authopenid' mod_auth_openid.la +mod_auth_singleid.la: libmodauthsingleid.la + ${APXS} -c -o $@ $< ${APACHE_CFLAGS} ${OPKELE_CFLAGS} ${OPKELE_LIBS} diff --git a/module/consumer.cc b/module/consumer.cc new file mode 100644 index 0000000..8ade43a --- /dev/null +++ b/module/consumer.cc @@ -0,0 +1,243 @@ + +#include "consumer.h" + +using opkele::assoc_t; +using opkele::association; +using opkele::endpoint_t; +using opkele::failed_lookup; +using opkele::params; +using opkele::prequeue_RP; +using opkele::secret_t; + +using std::string; +using std::vector; + +class Consumer : + public prequeue_RP +{ +private: // types + vector endpoints; + +public: // interface + Consumer(const char *url, singleid_board_t *board) + : _url(url), _board(board) + { } + +public: // overrides + + virtual void begin_queueing() const + { _endpoints.clear(); } + + virtual void queue_endpoint(const openid_endpoint_t& oep) + { _endpoints.push(oep); } + + virtual void set_normalized_id(const string& nid) + { _normalized = nid; } + + virtual const string get_normalized_id() const + { return _normalized; } + + virtual const string get_this_url() + { return _url; } + + virtual assoc_t store_assoc(const string& server, const string& handle, + const string& type, const secret_t& secret, + int expires_in); + + virtual assoc_t find_assoc(const string& server); + + virtual assoc_t retrieve_assoc(const string& server, const string& handle); + + virtual assoc_t invalidate_assoc(const string& server, const string& handle); + + virtual void check_nonce(const string& server, const string& nonce); + +private: // data + singleid_board_t _board; + endpoints _endpoints; + string _normalized; + string _url; +}; + +assoc_t +Consumer::store_assoc(const string& server, const string& handle, + const string& type, const secret_t& secret, + int expires_in) +{ + singleid_assoc_t data; + int res; + + data.server = server.c_str(); + data.handle = handle.c_str(); + data.type = type.c_str(); + data.secret = secret.data(); + data.n_secret = secret.size(); + data.expires = expires_in; + + { + LockShared lock; /* scoped lock */ + res = singleid_board_store_assoc (_board, &data); + } + + if (!res) + throw dump_RP("association data was too large to fit in shared storage"); + + return assoc_t(new association(server, handle, type, secret, expires_on, false)); +} + +assoc_t +Consumer::find_assoc(const string& server) +{ + singleid_assoc_t data = { 0, }; + association assoc = NULL; + + { + LockShared lock; + if (singleid_board_find_assoc (_board, server.c_str(), NULL, &data)) + assoc = new association(data.server, data.handle, data.type, + secret_t(data.secret, data.secret + data.n_secret), + data.expires, false); + } + + if (!assoc) + throw failed_lookup("could not find association for server: " + server); + + return assoc_t(assoc); +} + +assoc_t +Consumer::retrieve_assoc(const string& server, const string& handle) +{ + singleid_assoc_t data = { 0, }; + association assoc = NULL; + + { + LockShared lock; + if (singleid_board_find_assoc (_board, server.c_str(), handle.c_str(), &data)) + assoc = new association(data.server, data.handle, data.type, + secret_t(data.secret, data.secret + data.n_secret), + data.expires, false); + } + + if (!assoc) + throw failed_lookup("could not retrieve association for server: " + server); + + return assoc_t(assoc); +} + +assoc_t +Consumer::invalidate_assoc(const string& server, const string& handle) +{ + LockShared lock; + singleid_board_invalidate_assoc (_board, server.c_str(), handle.c_str()); +} + + +void +Consumer::check_nonce(const string& server, const string& nonce) +{ + LockShared lock; + singleid_board_check_nonce (_board, server.c_str(), nonce.c_str()); +} + +/* ----------------------------------------------------------------------- + * AUTHENTICATION + */ + +static void +filter_openid_params (params_t ¶ms, params_t &extensions) +{ + for (params_t::iterator it = params.begin(); it != params.end(); ) { + const string& name = it->first; + if (name.find ("openid.") == 0) { + /* Extension params have at least a second dot */ + if (name.find ('.', sizeof ("openid."))) + extensions.insert(*it); + + /* We erase an increment together, must use post-increment operator */ + it->erase(it++); + } else { + /* Did not match, just go to next element */ + ++it; + } + } +} + +static void +start_auth (sid_request_t *req, Consumer &consumer, params_t ¶ms, + const string& trust_root, const string &identity) +{ + /* Remove all openid params, and stash away extensions */ + filter_openid_params (params); + string return_to = params.append_query (consumer.get_this_url(), ""); + + params_t result; + + try { + openid_message_t cm; + consumer.initiate (identity); + result = consumer.checkid_ (cm, opkele::mode_checkid_setup, return_to, trust_root); + output = result.append_query (consumer.get_endpoint().uri); + + } catch (failed_xri_resolution &ex) { + sid_request_respond (xxx, xxx, xxx); + return; + + } catch (failed_discovery &ex) { + sid_request_respond (xxx, xxx, xxx); + return; + + } catch (bad_input &ex) { + sid_request_respond (xxx, xxx, xxx); + return; + + } catch (exception &ex) { + sid_request_respond (xxx, xxx, xxx); + xxx log xxx; + return; + } + + sid_request_respond (xxx, xxx, xxx); +} + +static void +complete_auth (sid_request_t *req, Consumer &consumer, params_t ¶ms) +{ + try { + consumer.id_res(AdaptorFix(params)); + string identity = consumer.get_claimed_id(); + sid_request_authenticated (req, identity.c_str()); + } catch (exception &ex) { + sid_request_respond (xxx, xxx, xxx); + } +} + +static void +cancelled_auth (sid_request_t *req, Consumer &consumer, params_t ¶ms) +{ + sid_request_respond (xxx, xxx, xxx); +} + +void +sid_consumer_authenticate(sid_request_t *req, sid_storage_t *store, const char *identity) +{ + params_t params; + + assert (req); + assert (store); + + const char *qs = sid_request_qs (req); + parse_query_string (qs, params); + + const char *url = sid_request_url (req); + Consumer consumer(url, store); + + if (params.has_param("openid.assoc_handle")) + complete_auth (req, consumer, params); + else if (params.has_param("openid.mode") && params.get_param("openid.mode") == "cancel") + cancelled_auth (req, consumer, params); + else + begin_auth (req, consumer, params, trust_root, identity); + + consumer.close(); +} diff --git a/module/consumer.h b/module/consumer.h new file mode 100644 index 0000000..18ddbe4 --- /dev/null +++ b/module/consumer.h @@ -0,0 +1,14 @@ +#ifndef CONSUMER_H_ +#define CONSUMER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +singleid_consumer_authenticate (request_rec *r, ); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* CONSUMER_H_ */ diff --git a/module/mod_auth_singleid.c b/module/mod_auth_singleid.c new file mode 100644 index 0000000..5d8ddb4 --- /dev/null +++ b/module/mod_auth_singleid.c @@ -0,0 +1,1621 @@ +/* + * Copyright (c) 2009, Stefan Walter + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Stef Walter + * + */ + +#include +#include +#include +#include +#if 0 +#include +#include +#include +#include +#include +#include +#endif + +#include "consumer.h" +#include "storage.h" + +/* Apache defines these */ +#undef PACKAGE_BUGREPORT +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION + +#include "config.h" +#include + +extern "C" module AP_MODULE_DECLARE_DATA auth_singleid_module; + +#if 0 + +/* Keep track of a unique identifier */ +static void* conn_current = NULL; + +/* And increment this when it goes out of scope */ +static unsigned int conn_seen = 0; + +/* + * Per directory configuration. + */ +typedef struct httpauth_context { + const char* socketname; + int socket; + + int types; + const char* handler; + const char* domain; + char* needed_groups; + int alloced_groups; + apr_pool_t* child_pool; + + int address_seed; + int retries; + + int shared_version; + void *shared_block; +} httpauth_context_t; + +/* + * Tagged onto a request once authenticated, used for access + * groups and revalidating an already authenticated request. + */ +typedef struct httpauth_request { + const char *user; + const char *groups; +} httpauth_request_t; + +/* + * Shared between all instances of a httpauth_context in + * different processes on a server. + */ +typedef struct httpauth_shared { + int version; + struct sockaddr_any address; +} httpauth_shared_t; + +/* TODO: Support proxy authentication properly */ + +#define AUTH_PREFIX_BASIC "Basic" +#define AUTH_PREFIX_DIGEST "Digest" +#define AUTH_PREFIX_NTLM "NTLM" + +#define AUTH_TYPE_BASIC 1 << 1 +#define AUTH_TYPE_DIGEST 1 << 2 +#define AUTH_TYPE_NTLM 1 << 3 +#define AUTH_TYPE_ANY 0x0000FFFF + +#endif + +#define SINGLEID_AUTHTYPE "SINGLEID" + +/* ------------------------------------------------------------------------------- + * SHARED MEMORY + */ + +static apr_global_mutex_t *shared_lock = NULL; +static const char *shared_lock_name = NULL; + +static int +shared_initialize (apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + apr_file_t *file = NULL; + const char *tmpdir; + char *lock_name; + int rc; + + /* This may be called more than once */ + if (shared_lock) + return OK; + + rc = apr_temp_dir_get (&tmpdir, p); + if (rc != APR_SUCCESS) + ap_log_error (APLOG_MARK, APLOG_ERR, rc, s, + "auth-singleid: couldn't get temporary directory"); + + if (rc == APR_SUCCESS) { + lock_name = apr_pstrcat (p, tmpdir, "/", "mod-auth-singleid.lock.XXXXXX", NULL); + rc = apr_file_mktemp (&file, lock_name, 0, p); + if (rc != APR_SUCCESS) + ap_log_error (APLOG_MARK, APLOG_ERR, rc, NULL, + "auth-singleid: couldn't create temporary file: %s", lock_name); + } + + if (file != NULL) + apr_file_close (file); + + if (rc == APR_SUCCESS) { + rc = apr_global_mutex_create (&shared_lock, lock_name, APR_LOCK_DEFAULT, p); + if (rc != APR_SUCCESS) + ap_log_error (APLOG_MARK, APLOG_ERR, rc, s, + "auth-singleid: couldn't create shared memory lock: %s", lock_name); + } + +#ifdef AP_NEED_SET_MUTEX_PERMS + if (rc == APR_SUCCESS) { + rc = unixd_set_global_mutex_perms (shared_lock); + if (rc != APR_SUCCESS) + ap_log_error (APLOG_MARK, APLOG_ERR, rc, s, + "auth-singleid: Could not set permissions on lock. " + "check User and Group directives"); + } +#endif + + if (rc == APR_SUCCESS) + shared_lock_name = lock_name; + + return OK; +} + +static void +shared_child (apr_pool_t *p, server_rec *s) +{ + apr_status_t rc; + + if (!shared_lock || !shared_lock_name) + return; + + rc = apr_global_mutex_child_init (&shared_lock, shared_lock_name, p); + if (rc != APR_SUCCESS) { + ap_log_error (APLOG_MARK, APLOG_ERR, rc, s, + "httpauth: couldn't create lock for shared memory in child: %s", shared_lock_name); + shared_lock = NULL; + } +} + +static void* +shared_create (apr_pool_t* p, size_t size) +{ + const char *tmpdir; + char *filename; + apr_file_t *file; + apr_mmap_t *map; + void *addr; + int rc; + + /* Get the temp directory */ + rc = apr_temp_dir_get (&tmpdir, p); + if (rc != APR_SUCCESS) { + ap_log_error (APLOG_MARK, APLOG_ERR, rc, NULL, + "auth-singleid: couldn't get temporary directory"); + } + + /* Create the shared file */ + if (rc == APR_SUCCESS) { + filename = apr_pstrcat (p, tmpdir, "/", "mod-auth-singleid.shared.XXXXXX", NULL); + rc = apr_file_mktemp (&file, filename, 0, p); + if (rc != APR_SUCCESS) + ap_log_error (APLOG_MARK, APLOG_ERR, rc, NULL, + "auth-singleid: couldn't create temporary file: %s", filename); + } + + /* Write a shared block to file */ + if (rc == APR_SUCCESS) { + memset (&shared, 0, sizeof (shared)); + xxxxxx + rc = apr_file_write_full (file, &xxxx, size, NULL); + if (rc != APR_SUCCESS) + ap_log_error (APLOG_MARK, APLOG_ERR, rc, NULL, + "auth-singleid: couldn't write to temporary file: %s", filename); + } + + /* Map the shared file into memory */ + if (rc == APR_SUCCESS) { + rc = apr_mmap_create (&map, file, 0, size, APR_MMAP_READ | APR_MMAP_WRITE, p); + if (rc != APR_SUCCESS) + ap_log_error (APLOG_MARK, APLOG_ERR, rc, NULL, + "auth-singleid: couldn't map temporary file: %s", filename); + } + + /* Get the actual address of the mapping */ + if (rc == APR_SUCCESS) { + rc = apr_mmap_offset (&addr, map, 0); + if (rc != APR_SUCCESS) + ap_log_error (APLOG_MARK, APLOG_ERR, rc, NULL, + "auth-singleid: couldn't get shared memory"); + } + + if (rc == APR_SUCCESS) + return addr; + + return NULL; +} + +/* ------------------------------------------------------------------------------------------------- + * COMMON STORAGE + */ + +typedef struct storage_context { + void* shared; + size_t size; +} storage_context_t; + +/* ------------------------------------------------------------------------------------------------- + * OPENID CONSUMER + */ + +#if 0 +static void* storage_shared = NULL; +static size_t storage_size = NULL; +#endif + +#if 0 + +static int +shared_get_if_changed (httpauth_context_t *ctx, int version, httpauth_shared_t *shared) +{ + httpauth_shared_t *block; + int ret = 0; + + if (!ctx->shared_block || !shared_lock) + return 0; + + apr_global_mutex_lock (shared_lock); + + block = ctx->shared_block; + if (block->version != version) { + ret = 1; + if (shared) + memcpy (shared, block, sizeof (*shared)); + } + + apr_global_mutex_unlock (shared_lock); + + return ret; +} + +static void +shared_set_if_changed (httpauth_context_t *ctx, httpauth_shared_t *shared) +{ + httpauth_shared_t *block; + + if (!ctx->shared_block || !shared_lock) + return; + + apr_global_mutex_lock (shared_lock); + + block = ctx->shared_block; + if (memcmp (shared, block, sizeof (*shared)) != 0) { + + /* Increment the version beyond all */ + if (block->version > shared->version) + shared->version = block->version; + ++shared->version; + + /* And write it out */ + memcpy (block, shared, sizeof (*shared)); + } + + apr_global_mutex_unlock (shared_lock); +} +#endif + +/* ------------------------------------------------------------------------------- + * Per Directory Config and Context Code + */ + +static void* +dir_config_creator (apr_pool_t* p, char* dir) +{ +#if 0 + httpauth_context_t* ctx; + httpauth_shared_t shared; + const char *tmpdir; + char *filename; + apr_file_t *file; + apr_mmap_t *map; + void *addr; + int rc; + + ctx = (httpauth_context_t*)apr_pcalloc(p, sizeof(*ctx)); + memset(ctx, 0, sizeof(*ctx)); + + ctx->socket = -1; + ctx->types = 0xFFFFFFFF; + ctx->child_pool = p; + ctx->needed_groups = NULL; + ctx->alloced_groups = 0; + ctx->shared_version = 0; + ctx->retries = 1; + + if (!dir) + return ctx; + + /* Get the temp directory */ + rc = apr_temp_dir_get (&tmpdir, p); + if (rc != APR_SUCCESS) + ap_log_error (APLOG_MARK, APLOG_ERR, rc, NULL, + "httpauth: couldn't get temporary directory"); + + /* Create the shared file */ + if (rc == APR_SUCCESS) { + filename = apr_pstrcat (p, tmpdir, "/", "mod-httpauth.board.XXXXXX", NULL); + rc = apr_file_mktemp (&file, filename, 0, p); + if (rc != APR_SUCCESS) + ap_log_error (APLOG_MARK, APLOG_ERR, rc, NULL, + "httpauth: couldn't create temporary file: %s", filename); + } + + /* Write a shared block to file */ + if (rc == APR_SUCCESS) { + memset (&shared, 0, sizeof (shared)); + rc = apr_file_write_full (file, &shared, sizeof (shared), NULL); + if (rc != APR_SUCCESS) + ap_log_error (APLOG_MARK, APLOG_ERR, rc, NULL, + "httpauth: couldn't write to temporary file: %s", filename); + } + + /* Map the shared file into memory */ + if (rc == APR_SUCCESS) { + rc = apr_mmap_create (&map, file, 0, sizeof (shared), + APR_MMAP_READ | APR_MMAP_WRITE, p); + if (rc != APR_SUCCESS) + ap_log_error (APLOG_MARK, APLOG_ERR, rc, NULL, + "httpauth: couldn't map temporary file: %s", filename); + } + + /* Get the actual address of the mapping */ + if (rc == APR_SUCCESS) { + rc = apr_mmap_offset (&addr, map, 0); + if (rc != APR_SUCCESS) + ap_log_error (APLOG_MARK, APLOG_ERR, rc, NULL, + "httpauth: couldn't get shared memory"); + } + + if (rc == APR_SUCCESS) + ctx->shared_block = addr; + + return ctx; +#endif + return NULL; +} + +#if 0 +static const char* set_socket(cmd_parms* cmd, void* config, const char* val) +{ + struct sockaddr_any sany; + + if (sock_any_pton_n (val, &sany, 1, DEFAULT_PORT | SANY_OPT_NORESOLV) == -1) + return "Invalid socket name or ip in HttpAuthSocket"; + + ((httpauth_context_t*)config)->socketname = val; + return NULL; +} + +static const char* set_handler(cmd_parms* cmd, void* config, const char* val) +{ + httpauth_context_t* conf = (httpauth_context_t*)config; + conf->handler = val; + return NULL; +} + +static const char* set_types(cmd_parms* cmd, void* config, const char* val) +{ + httpauth_context_t* conf = (httpauth_context_t*)config; + int type = 0; + + if(strcasecmp(val, AUTH_PREFIX_BASIC) == 0) + type = AUTH_TYPE_BASIC; + else if(strcasecmp(val, AUTH_PREFIX_DIGEST) == 0) + type = AUTH_TYPE_DIGEST; + else if(strcasecmp(val, AUTH_PREFIX_NTLM) == 0) + type = AUTH_TYPE_NTLM; + else if(strcasecmp(val, "any")) + type = AUTH_TYPE_ANY; + else + return "Invalid type in HttpAuthTypes"; + + if(conf->types == 0xFFFFFFFF) + conf->types = type; + else + conf->types |= type; + + return NULL; +} + +static const char* set_domain(cmd_parms* cmd, void* config, const char* val) +{ + httpauth_context_t* conf = (httpauth_context_t*)config; + conf->domain = trim_space(apr_pstrdup(cmd->pool, val)); + return NULL; +} +#endif + +static const command_rec command_table[] = +{ +#if 0 + AP_INIT_RAW_ARGS( "HttpAuthSocket", set_socket, NULL, OR_AUTHCFG, + "The socket that httpauthd is listening on" ), + AP_INIT_TAKE1( "HttpAuthHandler", set_handler, NULL, OR_AUTHCFG, + "The handler that httpauthd should use to authenticate" ), + AP_INIT_ITERATE( "HttpAuthTypes", set_types, NULL, OR_AUTHCFG, + "The types of authentiction allowed (Basic, Digest, NTLM ...)" ), + AP_INIT_RAW_ARGS( "HttpAuthDigestDomain", set_domain, NULL, OR_AUTHCFG, + "The domain for which digest authentication is relevant" ), +#endif + { NULL } +}; + +#if 0 + +/* ------------------------------------------------------------------------------- + * Socket handling code + */ + +static apr_status_t cleanup_socket(void *fdv) +{ + close((int)(long)fdv); + return OK; +} + +void disconnect_socket(httpauth_context_t* ctx, server_rec* s) +{ + if(ctx->socket != -1) + { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "httpauth: disconnecting from daemon"); + + apr_pool_cleanup_kill(ctx->child_pool, (void*)(long)ctx->socket, + cleanup_socket); + close(ctx->socket); + ctx->socket = -1; + + /* Make sure we send our list of groups to daemon again */ + if (ctx->needed_groups) + ctx->needed_groups[0] = 0; + } +} + +void read_junk(httpauth_context_t* ctx, request_rec* r) +{ + char buf[16]; + const char* t; + int said = 0; + int l; + + if(ctx->socket == -1) + return; + + /* Make it non blocking */ + fcntl(ctx->socket, F_SETFL, fcntl(ctx->socket, F_GETFL, 0) | O_NONBLOCK); + + for(;;) + { + l = read(ctx->socket, buf, sizeof(buf) - 1); + if(l <= 0) + break; + + buf[l] = 0; + t = trim_start(buf); + + if(!said && *t) + { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "httpauth: received junk data from daemon"); + said = 1; + } + } + + fcntl(ctx->socket, F_SETFL, fcntl(ctx->socket, F_GETFL, 0) & ~O_NONBLOCK); +} + +int read_line(httpauth_context_t* ctx, request_rec* r, char** line) +{ + int l; + int al = 256; + char* t; + const char* e; + + e = t = NULL; + *line = NULL; + + for(;;) + { + if(!*line || t + 2 == e) + { + char* n; + int d; + + n = (char*)apr_palloc(r->pool, al * 2); + + if(*line) + memcpy(n, *line, al); + + al *= 2; + + /* The difference */ + d = t - *line; + + *line = n; + t = n + d; + e = n + al; + } + + l = read(ctx->socket, (void*)t, sizeof(char)); + + /* We got a character */ + if(l == 1) + { + /* Skip junky CRLFs */ + if(*t == '\r') + { + *t = ' '; + continue; + } + + /* End of line */ + else if(*t == '\n') + { + t++; + break; + } + + t++; + } + + /* If it's the end of file then return that */ + else if(l == 0) + { + /* Disconnect from socket quietly so we can reconnect later */ + disconnect_socket(ctx, r->server); + return -1; + } + + /* Transient errors */ + else if(l == -1 && errno == EAGAIN) + continue; + + /* Fatal errors */ + else if(l == -1) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(errno), r, + "httpauth: couldn't read data from daemon"); + return -1; + } + } + + *t = 0; + return 0; +} + +int +read_response (httpauth_context_t *ctx, request_rec *r, + int *code, int *ccode, char **details, + int return_errors) +{ + int c, ret = -1; + char *line; + char *t; + char *t2; + + if (read_line (ctx, r, &line) == -1) + return -1; + + line = trim_space (line); + + ap_log_rerror (APLOG_MARK, APLOG_DEBUG, 0, r, + "httpauth: received response line from daemon: %s", line); + + /* Get response code */ + t = ap_getword_nc (r->pool, &line, ' '); + c = strtol (t, &t2, 10); + if (*t2 || c < 100 || c > 999) { + ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: protocol error: invalid code: %s", t); + goto finally; + } + + if (code) + *code = c; + + if (c >= 400 && !return_errors) { + ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: received error from httpauthd: %d %s", c, line); + goto finally; + } + + /* Get the second response code if we're a 200 */ + if (c == 200) { + t = ap_getword_nc (r->pool, &line, ' '); + c = strtol (t, &t2, 10); + if (*t2 || c < 100 || c > 999) { + ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: protocol error: invalid code: %s", t); + goto finally; + } + + if (ccode) + *ccode = c; + } + + if (details) + *details = trim_space (line); + + ret = 0; + +finally: + if (ret < 0 && ctx->socket >= 0) { + disconnect_socket (ctx, r->server); + ++ctx->address_seed; + } + + return ret; +} + +static int +read_process_headers(httpauth_context_t* ctx, int ccode, + request_rec* r, char **groups) +{ + char* line; + const char* name; + apr_table_t* headers; + int c = 0; + + if(ccode > 299) + headers = r->err_headers_out; + else + headers = r->headers_out; + + for(;;) + { + if(read_line(ctx, r, &line) == -1) + return -1; + + /* If that's it then break */ + if(!*line) + break; + + if(apr_isspace(*line)) + { + line = (char*)trim_start(line); + + /* End of headers */ + if(!*line) + break; + + if(c > 0) + { + /* + * TODO: We really should be supporting headers split + * across lines. But httpauthd doesn't currently produce + * headers like that, so we don't need to care about it. + */ + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "httpauth: protocol error: server sent us an split header, which we don't support."); + } + else + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: protocol error: invalid headers."); + } + } + + name = ap_getword_nc(r->pool, &line, ':'); + if(!name || !*name) + break; + + /* + * If that was the end of the line, then it's an + * invalid header :( + */ + if(!*line) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: protocol header: invalid headers"); + return -1; + } + + line = trim_space(line); + + if(strcasecmp(name, "WWW-Authenticate") == 0) + { + if(strncasecmp(line, AUTH_PREFIX_BASIC, strlen(AUTH_PREFIX_BASIC)) == 0 && + !(ctx->types & AUTH_TYPE_BASIC)) + continue; + + else if(strncasecmp(line, AUTH_PREFIX_DIGEST, strlen(AUTH_PREFIX_DIGEST)) == 0 && + !(ctx->types & AUTH_TYPE_DIGEST)) + continue; + + /* Only allow unknown if we don't have it */ + else if(!(ctx->types & AUTH_TYPE_ANY)) + continue; + + /* Fix up when we're a proxy */ + if(r->proxyreq == PROXYREQ_PROXY) + name = "Proxy-Authenticate"; + } + + else if(strcasecmp(name, "Authentication-Info") == 0) + { + if(r->proxyreq == PROXYREQ_PROXY) + name = "Proxy-Authentication-Info"; + } + + else if (strcasecmp(name, "X-HttpAuth-Groups") == 0) + { + if (groups && line) + *groups = line; + } + + c++; + apr_table_addn(headers, name, line); + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "httpauth: received %d headers from daemon", c); + + return 0; +} + +int write_data(httpauth_context_t* ctx, server_rec* s, const char* data) +{ + int r; + + if(ctx->socket == -1) + { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, + "httpauth: Socket to httpauthd daemon closed. Can't write data."); + return -1; + } + + while(*data != 0) + { + r = write(ctx->socket, data, strlen(data)); + + if(r > 0) + data += r; + + else if(r == -1) + { + if(errno == EAGAIN) + continue; + + /* The other end closed. no message */ + if(errno == EPIPE) + disconnect_socket(ctx, s); + + else + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(errno), s, + "httpauth: Couldn't write data to daemon"); + + return -1; + } + } + + return 0; +} + +static int +try_connect_socket (httpauth_context_t *ctx, struct sockaddr_any *sany, + request_rec *r) +{ + char peername[256]; + int rc; + + if (sock_any_ntop (sany, peername, sizeof (peername), 0) < 0) + strcpy (peername, "[unknown]"); + + ctx->socket = socket (SANY_TYPE (*sany), SOCK_STREAM, 0); + if(ctx->socket == -1) { + ap_log_rerror (APLOG_MARK, APLOG_CRIT, APR_FROM_OS_ERROR (errno), r, + "httpauth: Can't create socket"); + return -1; + } + + if (connect (ctx->socket, &SANY_ADDR (*sany), SANY_LEN(*sany)) != 0) { + rc = APR_FROM_OS_ERROR (errno); + ap_log_rerror (APLOG_MARK, APLOG_CRIT, rc, r, + "httpauth: Can't connect to httpauthd at: %s", peername); + close (ctx->socket); + ctx->socket = -1; + return -1; + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "httpauth: connected to daemon: %s", peername); + + return 0; +} + +static int +connect_socket(httpauth_context_t* ctx, request_rec* r) +{ + httpauth_shared_t shared; + struct sockaddr_any sany[16]; + int i, which, count = 0; + int rc = -1; + + disconnect_socket(ctx, r->server); + memset (&shared, 0, sizeof (shared)); + + /* Find out what everyone else is connected to */ + if (shared_get_if_changed (ctx, ctx->shared_version, &shared) && shared.version > 0) { + ap_log_rerror (APLOG_MARK, APLOG_DEBUG, 0, r, + "httpauth: trying shared address..."); + rc = try_connect_socket (ctx, &shared.address, r); + } + + /* Now try to connect to all the other addresses */ + if (rc < 0) { + ap_log_rerror (APLOG_MARK, APLOG_DEBUG, 0, r, + "httpauth: resolving daemon address(s)"); + + count = sock_any_pton_n (ctx->socketname, sany, 16, DEFAULT_PORT); + if (count < 0) { + ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, + "httpauth: Invalid socket name or ip: %s", ctx->socketname); + rc = -1; + } + + /* We know how many addresses we have to retry with */ + if (count > 0) + ctx->retries = count; + + for (i = 0; i != count; ++i) { + which = (i + ctx->address_seed) % count; + rc = try_connect_socket (ctx, &sany[which], r); + + /* Successful, then let others know we're connected here */ + if (rc >= 0) { + memcpy (&shared.address, &sany[which], sizeof (shared.address)); + break; + } + } + } + + /* Yay, successful */ + if (rc >= 0) { + shared_set_if_changed (ctx, &shared); + ctx->shared_version = shared.version; + apr_pool_cleanup_register(ctx->child_pool, (void*)(long)ctx->socket, + cleanup_socket, cleanup_socket); + errno = 0; + } + + return rc; +} + +int connect_httpauth(httpauth_context_t* ctx, request_rec* r) +{ + int ret = -1; + int code; + char* details; + const char* t; + + if(connect_socket(ctx, r) == -1) + goto finally; + + if(read_response(ctx, r, &code, NULL, &details, 0) == -1) + goto finally; + + if(code != 100) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: protocol error (Expected 100, got %d)", code); + goto finally; + } + + /* Check theversion number */ + details = trim_space(details); + + if(strcmp(details, "HTTPAUTH/1.0") != 0) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: Daemon speaking incompatible protocol version: %s", details); + goto finally; + } + + /* Send our handler */ + if(ctx->handler) + { + t = apr_pstrcat(r->pool, "SET Handler ", ctx->handler, "\n", NULL); + + if(write_data(ctx, r->server, t) == -1) + goto finally; + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "httpauth: sent handler to daemon: %s", t); + + if(read_response(ctx, r, &code, NULL, NULL, 0) == -1) + goto finally; + + if(code != 202) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: protocol error: couldn't send handler to daemon (Expected 202, got %d)", code); + goto finally; + } + } + + /* Send any setup info we have */ + if(ctx->domain) + { + t = apr_pstrcat(r->pool, "SET Domain ", ctx->domain, "\n", NULL); + + if(write_data(ctx, r->server, t) == -1) + goto finally; + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "httpauth: sent domains to daemon: %s", t); + + if(read_response(ctx, r, &code, NULL, NULL, 0) == -1) + goto finally; + + if(code != 202) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: protocol error: couldn't send domain to daemon (Expected 202, got %d)", code); + goto finally; + } + } + + /* We're cool! */ + ret = 0; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "httpauth: handshake with daemon completed"); + +finally: + if(ret == -1 && ctx->socket >= 0) { + disconnect_socket(ctx, r->server); + ++ctx->address_seed; + } + + return ret; +} + +/* Make sure our connection identifier is unique */ +static apr_status_t connection_gone (void *data) +{ + conn_current = NULL; + conn_seen++; + return APR_SUCCESS; +} + +int write_request(httpauth_context_t* ctx, request_rec* r) +{ + char pidid[40]; + char connid[40]; + int i, c = 0; + const char* t; + const apr_array_header_t* hdrs_arr; + const apr_table_entry_t* elts; + + /* When the connection goes away, call our handler */ + if(conn_current != r->connection) + { + conn_current = r->connection; + apr_pool_cleanup_register(r->connection->pool, r, + connection_gone, apr_pool_cleanup_null); + } + + /* A unique per connection id */ + snprintf(connid, sizeof(connid), "0x%X-%X-%X", + (unsigned int)r->connection, conn_seen, (unsigned int)r->connection->id); + connid[sizeof(connid) - 1] = 0; + snprintf(pidid, sizeof(pidid), "%d", (unsigned int)getpid()); + pidid[sizeof(pidid) - 1] = 0; + t = apr_pstrcat(r->pool, pidid, ":", connid, NULL); + + /* Send the request header to httpauthd */ + t = apr_pstrcat(r->pool, "AUTH ", t, " ", r->method, + " ", r->unparsed_uri, "\n", NULL); + + if(write_data(ctx, r->server, t) == -1) + return -1; + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "httpauth: sent auth request to daemon: %s", t); + + /* Now send the headers to httpauthd */ + + hdrs_arr = apr_table_elts(r->headers_in); + elts = (const apr_table_entry_t*)hdrs_arr->elts; + + for(i = 0; i < hdrs_arr->nelts; i++) + { + if(!elts[i].val) + continue; + + /* Filter out headers we don't want */ + if(strcasecmp(elts[i].key, r->proxyreq == PROXYREQ_PROXY ? + "Proxy-Authorization" : "Authorization") == 0) + { + t = trim_start(elts[i].val); + + if(strncasecmp(t, AUTH_PREFIX_BASIC, strlen(AUTH_PREFIX_BASIC)) == 0 && + !(ctx->types & AUTH_TYPE_BASIC)) + continue; + + else if(strncasecmp(t, AUTH_PREFIX_DIGEST, strlen(AUTH_PREFIX_DIGEST)) == 0 && + !(ctx->types & AUTH_TYPE_DIGEST)) + continue; + + else if(strncasecmp(t, AUTH_PREFIX_NTLM, strlen(AUTH_PREFIX_NTLM)) == 0 && + !(ctx->types & AUTH_TYPE_NTLM)) + continue; + + /* Only allow unknown if we don't have it */ + else if(!(ctx->types & AUTH_TYPE_ANY)) + continue; + + /* Extra blank line when at end */ + t = apr_pstrcat(r->pool, "Authorization: ", elts[i].val, "\n", NULL); + + if(write_data(ctx, r->server, t) == -1) + return HTTP_INTERNAL_SERVER_ERROR; + + c++; + } + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "httpauth: sent %d headers to daemon", c); + + return write_data(ctx, r->server, "\n"); +} + +static int +write_needed_groups(httpauth_context_t *ctx, request_rec *r) +{ + const apr_array_header_t *reqs_arr = ap_requires(r); + require_line *reqs; + const char *groups = NULL; + const char *text; + char *word; + register int x; + int m = r->method_number; + int code, len; + + if(reqs_arr) { + reqs = (require_line*)reqs_arr->elts; + for (x = 0; x < reqs_arr->nelts; x++) { + if (!(reqs[x].method_mask & (AP_METHOD_BIT << m))) + continue; + + text = reqs[x].requirement; + word = ap_getword_white (r->pool, &text); + + /* Append all groups to the string */ + if (strcmp (word, "group") == 0 && text && text[0]) { + if (!groups) + groups = text; + else + groups = apr_pstrcat (r->pool, text, " ", groups, NULL); + } + } + } + + /* No groups, no need to send */ + if (!groups && !ctx->needed_groups) + return 0; + + if (!groups) + groups = ""; + + /* Equal groups, no need to send */ + if (ctx->needed_groups && strcmp (groups, ctx->needed_groups) == 0) + return 0; + + /* Groups changed, send to daemon */ + text = apr_pstrcat (r->pool, "SET Groups ", groups, "\n", NULL); + + if (write_data (ctx, r->server, text) < 0) + return -1; + + ap_log_rerror (APLOG_MARK, APLOG_DEBUG, 0, r, + "httpauth: sent groups to daemon: %s", text); + + if (read_response (ctx, r, &code, NULL, NULL, 1) < 0) + return -1; + + if (code != 202) { + ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: couldn't send groups to daemon (Expected 202, got %d)", code); + /* Older versions of the daemon did not support the 'SET Groups' command */ + if (code != 400) + return -1; + } + + /* Save away the groups for next time */ + len = strlen (groups); + if (len >= ctx->alloced_groups) { + if (len < 512) + len = 512; + ctx->needed_groups = (char*)apr_pcalloc (ctx->child_pool, len * 2); + ctx->alloced_groups = len * 2; + } + strcpy (ctx->needed_groups, groups); + return 0; +} + +static httpauth_request_t* +setup_request_hreq (request_rec *r, char *user, char *groups) +{ + httpauth_request_t* hreq; + + hreq = (httpauth_request_t*)apr_pcalloc (r->pool, sizeof (*hreq)); + hreq->user = r->user; + hreq->groups = groups; + + if (groups) + apr_table_setn (r->subprocess_env, "HTTPAUTH_GROUPS", groups); + else + apr_table_unset (r->subprocess_env, "HTTPAUTH_GROUPS"); + + ap_set_module_config (r->request_config, &httpauth_module, hreq); + + return hreq; +} + +#endif + +typedef struct session_info { + const char *identifier; + time_t expiry; +} session_info_t; + +static const char* +session_cookie_value (request_rec *r, const char *name) +{ + const char *cookies; + const char *value; + char *pair; + + cookies = apr_table_get (r->headers_in, "Cookie"); + if (cookies == NULL) + return NULL; + + while (*cookies) { + pair = ap_get_token (r->pool, &cookies, 0); + if (!pair) + break; + if (pair[0] == '$') + continue; + + value = ap_stripprefix (pair, name); + if (value == pair) + continue; + while (isspace (value)) + ++value; + + if (value != '=') + continue; + while (isspace (value)) + ++value; + + return value; + } + + return NULL; +} + +static char* +session_create_sig (apr_pool_t *p, const char *value) +{ + unsigned char digest[APR_SHA1_DIGESTSIZE]; + char *sig; + + apr_sha1_ctx_t ctx; + apr_sha1_init (&ctx); + apr_sha1_update (&ctx, session_secret, strlen (session_secret)); + apr_sha1_update (&ctx, "\0", 1); + apr_sha1_update (&ctx, value, strlen (value)); + apr_sha1_final (digest, &ctx); + + sig = apr_pcalloc (p, apr_base64_encode_len (digest)); + apr_base64_encode (sig, digest, sizeof (digest)); + return sig; +} + +static int +session_validate_sig (apr_pool_t *p, const char *sig, const char *value) +{ + char *valid = session_create_sig (p, value); + return strcmp (sig, valid) == 0; +} + +static session_info_t* +session_load_info (request_rec *r) +{ + session_info_t *sess; + const char *value; + char *token, *sig; + const char *t; + long expiry; + + value = session_cookie_value (r, "mod-auth-single-id"); + if (!value) + return NULL; + + sig = ap_get_token (r->pool, &value, 1); + + /* The version of the session info, only 1 supported for now */ + version = ap_get_token (r->pool, &value, 1); + if (strcmp(version, "1") != 0) + return NULL; + + if (!session_validate_sig (r->pool, sig, value)) + return NULL; + + token = ap_get_token (r->pool, &value, 1); + expiry = strtol (token, &t, 10); + if (*t != '\0') + return NULL; + + /* Don't let expired sessions be valid */ + if (expiry < time (NULL)) + return NULL; + + /* The identifier */ + identifier = ap_get_token (r->pool, &value, 1); + if (!ap_is_url (identifier)) + return NULL; + + sess = apr_pcalloc (r->pool, sizeof (session_info_t)); + sess->expiry = expiry; + sess->identifier = identifier; + + return sess; +} + +static void +session_send_info (request_rec *r, session_info_t *sess) +{ + char *cookie, *sig, *value; + + /* Create the cookie value and sign it */ + value = apr_psprintf (r->pool, "1 %d \"%s\"", sess->expiry, ap_escape_quotes (sess->identifier)); + sig = session_create_sig (r->pool, value); + + /* Build up the full cookie spec */ + cookie = apr_psprintf (r->pool, "mod-auth-single-id=%s %s; httponly; max-age=86400", sig, value); + apr_table_addn (r->headers_out, "Set-Cookie", cookie); +} + +static session_info_t* +session_copy_info (apr_pool_t *p, session_info_t *sess) +{ + session_info_t *copy = apr_pmalloc (p, sizeof (*sess)); + copy->expiry = sess->expiry; + copy->identifier = apr_pstrdup (sess->identifier); + return copy; +} + +static void +set_request_authenticated (request_rec *r, session_info_t *sess) +{ + r->user = sess->identifier; + r->ap_auth_type = SINGLEID_AUTHTYPE; + ap_set_module_config (r->request_config, &auth_singleid_module, sess); +} + +singleid_request_xxxx +{ + +} + +static int +hook_authenticate (request_rec* r) +{ + session_info_t *sess; +#if 0 + httpauth_context_t* ctx; + httpauth_request_t* hreq; +#endif + const char* authtype; +#if 0 + int code = 0; + int ccode = 0; + char *groups = NULL; + char* details = NULL; +#endif + request_rec* mainreq; +#if 0 + int retried = 0; +#endif + + /* Make sure it's for us */ + if (!(authtype = ap_auth_type (r)) || strcasecmp (SINGLEID_AUTHTYPE, authtype) != 0) + return DECLINED; + +#if 0 + ctx = (httpauth_context_t*)ap_get_module_config(r->per_dir_config, &httpauth_module); + + if(!ctx->socketname || !ctx->handler) + return DECLINED; +#endif + + mainreq = r; + + while (mainreq->main != NULL) + mainreq = mainreq->main; + + while (mainreq->prev != NULL) + mainreq = mainreq->prev; + + /* Check if we've already authenticated this request */ + sess = ap_get_module_config (mainreq->request_config, &auth_singleid_module); + if (sess != NULL) { + if (mainreq != r) { + sess = session_copy_info (r->pool, sess); + set_request_authenticated (r, sess); + } + return OK; + } + + /* Load the session info from the request and see if we've authenticated */ + sess = session_load_info (r); + if (sess != NULL) { + set_request_authenticated (r, sess); + return OK; + } + + singleid_consumer (r) + Consumer consumer(uri, ) + opkele::params_t params; + parse_query_string(r, params); + + /* Is it an openid response? */ + if () + + query = openid_parse_response (r); + if (query != NULL) { + (r, ) + + + } + + /* Otherwise start a new openid authentication */ + + +#if 0 + /* + * Check if we're in sync with the other processes, + * and connected to the same daemon + */ + if (ctx->socket != -1 && shared_get_if_changed (ctx, ctx->shared_version, NULL)) { + ap_log_rerror (APLOG_MARK, APLOG_INFO, 0, r, + "httpauth: syncing connection with other processes"); + disconnect_socket (ctx, r->server); + } + +/* For jumping to when a connection has been closed */ +retry: + + if (ctx->socket == -1) { + if (connect_httpauth (ctx, r) == -1) { + + if (ctx->socket == -1 && retried < ctx->retries) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "httpauth: trying to connect to daemon again"); + ++retried; + goto retry; + } + + return HTTP_INTERNAL_SERVER_ERROR; + } + } + + /* Make sure we're starting on a clean slate */ + read_junk (ctx, r); + + + + /* Send off a request, along with groups, and read response */ + if (write_needed_groups (ctx, r) == -1 || + write_request (ctx, r) == -1 || + read_response (ctx, r, &code, &ccode, &details, 0) == -1) { + + /* + * If our connection was closed by httpauthd then this + * is where we get the error. Just do one retry to + * try and reconnect. This happens often when restarting + * httpauthd. + */ + + if (ctx->socket == -1 && retried < ctx->retries) { + ap_log_rerror (APLOG_MARK, APLOG_WARNING, 0, r, + "httpauth: reconnecting to to httpauthd"); + ++retried; + goto retry; + } + + ap_log_rerror (APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(errno), r, + "httpauth: couldn't send request to httpauthd"); + + return HTTP_INTERNAL_SERVER_ERROR; + } + + if(code != 200) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: protocol error: unexpected code while authenticating: %d", code); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Copy over other headers */ + if(read_process_headers(ctx, ccode, r, &groups) == -1) + return HTTP_INTERNAL_SERVER_ERROR; + + if (ccode == 200) { + ap_log_rerror (APLOG_MARK, APLOG_INFO, 0, r, + "httpauth: successful authentication for user: %s", details); + + r->user = apr_pstrdup (r->pool, details); + r->ap_auth_type = HTTPAUTH_AUTHTYPE; + + /* Mark request as successfully authenticated */ + hreq = setup_request_hreq (r, details, groups); + return OK; + } + + return ccode; +} + +#endif +#if 0 + +static const char* +find_word_quoted (const char *all, const char *word) +{ + const char *at; + char before, after; + size_t len = strlen (word); + + at = all; + for (;;) { + at = strstr (at, word); + if (!at) + return NULL; + + before = (at == all) ? 0 : *(at - 1); + after = *(at + len); + + /* Beginning and end of a space delimited word */ + if ((!before || isspace (before)) && + (!after || isspace (after))) { + return at; + } + + /* Beginning and end of a quoted word */ + if ((before == '"' || before == '\'') && after == before) + return at; + + at += len; + } +} + +static int +httpauth_access(request_rec *r) +{ + httpauth_context_t *ctx; + httpauth_request_t *hreq; + const char* authtype; + char *user = r->user; + int m = r->method_number; + int method_restricted = 0; + register int x; + const char *text, *word; + const apr_array_header_t *reqs_arr = ap_requires (r); + require_line *reqs; + + /* Make sure it's for us */ + if (!(authtype = ap_auth_type (r)) || strcasecmp (HTTPAUTH_AUTHTYPE, authtype) != 0) + return DECLINED; + + if (!reqs_arr) + return OK; + + /* Dig out our configuration */ + ctx = ap_get_module_config (r->per_dir_config, &httpauth_module); + hreq = ap_get_module_config (r->request_config, &httpauth_module); + reqs = (require_line *)reqs_arr->elts; + + for (x = 0; x < reqs_arr->nelts; x++) { + if (!(reqs[x].method_mask & (AP_METHOD_BIT << m))) + continue; + + method_restricted = 1; + + text = reqs[x].requirement; + word = ap_getword_white(r->pool, &text); + + /* Any valid user */ + if (strcmp (word, "valid-user") == 0) { + return OK; + + /* Specific listed users */ + } else if (strcmp (word, "user") == 0) { + while (text[0]) { + word = ap_getword_conf (r->pool, &text); + if (strcmp (user, word) == 0) { + return OK; + } + } + + /* Specific groups */ + } else if (strcmp (word, "group") == 0) { + if (hreq && hreq->groups) { + while (text[0]) { + word = ap_getword_conf (r->pool, &text); + if (find_word_quoted (hreq->groups, word)) + return OK; + } + } + + /* What is this? */ + } else { + ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, + "access to %s failed, reason: unknown require " + "directive:\"%s\"", r->uri, reqs[x].requirement); + } + } + + if (!method_restricted) + return OK; + + ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, + "access to %s failed, reason: user %s not allowed access", + r->uri, user); + return HTTP_UNAUTHORIZED; +} +#endif + +static void +register_hooks(apr_pool_t *p) +{ + ap_log_perror (APLOG_MARK, APLOG_ERR, 0, p, "mod_auth_singleid registering hooks"); +#if 0 + ap_hook_post_config (httpauth_initialize, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_child_init (httpauth_child, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_check_user_id (hook_authenticate, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_auth_checker (httpauth_access, NULL, NULL, APR_HOOK_MIDDLE); +#endif +} + +module AP_MODULE_DECLARE_DATA auth_singleid_module = { + STANDARD20_MODULE_STUFF, + dir_config_creator, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + command_table, /* command table */ + register_hooks /* register hooks */ +}; diff --git a/module/storage.c b/module/storage.c new file mode 100644 index 0000000..3cb9cdd --- /dev/null +++ b/module/storage.c @@ -0,0 +1,200 @@ + +/* Yes, this looks backwards. */ +typedef char nonce_t[40]; + +struct singleid_board { + + /* The association with our server */ + char server[256]; + char handle[256]; + unsigned char secret[256]; + char type[64]; + size_t n_secret; + time_t expires; + + /* Book keeping for the records */ + nonce_t *records; + size_t first; + size_t total; + size_t count; + int wrapped; +}; + +int +singleid_board_store_assoc (singleid_board_t *board, const singleid_assoc_t *assoc) +{ + assert (board); + assert (assoc); + + /* Check that we have enough space to store this information */ + if ((assoc->server && strlen (assoc->server) > sizeof (board->server)) || + (assoc->handle && strlen (assoc->handle) > sizeof (board->handle)) || + (assoc->n_secret && assoc->n_secret > sizeof (board->secret)) || + (assoc->type && strlen (assoc->type) > sizeof (board->type))) + return 0; + + strcpy (board->server, assoc->server ? assoc->server : ""); + strcpy (board->handle, assoc->handle ? assoc->handle : ""); + memcpy (board->secret, assoc->secret, assoc->n_secret); + strcpy (board->type, assoc->type ? assoc->type : ""); + board->expires = assoc->expires; + return 1; +} + +int +singleid_board_find_assoc (singleid_board_t *board, const char *server, + const char *handle, singleid_assoc_t *assoc) +{ + assert (board); + assert (assoc); + + if (server && (strlen (server) > sizeof (board->server) || + strcmp (server, board->server) != 0)) + return 0; + + if (handle && (strlen (handle) > sizeof (board->handle) || + strcmp (handle, board->handle) != 0)) + return 0; + + assoc->server = board->server; + assoc->handle = board->handle; + assoc->type = board->type; + assoc->secret = board->secret; + assoc->n_secret = board->n_secret; + assoc->expires = board->expires; + return 1; +} + +void +singleid_board_invalidate_assoc (singleid_board_t *board, const char *server, + const char *handle) +{ + singleid_assoc_t dummy; + assert (board); + + if (singleid_board_find_assoc (board, server, handle, &dummy)) { + board->server[0] = 0; + board->handle[0] = 0; + board->type[0] = 0; + memset (board->secret, 0, sizeof (board->secret)); + board->n_secret = 0; + board->expires = 0; + board->secret[0] = 0; + } +} + +#define nonce_compare(a, b) \ + memcmp (a, b, sizeof (nonce_t)) + +static void +nonce_put (nonce_t rec, const char *nonce) +{ + size_t len = strlen (nonce); + char *dst = (char*)rec; + + /* If it's short enough, then just board. Fast */ + if (len < sizeof (rec)) { + memcpy (dst, nonce, len); + memset (dst + len, 0, sizeof (rec) - len); + + /* Too long, need to compress into the record */ + } else { + apr_sha1_ctx_t ctx; + + assert (sizeof (nonce_t) == APR_SHA1_DIGESTSIZE + 20); + assert (len > 20); + + /* The date prefix we just copy in */ + memcpy (dst, nonce, 20); + + /* Hash the rest into the buffer */ + apr_sha1_init (&ctx); + apr_sha1_update (&ctx, nonce + 20, len - 20); + apr_sha1_final (dst + 20, &ctx); + } +} + +static void +insert_nonce_record (singleid_board_t *board, nonce_t rec, size_t at) +{ + nonce_t *records = board->records; + size_t from, to, num; + + assert (board->total > 2); + assert (at < board->total); + assert (at != board->first); + + /* Insertion right after latest, either ancient, more likely top */ + if (at == board->first + 1 % board->total) { + /* We can just copy in at this point */ + + /* Our ring has empty space in it, so always push forwards, but only until first */ + } else if (!board->wrapped) { + memmove (records + at + 1, records + at, sizeof (rec) * (board->first - at)); + board->first += 1; + + /* Move data backwards to make space */ + } else if (board->first < at) { + memmove (records + board->first + 2, records + board->first + 1, + sizeof (rec) * (at - board->first)); + + /* Move data forwards to make space simply */ + } else { + memmove (records + at + 1, records + at, sizeof (rec) * (board->first - at - 1)); + } + + memcpy (records[at], rec, sizeof (rec)); + ++board->count; + + /* Track whether we have a full ring or not. */ + if (!board->wrapped && board->count > board->total) + board->wrapped = 1; +} + +int +singleid_board_check_nonce (singleid_board_t *board, const char *nonce) +{ + nonce_t *records; + nonce_t rec; + size_t at, lower, upper, mid; + int res; + + assert (board); + assert (nonce); + assert (board->records); + assert (board->first < board->total); + + nonce_put (rec, nonce); + records = board->records; + + /* Best case scenario, new nonce is higher than our latest one */ + res = nonce_compare (rec, records[top]); + + /* Was the last nonce */ + if (res == 0) { + return 1; + + /* Newer than anything, push on top */ + } else if (res < 0) { + at = (board->first + 1) % board->total; + insert_nonce_record (board, rec, at); + board->first = at; + return 0; + } + + /* Do a binary search for the item */ + for (lower = 0, upper = board->total; lower < upper; ) { + mid = lower + ((upper - lower) / 2); // Note: not (low + high) / 2 !! + at = at + board->first % board->total; + res = nonce_compare (rec, records[at]); + if (res == 0) + return 1; /* Have the nonce */ + else if (res > 0) + lower = mid + 1; + else + upper = mid; + } + + insert_nonce_record (board, rec, at); + return 0; /* Didn't find nonce */ +} diff --git a/module/storage.h b/module/storage.h new file mode 100644 index 0000000..b3a2ad7 --- /dev/null +++ b/module/storage.h @@ -0,0 +1,32 @@ +#ifndef BOARD_H_ +#define BOARD_H_ + +/* Communications white-board between processes/threads */ + +typedef struct singleid_board singleid_board_t; + +typedef struct singleid_assoc { + const char *server; + const char *handle; + const char *type; + const unsigned char *secret; + const size_t n_secret; + time_t expires; +} singleid_assoc_t; + +int singleid_board_check_nonce (singleid_board_t *board, + const char *nonce); + +int singleid_board_store_assoc (singleid_board_t *board, + const singleid_assoc_t *assoc); + +int singleid_board_find_assoc (singleid_board_t *board, + const char *server, + const char *handle, + singleid_assoc_t *assoc); + +void singleid_board_invalidate_assoc (singleid_board_t *board, + const char *server, + const char *handle); + +#endif /* BOARD_H_ */ -- cgit v1.2.3