proxy.c
6fbf66fa
 /*
  *  OpenVPN -- An application to securely tunnel IP networks
  *             over a single TCP/UDP port, with support for SSL/TLS-based
  *             session authentication and key exchange,
  *             packet encryption, packet authentication, and
  *             packet compression.
  *
564a2109
  *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
6fbf66fa
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2
  *  as published by the Free Software Foundation.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program (see the file COPYING included with this
  *  distribution); if not, write to the Free Software Foundation, Inc.,
  *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #include "syshead.h"
 
 #include "common.h"
 #include "misc.h"
b27dc04c
 #include "crypto.h"
6fbf66fa
 #include "win32.h"
 #include "socket.h"
 #include "fdmisc.h"
 #include "proxy.h"
f214bb21
 #include "base64.h"
b27dc04c
 #include "httpdigest.h"
6fbf66fa
 #include "ntlm.h"
 
df5722cc
 #ifdef WIN32
 #include "ieproxy.h"
 #endif
 
6fbf66fa
 #include "memdbg.h"
 
f214bb21
 #ifdef ENABLE_HTTP_PROXY
df5722cc
 
6624b877
 #define UP_TYPE_PROXY        "HTTP Proxy"
 
6fbf66fa
 /* cached proxy username/password */
 static struct user_pass static_proxy_user_pass;
 
 static bool
 recv_line (socket_descriptor_t sd,
 	   char *buf,
 	   int len,
 	   const int timeout_sec,
 	   const bool verbose,
 	   struct buffer *lookahead,
 	   volatile int *signal_received)
 {
   struct buffer la;
   int lastc = 0;
 
   CLEAR (la);
   if (lookahead)
     la = *lookahead;
 
   while (true)
     {
       int status;
       ssize_t size;
       fd_set reads;
       struct timeval tv;
       uint8_t c;
 
       if (buf_defined (&la))
 	{
 	  ASSERT (buf_init (&la, 0));
 	}
 
       FD_ZERO (&reads);
       FD_SET (sd, &reads);
       tv.tv_sec = timeout_sec;
       tv.tv_usec = 0;
 
       status = select (sd + 1, &reads, NULL, NULL, &tv);
 
       get_signal (signal_received);
       if (*signal_received)
 	goto error;
 
       /* timeout? */
       if (status == 0)
 	{
 	  if (verbose)
 	    msg (D_LINK_ERRORS | M_ERRNO_SOCK, "recv_line: TCP port read timeout expired");
 	  goto error;
 	}
 
       /* error */
       if (status < 0)
 	{
 	  if (verbose)
 	    msg (D_LINK_ERRORS | M_ERRNO_SOCK, "recv_line: TCP port read failed on select()");
 	  goto error;
 	}
 
       /* read single char */
       size = recv (sd, &c, 1, MSG_NOSIGNAL);
 
       /* error? */
       if (size != 1)
 	{
 	  if (verbose)
 	    msg (D_LINK_ERRORS | M_ERRNO_SOCK, "recv_line: TCP port read failed on recv()");
 	  goto error;
 	}
 
 #if 0
       if (isprint(c))
 	msg (M_INFO, "PROXY: read '%c' (%d)", c, (int)c);
       else
 	msg (M_INFO, "PROXY: read (%d)", (int)c);
 #endif
 
       /* store char in buffer */
       if (len > 1)
 	{
 	  *buf++ = c;
 	  --len;
 	}
 
       /* also store char in lookahead buffer */
       if (buf_defined (&la))
 	{
 	  buf_write_u8 (&la, c);
 	  if (!isprint(c) && !isspace(c)) /* not ascii? */
 	    {
 	      if (verbose)
 		msg (D_LINK_ERRORS | M_ERRNO_SOCK, "recv_line: Non-ASCII character (%d) read on recv()", (int)c);
 	      *lookahead = la;
 	      return false;
 	    }
 	}
 
       /* end of line? */
       if (lastc == '\r' && c == '\n')
 	break;
 
       lastc = c;
     }
 
   /* append trailing null */
   if (len > 0)
     *buf++ = '\0';
 
   return true;
 
  error:
   return false;
 }
 
 static bool
 send_line (socket_descriptor_t sd,
 	   const char *buf)
 {
   const ssize_t size = send (sd, buf, strlen (buf), MSG_NOSIGNAL);
   if (size != (ssize_t) strlen (buf))
     {
       msg (D_LINK_ERRORS | M_ERRNO_SOCK, "send_line: TCP port write failed on send()");
       return false;
     }
   return true;
 }
 
 static bool
 send_line_crlf (socket_descriptor_t sd,
 		const char *src)
 {
   bool ret;
 
   struct buffer buf = alloc_buf (strlen (src) + 3);
   ASSERT (buf_write (&buf, src, strlen (src)));
   ASSERT (buf_write (&buf, "\r\n", 3));
   ret = send_line (sd, BSTR (&buf));
   free_buf (&buf);
   return ret;
 }
 
 static bool
 send_crlf (socket_descriptor_t sd)
 {
   return send_line_crlf (sd, "");
 }
 
 uint8_t *
 make_base64_string2 (const uint8_t *str, int src_len, struct gc_arena *gc)
 {
f214bb21
   uint8_t *ret = NULL;
   char *b64out = NULL;
   ASSERT (base64_encode ((const void *)str, src_len, &b64out) >= 0);
   ret = (uint8_t *) string_alloc (b64out, gc);
   free (b64out);
   return ret;
6fbf66fa
 }
 
 uint8_t *
 make_base64_string (const uint8_t *str, struct gc_arena *gc)
 {
   return make_base64_string2 (str, strlen ((const char *)str), gc);
 }
 
 static const char *
 username_password_as_base64 (const struct http_proxy_info *p,
 			     struct gc_arena *gc)
 {
   struct buffer out = alloc_buf_gc (strlen (p->up.username) + strlen (p->up.password) + 2, gc);
   ASSERT (strlen (p->up.username) > 0);
   buf_printf (&out, "%s:%s", p->up.username, p->up.password);
   return (const char *)make_base64_string ((const uint8_t*)BSTR (&out), gc);
 }
 
