summaryrefslogtreecommitdiffstats
path: root/dirmngr/http.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dirmngr/http.c1671
1 files changed, 1221 insertions, 450 deletions
diff --git a/dirmngr/http.c b/dirmngr/http.c
index 946234e..7fe3b50 100644
--- a/dirmngr/http.c
+++ b/dirmngr/http.c
@@ -2,7 +2,7 @@
* Copyright (C) 1999, 2001-2004, 2006, 2009, 2010,
* 2011 Free Software Foundation, Inc.
* Copyright (C) 1999, 2001-2004, 2006, 2009, 2010, 2011, 2014 Werner Koch
- * Copyright (C) 2015-2017, 2021 g10 Code GmbH
+ * Copyright (C) 2015-2017, 2021, 2023 g10 Code GmbH
*
* This file is part of GnuPG.
*
@@ -65,6 +65,9 @@
# include <winsock2.h>
# endif
# include <windows.h>
+# include <winhttp.h>
+# define SECURITY_WIN32 1
+# include <security.h>
#else /*!HAVE_W32_SYSTEM*/
# include <sys/types.h>
# include <sys/socket.h>
@@ -207,10 +210,29 @@ struct cookie_s
/* True if TLS is to be used. */
int use_tls;
+ /* Optional malloced buffer holding pending bytes for the read
+ * function. LEN gives the used length, SIZE the allocated length.
+ * Used by the up_to_empty_line machinery. */
+ struct {
+ size_t size;
+ size_t len;
+ char *data;
+ } pending;
+
/* The remaining content length and a flag telling whether to use
the content length. */
uint64_t content_length;
unsigned int content_length_valid:1;
+
+ /* If the next flag is set the read function will limit the returned
+ * buffer to an empty line. That is the the pattern "\n\r\n" is
+ * detected and any further bytes are not returned to the caller.
+ * The flag is then reset. For technical reason we might have
+ * already read more which will be then saved for the next call in
+ * the PENDING buffer. */
+ unsigned int up_to_empty_line:1;
+ unsigned int last_was_lf:1; /* Helper to detect empty line. */
+ unsigned int last_was_lfcr:1; /* Helper to detect empty line. */
};
typedef struct cookie_s *cookie_t;
@@ -227,6 +249,33 @@ static es_cookie_io_functions_t simple_cookie_functions =
};
#endif
+enum auth_negotiate_states
+ {
+ AUTH_NGT_NONE = 0,
+ AUTH_NGT_RCVD = 1,
+ AUTH_NGT_SENT = 2
+ };
+
+/* An object to store information about a proxy. */
+struct proxy_info_s
+{
+ parsed_uri_t uri; /* The parsed proxy URL. */
+ int is_http_proxy; /* This is an http proxy. */
+
+#ifdef HAVE_W32_SYSTEM
+ CredHandle cred_handle; /* Credential handle. */
+ wchar_t *spn; /* Service principal name. */
+ CtxtHandle ctxt_handle; /* Security context. */
+ unsigned long token_size; /* Max. length of a token. */
+ unsigned int cred_handle_valid:1;
+ unsigned int ctxt_handle_valid:1;
+#endif /*HAVE_W32_SYSTEM*/
+
+ unsigned char *outtoken; /* The output token allocated with token_size. */
+ unsigned long outtoklen; /* The current length of the token. */
+};
+typedef struct proxy_info_s *proxy_info_t;
+
#if SIZEOF_UNSIGNED_LONG == 8
# define HTTP_SESSION_MAGIC 0x0068545470534553 /* "hTTpSES" */
@@ -293,6 +342,7 @@ struct http_context_s
my_socket_t sock;
unsigned int in_data:1;
unsigned int is_http_0_9:1;
+ unsigned int keep_alive:1; /* Keep the connection alive. */
estream_t fp_read;
estream_t fp_write;
void *write_cookie;
@@ -313,13 +363,13 @@ struct http_context_s
static int opt_verbose;
static int opt_debug;
-/* The global callback for the verification function. */
+/* The global callback for the verification function for GNUTLS. */
static gpg_error_t (*tls_callback) (http_t, http_session_t, int);
-/* The list of files with trusted CA certificates. */
+/* The list of files with trusted CA certificates for GNUTLS. */
static strlist_t tls_ca_certlist;
-/* The list of files with extra trusted CA certificates. */
+/* The list of files with extra trusted CA certificates for GNUTLS. */
static strlist_t cfg_ca_certlist;
/* The global callback for net activity. */
@@ -564,7 +614,7 @@ http_set_verbose (int verbose, int debug)
/* Register a non-standard global TLS callback function. If no
verification is desired a callback needs to be registered which
- always returns NULL. */
+ always returns NULL. Only used for GNUTLS. */
void
http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int))
{
@@ -575,7 +625,7 @@ http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int))
/* Register a CA certificate for future use. The certificate is
expected to be in FNAME. PEM format is assume if FNAME has a
suffix of ".pem". If FNAME is NULL the list of CA files is
- removed. */
+ removed. Only used for GNUTLS. */
void
http_register_tls_ca (const char *fname)
{
@@ -604,7 +654,8 @@ http_register_tls_ca (const char *fname)
* expected to be in FNAME. PEM format is assume if FNAME has a
* suffix of ".pem". If FNAME is NULL the list of CA files is
* removed. This is a variant of http_register_tls_ca which puts the
- * certificate into a separate list enabled using HTTP_FLAG_TRUST_CFG. */
+ * certificate into a separate list enabled using HTTP_FLAG_TRUST_CFG.
+ * Only used for GNUTLS. */
void
http_register_cfg_ca (const char *fname)
{
@@ -708,6 +759,64 @@ http_session_release (http_session_t sess)
}
+/* Create a write stream and store it in the fp_write member. Also
+ * store the tls flag and the session. */
+static gpg_error_t
+make_fp_write (http_t hd, int use_tls, http_session_t session)
+{
+ cookie_t cookie;
+
+ cookie = xtrycalloc (1, sizeof *cookie);
+ if (!cookie)
+ return gpg_error_from_syserror ();
+ cookie->sock = my_socket_ref (hd->sock);
+ cookie->use_tls = use_tls;
+ if (session)
+ cookie->session = http_session_ref (session);
+ hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
+ if (!hd->fp_write)
+ {
+ gpg_error_t err = gpg_error_from_syserror ();
+ my_socket_unref (cookie->sock, NULL, NULL);
+ if (session)
+ http_session_unref (cookie->session);
+ xfree (cookie);
+ return err;
+ }
+ hd->write_cookie = cookie; /* Cookie now owned by FP_WRITE. */
+ return 0;
+}
+
+
+/* Create a read stream and store it in the fp_read member. Also
+ * store the tls flag and the session. */
+static gpg_error_t
+make_fp_read (http_t hd, int use_tls, http_session_t session)
+{
+ cookie_t cookie;
+
+ cookie = xtrycalloc (1, sizeof *cookie);
+ if (!cookie)
+ return gpg_error_from_syserror ();
+ cookie->sock = my_socket_ref (hd->sock);
+ cookie->use_tls = use_tls;
+ if (session)
+ cookie->session = http_session_ref (session);
+ hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
+ if (!hd->fp_read)
+ {
+ gpg_error_t err = gpg_error_from_syserror ();
+ my_socket_unref (cookie->sock, NULL, NULL);
+ if (session)
+ http_session_unref (cookie->session);
+ xfree (cookie);
+ return err;
+ }
+ hd->read_cookie = cookie; /* Cookie now owned by FP_READ. */
+ return 0;
+}
+
+
/* Create a new session object which is currently used to enable TLS
* support. It may eventually allow reusing existing connections.
* Valid values for FLAGS are:
@@ -757,6 +866,8 @@ http_session_new (http_session_t *r_session,
int add_system_cas = !!(flags & HTTP_FLAG_TRUST_SYS);
int is_hkps_pool;
+ (void)intended_hostname;
+
rc = gnutls_certificate_allocate_credentials (&sess->certcred);
if (rc < 0)
{
@@ -821,7 +932,6 @@ http_session_new (http_session_t *r_session,
/* Add system certificates to the session. */
if (add_system_cas)
{
-#if GNUTLS_VERSION_NUMBER >= 0x030014
static int shown;
rc = gnutls_certificate_set_x509_system_trust (sess->certcred);
@@ -832,7 +942,6 @@ http_session_new (http_session_t *r_session,
shown = 1;
log_info ("number of system provided CAs: %d\n", rc);
}
-#endif /* gnutls >= 3.0.20 */
}
/* Add other configured certificates to the session. */
@@ -999,7 +1108,6 @@ http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
{
gpg_error_t err = 0;
http_t hd;
- cookie_t cookie;
*r_hd = NULL;
@@ -1046,39 +1154,13 @@ http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
}
/* Setup estreams for reading and writing. */
- cookie = xtrycalloc (1, sizeof *cookie);
- if (!cookie)
- {
- err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
- goto leave;
- }
- cookie->sock = my_socket_ref (hd->sock);
- hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
- if (!hd->fp_write)
- {
- err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
- my_socket_unref (cookie->sock, NULL, NULL);
- xfree (cookie);
- goto leave;
- }
- hd->write_cookie = cookie; /* Cookie now owned by FP_WRITE. */
+ err = make_fp_write (hd, 0, NULL);
+ if (err)
+ goto leave;
- cookie = xtrycalloc (1, sizeof *cookie);
- if (!cookie)
- {
- err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
- goto leave;
- }
- cookie->sock = my_socket_ref (hd->sock);
- hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
- if (!hd->fp_read)
- {
- err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
- my_socket_unref (cookie->sock, NULL, NULL);
- xfree (cookie);
- goto leave;
- }
- hd->read_cookie = cookie; /* Cookie now owned by FP_READ. */
+ err = make_fp_read (hd, 0, NULL);
+ if (err)
+ goto leave;
/* Register close notification to interlock the use of es_fclose in
http_close and in user code. */
@@ -1110,7 +1192,7 @@ http_start_data (http_t hd)
if (!hd->in_data)
{
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
- log_debug_with_string ("\r\n", "http.c:request-header:");
+ log_debug ("http.c:request-header:start_data:\n");
es_fputs ("\r\n", hd->fp_write);
es_fflush (hd->fp_write);
hd->in_data = 1;
@@ -1126,6 +1208,7 @@ http_wait_response (http_t hd)
gpg_error_t err;
cookie_t cookie;
int use_tls;
+ int newfpread;
/* Make sure that we are in the data. */
http_start_data (hd);
@@ -1137,41 +1220,36 @@ http_wait_response (http_t hd)
return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
use_tls = cookie->use_tls;
- es_fclose (hd->fp_write);
- hd->fp_write = NULL;
- /* The close has released the cookie and thus we better set it to NULL. */
- hd->write_cookie = NULL;
+ if (!hd->keep_alive)
+ {
+ es_fclose (hd->fp_write);
+ hd->fp_write = NULL;
+ /* The close has released the cookie and thus we better set it
+ * to NULL. */
+ hd->write_cookie = NULL;
+ }
/* Shutdown one end of the socket is desired. As per HTTP/1.0 this
is not required but some very old servers (e.g. the original pksd
keyserver didn't worked without it. */
- if ((hd->flags & HTTP_FLAG_SHUTDOWN))
+ if (!hd->keep_alive && (hd->flags & HTTP_FLAG_SHUTDOWN))
shutdown (FD2INT (hd->sock->fd), 1);
hd->in_data = 0;
/* Create a new cookie and a stream for reading. */
- cookie = xtrycalloc (1, sizeof *cookie);
- if (!cookie)
- return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
- cookie->sock = my_socket_ref (hd->sock);
- cookie->session = http_session_ref (hd->session);
- cookie->use_tls = use_tls;
-
- hd->read_cookie = cookie;
- hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
- if (!hd->fp_read)
+ newfpread = 0;
+ if (!hd->keep_alive || !hd->fp_read)
{
- err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
- my_socket_unref (cookie->sock, NULL, NULL);
- http_session_unref (cookie->session);
- xfree (cookie);
- hd->read_cookie = NULL;
- return err;
+ err = make_fp_read (hd, use_tls, hd->session);
+ if (err)
+ return err;
+ newfpread = 1;
+ ((cookie_t)(hd->read_cookie))->up_to_empty_line = 1;
}
err = parse_response (hd);
- if (!err)
+ if (!err && newfpread)
err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
return err;
@@ -1744,37 +1822,267 @@ is_hostname_port (const char *string)
}
-/*
- * Send a HTTP request to the server
- * Returns 0 if the request was successful
- */
+/* Free the PROXY object. */
+static void
+release_proxy_info (proxy_info_t proxy)
+{
+ if (!proxy)
+ return;
+ http_release_parsed_uri (proxy->uri);
+ xfree (proxy->outtoken);
+#ifdef HAVE_W32_SYSTEM
+ if (proxy->ctxt_handle_valid)
+ DeleteSecurityContext (&proxy->ctxt_handle);
+ if (proxy->cred_handle_valid)
+ FreeCredentialsHandle (&proxy->cred_handle);
+#endif
+ xfree (proxy);
+}
+
+
+/* Return an http session object. If clear is set, the object is
+ * destroyed. On error nULL is returned. */
+#ifdef HAVE_W32_SYSTEM
+static HINTERNET
+w32_get_internet_session (int clear)
+{
+ static HINTERNET session;
+
+ if (clear)
+ {
+ if (session)
+ {
+ WinHttpCloseHandle (session);
+ session = NULL;
+ }
+ return NULL;
+ }
+
+ if (!session)
+ {
+ session = WinHttpOpen (L"GnuPG dirmngr",
+ WINHTTP_ACCESS_TYPE_NO_PROXY,
+ WINHTTP_NO_PROXY_NAME,
+ WINHTTP_NO_PROXY_BYPASS,
+ 0);
+ if (!session)
+ {
+ log_error ("WinHttpOpen failed: %s\n", w32_strerror (-1));
+ return NULL;
+ }
+ }
+
+ return session;
+}
+#endif /*HAVE_W32_SYSTEM*/
+
+
+/* Return a proxy using a Windows API. */
+#ifdef HAVE_W32_SYSTEM
+static char *
+w32_get_proxy (const char *url)
+{
+ WINHTTP_AUTOPROXY_OPTIONS options = {0};
+ WINHTTP_PROXY_INFO info;
+ char *result = NULL;
+ char *p;
+ wchar_t *wurl;
+ int defaultcfg = 0;
+
+ wurl = utf8_to_wchar (url);
+ if (!wurl)
+ {
+ log_error ("utf8_to_wchar failed: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ return NULL;
+ }
+
+ options.dwFlags = (WINHTTP_AUTOPROXY_ALLOW_AUTOCONFIG
+ | WINHTTP_AUTOPROXY_ALLOW_CM
+ | WINHTTP_AUTOPROXY_ALLOW_STATIC
+ | WINHTTP_AUTOPROXY_AUTO_DETECT
+ | WINHTTP_AUTOPROXY_SORT_RESULTS);
+ options.dwAutoDetectFlags = (WINHTTP_AUTO_DETECT_TYPE_DHCP
+ | WINHTTP_AUTO_DETECT_TYPE_DNS_A);
+ options.fAutoLogonIfChallenged = TRUE;
+
+ if (opt_debug)
+ log_debug ("calling WinHttpGetProxyForUrl (%s)\n", url);
+ if (!WinHttpGetProxyForUrl (w32_get_internet_session (0),
+ wurl, &options, &info))
+ {
+ int ec = (int)GetLastError ();
+ if (ec == ERROR_WINHTTP_AUTODETECTION_FAILED)
+ {
+ if (opt_debug)
+ log_debug ("calling WinHttpGetDefaultProxyConfiguration\n");
+ if (!WinHttpGetDefaultProxyConfiguration (&info))
+ {
+ if (opt_verbose)
+ log_info ("WinHttpGetDefaultProxyConfiguration failed: "
+ "%s (%d)\n", w32_strerror (ec), ec);
+ xfree (wurl);
+ return NULL;
+ }
+ defaultcfg = 1;
+ }
+ else
+ {
+ if (opt_verbose)
+ log_info ("WinHttpGetProxyForUrl failed: %s (%d)\n",
+ w32_strerror (ec), ec);
+ xfree (wurl);
+ return NULL;
+ }
+ }
+ xfree (wurl);
+
+ if (info.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY)
+ {
+ result = wchar_to_utf8 (info.lpszProxy);
+ if (!result)
+ log_error ("wchar_to_utf8 failed: %s\n",
+ gpg_strerror (gpg_error_from_syserror ()));
+ else
+ {
+ if (opt_debug)
+ log_debug ("proxies to use: '%s'\n", result);
+ /* The returned proxies are delimited by whitespace or
+ * semicolons. We return only the first proxy. */
+ for (p=result; *p; p++)
+ if (spacep (p) || *p == ';')
+ {
+ *p = 0;
+ break;
+ }
+ }
+ }
+ else if (info.dwAccessType == WINHTTP_ACCESS_TYPE_NO_PROXY)
+ {
+ /* No proxy shall be used. */
+ }
+ else
+ log_error ("%s returned unexpected code %lu\n",
+ defaultcfg? "WinHttpGetDefaultProxyConfiguration"
+ :"WinHttpGetProxyForUrl", info.dwAccessType);
+
+ if (info.lpszProxy)
+ GlobalFree (info.lpszProxy);
+ if (info.lpszProxyBypass)
+ GlobalFree (info.lpszProxyBypass);
+ return result;
+}
+#endif /*HAVE_W32_SYSTEM*/
+
+
+/* Return the proxy to be used for the URL or host specified in HD.
+ * If OVERRIDE_PROXY is not NULL and not empty, this proxy will be
+ * used instead of any configured or dynamically determined proxy. If
+ * the function runs into an error an error code is returned and NULL
+ * is stored at R_PROXY. If the fucntion was successful and a proxy
+ * is to be used, information on the procy is stored at R_PROXY; if no
+ * proxy shall be used R_PROXY is set to NULL. Caller should always
+ * use release_proxy_info on the value stored at R_PROXY. */
static gpg_error_t
-send_request (http_t hd, const char *httphost, const char *auth,
- const char *proxy, const char *srvtag, unsigned int timeout,
- strlist_t headers)
+get_proxy_for_url (http_t hd, const char *override_proxy, proxy_info_t *r_proxy)
{
- gpg_error_t err;
- const char *server;
- char *request, *p;
- unsigned short port;
- const char *http_proxy = NULL;
- char *proxy_authstr = NULL;
- char *authstr = NULL;
- assuan_fd_t sock;
-#ifdef USE_TLS
- int have_http_proxy = 0;
+ gpg_error_t err = 0;
+ const char *proxystr, *s;
+ proxy_info_t proxy;
+#ifdef HAVE_W32_SYSTEM
+ char *proxystrbuf = NULL;
+#endif
+
+ *r_proxy = NULL;
+
+ if (override_proxy && *override_proxy)
+ proxystr = override_proxy;
+ else if (!(hd->flags & HTTP_FLAG_TRY_PROXY))
+ return 0; /* --honor-http-proxy not active */
+ else if ((s = getenv (HTTP_PROXY_ENV)) && *s)
+ proxystr = s;
+#ifdef HAVE_W32_SYSTEM
+ else if (hd->uri && hd->uri->original
+ && (proxystrbuf = w32_get_proxy (hd->uri->original)))
+ proxystr = proxystrbuf;
+#endif
+ else
+ return 0; /* No proxy known. */
+
+ proxy = xtrycalloc (1, sizeof *proxy);
+ if (!proxy)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error allocating memory for proxy\n");
+ goto leave;
+ }
+
+ err = parse_uri (&proxy->uri, proxystr, 0, 0);
+ if (gpg_err_code (err) == GPG_ERR_INV_URI
+ && is_hostname_port (proxystr))
+ {
+ /* Retry assuming a "hostname:port" string. */
+ char *tmpname = strconcat ("http://", proxystr, NULL);
+ if (!tmpname)
+ err = gpg_error_from_syserror ();
+ else if (!parse_uri (&proxy->uri, tmpname, 0, 0))
+ err = 0;
+ xfree (tmpname);
+ }
+
+ if (!err)
+ {
+ /* Get rid of the escapes in the authstring. */
+ if (proxy->uri->auth)
+ remove_escapes (proxy->uri->auth);
+
+ if (!strcmp (proxy->uri->scheme, "http"))
+ proxy->is_http_proxy = 1;
+ else if (!strcmp (proxy->uri->scheme, "socks4")
+ || !strcmp (proxy->uri->scheme, "socks5h"))
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+ else
+ err = gpg_error (GPG_ERR_INV_URI);
+
+ if (err)
+ {
+ log_error ("invalid HTTP proxy (%s): %s\n",
+ proxystr, gpg_strerror (err));
+ err = gpg_err_make (default_errsource, GPG_ERR_CONFIGURATION);
+ }
+ else if (opt_verbose)
+ log_info ("using '%s' to proxy '%s'\n",
+ proxystr, hd->uri? hd->uri->original : NULL);
+ }
+
+ leave:
+#ifdef HAVE_W32_SYSTEM
+ xfree (proxystrbuf);
#endif
+ if (err)
+ xfree (proxy);
+ else
+ *r_proxy = proxy;
+ return err;
+}
+
+
+/* Some checks done by send_request. */
+static gpg_error_t
+send_request_basic_checks (http_t hd)
+{
+ int mode;
if (hd->uri->use_tls && !hd->session)
{
log_error ("TLS requested but no session object provided\n");
- return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
+ return gpg_error (GPG_ERR_INTERNAL);
}
#ifdef USE_TLS
if (hd->uri->use_tls && !hd->session->tls_session)
{
log_error ("TLS requested but no TLS context available\n");
- return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
+ return gpg_error (GPG_ERR_INTERNAL);
}
if (opt_debug)
log_debug ("Using TLS library: %s %s\n",
@@ -1788,38 +2096,36 @@ send_request (http_t hd, const char *httphost, const char *auth,
);
#endif /*USE_TLS*/
- if ((hd->flags & HTTP_FLAG_FORCE_TOR))
+ if ((hd->flags & HTTP_FLAG_FORCE_TOR)
+ && (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode))
{
- int mode;
-
- if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
- {
- log_error ("Tor support is not available\n");
- return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
- }
- /* Non-blocking connects do not work with our Tor proxy because
- * we can't continue the Socks protocol after the EINPROGRESS.
- * Disable the timeout to use a blocking connect. */
- timeout = 0;
+ log_error ("Tor support is not available\n");
+ return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
- server = *hd->uri->host ? hd->uri->host : "localhost";
- port = hd->uri->port ? hd->uri->port : 80;
+ return 0;
+}
+
+
+/* Helper for send_request to set the servername. */
+static gpg_error_t
+send_request_set_sni (http_t hd, const char *name)
+{
+ gpg_error_t err = 0;
+# if HTTP_USE_GNUTLS
+ int rc;
+# endif
/* Try to use SNI. */
#ifdef USE_TLS
if (hd->uri->use_tls)
{
-# if HTTP_USE_GNUTLS
- int rc;
-# endif
-
xfree (hd->session->servername);
- hd->session->servername = xtrystrdup (httphost? httphost : server);
+ hd->session->servername = xtrystrdup (name);
if (!hd->session->servername)
{
- err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
- return err;
+ err = gpg_error_from_syserror ();
+ goto leave;
}
# if HTTP_USE_NTBTLS
@@ -1828,7 +2134,7 @@ send_request (http_t hd, const char *httphost, const char *auth,
if (err)
{
log_info ("ntbtls_set_hostname failed: %s\n", gpg_strerror (err));
- return err;
+ goto leave;
}
# elif HTTP_USE_GNUTLS
rc = gnutls_server_name_set (hd->session->tls_session,
@@ -1841,177 +2147,21 @@ send_request (http_t hd, const char *httphost, const char *auth,
}
#endif /*USE_TLS*/
- if ( (proxy && *proxy)
- || ( (hd->flags & HTTP_FLAG_TRY_PROXY)
- && (http_proxy = getenv (HTTP_PROXY_ENV))
- && *http_proxy ))
- {
- parsed_uri_t uri;
-
- if (proxy)
- http_proxy = proxy;
-
- err = parse_uri (&uri, http_proxy, 0, 0);
- if (gpg_err_code (err) == GPG_ERR_INV_URI
- && is_hostname_port (http_proxy))
- {
- /* Retry assuming a "hostname:port" string. */
- char *tmpname = strconcat ("http://", http_proxy, NULL);
- if (tmpname && !parse_uri (&uri, tmpname, 0, 0))
- err = 0;
- xfree (tmpname);
- }
-
- if (err)
- ;
-#ifdef USE_TLS
- else if (!strcmp (uri->scheme, "http"))
- have_http_proxy = 1;
-#endif
- else if (!strcmp (uri->scheme, "socks4")
- || !strcmp (uri->scheme, "socks5h"))
- err = gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
- else
- err = gpg_err_make (default_errsource, GPG_ERR_INV_URI);
-
- if (err)
- {
- log_error ("invalid HTTP proxy (%s): %s\n",
- http_proxy, gpg_strerror (err));
- return gpg_err_make (default_errsource, GPG_ERR_CONFIGURATION);
- }
-
- if (uri->auth)
- {
- remove_escapes (uri->auth);
- proxy_authstr = make_header_line ("Proxy-Authorization: Basic ",
- "\r\n",
- uri->auth, strlen(uri->auth));
- if (!proxy_authstr)
- {
- err = gpg_err_make (default_errsource,
- gpg_err_code_from_syserror ());
- http_release_parsed_uri (uri);
- return err;
- }
- }
-
- err = connect_server (*uri->host ? uri->host : "localhost",
- uri->port ? uri->port : 80,
- hd->flags, NULL, timeout, &sock);
- http_release_parsed_uri (uri);
- }
- else
- {
- err = connect_server (server, port, hd->flags, srvtag, timeout, &sock);
- }
-
- if (err)
- {
- xfree (proxy_authstr);
- return err;
- }
- hd->sock = my_socket_new (sock);
- if (!hd->sock)
- {
- xfree (proxy_authstr);
- return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
- }
-
-#if USE_TLS
- if (have_http_proxy && hd->uri->use_tls)
- {
- int saved_flags;
- cookie_t cookie;
-
- /* Try to use the CONNECT method to proxy our TLS stream. */
- request = es_bsprintf
- ("CONNECT %s:%hu HTTP/1.0\r\nHost: %s:%hu\r\n%s",
- httphost ? httphost : server,
- port,
- httphost ? httphost : server,
- port,
- proxy_authstr ? proxy_authstr : "");
- xfree (proxy_authstr);
- proxy_authstr = NULL;
-
- if (! request)
- return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
-
- if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
- log_debug_with_string (request, "http.c:request:");
-
- cookie = xtrycalloc (1, sizeof *cookie);
- if (! cookie)
- {
- err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
- xfree (request);
- return err;
- }
- cookie->sock = my_socket_ref (hd->sock);
- hd->write_cookie = cookie;
-
- hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
- if (! hd->fp_write)
- {
- err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
- my_socket_unref (cookie->sock, NULL, NULL);
- xfree (cookie);
- xfree (request);
- hd->write_cookie = NULL;
- return err;
- }
- else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
- err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
-
- xfree (request);
- request = NULL;
-
- /* Make sure http_wait_response doesn't close the stream. */
- saved_flags = hd->flags;
- hd->flags &= ~HTTP_FLAG_SHUTDOWN;
-
- /* Get the response. */
- err = http_wait_response (hd);
-
- /* Restore flags, destroy stream. */
- hd->flags = saved_flags;
- es_fclose (hd->fp_read);
- hd->fp_read = NULL;
- hd->read_cookie = NULL;
-
- /* Reset state. */
- hd->in_data = 0;
-
- if (err)
- return err;
-
- if (hd->status_code != 200)
- {
- request = es_bsprintf
- ("CONNECT %s:%hu",
- httphost ? httphost : server,
- port);
-
- log_error (_("error accessing '%s': http status %u\n"),
- request ? request : "out of core",
- http_get_status_code (hd));
-
- xfree (request);
- return gpg_error (GPG_ERR_NO_DATA);
- }
+ leave:
+ return err;
+}
- /* We are done with the proxy, the code below will establish a
- * TLS session and talk directly to the target server. */
- http_proxy = NULL;
- }
-#endif /* USE_TLS */
+/* Run the NTBTLS handshake if needed. */
#if HTTP_USE_NTBTLS
+static gpg_error_t
+run_ntbtls_handshake (http_t hd)
+{
+ gpg_error_t err;
+ estream_t in, out;
+
if (hd->uri->use_tls)
{
- estream_t in, out;
-
my_socket_ref (hd->sock);
/* Until we support send/recv in estream under Windows we need
@@ -2025,8 +2175,7 @@ send_request (http_t hd, const char *httphost, const char *auth,
if (!in)
{
err = gpg_error_from_syserror ();
- xfree (proxy_authstr);
- return err;
+ goto leave;
}
#ifdef HAVE_W32_SYSTEM
@@ -2039,8 +2188,7 @@ send_request (http_t hd, const char *httphost, const char *auth,
{
err = gpg_error_from_syserror ();
es_fclose (in);
- xfree (proxy_authstr);
- return err;
+ goto leave;
}
err = ntbtls_set_transport (hd->session->tls_session, in, out);
@@ -2048,11 +2196,11 @@ send_request (http_t hd, const char *httphost, const char *auth,
{
log_info ("TLS set_transport failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
- xfree (proxy_authstr);
- return err;
+ es_fclose (in);
+ es_fclose (out);
+ goto leave;
}
-#ifdef HTTP_USE_NTBTLS
if (hd->session->verify_cb)
{
err = ntbtls_set_verify_cb (hd->session->tls_session,
@@ -2061,74 +2209,62 @@ send_request (http_t hd, const char *httphost, const char *auth,
{
log_error ("ntbtls_set_verify_cb failed: %s\n",
gpg_strerror (err));
- xfree (proxy_authstr);
- return err;
+ goto leave;
}
}
-#endif /*HTTP_USE_NTBTLS*/
while ((err = ntbtls_handshake (hd->session->tls_session)))
{
-#if NTBTLS_VERSION_NUMBER >= 0x000200
unsigned int tlevel, ttype;
- const char *s = ntbtls_get_last_alert (hd->session->tls_session,
- &tlevel, &ttype);
+ const char *s;
+
+ s = ntbtls_get_last_alert (hd->session->tls_session, &tlevel, &ttype);
if (s)
log_info ("TLS alert: %s (%u.%u)\n", s, tlevel, ttype);
-#endif
switch (err)
{
default:
log_info ("TLS handshake failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
- xfree (proxy_authstr);
- return err;
+ goto leave;
}
}
hd->session->verify.done = 0;
- /* Try the available verify callbacks until one returns success
- * or a real error. Note that NTBTLS does the verification
- * during the handshake via */
-#ifdef HTTP_USE_NTBTLS
- err = 0; /* Fixme check that the CB has been called. */
-#else
- err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
-#endif
+ /* Note that in contrast to GNUTLS NTBTLS uses a registered
+ * callback to run the verification as part of the handshake. */
+ err = 0;
+ /* FIXME: We could check that the CB has been called and if not
+ * error out with this warning:
+ * if (err)
+ * {
+ * log_info ("TLS connection authentication failed: %s <%s>\n",
+ * gpg_strerror (err), gpg_strsource (err));
+ * goto leave;
+ * }
+ */
+ }
+ else
+ err = 0;
- if (hd->session->verify_cb
- && gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
- && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
- err = hd->session->verify_cb (hd->session->verify_cb_value,
- hd, hd->session,
- (hd->flags | hd->session->flags),
- hd->session->tls_session);
-
- if (tls_callback
- && gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
- && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
- err = tls_callback (hd, hd->session, 0);
+ leave:
+ return err;
+}
+#endif /*HTTP_USE_NTBTLS*/
- if (gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
- && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
- err = http_verify_server_credentials (hd->session);
- if (err)
- {
- log_info ("TLS connection authentication failed: %s <%s>\n",
- gpg_strerror (err), gpg_strsource (err));
- xfree (proxy_authstr);
- return err;
- }
+/* Run the GNUTLS handshake if needed. */
+#if HTTP_USE_GNUTLS
+static gpg_error_t
+run_gnutls_handshake (http_t hd, const char *server)
+{
+ gpg_error_t err;
+ int rc;
- }
-#elif HTTP_USE_GNUTLS
if (hd->uri->use_tls)
{
- int rc;
-
my_socket_ref (hd->sock);
gnutls_transport_set_ptr (hd->session->tls_session, hd->sock);
gnutls_transport_set_pull_function (hd->session->tls_session,
@@ -2164,8 +2300,8 @@ send_request (http_t hd, const char *httphost, const char *auth,
}
else
log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
- xfree (proxy_authstr);
- return gpg_err_make (default_errsource, GPG_ERR_NETWORK);
+ err = gpg_error (GPG_ERR_NETWORK);
+ goto leave;
}
hd->session->verify.done = 0;
@@ -2177,12 +2313,576 @@ send_request (http_t hd, const char *httphost, const char *auth,
{
log_info ("TLS connection authentication failed: %s\n",
gpg_strerror (err));
- xfree (proxy_authstr);
- return err;
+ goto leave;
}
}
+ else
+ err =0;
+
+ leave:
+ return err;
+}
#endif /*HTTP_USE_GNUTLS*/
+
+/* It INPUTSTRING is NULL get the intial token. If INPUTSTRING is not
+ * NULL, decode the string and use this as input from teh server. On
+ * success the final output token is stored at PROXY->OUTTOKEN and
+ * OUTTOKLEN. IF the authentication succeeded OUTTOKLEN is zero. */
+static gpg_error_t
+proxy_get_token (proxy_info_t proxy, const char *inputstring)
+{
+#ifdef HAVE_W32_SYSTEM
+ gpg_error_t err;
+ int rc;
+ SecBuffer chlg_buf; /* challenge buffer */
+ SecBufferDesc chlg_desc; /* challenge descriptor */
+ SecBuffer resp_buf; /* response buffer */
+ SecBufferDesc resp_desc; /* response descriptor */
+ unsigned long attrs;
+ TimeStamp expiry; /* (value not used) */
+ void *intoken = NULL;
+ size_t intoklen;
+
+ if (inputstring)
+ {
+ /* The input is expected in the token parameter but the paremter
+ * name is often forgotten. Thus we simply detect the parameter
+ * name and skip it, assuming no other parameters are given. */
+ if (!strncmp (inputstring, "token=", 6))
+ inputstring += 6;
+
+ err = b64decode (inputstring, NULL, &intoken, &intoklen);
+ /* Just to be safe that we don't overflow an ulong we check the
+ * actual size against an arbitrary limit. */
+ if (!err && intoklen > 65535)
+ err = gpg_error (GPG_ERR_ERANGE);
+ if (err || !intoklen)
+ {
+ log_error ("error decoding received auth token: %s\n",
+ err? gpg_strerror (err):"empty challenge token received");
+ if (!err)
+ err = gpg_error (GPG_ERR_BAD_AUTH);
+ goto leave;
+ }
+ }
+
+ if (!proxy->spn)
+ {
+ char *buffer = strconcat ("HTTP/", (*proxy->uri->host
+ ?proxy->uri->host:"localhost"), NULL);
+ if (!buffer)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ if (opt_debug)
+ log_debug ("http.c:proxy_connect: using '%s' as SPN\n", buffer);
+ proxy->spn = utf8_to_wchar (buffer);
+ xfree (buffer);
+ if (!proxy->spn)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+
+ if (!proxy->token_size || !proxy->outtoken) /* Not yet initialized. */
+ {
+ PSecPkgInfoW pinfo;
+
+ rc = QuerySecurityPackageInfoW (NEGOSSP_NAME_W, &pinfo);
+ if (rc)
+ {
+ log_error ("QSPI(Negotiate) failed: %s (%d)\n",
+ w32_strerror (rc), rc);
+ err = gpg_error (GPG_ERR_BAD_AUTH);
+ goto leave;
+ }
+ proxy->token_size = pinfo->cbMaxToken;
+ FreeContextBuffer (pinfo);
+
+ proxy->outtoken = xtrymalloc (proxy->token_size);
+ if (!proxy->outtoken)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+
+ if (!proxy->cred_handle_valid)
+ {
+ rc = AcquireCredentialsHandleW (NULL, NEGOSSP_NAME_W,
+ SECPKG_CRED_OUTBOUND, NULL,
+ NULL, /* Current user */
+ NULL, /* reserved */
+ NULL, /* reserved */
+ &proxy->cred_handle,
+ NULL /* expiry */);
+ if (rc)
+ {
+ log_error ("ACH(Negotiate) failed: %s (%d)\n", w32_strerror (rc), rc);
+ err = gpg_error (GPG_ERR_NO_AUTH);
+ goto leave;
+ }
+ proxy->cred_handle_valid = 1;
+ }
+
+ /* Now generate our challenge-response message. */
+ if (intoken)
+ {
+ chlg_buf.BufferType = SECBUFFER_TOKEN;
+ chlg_buf.pvBuffer = intoken;
+ chlg_buf.cbBuffer = intoklen;
+ chlg_desc.ulVersion = SECBUFFER_VERSION;
+ chlg_desc.cBuffers = 1;
+ chlg_desc.pBuffers = &chlg_buf;
+ }
+
+ resp_buf.BufferType = SECBUFFER_TOKEN;
+ resp_buf.pvBuffer = proxy->outtoken;
+ resp_buf.cbBuffer = proxy->token_size;
+ resp_desc.ulVersion = SECBUFFER_VERSION;
+ resp_desc.cBuffers = 1;
+ resp_desc.pBuffers = &resp_buf;
+ rc = InitializeSecurityContextW (&proxy->cred_handle,
+ (intoken && proxy->ctxt_handle_valid)
+ ? &proxy->ctxt_handle : NULL,
+ proxy->spn, /* service principal name */
+ ISC_REQ_CONFIDENTIALITY,
+ 0, /* reserved */
+ SECURITY_NATIVE_DREP,
+ intoken? &chlg_desc : NULL,
+ 0, /* reserved */
+ &proxy->ctxt_handle, /* new context */
+ &resp_desc, /* the output. */
+ &attrs, /* attribs of the context. */
+ &expiry);
+ switch (rc)
+ {
+ case SEC_E_OK: /* All done and no more ISC expected. */
+ break;
+
+ case SEC_I_COMPLETE_AND_CONTINUE: /* Need to call CompleteAuthToken. */
+ case SEC_I_COMPLETE_NEEDED:
+ rc = CompleteAuthToken (&proxy->ctxt_handle, &resp_desc);
+ log_error ("CompleteAuthToken failed: %s (%d)\n", w32_strerror (rc), rc);
+ err = gpg_error (GPG_ERR_NO_AUTH);
+ goto leave;
+ break;
+
+ case SEC_I_CONTINUE_NEEDED: /* Send the new token to the client. */
+ break;
+
+ default:
+ log_error ("ISC(Negotiate) failed: %s (%d)\n", w32_strerror (rc), rc);
+ err = gpg_error (GPG_ERR_NO_AUTH);
+ goto leave;
+ }
+
+ proxy->outtoklen = resp_buf.cbBuffer;
+ proxy->ctxt_handle_valid = 1;
+ err = 0;
+
+ leave:
+ xfree (intoken);
+ return err;
+
+#else /*!HAVE_W32_SYSTEM*/
+
+ (void)proxy;
+ (void)inputstring;
+ return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
+#endif /*!HAVE_W32_SYSTEM*/
+}
+
+
+/* Use the CONNECT method to proxy our TLS stream. */
+static gpg_error_t
+run_proxy_connect (http_t hd, proxy_info_t proxy,
+ const char *httphost, const char *server,
+ unsigned short port)
+{
+ gpg_error_t err;
+ int saved_flags = hd->flags;
+ char *authhdr = NULL;
+ char *request = NULL;
+ char *tmpstr = NULL;
+ const char *s, *parms;
+ unsigned int idx;
+ int auth_basic = 0;
+ enum auth_negotiate_states authstate = 0;
+ unsigned int authpasses = 0;
+
+ /* Authentication methods implemented here:
+ * RFC-2617 - HTTP Authentication: Basic and Digest Access Authentication
+ * RFC-4559 - SPNEGO-based Kerberos and NTLM HTTP Authentication
+ */
+ auth_basic = !!proxy->uri->auth;
+ hd->keep_alive = !auth_basic; /* We may need to send more requests. */
+
+ /* For basic authentication we need to send just one request. */
+ if (auth_basic
+ && !(authhdr = make_header_line ("Proxy-Authorization: Basic ",
+ "\r\n",
+ proxy->uri->auth,
+ strlen (proxy->uri->auth))))
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ again:
+ xfree (request);
+ request = es_bsprintf ("CONNECT %s:%hu HTTP/1.%c\r\nHost: %s:%hu\r\n%s%s",
+ httphost ? httphost : server,
+ port,
+ auth_basic? '0' : '1',
+ httphost ? httphost : server,
+ port,
+ authhdr ? authhdr : "",
+ hd->keep_alive? "Connection: keep-alive\r\n" : "");
+ if (!request)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
+ log_debug_with_string (request, "http.c:proxy:request:");
+
+ if (!hd->fp_write)
+ {
+ err = make_fp_write (hd, 0, NULL);
+ if (err)
+ goto leave;
+ }
+
+ if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ /* Make sure http_wait_response doesn't close the stream. */
+ saved_flags = hd->flags;
+ hd->flags &= ~HTTP_FLAG_SHUTDOWN;
+
+ /* Get the response and set hd->fp_read */
+ err = http_wait_response (hd);
+ if (err)
+ goto leave;
+
+ /* Reset state. */
+ es_clearerr (hd->fp_read);
+ ((cookie_t)(hd->read_cookie))->up_to_empty_line = 1;
+ hd->in_data = 0;
+
+ if (hd->status_code >= 200 && hd->status_code < 300 )
+ err = 0; /* Success. */
+ else if (hd->status_code == 407)
+ {
+ if (opt_debug)
+ log_debug ("http.c:proxy_connect: 407 seen\n");
+ parms = NULL;
+ for (idx=0; (s = http_get_header (hd, "Proxy-Authenticate", idx)); idx++)
+ {
+ if (opt_debug)
+ log_debug ("http.c:proxy_connect: method=%s\n", s);
+ if (!parms)
+ parms = has_leading_keyword (s, "Negotiate");
+ }
+ if (!parms)
+ authstate = AUTH_NGT_NONE;
+ else if (authstate == AUTH_NGT_NONE)
+ authstate = AUTH_NGT_RCVD;
+
+ switch (authstate)
+ {
+ case AUTH_NGT_NONE:
+ if (opt_debug)
+ log_debug ("http.c:proxy_connect: no supported auth method\n");
+ err = gpg_error (GPG_ERR_NO_AUTH);
+ break;
+
+ case AUTH_NGT_RCVD:
+ if (opt_debug)
+ log_debug ("http.c:proxy_connect: using negotiate - init\n");
+ err = proxy_get_token (proxy, NULL);
+ if (err)
+ goto leave;
+ if (proxy->outtoklen) /* Authentication needs to continue. */
+ {
+ xfree (authhdr);
+ authhdr = make_header_line ("Proxy-Authorization: Negotiate ",
+ "\r\n",
+ proxy->outtoken, proxy->outtoklen);
+ if (!authhdr)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ authstate = AUTH_NGT_SENT;
+ authpasses++;
+ goto again;
+ }
+ break;
+
+ case AUTH_NGT_SENT:
+ if (opt_debug)
+ log_debug ("http.c:proxy_connect: using negotiate - next\n");
+ if (!*parms)
+ {
+ log_debug ("proxy authentication failed"
+ " due to server not accepting our challenge\n");
+ err = gpg_error (GPG_ERR_BAD_AUTH);
+ goto leave;
+ }
+ if (authpasses > 5)
+ {
+ log_error ("proxy authentication failed"
+ " due to too many passes\n");
+ err = gpg_error (GPG_ERR_BAD_AUTH);
+ goto leave;
+
+ }
+ err = proxy_get_token (proxy, parms);
+ if (err)
+ goto leave;
+ if (proxy->outtoklen) /* Authentication needs to continue. */
+ {
+ xfree (authhdr);
+ authhdr = make_header_line ("Proxy-Authorization: Negotiate ",
+ "\r\n",
+ proxy->outtoken, proxy->outtoklen);
+ if (!authhdr)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ authpasses++;
+ goto again;
+ }
+ break;
+
+ default:
+ BUG();
+ }
+ }
+ else
+ err = gpg_error (GPG_ERR_NO_DATA);
+
+ if (err)
+ {
+ xfree (tmpstr);
+ tmpstr = es_bsprintf ("%s:%hu", httphost ? httphost : server, port);
+ log_error (_("error accessing '%s': http status %u\n"),
+ tmpstr ? tmpstr : "out of core",
+ http_get_status_code (hd));
+ goto leave;
+ }
+
+ leave:
+ if (hd->keep_alive)
+ {
+ es_fclose (hd->fp_write);
+ hd->fp_write = NULL;
+ /* The close has released the cookie and thus we better set it
+ * to NULL. */
+ hd->write_cookie = NULL;
+ }
+ /* Restore flags, destroy stream, reset state. */
+ hd->flags = saved_flags;
+ es_fclose (hd->fp_read);
+ hd->fp_read = NULL;
+ hd->read_cookie = NULL;
+ hd->keep_alive = 0;
+ hd->in_data = 0;
+
+ xfree (request);
+ xfree (authhdr);
+ xfree (tmpstr);
+ return err;
+}
+
+
+/* Make a request string using a standard proxy. On success the
+ * request is stored at R_REQUEST (and will never be NULL). */
+static gpg_error_t
+mk_proxy_request (http_t hd, proxy_info_t proxy,
+ const char *httphost, const char *server,
+ unsigned short port, const char *relpath,
+ const char *authstr,
+ char **r_request)
+{
+ gpg_error_t err = 0;
+ char *authhdr = NULL;
+ char *request = NULL;
+
+ *r_request = NULL;
+
+ if (proxy->uri->auth
+ && !(authhdr = make_header_line ("Proxy-Authorization: Basic ",
+ "\r\n",
+ proxy->uri->auth,
+ strlen (proxy->uri->auth))))
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ request = es_bsprintf ("%s %s://%s:%hu%s%s HTTP/1.0\r\n%s%s",
+ hd->req_type == HTTP_REQ_GET ? "GET" :
+ hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
+ hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
+ hd->uri->use_tls? "https" : "http",
+ httphost? httphost : server,
+ port, *relpath == '/' ? "" : "/", relpath,
+ authstr ? authstr : "",
+ authhdr ? authhdr : "");
+ if (!request)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ *r_request = request;
+ request = NULL;
+
+ leave:
+ xfree (request);
+ xfree (authhdr);
+ return err;
+}
+
+
+/* Make a request string using. On success the request is stored at
+ * R_REQUEST (and will never be NULL). */
+static gpg_error_t
+mk_std_request (http_t hd,
+ const char *httphost, const char *server,
+ unsigned short port, const char *relpath,
+ const char *authstr,
+ char **r_request)
+{
+ gpg_error_t err = 0;
+ char portstr[35];
+ char *request = NULL;
+
+ *r_request = NULL;
+
+ if (port == (hd->uri->use_tls? 443 : 80))
+ *portstr = 0;
+ else
+ snprintf (portstr, sizeof portstr, ":%u", port);
+
+ request = es_bsprintf ("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s",
+ hd->req_type == HTTP_REQ_GET ? "GET" :
+ hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
+ hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
+ *relpath == '/' ? "" : "/", relpath,
+ httphost? httphost : server,
+ portstr,
+ authstr? authstr:"");
+ if (!request)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ *r_request = request;
+ request = NULL;
+
+ leave:
+ xfree (request);
+ return err;
+}
+
+
+/*
+ * Send a HTTP request to the server
+ * Returns 0 if the request was successful
+ */
+static gpg_error_t
+send_request (http_t hd, const char *httphost, const char *auth,
+ const char *override_proxy,
+ const char *srvtag, unsigned int timeout,
+ strlist_t headers)
+{
+ gpg_error_t err;
+ const char *server;
+ char *request = NULL;
+ char *relpath = NULL;
+ unsigned short port;
+ int use_http_proxy = 0;
+ char *proxy_authstr = NULL;
+ char *authstr = NULL;
+ assuan_fd_t sock;
+ proxy_info_t proxy = NULL;
+
+ err = send_request_basic_checks (hd);
+ if (err)
+ goto leave;
+
+ if ((hd->flags & HTTP_FLAG_FORCE_TOR))
+ {
+ /* Non-blocking connects do not work with our Tor proxy because
+ * we can't continue the Socks protocol after the EINPROGRESS.
+ * Disable the timeout to use a blocking connect. */
+ timeout = 0;
+ }
+
+ server = *hd->uri->host ? hd->uri->host : "localhost";
+ port = hd->uri->port ? hd->uri->port : 80;
+
+ if ((err = send_request_set_sni (hd, httphost? httphost : server)))
+ goto leave;
+
+ if ((err = get_proxy_for_url (hd, override_proxy, &proxy)))
+ goto leave;
+
+ if (proxy && proxy->is_http_proxy)
+ {
+ use_http_proxy = 1; /* We want to use a proxy for the conenction. */
+ err = connect_server (*proxy->uri->host ? proxy->uri->host : "localhost",
+ proxy->uri->port ? proxy->uri->port : 80,
+ hd->flags, NULL, timeout, &sock);
+ }
+ else
+ {
+ err = connect_server (server, port, hd->flags, srvtag, timeout, &sock);
+ }
+ if (err)
+ goto leave;
+
+ hd->sock = my_socket_new (sock);
+ if (!hd->sock)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ if (use_http_proxy && hd->uri->use_tls)
+ {
+ err = run_proxy_connect (hd, proxy, httphost, server, port);
+ if (err)
+ goto leave;
+
+ /* We are done with the proxy, the code below will establish a
+ * TLS session and talk directly to the target server. Thus we
+ * clear the flag to indicate this. */
+ use_http_proxy = 0;
+ }
+
+#if HTTP_USE_NTBTLS
+ err = run_ntbtls_handshake (hd);
+#elif HTTP_USE_GNUTLS
+ err = run_gnutls_handshake (hd, server);
+#else
+ err = 0;
+#endif
+ if (err)
+ goto leave;
+
if (auth || hd->uri->auth)
{
char *myauth;
@@ -2192,8 +2892,8 @@ send_request (http_t hd, const char *httphost, const char *auth,
myauth = xtrystrdup (auth);
if (!myauth)
{
- xfree (proxy_authstr);
- return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+ err = gpg_error_from_syserror ();
+ goto leave;
}
remove_escapes (myauth);
}
@@ -2205,115 +2905,65 @@ send_request (http_t hd, const char *httphost, const char *auth,
authstr = make_header_line ("Authorization: Basic ", "\r\n",
myauth, strlen (myauth));
- if (auth)
+ if (auth) /* (Was allocated.) */
xfree (myauth);
if (!authstr)
{
- xfree (proxy_authstr);
- return gpg_err_make (default_errsource,
- gpg_err_code_from_syserror ());
+ err = gpg_error_from_syserror ();
+ goto leave;
}
}
- p = build_rel_path (hd->uri);
- if (!p)
- return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
-
- if (http_proxy && *http_proxy)
+ relpath = build_rel_path (hd->uri);
+ if (!relpath)
{
- request = es_bsprintf
- ("%s %s://%s:%hu%s%s HTTP/1.0\r\n%s%s",
- hd->req_type == HTTP_REQ_GET ? "GET" :
- hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
- hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
- hd->uri->use_tls? "https" : "http",
- httphost? httphost : server,
- port, *p == '/' ? "" : "/", p,
- authstr ? authstr : "",
- proxy_authstr ? proxy_authstr : "");
+ err = gpg_error_from_syserror ();
+ goto leave;
}
- else
- {
- char portstr[35];
- if (port == (hd->uri->use_tls? 443 : 80))
- *portstr = 0;
- else
- snprintf (portstr, sizeof portstr, ":%u", port);
-
- request = es_bsprintf
- ("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s",
- hd->req_type == HTTP_REQ_GET ? "GET" :
- hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
- hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
- *p == '/' ? "" : "/", p,
- httphost? httphost : server,
- portstr,
- authstr? authstr:"");
- }
- xfree (p);
- if (!request)
- {
- err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
- xfree (authstr);
- xfree (proxy_authstr);
- return err;
- }
+ if (use_http_proxy)
+ err = mk_proxy_request (hd, proxy, httphost, server, port,
+ relpath, authstr, &request);
+ else
+ err = mk_std_request (hd, httphost, server, port,
+ relpath, authstr, &request);
+ if (err)
+ goto leave;
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_with_string (request, "http.c:request:");
/* First setup estream so that we can write even the first line
using estream. This is also required for the sake of gnutls. */
- {
- cookie_t cookie;
-
- cookie = xtrycalloc (1, sizeof *cookie);
- if (!cookie)
- {
- err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
- goto leave;
- }
- cookie->sock = my_socket_ref (hd->sock);
- hd->write_cookie = cookie;
- cookie->use_tls = hd->uri->use_tls;
- cookie->session = http_session_ref (hd->session);
+ err = make_fp_write (hd, hd->uri->use_tls, hd->session);
+ if (err)
+ goto leave;
- hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
- if (!hd->fp_write)
- {
- err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
- my_socket_unref (cookie->sock, NULL, NULL);
- xfree (cookie);
- hd->write_cookie = NULL;
- }
- else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
- err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
- else
- err = 0;
+ if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
- if (!err)
+ for (;headers; headers=headers->next)
{
- for (;headers; headers=headers->next)
+ if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
+ log_debug_with_string (headers->d, "http.c:request-header:");
+ if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write))
+ || (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write)))
{
- if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
- log_debug_with_string (headers->d, "http.c:request-header:");
- if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write))
- || (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write)))
- {
- err = gpg_err_make (default_errsource,
- gpg_err_code_from_syserror ());
- break;
- }
+ err = gpg_error_from_syserror ();
+ goto leave;
}
}
- }
leave:
es_free (request);
xfree (authstr);
xfree (proxy_authstr);
+ xfree (relpath);
+ release_proxy_info (proxy);
return err;
}
@@ -2439,19 +3089,26 @@ store_header (http_t hd, char *line)
p++;
value = p;
- for (h=hd->headers; h; h = h->next)
- if ( !strcmp (h->name, line) )
- break;
- if (h)
+ /* Check whether we have already seen a line with that name. In
+ * that case we assume it is a comma separated list and merge
+ * them. Of course there are a few exceptions. */
+ if (!strcmp (line, "Proxy-Authenticate")
+ || !strcmp (line, "Www-Authenticate"))
+ ; /* Better to have them separate. */
+ else
{
- /* We have already seen a line with that name. Thus we assume
- * it is a comma separated list and merge them. */
- p = strconcat (h->value, ",", value, NULL);
- if (!p)
- return gpg_err_code_from_syserror ();
- xfree (h->value);
- h->value = p;
- return 0;
+ for (h=hd->headers; h; h = h->next)
+ if ( !strcmp (h->name, line) )
+ break;
+ if (h)
+ {
+ p = strconcat (h->value, ",", value, NULL);
+ if (!p)
+ return gpg_err_code_from_syserror ();
+ xfree (h->value);
+ h->value = p;
+ return 0;
+ }
}
/* Append a new header. */
@@ -2474,18 +3131,27 @@ store_header (http_t hd, char *line)
/* Return the header NAME from the last response. The returned value
- is valid as along as HD has not been closed and no other request
- has been send. If the header was not found, NULL is returned. NAME
- must be canonicalized, that is the first letter of each dash
- delimited part must be uppercase and all other letters lowercase. */
+ * is valid as along as HD has not been closed and no other request
+ * has been send. If the header was not found, NULL is returned. NAME
+ * must be canonicalized, that is the first letter of each dash
+ * delimited part must be uppercase and all other letters lowercase.
+ * SKIP gives the number of entries of the requested NAME to skip
+ * before returning; this can be used to enumerate headers with the
+ * same name (see store_header).
+*/
const char *
-http_get_header (http_t hd, const char *name)
+http_get_header (http_t hd, const char *name, unsigned int skip)
{
header_t h;
for (h=hd->headers; h; h = h->next)
- if ( !strcmp (h->name, name) )
- return h->value;
+ if (!strcmp (h->name, name))
+ {
+ if (skip)
+ skip--;
+ else
+ return h->value;
+ }
return NULL;
}
@@ -2608,7 +3274,7 @@ parse_response (http_t hd)
cookie->content_length_valid = 0;
if (!(hd->flags & HTTP_FLAG_IGNORE_CL))
{
- s = http_get_header (hd, "Content-Length");
+ s = http_get_header (hd, "Content-Length", 0);
if (s)
{
cookie->content_length_valid = 1;
@@ -3063,8 +3729,14 @@ connect_server (const char *server, unsigned short port,
if (!connected)
{
if (!hostfound)
- log_error ("can't connect to '%s': %s\n",
- server, "host not found");
+ {
+ log_error ("can't connect to '%s': %s\n",
+ server, "host not found");
+ /* If the resolver told us "no name" translate this in this
+ * case to "unknown host". */
+ if (gpg_err_code (last_err) == GPG_ERR_NO_NAME)
+ last_err = 0;
+ }
else if (!anyhostaddr)
log_error ("can't connect to '%s': %s\n",
server, "no IP address for host");
@@ -3185,31 +3857,48 @@ cookie_read (void *cookie, void *buffer, size_t size)
{
cookie_t c = cookie;
int nread;
+ size_t offset = 0;
if (c->content_length_valid)
{
if (!c->content_length)
- return 0; /* EOF */
+ {
+ c->content_length_valid = 0;
+ return 0; /* EOF */
+ }
if (c->content_length < size)
size = c->content_length;
}
+ if (c->pending.len)
+ {
+ offset = c->pending.len > size? size : c->pending.len;
+ memcpy (buffer, c->pending.data, offset);
+ c->pending.len -= offset;
+ }
+
+ if (offset >= size)
+ nread = offset;
+ else
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
estream_t in, out;
ntbtls_get_stream (c->session->tls_session, &in, &out);
- nread = es_fread (buffer, 1, size, in);
+ nread = es_fread ((char*)buffer+offset, 1, size-offset, in);
if (opt_debug)
- log_debug ("TLS network read: %d/%zu\n", nread, size);
+ log_debug ("TLS network read: %d/%zu\n", nread, size-offset);
+ if (nread >= 0)
+ nread += offset;
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
{
again:
- nread = gnutls_record_recv (c->session->tls_session, buffer, size);
+ nread = gnutls_record_recv (c->session->tls_session,
+ (char*)buffer+offset, size-offset);
if (nread < 0)
{
if (nread == GNUTLS_E_INTERRUPTED)
@@ -3236,11 +3925,86 @@ cookie_read (void *cookie, void *buffer, size_t size)
gpg_err_set_errno (EIO);
return -1;
}
+ if (nread >= 0)
+ nread += offset;
}
else
#endif /*HTTP_USE_GNUTLS*/
{
- nread = read_server (c->sock->fd, buffer, size);
+ nread = read_server (c->sock->fd, (char*)buffer+offset, size-offset);
+ if (opt_debug)
+ log_debug ("network read: %d/%zu\n", nread, size);
+ if (nread >= 0)
+ nread += offset;
+ }
+
+ if (nread > 0 && c->up_to_empty_line)
+ {
+ gpg_error_t err;
+ const char *s;
+ size_t n;
+ int extra;
+ int lfcr_pending = 0;
+ char *bufp = buffer;
+
+ if (c->last_was_lf && nread > 1 && bufp[0] == '\r' && bufp[1] == '\n')
+ {
+ s = buffer;
+ extra = 2;
+ }
+ else if (c->last_was_lf && bufp[0] == '\r')
+ {
+ lfcr_pending = 1;
+ s = buffer; /* Only to avoid the call to gnupg_memstr. */
+ }
+ else if (c->last_was_lfcr && bufp[0] == '\n')
+ {
+ s = buffer;
+ extra = 1;
+ }
+ else
+ s = NULL;
+
+ c->last_was_lfcr = c->last_was_lf = 0;
+
+ if (!s)
+ {
+ s = gnupg_memstr (buffer, nread, "\n\r\n");
+ extra = 3;
+ }
+
+ if (lfcr_pending)
+ c->last_was_lfcr = 1;
+ else if (s)
+ {
+ /* Save away the rest and return up to the LF. */
+ log_assert (!c->pending.len);
+ n = (s+extra) - bufp;
+ log_assert (n <= nread);
+ c->pending.len = nread - n;
+ if (!c->pending.data || c->pending.len >= c->pending.size)
+ {
+ xfree (c->pending.data);
+ c->pending.size = c->pending.len + 256; /* Some extra space. */
+ c->pending.data = xtrymalloc (c->pending.size);
+ if (!c->pending.data)
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error allocating network read buffer: %s\n",
+ gpg_strerror (err));
+ return -1;
+ }
+ memcpy (c->pending.data, bufp + n, c->pending.len);
+ }
+ else
+ memcpy (c->pending.data, bufp + n, c->pending.len);
+ nread = n; /* Return everything up to the empty line. */
+ c->up_to_empty_line = 0;
+ }
+ else if (bufp[nread-1] == '\n')
+ c->last_was_lf = 1;
+ else if (nread > 1 && bufp[nread-2] == '\n' && bufp[nread-1] == '\r')
+ c->last_was_lfcr = 1;
}
if (c->content_length_valid && nread > 0)
@@ -3401,6 +4165,7 @@ cookie_close (void *cookie)
if (c->session)
http_session_unref (c->session);
+ xfree (c->pending.data);
xfree (c);
return 0;
}
@@ -3409,7 +4174,7 @@ cookie_close (void *cookie)
/* Verify the credentials of the server. Returns 0 on success and
- store the result in the session object. */
+ store the result in the session object. Only used by GNUTLS. */
gpg_error_t
http_verify_server_credentials (http_session_t sess)
{
@@ -3443,19 +4208,15 @@ http_verify_server_credentials (http_session_t sess)
}
else if (status)
{
- log_error ("%s: status=0x%04x\n", errprefix, status);
-#if GNUTLS_VERSION_NUMBER >= 0x030104
- {
- gnutls_datum_t statusdat;
+ gnutls_datum_t statusdat;
- if (!gnutls_certificate_verification_status_print
- (status, GNUTLS_CRT_X509, &statusdat, 0))
- {
- log_info ("%s: %s\n", errprefix, statusdat.data);
- gnutls_free (statusdat.data);
- }
- }
-#endif /*gnutls >= 3.1.4*/
+ log_error ("%s: status=0x%04x\n", errprefix, status);
+ if (!gnutls_certificate_verification_status_print
+ (status, GNUTLS_CRT_X509, &statusdat, 0))
+ {
+ log_info ("%s: %s\n", errprefix, statusdat.data);
+ gnutls_free (statusdat.data);
+ }
sess->verify.status = status;
if (!err)
@@ -3796,3 +4557,13 @@ http_status2string (unsigned int status)
return "";
}
+
+
+/* Fucntion called on SIGHUP to flush internal variables. */
+void
+http_reinitialize (void)
+{
+#ifdef HAVE_W32_SYSTEM
+ w32_get_internet_session (1); /* Clear our session. */
+#endif /*HAVE_W32_SYSTEM*/
+}