Detect potential forwarding loops based on count.
authorMark Michelson <mmichelson@digium.com>
Wed, 15 Apr 2015 15:38:02 +0000 (10:38 -0500)
committerMatt Jordan <mjordan@digium.com>
Fri, 17 Apr 2015 20:58:07 +0000 (15:58 -0500)
A potential problem that can arise is the following:

* Bob's phone is programmed to automatically forward to Carol.
* Carol's phone is programmed to automatically forward to Bob.
* Alice calls Bob.

If left unchecked, this results in an endless loops of call forwards
that would eventually result in some sort of fiery crash.

Asterisk's method of solving this issue was to track which interfaces
had been dialed. If a destination were dialed a second time, then
the attempt to call that destination would fail since a loop was
detected.

The problem with this method is that call forwarding has evolved. Some
SIP phones allow for a user to manually forward an incoming call to an
ad-hoc destination. This can mean that:

* There are legitimate use cases where a device may be dialed multiple
times, or
* There can be human error when forwarding calls.

This change removes the old method of detecting forwarding loops in
favor of keeping a count of the number of destinations a channel has
dialed on a particular branch of a call. If the number exceeds the
set number of max forwards, then the call fails. This approach has
the following advantages over the old:

* It is much simpler.
* It can detect loops involving local channels.
* It is user configurable.

The only disadvantage it has is that in the case where there is a
legitimate forwarding loop present, it takes longer to detect it.
However, the forwarding loop is still properly detected and the
call is cleaned up as it should be.

Address review feedback on gerrit.

* Correct "mfgium" to "Digium"
* Decrement max forwards by one in the case where allocation of the
  max forwards datastore is required.
* Remove irrelevant code change from pjsip_global_headers.c

ASTERISK-24958 #close

Change-Id: Ia7e4b7cd3bccfbd34d9a859838356931bba56c23

13 files changed:
apps/app_dial.c
apps/app_followme.c
apps/app_queue.c
funcs/func_channel.c
include/asterisk/global_datastores.h
include/asterisk/max_forwards.h [new file with mode: 0644]
main/ccss.c
main/channel.c
main/dial.c
main/features.c
main/global_datastores.c
main/max_forwards.c [new file with mode: 0644]
res/res_pjsip_diversion.c

index 0390cfe..895d4b8 100644 (file)
@@ -58,7 +58,6 @@ ASTERISK_REGISTER_FILE()
 #include "asterisk/manager.h"
 #include "asterisk/privacy.h"
 #include "asterisk/stringfields.h"
-#include "asterisk/global_datastores.h"
 #include "asterisk/dsp.h"
 #include "asterisk/aoc.h"
 #include "asterisk/ccss.h"
@@ -68,6 +67,7 @@ ASTERISK_REGISTER_FILE()
 #include "asterisk/stasis_channels.h"
 #include "asterisk/bridge_after.h"
 #include "asterisk/features_config.h"
+#include "asterisk/max_forwards.h"
 
 /*** DOCUMENTATION
        <application name="Dial" language="en_US">
@@ -881,6 +881,7 @@ static void do_forward(struct chanlist *o, struct cause_args *num,
                        ast_channel_lock_both(in, o->chan);
                        ast_channel_inherit_variables(in, o->chan);
                        ast_channel_datastore_inherit(in, o->chan);
+                       ast_max_forwards_decrement(o->chan);
                        ast_channel_unlock(in);
                        ast_channel_unlock(o->chan);
                        /* When a call is forwarded, we don't want to track new interfaces
@@ -2074,7 +2075,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
        );
        struct ast_flags64 opts = { 0, };
        char *opt_args[OPT_ARG_ARRAY_SIZE];
-       struct ast_datastore *datastore = NULL;
        int fulldial = 0, num_dialed = 0;
        int ignore_cc = 0;
        char device_name[AST_CHANNEL_NAME];
@@ -2101,6 +2101,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
         * \note This will not have any malloced strings so do not free it.
         */
        struct ast_party_caller caller;
+       int max_forwards;
 
        /* Reset all DIAL variables back to blank, to prevent confusion (in case we don't reset all of them). */
        ast_channel_lock(chan);
@@ -2111,8 +2112,16 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
        pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", "");
        pbx_builtin_setvar_helper(chan, "DIALEDTIME", "");
        ast_channel_stage_snapshot_done(chan);
+       max_forwards = ast_max_forwards_get(chan);
        ast_channel_unlock(chan);
 
