]> git.ipfire.org Git - thirdparty/openvpn.git/commitdiff
Proxy improvements:
authorJames Yonan <james@openvpn.net>
Tue, 11 May 2010 19:32:41 +0000 (19:32 +0000)
committerJames Yonan <james@openvpn.net>
Tue, 11 May 2010 19:32:41 +0000 (19:32 +0000)
Improved the ability of http-auth "auto" flag to dynamically detect
the auth method required by the proxy.

Added http-auth "auto-nct" flag to reject weak proxy auth methods.

Added HTTP proxy digest authentication method.

Removed extraneous openvpn_sleep calls from proxy.c.

git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@5628 e7ae566f-a301-0410-adde-c780ea21d3b5

Makefile.am
config-win32.h
httpdigest.c [new file with mode: 0644]
httpdigest.h [new file with mode: 0644]
openvpn.8
options.c
proxy.c
proxy.h
syshead.h
win/build.py

index f453cdb57f67afbcd7ea71df01ec9486511b3b02..2980daccf5a38dde87657d72f292ba75a402e8ba 100644 (file)
@@ -88,6 +88,7 @@ openvpn_SOURCES = \
        fragment.c fragment.h \
        gremlin.c gremlin.h \
        helper.c helper.h \
+       httpdigest.c httpdigest.h \
        lladdr.c lladdr.h \
        init.c init.h \
        integer.h \
index c26b0af41cd5c9e3a0a442751ae2ede9ffb442bd..bed043c063d884a17c5df6f218e357d412dd617b 100644 (file)
@@ -291,6 +291,7 @@ typedef unsigned long in_addr_t;
 #define lseek _lseek\r
 #define chdir _chdir\r
 #define strdup _strdup\r
+#define stricmp _stricmp\r
 #define chsize _chsize\r
 #define S_IRUSR 0\r
 #define S_IWUSR 0\r
