/* * Copyright (c) 2004, Nate Nielsen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and * the following disclaimer in the documentation and/or * other materials provided with the distribution. * * The names of contributors to this software may not be * used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * * CONTRIBUTORS * Nate Nielsen * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usuals.h" #include "httpauthd.h" #include "defaults.h" #include "sock_any.h" #include "stringx.h" /* * This shouldn't be used by handlers, * they should return HA_FAILED instead. */ #define HA_SERVER_ERROR 500 /* ----------------------------------------------------------------------- * Handlers Registered Here */ extern ha_handler_t simple_handler; extern ha_handler_t ldap_handler; extern ha_handler_t pgsql_handler; extern ha_handler_t mysql_handler; /* This is the list of all available handlers */ ha_handler_t* g_handlerlist[] = { #if WITH_LDAP &ldap_handler, #endif #if WITH_PGSQL &pgsql_handler, #endif #if WITH_MYSQL &mysql_handler, #endif &simple_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 word_args; /* Arguments to be parsed as words */ int rest_arg; /* Parse remainder as one arg? */ const char** headers; /* Headers needed/allowed */ } httpauth_command_t; /* The various valid headers for the auth command */ const char* kAuthHeaders[] = { "Authorization", NULL }; /* The command definitions */ const httpauth_command_t kCommands[] = { { "auth", REQTYPE_AUTH, 3, 0, kAuthHeaders }, { "set", REQTYPE_SET, 1, 1, NULL }, { "quit", REQTYPE_QUIT, 0, 0, NULL }, { NULL, -1, 0, 0, NULL } }; typedef struct httpauth_thread { pthread_t tid; int fd; } httpauth_thread_t; /* ----------------------------------------------------------------------- * Default Settings */ #define DEFAULT_CONFIG CONF_PREFIX "/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_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 */ unsigned int g_unique = 0x10000; /* A unique identifier (incremented) */ /* 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 */ static int usage(); static void writepid(const char* pid); static void* httpauth_thread(void* arg); static int httpauth_processor(int ifd, int ofd); static int httpauth_respond(ha_request_t* rq, int ofd, int scode, int ccode, const char* msg); static int process_auth(ha_request_t* rq); static int config_parse(const char* file, ha_buffer_t* buf); static void on_quit(int signal); /* ----------------------------------------------------------------------- * Main Program */ int main(int argc, char* argv[]) { const char* conf = DEFAULT_CONFIG; const char* pidfile = NULL; httpauth_thread_t* threads = NULL; httpauth_loaded_t* h; char peername[MAXPATHLEN]; struct sockaddr_any sany; 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, "d:f:p:X")) != -1) { switch(ch) { /* Don't daemonize */ case 'd': { 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 */ case 'f': conf = optarg; break; /* Write out a pid file */ case 'p': pidfile = optarg; break; /* Process console input instead */ case 'X': g_console = 1; daemonize = 0; break; /* Usage information */ case '?': default: return usage(); break; } } argc -= optind; argv += optind; if(argc != 0) return usage(); ha_messagex(NULL, LOG_DEBUG, "starting up..."); /* 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_console) { /* Create the thread buffers */ threads = (httpauth_thread_t*)calloc(g_maxthreads, sizeof(httpauth_thread_t)); if(!threads) errx(1, "out of memory"); /* Get the socket type */ if(sock_any_pton(g_socket, &sany, SANY_OPT_DEFANY | SANY_OPT_DEFPORT(DEFAULT_PORT)) == -1) errx(1, "invalid socket name or ip: %s", g_socket); /* Create the socket */ sock = socket(SANY_TYPE(sany), SOCK_STREAM, 0); if(sock < 0) err(1, "couldn't open socket"); i = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&i, sizeof(i)); /* Unlink the socket file if it exists */ if(SANY_TYPE(sany) == AF_UNIX) unlink(g_socket); if(bind(sock, &SANY_ADDR(sany), SANY_LEN(sany)) != 0) err(1, "couldn't bind to address: %s", g_socket); /* Let 5 connections queue up */ if(listen(sock, 5) != 0) err(1, "couldn't listen on socket"); ha_messagex(NULL, LOG_DEBUG, "created socket: %s", g_socket); } /* 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_console) { ha_messagex(NULL, LOG_DEBUG, "processing from console"); r = httpauth_processor(0, 1); goto finally; } /* This is the daemon section of the code */ else { if(daemonize) { /* Fork a daemon nicely here */ if(daemon(0, 0) == -1) { ha_message(NULL, LOG_ERR, "couldn't run httpauth as daemon"); exit(1); } ha_messagex(NULL, LOG_DEBUG, "running as a daemon"); g_daemonized = 1; } writepid(pidfile); /* Handle some signals */ signal(SIGPIPE, SIG_IGN); signal(SIGHUP, SIG_IGN); signal(SIGINT, on_quit); signal(SIGTERM, on_quit); siginterrupt(SIGINT, 1); siginterrupt(SIGTERM, 1); /* Open the system log */ openlog("httpauthd", 0, LOG_AUTHPRIV); ha_messagex(NULL, LOG_DEBUG, "accepting connections"); /* Now loop and accept the connections */ while(!g_quit) { int fd; fd = accept(sock, NULL, NULL); if(fd == -1) { switch(errno) { case EINTR: case EAGAIN: break; case ECONNABORTED: ha_message(NULL, LOG_ERR, "couldn't accept a connection"); break; default: ha_message(NULL, LOG_ERR, "couldn't accept a connection"); g_quit = 1; break; }; if(g_quit) break; continue; } memset(&sany, 0, sizeof(sany)); SANY_LEN(sany) = sizeof(sany); /* 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 == -1) { ha_messagex(NULL, LOG_DEBUG, "cleaning up completed thread"); pthread_join(threads[i].tid, NULL); threads[i].tid = 0; } } /* Start a new thread if neccessary */ if(fd != -1 && 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(NULL, LOG_ERR, "couldn't create thread"); g_quit = 1; break; } ha_messagex(NULL, LOG_DEBUG, "created thread for connection: %d", fd); fd = -1; break; } } /* Check to make sure we have a thread */ if(fd != -1) { ha_messagex(NULL, LOG_ERR, "too many connections open (max %d)", g_maxthreads); httpauth_respond(NULL, fd, HA_SERVER_ERROR, 0, "too many connections"); shutdown(fd, SHUT_RDWR); } } ha_messagex(NULL, 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) { if(threads[i].fd != -1) shutdown(threads[i].fd, SHUT_RDWR); pthread_join(threads[i].tid, NULL); } } r = 0; } finally: ha_messagex(NULL, LOG_DEBUG, "performing cleanup..."); /* 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); ha_messagex(NULL, LOG_DEBUG, "stopped"); return r == -1 ? 1 : 0; } static void on_quit(int signal) { g_quit = 1; fprintf(stderr, "httpauthd: got signal to quit\n"); } static int usage() { fprintf(stderr, "usage: httpauthd [-d level] [-X] [-p pidfile] [-f conffile]\n"); return 2; } static void writepid(const char* pidfile) { FILE* f = fopen(pidfile, "w"); if(f == NULL) { warnx("couldn't open pid file: %s", pidfile); } else { fprintf(f, "%d\n", (int)getpid()); if(ferror(f)) warnx("couldn't write to pid file: %s", pidfile); fclose(f); } } static void* httpauth_thread(void* arg) { httpauth_thread_t* thread = (httpauth_thread_t*)arg; int r; siginterrupt(SIGINT, 1); siginterrupt(SIGTERM, 1); 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* rq) { const httpauth_command_t* cmd; const char* t; const char* t2; int i; if(g_debuglevel < LOG_DEBUG) return; if(rq->req_type == REQTYPE_IGNORE || rq->req_type == -1) return; ha_bufcpy(rq->buf, ""); for(i = 0; i < HA_MAX_ARGS; i++) { if(rq->req_args[i]) { ha_bufjoin(rq->buf); ha_bufmcat(rq->buf, ha_buflen(rq->buf) > 0 ? ", " : "", rq->req_args[i], NULL); } } t = ha_bufdata(rq->buf); t2 = NULL; /* Figure out which command it is */ for(cmd = kCommands; cmd->name; cmd++) { if(cmd->code == rq->req_type) { t2 = cmd->name; break; } } ASSERT(t2); ha_messagex(rq, LOG_DEBUG, "received request: [ type: %s / args: %s ]", t2, t); for(i = 0; i < HA_MAX_HEADERS; i++) { if(rq->req_headers[i].name) { ASSERT(rq->req_headers[i].data); ha_messagex(rq, LOG_DEBUG, "received header: [ %s: %s ]", rq->req_headers[i].name, rq->req_headers[i].data); } } } void log_response(ha_request_t* rq) { int i; if(g_debuglevel < LOG_DEBUG) return; ha_messagex(rq, LOG_DEBUG, "sending response: [ code: 200 / ccode: %d / detail: %s ]", rq->resp_code, rq->resp_detail ? rq->resp_detail : ""); for(i = 0; i < HA_MAX_HEADERS; i++) { if(rq->resp_headers[i].name) { ASSERT(rq->resp_headers[i].data); ha_messagex(rq, LOG_DEBUG, "sending header: [ %s: %s ]", rq->resp_headers[i].name, rq->resp_headers[i].data); } } } void log_respcode(ha_request_t* rq, int code, const char* msg) { if(g_debuglevel < LOG_DEBUG) return; ha_messagex(rq, LOG_DEBUG, "sending response: [ code: %d / detail: %s ]", code, msg ? msg : ""); } /* ----------------------------------------------------------------------- * Command Parsing and Handling */ static int httpauth_read(ha_request_t* rq, int ifd) { const httpauth_command_t* cmd; char* t; int i, r; int more = 1; ASSERT(r); ASSERT(ifd != -1); /* Clean up the request header */ rq->req_type = -1; memset(rq->req_args, 0, sizeof(rq->req_args)); memset(rq->req_headers, 0, sizeof(rq->req_headers)); /* This guarantees a bit of memory allocated, and resets buffer */ ha_bufreset(rq->buf); r = ha_bufreadline(ifd, rq->buf); if(r == -1) { ha_message(rq, LOG_ERR, "error reading from socket"); return -1; } /* Check if this is the last line */ if(r == 0) more = 0; /* Check to see if we got anything */ if(ha_buflen(rq->buf) == 0) { rq->req_type = REQTYPE_IGNORE; return more; } /* Find the first space in the line */ t = ha_bufparseword(rq->buf, " \t"); if(t) { /* Figure out which command it is */ for(cmd = kCommands; cmd->name; cmd++) { if(strcasecmp(t, cmd->name) == 0) { rq->req_type = cmd->code; break; } } } else { rq->req_type = REQTYPE_IGNORE; return more; } /* Check for invalid command */ if(rq->req_type == -1) return more; /* Now parse the arguments if any */ for(i = 0; i < cmd->word_args; i++) rq->req_args[i] = ha_bufparseword(rq->buf, " \t"); /* Does it want the rest as one argument? */ if(cmd->rest_arg) rq->req_args[i] = ha_bufparseline(rq->buf, 1); /* Now skip anything else we have in the buffer */ ha_bufskip(rq->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, rq->buf); if(r == -1) { ha_message(rq, LOG_ERR, "error reading from socket"); 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(rq->buf) == 0) break; /* Check if the header starts with a space */ if(isspace(ha_bufchar(rq->buf))) { /* Skip all the spaces */ while(ha_buflen(rq->buf) > 0 && isspace(ha_bufchar(rq->buf))) ha_bufeat(rq->buf); /* An empty line is the end of the headers even if that line has spaces on it */ if(ha_buflen(rq->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(rq->buf, 0); if(t) { char* t2 = (char*)rq->req_headers[i - 1].data + strlen(rq->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 < HA_MAX_HEADERS) { t = ha_bufparseword(rq->buf, ":"); if(t) { for(head = cmd->headers; ; head++) { if(!(*head)) { t = NULL; break; } if(strcasecmp(t, *head) == 0) break; } } if(t) { rq->req_headers[i].data = ha_bufparseline(rq->buf, 1); /* We always need to have data for a header */ if(rq->req_headers[i].data) { rq->req_headers[i].name = t; i++; } } valid = (t != NULL) ? 1 : 0; } } ha_bufskip(rq->buf); } } return more; } static int write_data(ha_request_t* rq, 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) continue; /* The other end closed. no message */ if(errno != EPIPE) ha_message(rq, LOG_ERR, "couldn't write data"); return HA_CRITERROR; } } return 0; } static int httpauth_respond(ha_request_t* rq, int ofd, int scode, int ccode, const char* msg) { char num[16]; ASSERT(ofd != -1); ASSERT(scode > 99 && scode < 1000); ASSERT(ccode == 0 || (ccode > 99 && ccode < 1000)); /* Can only have a client code when server code is 200 */ ASSERT(ccode == 0 || scode == HA_SERVER_OK); sprintf(num, "%d ", scode); if(write_data(rq, ofd, num) < 0) return HA_CRITERROR; if(ccode != 0) { sprintf(num, "%d ", ccode); if(write_data(rq, ofd, num) < 0) return HA_CRITERROR; } if(!msg) { switch(scode) { case HA_SERVER_ACCEPTED: msg = "Accepted"; break; case HA_SERVER_ERROR: msg = "Internal Error "; break; case HA_SERVER_BADREQ: msg = "Bad Request "; break; case HA_SERVER_DECLINE: msg = "Unauthorized "; break; default: msg = NULL; break; }; } if(msg && write_data(rq, ofd, msg) < 0) return HA_CRITERROR; /* When the client code is 0, then caller should log */ if(ccode == 0) log_respcode(rq, scode, msg); return write_data(rq, ofd, "\n"); } const char kHeaderDelimiter[] = ": "; static int httpauth_write(ha_request_t* rq, int ofd) { int i; int wrote = 0; ASSERT(ofd != -1); ASSERT(rq); if(httpauth_respond(rq, ofd, HA_SERVER_OK, rq->resp_code, rq->resp_detail) < 0) return HA_CRITERROR; for(i = 0; i < HA_MAX_HEADERS; i++) { if(rq->resp_headers[i].name) { if(write_data(rq, ofd, rq->resp_headers[i].name) == -1 || write_data(rq, ofd, kHeaderDelimiter) == -1 || write_data(rq, ofd, rq->resp_headers[i].data) == -1 || write_data(rq, ofd, "\n") == -1) return -1; wrote = 1; } } if(write_data(rq, ofd, "\n") == -1) return -1; log_response(rq); return 0; } static int httpauth_error(ha_request_t* rq, int ofd, int r) { int scode = 0; const char* msg = NULL; ASSERT(r < 0); switch(r) { case HA_BADREQ: scode = HA_SERVER_BADREQ; break; case HA_CRITERROR: msg = "Critical Error"; /* fall through */ case HA_FAILED: scode = HA_SERVER_ERROR; break; default: ASSERT(0 && "invalid error code"); break; }; return httpauth_respond(rq, ofd, scode, 0, msg); } static int httpauth_ready(ha_request_t* rq, int ofd) { const char* t; httpauth_loaded_t* h; ASSERT(ofd != -1); ASSERT(rq); /* We send a ready banner to our client */ if(CHECK_RBUF(rq)) return httpauth_error(rq, ofd, HA_CRITERROR); else return httpauth_respond(rq, ofd, HA_SERVER_READY, 0, "HTTPAUTH/1.0"); } static int httpauth_auth(ha_request_t* rq, int ofd) { int r; ASSERT(rq); if(!rq->context) { ha_messagex(rq, LOG_ERR, "no auth handler set"); return httpauth_respond(rq, ofd, HA_SERVER_BADREQ, 0, "No Auth Handler Set"); } /* Clear out our response */ rq->resp_code = -1; rq->resp_detail = NULL; memset(rq->resp_headers, 0, sizeof(rq->resp_headers)); /* Check our connection argument */ if(!rq->req_args[AUTH_ARG_CONN] || !(rq->req_args[AUTH_ARG_CONN][0])) { ha_messagex(rq, LOG_ERR, "missing connection ID in request"); return httpauth_respond(rq, ofd, HA_SERVER_BADREQ, 0, "Missing Connection ID"); } /* Check our uri argument */ if(!rq->req_args[AUTH_ARG_URI] || !(rq->req_args[AUTH_ARG_URI][0])) { ha_messagex(rq, LOG_ERR, "missing URI in request"); return httpauth_respond(rq, ofd, HA_SERVER_BADREQ, 0, "Missing URI"); } /* Check our connection arguments */ if(!rq->req_args[AUTH_ARG_METHOD] || !(rq->req_args[AUTH_ARG_METHOD][0])) { ha_messagex(rq, LOG_ERR, "missing HTTP method in request"); return httpauth_respond(rq, ofd, HA_SERVER_BADREQ, 0, "Missing HTTP Method"); } ASSERT(rq->context->handler && rq->context->handler->f_process); r = (rq->context->handler->f_process)(rq); if(r < 0) return r; if(httpauth_write(rq, ofd) < 0) return HA_CRITERROR; return HA_OK; } static int httpauth_set(ha_request_t* rq, ha_buffer_t* cbuf, int ofd) { httpauth_loaded_t* h; const char* name = rq->req_args[0]; const char* value = rq->req_args[1]; /* Check our name argument */ if(!name || !*name) { ha_messagex(rq, LOG_ERR, "missing name in SET request"); return HA_BADREQ; } if(strcasecmp(name, "Domain") == 0) { /* We need to copy this string so it doesn't get destroyed on next req */ rq->digest_domain = ha_bufcpy(rq->conn_buf, value ? value : ""); } else if(strcasecmp(name, "Handler") == 0) { if(!value || !*value) { ha_messagex(rq, LOG_ERR, "no auth handler specified in SET request."); return HA_BADREQ; } /* Find a handler for this type */ for(h = g_handlers; h; h = h->next) { if(strcasecmp(h->ctx.name, value) == 0) { rq->context = &(h->ctx); value = NULL; break; } } if(value != NULL) { ha_messagex(rq, LOG_ERR, "unknown authentication type: %s", rq->req_args[0]); return httpauth_respond(rq, ofd, HA_SERVER_BADREQ, 0, "Unknown Auth Handler"); } } else { ha_messagex(rq, LOG_ERR, "bad option in SET request"); return HA_BADREQ; } return httpauth_respond(rq, ofd, HA_SERVER_ACCEPTED, 0, NULL); } static void httpauth_conninfo(ha_request_t* rq, int fd) { struct sockaddr_any addr; char peername[MAXPATHLEN]; ha_messagex(rq, LOG_DEBUG, "processing %d on thread %x", fd, (int)pthread_self()); memset(&addr, 0, sizeof(addr)); SANY_LEN(addr) = sizeof(addr); /* Get the peer name */ if(getpeername(fd, &SANY_ADDR(addr), &SANY_LEN(addr)) == -1 || sock_any_ntop(&addr, peername, MAXPATHLEN, SANY_OPT_NOPORT) == -1) ha_messagex(rq, LOG_WARNING, "couldn't get peer address"); else ha_messagex(rq, LOG_INFO, "accepted connection from: %s", peername); } static int httpauth_processor(int ifd, int ofd) { ha_buffer_t cbuf; ha_buffer_t buf; ha_request_t rq; int result = -1; int r; ASSERT(ifd != -1); ASSERT(ofd != -1); memset(&rq, 0, sizeof(rq)); ha_lock(NULL); rq.id = g_unique++; ha_unlock(NULL); /* Used when processing a socket */ if(ifd == ofd) httpauth_conninfo(&rq, ifd); /* Initialize the memory buffers */ ha_bufinit(&buf); ha_bufinit(&cbuf); /* Set up some context stuff */ rq.digest_domain = ""; rq.buf = &buf; rq.conn_buf = &cbuf; if(httpauth_ready(&rq, ofd) == -1) { result = 1; goto finally; } /* Now loop and handle the commands */ while(result == -1) { ha_bufreset(&buf); r = httpauth_read(&rq, ifd); if(CHECK_RBUF(&rq)) r = HA_CRITERROR; if(r < 0) { httpauth_error(&rq, ofd, r); result = 1; break; } log_request(&rq); if(r == 0) result = 0; switch(rq.req_type) { case REQTYPE_AUTH: r = httpauth_auth(&rq, ofd); break; case REQTYPE_SET: r = httpauth_set(&rq, &cbuf, ofd); break; case REQTYPE_QUIT: r = HA_OK; result = 0; break; case REQTYPE_IGNORE: r = HA_FALSE; break; default: ha_messagex(&rq, LOG_WARNING, "received unknown command from client"); r = httpauth_respond(&rq, ofd, HA_SERVER_BADREQ, 0, "Unknown command"); break; }; if(CHECK_RBUF(&rq)) r = HA_CRITERROR; if(r < 0) { httpauth_error(&rq, ofd, r); if(r == HA_CRITERROR) result = 1; } } finally: if(ifd == ofd) { shutdown(ifd, SHUT_RDWR); close(ifd); } else { close(ifd); close(ofd); } ha_messagex(&rq, LOG_INFO, "closed connection"); ha_buffree(&cbuf); ha_buffree(&buf); return result; } /* ----------------------------------------------------------------------- * Configuration */ static 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); /* Setup the options from the defaults */ 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.ctx_data = mem; } else { loaded->ctx.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; } ha_messagex(NULL, LOG_DEBUG, "configuration: handler: %s (%s)", alias, handler->type); return &(loaded->ctx); } #define VALID_ALIAS_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWYZ_-." static int config_parse(const char* file, ha_buffer_t* buf) { ha_context_t defaults; ha_context_t* ctx = NULL; int line = 0; int fd; 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.allowed_types = 0xFFFFFFFF; /* All types by default */ defaults.cache_timeout = DEFAULT_TIMEOUT; /* Timeout for cache */ defaults.cache_max = DEFAULT_CACHEMAX; defaults.realm = ""; /* Read each line and process */ while(more) { ha_bufskip(buf); if((more = ha_bufreadline(fd, buf)) == -1) err(1, "couldn't read from configuration file: %s", file); 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, "handler section invalid (line %d)", line); /* remove the bracket */ name[strlen(name) - 1] = 0; /* * Take out any colon found, past which would * be an aliased name */ t = strchr(name, ':'); if(t != NULL) { *t = 0; t++; /* Rid of whitespace on ends */ t = trim_space(t); /* Validate the alias name */ if(!*t || strspn(t, VALID_ALIAS_CHARS) != strlen(t)) errx(1, "invalid name for handler: %s", t); } /* Rid of whitespace */ name = trim_space(name); /* 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, t ? t : 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(ctx->handler->f_config) { r = (ctx->handler->f_config)(ctx, name, value); if(r < 0) return r; if(!recog && r == HA_OK) recog = 1; } } /* Options that are legal in both global and internal sections */ if(!recog) { ha_context_t* opts = ctx ? ctx : &defaults; ASSERT(opts); if(strcmp(name, "cachetimeout") == 0) { int v; if(ha_confint(name, value, 0, 86400, &v) < 0) exit(1); /* Message already printed */ opts->cache_timeout = v; recog = 1; } else if(strcmp(name, "cachemax") == 0) { int v; if(ha_confint(name, value, 0, 0x7FFFFFFF, &v) < 0) exit(1); /* Message already printed */ opts->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) { value = trim_space(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 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); opts->allowed_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, "digestignorenc") == 0) { int v; if(ha_confbool(name, value, &v) < 0) exit(1); /* Message already printed */ opts->digest_ignorenc = v; 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(NULL, LOG_DEBUG, "configuration: setting: [ %s: %s ]", name, value); } if(!g_handlers) ha_messagex(NULL, LOG_WARNING, "configuration: no handlers found in configuration file"); return 0; }