]> sjero.net Git - wget/blobdiff - src/openssl.c
[svn] Separate SSL error messages with "; ".
[wget] / src / openssl.c
index e72ea28af0e94d8fbb200b1e0b4dc30db98e478e..f617f79b4eb51ecbedf18cb53d70c4fddf1bfbd9 100644 (file)
@@ -15,8 +15,8 @@ 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+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
@@ -35,11 +35,7 @@ so, delete this exception statement from your version.  */
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif
-#ifdef HAVE_STRING_H
-# include <string.h>
-#else
-# include <strings.h>
-#endif
+#include <string.h>
 
 #include <openssl/ssl.h>
 #include <openssl/x509.h>
@@ -52,13 +48,9 @@ so, delete this exception statement from your version.  */
 #include "url.h"
 #include "ssl.h"
 
-#ifndef errno
-extern int errno;
-#endif
-
 /* Application-wide SSL context.  This is common to all SSL
    connections.  */
-SSL_CTX *ssl_ctx;
+static SSL_CTX *ssl_ctx;
 
 /* Initialize the SSL's PRNG using various methods. */
 
@@ -134,9 +126,9 @@ init_prng (void)
 static void
 print_errors (void) 
 {
-  unsigned long curerr = 0;
-  while ((curerr = ERR_get_error ()) != 0)
-    logprintf (LOG_NOTQUIET, "OpenSSL: %s\n", ERR_error_string (curerr, NULL));
+  unsigned long err;
+  while ((err = ERR_get_error ()) != 0)
+    logprintf (LOG_NOTQUIET, "OpenSSL: %s\n", ERR_error_string (err, NULL));
 }
 
 /* Convert keyfile type as used by options.h to a type as accepted by
@@ -162,16 +154,16 @@ key_type_to_ssl_type (enum keyfile_type type)
 /* Create an SSL Context and set default paths etc.  Called the first
    time an HTTP download is attempted.
 
-   Returns 1 on success, 0 otherwise.  */
+   Returns true on success, false otherwise.  */
 