+       if (max_forwards <= 0) {
+               ast_log(LOG_WARNING, "Cannot place outbound call from channel '%s'. Max forwards exceeded\n",
+                               ast_channel_name(chan));
+               pbx_builtin_setvar_helper(chan, "DIALSTATUS", "BUSY");
+               return -1;
+       }
+
        if (ast_strlen_zero(data)) {
                ast_log(LOG_WARNING, "Dial requires an argument (technology/resource)\n");
                pbx_builtin_setvar_helper(chan, "DIALSTATUS", pa.status);
@@ -2314,9 +2323,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
                char *tech = strsep(&number, "/");
                size_t tech_len;
                size_t number_len;
-               /* find if we already dialed this interface */
-               struct ast_dialed_interface *di;
-               AST_LIST_HEAD(,ast_dialed_interface) *dialed_interfaces;
 
                num_dialed++;
                if (ast_strlen_zero(number)) {
@@ -2360,7 +2366,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
                /* Request the peer */
 
                ast_channel_lock(chan);
-               datastore = ast_channel_datastore_find(chan, &dialed_interface_info, NULL);
                /*
                 * Seed the chanlist's connected line information with previously
                 * acquired connected line info from the incoming channel.  The
@@ -2370,61 +2375,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
                ast_party_connected_line_copy(&tmp->connected, ast_channel_connected(chan));
                ast_channel_unlock(chan);
 
-               if (datastore)
-                       dialed_interfaces = datastore->data;
-               else {
-                       if (!(datastore = ast_datastore_alloc(&dialed_interface_info, NULL))) {
-                               ast_log(LOG_WARNING, "Unable to create channel datastore for dialed interfaces. Aborting!\n");
-                               chanlist_free(tmp);
-                               goto out;
-                       }
-                       datastore->inheritance = DATASTORE_INHERIT_FOREVER;
-
-                       if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) {
-                               ast_datastore_free(datastore);
-                               chanlist_free(tmp);
-                               goto out;
-                       }
-
-                       datastore->data = dialed_interfaces;
-                       AST_LIST_HEAD_INIT(dialed_interfaces);
-
-                       ast_channel_lock(chan);
-                       ast_channel_datastore_add(chan, datastore);
-                       ast_channel_unlock(chan);
-               }
-
-               AST_LIST_LOCK(dialed_interfaces);
-               AST_LIST_TRAVERSE(dialed_interfaces, di, list) {
-                       if (!strcasecmp(di->interface, tmp->interface)) {
-                               ast_log(LOG_WARNING, "Skipping dialing interface '%s' again since it has already been dialed\n",
-                                       di->interface);
-                               break;
-                       }
-               }
-               AST_LIST_UNLOCK(dialed_interfaces);
-               if (di) {
-                       fulldial++;
-                       chanlist_free(tmp);
-                       continue;
-               }
-
-               /* It is always ok to dial a Local interface.  We only keep track of
-                * which "real" interfaces have been dialed.  The Local channel will
-                * inherit this list so that if it ends up dialing a real interface,
-                * it won't call one that has already been called. */
-               if (strcasecmp(tmp->tech, "Local")) {
-                       if (!(di = ast_calloc(1, sizeof(*di) + strlen(tmp->interface)))) {
-                               chanlist_free(tmp);
-                               goto out;
-                       }
-                       strcpy(di->interface, tmp->interface);
-
-                       AST_LIST_LOCK(dialed_interfaces);
-                       AST_LIST_INSERT_TAIL(dialed_interfaces, di, list);
-                       AST_LIST_UNLOCK(dialed_interfaces);
-               }
-
                tc = ast_request(tmp->tech, ast_channel_nativeformats(chan), NULL, chan, tmp->number, &cause);
                if (!tc) {
                        /* If we can't, just go on to the next call */
@@ -2465,6 +2415,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
                /* Inherit specially named variables from parent channel */
                ast_channel_inherit_variables(chan, tc);
                ast_channel_datastore_inherit(chan, tc);
+               ast_max_forwards_decrement(tc);
 
                ast_channel_appl_set(tc, "AppDial");
                ast_channel_data_set(tc, "(Outgoing Line)");
@@ -2680,18 +2631,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
        peer = wait_for_answer(chan, &out_chans, &to, peerflags, opt_args, &pa, &num, &result,
                dtmf_progress, ignore_cc, &forced_clid, &stored_clid);
 
-       /* The ast_channel_datastore_remove() function could fail here if the
-        * datastore was moved to another channel during a masquerade. If this is
-        * the case, don't free the datastore here because later, when the channel
-        * to which the datastore was moved hangs up, it will attempt to free this
-        * datastore again, causing a crash
-        */
-       ast_channel_lock(chan);
-       datastore = ast_channel_datastore_find(chan, &dialed_interface_info, NULL); /* make sure we weren't cleaned up already */
-       if (datastore && !ast_channel_datastore_remove(chan, datastore)) {
-               ast_datastore_free(datastore);
-       }
-       ast_channel_unlock(chan);
        if (!peer) {
                if (result) {
                        res = result;
index 4a2e569..5fd5d15 100644 (file)
@@ -64,6 +64,7 @@ ASTERISK_REGISTER_FILE()
 #include "asterisk/dsp.h"
 #include "asterisk/app.h"
 #include "asterisk/stasis_channels.h"
+#include "asterisk/max_forwards.h"
 
 /*** DOCUMENTATION
        <application name="FollowMe" language="en_US">
@@ -1069,6 +1070,7 @@ static struct ast_channel *findmeexec(struct fm_args *tpargs, struct ast_channel
                        ast_connected_line_copy_from_caller(ast_channel_connected(outbound), ast_channel_caller(caller));
                        ast_channel_inherit_variables(caller, outbound);
                        ast_channel_datastore_inherit(caller, outbound);
+                       ast_max_forwards_decrement(outbound);
                        ast_channel_language_set(outbound, ast_channel_language(caller));
                        ast_channel_req_accountcodes(outbound, caller, AST_CHANNEL_REQUESTOR_BRIDGE_PEER);
                        ast_channel_musicclass_set(outbound, ast_channel_musicclass(caller));
@@ -1304,12 +1306,23 @@ static int app_exec(struct ast_channel *chan, const char *data)
                AST_APP_ARG(options);
        );
        char *opt_args[FOLLOWMEFLAG_ARG_ARRAY_SIZE];
+       int max_forwards;
 
        if (ast_strlen_zero(data)) {
                ast_log(LOG_WARNING, "%s requires an argument (followmeid)\n", app);
                return -1;
        }
 
+       ast_channel_lock(chan);
+       max_forwards = ast_max_forwards_get(chan);
+       ast_channel_unlock(chan);
+
+       if (max_forwards <= 0) {
+               ast_log(LOG_WARNING, "Unable to execute FollowMe on channel %s. Max forwards exceeded\n",
+                               ast_channel_name(chan));
+               return -1;
+       }
+
        argstr = ast_strdupa((char *) data);
 
        AST_STANDARD_APP_ARGS(args, argstr);
index a82632d..0b8204c 100644 (file)
@@ -98,7 +98,6 @@ ASTERISK_REGISTER_FILE()
 #include "asterisk/stringfields.h"
 #include "asterisk/astobj2.h"
 #include "asterisk/strings.h"
-#include "asterisk/global_datastores.h"
 #include "asterisk/taskprocessor.h"
 #include "asterisk/aoc.h"
 #include "asterisk/callerid.h"
@@ -113,6 +112,7 @@ ASTERISK_REGISTER_FILE()
 #include "asterisk/mixmonitor.h"
 #include "asterisk/core_unreal.h"
 #include "asterisk/bridge_basic.h"
+#include "asterisk/max_forwards.h"
 
 /*!
  * \par Please read before modifying this file.
@@ -4301,6 +4301,7 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
        /* Inherit specially named variables from parent channel */
        ast_channel_inherit_variables(qe->chan, tmp->chan);
        ast_channel_datastore_inherit(qe->chan, tmp->chan);
+       ast_max_forwards_decrement(tmp->chan);
 
        /* Presense of ADSI CPE on outgoing channel follows ours */
        ast_channel_adsicpe_set(tmp->chan, ast_channel_adsicpe(qe->chan));
@@ -4794,6 +4795,7 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
                                                ast_channel_lock_both(o->chan, in);
                                                ast_channel_inherit_variables(in, o->chan);
                                                ast_channel_datastore_inherit(in, o->chan);
+                                               ast_max_forwards_decrement(o->chan);
 
                                                if (o->pending_connected_update) {
                                                        /*
@@ -6275,10 +6277,7 @@ static void setup_mixmonitor(struct queue_ent *qe, const char *filename)
  *
  * Here is the process of this function
  * 1. Process any options passed to the Queue() application. Options here mean the third argument to Queue()
- * 2. Iterate trough the members of the queue, creating a callattempt corresponding to each member. During this
- *    iteration, we also check the dialed_interfaces datastore to see if we have already attempted calling this
- *    member. If we have, we do not create a callattempt. This is in place to prevent call forwarding loops. Also
- *    during each iteration, we call calc_metric to determine which members should be rung when.
+ * 2. Iterate trough the members of the queue, creating a callattempt corresponding to each member.
  * 3. Call ring_one to place a call to the appropriate member(s)
  * 4. Call wait_for_answer to wait for an answer. If no one answers, return.
  * 5. Take care of any holdtime announcements, member delays, or other options which occur after a call has been answered.
@@ -6331,13 +6330,8 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
        int block_connected_line = 0;
        int callcompletedinsl;
        struct ao2_iterator memi;
-       struct ast_datastore *datastore;
        struct queue_end_bridge *queue_end_bridge = NULL;
 
-       ast_channel_lock(qe->chan);
-       datastore = ast_channel_datastore_find(qe->chan, &dialed_interface_info, NULL);
-       ast_channel_unlock(qe->chan);
-
        memset(&bridge_config, 0, sizeof(bridge_config));
        tmpid[0] = 0;
        time(&now);
@@ -6424,73 +6418,12 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
        memi = ao2_iterator_init(qe->parent->members, 0);
        while ((cur = ao2_iterator_next(&memi))) {
                struct callattempt *tmp = ast_calloc(1, sizeof(*tmp));
-               struct ast_dialed_interface *di;
-               AST_LIST_HEAD(,ast_dialed_interface) *dialed_interfaces;
                if (!tmp) {
                        ao2_ref(cur, -1);
                        ao2_iterator_destroy(&memi);
                        ao2_unlock(qe->parent);
                        goto out;
                }
-               if (!datastore) {
-                       if (!(datastore = ast_datastore_alloc(&dialed_interface_info, NULL))) {
-                               callattempt_free(tmp);
-                               ao2_ref(cur, -1);
-                               ao2_iterator_destroy(&memi);
-                               ao2_unlock(qe->parent);
-                               goto out;
-                       }
-                       datastore->inheritance = DATASTORE_INHERIT_FOREVER;
-                       if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) {
-                               callattempt_free(tmp);
-                               ao2_ref(cur, -1);
-                               ao2_iterator_destroy(&memi);
-                               ao2_unlock(qe->parent);
-                               goto out;
-                       }
-                       datastore->data = dialed_interfaces;
-                       AST_LIST_HEAD_INIT(dialed_interfaces);
-
-                       ast_channel_lock(qe->chan);
-                       ast_channel_datastore_add(qe->chan, datastore);
-                       ast_channel_unlock(qe->chan);
-               } else
-                       dialed_interfaces = datastore->data;
-
-               AST_LIST_LOCK(dialed_interfaces);
-               AST_LIST_TRAVERSE(dialed_interfaces, di, list) {
-                       if (!strcasecmp(cur->interface, di->interface)) {
-                               ast_debug(1, "Skipping dialing interface '%s' since it has already been dialed\n",
-                                       di->interface);
-                               break;
-                       }
-               }
-               AST_LIST_UNLOCK(dialed_interfaces);
-
-               if (di) {
-                       callattempt_free(tmp);
-                       ao2_ref(cur, -1);
-                       continue;
-               }
-
-               /* It is always ok to dial a Local interface.  We only keep track of
-                * which "real" interfaces have been dialed.  The Local channel will
-                * inherit this list so that if it ends up dialing a real interface,
-                * it won't call one that has already been called. */
-               if (strncasecmp(cur->interface, "Local/", 6)) {
-                       if (!(di = ast_calloc(1, sizeof(*di) + strlen(cur->interface)))) {
-                               callattempt_free(tmp);
-                               ao2_ref(cur, -1);
-                               ao2_iterator_destroy(&memi);
-                               ao2_unlock(qe->parent);
-                               goto out;
-                       }
-                       strcpy(di->interface, cur->interface);
-
-                       AST_LIST_LOCK(dialed_interfaces);
-                       AST_LIST_INSERT_TAIL(dialed_interfaces, di, list);
-                       AST_LIST_UNLOCK(dialed_interfaces);
-               }
 
                /*
                 * Seed the callattempt's connected line information with previously
@@ -6549,16 +6482,7 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
        lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies,
                ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT),
                forwardsallowed, ringing);
-       /* The ast_channel_datastore_remove() function could fail here if the
-        * datastore was moved to another channel during a masquerade. If this is
-        * the case, don't free the datastore here because later, when the channel
-        * to which the datastore was moved hangs up, it will attempt to free this
-        * datastore again, causing a crash
-        */
-       ast_channel_lock(qe->chan);
-       if (datastore && !ast_channel_datastore_remove(qe->chan, datastore)) {
-               ast_datastore_free(datastore);
-       }
+
        ast_channel_unlock(qe->chan);
        ao2_lock(qe->parent);
        if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY || qe->parent->strategy == QUEUE_STRATEGY_RRORDERED) {
@@ -7750,12 +7674,22 @@ static int queue_exec(struct ast_channel *chan, const char *data)
        struct queue_ent qe = { 0 };
        struct ast_flags opts = { 0, };
        char *opt_args[OPT_ARG_ARRAY_SIZE];
+       int max_forwards;
 
        if (ast_strlen_zero(data)) {
                ast_log(LOG_WARNING, "Queue requires an argument: queuename[,options[,URL[,announceoverride[,timeout[,agi[,macro[,gosub[,rule[,position]]]]]]]]]\n");
                return -1;
        }
 
+       ast_channel_lock(chan);
+       max_forwards = ast_max_forwards_get(chan);
+       ast_channel_unlock(chan);
+
+       if (max_forwards <= 0) {
+               ast_log(LOG_WARNING, "Channel '%s' cannot enter queue. Max forwards exceeded\n", ast_channel_name(chan));
+               return -1;
+       }
+
        parse = ast_strdupa(data);
        AST_STANDARD_APP_ARGS(args, parse);
 
index 77e18ae..b051d89 100644 (file)
@@ -46,6 +46,7 @@ ASTERISK_REGISTER_FILE()
 #include "asterisk/global_datastores.h"
 #include "asterisk/bridge_basic.h"
 #include "asterisk/bridge_after.h"
+#include "asterisk/max_forwards.h"
 
 /*** DOCUMENTATION
        <function name="CHANNELS" language="en_US">
@@ -391,6 +392,16 @@ ASTERISK_REGISTER_FILE()
                                        <enum name="caller_url">
                                                <para>R/0 Returns caller URL</para>
                                        </enum>
+                                       <enum name="max_forwards">
+                                               <para>R/W Get or set the maximum number of call forwards for this channel.
+
+                                               This number describes the number of times a call may be forwarded by this channel
+                                               before the call fails. "Forwards" in this case refers to redirects by phones as well
+                                               as calls to local channels.
+
+                                               Note that this has no relation to the SIP Max-Forwards header.
+                                               </para>
+                                       </enum>
                                </enumlist>
                        </parameter>
                </syntax>
@@ -583,6 +594,10 @@ static int func_channel_read(struct ast_channel *chan, const char *function,
                        }
                }
                ast_channel_unlock(chan);
+       } else if (!strcasecmp(data, "max_forwards")) {
+               ast_channel_lock(chan);
+               snprintf(buf, len, "%d", ast_max_forwards_get(chan));
+               ast_channel_unlock(chan);
        } else if (!ast_channel_tech(chan) || !ast_channel_tech(chan)->func_channel_read || ast_channel_tech(chan)->func_channel_read(chan, function, data, buf, len)) {
                ast_log(LOG_WARNING, "Unknown or unavailable item requested: '%s'\n", data);
                ret = -1;
@@ -743,6 +758,16 @@ static int func_channel_write_real(struct ast_channel *chan, const char *functio
                        store->media = ast_true(value) ? 1 : 0;
                }
                ast_channel_unlock(chan);
+       } else if (!strcasecmp(data, "max_forwards")) {
+               int max_forwards;
+               if (sscanf(value, "%d", &max_forwards) != 1) {
+                       ast_log(LOG_WARNING, "Unable to set max forwards to '%s'\n", value);
+                       ret = -1;
+               } else {
+                       ast_channel_lock(chan);
+                       ret = ast_max_forwards_set(chan, max_forwards);
+                       ast_channel_unlock(chan);
+               }
        } else if (!ast_channel_tech(chan)->func_channel_write
                 || ast_channel_tech(chan)->func_channel_write(chan, function, data, value)) {
                ast_log(LOG_WARNING, "Unknown or unavailable item requested: '%s'\n",
index 16267a8..2946ede 100644 (file)
 
 #include "asterisk/channel.h"
 
-extern const struct ast_datastore_info dialed_interface_info;
 extern const struct ast_datastore_info secure_call_info;
 
-struct ast_dialed_interface {
-       AST_LIST_ENTRY(ast_dialed_interface) list;
-       char interface[1];
-};
-
 struct ast_secure_call_store {
        unsigned int signaling:1;
        unsigned int media:1;
diff --git a/include/asterisk/max_forwards.h b/include/asterisk/max_forwards.h
new file mode 100644 (file)
index 0000000..3130b4b
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef MAX_FORWARDS_H
+
+struct ast_channel;
+
+/*!
+ * \brief Set the starting max forwards for a particular channel.
+ *
+ * \pre chan is locked
+ *
+ * \param starting_count The value to set the max forwards to.
+ * \param chan The channel on which to set the max forwards.
+ * \retval 0 Success
+ * \retval 1 Failure
+ */
+int ast_max_forwards_set(struct ast_channel *chan, int starting_count);
+
+/*!
+ * \brief Get the current max forwards for a particular channel.
+ *
+ * If the channel has not had max forwards set on it, then the channel
+ * will have the default max forwards set on it and that value will
+ * be returned.
+ *
+ * \pre chan is locked
+ *
+ * \param chan The channel to get the max forwards for.
+ * \return The current max forwards count on the channel
+ */
+int ast_max_forwards_get(struct ast_channel *chan);
+
+/*!
+ * \brief Decrement the max forwards count for a particular channel.
+ *
+ * If the channel has not had max forwards set on it, then the channel
+ * will have the default max forwards set on it and that value will
+ * not be decremented.
+ *
+ * \pre chan is locked
+ *
+ * \chan The channel for which the max forwards value should be decremented
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+int ast_max_forwards_decrement(struct ast_channel *chan);
+
+/*!
+ * \brief Reset the max forwards on a channel to its starting value.
+ *
+ * If the channel has not had max forwards set on it, then the channel
+ * will have the default max forwards set on it.
+ *
+ * \pre chan is locked.
+ *
+ * \param chan The channel on which to reset the max forwards count.
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+int ast_max_forwards_reset(struct ast_channel *chan);
+
+#endif /* MAX_FORWARDS_H */
index c1b3372..51edae7 100644 (file)
@@ -2237,9 +2237,7 @@ static void call_destructor_with_no_monitor(const char * const monitor_type, voi
  * Note that it is not necessarily erroneous to add the same
  * device to the tree twice. If the same device is called by
  * two different extension during the same call, then
- * that is a legitimate situation. Of course, I'm pretty sure
- * the dialed_interfaces global datastore will not allow that
- * to happen anyway.
+ * that is a legitimate situation.
  *
  * \param device_name The name of the device being added to the tree
  * \param dialstring The dialstring used to dial the device being added
index 4e418b6..db126db 100644 (file)
@@ -75,6 +75,7 @@ ASTERISK_REGISTER_FILE()
 #include "asterisk/bridge.h"
 #include "asterisk/test.h"
 #include "asterisk/stasis_channels.h"
+#include "asterisk/max_forwards.h"
 
 /*** DOCUMENTATION
  ***/
@@ -5621,6 +5622,7 @@ static void call_forward_inherit(struct ast_channel *new_chan, struct ast_channe
        ast_channel_lock_both(parent, new_chan);
        ast_channel_inherit_variables(parent, new_chan);
        ast_channel_datastore_inherit(parent, new_chan);
+       ast_max_forwards_decrement(new_chan);
        ast_channel_unlock(new_chan);
        ast_channel_unlock(parent);
 }
@@ -5740,6 +5742,7 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c
                        ast_channel_lock_both(oh->parent_channel, chan);
                        ast_channel_inherit_variables(oh->parent_channel, chan);
                        ast_channel_datastore_inherit(oh->parent_channel, chan);
+                       ast_max_forwards_decrement(chan);
                        ast_channel_unlock(oh->parent_channel);
                        ast_channel_unlock(chan);
                }
index f0cf127..b935b6d 100644 (file)
@@ -44,6 +44,7 @@ ASTERISK_REGISTER_FILE()
 #include "asterisk/app.h"
 #include "asterisk/causes.h"
 #include "asterisk/stasis_channels.h"
+#include "asterisk/max_forwards.h"
 
 /*! \brief Main dialing structure. Contains global options, channels being dialed, and more! */
 struct ast_dial {
@@ -299,6 +300,19 @@ static int begin_dial_prerun(struct ast_dial_channel *channel, struct ast_channe
                .uniqueid2 = channel->assignedid2,
        };
 
+       if (chan) {
+               int max_forwards;
+
+               ast_channel_lock(chan);
+               max_forwards = ast_max_forwards_get(chan);
+               ast_channel_unlock(chan);
+
+               if (max_forwards <= 0) {
+                       ast_log(LOG_WARNING, "Cannot dial from channel '%s'. Max forwards exceeded\n",
+                                       ast_channel_name(chan));
+               }
+       }
+
        /* Copy device string over */
        ast_copy_string(numsubst, channel->device, sizeof(numsubst));
 
@@ -337,6 +351,7 @@ static int begin_dial_prerun(struct ast_dial_channel *channel, struct ast_channe
        if (chan) {
                ast_channel_inherit_variables(chan, channel->owner);
                ast_channel_datastore_inherit(chan, channel->owner);
+               ast_max_forwards_decrement(channel->owner);
 
                /* Copy over callerid information */
                ast_party_redirecting_copy(ast_channel_redirecting(channel->owner), ast_channel_redirecting(chan));
index 971fb4a..4acd8aa 100644 (file)
@@ -78,6 +78,7 @@ ASTERISK_REGISTER_FILE()
 #include "asterisk/stasis.h"
 #include "asterisk/stasis_channels.h"
 #include "asterisk/features_config.h"
+#include "asterisk/max_forwards.h"
 
 /*** DOCUMENTATION
        <application name="Bridge" language="en_US">
@@ -420,22 +421,6 @@ static void add_features_datastores(struct ast_channel *caller, struct ast_chann
        add_features_datastore(callee, &config->features_callee, &config->features_caller);
 }
 
-static void clear_dialed_interfaces(struct ast_channel *chan)
-{
-       struct ast_datastore *di_datastore;
-
-       ast_channel_lock(chan);
-       if ((di_datastore = ast_channel_datastore_find(chan, &dialed_interface_info, NULL))) {
-               if (option_debug) {
-                       ast_log(LOG_DEBUG, "Removing dialed interfaces datastore on %s since we're bridging\n", ast_channel_name(chan));
-               }
-               if (!ast_channel_datastore_remove(chan, di_datastore)) {
-                       ast_datastore_free(di_datastore);
-               }
-       }
-       ast_channel_unlock(chan);
-}
-
 static void bridge_config_set_limits_warning_values(struct ast_bridge_config *config, struct ast_bridge_features_limits *limits)
 {
        if (config->end_sound) {
@@ -572,20 +557,13 @@ static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer,
        ast_channel_log("Pre-bridge PEER Channel info", peer);
 #endif
 
-       /*
-        * If we are bridging a call, stop worrying about forwarding
-        * loops.  We presume that if a call is being bridged, that the
-        * humans in charge know what they're doing.  If they don't,
-        * well, what can we do about that?
-        */
-       clear_dialed_interfaces(chan);
-       clear_dialed_interfaces(peer);
-
        res = 0;
        ast_channel_lock(chan);
+       ast_max_forwards_reset(chan);
        res |= ast_bridge_features_ds_append(chan, &config->features_caller);
        ast_channel_unlock(chan);
        ast_channel_lock(peer);
+       ast_max_forwards_reset(peer);
        res |= ast_bridge_features_ds_append(peer, &config->features_callee);
        ast_channel_unlock(peer);
 
index dd1e027..8ba769d 100644 (file)
 ASTERISK_REGISTER_FILE()
 
 #include "asterisk/global_datastores.h"
-#include "asterisk/linkedlists.h"
-
-static void dialed_interface_destroy(void *data)
-{
-       struct ast_dialed_interface *di = NULL;
-       AST_LIST_HEAD(, ast_dialed_interface) *dialed_interface_list = data;
-       
-       if (!dialed_interface_list) {
-               return;
-       }
-
-       AST_LIST_LOCK(dialed_interface_list);
-       while ((di = AST_LIST_REMOVE_HEAD(dialed_interface_list, list)))
-               ast_free(di);
-       AST_LIST_UNLOCK(dialed_interface_list);
-
-       AST_LIST_HEAD_DESTROY(dialed_interface_list);
-       ast_free(dialed_interface_list);
-}
-
-static void *dialed_interface_duplicate(void *data)
-{
-       struct ast_dialed_interface *di = NULL;
-       AST_LIST_HEAD(, ast_dialed_interface) *old_list;
-       AST_LIST_HEAD(, ast_dialed_interface) *new_list = NULL;
-
-       if(!(old_list = data)) {
-               return NULL;
-       }
-
-       if(!(new_list = ast_calloc(1, sizeof(*new_list)))) {
-               return NULL;
-       }
-
-       AST_LIST_HEAD_INIT(new_list);
-       AST_LIST_LOCK(old_list);
-       AST_LIST_TRAVERSE(old_list, di, list) {
-               struct ast_dialed_interface *di2 = ast_calloc(1, sizeof(*di2) + strlen(di->interface));
-               if(!di2) {
-                       AST_LIST_UNLOCK(old_list);
-                       dialed_interface_destroy(new_list);
-                       return NULL;
-               }
-               strcpy(di2->interface, di->interface);
-               AST_LIST_INSERT_TAIL(new_list, di2, list);
-       }
-       AST_LIST_UNLOCK(old_list);
-
-       return new_list;
-}
-
-const struct ast_datastore_info dialed_interface_info = {
-       .type = "dialed-interface",
-       .destroy = dialed_interface_destroy,
-       .duplicate = dialed_interface_duplicate,
-};
 
 static void secure_call_store_destroy(void *data)
 {
diff --git a/main/max_forwards.c b/main/max_forwards.c
new file mode 100644 (file)
index 0000000..8f1d4ee
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not mfrectly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, mfstributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+
+#include "asterisk/max_forwards.h"
+#include "asterisk/channel.h"
+
+#define DEFAULT_MAX_FORWARDS 20
+
+/*!
+ * \brief Channel datastore data for max forwards
+ */
+struct max_forwards {
+       /*! The starting count. Used to allow resetting to the original value */
+       int starting_count;
+       /*! The current count. When this reaches 0, you're outta luck */
+       int current_count;
+};
+
+static struct max_forwards *max_forwards_alloc(int starting_count, int current_count)
+{
+       struct max_forwards *mf;
+
+       mf = ast_malloc(sizeof(*mf));
+       if (!mf) {
+               return NULL;
+       }
+
+       mf->starting_count = starting_count;
+       mf->current_count = current_count;
+
+       return mf;
+}
+
+static void *max_forwards_duplicate(void *data)
+{
+       struct max_forwards *mf = data;
+
+       return max_forwards_alloc(mf->starting_count, mf->current_count);
+}
+
+static void max_forwards_destroy(void *data)
+{
+       ast_free(data);
+}
+
+const struct ast_datastore_info max_forwards_info = {
+       .type = "mfaled-interface",
+       .duplicate = max_forwards_duplicate,
+       .destroy = max_forwards_destroy,
+};
+
+static struct ast_datastore *max_forwards_datastore_alloc(struct ast_channel *chan,
+               int starting_count)
+{
+       struct ast_datastore *mf_datastore;
+       struct max_forwards *mf;
+
+       mf_datastore = ast_datastore_alloc(&max_forwards_info, NULL);
+       if (!mf_datastore) {
+               return NULL;
+       }
+       mf_datastore->inheritance = DATASTORE_INHERIT_FOREVER;
+
+       mf = max_forwards_alloc(starting_count, starting_count);
+       if (!mf) {
+               ast_datastore_free(mf_datastore);
+               return NULL;
+       }
+       mf_datastore->data = mf;
+
+       ast_channel_datastore_add(chan, mf_datastore);
+
+       return mf_datastore;
+}
+
+static struct ast_datastore *max_forwards_datastore_find_or_alloc(struct ast_channel *chan)
+{
+       struct ast_datastore *mf_datastore;
+
+       mf_datastore = ast_channel_datastore_find(chan, &max_forwards_info, NULL);
+       if (!mf_datastore) {
+               mf_datastore = max_forwards_datastore_alloc(chan, DEFAULT_MAX_FORWARDS);
+       }
+
+       return mf_datastore;
+}
+
+int ast_max_forwards_set(struct ast_channel *chan, int starting_count)
+{
+       struct ast_datastore *mf_datastore;
+       struct max_forwards *mf;
+
+       mf_datastore = max_forwards_datastore_find_or_alloc(chan);
+       if (!mf_datastore) {
+               return -1;
+       }
+
+       mf = mf_datastore->data;
+       mf->starting_count = mf->current_count = starting_count;
+
+       return 0;
+}
+
+int ast_max_forwards_get(struct ast_channel *chan)
+{
+       struct ast_datastore *mf_datastore;
+       struct max_forwards *mf;
+
+       mf_datastore = max_forwards_datastore_find_or_alloc(chan);
+       if (!mf_datastore) {
+               return -1;
+       }
+
+       mf = mf_datastore->data;
+       return mf->current_count;
+}
+
+int ast_max_forwards_decrement(struct ast_channel *chan)
+{
+       struct ast_datastore *mf_datastore;
+       struct max_forwards *mf;
+
+       mf_datastore = max_forwards_datastore_find_or_alloc(chan);
+       if (!mf_datastore) {
+               return -1;
+       }
+
+       mf = mf_datastore->data;
+       --mf->current_count;
+
+       return 0;
+}
+
+int ast_max_forwards_reset(struct ast_channel *chan)
+{
+       struct ast_datastore *mf_datastore;
+       struct max_forwards *mf;
+
+       mf_datastore = max_forwards_datastore_find_or_alloc(chan);
+       if (!mf_datastore) {
+               return -1;
+       }
+
+       mf = mf_datastore->data;
+       mf->current_count = mf->starting_count;
+
+       return 0;
+}
index a4ac157..49f7892 100644 (file)
@@ -248,6 +248,7 @@ static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirect
        pjsip_name_addr *name_addr;
        pjsip_sip_uri *uri;
        pjsip_param *param;
+       pjsip_fromto_hdr *old_hdr;
 
        struct ast_party_id *id = &data->from;
        pjsip_uri *base = PJSIP_MSG_FROM_HDR(tdata->msg)->uri;
@@ -273,6 +274,10 @@ static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirect
        pj_list_insert_before(&hdr->other_param, param);
 
        hdr->uri = (pjsip_uri *) name_addr;
+       old_hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &diversion_name, NULL);
+       if (old_hdr) {
+               pj_list_erase(old_hdr);
+       }
        pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr);
 }