Skip to content
Snippets Groups Projects
res_features.c 74.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    /*
    
     * Asterisk -- An open source telephony toolkit.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Copyright (C) 1999 - 2006, Digium, Inc.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Mark Spencer <markster@digium.com>
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * 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.
     *
    
    Mark Spencer's avatar
    Mark Spencer committed
     * 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.
     */
    
    
    Olle Johansson's avatar
    Olle Johansson committed
     * \brief Routines implementing call features as call pickup, parking and transfer
    
     *
     * \author Mark Spencer <markster@digium.com> 
    
    #include "asterisk.h"
    
    ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
    
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    #include <pthread.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <sys/time.h>
    #include <sys/signal.h>
    #include <netinet/in.h>
    
    
    #include "asterisk/lock.h"
    #include "asterisk/file.h"
    #include "asterisk/logger.h"
    #include "asterisk/channel.h"
    #include "asterisk/pbx.h"
    #include "asterisk/options.h"
    
    #include "asterisk/module.h"
    #include "asterisk/translate.h"
    #include "asterisk/app.h"
    #include "asterisk/say.h"
    #include "asterisk/features.h"
    #include "asterisk/musiconhold.h"
    #include "asterisk/config.h"
    #include "asterisk/cli.h"
    #include "asterisk/manager.h"
    #include "asterisk/utils.h"
    #include "asterisk/adsi.h"
    
    Olle Johansson's avatar
    Olle Johansson committed
    #include "asterisk/devicestate.h"
    
    #include "asterisk/monitor.h"
    
    Mark Spencer's avatar
    Mark Spencer committed
    #define DEFAULT_PARK_TIME 45000
    
    #define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000
    
    #define DEFAULT_FEATURE_DIGIT_TIMEOUT 500
    
    #define DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER 15000
    
    enum {
    	AST_FEATURE_FLAG_NEEDSDTMF = (1 << 0),
    	AST_FEATURE_FLAG_ONPEER =    (1 << 1),
    	AST_FEATURE_FLAG_ONSELF =    (1 << 2),
    	AST_FEATURE_FLAG_BYCALLEE =  (1 << 3),
    	AST_FEATURE_FLAG_BYCALLER =  (1 << 4),
    	AST_FEATURE_FLAG_BYBOTH	 =   (3 << 3),
    };
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static char *parkedcall = "ParkedCall";
    
    
    Olle Johansson's avatar
    Olle Johansson committed
    static int parkaddhints = 0;                               /*!< Add parking hints automatically */
    
    static int parkedcalltransfers = 0;                        /*!< Enable DTMF based transfers on bridge when picking up parked calls */
    
    static int parkingtime = DEFAULT_PARK_TIME;                /*!< No more than 45 seconds parked before you do something with them */
    static char parking_con[AST_MAX_EXTENSION];                /*!< Context for which parking is made accessible */
    static char parking_con_dial[AST_MAX_EXTENSION];           /*!< Context for dialback for parking (KLUDGE) */
    static char parking_ext[AST_MAX_EXTENSION];                /*!< Extension you type to park the call */
    static char pickup_ext[AST_MAX_EXTENSION];                 /*!< Call pickup extension */
    
    static char parkmohclass[MAX_MUSICCLASS];                  /*!< Music class used for parking */
    
    static int parking_start;                                  /*!< First available extension for parking */
    static int parking_stop;                                   /*!< Last available extension for parking */
    
    static char courtesytone[256];                             /*!< Courtesy tone */
    static int parkedplay = 0;                                 /*!< Who to play the courtesy tone to */
    static char xfersound[256];                                /*!< Call transfer sound */
    static char xferfailsound[256];                            /*!< Call transfer failure sound */
    
    static int parking_offset;
    static int parkfindnext;
    
    static int transferdigittimeout;
    static int featuredigittimeout;
    
    static int comebacktoorigin = 1;
    
    static int atxfernoanswertimeout;
    
    
    static char *registrar = "res_features";		   /*!< Registrar for operations */
    
    Olle Johansson's avatar
    Olle Johansson committed
    /* module and CLI command definitions */
    
    Mark Spencer's avatar
    Mark Spencer committed
    static char *synopsis = "Answer a parked call";
    
    static char *descrip = "ParkedCall(exten):"
    
    "Used to connect to a parked call.  This application is always\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    "registered internally and does not need to be explicitly added\n"
    "into the dialplan, although you should include the 'parkedcalls'\n"
    "context.\n";
    
    
    static char *parkcall = "Park";
    
    static char *synopsis2 = "Park yourself";
    
    
    static char *descrip2 = "Park():"
    
    "Used to park yourself (typically in combination with a supervised\n"
    
    "transfer to know the parking space). This application is always\n"
    
    "registered internally and does not need to be explicitly added\n"
    "into the dialplan, although you should include the 'parkedcalls'\n"
    
    Olle Johansson's avatar
    Olle Johansson committed
    "context (or the context specified in features.conf).\n\n"
    "If you set the PARKINGEXTEN variable to an extension in your\n"
    "parking context, park() will park the call on that extension, unless\n"
    "it already exists. In that case, execution will continue at next\n"
    "priority.\n" ;
    
    Olle Johansson's avatar
    Olle Johansson committed
    static struct ast_app *monitor_app = NULL;
    static int monitor_ok = 1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    struct parkeduser {
    
    	struct ast_channel *chan;                   /*!< Parking channel */
    	struct timeval start;                       /*!< Time the parking started */
    	int parkingnum;                             /*!< Parking lot */
    
    Olle Johansson's avatar
    Olle Johansson committed
    	char parkingexten[AST_MAX_EXTENSION];       /*!< If set beforehand, parking extension used for this call */
    
    	char context[AST_MAX_CONTEXT];              /*!< Where to go if our parking time expires */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char exten[AST_MAX_EXTENSION];
    	int priority;
    
    	int parkingtime;                            /*!< Maximum length in parking lot before return */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct parkeduser *next;
    };
    
    static struct parkeduser *parkinglot;
    
    
    Olle Johansson's avatar
    Olle Johansson committed
    AST_MUTEX_DEFINE_STATIC(parking_lock);	/*!< protects all static variables above */
    
    Mark Spencer's avatar
    Mark Spencer committed
    
    static pthread_t parking_thread;
    
    char *ast_parking_ext(void)
    {
    	return parking_ext;
    }
    
    
    char *ast_pickup_ext(void)
    {
    	return pickup_ext;
    }
    
    
    struct ast_bridge_thread_obj 
    {
    	struct ast_bridge_config bconfig;
    	struct ast_channel *chan;
    	struct ast_channel *peer;
    };
    
    
    Olle Johansson's avatar
    Olle Johansson committed
    /*! \brief store context, priority and extension */
    static void set_c_e_p(struct ast_channel *chan, const char *context, const char *ext, int pri)
    
    Olle Johansson's avatar
    Olle Johansson committed
    	ast_copy_string(chan->context, context, sizeof(chan->context));
    
    	ast_copy_string(chan->exten, ext, sizeof(chan->exten));
    	chan->priority = pri;
    }
    
    
    static void check_goto_on_transfer(struct ast_channel *chan) 
    {
    	struct ast_channel *xferchan;
    
    	const char *val = pbx_builtin_getvar_helper(chan, "GOTO_ON_BLINDXFR");
    	char *x, *goto_on_transfer;
    	struct ast_frame *f;
    
    	if (ast_strlen_zero(val))
    		return;
    
    	goto_on_transfer = ast_strdupa(val);
    
    
    	if (!(xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, chan->name)))
    
    		return;
    
    	for (x = goto_on_transfer; x && *x; x++) {
    		if (*x == '^')
    			*x = '|';
    	}
    	/* Make formats okay */
    	xferchan->readformat = chan->readformat;
    	xferchan->writeformat = chan->writeformat;
    	ast_channel_masquerade(xferchan, chan);
    	ast_parseable_goto(xferchan, goto_on_transfer);
    	xferchan->_state = AST_STATE_UP;
    	ast_clear_flag(xferchan, AST_FLAGS_ALL);	
    	xferchan->_softhangup = 0;
    	if ((f = ast_read(xferchan))) {
    		ast_frfree(f);
    		f = NULL;
    		ast_pbx_start(xferchan);
    	} else {
    		ast_hangup(xferchan);
    
    static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name);
    
    
    
    static void *ast_bridge_call_thread(void *data) 
    {
    	struct ast_bridge_thread_obj *tobj = data;
    
    	tobj->chan->appl = "Transferred Call";
    
    	tobj->chan->data = tobj->peer->name;
    
    	tobj->peer->appl = "Transferred Call";
    
    	tobj->peer->data = tobj->chan->name;
    
    		ast_cdr_reset(tobj->chan->cdr, NULL);
    
    		ast_cdr_setdestchan(tobj->chan->cdr, tobj->peer->name);
    	}
    	if (tobj->peer->cdr) {
    
    		ast_cdr_reset(tobj->peer->cdr, NULL);
    
    		ast_cdr_setdestchan(tobj->peer->cdr, tobj->chan->name);
    	}
    
    	ast_bridge_call(tobj->peer, tobj->chan, &tobj->bconfig);
    	ast_hangup(tobj->chan);
    	ast_hangup(tobj->peer);
    
    Olle Johansson's avatar
    Olle Johansson committed
    	bzero(tobj, sizeof(*tobj)); /*! \todo XXX for safety */
    
    	free(tobj);
    	return NULL;
    }
    
    static void ast_bridge_call_thread_launch(void *data) 
    {
    	pthread_t thread;
    	pthread_attr_t attr;
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    	struct sched_param sched;
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    	pthread_attr_init(&attr);
    
    	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    	ast_pthread_create(&thread, &attr,ast_bridge_call_thread, data);
    	pthread_attr_destroy(&attr);
    	memset(&sched, 0, sizeof(sched));
    	pthread_setschedparam(thread, SCHED_RR, &sched);
    
    Olle Johansson's avatar
    Olle Johansson committed
    static int adsi_announce_park(struct ast_channel *chan, char *parkingexten)
    
    {
    	int res;
    	int justify[5] = {ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT};
    
    	char *message[5] = {NULL, NULL, NULL, NULL, NULL};
    
    Olle Johansson's avatar
    Olle Johansson committed
    	snprintf(tmp, sizeof(tmp), "Parked on %s", parkingexten);
    
    	message[0] = tmp;
    
    	res = ast_adsi_load_session(chan, NULL, 0, 1);
    
    	return ast_adsi_print(chan, message, justify, 1);
    
    Olle Johansson's avatar
    Olle Johansson committed
    /*! \brief Notify metermaids that we've changed an extension */
    static void notify_metermaids(char *exten, char *context)
    {
    	if (option_debug > 3)
    		ast_log(LOG_DEBUG, "Notification of state change to metermaids %s@%s\n", exten, context);
    
    	/* Send notification to devicestate subsystem */
    	ast_device_state_changed("park:%s@%s", exten, context);
    	return;
    }
    
    /*! \brief metermaids callback from devicestate.c */
    
    static enum ast_device_state metermaidstate(const char *data)
    
    Olle Johansson's avatar
    Olle Johansson committed
    {
    
    	enum ast_device_state res = AST_DEVICE_INVALID;
    
    Olle Johansson's avatar
    Olle Johansson committed
    	char *context = ast_strdupa(data);
    	char *exten;
    
    	exten = strsep(&context, "@");
    	if (!context)
    		return res;
    	
    	if (option_debug > 3)
    		ast_log(LOG_DEBUG, "Checking state of exten %s in context %s\n", exten, context);
    
    	res = ast_exists_extension(NULL, context, exten, 1, NULL);
    
    
    Olle Johansson's avatar
    Olle Johansson committed
    		return AST_DEVICE_NOT_INUSE;
    	else
    		return AST_DEVICE_INUSE;
    }
    
    
    Olle Johansson's avatar
    Olle Johansson committed
    /*! \brief Park a call 
    
     	\note We put the user in the parking list, then wake up the parking thread to be sure it looks
    
    Olle Johansson's avatar
    Olle Johansson committed
    	after these channels too */
    
    Mark Spencer's avatar
    Mark Spencer committed
    int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeout, int *extout)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	struct parkeduser *pu, *cur;
    
    Olle Johansson's avatar
    Olle Johansson committed
    	int i, x = -1, parking_range;
    
    Olle Johansson's avatar
    Olle Johansson committed
    	const char *parkingexten;
    
    Olle Johansson's avatar
    Olle Johansson committed
    	/* Allocate memory for parking data */
    
    	if (!(pu = ast_calloc(1, sizeof(*pu)))) 
    
    		return -1;
    
    Olle Johansson's avatar
    Olle Johansson committed
    	/* Lock parking lot */
    
    	ast_mutex_lock(&parking_lock);
    
    Olle Johansson's avatar
    Olle Johansson committed
    	/* Check for channel variable PARKINGEXTEN */
    	parkingexten = pbx_builtin_getvar_helper(chan, "PARKINGEXTEN");
    	if (!ast_strlen_zero(parkingexten)) {
    		if (ast_exists_extension(NULL, parking_con, parkingexten, 1, NULL)) {
    			ast_log(LOG_WARNING, "Requested parking extension already exists: %s@%s\n", parkingexten, parking_con);
    			return 0;	/* Continue execution if possible */
    		}
    		ast_copy_string(pu->parkingexten, parkingexten, sizeof(pu->parkingexten));
    	} else {
    		/* Select parking space within range */
    		parking_range = parking_stop - parking_start+1;
    		for (i = 0; i < parking_range; i++) {
    			x = (i + parking_offset) % parking_range + parking_start;
    			cur = parkinglot;
    			while(cur) {
    				if (cur->parkingnum == x) 
    					break;
    				cur = cur->next;
    			}
    			if (!cur)
    
    Mark Spencer's avatar
    Mark Spencer committed
    				break;
    		}
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (!(i < parking_range)) {
    			ast_log(LOG_WARNING, "No more parking spaces\n");
    			free(pu);
    			ast_mutex_unlock(&parking_lock);
    			return -1;
    		}
    		/* Set pointer for next parking */
    		if (parkfindnext) 
    			parking_offset = x - parking_start + 1;
    
    Olle Johansson's avatar
    Olle Johansson committed
    	
    
    	chan->appl = "Parked Call";
    	chan->data = NULL; 
    
    	pu->chan = chan;
    
    Olle Johansson's avatar
    Olle Johansson committed
    	
    
    	/* Put the parked channel on hold if we have two different channels */
    
    	if (chan != peer) {
    
    		ast_indicate_data(pu->chan, AST_CONTROL_HOLD, 
    			S_OR(parkmohclass, NULL),
    			!ast_strlen_zero(parkmohclass) ? strlen(parkmohclass) + 1 : 0);
    
    	pu->start = ast_tvnow();
    	pu->parkingnum = x;
    
    	pu->parkingtime = (timeout > 0) ? timeout : parkingtime;
    
    	if (extout)
    		*extout = x;
    
    Olle Johansson's avatar
    Olle Johansson committed
    
    
    	if (peer) 
    		ast_copy_string(pu->peername, peer->name, sizeof(pu->peername));
    
    	/* Remember what had been dialed, so that if the parking
    	   expires, we try to come back to the same place */
    
    	ast_copy_string(pu->context, S_OR(chan->macrocontext, chan->context), sizeof(pu->context));
    	ast_copy_string(pu->exten, S_OR(chan->macroexten, chan->exten), sizeof(pu->exten));
    	pu->priority = chan->macropriority ? chan->macropriority : chan->priority;
    
    	pu->next = parkinglot;
    	parkinglot = pu;
    
    Olle Johansson's avatar
    Olle Johansson committed
    
    
    	/* If parking a channel directly, don't quiet yet get parking running on it */
    	if (peer == chan) 
    		pu->notquiteyet = 1;
    	ast_mutex_unlock(&parking_lock);
    	/* Wake up the (presumably select()ing) thread */
    	pthread_kill(parking_thread, SIGURG);
    	if (option_verbose > 1) 
    
    Olle Johansson's avatar
    Olle Johansson committed
    		ast_verbose(VERBOSE_PREFIX_2 "Parked %s on %d@%s. Will timeout back to extension [%s] %s, %d in %d seconds\n", pu->chan->name, pu->parkingnum, parking_con, pu->context, pu->exten, pu->priority, (pu->parkingtime/1000));
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (pu->parkingnum != -1)
    		snprintf(pu->parkingexten, sizeof(pu->parkingexten), "%d", x);
    
    	manager_event(EVENT_FLAG_CALL, "ParkedCall",
    
    Olle Johansson's avatar
    Olle Johansson committed
    		"Exten: %s\r\n"
    
    		"Channel: %s\r\n"
    		"From: %s\r\n"
    		"Timeout: %ld\r\n"
    
    		"CallerIDName: %s\r\n",
    
    Olle Johansson's avatar
    Olle Johansson committed
    		pu->parkingexten, pu->chan->name, peer ? peer->name : "",
    
    		(long)pu->start.tv_sec + (long)(pu->parkingtime/1000) - (long)time(NULL),
    		S_OR(pu->chan->cid.cid_num, "<unknown>"),
    		S_OR(pu->chan->cid.cid_name, "<unknown>")
    
    	if (peer && adsipark && ast_adsi_available(peer)) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		adsi_announce_park(peer, pu->parkingexten);	/* Only supports parking numbers */
    
    		ast_adsi_unload_session(peer);
    
    	con = ast_context_find(parking_con);
    
    		con = ast_context_create(NULL, parking_con, registrar);
    
    	if (!con)	/* Still no context? Bad */
    		ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
    	else {		/* Add extension to context */
    
    		if (!ast_add_extension2(con, 1, pu->parkingexten, 1, NULL, NULL, parkedcall, strdup(pu->parkingexten), ast_free, registrar))
    
    Olle Johansson's avatar
    Olle Johansson committed
    			notify_metermaids(pu->parkingexten, parking_con);
    
    	/* Tell the peer channel the number of the parking space */
    
    	if (peer && pu->parkingnum != -1) /* Only say number if it's a number */
    
    		ast_say_digits(peer, pu->parkingnum, "", peer->language);
    	if (pu->notquiteyet) {
    		/* Wake up parking thread if we're really done */
    
    		ast_indicate_data(pu->chan, AST_CONTROL_HOLD, 
    			S_OR(parkmohclass, NULL),
    			!ast_strlen_zero(parkmohclass) ? strlen(parkmohclass) + 1 : 0);
    
    		pu->notquiteyet = 0;
    		pthread_kill(parking_thread, SIGURG);
    
    Mark Spencer's avatar
    Mark Spencer committed
    int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int timeout, int *extout)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	struct ast_channel *chan;
    	struct ast_frame *f;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Make a new, fake channel that we'll use to masquerade in the real one */
    
    	if (!(chan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "Parked/%s",rchan->name))) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		ast_log(LOG_WARNING, "Unable to create parked channel\n");
    		return -1;
    	}
    
    
    	/* Make formats okay */
    	chan->readformat = rchan->readformat;
    	chan->writeformat = rchan->writeformat;
    	ast_channel_masquerade(chan, rchan);
    
    	/* Setup the extensions and such */
    	set_c_e_p(chan, rchan->context, rchan->exten, rchan->priority);
    
    	/* Make the masq execute */
    	f = ast_read(chan);
    	if (f)
    		ast_frfree(f);
    
    	ast_park_call(chan, peer, timeout, extout);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    
    
    #define FEATURE_RETURN_HANGUP		-1
    #define FEATURE_RETURN_SUCCESSBREAK	 0
    #define FEATURE_RETURN_PBX_KEEPALIVE	AST_PBX_KEEPALIVE
    #define FEATURE_RETURN_NO_HANGUP_PEER	AST_PBX_NO_HANGUP_PEER
    #define FEATURE_RETURN_PASSDIGITS	 21
    #define FEATURE_RETURN_STOREDIGITS	 22
    #define FEATURE_RETURN_SUCCESS	 	 23
    
    #define FEATURE_SENSE_CHAN	(1 << 0)
    #define FEATURE_SENSE_PEER	(1 << 1)
    
    Olle Johansson's avatar
    Olle Johansson committed
    /*! \brief
    
     * set caller and callee according to the direction
     */
    static void set_peers(struct ast_channel **caller, struct ast_channel **callee,
    	struct ast_channel *peer, struct ast_channel *chan, int sense)
    {
    	if (sense == FEATURE_SENSE_PEER) {
    		*caller = peer;
    		*callee = chan;
    	} else {
    		*callee = peer;
    		*caller = chan;
    	}
    }
    
    Olle Johansson's avatar
    Olle Johansson committed
    /*! \brief support routing for one touch call parking */
    
    static int builtin_parkcall(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
    {
    	struct ast_channel *parker;
            struct ast_channel *parkee;
    
    	int res = 0;
    	struct ast_module_user *u;
    
    	u = ast_module_user_add(chan);
    
    
    	set_peers(&parker, &parkee, peer, chan, sense);
    	/* Setup the exten/priority to be s/1 since we don't know
    	   where this call should return */
    	strcpy(chan->exten, "s");
    	chan->priority = 1;
    	if (chan->_state != AST_STATE_UP)
    		res = ast_answer(chan);
    	if (!res)
    		res = ast_safe_sleep(chan, 1000);
    	if (!res)
    		res = ast_park_call(parkee, parker, 0, NULL);
    
    	if (!res) {
    		if (sense == FEATURE_SENSE_CHAN)
    			res = AST_PBX_NO_HANGUP_PEER;
    		else
    			res = AST_PBX_KEEPALIVE;
    	}
    	return res;
    
    }
    
    
    static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
    {
    
    	char *caller_chan_id = NULL, *callee_chan_id = NULL, *args = NULL, *touch_filename = NULL;
    
    	struct ast_channel *caller_chan, *callee_chan;
    
    	if (!monitor_ok) {
    		ast_log(LOG_ERROR,"Cannot record the call. The monitor application is disabled.\n");
    		return -1;
    	}
    
    	if (!monitor_app && !(monitor_app = pbx_findapp("Monitor"))) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		monitor_ok = 0;
    
    		ast_log(LOG_ERROR,"Cannot record the call. The monitor application is disabled.\n");
    		return -1;
    
    
    	set_peers(&caller_chan, &callee_chan, peer, chan, sense);
    
    
    	if (!ast_strlen_zero(courtesytone)) {
    
    		if (ast_autoservice_start(callee_chan))
    
    		if (ast_stream_and_wait(caller_chan, courtesytone, "")) {
    
    			ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
    			ast_autoservice_stop(callee_chan);
    			return -1;
    
    		if (ast_autoservice_stop(callee_chan))
    
    	if (callee_chan->monitor) {
    
    		if (option_verbose > 3)
    			ast_verbose(VERBOSE_PREFIX_3 "User hit '%s' to stop recording call.\n", code);
    
    		ast_monitor_stop(callee_chan, 1);
    
    	if (caller_chan && callee_chan) {
    
    		const char *touch_format = pbx_builtin_getvar_helper(caller_chan, "TOUCH_MONITOR_FORMAT");
    		const char *touch_monitor = pbx_builtin_getvar_helper(caller_chan, "TOUCH_MONITOR");
    
    
    		if (!touch_format)
    			touch_format = pbx_builtin_getvar_helper(callee_chan, "TOUCH_MONITOR_FORMAT");
    
    
    			touch_monitor = pbx_builtin_getvar_helper(callee_chan, "TOUCH_MONITOR");
    
    		if (touch_monitor) {
    			len = strlen(touch_monitor) + 50;
    			args = alloca(len);
    
    			snprintf(touch_filename, len, "auto-%ld-%s", (long)time(NULL), touch_monitor);
    
    			snprintf(args, len, "%s|%s|m", (touch_format) ? touch_format : "wav", touch_filename);
    
    			caller_chan_id = ast_strdupa(S_OR(caller_chan->cid.cid_num, caller_chan->name));
    			callee_chan_id = ast_strdupa(S_OR(callee_chan->cid.cid_num, callee_chan->name));
    
    			len = strlen(caller_chan_id) + strlen(callee_chan_id) + 50;
    
    			snprintf(touch_filename, len, "auto-%ld-%s-%s", (long)time(NULL), caller_chan_id, callee_chan_id);
    
    			snprintf(args, len, "%s|%s|m", S_OR(touch_format, "wav"), touch_filename);
    
    		for( x = 0; x < strlen(args); x++) {
    
    			if (args[x] == '/')
    				args[x] = '-';
    
    		
    		if (option_verbose > 3)
    			ast_verbose(VERBOSE_PREFIX_3 "User hit '%s' to record call. filename: %s\n", code, args);
    
    
    		pbx_exec(callee_chan, monitor_app, args);
    
    		pbx_builtin_setvar_helper(callee_chan, "TOUCH_MONITOR_OUTPUT", touch_filename);
    		pbx_builtin_setvar_helper(caller_chan, "TOUCH_MONITOR_OUTPUT", touch_filename);
    	
    
    		return FEATURE_RETURN_SUCCESS;
    	}
    	
    	ast_log(LOG_NOTICE,"Cannot record the call. One or both channels have gone away.\n");	
    
    static int builtin_disconnect(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
    {
    	if (option_verbose > 3)
    		ast_verbose(VERBOSE_PREFIX_3 "User hit '%s' to disconnect call.\n", code);
    	return FEATURE_RETURN_HANGUP;
    }
    
    
    static int finishup(struct ast_channel *chan)
    {
            ast_indicate(chan, AST_CONTROL_UNHOLD);
    
    Olle Johansson's avatar
    Olle Johansson committed
    /*! \brief Find the context for the transfer */
    
    static const char *real_ctx(struct ast_channel *transferer, struct ast_channel *transferee)
    {
    
            const char *s = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT");
    
                    s = pbx_builtin_getvar_helper(transferee, "TRANSFER_CONTEXT");
    
            if (ast_strlen_zero(s)) /* Use the non-macro context to transfer the call XXX ? */
                    s = transferer->macrocontext;
            if (ast_strlen_zero(s))
                    s = transferer->context;
            return s;  
    }
    
    
    static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
    {
    	struct ast_channel *transferer;
    	struct ast_channel *transferee;
    
    	const char *transferer_real_context;
    
    	set_peers(&transferer, &transferee, peer, chan, sense);
    	transferer_real_context = real_ctx(transferer, transferee);
    
    	/* Start autoservice on chan while we talk to the originator */
    
    	ast_autoservice_start(transferee);
    
    	ast_indicate(transferee, AST_CONTROL_HOLD);
    
    	memset(xferto, 0, sizeof(xferto));
    
    	res = ast_stream_and_wait(transferer, "pbx-transfer", AST_DIGIT_ANY);
    
    	if (res < 0) {
    		finishup(transferee);
    		return -1; /* error ? */
    
    	if (res > 0)	/* If they've typed a digit already, handle it */
    		xferto[0] = (char) res;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	ast_stopstream(transferer);
    
    	res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout);
    
    	if (res < 0) {  /* hangup, would be 0 for invalid and 1 for valid */
    		finishup(transferee);
    
    	if (!strcmp(xferto, ast_parking_ext())) {
    
    		res = finishup(transferee);
    
    		if (res)
    
    		else if (!ast_park_call(transferee, transferer, 0, NULL)) {	/* success */
    
    			/* We return non-zero, but tell the PBX not to hang the channel when
    			   the thread dies -- We have to be careful now though.  We are responsible for 
    			   hanging up the channel, else it will never be hung up! */
    
    
    			return (transferer == peer) ? AST_PBX_KEEPALIVE : AST_PBX_NO_HANGUP_PEER;
    
    		} else {
    			ast_log(LOG_WARNING, "Unable to park call %s\n", transferee->name);
    		}
    
    Olle Johansson's avatar
    Olle Johansson committed
    		/*! \todo XXX Maybe we should have another message here instead of invalid extension XXX */
    
    	} else if (ast_exists_extension(transferee, transferer_real_context, xferto, 1, transferer->cid.cid_num)) {
    
    		pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", chan->name);
    		pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", peer->name);
    
    		res=finishup(transferee);
    
    		if (!transferee->pbx) {
    			/* Doh!  Use our handy async_goto functions */
    			if (option_verbose > 2) 
    				ast_verbose(VERBOSE_PREFIX_3 "Transferring %s to '%s' (context %s) priority 1\n"
    
    								,transferee->name, xferto, transferer_real_context);
    			if (ast_async_goto(transferee, transferer_real_context, xferto, 1))
    
    				ast_log(LOG_WARNING, "Async goto failed :-(\n");
    			res = -1;
    		} else {
    			/* Set the channel's new extension, since it exists, using transferer context */
    
    			set_c_e_p(transferee, transferer_real_context, xferto, 0);
    
    		return res;
    	} else {
    		if (option_verbose > 2)	
    
    			ast_verbose(VERBOSE_PREFIX_3 "Unable to find extension '%s' in context '%s'\n", xferto, transferer_real_context);
    
    	if (ast_stream_and_wait(transferer, xferfailsound, AST_DIGIT_ANY) < 0 ) {
    
    		finishup(transferee);
    		return -1;
    
    	}
    	ast_stopstream(transferer);
    
    	res = finishup(transferee);
    
    	if (res) {
    		if (option_verbose > 1)
    			ast_verbose(VERBOSE_PREFIX_2 "Hungup during autoservice stop on '%s'\n", transferee->name);
    		return res;
    	}
    	return FEATURE_RETURN_SUCCESS;
    }
    
    
    static int check_compat(struct ast_channel *c, struct ast_channel *newchan)
    {
    	if (ast_channel_make_compatible(c, newchan) < 0) {
    		ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n",
    			c->name, newchan->name);
    		ast_hangup(newchan);
    		return -1;
    	}
    	return 0;
    }
    
    
    static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
    {
    	struct ast_channel *transferer;
    	struct ast_channel *transferee;
    
    	const char *transferer_real_context;
    
    	int outstate=0;
    	struct ast_channel *newchan;
    	struct ast_channel *xferchan;
    
    	struct ast_bridge_thread_obj *tobj;
    
    	struct ast_bridge_config bconfig;
    	struct ast_frame *f;
    	int l;
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (option_debug)
    		ast_log(LOG_DEBUG, "Executing Attended Transfer %s, %s (sense=%d) \n", chan->name, peer->name, sense);
    
    	set_peers(&transferer, &transferee, peer, chan, sense);
            transferer_real_context = real_ctx(transferer, transferee);
    
    	/* Start autoservice on chan while we talk to the originator */
    
    	ast_autoservice_start(transferee);
    
    	res = ast_stream_and_wait(transferer, "pbx-transfer", AST_DIGIT_ANY);
    
    	if (res < 0) {
    		finishup(transferee);
    		return res;
    
    	}
    	if (res > 0) /* If they've typed a digit already, handle it */
    
    		xferto[0] = (char) res;
    
    	/* this is specific of atxfer */
    	res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout);
    
            if (res < 0) {  /* hangup, would be 0 for invalid and 1 for valid */
                    finishup(transferee);
                    return res;
            }
    	if (res == 0) {
    
    		ast_log(LOG_WARNING, "Did not read data.\n");
    
    		if (ast_stream_and_wait(transferer, "beeperr", ""))
    
    			return -1;
    
    		return FEATURE_RETURN_SUCCESS;
    	}
    
    	/* valid extension, res == 1 */
    
    	if (!ast_exists_extension(transferer, transferer_real_context, xferto, 1, transferer->cid.cid_num)) {
    		ast_log(LOG_WARNING, "Extension %s does not exist in context %s\n",xferto,transferer_real_context);
    		finishup(transferee);
    
    		if (ast_stream_and_wait(transferer, "beeperr", ""))
    
    			return -1;
    		return FEATURE_RETURN_SUCCESS;
    	}
    
    	l = strlen(xferto);
    	snprintf(xferto + l, sizeof(xferto) - l, "@%s/n", transferer_real_context);	/* append context */
    	newchan = ast_feature_request_and_dial(transferer, "Local", ast_best_codec(transferer->nativeformats),
    
    		xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name);
    
    	ast_indicate(transferer, -1);
    	if (!newchan) {
    		finishup(transferee);
    		/* any reason besides user requested cancel and busy triggers the failed sound */
    		if (outstate != AST_CONTROL_UNHOLD && outstate != AST_CONTROL_BUSY &&
    
    				ast_stream_and_wait(transferer, xferfailsound, ""))
    
    			return -1;
    		return FEATURE_RETURN_SUCCESS;
    	}
    
    	if (check_compat(transferer, newchan))
    		return -1;
    	memset(&bconfig,0,sizeof(struct ast_bridge_config));
    	ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT);
    	ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT);
    	res = ast_bridge_call(transferer, newchan, &bconfig);
    	if (newchan->_softhangup || newchan->_state != AST_STATE_UP || !transferer->_softhangup) {
    		ast_hangup(newchan);
    
    		if (ast_stream_and_wait(transferer, xfersound, ""))
    
    			ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
    
    		finishup(transferee);
    		transferer->_softhangup = 0;
    		return FEATURE_RETURN_SUCCESS;
    	}
    
    	if (check_compat(transferee, newchan))
    		return -1;
    
    
    	ast_indicate(transferee, AST_CONTROL_UNHOLD);
    
    	
    	if ((ast_autoservice_stop(transferee) < 0)
    	   || (ast_waitfordigit(transferee, 100) < 0)
    	   || (ast_waitfordigit(newchan, 100) < 0) 
    	   || ast_check_hangup(transferee) 
    	   || ast_check_hangup(newchan)) {
    		ast_hangup(newchan);
    		return -1;
    
    	xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "Transfered/%s", transferee->name);
    
    	if (!xferchan) {
    		ast_hangup(newchan);
    		return -1;
    	}
    	/* Make formats okay */
    	xferchan->readformat = transferee->readformat;
    	xferchan->writeformat = transferee->writeformat;
    	ast_channel_masquerade(xferchan, transferee);
    	ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
    	xferchan->_state = AST_STATE_UP;
    	ast_clear_flag(xferchan, AST_FLAGS_ALL);	
    	xferchan->_softhangup = 0;
    
    	if ((f = ast_read(xferchan)))
    		ast_frfree(f);
    
    	newchan->_state = AST_STATE_UP;
    	ast_clear_flag(newchan, AST_FLAGS_ALL);	
    	newchan->_softhangup = 0;
    
    	tobj = ast_calloc(1, sizeof(struct ast_bridge_thread_obj));
    	if (!tobj) {
    		ast_hangup(xferchan);
    		ast_hangup(newchan);
    		return -1;
    	}
    	tobj->chan = xferchan;
    	tobj->peer = newchan;
    	tobj->bconfig = *config;
    
    
    	if (ast_stream_and_wait(newchan, xfersound, ""))
    
    		ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
    
    	ast_bridge_call_thread_launch(tobj);
    	return -1;	/* XXX meaning the channel is bridged ? */
    
    /* add atxfer and automon as undefined so you can only use em if you configure them */
    
    #define FEATURES_COUNT (sizeof(builtin_features) / sizeof(builtin_features[0]))
    
    Olle Johansson's avatar
    Olle Johansson committed
    
    
    struct ast_call_feature builtin_features[] = 
    
    	{ AST_FEATURE_REDIRECT, "Blind Transfer", "blindxfer", "#", "#", builtin_blindtransfer, AST_FEATURE_FLAG_NEEDSDTMF, "" },
    	{ AST_FEATURE_REDIRECT, "Attended Transfer", "atxfer", "", "", builtin_atxfer, AST_FEATURE_FLAG_NEEDSDTMF, "" },
    	{ AST_FEATURE_AUTOMON, "One Touch Monitor", "automon", "", "", builtin_automonitor, AST_FEATURE_FLAG_NEEDSDTMF, "" },
    	{ AST_FEATURE_DISCONNECT, "Disconnect Call", "disconnect", "*", "*", builtin_disconnect, AST_FEATURE_FLAG_NEEDSDTMF, "" },
    	{ AST_FEATURE_PARKCALL, "Park Call", "parkcall", "", "", builtin_parkcall, AST_FEATURE_FLAG_NEEDSDTMF, "" },
    
    static AST_LIST_HEAD_STATIC(feature_list,ast_call_feature);
    
    Olle Johansson's avatar
    Olle Johansson committed
    /*! \brief register new feature into feature_list*/
    
    void ast_register_feature(struct ast_call_feature *feature)
    {
    	if (!feature) {
    		ast_log(LOG_NOTICE,"You didn't pass a feature!\n");
        		return;
    	}
      
    	AST_LIST_LOCK(&feature_list);
    	AST_LIST_INSERT_HEAD(&feature_list,feature,feature_entry);
    	AST_LIST_UNLOCK(&feature_list);
    
    	if (option_verbose >= 2) 
    		ast_verbose(VERBOSE_PREFIX_2 "Registered Feature '%s'\n",feature->sname);
    }
    
    
    Olle Johansson's avatar
    Olle Johansson committed
    /*! \brief unregister feature from feature_list */
    
    void ast_unregister_feature(struct ast_call_feature *feature)
    {
    
    
    	AST_LIST_LOCK(&feature_list);
    	AST_LIST_REMOVE(&feature_list,feature,feature_entry);
    	AST_LIST_UNLOCK(&feature_list);
    	free(feature);
    }
    
    
    Olle Johansson's avatar
    Olle Johansson committed
    /*! \brief Remove all features in the list */
    
    static void ast_unregister_features(void)
    {
    	struct ast_call_feature *feature;
    
    	AST_LIST_LOCK(&feature_list);
    	while ((feature = AST_LIST_REMOVE_HEAD(&feature_list,feature_entry)))
    		free(feature);
    	AST_LIST_UNLOCK(&feature_list);
    }
    
    Olle Johansson's avatar
    Olle Johansson committed
    /*! \brief find a feature by name */
    
    static struct ast_call_feature *find_feature(char *name)
    {
    	struct ast_call_feature *tmp;
    
    	AST_LIST_LOCK(&feature_list);
    
    	AST_LIST_TRAVERSE(&feature_list, tmp, feature_entry) {
    		if (!strcasecmp(tmp->sname, name))
    			break;
    
    Olle Johansson's avatar
    Olle Johansson committed
    /*! \brief exec an app by feature */
    
    static int feature_exec_app(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
    {
    	struct ast_app *app;
    	struct ast_call_feature *feature;
    
    	AST_LIST_TRAVERSE(&feature_list, feature, feature_entry) {
    		if (!strcasecmp(feature->exten, code))
    
    	}
    	AST_LIST_UNLOCK(&feature_list);
    
    	if (!feature) { /* shouldn't ever happen! */
    		ast_log(LOG_NOTICE, "Found feature before, but at execing we've lost it??\n");
    		return -1; 
    	}
    
    
    	if (sense == FEATURE_SENSE_CHAN) {
    		if (!ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLER))
    			return FEATURE_RETURN_PASSDIGITS;
    
    		if (ast_test_flag(feature, AST_FEATURE_FLAG_ONSELF)) {
    			work = chan;
    			idle = peer;
    		} else {
    			work = peer;
    			idle = chan;
    		}
    
    		if (!ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLEE))
    			return FEATURE_RETURN_PASSDIGITS;
    
    		if (ast_test_flag(feature, AST_FEATURE_FLAG_ONSELF)) {
    			work = peer;
    			idle = chan;
    		} else {
    			work = chan;
    			idle = peer;
    		}
    
    		ast_log(LOG_WARNING, "Could not find application (%s)\n", feature->app);
    
    	ast_autoservice_start(idle);
    	
    	if (!ast_strlen_zero(feature->moh_class))
    		ast_moh_start(idle, feature->moh_class, NULL);
    
    
    	res = pbx_exec(work, app, feature->app_args);
    
    
    	if (!ast_strlen_zero(feature->moh_class))
    		ast_moh_stop(idle);
    
    	ast_autoservice_stop(idle);
    
    
    	if (res == AST_PBX_KEEPALIVE)
    		return FEATURE_RETURN_PBX_KEEPALIVE;
    	else if (res == AST_PBX_NO_HANGUP_PEER)
    		return FEATURE_RETURN_NO_HANGUP_PEER;
    	else if (res)
    		return FEATURE_RETURN_SUCCESSBREAK;