Newer
Older
/*
* Asterisk -- A telephony toolkit for Linux.
*
* 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>
*
Kevin P. Fleming
committed
#include "asterisk.h"
Jason Parker
committed
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#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/monitor.h"
#include "asterisk/utils.h"
#include "asterisk/causes.h"
#include "asterisk/astdb.h"
#include "asterisk/app.h"
static char *app = "FollowMe";
static char *synopsis = "Find-Me/Follow-Me application";
" FollowMe(followmeid[,options]):\n"
"This application performs Find-Me/Follow-Me functionality for the caller\n"
"as defined in the profile matching the <followmeid> parameter in\n"
"followme.conf. If the specified <followmeid> profile doesn't exist in\n"
"followme.conf, execution will be returned to the dialplan and call\n"
"execution will continue at the next priority.\n\n"
" Options:\n"
" s - Playback the incoming status message prior to starting the follow-me step(s)\n"
" a - Record the caller's name so it can be announced to the callee on each step\n"
" n - Playback the unreachable status message if we've run out of steps to reach the\n"
" or the callee has elected not to be reachable.\n"
"Returns -1 on hangup\n";
/*! \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[AST_MAX_CONTEXT]; /*!< 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). */
char takecall[20]; /*!< Digit mapping to take a call */
char nextindp[20]; /*!< 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 */
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 {
struct ast_channel *chan;
char *mohclass;
AST_LIST_HEAD_NOLOCK(cnumbers, number) cnumbers;
int status;
char context[AST_MAX_CONTEXT];
char namerecloc[AST_MAX_CONTEXT];
struct ast_channel *outbound;
char takecall[20]; /*!< Digit mapping to take a call */
char nextindp[20]; /*!< 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 */
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
struct ast_flags followmeflags;
};
struct findme_user {
struct ast_channel *ochan;
int state;
char dialarg[256];
char yn[10];
int ynidx;
long digts;
int cleared;
AST_LIST_ENTRY(findme_user) entry;
};
enum {
FOLLOWMEFLAG_STATUSMSG = (1 << 0),
FOLLOWMEFLAG_RECORDNAME = (1 << 1),
FOLLOWMEFLAG_UNREACHABLEMSG = (1 << 2)
};
AST_APP_OPTIONS(followme_opts, {
AST_APP_OPTION('s', FOLLOWMEFLAG_STATUSMSG ),
AST_APP_OPTION('a', FOLLOWMEFLAG_RECORDNAME ),
AST_APP_OPTION('n', FOLLOWMEFLAG_UNREACHABLEMSG ),
});
static int ynlongest = 0;
static time_t start_time, answer_time, end_time;
static const char *featuredigittostr;
static int featuredigittimeout = 5000; /*!< Feature Digit Timeout */
static const char *defaultmoh = "default"; /*!< Default Music-On-Hold Class */
static char takecall[20] = "1", nextindp[20] = "2";
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 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));
f->moh[0] = '\0';
f->context[0] = '\0';
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_LIST_HEAD_INIT_NOLOCK(&f->numbers);
AST_LIST_HEAD_INIT_NOLOCK(&f->blnumbers);
AST_LIST_HEAD_INIT_NOLOCK(&f->wlnumbers);
static void init_profile(struct call_followme *f)
{
f->active = 1;
ast_copy_string(f->moh, defaultmoh, sizeof(f->moh));
}
/*! \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, "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"))
ast_copy_string(f->callfromprompt, val, sizeof(f->callfromprompt));
else if (!strcasecmp(param, "followme-norecording-prompt"))
ast_copy_string(f->norecordingprompt, val, sizeof(f->norecordingprompt));
else if (!strcasecmp(param, "followme-options-prompt"))
ast_copy_string(f->optionsprompt, val, sizeof(f->optionsprompt));
else if (!strcasecmp(param, "followme-pls-hold-prompt"))
ast_copy_string(f->plsholdprompt, val, sizeof(f->plsholdprompt));
else if (!strcasecmp(param, "followme-status-prompt"))
ast_copy_string(f->statusprompt, val, sizeof(f->statusprompt));
else if (!strcasecmp(param, "followme-sorry-prompt"))
ast_copy_string(f->sorryprompt, val, sizeof(f->sorryprompt));
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(char *number, int timeout, int numorder)
{
struct number *cur;
char *tmp;
if (!(cur = ast_calloc(1, sizeof(*cur))))
return NULL;
cur->timeout = timeout;
if ((tmp = strchr(number, ',')))
*tmp = '\0';
ast_copy_string(cur->number, number, 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;
char numberstr[90];
int timeout;
char *timeoutstr;
int numorder;
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;
} else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
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, "%d", &featuredigittimeout))
}
takecallstr = ast_variable_retrieve(cfg, "general", "takecall");
if (!ast_strlen_zero(takecallstr))
ast_copy_string(takecall, takecallstr, sizeof(takecall));
declinecallstr = ast_variable_retrieve(cfg, "general", "declinecall");
if (!ast_strlen_zero(declinecallstr))
ast_copy_string(nextindp, declinecallstr, sizeof(nextindp));
tmpstr = ast_variable_retrieve(cfg, "general", "call-from-prompt");
if (!ast_strlen_zero(tmpstr))
ast_copy_string(callfromprompt, tmpstr, sizeof(callfromprompt));
tmpstr = ast_variable_retrieve(cfg, "general", "norecording-prompt");
if (!ast_strlen_zero(tmpstr))
ast_copy_string(norecordingprompt, tmpstr, sizeof(norecordingprompt));
tmpstr = ast_variable_retrieve(cfg, "general", "options-prompt");
if (!ast_strlen_zero(tmpstr))
ast_copy_string(optionsprompt, tmpstr, sizeof(optionsprompt));
tmpstr = ast_variable_retrieve(cfg, "general", "pls-hold-prompt");
if (!ast_strlen_zero(tmpstr))
ast_copy_string(plsholdprompt, tmpstr, sizeof(plsholdprompt));
tmpstr = ast_variable_retrieve(cfg, "general", "status-prompt");
if (!ast_strlen_zero(tmpstr))
ast_copy_string(statusprompt, tmpstr, sizeof(statusprompt));
tmpstr = ast_variable_retrieve(cfg, "general", "sorry-prompt");
if (!ast_strlen_zero(tmpstr))
ast_copy_string(sorryprompt, tmpstr, sizeof(sorryprompt));
/* Chug through config file */
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 */
init_profile(f);
free_numbers(f);
var = ast_variable_browse(cfg, cat);
while(var) {
if (!strcasecmp(var->name, "number")) {
int idx = 0;
/* Add a new number */
ast_copy_string(numberstr, var->value, sizeof(numberstr));
if ((tmp = strchr(numberstr, ','))) {
*tmp++ = '\0';
timeoutstr = ast_strdupa(tmp);
if ((tmp = strchr(timeoutstr, ','))) {
*tmp++ = '\0';
numorder = atoi(tmp);
if (numorder < 0)
timeout = atoi(timeoutstr);
if (timeout < 0)
timeout = 25;
if (!numorder) {
idx = 1;
AST_LIST_TRAVERSE(&f->numbers, nm, entry)
idx++;
numorder = idx;
}
cur = create_followme_number(numberstr, timeout, numorder);
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 */
if (!new)
ast_mutex_unlock(&f->lock);
else
AST_RWLIST_INSERT_HEAD(&followmes, f, entry);
return 1;
}
static void clear_caller(struct findme_user *tmpuser)
{
struct ast_channel *outbound;
if (tmpuser && tmpuser->ochan && tmpuser->state >= 0) {
outbound = tmpuser->ochan;
if (!outbound->cdr) {
outbound->cdr = ast_cdr_alloc();
if (outbound->cdr)
ast_cdr_init(outbound->cdr, outbound);
}
if (outbound->cdr) {
char tmp[256];
snprintf(tmp, sizeof(tmp), "%s/%s", "Local", tmpuser->dialarg);
ast_cdr_setapp(outbound->cdr, "FollowMe", tmp);
ast_cdr_update(outbound);
ast_cdr_start(outbound->cdr);
ast_cdr_end(outbound->cdr);
/* If the cause wasn't handled properly */
if (ast_cdr_disposition(outbound->cdr, outbound->hangupcause))
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
ast_cdr_failed(outbound->cdr);
} else
ast_log(LOG_WARNING, "Unable to create Call Detail Record\n");
ast_hangup(tmpuser->ochan);
}
}
static void clear_calling_tree(struct findme_user_listptr *findme_user_list)
{
struct findme_user *tmpuser;
AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry) {
clear_caller(tmpuser);
tmpuser->cleared = 1;
}
}
static struct ast_channel *wait_for_winner(struct findme_user_listptr *findme_user_list, struct number *nm, struct ast_channel *caller, char *namerecloc, int *status, struct fm_args *tpargs)
{
struct ast_channel *watchers[256];
int pos;
struct ast_channel *winner;
struct ast_frame *f;
int dg;
struct findme_user *tmpuser;
char *callfromname;
char *pressbuttonname;
/* ------------ wait_for_winner_channel start --------------- */
callfromname = ast_strdupa(tpargs->callfromprompt);
pressbuttonname = ast_strdupa(tpargs->optionsprompt);
if (AST_LIST_EMPTY(findme_user_list)) {
ast_verb(3, "couldn't reach at this number.\n");
return NULL;
}
if (!caller) {
ast_verb(3, "Original caller hungup. Cleanup.\n");
clear_calling_tree(findme_user_list);
return NULL;
}
totalwait = nm->timeout * 1000;
while (!ctstatus) {
to = 1000;
pos = 1;
livechannels = 0;
watchers[0] = caller;
dg = 0;
winner = NULL;
AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry) {
if (tmpuser->state >= 0 && tmpuser->ochan) {
if (tmpuser->state == 3)
tmpuser->digts += (towas - wtd);
if (tmpuser->digts && (tmpuser->digts > featuredigittimeout)) {
ast_verb(3, "We've been waiting for digits longer than we should have.\n");
if (!ast_strlen_zero(namerecloc)) {
tmpuser->state = 1;
tmpuser->digts = 0;
if (!ast_streamfile(tmpuser->ochan, callfromname, tmpuser->ochan->language)) {
ast_sched_runq(tmpuser->ochan->sched);
ast_log(LOG_WARNING, "Unable to playback %s.\n", callfromname);
return NULL;
}
} else {
tmpuser->state = 2;
tmpuser->digts = 0;
if (!ast_streamfile(tmpuser->ochan, tpargs->norecordingprompt, tmpuser->ochan->language))
ast_sched_runq(tmpuser->ochan->sched);
else {
ast_log(LOG_WARNING, "Unable to playback %s.\n", tpargs->norecordingprompt);
return NULL;
}
if (tmpuser->ochan->stream) {
ast_sched_runq(tmpuser->ochan->sched);
tmpto = ast_sched_wait(tmpuser->ochan->sched);
if (tmpto > 0 && tmpto < to)
to = tmpto;
else if (tmpto < 0 && !tmpuser->ochan->timingfunc) {
ast_stopstream(tmpuser->ochan);
if (tmpuser->state == 1) {
ast_verb(3, "Playback of the call-from file appears to be done.\n");
if (!ast_streamfile(tmpuser->ochan, namerecloc, tmpuser->ochan->language)) {
tmpuser->state = 2;
} else {
ast_log(LOG_NOTICE, "Unable to playback %s. Maybe the caller didn't record their name?\n", namerecloc);
memset(tmpuser->yn, 0, sizeof(tmpuser->yn));
tmpuser->ynidx = 0;
if (!ast_streamfile(tmpuser->ochan, pressbuttonname, tmpuser->ochan->language))
else {
ast_log(LOG_WARNING, "Unable to playback %s.\n", pressbuttonname);
} else if (tmpuser->state == 2) {
ast_verb(3, "Playback of name file appears to be done.\n");
memset(tmpuser->yn, 0, sizeof(tmpuser->yn));
tmpuser->ynidx = 0;
if (!ast_streamfile(tmpuser->ochan, pressbuttonname, tmpuser->ochan->language)) {
tmpuser->state = 3;
} else {
return NULL;
}
} else if (tmpuser->state == 3) {
ast_verb(3, "Playback of the next step file appears to be done.\n");
tmpuser->digts = 0;
watchers[pos++] = tmpuser->ochan;
livechannels++;
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
}
tmpto = to;
if (to < 0) {
to = 1000;
tmpto = 1000;
}
towas = to;
winner = ast_waitfor_n(watchers, pos, &to);
tmpto -= to;
totalwait -= tmpto;
wtd = to;
if (totalwait <= 0) {
ast_verb(3, "We've hit our timeout for this step. Drop everyone and move on to the next one. %ld\n", totalwait);
clear_calling_tree(findme_user_list);
return NULL;
}
if (winner) {
/* Need to find out which channel this is */
dg = 0;
while ((winner != watchers[dg]) && (dg < 256))
dg++;
AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry)
if (tmpuser->ochan == winner)
break;
f = ast_read(winner);
if (f) {
if (f->frametype == AST_FRAME_CONTROL) {
switch(f->subclass) {
case AST_CONTROL_HANGUP:
if (option_verbose > 2)
ast_verb(3, "%s received a hangup frame.\n", winner->name);
if (dg == 0) {
ast_verb(3, "The calling channel hungup. Need to drop everyone else.\n");
clear_calling_tree(findme_user_list);
ctstatus = -1;
}
case AST_CONTROL_ANSWER:
if (option_verbose > 2)
ast_verb(3, "%s answered %s\n", winner->name, caller->name);
/* If call has been answered, then the eventual hangup is likely to be normal hangup */
winner->hangupcause = AST_CAUSE_NORMAL_CLEARING;
caller->hangupcause = AST_CAUSE_NORMAL_CLEARING;
ast_verb(3, "Starting playback of %s\n", callfromname);
if (dg > 0) {
if (!ast_strlen_zero(namerecloc)) {
if (!ast_streamfile(winner, callfromname, winner->language)) {
ast_sched_runq(winner->sched);
tmpuser->state = 1;
} else {
ast_log(LOG_WARNING, "Unable to playback %s.\n", callfromname);
ast_frfree(f);
return NULL;
}
} else {
tmpuser->state = 2;
if (!ast_streamfile(tmpuser->ochan, tpargs->norecordingprompt, tmpuser->ochan->language))
ast_sched_runq(tmpuser->ochan->sched);
else {
ast_log(LOG_WARNING, "Unable to playback %s.\n", tpargs->norecordingprompt);
ast_frfree(f);
return NULL;
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
break;
case AST_CONTROL_BUSY:
ast_verb(3, "%s is busy\n", winner->name);
break;
case AST_CONTROL_CONGESTION:
ast_verb(3, "%s is circuit-busy\n", winner->name);
break;
case AST_CONTROL_RINGING:
ast_verb(3, "%s is ringing\n", winner->name);
break;
case AST_CONTROL_PROGRESS:
ast_verb(3, "%s is making progress passing it to %s\n", winner->name, caller->name);
break;
case AST_CONTROL_VIDUPDATE:
ast_verb(3, "%s requested a video update, passing it to %s\n", winner->name, caller->name);
break;
case AST_CONTROL_PROCEEDING:
ast_verb(3, "%s is proceeding passing it to %s\n", winner->name,caller->name);
break;
case AST_CONTROL_HOLD:
ast_verb(3, "Call on %s placed on hold\n", winner->name);
break;
case AST_CONTROL_UNHOLD:
ast_verb(3, "Call on %s left from hold\n", winner->name);
break;
case AST_CONTROL_OFFHOOK:
case AST_CONTROL_FLASH:
/* Ignore going off hook and flash */
break;
case -1:
ast_verb(3, "%s stopped sounds\n", winner->name);
break;
default:
ast_debug(1, "Dunno what to do with control type %d\n", f->subclass);
break;
}
}
if (tmpuser && tmpuser->state == 3 && f->frametype == AST_FRAME_DTMF) {
if (winner->stream)
ast_stopstream(winner);
tmpuser->digts = 0;
ast_debug(1, "DTMF received: %c\n",(char) f->subclass);
tmpuser->yn[tmpuser->ynidx] = (char) f->subclass;
tmpuser->ynidx++;
ast_debug(1, "DTMF string: %s\n", tmpuser->yn);
if (tmpuser->ynidx >= ynlongest) {
ast_debug(1, "reached longest possible match - doing evals\n");
if (!strcmp(tmpuser->yn, tpargs->takecall)) {
ast_debug(1, "Match to take the call!\n");
ast_frfree(f);
return tmpuser->ochan;
if (!strcmp(tmpuser->yn, tpargs->nextindp)) {
ast_debug(1, "Next in dial plan step requested.\n");
*status = 1;
ast_frfree(f);
return NULL;
}
}
ast_frfree(f);
} else {
if (winner) {
ast_debug(1, "we didn't get a frame. hanging up. dg is %d\n",dg);
if (!dg) {
clear_calling_tree(findme_user_list);
return NULL;
} else {
tmpuser->state = -1;
ast_hangup(winner);
livechannels--;
ast_debug(1, "live channels left %d\n", livechannels);
if (!livechannels) {
ast_verb(3, "no live channels left. exiting.\n");
}
}
} else
ast_debug(1, "timed out waiting for action\n");
}
/* --- WAIT FOR WINNER NUMBER END! -----------*/
return NULL;
}
{
struct number *nm;
struct ast_channel *outbound;
struct ast_channel *caller;
struct ast_channel *winner = NULL;
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
char dialarg[512];
int dg, idx;
char *rest, *number;
struct findme_user *tmpuser;
struct findme_user *fmuser;
struct findme_user *headuser;
struct findme_user_listptr *findme_user_list;
int status;
findme_user_list = ast_calloc(1, sizeof(*findme_user_list));
AST_LIST_HEAD_INIT_NOLOCK(findme_user_list);
/* We're going to figure out what the longest possible string of digits to collect is */
ynlongest = 0;
if (strlen(tpargs->takecall) > ynlongest)
ynlongest = strlen(tpargs->takecall);
if (strlen(tpargs->nextindp) > ynlongest)
ynlongest = strlen(tpargs->nextindp);
idx = 1;
caller = tpargs->chan;
AST_LIST_TRAVERSE(&tpargs->cnumbers, nm, entry)
if (nm->order == idx)
break;
while (nm) {
ast_debug(2, "Number %s timeout %ld\n", nm->number,nm->timeout);
time(&start_time);
number = ast_strdupa(nm->number);
ast_debug(3, "examining %s\n", number);
do {
rest = strchr(number, '&');
if (rest) {
*rest = 0;
rest++;
}
if (!strcmp(tpargs->context, ""))
sprintf(dialarg, "%s", number);
else
sprintf(dialarg, "%s@%s", number, tpargs->context);
tmpuser = ast_calloc(1, sizeof(*tmpuser));
if (!tmpuser) {
ast_log(LOG_WARNING, "Out of memory!\n");
Tilghman Lesher
committed
ast_free(findme_user_list);
return;
}
outbound = ast_request("Local", ast_best_codec(caller->nativeformats), dialarg, &dg);
if (outbound) {
ast_set_callerid(outbound, caller->cid.cid_num, caller->cid.cid_name, caller->cid.cid_num);
ast_channel_inherit_variables(tpargs->chan, outbound);
ast_verb(3, "calling %s\n", dialarg);
if (!ast_call(outbound,dialarg,0)) {
tmpuser->ochan = outbound;
tmpuser->state = 0;
tmpuser->cleared = 0;
ast_copy_string(tmpuser->dialarg, dialarg, sizeof(dialarg));
AST_LIST_INSERT_TAIL(findme_user_list, tmpuser, entry);
} else {
ast_verb(3, "couldn't reach at this number.\n");
if (!outbound->cdr)
outbound->cdr = ast_cdr_alloc();
if (outbound->cdr) {
char tmp[256];
ast_cdr_init(outbound->cdr, outbound);
snprintf(tmp, sizeof(tmp), "%s/%s", "Local", dialarg);
ast_cdr_setapp(outbound->cdr, "FollowMe", tmp);
ast_cdr_update(outbound);
ast_cdr_start(outbound->cdr);
ast_cdr_end(outbound->cdr);
/* If the cause wasn't handled properly */
if (ast_cdr_disposition(outbound->cdr,outbound->hangupcause))
ast_cdr_failed(outbound->cdr);
} else {
ast_log(LOG_ERROR, "Unable to create Call Detail Record\n");
ast_hangup(outbound);
outbound = NULL;
}
}
}
} else
ast_log(LOG_WARNING, "Unable to allocate a channel for Local/%s cause: %s\n", dialarg, ast_cause2str(dg));
number = rest;
} while (number);
status = 0;
if (!AST_LIST_EMPTY(findme_user_list))
winner = wait_for_winner(findme_user_list, nm, caller, tpargs->namerecloc, &status, tpargs);
while ((fmuser = AST_LIST_REMOVE_HEAD(findme_user_list, entry))) {
if (!fmuser->cleared && fmuser->ochan != winner)
clear_caller(fmuser);
Tilghman Lesher
committed
ast_free(fmuser);
fmuser = NULL;
tmpuser = NULL;
headuser = NULL;
if (winner)
break;
if (!caller) {
tpargs->status = 1;
Tilghman Lesher
committed
ast_free(findme_user_list);
return;
}
idx++;
AST_LIST_TRAVERSE(&tpargs->cnumbers, nm, entry)
if (nm->order == idx)
break;
}
Tilghman Lesher
committed
ast_free(findme_user_list);
if (!winner)
tpargs->status = 1;
else {
tpargs->status = 100;
tpargs->outbound = winner;
}
return;
}
static int app_exec(struct ast_channel *chan, void *data)
{
struct fm_args targs;
struct ast_bridge_config config;
struct call_followme *f;
struct number *nm, *newnm;
int res = 0;
char *argstr;
char namerecloc[255];
int duration = 0;
struct ast_channel *caller;
struct ast_channel *outbound;
static char toast[80];
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(followmeid);
AST_APP_ARG(options);
);
if (ast_strlen_zero(data)) {
ast_log(LOG_WARNING, "%s requires an argument (followmeid)\n", app);
return -1;
}
if (!(argstr = ast_strdupa((char *)data))) {
ast_log(LOG_ERROR, "Out of memory!\n");
return -1;
}
AST_STANDARD_APP_ARGS(args, argstr);
if (ast_strlen_zero(args.followmeid)) {
ast_log(LOG_WARNING, "%s requires an argument (followmeid)\n", app);
AST_RWLIST_RDLOCK(&followmes);
AST_RWLIST_TRAVERSE(&followmes, f, entry) {
if (!strcasecmp(f->name, args.followmeid) && (f->active))
break;
}
ast_debug(1, "New profile %s.\n", args.followmeid);
if (!f) {
ast_log(LOG_WARNING, "Profile requested, %s, not found in the configuration.\n", args.followmeid);
return 0;
}
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
955
956
957
/* XXX TODO: Reinsert the db check value to see whether or not follow-me is on or off */
if (args.options)
ast_app_parse_options(followme_opts, &targs.followmeflags, NULL, args.options);
/* Lock the profile lock and copy out everything we need to run with before unlocking it again */
ast_mutex_lock(&f->lock);
targs.mohclass = ast_strdupa(f->moh);
ast_copy_string(targs.context, f->context, sizeof(targs.context));
ast_copy_string(targs.takecall, f->takecall, sizeof(targs.takecall));
ast_copy_string(targs.nextindp, f->nextindp, sizeof(targs.nextindp));
ast_copy_string(targs.callfromprompt, f->callfromprompt, sizeof(targs.callfromprompt));
ast_copy_string(targs.norecordingprompt, f->norecordingprompt, sizeof(targs.norecordingprompt));
ast_copy_string(targs.optionsprompt, f->optionsprompt, sizeof(targs.optionsprompt));
ast_copy_string(targs.plsholdprompt, f->plsholdprompt, sizeof(targs.plsholdprompt));
ast_copy_string(targs.statusprompt, f->statusprompt, sizeof(targs.statusprompt));
ast_copy_string(targs.sorryprompt, f->sorryprompt, sizeof(targs.sorryprompt));
/* Copy the numbers we're going to use into another list in case the master list should get modified
(and locked) while we're trying to do a follow-me */
AST_LIST_HEAD_INIT_NOLOCK(&targs.cnumbers);
AST_LIST_TRAVERSE(&f->numbers, nm, entry) {
newnm = create_followme_number(nm->number, nm->timeout, nm->order);
AST_LIST_INSERT_TAIL(&targs.cnumbers, newnm, entry);
}
ast_mutex_unlock(&f->lock);
if (ast_test_flag(&targs.followmeflags, FOLLOWMEFLAG_STATUSMSG))
ast_stream_and_wait(chan, targs.statusprompt, "");
snprintf(namerecloc,sizeof(namerecloc),"%s/followme.%s",ast_config_AST_SPOOL_DIR,chan->uniqueid);
duration = 5;
if (ast_test_flag(&targs.followmeflags, FOLLOWMEFLAG_RECORDNAME))
if (ast_play_and_record(chan, "vm-rec-name", namerecloc, 5, "sln", &duration, 128, 0, NULL) < 0)
if (!ast_fileexists(namerecloc, NULL, chan->language))
ast_copy_string(namerecloc, "", sizeof(namerecloc));
if (ast_streamfile(chan, targs.plsholdprompt, chan->language))
goto outrun;
if (ast_waitstream(chan, "") < 0)
goto outrun;
ast_moh_start(chan, S_OR(targs.mohclass, NULL), NULL);
targs.status = 0;
targs.chan = chan;
ast_copy_string(targs.namerecloc, namerecloc, sizeof(targs.namerecloc));
findmeexec(&targs);
while ((nm = AST_LIST_REMOVE_HEAD(&targs.cnumbers, entry)))
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
ast_free(nm);
if (!ast_strlen_zero(namerecloc))
unlink(namerecloc);
if (targs.status != 100) {
ast_moh_stop(chan);
if (ast_test_flag(&targs.followmeflags, FOLLOWMEFLAG_UNREACHABLEMSG))
ast_stream_and_wait(chan, targs.sorryprompt, "");
res = 0;
} else {
caller = chan;
outbound = targs.outbound;
/* Bridge the two channels. */
memset(&config,0,sizeof(struct ast_bridge_config));
ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMON);
ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMON);
ast_moh_stop(caller);
/* Be sure no generators are left on it */
ast_deactivate_generator(caller);
/* Make sure channels are compatible */
res = ast_channel_make_compatible(caller, outbound);