Hello and thank you for stunnel!  After switching from my web-server's
built-in SSL support, I now have a much more secure system and SNI,
too!

In stunnel(8), I read this about the sni option:

"sni option can also be specified more than once within a single slave
service."

However, I couldn't get it to work, and reading the source code
verified that only one sni option is tracked per section.

So, I present my patch to allow multiple sni options.  It enables
configurations like these:

  [https]
  ...

  [group1]
  sni = https:a.example.org
  sni = https:b.example.org
  ...

  [group2]
  sni = https:c.example.org
  sni = https:d.example.org
  ...

If you have a naming scheme that doesn't lend itself to the wildcard
matching already supported, this can cut your config size quite a bit.



DETAILS

The sni option for any section was originally tracked in the
section->sni member (a string).  Now, a linked-list of sni options is
added to the section structure, with every sni option being appended
to the end.

For server setup, each slave section walks over its list, splitting
each sni option and adding that slave section to the master's slave
list.  Splitting options and adding slaves to masters works as before,
but now the slave section iterates over a list instead of referencing
a single string.

For client setup, each slave section checks that it has either no sni
options, or exactly one.  Multiple sni options results in an error.
If exactly one option is found, it is copied to the old sni field of
the section struct.  The old sni field has been renamed to
servername_to_request, as it is now used only in client mode, and this
makes its purpose more clear.  When no sni option is found,
servername_to_request is initialized from the protocolHost or connect
options, as before.



I hope this patch is useful to others.  Please let me know how I can
make it acceptable for inclusing in the stunnel distribution.

