src/openvpn/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.
  *
49979459
  *  Copyright (C) 2002-2018 OpenVPN 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.
  *
caa54ac3
  *  You should have received a copy of the GNU General Public License along
  *  with this program; if not, write to the Free Software Foundation, Inc.,
  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
6fbf66fa
  */
 
c110b289
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #elif defined(_MSC_VER)
 #include "config-msvc.h"
 #endif
 
6fbf66fa
 #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"
 #include "memdbg.h"
f2134b7b
 #include "forward.h"
6fbf66fa
 
6624b877
 #define UP_TYPE_PROXY        "HTTP Proxy"
 
af1bf85a
 struct http_proxy_options *
81d882d5
 init_http_proxy_options_once(struct http_proxy_options **hpo,
                              struct gc_arena *gc)
af1bf85a
 {
81d882d5
     if (!*hpo)
af1bf85a
     {
81d882d5
         ALLOC_OBJ_CLEAR_GC(*hpo, struct http_proxy_options, gc);
         /* http proxy defaults */
         (*hpo)->http_version = "1.0";
af1bf85a
     }
81d882d5
     return *hpo;
af1bf85a
 }
 
 
6fbf66fa
 /* cached proxy username/password */
 static struct user_pass static_proxy_user_pass;
 
 static bool
81d882d5
 recv_line(socket_descriptor_t sd,
           char *buf,
           int len,
           const int timeout_sec,
           const bool verbose,
           struct buffer *lookahead,
           volatile int *signal_received)
6fbf66fa
 {
81d882d5
     struct buffer la;
     int lastc = 0;
6fbf66fa
 
81d882d5
     CLEAR(la);
     if (lookahead)
     {
         la = *lookahead;
     }
6fbf66fa
 
81d882d5
     while (true)
6fbf66fa
     {
81d882d5
         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);
         openvpn_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, "recv_line: TCP port read timeout expired");
             }
             goto error;
         }
 
         /* error */
         if (status < 0)
         {
             if (verbose)
             {
                 msg(D_LINK_ERRORS | M_ERRNO, "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, "recv_line: TCP port read failed on recv()");
             }
             goto error;
         }
6fbf66fa
 
 #if 0
81d882d5
         if (isprint(c))
         {
             msg(M_INFO, "PROXY: read '%c' (%d)", c, (int)c);
         }
         else
         {
             msg(M_INFO, "PROXY: read (%d)", (int)c);
         }
6fbf66fa
 #endif
 
81d882d5
         /* 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, "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;
6fbf66fa
     }
 
81d882d5
     /* append trailing null */
     if (len > 0)
     {
         *buf++ = '\0';
     }
6fbf66fa
 
81d882d5
     return true;
6fbf66fa
 
81d882d5
 error:
     return false;
6fbf66fa
 }
 
 static bool
81d882d5
 send_line(socket_descriptor_t sd,
           const char *buf)
6fbf66fa
 {
81d882d5
     const ssize_t size = send(sd, buf, strlen(buf), MSG_NOSIGNAL);
     if (size != (ssize_t) strlen(buf))
6fbf66fa
     {
81d882d5
         msg(D_LINK_ERRORS | M_ERRNO, "send_line: TCP port write failed on send()");
         return false;
6fbf66fa
     }
81d882d5
     return true;
6fbf66fa
 }
 
 static bool
81d882d5
 send_line_crlf(socket_descriptor_t sd,
                const char *src)
6fbf66fa
 {
81d882d5
     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;
6fbf66fa
 }
 
 static bool
81d882d5
 send_crlf(socket_descriptor_t sd)
6fbf66fa
 {
81d882d5
     return send_line_crlf(sd, "");
6fbf66fa
 }
 
 uint8_t *
81d882d5
 make_base64_string2(const uint8_t *str, int src_len, struct gc_arena *gc)
6fbf66fa
 {
81d882d5
     uint8_t *ret = NULL;
     char *b64out = NULL;
     ASSERT(openvpn_base64_encode((const void *)str, src_len, &b64out) >= 0);
     ret = (uint8_t *) string_alloc(b64out, gc);
     free(b64out);
     return ret;
6fbf66fa
 }
 
 uint8_t *
81d882d5
 make_base64_string(const uint8_t *str, struct gc_arena *gc)
6fbf66fa
 {
81d882d5
     return make_base64_string2(str, strlen((const char *)str), gc);
6fbf66fa
 }
 
 static const char *
