/* * HttpAuth * * Copyright (C) 2004 Stefan Walter * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 59 Temple Place, Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usuals.h" #include "httpauthd.h" #include "defaults.h" #include "common/hash.h" #include "common/server-mainloop.h" #include "common/sock-any.h" #include "common/stringx.h" #include "common/tpool.h" /* ----------------------------------------------------------------------- * Handlers Registered Here */ extern ha_handler_t simple_handler; extern ha_handler_t dummy_handler; extern ha_handler_t ldap_handler; extern ha_handler_t ntlm_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_NTLM &ntlm_handler, #endif #if WITH_PGSQL &pgsql_handler, #endif #if WITH_MYSQL &mysql_handler, #endif &simple_handler, &dummy_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; extern int pthread_mutexattr_settype (pthread_mutexattr_t *attr, int kind); 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_MINTHREADS 8 #define DEFAULT_MAXTHREADS 128 /* ----------------------------------------------------------------------- * Globals */ int g_debuglevel = LOG_ERR; /* what gets logged to console */ int g_daemonized = 0; /* Currently running as a daemon */ static int g_console = 0; /* debug mode read write from console */ static const char* g_socket = DEFAULT_SOCKET; /* The socket to communicate on */ static int g_maxthreads = DEFAULT_MAXTHREADS; /* The maximum number of threads */ static int g_minthreads = DEFAULT_MINTHREADS; /* The maximum number of threads */ static unsigned int g_unique = 0x10000; /* A unique identifier (incremented) */ static hsh_t *g_requests = NULL; /* All the watched (ie: active) requests */ /* 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 accept_handler (int fd, int type, void *arg); static void close_all (void); 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_loaded_t* h; struct sockaddr_any sany; int daemonize = 1; ha_buffer_t cbuf; int r, i, sock; int ch = 0; /* 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); /* A hash table id -> request */ g_requests = hsh_create (sizeof (int)); if (g_requests == NULL) err(1, "out of memory"); if(!g_console) { /* 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"); ha_request_loop(0, 1); r = 0; 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; } if(pidfile != NULL) 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_DAEMON); ha_messagex(NULL, LOG_DEBUG, "accepting connections"); /* Initialize server stuff */ if (server_init () < 0) { ha_message (NULL, LOG_CRIT, "couldn't server internals"); exit (1); } /* The thread pool */ if (tpool_init (g_maxthreads, g_minthreads, 1) < 0) { ha_message (NULL, LOG_CRIT, "couldn't initialize thread pool"); exit (1); } /* Wait on various messages */ if (server_watch (sock, SERVER_READ, accept_handler, NULL)) { ha_message (NULL, LOG_CRIT, "couldn't watch socket properly"); exit (1); } if (server_run () < 0) ha_message (NULL, LOG_ERR, "error running main loop"); close_all (); tpool_destroy (1); server_uninit (); 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) { server_stop (); 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); } } /* ----------------------------------------------------------------------- * Connection Handling */ static 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) continue; /* The other end closed. no message */ if (errno != EPIPE) ha_message(NULL, LOG_ERR, "couldn't write data"); return HA_CRITERROR; } } return HA_OK; } /* Called when we cannot process a request */ static void httpauth_respond_busy (int fd) { char buf[16]; int l; assert (fd != -1); /* Make it non blocking */ fcntl (fd, F_SETFL, fcntl (fd, F_GETFL, 0) | O_NONBLOCK); /* Now read all the data sent from the client */ for(;;) { l = read (fd, buf, sizeof (buf)); if (l <= 0) break; } /* Back to blocking mode */ fcntl (fd, F_SETFL, fcntl (fd, F_GETFL, 0) & ~O_NONBLOCK); write_data (fd, "500 server too busy\n"); } /* * Called when a new connection is made, we initialize * the new connection in a thread */ static void accept_handler (int sock, int type, void *arg) { int fd; fd = accept(sock, NULL, NULL); if (fd == -1) { switch (errno) { case EINTR: case EAGAIN: return; case ECONNABORTED: ha_message (NULL, LOG_ERR, "couldn't accept a connection"); break; default: ha_message (NULL, LOG_CRIT, "couldn't accept a connection"); server_stop (); break; }; } /* Try to queue the request, or send back too busy if cannot */ if (tpool_add_work (ha_request_setup_handler, (void*)fd) < 0) { ha_message (NULL, LOG_ERR, "too many requests active (max %d)", g_maxthreads); httpauth_respond_busy (fd); shutdown (fd, SHUT_RDWR); close (fd); } } static void close_all (void) { hsh_index_t *hi; ha_request_t *rq; ha_lock (NULL); /* Get all the connections out of a wait state */ for (hi = hsh_first (g_requests); hi; hi = hsh_next (hi)) { rq = hsh_this (hi, NULL); shutdown (rq->ifd, SHUT_RDWR); } ha_unlock (NULL); } /* * Called when a connection has data available on it, we * process the data in a thread. */ static void httpauth_request_handler (int sock, int type, void *arg) { ha_request_t *rq = arg; assert (arg); /* Unregister this socket, until ready for more */ server_unwatch (sock); if (tpool_add_work (ha_request_process_handler, rq)) { ha_message (NULL, LOG_ERR, "too many requests active (max %d)", g_maxthreads); httpauth_respond_busy (rq->ofd); } } int ha_register_request (ha_request_t *rq) { int ret = HA_OK; ha_lock (NULL); rq->id = ++g_unique; if (!hsh_set (g_requests, &rq->id, rq)) { ha_message (rq, LOG_ERR, "couldn't register new connection"); ret = HA_CRITERROR; } ha_unlock (NULL); return ret; } int ha_register_watch (ha_request_t *rq) { int ret = HA_OK; ha_lock (NULL); if (server_watch (rq->ifd, SERVER_READ, httpauth_request_handler, rq) < 0) { ha_message (rq, LOG_ERR, "couldn't watch new connection"); ret = HA_CRITERROR; } ha_unlock (NULL); return ret; } void ha_unregister_request (ha_request_t *rq) { ha_lock (NULL); hsh_rem (g_requests, &rq->id); ha_unlock (NULL); } ha_context_t* ha_lookup_handler (const char *name) { httpauth_loaded_t* h; ha_context_t *ret = NULL; assert (name); ha_lock (NULL); for (h = g_handlers; h; h = h->next) { if (strcasecmp (h->ctx.name, name) == 0) { ret = (&h->ctx); break; } } ha_unlock (NULL); return ret; } /* ----------------------------------------------------------------------- * 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_-.0123456789" 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; 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, 0, 4096, &g_maxthreads) == -1) exit(1); recog = 1; } else if(strcmp("minthreads", name) == 0) { if(ha_confint(name, value, 0, 4096, &g_minthreads) == -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; char* v; strlwr(value); v = value; /* Split the line into tokens at the spaces */ while(*v) { v = trim_space(v); t = v; while(*t && !isspace(*t)) t++; if(strncmp(v, "basic", 5) == 0) types |= HA_TYPE_BASIC; else if(strncmp(v, "digest", 6) == 0) types |= HA_TYPE_DIGEST; else if(strncmp(v, "ntlm", 4) == 0) types |= HA_TYPE_NTLM; else errx(1, "invalid type for '%s': %s (line %d)", name, value, line); v = 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 */ ha_messagex(NULL, LOG_WARNING, "DigestIgnoreURI is deprecated, use DigestAllowAnyPath"); opts->digest_allowany = 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; } else if(strcmp(name, "digestallowanypath") == 0) { int v; if(ha_confbool(name, value, &v) < 0) exit(1); /* Message already printed */ opts->digest_allowany = 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; }