f214bb21
 static void
 get_user_pass_http (struct http_proxy_info *p, const bool force)
 {
   if (!static_proxy_user_pass.defined || force)
     {
3cf6c932
       unsigned int flags = GET_USER_PASS_MANAGEMENT;
       if (p->queried_creds)
 	flags |= GET_USER_PASS_PREVIOUS_CREDS_FAILED;
f214bb21
       get_user_pass (&static_proxy_user_pass,
 		     p->options.auth_file,
6624b877
 		     UP_TYPE_PROXY,
3cf6c932
 		     flags);
       p->queried_creds = true;
f214bb21
       p->up = static_proxy_user_pass;
     }
 }
b27dc04c
 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;
     }
 }
f214bb21
 
6fbf66fa
 struct http_proxy_info *
4e9a51d7
 http_proxy_new (const struct http_proxy_options *o,
 		struct auto_proxy_info *auto_proxy_info)
6fbf66fa
 {
   struct http_proxy_info *p;
f214bb21
   struct http_proxy_options opt;
 
   if (auto_proxy_info)
     {
       if (o && o->server)
 	{
 	  /* if --http-proxy explicitly given, disable auto-proxy */
 	  auto_proxy_info = NULL;
 	}
       else
 	{
 	  /* if no --http-proxy explicitly given and no auto settings, fail */
 	  if (!auto_proxy_info->http.server)
 	    return NULL;
6fbf66fa
 
f214bb21
 	  if (o)
 	    {
 	      opt = *o;
 	    }
 	  else
 	    {
 	      CLEAR (opt);
 	  
 	      /* These settings are only used for --auto-proxy */
 	      opt.timeout = 5;
 	      opt.http_version = "1.0";
 	    }
 
 	  opt.server = auto_proxy_info->http.server;
 	  opt.port = auto_proxy_info->http.port;
b27dc04c
 	  if (!opt.auth_retry)
 	    opt.auth_retry = PAR_ALL;
f214bb21
 
 	  o = &opt;
 	}
     }
 
   if (!o || !o->server)
6fbf66fa
     msg (M_FATAL, "HTTP_PROXY: server not specified");
 
   ASSERT (legal_ipv4_port (o->port));
 
4e9a51d7
   ALLOC_OBJ_CLEAR (p, struct http_proxy_info);
6fbf66fa
   p->options = *o;
 
   /* parse authentication method */
   p->auth_method = HTTP_AUTH_NONE;
   if (o->auth_method_string)
     {
       if (!strcmp (o->auth_method_string, "none"))
 	p->auth_method = HTTP_AUTH_NONE;
       else if (!strcmp (o->auth_method_string, "basic"))
 	p->auth_method = HTTP_AUTH_BASIC;
b27dc04c
 #if NTLM
6fbf66fa
       else if (!strcmp (o->auth_method_string, "ntlm"))
 	p->auth_method = HTTP_AUTH_NTLM;
1bda73a7
       else if (!strcmp (o->auth_method_string, "ntlm2"))
 	p->auth_method = HTTP_AUTH_NTLM2;
b27dc04c
 #endif
6fbf66fa
       else
b27dc04c
 	msg (M_FATAL, "ERROR: unknown HTTP authentication method: '%s'",
6fbf66fa
 	     o->auth_method_string);
     }
 
1bda73a7
   /* only basic and NTLM/NTLMv2 authentication supported so far */
   if (p->auth_method == HTTP_AUTH_BASIC || p->auth_method == HTTP_AUTH_NTLM || p->auth_method == HTTP_AUTH_NTLM2)
6fbf66fa
     {
f214bb21
       get_user_pass_http (p, true);
6fbf66fa
     }
 
 #if !NTLM
1bda73a7
   if (p->auth_method == HTTP_AUTH_NTLM || p->auth_method == HTTP_AUTH_NTLM2)
6fbf66fa
     msg (M_FATAL, "Sorry, this version of " PACKAGE_NAME " was built without NTLM Proxy support.");
 #endif
 
   p->defined = true;
   return p;
 }
 
