MEDIUM: ssl: improve crt-list format to support negation

Improve the crt-list file format to allow a rule to negate a certain SNI :

        <crtfile> [[!]<snifilter> ...]

This can be useful when a domain supports a wildcard but you don't want to
deliver the wildcard cert for certain specific domains.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 67c18ec..eb26285 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -7224,17 +7224,19 @@
 
 crt-list <file>
   This setting is only available when support for OpenSSL was built in. It
-  designates a list of PEM file with an optional SNI filter per certificate,
-  with the following format for each line :
+  designates a list of PEM file with an optional list of SNI filter per
+  certificate, with the following format for each line :
 
-        <crtfile> [<snifilter>]
+        <crtfile> [[!]<snifilter> ...]
 
-  Wildcards are supported in the SNI filter. The certificates will be presented
-  to clients who provide a valid TLS Server Name Indication field matching one
-  of SNI filter. If no SNI filter is specified the CN and alt subjects are
-  used. This directive may be specified multiple times. See the "crt" option
-  for more information. The default certificate is still needed to meet OpenSSL
-  expectations. If it is not used, the strict-sni option may be used.
+  Wildcards are supported in the SNI filter. Negative filter are also supported,
+  only useful in combination with a wildcard filter to exclude a particular SNI.
+  The certificates will be presented to clients who provide a valid TLS Server
+  Name Indication field matching one of the SNI filters. If no SNI filter is
+  specified, the CN and alt subjects are used. This directive may be specified
+  multiple times. See the "crt" option for more information. The default
+  certificate is still needed to meet OpenSSL expectations. If it is not used,
+  the 'strict-sni' option may be used.
 
 defer-accept
   Is an optional keyword which is supported only on certain Linux kernels. It
