Hello Emeric,

On Tue, May 22, 2018 at 05:37:58PM +0200, Emeric Brun wrote:
> Hi Auréline
> 
> I see that you're using the domain to known the certificate to delete.
> 
> If you take a look to crt-list, you will see that the identifier of the 
> certificate
> is customizable and is not necessarily the domain.
> 
> I think to perform the adds/delete operation we should use the same 
> identifiers than the crt-list option
> 

Right, I forgot about these.
It's now possible to use one of the identifiers (if any) of a
certificate from a crt-list in the deletion.

I added a refcount in the struct sni_ctx, added wildcard support that I
forgot and corrected a typo in an error message.

It's still RFC patches.

-- 
Aurélien.
>From 9612ff9dcfda0611e5287ea0e9d9959d972e0061 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aur=C3=A9lien=20Nephtali?= <aurelien.nepht...@corp.ovh.com>
Date: Wed, 18 Apr 2018 15:34:33 +0200
Subject: [PATCH 1/2] MINOR/WIP: ssl: Refactor ssl_sock_load_cert_file() into
 ssl_sock_load_cert2()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

    - ssl_sock_get_dh_from_file() -> ssl_sock_get_dh()
    - ssl_sock_load_dh_params() takes a BIO * instead of a char *
    - ssl_sock_load_cert_chain_file() -> ssl_sock_load_cert_chain() + takes a
      BIO * instead of a char *

Signed-off-by: Aurélien Nephtali <aurelien.nepht...@corp.ovh.com>
---
 src/ssl_sock.c | 210 +++++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 136 insertions(+), 74 deletions(-)

diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 7a602ad57..eb0d43ded 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -2583,43 +2583,39 @@ static DH *ssl_get_tmp_dh(SSL *ssl, int export, int keylen)
 	return dh;
 }
 
-static DH * ssl_sock_get_dh_from_file(const char *filename)
+static DH * ssl_sock_get_dh(BIO *in)
 {
-	DH *dh = NULL;
-	BIO *in = BIO_new(BIO_s_file());
+	return PEM_read_bio_DHparams(in, NULL, NULL, NULL);
+}
 
-	if (in == NULL)
-		goto end;
+int ssl_sock_load_global_dh_param_from_file(const char *filename)
+{
+	BIO *in;
+	int ret = -1;
+
+	in = BIO_new(BIO_s_file());
+	if (!in)
+		return -1;
 
 	if (BIO_read_filename(in, filename) <= 0)
 		goto end;
 
-	dh = PEM_read_bio_DHparams(in, NULL, NULL, NULL);
+	global_dh = ssl_sock_get_dh(in);
+	if (global_dh)
+		ret = 0;
 
 end:
-        if (in)
-                BIO_free(in);
+	BIO_free(in);
 
-	return dh;
-}
-
-int ssl_sock_load_global_dh_param_from_file(const char *filename)
-{
-	global_dh = ssl_sock_get_dh_from_file(filename);
-
-	if (global_dh) {
-		return 0;
-	}
-
-	return -1;
+	return ret;
 }
 
 /* Loads Diffie-Hellman parameter from a file. Returns 1 if loaded, else -1
    if an error occured, and 0 if parameter not found. */
