diff options
Diffstat (limited to '')
-rw-r--r-- | configure.ac | 15 | ||||
-rw-r--r-- | make-authstring.py | 10 | ||||
-rw-r--r-- | src/conf.c | 31 | ||||
-rw-r--r-- | src/conf.h | 4 | ||||
-rw-r--r-- | src/html-error.c | 11 | ||||
-rw-r--r-- | src/reqs.c | 239 |
6 files changed, 309 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac index 3c162f0..3d2ab70 100644 --- a/configure.ac +++ b/configure.ac @@ -87,6 +87,17 @@ if test x"$xtinyproxy_enabled" = x"yes"; then AC_DEFINE(XTINYPROXY_ENABLE) fi +dnl Include proxy authentication +AH_TEMPLATE([AUTH_SUPPORT], + [Include support for proxy authentication.]) +TP_ARG_ENABLE(auth, + [Enable proxy authentication support (requires OpenSSL, default is YES)], + yes) + +if test x"$auth_enabled" = x"yes"; then + AC_DEFINE(AUTH_SUPPORT) +fi + dnl Include filtering for domain/URLs AH_TEMPLATE([FILTER_ENABLE], [Defined if you would like filtering code included.]) @@ -160,6 +171,10 @@ fi AC_CHECK_LIB(resolv, inet_aton) +if test x"$auth_enabled" = x"yes"; then + AC_CHECK_LIB([crypto], [MD5_Init], [], [AC_MSG_FAILURE([could not find OpenSSL])]) +fi + dnl dnl Checks for headers dnl diff --git a/make-authstring.py b/make-authstring.py new file mode 100644 index 0000000..265c26d --- /dev/null +++ b/make-authstring.py @@ -0,0 +1,10 @@ +import hashlib + +username = input('Username:') +realm = 'tinyproxy' +password = input('Password:') +a1 = ':'.join((username, realm, password)) + +m = hashlib.md5(a1.encode('latin-1')) +print(m.hexdigest()) + @@ -115,6 +115,10 @@ static HANDLE_FUNC (handle_nop) return 0; } /* do nothing function */ +#ifdef AUTH_SUPPORT +static HANDLE_FUNC (handle_authusername); +static HANDLE_FUNC (handle_authstring); +#endif static HANDLE_FUNC (handle_allow); static HANDLE_FUNC (handle_anonymous); static HANDLE_FUNC (handle_bind); @@ -233,6 +237,10 @@ struct { STDCONF ("errorfile", INT WS STR, handle_errorfile), STDCONF ("addheader", STR WS STR, handle_addheader), +#ifdef AUTH_SUPPORT + STDCONF ("authusername", STR, handle_authusername), + STDCONF ("authstring", STR, handle_authstring), +#endif #ifdef FILTER_ENABLE /* filtering */ STDCONF ("filter", STR, handle_filter), @@ -289,6 +297,10 @@ static void free_config (struct config_s *conf) safefree (conf->user); safefree (conf->group); vector_delete(conf->listen_addrs); +#ifdef AUTH_SUPPORT + safefree (conf->auth_username); + safefree (conf->auth_string); +#endif #ifdef FILTER_ENABLE safefree (conf->filter); #endif /* FILTER_ENABLE */ @@ -479,6 +491,14 @@ static void initialize_with_defaults (struct config_s *conf, } +#ifdef AUTH_SUPPORT + if (conf->auth_username) { + conf->auth_username = safestrdup (defaults->auth_username); + } + if (conf->auth_string) { + conf->auth_string = safestrdup (defaults->auth_string); + } +#endif #ifdef FILTER_ENABLE if (defaults->filter) { conf->filter = safestrdup (defaults->filter); @@ -994,6 +1014,17 @@ static HANDLE_FUNC (handle_loglevel) return -1; } +#ifdef AUTH_SUPPORT +static HANDLE_FUNC (handle_authusername) +{ + return set_string_arg (&conf->auth_username, line, &match[2]); +} +static HANDLE_FUNC (handle_authstring) +{ + return set_string_arg (&conf->auth_string, line, &match[2]); +} +#endif + #ifdef FILTER_ENABLE static HANDLE_FUNC (handle_filter) { @@ -47,6 +47,10 @@ struct config_s { char *user; char *group; vector_t listen_addrs; +#ifdef AUTH_SUPPORT + char *auth_username; + char *auth_string; +#endif #ifdef FILTER_ENABLE char *filter; unsigned int filter_url; /* boolean */ diff --git a/src/html-error.c b/src/html-error.c index 2721a40..09809cc 100644 --- a/src/html-error.c +++ b/src/html-error.c @@ -180,10 +180,19 @@ int send_http_headers (struct conn_s *connptr, int code, const char *message) const char *headers = "HTTP/1.0 %d %s\r\n" "Server: %s/%s\r\n" + "%s" "Content-Type: text/html\r\n" "Connection: close\r\n" "\r\n"; +#ifdef AUTH_SUPPORT + const char *auth_header = lookup_variable(connptr, "auth-header"); + if (auth_header == NULL) + auth_header = ""; +#else + const char *auth_header = ""; +#endif + return (write_message (connptr->client_fd, headers, - code, message, PACKAGE, VERSION)); + code, message, PACKAGE, VERSION, auth_header)); } /* @@ -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) { /* |