4e9a51d7
 void
 http_proxy_close (struct http_proxy_info *hp)
 {
   free (hp);
 }
 
f214bb21
 bool
6fbf66fa
 establish_http_proxy_passthru (struct http_proxy_info *p,
 			       socket_descriptor_t sd, /* already open to proxy */
 			       const char *host,       /* openvpn server remote */
 			       const int port,         /* openvpn server port */
 			       struct buffer *lookahead,
 			       volatile int *signal_received)
 {
   struct gc_arena gc = gc_new ();
b27dc04c
   char buf[512];
6fbf66fa
   char buf2[128];
   char get[80];
   int status;
   int nparms;
f214bb21
   bool ret = false;
b27dc04c
   bool processed = false;
f214bb21
 
   /* get user/pass if not previously given or if --auto-proxy is being used */
   if (p->auth_method == HTTP_AUTH_BASIC
b27dc04c
       || p->auth_method == HTTP_AUTH_DIGEST
f214bb21
       || p->auth_method == HTTP_AUTH_NTLM)
     get_user_pass_http (p, false);
6fbf66fa
 
b27dc04c
   /* 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)
6fbf66fa
     {
b27dc04c
       nparms = 1;
       status = 407;
6fbf66fa
     }
b27dc04c
   else
6fbf66fa
     {
b27dc04c
       /* 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 */
6fbf66fa
       if (!send_line_crlf (sd, buf))
 	goto error;
b27dc04c
 
       /* 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;
6fbf66fa
 
 #if NTLM
b27dc04c
 	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;
1bda73a7
 
b27dc04c
 	  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;
6fbf66fa
 #endif
 
b27dc04c
 	default:
 	  ASSERT (0);
 	}
6fbf66fa
 
b27dc04c
       /* send empty CR, LF */
       if (!send_crlf (sd))
 	goto error;
6fbf66fa
 
b27dc04c
       /* receive reply from proxy */
       if (!recv_line (sd, buf, sizeof(buf), p->options.timeout, true, NULL, signal_received))
 	goto error;
6fbf66fa
 
b27dc04c
       /* remove trailing CR, LF */
       chomp (buf);
6fbf66fa
 
b27dc04c
       msg (D_PROXY, "HTTP proxy returned: '%s'", buf);
6fbf66fa
 
b27dc04c
       /* parse return string */
       nparms = sscanf (buf, "%*s %d", &status);
 
     }