-int
+bool
 ssl_init ()
 {
   SSL_METHOD *meth;
 
   if (ssl_ctx)
     /* The SSL has already been initialized. */
-    return 1;
+    return true;
 
   /* Init the PRNG.  If that fails, bail out.  */
   init_prng ();
@@ -233,20 +225,20 @@ ssl_init ()
      handles them correctly), allow them in OpenSSL.  */
   SSL_CTX_set_mode (ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
 
-  return 1;
+  return true;
 
  error:
   if (ssl_ctx)
     SSL_CTX_free (ssl_ctx);
   print_errors ();
-  return 0;
+  return false;
 }
 
 static int
 openssl_read (int fd, char *buf, int bufsize, void *ctx)
 {
   int ret;
-  SSL *ssl = (SSL *) ctx;
+  SSL *ssl = ctx;
   do
     ret = SSL_read (ssl, buf, bufsize);
   while (ret == -1
@@ -259,7 +251,7 @@ static int
 openssl_write (int fd, char *buf, int bufsize, void *ctx)
 {
   int ret = 0;
-  SSL *ssl = (SSL *) ctx;
+  SSL *ssl = ctx;
   do
     ret = SSL_write (ssl, buf, bufsize);
   while (ret == -1
@@ -271,7 +263,7 @@ openssl_write (int fd, char *buf, int bufsize, void *ctx)
 static int
 openssl_poll (int fd, double timeout, int wait_for, void *ctx)
 {
-  SSL *ssl = (SSL *) ctx;
+  SSL *ssl = ctx;
   if (timeout == 0)
     return 1;
   if (SSL_pending (ssl))
@@ -283,7 +275,7 @@ static int
 openssl_peek (int fd, char *buf, int bufsize, void *ctx)
 {
   int ret;
-  SSL *ssl = (SSL *) ctx;
+  SSL *ssl = ctx;
   do
     ret = SSL_peek (ssl, buf, bufsize);
   while (ret == -1
@@ -292,10 +284,44 @@ openssl_peek (int fd, char *buf, int bufsize, void *ctx)
   return ret;
 }
 
+static const char *
+openssl_errstr (int fd, void *ctx)
+{
+  /* Unfortunately we cannot use ERR_error_string's internal buffer
+     because we must be prepared to print more than one error.  */
+  static char errbuf[512];
+  char *p = errbuf, *end = errbuf + sizeof errbuf;
+  unsigned long err;
+
+  if ((err = ERR_get_error ()) == 0)
+    /* Inform the caller that there have been no errors */
+    return NULL;
+
+  /* Iterate over OpenSSL's error stack and print errors to ERRBUF,
+     separated by "; ", while being careful not to overrun ERRBUF.  */
+  do
+    {
+      ERR_error_string_n (err, p, end - p);
+      p = strchr (p, '\0');
+      err = ERR_get_error ();
+      if (err == 0)
+       break;
+      if (p < end) *p++ = ';';
+      if (p < end) *p++ = ' ';
+    }
+  while (p < end);
+
+  if (p < end)
+    *p++ = '\0';
+  else
+    end[-1] = '\0';
+  return errbuf;
+}
+
 static void
 openssl_close (int fd, void *ctx)
 {
-  SSL *ssl = (SSL *) ctx;
+  SSL *ssl = ctx;
   SSL_shutdown (ssl);
   SSL_free (ssl);
 
@@ -308,15 +334,23 @@ openssl_close (int fd, void *ctx)
   DEBUGP (("Closed %d/SSL 0x%0lx\n", fd, (unsigned long) ssl));
 }
 
+/* openssl_transport is the singleton that describes the SSL transport
+   methods provided by this file.  */
+
+static struct transport_implementation openssl_transport = {
+  openssl_read, openssl_write, openssl_poll,
+  openssl_peek, openssl_errstr, openssl_close
+};
+
 /* Perform the SSL handshake on file descriptor FD, which is assumed
    to be connected to an SSL server.  The SSL handle provided by
    OpenSSL is registered with the file descriptor FD using
    fd_register_transport, so that subsequent calls to fd_read,
    fd_write, etc., will use the corresponding SSL functions.
 
-   Returns 1 on success, 0 on failure.  */
+   Returns true on success, false on failure.  */
 
-int
+bool
 ssl_connect (int fd) 
 {
   SSL *ssl;
@@ -335,23 +369,22 @@ ssl_connect (int fd)
 
   /* Register FD with Wget's transport layer, i.e. arrange that our
      functions are used for reading, writing, and polling.  */
-  fd_register_transport (fd, openssl_read, openssl_write, openssl_poll,
-                        openssl_peek, openssl_close, ssl);
+  fd_register_transport (fd, &openssl_transport, ssl);
   DEBUGP (("Handshake successful; connected socket %d to SSL handle 0x%0*lx\n",
           fd, PTR_FORMAT (ssl)));
-  return 1;
+  return true;
 
  error:
   DEBUGP (("SSL handshake failed.\n"));
   print_errors ();
   if (ssl)
     SSL_free (ssl);
-  return 0;
+  return false;
 }
 
 #define ASTERISK_EXCLUDES_DOT  /* mandated by rfc2818 */
 
-/* Return 1 is STRING (case-insensitively) matches PATTERN, 0
+/* Return true is STRING (case-insensitively) matches PATTERN, false
    otherwise.  The recognized wildcard character is "*", which matches
    any character in STRING except ".".  Any number of the "*" wildcard
    may be present in the pattern.
@@ -365,7 +398,7 @@ ssl_connect (int fd)
    If the pattern contain no wildcards, pattern_match(a, b) is
    equivalent to !strcasecmp(a, b).  */
 
-static int
+static bool
 pattern_match (const char *pattern, const char *string)
 {
   const char *p = pattern, *n = string;
@@ -377,17 +410,17 @@ pattern_match (const char *pattern, const char *string)
          ;
        for (; *n != '\0'; n++)
          if (TOLOWER (*n) == c && pattern_match (p, n))
-           return 1;
+           return true;
 #ifdef ASTERISK_EXCLUDES_DOT
          else if (*n == '.')
-           return 0;
+           return false;
 #endif
        return c == '\0';
       }
     else
       {
        if (c != TOLOWER (*n))
-         return 0;
+         return false;
       }
   return *n == '\0';
 }
@@ -401,18 +434,18 @@ pattern_match (const char *pattern, const char *string)
    the SSL handshake has been performed and that FD is connected to an
    SSL handle.
 
-   If opt.check_cert is non-zero (the default), this returns 1 if the
+   If opt.check_cert is true (the default), this returns 1 if the
    certificate is valid, 0 otherwise.  If opt.check_cert is 0, the
    function always returns 1, but should still be called because it
    warns the user about any problems with the certificate.  */
 
-int
+bool
 ssl_check_certificate (int fd, const char *host)
 {
   X509 *cert;
   char common_name[256];
   long vresult;
-  int success;
+  bool success = true;
 
   /* If the user has specified --no-check-cert, we still want to warn
      him about problems with the server's certificate.  */
@@ -426,12 +459,11 @@ ssl_check_certificate (int fd, const char *host)
     {
       logprintf (LOG_NOTQUIET, _("%s: No certificate presented by %s.\n"),
                 severity, escnonprint (host));
-      success = 0;
-      goto out;
+      success = false;
+      goto no_cert;            /* must bail out since CERT is NULL */
     }
 
-#ifdef ENABLE_DEBUG
-  if (opt.debug)
+  IF_DEBUG
     {
       char *subject = X509_NAME_oneline (X509_get_subject_name (cert), 0, 0);
       char *issuer = X509_NAME_oneline (X509_get_issuer_name (cert), 0, 0);
@@ -440,17 +472,26 @@ ssl_check_certificate (int fd, const char *host)
       OPENSSL_free (subject);
       OPENSSL_free (issuer);
     }
-#endif
 
   vresult = SSL_get_verify_result (ssl);
   if (vresult != X509_V_OK)
     {
+      /* #### We might want to print saner (and translatable) error
+        messages for several frequently encountered errors.  The
+        candidates would include
+        X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
+        X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,
+        X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
+        X509_V_ERR_CERT_NOT_YET_VALID, X509_V_ERR_CERT_HAS_EXPIRED,
+        and possibly others.  The current approach would still be
+        used for the less frequent failure cases.  */
       logprintf (LOG_NOTQUIET,
                 _("%s: Certificate verification error for %s: %s\n"),
                 severity, escnonprint (host),
                 X509_verify_cert_error_string (vresult));
-      success = 0;
-      goto out;
+      success = false;
+      /* Fall through, so that the user is warned about *all* issues
+        with the cert (important with --no-check-certificate.)  */
     }
 
   /* Check that HOST matches the common name in the certificate.
@@ -464,8 +505,8 @@ ssl_check_certificate (int fd, const char *host)
        common names and choose the most specific one, i.e. the last
        one, not the first one, which the current code picks.
 
-     - Make sure that the names are encoded as UTF-8 which, being
-       ASCII-compatible, can be easily compared against HOST.  */
+     - Ensure that ASN1 strings from the certificate are encoded as
+       UTF-8 which can be meaningfully compared to HOST.  */
 
   common_name[0] = '\0';
   X509_NAME_get_text_by_NID (X509_get_subject_name (cert),
@@ -475,24 +516,20 @@ ssl_check_certificate (int fd, const char *host)
       logprintf (LOG_NOTQUIET, _("\
 %s: certificate common name `%s' doesn't match requested host name `%s'.\n"),
                 severity, escnonprint (common_name), escnonprint (host));
-      success = 0;
-      goto out;
+      success = false;
     }
 
-  /* The certificate was found, verified, and matched HOST. */
-  success = 1;
-  DEBUGP (("X509 certificate successfully verified and matches host %s\n",
-          escnonprint (host)));
-
- out:
-  if (cert)
-    X509_free (cert);
+  if (success)
+    DEBUGP (("X509 certificate successfully verified and matches host %s\n",
+            escnonprint (host)));
+  X509_free (cert);
 
+ no_cert:
   if (opt.check_cert && !success)
     logprintf (LOG_NOTQUIET, _("\
 To connect to %s insecurely, use `--no-check-certificate'.\n"),
               escnonprint (host));
 
   /* Allow --no-check-cert to disable certificate checking. */
-  return opt.check_cert ? success : 1;
+  return opt.check_cert ? success : true;
 }