81d882d5
 username_password_as_base64(const struct http_proxy_info *p,
                             struct gc_arena *gc)
6fbf66fa
 {
81d882d5
     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);
6fbf66fa
 }
 
f214bb21
 static void
86b58ceb
 clear_user_pass_http(void)
 {
     purge_user_pass(&static_proxy_user_pass, true);
 }
 
 static void
81d882d5
 get_user_pass_http(struct http_proxy_info *p, const bool force)
f214bb21
 {
86b58ceb
     /*
      * in case of forced (re)load, make sure the static storage is set as
      * undefined, otherwise get_user_pass() won't try to load any credential
      */
     if (force)
     {
         clear_user_pass_http();
     }
 
     if (!static_proxy_user_pass.defined)
f214bb21
     {
81d882d5
         unsigned int flags = GET_USER_PASS_MANAGEMENT;
         if (p->queried_creds)
         {
             flags |= GET_USER_PASS_PREVIOUS_CREDS_FAILED;
         }
         if (p->options.inline_creds)
         {
             flags |= GET_USER_PASS_INLINE_CREDS;
         }
         get_user_pass(&static_proxy_user_pass,
                       p->options.auth_file,
                       UP_TYPE_PROXY,
                       flags);
         p->queried_creds = true;
         p->up = static_proxy_user_pass;
f214bb21
     }
 }
b27dc04c
 
f2134b7b
 #if 0
 /* function only used in #if 0 debug statement */
b27dc04c
 static void
81d882d5
 dump_residual(socket_descriptor_t sd,
               int timeout,
               volatile int *signal_received)
b27dc04c
 {
81d882d5
     char buf[256];
     while (true)
b27dc04c
     {
81d882d5
         if (!recv_line(sd, buf, sizeof(buf), timeout, true, NULL, signal_received))
         {
             return;
         }
         chomp(buf);
         msg(D_PROXY, "PROXY HEADER: '%s'", buf);
b27dc04c
     }
 }
f2134b7b
 #endif
b27dc04c
 
 /*
  * Extract the Proxy-Authenticate header from the stream.
  * Consumes all headers.
  */
 static int
81d882d5
 get_proxy_authenticate(socket_descriptor_t sd,
                        int timeout,
                        char **data,
                        volatile int *signal_received)
b27dc04c
 {
81d882d5
     char buf[256];
     int ret = HTTP_AUTH_NONE;
     while (true)
b27dc04c
     {
81d882d5
         if (!recv_line(sd, buf, sizeof(buf), timeout, true, NULL, signal_received))
         {
8d606cd3
             free(*data);
81d882d5
             *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);
2dca268a
                 *data = string_alloc(buf+26, NULL);
81d882d5
                 ret = HTTP_AUTH_BASIC;
             }
b27dc04c
 #if PROXY_DIGEST_AUTH
81d882d5
             else if (!strncmp(buf+20, "Digest ", 7))
             {
                 msg(D_PROXY, "PROXY AUTH DIGEST: '%s'", buf);
2dca268a
                 *data = string_alloc(buf+27, NULL);
81d882d5
                 ret = HTTP_AUTH_DIGEST;
             }
b27dc04c
 #endif
 #if NTLM
81d882d5
             else if (!strncmp(buf+20, "NTLM", 4))
             {
                 msg(D_PROXY, "PROXY AUTH HTLM: '%s'", buf);
                 *data = NULL;
                 ret = HTTP_AUTH_NTLM;
             }
b27dc04c
 #endif
81d882d5
         }
b27dc04c
     }
 }
 
 static void
81d882d5
 store_proxy_authenticate(struct http_proxy_info *p, char *data)
b27dc04c
 {
81d882d5
     if (p->proxy_authenticate)
     {
         free(p->proxy_authenticate);
     }
     p->proxy_authenticate = data;
b27dc04c
 }
 
 /*
  * 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 */
81d882d5
               char *key,             /* key stored here */
               char *value,           /* value stored here */
               int max_key_len,
               int max_value_len,
               const char **endptr)   /* next search position */
