#include "usuals.h" #include "httpauthd.h" #include "md5.h" #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); /* 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); /* 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; 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; 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].value, 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; 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) { if(value == NULL || value[0] == 0 || strcasecmp(conf, "0") == 0 || strcasecmp(conf, "no") == 0 || strcasecmp(conf, "false") == 0 || strcasecmp(conf, "f") == 0 || strcasecmp(conf, "off")) { *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")) { *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) { const char* p; errno = 0; *value = strtol(conf, &p, 10); if(p != (name + strlen(name)) || 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; } /* ----------------------------------------------------------------------- * 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* t; memset(rec, 0, sizeof(*rec)); /* 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); header = ha_bufdata(buf); if(!header) return HA_ERROR; /* We have a cache key at this point so hash it */ ha_md5string(header, rec->key); /* Parse the user. We need it in any case */ t = strchr(header, ':'); if(t != NULL) { /* Break the string in half */ *t = 0; rec->user = header; rec->password = t + 1; } return HA_OK; } /* * 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. */ 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. */ 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]) { /* find key */ while(header[0] && isspace(header[0])) header++; key = header; while(header[0] && header[0] != '=' && header[0] != ',' && !isspace(header[0])) header++; /* 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++; next = header[0]; } /* find value */ if(next == '=') { header++; while(header[0] && isspace(header[0])) header++; vv = 0; if(header[0] == '\"') /* quoted string */ { header++; value = header; while(header[0] && header[0] != '\"') header++; header[0] = 0; header++; } else /* token */ { value = header; while(header[0] && header[0] != ',' && !isspace(header[0])) header++; header[0] = 0; header++; } 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; } } if(rec->nonce) ha_md5string(rec->nonce, rec->key); return HA_OK; } 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; /* Check for digest */ if(!dg->digest || !dg->digest[0]) { ha_messagex(LOG_WARNING, "digest response missing digest"); return HA_FALSE; } /* 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; } /* The realm */ if(!dg->realm || strcmp(dg->realm, realm) != 0) { ha_messagex(LOG_WARNING, "digest response contains invalid realm: '%s'", dg->realm ? dg->realm : ""); return HA_FALSE; } /* Components in the new RFC */ if(dg->qop) { /* * We only support 'auth' qop. We don't have access to the data * and wouldn't be able to support anything else. */ 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]) { ha_messagex(LOG_WARNING, "digest response is missing cnonce value"); return HA_FALSE; } /* The nonce count */ if(!dg->nc || !dg->nc[0]) { ha_messagex(LOG_WARNING, "digest response is missing nc value"); return HA_FALSE; } /* Validate the nc */ else { char* e; long nc = strtol(dg->nc, &e, 10); if(e != (dg->nc + strlen(e)) || nc != rec->nc) { ha_messagex(LOG_WARNING, "digest response has invalid nc value: %s", dg->nc); return HA_FALSE; } } } /* 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; if(ha_uriparse(dg->uri, &d_uri) == HA_ERROR) { ha_messagex(LOG_WARNING, "digest response constains invalid uri: %s", dg->uri); return HA_FALSE; } if(ha_uriparse(uri, &s_uri) == HA_ERROR) { ha_messagex(LOG_ERR, "server sent us an invalid uri"); return HA_ERROR; } 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; } } /* * 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. */ /* * Now we validate the digest response */ /* Encode ha1 */ ha_bufnext(buf); ha_bufenchex(buf, rec->ha1, MD5_LEN); if((t = ha_bufdata(buf)) == NULL) return HA_ERROR; /* Encode ha2 */ MD5Init(&md5); MD5Update(&md5, method, strlen(method)); MD5Update(&md5, ":", 1); MD5Update(&md5, dg->uri, strlen(dg->uri)); MD5Final(hash, &md5); ha_bufnext(buf); ha_bufenchex(buf, hash, MD5_LEN); if(!ha_bufdata(buf)) return HA_ERROR; /* 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); } /* New style 'auth' digest (RFC 2617) */ else { 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); } /* Encode the digest */ ha_bufnext(buf); ha_bufenchex(buf, hash, MD5_LEN); if((digest = ha_bufdata(buf)) == NULL) return HA_ERROR; if(strcasecmp(dg->digest, digest) != 0) { ha_messagex(LOG_WARNING, "digest authentication failed for user: %s", dg->username); return HA_FALSE; } return HA_OK; } const char* ha_digestrespond(ha_buffer_t* buf, ha_digest_header_t* dg, ha_digest_rec_t* rec) { unsigned char hash[MD5_LEN]; struct MD5Context md5; const char* t; const char* digest; /* Encode ha1 */ ha_bufnext(buf); ha_bufenchex(buf, rec->ha1, MD5_LEN); if((t = ha_bufdata(buf)) == NULL) return HA_ERROR; /* Encode ha2 */ MD5Init(&md5); MD5Update(&md5, method, strlen(method)); MD5Update(&md5, ":", 1); MD5Update(&md5, dg->uri, strlen(dg->uri)); MD5Final(hash, &md5); ha_bufnext(buf); ha_bufenchex(buf, hash, MD5_LEN); if(!ha_bufdata(buf)) return HA_ERROR; } char* ha_uriformat(const ha_uri_t* uri, ha_buffer_t* buf); int ha_uriparse(const char* str, ha_uri_t* uri);