diff options
author | Stef Walter <stef@memberwebs.com> | 2004-04-28 20:59:48 +0000 |
---|---|---|
committer | Stef Walter <stef@memberwebs.com> | 2004-04-28 20:59:48 +0000 |
commit | dbbf162dc9be0aef47f2d1f1fcddb7ae4e074d47 (patch) | |
tree | c06b131bc9f249557d9891d07977c4f0f3d0f15b | |
parent | 8368de7830f336533f9fe6369641070239bf739c (diff) |
Tons of fixes, debugging, changes, added apache module
-rw-r--r-- | apache1x/Makefile | 40 | ||||
-rw-r--r-- | apache1x/mod_httpauth.c | 722 | ||||
-rw-r--r-- | common/buffer.c | 6 | ||||
-rw-r--r-- | common/sock_any.c | 152 | ||||
-rw-r--r-- | common/sock_any.h | 32 | ||||
-rw-r--r-- | daemon/Makefile.am | 3 | ||||
-rw-r--r-- | daemon/defaults.h | 1 | ||||
-rw-r--r-- | daemon/httpauthd.c | 353 | ||||
-rw-r--r-- | daemon/httpauthd.h | 35 | ||||
-rw-r--r-- | daemon/ldap.c | 219 | ||||
-rw-r--r-- | daemon/misc.c | 72 | ||||
-rw-r--r-- | daemon/ntlm.c | 138 | ||||
-rw-r--r-- | daemon/simple.c | 143 | ||||
-rw-r--r-- | doc/httpauth.conf.5 | 82 | ||||
-rw-r--r-- | doc/httpauth.conf.sample | 0 | ||||
-rw-r--r-- | doc/protocol.txt | 95 | ||||
-rw-r--r-- | sample/httpauthd.conf | 1 |
17 files changed, 1744 insertions, 350 deletions
diff --git a/apache1x/Makefile b/apache1x/Makefile new file mode 100644 index 0000000..3ddc156 --- /dev/null +++ b/apache1x/Makefile @@ -0,0 +1,40 @@ +## +## Makefile -- Build procedure for sample httpauth Apache module +## Autogenerated via ``apxs -n httpauth -g''. +## + +# the used tools +APXS=apxs +APACHECTL=apachectl + +# additional user defines, includes and libraries +#DEF=-Dmy_define=my_value +INC=-I../ -I../common/ + +# the default target +all: mod_httpauth.so + +# compile the DSO file +mod_httpauth.so: mod_httpauth.c ../common/sock_any.c + $(APXS) -c -Wc,-g -Wc,-O0 $(DEF) $(INC) $(LIB) mod_httpauth.c ../common/sock_any.c + +# install the DSO file into the Apache installation +# and activate it in the Apache configuration +install: all + $(APXS) -i -a -n 'httpauth' mod_httpauth.so + +# cleanup +clean: + -rm -f mod_httpauth.o mod_httpauth.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 + diff --git a/apache1x/mod_httpauth.c b/apache1x/mod_httpauth.c new file mode 100644 index 0000000..b112120 --- /dev/null +++ b/apache1x/mod_httpauth.c @@ -0,0 +1,722 @@ + +#include <httpd.h> +#include <http_core.h> +#include <http_config.h> +#include <http_log.h> +#include <http_protocol.h> +#include <ap_config.h> +#include <ap_alloc.h> + +#include "sock_any.h" + +#define DEFAULT_PORT 8020 + +module MODULE_VAR_EXPORT httpauth_module; + +typedef struct httpauth_context +{ + const char* socketname; + int socket; + const char* method; + int types; + char* authtypes; + pool* child_pool; +} +httpauth_context_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 + +#define HTTPAUTH_AUTHTYPE "HTTPAUTH" + +/* ------------------------------------------------------------------------------- + * Configuration code + */ + +static void* httpauth_dir_config(pool* p, char* dir) +{ + httpauth_context_t* ctx; + + ctx = (httpauth_context_t*)ap_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_method(cmd_parms* cmd, void* config, const char* val) +{ + httpauth_context_t* conf = (httpauth_context_t*)config; + conf->method = ap_pstrdup(cmd->pool, 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 command_rec httpauth_cmds[] = +{ + { "HttpAuthSocket", set_socket, NULL, OR_AUTHCFG, TAKE1, + "The socket that httpauthd is listening on" }, + { "HttpAuthMethod", set_method, NULL, OR_AUTHCFG, TAKE1, + "The method that httpauthd should use to authenticate" }, + { "HttpAuthTypes", set_types, NULL, OR_AUTHCFG, ITERATE, + "The types of authentiction allowed (Basic, Digest, NTLM ...)" }, + { NULL, NULL, NULL, 0, 0, NULL } +}; + +/* ------------------------------------------------------------------------------- + * Socket handling code + */ + +const char* trim_start(const char* data) +{ + while(*data && ap_isspace(*data)) + ++data; + return data; +} + +char* trim_end(char* data) +{ + char* t = data + strlen(data); + + while(t > data && ap_isspace(*(t - 1))) + { + t--; + *t = 0; + } + + return data; +} + +char* trim_space(char* data) +{ + data = (char*)trim_start(data); + return trim_end(data); +} + +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(t); + + if(!said && *t) + { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, r, + "httpauth: received junk data from daemon"); + said = 1; + } + } + + fcntl(ctx->socket, F_SETFL, fcntl(ctx->socket, F_GETFL, 0) & ~O_NONBLOCK); + errno = 0; +} + +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*)ap_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, 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, r, + "httpauth: couldn't read data from daemon"); + return -1; + } + } + + *t = 0; + errno = 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, 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, r, + "httpauth: protocol error: invalid code: %s", t); + return -1; + } + + if(code) + *code = c; + + /* 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, r, + "httpauth: protocol error: invalid code: %s", t); + return -1; + } + + if(ccode) + *ccode = c; + } + + *details = trim_space(line); + return 0; +} + +int read_copy_headers(httpauth_context_t* ctx, int ccode, request_rec* r) +{ + char* line; + const char* name; + table* 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(ap_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, r, + "httpauth: protocol error: server sent us an split header, which we don't support."); + } + else + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 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, r, + "httpauth: protocol header: invalid headers"); + return -1; + } + + line = trim_space(line); + + /* Fix up when we're a proxy */ + if(r->proxyreq == STD_PROXY) + { + if(strcasecmp(name, "WWW-Authenticate") == 0) + name = "Proxy-Authenticate"; + else if(strcasecmp(name, "Authentication-Info") == 0) + name = "Proxy-Authentication-Info"; + } + + c++; + ap_table_addn(headers, name, line); + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, r, + "httpauth: received %d headers from daemon", c); + + return 0; +} + +void disconnect_socket(httpauth_context_t* ctx, server_rec* s) +{ + if(ctx->socket != -1) + { + ap_log_error(APLOG_MARK, APLOG_DEBUG, s, + "httpauth: disconnecting from daemon"); + + /* TODO: Should we be closing this nicely somewhere? */ + ap_pclosesocket(ctx->child_pool, ctx->socket); + ctx->socket = -1; + errno = 0; + } +} + +int connect_socket(httpauth_context_t* ctx, request_rec* r) +{ + struct sockaddr_any sany; + 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, r, + "httpauth: Invalid socket name or ip."); + goto finally; + } + + ctx->socket = ap_psocket(ctx->child_pool, SANY_TYPE(sany), SOCK_STREAM, 0); + if(ctx->socket == -1) + { + ap_log_rerror(APLOG_MARK, APLOG_CRIT, r, + "httpauth: Can't create socket: %s", ctx->socketname); + goto finally; + } + + if(connect(ctx->socket, SANY_ADDR(sany), SANY_LEN(sany)) != 0) + { + ap_log_rerror(APLOG_MARK, APLOG_CRIT, 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 >= 400) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "httpauth: received error from httpauthd: %d", code); + goto finally; + } + + if(code != 100) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "httpauth: protocol error (Expected 100, got %d)", code); + goto finally; + } + + /* + * Not pretty code :) In order to keep from parsing up + * the whole available types string that we get from the + * client, and keeping an array etc... we just make sure + * that the auth type requested is in the string, and + * it's on word boundaries. + */ + + t = ap_strcasestr(details, ctx->method); + if(t) + { + /* Make sure we're at a parse mark */ + if(t == details || ap_isspace(*(t - 1))) + { + /* Make sure end is at a parse mark */ + t += strlen(ctx->method); + if(!*t || ap_isspace(*t)) + { + ap_log_rerror(APLOG_MARK, APLOG_INFO, r, + "httpauth: connected to daemon (methods: %s)", details); + + /* We're cool! */ + ret = 0; + goto finally; + } + } + } + + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "httpauth: The configured method '%s' is not provided by httpauthd: %s", + ctx->method, details); + +finally: + if(ret == -1) + disconnect_socket(ctx, r->server); + + return ret; +} + +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, 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, s, + "httpauth: Couldn't write data to daemon"); + + errno = 0; + return -1; + } + } + + errno = 0; + return 0; +} + +int write_request(httpauth_context_t* ctx, request_rec* r) +{ + int i, c = 0; + const char* t; + const array_header* hdrs_arr; + const table_entry* elts; + + /* + * TODO: We need to use a valid connection id for + * NTLM connections to work properly. + */ + + /* Send the request header to httpauthd */ + t = ap_pstrcat(r->pool, "AUTH ", ctx->method, " 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, r, + "httpauth: sent auth request to daemon: %s", t); + + /* Now send the headers to httpauthd */ + + hdrs_arr = ap_table_elts(r->headers_in); + elts = (const table_entry*)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 == STD_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 = ap_pstrcat(r->pool, "Authorization: ", elts[i].val, "\n", NULL); + + if(write_data(ctx, r->server, t) == -1) + return SERVER_ERROR; + + c++; + } + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 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; + + /* 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->method) + return DECLINED; + + if(ctx->socket == -1) + { + if(connect_httpauth(ctx, r) == -1) + return 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) + return SERVER_ERROR; + + /* Read a response line */ + if(read_response(ctx, r, &code, &ccode, &details) == -1) + return SERVER_ERROR; + + if(code >= 400 && code < 600) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "httpauth: received server error from httpauthd: %d%s%s%s", + code, details ? " (" : "", details ? details : "", details ? ")" : ""); + return SERVER_ERROR; + } + + if(code != 200) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "httpauth: protocol error: unexpected code: %d", code); + return SERVER_ERROR; + } + + /* Copy over other headers */ + if(read_copy_headers(ctx, ccode, r) == -1) + return SERVER_ERROR; + + if(ccode == 200) + { + ap_log_rerror(APLOG_MARK, APLOG_INFO, r, + "httpauth: successful authentication for user: %s", details); + + (char*)(r->connection->user) = ap_pstrdup(r->connection->pool, details); + r->connection->ap_auth_type = HTTPAUTH_AUTHTYPE; + return OK; + } + + return ccode; +} + +static int httpauth_access(request_rec *r) +{ + /* TODO: We need to support require directives */ + return OK; +} + +/* Dispatch list for API hooks */ +module MODULE_VAR_EXPORT httpauth_module = +{ + STANDARD_MODULE_STUFF, + NULL, /* module initializer */ + httpauth_dir_config, /* create per-dir config structures */ + NULL, /* merge per-dir config structures */ + NULL, /* create per-server config structures */ + NULL, /* merge per-server config structures */ + httpauth_cmds, /* table of config file commands */ + NULL, /* [#8] MIME-typed-dispatched handlers */ + NULL, /* [#1] URI to filename translation */ + httpauth_authenticate, /* [#4] validate user id from request */ + httpauth_access, /* [#5] check if the user is ok _here_ */ + NULL, /* [#3] check access by host address */ + NULL, /* [#6] determine MIME type */ + NULL, /* [#7] pre-run fixups */ + NULL, /* [#9] log a transaction */ + NULL, /* [#2] header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* [#0] post read-request */ +#ifdef EAPI + ,NULL, /* EAPI: add_module */ + NULL, /* EAPI: remove_module */ + NULL, /* EAPI: rewrite_command */ + NULL /* EAPI: new_connection */ +#endif +}; + +/* + * 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" diff --git a/common/buffer.c b/common/buffer.c index 57d20e2..72f0b6c 100644 --- a/common/buffer.c +++ b/common/buffer.c @@ -214,13 +214,15 @@ int ha_bufreadline(int fd, ha_buffer_t* buf) return 0; /* Transient errors */ - else if(l == -1 && (errno == EINTR || errno == EAGAIN)) + else if(l == -1 && errno == EAGAIN) continue; /* Fatal errors */ else if(l == -1) { - ha_message(LOG_ERR, "couldn't read data"); + if(errno != EINTR) + ha_message(LOG_ERR, "couldn't read data"); + return 0; } } diff --git a/common/sock_any.c b/common/sock_any.c new file mode 100644 index 0000000..32db795 --- /dev/null +++ b/common/sock_any.c @@ -0,0 +1,152 @@ + +#include <stdlib.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> + +#include "sock_any.h" + +int sock_any_pton(const char* addr, struct sockaddr_any* any, int defport) +{ + size_t l; + const char* t; + + memset(any, 0, sizeof(*any)); + + /* Look and see if we can parse an ipv4 address */ + do + { + #define IPV4_CHARS "0123456789." + #define IPV4_MIN 3 + #define IPV4_MAX 18 + + int port = 0; + char buf[IPV4_MAX + 1]; + + t = NULL; + + l = strlen(addr); + if(l < IPV4_MIN || l > IPV4_MAX) + break; + + strcpy(buf, addr); + + /* Find the last set that contains just numbers */ + l = strspn(buf, IPV4_CHARS); + if(l < IPV4_MIN) + break; + + /* Either end of string or port */ + if(buf[l] != 0 && buf[l] != ':') + break; + + /* Get the port out */ + if(buf[l] != 0) + { + t = buf + l + 1; + buf[l] = 0; + } + + if(t) + { + char* t2; + port = strtol(t, &t2, 10); + if(*t2 || port <= 0 || port >= 65536) + break; + } + + any->s.in.sin_family = AF_INET; + any->s.in.sin_port = htons((unsigned short)(port <= 0 ? defport : port)); + + if(inet_pton(AF_INET, buf, &(any->s.in.sin_addr)) <= 0) + break; + + any->namelen = sizeof(any->s.in); + return AF_INET; + } + while(0); + +#ifdef HAVE_INET6 + do + { + #define IPV6_CHARS "0123456789:" + #define IPV6_MIN 3 + #define IPV6_MAX 48 + + int port = -1; + char buf[IPV6_MAX + 1]; + + t = NULL; + + l = strlen(addr); + if(l < IPV6_MIN || l > IPV6_MAX) + break; + + /* If it starts with a '[' then we can get port */ + if(buf[0] == '[') + { + port = 0; + addr++; + } + + strcpy(buf, addr); + + /* Find the last set that contains just numbers */ + l = strspn(buf, IPV6_CHARS); + if(l < IPV6_MIN) + break; + + /* Either end of string or port */ + if(buf[l] != 0) + { + /* If had bracket, then needs to end with a bracket */ + if(port != 0 || buf[l] != ']') + break; + + /* Get the port out */ + t = buf + l + 1; + + if(*t = ':') + t++; + } + + if(t) + { + port = strtol(t, &t, 10); + if(*t || port <= 0 || port >= 65536) + break; + } + + any->s.in6.sin6_family = AF_INET6; + any->s.in6.sin6_port = htons((unsigned short)port <= 0 : defport : port); + + if(inet_pton(AF_INET6, buf, &(any->s.in6.sin6_addr)) >= 0) + break; + + any->namelen = sizeof(any->s.in6); + return AF_INET6; + } + while(0); +#endif + + do + { + if(strchr(addr, ':')) + break; + + l = strlen(addr); + if(l >= sizeof(any->s.un.sun_path)) + break; + + any->s.un.sun_family = AF_UNIX; + strcpy(any->s.un.sun_path, addr); + + any->namelen = sizeof(any->s.un) - (sizeof(any->s.un.sun_path) - l); + return AF_UNIX; + } + while(0); + + return -1; +} + diff --git a/common/sock_any.h b/common/sock_any.h new file mode 100644 index 0000000..f281020 --- /dev/null +++ b/common/sock_any.h @@ -0,0 +1,32 @@ + +#ifndef __SOCK_ANY_H__ +#define __SOCK_ANY_H__ + +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> + +struct sockaddr_any +{ + size_t namelen; + union _sockaddr_any + { + /* The header */ + struct sockaddr a; + + /* The different types */ + struct sockaddr_un un; + struct sockaddr_in in; +#ifdef HAVE_INET6 + struct sockaddr_in6 in6; +#endif + } s; +}; + +#define SANY_ADDR(any) (&((any).s.a)) +#define SANY_LEN(any) ((any).namelen) +#define SANY_TYPE(any) ((any).s.a.sa_family) + +int sock_any_pton(const char* addr, struct sockaddr_any* any, int defport); + +#endif /* __SOCK_ANY_H__ */ diff --git a/daemon/Makefile.am b/daemon/Makefile.am index c93e89f..39a0532 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -4,10 +4,11 @@ bin_PROGRAMS = httpauthd httpauthd_SOURCES = httpauthd.c httpauthd.h usuals.h compat.h compat.c \ buffer.c misc.c basic.h basic.c ntlm.c hash.c hash.h ntlmssp.h ntlmssp.c \ md5.c md5.h sha1.c sha1.h digest.h digest.c ldap.c defaults.h simple.c \ + ../common/sock_any.c ../common/sock_any.h \ smblib/smblib.c smblib/smblib-util.c smblib/file.c smblib/smb-errors.c smblib/exper.c smblib/smblib-api.c \ rfcnb/rfcnb-io.c rfcnb/rfcnb-util.c rfcnb/session.c -httpauthd_CFLAGS = -DLinux +httpauthd_CFLAGS = -DLinux -I../common/ -I../ # man_MANS = rtfm.1 EXTRA_DIST = protocol.txt smblib rfcnb diff --git a/daemon/defaults.h b/daemon/defaults.h index a72e51a..828bec1 100644 --- a/daemon/defaults.h +++ b/daemon/defaults.h @@ -6,5 +6,6 @@ #define DEFAULT_PENDING_TIMEOUT 60 #define DEFAULT_TIMEOUT 900 #define DEFAULT_CACHEMAX 1024 +#define DEFAULT_PORT 8020 #endif /* __DEFAULTS_H__ */ diff --git a/daemon/httpauthd.c b/daemon/httpauthd.c index 2dba6c2..8c0f000 100644 --- a/daemon/httpauthd.c +++ b/daemon/httpauthd.c @@ -10,12 +10,12 @@ #include <pthread.h> #include <fcntl.h> #include <err.h> -#include <sys/un.h> -#include <sys/socket.h> +#include <signal.h> #include "usuals.h" #include "httpauthd.h" #include "defaults.h" +#include "sock_any.h" /* * This shouldn't be used by handlers, @@ -100,7 +100,8 @@ httpauth_thread_t; */ int g_daemonized = 0; /* Currently running as a daemon */ -int g_debugging = 0; /* In debug mode */ +int g_console = 0; /* debug mode read write from console */ +int g_debuglevel = LOG_ERR; /* what gets logged to console */ const char* g_socket = DEFAULT_SOCKET; /* The socket to communicate on */ int g_maxthreads = DEFAULT_MAXTHREADS; /* The maximum number of threads */ @@ -118,13 +119,14 @@ pthread_mutexattr_t g_mutexattr; * Forward Declarations */ -int usage(); -void* httpauth_thread(void* arg); -int httpauth_processor(int ifd, int ofd); -int process_auth(ha_request_t* req, ha_response_t* resp, - ha_buffer_t* outb); -int config_parse(const char* file, ha_buffer_t* buf); - +static int usage(); +static void* httpauth_thread(void* arg); +static int httpauth_processor(int ifd, int ofd); +static int httpauth_respond(int ofd, int scode, int ccode, const char* msg); +static int process_auth(ha_request_t* req, ha_response_t* resp, + ha_buffer_t* outb); +static int config_parse(const char* file, ha_buffer_t* buf); +static void on_quit(int signal); /* ----------------------------------------------------------------------- * Main Program @@ -150,13 +152,21 @@ int main(int argc, char* argv[]) errx(1, "threading problem. can't create mutex"); /* Parse the arguments nicely */ - while((ch = getopt(argc, argv, "df:X")) != -1) + while((ch = getopt(argc, argv, "d:f:X")) != -1) { switch(ch) { /* Don't daemonize */ case 'd': - daemonize = 0; + { + char* t; + + daemonize = 0; + g_debuglevel = strtol(optarg, &t, 10); + if(*t || g_debuglevel > 4) + errx(1, "invalid debug log level"); + g_debuglevel += LOG_ERR; + } break; /* The configuration file */ @@ -166,7 +176,8 @@ int main(int argc, char* argv[]) /* Process console input instead */ case 'X': - g_debugging = 1; + g_console = 1; + daemonize = 0; break; /* Usage information */ @@ -183,6 +194,7 @@ int main(int argc, char* argv[]) if(argc != 0) return usage(); + ha_messagex(LOG_DEBUG, "starting up..."); /* From here on out we need to quit in an orderly fashion */ @@ -204,42 +216,37 @@ int main(int argc, char* argv[]) config_parse(conf, &cbuf); - if(!g_debugging) + if(!g_console) { - struct sockaddr_un sau; + struct sockaddr_any sany; /* Create the thread buffers */ threads = (httpauth_thread_t*)malloc(sizeof(httpauth_thread_t) * g_maxthreads); if(!threads) errx(1, "out of memory"); + /* Get the socket type */ + if(sock_any_pton(g_socket, &sany, DEFAULT_PORT) == -1) + errx(1, "invalid socket name or ip: %s", g_socket); + /* Create the socket */ - sock = socket(AF_UNIX, SOCK_STREAM, 0); + sock = socket(SANY_TYPE(sany), SOCK_STREAM, 0); if(sock < 0) err(1, "couldn't open socket"); - /* Unlink the socket file if it exists */ /* TODO: Is this safe? */ unlink(g_socket); - /* Setup the socket */ - strncmp(sau.sun_path, g_socket, sizeof(sau.sun_path)); - sau.sun_path[sizeof(sau.sun_path) - 1] = 0; - - /* Bind to the socket */ - if(bind(sock, (struct sockaddr*)&sau, - sizeof(sau) - (sizeof(sau.sun_path) - strlen(sau.sun_path)))) - err(1, "couldn't bind to socket: %s", sau.sun_path); - + if(bind(sock, SANY_ADDR(sany), SANY_LEN(sany)) != 0) + err(1, "couldn't bind to socket: %s", g_socket); /* Let 5 connections queue up */ if(listen(sock, 5) != 0) err(1, "couldn't listen on socket"); - } - - /* TODO: Enable signal processing here */ + ha_messagex(LOG_DEBUG, "created socket: %s", g_socket); + } /* Initialize all the handlers */ for(h = g_handlers; h; h = h->next) @@ -253,8 +260,9 @@ int main(int argc, char* argv[]) /* This is for debugging the internal processes */ - if(g_debugging) + if(g_console) { + ha_messagex(LOG_DEBUG, "processing from console"); r = httpauth_processor(0, 1); goto finally; } @@ -263,27 +271,41 @@ int main(int argc, char* argv[]) /* This is the daemon section of the code */ else { - /* TODO: Daemonize here, and disconnect from terminal */ + if(daemonize) + { + /* Fork a daemon nicely here */ + if(daemon(0, 0) == -1) + { + ha_message(LOG_ERR, "couldn't run httpauth as daemon"); + exit(1); + } + + g_daemonized = 1; + } + + /* Handle some signals */ + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); +/* signal(SIGINT, on_quit); + signal(SIGTERM, on_quit); */ /* Open the system log */ openlog("httpauthd", 0, LOG_AUTHPRIV); - g_daemonized = 1; + ha_messagex(LOG_DEBUG, "accepting connections"); /* Now loop and accept the connections */ while(!g_quit) { int fd; - /* TODO: A nice way to break out of the loop here */ - fd = accept(sock, 0, 0); if(fd == -1) { switch(errno) { - case EAGAIN: case EINTR: + case EAGAIN: break; case ECONNABORTED: @@ -300,13 +322,15 @@ int main(int argc, char* argv[]) continue; } + ha_messagex(LOG_INFO, "accepted connection: %d", fd); + /* Look for thread and also clean up others */ for(i = 0; i < g_maxthreads; i++) { /* Clean up quit threads */ if(threads[i].tid != 0) { - if(threads[i].fd == 0) + if(threads[i].fd == -1) { pthread_join(threads[i].tid, NULL); threads[i].tid = 0; @@ -327,12 +351,14 @@ int main(int argc, char* argv[]) break; } - fd = 0; + ha_messagex(LOG_DEBUG, "created thread for connection: %d", fd); + fd = -1; + break; } } /* Check to make sure we have a thread */ - if(fd != 0) + if(fd != -1) { ha_messagex(LOG_ERR, "too many connections open (max %d)", g_maxthreads); httpauth_respond(fd, HA_SERVER_ERROR, 0, "too many connections"); @@ -340,7 +366,15 @@ int main(int argc, char* argv[]) } } - /* TODO: Quit all threads here */ + ha_messagex(LOG_INFO, "waiting for threads to quit"); + + /* Quit all threads here */ + for(i = 0; i < g_maxthreads; i++) + { + /* Clean up quit threads */ + if(threads[i].tid != 0) + pthread_join(threads[i].tid, NULL); + } r = 0; } @@ -371,28 +405,129 @@ finally: return r == -1 ? 1 : 0; } -int usage() +static void on_quit(int signal) { - fprintf(stderr, "usage: httpauthd [-dX] [-f conffile]\n"); + g_quit = 1; +} + +static int usage() +{ + fprintf(stderr, "usage: httpauthd [-d level] [-X] [-f conffile]\n"); return 2; } -void* httpauth_thread(void* arg) +static void* httpauth_thread(void* arg) +{ + httpauth_thread_t* thread = (httpauth_thread_t*)arg; + int r; + + ASSERT(thread); + ASSERT(thread->fd != -1); + + /* call the processor */ + r = httpauth_processor(thread->fd, thread->fd); + + /* mark this as done */ + thread->fd = -1; + + return (void*)r; +} + +/* ----------------------------------------------------------------------- + * Logging + */ + +void log_request(ha_request_t* req, ha_buffer_t* buf, int fd) +{ + const httpauth_command_t* cmd; + const char* t; + const char* t2; + int i; + + if(g_debuglevel < LOG_DEBUG) + return; + + if(req->type == REQTYPE_IGNORE || req->type == -1) + return; + + ha_bufcpy(buf, ""); + + for(i = 0; i < HA_MAX_ARGS; i++) + { + if(req->args[i]) + { + ha_bufjoin(buf); + ha_bufmcat(buf, ha_buflen(buf) > 0 ? ", " : "", req->args[i], NULL); + } + } + + t = ha_bufdata(buf); + + t2 = NULL; + + /* Figure out which command it is */ + for(cmd = kCommands; cmd->name; cmd++) + { + if(cmd->code == req->type) + { + t2 = cmd->name; + break; + } + } + + ASSERT(t2); + + ha_messagex(LOG_DEBUG, "received request (from %d): " + "[ type: %s / args: %s ]", fd, t2, t); + + for(i = 0; i < HA_MAX_HEADERS; i++) + { + if(req->headers[i].name) + { + ASSERT(req->headers[i].data); + ha_messagex(LOG_DEBUG, "received header (from %d): [ %s: %s ]", fd, + req->headers[i].name, req->headers[i].data); + } + } +} + +void log_response(ha_response_t* resp, int fd) { - int fd, r; + int i; + + if(g_debuglevel < LOG_DEBUG) + return; + + ha_messagex(LOG_DEBUG, "sending response (to %d): " + "[ code: 200 / ccode: %d / detail: %s ]", fd, + resp->code, resp->detail ? resp->detail : ""); + + for(i = 0; i < HA_MAX_HEADERS; i++) + { + if(resp->headers[i].name) + { + ASSERT(resp->headers[i].data); + ha_messagex(LOG_DEBUG, "sending header (to %d): [ %s: %s ]", fd, + resp->headers[i].name, resp->headers[i].data); + } + } +} - ASSERT(arg); +void log_respcode(int code, const char* msg, int fd) +{ + if(g_debuglevel < LOG_DEBUG) + return; - fd = (int)arg; - return (void*)httpauth_processor(fd, fd); + ha_messagex(LOG_DEBUG, "sending response (to %d): " + "[ code: %d / detail: %s ]", fd, code, msg ? msg : ""); } /* ----------------------------------------------------------------------- * Command Parsing and Handling */ -int httpauth_read(int ifd, ha_request_t* req, - ha_buffer_t* buf) +static int httpauth_read(int ifd, ha_request_t* req, + ha_buffer_t* buf) { const httpauth_command_t* cmd; char* t; @@ -513,7 +648,7 @@ int httpauth_read(int ifd, ha_request_t* req, } else { - if(i < MAX_HEADERS) + if(i < HA_MAX_HEADERS) { t = ha_bufparseword(buf, ":"); @@ -550,7 +685,7 @@ int httpauth_read(int ifd, ha_request_t* req, return more; } -int write_data(int ofd, const char* data) +static int write_data(int ofd, const char* data) { int r; @@ -566,7 +701,7 @@ int write_data(int ofd, const char* data) else if(r == -1) { - if(errno == EAGAIN || errno == EINTR) + if(errno == EAGAIN) continue; /* The other end closed. no message */ @@ -580,7 +715,7 @@ int write_data(int ofd, const char* data) return 0; } -int httpauth_respond(int ofd, int scode, int ccode, const char* msg) +static int httpauth_respond(int ofd, int scode, int ccode, const char* msg) { char num[16]; @@ -626,12 +761,16 @@ int httpauth_respond(int ofd, int scode, int ccode, const char* msg) if(msg && write_data(ofd, msg) < 0) return HA_CRITERROR; + /* When the client code is 0, then caller should log */ + if(ccode == 0) + log_respcode(scode, msg, ofd); + return write_data(ofd, "\n"); } const char kHeaderDelimiter[] = ": "; -int httpauth_write(int ofd, ha_response_t* resp) +static int httpauth_write(int ofd, ha_response_t* resp) { int i; int wrote = 0; @@ -642,7 +781,7 @@ int httpauth_write(int ofd, ha_response_t* resp) if(httpauth_respond(ofd, HA_SERVER_ACCEPT, resp->code, resp->detail) < 0) return HA_CRITERROR; - for(i = 0; i < MAX_HEADERS; i++) + for(i = 0; i < HA_MAX_HEADERS; i++) { if(resp->headers[i].name) { @@ -659,10 +798,12 @@ int httpauth_write(int ofd, ha_response_t* resp) if(write_data(ofd, "\n") == -1) return -1; + log_response(resp, ofd); + return 0; } -int httpauth_error(int ofd, int r) +static int httpauth_error(int ofd, int r) { int scode = 0; const char* msg = NULL; @@ -691,7 +832,7 @@ int httpauth_error(int ofd, int r) return httpauth_respond(ofd, scode, 0, msg); } -int httpauth_ready(int ofd, ha_buffer_t* buf) +static int httpauth_ready(int ofd, ha_buffer_t* buf) { const char* t; httpauth_loaded_t* h; @@ -720,7 +861,7 @@ int httpauth_ready(int ofd, ha_buffer_t* buf) } } -int httpauth_processor(int ifd, int ofd) +static int httpauth_processor(int ifd, int ofd) { ha_buffer_t inb; ha_buffer_t outb; @@ -743,13 +884,16 @@ int httpauth_processor(int ifd, int ofd) } /* Now loop and handle the commands */ - while(result == -1) + while(result == -1 && !g_quit) { ha_bufreset(&outb); ha_bufreset(&inb); r = httpauth_read(ifd, &req, &inb); + if(g_quit) + continue; + if(ha_buferr(&inb)) r = HA_CRITERROR; @@ -757,9 +901,11 @@ int httpauth_processor(int ifd, int ofd) { httpauth_error(ofd, r); result = 1; - continue; + break; } + log_request(&req, &inb, ifd); + if(r == 0) result = 0; @@ -769,13 +915,19 @@ int httpauth_processor(int ifd, int ofd) r = process_auth(&req, &resp, &outb); + if(g_quit) + continue; + if(ha_buferr(&outb)) r = HA_CRITERROR; if(r < 0) { httpauth_error(ofd, r); - result = 1; + + if(r == HA_CRITERROR) + result = 1; + continue; } @@ -797,6 +949,8 @@ int httpauth_processor(int ifd, int ofd) break; default: + ha_messagex(LOG_WARNING, "received unknown command from client: %d", ifd); + if(httpauth_respond(ofd, HA_SERVER_BADREQ, 0, "Unknown command") == -1) { result = -1; @@ -819,8 +973,8 @@ finally: return result; } -int process_auth(ha_request_t* req, ha_response_t* resp, - ha_buffer_t* outb) +static int process_auth(ha_request_t* req, ha_response_t* resp, + ha_buffer_t* outb) { httpauth_loaded_t* h; @@ -832,21 +986,21 @@ int process_auth(ha_request_t* req, ha_response_t* resp, /* Check our connection argument */ if(!req->args[AUTH_ARG_CONN] || !(req->args[AUTH_ARG_CONN][0])) { - ha_messagex(LOG_ERR, "Missing connection ID in request"); + ha_messagex(LOG_ERR, "missing connection ID in request"); return HA_BADREQ; } /* Check our uri argument */ if(!req->args[AUTH_ARG_URI] || !(req->args[AUTH_ARG_URI][0])) { - ha_messagex(LOG_ERR, "Missing URI in request"); + ha_messagex(LOG_ERR, "missing URI in request"); return HA_BADREQ; } /* Check our connection arguments */ if(!req->args[AUTH_ARG_METHOD] || !(req->args[AUTH_ARG_METHOD][0])) { - ha_messagex(LOG_ERR, "Missing method in request"); + ha_messagex(LOG_ERR, "missing method in request"); return HA_BADREQ; } @@ -856,13 +1010,16 @@ int process_auth(ha_request_t* req, ha_response_t* resp, { if(strcasecmp(h->ctx.name, req->args[0]) == 0) { + ha_messagex(LOG_INFO, "processing request with method: %s (%s)", + h->ctx.name, h->ctx.handler->type); + /* Now let the handler handle it */ ASSERT(h->ctx.handler->f_process); return (h->ctx.handler->f_process)(&(h->ctx), req, resp, outb); } } - ha_messagex(LOG_ERR, "Unknown authentication type: %s", req->args[0]); + ha_messagex(LOG_ERR, "unknown authentication type: %s", req->args[0]); return HA_BADREQ; } @@ -870,8 +1027,8 @@ int process_auth(ha_request_t* req, ha_response_t* resp, * Configuration */ -ha_context_t* config_addhandler(ha_buffer_t* buf, const char* alias, - ha_handler_t* handler, const ha_context_t* defaults) +static ha_context_t* config_addhandler(ha_buffer_t* buf, const char* alias, + ha_handler_t* handler, const ha_options_t* defaults) { httpauth_loaded_t* loaded; int len; @@ -885,7 +1042,7 @@ ha_context_t* config_addhandler(ha_buffer_t* buf, const char* alias, errx(1, "out of memory"); memset(loaded, 0, len); - memcpy(&(loaded->ctx), defaults, sizeof(ha_context_t)); + memcpy(&(loaded->ctx.opts), defaults, sizeof(ha_options_t)); if(handler->context_size) { @@ -917,7 +1074,7 @@ ha_context_t* config_addhandler(ha_buffer_t* buf, const char* alias, for(;;) { if(strcasecmp(alias, l->ctx.name) == 0) - errx(1, "duplicate handler section for '%s'", alias); + errx(1, "duplicate method section for '%s'", alias); if(!(l->next)) break; @@ -928,13 +1085,13 @@ ha_context_t* config_addhandler(ha_buffer_t* buf, const char* alias, l->next = loaded; } + ha_messagex(LOG_DEBUG, "configuration: method: %s (%s)", alias, handler->type); return &(loaded->ctx); } - -int config_parse(const char* file, ha_buffer_t* buf) +static int config_parse(const char* file, ha_buffer_t* buf) { - ha_context_t defaults; + ha_options_t defaults; ha_context_t* ctx = NULL; int line = 0; int fd; @@ -957,6 +1114,8 @@ int config_parse(const char* file, ha_buffer_t* buf) defaults.types = 0xFFFFFFFF; /* All types by default */ defaults.cache_timeout = DEFAULT_TIMEOUT; /* Timeout for cache */ defaults.cache_max = DEFAULT_CACHEMAX; + defaults.realm = ""; + ha_bufreset(buf); @@ -992,7 +1151,7 @@ int config_parse(const char* file, ha_buffer_t* buf) name = ha_bufparseline(buf, 1); if(!name || name[strlen(name) - 1] != ']') - errx(1, "configuration section invalid (line %d)", line); + errx(1, "method section invalid (line %d)", line); /* remove the bracket */ @@ -1011,7 +1170,7 @@ int config_parse(const char* file, ha_buffer_t* buf) } if(handler == NULL) - errx(1, "unknown handler type '%s' (line %d)", name, line); + errx(1, "unknown method type '%s' (line %d)", name, line); /* If we had a last handler then add it to handlers */ ctx = config_addhandler(buf, name, handler, &defaults); @@ -1052,7 +1211,6 @@ int config_parse(const char* file, ha_buffer_t* buf) exit(1); recog = 1; } - } /* Otherwise we're in a handler */ @@ -1078,13 +1236,16 @@ int config_parse(const char* file, ha_buffer_t* buf) /* Options that are legal in both global and internal sections */ if(!recog) { + ha_options_t* opts = ctx ? &(ctx->opts) : &defaults; + ASSERT(opts); + if(strcmp(name, "cachetimeout") == 0) { int v; if(ha_confint(name, value, 0, 86400, &v) < 0) exit(1); /* Message already printed */ - (ctx ? ctx : &defaults)->cache_timeout = v; + opts->cache_timeout = v; recog = 1; } @@ -1094,7 +1255,7 @@ int config_parse(const char* file, ha_buffer_t* buf) if(ha_confint(name, value, 0, 0x7FFFFFFF, &v) < 0) exit(1); /* Message already printed */ - (ctx ? ctx : &defaults)->cache_max = v; + opts->cache_max = v; recog = 1; } @@ -1134,17 +1295,49 @@ int config_parse(const char* file, ha_buffer_t* buf) if(types == 0) errx(1, "no authentication types for '%s' (line %d)", name, line); - (ctx ? ctx : &defaults)->types = types; + opts->types = types; recog = 1; } + + else if(strcmp(name, "realm") == 0) + { + opts->realm = value; + recog = 1; + } + + else if(strcmp(name, "digestignoreuri") == 0) + { + int v; + if(ha_confbool(name, value, &v) < 0) + exit(1); /* Message already printed */ + + opts->digest_ignoreuri = v; + recog = 1; + } + + else if(strcmp(name, "digestdomains") == 0) + { + opts->digest_domains = value; + recog = 1; + } + +#ifdef _DEBUG + else if(strcmp(name, "digestdebugnonce") == 0) + { + opts->digest_debugnonce = value; + recog = 1; + } +#endif } if(!recog) errx(1, "unrecognized configuration setting '%s' (line %d)", name, line); + else + ha_messagex(LOG_DEBUG, "configuration: setting: [ %s: %s ]", name, value); } if(!g_handlers) - ha_messagex(LOG_INFO, "no handlers found in configuration file"); + ha_messagex(LOG_WARNING, "configuration: no methods found in configuration file"); return 0; } diff --git a/daemon/httpauthd.h b/daemon/httpauthd.h index 95187be..cd1d39e 100644 --- a/daemon/httpauthd.h +++ b/daemon/httpauthd.h @@ -177,16 +177,29 @@ ha_handler_t; -struct ha_options; +typedef struct ha_options +{ + /* Basic options */ + unsigned int types; + int cache_timeout; + int cache_max; + + /* For basic and digest auth: */ + const char* realm; + + /* For digest auth: */ + int digest_ignoreuri; + const char* digest_domains; + const char* digest_debugnonce; +} +ha_options_t; /* Context passed to the handler functions below */ typedef struct ha_context { const char* name; /* A name assigned by the configuration file */ ha_handler_t* handler; /* The original handler structure */ - unsigned int types; /* The types of authentication allowed */ - int cache_timeout; /* Timeout for cached connections */ - int cache_max; /* Maximum amount of cached connections */ + ha_options_t opts; /* The options */ void* data; /* Handler specific data */ } ha_context_t; @@ -202,7 +215,7 @@ ha_context_t; * should be no need to change it unless we're * adding or removing commands */ -#define MAX_ARGS 4 +#define HA_MAX_ARGS 4 /* * The maximum number of pertinent headers to read @@ -211,13 +224,13 @@ ha_context_t; * of valid headers in httpauthd.c */ -#define MAX_HEADERS 2 +#define HA_MAX_HEADERS 2 /* * The maximum number of handlers. If you add * handlers make sure to update this number. */ -#define MAX_HANDLERS 4 +#define HA_MAX_HANDLERS 4 /* A single header in memory */ @@ -241,8 +254,8 @@ ha_header_t; typedef struct ha_request { int type; - const char* args[MAX_ARGS]; - ha_header_t headers[MAX_HEADERS]; + const char* args[HA_MAX_ARGS]; + ha_header_t headers[HA_MAX_HEADERS]; } ha_request_t; @@ -250,7 +263,7 @@ ha_request_t; #define HA_SERVER_READY 100 #define HA_SERVER_ACCEPT 200 #define HA_SERVER_DECLINE 401 -#define HA_SERVER_BADREQ 402 +#define HA_SERVER_BADREQ 400 #define HA_SERVER_BUSY 500 /* A response to the client */ @@ -258,7 +271,7 @@ typedef struct ha_response { int code; const char* detail; - ha_header_t headers[MAX_HEADERS]; + ha_header_t headers[HA_MAX_HEADERS]; } ha_response_t; diff --git a/daemon/ldap.c b/daemon/ldap.c index 9a79d43..a5abd97 100644 --- a/daemon/ldap.c +++ b/daemon/ldap.c @@ -69,24 +69,17 @@ typedef struct ldap_context int port; /* Port to connect to LDAP server on */ int scope; /* Scope for filter */ - const char* realm; /* The realm to use in authentication */ - const char* domains; /* Domains for which digest auth is valid */ - int dobind; /* Bind to do simple authentication */ - int cache_max; /* Maximum number of connections at once */ - int cache_timeout; int ldap_max; /* Number of open connections allowed */ int ldap_timeout; /* Maximum amount of time to dedicate to an ldap query */ + ha_options_t* opts; /* Options from httpauthd.c */ + /* Context ----------------------------------------------------------- */ hash_t* cache; /* Some cached records or basic */ LDAP** pool; /* Pool of available connections */ int pool_mark; /* Amount of connections allocated */ - -#ifdef _DEBUG - const char* debug_nonce; -#endif } ldap_context_t; @@ -104,19 +97,12 @@ static const ldap_context_t ldap_defaults = NULL, /* dnmap */ 389, /* port */ LDAP_SCOPE_SUBTREE, /* scope */ - "", /* realm */ - NULL, /* domains */ 1, /* dobind */ - 1000, /* cache_max */ - 30, /* cache_timeout */ 10, /* ldap_max */ 30, /* ldap_timeout */ NULL, /* cache */ NULL, /* pool */ 0 /* pool_mark */ -#ifdef _DEBUG - , NULL /* debug_nonce */ -#endif }; @@ -134,17 +120,17 @@ static int report_ldap(const char* msg, int code) { ASSERT(code != LDAP_SUCCESS); - if(!msg) - msg = "ldap error"; - - ha_messagex(LOG_ERR, "%s: %s", msg, ldap_err2string(code)); - switch(code) { case LDAP_NO_MEMORY: + ha_messagex(LOG_CRIT, "out of memory"); return HA_CRITERROR; default: + if(!msg) + msg = "error"; + + ha_messagex(LOG_ERR, "ldap: %s: %s", msg, ldap_err2string(code)); return HA_FAILED; }; } @@ -155,7 +141,7 @@ static digest_record_t* get_cached_digest(ldap_context_t* ctx, unsigned char* no ASSERT(ctx && nonce); - if(ctx->cache_max == 0) + if(ctx->opts->cache_max == 0) return NULL; ha_lock(NULL); @@ -193,7 +179,7 @@ static int save_cached_digest(ldap_context_t* ctx, digest_record_t* rec) ASSERT(ctx && rec); - if(ctx->cache_max == 0) + if(ctx->opts->cache_max == 0) { free_hash_object(NULL, rec); return HA_FALSE; @@ -201,7 +187,7 @@ static int save_cached_digest(ldap_context_t* ctx, digest_record_t* rec) ha_lock(NULL); - while(hash_count(ctx->cache) >= ctx->cache_max) + while(hash_count(ctx->cache) >= ctx->opts->cache_max) hash_bump(ctx->cache); r = hash_set(ctx->cache, rec->nonce, rec); @@ -223,12 +209,12 @@ static int add_cached_basic(ldap_context_t* ctx, unsigned char* key) ASSERT(ctx && key); - if(ctx->cache_max == 0) + if(ctx->opts->cache_max == 0) return HA_FALSE; ha_lock(NULL); - while(hash_count(ctx->cache) >= ctx->cache_max) + while(hash_count(ctx->cache) >= ctx->opts->cache_max) hash_bump(ctx->cache); r = hash_set(ctx->cache, key, BASIC_ESTABLISHED); @@ -279,7 +265,7 @@ static const char* substitute_params(ldap_context_t* ctx, ha_buffer_t* buf, case 'r': ha_bufjoin(buf); - ha_bufcpy(buf, ctx->realm); + ha_bufcpy(buf, ctx->opts->realm); t++; break; @@ -388,6 +374,7 @@ static int parse_ldap_ha1(ha_buffer_t* buf, struct berval* bv, unsigned char* ha /* Raw binary */ if(bv->bv_len == MD5_LEN) { + ha_messagex(LOG_DEBUG, "ldap: found ha1 in raw binary format"); memcpy(ha1, bv->bv_val, MD5_LEN); return HA_OK; } @@ -400,6 +387,7 @@ static int parse_ldap_ha1(ha_buffer_t* buf, struct berval* bv, unsigned char* ha if(d && len == MD5_LEN) { + ha_messagex(LOG_DEBUG, "ldap: found ha1 in hex encoded format"); memcpy(ha1, d, MD5_LEN); return HA_OK; } @@ -413,6 +401,7 @@ static int parse_ldap_ha1(ha_buffer_t* buf, struct berval* bv, unsigned char* ha if(d && len == MD5_LEN) { + ha_messagex(LOG_DEBUG, "ldap: found ha1 in b64 encoded format"); memcpy(ha1, ha_bufdata(buf), MD5_LEN); return HA_OK; } @@ -483,6 +472,7 @@ static int validate_ldap_password(ldap_context_t* ctx, LDAP* ld, LDAPMessage* en if(strcmp(pw, p) == 0) { + ha_messagex(LOG_DEBUG, "ldap: successful validate against password"); res = HA_OK; break; } @@ -492,7 +482,7 @@ static int validate_ldap_password(ldap_context_t* ctx, LDAP* ld, LDAPMessage* en } if(res == HA_FALSE && unknown) - ha_messagex(LOG_ERR, "LDAP does not contain any compatible passwords for user: %s", user); + ha_messagex(LOG_ERR, "ldap: server does not contain any compatible passwords for user: %s", user); return res; } @@ -516,7 +506,7 @@ static int validate_ldap_ha1(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry, if(ha1s) { - digest_makeha1(key, user, ctx->realm, clearpw); + digest_makeha1(key, user, ctx->opts->realm, clearpw); for(b = ha1s ; *b; b++) { @@ -530,7 +520,7 @@ static int validate_ldap_ha1(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry, if(r == HA_FALSE) { if(first) - ha_messagex(LOG_ERR, "LDAP contains invalid HA1 digest hash for user: %s", user); + ha_messagex(LOG_ERR, "ldap: server contains invalid HA1 digest hash for user: %s", user); first = 0; continue; @@ -538,6 +528,7 @@ static int validate_ldap_ha1(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry, if(memcmp(key, k, MD5_LEN) == 0) { + ha_messagex(LOG_DEBUG, "ldap: successful validate against ha1"); res = HA_OK; break; } @@ -561,6 +552,7 @@ static LDAP* get_ldap_connection(ldap_context_t* ctx) /* An open connection in the pool */ if(ctx->pool[i]) { + ha_messagex(LOG_DEBUG, "ldap: using cached connection"); ld = ctx->pool[i]; ctx->pool[i] = NULL; return ld; @@ -569,14 +561,14 @@ static LDAP* get_ldap_connection(ldap_context_t* ctx) if(ctx->pool_mark >= ctx->ldap_max) { - ha_messagex(LOG_ERR, "too many open connections to LDAP"); + ha_messagex(LOG_ERR, "ldap: too many open connections"); return NULL; } ld = ldap_init(ctx->servers, ctx->port); if(!ld) { - ha_message(LOG_ERR, "couldn't initialize ldap connection"); + ha_message(LOG_ERR, "ldap: couldn't initialize connection"); return NULL; } @@ -586,13 +578,14 @@ static LDAP* get_ldap_connection(ldap_context_t* ctx) ctx->password ? ctx->password : ""); if(r != LDAP_SUCCESS) { - report_ldap("couldn't bind to LDAP server", r); + report_ldap("ldap: couldn't bind to LDAP server", r); ldap_unbind_s(ld); return NULL; } } ctx->pool_mark++; + ha_messagex(LOG_DEBUG, "ldap: opened new connection (total %d)", ctx->pool_mark); return ld; } @@ -621,6 +614,7 @@ static void save_ldap_connection(ldap_context_t* ctx, LDAP* ld) /* An open connection in the pool */ if(!ctx->pool[i]) { + ha_messagex(LOG_DEBUG, "ldap: caching connection for later use"); ctx->pool[i] = ld; ld = NULL; break; @@ -634,6 +628,7 @@ static void save_ldap_connection(ldap_context_t* ctx, LDAP* ld) { ldap_unbind_s(ld); ctx->pool_mark--; + ha_messagex(LOG_DEBUG, "ldap: discarding connection (total %d)", ctx->pool_mark); } } @@ -644,6 +639,8 @@ static int retrieve_user_entry(ldap_context_t* ctx, ha_buffer_t* buf, LDAP* ld, struct timeval tv; const char* filter; const char* attrs[3]; + const char* base; + int scope; int r; ASSERT(ctx && buf && ld && user && dn && entry && result); @@ -667,15 +664,19 @@ static int retrieve_user_entry(ldap_context_t* ctx, ha_buffer_t* buf, LDAP* ld, tv.tv_sec = ctx->ldap_timeout; tv.tv_usec = 0; - r = ldap_search_st(ld, *dn ? *dn : ctx->base, - *dn ? LDAP_SCOPE_BASE : ctx->scope, - filter, (char**)attrs, 0, &tv, result); + base = *dn ? *dn : ctx->base; + scope = *dn ? LDAP_SCOPE_BASE : ctx->scope; + + ha_messagex(LOG_DEBUG, "ldap: performing search: [ base: %s / scope: %d / filter: %s ]", + base, scope, filter); + + r = ldap_search_st(ld, base, scope, filter, (char**)attrs, 0, &tv, result); if(r != LDAP_SUCCESS) { if(r == LDAP_NO_SUCH_OBJECT) { - ha_messagex(LOG_WARNING, "user not found in LDAP: %s", user); + ha_messagex(LOG_WARNING, "ldap: user not found in LDAP: %s", user); return HA_FALSE; } @@ -689,15 +690,18 @@ static int retrieve_user_entry(ldap_context_t* ctx, ha_buffer_t* buf, LDAP* ld, case 1: *entry = ldap_first_entry(ld, *result); if(!(*dn)) + { *dn = ldap_get_dn(ld, *entry); + ha_messagex(LOG_DEBUG, "ldap: found entry for user: %s", *dn); + } return HA_OK; case 0: - ha_messagex(LOG_WARNING, "user not found in LDAP: %s", user); + ha_messagex(LOG_WARNING, "ldap: user not found in LDAP: %s", user); break; default: - ha_messagex(LOG_WARNING, "more than one user found for filter: %s", filter); + ha_messagex(LOG_WARNING, "ldap: more than one user found for filter: %s", filter); break; }; @@ -739,6 +743,8 @@ static int complete_digest_ha1(ldap_context_t* ctx, digest_record_t* rec, ret = HA_CRITERROR; goto finally; } + + ha_messagex(LOG_INFO, "ldap: mapped %s to %s", user, dn); } /* Okay now we contact the LDAP server. */ @@ -761,10 +767,11 @@ static int complete_digest_ha1(ldap_context_t* ctx, digest_record_t* rec, if(ret != HA_OK) { if(ret == HA_FALSE) - ha_messagex(LOG_ERR, "LDAP contains invalid HA1 digest hash for user: %s", user); + ha_messagex(LOG_ERR, "ldap: server contains invalid HA1 for user: %s", user); } } + ha_messagex(LOG_DEBUG, "ldap: using HA1 from ldap"); ldap_value_free_len(ha1s); goto finally; } @@ -779,17 +786,20 @@ static int complete_digest_ha1(ldap_context_t* ctx, digest_record_t* rec, if(t) { - digest_makeha1(rec->ha1, user, ctx->realm, t); + digest_makeha1(rec->ha1, user, ctx->opts->realm, t); ret = HA_OK; } ldap_value_free(pws); if(ret == HA_OK) + { + ha_messagex(LOG_DEBUG, "ldap: using clear password from ldap"); goto finally; + } } - ha_messagex(LOG_ERR, "LDAP contains no cleartext password for user: %s", user); + ha_messagex(LOG_ERR, "ldap: server contains no clear password or HA1 for user: %s", user); finally: @@ -824,6 +834,8 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, /* Check and see if this connection is in the cache */ if(have_cached_basic(ctx, basic.key)) { + ha_messagex(LOG_NOTICE, "ldap: validated basic user against cache: %s", + basic.user); found = 1; ret = HA_OK; goto finally; @@ -832,7 +844,10 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, /* If we have a user name and password */ if(!basic.user || !basic.user[0] || !basic.password || !basic.password[0]) + { + ha_messagex(LOG_NOTICE, "ldap: no valid basic auth info"); goto finally; + } ld = get_ldap_connection(ctx); @@ -857,6 +872,8 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, ret = HA_CRITERROR; goto finally; } + + ha_messagex(LOG_INFO, "ldap: mapped %s to %s", basic.user, dn); } @@ -895,7 +912,9 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, if(r != LDAP_SUCCESS) { if(r == LDAP_INVALID_CREDENTIALS) - ha_messagex(LOG_WARNING, "invalid login for: %s", basic.user); + ha_messagex(LOG_WARNING, "ldap: basic authentication (via bind) failed for user: %s", + basic.user); + else report_ldap("couldn't bind to LDAP server", r); @@ -903,6 +922,7 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, } /* It worked! */ + ha_messagex(LOG_NOTICE, "ldap: validated basic user using bind: %s", basic.user); found = 1; } @@ -915,10 +935,15 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, ret = validate_ldap_ha1(ctx, ld, entry, buf, basic.user, basic.password); if(ret == HA_OK) + { + ha_messagex(LOG_NOTICE, "ldap: validated basic user password/ha1: %s", basic.user); found = 1; + } else - ha_messagex(LOG_WARNING, "invalid, unreadable or unrecognized password for user: %s", basic.user); + { + ha_messagex(LOG_WARNING, "ldap: invalid, unreadable or unrecognized password for user: %s", basic.user); + } } @@ -952,10 +977,10 @@ static int digest_ldap_challenge(ldap_context_t* ctx, ha_response_t* resp, ASSERT(ctx && resp && buf); #ifdef _DEBUG - if(ctx->debug_nonce) + if(ctx->opts->digest_debugnonce) { - nonce_str = ctx->debug_nonce; - ha_messagex(LOG_WARNING, "using debug nonce. security non-existant."); + nonce_str = ctx->opts->digest_debugnonce; + ha_messagex(LOG_WARNING, "simple: using debug nonce. security non-existant."); } else #endif @@ -969,7 +994,8 @@ static int digest_ldap_challenge(ldap_context_t* ctx, ha_response_t* resp, } /* Now generate a message to send */ - header = digest_challenge(buf, nonce_str, ctx->realm, ctx->domains, stale); + header = digest_challenge(buf, nonce_str, ctx->opts->realm, + ctx->opts->digest_domains, stale); if(!header) return HA_CRITERROR; @@ -978,6 +1004,7 @@ static int digest_ldap_challenge(ldap_context_t* ctx, ha_response_t* resp, resp->code = HA_SERVER_DECLINE; ha_addheader(resp, "WWW-Authenticate", header); + ha_messagex(LOG_DEBUG, "ldap: created digest challenge with nonce: %s", nonce_str); return HA_OK; } @@ -1003,18 +1030,18 @@ static int digest_ldap_response(ldap_context_t* ctx, const char* header, return r; #ifdef _DEBUG - if(ctx->debug_nonce) + if(ctx->opts->digest_debugnonce) { - if(dg.nonce && strcmp(dg.nonce, ctx->debug_nonce) != 0) + if(dg.nonce && strcmp(dg.nonce, ctx->opts->digest_debugnonce) != 0) { ret = HA_FALSE; resp->code = HA_SERVER_BADREQ; - ha_messagex(LOG_WARNING, "digest response contains invalid nonce"); + ha_messagex(LOG_WARNING, "ldap: digest response contains invalid nonce"); goto finally; } /* Do a rough hash into the real nonce, for use as a key */ - md5_string(nonce, ctx->debug_nonce); + md5_string(nonce, ctx->opts->digest_debugnonce); /* Debug nonce's never expire */ expiry = time(NULL); @@ -1028,25 +1055,29 @@ static int digest_ldap_response(ldap_context_t* ctx, const char* header, if(r == HA_FALSE) { resp->code = HA_SERVER_BADREQ; - ha_messagex(LOG_WARNING, "digest response contains invalid nonce"); + ha_messagex(LOG_WARNING, "ldap: digest response contains invalid nonce"); } goto finally; } } - rec = get_cached_digest(ctx, nonce); /* Check to see if we're stale */ - if((expiry + ctx->cache_timeout) <= time(NULL)) + if((expiry + ctx->opts->cache_timeout) <= time(NULL)) { + ha_messagex(LOG_INFO, "ldap: nonce expired, sending stale challenge: %s", + dg.username); + stale = 1; goto finally; } if(!rec) { + ha_messagex(LOG_INFO, "ldap: no record in cache, creating one: %s", dg.username); + /* * If we're valid but don't have a record in the * cache then complete the record properly. @@ -1070,7 +1101,8 @@ static int digest_ldap_response(ldap_context_t* ctx, const char* header, /* Increment our nonce count */ rec->nc++; - ret = digest_check(ctx->realm, method, uri, buf, &dg, rec); + ret = digest_check(ctx->opts->realm, method, + ctx->opts->digest_ignoreuri ? NULL : uri, buf, &dg, rec); if(ret == HA_BADREQ) { @@ -1084,8 +1116,12 @@ static int digest_ldap_response(ldap_context_t* ctx, const char* header, resp->detail = dg.username; /* Figure out if we need a new nonce */ - if((expiry + (ctx->cache_timeout - (ctx->cache_timeout / 8))) < time(NULL)) + if((expiry + (ctx->opts->cache_timeout - + (ctx->opts->cache_timeout / 8))) < time(NULL)) { + ha_messagex(LOG_INFO, "ldap: nonce almost expired, creating new one: %s", + dg.username); + digest_makenonce(nonce, g_ldap_secret, NULL); stale = 1; } @@ -1100,6 +1136,8 @@ static int digest_ldap_response(ldap_context_t* ctx, const char* header, if(t[0]) ha_addheader(resp, "Authentication-Info", t); + ha_messagex(LOG_NOTICE, "ldap: validated digest user: %s", dg.username); + /* Put the connection into the cache */ if((r = save_cached_digest(ctx, rec)) < 0) ret = r; @@ -1179,18 +1217,6 @@ int ldap_config(ha_context_t* context, const char* name, const char* value) return HA_OK; } - else if(strcmp(name, "realm") == 0) - { - ctx->realm = value; - return HA_OK; - } - - else if(strcmp(name, "digestdomains") == 0) - { - ctx->domains = value; - return HA_OK; - } - else if(strcmp(name, "ldapscope") == 0) { if(strcmp(value, "sub") == 0 || strcmp(value, "subtree") == 0) @@ -1224,14 +1250,6 @@ int ldap_config(ha_context_t* context, const char* name, const char* value) return ha_confint(name, value, 0, 86400, &(ctx->ldap_timeout)); } -#ifdef _DEBUG - else if(strcmp(name, "digestdebugnonce") == 0) - { - ctx->debug_nonce = value; - return HA_OK; - } -#endif - return HA_FALSE; } @@ -1240,6 +1258,7 @@ int ldap_inithand(ha_context_t* context) /* Global initialization */ if(!context) { + ha_messagex(LOG_DEBUG, "ldap: generating secret"); return ha_genrandom(g_ldap_secret, DIGEST_SECRET_LEN); } @@ -1252,9 +1271,9 @@ int ldap_inithand(ha_context_t* context) ASSERT(ctx); /* Make sure there are some types of authentication we can do */ - if(!(context->types & (HA_TYPE_BASIC | HA_TYPE_DIGEST))) + if(!(context->opts.types & (HA_TYPE_BASIC | HA_TYPE_DIGEST))) { - ha_messagex(LOG_ERR, "LDAP module configured, but does not implement any " + ha_messagex(LOG_ERR, "ldap: module configured, but does not implement any " "configured authentication type."); return HA_FAILED; } @@ -1262,14 +1281,14 @@ int ldap_inithand(ha_context_t* context) /* Check for mandatory configuration */ if(!ctx->servers) { - ha_messagex(LOG_ERR, "LDAP configuration incomplete. " + ha_messagex(LOG_ERR, "ldap: configuration incomplete. " "Must have LDAPServers."); return HA_FAILED; } if(!ctx->dnmap && (!ctx->filter || !ctx->base)) { - ha_messagex(LOG_ERR, "LDAP configuration incomplete. " + ha_messagex(LOG_ERR, "ldap: configuration incomplete. " "When not using LDAPDNMap must specify LDAPBase and LDAPFilter."); return HA_FAILED; } @@ -1299,8 +1318,9 @@ int ldap_inithand(ha_context_t* context) memset(ctx->pool, 0, sizeof(LDAP*) * ctx->ldap_max); /* Copy some settings over for easy access */ - ctx->cache_max = context->cache_max; - ctx->cache_timeout = context->cache_timeout; + ctx->opts = &(context->opts); + + ha_messagex(LOG_INFO, "ldap: initialized handler"); } return HA_OK; @@ -1333,6 +1353,8 @@ void ldap_destroy(ha_context_t* context) /* And free the connection pool */ free(ctx->pool); } + + ha_messagex(LOG_INFO, "ldap: uninitialized handler"); } int ldap_process(ha_context_t* context, ha_request_t* req, @@ -1341,7 +1363,7 @@ int ldap_process(ha_context_t* context, ha_request_t* req, ldap_context_t* ctx = (ldap_context_t*)context->data; time_t t = time(NULL); const char* header = NULL; - int ret; + int ret, r; ASSERT(req && resp && buf); ASSERT(req->args[AUTH_ARG_METHOD]); @@ -1350,21 +1372,24 @@ int ldap_process(ha_context_t* context, ha_request_t* req, ha_lock(NULL); /* Purge out stale connection stuff. */ - hash_purge(ctx->cache, t - ctx->cache_timeout); + r = hash_purge(ctx->cache, t - ctx->opts->cache_timeout); ha_unlock(NULL); + if(r > 0) + ha_messagex(LOG_DEBUG, "ldap: purged cache records: %d", r); /* We use this below to detect whether to send a default response */ resp->code = -1; /* Check the headers and see if we got a response thingy */ - if(context->types & HA_TYPE_DIGEST) + if(context->opts.types & HA_TYPE_DIGEST) { header = ha_getheader(req, "Authorization", HA_PREFIX_DIGEST); if(header) { + ha_messagex(LOG_DEBUG, "ldap: processing basic auth header"); ret = digest_ldap_response(ctx, header, req->args[AUTH_ARG_METHOD], req->args[AUTH_ARG_URI], resp, buf); if(ret < 0) @@ -1373,11 +1398,12 @@ int ldap_process(ha_context_t* context, ha_request_t* req, } /* Or a basic authentication */ - if(!header && context->types & HA_TYPE_BASIC) + if(!header && context->opts.types & HA_TYPE_BASIC) { header = ha_getheader(req, "Authorization", HA_PREFIX_BASIC); if(header) { + ha_messagex(LOG_DEBUG, "ldap: processing digest auth header"); ret = basic_ldap_response(ctx, header, resp, buf); if(ret < 0) return ret; @@ -1390,21 +1416,22 @@ int ldap_process(ha_context_t* context, ha_request_t* req, { resp->code = HA_SERVER_DECLINE; - if(context->types & HA_TYPE_DIGEST) - { - ret = digest_ldap_challenge(ctx, resp, buf, 0); - if(ret < 0) - return ret; - } - - if(context->types & HA_TYPE_BASIC) + if(context->opts.types & HA_TYPE_BASIC) { - ha_bufmcat(buf, "BASIC realm=\"", ctx->realm , "\"", NULL); + ha_bufmcat(buf, "BASIC realm=\"", ctx->opts->realm , "\"", NULL); if(ha_buferr(buf)) return HA_CRITERROR; ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf)); + ha_messagex(LOG_DEBUG, "ldap: sent basic auth request"); + } + + if(context->opts.types & HA_TYPE_DIGEST) + { + ret = digest_ldap_challenge(ctx, resp, buf, 0); + if(ret < 0) + return ret; } } diff --git a/daemon/misc.c b/daemon/misc.c index cda6dc2..a38eda9 100644 --- a/daemon/misc.c +++ b/daemon/misc.c @@ -8,7 +8,7 @@ #include <stdio.h> #include <fcntl.h> -extern int g_debugging; +extern int g_debuglevel; extern int g_daemonized; extern pthread_mutex_t g_mutex; @@ -29,11 +29,14 @@ void ha_messagex(int level, const char* msg, ...) /* Either to syslog or stderr */ if(g_daemonized) - vsyslog(level, msg, ap); + { + if(level < LOG_DEBUG) + vsyslog(level, msg, ap); + } else { - if(g_debugging || level > LOG_INFO) + if(g_debuglevel >= level) vwarnx(msg, ap); } @@ -50,21 +53,24 @@ void ha_message(int level, const char* msg, ...) /* Either to syslog or stderr */ if(g_daemonized) { - char* m = (char*)alloca(strlen(msg) + MAX_MSGLEN + sizeof(kMsgDelimiter)); - - if(m) + if(level < LOG_DEBUG) { - strcpy(m, msg); - strcat(m, kMsgDelimiter); - strerror_r(errno, m + strlen(m), MAX_MSGLEN); - } + char* m = (char*)alloca(strlen(msg) + MAX_MSGLEN + sizeof(kMsgDelimiter)); - vsyslog(LOG_ERR, m ? m : msg, ap); + if(m) + { + strcpy(m, msg); + strcat(m, kMsgDelimiter); + strerror_r(errno, m + strlen(m), MAX_MSGLEN); + } + + vsyslog(LOG_ERR, m ? m : msg, ap); + } } else { - if(g_debugging || level > LOG_INFO) - vwarnx(msg, ap); + if(g_debuglevel >= level) + vwarn(msg, ap); } va_end(ap); @@ -81,7 +87,7 @@ ha_header_t* ha_findheader(ha_request_t* req, const char* name) ASSERT(req && name); - for(i = 0; i < MAX_HEADERS; i++) + for(i = 0; i < HA_MAX_HEADERS; i++) { if(req->headers[i].name) { @@ -99,7 +105,7 @@ const char* ha_getheader(ha_request_t* req, const char* name, const char* prefix ASSERT(req && name); - for(i = 0; i < MAX_HEADERS; i++) + for(i = 0; i < HA_MAX_HEADERS; i++) { if(req->headers[i].name) { @@ -125,7 +131,7 @@ void ha_addheader(ha_response_t* resp, const char* name, const char* data) ASSERT(resp && name && data); - for(i = 0; i < MAX_HEADERS; i++) + for(i = 0; i < HA_MAX_HEADERS; i++) { if(!(resp->headers[i].name)) { @@ -135,7 +141,7 @@ void ha_addheader(ha_response_t* resp, const char* name, const char* data) } } - ha_messagex(LOG_ERR, "too many headers in response. discarding '%s'", name); + ha_messagex(LOG_WARNING, "too many headers in response. discarding '%s'", name); } @@ -145,12 +151,41 @@ void ha_addheader(ha_response_t* resp, const char* name, const char* data) void ha_lock(pthread_mutex_t* mtx) { - int r = pthread_mutex_lock(mtx ? mtx : &g_mutex); + int r; + +#ifdef _DEBUG + int wait = 0; +#endif + + if(!mtx) + mtx = &g_mutex; + +#ifdef _DEBUG + r = pthread_mutex_trylock(mtx); + if(r == EBUSY) + { + wait = 1; + ha_message(LOG_DEBUG, "thread will block: %d", pthread_self()); + r = pthread_mutex_lock(mtx); + } + +#else + r = pthread_mutex_lock(mtx); + +#endif + if(r != 0) { errno = r; ha_message(LOG_CRIT, "threading problem. couldn't lock mutex"); } + +#ifdef _DEBUG + else if(wait) + { + ha_message(LOG_DEBUG, "thread unblocked: %d", pthread_self()); + } +#endif } void ha_unlock(pthread_mutex_t* mtx) @@ -586,3 +621,4 @@ int ha_genrandom(unsigned char* data, size_t len) close(dd); return r == -1 ? HA_FAILED : HA_OK; } + diff --git a/daemon/ntlm.c b/daemon/ntlm.c index ef889f1..bbbac5e 100644 --- a/daemon/ntlm.c +++ b/daemon/ntlm.c @@ -44,7 +44,6 @@ typedef struct ntlm_context const char* backup; /* Backup server if primary is down */ int pending_max; /* Maximum number of connections at once */ int pending_timeout; /* Timeout for authentication (in seconds) */ - const char* basic_realm; /* The realm for basic authentication */ /* Context ----------------------------------------------------------- */ hash_t* pending; /* Pending connections */ @@ -57,7 +56,7 @@ ntlm_context_t; static const ntlm_context_t ntlm_defaults = { NULL, NULL, NULL, DEFAULT_PENDING_MAX, DEFAULT_PENDING_TIMEOUT, - NULL, NULL, NULL + NULL, NULL }; @@ -94,12 +93,13 @@ static ntlm_connection_t* makeconnection(ntlm_context_t* ctx) ctx->domain, conn->nonce); if(!conn->handle) { - ha_messagex(LOG_ERR, "couldn't connect to the domain server %s (backup: %s)", + ha_messagex(LOG_ERR, "ntlm: couldn't connect to the domain server %s (backup: %s)", ctx->server, ctx->backup ? ctx->backup : "none"); free(conn); return NULL; } + ha_messagex(LOG_INFO, "ntlm: established connection to server"); return conn; } @@ -109,6 +109,7 @@ static void freeconnection(ntlm_connection_t* conn) if(conn->handle) { + ha_messagex(LOG_DEBUG, "ntlm: disconnected from server"); ntlmssp_disconnect(conn->handle); conn->handle = NULL; } @@ -183,7 +184,10 @@ int ntlm_auth_basic(ntlm_context_t* ctx, char* key, const char* header, */ conn = getpending(ctx, key); if(conn) + { + ha_messagex(LOG_WARNING, "ntlm: basic auth killed a pending ntlm auth in progress"); freeconnection(conn); + } if((r = basic_parse(header, buf, &basic)) < 0) return r; @@ -196,43 +200,53 @@ int ntlm_auth_basic(ntlm_context_t* ctx, char* key, const char* header, ha_unlock(NULL); + if(found) + ha_messagex(LOG_NOTICE, "ntlm: validated basic user against cache: %s", basic.user); - /* Try to find a domain in the user */ - if((t = strchr(basic.user, '\\')) != NULL || - (t = strchr(basic.user, '/')) != NULL) + else { - /* Break at the domain */ - domain = basic.user; - basic.user = t + 1; - *t = 0; - - /* Make sure this is our domain */ - if(strcasecmp(domain, ctx->domain) != 0) - domain = NULL; - } + /* Try to find a domain in the user */ + if((t = strchr(basic.user, '\\')) != NULL || + (t = strchr(basic.user, '/')) != NULL) + { + /* Break at the domain */ + domain = basic.user; + basic.user = t + 1; + *t = 0; + + /* Make sure this is our domain */ + if(strcasecmp(domain, ctx->domain) != 0) + domain = NULL; + } - if(!domain) - { - /* Use the default domain if none specified */ - domain = ctx->domain; - } + if(!domain) + { + /* Use the default domain if none specified */ + domain = ctx->domain; + } - /* Make sure above did not fail */ - if(!found && basic.user && basic.user[0] && basic.password && - domain && domain[0]) - { - /* We need to lock to go into smblib */ - ha_lock(&g_smblib_mutex); + /* Make sure above did not fail */ + if(basic.user && basic.user[0] && basic.password && + domain && domain[0]) + { + ha_messagex(LOG_DEBUG, "ntlm: checking user against server: %s", basic.user); - /* Found in smbval/valid.h */ - if(ntlmssp_validuser(basic.user, basic.password, ctx->server, - ctx->backup, domain) == NTV_NO_ERROR) - { - /* If valid then we return */ - found = 1; - } + /* We need to lock to go into smblib */ + ha_lock(&g_smblib_mutex); - ha_unlock(&g_smblib_mutex); + /* Found in smbval/valid.h */ + if(ntlmssp_validuser(basic.user, basic.password, ctx->server, + ctx->backup, domain) == NTV_NO_ERROR) + { + /* If valid then we return */ + found = 1; + } + + ha_unlock(&g_smblib_mutex); + } + + if(found) + ha_messagex(LOG_NOTICE, "ntlm: validated basic user against server: %s", basic.user); } if(found) @@ -300,7 +314,7 @@ int ntlm_auth_ntlm(ntlm_context_t* ctx, void* key, const char* header, r = ntlmssp_decode_msg(&ntlmssp, d, len, &flags); if(r != 0) { - ha_messagex(LOG_ERR, "decoding NTLM message failed (error %d)", r); + ha_messagex(LOG_WARNING, "ntlm: decoding NTLMSSP message failed (error %d)", r); resp->code = HA_SERVER_BADREQ; goto finally; } @@ -340,7 +354,7 @@ int ntlm_auth_ntlm(ntlm_context_t* ctx, void* key, const char* header, } else { - ha_messagex(LOG_ERR, "received out of order NTLM request from client"); + ha_messagex(LOG_ERR, "ntlm: received out of order NTLM request from client"); resp->code = HA_SERVER_BADREQ; } @@ -421,6 +435,7 @@ int ntlm_auth_ntlm(ntlm_context_t* ctx, void* key, const char* header, } else { + ha_messagex(LOG_DEBUG, "ntlm: sending ntlm challenge"); ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf)); resp->code = HA_SERVER_DECLINE; } @@ -439,14 +454,14 @@ int ntlm_auth_ntlm(ntlm_context_t* ctx, void* key, const char* header, */ if(!conn || !conn->handle) { - ha_messagex(LOG_ERR, "received out of order NTLM response from client"); + ha_messagex(LOG_WARNING, "ntlm: received out of order NTLM response from client"); resp->code = HA_SERVER_BADREQ; goto finally; } if(!ntlmssp.user) { - ha_messagex(LOG_ERR, "received NTLM response without user name"); + ha_messagex(LOG_WARNING, "ntlm: received NTLM response without user name"); resp->code = HA_SERVER_BADREQ; goto finally; } @@ -468,7 +483,7 @@ int ntlm_auth_ntlm(ntlm_context_t* ctx, void* key, const char* header, * Note that we don't set a code here. This causes our * caller to put in all the proper headers for us. */ - ha_messagex(LOG_ERR, "failed NTLM logon for user '%s'", ntlmssp.user); + ha_messagex(LOG_WARNING, "ntlm: failed NTLM logon for user '%s'", ntlmssp.user); ret = HA_FALSE; } @@ -477,6 +492,7 @@ int ntlm_auth_ntlm(ntlm_context_t* ctx, void* key, const char* header, { int r; resp->detail = ntlmssp.user; + ha_messagex(LOG_NOTICE, "ntlm: validated ntlm user against server", ntlmssp.user); ha_lock(NULL); @@ -500,7 +516,7 @@ int ntlm_auth_ntlm(ntlm_context_t* ctx, void* key, const char* header, } default: - ha_messagex(LOG_ERR, "received invalid NTLM message (type %d)", ntlmssp.msg_type); + ha_messagex(LOG_WARNING, "ntlm: received invalid NTLM message (type %d)", ntlmssp.msg_type); resp->code = HA_SERVER_BADREQ; goto finally; }; @@ -555,12 +571,6 @@ int ntlm_config(ha_context_t* context, const char* name, const char* value) return ha_confint(name, value, 1, 86400, &(ctx->pending_timeout)); } - else if(strcmp(name, "realm") == 0) - { - ctx->basic_realm = value; - return HA_OK; - } - return HA_FALSE; } @@ -574,7 +584,7 @@ int ntlm_init(ha_context_t* context) ASSERT(ctx); /* Make sure there are some types of authentication we can do */ - if(!(context->types & (HA_TYPE_BASIC | HA_TYPE_NTLM))) + if(!(context->opts.types & (HA_TYPE_BASIC | HA_TYPE_NTLM))) { ha_messagex(LOG_ERR, "NTLM module configured, but does not implement any " "configured authentication type."); @@ -599,6 +609,8 @@ int ntlm_init(ha_context_t* context) ha_messagex(LOG_CRIT, "out of memory"); return HA_CRITERROR; } + + ha_messagex(LOG_INFO, "ntlm: initialized handler"); } /* Global Initialization */ @@ -630,6 +642,8 @@ void ntlm_destroy(ha_context_t* context) if(ctx->established) hash_free(ctx->established); + + ha_messagex(LOG_INFO, "ntlm: uninitialized handler"); } /* Global Destroy */ @@ -649,7 +663,7 @@ int ntlm_process(ha_context_t* context, ha_request_t* req, unsigned char key[NTLM_HASH_KEY_LEN]; const char* header = NULL; time_t t = time(NULL); - int ret; + int ret, r; ASSERT(context && req && resp && buf); ASSERT(req->args[AUTH_ARG_CONN]); @@ -667,14 +681,16 @@ int ntlm_process(ha_context_t* context, ha_request_t* req, * authenticated connections which have expired as * well as half open connections which expire. */ - hash_purge(ctx->pending, t - ctx->pending_timeout); - hash_purge(ctx->established, t - context->cache_timeout); + r = hash_purge(ctx->pending, t - ctx->pending_timeout); + r += hash_purge(ctx->established, t - context->opts.cache_timeout); ha_unlock(NULL); + if(r > 0) + ha_messagex(LOG_DEBUG, "ntlm: purged info from cache: %d", r); /* Look for a NTLM header */ - if(context->types & HA_TYPE_NTLM) + if(context->opts.types & HA_TYPE_NTLM) { header = ha_getheader(req, "Authorization", HA_PREFIX_NTLM); if(header) @@ -683,6 +699,7 @@ int ntlm_process(ha_context_t* context, ha_request_t* req, while(*header && isspace(*header)) header++; + ha_messagex(LOG_DEBUG, "ntlm: processing ntlm auth header"); ret = ntlm_auth_ntlm(ctx, key, header, resp, buf); if(ret < 0) return ret; @@ -690,7 +707,7 @@ int ntlm_process(ha_context_t* context, ha_request_t* req, } /* If basic is enabled, and no NTLM */ - if(!header && context->types & HA_TYPE_BASIC) + if(!header && context->opts.types & HA_TYPE_BASIC) { /* Look for a Basic header */ header = ha_getheader(req, "Authorization", HA_PREFIX_BASIC); @@ -700,6 +717,7 @@ int ntlm_process(ha_context_t* context, ha_request_t* req, while(*header && isspace(*header)) header++; + ha_messagex(LOG_DEBUG, "ntlm: processing basic auth header"); ret = ntlm_auth_basic(ctx, key, header, resp, buf); if(ret < 0) return ret; @@ -725,6 +743,11 @@ int ntlm_process(ha_context_t* context, ha_request_t* req, } ha_unlock(NULL); + + if(resp->code == HA_SERVER_ACCEPT) + ha_messagex(LOG_NOTICE, "ntlm: validated user against connection cache"); + + /* TODO: We need to be able to retrieve the user here somehow */ } @@ -734,18 +757,21 @@ int ntlm_process(ha_context_t* context, ha_request_t* req, /* If authentication failed tell the browser about it */ resp->code = HA_SERVER_DECLINE; - if(context->types & HA_TYPE_NTLM) + if(context->opts.types & HA_TYPE_NTLM) + { ha_addheader(resp, "WWW-Authenticate", HA_PREFIX_NTLM); + ha_messagex(LOG_DEBUG, "ntlm: sent ntlm auth request"); + } - if(context->types & HA_TYPE_BASIC) + if(context->opts.types & HA_TYPE_BASIC) { - ha_bufmcat(buf, HA_PREFIX_BASIC, "realm=\"", - ctx->basic_realm ? ctx->basic_realm : "", "\"", NULL); + ha_bufmcat(buf, HA_PREFIX_BASIC, "realm=\"", context->opts.realm, "\"", NULL); if(ha_buferr(buf)) return HA_CRITERROR; ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf)); + ha_messagex(LOG_DEBUG, "ntlm: sent basic auth request"); } } diff --git a/daemon/simple.c b/daemon/simple.c index 4c7fb28..5b4eb60 100644 --- a/daemon/simple.c +++ b/daemon/simple.c @@ -25,18 +25,12 @@ unsigned char g_simple_secret[DIGEST_SECRET_LEN]; typedef struct simple_context { + /* Settings ----------------------------------------------------------- */ const char* filename; /* The file name with the user names */ - const char* realm; /* The realm for basic authentication */ - const char* domains; /* Domains for which digest auth is valid */ - int cache_max; /* Maximum number of connections at once */ - int cache_timeout; + ha_options_t* opts; /* Options from httpauthd.c */ /* Context ----------------------------------------------------------- */ hash_t* cache; /* Some cached records or basic */ - -#ifdef _DEBUG - const char* debug_nonce; -#endif } simple_context_t; @@ -57,7 +51,7 @@ static digest_record_t* get_cached_digest(simple_context_t* ctx, unsigned char* ASSERT(ctx && nonce); - if(ctx->cache_max == 0) + if(ctx->opts->cache_max == 0) return NULL; ha_lock(NULL); @@ -95,7 +89,7 @@ static int save_cached_digest(simple_context_t* ctx, digest_record_t* rec) ASSERT(ctx && rec); - if(ctx->cache_max == 0) + if(ctx->opts->cache_max == 0) { free_hash_object(NULL, rec); return HA_FALSE; @@ -103,7 +97,7 @@ static int save_cached_digest(simple_context_t* ctx, digest_record_t* rec) ha_lock(NULL); - while(hash_count(ctx->cache) >= ctx->cache_max) + while(hash_count(ctx->cache) >= ctx->opts->cache_max) hash_bump(ctx->cache); r = hash_set(ctx->cache, rec->nonce, rec); @@ -126,12 +120,12 @@ static int add_cached_basic(simple_context_t* ctx, unsigned char* key) ASSERT(ctx && key); - if(ctx->cache_max == 0) + if(ctx->opts->cache_max == 0) return HA_FALSE; ha_lock(NULL); - while(hash_count(ctx->cache) >= ctx->cache_max) + while(hash_count(ctx->cache) >= ctx->opts->cache_max) hash_bump(ctx->cache); r = hash_set(ctx->cache, key, BASIC_ESTABLISHED); @@ -158,11 +152,12 @@ static int complete_digest_ha1(simple_context_t* ctx, digest_record_t* rec, int ret = HA_FALSE; ASSERT(ctx && rec && buf && user && user[0]); + ha_messagex(LOG_DEBUG, "searching password file for user's ha1: %s", user); f = fopen(ctx->filename, "r"); if(!f) { - ha_message(LOG_ERR, "can't open file for basic auth: %s", ctx->filename); + ha_message(LOG_ERR, "simple: can't open file for basic auth: %s", ctx->filename); return HA_FAILED; } @@ -178,7 +173,7 @@ static int complete_digest_ha1(simple_context_t* ctx, digest_record_t* rec, if(ferror(f)) { - ha_message(LOG_ERR, "error reading basic password file"); + ha_message(LOG_ERR, "simple: error reading basic password file"); ret = HA_FAILED; break; } @@ -201,7 +196,7 @@ static int complete_digest_ha1(simple_context_t* ctx, digest_record_t* rec, t2++; /* Check the realm */ - if(strcmp(t, ctx->realm) == 0) + if(strcmp(t, ctx->opts->realm) == 0) { len = MD5_LEN; @@ -209,6 +204,7 @@ static int complete_digest_ha1(simple_context_t* ctx, digest_record_t* rec, t = ha_bufdechex(buf, t2, &len); if(t && len == MD5_LEN) { + ha_messagex(LOG_DEBUG, "simple: found ha1 for user: %s", user); memcpy(rec->ha1, t, MD5_LEN); ret = HA_OK; break; @@ -217,7 +213,7 @@ static int complete_digest_ha1(simple_context_t* ctx, digest_record_t* rec, } if(!t2 || ret != HA_OK) - ha_messagex(LOG_WARNING, "user '%s' found in file, but password not in digest format", user); + ha_messagex(LOG_WARNING, "simple: user '%s' found in file, but password not in digest format", user); } } } @@ -243,15 +239,16 @@ static int validate_user_password(simple_context_t* ctx, ha_buffer_t* buf, ASSERT(ctx && buf); ASSERT(user && user[0] && clearpw); + ha_messagex(LOG_DEBUG, "simple: validating user against password file: %s", user); f = fopen(ctx->filename, "r"); if(!f) { - ha_message(LOG_ERR, "can't open file for basic auth: %s", ctx->filename); + ha_message(LOG_ERR, "simple: can't open file for basic auth: %s", ctx->filename); return HA_FAILED; } - digest_makeha1(ha1, user, ctx->realm, clearpw); + digest_makeha1(ha1, user, ctx->opts->realm, clearpw); /* * Note: There should be no returns or jumps between @@ -265,7 +262,7 @@ static int validate_user_password(simple_context_t* ctx, ha_buffer_t* buf, if(ferror(f)) { - ha_message(LOG_ERR, "error reading basic password file"); + ha_message(LOG_ERR, "simple: error reading basic password file"); ret = HA_FAILED; break; } @@ -298,6 +295,7 @@ static int validate_user_password(simple_context_t* ctx, ha_buffer_t* buf, if(strcmp(crypt(clearpw, t), t) == 0) { + ha_messagex(LOG_DEBUG, "simple: found valid crypt password for user: %s", user); ret = HA_OK; break; } @@ -310,7 +308,7 @@ static int validate_user_password(simple_context_t* ctx, ha_buffer_t* buf, t2++; /* Check the realm */ - if(strcmp(t, ctx->realm) == 0) + if(strcmp(t, ctx->opts->realm) == 0) { len = MD5_LEN; @@ -318,6 +316,7 @@ static int validate_user_password(simple_context_t* ctx, ha_buffer_t* buf, t = ha_bufdechex(buf, t2, &len); if(t && len == MD5_LEN && memcmp(ha1, t, MD5_LEN) == 0) { + ha_messagex(LOG_DEBUG, "simple: found valid ha1 for user: %s", user); ret = HA_OK; break; } @@ -356,6 +355,7 @@ static int simple_basic_response(simple_context_t* ctx, const char* header, /* Check and see if this connection is in the cache */ if(have_cached_basic(ctx, basic.key)) { + ha_messagex(LOG_NOTICE, "simple: validated basic user against cache: %s", basic.user); found = 1; ret = HA_OK; goto finally; @@ -369,9 +369,15 @@ static int simple_basic_response(simple_context_t* ctx, const char* header, ret = validate_user_password(ctx, buf, basic.user, basic.password); + if(ret == HA_OK) + ha_messagex(LOG_NOTICE, "simple: validated basic user against file: %s", basic.user); + + else + ha_messagex(LOG_WARNING, "simple: basic authentication failed for user: %s", basic.user); + finally: - if(ret = HA_OK) + if(ret == HA_OK) { resp->code = HA_SERVER_ACCEPT; resp->detail = basic.user; @@ -384,7 +390,7 @@ finally: } static int simple_digest_challenge(simple_context_t* ctx, ha_response_t* resp, - ha_buffer_t* buf, int stale) + ha_buffer_t* buf, int stale) { const char* nonce_str; const char* header; @@ -394,10 +400,10 @@ static int simple_digest_challenge(simple_context_t* ctx, ha_response_t* resp, /* Generate an nonce */ #ifdef _DEBUG - if(ctx->debug_nonce) + if(ctx->opts->digest_debugnonce) { - nonce_str = ctx->debug_nonce; - ha_messagex(LOG_WARNING, "using debug nonce. security non-existant."); + nonce_str = ctx->opts->digest_debugnonce; + ha_messagex(LOG_WARNING, "simple: using debug nonce. security non-existant."); } else #endif @@ -412,7 +418,8 @@ static int simple_digest_challenge(simple_context_t* ctx, ha_response_t* resp, /* Now generate a message to send */ - header = digest_challenge(buf, nonce_str, ctx->realm, ctx->domains, stale); + header = digest_challenge(buf, nonce_str, ctx->opts->realm, + ctx->opts->digest_domains, stale); if(!header) return HA_CRITERROR; @@ -421,6 +428,7 @@ static int simple_digest_challenge(simple_context_t* ctx, ha_response_t* resp, resp->code = HA_SERVER_DECLINE; ha_addheader(resp, "WWW-Authenticate", header); + ha_messagex(LOG_DEBUG, "simple: created digest challenge with nonce: %s", nonce_str); return HA_OK; } @@ -446,18 +454,18 @@ static int simple_digest_response(simple_context_t* ctx, const char* header, return r; #ifdef _DEBUG - if(ctx->debug_nonce) + if(ctx->opts->digest_debugnonce) { - if(dg.nonce && strcmp(dg.nonce, ctx->debug_nonce) != 0) + if(dg.nonce && strcmp(dg.nonce, ctx->opts->digest_debugnonce) != 0) { resp->code = HA_SERVER_BADREQ; ret = HA_FALSE; - ha_messagex(LOG_WARNING, "digest response contains invalid nonce"); + ha_messagex(LOG_WARNING, "simple: digest response contains invalid nonce"); goto finally; } /* Do a rough hash into the real nonce, for use as a key */ - md5_string(nonce, ctx->debug_nonce); + md5_string(nonce, ctx->opts->digest_debugnonce); /* Debug nonce's never expire */ expiry = time(NULL); @@ -471,7 +479,7 @@ static int simple_digest_response(simple_context_t* ctx, const char* header, if(r == HA_FALSE) { resp->code = HA_SERVER_BADREQ; - ha_messagex(LOG_WARNING, "digest response contains invalid nonce"); + ha_messagex(LOG_WARNING, "simple: digest response contains invalid nonce"); } ret = r; @@ -482,14 +490,19 @@ static int simple_digest_response(simple_context_t* ctx, const char* header, rec = get_cached_digest(ctx, nonce); /* Check to see if we're stale */ - if((expiry + ctx->cache_timeout) <= time(NULL)) + if((expiry + ctx->opts->cache_timeout) <= time(NULL)) { + ha_messagex(LOG_INFO, "simple: nonce expired, sending stale challenge: %s", + dg.username); + stale = 1; goto finally; } if(!rec) { + ha_messagex(LOG_INFO, "simple: no record in cache, creating one: %s", dg.username); + /* * If we're valid but don't have a record in the * cache then complete the record properly. @@ -513,7 +526,8 @@ static int simple_digest_response(simple_context_t* ctx, const char* header, /* Increment our nonce count */ rec->nc++; - ret = digest_check(ctx->realm, method, uri, buf, &dg, rec); + ret = digest_check(ctx->opts->realm, method, + ctx->opts->digest_ignoreuri ? NULL : uri, buf, &dg, rec); if(ret == HA_BADREQ) { @@ -527,8 +541,12 @@ static int simple_digest_response(simple_context_t* ctx, const char* header, resp->detail = dg.username; /* Figure out if we need a new nonce */ - if((expiry + (ctx->cache_timeout - (ctx->cache_timeout / 8))) < time(NULL)) + if((expiry + (ctx->opts->cache_timeout - + (ctx->opts->cache_timeout / 8))) < time(NULL)) { + ha_messagex(LOG_INFO, "simple: nonce almost expired, creating new one: %s", + dg.username); + digest_makenonce(nonce, g_simple_secret, NULL); stale = 1; } @@ -543,6 +561,8 @@ static int simple_digest_response(simple_context_t* ctx, const char* header, if(t[0]) ha_addheader(resp, "Authentication-Info", t); + ha_messagex(LOG_NOTICE, "simple: validated digest user: %s", dg.username); + /* Put the connection into the cache */ if((r = save_cached_digest(ctx, rec)) < 0) ret = r; @@ -580,26 +600,6 @@ int simple_config(ha_context_t* context, const char* name, const char* value) return HA_OK; } - else if(strcmp(name, "realm") == 0) - { - ctx->realm = value; - return HA_OK; - } - - else if(strcmp(name, "digestdomains") == 0) - { - ctx->domains = value; - return HA_OK; - } - -#ifdef _DEBUG - else if(strcmp(name, "digestdebugnonce") == 0) - { - ctx->debug_nonce = value; - return HA_OK; - } -#endif - return HA_FALSE; } @@ -608,6 +608,7 @@ int simple_init(ha_context_t* context) /* Global initialization */ if(!context) { + ha_messagex(LOG_DEBUG, "simple: generating secret"); return ha_genrandom(g_simple_secret, DIGEST_SECRET_LEN); } @@ -620,9 +621,9 @@ int simple_init(ha_context_t* context) ASSERT(ctx); /* Make sure there are some types of authentication we can do */ - if(!(context->types & (HA_TYPE_BASIC | HA_TYPE_DIGEST))) + if(!(context->opts.types & (HA_TYPE_BASIC | HA_TYPE_DIGEST))) { - ha_messagex(LOG_ERR, "Simple module configured, but does not implement any " + ha_messagex(LOG_ERR, "simple: module configured, but does not implement any " "configured authentication type."); return HA_FAILED; } @@ -631,7 +632,7 @@ int simple_init(ha_context_t* context) /* Check to make sure the file exists */ if(!ctx->filename) { - ha_messagex(LOG_ERR, "Basic configuration incomplete. " + ha_messagex(LOG_ERR, "simple: configuration incomplete. " "Must have a PasswordFile configured."); return HA_FAILED; } @@ -639,7 +640,7 @@ int simple_init(ha_context_t* context) fd = open(ctx->filename, O_RDONLY); if(fd == -1) { - ha_message(LOG_ERR, "can't open file for simple authentication: %s", ctx->filename); + ha_message(LOG_ERR, "simple: can't open file for authentication: %s", ctx->filename); return HA_FAILED; } @@ -655,9 +656,9 @@ int simple_init(ha_context_t* context) } /* Copy some settings over for easy access */ - ctx->cache_max = context->cache_max; - ctx->cache_timeout = context->cache_timeout; + ctx->opts = &(context->opts); + ha_messagex(LOG_INFO, "simple: initialized handler"); } return HA_OK; @@ -673,6 +674,8 @@ void simple_destroy(ha_context_t* context) if(ctx->cache) hash_free(ctx->cache); + + ha_messagex(LOG_INFO, "simple: uninitialized handler"); } } @@ -684,6 +687,7 @@ int simple_process(ha_context_t* context, ha_request_t* req, int ret = HA_FALSE; int found = 0; basic_header_t basic; + int r; ASSERT(context && req && resp && buf); ASSERT(req->args[AUTH_ARG_METHOD]); @@ -692,21 +696,24 @@ int simple_process(ha_context_t* context, ha_request_t* req, ha_lock(NULL); /* Purge the cache */ - hash_purge(ctx->cache, time(NULL) - ctx->cache_timeout); + r = hash_purge(ctx->cache, time(NULL) - ctx->opts->cache_timeout); ha_unlock(NULL); + if(r > 0) + ha_messagex(LOG_DEBUG, "simple: purged cache records: %d", r); /* We use this below to detect whether to send a default response */ resp->code = -1; /* Check the headers and see if we got a response thingy */ - if(context->types & HA_TYPE_DIGEST) + if(context->opts.types & HA_TYPE_DIGEST) { header = ha_getheader(req, "Authorization", HA_PREFIX_DIGEST); if(header) { + ha_messagex(LOG_DEBUG, "simple: processing digest auth header"); ret = simple_digest_response(ctx, header, req->args[AUTH_ARG_METHOD], req->args[AUTH_ARG_URI], resp, buf); if(ret < 0) @@ -715,8 +722,9 @@ int simple_process(ha_context_t* context, ha_request_t* req, } /* Or a basic authentication */ - if(!header && context->types & HA_TYPE_BASIC) + if(!header && context->opts.types & HA_TYPE_BASIC) { + ha_messagex(LOG_DEBUG, "simple: processing basic auth header"); header = ha_getheader(req, "Authorization", HA_PREFIX_BASIC); if(header) { @@ -732,17 +740,18 @@ int simple_process(ha_context_t* context, ha_request_t* req, { resp->code = HA_SERVER_DECLINE; - if(context->types & HA_TYPE_BASIC) + if(context->opts.types & HA_TYPE_BASIC) { - ha_bufmcat(buf, "BASIC realm=\"", ctx->realm , "\"", NULL); + ha_bufmcat(buf, "BASIC realm=\"", ctx->opts->realm , "\"", NULL); if(ha_buferr(buf)) return HA_CRITERROR; ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf)); + ha_messagex(LOG_DEBUG, "simple: sent basic auth request"); } - if(context->types & HA_TYPE_DIGEST) + if(context->opts.types & HA_TYPE_DIGEST) { ret = simple_digest_challenge(ctx, resp, buf, 0); if(ret < 0) diff --git a/doc/httpauth.conf.5 b/doc/httpauth.conf.5 new file mode 100644 index 0000000..fd26c7b --- /dev/null +++ b/doc/httpauth.conf.5 @@ -0,0 +1,82 @@ +.Dd April, 2004 +.Dt HTTPAUTH.CONF 5 +.Os httpauth +.Sh NAME +.Nm httpauth.conf +.Nd the configuration file for +.Em httpauthd +.Sh DESCRIPTION +The XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx +.Nm +scripting language is a regular expression language used for fine grained, +buffer based search and replace. It is not limited to lines. A full description +of what +.Nm +is capable of is outside the scope of this document. +.Pp +.Ar script +is a text or compiled +.Nm +script. For details see the language documentation that came along with the distribution. +.Pp +When used with the +.Fl f +argument +.Nm +replaces files in place. Otherwise it reads from +.Ar infile +and writes to +.Ar outfile +\&. If either infile or outfile are missing or are equal to a dash +.Sq Li - +, then rep processes +.Em stdin +or +.Em stdout +respectively. +.Sh OPTIONS +The options are as follows: +.Bl -tag -width Fl +.It Fl b +Backup files where replacements have occurred. The backup files have an +.Sq x_r +extension appended to their filename. +.It Fl i +Prompt for confirmation before each replacement. +.It Fl p +Only output replaced text. Can be used as a rudimentary parser. +.It Fl q +Supress status messages. Only errors will be sent to stderr. +.It Fl z +Set the replacement buffer size to +.Ar buffsize . +This speeds up execution as regular expressions only have to act on a small +portion of the whole file at once. However the largest match will be limited to +roughly +.Ar buffsize +, so use this option with care. The script loops over each buffer until no more +matches are found within it. Care is taken to overlap the buffers as much as +possible to ensure that any match smaller than +.Ar buffsize +can be matched. +.Sh NOTE +The +.Nm +command uses +.Xr getopt 3 +to parse it's arguments, which allows it to accept +the +.Sq Li -- +option which will stop processing of flag options at that point. This allows +the processing of files with names that begin with a dash +.Pq Sq - . +.Sh BUGS +When reading from +.Em stdin +you must specify a buffer size. +.Sh SEE ALSO +.Xr repc 1 , +.Xr rlib 3 , +.Xr pcre 3 +.Sh AUTHOR +.An Nate Nielsen Aq nielsen@memberwebs.com
\ No newline at end of file diff --git a/doc/httpauth.conf.sample b/doc/httpauth.conf.sample new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/doc/httpauth.conf.sample diff --git a/doc/protocol.txt b/doc/protocol.txt index 05e32f2..b6bc0ba 100644 --- a/doc/protocol.txt +++ b/doc/protocol.txt @@ -1,22 +1,39 @@ - HTTP/AUTH PROTOCOL + HTTPAUTH PROTOCOL The protocol used between the stateful authenticator and the web servers that wish to authenticate is described below. It's a simple text protocol, -similar to HTTP. The web servers send commands and headers to the daemon, +similar to HTTP. The web server sends commands and headers to the daemon, which replies with HTTP codes and headers. Multiple authentication requests can be processed on the same connection, -although the connection is not stateful. A authentication request initially -processed through one connection to the daemon can later be completed -through another. The commands are described below. +although the connection is not necessarily stateful. A authentication +request initially processed through one connection to the daemon can +later be completed through another. The commands are described below. -AUTH method uri +After connecting to the daemon, you'll need to retrieve the initial +'ready' response before sending requests. See below. If the server +responds with a 5xx message then something's gone wrong and no requests +will be accepted on the connection. + + +REQUESTS ------------------------------------------------------------------- + +AUTH authmethod connid method uri The AUTH command asks the daemon to perform authentication - for a given set of headers. + for a given set of header from the client. None of the + arguments should contain spaces. + + authmethod: is the authentication method. Methods are + defined by the daemon in it's config file. + + connid: a unique string identifying the connection from + the client. This is only important when NTLM is + being used. If not, pass a random string. + + method: The HTTP method employed. 'GET' or 'POST' etc... - method: is the authentication type. It might be 'NTLM'. uri: the URI being authenticated. The AUTH command is followed by HTTP headers, one per line @@ -24,23 +41,63 @@ AUTH method uri authentication headers for the authentication protocol being used. Extraneous headers are ignored. - If multiple HTTP headers with the same name are received, then - the last one is used. Note that this is somewhat different than - the HTTP protocol. + Headers should be specified on one line, not 'wrapped' as is + permissible in HTTP. QUIT This closes the connection to the daemon. -The response from the daemon consists of an HTTP code, followed by headers -one per line. Note that only the headers to be added for authentication are -returned. For example: - 401 - Header: value - Header2: value +RESPONSES ------------------------------------------------------------------- + +The response from the daemon consists of a code, followed by a detail +message value or set of values. This is separated from the code by a +space. The content of the detail message is described below. + +The codes are similar to HTTP: + + 100 Ready + (detail is the list of available authmethods) + + 200 Successful Request + (detail is described below) + + 4xx Request Error + (detail is an error message) + + 5xx Server Error + (detail is an error message) + + +READY + +After opening a connection to the daemon, you should receive a response +(outlined below) of 100 indicating ready. The 'detail' value is set +to the list of authmethods that the daemon is configured to provide. +These are separated by spaces: + + 100 Domain Simple Test LDAP + + +SUCCESS + +Successful processing of a request returns a 200. The detail constists of, +an HTTP code to send to the client and the user name (when authentication is +successful). These are separated by spaces. + +In addition the daemon might send a set of headers that must be sent to +the client. These are ended by a blank line. + +A response after client authentication failed might look like this: + + 200 401 + WWW-Authenticate: realm="blah" + +... or a client authentication success response like this: + + 200 200 testo + Authorization-Info: Digest rspauth="2034980294820398" nonce="2049823094328" ... -Success returns a 200, just like normal HTTP. Note that success can contain -headers that must also be sent to the client. diff --git a/sample/httpauthd.conf b/sample/httpauthd.conf index ba8af0a..ae59ce2 100644 --- a/sample/httpauthd.conf +++ b/sample/httpauthd.conf @@ -4,6 +4,7 @@ MaxThreads: 18 CacheTimeout: 300 AuthTypes: Basic Digest +Socket: 0.0.0.0:8020 [Simple] Realm: blah |