-int ssl_sock_load_dh_params(SSL_CTX *ctx, const char *file)
+int ssl_sock_load_dh_params(SSL_CTX *ctx, BIO *in)
 {
 	int ret = -1;
-	DH *dh = ssl_sock_get_dh_from_file(file);
+	DH *dh = ssl_sock_get_dh(in);
 
 	if (dh) {
 		ret = 1;
@@ -3192,10 +3188,9 @@ static int ssl_sock_load_multi_cert(const char *path, struct bind_conf *bind_con
 /* 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.
  */
-static int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct bind_conf *s,
-					 struct ssl_bind_conf *ssl_conf, char **sni_filter, int fcount)
+static int ssl_sock_load_cert_chain(SSL_CTX *ctx, BIO *in, struct bind_conf *s,
+				    struct ssl_bind_conf *ssl_conf, char **sni_filter, int fcount)
 {
-	BIO *in;
 	X509 *x = NULL, *ca;
 	int i, err;
 	int ret = -1;
@@ -3211,14 +3206,6 @@ static int ssl_sock_load_cert_chain_file(SSL_CTX *ctx, const char *file, struct
 	STACK_OF(GENERAL_NAME) *names;
 #endif
 
-	in = BIO_new(BIO_s_file());
-	if (in == NULL)
-		goto end;
-
-	if (BIO_read_filename(in, file) <= 0)
-		goto end;
-
-
 	passwd_cb = SSL_CTX_get_default_passwd_cb(ctx);
 	passwd_cb_userdata = SSL_CTX_get_default_passwd_cb_userdata(ctx);
 
@@ -3308,44 +3295,110 @@ end:
 	if (x)
 		X509_free(x);
 
-	if (in)
-		BIO_free(in);
-
 	return ret;
 }
 
-static int ssl_sock_load_cert_file(const char *path, struct bind_conf *bind_conf, struct ssl_bind_conf *ssl_conf,
-				   char **sni_filter, int fcount, char **err)
+struct ssl_input {
+    const char *cert_path;
+    struct chunk *cert_buf;
+};
+
+static int ssl_sock_load_cert2(struct ssl_input *si, struct bind_conf *bind_conf, struct ssl_bind_conf *ssl_conf,
+                               char **sni_filter, int fcount, char **err)
 {
 	int ret;
 	SSL_CTX *ctx;
+	BIO *in;
+
+	if (!si->cert_path && (!si->cert_buf || !si->cert_buf->len))
+		return 1;
 
 	ctx = SSL_CTX_new(SSLv23_server_method());
 	if (!ctx) {
-		memprintf(err, "%sunable to allocate SSL context for cert '%s'.\n",
-		          err && *err ? *err : "", path);
+		if (si->cert_path)
+			memprintf(err, "%sunable to allocate SSL context for cert '%s'.\n",
+				  err && *err ? *err : "", si->cert_path);
+		else
+			memprintf(err, "%sunable to allocate SSL context.\n",
+				  err && *err ? *err : "");
 		return 1;
 	}
 
-	if (SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM) <= 0) {
-		memprintf(err, "%sunable to load SSL private key from PEM file '%s'.\n",
-		          err && *err ? *err : "", path);
-		SSL_CTX_free(ctx);
-		return 1;
+	if (si->cert_path) {
+		if (SSL_CTX_use_PrivateKey_file(ctx, si->cert_path, SSL_FILETYPE_PEM) <= 0) {
+			memprintf(err, "%sunable to load SSL private key from PEM file '%s'.\n",
+				  err && *err ? *err : "", si->cert_path);
+			SSL_CTX_free(ctx);
+			return 1;
+		}
+
+		in = BIO_new(BIO_s_file());
+		if (!in) {
+			SSL_CTX_free(ctx);
+			return 1;
+		}
+
+		if (BIO_read_filename(in, si->cert_path) <= 0) {
+			SSL_CTX_free(ctx);
+			BIO_free(in);
+			return 1;
+		}
 	}
+	else {
+		EVP_PKEY *pkey;
+
+		in = BIO_new_mem_buf(si->cert_buf->str, si->cert_buf->len);
+		if (!in) {
+			memprintf(err, "%sunable to create a memory buffer.\n",
+				  err && *err ? *err : "");
+			SSL_CTX_free(ctx);
+			return 1;
+		}
 
-	ret = ssl_sock_load_cert_chain_file(ctx, path, bind_conf, ssl_conf, sni_filter, fcount);
+		pkey = PEM_read_bio_PrivateKey(in, NULL,
+					       SSL_CTX_get_default_passwd_cb(ctx),
+					       SSL_CTX_get_default_passwd_cb_userdata(ctx));
+		if (!pkey) {
+			memprintf(err, "%sunable to read the private key.\n",
+				  err && *err ? *err : "");
+			BIO_free(in);
+			SSL_CTX_free(ctx);
+			return 1;
+		}
+		ret = SSL_CTX_use_PrivateKey(ctx, pkey);
+		EVP_PKEY_free(pkey);
+		if (ret <= 0) {
+			memprintf(err, "%sunable to use the private key.\n",
+				  err && *err ? *err : "");
+			BIO_free(in);
+			SSL_CTX_free(ctx);
+			return 1;
+		}
+	}
+
+	BIO_reset(in);
+	ret = ssl_sock_load_cert_chain(ctx, in, bind_conf, ssl_conf, sni_filter, fcount);
 	if (ret <= 0) {
-		memprintf(err, "%sunable to load SSL certificate from PEM file '%s'.\n",
-		          err && *err ? *err : "", path);
+		if (si->cert_path)
+			memprintf(err, "%sunable to load SSL certificate from PEM file '%s'.\n",
+				  err && *err ? *err : "", si->cert_path);
+		else
+			memprintf(err, "%sunable to load SSL certificate.\n",
+				  err && *err ? *err : "");
+		BIO_free(in);
 		if (ret < 0) /* serious error, must do that ourselves */
 			SSL_CTX_free(ctx);
 		return 1;
 	}
 
 	if (SSL_CTX_check_private_key(ctx) <= 0) {
-		memprintf(err, "%sinconsistencies between private key and certificate loaded from PEM file '%s'.\n",
-		          err && *err ? *err : "", path);
+		if (si->cert_path)
+			memprintf(err, "%sinconsistencies between private key and certificate loaded from PEM file '%s'.\n",
+				  err && *err ? *err : "", si->cert_path);
+		else
+			memprintf(err, "%sinconsistencies between private key and certificate.\n",
+				  err && *err ? *err : "");
+		BIO_free(in);
 		return 1;
 	}
 
@@ -3359,38 +3412,47 @@ static int ssl_sock_load_cert_file(const char *path, struct bind_conf *bind_conf
 		SSL_CTX_set_ex_data(ctx, ssl_dh_ptr_index, NULL);
 	}
 
-	ret = ssl_sock_load_dh_params(ctx, path);
+	BIO_reset(in);
+	ret = ssl_sock_load_dh_params(ctx, in);
+	BIO_free(in);
 	if (ret < 0) {
-		if (err)
-			memprintf(err, "%sunable to load DH parameters from file '%s'.\n",
-				  *err ? *err : "", path);
+		if (err) {
+			if (si->cert_path)
+				memprintf(err, "%sunable to load DH parameters from file '%s'.\n",
+					  *err ? *err : "", si->cert_path);
+			else
+				memprintf(err, "%sunable to load DH parameters.\n",
+					  *err ? *err : "");
+		}
 		return 1;
 	}
 #endif
 
+	if (si->cert_path) {
 #if (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP)
-	ret = ssl_sock_load_ocsp(ctx, path);
-	if (ret < 0) {
-		if (err)
-			memprintf(err, "%s '%s.ocsp' is present and activates OCSP but it is impossible to compute the OCSP certificate ID (maybe the issuer could not be found)'.\n",
-				  *err ? *err : "", path);
-		return 1;
-	}
+		ret = ssl_sock_load_ocsp(ctx, si->cert_path);
+		if (ret < 0) {
+			if (err)
+				memprintf(err, "%s '%s.ocsp' is present and activates OCSP but it is impossible to compute the OCSP certificate ID (maybe the issuer could not be found)'.\n",
+					  *err ? *err : "", si->cert_path);
+			return 1;
+		}
 #elif (defined OPENSSL_IS_BORINGSSL)
-	ssl_sock_set_ocsp_response_from_file(ctx, path);
+		ssl_sock_set_ocsp_response_from_file(ctx, si->cert_path);
 #endif
 
 #if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL && !defined LIBRESSL_VERSION_NUMBER)
-	if (sctl_ex_index >= 0) {
-		ret = ssl_sock_load_sctl(ctx, path);
-		if (ret < 0) {
-			if (err)
-				memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n",
-					  *err ? *err : "", path);
-			return 1;
+		if (sctl_ex_index >= 0) {
+			ret = ssl_sock_load_sctl(ctx, si->cert_path);
+			if (ret < 0) {
+				if (err)
+					memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n",
+						  *err ? *err : "", si->cert_path);
+				return 1;
+			}
 		}
-	}
 #endif
+	}
 
 #ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME
 	if (bind_conf->default_ctx) {
@@ -3424,7 +3486,7 @@ int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, char **err)
 	if (stat(path, &buf) == 0) {
 		dir = opendir(path);
 		if (!dir)
-			return ssl_sock_load_cert_file(path, bind_conf, NULL, NULL, 0, err);
+			return ssl_sock_load_cert2(&(struct ssl_input){ .cert_path = path }, bind_conf, NULL, NULL, 0, err);
 
 		/* strip trailing slashes, including first one */
 		for (end = path + strlen(path) - 1; end >= path && *end == '/'; end--)
@@ -3492,7 +3554,7 @@ int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, char **err)
 				}
 
 #endif
-				cfgerr += ssl_sock_load_cert_file(fp, bind_conf, NULL, NULL, 0, err);
+				cfgerr += ssl_sock_load_cert2(&(struct ssl_input){ .cert_path = fp }, bind_conf, NULL, NULL, 0, err);
 ignore_entry:
 				free(de);
 			}
@@ -3683,8 +3745,8 @@ int ssl_sock_load_cert_list_file(char *file, struct bind_conf *bind_conf, struct
 		}
 
 		if (stat(crt_path, &buf) == 0) {
-			cfgerr = ssl_sock_load_cert_file(crt_path, bind_conf, ssl_conf,
-							 &args[cur_arg], arg - cur_arg - 1, err);
+			cfgerr = ssl_sock_load_cert2(&(struct ssl_input){ .cert_path = crt_path }, bind_conf, ssl_conf,
+						     &args[cur_arg], arg - cur_arg - 1, err);
 		} else {
 			cfgerr = ssl_sock_load_multi_cert(crt_path, bind_conf, ssl_conf,
 							  &args[cur_arg], arg - cur_arg - 1, err);
-- 
2.11.0

>From 6e773ddc07ba720c0999cc20b67b2a0745ba2614 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aur=C3=A9lien=20Nephtali?= <aurelien.nepht...@corp.ovh.com>
Date: Wed, 18 Apr 2018 18:20:12 +0200
Subject: [PATCH 2/2] MINOR/WIP: ssl: Add "add ssl cert" and "del ssl cert"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Aurélien Nephtali <aurelien.nepht...@corp.ovh.com>
---
 doc/management.txt         |  20 ++++
 include/common/hathreads.h |   4 +
 include/types/listener.h   |   1 +
 include/types/ssl_sock.h   |   6 +
 src/ssl_sock.c             | 266 +++++++++++++++++++++++++++++++++++++++------
 5 files changed, 263 insertions(+), 34 deletions(-)

diff --git a/doc/management.txt b/doc/management.txt
index a2e8d8fc3..e724fdda7 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -1736,6 +1736,26 @@ set ssl tls-key <id> <tlskey>
   #<id> or <file> returned by "show tls-keys". <tlskey> is a base64 encoded 48
   bit TLS ticket key (ex. openssl rand -base64 48).
 
+add ssl cert <frontend[:bind_name]> <PEM certificate as payload>
+  Add a new SSL certificate to a frontend. If no bind_name is specified, the
+  certificate is added on all listeners. If per-listener certificates is to be
+  used, the listeners must be named (using the "name" option of the "bind"
+  line) in the configuration.
+
+  Example:
+    echo -e "add ssl cert fe:bind_foo <<\n$(cat cert.pem)\n" | socat /tmp/sock1 -
+
+del ssl cert <frontend[:bind_name]> <domain>
+  Delete an SSL certificate from a frontend. If no bind_name is specified, the
+  certificate is removed from all listeners. If per-listener certificates is to
+  be used, the listeners must be named (using the "name" option of the "bind"
+  line) in the configuration.
+
+  Example:
+    echo "del ssl cert fe:bind_foo example.com" | socat /tmp/sock1 -
+
+  The default certificate cannot be deleted.
+
 set table <table> key <key> [data.<data_type> <value>]*
   Create or update a stick-table entry in the table. If the key is not present,
   an entry is inserted. See stick-table in section 4.2 to find all possible
diff --git a/include/common/hathreads.h b/include/common/hathreads.h
index e27ecc63f..b217243a0 100644
--- a/include/common/hathreads.h
+++ b/include/common/hathreads.h
@@ -297,6 +297,8 @@ enum lock_label {
 	START_LOCK,
 	TLSKEYS_REF_LOCK,
 	PENDCONN_LOCK,
+	SSL_SNI_LOCK,
+	SSL_SNI_CTX_LOCK,
 	LOCK_LABELS
 };
 struct lock_stat {
@@ -415,6 +417,8 @@ static inline const char *lock_label(enum lock_label label)
 	case START_LOCK:           return "START";
 	case TLSKEYS_REF_LOCK:     return "TLSKEYS_REF";
 	case PENDCONN_LOCK:        return "PENDCONN";
+	case SSL_SNI_LOCK:         return "SSL_SNI";
+	case SSL_SNI_CTX_LOCK:     return "SSL_SNI_CTX_LOCK";
 	case LOCK_LABELS:          break; /* keep compiler happy */
 	};
 	/* only way to come here is consecutive to an internal bug */
diff --git a/include/types/listener.h b/include/types/listener.h
index c55569cd3..05e1d94d3 100644
--- a/include/types/listener.h
+++ b/include/types/listener.h
@@ -147,6 +147,7 @@ struct bind_conf {
 	int ssl_options;           /* ssl options */
 	struct eb_root sni_ctx;    /* sni_ctx tree of all known certs full-names sorted by name */
 	struct eb_root sni_w_ctx;  /* sni_ctx tree of all known certs wildcards sorted by name */
+	__decl_hathreads(HA_RWLOCK_T sni_lock); /* used to protect sni_ctx and sni_w_ctx */
 	struct tls_keys_ref *keys_ref; /* TLS ticket keys reference */
 
 	char *ca_sign_file;        /* CAFile used to generate and sign server certificates */
diff --git a/include/types/ssl_sock.h b/include/types/ssl_sock.h
index 8a5b3eeb8..63f1b0d64 100644
--- a/include/types/ssl_sock.h
+++ b/include/types/ssl_sock.h
@@ -32,12 +32,18 @@ struct pkey_info {
 	uint16_t bits;        /* key size in bits */
 };
 
+struct sni_ctx_lock {
+	unsigned int ref_count;
+	__decl_hathreads(HA_SPINLOCK_T lock); /* spin lock to protect the refcount */
+};
+
 struct sni_ctx {
 	SSL_CTX *ctx;             /* context associated to the certificate */
 	int order;                /* load order for the certificate */
 	uint8_t neg;              /* reject if match */
 	struct pkey_info kinfo;   /* pkey info */
 	struct ssl_bind_conf *conf; /* ssl "bind" conf for the certificate */
+	struct sni_ctx_lock *ctx_lock;
 	struct ebmb_node name;    /* node holding the servername value */
 };
 
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index eb0d43ded..a89f651c2 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -2227,6 +2227,7 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
 	trash.str[i] = 0;
 
 	/* lookup in full qualified names */
+	HA_RWLOCK_RDLOCK(SSL_SNI_LOCK, &s->sni_lock);
 	node = ebst_lookup(&s->sni_ctx, trash.str);
 
 	/* lookup a not neg filter */
@@ -2294,8 +2295,10 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
 				if (conf->early_data)
 					allow_early = 1;
 			}
+		HA_RWLOCK_RDUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
 			goto allow_early;
 	}
+	HA_RWLOCK_RDUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
 #if (!defined SSL_NO_GENERATE_CERTIFICATES)
 	if (s->generate_certs && ssl_sock_generate_certificate(trash.str, s, ssl)) {
 		/* switch ctx done in ssl_sock_generate_certificate */
@@ -2363,6 +2366,7 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv)
 	trash.str[i] = 0;
 
 	/* lookup in full qualified names */
+	HA_RWLOCK_RDLOCK(SSL_SNI_LOCK, &s->sni_lock);
 	node = ebst_lookup(&s->sni_ctx, trash.str);
 
 	/* lookup a not neg filter */
@@ -2377,6 +2381,7 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv)
 		node = ebst_lookup(&s->sni_w_ctx, wildp);
 	}
 	if (!node || container_of(node, struct sni_ctx, name)->neg) {
+		HA_RWLOCK_RDUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
 #if (!defined SSL_NO_GENERATE_CERTIFICATES)
 		if (s->generate_certs && ssl_sock_generate_certificate(servername, s, ssl)) {
 			/* switch ctx done in ssl_sock_generate_certificate */
@@ -2391,6 +2396,7 @@ static int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *priv)
 
 	/* switch ctx */
 	ssl_sock_switchctx_set(ssl, container_of(node, struct sni_ctx, name)->ctx);
+	HA_RWLOCK_RDUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
 	return SSL_TLSEXT_ERR_OK;
 }
 #endif /* (!) OPENSSL_IS_BORINGSSL */