Thanks,
David
=== modified file 'src/client.c'
--- old/src/client.c	2013-09-27 14:48:19 +0000
+++ new/src/client.c	2013-09-27 19:11:46 +0000
@@ -336,9 +336,9 @@
     SSL_set_ex_data(c->ssl, cli_index, c); /* for callbacks */
     if(c->opt->option.client) {
 #ifndef OPENSSL_NO_TLSEXT
-        if(c->opt->sni) {
-            s_log(LOG_DEBUG, "SNI: sending servername: %s", c->opt->sni);
-            if(!SSL_set_tlsext_host_name(c->ssl, c->opt->sni)) {
+        if(c->opt->servername_to_request) {
+            s_log(LOG_DEBUG, "SNI: sending servername: %s", c->opt->servername_to_request);
+            if(!SSL_set_tlsext_host_name(c->ssl, c->opt->servername_to_request)) {
                 sslerror("SSL_set_tlsext_host_name");
                 longjmp(c->err, 1);
             }

=== modified file 'src/options.c'
--- old/src/options.c	2013-09-27 14:48:19 +0000
+++ new/src/options.c	2013-09-27 19:21:18 +0000
@@ -1607,7 +1607,14 @@
     case CMD_EXEC:
         if(strcasecmp(opt, "sni"))
             break;
-        section->sni=str_dup(arg);
+        if(!section->sni_list_tail) {
+            section->sni_list_head=str_alloc(sizeof(SNI_OPT));
+            section->sni_list_tail=section->sni_list_head;
+        } else {
+            section->sni_list_tail->next=str_alloc(sizeof(SNI_OPT));
+            section->sni_list_tail=section->sni_list_tail->next;
+        }
+        section->sni_list_tail->sni=strdup(arg);
         return NULL; /* OK */
     case CMD_END:
         tmpstr=init_sni(section);
@@ -2126,54 +2133,68 @@
 
 #ifndef OPENSSL_NO_TLSEXT
 static char *init_sni(SERVICE_OPTIONS *section) {
+    SNI_OPT *tmpsni;
     char *tmpstr;
     SERVICE_OPTIONS *tmpsrv;
 
-    /* server mode: update servername_list based on SNI option */
-    if(!section->option.client && section->sni) {
-        tmpstr=strchr(section->sni, ':');
-        if(!tmpstr)
-            return "Invalid SNI parameter format";
-        *tmpstr++='\0';
-        for(tmpsrv=new_service_options.next; tmpsrv; tmpsrv=tmpsrv->next)
-            if(!strcmp(tmpsrv->servname, section->sni))
-                break;
-        if(!tmpsrv)
-            return "SNI section name not found";
-        if(tmpsrv->option.client)
-            return "SNI master service is a TLS client";
-        if(tmpsrv->servername_list_tail) {
-            tmpsrv->servername_list_tail->next=str_alloc(sizeof(SERVERNAME_LIST));
-            tmpsrv->servername_list_tail=tmpsrv->servername_list_tail->next;
-        } else { /* first virtual service */
-            tmpsrv->servername_list_head=
-                tmpsrv->servername_list_tail=
-                str_alloc(sizeof(SERVERNAME_LIST));
-            tmpsrv->ssl_options|=SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
-        }
-        tmpsrv->servername_list_tail->servername=str_dup(tmpstr);
-        tmpsrv->servername_list_tail->opt=section;
-        tmpsrv->servername_list_tail->next=NULL;
-        section->option.sni=1;
-        /* always negotiate a new session on renegotiation, as the SSL
-         * context settings (including access control) may be different */
-        section->ssl_options|=SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
+    /* server mode: update servername_list based on SNI options */
+    if(!section->option.client && section->sni_list_head) {
+        tmpsni=section->sni_list_head;
+        do {
+            tmpstr=strchr(tmpsni->sni, ':');
+            if(!tmpstr)
+                return "Invalid SNI parameter format";
+            *tmpstr++='\0';
+            for(tmpsrv=new_service_options.next; tmpsrv; tmpsrv=tmpsrv->next)
+                if(!strcmp(tmpsrv->servname, tmpsni->sni))
+                    break;
+            if(!tmpsrv)
+                return "SNI section name not found";
+            if(tmpsrv->option.client)
+                return "SNI master service is a TLS client";
+            if(tmpsrv->servername_list_tail) {
+                tmpsrv->servername_list_tail->next=str_alloc(sizeof(SERVERNAME_LIST));
+                tmpsrv->servername_list_tail=tmpsrv->servername_list_tail->next;
+            } else { /* first virtual service */
+                tmpsrv->servername_list_head=
+                    tmpsrv->servername_list_tail=
+                    str_alloc(sizeof(SERVERNAME_LIST));
+                tmpsrv->ssl_options|=SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
+            }
+            tmpsrv->servername_list_tail->servername=str_dup(tmpstr);
+            tmpsrv->servername_list_tail->opt=section;
+            tmpsrv->servername_list_tail->next=NULL;
+            section->option.sni=1;
+            /* always negotiate a new session on renegotiation, as the
+             * SSL context settings (including access control) may be
+             * different */
+            section->ssl_options|=SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
+            tmpsni=tmpsni->next;
+        } while(tmpsni);
     }
 
     /* client mode: setup SNI default based on 'protocolHost' and 'connect' options */
-    if(section->option.client && !section->sni) {
-        /* setup host_name for SNI, prefer SNI and protocolHost if specified */
-        if(section->protocol_host) /* 'protocolHost' option */
-            section->sni=str_dup(section->protocol_host);
-        else if(section->connect_list) /* 'connect' option */
-            section->sni=str_dup(section->connect_list->name); /* first hostname */
-        if(section->sni) { /* either 'protocolHost' or 'connect' specified */
-            tmpstr=strrchr(section->sni, ':');
-            if(tmpstr) { /* 'host:port' -> drop ':port' */
-                *tmpstr='\0';
-            } else { /* 'port' -> default to 'localhost' */
-                str_free(section->sni);
-                section->sni=str_dup("localhost");
+    if(section->option.client) {
+        if(section->sni_list_head) {
+            if(section->sni_list_head->next)
+                return "Only one sni option is permitted in client mode";
+            section->servername_to_request=strdup(section->sni_list_head->sni);
+        }
+        if(!section->servername_to_request) {
+            /* setup host_name for SNI, prefer SNI and protocolHost if
+             * specified */
+            if(section->protocol_host) /* 'protocolHost' option */
+                section->servername_to_request=str_dup(section->protocol_host);
+            else if(section->connect_list) /* 'connect' option */
+                section->servername_to_request=str_dup(section->connect_list->name); /* first hostname */
+            if(section->servername_to_request) { /* either 'protocolHost' or 'connect' specified */
+                tmpstr=strrchr(section->servername_to_request, ':');
+                if(tmpstr) { /* 'host:port' -> drop ':port' */
+                    *tmpstr='\0';
+                } else { /* 'port' -> default to 'localhost' */
+                    str_free(section->servername_to_request);
+                    section->servername_to_request=str_dup("localhost");
+                }
             }
         }
     }

=== modified file 'src/prototypes.h'
--- old/src/prototypes.h	2013-09-27 14:48:19 +0000
+++ new/src/prototypes.h	2013-09-27 19:10:58 +0000
@@ -124,6 +124,11 @@
 typedef struct servername_list_struct SERVERNAME_LIST;/* forward declaration */
 #endif
 
+typedef struct sni_opt_struct {
+    const char *sni;
+    struct sni_opt_struct *next;
+} SNI_OPT;
+
 typedef struct service_options_struct {
     struct service_options_struct *next;   /* next node in the services list */
     SSL_CTX *ctx;                                            /*  SSL context */
@@ -156,7 +161,8 @@
     SSL_METHOD *client_method, *server_method;
     SOCKADDR_UNION sessiond_addr;
 #ifndef OPENSSL_NO_TLSEXT
-    char *sni;
+    SNI_OPT *sni_list_head, *sni_list_tail;
+    char *servername_to_request;
     SERVERNAME_LIST *servername_list_head, *servername_list_tail;
 #endif
 #ifndef OPENSSL_NO_ECDH

_______________________________________________
stunnel-users mailing list
[email protected]
https://www.stunnel.org/cgi-bin/mailman/listinfo/stunnel-users

Reply via email to