Commit b5c3c86aa0 for freeswitch.com

commit b5c3c86aa078b0e63cdee4361f4b73f5991de0b9
Author: Andrey Volk <andywolk@gmail.com>
Date:   Tue May 26 00:25:56 2026 +0300

    [mod_commands, mod_verto] Add new reloadcert API and let mod_verto reload certificates on the fly without disconnects. (#3033)

diff --git a/src/include/switch_types.h b/src/include/switch_types.h
index 294c64ee3f..a68d5bbf70 100644
--- a/src/include/switch_types.h
+++ b/src/include/switch_types.h
@@ -2075,6 +2075,7 @@ typedef uint32_t switch_io_flag_t;
     SWITCH_EVENT_CALL_DETAIL
     SWITCH_EVENT_DEVICE_STATE
     SWITCH_EVENT_SHUTDOWN_REQUESTED		- Shutdown of the system has been requested
+    SWITCH_EVENT_CERT_RELOAD			- SSL/TLS certificates reload has been requested
     SWITCH_EVENT_ALL				- All events at once
 </pre>

@@ -2172,6 +2173,7 @@ typedef enum {
 	SWITCH_EVENT_DEVICE_STATE,
 	SWITCH_EVENT_TEXT,
 	SWITCH_EVENT_SHUTDOWN_REQUESTED,
+	SWITCH_EVENT_CERT_RELOAD,
 	SWITCH_EVENT_ALL
 } switch_event_types_t;

diff --git a/src/mod/applications/mod_commands/mod_commands.c b/src/mod/applications/mod_commands/mod_commands.c
index 22e25ea8a6..b44035044a 100644
--- a/src/mod/applications/mod_commands/mod_commands.c
+++ b/src/mod/applications/mod_commands/mod_commands.c
@@ -2860,6 +2860,22 @@ SWITCH_STANDARD_API(reload_xml_function)
 	return SWITCH_STATUS_SUCCESS;
 }

+SWITCH_STANDARD_API(reload_cert_function)
+{
+	switch_event_t *event;
+
+	if (switch_event_create(&event, SWITCH_EVENT_CERT_RELOAD) == SWITCH_STATUS_SUCCESS) {
+		switch_event_fire(&event);
+		stream->write_function(stream, "+OK cert reload event sent\n");
+
+		return SWITCH_STATUS_SUCCESS;
+	}
+
+	stream->write_function(stream, "-ERR failed to create event\n");
+
+	return SWITCH_STATUS_FALSE;
+}
+
 #define KILL_SYNTAX "<uuid> [cause]"
 SWITCH_STANDARD_API(kill_function)
 {
@@ -7656,6 +7672,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load)
 	SWITCH_ADD_API(commands_api_interface, "reloadacl", "Reload ACL", reload_acl_function, "");
 	SWITCH_ADD_API(commands_api_interface, "reload", "Reload module", reload_function, UNLOAD_SYNTAX);
 	SWITCH_ADD_API(commands_api_interface, "reloadxml", "Reload XML", reload_xml_function, "");
+	SWITCH_ADD_API(commands_api_interface, "reloadcert", "Reload SSL/TLS certificates", reload_cert_function, "");
 	SWITCH_ADD_API(commands_api_interface, "replace", "Replace a string", replace_function, "<data>|<string1>|<string2>");
 	SWITCH_ADD_API(commands_api_interface, "say_string", "", say_string_function, SAY_STRING_SYNTAX);
 	SWITCH_ADD_API(commands_api_interface, "sched_api", "Schedule an api command", sched_api_function, SCHED_SYNTAX);
@@ -7831,6 +7848,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load)
 	switch_console_set_complete("add nat_map status");
 	switch_console_set_complete("add reload ::console::list_loaded_modules");
 	switch_console_set_complete("add reloadacl reloadxml");
+	switch_console_set_complete("add reloadcert");
 	switch_console_set_complete("add show aliases");
 	switch_console_set_complete("add show api");
 	switch_console_set_complete("add show application");
diff --git a/src/mod/endpoints/mod_verto/mod_verto.c b/src/mod/endpoints/mod_verto/mod_verto.c
index 1363416139..113a21c579 100644
--- a/src/mod/endpoints/mod_verto/mod_verto.c
+++ b/src/mod/endpoints/mod_verto/mod_verto.c
@@ -150,108 +150,162 @@ static void verto_deinit_ssl(verto_profile_t *profile)
 	}
 }

-static void close_file(ks_socket_t *sock)
+static SSL_CTX *verto_create_ssl_ctx(verto_profile_t *profile, const char **errp)
 {
-	if (*sock != KS_SOCK_INVALID) {
-#ifndef WIN32
-		close(*sock);
-#else
-		closesocket(*sock);
-#endif
-		*sock = KS_SOCK_INVALID;
-	}
-}
+	SSL_CTX *ctx = SSL_CTX_new(profile->ssl_method);