@@ -2661,7 +2667,7 @@ end:
 #endif
 
 static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, struct ssl_bind_conf *conf,
-				 struct pkey_info kinfo, char *name, int order)
+				 struct pkey_info kinfo, char *name, int order, struct sni_ctx **parent)
 {
 	struct sni_ctx *sc;
 	int wild = 0, neg = 0;
@@ -2688,31 +2694,54 @@ static int ssl_sock_add_cert_sni(SSL_CTX *ctx, struct bind_conf *s, struct ssl_b
 		trash.str[j] = 0;
 
 		/* Check for duplicates. */
+		HA_RWLOCK_WRLOCK(SSL_SNI_LOCK, &s->sni_lock);
 		if (wild)
 			node = ebst_lookup(&s->sni_w_ctx, trash.str);
 		else
 			node = ebst_lookup(&s->sni_ctx, trash.str);
 		for (; node; node = ebmb_next_dup(node)) {
 			sc = ebmb_entry(node, struct sni_ctx, name);
-			if (sc->ctx == ctx && sc->conf == conf && sc->neg == neg)
+			if (sc->ctx == ctx && sc->conf == conf && sc->neg == neg) {
+				HA_RWLOCK_WRUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
 				return order;
+			}
 		}
 
 		sc = malloc(sizeof(struct sni_ctx) + len + 1);
-		if (!sc)
+		if (!sc) {
+			HA_RWLOCK_WRUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
 			return order;
+		}
 		memcpy(sc->name.key, trash.str, len + 1);
 		sc->ctx = ctx;
 		sc->conf = conf;
 		sc->kinfo = kinfo;
 		sc->order = order++;
 		sc->neg = neg;
+		if (*parent) {
+			sc->ctx_lock = (*parent)->ctx_lock;
+			HA_SPIN_LOCK(SSL_SNI_CTX_LOCK, &(*parent)->ctx_lock->lock);
+			(*parent)->ctx_lock->ref_count++;
+			HA_SPIN_UNLOCK(SSL_SNI_CTX_LOCK, &(*parent)->ctx_lock->lock);
+		}
+		else {
+			sc->ctx_lock = malloc(sizeof(*sc->ctx_lock));
+			if (!sc->ctx_lock) {
+				HA_RWLOCK_WRUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
+				free(sc);
+				return order;
+			}
+			sc->ctx_lock->ref_count = 1;
+			HA_SPIN_INIT(&sc->ctx_lock->lock);
+			*parent = sc;
+		}
 		if (kinfo.sig != TLSEXT_signature_anonymous)
 			SSL_CTX_set_ex_data(ctx, ssl_pkey_info_index, &sc->kinfo);
 		if (wild)
 			ebst_insert(&s->sni_w_ctx, &sc->name);
 		else
 			ebst_insert(&s->sni_ctx, &sc->name);
+		HA_RWLOCK_WRUNLOCK(SSL_SNI_LOCK, &s->sni_lock);
 	}
 	return order;
 }
