diff -crp stunnel-4.27.0/doc/stunnel.pod stunnel-4.27/doc/stunnel.pod
*** stunnel-4.27.0/doc/stunnel.pod	Thu Apr 16 10:38:41 2009
--- stunnel-4.27/doc/stunnel.pod	Tue Jul 28 21:53:28 2009
*************** select engine number to read private key
*** 364,369 ****
--- 364,392 ----
  
  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<execargs> = $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 (Unix only)
  
  execute local inetd-type program 
diff -crp stunnel-4.27.0/src/client.c stunnel-4.27/src/client.c
*** stunnel-4.27.0/src/client.c	Thu Apr 16 10:51:43 2009
--- stunnel-4.27/src/client.c	Tue Jul 28 21:16:35 2009
*************** static void run_client(CLI *c) {
*** 143,148 ****
--- 143,149 ----
      c->fd=-1;
      c->ssl=NULL;
      c->sock_bytes=c->ssl_bytes=0;
+     c->evil_cert=0;
  
      error=setjmp(c->err);
      if(!error)
*************** static void init_remote(CLI *c) {
*** 278,283 ****
--- 279,285 ----
  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");
*************** static void init_ssl(CLI *c) {
*** 328,335 ****
              i=SSL_accept(c->ssl);
          leave_critical_section(CRIT_SSL);
          err=SSL_get_error(c->ssl, i);
!         if(err==SSL_ERROR_NONE)
!             break; /* ok -> done */
          if(err==SSL_ERROR_WANT_READ || err==SSL_ERROR_WANT_WRITE) {
              s_poll_zero(&c->fds);
              s_poll_add(&c->fds, c->ssl_rfd->fd,
--- 330,355 ----
              i=SSL_accept(c->ssl);
          leave_critical_section(CRIT_SSL);
          err=SSL_get_error(c->ssl, i);
!         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_zero(&c->fds);
              s_poll_add(&c->fds, c->ssl_rfd->fd,
*************** static int connect_local(CLI *c) { /* sp
*** 838,844 ****
          sigemptyset(&newmask);
          sigprocmask(SIG_SETMASK, &newmask, NULL);
  #endif
!         execvp(c->opt->execname, c->opt->execargs);
          ioerror(c->opt->execname); /* execv failed */
          _exit(1);
      default:
--- 858,867 ----
          sigemptyset(&newmask);
          sigprocmask(SIG_SETMASK, &newmask, NULL);
  #endif
! 	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:
*************** static int connect_remote(CLI *c) { /* c
*** 914,926 ****
      if(c->opt->option.delayed_lookup) {
          resolved_list.num=0;
          if(!name2addrlist(&resolved_list,
!                 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;
  
      /* try to connect each host from the list */
      for(ind_try=0; ind_try<address_list->num; ind_try++) {
--- 937,951 ----
      if(c->opt->option.delayed_lookup) {
          resolved_list.num=0;
          if(!name2addrlist(&resolved_list,
!                 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->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++) {
diff -crp stunnel-4.27.0/src/options.c stunnel-4.27/src/options.c
*** stunnel-4.27.0/src/options.c	Thu Apr 16 10:49:20 2009
--- stunnel-4.27/src/options.c	Tue Jul 28 21:55:30 2009
*************** static char *service_options(CMD cmd, LO
*** 781,786 ****
--- 781,855 ----
      }
  #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_RAW, "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_RAW, "%-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_RAW, "%-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_RAW, "%-15s = arguments for 'evilexec' (including $0)",
+             "evilexecargs");
+         break;
+     }
+ #endif
+ 
      /* exec */
  #ifndef USE_WIN32
      switch(cmd) {
*************** static void section_validate(char *filen
*** 1534,1539 ****
--- 1603,1620 ----
  #endif
          config_error(filename, line_number,
              "Each service section must define exactly two endpoints");
+     if((section->option.evilremote || section->option.evilprogram) &&
+ 	    section->option.client)
+ 	config_error(filename, line_number,
+ 	    "Evil cert actions have sense only in server mode");
+     if((section->option.evilremote || section->option.evilprogram) &&
+ 	    section->verify_level==SSL_VERIFY_NONE)
+ 	config_error(filename, line_number,
+ 	    "Evil cert actions are only meaningful when verify level > 0");
+     if((section->option.evilremote && !section->option.remote) ||
+ 	    (section->option.evilprogram && !section->option.program))
+ 	config_error(filename, line_number,
+ 	    "Evil cert actions can be only specified with their counterpart");
      return; /* All tests passed -- continue program execution */
  }
  
diff -crp stunnel-4.27.0/src/prototypes.h stunnel-4.27/src/prototypes.h
*** stunnel-4.27.0/src/prototypes.h	Thu Apr 16 10:52:20 2009
--- stunnel-4.27/src/prototypes.h	Tue Jul 28 21:18:14 2009
*************** typedef struct local_options {
*** 205,214 ****
          /* service-specific data for client.c */
      int fd;        /* file descriptor accepting connections for this service */
      char *execname, **execargs; /* program name and arguments for local mode */
      SOCKADDR_LIST local_addr, remote_addr;
      SOCKADDR_LIST source_addr;
      char *username;
!     char *remote_address;
      int timeout_busy; /* maximum waiting for data time */
      int timeout_close; /* maximum close_notify time */
      int timeout_connect; /* maximum connect() time */
--- 205,216 ----
          /* service-specific data for client.c */
      int fd;        /* file descriptor accepting connections for this service */
      char *execname, **execargs; /* program name and arguments for local mode */
+     char *evilexecname, **evilexecargs;
      SOCKADDR_LIST local_addr, remote_addr;
+     SOCKADDR_LIST evilremote_addr;
      SOCKADDR_LIST source_addr;
      char *username;
!     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 */
*************** typedef struct local_options {
*** 229,237 ****
--- 231,241 ----
          unsigned int delayed_lookup:1;
          unsigned int accept:1;
          unsigned int remote:1;
+         unsigned int evilremote:1;
          unsigned int retry:1; /* loop remote+program */
  #ifndef USE_WIN32
          unsigned int program:1;
+         unsigned int evilprogram:1;
          unsigned int pty:1;
          unsigned int transparent:1;
  #endif
*************** typedef struct {
*** 325,330 ****
--- 329,335 ----
      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 */
diff -crp stunnel-4.27.0/src/verify.c stunnel-4.27/src/verify.c
*** stunnel-4.27.0/src/verify.c	Thu Apr 16 10:54:17 2009
--- stunnel-4.27/src/verify.c	Tue Jul 28 21:17:39 2009
*************** static int verify_callback(int preverify
*** 153,158 ****
--- 153,159 ----
      SSL *ssl;
      CLI *c;
      char subject_name[STRLEN];
+     int evil_cert_ok;
  
      X509_NAME_oneline(X509_get_subject_name(callback_ctx->current_cert),
          subject_name, STRLEN);
*************** static int verify_callback(int preverify
*** 163,176 ****
      ssl=X509_STORE_CTX_get_ex_data(callback_ctx,
          SSL_get_ex_data_X509_STORE_CTX_idx());
      c=SSL_get_ex_data(ssl, cli_index);
  
      if(!cert_check(c, callback_ctx, subject_name, preverify_ok))
!         return 0; /* reject connection */
      if(!crl_check(c, callback_ctx, subject_name))
!         return 0; /* reject connection */
  #if SSLEAY_VERSION_NUMBER >= 0x00907000L
      if(c->opt->option.ocsp && !ocsp_check(c, callback_ctx, subject_name))
!         return 0; /* reject connection */
  #endif /* OpenSSL-0.9.7 */
  
      /* errnum=X509_STORE_CTX_get_error(ctx); */
--- 164,178 ----
      ssl=X509_STORE_CTX_get_ex_data(callback_ctx,
          SSL_get_ex_data_X509_STORE_CTX_idx());
      c=SSL_get_ex_data(ssl, cli_index);
+     evil_cert_ok = c->opt->option.evilremote | c->opt->option.evilprogram;
  
      if(!cert_check(c, callback_ctx, subject_name, preverify_ok))
!         return evil_cert_ok; /* reject connection */
      if(!crl_check(c, callback_ctx, subject_name))
!         return evil_cert_ok; /* reject connection */
  #if SSLEAY_VERSION_NUMBER >= 0x00907000L
      if(c->opt->option.ocsp && !ocsp_check(c, callback_ctx, subject_name))
!         return evil_cert_ok; /* reject connection */
  #endif /* OpenSSL-0.9.7 */
  
      /* errnum=X509_STORE_CTX_get_error(ctx); */
