From ef7788e0e4f49e742f7025ffe923995e75e9608a Mon Sep 17 00:00:00 2001
From: Naveen Albert <asterisk@phreaknet.org>
Date: Wed, 6 Dec 2023 12:01:49 -0500
Subject: [PATCH] manager.c: Add CLI command to kick AMI sessions.

This adds a CLI command that can be used to manually
kick specific AMI sessions.

Resolves: #485

UserNote: The "manager kick session" CLI command now
allows kicking a specified AMI session.
---
 main/manager.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 78 insertions(+), 2 deletions(-)

diff --git a/main/manager.c b/main/manager.c
index 20b434f1eb..7461c46d52 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -1728,6 +1728,7 @@ struct mansession_session {
 	time_t noncetime;	/*!< Timer for nonce value expiration */
 	unsigned long oldnonce;	/*!< Stale nonce value */
 	unsigned long nc;	/*!< incremental  nonce counter */
+	unsigned int kicked:1;	/*!< Flag set if session is forcibly kicked */
 	ast_mutex_t notify_lock; /*!< Lock for notifying this session of events */
 	AST_LIST_HEAD_NOLOCK(mansession_datastores, ast_datastore) datastores; /*!< Data stores on the session */
 	AST_LIST_ENTRY(mansession_session) list;
@@ -2772,6 +2773,76 @@ static char *handle_showmancmds(struct ast_cli_entry *e, int cmd, struct ast_cli
 	return CLI_SUCCESS;
 }
 
+/*! \brief CLI command manager kick session */
+static char *handle_kickmanconn(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ao2_container *sessions;
+	struct mansession_session *session;
+	struct ao2_iterator i;
+	int fd = -1;
+	int found = 0;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "manager kick session";
+		e->usage =
+			"Usage: manager kick session <file descriptor>\n"
+			"	Kick an active Asterisk Manager Interface session\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	fd = atoi(a->argv[3]);
+	if (fd <= 0) { /* STDOUT won't be a valid AMI fd either */
+		ast_cli(a->fd, "Invalid AMI file descriptor: %s\n", a->argv[3]);
+		return CLI_FAILURE;
+	}
+
+	sessions = ao2_global_obj_ref(mgr_sessions);
+	if (sessions) {
+		i = ao2_iterator_init(sessions, 0);
+		ao2_ref(sessions, -1);
+		while ((session = ao2_iterator_next(&i))) {
+			ao2_lock(session);
+			if (session->stream) {
+				if (ast_iostream_get_fd(session->stream) == fd) {
+					if (session->kicked) {
+						ast_cli(a->fd, "Manager session using file descriptor %d has already been kicked\n", fd);
+						ao2_unlock(session);
+						unref_mansession(session);
+						break;
+					}
+					fd = ast_iostream_get_fd(session->stream);
+					found = fd;
+					ast_cli(a->fd, "Kicking manager session connected using file descriptor %d\n", fd);
+					ast_mutex_lock(&session->notify_lock);
+					session->kicked = 1;
+					if (session->waiting_thread != AST_PTHREADT_NULL) {
+						pthread_kill(session->waiting_thread, SIGURG);
+					}
+					ast_mutex_unlock(&session->notify_lock);
+					ao2_unlock(session);
+					unref_mansession(session);
+					break;
+				}
+			}
+			ao2_unlock(session);
+			unref_mansession(session);
+		}
+		ao2_iterator_destroy(&i);
+	}
+
+	if (!found) {
+		ast_cli(a->fd, "No manager session found using file descriptor %d\n", fd);
+	}
+	return CLI_SUCCESS;
+}
+
 /*! \brief CLI command manager list connected */
 static char *handle_showmanconn(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
@@ -7465,6 +7536,10 @@ static int get_input(struct mansession *s, char *output)
 		ast_mutex_unlock(&s->session->notify_lock);
 	}
 	if (res < 0) {
+		if (s->session->kicked) {
+			ast_debug(1, "Manager session has been kicked\n");
+			return -1;
+		}
 		/* If we get a signal from some other thread (typically because
 		 * there are new events queued), return 0 to notify the caller.
 		 */
@@ -7664,7 +7739,7 @@ static void *session_do(void *data)
 
 	astman_append(&s, "Asterisk Call Manager/%s\r\n", AMI_VERSION);	/* welcome prompt */
 	for (;;) {
-		if ((res = do_message(&s)) < 0 || s.write_error) {
+		if ((res = do_message(&s)) < 0 || s.write_error || session->kicked) {
 			break;
 		}
 		if (session->authenticated) {
@@ -7674,7 +7749,7 @@ static void *session_do(void *data)
 	/* session is over, explain why and terminate */
 	if (session->authenticated) {
 		if (manager_displayconnects(session)) {
-			ast_verb(2, "Manager '%s' logged off from %s\n", session->username, ast_sockaddr_stringify_addr(&session->addr));
+			ast_verb(2, "Manager '%s' %s from %s\n", session->username, session->kicked ? "kicked" : "logged off", ast_sockaddr_stringify_addr(&session->addr));
 		}
 	} else {
 		ast_atomic_fetchadd_int(&unauth_sessions, -1);
@@ -9591,6 +9666,7 @@ static struct ast_cli_entry cli_manager[] = {
 	AST_CLI_DEFINE(handle_showmancmd, "Show a manager interface command"),
 	AST_CLI_DEFINE(handle_showmancmds, "List manager interface commands"),
 	AST_CLI_DEFINE(handle_showmanconn, "List connected manager interface users"),
+	AST_CLI_DEFINE(handle_kickmanconn, "Kick a connected manager interface connection"),
 	AST_CLI_DEFINE(handle_showmaneventq, "List manager interface queued events"),
 	AST_CLI_DEFINE(handle_showmanagers, "List configured manager users"),
 	AST_CLI_DEFINE(handle_showmanager, "Display information on a specific manager user"),