b27dc04c
 {
81d882d5
     int c;
     bool starts_with_quote = false;
     bool escape = false;
b27dc04c
 
81d882d5
     for (c = max_key_len-1; (*str && (*str != '=') && c--); )
4cd4899e
     {
81d882d5
         *key++ = *str++;
4cd4899e
     }
81d882d5
     *key = '\0';
b27dc04c
 
81d882d5
     if ('=' != *str++)
     {
         /* no key/value found */
         return false;
     }
b27dc04c
 
81d882d5
     if ('\"' == *str)
b27dc04c
     {
81d882d5
         /* quoted string */
         str++;
         starts_with_quote = true;
b27dc04c
     }
 
81d882d5
     for (c = max_value_len-1; *str && c--; str++)
b27dc04c
     {
81d882d5
         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;
b27dc04c
     }
81d882d5
     *value = '\0';
b27dc04c
 
81d882d5
     *endptr = str;
b27dc04c
 
81d882d5
     return true; /* success */
b27dc04c
 }
 
 static char *
81d882d5
 get_pa_var(const char *key, const char *pa, struct gc_arena *gc)
b27dc04c
 {
81d882d5
     char k[64];
     char v[256];
     const char *content = pa;
b27dc04c
 
81d882d5
     while (true)
b27dc04c
     {
81d882d5
         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))
4cd4899e
         {
81d882d5
             ++content;
4cd4899e
         }
b27dc04c
     }
 }
f214bb21
 
6fbf66fa
 struct http_proxy_info *
81d882d5
 http_proxy_new(const struct http_proxy_options *o)
6fbf66fa
 {
81d882d5
     struct http_proxy_info *p;
f214bb21
 
81d882d5
     if (!o || !o->server)
     {
         msg(M_FATAL, "HTTP_PROXY: server not specified");
     }
6fbf66fa
 
81d882d5
     ASSERT( o->port);
6fbf66fa
 
81d882d5
     ALLOC_OBJ_CLEAR(p, struct http_proxy_info);
     p->options = *o;
6fbf66fa
 
81d882d5
     /* parse authentication method */
     p->auth_method = HTTP_AUTH_NONE;
     if (o->auth_method_string)
6fbf66fa
     {
81d882d5
         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
81d882d5
         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;
         }
b27dc04c
 #endif
81d882d5
         else
         {
             msg(M_FATAL, "ERROR: unknown HTTP authentication method: '%s'",
                 o->auth_method_string);
         }
6fbf66fa
     }
 
81d882d5
     /* 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
     {
81d882d5
         get_user_pass_http(p, true);
6fbf66fa
     }
 
 #if !NTLM
81d882d5
     if (p->auth_method == HTTP_AUTH_NTLM || p->auth_method == HTTP_AUTH_NTLM2)
     {
         msg(M_FATAL, "Sorry, this version of " PACKAGE_NAME " was built without NTLM Proxy support.");
     }
6fbf66fa
 #endif
 
81d882d5
     p->defined = true;
     return p;
6fbf66fa
 }
 
4e9a51d7
 void
81d882d5
 http_proxy_close(struct http_proxy_info *hp)
4e9a51d7
 {
81d882d5
     free(hp);
4e9a51d7
 }
 
72bcdfdc
 static bool
81d882d5
 add_proxy_headers(struct http_proxy_info *p,
                   socket_descriptor_t sd, /* already open to proxy */
                   const char *host,       /* openvpn server remote */
                   const char *port        /* openvpn server port */
                   )
d0cb816c
 {
81d882d5
     char buf[512];
     int i;
     bool host_header_sent = false;
 
     /*
      * Send custom headers if provided
      * If content is NULL the whole header is in name
      * Also remember if we already sent a Host: header
      */
     for  (i = 0; i < MAX_CUSTOM_HTTP_HEADER && p->options.custom_headers[i].name; i++)
d0cb816c
     {
81d882d5
         if (p->options.custom_headers[i].content)
         {
             openvpn_snprintf(buf, sizeof(buf), "%s: %s",
                              p->options.custom_headers[i].name,
                              p->options.custom_headers[i].content);
             if (!strcasecmp(p->options.custom_headers[i].name, "Host"))
             {
                 host_header_sent = true;
             }
         }
         else
         {
             openvpn_snprintf(buf, sizeof(buf), "%s",
                              p->options.custom_headers[i].name);
             if (!strncasecmp(p->options.custom_headers[i].name, "Host:", 5))
             {
                 host_header_sent = true;
             }
         }
 
         msg(D_PROXY, "Send to HTTP proxy: '%s'", buf);
         if (!send_line_crlf(sd, buf))
         {
             return false;
         }
d0cb816c
     }
 
81d882d5
     if (!host_header_sent)
d0cb816c
     {
81d882d5
         openvpn_snprintf(buf, sizeof(buf), "Host: %s", host);
         msg(D_PROXY, "Send to HTTP proxy: '%s'", buf);
         if (!send_line_crlf(sd, buf))
         {
             return false;
         }
d0cb816c
     }
 
81d882d5
     /* send User-Agent string if provided */
     if (p->options.user_agent)
d0cb816c
     {
81d882d5
         openvpn_snprintf(buf, sizeof(buf), "User-Agent: %s",
                          p->options.user_agent);
         msg(D_PROXY, "Send to HTTP proxy: '%s'", buf);
         if (!send_line_crlf(sd, buf))
         {
             return false;
         }
d0cb816c
     }
 
81d882d5
     return true;
d0cb816c
 }
 
 
 bool
