#include #include #include #include #include #include #include #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); if(strcasecmp(name, "WWW-Authenticate") == 0) { if(strncasecmp(line, AUTH_PREFIX_BASIC, strlen(AUTH_PREFIX_BASIC)) == 0 && !(ctx->types & AUTH_TYPE_BASIC)) continue; else if(strncasecmp(line, AUTH_PREFIX_DIGEST, strlen(AUTH_PREFIX_DIGEST)) == 0 && !(ctx->types & AUTH_TYPE_DIGEST)) continue; else if(strncasecmp(line, 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; /* Fix up when we're a proxy */ if(r->proxyreq == STD_PROXY) name = "Proxy-Authenticate"; } else if(strcasecmp(name, "Authentication-Info") == 0) { if(r->proxyreq == STD_PROXY) 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; request_rec* mainreq; /* 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; mainreq = r; while(mainreq->main != NULL) mainreq = mainreq->main; while(mainreq->prev != NULL) mainreq = mainreq->prev; /* Check if we've already authenticated this request */ if(ap_get_module_config(mainreq->request_config, &httpauth_module)) return OK; 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; /* Mark request as successfully authenticated */ ap_set_module_config(r->request_config, &httpauth_module, details); return OK; } return ccode; } static int httpauth_access(request_rec *r) { /* TODO: We need to support require directives */ return OK; } /* 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"