Newer
Older
* Asterisk -- An open source telephony toolkit.
*
* A full-featured Find-Me/Follow-Me Application
* Copyright (C) 2005-2006, BJ Weschke All Rights Reserved.
*
* BJ Weschke <bweschke@btwtech.com>
*
* This code is released by the author with no restrictions on usage.
*
* 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.
*
*/
/*! \file
*
* \brief Find-Me Follow-Me application
*
* \author BJ Weschke <bweschke@btwtech.com>
*
* \ingroup applications
*/
/*! \li \ref app_followme.c uses the configuration file \ref followme.conf
* \addtogroup configuration_file Configuration Files
*/
* \page followme.conf followme.conf
* \verbinclude followme.conf.sample
*/
<support_level>core</support_level>
Kevin P. Fleming
committed
#include "asterisk.h"
#include "asterisk/paths.h" /* use ast_config_AST_SPOOL_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/features.h"
#include "asterisk/musiconhold.h"
#include "asterisk/cli.h"
#include "asterisk/manager.h"
#include "asterisk/config.h"
#include "asterisk/utils.h"
#include "asterisk/causes.h"
#include "asterisk/astdb.h"
#include "asterisk/dsp.h"
#include "asterisk/stasis_channels.h"
#include "asterisk/max_forwards.h"
#define REC_FORMAT "sln"
/*** DOCUMENTATION
<application name="FollowMe" language="en_US">
<synopsis>
Find-Me/Follow-Me application.
</synopsis>
<syntax>
<parameter name="followmeid" required="true" />
<parameter name="options">
<optionlist>
<option name="a">
<para>Record the caller's name so it can be announced to the
callee on each step.</para>
</option>
<option name="B" argsep="^">
<para>Before initiating the outgoing call(s), Gosub to the specified
location using the current channel.</para>
<argument name="context" required="false" />
<argument name="exten" required="false" />
<argument name="priority" required="true" hasparams="optional" argsep="^">
<argument name="arg1" multiple="true" required="true" />
<argument name="argN" />
</argument>
</option>
<option name="b" argsep="^">
<para>Before initiating an outgoing call, Gosub to the specified
location using the newly created channel. The Gosub will be
executed for each destination channel.</para>
<argument name="context" required="false" />
<argument name="exten" required="false" />
<argument name="priority" required="true" hasparams="optional" argsep="^">
<argument name="arg1" multiple="true" required="true" />
<argument name="argN" />
</argument>
</option>
Matthew Nicholson
committed
<option name="d">
<para>Disable the 'Please hold while we try to connect your call' announcement.</para>
</option>
Richard Mudgett
committed
<option name="I">
<para>Asterisk will ignore any connected line update requests
it may receive on this dial attempt.</para>
</option>
Jonathan Rose
committed
<option name="l">
<para>Disable local call optimization so that applications with
audio hooks between the local bridge don't get dropped when the
calls get joined directly.</para>
</option>
Richard Mudgett
committed
<option name="N">
<para>Don't answer the incoming call until we're ready to
connect the caller or give up.</para>
<note>
<para>This option is ignored if the call is already answered.</para>
</note>
<note>
<para>If the call is not already answered, the 'a' and 's'
options are ignored while the 'd' option is implicitly enabled.</para>
</note>
</option>
<option name="n">
<para>Playback the unreachable status message if we've run out
of steps or the callee has elected not to be reachable.</para>
</option>
<option name="s">
<para>Playback the incoming status message prior to starting
the follow-me step(s)</para>
</option>
</optionlist>
</parameter>
</syntax>
<description>
<para>This application performs Find-Me/Follow-Me functionality for the caller
as defined in the profile matching the <replaceable>followmeid</replaceable> parameter in
<filename>followme.conf</filename>. If the specified <replaceable>followmeid</replaceable>
profile doesn't exist in <filename>followme.conf</filename>, execution will be returned
to the dialplan and call execution will continue at the next priority.</para>
<para>Returns -1 on hangup.</para>
</description>
</application>
***/
/*! Maximum accept/decline DTMF string plus terminator. */
#define MAX_YN_STRING 20
/*! \brief Number structure */
struct number {
char number[512]; /*!< Phone Number(s) and/or Extension(s) */
long timeout; /*!< Dial Timeout, if used. */
int order; /*!< The order to dial in */
AST_LIST_ENTRY(number) entry; /*!< Next Number record */
};
/*! \brief Data structure for followme scripts */
struct call_followme {
ast_mutex_t lock;
char name[AST_MAX_EXTENSION]; /*!< Name - FollowMeID */
char moh[MAX_MUSICCLASS]; /*!< Music On Hold Class to be used */
char context[AST_MAX_CONTEXT]; /*!< Context to dial from */
unsigned int active; /*!< Profile is active (1), or disabled (0). */
int realtime; /*!< Cached from realtime */
/*! Allow callees to accept/reject the forwarded call */
unsigned int enable_callee_prompt:1;
char takecall[MAX_YN_STRING]; /*!< Digit mapping to take a call */
char nextindp[MAX_YN_STRING]; /*!< Digit mapping to decline a call */
char callfromprompt[PATH_MAX]; /*!< Sound prompt name and path */
char norecordingprompt[PATH_MAX]; /*!< Sound prompt name and path */
char optionsprompt[PATH_MAX]; /*!< Sound prompt name and path */
char plsholdprompt[PATH_MAX]; /*!< Sound prompt name and path */
char statusprompt[PATH_MAX]; /*!< Sound prompt name and path */
char sorryprompt[PATH_MAX]; /*!< Sound prompt name and path */
char connprompt[PATH_MAX]; /*!< Sound prompt name and path */
AST_LIST_HEAD_NOLOCK(numbers, number) numbers; /*!< Head of the list of follow-me numbers */
AST_LIST_HEAD_NOLOCK(blnumbers, number) blnumbers; /*!< Head of the list of black-listed numbers */
AST_LIST_HEAD_NOLOCK(wlnumbers, number) wlnumbers; /*!< Head of the list of white-listed numbers */
AST_LIST_ENTRY(call_followme) entry; /*!< Next Follow-Me record */
};
struct fm_args {
char *mohclass;
AST_LIST_HEAD_NOLOCK(cnumbers, number) cnumbers;
/*! Gosub app arguments for outgoing calls. NULL if not supplied. */
const char *predial_callee;
Richard Mudgett
committed
/*! Accumulated connected line information from inbound call. */
struct ast_party_connected_line connected_in;
/*! Accumulated connected line information from outbound call. */
struct ast_party_connected_line connected_out;
/*! TRUE if connected line information from inbound call changed. */
unsigned int pending_in_connected_update:1;
Richard Mudgett
committed
/*! TRUE if connected line information from outbound call is available. */
unsigned int pending_out_connected_update:1;
/*! TRUE if caller has a pending hold request for the winning call. */
unsigned int pending_hold:1;
/*! TRUE if callees will be prompted to answer */
unsigned int enable_callee_prompt:1;
/*! Music On Hold Class suggested by caller hold for winning call. */
char suggested_moh[MAX_MUSICCLASS];
char namerecloc[PATH_MAX];
char takecall[MAX_YN_STRING]; /*!< Digit mapping to take a call */
char nextindp[MAX_YN_STRING]; /*!< Digit mapping to decline a call */
char callfromprompt[PATH_MAX]; /*!< Sound prompt name and path */
char norecordingprompt[PATH_MAX]; /*!< Sound prompt name and path */
char optionsprompt[PATH_MAX]; /*!< Sound prompt name and path */
char plsholdprompt[PATH_MAX]; /*!< Sound prompt name and path */
char statusprompt[PATH_MAX]; /*!< Sound prompt name and path */
char sorryprompt[PATH_MAX]; /*!< Sound prompt name and path */
char connprompt[PATH_MAX]; /*!< Sound prompt name and path */
struct ast_flags followmeflags;
};
struct findme_user {
struct ast_channel *ochan;
Richard Mudgett
committed
/*! Accumulated connected line information from outgoing call. */
struct ast_party_connected_line connected;
long digts;
int ynidx;
/*! Collected digits to accept/decline the call. */
char yn[MAX_YN_STRING];
/*! TRUE if the outgoing call is answered. */
unsigned int answered:1;
Richard Mudgett
committed
/*! TRUE if connected line information is available. */
unsigned int pending_connected_update:1;
AST_LIST_ENTRY(findme_user) entry;
};
enum {
FOLLOWMEFLAG_STATUSMSG = (1 << 0),
FOLLOWMEFLAG_RECORDNAME = (1 << 1),
Matthew Nicholson
committed
FOLLOWMEFLAG_UNREACHABLEMSG = (1 << 2),
FOLLOWMEFLAG_DISABLEHOLDPROMPT = (1 << 3),
Jonathan Rose
committed
FOLLOWMEFLAG_NOANSWER = (1 << 4),
FOLLOWMEFLAG_DISABLEOPTIMIZATION = (1 << 5),
Richard Mudgett
committed
FOLLOWMEFLAG_IGNORE_CONNECTEDLINE = (1 << 6),
FOLLOWMEFLAG_PREDIAL_CALLER = (1 << 7),
FOLLOWMEFLAG_PREDIAL_CALLEE = (1 << 8),
};
enum {
FOLLOWMEFLAG_ARG_PREDIAL_CALLER,
FOLLOWMEFLAG_ARG_PREDIAL_CALLEE,
/* note: this entry _MUST_ be the last one in the enum */
FOLLOWMEFLAG_ARG_ARRAY_SIZE
};
AST_APP_OPTIONS(followme_opts, {
Richard Mudgett
committed
AST_APP_OPTION('a', FOLLOWMEFLAG_RECORDNAME),
AST_APP_OPTION_ARG('B', FOLLOWMEFLAG_PREDIAL_CALLER, FOLLOWMEFLAG_ARG_PREDIAL_CALLER),
AST_APP_OPTION_ARG('b', FOLLOWMEFLAG_PREDIAL_CALLEE, FOLLOWMEFLAG_ARG_PREDIAL_CALLEE),
Richard Mudgett
committed
AST_APP_OPTION('d', FOLLOWMEFLAG_DISABLEHOLDPROMPT),
AST_APP_OPTION('I', FOLLOWMEFLAG_IGNORE_CONNECTEDLINE),
AST_APP_OPTION('l', FOLLOWMEFLAG_DISABLEOPTIMIZATION),
AST_APP_OPTION('N', FOLLOWMEFLAG_NOANSWER),
AST_APP_OPTION('n', FOLLOWMEFLAG_UNREACHABLEMSG),
AST_APP_OPTION('s', FOLLOWMEFLAG_STATUSMSG),
static const char *featuredigittostr;
static int featuredigittimeout = 5000; /*!< Feature Digit Timeout */
static const char *defaultmoh = "default"; /*!< Default Music-On-Hold Class */
static char takecall[MAX_YN_STRING] = "1";
static char nextindp[MAX_YN_STRING] = "2";
static int enable_callee_prompt = 1;
Kevin P. Fleming
committed
static char callfromprompt[PATH_MAX] = "followme/call-from";
static char norecordingprompt[PATH_MAX] = "followme/no-recording";
static char optionsprompt[PATH_MAX] = "followme/options";
Kevin P. Fleming
committed
static char plsholdprompt[PATH_MAX] = "followme/pls-hold-while-try";
static char statusprompt[PATH_MAX] = "followme/status";
static char sorryprompt[PATH_MAX] = "followme/sorry";
static char connprompt[PATH_MAX] = "";
static AST_RWLIST_HEAD_STATIC(followmes, call_followme);
static void free_numbers(struct call_followme *f)
{
/* Free numbers attached to the profile */
struct number *prev;
while ((prev = AST_LIST_REMOVE_HEAD(&f->numbers, entry)))
/* Free the number */
Tilghman Lesher
committed
ast_free(prev);
AST_LIST_HEAD_INIT_NOLOCK(&f->numbers);
while ((prev = AST_LIST_REMOVE_HEAD(&f->blnumbers, entry)))
/* Free the blacklisted number */
Tilghman Lesher
committed
ast_free(prev);
AST_LIST_HEAD_INIT_NOLOCK(&f->blnumbers);
while ((prev = AST_LIST_REMOVE_HEAD(&f->wlnumbers, entry)))
/* Free the whitelisted number */
Tilghman Lesher
committed
ast_free(prev);
AST_LIST_HEAD_INIT_NOLOCK(&f->wlnumbers);
}
/*! \brief Allocate and initialize followme profile */
static struct call_followme *alloc_profile(const char *fmname)
struct call_followme *f;
if (!(f = ast_calloc(1, sizeof(*f))))
return NULL;
ast_mutex_init(&f->lock);
ast_copy_string(f->name, fmname, sizeof(f->name));
AST_LIST_HEAD_INIT_NOLOCK(&f->numbers);
AST_LIST_HEAD_INIT_NOLOCK(&f->blnumbers);
AST_LIST_HEAD_INIT_NOLOCK(&f->wlnumbers);
return f;
}
static void init_profile(struct call_followme *f, int activate)
{
f->enable_callee_prompt = enable_callee_prompt;
f->context[0] = '\0';
ast_copy_string(f->moh, defaultmoh, sizeof(f->moh));
ast_copy_string(f->takecall, takecall, sizeof(f->takecall));
ast_copy_string(f->nextindp, nextindp, sizeof(f->nextindp));
ast_copy_string(f->callfromprompt, callfromprompt, sizeof(f->callfromprompt));
ast_copy_string(f->norecordingprompt, norecordingprompt, sizeof(f->norecordingprompt));
ast_copy_string(f->optionsprompt, optionsprompt, sizeof(f->optionsprompt));
ast_copy_string(f->plsholdprompt, plsholdprompt, sizeof(f->plsholdprompt));
ast_copy_string(f->statusprompt, statusprompt, sizeof(f->statusprompt));
ast_copy_string(f->sorryprompt, sorryprompt, sizeof(f->sorryprompt));
ast_copy_string(f->connprompt, connprompt, sizeof(f->connprompt));
if (activate) {
f->active = 1;
}
/*! \brief Set parameter in profile from configuration file */
static void profile_set_param(struct call_followme *f, const char *param, const char *val, int linenum, int failunknown)
if (!strcasecmp(param, "musicclass") || !strcasecmp(param, "musiconhold") || !strcasecmp(param, "music"))
ast_copy_string(f->moh, val, sizeof(f->moh));
else if (!strcasecmp(param, "context"))
ast_copy_string(f->context, val, sizeof(f->context));
else if (!strcasecmp(param, "enable_callee_prompt"))
f->enable_callee_prompt = ast_true(val);
else if (!strcasecmp(param, "takecall"))
ast_copy_string(f->takecall, val, sizeof(f->takecall));
else if (!strcasecmp(param, "declinecall"))
ast_copy_string(f->nextindp, val, sizeof(f->nextindp));
else if (!strcasecmp(param, "call-from-prompt") || !strcasecmp(param, "call_from_prompt"))
ast_copy_string(f->callfromprompt, val, sizeof(f->callfromprompt));
else if (!strcasecmp(param, "followme-norecording-prompt") || !strcasecmp(param, "norecording_prompt"))
ast_copy_string(f->norecordingprompt, val, sizeof(f->norecordingprompt));
else if (!strcasecmp(param, "followme-options-prompt") || !strcasecmp(param, "options_prompt"))
ast_copy_string(f->optionsprompt, val, sizeof(f->optionsprompt));
else if (!strcasecmp(param, "followme-pls-hold-prompt") || !strcasecmp(param, "pls_hold_prompt"))
ast_copy_string(f->plsholdprompt, val, sizeof(f->plsholdprompt));
else if (!strcasecmp(param, "followme-status-prompt") || !strcasecmp(param, "status_prompt"))
ast_copy_string(f->statusprompt, val, sizeof(f->statusprompt));
else if (!strcasecmp(param, "followme-sorry-prompt") || !strcasecmp(param, "sorry_prompt"))
ast_copy_string(f->sorryprompt, val, sizeof(f->sorryprompt));
else if (!strcasecmp(param, "followme-connecting-prompt") || !strcasecmp(param, "connecting_prompt")) {
ast_copy_string(f->connprompt, val, sizeof(f->connprompt));
} else if (failunknown) {
if (linenum >= 0)
ast_log(LOG_WARNING, "Unknown keyword in profile '%s': %s at line %d of followme.conf\n", f->name, param, linenum);
else
ast_log(LOG_WARNING, "Unknown keyword in profile '%s': %s\n", f->name, param);
}
}
/*! \brief Add a new number */
static struct number *create_followme_number(const char *number, int timeout, int numorder)
char *buf = ast_strdupa(number);
if (!(cur = ast_calloc(1, sizeof(*cur))))
return NULL;
cur->timeout = timeout;
if ((tmp = strchr(buf, ',')))
ast_copy_string(cur->number, buf, sizeof(cur->number));
cur->order = numorder;
ast_debug(1, "Created a number, %s, order of , %d, with a timeout of %ld.\n", cur->number, cur->order, cur->timeout);
/*! \brief Reload followme application module */
static int reload_followme(int reload)
struct call_followme *f;
Russell Bryant
committed
char *cat = NULL, *tmp;
struct ast_variable *var;
struct number *cur, *nm;
int timeout;
int numorder;
const char* enable_callee_prompt_str;
const char *takecallstr;
const char *declinecallstr;
const char *tmpstr;
struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
if (!(cfg = ast_config_load("followme.conf", config_flags))) {
ast_log(LOG_WARNING, "No follow me config file (followme.conf), so no follow me\n");
return 0;
Tilghman Lesher
committed
} else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
Tilghman Lesher
committed
} else if (cfg == CONFIG_STATUS_FILEINVALID) {
ast_log(LOG_ERROR, "Config file followme.conf is in an invalid format. Aborting.\n");
return 0;
}
/* Reset Global Var Values */
featuredigittimeout = 5000;
/* Mark all profiles as inactive for the moment */
AST_RWLIST_TRAVERSE(&followmes, f, entry) {
featuredigittostr = ast_variable_retrieve(cfg, "general", "featuredigittimeout");
if (!ast_strlen_zero(featuredigittostr)) {
if (!sscanf(featuredigittostr, "%30d", &featuredigittimeout))
}
if ((enable_callee_prompt_str = ast_variable_retrieve(cfg, "general",
"enable_callee_prompt")) &&
!ast_strlen_zero(enable_callee_prompt_str)) {
enable_callee_prompt = ast_true(enable_callee_prompt_str);
}
Tilghman Lesher
committed
if ((takecallstr = ast_variable_retrieve(cfg, "general", "takecall")) && !ast_strlen_zero(takecallstr)) {
ast_copy_string(takecall, takecallstr, sizeof(takecall));
Tilghman Lesher
committed
}
Tilghman Lesher
committed
if ((declinecallstr = ast_variable_retrieve(cfg, "general", "declinecall")) && !ast_strlen_zero(declinecallstr)) {
ast_copy_string(nextindp, declinecallstr, sizeof(nextindp));
Tilghman Lesher
committed
}
Tilghman Lesher
committed
if ((tmpstr = ast_variable_retrieve(cfg, "general", "call-from-prompt")) && !ast_strlen_zero(tmpstr)) {
ast_copy_string(callfromprompt, tmpstr, sizeof(callfromprompt));
} else if ((tmpstr = ast_variable_retrieve(cfg, "general", "call_from_prompt")) && !ast_strlen_zero(tmpstr)) {
ast_copy_string(callfromprompt, tmpstr, sizeof(callfromprompt));
Tilghman Lesher
committed
}
Tilghman Lesher
committed
if ((tmpstr = ast_variable_retrieve(cfg, "general", "norecording-prompt")) && !ast_strlen_zero(tmpstr)) {
ast_copy_string(norecordingprompt, tmpstr, sizeof(norecordingprompt));
Tilghman Lesher
committed
} else if ((tmpstr = ast_variable_retrieve(cfg, "general", "norecording_prompt")) && !ast_strlen_zero(tmpstr)) {
ast_copy_string(norecordingprompt, tmpstr, sizeof(norecordingprompt));
Tilghman Lesher
committed
}
Tilghman Lesher
committed
if ((tmpstr = ast_variable_retrieve(cfg, "general", "options-prompt")) && !ast_strlen_zero(tmpstr)) {
ast_copy_string(optionsprompt, tmpstr, sizeof(optionsprompt));
Tilghman Lesher
committed
} else if ((tmpstr = ast_variable_retrieve(cfg, "general", "options_prompt")) && !ast_strlen_zero(tmpstr)) {
ast_copy_string(optionsprompt, tmpstr, sizeof(optionsprompt));
}
Tilghman Lesher
committed
if ((tmpstr = ast_variable_retrieve(cfg, "general", "pls-hold-prompt")) && !ast_strlen_zero(tmpstr)) {
ast_copy_string(plsholdprompt, tmpstr, sizeof(plsholdprompt));
Tilghman Lesher
committed
} else if ((tmpstr = ast_variable_retrieve(cfg, "general", "pls_hold_prompt")) && !ast_strlen_zero(tmpstr)) {
ast_copy_string(plsholdprompt, tmpstr, sizeof(plsholdprompt));
Tilghman Lesher
committed
}
Tilghman Lesher
committed
if ((tmpstr = ast_variable_retrieve(cfg, "general", "status-prompt")) && !ast_strlen_zero(tmpstr)) {
ast_copy_string(statusprompt, tmpstr, sizeof(statusprompt));
Tilghman Lesher
committed
} else if ((tmpstr = ast_variable_retrieve(cfg, "general", "status_prompt")) && !ast_strlen_zero(tmpstr)) {
ast_copy_string(statusprompt, tmpstr, sizeof(statusprompt));
Tilghman Lesher
committed
}
Tilghman Lesher
committed
if ((tmpstr = ast_variable_retrieve(cfg, "general", "sorry-prompt")) && !ast_strlen_zero(tmpstr)) {
ast_copy_string(sorryprompt, tmpstr, sizeof(sorryprompt));
Tilghman Lesher
committed
} else if ((tmpstr = ast_variable_retrieve(cfg, "general", "sorry_prompt")) && !ast_strlen_zero(tmpstr)) {
ast_copy_string(sorryprompt, tmpstr, sizeof(sorryprompt));
Tilghman Lesher
committed
}
if ((tmpstr = ast_variable_retrieve(cfg, "general", "connecting-prompt")) && !ast_strlen_zero(tmpstr)) {
ast_copy_string(connprompt, tmpstr, sizeof(connprompt));
} else if ((tmpstr = ast_variable_retrieve(cfg, "general", "connecting_prompt")) && !ast_strlen_zero(tmpstr)) {
ast_copy_string(connprompt, tmpstr, sizeof(connprompt));
}
Russell Bryant
committed
while ((cat = ast_category_browse(cfg, cat))) {
Russell Bryant
committed
if (!strcasecmp(cat, "general"))
continue;
/* Look for an existing one */
AST_LIST_TRAVERSE(&followmes, f, entry) {
if (!strcasecmp(f->name, cat))
break;
}
ast_debug(1, "New profile %s.\n", cat);
if (!f) {
/* Make one then */
f = alloc_profile(cat);
new = 1;
}
/* Totally fail if we fail to find/create an entry */
if (!f)
continue;
if (!new)
ast_mutex_lock(&f->lock);
/* Re-initialize the profile */
free_numbers(f);
var = ast_variable_browse(cfg, cat);
Tilghman Lesher
committed
while (var) {
if (!strcasecmp(var->name, "number")) {
int idx = 0;
char copy[strlen(var->value) + 1];
char *numberstr;
strcpy(copy, var->value); /* safe */
numberstr = copy;
if ((tmp = strchr(numberstr, ','))) {
*tmp++ = '\0';
timeout = atoi(tmp);
if (timeout < 0) {
timeout = 25;
}
if ((tmp = strchr(tmp, ','))) {
*tmp++ = '\0';
numorder = atoi(tmp);
if (numorder < 0)
if (!numorder) {
AST_LIST_TRAVERSE(&f->numbers, nm, entry)
idx++;
numorder = idx;
}
cur = create_followme_number(numberstr, timeout, numorder);
if (cur) {
AST_LIST_INSERT_TAIL(&f->numbers, cur, entry);
}
} else {
profile_set_param(f, var->name, var->value, var->lineno, 1);
ast_debug(2, "Logging parameter %s with value %s from lineno %d\n", var->name, var->value, var->lineno);
}
var = var->next;
} /* End while(var) loop */
ast_mutex_unlock(&f->lock);
else
AST_RWLIST_INSERT_HEAD(&followmes, f, entry);
static void publish_dial_end_event(struct ast_channel *in, struct findme_user_listptr *findme_user_list, struct ast_channel *exception, const char *status)
{
struct findme_user *tmpuser;
AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry) {
if (tmpuser->ochan && tmpuser->ochan != exception) {
ast_channel_publish_dial(in, tmpuser->ochan, NULL, status);
}
}
}
static void clear_caller(struct findme_user *tmpuser)
{
struct ast_channel *outbound;
if (!tmpuser->ochan) {
/* Call already cleared. */
return;
}
outbound = tmpuser->ochan;
ast_hangup(outbound);
tmpuser->ochan = NULL;
static void clear_unanswered_calls(struct findme_user_listptr *findme_user_list)
AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry) {
if (!tmpuser->answered) {
clear_caller(tmpuser);
}
static void destroy_calling_node(struct findme_user *node)
{
clear_caller(node);
ast_party_connected_line_free(&node->connected);
ast_free(node);
}
static void destroy_calling_tree(struct findme_user_listptr *findme_user_list)
{
struct findme_user *fmuser;
while ((fmuser = AST_LIST_REMOVE_HEAD(findme_user_list, entry))) {
destroy_calling_node(fmuser);
static struct ast_channel *wait_for_winner(struct findme_user_listptr *findme_user_list, struct number *nm, struct ast_channel *caller, struct fm_args *tpargs)
Richard Mudgett
committed
struct ast_party_connected_line connected;
struct ast_channel *watchers[256];
int pos;
struct ast_channel *winner;
struct ast_frame *f;
struct findme_user *tmpuser;
int livechannels;
char *callfromname;
char *pressbuttonname;
/* ------------ wait_for_winner_channel start --------------- */
callfromname = ast_strdupa(tpargs->callfromprompt);
pressbuttonname = ast_strdupa(tpargs->optionsprompt);
for (;;) {
livechannels = 0;
watchers[0] = caller;
winner = NULL;
AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry) {
if (!tmpuser->ochan) {
continue;
}
if (tmpuser->state == 3) {
tmpuser->digts += (towas - wtd);
}
if (tmpuser->digts && (tmpuser->digts > featuredigittimeout)) {
ast_verb(3, "<%s> We've been waiting for digits longer than we should have.\n",
ast_channel_name(tmpuser->ochan));
if (tpargs->enable_callee_prompt) {
if (!ast_strlen_zero(tpargs->namerecloc)) {
tmpuser->state = 1;
tmpuser->digts = 0;
if (!ast_streamfile(tmpuser->ochan, callfromname, ast_channel_language(tmpuser->ochan))) {
ast_sched_runq(ast_channel_sched(tmpuser->ochan));
} else {
ast_log(LOG_WARNING, "Unable to playback %s.\n", callfromname);
clear_caller(tmpuser);
continue;
}
tmpuser->state = 2;
tmpuser->digts = 0;
if (!ast_streamfile(tmpuser->ochan, tpargs->norecordingprompt, ast_channel_language(tmpuser->ochan)))
ast_sched_runq(ast_channel_sched(tmpuser->ochan));
else {
ast_log(LOG_WARNING, "Unable to playback %s.\n", tpargs->norecordingprompt);
clear_caller(tmpuser);
continue;
}
}
} else {
}
if (ast_channel_stream(tmpuser->ochan)) {
ast_sched_runq(ast_channel_sched(tmpuser->ochan));
tmpto = ast_sched_wait(ast_channel_sched(tmpuser->ochan));
if (tmpto > 0 && tmpto < to)
to = tmpto;
else if (tmpto < 0 && !ast_channel_timingfunc(tmpuser->ochan)) {
ast_stopstream(tmpuser->ochan);
switch (tmpuser->state) {
case 1:
ast_verb(3, "<%s> Playback of the call-from file appears to be done.\n",
ast_channel_name(tmpuser->ochan));
if (!ast_streamfile(tmpuser->ochan, tpargs->namerecloc, ast_channel_language(tmpuser->ochan))) {
tmpuser->state = 2;
} else {
ast_log(LOG_NOTICE, "<%s> Unable to playback %s. Maybe the caller didn't record their name?\n",
ast_channel_name(tmpuser->ochan), tpargs->namerecloc);
memset(tmpuser->yn, 0, sizeof(tmpuser->yn));
tmpuser->ynidx = 0;
if (!ast_streamfile(tmpuser->ochan, pressbuttonname, ast_channel_language(tmpuser->ochan)))
else {
ast_log(LOG_WARNING, "Unable to playback %s.\n", pressbuttonname);
clear_caller(tmpuser);
continue;
}
break;
case 2:
ast_verb(3, "<%s> Playback of name file appears to be done.\n",
ast_channel_name(tmpuser->ochan));
memset(tmpuser->yn, 0, sizeof(tmpuser->yn));
tmpuser->ynidx = 0;
if (!ast_streamfile(tmpuser->ochan, pressbuttonname, ast_channel_language(tmpuser->ochan))) {
tmpuser->state = 3;
} else {
clear_caller(tmpuser);
continue;
}
break;
case 3:
ast_verb(3, "<%s> Playback of the next step file appears to be done.\n",
ast_channel_name(tmpuser->ochan));
tmpuser->digts = 0;
break;
default:
break;
watchers[pos++] = tmpuser->ochan;
livechannels++;
}
if (!livechannels) {
ast_verb(3, "No live channels left for this step.\n");
return NULL;
tmpto = to;
towas = to;
winner = ast_waitfor_n(watchers, pos, &to);
tmpto -= to;
totalwait -= tmpto;
wtd = to;
ast_verb(3, "We've hit our timeout for this step. Dropping unanswered calls and starting the next step.\n");
clear_unanswered_calls(findme_user_list);
return NULL;
}
if (winner) {
/* Need to find out which channel this is */
if (winner != caller) {
Richard Mudgett
committed
/* The winner is an outgoing channel. */
AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry) {
if (tmpuser->ochan == winner) {
break;
}
}
} else {
tmpuser = NULL;
}
f = ast_read(winner);
if (f) {
if (f->frametype == AST_FRAME_CONTROL) {
switch (f->subclass.integer) {
ast_verb(3, "%s received a hangup frame.\n", ast_channel_name(winner));
Michiel van Baak
committed
if (f->data.uint32) {
ast_channel_hangupcause_set(winner, f->data.uint32);
if (!tmpuser) {
ast_verb(3, "The calling channel hungup. Need to drop everyone.\n");
publish_dial_end_event(caller, findme_user_list, NULL, "CANCEL");
ast_frfree(f);
return NULL;
clear_caller(tmpuser);
if (!tmpuser) {
/* The caller answered? We want an outgoing channel to answer. */
break;
}
ast_verb(3, "%s answered %s\n", ast_channel_name(winner), ast_channel_name(caller));
ast_channel_publish_dial(caller, winner, NULL, "ANSWER");
publish_dial_end_event(caller, findme_user_list, winner, "CANCEL");
tmpuser->answered = 1;
/* If call has been answered, then the eventual hangup is likely to be normal hangup */
ast_channel_hangupcause_set(winner, AST_CAUSE_NORMAL_CLEARING);
ast_channel_hangupcause_set(caller, AST_CAUSE_NORMAL_CLEARING);
if (tpargs->enable_callee_prompt) {
ast_verb(3, "Starting playback of %s\n", callfromname);
if (!ast_strlen_zero(tpargs->namerecloc)) {
if (!ast_streamfile(winner, callfromname, ast_channel_language(winner))) {
ast_sched_runq(ast_channel_sched(winner));
tmpuser->state = 1;
} else {
ast_log(LOG_WARNING, "Unable to playback %s.\n", callfromname);
clear_caller(tmpuser);
}
} else {
tmpuser->state = 2;
if (!ast_streamfile(tmpuser->ochan, tpargs->norecordingprompt, ast_channel_language(tmpuser->ochan)))
ast_sched_runq(ast_channel_sched(tmpuser->ochan));
else {
ast_log(LOG_WARNING, "Unable to playback %s.\n", tpargs->norecordingprompt);
clear_caller(tmpuser);
}
}
} else {
ast_verb(3, "Skip playback of caller name / norecording\n");
tmpuser->state = 2;
ast_verb(3, "%s is busy\n", ast_channel_name(winner));
if (tmpuser) {
/* Outbound call was busy. Drop it. */
ast_channel_publish_dial(caller, winner, NULL, "BUSY");
clear_caller(tmpuser);
}
ast_verb(3, "%s is circuit-busy\n", ast_channel_name(winner));
if (tmpuser) {
/* Outbound call was congested. Drop it. */
ast_channel_publish_dial(caller, winner, NULL, "CONGESTION");
clear_caller(tmpuser);
}
ast_verb(3, "%s is ringing\n", ast_channel_name(winner));
ast_channel_publish_dial(caller, winner, NULL, "RINGING");
Richard Mudgett
committed
ast_verb(3, "%s is making progress\n", ast_channel_name(winner));
ast_channel_publish_dial(caller, winner, NULL, "PROGRESS");
Richard Mudgett
committed
ast_verb(3, "%s requested a video update\n", ast_channel_name(winner));
Richard Mudgett
committed
ast_verb(3, "%s requested a source update\n", ast_channel_name(winner));
Richard Mudgett
committed
ast_verb(3, "%s is proceeding\n", ast_channel_name(winner));
ast_channel_publish_dial(caller, winner, NULL, "PROCEEDING");
Richard Mudgett
committed
ast_verb(3, "%s placed call on hold\n", ast_channel_name(winner));
if (!tmpuser) {
/* Caller placed outgoing calls on hold. */
tpargs->pending_hold = 1;
if (f->data.ptr) {
ast_copy_string(tpargs->suggested_moh, f->data.ptr,
sizeof(tpargs->suggested_moh));
} else {
tpargs->suggested_moh[0] = '\0';
}
} else {
/*
* Outgoing call placed caller on hold.
*
* Ignore because the outgoing call should not be able to place
* the caller on hold until after they are bridged.
*/
}
Richard Mudgett
committed
ast_verb(3, "%s removed call from hold\n", ast_channel_name(winner));
if (!tmpuser) {
/* Caller removed outgoing calls from hold. */
tpargs->pending_hold = 0;
} else {
/*
* Outgoing call removed caller from hold.
*
* Ignore because the outgoing call should not be able to place
* the caller on hold until after they are bridged.
*/
}
break;
case AST_CONTROL_OFFHOOK:
case AST_CONTROL_FLASH:
/* Ignore going off hook and flash */
break;
Richard Mudgett
committed
case AST_CONTROL_CONNECTED_LINE:
Alexei Gradinari
committed
if (ast_test_flag(&tpargs->followmeflags, FOLLOWMEFLAG_IGNORE_CONNECTEDLINE)) {
ast_verb(3, "Connected line update from %s prevented.\n",
ast_channel_name(winner));
break;
}
Richard Mudgett
committed
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
if (!tmpuser) {
/*
* Hold connected line update from caller until we have a
* winner.
*/
ast_verb(3,
"%s connected line has changed. Saving it until we have a winner.\n",
ast_channel_name(winner));
ast_party_connected_line_set_init(&connected, &tpargs->connected_in);
if (!ast_connected_line_parse_data(f->data.ptr, f->datalen, &connected)) {
ast_party_connected_line_set(&tpargs->connected_in,
&connected, NULL);
tpargs->pending_in_connected_update = 1;
}
ast_party_connected_line_free(&connected);
} else {
ast_verb(3,
"%s connected line has changed. Saving it until answer.\n",
ast_channel_name(winner));
ast_party_connected_line_set_init(&connected, &tmpuser->connected);
if (!ast_connected_line_parse_data(f->data.ptr, f->datalen, &connected)) {
ast_party_connected_line_set(&tmpuser->connected,
&connected, NULL);
tmpuser->pending_connected_update = 1;
}
ast_party_connected_line_free(&connected);
}
break;
case AST_CONTROL_REDIRECTING:
/*
* Ignore because we are masking the FollowMe search progress to
* the caller.
*/
break;
case AST_CONTROL_PVT_CAUSE_CODE:
ast_indicate_data(caller, f->subclass.integer, f->data.ptr, f->datalen);
break;
ast_verb(3, "%s stopped sounds\n", ast_channel_name(winner));
Richard Mudgett
committed
ast_debug(1, "Dunno what to do with control type %d from %s\n",
f->subclass.integer, ast_channel_name(winner));
if (!tpargs->enable_callee_prompt && tmpuser) {
ast_debug(1, "Taking call with no prompt\n");
ast_frfree(f);
return tmpuser->ochan;
}
if (tmpuser && tmpuser->state == 3 && f->frametype == AST_FRAME_DTMF) {
int cmp_len;
if (ast_channel_stream(winner))
ast_stopstream(winner);
tmpuser->digts = 0;
ast_debug(1, "DTMF received: %c\n", (char) f->subclass.integer);
if (tmpuser->ynidx < ARRAY_LEN(tmpuser->yn) - 1) {
tmpuser->yn[tmpuser->ynidx++] = f->subclass.integer;
} else {
/* Discard oldest digit. */
memmove(tmpuser->yn, tmpuser->yn + 1,
sizeof(tmpuser->yn) - 2 * sizeof(tmpuser->yn[0]));
tmpuser->yn[ARRAY_LEN(tmpuser->yn) - 2] = f->subclass.integer;
ast_debug(1, "DTMF string: %s\n", tmpuser->yn);
cmp_len = strlen(tpargs->takecall);
if (cmp_len <= tmpuser->ynidx
&& !strcmp(tmpuser->yn + (tmpuser->ynidx - cmp_len), tpargs->takecall)) {
ast_debug(1, "Match to take the call!\n");
ast_frfree(f);
return tmpuser->ochan;
}
cmp_len = strlen(tpargs->nextindp);
if (cmp_len <= tmpuser->ynidx
&& !strcmp(tmpuser->yn + (tmpuser->ynidx - cmp_len), tpargs->nextindp)) {
ast_debug(1, "Declined to take the call.\n");
clear_caller(tmpuser);