diff --git a/httpdigest.c b/httpdigest.c
new file mode 100644 (file)
index 0000000..abe5bea
--- /dev/null
@@ -0,0 +1,143 @@
+/*\r
+ *  OpenVPN -- An application to securely tunnel IP networks\r
+ *             over a single TCP/UDP port, with support for SSL/TLS-based\r
+ *             session authentication and key exchange,\r
+ *             packet encryption, packet authentication, and\r
+ *             packet compression.\r
+ *\r
+ *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>\r
+ *\r
+ *  This program is free software; you can redistribute it and/or modify\r
+ *  it under the terms of the GNU General Public License version 2\r
+ *  as published by the Free Software Foundation.\r
+ *\r
+ *  This program is distributed in the hope that it will be useful,\r
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ *  GNU General Public License for more details.\r
+ *\r
+ *  You should have received a copy of the GNU General Public License\r
+ *  along with this program (see the file COPYING included with this\r
+ *  distribution); if not, write to the Free Software Foundation, Inc.,\r
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
+ */\r
+\r
+#include "syshead.h"\r
+\r
+#if PROXY_DIGEST_AUTH\r
+\r
+#include "crypto.h"\r
+#include "httpdigest.h"\r
+\r
+static void\r
+CvtHex(\r
+       IN HASH Bin,\r
+       OUT HASHHEX Hex\r
+       )\r
+{\r
+  unsigned short i;\r
+  unsigned char j;\r
+\r
+  for (i = 0; i < HASHLEN; i++) {\r
+    j = (Bin[i] >> 4) & 0xf;\r
+    if (j <= 9)\r
+      Hex[i*2] = (j + '0');\r
+    else\r
+      Hex[i*2] = (j + 'a' - 10);\r
+    j = Bin[i] & 0xf;\r
+    if (j <= 9)\r
+      Hex[i*2+1] = (j + '0');\r
+    else\r
+      Hex[i*2+1] = (j + 'a' - 10);\r
+  };\r
+  Hex[HASHHEXLEN] = '\0';\r
+};\r
+\r
+/* calculate H(A1) as per spec */\r
+void\r
+DigestCalcHA1(\r
+             IN char * pszAlg,\r
+             IN char * pszUserName,\r
+             IN char * pszRealm,\r
+             IN char * pszPassword,\r
+             IN char * pszNonce,\r
+             IN char * pszCNonce,\r
+             OUT HASHHEX SessionKey\r
+             )\r
+{\r
+  MD5_CTX Md5Ctx;\r
+  HASH HA1;\r
+\r
+  MD5_Init(&Md5Ctx);\r
+  MD5_Update(&Md5Ctx, pszUserName, strlen(pszUserName));\r
+  MD5_Update(&Md5Ctx, ":", 1);\r
+  MD5_Update(&Md5Ctx, pszRealm, strlen(pszRealm));\r
+  MD5_Update(&Md5Ctx, ":", 1);\r
+  MD5_Update(&Md5Ctx, pszPassword, strlen(pszPassword));\r
+  MD5_Final(HA1, &Md5Ctx);\r
+  if (pszAlg && stricmp(pszAlg, "md5-sess") == 0)\r
+    {\r
+      MD5_Init(&Md5Ctx);\r
+      MD5_Update(&Md5Ctx, HA1, HASHLEN);\r
+      MD5_Update(&Md5Ctx, ":", 1);\r
+      MD5_Update(&Md5Ctx, pszNonce, strlen(pszNonce));\r
+      MD5_Update(&Md5Ctx, ":", 1);\r
+      MD5_Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));\r
+      MD5_Final(HA1, &Md5Ctx);\r
+    };\r
+  CvtHex(HA1, SessionKey);\r
+}\r
+\r
+/* calculate request-digest/response-digest as per HTTP Digest spec */\r
+void\r
+DigestCalcResponse(\r
+                  IN HASHHEX HA1,           /* H(A1) */\r
+                  IN char * pszNonce,       /* nonce from server */\r
+                  IN char * pszNonceCount,  /* 8 hex digits */\r
+                  IN char * pszCNonce,      /* client nonce */\r
+                  IN char * pszQop,         /* qop-value: "", "auth", "auth-int" */\r
+                  IN char * pszMethod,      /* method from the request */\r
+                  IN char * pszDigestUri,   /* requested URL */\r
+                  IN HASHHEX HEntity,       /* H(entity body) if qop="auth-int" */\r
+                  OUT HASHHEX Response      /* request-digest or response-digest */\r
+                  )\r
+{\r
+  MD5_CTX Md5Ctx;\r
+  HASH HA2;\r
+  HASH RespHash;\r
+  HASHHEX HA2Hex;\r
+\r
+  // calculate H(A2)\r
+  MD5_Init(&Md5Ctx);\r
+  MD5_Update(&Md5Ctx, pszMethod, strlen(pszMethod));\r
+  MD5_Update(&Md5Ctx, ":", 1);\r
+  MD5_Update(&Md5Ctx, pszDigestUri, strlen(pszDigestUri));\r
+  if (stricmp(pszQop, "auth-int") == 0)\r
+    {\r
+      MD5_Update(&Md5Ctx, ":", 1);\r
+      MD5_Update(&Md5Ctx, HEntity, HASHHEXLEN);\r
+    };\r
+  MD5_Final(HA2, &Md5Ctx);\r
+  CvtHex(HA2, HA2Hex);\r
+\r
+  // calculate response\r
+  MD5_Init(&Md5Ctx);\r
+  MD5_Update(&Md5Ctx, HA1, HASHHEXLEN);\r
+  MD5_Update(&Md5Ctx, ":", 1);\r
+  MD5_Update(&Md5Ctx, pszNonce, strlen(pszNonce));\r
+  MD5_Update(&Md5Ctx, ":", 1);\r
+  if (*pszQop)\r
+    {\r
+      MD5_Update(&Md5Ctx, pszNonceCount, strlen(pszNonceCount));\r
+      MD5_Update(&Md5Ctx, ":", 1);\r
+      MD5_Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));\r
+      MD5_Update(&Md5Ctx, ":", 1);\r
+      MD5_Update(&Md5Ctx, pszQop, strlen(pszQop));\r
+      MD5_Update(&Md5Ctx, ":", 1);\r
+    };\r
+  MD5_Update(&Md5Ctx, HA2Hex, HASHHEXLEN);\r
+  MD5_Final(RespHash, &Md5Ctx);\r
+  CvtHex(RespHash, Response);\r
+}\r
+\r
+#endif\r
diff --git a/httpdigest.h b/httpdigest.h
new file mode 100644 (file)
index 0000000..d4764a1
--- /dev/null
@@ -0,0 +1,60 @@
+/*\r
+ *  OpenVPN -- An application to securely tunnel IP networks\r
+ *             over a single TCP/UDP port, with support for SSL/TLS-based\r
+ *             session authentication and key exchange,\r
+ *             packet encryption, packet authentication, and\r
+ *             packet compression.\r
+ *\r
+ *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>\r
+ *\r
+ *  This program is free software; you can redistribute it and/or modify\r
+ *  it under the terms of the GNU General Public License version 2\r
+ *  as published by the Free Software Foundation.\r
+ *\r
+ *  This program is distributed in the hope that it will be useful,\r
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ *  GNU General Public License for more details.\r
+ *\r
+ *  You should have received a copy of the GNU General Public License\r
+ *  along with this program (see the file COPYING included with this\r
+ *  distribution); if not, write to the Free Software Foundation, Inc.,\r
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
+ */\r
+\r
+#if PROXY_DIGEST_AUTH\r
+\r
+#define HASHLEN 16\r
+typedef char HASH[HASHLEN];\r
+#define HASHHEXLEN 32\r
+typedef char HASHHEX[HASHHEXLEN+1];\r
+#undef IN\r
+#undef OUT\r
+#define IN const\r
+#define OUT\r
+\r
+/* calculate H(A1) as per HTTP Digest spec */\r
+void DigestCalcHA1(\r
+    IN char * pszAlg,\r
+    IN char * pszUserName,\r
+    IN char * pszRealm,\r
+    IN char * pszPassword,\r
+    IN char * pszNonce,\r
+    IN char * pszCNonce,\r
+    OUT HASHHEX SessionKey\r
+    );\r
+\r
+/* calculate request-digest/response-digest as per HTTP Digest spec */\r
+void DigestCalcResponse(\r
+    IN HASHHEX HA1,           /* H(A1) */\r
+    IN char * pszNonce,       /* nonce from server */\r
+    IN char * pszNonceCount,  /* 8 hex digits */\r
+    IN char * pszCNonce,      /* client nonce */\r
+    IN char * pszQop,         /* qop-value: "", "auth", "auth-int" */\r
+    IN char * pszMethod,      /* method from the request */\r
+    IN char * pszDigestUri,   /* requested URL */\r
+    IN HASHHEX HEntity,       /* H(entity body) if qop="auth-int" */\r
+    OUT HASHHEX Response      /* request-digest or response-digest */\r
+    );\r
+\r
+#endif\r
index a29ad29ec6ab7176d5b859255b631a932ee9777b..12294be806fa2eab2e4aee17dac8c4b0896a99b8 100644 (file)
--- a/openvpn.8
+++ b/openvpn.8
@@ -474,7 +474,7 @@ InternetQueryOption API.
 This option exists in OpenVPN 2.1 or higher.
 .\"*********************************************************
 .TP