6fbf66fa
 
   /* check for a "407 Proxy Authentication Required" response */
b27dc04c
   while (nparms >= 1 && status == 407)
6fbf66fa
     {
       msg (D_PROXY, "Proxy requires authentication");
 
d222fcc5
       if (p->auth_method == HTTP_AUTH_BASIC && !processed)
 	{
 	  processed = true;
 	}
       else if ((p->auth_method == HTTP_AUTH_NTLM || p->auth_method == HTTP_AUTH_NTLM2) && !processed) /* check for NTLM */
6fbf66fa
         {
 #if NTLM
           /* look for the phase 2 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);
 
               openvpn_snprintf (get, sizeof get, "%%*s NTLM %%%ds", (int) sizeof (buf2) - 1);
               nparms = sscanf (buf, get, buf2);
               buf2[127] = 0; /* we only need the beginning - ensure it's null terminated. */
 
               /* check for "Proxy-Authenticate: NTLM TlRM..." */
               if (nparms == 1)
                 {
                   /* parse buf2 */
                   msg (D_PROXY, "auth string: '%s'", buf2);
                   break;
                 }
             }
           /* if we are here then auth string was got */
           msg (D_PROXY, "Received NTLM Proxy-Authorization phase 2 response");
 
           /* receive and discard everything else */
b27dc04c
           while (recv_line (sd, NULL, 0, 2, true, NULL, signal_received))
6fbf66fa
             ;
 
           /* now send the phase 3 reply */
 
           /* 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;
 
1bda73a7
           /* keep-alive connection */
           openvpn_snprintf (buf, sizeof(buf), "Proxy-Connection: Keep-Alive");
           if (!send_line_crlf (sd, buf))
             goto error;
 
           
6fbf66fa
           /* 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;
 
           msg (D_PROXY, "Attempting NTLM Proxy-Authorization phase 3");
e691cd56
 	  {
 	    const char *np3 = ntlm_phase_3 (p, buf2, &gc);
 	    if (!np3)
 	      {
 		msg (D_PROXY, "NTLM Proxy-Authorization phase 3 failed: received corrupted data from proxy server");
 		goto error;
 	      }
 	    openvpn_snprintf (buf, sizeof(buf), "Proxy-Authorization: NTLM %s", np3);
 	  }
 
6fbf66fa
           msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
           if (!send_line_crlf (sd, buf))
 	    goto error;
           /* ok so far... */
           /* 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;
 
           /* remove trailing CR, LF */
           chomp (buf);
 
           msg (D_PROXY, "HTTP proxy returned: '%s'", buf);
 
           /* parse return string */
           nparms = sscanf (buf, "%*s %d", &status);
b27dc04c
 	  processed = true;
6fbf66fa
 #endif
 	}
b27dc04c
 #if PROXY_DIGEST_AUTH
       else if (p->auth_method == HTTP_AUTH_DIGEST && !processed)
f214bb21
 	{
b27dc04c
 	  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,
3cf6c932
 			    (char *)cnonce,
b27dc04c
 			    session_key);
 	      DigestCalcResponse(session_key,
 				 nonce,
 				 nonce_count,
3cf6c932
 				 (char *)cnonce,
b27dc04c
 				 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)
 	{
 	  /* 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;
 	    }
f214bb21
 	}
       else
b27dc04c
 	{
 	  if (!processed)
 	    msg (D_PROXY, "HTTP proxy: no support for proxy authentication method");
 	  goto error;
 	}
6fbf66fa
 
b27dc04c
       /* clear state */
       if (p->options.auth_retry)
 	clear_user_pass_http();
       store_proxy_authenticate(p, NULL);
     }
