diff options
Diffstat (limited to 'src/openvpn/proxy.c')
-rw-r--r-- | src/openvpn/proxy.c | 907 |
1 files changed, 907 insertions, 0 deletions
diff --git a/src/openvpn/proxy.c b/src/openvpn/proxy.c new file mode 100644 index 0000000..363d8a7 --- /dev/null +++ b/src/openvpn/proxy.c @@ -0,0 +1,907 @@ +/* + * 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 + */ + +#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" + +#ifdef ENABLE_HTTP_PROXY + +#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)->timeout = 5; + (*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); + 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 +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)) + { + *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 http_proxy_info *p; + struct http_proxy_options 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[128]; + 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:%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[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 */ + 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); + + /* 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"); + 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 */ + |