From e4252eac1010af969007afb525871242855d39e7 Mon Sep 17 00:00:00 2001 From: Mark Murawki <markm@intellasoft.net> Date: Tue, 3 Apr 2012 19:31:25 +0000 Subject: [PATCH] Allow the Hangup manager action to match channels by regex * Hangup now can take a regular expression as the Channel option. If you want to hangup multiple channels, use /regex/ as the Channel option. Existing behavior to hanging up a single channel is unchanged, but if you pass a regex, the manager will send you a list of channels back that were hung up. (closes issue ASTERISK-19575) Reported by: Mark Murawski Tested by: Mark Murawski git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@361038 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- CHANGES | 5 ++ include/asterisk/channel.h | 8 +++ include/asterisk/manager.h | 3 + include/asterisk/strings.h | 21 ++++++- main/channel.c | 20 +++++++ main/manager.c | 120 ++++++++++++++++++++++++++++++++----- main/utils.c | 20 +++++++ 7 files changed, 179 insertions(+), 18 deletions(-) diff --git a/CHANGES b/CHANGES index 75a7e87aed..32391ec1d5 100644 --- a/CHANGES +++ b/CHANGES @@ -171,6 +171,11 @@ AMI (Asterisk Manager Interface) changes set and nat is not detected. "Y" and "N" are still returned if auto_force_rport is not enabled. + * Hangup now can take a regular expression as the Channel option. If you want + to hangup multiple channels, use /regex/ as the Channel option. Existing + behavior to hanging up a single channel is unchanged, but if you pass a regex, + the manager will send you a list of channels back that were hung up. + FAX changes ----------- * FAXOPT(faxdetect) will enable a generic fax detect framehook for dialplan diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 8e438bbd59..54faa1d514 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -1382,6 +1382,14 @@ int ast_check_hangup(struct ast_channel *chan); int ast_check_hangup_locked(struct ast_channel *chan); +/*! + * \brief Lock the given channel, then request softhangup on the channel with the given causecode + * \param obj channel on which to hang up + * \param causecode cause code to use + * \return 0 + */ +int ast_channel_softhangup_withcause_locked(void *obj, int causecode); + /*! * \brief Compare a offset with the settings of when to hang a channel up * \param chan channel on which to check for hang up diff --git a/include/asterisk/manager.h b/include/asterisk/manager.h index 5ffe7fac15..65aa1fb281 100644 --- a/include/asterisk/manager.h +++ b/include/asterisk/manager.h @@ -261,6 +261,9 @@ struct ast_variable *astman_get_variables(const struct message *m); /*! \brief Send error in manager transaction */ void astman_send_error(struct mansession *s, const struct message *m, char *error); +/*! \brief Send error in manager transaction (with va_args support) */ +void astman_send_error_va(struct mansession *s, const struct message *m, const char *fmt, ...); + /*! \brief Send response in manager transaction */ void astman_send_response(struct mansession *s, const struct message *m, char *resp, char *msg); diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h index 6764125d13..e3ff4c4fe9 100644 --- a/include/asterisk/strings.h +++ b/include/asterisk/strings.h @@ -252,11 +252,26 @@ int ast_build_string(char **buffer, size_t *space, const char *fmt, ...) __attri */ int ast_build_string_va(char **buffer, size_t *space, const char *fmt, va_list ap) __attribute__((format(printf, 3, 0))); -/*! +/*! + \brief Given a string regex_string in the form of "/regex/", convert it into the form of "regex" + + This function will trim one leading / and one trailing / from a given input string + ast_str regex_pattern must be preallocated before calling this function + + \return 0 on success, non-zero on failure. + \return 1 if we only stripped a leading / + \return 2 if we only stripped a trailing / + \return 3 if we did not strip any / characters + \param regex_string the string containing /regex/ + \param regex_pattern the destination ast_str which will contain "regex" after execution +*/ +int ast_regex_string_to_regex_pattern(const char *regex_string, struct ast_str *regex_pattern); + +/*! * \brief Make sure something is true. * Determine if a string containing a boolean value is "true". - * This function checks to see whether a string passed to it is an indication of an "true" value. - * It checks to see if the string is "yes", "true", "y", "t", "on" or "1". + * This function checks to see whether a string passed to it is an indication of an "true" value. + * It checks to see if the string is "yes", "true", "y", "t", "on" or "1". * * \retval 0 if val is a NULL pointer. * \retval -1 if "true". diff --git a/main/channel.c b/main/channel.c index 233acc7681..b86cd5252b 100644 --- a/main/channel.c +++ b/main/channel.c @@ -604,6 +604,26 @@ int ast_check_hangup_locked(struct ast_channel *chan) return res; } +int ast_channel_softhangup_withcause_locked(void *obj, int causecode) +{ + struct ast_channel *chan = obj; + + ast_channel_lock(chan); + + if (causecode > 0) { + ast_debug(1, "Setting hangupcause of channel %s to %d (is %d now)\n", + ast_channel_name(chan), causecode, ast_channel_hangupcause(chan)); + + ast_channel_hangupcause_set(chan, causecode); + } + + ast_softhangup_nolock(chan, AST_SOFTHANGUP_EXPLICIT); + + ast_channel_unlock(chan); + + return 0; +} + static int ast_channel_softhangup_cb(void *obj, void *arg, int flags) { struct ast_channel *chan = obj; diff --git a/main/manager.c b/main/manager.c index 2b4904256e..413adf29c9 100644 --- a/main/manager.c +++ b/main/manager.c @@ -174,7 +174,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <syntax> <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" /> <parameter name="Channel" required="true"> - <para>The channel name to be hangup.</para> + <para>The exact channel name to be hungup, or to use a regular expression, set this parameter to: /regex/</para> + <para>Example exact channel: SIP/provider-0000012a</para> + <para>Example regular expression: /^SIP/provider-.*$/</para> </parameter> <parameter name="Cause"> <para>Numeric hangup cause.</para> @@ -2034,7 +2036,7 @@ AST_THREADSTORAGE(astman_append_buf); AST_THREADSTORAGE(userevent_buf); -/*! \brief initial allocated size for the astman_append_buf */ +/*! \brief initial allocated size for the astman_append_buf and astman_send_*_va */ #define ASTMAN_APPEND_BUF_INITSIZE 256 /*! @@ -2109,6 +2111,23 @@ void astman_send_error(struct mansession *s, const struct message *m, char *erro astman_send_response_full(s, m, "Error", error, NULL); } +void astman_send_error_va(struct mansession *s, const struct message *m, const char *fmt, ...) +{ + va_list ap; + struct ast_str *buf; + + if (!(buf = ast_str_thread_get(&astman_append_buf, ASTMAN_APPEND_BUF_INITSIZE))) { + return; + } + + va_start(ap, fmt); + ast_str_set_va(&buf, 0, fmt, ap); + va_end(ap); + + astman_send_response_full(s, m, "Error", ast_str_buffer(buf), NULL); + ast_free(buf); +} + void astman_send_ack(struct mansession *s, const struct message *m, char *msg) { astman_send_response_full(s, m, "Success", msg, NULL); @@ -3139,14 +3158,24 @@ static int action_hangup(struct mansession *s, const struct message *m) { struct ast_channel *c = NULL; int causecode = 0; /* all values <= 0 mean 'do not set hangupcause in channel' */ - const char *name = astman_get_header(m, "Channel"); + const char *id = astman_get_header(m, "ActionID"); + const char *name_or_regex = astman_get_header(m, "Channel"); const char *cause = astman_get_header(m, "Cause"); + char idText[256] = ""; + regex_t regexbuf; + struct ast_channel_iterator *iter = NULL; + struct ast_str *regex_string; + int channels_matched = 0; - if (ast_strlen_zero(name)) { + if (ast_strlen_zero(name_or_regex)) { astman_send_error(s, m, "No channel specified"); return 0; } + if (!ast_strlen_zero(id)) { + snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); + } + if (!ast_strlen_zero(cause)) { char *endptr; causecode = strtol(cause, &endptr, 10); @@ -3157,23 +3186,84 @@ static int action_hangup(struct mansession *s, const struct message *m) } } - if (!(c = ast_channel_get_by_name(name))) { - astman_send_error(s, m, "No such channel"); + /************************************************/ + /* Regular explicit match channel byname hangup */ + + if (name_or_regex[0] != '/') { + if (!(c = ast_channel_get_by_name(name_or_regex))) { + astman_send_error(s, m, "No such channel"); + return 0; + } + + ast_verb(3, "%sManager '%s' from %s, hanging up channel: %s\n", + (s->session->managerid ? "HTTP " : ""), + s->session->username, + ast_inet_ntoa(s->session->sin.sin_addr), + ast_channel_name(c)); + + ast_channel_softhangup_withcause_locked(c, causecode); + c = ast_channel_unref(c); + + astman_send_ack(s, m, "Channel Hungup"); + return 0; } - ast_channel_lock(c); - if (causecode > 0) { - ast_debug(1, "Setting hangupcause of channel %s to %d (is %d now)\n", - ast_channel_name(c), causecode, ast_channel_hangupcause(c)); - ast_channel_hangupcause_set(c, causecode); + /***********************************************/ + /* find and hangup any channels matching regex */ + + regex_string = ast_str_create(strlen(name_or_regex)); + + /* Make "/regex/" into "regex" */ + if (ast_regex_string_to_regex_pattern(name_or_regex, regex_string) != 0) { + astman_send_error(s, m, "Regex format invalid, Channel param should be /regex/"); + ast_free(regex_string); + return 0; } - ast_softhangup_nolock(c, AST_SOFTHANGUP_EXPLICIT); - ast_channel_unlock(c); - c = ast_channel_unref(c); + /* if regex compilation fails, hangup fails */ + if (regcomp(®exbuf, ast_str_buffer(regex_string), REG_EXTENDED | REG_NOSUB)) { + astman_send_error_va(s, m, "Regex compile failed on: %s\n", name_or_regex); + ast_free(regex_string); + return 0; + } + + astman_send_listack(s, m, "Channels hung up will follow", "start"); + + for (iter = ast_channel_iterator_all_new(); iter && (c = ast_channel_iterator_next(iter)); ) { + if (regexec(®exbuf, ast_channel_name(c), 0, NULL, 0)) { + ast_channel_unref(c); + continue; + } - astman_send_ack(s, m, "Channel Hungup"); + ast_verb(3, "%sManager '%s' from %s, hanging up channel: %s\n", + (s->session->managerid ? "HTTP " : ""), + s->session->username, + ast_inet_ntoa(s->session->sin.sin_addr), + ast_channel_name(c)); + + ast_channel_softhangup_withcause_locked(c, causecode); + channels_matched++; + + astman_append(s, + "Event: ChannelHungup\r\n" + "Channel: %s\r\n" + "%s" + "\r\n", ast_channel_name(c), idText); + + ast_channel_unref(c); + } + + ast_channel_iterator_destroy(iter); + regfree(®exbuf); + ast_free(regex_string); + + astman_append(s, + "Event: ChannelsHungupListComplete\r\n" + "EventList: Complete\r\n" + "ListItems: %d\r\n" + "%s" + "\r\n", channels_matched, idText); return 0; } diff --git a/main/utils.c b/main/utils.c index ffcd37c2aa..379a179959 100644 --- a/main/utils.c +++ b/main/utils.c @@ -1350,6 +1350,26 @@ int ast_build_string(char **buffer, size_t *space, const char *fmt, ...) return result; } +int ast_regex_string_to_regex_pattern(const char *regex_string, struct ast_str *regex_pattern) +{ + int regex_len = strlen(regex_string); + int ret = 3; + + /* Chop off the leading / if there is one */ + if ((regex_len >= 1) && (regex_string[0] == '/')) { + ast_str_set(®ex_pattern, 0, "%s", regex_string + 1); + ret -= 2; + } + + /* Chop off the ending / if there is one */ + if ((regex_len > 1) && (regex_string[regex_len - 1] == '/')) { + ast_str_truncate(regex_pattern, -1); + ret -= 1; + } + + return ret; +} + int ast_true(const char *s) { if (ast_strlen_zero(s)) -- GitLab