declare smobs in alloc.c
[bpt/emacs.git] / src / gnutls.c
index db0a6da..ef9367c 100644 (file)
@@ -1,5 +1,5 @@
 /* GnuTLS glue for GNU Emacs.
-   Copyright (C) 2010-2013 Free Software Foundation, Inc.
+   Copyright (C) 2010-2014 Free Software Foundation, Inc.
 
 This file is part of GNU Emacs.
 
@@ -21,6 +21,7 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "lisp.h"
 #include "process.h"
+#include "coding.h"
 
 #ifdef HAVE_GNUTLS
 #include <gnutls/gnutls.h>
@@ -49,13 +50,16 @@ static Lisp_Object QCgnutls_bootprop_loglevel;
 static Lisp_Object QCgnutls_bootprop_hostname;
 static Lisp_Object QCgnutls_bootprop_min_prime_bits;
 static Lisp_Object QCgnutls_bootprop_verify_flags;
-static Lisp_Object QCgnutls_bootprop_verify_hostname_error;
+static Lisp_Object QCgnutls_bootprop_verify_error;
 
 /* Callback keys for `gnutls-boot'.  Unused currently.  */
 static Lisp_Object QCgnutls_bootprop_callbacks_verify;
 
 static void gnutls_log_function (int, const char *);
-static void gnutls_log_function2 (int, const char*, const char*);
+static void gnutls_log_function2 (int, const char *, const char *);
+#ifdef HAVE_GNUTLS3
+static void gnutls_audit_log_function (gnutls_session_t, const char *);
+#endif
 
 \f
 #ifdef WINDOWSNT
