diff options
Diffstat (limited to '')
-rw-r--r-- | src/reqs.c | 239 |
1 files changed, 239 insertions, 0 deletions
@@ -48,6 +48,9 @@ #include "upstream.h" #include "connect-ports.h" #include "conf.h" +#ifdef AUTH_SUPPORT +#include <openssl/md5.h> +#endif /* * Maximum length of a HTTP line @@ -79,6 +82,229 @@ #define CHECK_LWS(header, len) \ ((len) > 0 && (header[0] == ' ' || header[0] == '\t')) + +#ifdef AUTH_SUPPORT +#define AUTH_ENABLED() ((config.auth_username != NULL) && (config.auth_string != NULL)) + +#define AUTH_STATUS_OK 1 +#define AUTH_STATUS_STALE_NONCE 0 +#define AUTH_STATUS_INVALID -1 +#define AUTH_STATUS_FAKE_URI -2 + + +/* holds info extracted from an authorization header */ +struct auth_s { + char *username, *realm, *nonce, *digest_uri, + *response, *algorithm, *cnonce, *opaque, + *message_qop, *nonce_count; +}; + +static void init_auth_struct (struct auth_s *auth) +{ + memset (auth, 0, sizeof (struct auth_s)); +} +static void free_auth_struct (struct auth_s *auth) +{ + safefree (auth->username); + safefree (auth->realm); + safefree (auth->nonce); + safefree (auth->digest_uri); + safefree (auth->response); + safefree (auth->algorithm); + safefree (auth->cnonce); + safefree (auth->opaque); + safefree (auth->message_qop); + safefree (auth->nonce_count); +} + +static void parse_auth (struct auth_s *auth, const char *str) +{ + const char *key, *equals, *value, *value_end; + ssize_t value_len; + char **write_where; + + while (*str) { + /* whitespace can be ignored here */ + if (*str == ' ') { + ++str; + continue; + } + + /* this should be a key, right? */ + key = str; + equals = strpbrk (key, "=\" "); + if (equals == NULL) + return; /* invalid string, bail out */ + if (equals[0] != '=') + return; /* same thing */ + + /* now we can probably expect a value */ + /* I'm not sure if non-quoted strings are valid here, + * but no harm in supporing them */ + value = equals + 1; + if (*value == '"') { + /* quoted string, find the end */ + ++value; + value_end = strchr (value, '"'); + if (value_end == NULL) + return; + str = value_end + 1; + + } else if (*value == 0) { + /* string abruptly terminated, bail out */ + return; + + } else { + /* non-quoted string */ + value_end = strpbrk (value, ", "); + if (value_end == NULL) + value_end = value + strlen(value); + str = value_end; + } + + /* string extracted, copy it someplace */ + if (strncasecmp (key, "username=", 9) == 0) + write_where = &auth->username; + else if (strncasecmp (key, "realm=", 6) == 0) + write_where = &auth->realm; + else if (strncasecmp (key, "nonce=", 6) == 0) + write_where = &auth->nonce; + else if (strncasecmp (key, "uri=", 4) == 0) + write_where = &auth->digest_uri; + else if (strncasecmp (key, "response=", 9) == 0) + write_where = &auth->response; + else if (strncasecmp (key, "algorithm=", 10) == 0) + write_where = &auth->algorithm; + else if (strncasecmp (key, "cnonce=", 7) == 0) + write_where = &auth->cnonce; + else if (strncasecmp (key, "opaque=", 7) == 0) + write_where = &auth->opaque; + else if (strncasecmp (key, "qop=", 4) == 0) + write_where = &auth->message_qop; + else if (strncasecmp (key, "nonce_count=", 12) == 0) + write_where = &auth->nonce_count; + else + write_where = NULL; + + if (write_where != NULL) { + safefree (*write_where); + + value_len = value_end - value; + *write_where = (char *) safemalloc (value_len + 1); + (*write_where)[value_len] = 0; + if (value_len > 0) { + memcpy (*write_where, value, value_len); + } + } + + if (*str == ',') + ++str; + } +} + + +static void hexify_hash(const unsigned char *input, char *output) +{ + static const char *hex = "0123456789abcdef"; + int i; + + for (i = 0; i < 16; i++) { + output[i * 2] = hex[input[i] >> 4]; + output[i * 2 + 1] = hex[input[i] & 15]; + } + output[32] = 0; +} + +static int validate_auth_digest (struct request_s *request, struct auth_s *auth) +{ + MD5_CTX md5; + unsigned char hexhash[16]; + char h_a2[33], final_hash[33]; + + /* how does this response look? */ + /* these params are obligatory according to the spec */ + if (!auth->username || !auth->realm || !auth->nonce || !auth->digest_uri || !auth->response) + return AUTH_STATUS_INVALID; + + /* auth details */ + if (strcmp (auth->username, config.auth_username) != 0) + return AUTH_STATUS_INVALID; + if (strcmp (auth->realm, "tinyproxy") != 0) + return AUTH_STATUS_INVALID; + if (strcmp (auth->nonce, "et346f2a") != 0) + return AUTH_STATUS_INVALID; + + /* configuration parameters */ + if (auth->cnonce || auth->nonce_count) + return AUTH_STATUS_INVALID; + if (auth->algorithm) { + if (strcmp (auth->algorithm, "MD5") != 0) + return AUTH_STATUS_INVALID; + } + + /* generate the correct response hash */ + /* A1 = username:realm:password */ + /* A2 = method:digest-uri */ + MD5_Init (&md5); + MD5_Update (&md5, request->method, strlen (request->method)); + MD5_Update (&md5, ":", 1); + MD5_Update (&md5, auth->digest_uri, strlen (auth->digest_uri)); + MD5_Final (hexhash, &md5); + hexify_hash (hexhash, h_a2); + + /* response = H(A1):nonce:H(A2) */ + MD5_Init (&md5); + MD5_Update (&md5, config.auth_string, strlen (config.auth_string)); + MD5_Update (&md5, ":", 1); + MD5_Update (&md5, auth->nonce, strlen (auth->nonce)); + MD5_Update (&md5, ":", 1); + MD5_Update (&md5, h_a2, 32); + MD5_Final (hexhash, &md5); + hexify_hash (hexhash, final_hash); + + if (strcmp (auth->response, final_hash) == 0) + return AUTH_STATUS_OK; + else + return AUTH_STATUS_INVALID; +} + + +static int valid_auth_value (struct request_s *request, const char *url, hashmap_t hashofheaders) +{ + ssize_t len; + char *data; + struct auth_s auth; + int status = AUTH_STATUS_INVALID; + + len = + hashmap_entry_by_key (hashofheaders, "proxy-authorization", + (void **) &data); + if (len <= 0) + return AUTH_STATUS_INVALID; + log_message (LOG_CONN, "Proxy Authorization: %s", data); + + if (strncmp (data, "Digest ", 7) == 0) { + /* check this for correctness! */ + init_auth_struct (&auth); + parse_auth (&auth, &data[7]); + status = validate_auth_digest (request, &auth); + + if (status == AUTH_STATUS_OK && auth.digest_uri) { + /* this prevents replay attacks with different URI + * as per RFC 2617 3.2.2.5 */ + if (strcmp (auth.digest_uri, url) != 0) + status = AUTH_STATUS_FAKE_URI; + } + + free_auth_struct (&auth); + } + + hashmap_remove (hashofheaders, "proxy-authorization"); + return status; +} +#endif + + /* * Read in the first line from the client (the request line for HTTP * connections. The request line is allocated from the heap, but it must @@ -357,6 +583,19 @@ BAD_REQUEST_ERROR: goto fail; } +#ifdef AUTH_SUPPORT + /* Send proxy auth request */ + if (AUTH_ENABLED () && (valid_auth_value (request, url, hashofheaders) != AUTH_STATUS_OK)) { + /* todo: support stale nonces here (once nonces are generated) */ + indicate_http_error(connptr, 407, "Proxy Authentication Required", + "detail", "x", + "url", url, + "auth-header", "Proxy-Authenticate: Digest realm=\"tinyproxy\", nonce=\"et346f2a\"\r\n", + NULL); + goto fail; + } +#endif + #ifdef REVERSE_SUPPORT if (config.reversepath_list != NULL) { /* |