6fbf66fa
 
   /* check return code, success = 200 */
   if (nparms < 1 || status != 200)
     {
       msg (D_LINK_ERRORS, "HTTP proxy returned bad status");
f214bb21
 #if 0
6fbf66fa
       /* DEBUGGING -- show a multi-line HTTP error response */
b27dc04c
       dump_residual(sd, p->options.timeout, signal_received);
6fbf66fa
 #endif
       goto error;
     }
 
3cf6c932
   /* SUCCESS */
 
6fbf66fa
   /* receive line from proxy and discard */
   if (!recv_line (sd, NULL, 0, p->options.timeout, true, NULL, signal_received))
     goto error;
 
   /*
    * Toss out any extraneous chars, but don't throw away the
    * start of the OpenVPN data stream (put it in lookahead).
    */
   while (recv_line (sd, NULL, 0, 2, false, lookahead, signal_received))
     ;
 
3cf6c932
   /* reset queried_creds so that we don't think that the next creds request is due to an auth error */
   p->queried_creds = false;
 
6fbf66fa
 #if 0
   if (lookahead && BLEN (lookahead))
     msg (M_INFO, "HTTP PROXY: lookahead: %s", format_hex (BPTR (lookahead), BLEN (lookahead), 0));
 #endif
 
f214bb21
  done:
6fbf66fa
   gc_free (&gc);
f214bb21
   return ret;
6fbf66fa
 
  error:
   /* on error, should we exit or restart? */
   if (!*signal_received)
     *signal_received = (p->options.retry ? SIGUSR1 : SIGTERM); /* SOFT-SIGUSR1 -- HTTP proxy error */
   gc_free (&gc);
f214bb21
   return ret;
6fbf66fa
 }
 
 #else
 static void dummy(void) {}
 #endif /* ENABLE_HTTP_PROXY */
