diff options
Diffstat (limited to 'daemon/httpauthd.c')
-rw-r--r-- | daemon/httpauthd.c | 1031 |
1 files changed, 1031 insertions, 0 deletions
diff --git a/daemon/httpauthd.c b/daemon/httpauthd.c new file mode 100644 index 0000000..8af5d7f --- /dev/null +++ b/daemon/httpauthd.c @@ -0,0 +1,1031 @@ + +#include <sys/types.h> +#include <stddef.h> +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdarg.h> +#include <syslog.h> +#include <pthread.h> +#include <fcntl.h> +#include <err.h> +#include <sys/un.h> +#include <sys/socket.h> + +#include "usuals.h" +#include "httpauthd.h" + +/* ----------------------------------------------------------------------- + * Handlers Registered Here + */ + +extern ha_handler_t basic_handler; + +/* This is the list of all available handlers */ +const ha_handler_t* g_handlerlist[] = +{ + &basic_handler +}; + +typedef struct httpauth_loaded +{ + ha_context_t ctx; + struct httpauth_loaded* next; +} +httpauth_loaded_t; + +/* The list of handlers in use */ +httpauth_loaded_t* g_handlers = NULL; + +/* ----------------------------------------------------------------------- + * Structures and Constants + */ + +/* A command definition. Used in parsing */ +typedef struct httpauth_command +{ + const char* name; + int code; + int args; + const char** headers; +} +httpauth_command_t; + +/* The various valid headers for the auth command */ +const char* kAuthHeaders[] = +{ + "Authorization", + "Proxy-Authorization", + NULL +}; + +/* The command definitions */ +const httpauth_command_t kCommands[] = +{ + { "auth", REQTYPE_AUTH, 2, kAuthHeaders }, + { "quit", REQTYPE_QUIT, 0, 0 }, + { NULL, -1, -1 } +}; + +typedef struct httpauth_thread +{ + pthread_t tid; + int fd; +} +httpauth_thread_t; + +/* ----------------------------------------------------------------------- + * Default Settings + */ + +#define DEFAULT_CONFIG "/usr/local/etc/httpauthd.conf" +#define DEFAULT_SOCKET "/var/run/httpauthd.sock" +#define DEFAULT_MAXTHREADS 32 + + +/* ----------------------------------------------------------------------- + * Globals + */ + +int g_daemonized = 0; /* Currently running as a daemon */ +int g_debugging = 0; /* In debug mode */ +const char* g_socket = DEFAULT_SOCKET; /* The socket to communicate on */ +int g_maxthreads = DEFAULT_MAXTHREADS; /* The maximum number of threads */ + +/* For main loop and signal handlers */ +int g_quit = 0; + +/* The main thread */ +pthread_t g_mainthread; + +/* The main mutex */ +pthread_mutex_t g_mutex; +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); + + +/* ----------------------------------------------------------------------- + * Main Program + */ + +int main(int argc, char* argv[]) +{ + const char* conf = DEFAULT_CONFIG; + httpauth_thread_t* threads = NULL; + const httpauth_loaded_t* h; + int daemonize = 1; + ha_buffer_t cbuf; + int r, i, sock; + int ch = 0; + + /* Keep note of the main thread */ + g_mainthread = pthread_self(); + + /* Create the main mutex */ + if(pthread_mutexattr_init(&g_mutexattr) != 0 || + pthread_mutexattr_settype(&g_mutexattr, HA_MUTEX_TYPE) || + pthread_mutex_init(&g_mutex, &g_mutexattr) != 0) + errx(1, "threading problem. can't create mutex"); + + /* Parse the arguments nicely */ + while((ch = getopt(argc, argv, "df:X")) != -1) + { + switch(ch) + { + /* Don't daemonize */ + case 'd': + daemonize = 0; + break; + + /* The configuration file */ + case 'f': + conf = optarg; + break; + + /* Process console input instead */ + case 'X': + g_debugging = 1; + break; + + /* Usage information */ + case '?': + default: + return usage(); + break; + } + } + + argc -= optind; + argv += optind; + + if(argc != 0) + return usage(); + + + /* From here on out we need to quit in an orderly fashion */ + + /* Run global initialization on all handlers */ + for(i = 0; i < countof(g_handlerlist); i++) + { + if(g_handlerlist[i]->f_init) + { + if((r = (g_handlerlist[i]->f_init)(NULL)) == -1) + goto finally; + } + } + + /* Initialize our configuration buffer */ + ha_bufinit(&cbuf); + + + /* Parse the configuration */ + config_parse(conf, &cbuf); + + + if(!g_debugging) + { + struct sockaddr_un sau; + + /* Create the thread buffers */ + threads = (httpauth_thread_t*)malloc(sizeof(httpauth_thread_t) * g_maxthreads); + if(!threads) + errx(1, "out of memory"); + + /* Create the socket */ + sock = socket(AF_UNIX, 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); + + + /* Let 5 connections queue up */ + if(listen(sock, 5) != 0) + err(1, "couldn't listen on socket"); + } + + + /* TODO: Enable signal processing here */ + + /* Initialize all the handlers */ + for(h = g_handlers; h; h = h->next) + { + if(h->ctx.handler->f_init) + { + if((r = (h->ctx.handler->f_init)(&(h->ctx))) == -1) + goto finally; + } + } + + + /* This is for debugging the internal processes */ + if(g_debugging) + { + r = httpauth_processor(0, 1); + goto finally; + } + + + /* This is the daemon section of the code */ + else + { + /* TODO: Daemonize here, and disconnect from terminal */ + + /* Open the system log */ + openlog("httpauthd", 0, LOG_AUTHPRIV); + g_daemonized = 1; + + + /* 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: + break; + + case ECONNABORTED: + case EPROTO: + ha_message(LOG_ERR, "couldn't accept a connection"); + break; + + default: + ha_message(LOG_ERR, "couldn't accept a connection"); + g_quit = 1; + break; + }; + + continue; + } + + /* 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) + { + pthread_join(threads[i].tid, NULL); + threads[i].tid = 0; + } + } + + /* Start a new thread if neccessary */ + if(fd != 0 && threads[i].tid == 0) + { + threads[i].fd = fd; + r = pthread_create(&(threads[i].tid), NULL, httpauth_thread, + (void*)(threads + i)); + if(r != 0) + { + errno = r; + ha_message(LOG_ERR, "couldn't create thread"); + g_quit = 1; + break; + } + + fd = 0; + } + } + + /* Check to make sure we have a thread */ + if(fd != 0) + { + ha_messagex(LOG_ERR, "too many connections open (max %d)", g_maxthreads); + httpauth_respond(fd, HA_SERVER_ERROR, "too many connections"); + shutdown(fd, SHUT_RDWR); + } + } + + /* TODO: Quit all threads here */ + + r = 0; + } + +finally: + + /* Uninitialize all the handlers */ + for(h = g_handlers; h; h = h->next) + { + if(h->ctx.handler->f_destroy) + (h->ctx.handler->f_destroy)(&(h->ctx)); + } + + /* Run global destroy for all handlers */ + for(i = 0; i < countof(g_handlerlist); i++) + { + if(g_handlerlist[i]->f_destroy) + (g_handlerlist[i]->f_destroy)(NULL); + } + + /* Clean up memory and stuff */ + ha_buffree(&cbuf); + + + /* Close the mutex */ + pthread_mutex_destroy(&g_mutex); + pthread_mutexattr_destroy(&g_mutexattr); + return r == -1 ? 1 : 0; +} + +int usage() +{ + fprintf(stderr, "usage: httpauthd [-dX] [-f conffile]\n"); + return 2; +} + +void* httpauth_thread(void* arg) +{ + int fd = (int)arg; + int r = httpauth_processor(fd, fd); + return (void*)r; +} + +/* ----------------------------------------------------------------------- + * Command Parsing and Handling + */ + +int httpauth_read(int ifd, ha_request_t* req, + ha_buffer_t* buf) +{ + const httpauth_command_t* cmd; + char* t; + int i, r; + int more = 1; + + /* Clean up the request header */ + memset(req, 0, sizeof(*req)); + req->type = -1; + + /* This guarantees a bit of memory allocated, and resets buffer */ + ha_bufreset(buf); + + r = ha_readline(ifd, buf); + if(r == -1) + return -1; + + /* Check if this is the last line */ + if(r == 0) + more = 0; + + /* Check to see if we got anything */ + if(ha_buflen(buf) == 0) + { + req->type = REQTYPE_IGNORE; + return more; + } + + /* Find the first space in the line */ + t = ha_parseword(buf, " \t"); + + if(t) + { + /* Figure out which command it is */ + for(cmd = kCommands; cmd->name; cmd++) + { + if(strcasecmp(t, cmd->name) == 0) + { + req->type = cmd->code; + break; + } + } + } + + /* Check for invalid command */ + if(req->type == -1) + return more; + + /* Now parse the arguments if any */ + for(i = 0; i < cmd->args; i++) + req->args[i] = ha_parseword(buf, " \t"); + + + /* Now skip anything else we have in the buffer */ + ha_bufskip(buf); + + + /* If we need headers, then read them now */ + if(cmd->headers) + { + const char** head; /* For iterating through valid headers */ + int valid = 0; /* The last header was valid */ + i = 0; /* The header we're working with */ + + for(;;) + { + /* Make sure we have more data */ + if(!more) + break; + + r = ha_readline(ifd, buf); + if(r == -1) + return -1; + + /* Check if this is the last line */ + if(r == 0) + more = 0; + + /* An empty line is the end of the headers */ + if(ha_buflen(buf) == 0) + break; + + /* Check if the header starts with a space */ + if(isspace(ha_bufchar(buf))) + { + /* Skip all the spaces */ + while(ha_buflen(buf) > 0 && isspace(ha_bufchar(buf))) + ha_bufeat(buf); + + /* An empty line is the end of the headers + even if that line has spaces on it */ + if(ha_buflen(buf) == 0) + break; + + /* A header that has data on it but started + with a space continues the previous header */ + if(valid && i > 0) + { + t = ha_parseline(buf, 0); + if(t) + { + char* t2 = (char*)req->headers[i - 1].data + strlen(req->headers[i - 1].data); + + /* Fill the area between the end of the last + valid header and this with spaces */ + memset(t2, ' ', t - t2); + } + } + } + else + { + if(i < MAX_HEADERS) + { + t = ha_parseword(buf, ":"); + + if(t) + { + for(head = cmd->headers; ; head++) + { + if(!(*head)) + { + t = NULL; + break; + } + + if(strcasecmp(t, *head) == 0) + break; + } + } + + if(t) + { + req->headers[i].name = t; + req->headers[i].data = ha_parseline(buf, 1); + i++; + } + + valid = (t != NULL) ? 1 : 0; + } + } + + ha_bufskip(buf); + } + } + + return more; +} + +int write_data(int ofd, const char* data) +{ + int r; + + while(*data != 0) + { + r = write(ofd, data, strlen(data)); + + if(r > 0) + data += r; + + else if(r == -1) + { + if(errno == EAGAIN || errno == EINTR) + continue; + + /* The other end closed. no message */ + if(errno != EPIPE) + ha_message(LOG_ERR, "couldn't write data"); + + return -1; + } + } + + return 0; +} + +int httpauth_respond(int ofd, int code, const char* msg) +{ + const char* t; + char num[16]; + + sprintf(num, "%d", code); + + if(write_data(ofd, num) == -1 || + write_data(ofd, " ") == -1) + return -1; + + switch(code) + { + case HA_SERVER_ERROR: + t = "Internal Error "; + break; + case HA_SERVER_BADREQ: + t = "Bad Request "; + break; + case HA_SERVER_DECLINE: + t = "Unauthorized "; + break; + default: + t = NULL; + break; + }; + + if(t && write_data(ofd, t) == -1) + return -1; + + if(msg) + { + if(write_data(ofd, "[") == -1 || + write_data(ofd, msg) == -1 || + write_data(ofd, "]") == -1) + return -1; + } + + return write_data(ofd, "\n"); +} + +const char kHeaderDelimiter[] = ": "; + +int httpauth_write(int ofd, ha_response_t* resp) +{ + int i; + int wrote = 0; + + if(httpauth_respond(ofd, resp->code, resp->detail) == -1) + return -1; + + for(i = 0; i < MAX_HEADERS; i++) + { + if(resp->headers[i].name) + { + if(write_data(ofd, resp->headers[i].name) == -1 || + write_data(ofd, kHeaderDelimiter) == -1 || + write_data(ofd, resp->headers[i].data) == -1 || + write_data(ofd, "\n") == -1) + return -1; + + wrote = 1; + } + } + + if(wrote && write_data(ofd, "\n") == -1) + return -1; + + return 0; +} + +int httpauth_ready(int ofd, ha_buffer_t* buf) +{ + const char* t; + httpauth_loaded_t* h; + + /* We send a ready banner to our client */ + ha_bufnext(buf); + + for(h = g_handlers; h; h = h->next) + ha_bufcat(buf, (h == g_handlers) ? "" : " ", + h->ctx.name, NULL); + + if(ha_buferr(buf)) + return httpauth_respond(ofd, HA_SERVER_ERROR, NULL); + else + return httpauth_respond(ofd, HA_SERVER_READY, ha_bufdata(buf)); +} + +int httpauth_processor(int ifd, int ofd) +{ + ha_buffer_t inb; + ha_buffer_t outb; + ha_request_t req; + ha_response_t resp; + int result = -1; + int r; + + /* Initialize the memory buffers */ + ha_bufinit(&inb); + ha_bufinit(&outb); + + if(httpauth_ready(ofd, &outb) == -1) + { + result = 1; + goto finally; + } + + /* Now loop and handle the commands */ + while(result == -1) + { + ha_bufreset(&outb); + ha_bufreset(&inb); + + r = httpauth_read(ifd, &req, &inb); + if(r == -1 || ha_buferr(&inb)) + { + httpauth_respond(ofd, HA_SERVER_ERROR, NULL); + result = 1; + continue; + } + + if(r == 0) + result = 0; + + switch(req.type) + { + case REQTYPE_AUTH: + + r = process_auth(&req, &resp, &outb); + if(r == -1 || ha_buferr(&outb)) + { + httpauth_respond(ofd, HA_SERVER_ERROR, NULL); + result = 1; + continue; + } + + if(httpauth_write(ofd, &resp) == -1) + { + result = 1; + continue; + } + + break; + + + case REQTYPE_QUIT: + result = 0; + break; + + + default: + if(httpauth_respond(ofd, HA_SERVER_BADREQ, "Unknown command") == -1) + { + result = -1; + continue; + } + + break; + }; + } + + if(ifd == ofd) + shutdown(ofd, SHUT_RDWR); + else + close(ofd); + +finally: + ha_buffree(&outb); + ha_buffree(&inb); + + return result; +} + +int process_auth(ha_request_t* req, ha_response_t* resp, + ha_buffer_t* outb) +{ + const httpauth_loaded_t* h; + + /* Clear out our response */ + memset(resp, 0, sizeof(*resp)); + + /* Check our connection argument */ + if(!req->args[1] || !(req->args[1][0])) + { + ha_messagex(LOG_ERR, "Missing connection ID in request"); + resp->detail = "Missing connection ID"; + resp->code = HA_SERVER_BADREQ; + return 0; + } + + /* Find a handler for this type */ + for(h = g_handlers; h; h = h->next) + { + if(strcasecmp(h->ctx.name, req->args[0]) == 0) + { + /* Now let the handler handle it */ + if(h->ctx.handler->f_process) + return (h->ctx.handler->f_process)(&(h->ctx), req, resp, outb); + + return 0; + } + } + + ha_messagex(LOG_ERR, "Unknown authentication type: %s", req->args[0]); + resp->detail = "Unknown authentication type"; + resp->code = HA_SERVER_BADREQ; + return -1; +} + +/* ----------------------------------------------------------------------- + * Configuration + */ + +ha_context_t* config_addhandler(ha_buffer_t* buf, const char* alias, + const ha_handler_t* handler, const ha_context_t* defaults) +{ + httpauth_loaded_t* loaded; + int len = sizeof(httpauth_loaded_t) + handler->context_size; + + loaded = (httpauth_loaded_t*)ha_bufmalloc(buf, len); + if(!loaded) + errx(1, "out of memory"); + + memset(loaded, 0, len); + memcpy(&(loaded->ctx), defaults, sizeof(ha_context_t)); + + if(handler->context_size) + { + void* mem = ((unsigned char*)loaded) + sizeof(httpauth_loaded_t); + + /* Initialize the defaults properly */ + if(handler->context_default) + memcpy(mem, handler->context_default, handler->context_size); + + loaded->ctx.data = mem; + } + + else + { + loaded->ctx.data = NULL; + } + + loaded->ctx.name = (char*)alias; + loaded->ctx.handler = handler; + + if(!g_handlers) + { + g_handlers = loaded; + } + else + { + httpauth_loaded_t* l = g_handlers; + + for(;;) + { + if(strcasecmp(alias, l->ctx.name) == 0) + errx(1, "duplicate handler section for '%s'", alias); + + if(!(l->next)) + break; + + l = l->next; + } + + l->next = loaded; + } + + return &(loaded->ctx); +} + + +int config_parse(const char* file, ha_buffer_t* buf) +{ + ha_context_t defaults; + ha_context_t* ctx = NULL; + int line = 0; + int fd; + const char** t; + char* name; + const char* value; + int more = 1; + int recog; + int r, i; + + /* Open the configuration file */ + fd = open(file, O_RDONLY); + if(fd == -1) + err(1, "couldn't open configuration file: %s", file); + + /* These are the default options for the contexts */ + memset(&defaults, 0, sizeof(defaults)); + defaults.types = 0xFFFFFFFF; /* All types by default */ + defaults.timeout = DEFAULT_TIMEOUT; /* Timeout for cache */ + + ha_bufreset(buf); + + /* Read each line and process */ + while(more) + { + ha_bufskip(buf); + + if((more = ha_readline(fd, buf)) == -1) + return -1; + + line++; + + /* Eat all white space at beginning of line */ + while(ha_buflen(buf) && isspace(ha_bufchar(buf))) + ha_bufeat(buf); + + /* Skip blank lines */ + if(ha_buflen(buf) == 0) + continue; + + /* Skip comment lines */ + if(ha_bufchar(buf) == '#') + continue; + + /* Check for a handler */ + if(ha_bufchar(buf) == '[') + { + const ha_handler_t* handler = NULL; + const char* x; + + ha_bufeat(buf); + name = ha_parseline(buf, 1); + + if(!name || name[strlen(name) - 1] != ']') + errx(1, "configuration section invalid (line %d)", line); + + + /* remove the bracket */ + name[strlen(name) - 1] = 0; + + + /* Look for a handler with this type */ + for(i = 0; i < countof(g_handlerlist); i++) + { + if(g_handlerlist[i] && g_handlerlist[i]->type && + strcasecmp(name, g_handlerlist[i]->type) == 0) + { + handler = g_handlerlist[i]; + break; + } + } + + if(handler == NULL) + errx(1, "unknown handler type '%s' (line %d)", name, line); + + /* If we had a last handler then add it to handlers */ + loaded = config_addhandler(buf, name, handler, defaults); + + /* Rest doesn't apply to handler headers */ + continue; + } + + + /* Parse out the name */ + name = ha_parseword(buf, ":"); + if(!name || !name[0]) + errx(1, "configuration file invalid (line %d)", line); + + strlwr(name); + + /* And get the rest of the line */ + value = ha_parseline(buf, 1); + if(value == NULL) + errx(1, "configuration missing value at (line %d)", line); + + + recog = 0; + + /* Is this the global section? */ + if(!ctx) + { + /* Look and see if that's a name we want */ + if(strcmp("socket", name) == 0) + { + g_socket = value; + recog = 1; + } + + else if(strcmp("maxthreads", name) == 0) + { + if(ha_confint(value, 1, 256, &g_maxthreads) == -1) + errx(1, "invalid value for '%s'. must be a number between 1 and 256", name); + recog = 1; + } + + } + + /* Otherwise we're in a handler */ + else + { + if(strcmp("alias", name) == 0) + { + loaded->alias = value; + recog = 1; + } + + if(loaded->ctx.handler->f_config) + { + r = (loaded->ctx.handler->f_config)(&(loaded->ctx), name, value); + if(r == -1) + return -1; + + if(!recog && r) + recog = 1; + } + } + + /* Options that are legal in both global and internal sections */ + if(!recog) + { + if(strcmp(name, "cachetimeout") == 0) + { + int v; + if(ha_confint(name, value, 0, 86400, &v) == HA_ERROR) + exit(1); /* Message already printed */ + + (ctx ? ctx : &defaults)->timeout = v; + recog = 1; + } + + else if(strcmp(name, "authtypes") == 0) + { + int types = 0; + char* t; + + strlwr(value); + + /* Split the line into tokens at the spaces */ + while(*value) + { + while(*value && isspace(*value)) + value++; + + t = value; + + while(*t && !isspace(*t)) + t++; + + if(strncmp(value, "basic", 5) == 0) + types |= HA_TYPE_BASIC; + + else if(strncmp(value, "digest", 6) == 0) + types |= HA_TYPE_DIGEST; + + else if(strncmp(value, "ntlm", 4) == 0) + types |= HA_TYPE_NTLM; + + else + errx(1, "invalid type for '%s': %s (line %d)", name, value, line); + + value = t; + } + + if(types == 0) + errx(1, "no authentication types for '%s' (line %d)", name, line); + + (ctx ? ctx : &defaults)->types = types; + recog = 1; + } + } + + if(!recog) + errx(1, "unrecognized configuration setting '%s' (line %d)", name, line); + } + + if(!g_handlers) + ha_messagex(LOG_INFO, "no handlers found in configuration file"); + + return 0; +} |