summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configure.ac15
-rw-r--r--make-authstring.py10
-rw-r--r--src/conf.c31
-rw-r--r--src/conf.h4
-rw-r--r--src/html-error.c11
-rw-r--r--src/reqs.c239
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())
+
diff --git a/src/conf.c b/src/conf.c
index c003627..41c19d5 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -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)
{
diff --git a/src/conf.h b/src/conf.h
index 0fb4226..9d5bd01 100644
--- a/src/conf.h
+++ b/src/conf.h
@@ -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));
}
/*
diff --git a/src/reqs.c b/src/reqs.c
index d0f296f..ae718d2 100644
--- a/src/reqs.c
+++ b/src/reqs.c
@@ -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) {
/*