-static void close_socket(ks_socket_t *sock)
-{
-	if (*sock != KS_SOCK_INVALID) {
-		shutdown(*sock, 2);
-		close_file(sock);
-	}
-}
-
-void verto_broadcast(const char *event_channel, cJSON *json, const char *key, switch_event_channel_id_t id, void *user_data);
+	if (!ctx) {
+		*errp = "Failed to create SSL context";

-static int verto_init_ssl(verto_profile_t *profile)
-{
-	const char *err;
-	int i = 0;
-
-	profile->ssl_method = SSLv23_server_method();   /* create server instance */
-	profile->ssl_ctx = SSL_CTX_new(profile->ssl_method);         /* create context */
-	profile->ssl_ready = 1;
-	assert(profile->ssl_ctx);
+		return NULL;
+	}

 	/* Disable SSLv2 */
-	SSL_CTX_set_options(profile->ssl_ctx, SSL_OP_NO_SSLv2);
+	SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
 	/* Disable SSLv3 */
-	SSL_CTX_set_options(profile->ssl_ctx, SSL_OP_NO_SSLv3);
+	SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
 	/* Disable TLSv1 */
-	SSL_CTX_set_options(profile->ssl_ctx, SSL_OP_NO_TLSv1);
+	SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1);
 	/* Disable Compression CRIME (Compression Ratio Info-leak Made Easy) */
-	SSL_CTX_set_options(profile->ssl_ctx, SSL_OP_NO_COMPRESSION);
+	SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);

