summaryrefslogtreecommitdiffstats
path: root/ncat/ncat_proxy.c
diff options
context:
space:
mode:
Diffstat (limited to 'ncat/ncat_proxy.c')
-rw-r--r--ncat/ncat_proxy.c867
1 files changed, 867 insertions, 0 deletions
diff --git a/ncat/ncat_proxy.c b/ncat/ncat_proxy.c
new file mode 100644
index 0000000..e1e3f0e
--- /dev/null
+++ b/ncat/ncat_proxy.c
@@ -0,0 +1,867 @@
+/***************************************************************************
+ * ncat_proxy.c -- HTTP proxy server. *
+ ***********************IMPORTANT NMAP LICENSE TERMS************************
+ *
+ * The Nmap Security Scanner is (C) 1996-2023 Nmap Software LLC ("The Nmap
+ * Project"). Nmap is also a registered trademark of the Nmap Project.
+ *
+ * This program is distributed under the terms of the Nmap Public Source
+ * License (NPSL). The exact license text applying to a particular Nmap
+ * release or source code control revision is contained in the LICENSE
+ * file distributed with that version of Nmap or source code control
+ * revision. More Nmap copyright/legal information is available from
+ * https://nmap.org/book/man-legal.html, and further information on the
+ * NPSL license itself can be found at https://nmap.org/npsl/ . This
+ * header summarizes some key points from the Nmap license, but is no
+ * substitute for the actual license text.
+ *
+ * Nmap is generally free for end users to download and use themselves,
+ * including commercial use. It is available from https://nmap.org.
+ *
+ * The Nmap license generally prohibits companies from using and
+ * redistributing Nmap in commercial products, but we sell a special Nmap
+ * OEM Edition with a more permissive license and special features for
+ * this purpose. See https://nmap.org/oem/
+ *
+ * If you have received a written Nmap license agreement or contract
+ * stating terms other than these (such as an Nmap OEM license), you may
+ * choose to use and redistribute Nmap under those terms instead.
+ *
+ * The official Nmap Windows builds include the Npcap software
+ * (https://npcap.com) for packet capture and transmission. It is under
+ * separate license terms which forbid redistribution without special
+ * permission. So the official Nmap Windows builds may not be redistributed
+ * without special permission (such as an Nmap OEM license).
+ *
+ * Source is provided to this software because we believe users have a
+ * right to know exactly what a program is going to do before they run it.
+ * This also allows you to audit the software for security holes.
+ *
+ * Source code also allows you to port Nmap to new platforms, fix bugs, and add
+ * new features. You are highly encouraged to submit your changes as a Github PR
+ * or by email to the dev@nmap.org mailing list for possible incorporation into
+ * the main distribution. Unless you specify otherwise, it is understood that
+ * you are offering us very broad rights to use your submissions as described in
+ * the Nmap Public Source License Contributor Agreement. This is important
+ * because we fund the project by selling licenses with various terms, and also
+ * because the inability to relicense code has caused devastating problems for
+ * other Free Software projects (such as KDE and NASM).
+ *
+ * The free version of Nmap 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. Warranties,
+ * indemnification and commercial support are all available through the
+ * Npcap OEM program--see https://nmap.org/oem/
+ *
+ ***************************************************************************/
+
+/* $Id$ */
+
+#include "base64.h"
+#include "http.h"
+#include "nsock.h"
+#include "ncat.h"
+#include "sys_wrap.h"
+
+#ifndef WIN32
+#include <unistd.h>
+#endif
+
+#ifndef WIN32
+/* SIG_CHLD handler */
+static void proxyreaper(int signo)
+{
+ while (waitpid(-1, NULL, WNOHANG) > 0)
+ ;
+}
+#endif
+
+/* send a '\0'-terminated string. */
+static int send_string(struct fdinfo *fdn, const char *s)
+{
+ return fdinfo_send(fdn, s, strlen(s));
+}
+
+static void http_server_handler(int c);
+static int send_proxy_authenticate(struct fdinfo *fdn, int stale);
+static char *http_code2str(int code);
+
+static void fork_handler(int s, int c);
+
+static int handle_connect(struct socket_buffer *client_sock,
+ struct http_request *request);
+static int handle_method(struct socket_buffer *client_sock,
+ struct http_request *request);
+
+static int check_auth(const struct http_request *request,
+ const struct http_credentials *credentials, int *stale);
+
+/*
+ * Simple forking HTTP proxy. It is an HTTP/1.0 proxy with knowledge of
+ * HTTP/1.1. (The things lacking for HTTP/1.1 are the chunked transfer encoding
+ * and the expect mechanism.) The proxy supports the CONNECT, GET, HEAD, and
+ * POST methods. It supports Basic and Digest authentication of clients (use the
+ * --proxy-auth option).
+ *
+ * HTTP/1.1 is defined in RFC 2616. Many comments refer to that document.
+ * http://tools.ietf.org/html/rfc2616
+ *
+ * HTTP authentication is discussed in RFC 2617.
+ * http://tools.ietf.org/html/rfc2617
+ *
+ * The CONNECT method is documented in an Internet draft and is specified as the
+ * way to proxy HTTPS in RFC 2817, section 5.
+ * http://tools.ietf.org/html/draft-luotonen-web-proxy-tunneling-01
+ * http://tools.ietf.org/html/rfc2817#section-5
+ *
+ * The CONNECT method is not limited to HTTP, but is potentially capable of
+ * connecting to any TCP port on any host. The proxy connection is requested
+ * with an HTTP request, but after that, the proxy does no interpretation of the
+ * data passing through it. See section 6 of the above mentioned draft for the
+ * security implications.
+ */
+int ncat_http_server(void)
+{
+ int c, i, j;
+ int listen_socket[NUM_LISTEN_ADDRS];
+ socklen_t sslen;
+ union sockaddr_u conn;
+ unsigned int num_sockets;
+
+#ifndef WIN32
+ Signal(SIGCHLD, proxyreaper);
+#endif
+
+#if HAVE_HTTP_DIGEST
+ http_digest_init_secret();
+#endif
+
+#ifdef HAVE_OPENSSL
+ if (o.ssl)
+ setup_ssl_listen(SSLv23_server_method());
+#endif
+ /* Clear the socket list */
+ for (i = 0; i < NUM_LISTEN_ADDRS; i++)
+ listen_socket[i] = -1;
+
+ /* set for selecting listening sockets */
+ fd_set listen_fds;
+ fd_list_t listen_fdlist;
+ FD_ZERO(&listen_fds);
+ init_fdlist(&listen_fdlist, num_listenaddrs);
+
+ /* Listen on each address, set up lists for select */
+ num_sockets = 0;
+ for (i = 0; i < num_listenaddrs; i++) {
+ listen_socket[num_sockets] = do_listen(SOCK_STREAM, IPPROTO_TCP, &listenaddrs[i]);
+ if (listen_socket[num_sockets] == -1) {
+ if (o.debug > 0)
+ logdebug("do_listen(\"%s\"): %s\n", socktop(&listenaddrs[i], 0), socket_strerror(socket_errno()));
+ continue;
+ }
+
+ /* make us not block on accepts in weird cases. See ncat_listen.c:209 */
+ unblock_socket(listen_socket[num_sockets]);
+
+ /* setup select sets and max fd */
+ checked_fd_set(listen_socket[num_sockets], &listen_fds);
+ add_fd(&listen_fdlist, listen_socket[num_sockets]);
+
+ num_sockets++;
+ }
+ if (num_sockets == 0) {
+ if (num_listenaddrs == 1)
+ bye("Unable to open listening socket on %s: %s", socktop(&listenaddrs[0], 0), socket_strerror(socket_errno()));
+ else
+ bye("Unable to open any listening sockets.");
+ }
+
+ for (;;) {
+ fd_set read_fds;
+
+ sslen = sizeof(conn.storage);
+ /*
+ * We just select to get a list of sockets which we can talk to
+ */
+ if (o.debug > 1)
+ logdebug("selecting, fdmax %d\n", listen_fdlist.fdmax);
+ read_fds = listen_fds;
+
+ int fds_ready = fselect(listen_fdlist.fdmax + 1, &read_fds, NULL, NULL, NULL);
+
+ if (o.debug > 1)
+ logdebug("select returned %d fds ready\n", fds_ready);
+
+ if (fds_ready == 0)
+ bye("Idle timeout expired (%d ms).", o.idletimeout);
+
+ for (i = 0; i <= listen_fdlist.fdmax && fds_ready > 0; i++) {
+ /* Loop through descriptors until there is something ready */
+ if (!checked_fd_isset(i, &read_fds))
+ continue;
+
+ /* Check each listening socket */
+ for (j = 0; j < num_sockets; j++) {
+ if (i == listen_socket[j]) {
+ fds_ready--;
+ c = accept(i, &conn.sockaddr, &sslen);
+
+ if (c == -1) {
+ if (errno == EINTR)
+ continue;
+ die("accept");
+ }
+
+ if (!allow_access(&conn)) {
+ Close(c);
+ continue;
+ }
+ if (o.debug > 1)
+ logdebug("forking handler for %d\n", i);
+ fork_handler(i, c);
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+#ifdef WIN32
+/* On Windows we don't actually fork but rather start a thread. */
+
+static DWORD WINAPI handler_thread_func(void *data)
+{
+ http_server_handler(*((int *) data));
+ free(data);
+
+ return 0;
+}
+
+static void fork_handler(int s, int c)
+{
+ int *data;
+ HANDLE thread;
+
+ data = (int *) safe_malloc(sizeof(int));
+ *data = c;
+ thread = CreateThread(NULL, 0, handler_thread_func, data, 0, NULL);
+ if (thread == NULL) {
+ if (o.verbose)
+ logdebug("Error in CreateThread: %d\n", GetLastError());
+ free(data);
+ return;
+ }
+ CloseHandle(thread);
+}
+#else
+static void fork_handler(int s, int c)
+{
+ int rc;
+
+ rc = fork();
+ if (rc == -1) {
+ return;
+ } else if (rc == 0) {
+ Close(s);
+
+ if (!o.debug) {
+ Close(STDIN_FILENO);
+ Close(STDOUT_FILENO);
+ Close(STDERR_FILENO);
+ }
+
+ http_server_handler(c);
+ exit(0);
+ } else {
+ Close(c);
+ }
+}
+#endif
+
+/* Is this one of the methods we can handle? */
+static int method_is_known(const char *method)
+{
+ return strcmp(method, "CONNECT") == 0
+ || strcmp(method, "GET") == 0
+ || strcmp(method, "HEAD") == 0
+ || strcmp(method, "POST") == 0;
+}
+
+static void http_server_handler(int c)
+{
+ int code;
+ struct socket_buffer sock;
+ struct http_request request;
+ char *buf;
+
+ socket_buffer_init(&sock, c);
+#if HAVE_OPENSSL
+ if (o.ssl) {
+ sock.fdn.ssl = new_ssl(sock.fdn.fd);
+ if (SSL_accept(sock.fdn.ssl) != 1) {
+ loguser("Failed SSL connection: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ fdinfo_close(&sock.fdn);
+ return;
+ }
+ }
+#endif
+
+ code = http_read_request_line(&sock, &buf);
+ if (code != 0) {
+ if (o.verbose)
+ logdebug("Error reading Request-Line.\n");
+ send_string(&sock.fdn, http_code2str(code));
+ fdinfo_close(&sock.fdn);
+ return;
+ }
+ if (o.debug > 1)
+ logdebug("Request-Line: %s", buf);
+ code = http_parse_request_line(buf, &request);
+ free(buf);
+ if (code != 0) {
+ if (o.verbose)
+ logdebug("Error parsing Request-Line.\n");
+ send_string(&sock.fdn, http_code2str(code));
+ fdinfo_close(&sock.fdn);
+ return;
+ }
+
+ if (!method_is_known(request.method)) {
+ if (o.debug > 1)
+ logdebug("Bad method: %s.\n", request.method);
+ http_request_free(&request);
+ send_string(&sock.fdn, http_code2str(405));
+ fdinfo_close(&sock.fdn);
+ return;
+ }
+
+ code = http_read_header(&sock, &buf);
+ if (code != 0) {
+ if (o.verbose)
+ logdebug("Error reading header.\n");
+ http_request_free(&request);
+ send_string(&sock.fdn, http_code2str(code));
+ fdinfo_close(&sock.fdn);
+ return;
+ }
+ if (o.debug > 1)
+ logdebug("Header:\n%s", buf);
+ code = http_request_parse_header(&request, buf);
+ free(buf);
+ if (code != 0) {
+ if (o.verbose)
+ logdebug("Error parsing header.\n");
+ http_request_free(&request);
+ send_string(&sock.fdn, http_code2str(code));
+ fdinfo_close(&sock.fdn);
+ return;
+ }
+
+ /* Check authentication. */
+ if (o.proxy_auth) {
+ struct http_credentials credentials;
+ int ret, stale;
+
+ if (http_header_get_proxy_credentials(request.header, &credentials) == NULL) {
+ /* No credentials or a parsing error. */
+ send_proxy_authenticate(&sock.fdn, 0);
+ http_request_free(&request);
+ fdinfo_close(&sock.fdn);
+ return;
+ }
+
+ ret = check_auth(&request, &credentials, &stale);
+ http_credentials_free(&credentials);
+ if (!ret) {
+ /* Password doesn't match. */
+ /* RFC 2617, section 1.2: "If a proxy does not accept the
+ credentials sent with a request, it SHOULD return a 407 (Proxy
+ Authentication Required). */
+ send_proxy_authenticate(&sock.fdn, stale);
+ http_request_free(&request);
+ fdinfo_close(&sock.fdn);
+ return;
+ }
+ }
+
+ if (strcmp(request.method, "CONNECT") == 0) {
+ code = handle_connect(&sock, &request);
+ } else if (strcmp(request.method, "GET") == 0
+ || strcmp(request.method, "HEAD") == 0
+ || strcmp(request.method, "POST") == 0) {
+ code = handle_method(&sock, &request);
+ } else {
+ code = 500;
+ }
+ http_request_free(&request);
+
+ if (code != 0) {
+ send_string(&sock.fdn, http_code2str(code));
+ fdinfo_close(&sock.fdn);
+ return;
+ }
+
+ fdinfo_close(&sock.fdn);
+}
+
+static int handle_connect(struct socket_buffer *client_sock,
+ struct http_request *request)
+{
+ union sockaddr_u su;
+ size_t sslen = sizeof(su.storage);
+ int maxfd, s, rc;
+ char *line;
+ size_t len;
+ fd_set m, r;
+
+ if (request->uri.port == -1) {
+ if (o.verbose)
+ logdebug("No port number in CONNECT URI.\n");
+ return 400;
+ }
+ if (o.debug > 1)
+ logdebug("CONNECT to %s:%d.\n", request->uri.host, request->uri.port);
+
+ rc = resolve(request->uri.host, request->uri.port, &su.storage, &sslen, o.af);
+ if (rc != 0) {
+ if (o.debug) {
+ logdebug("Can't resolve name \"%s\": %s.\n",
+ request->uri.host, gai_strerror(rc));
+ }
+ return 504;
+ }
+
+ s = Socket(su.storage.ss_family, SOCK_STREAM, IPPROTO_TCP);
+
+ if (connect(s, &su.sockaddr, sslen) == -1) {
+ if (o.debug)
+ logdebug("Can't connect to %s: %s.\n", socktop(&su, sslen), socket_strerror(socket_errno()));
+ Close(s);
+ return 504;
+ }
+
+ send_string(&client_sock->fdn, http_code2str(200));
+
+ /* Clear out whatever is left in the socket buffer. The client may have
+ already sent the first part of its request to the origin server. */
+ line = socket_buffer_remainder(client_sock, &len);
+ if (send(s, line, len, 0) < 0) {
+ if (o.debug)
+ logdebug("Error sending %lu leftover bytes: %s.\n", (unsigned long) len, strerror(errno));
+ Close(s);
+ return 0;
+ }
+
+ maxfd = client_sock->fdn.fd < s ? s : client_sock->fdn.fd;
+ FD_ZERO(&m);
+ checked_fd_set(client_sock->fdn.fd, &m);
+ checked_fd_set(s, &m);
+
+ errno = 0;
+
+ while (!socket_errno() || socket_errno() == EINTR) {
+ char buf[DEFAULT_TCP_BUF_LEN];
+ int len, rc;
+
+ r = m;
+
+ fselect(maxfd + 1, &r, NULL, NULL, NULL);
+
+ zmem(buf, sizeof(buf));
+
+ if (checked_fd_isset(client_sock->fdn.fd, &r)) {
+ do {
+ do {
+ len = fdinfo_recv(&client_sock->fdn, buf, sizeof(buf));
+ } while (len == -1 && socket_errno() == EINTR);
+ if (len <= 0)
+ goto end;
+
+ do {
+ rc = send(s, buf, len, 0);
+ } while (rc == -1 && socket_errno() == EINTR);
+ if (rc == -1)
+ goto end;
+ } while (fdinfo_pending(&client_sock->fdn));
+ }
+
+ if (checked_fd_isset(s, &r)) {
+ do {
+ len = recv(s, buf, sizeof(buf), 0);
+ } while (len == -1 && socket_errno() == EINTR);
+ if (len <= 0)
+ goto end;
+
+ do {
+ rc = fdinfo_send(&client_sock->fdn, buf, len);
+ } while (rc == -1 && socket_errno() == EINTR);
+ if (rc == -1)
+ goto end;
+ }
+ }
+end:
+
+ close(s);
+
+ return 0;
+}
+
+static int do_transaction(struct http_request *request,
+ struct socket_buffer *client_sock, struct socket_buffer *server_sock);
+
+/* Generic handler for GET, HEAD, and POST methods. */
+static int handle_method(struct socket_buffer *client_sock,
+ struct http_request *request)
+{
+ struct socket_buffer server_sock;
+ union sockaddr_u su;
+ size_t sslen = sizeof(su.storage);
+ int code;
+ int s, rc;
+
+ if (strcmp(request->uri.scheme, "http") != 0) {
+ if (o.verbose)
+ logdebug("Unknown scheme in URI: %s.\n", request->uri.scheme);
+ return 400;
+ }
+ if (request->uri.port == -1) {
+ if (o.verbose)
+ logdebug("Unknown port in URI.\n");
+ return 400;
+ }
+
+ rc = resolve(request->uri.host, request->uri.port, &su.storage, &sslen, o.af);
+ if (rc != 0) {
+ if (o.debug) {
+ logdebug("Can't resolve name %s:%d: %s.\n",
+ request->uri.host, request->uri.port, gai_strerror(rc));
+ }
+ return 504;
+ }
+
+ /* RFC 2616, section 5.1.2: "In order to avoid request loops, a proxy MUST
+ be able to recognize all of its server names, including any aliases,
+ local variations, and the numeric IP address. */
+ if (request->uri.port == o.portno && addr_is_local(&su)) {
+ if (o.verbose)
+ logdebug("Proxy loop detected: %s:%d\n", request->uri.host, request->uri.port);
+ return 403;
+ }
+
+ s = Socket(su.storage.ss_family, SOCK_STREAM, IPPROTO_TCP);
+
+ if (connect(s, &su.sockaddr, sslen) == -1) {
+ if (o.debug)
+ logdebug("Can't connect to %s: %s.\n", socktop(&su, sslen), socket_strerror(socket_errno()));
+ Close(s);
+ return 504;
+ }
+
+ socket_buffer_init(&server_sock, s);
+
+ code = do_transaction(request, client_sock, &server_sock);
+
+ fdinfo_close(&server_sock.fdn);
+
+ if (code != 0)
+ return code;
+
+ return 0;
+}
+
+/* Do a GET, HEAD, or POST transaction. */
+static int do_transaction(struct http_request *request,
+ struct socket_buffer *client_sock, struct socket_buffer *server_sock)
+{
+ char buf[BUFSIZ];
+ struct http_response response;
+ char *line;
+ char *request_str, *response_str;
+ size_t len;
+ int code, n;
+
+ /* We don't handle the chunked transfer encoding, which in the absence of a
+ Content-Length is the only way we know the end of a request body. RFC
+ 2616, section 4.4 says, "If a request contains a message-body and a
+ Content-Length is not given, the server SHOULD respond with 400 (bad
+ request) if it cannot determine the length of the message, or with 411
+ (length required) if it wishes to insist on receiving a valid
+ Content-Length." */
+ if (strcmp(request->method, "POST") == 0 && !request->content_length_set) {
+ if (o.debug)
+ logdebug("POST request with no Content-Length.\n");
+ return 400;
+ }
+
+ /* The version we use to talk to the server. */
+ request->version = HTTP_10;
+
+ /* Remove headers that only apply to our connection with the client. */
+ code = http_header_remove_hop_by_hop(&request->header);
+ if (code != 0) {
+ if (o.verbose)
+ logdebug("Error removing hop-by-hop headers.\n");
+ return code;
+ }
+
+ /* Build the Host header. */
+ if (request->uri.port == -1 || request->uri.port == 80)
+ n = Snprintf(buf, sizeof(buf), "%s", request->uri.host);
+ else
+ n = Snprintf(buf, sizeof(buf), "%s:%d", request->uri.host, request->uri.port);
+ if (n < 0 || n >= sizeof(buf)) {
+ /* Request Entity Too Large. */
+ return 501;
+ }
+ request->header = http_header_set(request->header, "Host", buf);
+
+ request->header = http_header_set(request->header, "Connection", "close");
+
+ /* Send the request to the server. */
+ request_str = http_request_to_string(request, &len);
+ n = send(server_sock->fdn.fd, request_str, len, 0);
+ free(request_str);
+ if (n < 0)
+ return 504;
+ /* Send the request body, if any. Count up to Content-Length. */
+ while (request->bytes_transferred < request->content_length) {
+ n = socket_buffer_read(client_sock, buf, MIN(sizeof(buf), request->content_length - request->bytes_transferred));
+ if (n < 0)
+ return 504;
+ if (n == 0)
+ break;
+ request->bytes_transferred += n;
+ n = send(server_sock->fdn.fd, buf, n, 0);
+ if (n < 0)
+ return 504;
+ }
+ if (o.debug && request->bytes_transferred < request->content_length)
+ logdebug("Received only %lu request body bytes (Content-Length was %lu).\n", request->bytes_transferred, request->content_length);
+
+
+ /* Read the response. */
+ code = http_read_status_line(server_sock, &line);
+ if (o.debug > 1)
+ logdebug("Status-Line: %s", line);
+ if (code != 0) {
+ if (o.verbose)
+ logdebug("Error reading Status-Line.\n");
+ return 0;
+ }
+ code = http_parse_status_line(line, &response);
+ free(line);
+ if (code != 0) {
+ if (o.verbose)
+ logdebug("Error parsing Status-Line.\n");
+ return 0;
+ }
+
+ code = http_read_header(server_sock, &line);
+ if (code != 0) {
+ if (o.verbose)
+ logdebug("Error reading header.\n");
+ return 0;
+ }
+ if (o.debug > 1)
+ logdebug("Response header:\n%s", line);
+
+ code = http_response_parse_header(&response, line);
+ free(line);
+ if (code != 0) {
+ if (o.verbose)
+ logdebug("Error parsing response header.\n");
+ return 0;
+ }
+
+
+ /* The version we use to talk to the client. */
+ response.version = HTTP_10;
+
+ /* Remove headers that only apply to our connection with the server. */
+ code = http_header_remove_hop_by_hop(&response.header);
+ if (code != 0) {
+ if (o.verbose)
+ logdebug("Error removing hop-by-hop headers.\n");
+ return code;
+ }
+
+ response.header = http_header_set(response.header, "Connection", "close");
+
+ /* Send the response to the client. */
+ response_str = http_response_to_string(&response, &len);
+ n = fdinfo_send(&client_sock->fdn, response_str, len);
+ free(response_str);
+ if (n < 0) {
+ http_response_free(&response);
+ return 504;
+ }
+ /* If the Content-Length is 0, read until the connection is closed.
+ Otherwise read until the Content-Length. At this point it's too late to
+ return our own error code so return 0 in case of any error. */
+ while (!response.content_length_set
+ || response.bytes_transferred < response.content_length) {
+ size_t count;
+
+ count = sizeof(buf);
+ if (response.content_length_set) {
+ size_t remaining = response.content_length - response.bytes_transferred;
+ if (remaining < count)
+ count = remaining;
+ }
+ n = socket_buffer_read(server_sock, buf, count);
+ if (n <= 0)
+ break;
+ response.bytes_transferred += n;
+ n = fdinfo_send(&client_sock->fdn, buf, n);
+ if (n < 0)
+ break;
+ }
+
+ http_response_free(&response);
+
+ return 0;
+}
+
+/* Send a 407 Proxy Authenticate Required response. */
+static int send_proxy_authenticate(struct fdinfo *fdn, int stale)
+{
+ char *buf = NULL;
+ size_t size = 0, offset = 0;
+ int n;
+
+ strbuf_append_str(&buf, &size, &offset, "HTTP/1.0 407 Proxy Authentication Required\r\n");
+ strbuf_append_str(&buf, &size, &offset, "Proxy-Authenticate: Basic realm=\"Ncat\"\r\n");
+#if HAVE_HTTP_DIGEST
+ {
+ char *hdr;
+
+ hdr = http_digest_proxy_authenticate("Ncat", stale);
+ strbuf_sprintf(&buf, &size, &offset, "Proxy-Authenticate: %s\r\n", hdr);
+ free(hdr);
+ }
+#endif
+ strbuf_append_str(&buf, &size, &offset, "\r\n");
+
+ if (o.debug > 1)
+ logdebug("RESPONSE:\n%s", buf);
+
+ n = send_string(fdn, buf);
+ free(buf);
+
+ return n;
+}
+
+static char *http_code2str(int code)
+{
+ /* See RFC 2616, section 6.1.1 for status codes. */
+ switch (code) {
+ case 200:
+ return "HTTP/1.0 200 OK\r\n\r\n";
+ case 400:
+ return "HTTP/1.0 400 Bad Request\r\n\r\n";
+ case 403:
+ return "HTTP/1.0 403 Forbidden\r\n\r\n";
+ case 405:
+ /* RFC 2616, section 14.7 for Allow. */
+ return "\
+HTTP/1.0 405 Method Not Allowed\r\n\
+Allow: CONNECT, GET, HEAD, POST\r\n\
+\r\n";
+ case 413:
+ return "HTTP/1.0 413 Request Entity Too Large\r\n\r\n";
+ case 501:
+ return "HTTP/1.0 501 Not Implemented\r\n\r\n";
+ case 504:
+ return "HTTP/1.0 504 Gateway Timeout\r\n\r\n";
+ default:
+ return "HTTP/1.0 500 Internal Server Error\r\n\r\n";
+ }
+
+ return NULL;
+}
+
+/* userpass is a user:pass string (the argument to --proxy-auth). value is the
+ value of the Proxy-Authorization header field. Returns 0 on authentication
+ failure and nonzero on success. *stale is set to 1 if HTTP Digest credentials
+ are valid but out of date. */
+static int check_auth(const struct http_request *request,
+ const struct http_credentials *credentials, int *stale)
+{
+ if (o.proxy_auth == NULL)
+ return 1;
+
+ *stale = 0;
+
+ if (credentials->scheme == AUTH_BASIC) {
+ char *expected;
+ int cmp;
+
+ if (credentials->u.basic == NULL)
+ return 0;
+
+ /* We don't decode the received password, we encode the expected
+ password and compare the encoded strings. */
+ expected = b64enc((unsigned char *) o.proxy_auth, strlen(o.proxy_auth));
+ cmp = strcmp(expected, credentials->u.basic);
+ free(expected);
+
+ return cmp == 0;
+ }
+#if HAVE_HTTP_DIGEST
+ else if (credentials->scheme == AUTH_DIGEST) {
+ char *username, *password;
+ char *proxy_auth;
+ struct timeval nonce_tv, now;
+ int nonce_age;
+ int ret;
+
+ /* Split up the proxy auth argument. */
+ proxy_auth = Strdup(o.proxy_auth);
+ username = proxy_auth;
+ password = strchr(proxy_auth, ':');
+ if (password == NULL) {
+ free(proxy_auth);
+ return 0;
+ }
+ *password++ = '\0';
+ ret = http_digest_check_credentials(username, "Ncat", password,
+ request->method, credentials);
+ free(proxy_auth);
+
+ if (!ret)
+ return 0;
+
+ /* The nonce checks out as one we issued and it matches what we expect
+ given the credentials. Now check if it's too old. */
+ if (credentials->u.digest.nonce == NULL
+ || http_digest_nonce_time(credentials->u.digest.nonce, &nonce_tv) == -1)
+ return 0;
+ gettimeofday(&now, NULL);
+ if (TIMEVAL_AFTER(nonce_tv, now))
+ return 0;
+ nonce_age = TIMEVAL_SEC_SUBTRACT(now, nonce_tv);
+
+ if (nonce_age > HTTP_DIGEST_NONCE_EXPIRY) {
+ if (o.verbose)
+ loguser("Nonce is %d seconds old; rejecting.\n", nonce_age);
+ *stale = 1;
+ return 0;
+ }
+
+ /* To prevent replays, here we should additionally check against a list
+ of recently used nonces, where "recently used nonce" is one that has
+ been used to successfully authenticate within the last
+ HTTP_DIGEST_NONCE_EXPIRY seconds. (Older than that and we don't need
+ to keep it in the list, because the expiry test above will catch it.
+ This isn't supported because the fork-and-process architecture of the
+ proxy server makes it hard for us to change state in the parent
+ process from here in the child. */
+
+ return 1;
+ }
+#endif
+ else {
+ return 0;
+ }
+}