From cbbe71752d7f9c6204ab0f16600fe7f10490f203 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Sat, 24 Apr 2004 22:38:50 +0000 Subject: Completed implementation of ldap/ntlm/simple handlers --- daemon/misc.c | 599 ++++++++++++++++++++++++++-------------------------------- 1 file changed, 270 insertions(+), 329 deletions(-) (limited to 'daemon/misc.c') diff --git a/daemon/misc.c b/daemon/misc.c index 7091bfa..b585d8b 100644 --- a/daemon/misc.c +++ b/daemon/misc.c @@ -5,6 +5,8 @@ #include #include +#include +#include extern int g_debugging; extern int g_daemonized; @@ -98,9 +100,9 @@ const char* ha_getheader(ha_request_t* req, const char* name, const char* prefix if(!prefix) return req->headers[i].data; - l = strlen(prefix) + l = strlen(prefix); - if(strncasecmp(prefix, req->headers[i].value, l) == 0) + if(strncasecmp(prefix, req->headers[i].data, l) == 0) return req->headers[i].data + l; } } @@ -185,7 +187,7 @@ int ha_confbool(const char* name, const char* conf, int* value) int ha_confint(const char* name, const char* conf, int min, int max, int* value) { - const char* p; + char* p; errno = 0; *value = strtol(conf, &p, 10); @@ -200,422 +202,361 @@ int ha_confint(const char* name, const char* conf, int min, int max, int* value) } -/* ----------------------------------------------------------------------- - * Hash Stuff - */ - -void ha_md5string(const char* data, unsigned char* hash) -{ - struct MD5Context md5; - MD5Init(&md5); - MD5Update(&md5, data, strlen(data)); - MD5Final(hash, &md5); -} - - /* ----------------------------------------------------------------------- * Client Authentication Functions */ -int ha_parsebasic(char* header, ha_buffer_t* buf, ha_basic_header_t* rec) +char* ha_uriformat(ha_buffer_t* buf, const ha_uri_t* uri) { - char* t; - - memset(rec, 0, sizeof(*rec)); + /* This creates a new block */ + ha_bufcpy(buf, ""); - /* Trim the white space */ - while(*header && isspace(*header)) - header++; - - /* - * Authorization header is in this format: - * - * "Basic " B64(user ":" password) - */ - ha_bufnext(buf); - ha_bufdec64(buf, header); + if(uri->host) + { + const char* l = ""; + const char* r = ""; - header = ha_bufdata(buf); + ha_bufmcat(buf, uri->scheme ? uri->scheme : "http", + "://", NULL); - if(!header) - return HA_ERROR; + 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 = "]"; + } - /* We have a cache key at this point so hash it */ - ha_md5string(header, rec->key); + ha_bufjoin(buf); + ha_bufmcat(buf, l, uri->host, r, NULL); + } + if(uri->path) + { + ha_bufjoin(buf); + ha_bufmcat(buf, "/", uri->path); + } - /* Parse the user. We need it in any case */ - t = strchr(header, ':'); - if(t != NULL) + if(uri->query) { - /* Break the string in half */ - *t = 0; + ha_bufjoin(buf); + ha_bufmcat(buf, "?", uri->query); + } - rec->user = header; - rec->password = t + 1; + if(uri->fragment) + { + ha_bufjoin(buf); + ha_bufmcat(buf, "#", uri->fragment); } - return HA_OK; + 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; + + /* Copy the memory */ + str = ha_bufcpy(buf, suri); + + if(!str) + return HA_ERROR; -/* - * Copyright 1999-2004 The Apache Software Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + /* Initialize the structure */ + memset(uri, 0, sizeof(*uri)); -int ha_parsedigest(char* header, ha_buffer_t* buf, ha_digest_header_t* rec) -{ /* - * This function destroys the contents of header by - * terminating strings in it all over the place. + * 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 */ - char next; - char* key; - char* value; - - ha_bufnext(buf); - ha_bufcat(buf, header, NULL); - - header = ha_bufdata(buf); - - if(!header) - return HA_ERROR; - - memset(rec, 0, sizeof(rec)); - - while(header[0]) + if(str[0] == '/') { - /* find key */ - - while(header[0] && isspace(header[0])) - header++; - - key = header; - - while(header[0] && header[0] != '=' && header[0] != ',' && - !isspace(header[0])) - header++; +deal_with_path: + *str == 0; + ++str; - /* null terminate and move on */ - next = header[0]; - header[0] = '\0'; - header++; - - if(!next) - break; - - if(isspace(t)) - { - while(header[0] && isspace(header[0])) - header++; + /* + * 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; - next = header[0]; - } + if(s != str) + uri->path = str; - /* find value */ + if(*s == 0) + return HA_OK; - if(next == '=') + if(*s == '?') { - header++; - - while(header[0] && isspace(header[0])) - header++; + *s = 0; + ++s; + uri->query = s; - vv = 0; - if(header[0] == '\"') /* quoted string */ + s1 = strchr(s, '#'); + if(s1) { - header++; - value = header; - - while(header[0] && header[0] != '\"') - header++; - - header[0] = 0; - header++; + *s1 = 0; + ++s1; + uri->fragment = s1; } - else /* token */ - { - value = header; - - while(header[0] && header[0] != ',' && !isspace(header[0])) - header++; + return HA_OK; + } - header[0] = 0; - header++; - } + *s = 0; + ++s; - while(header[0] && header[0] != ',') - header++; - - if(header[0]) - header++; - - if(!strcasecmp(key, "username")) - rec->username = value; - else if(!strcasecmp(key, "realm")) - rec->realm = value; - else if (!strcasecmp(key, "nonce")) - rec->nonce = value; - else if (!strcasecmp(key, "uri")) - rec->uri = value; - else if (!strcasecmp(key, "response")) - rec->digest = value; - else if (!strcasecmp(key, "algorithm")) - rec->algorithm = value; - else if (!strcasecmp(key, "cnonce")) - rec->cnonce = value; - else if (!strcasecmp(key, "opaque")) - rec->opaque = value; - else if (!strcasecmp(key, "qop")) - rec->qop = value; - else if (!strcasecmp(key, "nc")) - rec->nc = value; - } + /* otherwise it's a fragment */ + uri->fragment = s; + return HA_OK; } - if(rec->nonce) - ha_md5string(rec->nonce, rec->key); + /* find the scheme: */ + s = str; - return HA_OK; -} + while(*s && *s != ':' && *s != '/' && *s != '?' && *s != '#') + s++; -int ha_digestcheck(const char* realm, const char* method, const char* uri, - ha_buffer_t* buf, ha_digest_header_t* dg, ha_digest_record_t* rec) -{ - unsigned char hash[MD5_LEN]; - struct MD5Context md5; - const char* digest; - const char* t; + /* scheme must be non-empty and followed by :// */ + if(s == str || s[0] != ':' || s[1] != '/' || s[2] != '/') + goto deal_with_path; /* backwards predicted taken! */ - /* Check for digest */ - if(!dg->digest || !dg->digest[0]) - { - ha_messagex(LOG_WARNING, "digest response missing digest"); - return HA_FALSE; - } + uri->scheme = str; + *s = 0; + s += 3; - /* Username */ - if(!dg->username || !dg->username[0] || - ha_md5strcmp(dg->username, rec->userhash) != 0) - { - ha_messagex(LOG_WARNING, "digest response missing username"); - return HA_FALSE; - } + hostinfo = s; + + while(*s && *s != '/' && *s != '?' && *s != '#') + ++s; - /* The realm */ - if(!dg->realm || strcmp(dg->realm, realm) != 0) + 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 { - ha_messagex(LOG_WARNING, "digest response contains invalid realm: '%s'", - dg->realm ? dg->realm : ""); - return HA_FALSE; + --s; } + while(s >= hostinfo && *s != '@'); - /* Components in the new RFC */ - if(dg->qop) + if(s < hostinfo) { + /* again we want the common case to be fall through */ +deal_with_host: /* - * We only support 'auth' qop. We don't have access to the data - * and wouldn't be able to support anything else. + * 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(strcmp(dg->qop, "auth") != 0) - { - ha_messagex(LOG_WARNING, "digest response contains unknown or unsupported qop: '%s'", - dg->qop ? dg->qop : ""); - return HA_FALSE; - } - - /* The nonce */ - if(!dg->cnonce || !dg->cnonce[0]) + if(*hostinfo == '[') { - ha_messagex(LOG_WARNING, "digest response is missing cnonce value"); - return HA_FALSE; - } + v6_offset1 = 1; - /* The nonce count */ - if(!dg->nc || !dg->nc[0]) - { - ha_messagex(LOG_WARNING, "digest response is missing nc value"); - return HA_FALSE; + 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; + } + } } - /* Validate the nc */ else { - char* e; - long nc = strtol(dg->nc, &e, 10); - - if(e != (dg->nc + strlen(e)) || nc != rec->nc) + s = memchr(hostinfo, ':', str - hostinfo); + if(s) { - ha_messagex(LOG_WARNING, "digest response has invalid nc value: %s", - dg->nc); - return HA_FALSE; + *s = 0; + ++s; } } - } - - /* The algorithm */ - if(dg->algorithm && strcasecmp(dg->algorithm, "MD5") != 0) - { - ha_messagex(LOG_WARNING, "digest response contains unknown or unsupported algorithm: '%s'", - dg->algorithm ? dg->algorithm : ""); - return HA_FALSE; - } - /* Request URI */ - if(!dg->uri) - { - ha_messagex(LOG_WARNING, "digest response is missing uri"); - return HA_FALSE; - } - - if(strcmp(dg->uri, uri) != 0) - { - ha_uri_t d_uri; - ha_uri_t s_uri; + uri->host = hostinfo + v6_offset1; - if(ha_uriparse(dg->uri, &d_uri) == HA_ERROR) + if(s == NULL) { - ha_messagex(LOG_WARNING, "digest response constains invalid uri: %s", dg->uri); - return HA_FALSE; + /* we expect the common case to have no port */ + goto deal_with_path; } - if(ha_uriparse(uri, &s_uri) == HA_ERROR) + if(str != s) { - ha_messagex(LOG_ERR, "server sent us an invalid uri"); - return HA_ERROR; + port = strtol(s, &endstr, 10); + uri->port = port; + if(*endstr != '\0') + return HA_FALSE; } - if(ha_uricmp(&d_uri, &s_uri) != 0) - { - ha_messagex(LOG_WARNING, "digest response contains wrong uri: %s " - "(should be %s)", dg->uri, uri); - return HA_ERROR; - } + goto deal_with_path; } - /* - * nonce: should have already been validated by the caller who - * found this nice rec structure to pass us. - * - * opaque: We also don't use opaque. The caller should have validated it - * if it's used there. - */ + /* Remove the @ symbol */ + *s = 0; - /* - * Now we validate the digest response - */ + /* first colon delimits username:password */ + s1 = memchr(hostinfo, ':', s - hostinfo); + if(s1) + { + *s1 = 0; + ++s1; - /* Encode ha1 */ - ha_bufnext(buf); - ha_bufenchex(buf, rec->ha1, MD5_LEN); + uri->user = hostinfo; + uri->pw = s1; + } + else + { + str = hostinfo; + } - if((t = ha_bufdata(buf)) == NULL) - return HA_ERROR; + hostinfo = s + 1; + goto deal_with_host; +} - /* Encode ha2 */ - MD5Init(&md5); - MD5Update(&md5, method, strlen(method)); - MD5Update(&md5, ":", 1); - MD5Update(&md5, dg->uri, strlen(dg->uri)); - MD5Final(hash, &md5); +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; + } - ha_bufnext(buf); - ha_bufenchex(buf, hash, MD5_LEN); + if(!s1 && !s2) + return 0; - if(!ha_bufdata(buf)) - return HA_ERROR; + if(!s1) + return -1; + if(!s2) + return 1; - /* Old style digest (RFC 2069) */ - if(!dg->qop) - { - MD5Init(&md5); - MD5Update(&md5, t, MD5_LEN * 2); /* ha1 */ - MD5Update(&md5, ":", 1); - MD5Update(&md5, dg->nonce, strlen(dg->nonce)); /* nonce */ - MD5Update(&md5, ":", 1); - MD5Update(&md5, ha_bufdata(buf), MD5_LEN * 2); /* ha1 */ - MD5Final(hash, &md5); - } + return cs ? strcasecmp(s1, s2) : strcmp(s1, s2); +} - /* New style 'auth' digest (RFC 2617) */ - else +int uri_cmp_part_n(int n1, int n2, int def) +{ + if(def) { - MD5Init(&md5); - MD5Update(&md5, t, MD5_LEN * 2); /* ha1 */ - MD5Update(&md5, ":", 1); - MD5Update(&md5, dg->nonce, strlen(dg->nonce)); /* nonce */ - MD5Update(&md5, ":", 1); - MD5Update(&md5, dg->nc, strlen(dg->nc)); /* nc */ - MD5Update(&md5, ":", 1); - MD5Update(&md5, dg->cnonce, strlen(dg->cnonce)); /* cnonce */ - MD5Update(&md5, ":", 1); - MD5Update(&md5, dg->qop, strlen(dg->qop)); /* qop */ - MD5Update(&md5, ":", 1); - MD5Update(&md5, ha_bufdata(buf), MD5_LEN * 2); /* ha2 */ - MD5Final(hash, &md5); + if(n1 == def) + n1 = 0; + if(n2 == def) + n2 = 0; } - /* Encode the digest */ - ha_bufnext(buf); - ha_bufenchex(buf, hash, MD5_LEN); + if(n1 == n2) + return 0; - if((digest = ha_bufdata(buf)) == NULL) - return HA_ERROR; + else if(n1 < n2) + return -1; - if(strcasecmp(dg->digest, digest) != 0) - { - ha_messagex(LOG_WARNING, "digest authentication failed for user: %s", dg->username); - return HA_FALSE; - } + else if(n2 > n1) + return 1; - return HA_OK; + /* Not reached */ + ASSERT(0); } -const char* ha_digestrespond(ha_buffer_t* buf, ha_digest_header_t* dg, - ha_digest_rec_t* rec) +int ha_uricmp(ha_uri_t* one, ha_uri_t* two) { - unsigned char hash[MD5_LEN]; - struct MD5Context md5; - const char* t; - const char* digest; + int r; - /* Encode ha1 */ - ha_bufnext(buf); - ha_bufenchex(buf, rec->ha1, MD5_LEN); + /* We don't compare user or password */ - if((t = ha_bufdata(buf)) == NULL) - return HA_ERROR; + /* 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; - /* Encode ha2 */ - MD5Init(&md5); - MD5Update(&md5, method, strlen(method)); - MD5Update(&md5, ":", 1); - MD5Update(&md5, dg->uri, strlen(dg->uri)); - MD5Final(hash, &md5); + return 0; +} - ha_bufnext(buf); - ha_bufenchex(buf, hash, MD5_LEN); +int ha_genrandom(unsigned char* data, size_t len) +{ + int r, dd; - if(!ha_bufdata(buf)) + 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; -char* ha_uriformat(const ha_uri_t* uri, ha_buffer_t* buf); -int ha_uriparse(const char* str, ha_uri_t* uri); + len -= r; + data += r; + } + } + + close(dd); + return r == -1 ? HA_ERROR : HA_OK; +} -- cgit v1.2.3