From 9625707a85049607fc96338379e86e19b4a4fe94 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Mon, 12 Sep 2005 22:28:11 +0000 Subject: Added new Apache 2x module to httpauth --- apache2x/mod_httpauth.c | 799 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 799 insertions(+) create mode 100644 apache2x/mod_httpauth.c (limited to 'apache2x/mod_httpauth.c') diff --git a/apache2x/mod_httpauth.c b/apache2x/mod_httpauth.c new file mode 100644 index 0000000..c6d6e56 --- /dev/null +++ b/apache2x/mod_httpauth.c @@ -0,0 +1,799 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * 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 + * Nate Nielsen + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sock_any.h" +#include "stringx.h" + +#define DEFAULT_PORT 8020 + +module AP_MODULE_DECLARE_DATA httpauth_module; + +typedef struct httpauth_context +{ + const char* socketname; + int socket; + int types; + const char* handler; + const char* domain; + apr_pool_t* child_pool; +} +httpauth_context_t; + +/* TODO: Support proxy authentication properly */ + +#define AUTH_PREFIX_BASIC "Basic" +#define AUTH_PREFIX_DIGEST "Digest" + +#define AUTH_TYPE_BASIC 1 << 1 +#define AUTH_TYPE_DIGEST 1 << 2 +#define AUTH_TYPE_ANY 0x0000FFFF + +#define HTTPAUTH_AUTHTYPE "HTTPAUTH" + +/* ------------------------------------------------------------------------------- + * Configuration code + */ + +static void* httpauth_dir_config(apr_pool_t* p, char* dir) +{ + httpauth_context_t* ctx; + + ctx = (httpauth_context_t*)apr_pcalloc(p, sizeof(*ctx)); + memset(ctx, 0, sizeof(*ctx)); + + ctx->socket = -1; + ctx->types = 0xFFFFFFFF; + ctx->child_pool = p; + return ctx; +} + +static const char* set_socket(cmd_parms* cmd, void* config, const char* val) +{ + struct sockaddr_any sany; + + if(sock_any_pton(val, &sany, DEFAULT_PORT) == -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, "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; +} + +static const command_rec httpauth_cmds[] = +{ + AP_INIT_TAKE1( "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, ...)" ), + AP_INIT_RAW_ARGS( "HttpAuthDigestDomain", set_domain, NULL, OR_AUTHCFG, + "The domain for which digest authentication is relevant" ), + { NULL } +}; + +/* ------------------------------------------------------------------------------- + * Socket handling code + */ + +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 = 128; + 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) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: unexpected end of data from daemon"); + 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 c; + 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); + return -1; + } + + if(code) + *code = c; + + if(c >= 400) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: received error from httpauthd: %d %s", c, line); + return -1; + } + + /* 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); + return -1; + } + + if(ccode) + *ccode = c; + } + + if(details) + *details = trim_space(line); + + return 0; +} + +int read_copy_headers(httpauth_context_t* ctx, int ccode, request_rec* r) +{ + 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"; + } + + 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; +} + + +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; + } +} + +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; +} + +int connect_socket(httpauth_context_t* ctx, request_rec* r) +{ + struct sockaddr_any sany; + apr_status_t st; + int ret = -1; + + disconnect_socket(ctx, r->server); + + if(sock_any_pton(ctx->socketname, &sany, DEFAULT_PORT) == -1) + { + ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, + "httpauth: Invalid socket name or ip."); + goto finally; + } + + 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: %s", ctx->socketname); + goto finally; + } + + apr_pool_cleanup_register(ctx->child_pool, (void*)(long)ctx->socket, + cleanup_socket, cleanup_socket); + + if(connect(ctx->socket, &SANY_ADDR(sany), SANY_LEN(sany)) != 0) + { + ap_log_rerror(APLOG_MARK, APLOG_CRIT, APR_FROM_OS_ERROR(errno), r, + "httpauth: Can't connect to httpauthd"); + goto finally; + } + + ret = 0; + +finally: + if(ret == -1) + disconnect_socket(ctx, r->server); + + errno = 0; + return ret; +} + +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) == -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) == -1) + goto finally; + + if(code != 202) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: protocol error (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) == -1) + goto finally; + + if(code != 202) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: protocol error (Expected 202, got %d)", code); + goto finally; + } + } + + /* We're cool! */ + ret = 0; + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "httpauth: connected to daemon"); + +finally: + if(ret == -1) + disconnect_socket(ctx, r->server); + + return ret; +} + +int write_request(httpauth_context_t* ctx, request_rec* r) +{ + int i, c = 0; + const char* t; + const apr_array_header_t* hdrs_arr; + const apr_table_entry_t* elts; + + /* Send the request header to httpauthd */ + t = apr_pstrcat(r->pool, "AUTH XXX ", 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; + + /* 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 httpauth_authenticate(request_rec* r) +{ + httpauth_context_t* ctx; + const char* authtype; + int code = 0; + int ccode = 0; + char* details = NULL; + request_rec* mainreq; + int retried = 0; + + /* Make sure it's for us */ + if(!(authtype = ap_auth_type(r)) || strcasecmp(HTTPAUTH_AUTHTYPE, authtype) != 0) + return DECLINED; + + ctx = (httpauth_context_t*)ap_get_module_config(r->per_dir_config, + &httpauth_module); + + if(!ctx->socketname || !ctx->handler) + return DECLINED; + + mainreq = r; + + while(mainreq->main != NULL) + mainreq = mainreq->main; + + while(mainreq->prev != NULL) + mainreq = mainreq->prev; + + /* Check if we've already authenticated this request */ + if(ap_get_module_config(mainreq->request_config, &httpauth_module)) + return OK; + +/* For jumping to when a connection has been closed */ +retry: + + if(ctx->socket == -1) + { + if(connect_httpauth(ctx, r) == -1) + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Make sure we're starting on a clean slate */ + read_junk(ctx, r); + + /* Send off a request */ + if(write_request(ctx, r) == -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) + { + retried = 1; + goto retry; + } + + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Read a response line */ + if(read_response(ctx, r, &code, &ccode, &details) == -1) + return HTTP_INTERNAL_SERVER_ERROR; + + if(code != 200) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: protocol error: unexpected code: %d", code); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Copy over other headers */ + if(read_copy_headers(ctx, ccode, r) == -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 */ + ap_set_module_config(r->request_config, &httpauth_module, details); + return OK; + } + + return ccode; +} + +static int httpauth_access(request_rec *r) +{ + /* TODO: We need to support require directives */ + return OK; +} + +static void register_hooks(apr_pool_t *p) +{ + static const char* cfg_post[] = { "http_core.c", NULL }; + + ap_hook_check_user_id(httpauth_authenticate, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_auth_checker(httpauth_access, NULL, NULL, APR_HOOK_MIDDLE); +} + +module AP_MODULE_DECLARE_DATA httpauth_module = +{ + STANDARD20_MODULE_STUFF, + httpauth_dir_config, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + httpauth_cmds, /* command table */ + register_hooks /* register hooks */ +}; + +/* + * Apache modules seem to want to be all in one + * object file, at least when statically compiled. + * so we include this here + */ +#include "../common/sock_any.c" +#include "../common/stringx.c" -- cgit v1.2.3