% Evil cert patch: proceed elsewhere when certificate cannot be verified
% ======================================================================
%
% Current version: stunnel-4.35
%
% This patch brings in the two following directives:
% - evilconnect
% - evilexec/evilexecargs
%
% When stunnel works in server mode and is asked to verify the client's
% certificate, if the latter happens to be invalid the connection will
% be normally shut down.  When one of these options is used and the
% certificate cannot be verified, stunnel redirects the "evil"
% connection to another destination.
%
% The purpose of this feature is to cover up a suspicious SSL tunnel
% aimed to evade a dictatorial proxy.
%
% How to apply this patch?  Execute the following command in the top
% stunnel directory:
%   patch -p0 < evil.patch
%
% Written by Jeremie LE HEN, jeremie at le-hen org, Jul 2009.
% Updated to version 4.35 by Olivier LARDIT, Feb 2011.
%
Index: doc/stunnel.pod
===================================================================
RCS file: /home/cvs/tataz/src/stunnel/doc/stunnel.pod,v
retrieving revision 1.1.1.3
retrieving revision 1.4
diff -u -p -u -p -r1.1.1.3 -r1.4
--- doc/stunnel.pod	22 Feb 2011 08:21:15 -0000	1.1.1.3
+++ doc/stunnel.pod	22 Feb 2011 14:29:54 -0000	1.4
@@ -394,6 +394,29 @@ select engine number to read private key
 
 The engines are numbered starting from 1.
 
+=item B<evilconnect> = [host:]port
+
+connect to a remote host:port when the client's certificate cannot
+be verified
+
+This is only meaningful in server mode when I<connect> and I<verify> are used.
+Otherwise it has the same properties as the I<connect> option.
+
+=item B<evilexec> = executable_path (Unix only)
+
+execute local inetd-type program when the client's certificate cannot
+be verified
+
+This is only meaningful in server mode when I<exec> and I<verify> are used.
+Otherwise it has the same properties as the I<exec> option.
+
+=item B<evilexecargs> = $0 $1 $2 ... (Unix only)
+
+arguments for I<evilexec> including program name ($0)
+
+Quoting is currently not supported.
+Arguments are separated with arbitrary number of whitespaces.
+
 =item B<exec> = executable_path
 
 execute local inetd-type program 