@@ -2741,6 +2770,7 @@ struct cert_key_and_chain {
 struct key_combo_ctx {
 	SSL_CTX *ctx;
 	int order;
+	struct sni_ctx *parent;
 };
 
 /* Map used for processing multiple keypairs for a single purpose
@@ -3140,7 +3170,8 @@ static int ssl_sock_load_multi_cert(const char *path, struct bind_conf *bind_con
 
 		/* Update SNI Tree */
 		key_combos[i-1].order = ssl_sock_add_cert_sni(cur_ctx, bind_conf, ssl_conf,
-							      kinfo, str, key_combos[i-1].order);
+							      kinfo, str, key_combos[i-1].order,
+							      &key_combos[i-1].parent);
 		node = ebmb_next(node);
 	}
 
@@ -3201,6 +3232,7 @@ static int ssl_sock_load_cert_chain(SSL_CTX *ctx, BIO *in, struct bind_conf *s,
 	void *passwd_cb_userdata;
 	EVP_PKEY *pkey;
 	struct pkey_info kinfo = { .sig = TLSEXT_signature_anonymous, .bits = 0 };
+	struct sni_ctx *parent = NULL;
 
 #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
 	STACK_OF(GENERAL_NAME) *names;
@@ -3230,9 +3262,38 @@ static int ssl_sock_load_cert_chain(SSL_CTX *ctx, BIO *in, struct bind_conf *s,
 		EVP_PKEY_free(pkey);
 	}
 
+	if (!SSL_CTX_use_certificate(ctx, x))
+		goto end;
+
+#ifdef SSL_CTX_clear_extra_chain_certs
+	SSL_CTX_clear_extra_chain_certs(ctx);
+#else
+	if (ctx->extra_certs != NULL) {
+		sk_X509_pop_free(ctx->extra_certs, X509_free);
+		ctx->extra_certs = NULL;
+	}
+#endif
+
+	while ((ca = PEM_read_bio_X509(in, NULL, passwd_cb, passwd_cb_userdata))) {
+		if (!SSL_CTX_add_extra_chain_cert(ctx, ca)) {
+			X509_free(ca);
+			goto end;
+		}
+	}
+
+	err = ERR_get_error();
+	if (!err || (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
+		/* we successfully reached the last cert in the file */
+		ret = 1;
+	}
+	ERR_clear_error();
+
+	if (!ret)
+		goto end;
+
 	if (fcount) {
 		while (fcount--)
-			order = ssl_sock_add_cert_sni(ctx, s, ssl_conf, kinfo, sni_filter[fcount], order);
+			order = ssl_sock_add_cert_sni(ctx, s, ssl_conf, kinfo, sni_filter[fcount], order, &parent);
 	}
 	else {
 #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
@@ -3242,7 +3303,7 @@ static int ssl_sock_load_cert_chain(SSL_CTX *ctx, BIO *in, struct bind_conf *s,
 				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) {
-						order = ssl_sock_add_cert_sni(ctx, s, ssl_conf, kinfo, str, order);
+						order = ssl_sock_add_cert_sni(ctx, s, ssl_conf, kinfo, str, order, &parent);
 						OPENSSL_free(str);
 					}
 				}
@@ -3258,39 +3319,12 @@ static int ssl_sock_load_cert_chain(SSL_CTX *ctx, BIO *in, struct bind_conf *s,
 
 			value = X509_NAME_ENTRY_get_data(entry);
 			if (ASN1_STRING_to_UTF8((unsigned char **)&str, value) >= 0) {
-				order = ssl_sock_add_cert_sni(ctx, s, ssl_conf, kinfo, str, order);
+				order = ssl_sock_add_cert_sni(ctx, s, ssl_conf, kinfo, str, order, &parent);
 				OPENSSL_free(str);
 			}
 		}
 	}
 
-	ret = 0; /* the caller must not free the SSL_CTX argument anymore */
-	if (!SSL_CTX_use_certificate(ctx, x))
-		goto end;
-
-#ifdef SSL_CTX_clear_extra_chain_certs
-	SSL_CTX_clear_extra_chain_certs(ctx);
-#else
-	if (ctx->extra_certs != NULL) {
-		sk_X509_pop_free(ctx->extra_certs, X509_free);
-		ctx->extra_certs = NULL;
-	}
-#endif
-
-	while ((ca = PEM_read_bio_X509(in, NULL, passwd_cb, passwd_cb_userdata))) {
-		if (!SSL_CTX_add_extra_chain_cert(ctx, ca)) {
-			X509_free(ca);
-			goto end;
-		}
-	}
-
-	err = ERR_get_error();
-	if (!err || (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
-		/* we successfully reached the last cert in the file */
-		ret = 1;
-	}
-	ERR_clear_error();
-
 end:
 	if (x)
 		X509_free(x);
@@ -4749,6 +4783,7 @@ int ssl_sock_prepare_all_ctx(struct bind_conf *bind_conf)
 	if (bind_conf->default_ctx)
 		err += ssl_sock_prepare_ctx(bind_conf, bind_conf->default_ssl_conf, bind_conf->default_ctx);
 
+	HA_RWLOCK_RDLOCK(SSL_SNI_LOCK, &bind_conf->sni_lock);
 	node = ebmb_first(&bind_conf->sni_ctx);
 	while (node) {
 		sni = ebmb_entry(node, struct sni_ctx, name);
@@ -4768,6 +4803,7 @@ int ssl_sock_prepare_all_ctx(struct bind_conf *bind_conf)
 			err += ssl_sock_prepare_ctx(bind_conf, sni->conf, sni->ctx);
 		node = ebmb_next(node);
 	}
+	HA_RWLOCK_RDUNLOCK(SSL_SNI_LOCK, &bind_conf->sni_lock);
 	return err;
 }
 
@@ -4824,6 +4860,8 @@ int ssl_sock_prepare_bind_conf(struct bind_conf *bind_conf)
 	/* initialize CA variables if the certificates generation is enabled */
 	err += ssl_sock_load_ca(bind_conf);
 
+	HA_RWLOCK_INIT(&bind_conf->sni_lock);
+
 	return -err;
 }
 
