/* * 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 "usuals.h" #include "httpauthd.h" #include "md5.h" #include #include #include #include extern int g_debuglevel; extern int g_daemonized; extern pthread_mutex_t g_mutex; /* For printing out sane logs */ static pthread_mutex_t g_log_mutex = PTHREAD_MUTEX_INITIALIZER; /* ----------------------------------------------------------------------- * Error Handling */ void ha_memerr(const ha_request_t* rq) { ha_messagex(rq, LOG_CRIT, "out of memory"); } const char kMsgDelimiter[] = ": "; #define MAX_MSGLEN 128 static void vmessage(const ha_request_t* rq, int level, int err, const char* msg, va_list ap) { size_t len; char* m; int e = errno; int x; if(g_daemonized) { if(level >= LOG_DEBUG) return; } else { if(g_debuglevel < level) return; } ASSERT(msg); len = strlen(msg) + 20 + MAX_MSGLEN; m = (char*)alloca(len); if(m) { if(rq) snprintf(m, len, "%06X: %s%s", rq->id, msg, err ? ": " : ""); else snprintf(m, len, "%s%s", msg, err ? ": " : ""); if(err) { /* TODO: strerror_r doesn't want to work for us strerror_r(e, m + strlen(m), MAX_MSGLEN); */ strncat(m, strerror(e), len); } m[len - 1] = 0; msg = m; } /* Either to syslog or stderr */ if(g_daemonized) vsyslog(level, msg, ap); else vwarnx(msg, ap); } void ha_messagex(const ha_request_t* rq, int level, const char* msg, ...) { va_list ap; pthread_mutex_lock (&g_log_mutex); va_start(ap, msg); vmessage(rq, level, 0, msg, ap); va_end(ap); pthread_mutex_unlock (&g_log_mutex); } void ha_message(const ha_request_t* rq, int level, const char* msg, ...) { va_list ap; pthread_mutex_lock (&g_log_mutex); va_start(ap, msg); vmessage(rq, level, 1, msg, ap); va_end(ap); pthread_mutex_unlock (&g_log_mutex); } /* ----------------------------------------------------------------------- * Header Functionality */ const ha_header_t* ha_findheader(const ha_request_t* rq, const char* name) { int i; ASSERT(rq && name); for(i = 0; i < HA_MAX_HEADERS; i++) { if(rq->req_headers[i].name) { if(strcasecmp(rq->req_headers[i].name, name) == 0) return &(rq->req_headers[i]); } } return NULL; } const char* ha_getheader(const ha_request_t* rq, const char* name, const char* prefix) { int i, l; ASSERT(rq && name); for(i = 0; i < HA_MAX_HEADERS; i++) { if(rq->req_headers[i].name) { if(strcasecmp(rq->req_headers[i].name, name) == 0) { if(!prefix) return rq->req_headers[i].data; l = strlen(prefix); if(strncasecmp(prefix, rq->req_headers[i].data, l) == 0) return rq->req_headers[i].data + l; } } } return NULL; } void ha_addheader(ha_request_t* rq, const char* name, const char* data) { int i = 0; ASSERT(rq && name && data); for(i = 0; i < HA_MAX_HEADERS; i++) { if(!(rq->resp_headers[i].name)) { rq->resp_headers[i].name = name; rq->resp_headers[i].data = data; return; } } ha_messagex(rq, LOG_WARNING, "too many headers in response. discarding '%s'", name); } /* ----------------------------------------------------------------------- * Locking */ void ha_lock(pthread_mutex_t* mtx) { int r; #ifdef _DEBUG int wait = 0; #endif if(!mtx) mtx = &g_mutex; #ifdef _DEBUG r = pthread_mutex_trylock(mtx); if(r == EBUSY) { wait = 1; ha_messagex(NULL, LOG_DEBUG, "thread will block: %d", pthread_self()); r = pthread_mutex_lock(mtx); } #else r = pthread_mutex_lock(mtx); #endif if(r != 0) { errno = r; ha_messagex(NULL, LOG_CRIT, "threading problem. couldn't lock mutex"); } #ifdef _DEBUG else if(wait) { ha_messagex(NULL, LOG_DEBUG, "thread unblocked: %d", pthread_self()); } #endif } void ha_unlock(pthread_mutex_t* mtx) { int r = pthread_mutex_unlock(mtx ? mtx : &g_mutex); if(r != 0) { errno = r; ha_message(NULL, LOG_CRIT, "threading problem. couldn't unlock mutex"); } } /* ----------------------------------------------------------------------- * Configuration */ int ha_confbool(const char* name, const char* conf, int* value) { ASSERT(name && value); if(conf == NULL || conf[0] == 0 || strcasecmp(conf, "0") == 0 || strcasecmp(conf, "no") == 0 || strcasecmp(conf, "false") == 0 || strcasecmp(conf, "f") == 0 || strcasecmp(conf, "off") == 0) { *value = 0; return HA_OK; } if(strcasecmp(conf, "1") == 0 || strcasecmp(conf, "yes") == 0 || strcasecmp(conf, "true") == 0 || strcasecmp(conf, "t") == 0 || strcasecmp(conf, "on") == 0) { *value = 1; return HA_OK; } ha_messagex(NULL, LOG_ERR, "invalid configuration value '%s': must be 'on' or 'off'.", name); return HA_FAILED; } int ha_confint(const char* name, const char* conf, int min, int max, int* value) { char* p; ASSERT(name && conf && value); ASSERT(min <= max); errno = 0; *value = strtol(conf, &p, 10); if(*p || errno == ERANGE || (*value < min) || (*value > max)) { ha_messagex(NULL, LOG_ERR, "invalid configuration value '%s': must be a number between %d and %d", name, min, max); return HA_FAILED; } return HA_OK; } /* ----------------------------------------------------------------------- * URI Functions */ char* ha_uriformat(ha_buffer_t* buf, const ha_uri_t* uri) { ASSERT(buf && uri); /* This creates a new block */ ha_bufcpy(buf, ""); if(uri->host) { const char* l = ""; const char* r = ""; ha_bufmcat(buf, uri->scheme ? uri->scheme : "http", "://", NULL); if(uri->user) { ha_bufjoin(buf); ha_bufmcat(buf, uri->user, uri->pw ? ":" : "", uri->pw ? uri->pw : "", "@", NULL); } if(strchr(uri->host, ':')) /* v6 ip */ { l = "["; r = "]"; } ha_bufjoin(buf); ha_bufmcat(buf, l, uri->host, r, NULL); } if(uri->path) { ha_bufjoin(buf); ha_bufmcat(buf, "/", uri->path); } if(uri->query) { ha_bufjoin(buf); ha_bufmcat(buf, "?", uri->query); } if(uri->fragment) { ha_bufjoin(buf); ha_bufmcat(buf, "#", uri->fragment); } return ha_bufdata(buf); } int ha_uriparse(ha_buffer_t* buf, const char* suri, ha_uri_t* uri) { char* s; char* s1; char* hostinfo; char* endstr; char* str; int port; int v6_offset1 = 0; ASSERT(buf && suri && uri); /* TODO: We need to http decode the uri */ /* Copy the memory */ str = ha_bufcpy(buf, suri); if(!str) return HA_CRITERROR; /* Initialize the structure */ memset(uri, 0, sizeof(*uri)); /* * We assume the processor has a branch predictor like most -- * it assumes forward branches are untaken and backwards are taken. That's * the reason for the gotos. -djg */ if(str[0] == '/') { deal_with_path: *str == 0; ++str; /* * we expect uri to point to first character of path ... remember * that the path could be empty -- http://foobar?query for example */ s = str; while(*s && *s != '?' && *s != '#') ++s; if(s != str) uri->path = str; if(*s == 0) return HA_OK; if(*s == '?') { *s = 0; ++s; uri->query = s; s1 = strchr(s, '#'); if(s1) { *s1 = 0; ++s1; uri->fragment = s1; } return HA_OK; } *s = 0; ++s; /* otherwise it's a fragment */ uri->fragment = s; return HA_OK; } /* find the scheme: */ s = str; while(*s && *s != ':' && *s != '/' && *s != '?' && *s != '#') s++; /* scheme must be non-empty and followed by :// */ if(s == str || s[0] != ':' || s[1] != '/' || s[2] != '/') goto deal_with_path; /* backwards predicted taken! */ uri->scheme = str; *s = 0; s += 3; hostinfo = s; while(*s && *s != '/' && *s != '?' && *s != '#') ++s; str = s; /* whatever follows hostinfo is start of uri */ /* * If there's a username:password@host:port, the @ we want is the last @... * too bad there's no memrchr()... For the C purists, note that hostinfo * is definately not the first character of the original uri so therefore * &hostinfo[-1] < &hostinfo[0] ... and this loop is valid C. */ do { --s; } while(s >= hostinfo && *s != '@'); if(s < hostinfo) { /* again we want the common case to be fall through */ deal_with_host: /* * We expect hostinfo to point to the first character of * the hostname. If there's a port it is the first colon, * except with IPv6. */ if(*hostinfo == '[') { v6_offset1 = 1; s = str; for(;;) { --s; if(s < hostinfo) { s = NULL; /* no port */ break; } else if(*s == ']') { *s = 0; s = NULL; /* no port */; break; } else if(*s == ':') { *s = 0; ++s; /* found a port */ break; } } } else { s = memchr(hostinfo, ':', str - hostinfo); if(s) { *s = 0; ++s; } } uri->host = hostinfo + v6_offset1; if(s == NULL) { /* we expect the common case to have no port */ goto deal_with_path; } if(str != s) { port = strtol(s, &endstr, 10); uri->port = port; if(*endstr != '\0') return HA_FAILED; } goto deal_with_path; } /* Remove the @ symbol */ *s = 0; /* first colon delimits username:password */ s1 = memchr(hostinfo, ':', s - hostinfo); if(s1) { *s1 = 0; ++s1; uri->user = hostinfo; uri->pw = s1; } else { str = hostinfo; } hostinfo = s + 1; goto deal_with_host; } static int uri_cmp_part_s(const char* s1, const char* s2, const char* def, int cs) { if(def) { if(s1 && strcmp(def, s1) == 0) s1 = NULL; if(s2 && strcmp(def, s2) == 0) s2 = NULL; } if(!s1 && !s2) return 0; if(!s1) return -1; if(!s2) return 1; return cs ? strcasecmp(s1, s2) : strcmp(s1, s2); } static int uri_cmp_part_n(int n1, int n2, int def) { if(def) { if(n1 == def) n1 = 0; if(n2 == def) n2 = 0; } if(n1 == n2) return 0; else if(n1 < n2) return -1; else if(n2 > n1) return 1; /* Not reached */ ASSERT(0); } int ha_uricmp(ha_uri_t* one, ha_uri_t* two) { int r; ASSERT(one && two); /* We don't compare user or password */ /* The scheme */ if((r = uri_cmp_part_s(one->scheme, two->scheme, "http", 0)) != 0 || (r = uri_cmp_part_s(one->host, two->host, NULL, 1)) != 0 != 0 || (r = uri_cmp_part_n(one->port, two->port, 80)) != 0 || (r = uri_cmp_part_s(one->path, two->path, NULL, 0)) != 0 || (r = uri_cmp_part_s(one->query, two->query, NULL, 0)) != 0 || (r = uri_cmp_part_s(one->fragment, two->fragment, NULL, 0))) return r; return 0; } /* ---------------------------------------------------------------------------------- * Other miscellaneous stuff */ int ha_genrandom(unsigned char* data, size_t len) { int r, dd; ASSERT(data && len > 0); dd = open("/dev/urandom", O_RDONLY); if(dd == -1) { ha_message(NULL, LOG_ERR, "couldn't open /dev/urandom"); return HA_FAILED; } for(;;) { r = read(dd, data, len); if(r == -1) { switch(errno) { case EINTR: case EAGAIN: continue; default: ha_message(NULL, LOG_ERR, "couldn't read random bytes from /dev/urandom"); break; }; break; } else if(r >= 0) { if(r >= len) break; len -= r; data += r; } } close(dd); return r == -1 ? HA_FAILED : HA_OK; }