aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/apps/app_dial.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/apps/app_dial.c')
-rw-r--r--trunk/apps/app_dial.c2047
1 files changed, 2047 insertions, 0 deletions
diff --git a/trunk/apps/app_dial.c b/trunk/apps/app_dial.c
new file mode 100644
index 000000000..db1f76c8d
--- /dev/null
+++ b/trunk/apps/app_dial.c
@@ -0,0 +1,2047 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@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.
+ */
+
+/*! \file
+ *
+ * \brief dial() & retrydial() - Trivial application to dial a channel and send an URL on answer
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/time.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+
+#include "asterisk/paths.h" /* use ast_config_AST_DATA_DIR */
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/say.h"
+#include "asterisk/config.h"
+#include "asterisk/features.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/callerid.h"
+#include "asterisk/utils.h"
+#include "asterisk/app.h"
+#include "asterisk/causes.h"
+#include "asterisk/rtp.h"
+#include "asterisk/cdr.h"
+#include "asterisk/manager.h"
+#include "asterisk/privacy.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/global_datastores.h"
+
+static char *app = "Dial";
+
+static char *synopsis = "Place a call and connect to the current channel";
+
+static char *descrip =
+" Dial(Technology/resource[&Tech2/resource2...][,timeout][,options][,URL]):\n"
+"This application will place calls to one or more specified channels. As soon\n"
+"as one of the requested channels answers, the originating channel will be\n"
+"answered, if it has not already been answered. These two channels will then\n"
+"be active in a bridged call. All other channels that were requested will then\n"
+"be hung up.\n"
+" Unless there is a timeout specified, the Dial application will wait\n"
+"indefinitely until one of the called channels answers, the user hangs up, or\n"
+"if all of the called channels are busy or unavailable. Dialplan executing will\n"
+"continue if no requested channels can be called, or if the timeout expires.\n\n"
+" This application sets the following channel variables upon completion:\n"
+" DIALEDTIME - This is the time from dialing a channel until when it\n"
+" is disconnected.\n"
+" ANSWEREDTIME - This is the amount of time for actual call.\n"
+" DIALSTATUS - This is the status of the call:\n"
+" CHANUNAVAIL | CONGESTION | NOANSWER | BUSY | ANSWER | CANCEL\n"
+" DONTCALL | TORTURE | INVALIDARGS\n"
+" For the Privacy and Screening Modes, the DIALSTATUS variable will be set to\n"
+"DONTCALL if the called party chooses to send the calling party to the 'Go Away'\n"
+"script. The DIALSTATUS variable will be set to TORTURE if the called party\n"
+"wants to send the caller to the 'torture' script.\n"
+" This application will report normal termination if the originating channel\n"
+"hangs up, or if the call is bridged and either of the parties in the bridge\n"
+"ends the call.\n"
+" The optional URL will be sent to the called party if the channel supports it.\n"
+" If the OUTBOUND_GROUP variable is set, all peer channels created by this\n"
+"application will be put into that group (as in Set(GROUP()=...).\n"
+" If the OUTBOUND_GROUP_ONCE variable is set, all peer channels created by this\n"
+"application will be put into that group (as in Set(GROUP()=...). Unlike OUTBOUND_GROUP,\n"
+"however, the variable will be unset after use.\n\n"
+" Options:\n"
+" A(x) - Play an announcement to the called party, using 'x' as the file.\n"
+" C - Reset the CDR for this call.\n"
+" c - If DIAL cancels this call, always set the flag to tell the channel\n"
+" driver that the call is answered elsewhere.\n"
+" d - Allow the calling user to dial a 1 digit extension while waiting for\n"
+" a call to be answered. Exit to that extension if it exists in the\n"
+" current context, or the context defined in the EXITCONTEXT variable,\n"
+" if it exists.\n"
+" D([called][:calling]) - Send the specified DTMF strings *after* the called\n"
+" party has answered, but before the call gets bridged. The 'called'\n"
+" DTMF string is sent to the called party, and the 'calling' DTMF\n"
+" string is sent to the calling party. Both parameters can be used\n"
+" alone.\n"
+" e - execute the 'h' extension for peer after the call ends\n"
+" f - Force the callerid of the *calling* channel to be set as the\n"
+" extension associated with the channel using a dialplan 'hint'.\n"
+" For example, some PSTNs do not allow CallerID to be set to anything\n"
+" other than the number assigned to the caller.\n"
+" g - Proceed with dialplan execution at the current extension if the\n"
+" destination channel hangs up.\n"
+" G(context^exten^pri) - If the call is answered, transfer the calling party to\n"
+" the specified priority and the called party to the specified priority+1.\n"
+" Optionally, an extension, or extension and context may be specified. \n"
+" Otherwise, the current extension is used. You cannot use any additional\n"
+" action post answer options in conjunction with this option.\n"
+" h - Allow the called party to hang up by sending the '*' DTMF digit.\n"
+" H - Allow the calling party to hang up by hitting the '*' DTMF digit.\n"
+" i - Asterisk will ignore any forwarding requests it may receive on this\n"
+" dial attempt.\n"
+" k - Allow the called party to enable parking of the call by sending\n"
+" the DTMF sequence defined for call parking in features.conf.\n"
+" K - Allow the calling party to enable parking of the call by sending\n"
+" the DTMF sequence defined for call parking in features.conf.\n"
+" L(x[:y][:z]) - Limit the call to 'x' ms. Play a warning when 'y' ms are\n"
+" left. Repeat the warning every 'z' ms. The following special\n"
+" variables can be used with this option:\n"
+" * LIMIT_PLAYAUDIO_CALLER yes|no (default yes)\n"
+" Play sounds to the caller.\n"
+" * LIMIT_PLAYAUDIO_CALLEE yes|no\n"
+" Play sounds to the callee.\n"
+" * LIMIT_TIMEOUT_FILE File to play when time is up.\n"
+" * LIMIT_CONNECT_FILE File to play when call begins.\n"
+" * LIMIT_WARNING_FILE File to play as warning if 'y' is defined.\n"
+" The default is to say the time remaining.\n"
+" m([class]) - Provide hold music to the calling party until a requested\n"
+" channel answers. A specific MusicOnHold class can be\n"
+" specified.\n"
+" M(x[^arg]) - Execute the Macro for the *called* channel before connecting\n"
+" to the calling channel. Arguments can be specified to the Macro\n"
+" using '^' as a delimeter. The Macro can set the variable\n"
+" MACRO_RESULT to specify the following actions after the Macro is\n"
+" finished executing.\n"
+" * ABORT Hangup both legs of the call.\n"
+" * CONGESTION Behave as if line congestion was encountered.\n"
+" * BUSY Behave as if a busy signal was encountered.\n"
+" * CONTINUE Hangup the called party and allow the calling party\n"
+" to continue dialplan execution at the next priority.\n"
+" * GOTO:<context>^<exten>^<priority> - Transfer the call to the\n"
+" specified priority. Optionally, an extension, or\n"
+" extension and priority can be specified.\n"
+" You cannot use any additional action post answer options in conjunction\n"
+" with this option. Also, pbx services are not run on the peer (called) channel,\n"
+" so you will not be able to set timeouts via the TIMEOUT() function in this macro.\n"
+" n - This option is a modifier for the screen/privacy mode. It specifies\n"
+" that no introductions are to be saved in the priv-callerintros\n"
+" directory.\n"
+" N - This option is a modifier for the screen/privacy mode. It specifies\n"
+" that if callerID is present, do not screen the call.\n"
+" o - Specify that the CallerID that was present on the *calling* channel\n"
+" be set as the CallerID on the *called* channel. This was the\n"
+" behavior of Asterisk 1.0 and earlier.\n"
+" O([x]) - \"Operator Services\" mode (Zaptel channel to Zaptel channel\n"
+" only, if specified on non-Zaptel interface, it will be ignored).\n"
+" When the destination answers (presumably an operator services\n"
+" station), the originator no longer has control of their line.\n"
+" They may hang up, but the switch will not release their line\n"
+" until the destination party hangs up (the operator). Specified\n"
+" without an arg, or with 1 as an arg, the originator hanging up\n"
+" will cause the phone to ring back immediately. With a 2 specified,\n"
+" when the \"operator\" flashes the trunk, it will ring their phone\n"
+" back.\n"
+" p - This option enables screening mode. This is basically Privacy mode\n"
+" without memory.\n"
+" P([x]) - Enable privacy mode. Use 'x' as the family/key in the database if\n"
+" it is provided. The current extension is used if a database\n"
+" family/key is not specified.\n"
+" r - Indicate ringing to the calling party. Pass no audio to the calling\n"
+" party until the called channel has answered.\n"
+" S(x) - Hang up the call after 'x' seconds *after* the called party has\n"
+" answered the call.\n"
+" t - Allow the called party to transfer the calling party by sending the\n"
+" DTMF sequence defined in features.conf.\n"
+" T - Allow the calling party to transfer the called party by sending the\n"
+" DTMF sequence defined in features.conf.\n"
+" U(x[^arg]) - Execute via Gosub the routine 'x' for the *called* channel before connecting\n"
+" to the calling channel. Arguments can be specified to the Gosub\n"
+" using '^' as a delimeter. The Gosub routine can set the variable\n"
+" GOSUB_RESULT to specify the following actions after the Gosub returns.\n"
+" * ABORT Hangup both legs of the call.\n"
+" * CONGESTION Behave as if line congestion was encountered.\n"
+" * BUSY Behave as if a busy signal was encountered.\n"
+" * CONTINUE Hangup the called party and allow the calling party\n"
+" to continue dialplan execution at the next priority.\n"
+" * GOTO:<context>^<exten>^<priority> - Transfer the call to the\n"
+" specified priority. Optionally, an extension, or\n"
+" extension and priority can be specified.\n"
+" You cannot use any additional action post answer options in conjunction\n"
+" with this option. Also, pbx services are not run on the peer (called) channel,\n"
+" so you will not be able to set timeouts via the TIMEOUT() function in this routine.\n"
+" w - Allow the called party to enable recording of the call by sending\n"
+" the DTMF sequence defined for one-touch recording in features.conf.\n"
+" W - Allow the calling party to enable recording of the call by sending\n"
+" the DTMF sequence defined for one-touch recording in features.conf.\n"
+" x - Allow the called party to enable recording of the call by sending\n"
+" the DTMF sequence defined for one-touch automixmonitor in features.conf\n"
+" X - Allow the calling party to enable recording of the call by sending\n"
+" the DTMF sequence defined for one-touch automixmonitor in features.conf\n";
+
+/* RetryDial App by Anthony Minessale II <anthmct@yahoo.com> Jan/2005 */
+static char *rapp = "RetryDial";
+static char *rsynopsis = "Place a call, retrying on failure allowing optional exit extension.";
+static char *rdescrip =
+" RetryDial(announce,sleep,retries,dialargs): This application will attempt to\n"
+"place a call using the normal Dial application. If no channel can be reached,\n"
+"the 'announce' file will be played. Then, it will wait 'sleep' number of\n"
+"seconds before retying the call. After 'retires' number of attempts, the\n"
+"calling channel will continue at the next priority in the dialplan. If the\n"
+"'retries' setting is set to 0, this application will retry endlessly.\n"
+" While waiting to retry a call, a 1 digit extension may be dialed. If that\n"
+"extension exists in either the context defined in ${EXITCONTEXT} or the current\n"
+"one, The call will jump to that extension immediately.\n"
+" The 'dialargs' are specified in the same format that arguments are provided\n"
+"to the Dial application.\n";
+
+enum {
+ OPT_ANNOUNCE = (1 << 0),
+ OPT_RESETCDR = (1 << 1),
+ OPT_DTMF_EXIT = (1 << 2),
+ OPT_SENDDTMF = (1 << 3),
+ OPT_FORCECLID = (1 << 4),
+ OPT_GO_ON = (1 << 5),
+ OPT_CALLEE_HANGUP = (1 << 6),
+ OPT_CALLER_HANGUP = (1 << 7),
+ OPT_DURATION_LIMIT = (1 << 9),
+ OPT_MUSICBACK = (1 << 10),
+ OPT_CALLEE_MACRO = (1 << 11),
+ OPT_SCREEN_NOINTRO = (1 << 12),
+ OPT_SCREEN_NOCLID = (1 << 13),
+ OPT_ORIGINAL_CLID = (1 << 14),
+ OPT_SCREENING = (1 << 15),
+ OPT_PRIVACY = (1 << 16),
+ OPT_RINGBACK = (1 << 17),
+ OPT_DURATION_STOP = (1 << 18),
+ OPT_CALLEE_TRANSFER = (1 << 19),
+ OPT_CALLER_TRANSFER = (1 << 20),
+ OPT_CALLEE_MONITOR = (1 << 21),
+ OPT_CALLER_MONITOR = (1 << 22),
+ OPT_GOTO = (1 << 23),
+ OPT_OPERMODE = (1 << 24),
+ OPT_CALLEE_PARK = (1 << 25),
+ OPT_CALLER_PARK = (1 << 26),
+ OPT_IGNORE_FORWARDING = (1 << 27),
+ OPT_CALLEE_GOSUB = (1 << 28),
+ OPT_CALLEE_MIXMONITOR = (1 << 29),
+ OPT_CALLER_MIXMONITOR = (1 << 30),
+};
+
+#define DIAL_STILLGOING (1 << 31)
+#define DIAL_NOFORWARDHTML ((uint64_t)1 << 32) /* flags are now 64 bits, so keep it up! */
+#define OPT_CANCEL_ELSEWHERE ((uint64_t)1 << 33)
+#define OPT_PEER_H ((uint64_t)1 << 34)
+
+enum {
+ OPT_ARG_ANNOUNCE = 0,
+ OPT_ARG_SENDDTMF,
+ OPT_ARG_GOTO,
+ OPT_ARG_DURATION_LIMIT,
+ OPT_ARG_MUSICBACK,
+ OPT_ARG_CALLEE_MACRO,
+ OPT_ARG_CALLEE_GOSUB,
+ OPT_ARG_PRIVACY,
+ OPT_ARG_DURATION_STOP,
+ OPT_ARG_OPERMODE,
+ /* note: this entry _MUST_ be the last one in the enum */
+ OPT_ARG_ARRAY_SIZE,
+};
+
+AST_APP_OPTIONS(dial_exec_options, BEGIN_OPTIONS
+ AST_APP_OPTION_ARG('A', OPT_ANNOUNCE, OPT_ARG_ANNOUNCE),
+ AST_APP_OPTION('C', OPT_RESETCDR),
+ AST_APP_OPTION('c', OPT_CANCEL_ELSEWHERE),
+ AST_APP_OPTION('d', OPT_DTMF_EXIT),
+ AST_APP_OPTION_ARG('D', OPT_SENDDTMF, OPT_ARG_SENDDTMF),
+ AST_APP_OPTION('e', OPT_PEER_H),
+ AST_APP_OPTION('f', OPT_FORCECLID),
+ AST_APP_OPTION('g', OPT_GO_ON),
+ AST_APP_OPTION_ARG('G', OPT_GOTO, OPT_ARG_GOTO),
+ AST_APP_OPTION('h', OPT_CALLEE_HANGUP),
+ AST_APP_OPTION('H', OPT_CALLER_HANGUP),
+ AST_APP_OPTION('i', OPT_IGNORE_FORWARDING),
+ AST_APP_OPTION('k', OPT_CALLEE_PARK),
+ AST_APP_OPTION('K', OPT_CALLER_PARK),
+ AST_APP_OPTION('k', OPT_CALLEE_PARK),
+ AST_APP_OPTION('K', OPT_CALLER_PARK),
+ AST_APP_OPTION_ARG('L', OPT_DURATION_LIMIT, OPT_ARG_DURATION_LIMIT),
+ AST_APP_OPTION_ARG('m', OPT_MUSICBACK, OPT_ARG_MUSICBACK),
+ AST_APP_OPTION_ARG('M', OPT_CALLEE_MACRO, OPT_ARG_CALLEE_MACRO),
+ AST_APP_OPTION('n', OPT_SCREEN_NOINTRO),
+ AST_APP_OPTION('N', OPT_SCREEN_NOCLID),
+ AST_APP_OPTION('o', OPT_ORIGINAL_CLID),
+ AST_APP_OPTION_ARG('O', OPT_OPERMODE, OPT_ARG_OPERMODE),
+ AST_APP_OPTION('p', OPT_SCREENING),
+ AST_APP_OPTION_ARG('P', OPT_PRIVACY, OPT_ARG_PRIVACY),
+ AST_APP_OPTION('r', OPT_RINGBACK),
+ AST_APP_OPTION_ARG('S', OPT_DURATION_STOP, OPT_ARG_DURATION_STOP),
+ AST_APP_OPTION('t', OPT_CALLEE_TRANSFER),
+ AST_APP_OPTION('T', OPT_CALLER_TRANSFER),
+ AST_APP_OPTION_ARG('U', OPT_CALLEE_GOSUB, OPT_ARG_CALLEE_GOSUB),
+ AST_APP_OPTION('w', OPT_CALLEE_MONITOR),
+ AST_APP_OPTION('W', OPT_CALLER_MONITOR),
+ AST_APP_OPTION('x', OPT_CALLEE_MIXMONITOR),
+ AST_APP_OPTION('X', OPT_CALLER_MIXMONITOR),
+END_OPTIONS );
+
+#define CAN_EARLY_BRIDGE(flags) (!ast_test_flag64(flags, OPT_CALLEE_HANGUP | \
+ OPT_CALLER_HANGUP | OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER | \
+ OPT_CALLEE_MONITOR | OPT_CALLER_MONITOR | OPT_CALLEE_PARK | OPT_CALLER_PARK))
+
+/*
+ * The list of active channels
+ */
+struct chanlist {
+ struct chanlist *next;
+ struct ast_channel *chan;
+ uint64_t flags;
+};
+
+
+static void hanguptree(struct chanlist *outgoing, struct ast_channel *exception, int answered_elsewhere)
+{
+ /* Hang up a tree of stuff */
+ struct chanlist *oo;
+ while (outgoing) {
+ /* Hangup any existing lines we have open */
+ if (outgoing->chan && (outgoing->chan != exception)) {
+ if (answered_elsewhere)
+ ast_set_flag(outgoing->chan, AST_FLAG_ANSWERED_ELSEWHERE);
+ ast_hangup(outgoing->chan);
+ }
+ oo = outgoing;
+ outgoing = outgoing->next;
+ ast_free(oo);
+ }
+}
+
+#define AST_MAX_WATCHERS 256
+
+/*
+ * argument to handle_cause() and other functions.
+ */
+struct cause_args {
+ struct ast_channel *chan;
+ int busy;
+ int congestion;
+ int nochan;
+};
+
+static void handle_cause(int cause, struct cause_args *num)
+{
+ struct ast_cdr *cdr = num->chan->cdr;
+
+ switch(cause) {
+ case AST_CAUSE_BUSY:
+ if (cdr)
+ ast_cdr_busy(cdr);
+ num->busy++;
+ break;
+
+ case AST_CAUSE_CONGESTION:
+ if (cdr)
+ ast_cdr_failed(cdr);
+ num->congestion++;
+ break;
+
+ case AST_CAUSE_UNREGISTERED:
+ if (cdr)
+ ast_cdr_failed(cdr);
+ num->nochan++;
+ break;
+
+ case AST_CAUSE_NORMAL_CLEARING:
+ break;
+
+ default:
+ num->nochan++;
+ break;
+ }
+}
+
+/* free the buffer if allocated, and set the pointer to the second arg */
+#define S_REPLACE(s, new_val) \
+ do { \
+ if (s) \
+ ast_free(s); \
+ s = (new_val); \
+ } while (0)
+
+static int onedigit_goto(struct ast_channel *chan, const char *context, char exten, int pri)
+{
+ char rexten[2] = { exten, '\0' };
+
+ if (context) {
+ if (!ast_goto_if_exists(chan, context, rexten, pri))
+ return 1;
+ } else {
+ if (!ast_goto_if_exists(chan, chan->context, rexten, pri))
+ return 1;
+ else if (!ast_strlen_zero(chan->macrocontext)) {
+ if (!ast_goto_if_exists(chan, chan->macrocontext, rexten, pri))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+static const char *get_cid_name(char *name, int namelen, struct ast_channel *chan)
+{
+ const char *context = S_OR(chan->macrocontext, chan->context);
+ const char *exten = S_OR(chan->macroexten, chan->exten);
+
+ return ast_get_hint(NULL, 0, name, namelen, chan, context, exten) ? name : "";
+}
+
+static void senddialevent(struct ast_channel *src, struct ast_channel *dst, const char *dialstring)
+{
+ manager_event(EVENT_FLAG_CALL, "Dial",
+ "SubEvent: Begin\r\n"
+ "Channel: %s\r\n"
+ "Destination: %s\r\n"
+ "CallerIDNum: %s\r\n"
+ "CallerIDName: %s\r\n"
+ "UniqueID: %s\r\n"
+ "DestUniqueID: %s\r\n"
+ "Dialstring: %s\r\n",
+ src->name, dst->name, S_OR(src->cid.cid_num, "<unknown>"),
+ S_OR(src->cid.cid_name, "<unknown>"), src->uniqueid,
+ dst->uniqueid, dialstring ? dialstring : "");
+}
+
+static void senddialendevent(const struct ast_channel *src, const char *dialstatus)
+{
+ manager_event(EVENT_FLAG_CALL, "Dial",
+ "SubEvent: End\r\n"
+ "Channel: %s\r\n"
+ "UniqueID: %s\r\n"
+ "DialStatus: %s\r\n",
+ src->name, src->uniqueid, dialstatus);
+}
+
+/*!
+ * helper function for wait_for_answer()
+ *
+ * XXX this code is highly suspicious, as it essentially overwrites
+ * the outgoing channel without properly deleting it.
+ */
+static void do_forward(struct chanlist *o,
+ struct cause_args *num, struct ast_flags64 *peerflags, int single)
+{
+ char tmpchan[256];
+ struct ast_channel *original = o->chan;
+ struct ast_channel *c = o->chan; /* the winner */
+ struct ast_channel *in = num->chan; /* the input channel */
+ char *stuff;
+ char *tech;
+ int cause;
+
+ ast_copy_string(tmpchan, c->call_forward, sizeof(tmpchan));
+ if ((stuff = strchr(tmpchan, '/'))) {
+ *stuff++ = '\0';
+ tech = tmpchan;
+ } else {
+ const char *forward_context = pbx_builtin_getvar_helper(c, "FORWARD_CONTEXT");
+ snprintf(tmpchan, sizeof(tmpchan), "%s@%s", c->call_forward, forward_context ? forward_context : c->context);
+ stuff = tmpchan;
+ tech = "Local";
+ }
+ /* Before processing channel, go ahead and check for forwarding */
+ ast_verb(3, "Now forwarding %s to '%s/%s' (thanks to %s)\n", in->name, tech, stuff, c->name);
+ /* If we have been told to ignore forwards, just set this channel to null and continue processing extensions normally */
+ if (ast_test_flag64(peerflags, OPT_IGNORE_FORWARDING)) {
+ ast_verb(3, "Forwarding %s to '%s/%s' prevented.\n", in->name, tech, stuff);
+ c = o->chan = NULL;
+ cause = AST_CAUSE_BUSY;
+ } else {
+ /* Setup parameters */
+ c = o->chan = ast_request(tech, in->nativeformats, stuff, &cause);
+ if (c) {
+ if (single)
+ ast_channel_make_compatible(o->chan, in);
+ ast_channel_inherit_variables(in, o->chan);
+ ast_channel_datastore_inherit(in, o->chan);
+ } else
+ ast_log(LOG_NOTICE, "Unable to create local channel for call forward to '%s/%s' (cause = %d)\n", tech, stuff, cause);
+ }
+ if (!c) {
+ ast_clear_flag64(o, DIAL_STILLGOING);
+ handle_cause(cause, num);
+ } else {
+ char *new_cid_num, *new_cid_name;
+ struct ast_channel *src;
+
+ ast_rtp_make_compatible(c, in, single);
+ if (ast_test_flag64(o, OPT_FORCECLID)) {
+ new_cid_num = ast_strdup(S_OR(in->macroexten, in->exten));
+ new_cid_name = NULL; /* XXX no name ? */
+ src = c; /* XXX possible bug in previous code, which used 'winner' ? it may have changed */
+ } else {
+ new_cid_num = ast_strdup(in->cid.cid_num);
+ new_cid_name = ast_strdup(in->cid.cid_name);
+ src = in;
+ }
+ ast_string_field_set(c, accountcode, src->accountcode);
+ c->cdrflags = src->cdrflags;
+ S_REPLACE(c->cid.cid_num, new_cid_num);
+ S_REPLACE(c->cid.cid_name, new_cid_name);
+
+ if (in->cid.cid_ani) { /* XXX or maybe unconditional ? */
+ S_REPLACE(c->cid.cid_ani, ast_strdup(in->cid.cid_ani));
+ }
+ S_REPLACE(c->cid.cid_rdnis, ast_strdup(S_OR(in->macroexten, in->exten)));
+ if (ast_call(c, tmpchan, 0)) {
+ ast_log(LOG_NOTICE, "Failed to dial on local channel for call forward to '%s'\n", tmpchan);
+ ast_clear_flag64(o, DIAL_STILLGOING);
+ ast_hangup(original);
+ c = o->chan = NULL;
+ num->nochan++;
+ } else {
+ senddialevent(in, c, stuff);
+ /* After calling, set callerid to extension */
+ if (!ast_test_flag64(peerflags, OPT_ORIGINAL_CLID)) {
+ char cidname[AST_MAX_EXTENSION] = "";
+ ast_set_callerid(c, S_OR(in->macroexten, in->exten), get_cid_name(cidname, sizeof(cidname), in), NULL);
+ }
+ /* Hangup the original channel now, in case we needed it */
+ ast_hangup(original);
+ }
+ }
+}
+
+/* argument used for some functions. */
+struct privacy_args {
+ int sentringing;
+ int privdb_val;
+ char privcid[256];
+ char privintro[1024];
+ char status[256];
+};
+
+static struct ast_channel *wait_for_answer(struct ast_channel *in,
+ struct chanlist *outgoing, int *to, struct ast_flags64 *peerflags,
+ struct privacy_args *pa,
+ const struct cause_args *num_in, int *result)
+{
+ struct cause_args num = *num_in;
+ int prestart = num.busy + num.congestion + num.nochan;
+ int orig = *to;
+ struct ast_channel *peer = NULL;
+ /* single is set if only one destination is enabled */
+ int single = outgoing && !outgoing->next && !ast_test_flag64(outgoing, OPT_MUSICBACK | OPT_RINGBACK);
+#ifdef HAVE_EPOLL
+ struct chanlist *epollo;
+#endif
+
+ if (single) {
+ /* Turn off hold music, etc */
+ ast_deactivate_generator(in);
+ /* If we are calling a single channel, make them compatible for in-band tone purpose */
+ ast_channel_make_compatible(outgoing->chan, in);
+ }
+
+#ifdef HAVE_EPOLL
+ for (epollo = outgoing; epollo; epollo = epollo->next)
+ ast_poll_channel_add(in, epollo->chan);
+#endif
+
+ while (*to && !peer) {
+ struct chanlist *o;
+ int pos = 0; /* how many channels do we handle */
+ int numlines = prestart;
+ struct ast_channel *winner;
+ struct ast_channel *watchers[AST_MAX_WATCHERS];
+
+ watchers[pos++] = in;
+ for (o = outgoing; o; o = o->next) {
+ /* Keep track of important channels */
+ if (ast_test_flag64(o, DIAL_STILLGOING) && o->chan)
+ watchers[pos++] = o->chan;
+ numlines++;
+ }
+ if (pos == 1) { /* only the input channel is available */
+ if (numlines == (num.busy + num.congestion + num.nochan)) {
+ ast_verb(2, "Everyone is busy/congested at this time (%d:%d/%d/%d)\n", numlines, num.busy, num.congestion, num.nochan);
+ if (num.busy)
+ strcpy(pa->status, "BUSY");
+ else if (num.congestion)
+ strcpy(pa->status, "CONGESTION");
+ else if (num.nochan)
+ strcpy(pa->status, "CHANUNAVAIL");
+ } else {
+ ast_verb(3, "No one is available to answer at this time (%d:%d/%d/%d)\n", numlines, num.busy, num.congestion, num.nochan);
+ }
+ *to = 0;
+ return NULL;
+ }
+ winner = ast_waitfor_n(watchers, pos, to);
+ for (o = outgoing; o; o = o->next) {
+ struct ast_frame *f;
+ struct ast_channel *c = o->chan;
+
+ if (c == NULL)
+ continue;
+ if (ast_test_flag64(o, DIAL_STILLGOING) && c->_state == AST_STATE_UP) {
+ if (!peer) {
+ ast_verb(3, "%s answered %s\n", c->name, in->name);
+ peer = c;
+ ast_copy_flags64(peerflags, o,
+ OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER |
+ OPT_CALLEE_HANGUP | OPT_CALLER_HANGUP |
+ OPT_CALLEE_MONITOR | OPT_CALLER_MONITOR |
+ OPT_CALLEE_PARK | OPT_CALLER_PARK |
+ OPT_CALLEE_MIXMONITOR | OPT_CALLER_MIXMONITOR |
+ DIAL_NOFORWARDHTML);
+ ast_copy_string(c->dialcontext, "", sizeof(c->dialcontext));
+ ast_copy_string(c->exten, "", sizeof(c->exten));
+ }
+ continue;
+ }
+ if (c != winner)
+ continue;
+ /* here, o->chan == c == winner */
+ if (!ast_strlen_zero(c->call_forward)) {
+ do_forward(o, &num, peerflags, single);
+ continue;
+ }
+ f = ast_read(winner);
+ if (!f) {
+ in->hangupcause = c->hangupcause;
+#ifdef HAVE_EPOLL
+ ast_poll_channel_del(in, c);
+#endif
+ ast_hangup(c);
+ c = o->chan = NULL;
+ ast_clear_flag64(o, DIAL_STILLGOING);
+ handle_cause(in->hangupcause, &num);
+ continue;
+ }
+ if (f->frametype == AST_FRAME_CONTROL) {
+ switch(f->subclass) {
+ case AST_CONTROL_ANSWER:
+ /* This is our guy if someone answered. */
+ if (!peer) {
+ ast_verb(3, "%s answered %s\n", c->name, in->name);
+ peer = c;
+ ast_copy_flags64(peerflags, o,
+ OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER |
+ OPT_CALLEE_HANGUP | OPT_CALLER_HANGUP |
+ OPT_CALLEE_MONITOR | OPT_CALLER_MONITOR |
+ OPT_CALLEE_PARK | OPT_CALLER_PARK |
+ OPT_CALLEE_MIXMONITOR | OPT_CALLER_MIXMONITOR |
+ DIAL_NOFORWARDHTML);
+ ast_copy_string(c->dialcontext, "", sizeof(c->dialcontext));
+ ast_copy_string(c->exten, "", sizeof(c->exten));
+ if (CAN_EARLY_BRIDGE(peerflags))
+ /* Setup early bridge if appropriate */
+ ast_channel_early_bridge(in, peer);
+ }
+ /* If call has been answered, then the eventual hangup is likely to be normal hangup */
+ in->hangupcause = AST_CAUSE_NORMAL_CLEARING;
+ c->hangupcause = AST_CAUSE_NORMAL_CLEARING;
+ break;
+ case AST_CONTROL_BUSY:
+ ast_verb(3, "%s is busy\n", c->name);
+ in->hangupcause = c->hangupcause;
+ ast_hangup(c);
+ c = o->chan = NULL;
+ ast_clear_flag64(o, DIAL_STILLGOING);
+ handle_cause(AST_CAUSE_BUSY, &num);
+ break;
+ case AST_CONTROL_CONGESTION:
+ ast_verb(3, "%s is circuit-busy\n", c->name);
+ in->hangupcause = c->hangupcause;
+ ast_hangup(c);
+ c = o->chan = NULL;
+ ast_clear_flag64(o, DIAL_STILLGOING);
+ handle_cause(AST_CAUSE_CONGESTION, &num);
+ break;
+ case AST_CONTROL_RINGING:
+ ast_verb(3, "%s is ringing\n", c->name);
+ /* Setup early media if appropriate */
+ if (single && CAN_EARLY_BRIDGE(peerflags))
+ ast_channel_early_bridge(in, c);
+ if (!(pa->sentringing) && !ast_test_flag64(outgoing, OPT_MUSICBACK)) {
+ ast_indicate(in, AST_CONTROL_RINGING);
+ pa->sentringing++;
+ }
+ break;
+ case AST_CONTROL_PROGRESS:
+ ast_verb(3, "%s is making progress passing it to %s\n", c->name, in->name);
+ /* Setup early media if appropriate */
+ if (single && CAN_EARLY_BRIDGE(peerflags))
+ ast_channel_early_bridge(in, c);
+ if (!ast_test_flag64(outgoing, OPT_RINGBACK))
+ ast_indicate(in, AST_CONTROL_PROGRESS);
+ break;
+ case AST_CONTROL_VIDUPDATE:
+ ast_verb(3, "%s requested a video update, passing it to %s\n", c->name, in->name);
+ ast_indicate(in, AST_CONTROL_VIDUPDATE);
+ break;
+ case AST_CONTROL_PROCEEDING:
+ ast_verb(3, "%s is proceeding passing it to %s\n", c->name, in->name);
+ if (single && CAN_EARLY_BRIDGE(peerflags))
+ ast_channel_early_bridge(in, c);
+ if (!ast_test_flag64(outgoing, OPT_RINGBACK))
+ ast_indicate(in, AST_CONTROL_PROCEEDING);
+ break;
+ case AST_CONTROL_HOLD:
+ ast_verb(3, "Call on %s placed on hold\n", c->name);
+ ast_indicate(in, AST_CONTROL_HOLD);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_verb(3, "Call on %s left from hold\n", c->name);
+ ast_indicate(in, AST_CONTROL_UNHOLD);
+ break;
+ case AST_CONTROL_OFFHOOK:
+ case AST_CONTROL_FLASH:
+ /* Ignore going off hook and flash */
+ break;
+ case -1:
+ if (!ast_test_flag64(outgoing, OPT_RINGBACK | OPT_MUSICBACK)) {
+ ast_verb(3, "%s stopped sounds\n", c->name);
+ ast_indicate(in, -1);
+ pa->sentringing = 0;
+ }
+ break;
+ default:
+ ast_debug(1, "Dunno what to do with control type %d\n", f->subclass);
+ }
+ } else if (single) {
+ /* XXX are we sure the logic is correct ? or we should just switch on f->frametype ? */
+ if (f->frametype == AST_FRAME_VOICE && !ast_test_flag64(outgoing, OPT_RINGBACK|OPT_MUSICBACK)) {
+ if (ast_write(in, f))
+ ast_log(LOG_WARNING, "Unable to forward voice frame\n");
+ } else if (f->frametype == AST_FRAME_IMAGE && !ast_test_flag64(outgoing, OPT_RINGBACK|OPT_MUSICBACK)) {
+ if (ast_write(in, f))
+ ast_log(LOG_WARNING, "Unable to forward image\n");
+ } else if (f->frametype == AST_FRAME_TEXT && !ast_test_flag64(outgoing, OPT_RINGBACK|OPT_MUSICBACK)) {
+ if (ast_write(in, f))
+ ast_log(LOG_WARNING, "Unable to send text\n");
+ } else if (f->frametype == AST_FRAME_HTML && !ast_test_flag64(outgoing, DIAL_NOFORWARDHTML)) {
+ if (ast_channel_sendhtml(in, f->subclass, f->data, f->datalen) == -1)
+ ast_log(LOG_WARNING, "Unable to send URL\n");
+ }
+ }
+ ast_frfree(f);
+ } /* end for */
+ if (winner == in) {
+ struct ast_frame *f = ast_read(in);
+#if 0
+ if (f && (f->frametype != AST_FRAME_VOICE))
+ printf("Frame type: %d, %d\n", f->frametype, f->subclass);
+ else if (!f || (f->frametype != AST_FRAME_VOICE))
+ printf("Hangup received on %s\n", in->name);
+#endif
+ if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) {
+ /* Got hung up */
+ *to = -1;
+ strcpy(pa->status, "CANCEL");
+ ast_cdr_noanswer(in->cdr);
+ if (f)
+ ast_frfree(f);
+ return NULL;
+ }
+
+ /* now f is guaranteed non-NULL */
+ if (f->frametype == AST_FRAME_DTMF) {
+ if (ast_test_flag64(peerflags, OPT_DTMF_EXIT)) {
+ const char *context = pbx_builtin_getvar_helper(in, "EXITCONTEXT");
+ if (onedigit_goto(in, context, (char) f->subclass, 1)) {
+ ast_verb(3, "User hit %c to disconnect call.\n", f->subclass);
+ *to = 0;
+ ast_cdr_noanswer(in->cdr);
+ *result = f->subclass;
+ strcpy(pa->status, "CANCEL");
+ ast_frfree(f);
+ return NULL;
+ }
+ }
+
+ if (ast_test_flag64(peerflags, OPT_CALLER_HANGUP) &&
+ (f->subclass == '*')) { /* hmm it it not guaranteed to be '*' anymore. */
+ ast_verb(3, "User hit %c to disconnect call.\n", f->subclass);
+ *to = 0;
+ strcpy(pa->status, "CANCEL");
+ ast_cdr_noanswer(in->cdr);
+ ast_frfree(f);
+ return NULL;
+ }
+ }
+
+ /* Forward HTML stuff */
+ if (single && (f->frametype == AST_FRAME_HTML) && !ast_test_flag64(outgoing, DIAL_NOFORWARDHTML))
+ if (ast_channel_sendhtml(outgoing->chan, f->subclass, f->data, f->datalen) == -1)
+ ast_log(LOG_WARNING, "Unable to send URL\n");
+
+
+ if (single && ((f->frametype == AST_FRAME_VOICE) || (f->frametype == AST_FRAME_DTMF_BEGIN) || (f->frametype == AST_FRAME_DTMF_END))) {
+ if (ast_write(outgoing->chan, f))
+ ast_log(LOG_WARNING, "Unable to forward voice or dtmf\n");
+ }
+ if (single && (f->frametype == AST_FRAME_CONTROL) &&
+ ((f->subclass == AST_CONTROL_HOLD) ||
+ (f->subclass == AST_CONTROL_UNHOLD) ||
+ (f->subclass == AST_CONTROL_VIDUPDATE))) {
+ ast_verb(3, "%s requested special control %d, passing it to %s\n", in->name, f->subclass, outgoing->chan->name);
+ ast_indicate_data(outgoing->chan, f->subclass, f->data, f->datalen);
+ }
+ ast_frfree(f);
+ }
+ if (!*to)
+ ast_verb(3, "Nobody picked up in %d ms\n", orig);
+ if (!*to || ast_check_hangup(in)) {
+ ast_cdr_noanswer(in->cdr);
+ }
+
+ }
+ if (peer && !ast_cdr_log_unanswered()) {
+ /* suppress the CDR's that didn't win */
+ struct chanlist *o;
+ for (o = outgoing; o; o = o->next) {
+ struct ast_channel *c = o->chan;
+ if (c && c != peer && c->cdr) {
+ ast_set_flag(c->cdr, AST_CDR_FLAG_POST_DISABLED);
+ }
+ }
+ } else if (!peer && !ast_cdr_log_unanswered()) {
+ /* suppress the CDR's that didn't win */
+ struct chanlist *o;
+ for (o = outgoing; o; o = o->next) {
+ struct ast_channel *c = o->chan;
+ if (c && c->cdr) {
+ ast_set_flag(c->cdr, AST_CDR_FLAG_POST_DISABLED);
+ }
+ }
+ }
+
+#ifdef HAVE_EPOLL
+ for (epollo = outgoing; epollo; epollo = epollo->next) {
+ if (epollo->chan)
+ ast_poll_channel_del(in, epollo->chan);
+ }
+#endif
+
+ return peer;
+}
+
+static void replace_macro_delimiter(char *s)
+{
+ for (; *s; s++)
+ if (*s == '^')
+ *s = ',';
+}
+
+
+/* returns true if there is a valid privacy reply */
+static int valid_priv_reply(struct ast_flags64 *opts, int res)
+{
+ if (res < '1')
+ return 0;
+ if (ast_test_flag64(opts, OPT_PRIVACY) && res <= '5')
+ return 1;
+ if (ast_test_flag64(opts, OPT_SCREENING) && res <= '4')
+ return 1;
+ return 0;
+}
+
+static int do_timelimit(struct ast_channel *chan, struct ast_bridge_config *config,
+ char *parse, unsigned int *calldurationlimit)
+{
+ char *stringp = ast_strdupa(parse);
+ char *limit_str, *warning_str, *warnfreq_str;
+ const char *var;
+ int play_to_caller = 0, play_to_callee = 0;
+ int delta;
+
+ limit_str = strsep(&stringp, ":");
+ warning_str = strsep(&stringp, ":");
+ warnfreq_str = strsep(&stringp, ":");
+
+ config->timelimit = atol(limit_str);
+ if (warning_str)
+ config->play_warning = atol(warning_str);
+ if (warnfreq_str)
+ config->warning_freq = atol(warnfreq_str);
+
+ if (!config->timelimit) {
+ ast_log(LOG_WARNING, "Dial does not accept L(%s), hanging up.\n", limit_str);
+ config->timelimit = config->play_warning = config->warning_freq = 0;
+ config->warning_sound = NULL;
+ return -1; /* error */
+ } else if ( (delta = config->play_warning - config->timelimit) > 0) {
+ int w = config->warning_freq;
+
+ /* If the first warning is requested _after_ the entire call would end,
+ and no warning frequency is requested, then turn off the warning. If
+ a warning frequency is requested, reduce the 'first warning' time by
+ that frequency until it falls within the call's total time limit.
+ Graphically:
+ timelim->| delta |<-playwarning
+ 0__________________|_________________|
+ | w | | | |
+
+ so the number of intervals to cut is 1+(delta-1)/w
+ */
+
+ if (w == 0) {
+ config->play_warning = 0;
+ } else {
+ config->play_warning -= w * ( 1 + (delta-1)/w );
+ if (config->play_warning < 1)
+ config->play_warning = config->warning_freq = 0;
+ }
+ }
+
+ var = pbx_builtin_getvar_helper(chan, "LIMIT_PLAYAUDIO_CALLER");
+ play_to_caller = var ? ast_true(var) : 1;
+
+ var = pbx_builtin_getvar_helper(chan, "LIMIT_PLAYAUDIO_CALLEE");
+ play_to_callee = var ? ast_true(var) : 0;
+
+ if (!play_to_caller && !play_to_callee)
+ play_to_caller = 1;
+
+ var = pbx_builtin_getvar_helper(chan, "LIMIT_WARNING_FILE");
+ config->warning_sound = S_OR(var, "timeleft");
+
+ /* The code looking at config wants a NULL, not just "", to decide
+ * that the message should not be played, so we replace "" with NULL.
+ * Note, pbx_builtin_getvar_helper _can_ return NULL if the variable is
+ * not found.
+ */
+ var = pbx_builtin_getvar_helper(chan, "LIMIT_TIMEOUT_FILE");
+ config->end_sound = S_OR(var, NULL);
+ var = pbx_builtin_getvar_helper(chan, "LIMIT_CONNECT_FILE");
+ config->start_sound = S_OR(var, NULL);
+
+ /* undo effect of S(x) in case they are both used */
+ *calldurationlimit = 0;
+ /* more efficient to do it like S(x) does since no advanced opts */
+ if (!config->play_warning && !config->start_sound && !config->end_sound && config->timelimit) {
+ *calldurationlimit = config->timelimit / 1000;
+ ast_verb(3, "Setting call duration limit to %d seconds.\n",
+ *calldurationlimit);
+ config->timelimit = play_to_caller = play_to_callee =
+ config->play_warning = config->warning_freq = 0;
+ } else {
+ ast_verb(3, "Limit Data for this call:\n");
+ ast_verb(4, "timelimit = %ld\n", config->timelimit);
+ ast_verb(4, "play_warning = %ld\n", config->play_warning);
+ ast_verb(4, "play_to_caller = %s\n", play_to_caller ? "yes" : "no");
+ ast_verb(4, "play_to_callee = %s\n", play_to_callee ? "yes" : "no");
+ ast_verb(4, "warning_freq = %ld\n", config->warning_freq);
+ ast_verb(4, "start_sound = %s\n", S_OR(config->start_sound, ""));
+ ast_verb(4, "warning_sound = %s\n", config->warning_sound);
+ ast_verb(4, "end_sound = %s\n", S_OR(config->end_sound, ""));
+ }
+ if (play_to_caller)
+ ast_set_flag(&(config->features_caller), AST_FEATURE_PLAY_WARNING);
+ if (play_to_callee)
+ ast_set_flag(&(config->features_callee), AST_FEATURE_PLAY_WARNING);
+ return 0;
+}
+
+static int do_privacy(struct ast_channel *chan, struct ast_channel *peer,
+ struct ast_flags64 *opts, char **opt_args, struct privacy_args *pa)
+{
+
+ int res2;
+ int loopcount = 0;
+
+ /* Get the user's intro, store it in priv-callerintros/$CID,
+ unless it is already there-- this should be done before the
+ call is actually dialed */
+
+ /* all ring indications and moh for the caller has been halted as soon as the
+ target extension was picked up. We are going to have to kill some
+ time and make the caller believe the peer hasn't picked up yet */
+
+ if (ast_test_flag64(opts, OPT_MUSICBACK) && !ast_strlen_zero(opt_args[OPT_ARG_MUSICBACK])) {
+ char *original_moh = ast_strdupa(chan->musicclass);
+ ast_indicate(chan, -1);
+ ast_string_field_set(chan, musicclass, opt_args[OPT_ARG_MUSICBACK]);
+ ast_moh_start(chan, opt_args[OPT_ARG_MUSICBACK], NULL);
+ ast_string_field_set(chan, musicclass, original_moh);
+ } else if (ast_test_flag64(opts, OPT_RINGBACK)) {
+ ast_indicate(chan, AST_CONTROL_RINGING);
+ pa->sentringing++;
+ }
+
+ /* Start autoservice on the other chan ?? */
+ res2 = ast_autoservice_start(chan);
+ /* Now Stream the File */
+ for (loopcount = 0; loopcount < 3; loopcount++) {
+ if (res2 && loopcount == 0) /* error in ast_autoservice_start() */
+ break;
+ if (!res2) /* on timeout, play the message again */
+ res2 = ast_play_and_wait(peer, "priv-callpending");
+ if (!valid_priv_reply(opts, res2))
+ res2 = 0;
+ /* priv-callpending script:
+ "I have a caller waiting, who introduces themselves as:"
+ */
+ if (!res2)
+ res2 = ast_play_and_wait(peer, pa->privintro);
+ if (!valid_priv_reply(opts, res2))
+ res2 = 0;
+ /* now get input from the called party, as to their choice */
+ if (!res2) {
+ /* XXX can we have both, or they are mutually exclusive ? */
+ if (ast_test_flag64(opts, OPT_PRIVACY))
+ res2 = ast_play_and_wait(peer, "priv-callee-options");
+ if (ast_test_flag64(opts, OPT_SCREENING))
+ res2 = ast_play_and_wait(peer, "screen-callee-options");
+ }
+ /*! \page DialPrivacy Dial Privacy scripts
+ \par priv-callee-options script:
+ "Dial 1 if you wish this caller to reach you directly in the future,
+ and immediately connect to their incoming call
+ Dial 2 if you wish to send this caller to voicemail now and
+ forevermore.
+ Dial 3 to send this caller to the torture menus, now and forevermore.
+ Dial 4 to send this caller to a simple "go away" menu, now and forevermore.
+ Dial 5 to allow this caller to come straight thru to you in the future,
+ but right now, just this once, send them to voicemail."
+ \par screen-callee-options script:
+ "Dial 1 if you wish to immediately connect to the incoming call
+ Dial 2 if you wish to send this caller to voicemail.
+ Dial 3 to send this caller to the torture menus.
+ Dial 4 to send this caller to a simple "go away" menu.
+ */
+ if (valid_priv_reply(opts, res2))
+ break;
+ /* invalid option */
+ res2 = ast_play_and_wait(peer, "vm-sorry");
+ }
+
+ if (ast_test_flag64(opts, OPT_MUSICBACK)) {
+ ast_moh_stop(chan);
+ } else if (ast_test_flag64(opts, OPT_RINGBACK)) {
+ ast_indicate(chan, -1);
+ pa->sentringing = 0;
+ }
+ ast_autoservice_stop(chan);
+ if (ast_test_flag64(opts, OPT_PRIVACY) && (res2 >= '1' && res2 <= '5')) {
+ /* map keypresses to various things, the index is res2 - '1' */
+ static const char *_val[] = { "ALLOW", "DENY", "TORTURE", "KILL", "ALLOW" };
+ static const int _flag[] = { AST_PRIVACY_ALLOW, AST_PRIVACY_DENY, AST_PRIVACY_TORTURE, AST_PRIVACY_KILL, AST_PRIVACY_ALLOW};
+ int i = res2 - '1';
+ ast_verb(3, "--Set privacy database entry %s/%s to %s\n",
+ opt_args[OPT_ARG_PRIVACY], pa->privcid, _val[i]);
+ ast_privacy_set(opt_args[OPT_ARG_PRIVACY], pa->privcid, _flag[i]);
+ }
+ switch (res2) {
+ case '1':
+ break;
+ case '2':
+ ast_copy_string(pa->status, "NOANSWER", sizeof(pa->status));
+ break;
+ case '3':
+ ast_copy_string(pa->status, "TORTURE", sizeof(pa->status));
+ break;
+ case '4':
+ ast_copy_string(pa->status, "DONTCALL", sizeof(pa->status));
+ break;
+ case '5':
+ /* XXX should we set status to DENY ? */
+ if (ast_test_flag64(opts, OPT_PRIVACY))
+ break;
+ /* if not privacy, then 5 is the same as "default" case */
+ default: /* bad input or -1 if failure to start autoservice */
+ /* well, if the user messes up, ... he had his chance... What Is The Best Thing To Do? */
+ /* well, there seems basically two choices. Just patch the caller thru immediately,
+ or,... put 'em thru to voicemail. */
+ /* since the callee may have hung up, let's do the voicemail thing, no database decision */
+ ast_log(LOG_NOTICE, "privacy: no valid response from the callee. Sending the caller to voicemail, the callee isn't responding\n");
+ /* XXX should we set status to DENY ? */
+ /* XXX what about the privacy flags ? */
+ break;
+ }
+
+ if (res2 == '1') { /* the only case where we actually connect */
+ /* if the intro is NOCALLERID, then there's no reason to leave it on disk, it'll
+ just clog things up, and it's not useful information, not being tied to a CID */
+ if (strncmp(pa->privcid, "NOCALLERID", 10) == 0 || ast_test_flag64(opts, OPT_SCREEN_NOINTRO)) {
+ ast_filedelete(pa->privintro, NULL);
+ if (ast_fileexists(pa->privintro, NULL, NULL) > 0)
+ ast_log(LOG_NOTICE, "privacy: ast_filedelete didn't do its job on %s\n", pa->privintro);
+ else
+ ast_verb(3, "Successfully deleted %s intro file\n", pa->privintro);
+ }
+ return 0; /* the good exit path */
+ } else {
+ ast_hangup(peer); /* hang up on the callee -- he didn't want to talk anyway! */
+ return -1;
+ }
+}
+
+/*! \brief returns 1 if successful, 0 or <0 if the caller should 'goto out' */
+static int setup_privacy_args(struct privacy_args *pa,
+ struct ast_flags64 *opts, char *opt_args[], struct ast_channel *chan)
+{
+ char callerid[60];
+ int res;
+ char *l;
+
+ if (!ast_strlen_zero(chan->cid.cid_num)) {
+ l = ast_strdupa(chan->cid.cid_num);
+ ast_shrink_phone_number(l);
+ if (ast_test_flag64(opts, OPT_PRIVACY) ) {
+ ast_verb(3, "Privacy DB is '%s', clid is '%s'\n", opt_args[OPT_ARG_PRIVACY], l);
+ pa->privdb_val = ast_privacy_check(opt_args[OPT_ARG_PRIVACY], l);
+ } else {
+ ast_verb(3, "Privacy Screening, clid is '%s'\n", l);
+ pa->privdb_val = AST_PRIVACY_UNKNOWN;
+ }
+ } else {
+ char *tnam, *tn2;
+
+ tnam = ast_strdupa(chan->name);
+ /* clean the channel name so slashes don't try to end up in disk file name */
+ for (tn2 = tnam; *tn2; tn2++) {
+ if (*tn2 == '/') /* any other chars to be afraid of? */
+ *tn2 = '=';
+ }
+ ast_verb(3, "Privacy-- callerid is empty\n");
+
+ snprintf(callerid, sizeof(callerid), "NOCALLERID_%s%s", chan->exten, tnam);
+ l = callerid;
+ pa->privdb_val = AST_PRIVACY_UNKNOWN;
+ }
+
+ ast_copy_string(pa->privcid, l, sizeof(pa->privcid));
+
+ if (strncmp(pa->privcid, "NOCALLERID", 10) != 0 && ast_test_flag64(opts, OPT_SCREEN_NOCLID)) {
+ /* if callerid is set and OPT_SCREEN_NOCLID is set also */
+ ast_verb(3, "CallerID set (%s); N option set; Screening should be off\n", pa->privcid);
+ pa->privdb_val = AST_PRIVACY_ALLOW;
+ } else if (ast_test_flag64(opts, OPT_SCREEN_NOCLID) && strncmp(pa->privcid, "NOCALLERID", 10) == 0) {
+ ast_verb(3, "CallerID blank; N option set; Screening should happen; dbval is %d\n", pa->privdb_val);
+ }
+
+ if (pa->privdb_val == AST_PRIVACY_DENY) {
+ ast_verb(3, "Privacy DB reports PRIVACY_DENY for this callerid. Dial reports unavailable\n");
+ ast_copy_string(pa->status, "NOANSWER", sizeof(pa->status));
+ return 0;
+ } else if (pa->privdb_val == AST_PRIVACY_KILL) {
+ ast_copy_string(pa->status, "DONTCALL", sizeof(pa->status));
+ return 0; /* Is this right? */
+ } else if (pa->privdb_val == AST_PRIVACY_TORTURE) {
+ ast_copy_string(pa->status, "TORTURE", sizeof(pa->status));
+ return 0; /* is this right??? */
+ } else if (pa->privdb_val == AST_PRIVACY_UNKNOWN) {
+ /* Get the user's intro, store it in priv-callerintros/$CID,
+ unless it is already there-- this should be done before the
+ call is actually dialed */
+
+ /* make sure the priv-callerintros dir actually exists */
+ snprintf(pa->privintro, sizeof(pa->privintro), "%s/sounds/priv-callerintros", ast_config_AST_DATA_DIR);
+ if ((res = ast_mkdir(pa->privintro, 0755))) {
+ ast_log(LOG_WARNING, "privacy: can't create directory priv-callerintros: %s\n", strerror(res));
+ return -1;
+ }
+
+ snprintf(pa->privintro, sizeof(pa->privintro), "priv-callerintros/%s", pa->privcid);
+ if (ast_fileexists(pa->privintro, NULL, NULL ) > 0 && strncmp(pa->privcid, "NOCALLERID", 10) != 0) {
+ /* the DELUX version of this code would allow this caller the
+ option to hear and retape their previously recorded intro.
+ */
+ } else {
+ int duration; /* for feedback from play_and_wait */
+ /* the file doesn't exist yet. Let the caller submit his
+ vocal intro for posterity */
+ /* priv-recordintro script:
+
+ "At the tone, please say your name:"
+
+ */
+ ast_answer(chan);
+ res = ast_play_and_record(chan, "priv-recordintro", pa->privintro, 4, "gsm", &duration, 128, 2000, 0); /* NOTE: I've reduced the total time to 4 sec */
+ /* don't think we'll need a lock removed, we took care of
+ conflicts by naming the pa.privintro file */
+ if (res == -1) {
+ /* Delete the file regardless since they hung up during recording */
+ ast_filedelete(pa->privintro, NULL);
+ if (ast_fileexists(pa->privintro, NULL, NULL) > 0)
+ ast_log(LOG_NOTICE, "privacy: ast_filedelete didn't do its job on %s\n", pa->privintro);
+ else
+ ast_verb(3, "Successfully deleted %s intro file\n", pa->privintro);
+ return -1;
+ }
+ if (!ast_streamfile(chan, "vm-dialout", chan->language) )
+ ast_waitstream(chan, "");
+ }
+ }
+ return 1; /* success */
+}
+
+static int dial_exec_full(struct ast_channel *chan, void *data, struct ast_flags64 *peerflags, int *continue_exec)
+{
+ int res = -1; /* default: error */
+ char *rest, *cur; /* scan the list of destinations */
+ struct chanlist *outgoing = NULL; /* list of destinations */
+ struct ast_channel *peer;
+ int to; /* timeout */
+ struct cause_args num = { chan, 0, 0, 0 };
+ int cause;
+ char numsubst[256];
+ char cidname[AST_MAX_EXTENSION] = "";
+
+ struct ast_bridge_config config = { { 0, } };
+ unsigned int calldurationlimit = 0;
+ char *dtmfcalled = NULL, *dtmfcalling = NULL;
+ struct privacy_args pa = {
+ .sentringing = 0,
+ .privdb_val = 0,
+ .status = "INVALIDARGS",
+ };
+ int sentringing = 0, moh = 0;
+ const char *outbound_group = NULL;
+ int result = 0;
+ time_t start_time;
+ char *parse;
+ int opermode = 0;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(peers);
+ AST_APP_ARG(timeout);
+ AST_APP_ARG(options);
+ AST_APP_ARG(url);
+ );
+ struct ast_flags64 opts = { 0, };
+ char *opt_args[OPT_ARG_ARRAY_SIZE];
+ struct ast_datastore *datastore = NULL;
+ int fulldial = 0, num_dialed = 0;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "Dial requires an argument (technology/number)\n");
+ pbx_builtin_setvar_helper(chan, "DIALSTATUS", pa.status);
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (!ast_strlen_zero(args.options) &&
+ ast_app_parse_options64(dial_exec_options, &opts, opt_args, args.options)) {
+ pbx_builtin_setvar_helper(chan, "DIALSTATUS", pa.status);
+ goto done;
+ }
+
+ if (ast_strlen_zero(args.peers)) {
+ ast_log(LOG_WARNING, "Dial requires an argument (technology/number)\n");
+ pbx_builtin_setvar_helper(chan, "DIALSTATUS", pa.status);
+ goto done;
+ }
+
+ if (ast_test_flag64(&opts, OPT_OPERMODE)) {
+ opermode = ast_strlen_zero(opt_args[OPT_ARG_OPERMODE]) ? 1 : atoi(opt_args[OPT_ARG_OPERMODE]);
+ ast_verb(3, "Setting operator services mode to %d.\n", opermode);
+ }
+
+ if (ast_test_flag64(&opts, OPT_DURATION_STOP) && !ast_strlen_zero(opt_args[OPT_ARG_DURATION_STOP])) {
+ calldurationlimit = atoi(opt_args[OPT_ARG_DURATION_STOP]);
+ if (!calldurationlimit) {
+ ast_log(LOG_WARNING, "Dial does not accept S(%s), hanging up.\n", opt_args[OPT_ARG_DURATION_STOP]);
+ pbx_builtin_setvar_helper(chan, "DIALSTATUS", pa.status);
+ goto done;
+ }
+ ast_verb(3, "Setting call duration limit to %d seconds.\n", calldurationlimit);
+ }
+
+ if (ast_test_flag64(&opts, OPT_SENDDTMF) && !ast_strlen_zero(opt_args[OPT_ARG_SENDDTMF])) {
+ dtmfcalling = opt_args[OPT_ARG_SENDDTMF];
+ dtmfcalled = strsep(&dtmfcalling, ":");
+ }
+
+ if (ast_test_flag64(&opts, OPT_DURATION_LIMIT) && !ast_strlen_zero(opt_args[OPT_ARG_DURATION_LIMIT])) {
+ if (do_timelimit(chan, &config, opt_args[OPT_ARG_DURATION_LIMIT], &calldurationlimit))
+ goto done;
+ }
+
+ if (ast_test_flag64(&opts, OPT_RESETCDR) && chan->cdr)
+ ast_cdr_reset(chan->cdr, NULL);
+ if (ast_test_flag64(&opts, OPT_PRIVACY) && ast_strlen_zero(opt_args[OPT_ARG_PRIVACY]))
+ opt_args[OPT_ARG_PRIVACY] = ast_strdupa(chan->exten);
+
+ if (ast_test_flag64(&opts, OPT_PRIVACY) || ast_test_flag64(&opts, OPT_SCREENING)) {
+ res = setup_privacy_args(&pa, &opts, opt_args, chan);
+ if (res <= 0)
+ goto out;
+ res = -1; /* reset default */
+ }
+
+ if (continue_exec)
+ *continue_exec = 0;
+
+ /* If a channel group has been specified, get it for use when we create peer channels */
+ if ((outbound_group = pbx_builtin_getvar_helper(chan, "OUTBOUND_GROUP_ONCE"))) {
+ outbound_group = ast_strdupa(outbound_group);
+ pbx_builtin_setvar_helper(chan, "OUTBOUND_GROUP_ONCE", NULL);
+ } else {
+ outbound_group = pbx_builtin_getvar_helper(chan, "OUTBOUND_GROUP");
+ }
+
+ ast_copy_flags64(peerflags, &opts, OPT_DTMF_EXIT | OPT_GO_ON | OPT_ORIGINAL_CLID | OPT_CALLER_HANGUP | OPT_IGNORE_FORWARDING);
+ /* loop through the list of dial destinations */
+ rest = args.peers;
+ while ((cur = strsep(&rest, "&")) ) {
+ struct chanlist *tmp;
+ struct ast_channel *tc; /* channel for this destination */
+ /* Get a technology/[device:]number pair */
+ char *number = cur;
+ char *interface = ast_strdupa(number);
+ char *tech = strsep(&number, "/");
+ /* find if we already dialed this interface */
+ struct ast_dialed_interface *di;
+ AST_LIST_HEAD(, ast_dialed_interface) *dialed_interfaces;
+ num_dialed++;
+ if (!number) {
+ ast_log(LOG_WARNING, "Dial argument takes format (technology/[device:]number1)\n");
+ goto out;
+ }
+ if (!(tmp = ast_calloc(1, sizeof(*tmp))))
+ goto out;
+ if (opts.flags) {
+ ast_copy_flags64(tmp, &opts,
+ OPT_CANCEL_ELSEWHERE |
+ OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER |
+ OPT_CALLEE_HANGUP | OPT_CALLER_HANGUP |
+ OPT_CALLEE_MONITOR | OPT_CALLER_MONITOR |
+ OPT_CALLEE_PARK | OPT_CALLER_PARK |
+ OPT_CALLEE_MIXMONITOR | OPT_CALLER_MIXMONITOR |
+ OPT_RINGBACK | OPT_MUSICBACK | OPT_FORCECLID);
+ ast_set2_flag64(tmp, args.url, DIAL_NOFORWARDHTML);
+ }
+ ast_copy_string(numsubst, number, sizeof(numsubst));
+ /* Request the peer */
+
+ ast_channel_lock(chan);
+ datastore = ast_channel_datastore_find(chan, &dialed_interface_info, NULL);
+ ast_channel_unlock(chan);
+
+ if (datastore)
+ dialed_interfaces = datastore->data;
+ else {
+ if (!(datastore = ast_channel_datastore_alloc(&dialed_interface_info, NULL))) {
+ ast_log(LOG_WARNING, "Unable to create channel datastore for dialed interfaces. Aborting!\n");
+ ast_free(tmp);
+ goto out;
+ }
+
+ datastore->inheritance = DATASTORE_INHERIT_FOREVER;
+
+ if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) {
+ ast_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, 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++;
+ ast_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(tech, "Local")) {
+ if (!(di = ast_calloc(1, sizeof(*di) + strlen(interface)))) {
+ AST_LIST_UNLOCK(dialed_interfaces);
+ ast_free(tmp);
+ goto out;
+ }
+ strcpy(di->interface, interface);
+
+ AST_LIST_LOCK(dialed_interfaces);
+ AST_LIST_INSERT_TAIL(dialed_interfaces, di, list);
+ AST_LIST_UNLOCK(dialed_interfaces);
+ }
+
+ tc = ast_request(tech, chan->nativeformats, numsubst, &cause);
+ if (!tc) {
+ /* If we can't, just go on to the next call */
+ ast_log(LOG_WARNING, "Unable to create channel of type '%s' (cause %d - %s)\n",
+ tech, cause, ast_cause2str(cause));
+ handle_cause(cause, &num);
+ if (!rest) /* we are on the last destination */
+ chan->hangupcause = cause;
+ ast_free(tmp);
+ continue;
+ }
+ pbx_builtin_setvar_helper(tc, "DIALEDPEERNUMBER", numsubst);
+
+ /* Setup outgoing SDP to match incoming one */
+ ast_rtp_make_compatible(tc, chan, !outgoing && !rest);
+
+ /* Inherit specially named variables from parent channel */
+ ast_channel_inherit_variables(chan, tc);
+
+ tc->appl = "AppDial";
+ tc->data = "(Outgoing Line)";
+ tc->whentohangup = 0;
+
+ S_REPLACE(tc->cid.cid_num, ast_strdup(chan->cid.cid_num));
+ S_REPLACE(tc->cid.cid_name, ast_strdup(chan->cid.cid_name));
+ S_REPLACE(tc->cid.cid_ani, ast_strdup(chan->cid.cid_ani));
+ S_REPLACE(tc->cid.cid_rdnis, ast_strdup(chan->cid.cid_rdnis));
+
+ /* Copy language from incoming to outgoing */
+ ast_string_field_set(tc, language, chan->language);
+ ast_string_field_set(tc, accountcode, chan->accountcode);
+ tc->cdrflags = chan->cdrflags;
+ if (ast_strlen_zero(tc->musicclass))
+ ast_string_field_set(tc, musicclass, chan->musicclass);
+ /* Pass callingpres, type of number, tns, ADSI CPE, transfer capability */
+ tc->cid.cid_pres = chan->cid.cid_pres;
+ tc->cid.cid_ton = chan->cid.cid_ton;
+ tc->cid.cid_tns = chan->cid.cid_tns;
+ tc->cid.cid_ani2 = chan->cid.cid_ani2;
+ tc->adsicpe = chan->adsicpe;
+ tc->transfercapability = chan->transfercapability;
+
+ /* If we have an outbound group, set this peer channel to it */
+ if (outbound_group)
+ ast_app_group_set_channel(tc, outbound_group);
+
+ /* Inherit context and extension */
+ if (!ast_strlen_zero(chan->macrocontext))
+ ast_copy_string(tc->dialcontext, chan->macrocontext, sizeof(tc->dialcontext));
+ else
+ ast_copy_string(tc->dialcontext, chan->context, sizeof(tc->dialcontext));
+ if (!ast_strlen_zero(chan->macroexten))
+ ast_copy_string(tc->exten, chan->macroexten, sizeof(tc->exten));
+ else
+ ast_copy_string(tc->exten, chan->exten, sizeof(tc->exten));
+
+ res = ast_call(tc, numsubst, 0); /* Place the call, but don't wait on the answer */
+
+ /* Save the info in cdr's that we called them */
+ if (chan->cdr)
+ ast_cdr_setdestchan(chan->cdr, tc->name);
+
+ /* check the results of ast_call */
+ if (res) {
+ /* Again, keep going even if there's an error */
+ ast_debug(1, "ast call on peer returned %d\n", res);
+ ast_verb(3, "Couldn't call %s\n", numsubst);
+ ast_hangup(tc);
+ tc = NULL;
+ ast_free(tmp);
+ continue;
+ } else {
+ senddialevent(chan, tc, numsubst);
+ ast_verb(3, "Called %s\n", numsubst);
+ if (!ast_test_flag64(peerflags, OPT_ORIGINAL_CLID))
+ ast_set_callerid(tc, S_OR(chan->macroexten, chan->exten), get_cid_name(cidname, sizeof(cidname), chan), NULL);
+ }
+ /* Put them in the list of outgoing thingies... We're ready now.
+ XXX If we're forcibly removed, these outgoing calls won't get
+ hung up XXX */
+ ast_set_flag64(tmp, DIAL_STILLGOING);
+ tmp->chan = tc;
+ tmp->next = outgoing;
+ outgoing = tmp;
+ /* If this line is up, don't try anybody else */
+ if (outgoing->chan->_state == AST_STATE_UP)
+ break;
+ }
+
+ if (ast_strlen_zero(args.timeout)) {
+ to = -1;
+ } else {
+ to = atoi(args.timeout);
+ if (to > 0)
+ to *= 1000;
+ else
+ ast_log(LOG_WARNING, "Invalid timeout specified: '%s'\n", args.timeout);
+ }
+
+ if (!outgoing) {
+ strcpy(pa.status, "CHANUNAVAIL");
+ if (fulldial == num_dialed) {
+ res = -1;
+ goto out;
+ }
+ } else {
+ /* Our status will at least be NOANSWER */
+ strcpy(pa.status, "NOANSWER");
+ if (ast_test_flag64(outgoing, OPT_MUSICBACK)) {
+ moh = 1;
+ if (!ast_strlen_zero(opt_args[OPT_ARG_MUSICBACK])) {
+ char *original_moh = ast_strdupa(chan->musicclass);
+ ast_string_field_set(chan, musicclass, opt_args[OPT_ARG_MUSICBACK]);
+ ast_moh_start(chan, opt_args[OPT_ARG_MUSICBACK], NULL);
+ ast_string_field_set(chan, musicclass, original_moh);
+ } else {
+ ast_moh_start(chan, NULL, NULL);
+ }
+ ast_indicate(chan, AST_CONTROL_PROGRESS);
+ } else if (ast_test_flag64(outgoing, OPT_RINGBACK)) {
+ ast_indicate(chan, AST_CONTROL_RINGING);
+ sentringing++;
+ }
+ }
+
+ time(&start_time);
+ peer = wait_for_answer(chan, outgoing, &to, peerflags, &pa, &num, &result);
+
+ ast_channel_datastore_remove(chan, datastore);
+ ast_channel_datastore_free(datastore);
+ if (!peer) {
+ if (result) {
+ res = result;
+ } else if (to) { /* Musta gotten hung up */
+ res = -1;
+ } else { /* Nobody answered, next please? */
+ res = 0;
+ }
+ /* almost done, although the 'else' block is 400 lines */
+ } else {
+ const char *number;
+ time_t end_time, answer_time = time(NULL);
+ char toast[80]; /* buffer to set variables */
+
+ strcpy(pa.status, "ANSWER");
+ /* Ah ha! Someone answered within the desired timeframe. Of course after this
+ we will always return with -1 so that it is hung up properly after the
+ conversation. */
+ hanguptree(outgoing, peer, 1);
+ outgoing = NULL;
+ /* If appropriate, log that we have a destination channel */
+ if (chan->cdr)
+ ast_cdr_setdestchan(chan->cdr, peer->name);
+ if (peer->name)
+ pbx_builtin_setvar_helper(chan, "DIALEDPEERNAME", peer->name);
+
+ number = pbx_builtin_getvar_helper(peer, "DIALEDPEERNUMBER");
+ if (!number)
+ number = numsubst;
+ pbx_builtin_setvar_helper(chan, "DIALEDPEERNUMBER", number);
+ if (!ast_strlen_zero(args.url) && ast_channel_supports_html(peer) ) {
+ ast_debug(1, "app_dial: sendurl=%s.\n", args.url);
+ ast_channel_sendurl( peer, args.url );
+ }
+ if ( (ast_test_flag64(&opts, OPT_PRIVACY) || ast_test_flag64(&opts, OPT_SCREENING)) && pa.privdb_val == AST_PRIVACY_UNKNOWN) {
+ if (do_privacy(chan, peer, &opts, opt_args, &pa)) {
+ res = 0;
+ goto out;
+ }
+ }
+ if (!ast_test_flag64(&opts, OPT_ANNOUNCE) || ast_strlen_zero(opt_args[OPT_ARG_ANNOUNCE])) {
+ res = 0;
+ } else {
+ int digit = 0;
+ /* Start autoservice on the other chan */
+ res = ast_autoservice_start(chan);
+ /* Now Stream the File */
+ if (!res)
+ res = ast_streamfile(peer, opt_args[OPT_ARG_ANNOUNCE], peer->language);
+ if (!res) {
+ digit = ast_waitstream(peer, AST_DIGIT_ANY);
+ }
+ /* Ok, done. stop autoservice */
+ res = ast_autoservice_stop(chan);
+ if (digit > 0 && !res)
+ res = ast_senddigit(chan, digit, 0);
+ else
+ res = digit;
+
+ }
+
+ if (chan && peer && ast_test_flag64(&opts, OPT_GOTO) && !ast_strlen_zero(opt_args[OPT_ARG_GOTO])) {
+ replace_macro_delimiter(opt_args[OPT_ARG_GOTO]);
+ ast_parseable_goto(chan, opt_args[OPT_ARG_GOTO]);
+ /* peer goes to the same context and extension as chan, so just copy info from chan*/
+ ast_copy_string(peer->context, chan->context, sizeof(peer->context));
+ ast_copy_string(peer->exten, chan->exten, sizeof(peer->exten));
+ peer->priority = chan->priority + 2;
+ ast_pbx_start(peer);
+ hanguptree(outgoing, NULL, ast_test_flag64(&opts, OPT_CANCEL_ELSEWHERE) ? 1 : 0);
+ if (continue_exec)
+ *continue_exec = 1;
+ res = 0;
+ goto done;
+ }
+
+ if (ast_test_flag64(&opts, OPT_CALLEE_MACRO) && !ast_strlen_zero(opt_args[OPT_ARG_CALLEE_MACRO])) {
+ struct ast_app *theapp;
+ const char *macro_result;
+
+ res = ast_autoservice_start(chan);
+ if (res) {
+ ast_log(LOG_ERROR, "Unable to start autoservice on calling channel\n");
+ res = -1;
+ }
+
+ theapp = pbx_findapp("Macro");
+
+ if (theapp && !res) { /* XXX why check res here ? */
+ replace_macro_delimiter(opt_args[OPT_ARG_CALLEE_MACRO]);
+ res = pbx_exec(peer, theapp, opt_args[OPT_ARG_CALLEE_MACRO]);
+ ast_debug(1, "Macro exited with status %d\n", res);
+ res = 0;
+ } else {
+ ast_log(LOG_ERROR, "Could not find application Macro\n");
+ res = -1;
+ }
+
+ if (ast_autoservice_stop(chan) < 0) {
+ ast_log(LOG_ERROR, "Could not stop autoservice on calling channel\n");
+ res = -1;
+ }
+
+ if (!res && (macro_result = pbx_builtin_getvar_helper(peer, "MACRO_RESULT"))) {
+ char *macro_transfer_dest;
+
+ if (!strcasecmp(macro_result, "BUSY")) {
+ ast_copy_string(pa.status, macro_result, sizeof(pa.status));
+ ast_set_flag64(peerflags, OPT_GO_ON);
+ res = -1;
+ } else if (!strcasecmp(macro_result, "CONGESTION") || !strcasecmp(macro_result, "CHANUNAVAIL")) {
+ ast_copy_string(pa.status, macro_result, sizeof(pa.status));
+ ast_set_flag64(peerflags, OPT_GO_ON);
+ res = -1;
+ } else if (!strcasecmp(macro_result, "CONTINUE")) {
+ /* hangup peer and keep chan alive assuming the macro has changed
+ the context / exten / priority or perhaps
+ the next priority in the current exten is desired.
+ */
+ ast_set_flag64(peerflags, OPT_GO_ON);
+ res = -1;
+ } else if (!strcasecmp(macro_result, "ABORT")) {
+ /* Hangup both ends unless the caller has the g flag */
+ res = -1;
+ } else if (!strncasecmp(macro_result, "GOTO:", 5) && (macro_transfer_dest = ast_strdupa(macro_result + 5))) {
+ res = -1;
+ /* perform a transfer to a new extension */
+ if (strchr(macro_transfer_dest, '^')) { /* context^exten^priority*/
+ replace_macro_delimiter(macro_transfer_dest);
+ if (!ast_parseable_goto(chan, macro_transfer_dest))
+ ast_set_flag64(peerflags, OPT_GO_ON);
+ }
+ }
+ }
+ }
+
+ if (ast_test_flag64(&opts, OPT_CALLEE_GOSUB) && !ast_strlen_zero(opt_args[OPT_ARG_CALLEE_GOSUB])) {
+ struct ast_app *theapp;
+ const char *gosub_result;
+ char *gosub_args, *gosub_argstart;
+
+ res = ast_autoservice_start(chan);
+ if (res) {
+ ast_log(LOG_ERROR, "Unable to start autoservice on calling channel\n");
+ res = -1;
+ }
+
+ theapp = pbx_findapp("Gosub");
+
+ if (theapp && !res) { /* XXX why check res here ? */
+ replace_macro_delimiter(opt_args[OPT_ARG_CALLEE_GOSUB]);
+
+ /* Set where we came from */
+ ast_copy_string(peer->context, "app_dial_gosub_virtual_context", sizeof(peer->context));
+ ast_copy_string(peer->exten, "s", sizeof(peer->exten));
+ peer->priority = 0;
+
+ gosub_argstart = strchr(opt_args[OPT_ARG_CALLEE_GOSUB], '|');
+ if (gosub_argstart) {
+ *gosub_argstart = 0;
+ asprintf(&gosub_args, "%s|s|1(%s)", opt_args[OPT_ARG_CALLEE_GOSUB], gosub_argstart + 1);
+ *gosub_argstart = '|';
+ } else {
+ asprintf(&gosub_args, "%s|s|1", opt_args[OPT_ARG_CALLEE_GOSUB]);
+ }
+
+ if (gosub_args) {
+ res = pbx_exec(peer, theapp, gosub_args);
+ ast_pbx_run(peer);
+ ast_free(gosub_args);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Gosub exited with status %d\n", res);
+ } else
+ ast_log(LOG_ERROR, "Could not Allocate string for Gosub arguments -- Gosub Call Aborted!\n");
+
+ res = 0;
+ } else {
+ ast_log(LOG_ERROR, "Could not find application Gosub\n");
+ res = -1;
+ }
+
+ if (ast_autoservice_stop(chan) < 0) {
+ ast_log(LOG_ERROR, "Could not stop autoservice on calling channel\n");
+ res = -1;
+ }
+
+ if (!res && (gosub_result = pbx_builtin_getvar_helper(peer, "GOSUB_RESULT"))) {
+ char *gosub_transfer_dest;
+
+ if (!strcasecmp(gosub_result, "BUSY")) {
+ ast_copy_string(pa.status, gosub_result, sizeof(pa.status));
+ ast_set_flag64(peerflags, OPT_GO_ON);
+ res = -1;
+ } else if (!strcasecmp(gosub_result, "CONGESTION") || !strcasecmp(gosub_result, "CHANUNAVAIL")) {
+ ast_copy_string(pa.status, gosub_result, sizeof(pa.status));
+ ast_set_flag64(peerflags, OPT_GO_ON);
+ res = -1;
+ } else if (!strcasecmp(gosub_result, "CONTINUE")) {
+ /* hangup peer and keep chan alive assuming the macro has changed
+ the context / exten / priority or perhaps
+ the next priority in the current exten is desired.
+ */
+ ast_set_flag64(peerflags, OPT_GO_ON);
+ res = -1;
+ } else if (!strcasecmp(gosub_result, "ABORT")) {
+ /* Hangup both ends unless the caller has the g flag */
+ res = -1;
+ } else if (!strncasecmp(gosub_result, "GOTO:", 5) && (gosub_transfer_dest = ast_strdupa(gosub_result + 5))) {
+ res = -1;
+ /* perform a transfer to a new extension */
+ if (strchr(gosub_transfer_dest, '^')) { /* context^exten^priority*/
+ replace_macro_delimiter(gosub_transfer_dest);
+ if (!ast_parseable_goto(chan, gosub_transfer_dest))
+ ast_set_flag64(peerflags, OPT_GO_ON);
+ }
+ }
+ }
+ }
+
+ if (!res) {
+ if (calldurationlimit > 0) {
+ peer->whentohangup = time(NULL) + calldurationlimit;
+ }
+ if (!ast_strlen_zero(dtmfcalled)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Sending DTMF '%s' to the called party.\n", dtmfcalled);
+ res = ast_dtmf_stream(peer, chan, dtmfcalled, 250, 0);
+ }
+ if (!ast_strlen_zero(dtmfcalling)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Sending DTMF '%s' to the calling party.\n", dtmfcalling);
+ res = ast_dtmf_stream(chan, peer, dtmfcalling, 250, 0);
+ }
+ }
+
+ if (res) { /* some error */
+ res = -1;
+ end_time = time(NULL);
+ } else {
+ if (ast_test_flag64(peerflags, OPT_CALLEE_TRANSFER))
+ ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
+ if (ast_test_flag64(peerflags, OPT_CALLER_TRANSFER))
+ ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT);
+ if (ast_test_flag64(peerflags, OPT_CALLEE_HANGUP))
+ ast_set_flag(&(config.features_callee), AST_FEATURE_DISCONNECT);
+ if (ast_test_flag64(peerflags, OPT_CALLER_HANGUP))
+ ast_set_flag(&(config.features_caller), AST_FEATURE_DISCONNECT);
+ if (ast_test_flag64(peerflags, OPT_CALLEE_MONITOR))
+ ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMON);
+ if (ast_test_flag64(peerflags, OPT_CALLER_MONITOR))
+ ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMON);
+ if (ast_test_flag64(peerflags, OPT_CALLEE_PARK))
+ ast_set_flag(&(config.features_callee), AST_FEATURE_PARKCALL);
+ if (ast_test_flag64(peerflags, OPT_CALLER_PARK))
+ ast_set_flag(&(config.features_caller), AST_FEATURE_PARKCALL);
+ if (ast_test_flag64(peerflags, OPT_CALLEE_MIXMONITOR))
+ ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMIXMON);
+ if (ast_test_flag64(peerflags, OPT_CALLER_MIXMONITOR))
+ ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMIXMON);
+
+ if (moh) {
+ moh = 0;
+ ast_moh_stop(chan);
+ } else if (sentringing) {
+ sentringing = 0;
+ ast_indicate(chan, -1);
+ }
+ /* Be sure no generators are left on it */
+ ast_deactivate_generator(chan);
+ /* Make sure channels are compatible */
+ res = ast_channel_make_compatible(chan, peer);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", chan->name, peer->name);
+ ast_hangup(peer);
+ res = -1;
+ goto done;
+ }
+ if (opermode && !strncmp(chan->name, "Zap", 3) && !strncmp(peer->name, "Zap", 3)) {
+ /* what's this special handling for Zap <-> Zap ?
+ * A: Zap to Zap calls are natively bridged at the kernel driver
+ * level, so we need to ensure that this mode gets propagated
+ * all the way down. */
+ struct oprmode oprmode;
+
+ oprmode.peer = peer;
+ oprmode.mode = opermode;
+
+ ast_channel_setoption(chan, AST_OPTION_OPRMODE, &oprmode, sizeof(oprmode), 0);
+ }
+ res = ast_bridge_call(chan, peer, &config);
+ end_time = time(NULL);
+ snprintf(toast, sizeof(toast), "%ld", (long)(end_time - answer_time));
+ pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", toast);
+ }
+
+ snprintf(toast, sizeof(toast), "%ld", (long)(end_time - start_time));
+ pbx_builtin_setvar_helper(chan, "DIALEDTIME", toast);
+
+
+ if (ast_test_flag64(&opts, OPT_PEER_H)) {
+ ast_log(LOG_NOTICE, "PEER context: %s; PEER exten: %s; PEER priority: %d\n",
+ peer->context, peer->exten, peer->priority);
+ }
+
+ strcpy(peer->context, chan->context);
+
+ if (ast_test_flag64(&opts, OPT_PEER_H) && ast_exists_extension(peer, peer->context, "h", 1, peer->cid.cid_num)) {
+ int autoloopflag;
+ int found;
+ strcpy(peer->exten, "h");
+ peer->priority = 1;
+ autoloopflag = ast_test_flag(peer, AST_FLAG_IN_AUTOLOOP); /* save value to restore at the end */
+ ast_set_flag(peer, AST_FLAG_IN_AUTOLOOP);
+
+ while ((res = ast_spawn_extension(peer, peer->context, peer->exten, peer->priority, peer->cid.cid_num, &found, 1))) {
+ peer->priority++;
+ }
+ if (found && res) {
+ /* Something bad happened, or a hangup has been requested. */
+ ast_debug(1, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n", peer->context, peer->exten, peer->priority, peer->name);
+ ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", peer->context, peer->exten, peer->priority, peer->name);
+ }
+ ast_set2_flag(peer, autoloopflag, AST_FLAG_IN_AUTOLOOP); /* set it back the way it was */
+ }
+ if (res != AST_PBX_NO_HANGUP_PEER) {
+ if (!ast_check_hangup(chan))
+ chan->hangupcause = peer->hangupcause;
+ ast_hangup(peer);
+ }
+ }
+out:
+ if (moh) {
+ moh = 0;
+ ast_moh_stop(chan);
+ } else if (sentringing) {
+ sentringing = 0;
+ ast_indicate(chan, -1);
+ }
+ ast_channel_early_bridge(chan, NULL);
+ hanguptree(outgoing, NULL, 0); /* In this case, there's no answer anywhere */
+ pbx_builtin_setvar_helper(chan, "DIALSTATUS", pa.status);
+ senddialendevent(chan, pa.status);
+ ast_debug(1, "Exiting with DIALSTATUS=%s.\n", pa.status);
+
+ if ((ast_test_flag64(peerflags, OPT_GO_ON)) && !ast_check_hangup(chan) && (res != AST_PBX_KEEPALIVE)) {
+ if (calldurationlimit)
+ chan->whentohangup = 0;
+ res = 0;
+ }
+
+done:
+ return res;
+}
+
+static int dial_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_flags64 peerflags;
+
+ memset(&peerflags, 0, sizeof(peerflags));
+
+ return dial_exec_full(chan, data, &peerflags, NULL);
+}
+
+static int retrydial_exec(struct ast_channel *chan, void *data)
+{
+ char *parse;
+ const char *context = NULL;
+ int sleep = 0, loops = 0, res = -1;
+ struct ast_flags64 peerflags = { 0, };
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(announce);
+ AST_APP_ARG(sleep);
+ AST_APP_ARG(retries);
+ AST_APP_ARG(dialdata);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "RetryDial requires an argument!\n");
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if ((sleep = atoi(args.sleep))) {
+ sleep *= 1000;
+ }
+
+ loops = atoi(args.retries);
+
+ if (!args.dialdata) {
+ ast_log(LOG_ERROR, "%s requires a 4th argument (dialdata)\n", rapp);
+ goto done;
+ }
+
+ if (sleep < 1000)
+ sleep = 10000;
+
+ if (!loops)
+ loops = -1; /* run forever */
+
+ context = pbx_builtin_getvar_helper(chan, "EXITCONTEXT");
+
+ res = 0;
+ while (loops) {
+ int continue_exec;
+
+ chan->data = "Retrying";
+ if (ast_test_flag(chan, AST_FLAG_MOH))
+ ast_moh_stop(chan);
+
+ res = dial_exec_full(chan, args.dialdata, &peerflags, &continue_exec);
+ if (continue_exec)
+ break;
+
+ if (res == 0) {
+ if (ast_test_flag64(&peerflags, OPT_DTMF_EXIT)) {
+ if (!ast_strlen_zero(args.announce)) {
+ if (ast_fileexists(args.announce, NULL, chan->language) > 0) {
+ if (!(res = ast_streamfile(chan, args.announce, chan->language)))
+ ast_waitstream(chan, AST_DIGIT_ANY);
+ } else
+ ast_log(LOG_WARNING, "Announce file \"%s\" specified in Retrydial does not exist\n", args.announce);
+ }
+ if (!res && sleep) {
+ if (!ast_test_flag(chan, AST_FLAG_MOH))
+ ast_moh_start(chan, NULL, NULL);
+ res = ast_waitfordigit(chan, sleep);
+ }
+ } else {
+ if (!ast_strlen_zero(args.announce)) {
+ if (ast_fileexists(args.announce, NULL, chan->language) > 0) {
+ if (!(res = ast_streamfile(chan, args.announce, chan->language)))
+ res = ast_waitstream(chan, "");
+ } else
+ ast_log(LOG_WARNING, "Announce file \"%s\" specified in Retrydial does not exist\n", args.announce);
+ }
+ if (sleep) {
+ if (!ast_test_flag(chan, AST_FLAG_MOH))
+ ast_moh_start(chan, NULL, NULL);
+ if (!res)
+ res = ast_waitfordigit(chan, sleep);
+ }
+ }
+ }
+
+ if (res < 0)
+ break;
+ else if (res > 0) { /* Trying to send the call elsewhere (1 digit ext) */
+ if (onedigit_goto(chan, context, (char) res, 1)) {
+ res = 0;
+ break;
+ }
+ }
+ loops--;
+ }
+ if (loops == 0)
+ res = 0;
+ else if (res == 1)
+ res = 0;
+
+ if (ast_test_flag(chan, AST_FLAG_MOH))
+ ast_moh_stop(chan);
+ done:
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+ struct ast_context *con;
+
+ res = ast_unregister_application(app);
+ res |= ast_unregister_application(rapp);
+
+ if ((con = ast_context_find("app_dial_gosub_virtual_context")))
+ {
+ ast_context_remove_extension2(con, "s", 1, NULL);
+ ast_context_destroy(con, "app_dial"); /* leave nothing behind */
+ }
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+ struct ast_context *con;
+
+ con = ast_context_find("app_dial_gosub_virtual_context");
+ if (!con)
+ con = ast_context_create(NULL, "app_dial_gosub_virtual_context", "app_dial");
+ if (!con)
+ ast_log(LOG_ERROR, "Dial virtual context 'app_dial_gosub_virtual_context' does not exist and unable to create\n");
+ else
+ ast_add_extension2(con, 1, "s", 1, NULL, NULL, "KeepAlive", ast_strdup(""), ast_free_ptr, "app_dial");
+
+ res = ast_register_application(app, dial_exec, synopsis, descrip);
+ res |= ast_register_application(rapp, retrydial_exec, rsynopsis, rdescrip);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialing Application");