-.B --http-proxy server port [authfile|'auto'] [auth-method]
+.B --http-proxy server port [authfile|'auto'|'auto-nct'] [auth-method]
 Connect to remote host through an HTTP proxy at address
 .B server
 and port
@@ -487,6 +487,13 @@ is a file containing a username and password on 2 lines, or
 .B auth-method
 should be one of "none", "basic", or "ntlm".
 
+HTTP Digest authentication is supported as well, but only via
+the
+.B auto
+or
+.B auto-nct
+flags (below).
+
 The
 .B auto
 flag causes OpenVPN to automatically determine the
@@ -494,6 +501,12 @@ flag causes OpenVPN to automatically determine the
 and query stdin or the management interface for
 username/password credentials, if required.  This flag
 exists on OpenVPN 2.1 or higher.
+
+The
+.B auto-nct
+flag (no clear-text auth) instructs OpenVPN to automatically
+determine the authentication method, but to reject weak
+authentication protocols such as HTTP Basic Authentication.
 .\"*********************************************************
 .TP
 .B --http-proxy-retry
index 5d884331ebb0fb16bdcd40973726b7ad8cf757ab..0831e867f239cc360ca701fc768e365feaacb9c4 100644 (file)
--- a/options.c
+++ b/options.c
@@ -108,8 +108,9 @@ static const char usage_message[] =
   "                  up is a file containing username/password on 2 lines, or\n"
   "                  'stdin' to prompt from console.  Add auth='ntlm' if\n"
   "                  the proxy requires NTLM authentication.\n"
-  "--http-proxy s p 'auto': Like the above directive, but automatically determine\n"
-  "                         auth method and query for username/password if needed.\n"
+  "--http-proxy s p 'auto[-nct]' : Like the above directive, but automatically\n"
+  "                  determine auth method and query for username/password\n"
+  "                  if needed.  auto-nct disables weak proxy auth methods.\n"
   "--http-proxy-retry     : Retry indefinitely on HTTP proxy errors.\n"
   "--http-proxy-timeout n : Proxy timeout in seconds, default=5.\n"
   "--http-proxy-option type [parm] : Set extended HTTP proxy options.\n"
