diff --git a/ChangeLog b/ChangeLog index 0cfc11d3..d34bd9f4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2005-07-05 Hrvoje Niksic + + * configure.in: Add check for GnuTLS if --with-ssl=gnutls is used. + 2005-07-03 Hrvoje Niksic * po/POTFILES.in: Include src/ptimer.c. diff --git a/configure.in b/configure.in index 68107e04..ba3d28da 100644 --- a/configure.in +++ b/configure.in @@ -239,9 +239,20 @@ dnl dnl Checks for libraries. dnl -dnl Check for OpenSSL -if test x"$with_ssl" != x"no" +if test x"$with_ssl" = xgnutls then + dnl Now actually check for -lssl + AC_LIB_HAVE_LINKFLAGS([gnutls], [], [ +#include + ], [gnutls_global_init()]) + if test x"$LIBGNUTLS" != x + then + AC_MSG_NOTICE([compiling in support for SSL via GnuTLS]) + AC_DEFINE([HAVE_GNUTLS], 1, + [Define if support for the GnuTLS library is being compiled in.]) + SSL_OBJ='gnutls.o' + fi +else dnl As of this writing (OpenSSL 0.9.6), the libcrypto shared library dnl doesn't record its dependency on libdl, so we need to make sure dnl -ldl ends up in LIBS on systems that have it. Most OSes use @@ -262,12 +273,13 @@ then ], [SSL_library_init ()]) if test x"$LIBSSL" != x then - AC_MSG_NOTICE([compiling in support for SSL]) - AC_DEFINE([HAVE_SSL], 1, - [Define if SSL support is being compiled in.]) + AC_MSG_NOTICE([compiling in support for SSL via OpenSSL]) + AC_DEFINE([HAVE_OPENSSL], 1, + [Define if support for the OpenSSL library is being compiled in.]) SSL_OBJ='openssl.o' fi fi + AC_SUBST(SSL_OBJ) dnl Enable NTLM if requested and if SSL is available. diff --git a/src/ChangeLog b/src/ChangeLog index fb93734f..644559f2 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,10 @@ +2005-07-05 Hrvoje Niksic + + * wget.h (or): Define HAVE_SSL when either HAVE_OPENSSL or + HAVE_GNUTLS are defined. + + * gnutls.c: New file. + 2005-07-05 Hrvoje Niksic * http.c (gethttp): Don't print the request write error message diff --git a/src/Makefile.in b/src/Makefile.in index 7b793e12..5769b114 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -51,7 +51,7 @@ CPPFLAGS = @CPPFLAGS@ DEFS = @DEFS@ -DSYSTEM_WGETRC=\"$(sysconfdir)/wgetrc\" -DLOCALEDIR=\"$(localedir)\" CFLAGS = @CFLAGS@ LDFLAGS = @LDFLAGS@ -LIBS = @LIBS@ @LIBSSL@ +LIBS = @LIBS@ @LIBSSL@ @LIBGNUTLS@ exeext = @exeext@ INCLUDES = -I. -I$(srcdir) diff --git a/src/gnutls.c b/src/gnutls.c new file mode 100644 index 00000000..5cf8bd8d --- /dev/null +++ b/src/gnutls.c @@ -0,0 +1,254 @@ +/* SSL support via GnuTLS library. + Copyright (C) 2005 Free Software Foundation, Inc. + +This file is part of GNU Wget. + +GNU Wget is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GNU Wget is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Wget; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + +In addition, as a special exception, the Free Software Foundation +gives permission to link the code of its release of Wget with the +OpenSSL project's "OpenSSL" library (or with modified versions of it +that use the same license as the "OpenSSL" library), and distribute +the linked executables. You must obey the GNU General Public License +in all respects for all of the code used other than "OpenSSL". If you +modify this file, you may extend this exception to your version of the +file, but you are not obligated to do so. If you do not wish to do +so, delete this exception statement from your version. */ + +#include + +#include +#include +#ifdef HAVE_UNISTD_H +# include +#endif +#include +#include + +#include + +#include "wget.h" +#include "utils.h" +#include "connect.h" +#include "url.h" +#include "ssl.h" + +/* Note: some of the functions private to this file have names that + begin with "wgnutls_" (e.g. wgnutls_read) so that they wouldn't be + confused with actual gnutls functions -- such as the gnutls_read + preprocessor macro. */ + +static gnutls_certificate_credentials credentials; + +bool +ssl_init () +{ + gnutls_global_init (); + gnutls_certificate_allocate_credentials (&credentials); + if (opt.ca_cert) + gnutls_certificate_set_x509_trust_file (credentials, opt.ca_cert, + GNUTLS_X509_FMT_PEM); + return true; +} + +struct wgnutls_transport_context { + gnutls_session session; /* GnuTLS session handle */ + int last_error; /* last error returned by read/write/... */ + + /* Since GnuTLS doesn't support the equivalent to recv(..., + MSG_PEEK) or SSL_peek(), we have to do it ourselves. Peeked data + is stored to PEEKBUF, and wgnutls_read checks that buffer before + actually reading. */ + char peekbuf[512]; + int peekstart, peeklen; +}; + +#ifndef MIN +# define MIN(i, j) ((i) <= (j) ? (i) : (j)) +#endif + +static int +wgnutls_read (int fd, char *buf, int bufsize, void *arg) +{ + int ret; + struct wgnutls_transport_context *ctx = arg; + + if (ctx->peeklen) + { + /* If we have any peek data, simply return that. */ + int copysize = MIN (bufsize, ctx->peeklen); + memcpy (buf, ctx->peekbuf + ctx->peekstart, copysize); + ctx->peeklen -= copysize; + if (ctx->peeklen != 0) + ctx->peekstart += copysize; + else + ctx->peekstart = 0; + return copysize; + } + + do + ret = gnutls_record_recv (ctx->session, buf, bufsize); + while (ret == GNUTLS_E_INTERRUPTED); + + if (ret < 0) + ctx->last_error = ret; + return ret; +} + +static int +wgnutls_write (int fd, char *buf, int bufsize, void *arg) +{ + int ret; + struct wgnutls_transport_context *ctx = arg; + do + ret = gnutls_record_send (ctx->session, buf, bufsize); + while (ret == GNUTLS_E_INTERRUPTED); + if (ret < 0) + ctx->last_error = ret; + return ret; +} + +static int +wgnutls_poll (int fd, double timeout, int wait_for, void *arg) +{ + return 1; +} + +static int +wgnutls_peek (int fd, char *buf, int bufsize, void *arg) +{ + int ret; + struct wgnutls_transport_context *ctx = arg; + + /* We don't support peeks following peeks: the reader must drain all + peeked data before the next peek. */ + assert (ctx->peeklen == 0); + if (bufsize > sizeof ctx->peekbuf) + bufsize = sizeof ctx->peekbuf; + + do + ret = gnutls_record_recv (ctx->session, buf, bufsize); + while (ret == GNUTLS_E_INTERRUPTED); + + if (ret >= 0) + { + memcpy (ctx->peekbuf, buf, ret); + ctx->peeklen = ret; + } + return ret; +} + +static const char * +wgnutls_errstr (int fd, void *arg) +{ + struct wgnutls_transport_context *ctx = arg; + return gnutls_strerror (ctx->last_error); +} + +static void +wgnutls_close (int fd, void *arg) +{ + struct wgnutls_transport_context *ctx = arg; + /*gnutls_bye (ctx->session, GNUTLS_SHUT_RDWR);*/ + gnutls_deinit (ctx->session); + xfree (ctx); +#ifndef WINDOWS + close (fd); +#else + closesocket (fd); +#endif +} + +/* gnutls_transport is the singleton that describes the SSL transport + methods provided by this file. */ + +static struct transport_implementation wgnutls_transport = { + wgnutls_read, wgnutls_write, wgnutls_poll, + wgnutls_peek, wgnutls_errstr, wgnutls_close +}; + +bool +ssl_connect (int fd) +{ + static const int cert_type_priority[] = { + GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0 + }; + struct wgnutls_transport_context *ctx; + gnutls_session session; + int err; + gnutls_init (&session, GNUTLS_CLIENT); + gnutls_set_default_priority (session); + gnutls_certificate_type_set_priority (session, cert_type_priority); + gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, credentials); + gnutls_transport_set_ptr (session, (gnutls_transport_ptr) fd); + err = gnutls_handshake (session); + if (err < 0) + { + logprintf (LOG_NOTQUIET, "GnuTLS: %s\n", gnutls_strerror (err)); + gnutls_deinit (session); + return false; + } + ctx = xnew0 (struct wgnutls_transport_context); + ctx->session = session; + fd_register_transport (fd, &wgnutls_transport, ctx); + return true; +} + +bool +ssl_check_certificate (int fd, const char *host) +{ + struct wgnutls_transport_context *ctx = fd_transport_context (fd); + + unsigned int status; + const gnutls_datum *cert_list; + int cert_list_size, ret; + gnutls_x509_crt cert; + + /* If the user has specified --no-check-cert, we still want to warn + him about problems with the server's certificate. */ + const char *severity = opt.check_cert ? _("ERROR") : _("WARNING"); + bool success = true; + + ret = gnutls_certificate_verify_peers2 (ctx->session, &status); + if (ret < 0) + { + logprintf (LOG_NOTQUIET, _("%s: No certificate presented by %s.\n"), + severity, escnonprint (host)); + success = false; + goto no_cert; + } + + if (status & GNUTLS_CERT_INVALID) + { + logprintf (LOG_NOTQUIET, _("%s: The certificate of `%s' is not trusted.\n"), + severity, escnonprint (host)); + success = false; + } + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) + { + logprintf (LOG_NOTQUIET, _("%s: The certificate of `%s' hasn't got a known issuer.\n"), + severity, escnonprint (host)); + success = false; + } + if (status & GNUTLS_CERT_REVOKED) + { + logprintf (LOG_NOTQUIET, _("%s: The certificate of `%s' has been revoked.\n"), + severity, escnonprint (host)); + success = false; + } + + no_cert: + return opt.check_cert ? success : true; +} diff --git a/src/wget.h b/src/wget.h index 7ab17b7c..8c76282c 100644 --- a/src/wget.h +++ b/src/wget.h @@ -40,6 +40,10 @@ so, delete this exception statement from your version. */ # define NDEBUG #endif +#if defined HAVE_OPENSSL || defined HAVE_GNUTLS +# define HAVE_SSL +#endif + /* `gettext (FOO)' is long to write, so we use `_(FOO)'. If NLS is unavailable, _(STRING) simply returns STRING. */ #ifdef HAVE_NLS