dirmngr: Allow a timeout for HTTP and other TCP connects.

* dirmngr/http.c: Include fcntl.h.
(http_session_s): Add field 'connect_timeout'.
(http_session_new): Clear that.
(http_session_set_timeout): New function.
(my_wsagetlasterror) [W32]: New.
(connect_with_timeout): New function.
(connect_server): Add arg 'timeout' and call connect_with_timeout.
(send_request): Add arg 'timeout' and pass it to connect_server.
(http_raw_connect): Add arg 'timeout'.
(http_open): Pass TIMEOUT from the session to connect_server.
--

Note that the non-blocking connect we implement is traditional a
pretty non-portable thing due to slighly different semantics.  The
code uses the strategy W. Richard Stevens suggested in 1998.
Hopefully current OS versions got it all right.

The code has not been tested on Windows.

Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
Werner Koch 2017-06-08 08:23:06 +02:00
parent 17e5afd80f
commit 5b9025cfa1
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
3 changed files with 200 additions and 17 deletions

View File

@ -70,6 +70,7 @@
# include <sys/socket.h>
# include <sys/time.h>
# include <time.h>
# include <fcntl.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
@ -153,13 +154,14 @@ static int insert_escapes (char *buffer, const char *string,
static uri_tuple_t parse_tuple (char *string);
static gpg_error_t send_request (http_t hd, const char *httphost,
const char *auth,const char *proxy,
const char *srvtag,strlist_t headers);
const char *srvtag, unsigned int timeout,
strlist_t headers);
static char *build_rel_path (parsed_uri_t uri);
static gpg_error_t parse_response (http_t hd);
static gpg_error_t connect_server (const char *server, unsigned short port,
unsigned int flags, const char *srvtag,
assuan_fd_t *r_sock);
unsigned int timeout, assuan_fd_t *r_sock);
static gpgrt_ssize_t read_server (assuan_fd_t sock, void *buffer, size_t size);
static gpg_error_t write_server (assuan_fd_t sock, const char *data, size_t length);
@ -259,6 +261,9 @@ struct http_session_s
/* A per-session TLS verification callback. */
http_verify_cb_t verify_cb;
void *verify_cb_value;
/* The connect timeout */
unsigned int connect_timeout;
};
@ -695,6 +700,7 @@ http_session_new (http_session_t *r_session,
sess->flags = flags;
sess->verify_cb = verify_cb;
sess->verify_cb_value = verify_cb_value;
sess->connect_timeout = 0;
#if HTTP_USE_NTBTLS
{
@ -867,6 +873,15 @@ http_session_set_log_cb (http_session_t sess,
}
/* Set the TIMEOUT in milliseconds for the connection's connect
* calls. Using 0 disables the timeout. */
void
http_session_set_timeout (http_session_t sess, unsigned int timeout)
{
sess->connect_timeout = timeout;
}
/* Start a HTTP retrieval and on success store at R_HD a context
@ -898,7 +913,9 @@ http_open (http_t *r_hd, http_req_t reqtype, const char *url,
err = parse_uri (&hd->uri, url, 0, !!(flags & HTTP_FLAG_FORCE_TLS));
if (!err)
err = send_request (hd, httphost, auth, proxy, srvtag, headers);
err = send_request (hd, httphost, auth, proxy, srvtag,
hd->session? hd->session->connect_timeout : 0,
headers);
if (err)
{
@ -918,10 +935,10 @@ http_open (http_t *r_hd, http_req_t reqtype, const char *url,
/* This function is useful to connect to a generic TCP service using
this http abstraction layer. This has the advantage of providing
service tags and an estream interface. */
service tags and an estream interface. TIMEOUT is in milliseconds. */
gpg_error_t
http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
unsigned int flags, const char *srvtag)
unsigned int flags, const char *srvtag, unsigned int timeout)
{
gpg_error_t err = 0;
http_t hd;
@ -952,7 +969,7 @@ http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
{
assuan_fd_t sock;
err = connect_server (server, port, hd->flags, srvtag, &sock);
err = connect_server (server, port, hd->flags, srvtag, timeout, &sock);
if (err)
{
xfree (hd);
@ -1635,7 +1652,8 @@ is_hostname_port (const char *string)
*/
static gpg_error_t
send_request (http_t hd, const char *httphost, const char *auth,
const char *proxy, const char *srvtag, strlist_t headers)
const char *proxy, const char *srvtag, unsigned int timeout,
strlist_t headers)
{
gpg_error_t err;
const char *server;
@ -1762,12 +1780,12 @@ send_request (http_t hd, const char *httphost, const char *auth,
err = connect_server (*uri->host ? uri->host : "localhost",
uri->port ? uri->port : 80,
hd->flags, srvtag, &sock);
hd->flags, srvtag, timeout, &sock);
http_release_parsed_uri (uri);
}
else
{
err = connect_server (server, port, hd->flags, srvtag, &sock);
err = connect_server (server, port, hd->flags, srvtag, timeout, &sock);
}
if (err)
@ -2526,13 +2544,162 @@ my_sock_new_for_addr (struct sockaddr_storage *addr, int type, int proto)
}
/* Call WSAGetLastError and map it to a libgpg-error. */
#ifdef HAVE_W32_SYSTEM
static gpg_error_t
my_wsagetlasterror (void)
{
int wsaerr;
gpg_err_code_t ec;
wsaerr = WSAGetLastError ();
switch (wsaerr)
{
case WSAENOTSOCK: ec = GPG_ERR_EINVAL; break;
case WSAEWOULDBLOCK: ec = GPG_ERR_EAGAIN; break;
case ERROR_BROKEN_PIPE: ec = GPG_ERR_EPIPE; break;
case WSANOTINITIALISED: ec = GPG_ERR_ENOSYS; break;
case WSAENOBUFS: ec = GPG_ERR_ENOBUFS; break;
case WSAEMSGSIZE: ec = GPG_ERR_EMSGSIZE; break;
case WSAECONNREFUSED: ec = GPG_ERR_ECONNREFUSED; break;
case WSAEISCONN: ec = GPG_ERR_EISCONN; break;
case WSAEALREADY: ec = GPG_ERR_EALREADY; break;
case WSAETIMEDOUT: ec = GPG_ERR_ETIMEDOUT; break;
default: ec = GPG_ERR_EIO; break;
}
return gpg_err_make (default_errsource, ec);
}
#endif /*HAVE_W32_SYSTEM*/
/* Connect SOCK and return GPG_ERR_ETIMEOUT if a connection could not
* be established within TIMEOUT milliseconds. 0 indicates the
* system's default timeout. The other args are the usual connect
* args. On success 0 is returned, on timeout GPG_ERR_ETIMEDOUT, and
* another error code for other errors. On timeout the caller needs
* to close the socket as soon as possible to stop an ongoing
* handshake.
*
* This implementation is for well-behaving systems; see Stevens,
* Network Programming, 2nd edition, Vol 1, 15.4. */
static gpg_error_t
connect_with_timeout (assuan_fd_t sock,
struct sockaddr *addr, int addrlen,
unsigned int timeout)
{
gpg_error_t err;
int syserr;
socklen_t slen;
fd_set rset, wset;
struct timeval tval;
int n;
#ifndef HAVE_W32_SYSTEM
int oflags;
# define RESTORE_BLOCKING() do { \
fcntl (sock, F_SETFL, oflags); \
} while (0)
#else /*HAVE_W32_SYSTEM*/
# define RESTORE_BLOCKING() do { \
unsigned long along = 0; \
ioctlsocket (FD2INT (sock), FIONBIO, &along); \
} while (0)
#endif /*HAVE_W32_SYSTEM*/
if (!timeout)
{
/* Shortcut. */
if (assuan_sock_connect (sock, addr, addrlen))
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
else
err = 0;
return err;
}
/* Switch the socket into non-blocking mode. */
#ifdef HAVE_W32_SYSTEM
{
unsigned long along = 1;
if (ioctlsocket (FD2INT (sock), FIONBIO, &along))
return my_wsagetlasterror ();
}
#else
oflags = fcntl (sock, F_GETFL, 0);
if (fcntl (sock, F_SETFL, oflags | O_NONBLOCK))
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
#endif
/* Do the connect. */
if (!assuan_sock_connect (sock, addr, addrlen))
{
/* Immediate connect. Restore flags. */
RESTORE_BLOCKING ();
return 0; /* Success. */
}
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
if (gpg_err_code (err) != GPG_ERR_EINPROGRESS)
{
RESTORE_BLOCKING ();
return err;
}
FD_ZERO (&rset);
FD_SET (sock, &rset);
wset = rset;
tval.tv_sec = timeout / 1000;
tval.tv_usec = (timeout % 1000) * 1000;
n = my_select (FD2INT(sock)+1, &rset, &wset, NULL, &tval);
if (n < 0)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
RESTORE_BLOCKING ();
return err;
}
if (!n)
{
/* Timeout: We do not restore the socket flags on timeout
* because the caller is expected to close the socket. */
return gpg_err_make (default_errsource, GPG_ERR_ETIMEDOUT);
}
if (!FD_ISSET (sock, &rset) && !FD_ISSET (sock, &wset))
{
/* select misbehaved. */
return gpg_err_make (default_errsource, GPG_ERR_SYSTEM_BUG);
}
slen = sizeof (syserr);
if (getsockopt (FD2INT(sock), SOL_SOCKET, SO_ERROR,
(void*)&syserr, &slen) < 0)
{
/* Assume that this is Solaris which returns the error in ERRNO. */
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
else if (syserr)
err = gpg_err_make (default_errsource, gpg_err_code_from_errno (syserr));
else
err = 0; /* Connected. */
RESTORE_BLOCKING ();
return err;
#undef RESTORE_BLOCKING
}
/* Actually connect to a server. On success 0 is returned and the
* file descriptor for the socket is stored at R_SOCK; on error an
* error code is returned and ASSUAN_INVALID_FD is stored at
* R_SOCK. */
* error code is returned and ASSUAN_INVALID_FD is stored at R_SOCK.
* TIMEOUT is the connect timeout in milliseconds. Note that the
* function tries to connect to all known addresses and the timeout is
* for each one. */
static gpg_error_t
connect_server (const char *server, unsigned short port,
unsigned int flags, const char *srvtag, assuan_fd_t *r_sock)
unsigned int flags, const char *srvtag, unsigned int timeout,
assuan_fd_t *r_sock)
{
gpg_error_t err;
assuan_fd_t sock = ASSUAN_INVALID_FD;
@ -2645,11 +2812,11 @@ connect_server (const char *server, unsigned short port,
}
anyhostaddr = 1;
if (assuan_sock_connect (sock, (struct sockaddr *)ai->addr,
ai->addrlen))
err = connect_with_timeout (sock, (struct sockaddr *)ai->addr,
ai->addrlen, timeout);
if (err)
{
last_err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
last_err = err;
}
else
{

View File

@ -124,6 +124,7 @@ void http_session_set_log_cb (http_session_t sess,
void (*cb)(http_session_t, gpg_error_t,
const char *,
const void **, size_t *));
void http_session_set_timeout (http_session_t sess, unsigned int timeout);
gpg_error_t http_parse_uri (parsed_uri_t *ret_uri, const char *uri,
@ -133,7 +134,8 @@ void http_release_parsed_uri (parsed_uri_t uri);
gpg_error_t http_raw_connect (http_t *r_hd,
const char *server, unsigned short port,
unsigned int flags, const char *srvtag);
unsigned int flags, const char *srvtag,
unsigned int timeout);
gpg_error_t http_open (http_t *r_hd, http_req_t reqtype,
const char *url,

View File

@ -203,6 +203,7 @@ main (int argc, char **argv)
int no_crl = 0;
const char *cafile = NULL;
http_session_t session = NULL;
unsigned int timeout = 0;
gpgrt_init ();
log_set_prefix (PGM, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID);
@ -224,6 +225,7 @@ main (int argc, char **argv)
" --debug flyswatter\n"
" --tls-debug N use TLS debug level N\n"
" --cacert FNAME expect CA certificate in file FNAME\n"
" --timeout MS timeout for connect in MS\n"
" --no-verify do not verify the certificate\n"
" --force-tls use HTTP_FLAG_FORCE_TLS\n"
" --force-tor use HTTP_FLAG_FORCE_TOR\n"
@ -261,6 +263,15 @@ main (int argc, char **argv)
argc--; argv++;
}
}
else if (!strcmp (*argv, "--timeout"))
{
argc--; argv++;
if (argc)
{
timeout = strtoul (*argv, NULL, 10);
argc--; argv++;
}
}
else if (!strcmp (*argv, "--no-verify"))
{
no_verify = 1;
@ -407,6 +418,9 @@ main (int argc, char **argv)
http_release_parsed_uri (uri);
uri = NULL;
if (session)
http_session_set_timeout (session, timeout);
rc = http_open_document (&hd, *argv, NULL, my_http_flags,
NULL, session, NULL, NULL);
if (rc)