Commit Diff


commit - 9c5e42458ea8dc2aa8db5f122aaa4296d31df67b
commit + eead4a631feb4d3cb8d7fefb2b09207d771035ca
blob - af715af8c7c1b773680b63f14890663962467623
blob + a8896a1f634db1fd511a9129442e2d50b3b05420
--- src/ngircd/conf-ssl.h
+++ src/ngircd/conf-ssl.h
@@ -40,6 +40,7 @@ struct ConnSSL_State {
 	gnutls_session_t gnutls_session;
 	void *cookie;		/* pointer to server configuration structure
 				   (for outgoing connections), or NULL. */
+	size_t x509_cred_idx;	/* index of active x509 credential record */
 #endif
 	char *fingerprint;
 };
blob - 3f482dc7ff60263ce40822d41d20c4a7b345e409
blob + ae5fd572ae94abf7dbd090100a83535ca9219b0e
--- src/ngircd/conn-ssl.c
+++ src/ngircd/conn-ssl.c
@@ -62,7 +62,14 @@ static bool ConnSSL_LoadServerKey_openssl PARAMS(( SSL
 
 #define MAX_HASH_SIZE	64	/* from gnutls-int.h */
 
-static gnutls_certificate_credentials_t x509_cred;
+typedef struct {
+	int refcnt;
+	gnutls_certificate_credentials_t x509_cred;
+} x509_cred_slot;
+
+static array x509_creds = INIT_ARRAY;
+static size_t x509_cred_idx;
+
 static gnutls_dh_params_t dh_params;
 static gnutls_priority_t priorities_cache;
 static bool ConnSSL_LoadServerKey_gnutls PARAMS(( void ));
@@ -265,6 +272,20 @@ void ConnSSL_Free(CONNECTION *c)
 	if (Conn_OPTION_ISSET(c, CONN_SSL)) {
 		gnutls_bye(sess, GNUTLS_SHUT_RDWR);
 		gnutls_deinit(sess);
+	}
+	x509_cred_slot *slot = array_get(&x509_creds, sizeof(x509_cred_slot), c->ssl_state.x509_cred_idx);
+	assert(slot != NULL);
+	assert(slot->refcnt > 0);
+	assert(slot->x509_cred != NULL);
+	slot->refcnt--;
+	if ((c->ssl_state.x509_cred_idx != x509_cred_idx) && (slot->refcnt <= 0)) {
+		Log(LOG_INFO, "Discarding X509 certificate credentials from slot %zd.",
+		    c->ssl_state.x509_cred_idx);
+		/* TODO/FIXME: DH parameters will still leak memory. */
+		gnutls_certificate_free_keys(slot->x509_cred);
+		gnutls_certificate_free_credentials(slot->x509_cred);
+		slot->x509_cred = NULL;
+		slot->refcnt = 0;
 	}
 #endif
 	assert(Conn_OPTION_ISSET(c, CONN_SSL));
@@ -348,19 +369,15 @@ out:
 	int err;
 	static bool initialized;
 
-	if (initialized) {
-		/* TODO: cannot reload gnutls keys: can't simply free x509
-		 * context -- it may still be in use */
-		return false;
+	if (!initialized) {
+		err = gnutls_global_init();
+		if (err) {
+			Log(LOG_ERR, "Failed to initialize GnuTLS: %s",
+			    gnutls_strerror(err));
+			goto out;
+		}
 	}
 
-	err = gnutls_global_init();
-	if (err) {
-		Log(LOG_ERR, "Failed to initialize GnuTLS: %s",
-		    gnutls_strerror(err));
-		goto out;
-	}
-
 	if (!ConnSSL_LoadServerKey_gnutls())
 		goto out;
 
@@ -389,6 +406,9 @@ ConnSSL_LoadServerKey_gnutls(void)
 	int err;
 	const char *cert_file;
 
+	x509_cred_slot *slot = NULL;
+	gnutls_certificate_credentials_t x509_cred;
+
 	err = gnutls_certificate_allocate_credentials(&x509_cred);
 	if (err < 0) {
 		Log(LOG_ERR, "Failed to allocate certificate credentials: %s",
@@ -419,6 +439,42 @@ ConnSSL_LoadServerKey_gnutls(void)
 		    gnutls_strerror(err));
 		return false;
 	}
+
+	/* Free currently active x509 context (if any) unless it is still in use */
+	slot = array_get(&x509_creds, sizeof(x509_cred_slot), x509_cred_idx);
+	if ((slot != NULL) && (slot->refcnt <= 0) && (slot->x509_cred != NULL)) {
+		Log(LOG_INFO, "Discarding X509 certificate credentials from slot %zd.", x509_cred_idx);
+		/* TODO/FIXME: DH parameters will still leak memory. */
+		gnutls_certificate_free_keys(slot->x509_cred);
+		gnutls_certificate_free_credentials(slot->x509_cred);
+		slot->x509_cred = NULL;
+		slot->refcnt = 0;
+	}
+
+	/* Find free slot */
+	x509_cred_idx = (size_t) -1;
+	size_t i;
+	for (slot = array_start(&x509_creds), i = 0;
+	     i < array_length(&x509_creds, sizeof(x509_cred_slot));
+	     slot++, i++) {
+		if (slot->refcnt <= 0) {
+			x509_cred_idx = i;
+			break;
+		}
+	}
+	/* ... allocate new slot otherwise. */
+	if (x509_cred_idx == (size_t) -1) {
+		x509_cred_idx = array_length(&x509_creds, sizeof(x509_cred_slot));
+		slot = array_alloc(&x509_creds, sizeof(x509_cred_slot), x509_cred_idx);
+		if (slot == NULL) {
+			Log(LOG_ERR, "Failed to allocate new slot for certificate credentials");
+			return false;
+		}
+	}
+	Log(LOG_INFO, "Storing new X509 certificate credentials in slot %zd.", x509_cred_idx);
+	slot->x509_cred = x509_cred;
+	slot->refcnt = 0;
+
 	return true;
 }
 #endif
@@ -520,8 +576,13 @@ ConnSSL_Init_SSL(CONNECTION *c)
 				 (gnutls_transport_ptr_t) (long) c->sock);
 	gnutls_certificate_server_set_request(c->ssl_state.gnutls_session,
 					      GNUTLS_CERT_REQUEST);
+
+	Log(LOG_INFO, "Using X509 credentials from slot %zd", x509_cred_idx);
+	c->ssl_state.x509_cred_idx = x509_cred_idx;
+	x509_cred_slot *slot = array_get(&x509_creds, sizeof(x509_cred_slot), x509_cred_idx);
+	slot->refcnt++;
 	ret = gnutls_credentials_set(c->ssl_state.gnutls_session,
-				     GNUTLS_CRD_CERTIFICATE, x509_cred);
+				     GNUTLS_CRD_CERTIFICATE, slot->x509_cred);
 	if (ret != 0) {
 		Log(LOG_ERR, "Failed to set SSL credentials: %s",
 		    gnutls_strerror(ret));