@@ -108,6 +112,9 @@ DEF_GNUTLS_FN (void, gnutls_dh_set_prime_bits,
 DEF_GNUTLS_FN (int, gnutls_error_is_fatal, (int));
 DEF_GNUTLS_FN (int, gnutls_global_init, (void));
 DEF_GNUTLS_FN (void, gnutls_global_set_log_function, (gnutls_log_func));
+#ifdef HAVE_GNUTLS3
+DEF_GNUTLS_FN (void, gnutls_global_set_audit_log_function, (gnutls_audit_log_func));
+#endif
 DEF_GNUTLS_FN (void, gnutls_global_set_log_level, (int));
 DEF_GNUTLS_FN (void, gnutls_global_set_mem_functions,
               (gnutls_alloc_function, gnutls_alloc_function,
@@ -173,6 +180,9 @@ init_gnutls_functions (void)
   LOAD_GNUTLS_FN (library, gnutls_error_is_fatal);
   LOAD_GNUTLS_FN (library, gnutls_global_init);
   LOAD_GNUTLS_FN (library, gnutls_global_set_log_function);
+#ifdef HAVE_GNUTLS3
+  LOAD_GNUTLS_FN (library, gnutls_global_set_audit_log_function);
+#endif
   LOAD_GNUTLS_FN (library, gnutls_global_set_log_level);
   LOAD_GNUTLS_FN (library, gnutls_global_set_mem_functions);
   LOAD_GNUTLS_FN (library, gnutls_handshake);
@@ -230,6 +240,9 @@ init_gnutls_functions (void)
 #define fn_gnutls_error_is_fatal               gnutls_error_is_fatal
 #define fn_gnutls_global_init                  gnutls_global_init
 #define fn_gnutls_global_set_log_function      gnutls_global_set_log_function
+#ifdef HAVE_GNUTLS3
+#define fn_gnutls_global_set_audit_log_function        gnutls_global_set_audit_log_function
+#endif
 #define fn_gnutls_global_set_log_level         gnutls_global_set_log_level
 #define fn_gnutls_global_set_mem_functions     gnutls_global_set_mem_functions
 #define fn_gnutls_handshake                    gnutls_handshake
@@ -239,7 +252,9 @@ init_gnutls_functions (void)
 #define fn_gnutls_record_recv                  gnutls_record_recv
 #define fn_gnutls_record_send                  gnutls_record_send
 #define fn_gnutls_strerror                     gnutls_strerror
+#ifdef WINDOWSNT
 #define fn_gnutls_transport_set_errno          gnutls_transport_set_errno
+#endif
 #define fn_gnutls_transport_set_ptr2           gnutls_transport_set_ptr2
 #define fn_gnutls_x509_crt_check_hostname      gnutls_x509_crt_check_hostname
 #define fn_gnutls_x509_crt_deinit              gnutls_x509_crt_deinit
@@ -249,23 +264,35 @@ init_gnutls_functions (void)
 #endif /* !WINDOWSNT */
 
 \f
+#ifdef HAVE_GNUTLS3
+/* Function to log a simple audit message.  */
+static void
+gnutls_audit_log_function (gnutls_session_t session, const char *string)
+{
+  if (global_gnutls_log_level >= 1)
+    {
+      message ("gnutls.c: [audit] %s", string);
+    }
+}
+#endif
+
 /* Function to log a simple message.  */
 static void
-gnutls_log_function (int level, const charstring)
+gnutls_log_function (int level, const char *string)
 {
   message ("gnutls.c: [%d] %s", level, string);
 }
 
 /* Function to log a message and a string.  */
 static void
-gnutls_log_function2 (int level, const char* string, const char* extra)
+gnutls_log_function2 (int level, const char *string, const char *extra)
 {
   message ("gnutls.c: [%d] %s %s", level, string, extra);
 }
 
 /* Function to log a message and an integer.  */
 static void
-gnutls_log_function2i (int level, const charstring, int extra)
+gnutls_log_function2i (int level, const char *string, int extra)
 {
   message ("gnutls.c: [%d] %s %d", level, string, extra);
 }
@@ -338,17 +365,19 @@ emacs_gnutls_handshake (struct Lisp_Process *proc)
   return ret;
 }
 
-int
+ptrdiff_t
 emacs_gnutls_record_check_pending (gnutls_session_t state)
 {
   return fn_gnutls_record_check_pending (state);
 }
 
+#ifdef WINDOWSNT
 void
 emacs_gnutls_transport_set_errno (gnutls_session_t state, int err)
 {
   fn_gnutls_transport_set_errno (state, err);
 }
+#endif
 
 ptrdiff_t
 emacs_gnutls_write (struct Lisp_Process *proc, const char *buf, ptrdiff_t nbyte)
@@ -464,8 +493,20 @@ emacs_gnutls_handle_error (gnutls_session_t session, int err)
   else
     {
       ret = 1;
-      GNUTLS_LOG2 (1, max_log_level, "non-fatal error:", str);
-      /* TODO: EAGAIN AKA Qgnutls_e_again should be level 2.  */
+
+      switch (err)
+        {
+        case GNUTLS_E_AGAIN:
+          GNUTLS_LOG2 (3,
+                       max_log_level,
+                       "retry:",
+                       str);
+        default:
+          GNUTLS_LOG2 (1,
+                       max_log_level,
+                       "non-fatal error:",
+                       str);
+        }
     }
 
   if (err == GNUTLS_E_WARNING_ALERT_RECEIVED
@@ -713,8 +754,12 @@ certificates for `gnutls-x509pki'.
 :verify-flags is a bitset as per GnuTLS'
 gnutls_certificate_set_verify_flags.
 
-:verify-hostname-error, if non-nil, makes a hostname mismatch an
-error.  Otherwise it will be just a warning.
+:verify-hostname-error is ignored.  Pass :hostname in :verify-error
+instead.
+
+:verify-error is a list of symbols to express verification checks or
+`t' to do all checks.  Currently it can contain `:trustfiles' and
+`:hostname' to verify the certificate or the hostname respectively.
 
 :min-prime-bits is the minimum accepted number of bits the client will
 accept in Diffie-Hellman key exchange.
@@ -741,6 +786,7 @@ one trustfile (usually a CA bundle).  */)
 {
   int ret = GNUTLS_E_SUCCESS;
   int max_log_level = 0;
+  bool verify_error_all = 0;
 
   gnutls_session_t state;
   gnutls_certificate_credentials_t x509_cred = NULL;
@@ -748,7 +794,7 @@ one trustfile (usually a CA bundle).  */)
   Lisp_Object global_init;
   char const *priority_string_ptr = "NORMAL"; /* default priority string.  */
   unsigned int peer_verification;
-  charc_hostname;
+  char *c_hostname;
 
   /* Placeholders for the property list elements.  */
   Lisp_Object priority_string;
@@ -758,8 +804,7 @@ one trustfile (usually a CA bundle).  */)
   /* Lisp_Object callbacks; */
   Lisp_Object loglevel;
   Lisp_Object hostname;
-  /* Lisp_Object verify_error; */
-  Lisp_Object verify_hostname_error;
+  Lisp_Object verify_error;
   Lisp_Object prime_bits;
 
   CHECK_PROCESS (proc);
@@ -767,16 +812,10 @@ one trustfile (usually a CA bundle).  */)
   CHECK_LIST (proplist);
 
   if (NILP (Fgnutls_available_p ()))
-    {
-      error ("GnuTLS not available");
-      return gnutls_make_error (GNUTLS_EMACS_ERROR_NOT_LOADED);
-    }
+    error ("GnuTLS not available");
 
   if (!EQ (type, Qgnutls_x509pki) && !EQ (type, Qgnutls_anon))
-    {
-      error ("Invalid GnuTLS credential type");
-      return gnutls_make_error (GNUTLS_EMACS_ERROR_INVALID_TYPE);
-    }
+    error ("Invalid GnuTLS credential type");
 
   hostname              = Fplist_get (proplist, QCgnutls_bootprop_hostname);
   priority_string       = Fplist_get (proplist, QCgnutls_bootprop_priority);
@@ -784,19 +823,30 @@ one trustfile (usually a CA bundle).  */)
   keylist               = Fplist_get (proplist, QCgnutls_bootprop_keylist);
   crlfiles              = Fplist_get (proplist, QCgnutls_bootprop_crlfiles);
   loglevel              = Fplist_get (proplist, QCgnutls_bootprop_loglevel);
-  verify_hostname_error = Fplist_get (proplist, QCgnutls_bootprop_verify_hostname_error);
+  verify_error          = Fplist_get (proplist, QCgnutls_bootprop_verify_error);
   prime_bits            = Fplist_get (proplist, QCgnutls_bootprop_min_prime_bits);
 
+  if (EQ (verify_error, Qt))
+    {
+      verify_error_all = 1;
+    }
+  else if (NILP (Flistp (verify_error)))
+    {
+      error ("gnutls-boot: invalid :verify_error parameter (not a list)");
+    }
+
   if (!STRINGP (hostname))
-    error ("gnutls-boot: invalid :hostname parameter");
+    error ("gnutls-boot: invalid :hostname parameter (not a string)");
   c_hostname = SSDATA (hostname);
 
   state = XPROCESS (proc)->gnutls_state;
-  XPROCESS (proc)->gnutls_p = 1;
 
   if (TYPE_RANGED_INTEGERP (int, loglevel))
     {
       fn_gnutls_global_set_log_function (gnutls_log_function);
+#ifdef HAVE_GNUTLS3
+      fn_gnutls_global_set_audit_log_function (gnutls_audit_log_function);
+#endif
       fn_gnutls_global_set_log_level (XINT (loglevel));
       max_log_level = XINT (loglevel);
       XPROCESS (proc)->gnutls_log_level = max_log_level;
@@ -812,7 +862,6 @@ one trustfile (usually a CA bundle).  */)
   emacs_gnutls_deinit (proc);
 
   /* Mark PROC as a GnuTLS process.  */
-  XPROCESS (proc)->gnutls_p = 1;
   XPROCESS (proc)->gnutls_state = NULL;
   XPROCESS (proc)->gnutls_x509_cred = NULL;
   XPROCESS (proc)->gnutls_anon_cred = NULL;
@@ -864,6 +913,13 @@ one trustfile (usually a CA bundle).  */)
            {
              GNUTLS_LOG2 (1, max_log_level, "setting the trustfile: ",
                           SSDATA (trustfile));
+             trustfile = ENCODE_FILE (trustfile);
+#ifdef WINDOWSNT
+             /* Since GnuTLS doesn't support UTF-8 or UTF-16 encoded
+                file names on Windows, we need to re-encode the file
+                name using the current ANSI codepage.  */
+             trustfile = ansi_encode_filename (trustfile);
+#endif
              ret = fn_gnutls_certificate_set_x509_trust_file
                (x509_cred,
                 SSDATA (trustfile),
@@ -886,6 +942,10 @@ one trustfile (usually a CA bundle).  */)
            {
              GNUTLS_LOG2 (1, max_log_level, "setting the CRL file: ",
                           SSDATA (crlfile));
+             crlfile = ENCODE_FILE (crlfile);
+#ifdef WINDOWSNT
+             crlfile = ansi_encode_filename (crlfile);
+#endif
              ret = fn_gnutls_certificate_set_x509_crl_file
                (x509_cred, SSDATA (crlfile), file_format);
 
@@ -909,6 +969,12 @@ one trustfile (usually a CA bundle).  */)
                           SSDATA (keyfile));
              GNUTLS_LOG2 (1, max_log_level, "setting the client cert file: ",
                           SSDATA (certfile));
+             keyfile = ENCODE_FILE (keyfile);
+             certfile = ENCODE_FILE (certfile);
+#ifdef WINDOWSNT
+             keyfile = ansi_encode_filename (keyfile);
+             certfile = ansi_encode_filename (certfile);
+#endif
              ret = fn_gnutls_certificate_set_x509_key_file
                (x509_cred, SSDATA (certfile), SSDATA (keyfile), file_format);
 
@@ -1012,14 +1078,17 @@ one trustfile (usually a CA bundle).  */)
 
   if (peer_verification != 0)
     {
-      if (NILP (verify_hostname_error))
-       GNUTLS_LOG2 (1, max_log_level, "certificate validation failed:",
-                    c_hostname);
-      else
-       {
+      if (verify_error_all
+          || !NILP (Fmember (QCgnutls_bootprop_trustfiles, verify_error)))
+        {
          emacs_gnutls_deinit (proc);
          error ("Certificate validation failed %s, verification code %d",
                 c_hostname, peer_verification);
+        }
+      else
+       {
+          GNUTLS_LOG2 (1, max_log_level, "certificate validation failed:",
+                       c_hostname);
        }
     }
 
@@ -1059,19 +1128,25 @@ one trustfile (usually a CA bundle).  */)
 
       if (!fn_gnutls_x509_crt_check_hostname (gnutls_verify_cert, c_hostname))
        {
-         if (NILP (verify_hostname_error))
-           GNUTLS_LOG2 (1, max_log_level, "x509 certificate does not match:",
-                        c_hostname);
-         else
-           {
+          if (verify_error_all
+              || !NILP (Fmember (QCgnutls_bootprop_hostname, verify_error)))
+            {
              fn_gnutls_x509_crt_deinit (gnutls_verify_cert);
              emacs_gnutls_deinit (proc);
              error ("The x509 certificate does not match \"%s\"", c_hostname);
+            }
+         else
+           {
+              GNUTLS_LOG2 (1, max_log_level, "x509 certificate does not match:",
+                           c_hostname);
            }
        }
       fn_gnutls_x509_crt_deinit (gnutls_verify_cert);
     }
 
+  /* Set this flag only if the whole initialization succeeded.  */
+  XPROCESS (proc)->gnutls_p = 1;
+
   return gnutls_make_error (ret);
 }
 
@@ -1107,6 +1182,8 @@ This function may also return `gnutls-e-again', or
 void
 syms_of_gnutls (void)
 {
+#include "gnutls.x"
+
   gnutls_global_initialized = 0;
 
   DEFSYM (Qgnutls_dll, "gnutls");
@@ -1123,7 +1200,7 @@ syms_of_gnutls (void)
   DEFSYM (QCgnutls_bootprop_min_prime_bits, ":min-prime-bits");
   DEFSYM (QCgnutls_bootprop_loglevel, ":loglevel");
   DEFSYM (QCgnutls_bootprop_verify_flags, ":verify-flags");
-  DEFSYM (QCgnutls_bootprop_verify_hostname_error, ":verify-hostname-error");
+  DEFSYM (QCgnutls_bootprop_verify_error, ":verify-error");
 
   DEFSYM (Qgnutls_e_interrupted, "gnutls-e-interrupted");
   Fput (Qgnutls_e_interrupted, Qgnutls_code,
@@ -1141,15 +1218,6 @@ syms_of_gnutls (void)
   Fput (Qgnutls_e_not_ready_for_handshake, Qgnutls_code,
        make_number (GNUTLS_E_APPLICATION_ERROR_MIN));
 
-  defsubr (&Sgnutls_get_initstage);
-  defsubr (&Sgnutls_errorp);
-  defsubr (&Sgnutls_error_fatalp);
-  defsubr (&Sgnutls_error_string);
-  defsubr (&Sgnutls_boot);
-  defsubr (&Sgnutls_deinit);
-  defsubr (&Sgnutls_bye);
-  defsubr (&Sgnutls_available_p);
-
   DEFVAR_INT ("gnutls-log-level", global_gnutls_log_level,
              doc: /* Logging level used by the GnuTLS functions.
 Set this larger than 0 to get debug output in the *Messages* buffer.