f214bb21
 
 #ifdef GENERAL_PROXY_SUPPORT
 
 #ifdef WIN32
 
 #if 0
 char *
 get_windows_internet_string (const DWORD dwOption, struct gc_arena *gc)
 {
   DWORD size = 0;
   char *ret = NULL;
 
   /* Initially, get size of return buffer */
   InternetQueryOption (NULL, dwOption, NULL, &size);
   if (size)
     {
       /* Now get actual info */
       ret = (INTERNET_PROXY_INFO *) gc_malloc (size, false, gc);
       if (!InternetQueryOption (NULL, dwOption, (LPVOID) ret, &size))
 	ret = NULL;
     }
   return ret;
 }
 #endif
 
 static INTERNET_PROXY_INFO *
 get_windows_proxy_settings (struct gc_arena *gc)
 {
   DWORD size = 0;
   INTERNET_PROXY_INFO *ret = NULL;
 
   /* Initially, get size of return buffer */
   InternetQueryOption (NULL, INTERNET_OPTION_PROXY, NULL, &size);
   if (size)
     {
       /* Now get actual info */
       ret = (INTERNET_PROXY_INFO *) gc_malloc (size, false, gc);
       if (!InternetQueryOption (NULL, INTERNET_OPTION_PROXY, (LPVOID) ret, &size))
 	ret = NULL;
     }
   return ret;
 }
 
 static const char *
 parse_windows_proxy_setting (const char *str, struct auto_proxy_info_entry *e, struct gc_arena *gc)
 {
   char buf[128];
   const char *ret = NULL;
   struct buffer in;
 
   CLEAR (*e);
 
   buf_set_read (&in, (const uint8_t *)str, strlen (str));
 
   if (strchr (str, '=') != NULL)
     {
       if (buf_parse (&in, '=', buf, sizeof (buf)))
 	ret = string_alloc (buf, gc);
     }
 	
   if (buf_parse (&in, ':', buf, sizeof (buf)))
     e->server = string_alloc (buf, gc);
 
   if (e->server && buf_parse (&in, '\0', buf, sizeof (buf)))
     e->port = atoi (buf);
 
   return ret;
 }
 
 static void
 parse_windows_proxy_setting_list (const char *str, const char *type, struct auto_proxy_info_entry *e, struct gc_arena *gc)
 {
   struct gc_arena gc_local = gc_new ();
   struct auto_proxy_info_entry el;
 
   CLEAR (*e);
   if (type)
     {
       char buf[128];
       struct buffer in;
 
       buf_set_read (&in, (const uint8_t *)str, strlen (str));
       if (strchr (str, '=') != NULL)
 	{
 	  while (buf_parse (&in, ' ', buf, sizeof (buf)))
 	    {
 	      const char *t = parse_windows_proxy_setting (buf, &el, &gc_local);
 	      if (t && !strcmp (t, type))
 		goto found;
 	    }
 	}
     }
   else
     {
       if (!parse_windows_proxy_setting (str, &el, &gc_local))
 	goto found;
     }
   goto done;
 
  found:
   if (el.server && el.port > 0)
     {
       e->server = string_alloc (el.server, gc);
       e->port = el.port;
     }
 
  done:
   gc_free (&gc_local);
 }
 
 static const char *
 win_proxy_access_type (const DWORD dwAccessType)
 {
   switch (dwAccessType)
     {
     case INTERNET_OPEN_TYPE_DIRECT:
       return "INTERNET_OPEN_TYPE_DIRECT";
     case INTERNET_OPEN_TYPE_PROXY:
       return "INTERNET_OPEN_TYPE_PROXY";
     default:
       return "[UNKNOWN]";
     }
 }
 
 void
 show_win_proxy_settings (const int msglevel)
 {
   INTERNET_PROXY_INFO *info;
   struct gc_arena gc = gc_new ();
 
   info = get_windows_proxy_settings (&gc);
   msg (msglevel, "PROXY INFO: %s %s",
        win_proxy_access_type (info->dwAccessType),
        info->lpszProxy ? info->lpszProxy : "[NULL]");
 
   gc_free (&gc);
 }
 
 struct auto_proxy_info *
 get_proxy_settings (char **err, struct gc_arena *gc)
 {
   struct gc_arena gc_local = gc_new ();
   INTERNET_PROXY_INFO *info;
   struct auto_proxy_info *pi;
 
   ALLOC_OBJ_CLEAR_GC (pi, struct auto_proxy_info, gc);
 
   if (err)
     *err = NULL;
 
   info = get_windows_proxy_settings (&gc_local);
 
   if (!info)
     {
       if (err)
 	*err = "PROXY: failed to obtain windows proxy info";
       goto done;
     }
 
   switch (info->dwAccessType)
     {
     case INTERNET_OPEN_TYPE_DIRECT:
       break;
     case INTERNET_OPEN_TYPE_PROXY:
       if (!info->lpszProxy)
 	break;
       parse_windows_proxy_setting_list (info->lpszProxy, NULL, &pi->http, gc);
       if (!pi->http.server)
 	parse_windows_proxy_setting_list (info->lpszProxy, "http", &pi->http, gc);
       parse_windows_proxy_setting_list (info->lpszProxy, "socks", &pi->socks, gc);
       break;
     default:
       if (err)
 	*err = "PROXY: unknown proxy type";
       break;
     }
 
  done:
   gc_free (&gc_local);
   return pi;
 }
 
 #else
 
 struct auto_proxy_info *
 get_proxy_settings (char **err, struct gc_arena *gc)
 {
 #if 1
   if (err)
     *err = string_alloc ("PROXY: automatic detection not supported on this OS", gc);
   return NULL;
657ecf14
 #else /* test --auto-proxy feature */
f214bb21
   struct auto_proxy_info *pi;
   ALLOC_OBJ_CLEAR_GC (pi, struct auto_proxy_info, gc);
   pi->http.server = "10.10.0.2";
   pi->http.port = 4000;
   return pi;
 #endif
 }
 
 #endif
 
 #endif /* GENERAL_PROXY_SUPPORT */