mirror of
https://github.com/OpenVPN/openvpn.git
synced 2025-05-08 21:25:53 +08:00

Prevents that the client crashes if the peer does not specify the 'realm' and/or 'nonce' values. These pointers are dereferenced in DigestCalcHA1() and DigestCalcResponse(); hence, if not set, a null-pointer dereference would occur. Signed-off-by: Guido Vranken <guidovranken@gmail.com> Acked-by: Gert Doering <gert@greenie.muc.de> Message-Id: <1497574736-2092-1-git-send-email-gv@guidovranken.nl> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg14844.html Signed-off-by: Gert Doering <gert@greenie.muc.de> (cherry picked from commit 14865773ad64d861128bc80ad44c37bdc307c996) (cherry picked from commit 479b6d13d8c230c11b6315665bf00998a1424eef)
1138 lines
27 KiB
C
1138 lines
27 KiB
C
/*
|
|
* 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.
|
|
*
|
|
* Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
|
|
*
|
|
* 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"
|
|
#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
|
|
#include "ieproxy.h"
|
|
#endif
|
|
|
|
#include "memdbg.h"
|
|
|
|
#ifdef ENABLE_HTTP_PROXY
|
|
|
|
#define UP_TYPE_PROXY "HTTP Proxy"
|
|
|
|
/* 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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static void
|
|
get_user_pass_http (struct http_proxy_info *p, const bool force)
|
|
{
|
|
if (!static_proxy_user_pass.defined || force)
|
|
{
|
|
unsigned int flags = GET_USER_PASS_MANAGEMENT;
|
|
if (p->queried_creds)
|
|
flags |= GET_USER_PASS_PREVIOUS_CREDS_FAILED;
|
|
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;
|
|
}
|
|
}
|
|
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))
|
|
{
|
|
free(*data);
|
|
*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,
|
|
struct auto_proxy_info *auto_proxy_info)
|
|
{
|
|
struct http_proxy_info *p;
|
|
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;
|
|
|
|
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;
|
|
if (!opt.auth_retry)
|
|
opt.auth_retry = PAR_ALL;
|
|
|
|
o = &opt;
|
|
}
|
|
}
|
|
|
|
if (!o || !o->server)
|
|
msg (M_FATAL, "HTTP_PROXY: server not specified");
|
|
|
|
ASSERT (legal_ipv4_port (o->port));
|
|
|
|
ALLOC_OBJ_CLEAR (p, struct http_proxy_info);
|
|
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;
|
|
#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'",
|
|
o->auth_method_string);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
get_user_pass_http (p, true);
|
|
}
|
|
|
|
#if !NTLM
|
|
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.");
|
|
#endif
|
|
|
|
p->defined = true;
|
|
return p;
|
|
}
|
|
|
|
void
|
|
http_proxy_close (struct http_proxy_info *hp)
|
|
{
|
|
free (hp);
|
|
}
|
|
|
|
bool
|
|
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 ();
|
|
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 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);
|
|
|
|
/* 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)
|
|
{
|
|
nparms = 1;
|
|
status = 407;
|
|
}
|
|
else
|
|
{
|
|
/* 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;
|
|
|
|
openvpn_snprintf(buf, sizeof(buf), "Host: %s", host);
|
|
if (!send_line_crlf(sd, buf))
|
|
goto error;
|
|
|
|
/* 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;
|
|
|
|
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);
|
|
}
|
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
/* check for a "407 Proxy Authentication Required" response */
|
|
while (nparms >= 1 && status == 407)
|
|
{
|
|
msg (D_PROXY, "Proxy requires authentication");
|
|
|
|
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 */
|
|
{
|
|
#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[128] = 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 */
|
|
while (recv_line (sd, NULL, 0, 2, true, NULL, signal_received))
|
|
;
|
|
|
|
/* 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;
|
|
|
|
/* keep-alive connection */
|
|
openvpn_snprintf (buf, sizeof(buf), "Proxy-Connection: Keep-Alive");
|
|
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;
|
|
|
|
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;
|
|
|
|
/* 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;
|
|
#endif
|
|
}
|
|
#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);
|
|
|
|
if ( !realm || !nonce )
|
|
{
|
|
msg(D_LINK_ERRORS, "HTTP proxy: digest auth failed, malformed response from server: realm= or nonce= missing" );
|
|
goto error;
|
|
}
|
|
|
|
/* 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,
|
|
(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, */
|
|
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");
|
|
free(pa);
|
|
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);
|
|
}
|
|
|
|
/* check return code, success = 200 */
|
|
if (nparms < 1 || status != 200)
|
|
{
|
|
msg (D_LINK_ERRORS, "HTTP proxy returned bad status");
|
|
#if 0
|
|
/* DEBUGGING -- show a multi-line HTTP error response */
|
|
dump_residual(sd, p->options.timeout, signal_received);
|
|
#endif
|
|
goto error;
|
|
}
|
|
|
|
/* SUCCESS */
|
|
|
|
/* 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))
|
|
;
|
|
|
|
/* reset queried_creds so that we don't think that the next creds request is due to an auth error */
|
|
p->queried_creds = false;
|
|
|
|
#if 0
|
|
if (lookahead && BLEN (lookahead))
|
|
msg (M_INFO, "HTTP PROXY: lookahead: %s", format_hex (BPTR (lookahead), BLEN (lookahead), 0));
|
|
#endif
|
|
|
|
done:
|
|
gc_free (&gc);
|
|
return ret;
|
|
|
|
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);
|
|
return ret;
|
|
}
|
|
|
|
#else
|
|
static void dummy(void) {}
|
|
#endif /* ENABLE_HTTP_PROXY */
|
|
|
|
#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;
|
|
#else /* test --auto-proxy feature */
|
|
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 */
|