/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include /* Apache defines these */ #undef PACKAGE_BUGREPORT #undef PACKAGE_NAME #undef PACKAGE_STRING #undef PACKAGE_TARNAME #undef PACKAGE_VERSION #include "config.h" #include "mod_auth_singleid.h" #include #include #include #include extern module AP_MODULE_DECLARE_DATA auth_singleid_module; #define VALID_NAME "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-." #define VALID_ALIAS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-" #define MAX_POST_SIZE 256 * 1024 enum { NONE = 0, SUFFIX = 1, REGEX = 2 }; /* * Per request information. */ typedef struct sid_session { char *identifier; apr_array_header_t *values; time_t expiry; } sid_session_t; /* * Per directory configuration. */ typedef struct sid_context { const char *realm_uri; const char *identifier; const char *cookie_name; const char *cookie_domain; const char *cookie_path; int cookie_secure; int user_match; ap_regex_t *converter; sid_storage_t *store; sid_attribute_t *attributes; int redirect_after; const char *logout_path; const char *logout_dest; } sid_context_t; #define SID_AUTHTYPE "SingleID" /* ------------------------------------------------------------------------------- * UTILITY */ static void strupcase(char *str) { while (*str) { *str = apr_toupper(*str); ++str; } } static char* get_token (apr_pool_t *pool, const char **line, const char *delims) { const char *beg, *end; const char *str = *line; char *result, *from, *at; int quoted = 0; /* Find first non-white byte */ while (*str && apr_isspace(*str)) ++str; if (*str == '\0') return NULL; beg = str; while (*str && !strchr (delims, *str)) { if (*str++ == '"') { while (*str) if (*str++ == '"') break; } } end = str; /* Trim the quotes if present */ if (beg + 1 < end && *beg == '"' && *(end - 1) == '"') { quoted = 1; ++beg; --end; } /* Trim any spaces on end if present */ while (beg != end && apr_isspace (*(end - 1))) --end; /* The next token */ while (*str && strchr (delims, *str)) ++str; *line = str; result = apr_pstrndup (pool, beg, end - beg); /* Unquote vigorously */ if (quoted) { at = from = result; for (;;) { if (from[0] == '\\' && from[1] != '\0') *(at++) = *(++from); else *(at++) = *from; if (*from == '\0') break; ++from; } } return result; } static int compare_paths (const char *path1, const char *path2) { size_t len1, len2; if (!path1 && !path2) return 0; if (!path1) return -1; if (!path2) return 1; len1 = strlen(path1); while (len1 && path1[len1 - 1] == '/') --len1; len2 = strlen(path2); while (len2 && path2[len2 - 1] == '/') --len2; if (len1 < len2) return -1; if (len1 > len2) return 1; return strncmp (path1, path2, len1); } /* ------------------------------------------------------------------------------- * SHARED MEMORY and LOCKING */ static apr_global_mutex_t *shared_lock = NULL; static const char *shared_lock_name = NULL; static int shared_initialize (apr_pool_t *p) { apr_file_t *file = NULL; char *lock_name = NULL; const char *tmpdir; 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, NULL, "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, NULL, "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, NULL, "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; apr_off_t offset; void *addr; int rc; if (!shared_lock) shared_initialize (p); if (!shared_lock) return NULL; /* 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); } /* Extend file to required size */ if (rc == APR_SUCCESS) { offset = size - 1; rc = apr_file_seek (file, APR_SET, &offset); if (rc == APR_SUCCESS) rc = apr_file_write_full (file, "\0", 1, 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; } void sid_shared_lock (void) { apr_global_mutex_lock (shared_lock); } void sid_shared_unlock (void) { apr_global_mutex_unlock (shared_lock); } /* ------------------------------------------------------------------------------- * CONFIGURATION */ static void* dir_config_creator (apr_pool_t* p, char* dir) { sid_context_t* ctx = apr_pcalloc (p, sizeof (*ctx)); memset (ctx, 0, sizeof (*ctx)); ctx->cookie_name = "mod-auth-singleid"; ctx->redirect_after = 1; return ctx; } static const char* set_identifier (cmd_parms* cmd, void* config, const char* val) { sid_context_t *ctx = config; if (!ap_is_url (val)) return "Not a valid URL in SingleIDProvider"; ctx->identifier = apr_pstrdup (cmd->pool, val); return NULL; } static const char* set_realm (cmd_parms* cmd, void* config, const char* val) { sid_context_t *ctx = config; apr_uri_t uri; if (apr_uri_parse (cmd->pool, val, &uri) != APR_SUCCESS) return "Not a valid URL for SingleIDRealm"; if (uri.hostname && strchr (uri.hostname, '.')) ctx->cookie_domain = uri.hostname; if (uri.path && uri.path[0]) ctx->cookie_path = uri.path; else ctx->cookie_path = "/"; if (uri.scheme && strcasecmp (uri.scheme, "https") == 0) ctx->cookie_secure = 1; ctx->realm_uri = apr_pstrdup (cmd->pool, val); return NULL; } static const char* set_user_match (cmd_parms *cmd, void *config, const char *val) { sid_context_t *ctx = config; /* Remove extraneous spaces */ while (isspace (*val)) ++val; if (strcasecmp (val, "suffix") == 0 && !isalpha (val[6])) { ctx->user_match = SUFFIX; return NULL; } /* Try to compile as a regular expression */ ctx->converter = ap_pregcomp (cmd->pool, val, AP_REG_EXTENDED | AP_REG_ICASE | AP_REG_NEWLINE); if (!ctx->converter) return "Invalid regular expression in SingleIDUserMatch"; ctx->user_match = REGEX; return NULL; } static const char* set_cache_size (cmd_parms *cmd, void *config, const char *val) { sid_context_t *ctx = config; char *end; int size = 0; void *shared; size_t page; if (strcasecmp (val, "on") == 0) { size = 64 * 1024; } else if (strcasecmp (val, "off") == 0) { size = 0; } else { size = strtol (val, &end, 10); if (*end != '\0') return "Invalid number specified for SingleIDCache"; } if (size == 0) return NULL; /* Align to a page size */ page = getpagesize (); size = ((size + (page - 1)) / page) * page; shared = shared_create (cmd->pool, size); ctx->store = sid_storage_initialize (shared, size); return NULL; } static const char* set_cookie_name (cmd_parms *cmd, void *config, const char *val) { sid_context_t *ctx = config; const char *end; end = val + strspn (val, VALID_NAME); if (*val == '\0' || *end != '\0') return "Not a valid cookie name in SingleIDCookie"; ctx->cookie_name = apr_pstrdup (cmd->pool, val); return NULL; } static const char* set_attribute (cmd_parms *cmd, void *config, const char *val) { sid_context_t *ctx = config; sid_attribute_t attr, *at; const char *flag; char *end; memset (&attr, 0, sizeof (attr)); attr.count = 1; /* First word is the alias */ attr.alias = ap_getword_conf (cmd->pool, &val); if (!attr.alias || strspn (attr.alias, VALID_ALIAS) != strlen (attr.alias)) return "Invalid attribute alias"; /* Check if we already have this alias */ for (at = ctx->attributes; at; at = at->next) { if (strcasecmp (at->alias, attr.alias) == 0) return "Duplicate attribute alias"; } /* Next word is the url */ attr.url = ap_getword_conf (cmd->pool, &val); if (!attr.url || !ap_is_url (attr.url)) return "Invalid attribute URL"; /* Now come all the various flags */ for (;;) { flag = ap_getword_conf (cmd->pool, &val); if (!flag || !flag[0]) break; if (strcasecmp (flag, "required") == 0) { attr.required = 1; } else if (strcasecmp (flag, "unlimited") == 0) { attr.count = 0; } else { attr.count = strtol (flag, &end, 10); if (*end != '\0') { if (attr.count != 0) return "Invalid attribute count"; else return "Unrecognized value or flag"; } if (attr.count <= 0) return "Attribute count must a number greater than zero or 'unlimited'"; } } /* Instantiate the copy */ attr.next = ctx->attributes; at = apr_pcalloc (cmd->pool, sizeof (attr)); memcpy (at, &attr, sizeof (attr)); ctx->attributes = at; return NULL; } static const char* set_redirect (cmd_parms *cmd, void *config, int val) { sid_context_t *ctx = config; ctx->redirect_after = val; return NULL; } static const char* set_logout (cmd_parms *cmd, void *config, const char *val1, const char *val2) { sid_context_t *ctx = config; if (!val1 || val1[0] != '/') return "The SingleIdLogout must be an absolute path, and start with '/'"; ctx->logout_path = apr_pstrdup (cmd->pool, val1); if (val2) ctx->logout_dest = apr_pstrdup (cmd->pool, val2); else ctx->logout_dest = NULL; return NULL; } static const command_rec command_table[] = { AP_INIT_TAKE1 ("SingleIdProvider", set_identifier, NULL, OR_AUTHCFG, "The OpenID identifier we should perform identifier selection on when authenticating" ), AP_INIT_TAKE1 ("SingleIdRealm", set_realm, NULL, OR_AUTHCFG, "The OpenID realm (ie: trust root) of this site."), AP_INIT_TAKE1 ("SingleIdCache", set_cache_size, NULL, OR_AUTHCFG, "Enable and optionally set the size of the OpenID association cache"), AP_INIT_TAKE1 ("SingleIdCookie", set_cookie_name, NULL, OR_AUTHCFG, "Set the cookie name used once user has logged in via OpenID"), AP_INIT_RAW_ARGS ("SingleIdUserMatch", set_user_match, NULL, OR_AUTHCFG, "How to convert an OpenID identifier into a user name" ), AP_INIT_RAW_ARGS ("SingleIdAttribute", set_attribute, NULL, OR_AUTHCFG, "Specify an attribute exchange url and alias."), AP_INIT_FLAG ("SingleIdRedirect", set_redirect, NULL, OR_AUTHCFG, "Redirect after authentication for a clean bookmarkable URL."), AP_INIT_TAKE12 ("SingleIdLogout", set_logout, NULL, OR_AUTHCFG, "Path at which to logout, and an optional second URL to redirect to after local logout."), { NULL } }; /* ------------------------------------------------------------------------------------------- * COOKIE SESSIONS */ /* Secret used to sign session cookies */ static unsigned char session_secret[40]; static apr_status_t session_initialize (apr_pool_t *p, server_rec *s) { apr_status_t status; #if APR_HAS_RANDOM status = apr_generate_random_bytes (session_secret, sizeof (session_secret)); #else #error APR random number support is missing; you probably need to install the truerand library. #endif if (status != APR_SUCCESS) ap_log_error (APLOG_MARK, APLOG_CRIT, status, s, "auth-singleid: couldn't generate random secret"); return status; } 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, (const char*)session_secret, sizeof (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 (sizeof (digest))); apr_base64_encode (sig, (const char*)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 sid_session_t* session_parse_info (sid_context_t *ctx, request_rec *r, char *data) { sid_session_t *sess; const char *value; char *token, *sig, *end; char *identifier; char **here; long expiry; if (ap_unescape_url_keep2f (data, 1) != 0) { ap_log_rerror (APLOG_MARK, APLOG_WARNING, 0, r, "auth-singleid: invalidly encoded cookie: %s", data); return NULL; } value = data; sig = get_token (r->pool, &value, " "); if (!sig || !session_validate_sig (r->pool, sig, value)) { ap_log_rerror (APLOG_MARK, APLOG_WARNING, 0, r, "auth-singleid: invalid signature in cookie: %s", sig ? sig : ""); return NULL; } /* The version of the session info, only version 2 supported */ token = get_token (r->pool, &value, " "); if (!token || strcmp (token, "2") != 0) { ap_log_rerror (APLOG_MARK, APLOG_WARNING, 0, r, "auth-singleid: invalid version number in cookie: %s", token ? token : ""); return NULL; } token = get_token (r->pool, &value, " "); expiry = strtol (token ? token : "x", &end, 10); if (*end != '\0') { ap_log_rerror (APLOG_MARK, APLOG_WARNING, 0, r, "auth-singleid: invalid expiry time in cookie: %s", token ? token : ""); return NULL; } /* Don't let expired sessions be valid */ if (expiry < time (NULL)) { ap_log_rerror (APLOG_MARK, APLOG_WARNING, 0, r, "auth-singleid: cookie has expired"); return NULL; } /* The identifier */ identifier = get_token (r->pool, &value, " "); if (!identifier || !ap_is_url (identifier)) { ap_log_rerror (APLOG_MARK, APLOG_WARNING, 0, r, "auth-singleid: invalid identifier in cookie: %s", token ? token : ""); return NULL; } sess = apr_pcalloc (r->pool, sizeof (sid_session_t)); sess->expiry = expiry; sess->identifier = identifier; /* The remainder are values */ for (;;) { token = get_token (r->pool, &value, " "); if (!token) break; if (!sess->values) sess->values = apr_array_make (r->pool, 8, sizeof (char*)); here = (char**)apr_array_push (sess->values); *here = token; } return sess; } static sid_session_t* session_load_info (sid_context_t *ctx, request_rec *r) { sid_session_t *sess; const char *cookies; char *value; char *pair; cookies = apr_table_get (r->headers_in, "Cookie"); if (cookies == NULL) return NULL; while (*cookies) { pair = get_token (r->pool, &cookies, ";"); if (!pair) break; if (pair[0] == '$') continue; value = (char*)ap_stripprefix (pair, ctx->cookie_name); if (value == pair) continue; while (isspace (*value)) ++value; if (*value != '=') continue; ++value; while (isspace (*value)) ++value; /* Try to parse this cookie, modifies value */ sess = session_parse_info (ctx, r, value); if (sess != NULL) return sess; } return NULL; } static void session_send_info (sid_context_t *ctx, request_rec *r, sid_session_t *sess) { char *cookie, *sig, *payload, *values, *value; /* All the values */ if (sess->values) values = apr_array_pstrcat (r->pool, sess->values, ' '); else values = ""; /* Create the cookie value and sign it */ payload = apr_psprintf (r->pool, "2 %ld \"%s\"%s%s", (long)sess->expiry, ap_escape_quotes (r->pool, sess->identifier), values && values[0] ? " " : "", values); sig = session_create_sig (r->pool, payload); /* Build up and escape the cookie value */ value = ap_escape_path_segment (r->pool, apr_psprintf (r->pool, "%s %s", sig, payload)); /* Build up the full cookie spec */ cookie = apr_psprintf (r->pool, "%s=%s; httponly%s%s%s%s%s", ctx->cookie_name, value, ctx->cookie_domain ? "; domain=" : "", ctx->cookie_domain ? ctx->cookie_domain : "", ctx->cookie_path ? "; path=" : "", ctx->cookie_path ? ctx->cookie_path : "", ctx->cookie_secure ? "; secure" : ""); apr_table_addn (r->err_headers_out, "Set-Cookie", cookie); } static void session_send_clear (sid_context_t *ctx, request_rec *r) { char *cookie; /* Build up a cookie which expires, to delete the cookie */ cookie = apr_psprintf (r->pool, "%s=0; max-age=0; expires=Thu, 01-Jan-1970 00:00:01 GMT; httponly%s%s%s%s%s", ctx->cookie_name, ctx->cookie_domain ? "; domain=" : "", ctx->cookie_domain ? ctx->cookie_domain : "", ctx->cookie_path ? "; path=" : "", ctx->cookie_path ? ctx->cookie_path : "", ctx->cookie_secure ? "; secure" : ""); apr_table_addn (r->err_headers_out, "Set-Cookie", cookie); } static sid_session_t* session_copy_info (apr_pool_t *p, sid_session_t *sess) { sid_session_t *copy = apr_pcalloc (p, sizeof (*sess)); char **data; int i; copy->expiry = sess->expiry; copy->identifier = apr_pstrdup (p, sess->identifier); /* Copy the values if necessary */ if (sess->values) { copy->values = apr_array_copy (p, sess->values); data = (char**)copy->values->elts; for (i = 0; i < copy->values->nelts; ++i) data[i] = apr_pstrdup (p, data[i]); } return copy; } /* --------------------------------------------------------------------------------------- * REQUEST ABSTRACTIONS */ struct sid_request { int result; request_rec *rec; sid_session_t *sess; apr_bucket_brigade *output; }; void sid_request_log_error (sid_request_t *req, const char *message, const char *detail) { ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, req->rec, "%s%s%s", message, detail ? ": " : "", detail ? detail : ""); } const char* sid_request_qs (sid_request_t *req) { return req->rec->args; } const char* sid_request_form (sid_request_t *req) { int bytes, eos; apr_size_t count; apr_status_t rv; apr_bucket_brigade *bb; apr_bucket_brigade *bbin; char *buf; apr_bucket *b; apr_bucket *nextb; apr_bucket *copied; const char *clen; const char *data; apr_size_t len; request_rec *r = req->rec; clen = apr_table_get (r->headers_in, "Content-Length"); if (clen != NULL) { bytes = strtol (clen, NULL, 0); if (bytes >= MAX_POST_SIZE) { ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, "Request too big (%d bytes; limit %d)", bytes, MAX_POST_SIZE); return NULL; } } else { bytes = MAX_POST_SIZE; } bb = apr_brigade_create (r->pool, r->connection->bucket_alloc); bbin = apr_brigade_create (r->pool, r->connection->bucket_alloc); count = eos = 0; do { rv = ap_get_brigade (r->input_filters, bbin, AP_MODE_READBYTES, APR_BLOCK_READ, bytes); if (rv != APR_SUCCESS) return NULL; for (b = APR_BRIGADE_FIRST (bbin); b != APR_BRIGADE_SENTINEL (bbin); b = nextb) { nextb = APR_BUCKET_NEXT (b); if (APR_BUCKET_IS_EOS (b)) eos = 1; if (!APR_BUCKET_IS_METADATA (b)) { if (b->length != (apr_size_t)(-1)) { count += b->length; if (count > MAX_POST_SIZE) apr_bucket_delete (b); } } if (count <= MAX_POST_SIZE) { if (b->length != 0) { rv = apr_bucket_read (b, &data, &len, APR_BLOCK_READ); if (rv != APR_SUCCESS) { ap_log_rerror (APLOG_MARK, APLOG_ERR, rv, r, "Error reading post data in apr_bucket_read."); return NULL; } if (len > 0) { copied = apr_bucket_transient_create (apr_pmemdup (r->pool, data, len), len, r->connection->bucket_alloc); apr_bucket_delete (b); APR_BRIGADE_INSERT_TAIL (bb, copied); } } } } apr_brigade_cleanup (bbin); } while (!eos); if (count > MAX_POST_SIZE) { ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, "Request too big (%d bytes; limit %d)", bytes, MAX_POST_SIZE); return NULL; } buf = apr_palloc (r->pool, count + 1); rv = apr_brigade_flatten (bb, buf, &count); if (rv != APR_SUCCESS) { ap_log_rerror (APLOG_MARK, APLOG_ERR, rv, r, "Error flattenning reading form data"); return NULL; } buf[count] = '\0'; return buf; } const char* sid_request_method (sid_request_t *req) { return req->rec->method; } const char* sid_request_url (sid_request_t *req, int with_path) { /* function to determine if a connection is using https */ static APR_OPTIONAL_FN_TYPE(ssl_is_https) *using_https = NULL; static int using_https_inited = 0; const char *scheme; const char *host; const char *uri; apr_port_t port; int is_ssl; if (!using_https_inited) { using_https = APR_RETRIEVE_OPTIONAL_FN (ssl_is_https); using_https_inited = 1; } is_ssl = using_https && using_https (req->rec->connection); host = req->rec->hostname ? req->rec->hostname : ap_get_server_name (req->rec); scheme = is_ssl ? "https" : "http"; port = ap_get_server_port (req->rec); uri = with_path && req->rec->uri ? ap_escape_uri (req->rec->pool, req->rec->uri) : ""; /* Default ports? */ if ((port == 80 && !is_ssl) || (port == 443 && is_ssl)) return apr_psprintf (req->rec->pool, "%s://%s%s", scheme, host, uri); else return apr_psprintf (req->rec->pool, "%s://%s:%d%s", scheme, host, port, uri); } void sid_request_respond_headers (sid_request_t *req, int code, const char *reason, const char *header, ...) { const char *value; va_list va; if (reason) req->rec->status_line = apr_pstrdup (req->rec->pool, reason); va_start(va, header); while (header) { value = va_arg (va, const char*); apr_table_set (req->rec->err_headers_out, header, value); header = va_arg (va, const char*); } va_end(va); req->result = code; } void sid_request_respond_html (sid_request_t *req, int code, const char *reason, const char *data, ...) { apr_bucket_brigade *bb; apr_bucket *b; conn_rec *con; va_list va; if (reason) req->rec->status_line = apr_pstrdup (req->rec->pool, reason); ap_set_content_type (req->rec, "text/html"); con = req->rec->connection; bb = apr_brigade_create(req->rec->pool, con->bucket_alloc); va_start(va, data); while (data) { b = apr_bucket_pool_create (data, strlen (data), req->rec->pool, con->bucket_alloc); APR_BRIGADE_INSERT_TAIL (bb, b); data = va_arg (va, const char*); } va_end(va); b = apr_bucket_eos_create (con->bucket_alloc); APR_BRIGADE_INSERT_TAIL (bb, b); req->result = code; req->output = bb; } void sid_request_authenticated (sid_request_t *req, const char *identifier) { sid_session_t *sess; sess = apr_pcalloc (req->rec->pool, sizeof (sid_session_t)); sess->identifier = apr_pstrdup (req->rec->pool, identifier); sess->expiry = time (NULL) + 86400; req->sess = sess; } void sid_request_attribute_values (sid_request_t *req, sid_attribute_t *attr, const char **values, size_t n_values) { sid_session_t *sess; const char **val; char **here; int i = 0; assert (req->sess); /* Put all the values into the session array */ sess = req->sess; if (!sess->values) sess->values = apr_array_make (req->rec->pool, 8, sizeof (char*)); /* Setup all the values */ for (i = 0, val = values; *val; ++val, ++i) { here = apr_array_push (sess->values); *here = apr_psprintf (req->rec->pool, "%s:\"%s\"", attr->alias, ap_escape_quotes (req->rec->pool, *val)); } } /* --------------------------------------------------------------------------------------- * MAIN HOOKS */ static int hook_initialize (apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { int rc; rc = shared_initialize (p); if (rc != OK) return rc; return session_initialize (p, s); } static void hook_child (apr_pool_t *p, server_rec *s) { shared_child (p, s); } static void set_authenticated (sid_context_t *ctx, request_rec *r, sid_session_t *sess) { ap_regmatch_t matches[AP_MAX_REG_MATCH]; char *user = NULL; /* Try and calculate a user name */ switch (ctx->user_match) { case SUFFIX: if (ctx->identifier) { user = (char*)ap_stripprefix (sess->identifier, ctx->identifier); if (user != sess->identifier) { /* Some delimiters that we strip from between value and identifier */ while (strchr ("?/#", *user)) ++user; } } break; case REGEX: assert (ctx->converter); if (ap_regexec (ctx->converter, sess->identifier, AP_MAX_REG_MATCH, matches, 0) == 0) user = ap_pregsub (r->pool, "$1", sess->identifier, AP_MAX_REG_MATCH, matches); break; } if (!user) user = apr_pstrdup (r->pool, sess->identifier); r->user = user; r->ap_auth_type = SID_AUTHTYPE; } static void set_env_variables (sid_context_t *ctx, request_rec *r, sid_session_t *sess) { char *name, *value, *prev; const char *line; char **data; int i, count; apr_table_setn (r->subprocess_env, "OPENID_IDENTIFIER", sess->identifier); if (sess->values) { data = (char**)sess->values->elts; prev = name = NULL; count = 0; for (i = 0; i < sess->values->nelts; ++i) { line = data[i]; /* The two parts of the attribute value */ name = get_token (r->pool, &line, ":"); value = get_token (r->pool, &line, ":"); if (!name || !value) continue; strupcase (name); /* Set the last count if necessary */ if (prev && strcmp (name, prev) != 0) { apr_table_setn (r->subprocess_env, apr_psprintf (r->pool, "AX_COUNT_%s", prev), apr_psprintf (r->pool, "%d", count)); count = 0; } /* And dig out the value */ apr_table_setn (r->subprocess_env, apr_psprintf (r->pool, "AX_VALUE_%s_%d", name, count), value); prev = name; ++count; } /* Set the last count if necessary */ if (name) { apr_table_setn (r->subprocess_env, apr_psprintf (r->pool, "AX_COUNT_%s", prev), apr_psprintf (r->pool, "%d", count)); } } } static int hook_authenticate (request_rec* r) { sid_session_t *sess; sid_context_t *ctx; sid_request_t req; const char* authtype; request_rec* mainreq; int authenticated = 0; int res; /* Make sure it's for us */ if (!(authtype = ap_auth_type (r)) || strcasecmp (SID_AUTHTYPE, authtype) != 0) return DECLINED; ctx = (sid_context_t*)ap_get_module_config (r->per_dir_config, &auth_singleid_module); if (ctx->identifier == NULL) return DECLINED; req.result = OK; req.rec = r; req.sess = NULL; req.output = NULL; /* We never authenticate these special paths, that clients request without user */ if (compare_paths ("/favicon.ico", r->uri) == 0 || compare_paths ("/robots.txt", r->uri) == 0) return OK; /* The logout path is not authenticated.*/ if (compare_paths (ctx->logout_path, r->uri) == 0) { session_send_clear (ctx, r); return OK; } 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); } /* Load the session info from the request and see if we've authenticated */ if (sess == NULL) sess = session_load_info (ctx, r); /* Allocate a new empty session info */ if (sess == NULL) { /* Do the OpenID magic */ sid_consumer_authenticate (&req, ctx->store, ctx->realm_uri, ctx->identifier, ctx->attributes); authenticated = 1; sess = req.sess; } /* Setup the request response */ if (sess != NULL && req.result == OK) { set_authenticated (ctx, r, sess); set_env_variables (ctx, r, sess); ap_set_module_config (r->request_config, &auth_singleid_module, sess); /* If we actually authenticated the user, then set the cookie */ if (authenticated) { session_send_info (ctx, r, sess); if (ctx->redirect_after) sid_consumer_redirect_after (&req); } } /* If any output, send it */ if (req.output) { r->status = req.result; res = ap_pass_brigade (r->output_filters, req.output); if (res != APR_SUCCESS) return res; req.result = DONE; } return req.result; } static int hook_handler (request_rec *r) { sid_request_t req; sid_context_t *ctx; const char* authtype; /* Make sure it's for us */ if (!(authtype = ap_auth_type (r)) || strcasecmp (SID_AUTHTYPE, authtype) != 0) return DECLINED; ctx = (sid_context_t*)ap_get_module_config (r->per_dir_config, &auth_singleid_module); if (ctx->identifier == NULL) return DECLINED; /* Handle the logout redirect properly.*/ if (compare_paths (ctx->logout_path, r->uri) == 0) { if (ctx->logout_dest) { req.result = OK; req.rec = r; req.sess = NULL; req.output = NULL; sid_request_respond_headers (&req, 302, "Found", "Location", ctx->logout_dest, "Cache-Control", "no-cache", NULL); return req.result; } } return DECLINED; } #if AP_MODULE_MAGIC_AT_LEAST(20080403,1) static authz_status hook_user (request_rec *r, const char *require_args, const void *parsed_require_args) { const char *w; if (!r->user) return AUTHZ_DENIED_NO_USER; while ((w = ap_getword_conf (r->pool, &require_args)) && w[0]) { if (strcmp (w, r->user) == 0) return AUTHZ_GRANTED; } ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, "access to %s failed, reason: user '%s' does not meet access requriements", r->uri, r->user); return AUTHZ_DENIED; } static authz_status hook_validuser (request_rec *r, const char *require_line, const void *parsed_require_line) { return r->user ? AUTHZ_GRANTED : AUTHZ_DENIED_NO_USER; } static void register_hooks(apr_pool_t *p) { static const authz_provider provider_user = { hook_user, NULL, }; static const authz_provider provider_validuser = { hook_validuser, NULL, }; ap_hook_post_config (hook_initialize, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler (hook_handler, NULL, NULL, APR_HOOK_FIRST); ap_hook_child_init (hook_child, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_check_authn (hook_authenticate, NULL, NULL, APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_CONF); ap_register_auth_provider (p, AUTHZ_PROVIDER_GROUP, "valid-user", AUTHZ_PROVIDER_VERSION, &provider_validuser, AP_AUTH_INTERNAL_PER_CONF); ap_register_auth_provider (p, AUTHZ_PROVIDER_GROUP, "user", AUTHZ_PROVIDER_VERSION, &provider_user, AP_AUTH_INTERNAL_PER_CONF); } #else /* Old style Requires stuff */ static int hook_access(request_rec *r) { sid_context_t* ctx; const char* authtype; char *user = r->user; int m = r->method_number; int method_restricted = 0; register int x; const char *t, *w; const apr_array_header_t *reqs_arr; require_line *reqs; /* Make sure it's for us */ if (!(authtype = ap_auth_type (r)) || strcasecmp (SID_AUTHTYPE, authtype) != 0) return DECLINED; ctx = (sid_context_t*)ap_get_module_config (r->per_dir_config, &auth_singleid_module); reqs_arr = ap_requires (r); if (!reqs_arr) return OK; 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; t = reqs[x].requirement; w = ap_getword_white (r->pool, &t); if (!strcmp (w, "valid-user")) { return OK; } else if (!strcmp (w, "user")) { while (t[0]) { w = ap_getword_conf (r->pool, &t); if (!strcmp (user, w)) { return OK; } } } 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; } static void register_hooks(apr_pool_t *p) { ap_hook_post_config (hook_initialize, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler (hook_handler, NULL, NULL, APR_HOOK_FIRST); ap_hook_child_init (hook_child, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_check_user_id (hook_authenticate, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_auth_checker (hook_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 */ };