@@ -4197,8 +4198,13 @@ add_option (struct options *options,
 
       if (p[3])
        {
+         /* auto -- try to figure out proxy addr, port, and type automatically */
+         /* semiauto -- given proxy addr:port, try to figure out type automatically */
+         /* (auto|semiauto)-nct -- disable proxy auth cleartext protocols (i.e. basic auth) */
          if (streq (p[3], "auto"))
-           ho->auth_retry = true;
+           ho->auth_retry = PAR_ALL;
+         else if (streq (p[3], "auto-nct"))
+           ho->auth_retry = PAR_NCT;
          else
            {
              ho->auth_method_string = "basic";
diff --git a/proxy.c b/proxy.c
index c6d0e63631ed0765d6a899f2c8cb40b535d60510..7fb5b59e92bccb10dd21d6c490d7b1ebfe5fafac 100644 (file)
--- a/proxy.c
+++ b/proxy.c
 
 #include "common.h"
 #include "misc.h"
+#include "crypto.h"
 #include "win32.h"
 #include "socket.h"
 #include "fdmisc.h"
 #include "proxy.h"
 #include "base64.h"
+#include "httpdigest.h"
 #include "ntlm.h"
 
 #ifdef WIN32
@@ -229,6 +231,189 @@ get_user_pass_http (struct http_proxy_info *p, const bool force)
       p->up = static_proxy_user_pass;
     }
 }
+static void
+clear_user_pass_http (void)
+{
+  purge_user_pass (&static_proxy_user_pass, true);
+}
+
+static void
+dump_residual (socket_descriptor_t sd,
+              int timeout,
+              volatile int *signal_received)
+{
+  char buf[256];
+  while (true)
+    {
+      if (!recv_line (sd, buf, sizeof (buf), timeout, true, NULL, signal_received))
+       return;
+      chomp (buf);
+      msg (D_PROXY, "PROXY HEADER: '%s'", buf);
+    }
+}
+
+/*
+ * Extract the Proxy-Authenticate header from the stream.
+ * Consumes all headers.
+ */
+static int
+get_proxy_authenticate (socket_descriptor_t sd,
+                       int timeout,
+                       char **data,
+                       struct gc_arena *gc,
+                       volatile int *signal_received)
+{
+  char buf[256];
+  int ret = HTTP_AUTH_NONE;
+  while (true)
+    {
+      if (!recv_line (sd, buf, sizeof (buf), timeout, true, NULL, signal_received))
+       {
+         *data = NULL;
+         return HTTP_AUTH_NONE;
+       }
+      chomp (buf);
+      if (!strlen(buf))
+       return ret;
+      if (ret == HTTP_AUTH_NONE && !strncmp(buf, "Proxy-Authenticate: ", 20))
+       {
+         if (!strncmp(buf+20, "Basic ", 6))
+           {
+             msg (D_PROXY, "PROXY AUTH BASIC: '%s'", buf);
+             *data = string_alloc(buf+26, gc);
+             ret = HTTP_AUTH_BASIC;
+           }
+#if PROXY_DIGEST_AUTH
+         else if (!strncmp(buf+20, "Digest ", 7))
+           {
+             msg (D_PROXY, "PROXY AUTH DIGEST: '%s'", buf);
+             *data = string_alloc(buf+27, gc);
+             ret = HTTP_AUTH_DIGEST;
+           }
+#endif
+#if NTLM
+         else if (!strncmp(buf+20, "NTLM", 4))
+           {
+             msg (D_PROXY, "PROXY AUTH HTLM: '%s'", buf);
+             *data = NULL;
+             ret = HTTP_AUTH_NTLM;
+           }
+#endif
+       }
+    }
+}
+
+static void
+store_proxy_authenticate (struct http_proxy_info *p, char *data)
+{
+  if (p->proxy_authenticate)
+    free (p->proxy_authenticate);
+  p->proxy_authenticate = data;
+}
+
+/*
+ * Parse out key/value pairs from Proxy-Authenticate string.
+ * Return true on success, or false on parse failure.
+ */
+static bool
+get_key_value(const char *str,       /* source string */
+             char *key,             /* key stored here */
+             char *value,           /* value stored here */
+             int max_key_len,
+             int max_value_len,
+             const char **endptr)   /* next search position */
+{
+  int c;
+  bool starts_with_quote = false;
+  bool escape = false;
+
+  for (c = max_key_len-1; (*str && (*str != '=') && c--); )
+    *key++ = *str++;
+  *key = '\0';
+
+  if('=' != *str++)
+    /* no key/value found */
+    return false;
+
+  if('\"' == *str)
+    {
+      /* quoted string */
+      str++;
+      starts_with_quote = true;
+    }
+
+  for (c = max_value_len-1; *str && c--; str++)
+    {
+      switch (*str)
+       {
+       case '\\':
+         if (!escape)
+           {
+             /* possibly the start of an escaped quote */
+             escape = true;
+             *value++ = '\\'; /* even though this is an escape character, we still
+                                 store it as-is in the target buffer */
+             continue;
+           }
+         break;
+       case ',':
+         if (!starts_with_quote)
+           {
+             /* this signals the end of the value if we didn't get a starting quote
+                and then we do "sloppy" parsing */
+             c=0; /* the end */
+             continue;
+           }
+         break;
+       case '\r':
+       case '\n':
+         /* end of string */
+         c=0;
+       continue;
+       case '\"':
+         if (!escape && starts_with_quote)
+           {
+             /* end of string */
+             c=0;
+             continue;
+           }
+         break;
+       }
+      escape = false;
+      *value++ = *str;
+    }
+  *value = '\0';
+
+  *endptr = str;
+
+  return true; /* success */
+}
+
+static char *
+get_pa_var (const char *key, const char *pa, struct gc_arena *gc)
+{
+  char k[64];
+  char v[256];
+  const char *content = pa;
+
+  while (true)
+    {
+      const int status = get_key_value(content, k, v, sizeof(k), sizeof(v), &content);
+      if (status)
+       {
+         if (!strcmp(key, k))
+           return string_alloc(v, gc);
+       }
+      else
+       return NULL;
+
+      /* advance to start of next key */
+      if (*content == ',')
+       ++content;
+      while (*content && isspace(*content))
+       ++content;
+    }
+}
 
 struct http_proxy_info *
 http_proxy_new (const struct http_proxy_options *o,
@@ -265,7 +450,8 @@ http_proxy_new (const struct http_proxy_options *o,
 
          opt.server = auto_proxy_info->http.server;
          opt.port = auto_proxy_info->http.port;
-         opt.auth_retry = true;
+         if (!opt.auth_retry)
+           opt.auth_retry = PAR_ALL;
 
          o = &opt;
        }
@@ -287,12 +473,14 @@ http_proxy_new (const struct http_proxy_options *o,
        p->auth_method = HTTP_AUTH_NONE;
       else if (!strcmp (o->auth_method_string, "basic"))
        p->auth_method = HTTP_AUTH_BASIC;
+#if NTLM
       else if (!strcmp (o->auth_method_string, "ntlm"))
        p->auth_method = HTTP_AUTH_NTLM;
       else if (!strcmp (o->auth_method_string, "ntlm2"))
        p->auth_method = HTTP_AUTH_NTLM2;
+#endif
       else
-       msg (M_FATAL, "ERROR: unknown HTTP authentication method: '%s' -- only the 'none', 'basic', 'ntlm', or 'ntlm2' methods are currently supported",
+       msg (M_FATAL, "ERROR: unknown HTTP authentication method: '%s'",
             o->auth_method_string);
     }
 
@@ -326,101 +514,110 @@ establish_http_proxy_passthru (struct http_proxy_info *p,
                               volatile int *signal_received)
 {
   struct gc_arena gc = gc_new ();
-  char buf[256];
+  char buf[512];
   char buf2[128];
   char get[80];
   int status;
   int nparms;
   bool ret = false;
+  bool processed = false;
 
   /* get user/pass if not previously given or if --auto-proxy is being used */
   if (p->auth_method == HTTP_AUTH_BASIC
+      || p->auth_method == HTTP_AUTH_DIGEST
       || p->auth_method == HTTP_AUTH_NTLM)
     get_user_pass_http (p, false);
 
-  /* format HTTP CONNECT message */
-  openvpn_snprintf (buf, sizeof(buf), "CONNECT %s:%d HTTP/%s",
-                   host,
-                   port,
-                   p->options.http_version);
-
-  msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
-
-  /* send HTTP CONNECT message to proxy */
-  if (!send_line_crlf (sd, buf))
-    goto error;
-
-  /* send User-Agent string if provided */
-  if (p->options.user_agent)
+  /* are we being called again after getting the digest server nonce in the previous transaction? */
+  if (p->auth_method == HTTP_AUTH_DIGEST && p->proxy_authenticate)
     {
-      openvpn_snprintf (buf, sizeof(buf), "User-Agent: %s",
-                       p->options.user_agent);
-      if (!send_line_crlf (sd, buf))
-       goto error;
+      nparms = 1;
+      status = 407;
     }
-
-  /* auth specified? */
-  switch (p->auth_method)
+  else
     {
-    case HTTP_AUTH_NONE:
-      break;
+      /* format HTTP CONNECT message */
+      openvpn_snprintf (buf, sizeof(buf), "CONNECT %s:%d HTTP/%s",
+                       host,
+                       port,
+                       p->options.http_version);
 
-    case HTTP_AUTH_BASIC:
-      openvpn_snprintf (buf, sizeof(buf), "Proxy-Authorization: Basic %s",
-                       username_password_as_base64 (p, &gc));
-      msg (D_PROXY, "Attempting Basic Proxy-Authorization");
-      dmsg (D_SHOW_KEYS, "Send to HTTP proxy: '%s'", buf);
-      openvpn_sleep (1);
+      msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
+
+      /* send HTTP CONNECT message to proxy */
       if (!send_line_crlf (sd, buf))
        goto error;
-      break;
+
+      /* send User-Agent string if provided */
+      if (p->options.user_agent)
+       {
+         openvpn_snprintf (buf, sizeof(buf), "User-Agent: %s",
+                           p->options.user_agent);
+         if (!send_line_crlf (sd, buf))
+           goto error;
+       }
+
+      /* auth specified? */
+      switch (p->auth_method)
+       {
+       case HTTP_AUTH_NONE:
+         break;
+
+       case HTTP_AUTH_BASIC:
+         openvpn_snprintf (buf, sizeof(buf), "Proxy-Authorization: Basic %s",
+                           username_password_as_base64 (p, &gc));
+         msg (D_PROXY, "Attempting Basic Proxy-Authorization");
+         dmsg (D_SHOW_KEYS, "Send to HTTP proxy: '%s'", buf);
+         if (!send_line_crlf (sd, buf))
+           goto error;
+         break;
 
 #if NTLM
-    case HTTP_AUTH_NTLM:
-    case HTTP_AUTH_NTLM2:
-      /* keep-alive connection */
-      openvpn_snprintf (buf, sizeof(buf), "Proxy-Connection: Keep-Alive");
-      if (!send_line_crlf (sd, buf))
-       goto error;
+       case HTTP_AUTH_NTLM:
+       case HTTP_AUTH_NTLM2:
+         /* keep-alive connection */
+         openvpn_snprintf (buf, sizeof(buf), "Proxy-Connection: Keep-Alive");
+         if (!send_line_crlf (sd, buf))
+           goto error;
 
-      openvpn_snprintf (buf, sizeof(buf), "Proxy-Authorization: NTLM %s",
-                       ntlm_phase_1 (p, &gc));
-      msg (D_PROXY, "Attempting NTLM Proxy-Authorization phase 1");
-      dmsg (D_SHOW_KEYS, "Send to HTTP proxy: '%s'", buf);
-      openvpn_sleep (1);
-      if (!send_line_crlf (sd, buf))
-       goto error;
-      break;
+         openvpn_snprintf (buf, sizeof(buf), "Proxy-Authorization: NTLM %s",
+                           ntlm_phase_1 (p, &gc));
+         msg (D_PROXY, "Attempting NTLM Proxy-Authorization phase 1");
+         dmsg (D_SHOW_KEYS, "Send to HTTP proxy: '%s'", buf);
+         if (!send_line_crlf (sd, buf))
+           goto error;
+         break;
 #endif
 
-    default:
-      ASSERT (0);
-    }
+       default:
+         ASSERT (0);
+       }
 
-  /* send empty CR, LF */
-  openvpn_sleep (1);
-  if (!send_crlf (sd))
-    goto error;
+      /* send empty CR, LF */
+      if (!send_crlf (sd))
+       goto error;
 
-  /* receive reply from proxy */
-  if (!recv_line (sd, buf, sizeof(buf), p->options.timeout, true, NULL, signal_received))
-    goto error;
+      /* receive reply from proxy */
+      if (!recv_line (sd, buf, sizeof(buf), p->options.timeout, true, NULL, signal_received))
+       goto error;
+
+      /* remove trailing CR, LF */
+      chomp (buf);
 
-  /* remove trailing CR, LF */
-  chomp (buf);
+      msg (D_PROXY, "HTTP proxy returned: '%s'", buf);
 
-  msg (D_PROXY, "HTTP proxy returned: '%s'", buf);
+      /* parse return string */
+      nparms = sscanf (buf, "%*s %d", &status);
 
-  /* parse return string */
-  nparms = sscanf (buf, "%*s %d", &status);
+    }
 
   /* check for a "407 Proxy Authentication Required" response */
-  if (nparms >= 1 && status == 407)
+  while (nparms >= 1 && status == 407)
     {
       msg (D_PROXY, "Proxy requires authentication");
 
       /* check for NTLM */
-      if (p->auth_method == HTTP_AUTH_NTLM || p->auth_method == HTTP_AUTH_NTLM2)
+      if ((p->auth_method == HTTP_AUTH_NTLM || p->auth_method == HTTP_AUTH_NTLM2) && !processed)
         {
 #if NTLM
           /* look for the phase 2 response */
@@ -448,7 +645,7 @@ establish_http_proxy_passthru (struct http_proxy_info *p,
           msg (D_PROXY, "Received NTLM Proxy-Authorization phase 2 response");
 
           /* receive and discard everything else */
-          while (recv_line (sd, NULL, 0, p->options.timeout, true, NULL, signal_received))
+          while (recv_line (sd, NULL, 0, 2, true, NULL, signal_received))
             ;
 
           /* now send the phase 3 reply */
@@ -472,7 +669,6 @@ establish_http_proxy_passthru (struct http_proxy_info *p,
 
           
           /* send HOST etc, */
-          openvpn_sleep (1);
           openvpn_snprintf (buf, sizeof(buf), "Host: %s", host);
           msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
           if (!send_line_crlf (sd, buf))
@@ -490,12 +686,10 @@ establish_http_proxy_passthru (struct http_proxy_info *p,
          }
 
           msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
-          openvpn_sleep (1);
           if (!send_line_crlf (sd, buf))
            goto error;
           /* ok so far... */
           /* send empty CR, LF */
-          openvpn_sleep (1);
           if (!send_crlf (sd))
             goto error;
 
@@ -510,27 +704,167 @@ establish_http_proxy_passthru (struct http_proxy_info *p,
 
           /* parse return string */
           nparms = sscanf (buf, "%*s %d", &status);
-#else
-         ASSERT (0); /* No NTLM support */
+         processed = true;
 #endif
        }
-      else if (p->auth_method == HTTP_AUTH_NONE && p->options.auth_retry)
+#if PROXY_DIGEST_AUTH
+      else if (p->auth_method == HTTP_AUTH_DIGEST && !processed)
+       {
+         char *pa = p->proxy_authenticate;
+         const int method = p->auth_method;
+         ASSERT(pa);
+
+         if (method == HTTP_AUTH_DIGEST)
+           {
+             const char *http_method = "CONNECT";
+             const char *nonce_count = "00000001";
+             const char *qop = "auth";
+             const char *username = p->up.username;
+             const char *password = p->up.password;
+             char *opaque_kv = "";
+             char uri[128];
+             uint8_t cnonce_raw[8];
+             uint8_t *cnonce;
+             HASHHEX session_key;
+             HASHHEX response;
+
+             const char *realm = get_pa_var("realm", pa, &gc);
+             const char *nonce = get_pa_var("nonce", pa, &gc);
+             const char *algor = get_pa_var("algorithm", pa, &gc);
+             const char *opaque = get_pa_var("opaque", pa, &gc);
+
+             /* generate a client nonce */
+             ASSERT(RAND_bytes(cnonce_raw, sizeof(cnonce_raw)));
+             cnonce = make_base64_string2(cnonce_raw, sizeof(cnonce_raw), &gc);
+
+
+             /* build the digest response */
+             openvpn_snprintf (uri, sizeof(uri), "%s:%d",
+                               host,
+                               port);
+
+             if (opaque)
+               {
+                 const int len = strlen(opaque)+16;
+                 opaque_kv = gc_malloc(len, false, &gc);
+                 openvpn_snprintf (opaque_kv, len, ", opaque=\"%s\"", opaque);
+               }
+
+             DigestCalcHA1(algor,
+                           username,
+                           realm,
+                           password,
+                           nonce,
+                           cnonce,
+                           session_key);
+             DigestCalcResponse(session_key,
+                                nonce,
+                                nonce_count,
+                                cnonce,
+                                qop,
+                                http_method,
+                                uri,
+                                NULL,
+                                response);
+
+             /* format HTTP CONNECT message */
+             openvpn_snprintf (buf, sizeof(buf), "%s %s HTTP/%s",
+                               http_method,
+                               uri,
+                               p->options.http_version);
+
+             msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
+
+             /* send HTTP CONNECT message to proxy */
+             if (!send_line_crlf (sd, buf))
+               goto error;
+
+             /* send HOST etc, */
+             openvpn_snprintf (buf, sizeof(buf), "Host: %s", host);
+             msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
+             if (!send_line_crlf (sd, buf))
+               goto error;
+
+             /* send digest response */
+             openvpn_snprintf (buf, sizeof(buf), "Proxy-Authorization: Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", qop=%s, nc=%s, cnonce=\"%s\", response=\"%s\"%s",
+                               username,
+                               realm,
+                               nonce,
+                               uri,
+                               qop,
+                               nonce_count,
+                               cnonce,
+                               response,
+                               opaque_kv
+                               );
+             msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
+             if (!send_line_crlf (sd, buf))
+               goto error;
+             if (!send_crlf (sd))
+               goto error;
+
+             /* receive reply from proxy */
+             if (!recv_line (sd, buf, sizeof(buf), p->options.timeout, true, NULL, signal_received))
+               goto error;
+
+             /* remove trailing CR, LF */
+             chomp (buf);
+
+             msg (D_PROXY, "HTTP proxy returned: '%s'", buf);
+
+             /* parse return string */
+             nparms = sscanf (buf, "%*s %d", &status);
+             processed = true;
+           }
+         else
+           {
+             msg (D_PROXY, "HTTP proxy: digest method not supported");
+             goto error;
+           }
+       }
+#endif
+      else if (p->options.auth_retry)
        {
-         /*
-          * Proxy needs authentication, but we don't have a user/pass.
-          * Now we will change p->auth_method and return true so that
-          * our caller knows to call us again on a newly opened socket.
-          * JYFIXME: This code needs to check proxy error output and set
-          * JYFIXME: p->auth_method = HTTP_AUTH_NTLM if necessary.
-          */
-         p->auth_method = HTTP_AUTH_BASIC;
-         ret = true;
-         goto done;
+         /* figure out what kind of authentication the proxy needs */
+         char *pa = NULL;
+         const int method = get_proxy_authenticate(sd,
+                                                   p->options.timeout,
+                                                   &pa,
+                                                   NULL,
+                                                   signal_received);
+         if (method != HTTP_AUTH_NONE)
+           {
+             if (pa)
+               msg (D_PROXY, "HTTP proxy authenticate '%s'", pa);
+             if (p->options.auth_retry == PAR_NCT && method == HTTP_AUTH_BASIC)
+               {
+                 msg (D_PROXY, "HTTP proxy: support for basic auth and other cleartext proxy auth methods is disabled");
+                 goto error;
+               }
+             p->auth_method = method;
+             store_proxy_authenticate(p, pa);
+             ret = true;
+             goto done;
+           }
+         else
+           {
+             msg (D_PROXY, "HTTP proxy: do not recognize the authentication method required by proxy");
+             free (pa);
+             goto error;
+           }
        }
       else
-       goto error;
-    }
+       {
+         if (!processed)
+           msg (D_PROXY, "HTTP proxy: no support for proxy authentication method");
+         goto error;
+       }
 
+      /* clear state */
+      if (p->options.auth_retry)
+       clear_user_pass_http();
+      store_proxy_authenticate(p, NULL);
+    }
 
   /* check return code, success = 200 */
   if (nparms < 1 || status != 200)
@@ -538,13 +872,7 @@ establish_http_proxy_passthru (struct http_proxy_info *p,
       msg (D_LINK_ERRORS, "HTTP proxy returned bad status");
 #if 0
       /* DEBUGGING -- show a multi-line HTTP error response */
-      while (true)
-       {
-         if (!recv_line (sd, buf, sizeof (buf), p->options.timeout, true, NULL, signal_received))
-           goto error;
-         chomp (buf);
-         msg (D_PROXY, "HTTP proxy returned: '%s'", buf);
-       }
+      dump_residual(sd, p->options.timeout, signal_received);
 #endif
       goto error;
     }
diff --git a/proxy.h b/proxy.h
index c2afaec8f4de6c5a5c797b359e0f6518dffa4d4b..480cda14203429e4443bed7570bb5f4a67a90a43 100644 (file)
--- a/proxy.h
+++ b/proxy.h
@@ -55,18 +55,24 @@ void show_win_proxy_settings (const int msglevel);
 #ifdef ENABLE_HTTP_PROXY
 
 /* HTTP CONNECT authentication methods */
-#define HTTP_AUTH_NONE  0
-#define HTTP_AUTH_BASIC 1
-#define HTTP_AUTH_NTLM  2
-#define HTTP_AUTH_N     3
-#define HTTP_AUTH_NTLM2 4
+#define HTTP_AUTH_NONE   0
+#define HTTP_AUTH_BASIC  1
+#define HTTP_AUTH_DIGEST 2
+#define HTTP_AUTH_NTLM   3
+#define HTTP_AUTH_NTLM2  4
+#define HTTP_AUTH_N      5 /* number of HTTP_AUTH methods */
 
 struct http_proxy_options {
   const char *server;
   int port;
   bool retry;
   int timeout;
+
+# define PAR_NO  0  /* don't support any auth retries */
+# define PAR_ALL 1  /* allow all proxy auth protocols */
+# define PAR_NCT 2  /* disable cleartext proxy auth protocols */
   bool auth_retry;
+
   const char *auth_method_string;
   const char *auth_file;
   const char *http_version;
@@ -78,6 +84,7 @@ struct http_proxy_info {
   int auth_method;
   struct http_proxy_options options;
   struct user_pass up;
+  char *proxy_authenticate;
 };
 
 struct http_proxy_info *http_proxy_new (const struct http_proxy_options *o,
index bc54ce0e4d9d187ee7e6d0c16f1066fdbf846860..3d09ce61102123027bc3d1063614fd103e6cbc52 100644 (file)
--- a/syshead.h
+++ b/syshead.h
@@ -571,6 +571,15 @@ socket_defined (const socket_descriptor_t sd)
 #define NTLM 0
 #endif
 
+/*
+ * Should we include proxy digest auth functionality
+ */
+#if defined(USE_CRYPTO) && defined(ENABLE_HTTP_PROXY)
+#define PROXY_DIGEST_AUTH 1
+#else
+#define PROXY_DIGEST_AUTH 0
+#endif
+
 /*
  * Should we include code common to all proxy methods?
  */
index f42715bce3cc576505fd580a40737e9be565ed14..3a9fbc7cad540179ff1be3038a513e4ab684c396 100644 (file)
@@ -1,4 +1,4 @@
-import os\r
+import os, sys\r
 from wb import system, config, home_fn, cd_home\r
 \r
 os.environ['PATH'] += ";%s\\VC" % (os.path.normpath(config['MSVC']),)\r
@@ -10,6 +10,13 @@ def main():
     cd_home()\r
     build_vc("nmake /f %s" % (home_fn('msvc.mak'),))\r
 \r
+def clean():\r
+    cd_home()\r
+    build_vc("nmake /f %s clean" % (home_fn('msvc.mak'),))\r
+\r
 # if we are run directly, and not loaded as a module\r
 if __name__ == "__main__":\r
-    main()\r
+    if len(sys.argv) == 2 and sys.argv[1] == 'clean':\r
+        clean()\r
+    else:\r
+        main()\r