From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: claudio.bley@gmail.com (Claudio Bley) Newsgroups: gmane.emacs.devel Subject: Re: [PATCH] GnuTLS support on Woe32 Date: Sun, 20 Mar 2011 22:41:23 +0100 Message-ID: <87vczdwjuk.wl%claudio.bley@gmail.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> NNTP-Posting-Host: lo.gmane.org Mime-Version: 1.0 (generated by SEMI 1.14.6 - "Maruoka") Content-Type: multipart/mixed; boundary="Multipart_Sun_Mar_20_22:41:22_2011-1" X-Trace: dough.gmane.org 1300657361 20656 80.91.229.12 (20 Mar 2011 21:42:41 GMT) X-Complaints-To: usenet@dough.gmane.org NNTP-Posting-Date: Sun, 20 Mar 2011 21:42:41 +0000 (UTC) To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sun Mar 20 22:42:35 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 1Q1QNr-00068a-Ck for ged-emacs-devel@m.gmane.org; Sun, 20 Mar 2011 22:42:34 +0100 Original-Received: from localhost ([127.0.0.1]:48915 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Q1QNm-000334-9Z for ged-emacs-devel@m.gmane.org; Sun, 20 Mar 2011 17:41:54 -0400 Original-Received: from [140.186.70.92] (port=34095 helo=eggs.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Q1QNc-0002rj-PO for emacs-devel@gnu.org; Sun, 20 Mar 2011 17:41:48 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Q1QNY-0006Of-I9 for emacs-devel@gnu.org; Sun, 20 Mar 2011 17:41:44 -0400 Original-Received: from lo.gmane.org ([80.91.229.12]:40046) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Q1QNX-0006OU-Lv for emacs-devel@gnu.org; Sun, 20 Mar 2011 17:41:40 -0400 Original-Received: from list by lo.gmane.org with local (Exim 4.69) (envelope-from ) id 1Q1QNW-0005yX-08 for emacs-devel@gnu.org; Sun, 20 Mar 2011 22:41:38 +0100 Original-Received: from dslb-188-102-233-076.pools.arcor-ip.net ([188.102.233.76]) by main.gmane.org with esmtp (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for ; Sun, 20 Mar 2011 22:41:37 +0100 Original-Received: from claudio.bley by dslb-188-102-233-076.pools.arcor-ip.net with local (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for ; Sun, 20 Mar 2011 22:41:37 +0100 X-Injected-Via-Gmane: http://gmane.org/ Mail-Followup-To: emacs-devel@gnu.org Original-Lines: 1237 Original-X-Complaints-To: usenet@dough.gmane.org X-Gmane-NNTP-Posting-Host: dslb-188-102-233-076.pools.arcor-ip.net In-Reply-To: <87d3ls7n3b.fsf@lifelogs.com> User-Agent: Wanderlust/2.15.9 (Almost Unreal) SEMI/1.14.6 (Maruoka) FLIM/1.14.9 (=?UTF-8?B?R29qxY0=?=) APEL/10.8 Emacs/23.1 (i686-pc-linux-gnu) MULE/6.0 (HANACHIRUSATO) Mail-Copies-To: never 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:137458 Archived-At: --Multipart_Sun_Mar_20_22:41:22_2011-1 Content-Type: text/plain; charset=US-ASCII At Tue, 15 Mar 2011 04:24:40 -0500, Ted Zlatanov wrote: > > On Tue, 15 Mar 2011 08:57:05 +0100 claudio.bley@gmail.com (Claudio Bley) wrote: > > CB> Ted Zlatanov wrote: > >> Any update on the integration of our patches (I had some verify-flags > >> support started, also the buffer-local hostname and initial C-level > >> callback support)? Or are you waiting for me? > > CB> No, I'm not waiting for you, not at all. I haven't made any > CB> progress. I'm just very busy these days and am not 25 anymore where I > CB> could hack away all night... > > I know the feeling :) > > CB> I've send a mail to assign@gnu.org and are awaiting the papers now. > > CB> I'll see if I can get something done this weekend. > > OK. I just wanted to make sure we're not waiting for each other. I'm sorry that I'm responding so infrequently, but unfortunately I really have little time working on this right now. I made some minor changes and integrated your patch into my branch. Basically, the changes are in correspondence to what Eli requested. I backed out the changes to starttls.el, moved the pull/push functions to w32.c. Additionally, I reverted the change to gnutls-negotiate where I signal an error now instead of returning an error value since I had realized that some more code depended upon returning the given process. As the GnuTLS functionality should be almost transparent to other libraries making network connections I think this is the better solution. What do you think about that? Otherwise, your patch looks good so far. Except that I always got hostname mismatches for www.google.no and www.google.com. Btw, I could not reproduce the problem reported by Lars Magne Ingebrigsten, ie. this (progn (require 'gnutls) (url-retrieve "https://www.google.no" #'ignore) (url-retrieve "https://www.google.no" #'ignore)) works flawlessly and does not hang (tested on Windows and Linux). - Claudio --Multipart_Sun_Mar_20_22:41:22_2011-1 Content-Type: text/plain; charset=US-ASCII Content-Disposition: inline; filename=gnutls-win32_4.txt Content-Transfer-Encoding: 7bit # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: claudio.bley@gmail.com-20110320213233-nboi8zv43ji1rb2g # target_branch: bzr+ssh://USERNAME@bzr.savannah.gnu.org/emacs/trunk # testament_sha1: 1ba258fef53672775845666969738bf0b4484a11 # timestamp: 2011-03-20 22:36:44 +0100 # source_branch: ../trunk/ # base_revision_id: rgm@gnu.org-20110304084000-8thi67w6o3ze71wz # # Begin patch === modified file 'configure.in' --- configure.in 2011-03-03 08:03:01 +0000 +++ configure.in 2011-03-20 21:32:33 +0000 @@ -1972,12 +1972,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) @@ -3658,6 +3668,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-03 07:00:23 +0000 +++ lib-src/ChangeLog 2011-03-06 14:57:51 +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-02-22 17:51:38 +0000 +++ lib-src/makefile.w32-in 2011-03-06 14:57:51 +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-04 08:40:00 +0000 +++ lisp/ChangeLog 2011-03-06 14:57:51 +0000 @@ -1,3 +1,9 @@ +2011-03-06 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-04 Glenn Morris * outline.el (outline-regexp): No longer allow nil. === modified file 'lisp/net/gnutls.el' --- lisp/net/gnutls.el 2011-01-25 04:08:28 +0000 +++ lisp/net/gnutls.el 2011-03-20 21:32:33 +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. @@ -63,22 +67,50 @@ 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) + "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. + +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 +121,16 @@ :loglevel ,gnutls-log-level :trustfiles ,trustfiles :keyfiles ,keyfiles + :verify-flags ,verify-flags :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-02-27 19:48:31 +0000 +++ nt/ChangeLog 2011-03-06 14:57:51 +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-20 18:30:26 +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-06 14:57:51 +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 @@ -213,6 +220,14 @@ shift goto again +:userlibs +shift +echo. userlibs: %userlibs% +set userlibs=%userlibs%%sep4%%1 +set sep4= %nothing% +shift +goto again + rem ---------------------------------------------------------------------- :withoutpng @@ -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-06 14:57:51 +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-02 21:30:51 +0000 +++ src/ChangeLog 2011-03-16 20:42:30 +0000 @@ -1,3 +1,30 @@ +2011-03-16 vtc + + * 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. + +2011-03-06 Claudio Bley + + * 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-02 kbrown * sheap.c (STATIC_HEAP_SIZE): Increase to 13MB. === modified file 'src/gnutls.c' --- src/gnutls.c 2011-01-25 04:08:28 +0000 +++ src/gnutls.c 2011-03-20 21:32:33 +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,12 @@ Lisp_Object Qgnutls_bootprop_keyfiles; Lisp_Object Qgnutls_bootprop_callbacks; Lisp_Object Qgnutls_bootprop_loglevel; - -static void +Lisp_Object Qgnutls_bootprop_verify_flags; + +/* 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,17 +64,44 @@ 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 / 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) @@ -68,6 +109,11 @@ /* here we're finally done. */ proc->gnutls_initstage = GNUTLS_STAGE_READY; } + else + { + gnutls_alert_send_appropriate (state, ret); + } + return ret; } int @@ -98,7 +144,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 +171,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 +353,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 +366,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); } @@ -309,7 +397,7 @@ :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. The debug level will be set for this process AND globally for GnuTLS. @@ -324,6 +412,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. + 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,6 +427,11 @@ /* 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; @@ -349,6 +445,7 @@ Lisp_Object keyfiles; Lisp_Object callbacks; Lisp_Object loglevel; + Lisp_Object verify_flags; CHECK_PROCESS (proc); CHECK_SYMBOL (type); @@ -359,6 +456,7 @@ 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); state = XPROCESS (proc)->gnutls_state; XPROCESS (proc)->gnutls_p = 1; @@ -416,6 +514,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 +599,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 +664,56 @@ 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'. */ + + /* We should be calling gnutls_verify_peers2 around here I think? */ + + /* 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, SSDATA (intern_c_string ("gnutls-hostname")))) + { + gnutls_x509_crt_deinit (gnutls_verify_cert); + error ("The certificate's hostname does not match gnutls-hostname"); + } + + gnutls_x509_crt_deinit (gnutls_verify_cert); + } + + return gnutls_make_error (ret); } DEFUN ("gnutls-bye", Fgnutls_bye, @@ -578,7 +748,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 +759,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 +774,15 @@ 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_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-20 21:32:33 +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-02-21 20:00:19 +0000 +++ src/makefile.w32-in 2011-03-06 14:57:51 +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) # @@ -944,6 +946,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-02-18 17:37:30 +0000 +++ src/process.c 2011-03-16 16:56:17 +0000 @@ -4785,6 +4785,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-02-27 19:48:31 +0000 +++ src/w32.c 2011-03-18 14:29:56 +0000 @@ -6089,5 +6089,74 @@ 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-16 20:42:30 +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 */ # Begin bundle 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 kosGm5Sf4ftP/xdyRThQkO5dcaY= --Multipart_Sun_Mar_20_22:41:22_2011-1--