diff --git a/include/types/ssl_sock.h b/include/types/ssl_sock.h
index 1ded15e..a0b2d79 100644
--- a/include/types/ssl_sock.h
+++ b/include/types/ssl_sock.h
@@ -28,6 +28,7 @@
 struct sni_ctx {
 	SSL_CTX *ctx;             /* context associated to the certificate */
 	int order;                /* load order for the certificate */
+	int neg;                  /* reject if match */
 	struct ebmb_node name;    /* node holding the servername value */
 };
 
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 3ee6991..34f24b5 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -189,16 +189,15 @@
 {
 	const char *servername;
 	const char *wildp = NULL;
-	struct ebmb_node *node;
+	struct ebmb_node *node, *n;
 	int i;
 	(void)al; /* shut gcc stupid warning */
 
 	servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
 	if (!servername) {
-		if (s->strict_sni)
-			return SSL_TLSEXT_ERR_ALERT_FATAL;
-		else
-			return SSL_TLSEXT_ERR_NOACK;
+		return (s->strict_sni ?
+			SSL_TLSEXT_ERR_ALERT_FATAL :
+			SSL_TLSEXT_ERR_ALERT_WARNING);
 	}
 
 	for (i = 0; i < trash.size; i++) {
@@ -212,25 +211,27 @@
 
 	/* lookup in full qualified names */
 	node = ebst_lookup(&s->sni_ctx, trash.str);
-	if (!node) {
-		if (!wildp) {
-			if (s->strict_sni)
-				return SSL_TLSEXT_ERR_ALERT_FATAL;
-			else
-				return SSL_TLSEXT_ERR_ALERT_WARNING;
+
+	/* lookup a not neg filter */
+	for (n = node; n; n = ebmb_next_dup(n)) {
+		if (!container_of(n, struct sni_ctx, name)->neg) {
+			node = n;
+			break;
 		}
-		/* lookup in full wildcards names */
+		wildp = NULL; /* never match a wildcard after matching a neg */
+	}
+	if (!node && wildp) {
+		/* lookup in wildcards names */
 		node = ebst_lookup(&s->sni_w_ctx, wildp);
-		if (!node) {
-			if (s->strict_sni)
-				return SSL_TLSEXT_ERR_ALERT_FATAL;
-			else
-				return SSL_TLSEXT_ERR_ALERT_WARNING;
-		}
+	}
+	if (!node || container_of(node, struct sni_ctx, name)->neg) {
+		return (s->strict_sni ?
+			SSL_TLSEXT_ERR_ALERT_FATAL :
+			SSL_TLSEXT_ERR_ALERT_WARNING);
 	}
 
 	/* switch ctx */
-	SSL_set_SSL_CTX(ssl,  container_of(node, struct sni_ctx, name)->ctx);
+	SSL_set_SSL_CTX(ssl, container_of(node, struct sni_ctx, name)->ctx);
 	return SSL_TLSEXT_ERR_OK;
 }
 #endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */
@@ -309,24 +310,32 @@
 }
 #endif
 
-int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, int len, int order)
+static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, char *name, int order)
 {
 	struct sni_ctx *sc;
-	int wild = 0;
-	int j;
+	int wild = 0, neg = 0;
 
-	if (len) {
-		if (*name == '*') {
-			wild = 1;
-			name++;
-			len--;
-		}
+	if (*name == '!') {
+		neg = 1;
+		name++;
+	}
+	if (*name == '*') {
+		wild = 1;
+		name++;
+	}
+	/* !* filter is a nop */
+	if (neg && wild)
+		return order;
+	if (*name) {
+		int j, len;
+		len = strlen(name);
 		sc = malloc(sizeof(struct sni_ctx) + len + 1);
 		for (j = 0; j < len; j++)
 			sc->name.key[j] = tolower(name[j]);
 		sc->name.key[len] = 0;
-		sc->order = order++;
 		sc->ctx = ctx;
+		sc->order = order++;
+		sc->neg = neg;
 		if (wild)
 			ebst_insert(&s->sni_w_ctx, &sc->name);
 		else
@@ -338,11 +347,11 @@
 /* Loads a certificate key and CA chain from a file. Returns 0 on error, -1 if
  * an early error happens and the caller must call SSL_CTX_free() by itelf.
  */
-int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct bind_conf *s, char **sni_filter, int fcount)
+static int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct bind_conf *s, char **sni_filter, int fcount)
 {
 	BIO *in;
 	X509 *x = NULL, *ca;
-	int i, len, err;
+	int i, err;
 	int ret = -1;
 	int order = 0;
 	X509_NAME *xname;
@@ -364,7 +373,7 @@
 
 	if (fcount) {
 		while (fcount--)
-			order = ssl_sock_add_cert_sni(ctx, s, sni_filter[fcount], strlen(sni_filter[fcount]), order);
+			order = ssl_sock_add_cert_sni(ctx, s, sni_filter[fcount], order);
 	}
 	else {
 #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
@@ -374,8 +383,7 @@
 				GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
 				if (name->type == GEN_DNS) {
 					if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
-						len = strlen(str);
-						order = ssl_sock_add_cert_sni(ctx, s, str, len, order);
+						order = ssl_sock_add_cert_sni(ctx, s, str, order);
 						OPENSSL_free(str);
 					}
 				}
@@ -388,8 +396,7 @@
 		while ((i = X509_NAME_get_index_by_NID(xname, NID_commonName, i)) != -1) {
 			X509_NAME_ENTRY *entry = X509_NAME_get_entry(xname, i);
 			if (ASN1_STRING_to_UTF8((unsigned char **)&str, entry->value) >= 0) {
-				len = strlen(str);
-				order = ssl_sock_add_cert_sni(ctx, s, str, len, order);
+				order = ssl_sock_add_cert_sni(ctx, s, str, order);
 				OPENSSL_free(str);
 			}
 		}
@@ -538,7 +545,7 @@
 
 int ssl_sock_load_cert_list_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, char **err)
 {
-	char thisline[65536];
+	char thisline[LINESIZE];
 	FILE *f;
 	int linenum = 0;
 	int cfgerr = 0;
@@ -591,6 +598,8 @@
 			}
 			line++;
 		}
+		if (cfgerr)
+			break;
 
 		/* empty line */
 		if (!arg)