#include #include #include #include #include #include #include #include #include #include #include #include #include #include "usuals.h" #include "httpauthd.h" #include "defaults.h" /* ----------------------------------------------------------------------- * Handlers Registered Here */ extern ha_handler_t simple_handler; extern ha_handler_t ldap_handler; extern ha_handler_t ntlm_handler; /* This is the list of all available handlers */ ha_handler_t* g_handlerlist[] = { &simple_handler, &ldap_handler, &ntlm_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, 4, 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; 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, r; ASSERT(arg); fd = (int)arg; return (void*)httpauth_processor(fd, fd); } /* ----------------------------------------------------------------------- * 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; ASSERT(req && buf); ASSERT(ifd != -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_bufreadline(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_bufparseword(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; } } } else { req->type = REQTYPE_IGNORE; return more; } /* 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_bufparseword(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_bufreadline(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_bufparseline(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_bufparseword(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_bufparseline(buf, 1); i++; } valid = (t != NULL) ? 1 : 0; } } ha_bufskip(buf); } } return more; } int write_data(int ofd, const char* data) { int r; ASSERT(data); ASSERT(ofd != -1); 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]; ASSERT(ofd != -1); ASSERT(code > 99 && code < 1000); 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; ASSERT(ofd != -1); ASSERT(resp); 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; ASSERT(ofd != -1); ASSERT(buf); /* We send a ready banner to our client */ for(h = g_handlers; h; h = h->next) { if(h != g_handlers) ha_bufjoin(buf); ha_bufmcat(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; ASSERT(ifd != -1); ASSERT(ofd != -1); /* 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; case REQTYPE_IGNORE: 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) { httpauth_loaded_t* h; ASSERT(req && resp && outb); /* Clear out our response */ memset(resp, 0, sizeof(*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"); resp->detail = "Missing connection ID"; resp->code = HA_SERVER_BADREQ; return 0; } /* Check our uri argument */ if(!req->args[AUTH_ARG_URI] || !(req->args[AUTH_ARG_URI][0])) { ha_messagex(LOG_ERR, "Missing URI in request"); resp->detail = "Missing URI"; resp->code = HA_SERVER_BADREQ; return 0; } /* Check our connection arguments */ if(!req->args[AUTH_ARG_METHOD] || !(req->args[AUTH_ARG_METHOD][0])) { ha_messagex(LOG_ERR, "Missing method in request"); resp->detail = "Missing method"; 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, ha_handler_t* handler, const ha_context_t* defaults) { httpauth_loaded_t* loaded; int len; ASSERT(buf && alias && handler && defaults); 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; char* value; int more = 1; int recog; int r, i; ASSERT(file && buf); /* 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.cache_timeout = DEFAULT_TIMEOUT; /* Timeout for cache */ defaults.cache_max = DEFAULT_CACHEMAX; ha_bufreset(buf); /* Read each line and process */ while(more) { ha_bufskip(buf); if((more = ha_bufreadline(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) == '[') { ha_handler_t* handler = NULL; const char* x; ha_bufeat(buf); name = ha_bufparseline(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 */ ctx = config_addhandler(buf, name, handler, &defaults); /* Rest doesn't apply to handler headers */ continue; } /* Parse out the name */ name = ha_bufparseword(buf, ":"); if(!name || !name[0]) errx(1, "configuration file invalid (line %d)", line); strlwr(name); /* And get the rest of the line */ value = ha_bufparseline(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(name, value, 1, 256, &g_maxthreads) == -1) exit(1); recog = 1; } } /* Otherwise we're in a handler */ else { if(strcmp("alias", name) == 0) { ctx->name = value; recog = 1; } if(ctx->handler->f_config) { r = (ctx->handler->f_config)(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)->cache_timeout = v; recog = 1; } else if(strcmp(name, "cachemax") == 0) { int v; if(ha_confint(name, value, 0, 0x7FFFFFFF, &v) == HA_ERROR) exit(1); /* Message already printed */ (ctx ? ctx : &defaults)->cache_max = 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; }