/* * 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 */ };