-	/* set the local certificate from CertFile */
 	if (!zstr(profile->chain)) {
 		if (switch_file_exists(profile->chain, NULL) != SWITCH_STATUS_SUCCESS) {
-			err = "SUPPLIED CHAIN FILE NOT FOUND\n";
+			*errp = "SUPPLIED CHAIN FILE NOT FOUND";
 			goto fail;
 		}

-		if (!SSL_CTX_use_certificate_chain_file(profile->ssl_ctx, profile->chain)) {
-			err = "CERT CHAIN FILE ERROR";
+		if (!SSL_CTX_use_certificate_chain_file(ctx, profile->chain)) {
+			*errp = "CERT CHAIN FILE ERROR";
 			goto fail;
 		}
 	}

 	if (switch_file_exists(profile->cert, NULL) != SWITCH_STATUS_SUCCESS) {
-		err = "SUPPLIED CERT FILE NOT FOUND\n";
+		*errp = "SUPPLIED CERT FILE NOT FOUND";
 		goto fail;
 	}

-	if (!SSL_CTX_use_certificate_file(profile->ssl_ctx, profile->cert, SSL_FILETYPE_PEM)) {
-		err = "CERT FILE ERROR";
+	if (!SSL_CTX_use_certificate_file(ctx, profile->cert, SSL_FILETYPE_PEM)) {
+		*errp = "CERT FILE ERROR";
 		goto fail;
 	}

-	/* set the private key from KeyFile */
-
 	if (switch_file_exists(profile->key, NULL) != SWITCH_STATUS_SUCCESS) {
-		err = "SUPPLIED KEY FILE NOT FOUND\n";
+		*errp = "SUPPLIED KEY FILE NOT FOUND";
 		goto fail;
 	}

-	if (!SSL_CTX_use_PrivateKey_file(profile->ssl_ctx, profile->key, SSL_FILETYPE_PEM)) {
-		err = "PRIVATE KEY FILE ERROR";
+	if (!SSL_CTX_use_PrivateKey_file(ctx, profile->key, SSL_FILETYPE_PEM)) {
+		*errp = "PRIVATE KEY FILE ERROR";
 		goto fail;
 	}

-	/* verify private key */
-	if ( !SSL_CTX_check_private_key(profile->ssl_ctx) ) {
-		err = "PRIVATE KEY FILE ERROR";
+	if (!SSL_CTX_check_private_key(ctx)) {
+		*errp = "PRIVATE KEY FILE ERROR";
 		goto fail;
 	}

-	SSL_CTX_set_cipher_list(profile->ssl_ctx, "HIGH:!DSS:!aNULL@STRENGTH");
+	SSL_CTX_set_cipher_list(ctx, "HIGH:!DSS:!aNULL@STRENGTH");

-	return 1;
+	return ctx;

  fail:
-	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SSL ERR: %s\n", err);
+	SSL_CTX_free(ctx);

-	profile->ssl_ready = 0;
-	verto_deinit_ssl(profile);
+	return NULL;
+}

-	for (i = 0; i < profile->i; i++) {
-		if (profile->ip[i].secure) {
-			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SSL NOT ENABLED FOR LISTENER %s:%d. REVERTING TO WS\n",
-							  profile->ip[i].local_ip, profile->ip[i].local_port);
-			profile->ip[i].secure = 0;
+static int verto_reload_ssl(verto_profile_t *profile)
+{
+	const char *err = NULL;
+	SSL_CTX *new_ctx = verto_create_ssl_ctx(profile, &err);
+
+	if (!new_ctx) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SSL reload failed for profile %s: %s\n", profile->name, err);
+
+		return 0;
+	}
+
+	SSL_CTX_free(profile->ssl_ctx);
+
+	profile->ssl_ctx = new_ctx;
+	profile->ssl_ready = 1;
+
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SSL certificates reloaded for profile %s\n", profile->name);
+
+	return 1;
+}
+
+static void cert_reload_handler(switch_event_t *event)
+{
+	verto_profile_t *p;
+
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Certificate reload event received, processing\n");
+
+	switch_mutex_lock(verto_globals.mutex);
+
+	for (p = verto_globals.profile_head; p; p = p->next) {
+		if (p->running) {
+			switch_thread_rwlock_wrlock(p->rwlock);
+			verto_reload_ssl(p);
+			switch_thread_rwlock_unlock(p->rwlock);
 		}
 	}

-	return 0;
+	switch_mutex_unlock(verto_globals.mutex);
+
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Certificate reload event processed\n");
+}
+
+static void close_file(ks_socket_t *sock)
+{
+	if (*sock != KS_SOCK_INVALID) {
+#ifndef WIN32
+		close(*sock);
+#else
+		closesocket(*sock);
+#endif
+		*sock = KS_SOCK_INVALID;
+	}
+}
+
+static void close_socket(ks_socket_t *sock)
+{
+	if (*sock != KS_SOCK_INVALID) {
+		shutdown(*sock, 2);
+		close_file(sock);
+	}
+}
+
+void verto_broadcast(const char *event_channel, cJSON *json, const char *key, switch_event_channel_id_t id, void *user_data);
+
+static int verto_init_ssl(verto_profile_t *profile)
+{
+	const char *err = NULL;
+	int i = 0;
+
+	profile->ssl_method = SSLv23_server_method();
+	profile->ssl_ctx = verto_create_ssl_ctx(profile, &err);
+
+	if (!profile->ssl_ctx) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SSL ERR: %s\n", err);
+
+		profile->ssl_ready = 0;
+
+		for (i = 0; i < profile->i; i++) {
+			if (profile->ip[i].secure) {
+				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "SSL NOT READY FOR LISTENER %s:%d. USE reloadcert AFTER FIXING CERTIFICATES\n",
+								  profile->ip[i].local_ip, profile->ip[i].local_port);
+			}
+		}
+
+		return 0;
+	}
+
+	profile->ssl_ready = 1;

+	return 1;
 }


@@ -1972,6 +2026,7 @@ static void client_run(jsock_t *jsock)

 	ks_pool_open(&jsock->kpool);

+	switch_thread_rwlock_rdlock(jsock->profile->rwlock);
 #if defined(KS_VERSION_NUM) && KS_VERSION_NUM >= 20000
 	params = ks_json_create_object();
 	ks_json_add_number_to_object(params, "payload_size_max", 1000000);
@@ -1979,8 +2034,10 @@ static void client_run(jsock_t *jsock)
 #else
 	if (kws_init(&jsock->ws, jsock->client_socket, (jsock->ptype & PTYPE_CLIENT_SSL) ? jsock->profile->ssl_ctx : NULL, 0, flags, jsock->kpool) != KS_STATUS_SUCCESS) {
 #endif
+		switch_thread_rwlock_unlock(jsock->profile->rwlock);
 		log_and_exit(SWITCH_LOG_NOTICE, "%s WS SETUP FAILED\n", jsock->name);
 	}
+	switch_thread_rwlock_unlock(jsock->profile->rwlock);

 	if (kws_test_flag(jsock->ws, KWS_HTTP)) {
 		http_run(jsock);
@@ -4690,7 +4747,7 @@ static int start_jsock(verto_profile_t *profile, ks_socket_t sock, int family)

 	for (i = 0; i < profile->i; i++) {
 		if ( profile->server_socket[i] == sock ) {
-			if (profile->ip[i].secure) {
+			if (profile->ip[i].secure && profile->ssl_ready) {
 				ptype = PTYPE_CLIENT_SSL;
 			}
 			break;
@@ -6887,6 +6944,8 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_verto_load)

 	switch_core_register_secondary_recover_callback(modname, verto_recover_callback);

+	switch_event_bind(modname, SWITCH_EVENT_CERT_RELOAD, SWITCH_EVENT_SUBCLASS_ANY, cert_reload_handler, NULL);
+
 	if (verto_globals.enable_presence) {
 		switch_event_bind(modname, SWITCH_EVENT_CHANNEL_CALLSTATE, SWITCH_EVENT_SUBCLASS_ANY, presence_event_handler, NULL);
 	}
@@ -6922,6 +6981,7 @@ SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_verto_shutdown)
 	switch_core_hash_destroy(&json_GLOBALS.store_hash);

 	switch_event_channel_unbind(NULL, verto_broadcast, NULL);
+	switch_event_unbind_callback(cert_reload_handler);
 	switch_event_unbind_callback(presence_event_handler);
 	switch_event_unbind_callback(event_handler);

diff --git a/src/switch_event.c b/src/switch_event.c
index 8a8c8d6c35..25bf961512 100644
--- a/src/switch_event.c
+++ b/src/switch_event.c
@@ -227,6 +227,7 @@ static char *EVENT_NAMES[] = {
 	"DEVICE_STATE",
 	"TEXT",
 	"SHUTDOWN_REQUESTED",
+	"CERT_RELOAD",
 	"ALL"
 };