| /* |
| * 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-2018 OpenVPN 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; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #elif defined(_MSC_VER) |
| #include "config-msvc.h" |
| #endif |
| |
| #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" |
| #include "memdbg.h" |
| #include "forward.h" |
| |
| #define UP_TYPE_PROXY "HTTP Proxy" |
| |
| struct http_proxy_options * |
| init_http_proxy_options_once(struct http_proxy_options **hpo, |
| struct gc_arena *gc) |
| { |
| if (!*hpo) |
| { |
| ALLOC_OBJ_CLEAR_GC(*hpo, struct http_proxy_options, gc); |
| /* http proxy defaults */ |
| (*hpo)->http_version = "1.0"; |
| } |
| return *hpo; |
| } |
| |
| |
| /* 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); |
| 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; |
| } |
| |
| #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, "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, "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(openvpn_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 |
| clear_user_pass_http(void) |
| { |
| purge_user_pass(&static_proxy_user_pass, true); |
| } |
| |
| static void |
| get_user_pass_http(struct http_proxy_info *p, const bool force) |
| { |
| /* |
| * 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) |
| { |
| 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; |
| } |
| } |
| |
| #if 0 |
| /* function only used in #if 0 debug statement */ |
| 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); |
| } |
| } |
| #endif |
| |
| /* |
| * 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 NTLM: '%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 http_proxy_info *p; |
| |
| if (!o || !o->server) |
| { |
| msg(M_FATAL, "HTTP_PROXY: server not specified"); |
| } |
| |
| ASSERT( 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); |
| } |
| |
| static bool |
| 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 */ |
| ) |
| { |
| 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++) |
| { |
| 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; |
| } |
| } |
| |
| if (!host_header_sent) |
| { |
| 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; |
| } |
| } |
| |
| /* send User-Agent string if provided */ |
| if (p->options.user_agent) |
| { |
| 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; |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| 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 char *port, /* openvpn server port */ |
| struct event_timeout *server_poll_timeout, |
| 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 */ |
| 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:%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; |
| |
| #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), 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); |
| |
| } |
| |
| /* 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), get_server_poll_remaining_time(server_poll_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:%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; |
| } |
| |
| /* keep-alive connection */ |
| openvpn_snprintf(buf, sizeof(buf), "Proxy-Connection: Keep-Alive"); |
| if (!send_line_crlf(sd, buf)) |
| { |
| goto error; |
| } |
| |
| /* send HOST etc, */ |
| if (!add_proxy_headers(p, sd, host, port)) |
| { |
| 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), 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; |
| #endif /* if NTLM */ |
| } |
| #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:%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, |
| 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, get_server_poll_remaining_time(server_poll_timeout), signal_received); |
| #endif |
| goto error; |
| } |
| |
| /* SUCCESS */ |
| |
| /* 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; |
| } |
| |
| /* |
| * 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: |
| if (!*signal_received) |
| { |
| *signal_received = SIGUSR1; /* SOFT-SIGUSR1 -- HTTP proxy error */ |
| } |
| gc_free(&gc); |
| return ret; |
| } |