@@ -8706,12 +8744,172 @@ static int cli_parse_set_ocspresponse(char **args, char *payload, struct appctx
 
 }
 
+static int cli_parse_add_ssl_cert(char **args, char *payload, struct appctx *appctx, void *private)
+{
+       char *p;
+       const char *bind_name = NULL;
+       struct proxy *px;
+       struct bind_conf *bind_conf;
+       struct chunk c;
+       int changes = 0;
+
+       if (!*args[3] || !payload) {
+               appctx->ctx.cli.severity = LOG_ERR;
+               appctx->ctx.cli.msg = "'add ssl cert' expects a frontend and a certificate (as a payload)\n";
+               appctx->st0 = CLI_ST_PRINT;
+               return 1;
+       }
+       p = strrchr(args[3], ':');
+       if (p) {
+	       *p = 0;
+	       bind_name = p + 1;
+       }
+       px = cli_find_frontend(appctx, args[3]);
+       if (!px) {
+               appctx->ctx.cli.severity = LOG_ERR;
+               appctx->ctx.cli.msg = "Couldn't find frontend.\n";
+               appctx->st0 = CLI_ST_PRINT;
+               return 1;
+       }
+
+       c.str = payload;
+       c.len = strlen(payload);
+
+       list_for_each_entry(bind_conf, &px->conf.bind, by_fe) {
+               char *err = NULL;
+               struct listener *l;
+
+               if (!bind_conf->is_ssl)
+                       continue;
+
+	       list_for_each_entry(l, &bind_conf->listeners, by_bind) {
+		       if (bind_name && (!l->name || strcmp(l->name, bind_name)))
+			       continue;
+
+		       if (ssl_sock_load_cert2(&(struct ssl_input){ .cert_buf = &c }, bind_conf, NULL, NULL, 0, &err)) {
+			       if (err) {
+				       appctx->ctx.cli.err = err;
+				       appctx->st0 = CLI_ST_PRINT_FREE;
+			       }
+			       else {
+				       appctx->ctx.cli.severity = LOG_ERR;
+				       appctx->ctx.cli.msg = "An error occured while loading the certificate.\n";
+				       appctx->st0 = CLI_ST_PRINT;
+			       }
+		       }
+		       changes++;
+
+		       break;
+	       }
+       }
+
+       if (!changes) {
+	       appctx->ctx.cli.severity = LOG_ERR;
+	       appctx->ctx.cli.msg = "The certificate was not added to any listener.\n";
+	       appctx->st0 = CLI_ST_PRINT;
+       }
+
+       return 1;
+}
+
+static int cli_parse_del_ssl_cert(char **args, char *payload, struct appctx *appctx, void *private)
+{
+	char *p;
+	const char *bind_name = NULL;
+	struct proxy *px;
+	struct bind_conf *bind_conf;
+	int changes = 0;
+
+	if (!*args[4]) {
+		appctx->ctx.cli.severity = LOG_ERR;
+		appctx->ctx.cli.msg = "'del ssl cert' expects a frontend and a domain.\n";
+		appctx->st0 = CLI_ST_PRINT;
+		return 1;
+	}
+	p = strrchr(args[3], ':');
+	if (p) {
+		*p = 0;
+		bind_name = p + 1;
+	}
+	px = cli_find_frontend(appctx, args[3]);
+	if (!px) {
+		appctx->ctx.cli.severity = LOG_ERR;
+		appctx->ctx.cli.msg = "Couldn't find frontend.\n";
+		appctx->st0 = CLI_ST_PRINT;
+		return 1;
+	}
+	list_for_each_entry(bind_conf, &px->conf.bind, by_fe) {
+		struct eb_root *root;
+		struct listener *l;
+		struct ebmb_node *node;
+		struct sni_ctx *sni;
+
+		if (!bind_conf->is_ssl)
+			continue;
+
+		if (*args[4] == '*') {
+			root = &bind_conf->sni_w_ctx;
+			args[4]++;
+		}
+		else
+			root = &bind_conf->sni_ctx;
+
+	       list_for_each_entry(l, &bind_conf->listeners, by_bind) {
+		       if (bind_name && (!l->name || strcmp(l->name, bind_name)))
+			       continue;
+
+		       HA_RWLOCK_WRLOCK(SSL_SNI_LOCK, &bind_conf->sni_lock);
+		       node = ebst_lookup(root, args[4]);
+		       if (!node) {
+			       HA_RWLOCK_WRUNLOCK(SSL_SNI_LOCK, &bind_conf->sni_lock);
+			       continue;
+		       }
+		       sni = ebmb_entry(node, struct sni_ctx, name);
+		       if (sni->ctx == bind_conf->default_ctx) {
+			       appctx->ctx.cli.severity = LOG_ERR;
+			       appctx->ctx.cli.msg = "Default certificate can't be deleted.\n";
+			       appctx->st0 = CLI_ST_PRINT;
+			       HA_RWLOCK_WRUNLOCK(SSL_SNI_LOCK, &bind_conf->sni_lock);
+			       goto end;
+		       }
+		       HA_SPIN_LOCK(SSL_SNI_CTX_LOCK, &sni->ctx_lock->lock);
+		       sni->ctx_lock->ref_count--;
+		       if (!sni->ctx_lock->ref_count) {
+			       SSL_CTX_free(sni->ctx);
+			       ssl_sock_free_ssl_conf(sni->conf);
+			       free(sni->conf);
+			       free(sni->ctx_lock);
+		       }
+		       else
+			       HA_SPIN_UNLOCK(SSL_SNI_CTX_LOCK, &sni->ctx_lock->lock);
+		       ebmb_delete(node);
+		       free(sni);
+		       HA_RWLOCK_WRUNLOCK(SSL_SNI_LOCK, &bind_conf->sni_lock);
+		       changes++;
+
+		       break;
+	       }
+	}
+
+	if (!changes) {
+	       appctx->ctx.cli.severity = LOG_ERR;
+	       appctx->ctx.cli.msg = "The certificate was not removed from any listener.\n";
+	       appctx->st0 = CLI_ST_PRINT;
+	}
+
+end:
+
+	return 1;
+}
+
 /* register cli keywords */
 static struct cli_kw_list cli_kws = {{ },{
 #if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
 	{ { "show", "tls-keys", NULL }, "show tls-keys [id|*]: show tls keys references or dump tls ticket keys when id specified", cli_parse_show_tlskeys, NULL },
 	{ { "set", "ssl", "tls-key", NULL }, "set ssl tls-key [id|keyfile] <tlskey>: set the next TLS key for the <id> or <keyfile> listener to <tlskey>", cli_parse_set_tlskeys, NULL },
 #endif
+	{ { "add", "ssl", "cert", NULL }, "add ssl cert <frontend[:luid]> <PEM certificate>: add an SSL certificate to a frontend", cli_parse_add_ssl_cert, NULL },
+	{ { "del", "ssl", "cert", NULL }, "del ssl cert <frontend[:luid]> <domain>: delete an SSL certificate from a frontend", cli_parse_del_ssl_cert, NULL },
 	{ { "set", "ssl", "ocsp-response", NULL }, NULL, cli_parse_set_ocspresponse, NULL },
 	{ { NULL }, NULL, NULL, NULL }
 }};
-- 
2.11.0

Reply via email to