From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Ted Zlatanov Newsgroups: gmane.emacs.devel Subject: Re: [PATCH] GnuTLS support on Woe32 Date: Tue, 22 Mar 2011 16:14:08 -0500 Organization: =?utf-8?B?0KLQtdC+0LTQvtGAINCX0LvQsNGC0LDQvdC+0LI=?= @ Cienfuegos Message-ID: <87wrjquacf.fsf@lifelogs.com> References: <87ipvwl1nx.wl%claudio.bley@gmail.com> <83oc5ogp89.fsf@gnu.org> <87ipvuwslp.wl%claudio.bley@gmail.com> <87hbbc0zi6.wl%claudio.bley@gmail.com> <83oc5gsdwc.fsf@gnu.org> <87ei6bunxz.wl%claudio.bley@gmail.com> <83tyf6rhgn.fsf@gnu.org> <84zkoy6tah.wl%claudio.bley@gmail.com> <87hbb5a4xq.fsf@lifelogs.com> <84ipvkx1da.wl%claudio.bley@gmail.com> <87d3ls7n3b.fsf@lifelogs.com> <87vczdwjuk.wl%claudio.bley@gmail.com> <87k4fr3koi.fsf@lifelogs.com> <87aagn2tpr.fsf@lifelogs.com> NNTP-Posting-Host: lo.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: dough.gmane.org 1300828509 19411 80.91.229.12 (22 Mar 2011 21:15:09 GMT) X-Complaints-To: usenet@dough.gmane.org NNTP-Posting-Date: Tue, 22 Mar 2011 21:15:09 +0000 (UTC) To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Tue Mar 22 22:15:04 2011 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([199.232.76.165]) by lo.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1Q28ue-00032o-7t for ged-emacs-devel@m.gmane.org; Tue, 22 Mar 2011 22:15:03 +0100 Original-Received: from localhost ([127.0.0.1]:55041 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Q28uc-0006f2-1z for ged-emacs-devel@m.gmane.org; Tue, 22 Mar 2011 17:14:46 -0400 Original-Received: from [140.186.70.92] (port=52880 helo=eggs.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Q28uO-0006Wm-Iz for emacs-devel@gnu.org; Tue, 22 Mar 2011 17:14:35 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Q28uK-0007QL-Vu for emacs-devel@gnu.org; Tue, 22 Mar 2011 17:14:32 -0400 Original-Received: from lo.gmane.org ([80.91.229.12]:45004) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Q28uK-0007PY-8k for emacs-devel@gnu.org; Tue, 22 Mar 2011 17:14:28 -0400 Original-Received: from list by lo.gmane.org with local (Exim 4.69) (envelope-from ) id 1Q28uF-0002qJ-Rc for emacs-devel@gnu.org; Tue, 22 Mar 2011 22:14:23 +0100 Original-Received: from 38.98.147.130 ([38.98.147.130]) by main.gmane.org with esmtp (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for ; Tue, 22 Mar 2011 22:14:23 +0100 Original-Received: from tzz by 38.98.147.130 with local (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for ; Tue, 22 Mar 2011 22:14:23 +0100 X-Injected-Via-Gmane: http://gmane.org/ Original-Lines: 1199 Original-X-Complaints-To: usenet@dough.gmane.org X-Gmane-NNTP-Posting-Host: 38.98.147.130 X-Face: bd.DQ~'29fIs`T_%O%C\g%6jW)yi[zuz6; d4V0`@y-~$#3P_Ng{@m+e4o<4P'#(_GJQ%TT= D}[Ep*b!\e,fBZ'j_+#"Ps?s2!4H2-Y"sx" User-Agent: Gnus/5.110016 (No Gnus v0.16) Emacs/24.0.50 (gnu/linux) Cancel-Lock: sha1:r87qRF6UEJMCGaXyoHLMHiwcHNQ= X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 3) X-Received-From: 80.91.229.12 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:137542 Archived-At: --=-=-= Content-Type: text/plain On Tue, 22 Mar 2011 14:50:06 -0400 Stefan Monnier wrote: SM> BTW, I had not noticed this part in gnutls.el, which seems like an SM> error: why would you want it to be buffer-local? Gnutls is about SM> processes, so binding this var to buffers makes no sense to me. ... SM> Now that I look at it, I don't understand what this gnutls-hostname SM> variable is about. Why isn't it an additional keyword argument instead? SM> It needs better documentation than the current "Remote hostname.". Because of the way SSL and TLS work, the connection may start out unencrypted and the upgrade is sort of opportunistic. So we don't know in advance if we'll need the `gnutls-hostname'. Also the `gnutls-hostname' is not necessarily the actual host we connect to, so we can't keep it as a per-process property. And finally, making it a keyword parameter means the piece that *upgrades* the connection to TLS has to know the original hostname of the connection. I thought it was cleaner to separate them, so upgrading a connection is easier to do opportunistically. Emacs doesn't have per-process variables at the ELisp level so I had to associate it with the buffer and making it buffer-local seemed sensible. How would you do it? >> where I thought removing the braces looked confusing and ugly because of >> the nesting. SM> Fine (I personally prefer this code without the internal braces, but SM> it's no big deal). I'm not opposed to braces, but in the previous code SM> there was a lot of them around repetitive and "simple" code which lead SM> to the code being much too diluted. Yes, you were absolutely right to note those. Thanks. SM> We could come up with some font-lock rules to highlight "offending" SM> code, but I'm not sure it's worth the trouble. It would make my life easier. And maybe help other contributors. >> +:verify-flags is a bitset as per gnutls_certificate_set_verify_flags(). SM> In the GNU system we use the convention that "funname()" is a function SM> call and denotes the result of calling that function, rather than the SM> function itself. To refer to the function, just say "funname". Fixed. It's a habit for me :) >> +:verify-hostname-error determines if a hostname mismatch is a warning >> +or an error. SM> Try to use the form "if non-nil blabla", so it's clear which value gives SM> you which behavior. Fixed. >> + (set (intern "gnutls-hostname") host)) SM> Yuck!! SM> This should say "(setq gnutls-hostname host)": more efficient, more SM> concise, more understandable (also for the compiler), ... I was trying to shut up the byte-compilation warnings. proto-stream.el does some funky loading and I didn't know a better way (there's no `declare-variable'). If you have a better approach, please tell... Sorry this patch is getting so large. I'll try to fix all the issues ASAP. We need Claudio Bley's papers too, right? Ted --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=callbacks.patch Content-Transfer-Encoding: 8bit Content-Description: revised 2011-03-22 with proto-stream.el changes === modified file 'configure.in' --- configure.in 2011-03-20 23:58:23 +0000 +++ configure.in 2011-03-22 17:49:45 +0000 @@ -1973,12 +1973,22 @@ AC_SUBST(LIBSELINUX_LIBS) HAVE_GNUTLS=no +HAVE_GNUTLS_CALLBACK_CERTIFICATE_VERIFY=no if test "${with_gnutls}" = "yes" ; then PKG_CHECK_MODULES([LIBGNUTLS], [gnutls >= 2.2.4], HAVE_GNUTLS=yes, HAVE_GNUTLS=no) if test "${HAVE_GNUTLS}" = "yes"; then AC_DEFINE(HAVE_GNUTLS, 1, [Define if using GnuTLS.]) fi + + CFLAGS="$CFLAGS $LIBGNUTLS_CFLAGS" + LIBS="$LIBGNUTLS_LIBS $LIBS" + AC_CHECK_FUNCS(gnutls_certificate_set_verify_function, HAVE_GNUTLS_CALLBACK_CERTIFICATE_VERIFY=yes) + + if test "${HAVE_GNUTLS_CALLBACK_CERTIFICATE_VERIFY}" = "yes"; then + AC_DEFINE(HAVE_GNUTLS_CALLBACK_CERTIFICATE_VERIFY, 1, [Define if using GnuTLS certificate verification callbacks.]) + fi fi + AC_SUBST(LIBGNUTLS_LIBS) AC_SUBST(LIBGNUTLS_CFLAGS) @@ -3667,6 +3677,7 @@ echo " Does Emacs use -lgconf? ${HAVE_GCONF}" echo " Does Emacs use -lselinux? ${HAVE_LIBSELINUX}" echo " Does Emacs use -lgnutls? ${HAVE_GNUTLS}" +echo " Does Emacs use -lgnutls certificate verify callbacks? ${HAVE_GNUTLS_CALLBACK_CERTIFICATE_VERIFY}" echo " Does Emacs use -lxml2? ${HAVE_LIBXML2}" echo " Does Emacs use -lfreetype? ${HAVE_FREETYPE}" === modified file 'lib-src/ChangeLog' --- lib-src/ChangeLog 2011-03-12 19:19:47 +0000 +++ lib-src/ChangeLog 2011-03-22 17:49:45 +0000 @@ -1,3 +1,7 @@ +2011-03-06 Claudio Bley + + * makefile.w32-in (obj): Added gnutls.o. + 2011-03-03 Drake Wilson (tiny change) * emacsclient.c (longopts): Add quiet. === modified file 'lib-src/makefile.w32-in' --- lib-src/makefile.w32-in 2011-03-12 19:19:47 +0000 +++ lib-src/makefile.w32-in 2011-03-22 17:49:45 +0000 @@ -142,7 +142,8 @@ syntax.o bytecode.o \ process.o callproc.o unexw32.o \ region-cache.o sound.o atimer.o \ - doprnt.o intervals.o textprop.o composite.o + doprnt.o intervals.o textprop.o composite.o \ + gnutls.o # # These are the lisp files that are loaded up in loadup.el === modified file 'lisp/ChangeLog' --- lisp/ChangeLog 2011-03-22 15:38:40 +0000 +++ lisp/ChangeLog 2011-03-22 17:50:32 +0000 @@ -1,3 +1,18 @@ +2011-03-22 Teodor Zlatanov + + * net/gnutls.el (gnutls-hostname): New buffer-local variable for + hostname verification. + (gnutls-negotiate): Add verify-flags, verify-error, and + verify-hostname-error. + (open-gnutls-stream): Add usage example. + +2011-03-22 Claudio Bley + + * net/gnutls.el (gnutls-negotiate): Check whether default + trustfile exists before going to use it. Add missing argument to + gnutls-message-maybe call. Return return value. + + 2011-03-22 Leo Liu * abbrev.el (write-abbrev-file): Use utf-8 for writing if it can === modified file 'lisp/gnus/proto-stream.el' --- lisp/gnus/proto-stream.el 2011-02-06 22:27:28 +0000 +++ lisp/gnus/proto-stream.el 2011-03-22 17:55:28 +0000 @@ -61,7 +61,8 @@ :group 'comm) (declare-function gnutls-negotiate "gnutls" - (proc type &optional priority-string trustfiles keyfiles)) + (proc type &optional priority-string trustfiles keyfiles + verify-flags verify-error verify-hostname-error)) ;;;###autoload (defun open-protocol-stream (name buffer host service &rest parameters) @@ -190,7 +191,12 @@ (list stream greeting capabilities 'network))) ;; The server said it was OK to start doing STARTTLS negotiations. (if (fboundp 'open-gnutls-stream) - (gnutls-negotiate stream nil) + (progn + ;; Set the required buffer-local gnutls-hostname + ;; (defined in gnutls.el). + (with-current-buffer buffer + (set (intern "gnutls-hostname") host)) + (gnutls-negotiate stream nil)) (unless (starttls-negotiate stream) (delete-process stream) (setq stream nil))) === modified file 'lisp/net/gnutls.el' --- lisp/net/gnutls.el 2011-01-25 04:08:28 +0000 +++ lisp/net/gnutls.el 2011-03-22 18:55:41 +0000 @@ -44,6 +44,10 @@ :type 'integer :group 'gnutls) +(defvar gnutls-hostname nil + "Remote hostname. Always buffer-local.") +(make-variable-buffer-local 'gnutls-hostname) + (defun open-gnutls-stream (name buffer host service) "Open a SSL/TLS connection for a service to a host. Returns a subprocess-object to represent the connection. @@ -59,26 +63,77 @@ Fourth arg SERVICE is name of the service desired, or an integer specifying a port number to connect to. +Usage example: + + \(with-temp-buffer + \(open-gnutls-stream \"tls\" + \(current-buffer) + \"your server goes here\" + \"imaps\")) + This is a very simple wrapper around `gnutls-negotiate'. See its documentation for the specific parameters you can use to open a GnuTLS connection, including specifying the credential type, trust and key files, and priority string." - (let ((proc (open-network-stream name buffer host service))) - (gnutls-negotiate proc 'gnutls-x509pki))) + ;; remember the hostname associated with this buffer + (with-current-buffer buffer + (setq gnutls-hostname host)) + (gnutls-negotiate (open-network-stream name buffer host service) + 'gnutls-x509pki)) + +(put 'gnutls-error + 'error-conditions + '(error gnutls-error)) +(put 'gnutls-error + 'error-message "GnuTLS error") (declare-function gnutls-boot "gnutls.c" (proc type proplist)) (defun gnutls-negotiate (proc type &optional priority-string - trustfiles keyfiles) - "Negotiate a SSL/TLS connection. + trustfiles keyfiles verify-flags + verify-error verify-hostname-error) + "Negotiate a SSL/TLS connection. Returns proc. Signals gnutls-error. TYPE is `gnutls-x509pki' (default) or `gnutls-anon'. Use nil for the default. PROC is a process returned by `open-network-stream'. PRIORITY-STRING is as per the GnuTLS docs, default is \"NORMAL\". TRUSTFILES is a list of CA bundles. -KEYFILES is a list of client keys." +KEYFILES is a list of client keys. + +When VERIFY-HOSTNAME-ERROR is not nil, an error will be raised +when the hostname does not match the presented certificate's host +name. The exact verification algorithm is a basic implementation +of the matching described in RFC2818 (HTTPS), which takes into +account wildcards, and the DNSName/IPAddress subject alternative +name PKIX extension. See GnuTLS' gnutls_x509_crt_check_hostname +for details. When VERIFY-HOSTNAME-ERROR is nil, only a warning +will be issued. + +When VERIFY-ERROR is not nil, an error will be raised when the +peer certificate verification fails as per GnuTLS' +gnutls_certificate_verify_peers2. Otherwise, only warnings will +be shown about the verification failure. + +VERIFY-FLAGS is a numeric OR of verification flags only for +`gnutls-x509pki' connections. See GnuTLS' x509.h for details; +here's a recent version of the list. + + GNUTLS_VERIFY_DISABLE_CA_SIGN = 1, + GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT = 2, + GNUTLS_VERIFY_DO_NOT_ALLOW_SAME = 4, + GNUTLS_VERIFY_ALLOW_ANY_X509_V1_CA_CRT = 8, + GNUTLS_VERIFY_ALLOW_SIGN_RSA_MD2 = 16, + GNUTLS_VERIFY_ALLOW_SIGN_RSA_MD5 = 32, + GNUTLS_VERIFY_DISABLE_TIME_CHECKS = 64, + GNUTLS_VERIFY_DISABLE_TRUSTED_TIME_CHECKS = 128, + GNUTLS_VERIFY_DO_NOT_ALLOW_X509_V1_CA_CRT = 256 + +It must be omitted, a number, or nil; if omitted or nil it +defaults to GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT." (let* ((type (or type 'gnutls-x509pki)) + (default-trustfile "/etc/ssl/certs/ca-certificates.crt") (trustfiles (or trustfiles - '("/etc/ssl/certs/ca-certificates.crt"))) + (when (file-exists-p default-trustfile) + (list default-trustfile)))) (priority-string (or priority-string (cond ((eq type 'gnutls-anon) @@ -89,12 +144,18 @@ :loglevel ,gnutls-log-level :trustfiles ,trustfiles :keyfiles ,keyfiles + :verify-flags ,verify-flags + :verify-error ,verify-error + :verify-hostname-error ,verify-hostname-error :callbacks nil)) ret) (gnutls-message-maybe (setq ret (gnutls-boot proc type params)) - "boot: %s") + "boot: %s" params) + + (when (gnutls-errorp ret) + (signal 'gnutls-error (list proc ret))) proc)) === modified file 'nt/ChangeLog' --- nt/ChangeLog 2011-03-12 19:19:47 +0000 +++ nt/ChangeLog 2011-03-22 17:49:45 +0000 @@ -1,3 +1,10 @@ +2011-03-06 Claudio Bley + + * configure.bat: New options --without-gnutls and --lib, new build + variable USER_LIBS, automatically detect GnuTLS. + * INSTALL: Add instructions for GnuTLS support. + * gmake.defs: Prefix USER_LIB's with -l. + 2011-02-27 Eli Zaretskii * inc/unistd.h (readlink, symlink): Declare prototypes. === modified file 'nt/INSTALL' --- nt/INSTALL 2011-01-26 08:36:39 +0000 +++ nt/INSTALL 2011-03-22 17:49:45 +0000 @@ -306,6 +306,16 @@ `dynamic-library-alist' and the value of `libpng-version', and download compatible DLLs if needed. +* Optional GnuTLS support + + To build Emacs with GnuTLS support, make sure that the + gnutls/gnutls.h header file can be found in the include path and + link to the appropriate libraries (e.g. gnutls.dll and gcrypt.dll) + using the --lib option. + + Pre-built binaries and an installer can be found at + http://josefsson.org/gnutls4win/. + * Experimental SVG support SVG support is currently experimental, and not built by default. === modified file 'nt/configure.bat' --- nt/configure.bat 2011-01-29 12:36:11 +0000 +++ nt/configure.bat 2011-03-22 17:49:45 +0000 @@ -86,10 +86,13 @@ set usercflags= set docflags= set userldflags= +set userlibs= set doldflags= +set dolibs= set sep1= set sep2= set sep3= +set sep4= set distfiles= rem ---------------------------------------------------------------------- @@ -107,10 +110,12 @@ if "%1" == "--no-cygwin" goto nocygwin if "%1" == "--cflags" goto usercflags if "%1" == "--ldflags" goto userldflags +if "%1" == "--lib" goto userlibs if "%1" == "--without-png" goto withoutpng if "%1" == "--without-jpeg" goto withoutjpeg if "%1" == "--without-gif" goto withoutgif if "%1" == "--without-tiff" goto withouttiff +if "%1" == "--without-gnutls" goto withoutgnutls if "%1" == "--without-xpm" goto withoutxpm if "%1" == "--with-svg" goto withsvg if "%1" == "--distfiles" goto distfiles @@ -129,11 +134,13 @@ echo. --no-cygwin use -mno-cygwin option with GCC echo. --cflags FLAG pass FLAG to compiler echo. --ldflags FLAG pass FLAG to compiler when linking +echo. --lib LIB link to auxiliary library LIB echo. --without-png do not use PNG library even if it is installed echo. --without-jpeg do not use JPEG library even if it is installed echo. --without-gif do not use GIF library even if it is installed echo. --without-tiff do not use TIFF library even if it is installed echo. --without-xpm do not use XPM library even if it is installed +echo. --without-gnutls do not use GNUTLS library even if it is installed echo. --with-svg use the RSVG library (experimental) echo. --distfiles path to files for make dist, e.g. libXpm.dll goto end @@ -204,6 +211,14 @@ shift goto again +:userlibs +shift +echo. userlibs: %userlibs% +set userlibs=%userlibs%%sep4%%1 +set sep4= %nothing% +shift +goto again + rem ---------------------------------------------------------------------- :userldflags @@ -239,6 +254,14 @@ rem ---------------------------------------------------------------------- +:withoutgnutls +set tlssupport=N +set HAVE_GNUTLS= +shift +goto again + +rem ---------------------------------------------------------------------- + :withouttiff set tiffsupport=N set HAVE_TIFF= @@ -467,6 +490,29 @@ :pngDone rm -f junk.c junk.obj +if (%tlssupport%) == (N) goto tlsDone + +echo Checking for libgnutls... +echo #include "gnutls/gnutls.h" >junk.c +echo main (){} >>junk.c +rem -o option is ignored with cl, but allows result to be consistent. +echo %COMPILER% %usercflags% %mingwflag% -c junk.c -o junk.obj >>config.log +%COMPILER% %usercflags% %mingwflag% -c junk.c -o junk.obj >junk.out 2>>config.log +if exist junk.obj goto haveTls + +echo ...gnutls.h not found, building without TLS support. +echo The failed program was: >>config.log +type junk.c >>config.log +set HAVE_GNUTLS= +goto :tlsDone + +:haveTls +echo ...GNUTLS header available, building with GNUTLS support. +set HAVE_GNUTLS=1 + +:tlsDone +rm -f junk.c junk.obj + if (%jpegsupport%) == (N) goto jpegDone echo Checking for jpeg-6b... @@ -639,6 +685,8 @@ if (%docflags%)==(Y) echo USER_CFLAGS=%usercflags%>>config.settings for %%v in (%userldflags%) do if not (%%v)==() set doldflags=Y if (%doldflags%)==(Y) echo USER_LDFLAGS=%userldflags%>>config.settings +for %%v in (%userlibs%) do if not (%%v)==() set dolibs=Y +if (%dolibs%)==(Y) echo USER_LIBS=%userlibs%>>config.settings echo # End of settings from configure.bat>>config.settings echo. >>config.settings @@ -651,6 +699,7 @@ if (%doldflags%) == (Y) echo #define USER_LDFLAGS " %userldflags%">>config.tmp if (%profile%) == (Y) echo #define PROFILING 1 >>config.tmp if not "(%HAVE_PNG%)" == "()" echo #define HAVE_PNG 1 >>config.tmp +if not "(%HAVE_GNUTLS%)" == "()" echo #define HAVE_GNUTLS 1 >>config.tmp if not "(%HAVE_JPEG%)" == "()" echo #define HAVE_JPEG 1 >>config.tmp if not "(%HAVE_GIF%)" == "()" echo #define HAVE_GIF 1 >>config.tmp if not "(%HAVE_TIFF%)" == "()" echo #define HAVE_TIFF 1 >>config.tmp @@ -789,6 +838,7 @@ set HAVE_DISTFILES= set distFilesOk= set pngsupport= +set tlssupport= set jpegsupport= set gifsupport= set tiffsupport= === modified file 'nt/gmake.defs' --- nt/gmake.defs 2011-01-25 04:08:28 +0000 +++ nt/gmake.defs 2011-03-22 17:49:45 +0000 @@ -279,6 +279,10 @@ NOCYGWIN = -mno-cygwin endif +ifdef USER_LIBS +USER_LIBS := $(patsubst %,-l%,$(USER_LIBS)) +endif + ifeq "$(ARCH)" "i386" ifdef NOOPT ARCH_CFLAGS = -c $(DEBUG_FLAG) $(NOCYGWIN) === modified file 'src/ChangeLog' --- src/ChangeLog 2011-03-20 23:58:23 +0000 +++ src/ChangeLog 2011-03-22 17:49:45 +0000 @@ -1,3 +1,37 @@ +2011-03-22 Teodor Zlatanov + + * gnutls.c: Renamed global_initialized to + gnutls_global_initialized. Added internals for the + :verify-hostname-error, :verify-error, and :verify-flags + parameters of `gnutls-boot' and documented those parameters in the + docstring. Start callback support. + +2011-03-20 Claudio Bley + + * w32.h: (emacs_gnutls_pull): Add prototype. + (emacs_gnutls_push): Likewise. + + * w32.c: (emacs_gnutls_pull): New function for GnuTLS on Woe32. + (emacs_gnutls_push): Likewise. + + * process.c (wait_reading_process_output): Check if GnuTLS + buffered some data internally if no FDs are set for TLS + connections. + + * makefile.w32-in (OBJ2): Add gnutls.$(O). + (LIBS): Link to USER_LIBS. + ($(BLD)/gnutls.$(0)): New target. + + * gnutls.c (emacs_gnutls_handle_error): New function. + (wsaerror_to_errno): Likewise. + (emacs_gnutls_handshake): Add Woe32 support. Retry handshake + unless a fatal error occured. Call gnutls_alert_send_appropriate + on error. Return error code. + (emacs_gnutls_write): Call emacs_gnutls_handle_error. + (emacs_gnutls_read): Likewise. + (Fgnutls_boot): Return handshake error code. + + 2011-03-20 Glenn Morris * config.in: Remove file. === modified file 'src/gnutls.c' --- src/gnutls.c 2011-01-25 04:08:28 +0000 +++ src/gnutls.c 2011-03-22 18:58:00 +0000 @@ -26,11 +26,21 @@ #ifdef HAVE_GNUTLS #include +#ifdef WINDOWSNT +#include +#include "w32.h" +#endif + +static int +emacs_gnutls_handle_error (gnutls_session_t, int err); + Lisp_Object Qgnutls_code; Lisp_Object Qgnutls_anon, Qgnutls_x509pki; Lisp_Object Qgnutls_e_interrupted, Qgnutls_e_again, Qgnutls_e_invalid_session, Qgnutls_e_not_ready_for_handshake; -int global_initialized; +int gnutls_global_initialized; + +Lisp_Object Qgnutls_hostname; /* The following are for the property list of `gnutls-boot'. */ Lisp_Object Qgnutls_bootprop_priority; @@ -38,8 +48,14 @@ Lisp_Object Qgnutls_bootprop_keyfiles; Lisp_Object Qgnutls_bootprop_callbacks; Lisp_Object Qgnutls_bootprop_loglevel; - -static void +Lisp_Object Qgnutls_bootprop_verify_flags; +Lisp_Object Qgnutls_bootprop_verify_error; +Lisp_Object Qgnutls_bootprop_verify_hostname_error; + +/* Callback keys for `gnutls-boot'. Unused currently. */ +Lisp_Object Qgnutls_bootprop_callbacks_verify; + +static int emacs_gnutls_handshake (struct Lisp_Process *proc) { gnutls_session_t state = proc->gnutls_state; @@ -50,24 +66,56 @@ if (proc->gnutls_initstage < GNUTLS_STAGE_TRANSPORT_POINTERS_SET) { +#ifdef WINDOWSNT + /* On Windows we cannot transfer socket handles between + different runtime libraries. + + We must handle reading and writing ourselves. */ + gnutls_transport_set_ptr2 (state, + (gnutls_transport_ptr_t) proc, + (gnutls_transport_ptr_t) proc); + gnutls_transport_set_push_function (state, &emacs_gnutls_push); + gnutls_transport_set_pull_function (state, &emacs_gnutls_pull); + + /* For non blocking sockets or other custom made pull/push + functions the gnutls_transport_set_lowat must be called, with + a zero low water mark value. (GnuTLS 2.10.4 documentation) + + (Note: this is probably not strictly necessary as the lowat + value is only used when no custom pull/push functions are + set.) */ + gnutls_transport_set_lowat (state, 0); +#else /* This is how GnuTLS takes sockets: as file descriptors passed in. For an Emacs process socket, infd and outfd are the same but we use this two-argument version for clarity. */ gnutls_transport_set_ptr2 (state, - (gnutls_transport_ptr_t) (long) proc->infd, - (gnutls_transport_ptr_t) (long) proc->outfd); + (gnutls_transport_ptr_t) proc->infd, + (gnutls_transport_ptr_t) proc->outfd); +#endif proc->gnutls_initstage = GNUTLS_STAGE_TRANSPORT_POINTERS_SET; } - ret = gnutls_handshake (state); + do + { + ret = gnutls_handshake (state); + emacs_gnutls_handle_error (state, ret); + } + while (ret < 0 && gnutls_error_is_fatal (ret) == 0); + proc->gnutls_initstage = GNUTLS_STAGE_HANDSHAKE_TRIED; if (ret == GNUTLS_E_SUCCESS) { - /* here we're finally done. */ + /* Here we're finally done. */ proc->gnutls_initstage = GNUTLS_STAGE_READY; } + else + { + gnutls_alert_send_appropriate (state, ret); + } + return ret; } int @@ -98,7 +146,11 @@ if (rtnval == GNUTLS_E_AGAIN || rtnval == GNUTLS_E_INTERRUPTED) continue; else - return (bytes_written ? bytes_written : -1); + { + emacs_gnutls_handle_error (state, rtnval); + + return (bytes_written ? bytes_written : -1); + } } buf += rtnval; @@ -121,19 +173,57 @@ emacs_gnutls_handshake (proc); return -1; } - rtnval = gnutls_read (state, buf, nbyte); if (rtnval >= 0) return rtnval; + else if (emacs_gnutls_handle_error (state, rtnval) == 0) + /* non-fatal error */ + return -1; else { - if (rtnval == GNUTLS_E_AGAIN || - rtnval == GNUTLS_E_INTERRUPTED) - return -1; - else - return 0; + /* a fatal error occured */ + return 0; } } +/* report a GnuTLS error to the user. + Returns zero if the error code was successfully handled. */ +static int +emacs_gnutls_handle_error (gnutls_session_t session, int err) +{ + int alert, ret; + const char *err_type, *str; + + if (err >= 0) + return 0; + + if (gnutls_error_is_fatal (err) == 0) + { + ret = 0; + err_type = "Non fatal"; + } + else + { + ret = err; + err_type = "Fatal"; + } + + str = gnutls_strerror (err); + if (str == NULL) + str = "unknown"; + message ("gnutls.c *** %s error: %s", err_type, str); + + if (err == GNUTLS_E_WARNING_ALERT_RECEIVED + || err == GNUTLS_E_FATAL_ALERT_RECEIVED) + { + int alert = gnutls_alert_get (session); + str = gnutls_alert_get_name (alert); + if (str == NULL) + str = "unknown"; + message ("gnutls.c *** Received alert [%d]: %s", alert, str); + } + return ret; +} + /* convert an integer error to a Lisp_Object; it will be either a known symbol like `gnutls_e_interrupted' and `gnutls_e_again' or simply the integer value of the error. GNUTLS_E_SUCCESS is mapped @@ -265,10 +355,10 @@ { int ret = GNUTLS_E_SUCCESS; - if (!global_initialized) + if (!gnutls_global_initialized) ret = gnutls_global_init (); - global_initialized = 1; + gnutls_global_initialized = 1; return gnutls_make_error (ret); } @@ -278,10 +368,10 @@ static Lisp_Object gnutls_emacs_global_deinit (void) { - if (global_initialized) + if (gnutls_global_initialized) gnutls_global_deinit (); - global_initialized = 0; + gnutls_global_initialized = 0; return gnutls_make_error (GNUTLS_E_SUCCESS); } @@ -307,11 +397,24 @@ PROPLIST is a property list with the following keys: :priority is a GnuTLS priority string, defaults to "NORMAL". + :trustfiles is a list of PEM-encoded trust files for `gnutls-x509pki'. + :keyfiles is a list of PEM-encoded key files for `gnutls-x509pki'. -:callbacks is an alist of callback functions (TODO). + +:callbacks is an alist of callback functions, see below. + :loglevel is the debug level requested from GnuTLS, try 4. +:verify-flags is a bitset as per GnuTLS' +gnutls_certificate_set_verify_flags. + +:verify-error, if non-nil, makes failure of the certificate validation +an error. Otherwise it will be just a series of warnings. + +:verify-hostname-error, if non-nil, makes a hostname mismatch an +error. Otherwise it will be just a warning. + The debug level will be set for this process AND globally for GnuTLS. So if you set it higher or lower at any point, it affects global debugging. @@ -324,6 +427,9 @@ functions are used. This function allocates resources which can only be deallocated by calling `gnutls-deinit' or by calling it again. +The callbacks alist can have a `verify' key, associated with a +verification function (UNUSED). + Each authentication type may need additional information in order to work. For X.509 PKI (`gnutls-x509pki'), you probably need at least one trustfile (usually a CA bundle). */) @@ -336,12 +442,19 @@ /* TODO: GNUTLS_X509_FMT_DER is also an option. */ int file_format = GNUTLS_X509_FMT_PEM; + unsigned int gnutls_verify_flags = GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT; + gnutls_x509_crt_t gnutls_verify_cert; + unsigned int gnutls_verify_cert_list_size; + const gnutls_datum_t *gnutls_verify_cert_list; + gnutls_session_t state; gnutls_certificate_credentials_t x509_cred; gnutls_anon_client_credentials_t anon_cred; Lisp_Object global_init; char* priority_string_ptr = "NORMAL"; /* default priority string. */ Lisp_Object tail; + char* hostname; + int peer_verification; /* Placeholders for the property list elements. */ Lisp_Object priority_string; @@ -349,16 +462,26 @@ Lisp_Object keyfiles; Lisp_Object callbacks; Lisp_Object loglevel; + Lisp_Object verify_flags; + Lisp_Object verify_error; + Lisp_Object verify_hostname_error; CHECK_PROCESS (proc); CHECK_SYMBOL (type); CHECK_LIST (proplist); - priority_string = Fplist_get (proplist, Qgnutls_bootprop_priority); - trustfiles = Fplist_get (proplist, Qgnutls_bootprop_trustfiles); - keyfiles = Fplist_get (proplist, Qgnutls_bootprop_keyfiles); - callbacks = Fplist_get (proplist, Qgnutls_bootprop_callbacks); - loglevel = Fplist_get (proplist, Qgnutls_bootprop_loglevel); + priority_string = Fplist_get (proplist, Qgnutls_bootprop_priority); + trustfiles = Fplist_get (proplist, Qgnutls_bootprop_trustfiles); + keyfiles = Fplist_get (proplist, Qgnutls_bootprop_keyfiles); + callbacks = Fplist_get (proplist, Qgnutls_bootprop_callbacks); + loglevel = Fplist_get (proplist, Qgnutls_bootprop_loglevel); + verify_flags = Fplist_get (proplist, Qgnutls_bootprop_verify_flags); + verify_error = Fplist_get (proplist, Qgnutls_bootprop_verify_error); + verify_hostname_error = Fplist_get (proplist, Qgnutls_bootprop_verify_hostname_error); + + CHECK_STRING (Qgnutls_hostname); + + hostname = SSDATA (Fsymbol_value (Qgnutls_hostname)); state = XPROCESS (proc)->gnutls_state; XPROCESS (proc)->gnutls_p = 1; @@ -416,6 +539,23 @@ x509_cred = XPROCESS (proc)->gnutls_x509_cred; if (gnutls_certificate_allocate_credentials (&x509_cred) < 0) memory_full (); + + if (NUMBERP (verify_flags)) + { + gnutls_verify_flags = XINT (verify_flags); + GNUTLS_LOG (2, max_log_level, "setting verification flags"); + } + else if (NILP (verify_flags)) + { + /* The default is already GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT. */ + GNUTLS_LOG (2, max_log_level, "using default verification flags"); + } + else + { + /* The default is already GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT. */ + GNUTLS_LOG (2, max_log_level, "ignoring invalid verify-flags"); + } + gnutls_certificate_set_verify_flags (x509_cred, gnutls_verify_flags); } else if (EQ (type, Qgnutls_anon)) { @@ -484,6 +624,14 @@ GNUTLS_INITSTAGE (proc) = GNUTLS_STAGE_FILES; + GNUTLS_LOG (1, max_log_level, "gnutls callbacks"); + + GNUTLS_INITSTAGE (proc) = GNUTLS_STAGE_CALLBACKS; + +#ifdef HAVE_GNUTLS_CALLBACK_CERTIFICATE_VERIFY +#else +#endif + GNUTLS_LOG (1, max_log_level, "gnutls_init"); ret = gnutls_init (&state, GNUTLS_CLIENT); @@ -541,9 +689,105 @@ GNUTLS_INITSTAGE (proc) = GNUTLS_STAGE_CRED_SET; - emacs_gnutls_handshake (XPROCESS (proc)); - - return gnutls_make_error (GNUTLS_E_SUCCESS); + ret = emacs_gnutls_handshake (XPROCESS (proc)); + + if (ret < GNUTLS_E_SUCCESS) + return gnutls_make_error (ret); + + /* Now verify the peer, following + http://www.gnu.org/software/gnutls/manual/html_node/Verifying-peer_0027s-certificate.html. + The peer should present at least one certificate in the chain; do a + check of the certificate's hostname with + gnutls_x509_crt_check_hostname() against gnutls-hostname (which is + buffer-local and set by `open-gnutls-stream'. */ + + ret = gnutls_certificate_verify_peers2 (state, &peer_verification); + + if (ret < GNUTLS_E_SUCCESS) + return gnutls_make_error (ret); + + if (peer_verification & GNUTLS_CERT_INVALID) + message ("%s certificate could not be verified.", + hostname); + + if (peer_verification & GNUTLS_CERT_REVOKED) + message ("%s certificate was revoked (CRL).", + hostname); + + if (peer_verification & GNUTLS_CERT_SIGNER_NOT_FOUND) + message ("%s certificate's signer was not found.", + hostname); + + if (peer_verification & GNUTLS_CERT_SIGNER_NOT_CA) + message ("%s certificate's signer is not a CA.", + hostname); + + if (peer_verification & GNUTLS_CERT_INSECURE_ALGORITHM) + message ("%s certificate was signed with an insecure algorithm.", + hostname); + + if (peer_verification & GNUTLS_CERT_NOT_ACTIVATED) + message ("%s certificate is not yet activated.", hostname); + + if (peer_verification & GNUTLS_CERT_EXPIRED) + message ("%s certificate has expired.", hostname); + + if (peer_verification != 0) + { + if (NILP (verify_hostname_error)) + { + message ("Certificate validation failed for %s, verification code %d", + hostname, peer_verification); + } + else + { + error ("Certificate validation failed for %s, verification code %d", + hostname, peer_verification); + } + } + + /* Up to here the process is the same for X.509 certificates and + OpenPGP keys. From now on X.509 certificates are assumed. This + can be easily extended to work with openpgp keys as well. */ + if (gnutls_certificate_type_get (state) == GNUTLS_CRT_X509) + { + ret = gnutls_x509_crt_init (&gnutls_verify_cert); + + if (ret < GNUTLS_E_SUCCESS) + return gnutls_make_error (ret); + + gnutls_verify_cert_list = gnutls_certificate_get_peers (state, &gnutls_verify_cert_list_size); + if (NULL == gnutls_verify_cert_list) + { + error ("No certificate was found!\n"); + } + + /* We only check the first certificate in the given chain. */ + ret = gnutls_x509_crt_import (gnutls_verify_cert, &gnutls_verify_cert_list[0], GNUTLS_X509_FMT_DER); + + if (ret < GNUTLS_E_SUCCESS) + { + gnutls_x509_crt_deinit (gnutls_verify_cert); + return gnutls_make_error (ret); + } + + if (!gnutls_x509_crt_check_hostname (gnutls_verify_cert, hostname)) + { + if (NILP (verify_hostname_error)) + { + message ("GnuTLS warning: the certificate's hostname does not match gnutls-hostname \"%s\"", hostname); + } + else + { + gnutls_x509_crt_deinit (gnutls_verify_cert); + error ("The certificate's hostname does not match gnutls-hostname \"%s\"", hostname); + } + } + + gnutls_x509_crt_deinit (gnutls_verify_cert); + } + + return gnutls_make_error (ret); } DEFUN ("gnutls-bye", Fgnutls_bye, @@ -578,7 +822,7 @@ void syms_of_gnutls (void) { - global_initialized = 0; + gnutls_global_initialized = 0; Qgnutls_code = intern_c_string ("gnutls-code"); staticpro (&Qgnutls_code); @@ -589,6 +833,9 @@ Qgnutls_x509pki = intern_c_string ("gnutls-x509pki"); staticpro (&Qgnutls_x509pki); + Qgnutls_hostname = intern_c_string ("gnutls-hostname"); + staticpro (&Qgnutls_hostname); + Qgnutls_bootprop_priority = intern_c_string (":priority"); staticpro (&Qgnutls_bootprop_priority); @@ -601,9 +848,21 @@ Qgnutls_bootprop_callbacks = intern_c_string (":callbacks"); staticpro (&Qgnutls_bootprop_callbacks); + Qgnutls_bootprop_callbacks_verify = intern_c_string ("verify"); + staticpro (&Qgnutls_bootprop_callbacks_verify); + Qgnutls_bootprop_loglevel = intern_c_string (":loglevel"); staticpro (&Qgnutls_bootprop_loglevel); + Qgnutls_bootprop_verify_flags = intern_c_string (":verify-flags"); + staticpro (&Qgnutls_bootprop_verify_flags); + + Qgnutls_bootprop_verify_hostname_error = intern_c_string (":verify-error"); + staticpro (&Qgnutls_bootprop_verify_error); + + Qgnutls_bootprop_verify_hostname_error = intern_c_string (":verify-hostname-error"); + staticpro (&Qgnutls_bootprop_verify_hostname_error); + Qgnutls_e_interrupted = intern_c_string ("gnutls-e-interrupted"); staticpro (&Qgnutls_e_interrupted); Fput (Qgnutls_e_interrupted, Qgnutls_code, === modified file 'src/gnutls.h' --- src/gnutls.h 2011-01-25 04:08:28 +0000 +++ src/gnutls.h 2011-03-22 17:49:45 +0000 @@ -21,6 +21,7 @@ #ifdef HAVE_GNUTLS #include +#include typedef enum { @@ -28,6 +29,7 @@ GNUTLS_STAGE_EMPTY = 0, GNUTLS_STAGE_CRED_ALLOC, GNUTLS_STAGE_FILES, + GNUTLS_STAGE_CALLBACKS, GNUTLS_STAGE_INIT, GNUTLS_STAGE_PRIORITY, GNUTLS_STAGE_CRED_SET, === modified file 'src/makefile.w32-in' --- src/makefile.w32-in 2011-03-19 03:22:14 +0000 +++ src/makefile.w32-in 2011-03-22 17:49:45 +0000 @@ -105,6 +105,7 @@ $(BLD)/floatfns.$(O) \ $(BLD)/frame.$(O) \ $(BLD)/gmalloc.$(O) \ + $(BLD)/gnutls.$(O) \ $(BLD)/intervals.$(O) \ $(BLD)/composite.$(O) \ $(BLD)/ralloc.$(O) \ @@ -150,6 +151,7 @@ $(OLE32) \ $(COMCTL32) \ $(UNISCRIBE) \ + $(USER_LIBS) \ $(libc) # @@ -948,6 +950,14 @@ $(EMACS_ROOT)/nt/inc/unistd.h \ $(SRC)/getpagesize.h +$(BLD)/gnutls.$(O) : \ + $(SRC)/gnutls.h \ + $(SRC)/gnutls.c \ + $(CONFIG_H) \ + $(EMACS_ROOT)/nt/inc/sys/socket.h \ + $(SRC)/lisp.h \ + $(SRC)/process.h + $(BLD)/image.$(O) : \ $(SRC)/image.c \ $(CONFIG_H) \ === modified file 'src/process.c' --- src/process.c 2011-03-17 05:18:33 +0000 +++ src/process.c 2011-03-22 17:49:45 +0000 @@ -4780,6 +4780,19 @@ &Available, (check_write ? &Writeok : (SELECT_TYPE *)0), (SELECT_TYPE *)0, &timeout); + +#ifdef HAVE_GNUTLS + /* GnuTLS buffers data internally. In lowat mode it leaves + some data in the TCP buffers so that select works, but + with custom pull/push functions we need to check if some + data is available in the buffers manually. */ + if (nfds == 0 && wait_proc && wait_proc->gnutls_p + && gnutls_record_check_pending (wait_proc->gnutls_state) > 0) + { + FD_SET (wait_proc->infd, &Available); + nfds = 1; + } +#endif } xerrno = errno; === modified file 'src/w32.c' --- src/w32.c 2011-03-14 17:07:53 +0000 +++ src/w32.c 2011-03-22 17:49:45 +0000 @@ -6084,5 +6084,75 @@ p->childp = childp2; } +#ifdef HAVE_GNUTLS + +ssize_t +emacs_gnutls_pull (gnutls_transport_ptr_t p, void* buf, size_t sz) +{ + int n, sc; + SELECT_TYPE fdset; + EMACS_TIME timeout; + struct Lisp_Process *proc = (struct Lisp_Process *)p; + int fd = proc->infd; + + for (;;) + { + n = sys_read(fd, (char*)buf, sz); + + if (n >= 0) + return n; + else + { + int err = errno; + + if (err == EWOULDBLOCK) + { + EMACS_SET_SECS_USECS(timeout, 1, 0); + FD_ZERO (&fdset); + FD_SET ((int)fd, &fdset); + + sc = select (fd + 1, &fdset, (SELECT_TYPE *)0, (SELECT_TYPE *)0, + &timeout); + + if (sc > 0) + continue; + else if (sc == 0 || errno == EWOULDBLOCK) + /* We have to translate WSAEWOULDBLOCK alias + EWOULDBLOCK to EAGAIN for GnuTLS. */ + err = EAGAIN; + else + err = errno; + } + gnutls_transport_set_errno (proc->gnutls_state, err); + + return -1; + } + } +} + +ssize_t +emacs_gnutls_push (gnutls_transport_ptr_t p, const void* buf, size_t sz) +{ + struct Lisp_Process *proc = (struct Lisp_Process *)p; + int fd = proc->outfd; + ssize_t n = sys_write((int)fd, buf, sz); + + if (n >= 0) + return n; + else + { + gnutls_transport_set_errno (proc->gnutls_state, + /* Translate WSAEWOULDBLOCK alias + EWOULDBLOCK to EAGAIN for + GnuTLS. */ + errno == EWOULDBLOCK + ? EAGAIN + : errno); + + return -1; + } +} +#endif /* HAVE_GNUTLS */ + /* end of w32.c */ === modified file 'src/w32.h' --- src/w32.h 2011-01-25 04:08:28 +0000 +++ src/w32.h 2011-03-22 17:49:45 +0000 @@ -143,5 +143,14 @@ extern int _sys_read_ahead (int fd); extern int _sys_wait_accept (int fd); +#ifdef HAVE_GNUTLS +#include + +extern ssize_t emacs_gnutls_pull (gnutls_transport_ptr_t p, + void* buf, size_t sz); +extern ssize_t emacs_gnutls_push (gnutls_transport_ptr_t p, + const void* buf, size_t sz); +#endif /* HAVE_GNUTLS */ + #endif /* EMACS_W32_H */ --=-=-=--