MEDIUM: ssl: lookup and store in a ckch_node tree

Don't read a certificate file again if it was already stored in the
ckchn tree. It allows HAProxy to start more quickly if the same
certificate is used at different places in the configuration.

HAProxy lookup in the ssl_sock_load_cert() function, doing it at this
level allows to skip the reading of the certificate in the filesystem.

If the certificate is not found in the tree, we insert the ckch_node in
the tree once the certificate is read on the filesystem, the filename or
the bundle name is used as the key.
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 29b2a84..23f7fbb 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -2872,8 +2872,15 @@
 struct ckch_node {
 	struct cert_key_and_chain *ckch;
 	int multi; /* is it a multi-cert bundle ? */
+	struct ebmb_node node;
+	char path[0];
 };
 
+/*
+ * tree used to store the ckchn ordered by filename/bundle name
+ */
+struct eb_root ckchn_tree = EB_ROOT_UNIQUE;
+
 #define SSL_SOCK_POSSIBLE_KT_COMBOS (1<<(SSL_SOCK_NUM_KEYTYPES))
 
 struct key_combo_ctx {
@@ -3089,6 +3096,20 @@
 }
 
 /*
+ * lookup a path into the ckchn tree.
+ */
+static inline struct ckch_node *ckchn_lookup(char *path)
+{
+	struct ebmb_node *eb;
+
+	eb = ebst_lookup(&ckchn_tree, path);
+	if (!eb)
+		return NULL;
+
+	return ebmb_entry(eb, struct ckch_node, node);
+}
+
+/*
  * This function allocate a ckch_node and populate it with certificates from files.
  */
 static struct ckch_node *ckchn_load_cert_file(char *path, int multi, char **err)
@@ -3097,7 +3118,7 @@
 	char fp[MAXPATHLEN+1] = {0};
 	int n = 0;
 
-	ckchn = calloc(1, sizeof(*ckchn));
+	ckchn = calloc(1, sizeof(*ckchn) + strlen(path) + 1);
 	if (!ckchn) {
 		memprintf(err, "%sunable to allocate memory.\n", err && *err ? *err : "");
 		goto end;
@@ -3114,6 +3135,9 @@
 		if (ssl_sock_load_crt_file_into_ckch(path, ckchn->ckch, err) == 1)
 			goto end;
 
+		/* insert into the ckchn tree */
+		memcpy(ckchn->path, path, strlen(path) + 1);
+		ebst_insert(&ckchn_tree, &ckchn->node);
 	} else {
 		int found = 0;
 
@@ -3133,12 +3157,18 @@
 			memprintf(err, "%sDidn't find any certificate.\n", err && *err ? *err : "");
 			goto end;
 		}
+		/* insert into the ckchn tree */
+		memcpy(ckchn->path, path, strlen(path) + 1);
+		ebst_insert(&ckchn_tree, &ckchn->node);
 	}
 	return ckchn;
 
 end:
-	if (ckchn)
+	if (ckchn) {
 		free(ckchn->ckch);
+		ebmb_delete(&ckchn->node);
+	}
+
 	free(ckchn);
 
 	return NULL;
@@ -3538,6 +3568,16 @@
 	int j;
 #endif
 
+	if ((ckchn = ckchn_lookup(path))) {
+
+		/* we found the ckchn in the tree, we can use it directly */
+		if (ckchn->multi)
+			return ssl_sock_load_multi_ckchn(path, ckchn, bind_conf, NULL, NULL, 0, err);
+		else
+			return ssl_sock_load_ckchn(path, ckchn, bind_conf, NULL, NULL, 0, err);
+
+	}
+
 	if (stat(path, &buf) == 0) {
 		dir = opendir(path);
 		if (!dir) {
@@ -3605,7 +3645,8 @@
 						}
 
 						snprintf(fp, sizeof(fp), "%s/%s", path, dp);
-						ckchn =  ckchn_load_cert_file(fp, 1,  err);
+						if ((ckchn = ckchn_lookup(fp)) == NULL)
+							ckchn =  ckchn_load_cert_file(fp, 1,  err);
 						if (!ckchn)
 							return 1;
 						cfgerr += ssl_sock_load_multi_ckchn(fp, ckchn, bind_conf, NULL, NULL, 0, err);
@@ -3616,7 +3657,8 @@
 				}
 
 #endif
-				ckchn =  ckchn_load_cert_file(fp, 0,  err);
+				if ((ckchn = ckchn_lookup(fp)) == NULL)
+					ckchn =  ckchn_load_cert_file(fp, 0,  err);
 				if (!ckchn)
 					return 1;
 				cfgerr += ssl_sock_load_ckchn(fp, ckchn, bind_conf, NULL, NULL, 0, err);