81d882d5
 establish_http_proxy_passthru(struct http_proxy_info *p,
                               socket_descriptor_t sd,  /* already open to proxy */
                               const char *host,        /* openvpn server remote */
                               const char *port,          /* openvpn server port */
                               struct event_timeout *server_poll_timeout,
                               struct buffer *lookahead,
                               volatile int *signal_received)
6fbf66fa
 {
81d882d5
     struct gc_arena gc = gc_new();
     char buf[512];
     char buf2[129];
     char get[80];
     int status;
     int nparms;
     bool ret = false;
     bool processed = false;
 
     /* get user/pass if not previously given */
     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);
     }
 
     /* 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
     {
81d882d5
         nparms = 1;
         status = 407;
6fbf66fa
     }
81d882d5
     else
6fbf66fa
     {
81d882d5
         /* format HTTP CONNECT message */
         openvpn_snprintf(buf, sizeof(buf), "CONNECT %s:%s 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;
         }
 
         if (!add_proxy_headers(p, sd, host, port))
         {
             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
81d882d5
             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);
                 if (!send_line_crlf(sd, buf))
                 {
                     goto error;
                 }
                 break;
6fbf66fa
 #endif
 
81d882d5
             default:
                 ASSERT(0);
         }
6fbf66fa
 
81d882d5
         /* send empty CR, LF */
         if (!send_crlf(sd))
         {
             goto error;
         }
6fbf66fa
 
81d882d5
         /* receive reply from proxy */
         if (!recv_line(sd, buf, sizeof(buf), get_server_poll_remaining_time(server_poll_timeout), true, NULL, signal_received))
         {
             goto error;
         }
6fbf66fa
 
81d882d5
         /* remove trailing CR, LF */
         chomp(buf);
6fbf66fa
 
81d882d5
         msg(D_PROXY, "HTTP proxy returned: '%s'", buf);
6fbf66fa
 
81d882d5
         /* parse return string */
         nparms = sscanf(buf, "%*s %d", &status);
b27dc04c
 
     }
6fbf66fa
 
81d882d5
     /* check for a "407 Proxy Authentication Required" response */
     while (nparms >= 1 && status == 407)
6fbf66fa
     {
81d882d5
         msg(D_PROXY, "Proxy requires authentication");
6fbf66fa
 
81d882d5
         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
81d882d5
             /* look for the phase 2 response */
6fbf66fa
 
81d882d5
             while (true)
6fbf66fa
             {
81d882d5
                 if (!recv_line(sd, buf, sizeof(buf), get_server_poll_remaining_time(server_poll_timeout), true, NULL, signal_received))
                 {
                     goto error;
                 }
                 chomp(buf);
                 msg(D_PROXY, "HTTP proxy returned: '%s'", buf);
6fbf66fa
 
81d882d5
                 openvpn_snprintf(get, sizeof get, "%%*s NTLM %%%ds", (int) sizeof(buf2) - 1);
                 nparms = sscanf(buf, get, buf2);
                 buf2[128] = 0; /* we only need the beginning - ensure it's null terminated. */
6fbf66fa
 
81d882d5
                 /* check for "Proxy-Authenticate: NTLM TlRM..." */
                 if (nparms == 1)
6fbf66fa
                 {
81d882d5
                     /* parse buf2 */
                     msg(D_PROXY, "auth string: '%s'", buf2);
                     break;
6fbf66fa
                 }
             }
81d882d5
             /* if we are here then auth string was got */
             msg(D_PROXY, "Received NTLM Proxy-Authorization phase 2 response");
6fbf66fa
 
81d882d5
             /* receive and discard everything else */
             while (recv_line(sd, NULL, 0, 2, true, NULL, signal_received))
4cd4899e
             {
             }
6fbf66fa
 
81d882d5
             /* now send the phase 3 reply */
6fbf66fa
 
81d882d5
             /* format HTTP CONNECT message */
             openvpn_snprintf(buf, sizeof(buf), "CONNECT %s:%s HTTP/%s",
                              host,
                              port,
                              p->options.http_version);
6fbf66fa
 
81d882d5
             msg(D_PROXY, "Send to HTTP proxy: '%s'", buf);
6fbf66fa
 
81d882d5
             /* send HTTP CONNECT message to proxy */
             if (!send_line_crlf(sd, buf))
             {
                 goto error;
             }
6fbf66fa
 
81d882d5
             /* keep-alive connection */
             openvpn_snprintf(buf, sizeof(buf), "Proxy-Connection: Keep-Alive");
             if (!send_line_crlf(sd, buf))
             {
                 goto error;
             }
1bda73a7
 
81d882d5
             /* send HOST etc, */
             if (!add_proxy_headers(p, sd, host, port))
             {
                 goto error;
             }
6fbf66fa
 
81d882d5
             msg(D_PROXY, "Attempting NTLM Proxy-Authorization phase 3");
             {
                 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);
             }
 
             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;
             }
