#include "usuals.h" #include "httpauthd.h" #include "md5.h" #include #include #include #include extern int g_debugging; extern int g_daemonized; extern pthread_mutex_t g_mutex; /* ----------------------------------------------------------------------- * Error Handling */ const char kMsgDelimiter[] = ": "; #define MAX_MSGLEN 128 void ha_messagex(int level, const char* msg, ...) { va_list ap; va_start(ap, msg); ASSERT(msg); /* Either to syslog or stderr */ if(g_daemonized) vsyslog(level, msg, ap); else { if(g_debugging || level > LOG_INFO) vwarnx(msg, ap); } va_end(ap); } void ha_message(int level, const char* msg, ...) { va_list ap; va_start(ap, msg); ASSERT(msg); /* Either to syslog or stderr */ if(g_daemonized) { char* m = (char*)alloca(strlen(msg) + MAX_MSGLEN + sizeof(kMsgDelimiter)); if(m) { strcpy(m, msg); strcat(m, kMsgDelimiter); strerror_r(errno, m + strlen(m), MAX_MSGLEN); } vsyslog(LOG_ERR, m ? m : msg, ap); } else { if(g_debugging || level > LOG_INFO) vwarnx(msg, ap); } va_end(ap); } /* ----------------------------------------------------------------------- * Header Functionality */ ha_header_t* ha_findheader(ha_request_t* req, const char* name) { int i; ASSERT(req && name); for(i = 0; i < MAX_HEADERS; i++) { if(req->headers[i].name) { if(strcasecmp(req->headers[i].name, name) == 0) return &(req->headers[i]); } } return NULL; } const char* ha_getheader(ha_request_t* req, const char* name, const char* prefix) { int i, l; ASSERT(req && name); for(i = 0; i < MAX_HEADERS; i++) { if(req->headers[i].name) { if(strcasecmp(req->headers[i].name, name) == 0) { if(!prefix) return req->headers[i].data; l = strlen(prefix); if(strncasecmp(prefix, req->headers[i].data, l) == 0) return req->headers[i].data + l; } } } return NULL; } void ha_addheader(ha_response_t* resp, const char* name, const char* data) { int i = 0; ASSERT(resp && name && data); for(i = 0; i < MAX_HEADERS; i++) { if(!(resp->headers[i].name)) { resp->headers[i].name = name; resp->headers[i].data = data; return; } } ha_messagex(LOG_ERR, "too many headers in response. discarding '%s'", name); } /* ----------------------------------------------------------------------- * Locking */ void ha_lock(pthread_mutex_t* mtx) { int r = pthread_mutex_lock(mtx ? mtx : &g_mutex); if(r != 0) { errno = r; ha_message(LOG_CRIT, "threading problem. couldn't lock mutex"); } } void ha_unlock(pthread_mutex_t* mtx) { int r = pthread_mutex_unlock(mtx ? mtx : &g_mutex); if(r != 0) { errno = r; ha_message(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(LOG_ERR, "invalid configuration value '%s': must be 'on' or 'off'.", name); return HA_ERROR; } 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(LOG_ERR, "invalid configuration value '%s': must be a number between %d and %d", name, min, max); return HA_ERROR; } return HA_OK; } /* ----------------------------------------------------------------------- * Client Authentication 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); /* Copy the memory */ str = ha_bufcpy(buf, suri); if(!str) return HA_ERROR; /* 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_FALSE; } 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; } 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); } 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; } 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(LOG_ERR, "couldn't open /dev/urandom"); return HA_ERROR; } for(;;) { r = read(dd, data, len); if(r == -1) { switch(errno) { case EINTR: case EAGAIN: continue; default: ha_message(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_ERROR : HA_OK; }