Index: src/client.c
===================================================================
RCS file: /home/cvs/tataz/src/stunnel/src/client.c,v
retrieving revision 1.1.1.3
retrieving revision 1.4
diff -u -p -u -p -r1.1.1.3 -r1.4
--- src/client.c	22 Feb 2011 08:21:15 -0000	1.1.1.3
+++ src/client.c	22 Feb 2011 14:29:55 -0000	1.4
@@ -129,6 +129,7 @@ static void run_client(CLI *c) {
     c->fd=-1;
     c->ssl=NULL;
     c->sock_bytes=c->ssl_bytes=0;
+    c->evil_cert=0;
 
     error=setjmp(c->err);
     if(!error)
@@ -261,6 +262,7 @@ static void init_remote(CLI *c) {
 static void init_ssl(CLI *c) {
     int i, err;
     SSL_SESSION *old_session;
+    X509 *peer;
 
     if(!(c->ssl=SSL_new(c->opt->ctx))) {
         sslerror("SSL_new");
@@ -314,8 +316,26 @@ static void init_ssl(CLI *c) {
         leave_critical_section(CRIT_SSL);
 #endif /* OpenSSL version < 1.0.0b */
         err=SSL_get_error(c->ssl, i);
-        if(err==SSL_ERROR_NONE)
-            break; /* ok -> done */
+        if(err==SSL_ERROR_NONE) {
+	    if(c->opt->option.client)
+		break; /* ok -> done */
+	    if(!c->opt->option.evilremote && !c->opt->option.evilprogram)
+		break; /* ok -> done */
+	    peer=SSL_get_peer_certificate(c->ssl);
+	    if (!peer) {
+		if (c->opt->verify_level!=SSL_VERIFY_NONE) {
+		    s_log(LOG_ERR, "No cert, proceed to evil cert endpoint");
+		    c->evil_cert=1;
+		}
+		break;
+	    }
+	    if(SSL_get_verify_result(c->ssl)==X509_V_OK)
+		break;
+	    sslerror("SSL_accept");
+	    s_log(LOG_ERR, "Proceed to evil cert endpoint");
+	    c->evil_cert=1;
+	    break;
+	}
         if(err==SSL_ERROR_WANT_READ || err==SSL_ERROR_WANT_WRITE) {
             s_poll_init(&c->fds);
             s_poll_add(&c->fds, c->ssl_rfd->fd,
@@ -918,7 +938,10 @@ static int connect_local(CLI *c) { /* sp
         sigemptyset(&newmask);
         sigprocmask(SIG_SETMASK, &newmask, NULL);
 #endif
-        execvp(c->opt->execname, c->opt->execargs);
+	if (c->evil_cert)
+	    execvp(c->opt->evilexecname, c->opt->evilexecargs);
+	else
+	    execvp(c->opt->execname, c->opt->execargs);
         ioerror(c->opt->execname); /* execv failed */
         _exit(1);
     default: /* parent */
@@ -981,13 +1004,15 @@ static int connect_remote(CLI *c) { /* c
     if(c->opt->option.delayed_lookup) {
         resolved_list.num=0;
         if(!name2addrlist(&resolved_list,
-                c->opt->remote_address, DEFAULT_LOOPBACK)) {
+                c->evil_cert ? c->opt->evilremote_address :
+		c->opt->remote_address, DEFAULT_LOOPBACK)) {
             s_log(LOG_ERR, "No host resolved");
             longjmp(c->err, 1);
         }
         address_list=&resolved_list;
     } else /* use pre-resolved addresses */
-        address_list=&c->opt->remote_addr;
+        address_list=c->evil_cert ? &c->opt->evilremote_addr :
+		&c->opt->remote_addr;
 
     /* try to connect each host from the list */
     for(ind_try=0; ind_try<address_list->num; ind_try++) {
Index: src/options.c
===================================================================
RCS file: /home/cvs/tataz/src/stunnel/src/options.c,v
retrieving revision 1.1.1.3
retrieving revision 1.5
diff -u -p -u -p -r1.1.1.3 -r1.5
--- src/options.c	22 Feb 2011 08:21:15 -0000	1.1.1.3
+++ src/options.c	22 Feb 2011 22:57:46 -0000	1.5
@@ -818,6 +818,75 @@ static char *parse_service_option(CMD cm
     }
 #endif
 
+    /* evilconnect */
+    switch(cmd) {
+    case CMD_INIT:
+        section->option.evilremote=0;
+        section->evilremote_address=NULL;
+        section->evilremote_addr.num=0;
+        break;
+    case CMD_EXEC:
+        if(strcasecmp(opt, "evilconnect"))
+            break;
+        section->option.evilremote=1;
+        section->evilremote_address=stralloc(arg);
+        if(!section->option.delayed_lookup &&
+                !name2addrlist(&section->evilremote_addr, arg,
+		DEFAULT_LOOPBACK)) {
+            s_log(LOG_NOTICE, "Cannot resolve '%s' - delaying DNS lookup", arg);
+            section->option.delayed_lookup=1;
+        }
+        return NULL; /* OK */
+    case CMD_DEFAULT:
+        break;
+    case CMD_HELP:
+        s_log(LOG_NOTICE, "%-15s = [host:]port connect remote host:port "
+	    "on evil client cert", "evilconnect");
+        break;
+    }
+
+    /* evilexec */
+#ifndef USE_WIN32
+    switch(cmd) {
+    case CMD_INIT:
+        section->option.evilprogram=0;
+        section->evilexecname=NULL;
+        break;
+    case CMD_EXEC:
+        if(strcasecmp(opt, "evilexec"))
+            break;
+        section->option.evilprogram=1;
+        section->evilexecname=stralloc(arg);
+        return NULL; /* OK */
+    case CMD_DEFAULT:
+        break;
+    case CMD_HELP:
+        s_log(LOG_NOTICE, "%-15s = file execute local inetd-type program "
+	    "on evil client cert", "evilexec");
+        break;
+    }
+#endif
+
+    /* evilexecargs */
+#ifndef USE_WIN32
+    switch(cmd) {
+    case CMD_INIT:
+        section->evilexecargs=NULL;
+        break;
+    case CMD_EXEC:
+        if(strcasecmp(opt, "evilexecargs"))
+            break;
+        section->evilexecargs=argalloc(arg);
+        return NULL; /* OK */
+    case CMD_DEFAULT:
+        break;
+    case CMD_HELP:
+        s_log(LOG_NOTICE, "%-15s = arguments for 'evilexec' (including $0)",
+            "evilexecargs");
+        break;
+    }
+#endif
+
     /* exec */
     switch(cmd) {
     case CMD_INIT:
@@ -1726,6 +1795,19 @@ static int section_init(int last_line, S
                 "Each service must define two endpoints");
             return 0;
         }
+	if((section->option.evilremote || section->option.evilprogram) &&
+		section->option.client)
+	    section_error(last_line, section->servname,
+		"Evil cert actions have sense only in server mode");
+	if((section->option.evilremote || section->option.evilprogram) &&
+		section->verify_level==SSL_VERIFY_NONE)
+	    section_error(last_line, section->servname,
+		"Evil cert actions are only meaningful when verify level > 0");
+	if((section->option.evilremote && !section->option.remote) ||
+		(section->option.evilprogram && !section->option.program))
+	    section_error(last_line, section->servname,
+		"Evil cert actions can be only specified with their "
+		"program or remote node counterpart");
     }
     return 1; /* all tests passed -- continue program execution */
 }
Index: src/prototypes.h
===================================================================
RCS file: /home/cvs/tataz/src/stunnel/src/prototypes.h,v
retrieving revision 1.1.1.3
retrieving revision 1.4
diff -u -p -u -p -r1.1.1.3 -r1.4
--- src/prototypes.h	22 Feb 2011 08:21:15 -0000	1.1.1.3
+++ src/prototypes.h	22 Feb 2011 14:29:55 -0000	1.4
@@ -146,14 +146,17 @@ typedef struct service_options_struct {
         /* service-specific data for client.c */
     int fd;        /* file descriptor accepting connections for this service */
     char *execname; /* program name for local mode */
+    char *evilexecname; /* same, with bad certificate */
 #ifdef USE_WIN32
     char *execargs; /* program arguments for local mode */
+    char *evilexecargs; /* same, with bad certificate */
 #else
     char **execargs; /* program arguments for local mode */
+    char **evilexecargs; /* same, with bad certificate */
 #endif
-    SOCKADDR_LIST local_addr, remote_addr, source_addr;
+    SOCKADDR_LIST local_addr, remote_addr, source_addr, evilremote_addr;
     char *username;
-    char *remote_address;
+    char *remote_address, *evilremote_address;
     int timeout_busy; /* maximum waiting for data time */
     int timeout_close; /* maximum close_notify time */
     int timeout_connect; /* maximum connect() time */
@@ -173,9 +176,11 @@ typedef struct service_options_struct {
         unsigned int delayed_lookup:1;
         unsigned int accept:1;
         unsigned int remote:1;
+        unsigned int evilremote:1;
         unsigned int retry:1; /* loop remote+program */
         unsigned int sessiond:1;
         unsigned int program:1;
+        unsigned int evilprogram:1;
 #ifndef USE_WIN32
         unsigned int pty:1;
         unsigned int transparent_src:1;
@@ -343,6 +348,7 @@ typedef struct {
     unsigned long pid; /* PID of local process */
     int fd; /* temporary file descriptor */
     jmp_buf err;
+    int evil_cert; /* Certificate couldn't be verified */
 
     char sock_buff[BUFFSIZE]; /* socket read buffer */
     char ssl_buff[BUFFSIZE]; /* SSL read buffer */
Index: src/verify.c
===================================================================
RCS file: /home/cvs/tataz/src/stunnel/src/verify.c,v
retrieving revision 1.1.1.3
retrieving revision 1.4
diff -u -p -u -p -r1.1.1.3 -r1.4
--- src/verify.c	22 Feb 2011 08:21:15 -0000	1.1.1.3
+++ src/verify.c	22 Feb 2011 14:29:55 -0000	1.4
@@ -158,6 +158,7 @@ static int verify_callback(int preverify
     SSL *ssl;
     CLI *c;
     char subject_name[STRLEN];
+    int evil_cert_ok;
 
     /* retrieve application specific data */
     ssl=X509_STORE_CTX_get_ex_data(callback_ctx,
@@ -169,22 +170,23 @@ static int verify_callback(int preverify
         subject_name, STRLEN);
     safestring(subject_name);
 
+    evil_cert_ok = c->opt->option.evilremote | c->opt->option.evilprogram;
     s_log(LOG_DEBUG, "Starting certificate verification: depth=%d, %s",
         callback_ctx->error_depth, subject_name);
     if(!cert_check(c, callback_ctx, preverify_ok)) {
         s_log(LOG_WARNING, "Certificate check failed: depth=%d, %s",
             callback_ctx->error_depth, subject_name);
-        return 0; /* reject connection */
+        return evil_cert_ok; /* reject connection */
     }
     if(!crl_check(c, callback_ctx)) {
         s_log(LOG_WARNING, "CRL check failed: depth=%d, %s",
             callback_ctx->error_depth, subject_name);
-        return 0; /* reject connection */
+        return evil_cert_ok; /* reject connection */
     }
     if(c->opt->option.ocsp && !ocsp_check(c, callback_ctx)) {
         s_log(LOG_WARNING, "OCSP check failed: depth=%d, %s",
             callback_ctx->error_depth, subject_name);
-        return 0; /* reject connection */
+        return evil_cert_ok; /* reject connection */
     }
     /* errnum=X509_STORE_CTX_get_error(ctx); */
     s_log(LOG_NOTICE, "Certificate accepted: depth=%d, %s",