6fbf66fa
 
81d882d5
             /* receive reply from proxy */
             if (!recv_line(sd, buf, sizeof(buf), get_server_poll_remaining_time(server_poll_timeout), true, NULL, signal_received))
             {
                 goto error;
             }
6fbf66fa
 
81d882d5
             /* remove trailing CR, LF */
             chomp(buf);
6fbf66fa
 
81d882d5
             msg(D_PROXY, "HTTP proxy returned: '%s'", buf);
 
             /* parse return string */
             nparms = sscanf(buf, "%*s %d", &status);
             processed = true;
 #endif /* if NTLM */
         }
b27dc04c
 #if PROXY_DIGEST_AUTH
81d882d5
         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);
 
14865773
                 if ( !realm || !nonce )
                 {
                     msg(D_LINK_ERRORS, "HTTP proxy: digest auth failed, malformed response "
                             "from server: realm= or nonce= missing" );
                     goto error;
                 }
 
81d882d5
                 /* 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:%s",
                                  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,
                               (char *)cnonce,
                               session_key);
                 DigestCalcResponse(session_key,
                                    nonce,
                                    nonce_count,
                                    (char *)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, */
                 if (!add_proxy_headers(p, sd, host, port))
                 {
                     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), get_server_poll_remaining_time(server_poll_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 /* if PROXY_DIGEST_AUTH */
         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,
                                                       get_server_poll_remaining_time(server_poll_timeout),
                                                       &pa,
                                                       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");
8d606cd3
                     free(pa);
81d882d5
                     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
         {
             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);
b27dc04c
     }
6fbf66fa
 
81d882d5
     /* check return code, success = 200 */
     if (nparms < 1 || status != 200)
6fbf66fa
     {
81d882d5
         msg(D_LINK_ERRORS, "HTTP proxy returned bad status");
f214bb21
 #if 0
81d882d5
         /* DEBUGGING -- show a multi-line HTTP error response */
         dump_residual(sd, get_server_poll_remaining_time(server_poll_timeout), signal_received);
6fbf66fa
 #endif
81d882d5
         goto error;
6fbf66fa
     }
 
81d882d5
     /* SUCCESS */
3cf6c932
 
81d882d5
     /* receive line from proxy and discard */
     if (!recv_line(sd, NULL, 0, get_server_poll_remaining_time(server_poll_timeout), true, NULL, signal_received))
     {
         goto error;
     }
6fbf66fa
 
81d882d5
     /*
      * 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))
4cd4899e
     {
     }
6fbf66fa
 
81d882d5
     /* reset queried_creds so that we don't think that the next creds request is due to an auth error */
     p->queried_creds = false;
3cf6c932
 
6fbf66fa
 #if 0
81d882d5
     if (lookahead && BLEN(lookahead))
     {
         msg(M_INFO, "HTTP PROXY: lookahead: %s", format_hex(BPTR(lookahead), BLEN(lookahead), 0));
     }
6fbf66fa
 #endif
 
81d882d5
 done:
     gc_free(&gc);
     return ret;
6fbf66fa
 
81d882d5
 error:
     if (!*signal_received)
     {
         *signal_received = SIGUSR1; /* SOFT-SIGUSR1 -- HTTP proxy error */
     }
     gc_free(&gc);
     return ret;
6fbf66fa
 }