diff --git a/src/connect.c b/src/connect.c index da84145e..f4f31933 100644 --- a/src/connect.c +++ b/src/connect.c @@ -678,8 +678,8 @@ retryable_socket_connect_error (int err) should be taken as such (for example, it doesn't implement Wget's 0-timeout-means-no-timeout semantics.) */ -int -select_fd (int fd, double maxtime, int wait_for) +static int +select_fd_internal (int fd, double maxtime, int wait_for, bool convert_back _GL_UNUSED) { fd_set fdset; fd_set *rd = NULL, *wr = NULL; @@ -710,7 +710,8 @@ select_fd (int fd, double maxtime, int wait_for) #ifdef WINDOWS /* gnulib select() converts blocking sockets to nonblocking in windows. wget uses blocking sockets so we must convert them back to blocking. */ - set_windows_fd_as_blocking_socket (fd); + if (convert_back) + set_windows_fd_as_blocking_socket (fd); #endif } while (result < 0 && errno == EINTR); @@ -718,6 +719,20 @@ select_fd (int fd, double maxtime, int wait_for) return result; } +int +select_fd (int fd, double maxtime, int wait_for) +{ + return select_fd_internal (fd, maxtime, wait_for, true); +} + +#ifdef WINDOWS +int +select_fd_nb (int fd, double maxtime, int wait_for) +{ + return select_fd_internal (fd, maxtime, wait_for, false); +} +#endif + /* Return true if the connection to the remote site established through SOCK is still open. @@ -925,10 +940,11 @@ fd_read (int fd, char *buf, int bufsize, double timeout) { struct transport_info *info; LAZY_RETRIEVE_INFO (info); + if (!poll_internal (fd, info, WAIT_FOR_READ, timeout)) return -1; if (info && info->imp->reader) - return info->imp->reader (fd, buf, bufsize, info->ctx); + return info->imp->reader (fd, buf, bufsize, info->ctx, timeout); else return sock_read (fd, buf, bufsize); } @@ -953,7 +969,7 @@ fd_peek (int fd, char *buf, int bufsize, double timeout) if (!poll_internal (fd, info, WAIT_FOR_READ, timeout)) return -1; if (info && info->imp->peeker) - return info->imp->peeker (fd, buf, bufsize, info->ctx); + return info->imp->peeker (fd, buf, bufsize, info->ctx, timeout); else return sock_peek (fd, buf, bufsize); } @@ -1005,6 +1021,7 @@ fd_errstr (int fd) /* Don't bother with LAZY_RETRIEVE_INFO, as this will only be called in case of error, never in a tight loop. */ struct transport_info *info = NULL; + if (transport_map) info = hash_table_get (transport_map, (void *)(intptr_t) fd); diff --git a/src/connect.h b/src/connect.h index fcdbd604..84535591 100644 --- a/src/connect.h +++ b/src/connect.h @@ -63,10 +63,10 @@ int select_fd (int, double, int); bool test_socket_open (int); struct transport_implementation { - int (*reader) (int, char *, int, void *); + int (*reader) (int, char *, int, void *, double); int (*writer) (int, char *, int, void *); int (*poller) (int, double, int, void *); - int (*peeker) (int, char *, int, void *); + int (*peeker) (int, char *, int, void *, double); const char *(*errstr) (int, void *); void (*closer) (int, void *); }; @@ -80,4 +80,10 @@ const char *fd_errstr (int); void fd_close (int); void connect_cleanup (void); +#ifdef WINDOWS +int select_fd_nb (int, double, int); +#else +#define select_fd_nb select_fd +#endif + #endif /* CONNECT_H */ diff --git a/src/gnutls.c b/src/gnutls.c index 7ab1f088..d3652dba 100644 --- a/src/gnutls.c +++ b/src/gnutls.c @@ -59,11 +59,11 @@ as that of the covered work. */ #include "host.h" static int -_do_handshake (gnutls_session_t session, int fd, double timeout); +_do_handshake (gnutls_session_t session, int fd, double timeout, int is_nonblock); #if GNUTLS_VERSION_NUMBER >= 0x030604 static int -_do_reauth (gnutls_session_t session, int fd, double timeout); +_do_reauth (gnutls_session_t session, int fd, double timeout, int is_nonblock); #endif static int @@ -259,7 +259,12 @@ wgnutls_read_timeout (int fd, char *buf, int bufsize, void *arg, double timeout) struct ptimer *timer = NULL; struct wgnutls_transport_context *ctx = arg; int timed_out = 0; + double next_timeout; + errno = 0; + if (timeout == -1) + timeout = opt.read_timeout; + next_timeout = timeout; if (timeout) { #ifdef F_GETFL @@ -277,36 +282,41 @@ wgnutls_read_timeout (int fd, char *buf, int bufsize, void *arg, double timeout) timer = ptimer_new (); if (timer == NULL) - return -1; + { + ret = -1; + goto timer_err; + } } do { - double next_timeout = 0; + ret = GNUTLS_E_AGAIN; if (timeout) { next_timeout = timeout - ptimer_measure (timer); - if (next_timeout < 0) - break; + if (next_timeout <= 0) + { + timed_out = 1; + break; + } } - - ret = GNUTLS_E_AGAIN; + /* (rehandshake, reauth) needs some workaround for interactive timeout */ if (timeout == 0 || gnutls_record_check_pending (ctx->session) - || select_fd (fd, next_timeout, WAIT_FOR_READ)) + || select_fd_nb (fd, next_timeout, WAIT_FOR_READ)) { ret = gnutls_record_recv (ctx->session, buf, bufsize); timed_out = timeout && ptimer_measure (timer) >= timeout; if (!timed_out && ret == GNUTLS_E_REHANDSHAKE) { DEBUGP (("GnuTLS: *** REHANDSHAKE while reading\n")); - if ((ret = _do_handshake (ctx->session, fd, timeout)) == 0) + if ((ret = _do_handshake (ctx->session, fd, opt.read_timeout, 1)) == 0) ret = GNUTLS_E_AGAIN; /* restart reading */ } #if GNUTLS_VERSION_NUMBER >= 0x030604 if (!timed_out && ret == GNUTLS_E_REAUTH_REQUEST) { DEBUGP (("GnuTLS: *** re-authentication while reading\n")); - if ((ret = _do_reauth (ctx->session, fd, timeout)) == 0) + if ((ret = _do_reauth (ctx->session, fd, opt.read_timeout, 1)) == 0) ret = GNUTLS_E_AGAIN; /* restart reading */ } #endif @@ -317,7 +327,7 @@ wgnutls_read_timeout (int fd, char *buf, int bufsize, void *arg, double timeout) if (timeout) { ptimer_destroy (timer); - +timer_err: ; #ifdef F_GETFL if (fcntl (fd, F_SETFL, flags) < 0) return -1; @@ -326,7 +336,6 @@ wgnutls_read_timeout (int fd, char *buf, int bufsize, void *arg, double timeout) if (ioctl (fd, FIONBIO, &zero) < 0) return -1; #endif - if (timed_out && ret == GNUTLS_E_AGAIN) errno = ETIMEDOUT; } @@ -335,7 +344,7 @@ wgnutls_read_timeout (int fd, char *buf, int bufsize, void *arg, double timeout) } static int -wgnutls_read (int fd, char *buf, int bufsize, void *arg) +wgnutls_read (int fd, char *buf, int bufsize, void *arg, double timeout) { int ret = 0; struct wgnutls_transport_context *ctx = arg; @@ -352,7 +361,7 @@ wgnutls_read (int fd, char *buf, int bufsize, void *arg) return copysize; } - ret = wgnutls_read_timeout (fd, buf, bufsize, arg, opt.read_timeout); + ret = wgnutls_read_timeout (fd, buf, bufsize, arg, timeout); if (ret < 0) ctx->last_error = ret; @@ -385,7 +394,7 @@ wgnutls_poll (int fd, double timeout, int wait_for, void *arg) } static int -wgnutls_peek (int fd, char *buf, int bufsize, void *arg) +wgnutls_peek (int fd, char *buf, int bufsize, void *arg, double timeout) { int read = 0; struct wgnutls_transport_context *ctx = arg; @@ -402,12 +411,12 @@ wgnutls_peek (int fd, char *buf, int bufsize, void *arg) if (bufsize > offset) { - if (opt.read_timeout && gnutls_record_check_pending (ctx->session) == 0 + if (timeout && gnutls_record_check_pending (ctx->session) == 0 && select_fd (fd, 0.0, WAIT_FOR_READ) <= 0) read = 0; else read = wgnutls_read_timeout (fd, buf + offset, bufsize - offset, - ctx, opt.read_timeout); + ctx, timeout); if (read < 0) { if (offset) @@ -431,7 +440,8 @@ static const char * wgnutls_errstr (int fd _GL_UNUSED, void *arg) { struct wgnutls_transport_context *ctx = arg; - return gnutls_strerror (ctx->last_error); + return (ctx->last_error == GNUTLS_E_AGAIN && errno == ETIMEDOUT ? + strerror (ETIMEDOUT) : gnutls_strerror (ctx->last_error)); } static void @@ -459,14 +469,14 @@ static struct transport_implementation wgnutls_transport = }; static int -_do_handshake (gnutls_session_t session, int fd, double timeout) +_do_handshake (gnutls_session_t session, int fd, double timeout, int is_nonblock) { #ifdef F_GETFL int flags = 0; #endif int err; - if (timeout) + if (!is_nonblock && timeout) { #ifdef F_GETFL flags = fcntl (fd, F_GETFL, 0); @@ -492,12 +502,12 @@ _do_handshake (gnutls_session_t session, int fd, double timeout) if (gnutls_record_get_direction (session)) { /* wait for writeability */ - err = select_fd (fd, timeout, WAIT_FOR_WRITE); + err = select_fd_nb (fd, timeout, WAIT_FOR_WRITE); } else { /* wait for readability */ - err = select_fd (fd, timeout, WAIT_FOR_READ); + err = select_fd_nb (fd, timeout, WAIT_FOR_READ); } if (err <= 0) @@ -527,7 +537,7 @@ _do_handshake (gnutls_session_t session, int fd, double timeout) } while (err && gnutls_error_is_fatal (err) == 0); - if (timeout) + if (!is_nonblock && timeout) { #ifdef F_GETFL if (fcntl (fd, F_SETFL, flags) < 0) @@ -544,14 +554,14 @@ _do_handshake (gnutls_session_t session, int fd, double timeout) #if GNUTLS_VERSION_NUMBER >= 0x030604 static int -_do_reauth (gnutls_session_t session, int fd, double timeout) +_do_reauth (gnutls_session_t session, int fd, double timeout, int is_nonblock) { #ifdef F_GETFL int flags = 0; #endif int err; - if (timeout) + if (!is_nonblock && timeout) { #ifdef F_GETFL flags = fcntl (fd, F_GETFL, 0); @@ -577,12 +587,12 @@ _do_reauth (gnutls_session_t session, int fd, double timeout) if (gnutls_record_get_direction (session)) { /* wait for writeability */ - err = select_fd (fd, timeout, WAIT_FOR_WRITE); + err = select_fd_nb (fd, timeout, WAIT_FOR_WRITE); } else { /* wait for readability */ - err = select_fd (fd, timeout, WAIT_FOR_READ); + err = select_fd_nb (fd, timeout, WAIT_FOR_READ); } if (err <= 0) @@ -604,7 +614,7 @@ _do_reauth (gnutls_session_t session, int fd, double timeout) } while (err && gnutls_error_is_fatal (err) == 0); - if (timeout) + if (!is_nonblock && timeout) { #ifdef F_GETFL if (fcntl (fd, F_SETFL, flags) < 0) @@ -830,7 +840,7 @@ ssl_connect_wget (int fd, const char *hostname, int *continue_session) } } - err = _do_handshake (session, fd, opt.connect_timeout); + err = _do_handshake (session, fd, opt.read_timeout, 0); if (err < 0) { diff --git a/src/init.c b/src/init.c index 52db8952..6f4db4af 100644 --- a/src/init.c +++ b/src/init.c @@ -1423,6 +1423,13 @@ cmd_time (const char *com, const char *val, void *place) if (!simple_atof (val, end, &number)) goto err; + if (number < 0) + { + fprintf (stderr, _("%s: %s: Negative time period %s\n"), + exec_name, com, quote (val)); + return false; + } + *(double *)place = number * mult; return true; } diff --git a/src/openssl.c b/src/openssl.c index 4905cc84..0f578446 100644 --- a/src/openssl.c +++ b/src/openssl.c @@ -49,11 +49,16 @@ as that of the covered work. */ #endif #endif +#include <sys/ioctl.h> + #include "utils.h" #include "connect.h" +#include "ptimer.h" #include "url.h" #include "ssl.h" +#include <fcntl.h> + #ifdef WINDOWS # include <w32sock.h> #endif @@ -80,12 +85,20 @@ init_prng (void) /* Get the random file name using RAND_file_name. */ namebuf[0] = '\0'; random_file = RAND_file_name (namebuf, sizeof (namebuf)); + if (!file_exists_p (random_file, NULL)) + random_file = NULL; } if (random_file && *random_file) /* Seed at most 16k (apparently arbitrary value borrowed from curl) from random file. */ - RAND_load_file (random_file, 16384); + { + int _err = RAND_load_file (random_file, 16384); + if(_err == -1) + /* later the thread error queue will be cleared */ + if ( (_err = ERR_peek_last_error ()) ) + logprintf (LOG_VERBOSE, "WARNING: Could not load random file: %s, %s\n", opt.random_file, ERR_reason_error_string(_err)); + } #ifdef HAVE_RAND_EGD /* Get random data from EGD if opt.egd_file was used. */ @@ -427,44 +440,236 @@ struct openssl_transport_context char *last_error; /* last error printed with openssl_errstr */ }; +typedef int (*ssl_fn_t)(SSL *, void *, int); + +#ifdef OPENSSL_RUN_WITHTIMEOUT + +struct scwt_context +{ + SSL *ssl; + int result; +}; + +static void +ssl_connect_with_timeout_callback(void *arg) +{ + struct scwt_context *ctx = (struct scwt_context *)arg; + ctx->result = SSL_connect(ctx->ssl); +} + +static int +ssl_connect_with_timeout(int fd _GL_UNUSED, SSL *conn, double timeout) +{ + struct scwt_context scwt_ctx; + scwt_ctx.ssl = conn; + errno = 0; + if (run_with_timeout(timeout, ssl_connect_with_timeout_callback, + &scwt_ctx)) + { + errno = ETIMEDOUT; + return -1; + } + return scwt_ctx.result; +} + struct openssl_read_args { int fd; struct openssl_transport_context *ctx; + ssl_fn_t fn; char *buf; int bufsize; int retval; }; -static void openssl_read_callback(void *arg) +static void openssl_read_peek_callback(void *arg) { struct openssl_read_args *args = (struct openssl_read_args *) arg; struct openssl_transport_context *ctx = args->ctx; + ssl_fn_t fn = args->fn; SSL *conn = ctx->conn; char *buf = args->buf; int bufsize = args->bufsize; int ret; do - ret = SSL_read (conn, buf, bufsize); - while (ret == -1 && SSL_get_error (conn, ret) == SSL_ERROR_SYSCALL - && errno == EINTR); + { + ret = fn (conn, buf, bufsize); + } + while (ret == -1 && SSL_get_error (conn, ret) == SSL_ERROR_SYSCALL && errno == EINTR); args->retval = ret; } static int -openssl_read (int fd, char *buf, int bufsize, void *arg) +openssl_read_peek (int fd, char *buf, int bufsize, void *arg, double timeout, ssl_fn_t fn) { - struct openssl_read_args args; - args.fd = fd; - args.buf = buf; - args.bufsize = bufsize; - args.ctx = (struct openssl_transport_context*) arg; + struct openssl_transport_context *ctx = arg; + int ret = SSL_pending (ctx->conn); - if (run_with_timeout(opt.read_timeout, openssl_read_callback, &args)) { - return -1; + if (bufsize == 0) + return 0; + + if (ret) + ret = fn (ctx->conn, buf, MIN (bufsize, ret)); + else + { + struct openssl_read_args args; + args.fd = fd; + args.buf = buf; + args.bufsize = bufsize; + args.fn = fn; + args.ctx = ctx; + + errno = 0; + if (timeout == -1) + timeout = opt.read_timeout; + + if (run_with_timeout(timeout, openssl_read_peek_callback, &args)) + { + errno = ETIMEDOUT; + ret = -1; + } + else + ret = args.retval; + } + return ret; +} + +#else /* OPENSSL_RUN_WITHTIMEOUT */ + +#ifdef F_GETFL +#define NONBLOCK_DECL int flags = 0; +#define FD_SET_NONBLOCKED(_fd) \ + flags = fcntl (_fd, F_GETFL, 0);\ + if (flags < 0)\ + return flags;\ + if (fcntl (_fd, F_SETFL, flags | O_NONBLOCK))\ + return -1; +#define FD_SET_BLOCKED(_fd) \ + if (fcntl (_fd, F_SETFL, flags) < 0)\ + return -1; +#else +#define NONBLOCK_DECL +#define FD_SET_NONBLOCKED(_fd) \ + {\ + const int one = 1;\ + if (ioctl (_fd, FIONBIO, &one) < 0)\ + return -1;\ } - return args.retval; +#define FD_SET_BLOCKED(_fd) \ + {\ + const int zero = 0;\ + if (ioctl (_fd, FIONBIO, &zero) < 0)\ + return -1;\ + } +#endif /* F_GETFL */ + +#define TIMER_INIT(_fd, _ret, _timeout) \ + { \ + NONBLOCK_DECL \ + int timed_out = 0; \ + FD_SET_NONBLOCKED(_fd) \ + struct ptimer *timer = ptimer_new (); \ + if (timer == NULL) \ + _ret = -1; \ + else \ + { \ + if (_timeout == -1) \ + _timeout = opt.read_timeout; + +#define TIMER_FREE(_fd) \ + ptimer_destroy (timer); \ + } \ + FD_SET_BLOCKED(_fd) \ + if (timed_out) \ + { \ + errno = ETIMEDOUT; \ + } \ + } + +#define TIMER_WAIT(_fd, _conn, _ret, _timeout) \ + { \ + int wait_for; \ + double next_timeout; \ + int err = SSL_get_error(_conn, _ret); \ + if (err == SSL_ERROR_WANT_READ) \ + wait_for = WAIT_FOR_READ; \ + else if (err == SSL_ERROR_WANT_WRITE) \ + wait_for = WAIT_FOR_WRITE; \ + else \ + break; \ + next_timeout = _timeout - ptimer_measure (timer); \ + if (next_timeout < 0) \ + { \ + timed_out = 1; \ + break; \ + } \ + err = select_fd_nb (_fd, next_timeout, wait_for); \ + if (err <= 0) \ + { \ + if (err == 0) \ + timed_out = 1; \ + _ret = -1; \ + break; \ + } \ + } + +static int +ssl_connect_with_timeout(int fd, SSL *conn, double timeout) +{ + int ret; + + errno = 0; + if (timeout == 0) + ret = SSL_connect(conn); + else + { + TIMER_INIT(fd, ret, timeout) + ERR_clear_error(); + while( (ret = SSL_connect(conn)) < 0 ) + TIMER_WAIT(fd, conn, ret, timeout) + TIMER_FREE(fd) + } + + return ret; +} + +static int +openssl_read_peek (int fd, char *buf, int bufsize, void *arg, double timeout, ssl_fn_t fn) +{ + struct openssl_transport_context *ctx = arg; + int ret; + + if (bufsize == 0) + return 0; + /* avoid wrong 'interactive timeout' when errno == ETIMEDOUT */ + errno = 0; + ret = SSL_pending (ctx->conn); + /* If we have data available for immediate read, simply return that, + or do blocked read when timeout == 0 */ + if (ret || timeout == 0) + do + { + ret = fn (ctx->conn, buf, (ret ? MIN (bufsize, ret) : bufsize)); + } + while (ret == -1 && SSL_get_error (ctx->conn, ret) == SSL_ERROR_SYSCALL && errno == EINTR); + else + { + TIMER_INIT(fd, ret, timeout) + while( (ret = fn (ctx->conn, buf, bufsize)) <= 0 ) + TIMER_WAIT(fd, ctx->conn, ret, timeout) + TIMER_FREE(fd) + } + + return ret; +} + +#endif /* OPENSSL_RUN_WITHTIMEOUT */ + +static int +openssl_read (int fd, char *buf, int bufsize, void *arg, double timeout) +{ + return openssl_read_peek (fd, buf, bufsize, arg, timeout, SSL_read); } static int @@ -475,9 +680,7 @@ openssl_write (int fd _GL_UNUSED, char *buf, int bufsize, void *arg) SSL *conn = ctx->conn; do ret = SSL_write (conn, buf, bufsize); - while (ret == -1 - && SSL_get_error (conn, ret) == SSL_ERROR_SYSCALL - && errno == EINTR); + while (ret == -1 && SSL_get_error (conn, ret) == SSL_ERROR_SYSCALL && errno == EINTR); return ret; } @@ -488,25 +691,17 @@ openssl_poll (int fd, double timeout, int wait_for, void *arg) SSL *conn = ctx->conn; if (SSL_pending (conn)) return 1; - if (timeout == 0) - return 1; + /* if (timeout == 0) + return 1; */ + if (timeout == -1) + timeout = opt.read_timeout; return select_fd (fd, timeout, wait_for); } static int -openssl_peek (int fd, char *buf, int bufsize, void *arg) +openssl_peek (int fd, char *buf, int bufsize, void *arg, double timeout) { - int ret; - struct openssl_transport_context *ctx = arg; - SSL *conn = ctx->conn; - if (! openssl_poll (fd, 0.0, WAIT_FOR_READ, arg)) - return 0; - do - ret = SSL_peek (conn, buf, bufsize); - while (ret == -1 - && SSL_get_error (conn, ret) == SSL_ERROR_SYSCALL - && errno == EINTR); - return ret; + return openssl_read_peek (fd, buf, bufsize, arg, timeout, SSL_peek); } static const char * @@ -582,19 +777,6 @@ static struct transport_implementation openssl_transport = { openssl_peek, openssl_errstr, openssl_close }; -struct scwt_context -{ - SSL *ssl; - int result; -}; - -static void -ssl_connect_with_timeout_callback(void *arg) -{ - struct scwt_context *ctx = (struct scwt_context *)arg; - ctx->result = SSL_connect(ctx->ssl); -} - static const char * _sni_hostname(const char *hostname) { @@ -623,7 +805,6 @@ bool ssl_connect_wget (int fd, const char *hostname, int *continue_session) { SSL *conn; - struct scwt_context scwt_ctx; struct openssl_transport_context *ctx; DEBUGP (("Initiating SSL handshake.\n")); @@ -674,14 +855,9 @@ ssl_connect_wget (int fd, const char *hostname, int *continue_session) goto error; } - scwt_ctx.ssl = conn; - if (run_with_timeout(opt.read_timeout, ssl_connect_with_timeout_callback, - &scwt_ctx)) { - DEBUGP (("SSL handshake timed out.\n")); - goto timeout; - } - if (scwt_ctx.result <= 0 || !SSL_is_init_finished(conn)) - goto error; + if (ssl_connect_with_timeout(fd, conn, opt.read_timeout) <= 0 + || !SSL_is_init_finished(conn)) + goto timedout; ctx = xnew0 (struct openssl_transport_context); ctx->conn = conn; @@ -694,12 +870,17 @@ ssl_connect_wget (int fd, const char *hostname, int *continue_session) fd_register_transport (fd, &openssl_transport, ctx); DEBUGP (("Handshake successful; connected socket %d to SSL handle 0x%0*lx\n", fd, PTR_FORMAT (conn))); + + ERR_clear_error (); return true; + timedout: + if (errno == ETIMEDOUT) + DEBUGP (("SSL handshake timed out.\n")); + else error: - DEBUGP (("SSL handshake failed.\n")); + DEBUGP (("SSL handshake failed.\n")); print_errors (); - timeout: if (conn) SSL_free (conn); return false;