diff -Naur asterisk-1.4.18.1.old/configs/features.conf.sample asterisk-1.4.18.1.new/configs/features.conf.sample --- asterisk-1.4.18.1.old/configs/features.conf.sample 2007-10-15 18:39:59.000000000 +0200 +++ asterisk-1.4.18.1.new/configs/features.conf.sample 2008-05-19 17:15:18.000000000 +0200 @@ -3,24 +3,27 @@ ; [general] -parkext => 700 ; What extension to dial to park -parkpos => 701-720 ; What extensions to park calls on. These needs to be - ; numeric, as Asterisk starts from the start position +parkext => 700 ; What extension to dial to park (default parking lot) +parkpos => 701-720 ; What extensions to park calls on (default parking lot). + ; These needs to be numeric, as Asterisk starts from the start position ; and increments with one for the next parked call. -context => parkedcalls ; Which context parked calls are in +context => parkedcalls ; Which context parked calls are in (default parking lot) ;parkingtime => 45 ; Number of seconds a call can be parked for - ; (default is 45 seconds) + ; (default parking lot, default is 45 seconds) +;parkhints = yes ; Automatically add hints (default parking lot, default no) ;courtesytone = beep ; Sound file to play to the parked caller ; when someone dials a parked call ; or the Touch Monitor is activated/deactivated. -;parkedplay = caller ; Who to play the courtesy tone to when picking up a parked call + ; (default parking lot) +;parkedplay = caller ; Who to play the courtesy tone to when picking up a parked call (default parking lot). ; one of: parked, caller, both (default is caller) -;adsipark = yes ; if you want ADSI parking announcements -;findslot => next ; Continue to the 'next' free parking space. +;adsipark = yes ; if you want ADSI parking announcements (default parking lot) +;findslot => next ; Continue to the 'next' free parking space (default parking lot). ; Defaults to 'first' available ;parkedmusicclass=default ; This is the MOH class to use for the parked channel ; as long as the class is not set on the channel directly ; using Set(CHANNEL(musicclass)=whatever) in the dialplan + ; (default parking lot) ;transferdigittimeout => 3 ; Number of seconds to wait between digits when transferring a call ; (default is 3 seconds) @@ -93,3 +96,17 @@ ;unpauseMonitor => #3,self/callee,UnPauseMonitor ;Allow the callee to unpause monitoring ; ;on their channel ; + +;*** Define another parking lot +; +;[taigaparklot] +:parkext => 800 +;parkpos => 801-750 +;context => taigapark +;parkingtime => 45 +;parkhints = yes +;courtesytone = beep +;parkedplay = caller +;adsipark = yes +;findslot => next +;parkedmusicclass=default diff -Naur asterisk-1.4.18.1.old/include/asterisk/manager.h asterisk-1.4.18.1.new/include/asterisk/manager.h --- asterisk-1.4.18.1.old/include/asterisk/manager.h 2007-04-06 22:58:43.000000000 +0200 +++ asterisk-1.4.18.1.new/include/asterisk/manager.h 2008-05-19 17:14:20.000000000 +0200 @@ -132,7 +132,7 @@ const char *astman_get_header(const struct message *m, char *var); /*! Get a linked list of the Variable: headers */ -struct ast_variable *astman_get_variables(const struct message *m); +struct ast_variable *astman_get_variables(const struct message *m, char *variable); /*! Send error in manager transaction */ void astman_send_error(struct mansession *s, const struct message *m, char *error); diff -Naur asterisk-1.4.18.1.old/main/manager.c asterisk-1.4.18.1.new/main/manager.c --- asterisk-1.4.18.1.old/main/manager.c 2008-01-18 00:23:25.000000000 +0100 +++ asterisk-1.4.18.1.new/main/manager.c 2008-05-19 17:13:20.000000000 +0200 @@ -769,9 +769,10 @@ return ""; } -struct ast_variable *astman_get_variables(const struct message *m) +struct ast_variable *astman_get_variables(const struct message *m, char *variable) { - int varlen, x, y; + char cmp[80]; + int x, y; struct ast_variable *head = NULL, *cur; char *var, *val; @@ -780,13 +781,13 @@ AST_APP_ARG(vars)[32]; ); - varlen = strlen("Variable: "); + snprintf(cmp, sizeof(cmp), "%s: ", variable); for (x = 0; x < m->hdrcount; x++) { - if (strncasecmp("Variable: ", m->headers[x], varlen)) + if (strncasecmp(cmp, m->headers[x], strlen(cmp))) continue; - parse = ast_strdupa(m->headers[x] + varlen); + parse = ast_strdupa(m->headers[x] + strlen(cmp)); AST_STANDARD_APP_ARGS(args, parse); if (args.argc) { @@ -1605,81 +1606,6 @@ return 0; } -static char mandescr_redirect[] = -"Description: Redirect (transfer) a call.\n" -"Variables: (Names marked with * are required)\n" -" *Channel: Channel to redirect\n" -" ExtraChannel: Second call leg to transfer (optional)\n" -" *Exten: Extension to transfer to\n" -" *Context: Context to transfer to\n" -" *Priority: Priority to transfer to\n" -" ActionID: Optional Action id for message matching.\n"; - -/*! \brief action_redirect: The redirect manager command */ -static int action_redirect(struct mansession *s, const struct message *m) -{ - const char *name = astman_get_header(m, "Channel"); - const char *name2 = astman_get_header(m, "ExtraChannel"); - const char *exten = astman_get_header(m, "Exten"); - const char *context = astman_get_header(m, "Context"); - const char *priority = astman_get_header(m, "Priority"); - struct ast_channel *chan, *chan2 = NULL; - int pi = 0; - int res; - - if (ast_strlen_zero(name)) { - astman_send_error(s, m, "Channel not specified"); - return 0; - } - if (!ast_strlen_zero(priority) && (sscanf(priority, "%d", &pi) != 1)) { - if ((pi = ast_findlabel_extension(NULL, context, exten, priority, NULL)) < 1) { - astman_send_error(s, m, "Invalid priority\n"); - return 0; - } - } - /* XXX watch out, possible deadlock!!! */ - chan = ast_get_channel_by_name_locked(name); - if (!chan) { - char buf[BUFSIZ]; - snprintf(buf, sizeof(buf), "Channel does not exist: %s", name); - astman_send_error(s, m, buf); - return 0; - } - if (ast_check_hangup(chan)) { - astman_send_error(s, m, "Redirect failed, channel not up.\n"); - ast_channel_unlock(chan); - return 0; - } - if (!ast_strlen_zero(name2)) - chan2 = ast_get_channel_by_name_locked(name2); - if (chan2 && ast_check_hangup(chan2)) { - astman_send_error(s, m, "Redirect failed, extra channel not up.\n"); - ast_channel_unlock(chan); - ast_channel_unlock(chan2); - return 0; - } - res = ast_async_goto(chan, context, exten, pi); - if (!res) { - if (!ast_strlen_zero(name2)) { - if (chan2) - res = ast_async_goto(chan2, context, exten, pi); - else - res = -1; - if (!res) - astman_send_ack(s, m, "Dual Redirect successful"); - else - astman_send_error(s, m, "Secondary redirect failed"); - } else - astman_send_ack(s, m, "Redirect successful"); - } else - astman_send_error(s, m, "Redirect failed"); - if (chan) - ast_channel_unlock(chan); - if (chan2) - ast_channel_unlock(chan2); - return 0; -} - static char mandescr_command[] = "Description: Run a CLI command.\n" "Variables: (Names marked with * are required)\n" @@ -1809,7 +1735,7 @@ const char *appdata = astman_get_header(m, "Data"); const char *async = astman_get_header(m, "Async"); const char *id = astman_get_header(m, "ActionID"); - struct ast_variable *vars = astman_get_variables(m); + struct ast_variable *vars = astman_get_variables(m, "Variable"); char *tech, *data; char *l = NULL, *n = NULL; int pi = 0; @@ -2840,7 +2766,6 @@ ast_manager_register2("Getvar", EVENT_FLAG_CALL, action_getvar, "Gets a Channel Variable", mandescr_getvar ); ast_manager_register2("GetConfig", EVENT_FLAG_CONFIG, action_getconfig, "Retrieve configuration", mandescr_getconfig); ast_manager_register2("UpdateConfig", EVENT_FLAG_CONFIG, action_updateconfig, "Update basic configuration", mandescr_updateconfig); - ast_manager_register2("Redirect", EVENT_FLAG_CALL, action_redirect, "Redirect (transfer) a call", mandescr_redirect ); ast_manager_register2("Originate", EVENT_FLAG_CALL, action_originate, "Originate Call", mandescr_originate); ast_manager_register2("Command", EVENT_FLAG_COMMAND, action_command, "Execute Asterisk CLI Command", mandescr_command ); ast_manager_register2("ExtensionState", EVENT_FLAG_CALL, action_extensionstate, "Check Extension Status", mandescr_extensionstate ); diff -Naur asterisk-1.4.18.1.old/res/res_features.c asterisk-1.4.18.1.new/res/res_features.c --- asterisk-1.4.18.1.old/res/res_features.c 2008-01-28 19:26:31.000000000 +0100 +++ asterisk-1.4.18.1.new/res/res_features.c 2008-05-19 17:12:28.000000000 +0200 @@ -29,7 +29,7 @@ #include "asterisk.h" -ASTERISK_FILE_VERSION(__FILE__, "$Revision: 100626 $") +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include #include @@ -62,11 +62,13 @@ #include "asterisk/adsi.h" #include "asterisk/devicestate.h" #include "asterisk/monitor.h" +#include "asterisk/astobj.h" #define DEFAULT_PARK_TIME 45000 #define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000 #define DEFAULT_FEATURE_DIGIT_TIMEOUT 500 #define DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER 15000 +#define DEFAULT_PARKINGLOT "default" /*!< Default parking lot */ #define AST_MAX_WATCHERS 256 @@ -81,25 +83,34 @@ static char *parkedcall = "ParkedCall"; -static int parkaddhints = 0; /*!< Add parking hints automatically */ -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; +/*! \brief Structure for parking lots in a linked list. */ +struct ast_parkinglot { + ASTOBJ_COMPONENTS(struct ast_parkinglot); /*!< various object stuff, including ->name */ + char parking_con[AST_MAX_EXTENSION]; /*!< Context for which parking is made accessible */ + char parking_ext[AST_MAX_EXTENSION]; /*!< Extension you type to park the call */ + char parkmohclass[MAX_MUSICCLASS]; /*!< Music class used for parking */ + char courtesytone[256]; /*!< Courtesy tone */ + int parkedplay; /*!< Who to play the courtesy tone to */ + int parkaddhints; /*!< Add parking hints automatically */ + int adsipark; + int parking_start; /*!< First available extension for parking */ + int parking_stop; /*!< Last available extension for parking */ + int parking_offset; + int parkfindnext; + int parkingtime; /*!< Default parking time */ + struct parkeduser *occupiedlots; +}; + +struct ast_parkinglot_list { + ASTOBJ_CONTAINER_COMPONENTS(struct ast_parkinglot); +} parkinglots; -static int adsipark; +struct ast_parkinglot *default_parkinglot = NULL; static int transferdigittimeout; static int featuredigittimeout; @@ -135,6 +146,7 @@ static struct ast_app *monitor_app = NULL; static int monitor_ok = 1; +/*! \brief Structure to handle one parked user */ struct parkeduser { struct ast_channel *chan; /*!< Parking channel */ struct timeval start; /*!< Time the parking started */ @@ -148,17 +160,32 @@ char peername[1024]; unsigned char moh_trys; struct parkeduser *next; + struct ast_parkinglot *parkinglot; }; -static struct parkeduser *parkinglot; - -AST_MUTEX_DEFINE_STATIC(parking_lock); /*!< protects all static variables above */ - static pthread_t parking_thread; +/* Forward declarations */ +static struct ast_parkinglot *parkinglot_addref(struct ast_parkinglot *parkinglot); +static void parkinglot_unref(struct ast_parkinglot *parkinglot); +static void parkinglot_destroy(struct ast_parkinglot *ruin); +static struct ast_parkinglot *find_parkinglot(const char *parkinglotname); +static void park_add_hints(char *context, int start, int stop); +static int finishup(struct ast_channel *chan); +static const char *real_ctx(struct ast_channel *transferer, struct ast_channel *transferee); + char *ast_parking_ext(void) { - return parking_ext; + /* Each parking lot now uses its own 'parking extension', + so we can't return ONE value. + Function is used from channel drivers to check if + parking call can be handled directly. + As we return "" this check always fails and the action + is passed to ast_async_goto() which uses the dial plan. + Thats not the short way but it works. + Behaviour results in call of park_call_exec() to park the call + instead of ast_park_call(). */ + return ""; } char *ast_pickup_ext(void) @@ -309,54 +336,92 @@ return AST_DEVICE_INUSE; } -static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, int timeout, int *extout, char *orig_chan_name) +/* Park a call */ +static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, int timeout, int *extout, + char *orig_chan_name, const char *parkinglotname, const char *position) { struct parkeduser *pu, *cur; int i, x = -1, parking_range; struct ast_context *con; const char *parkingexten; - + struct ast_parkinglot *parkinglot = 0; + + if (parkinglotname) + parkinglot = find_parkinglot(parkinglotname); + if (!parkinglot) { + parkinglot_addref(default_parkinglot); + parkinglot = default_parkinglot; + } + + ASTOBJ_WRLOCK(parkinglot); + + if (option_debug) + ast_log(LOG_DEBUG, "Parkinglot: %s\n", parkinglot->name); + /* Allocate memory for parking data */ - if (!(pu = ast_calloc(1, sizeof(*pu)))) + if (!(pu = ast_calloc(1, sizeof(*pu)))) { + ASTOBJ_UNLOCK(parkinglot); + parkinglot_unref(parkinglot); return -1; + } - /* Lock parking lot */ - ast_mutex_lock(&parking_lock); /* 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_mutex_unlock(&parking_lock); + if (ast_exists_extension(NULL, parkinglot->parking_con, parkingexten, 1, NULL)) { + ast_log(LOG_WARNING, "Requested parking extension already exists: %s@%s\n", parkingexten, parkinglot->parking_con); + ASTOBJ_UNLOCK(parkinglot); + parkinglot_unref(parkinglot); free(pu); - ast_log(LOG_WARNING, "Requested parking extension already exists: %s@%s\n", parkingexten, parking_con); return 1; /* Continue execution if possible */ } ast_copy_string(pu->parkingexten, parkingexten, sizeof(pu->parkingexten)); x = atoi(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; + if (!ast_strlen_zero(position)) { + int exist = 0; + x = atoi(position); + cur = parkinglot->occupiedlots; while(cur) { - if (cur->parkingnum == x) + if (cur->parkingnum == x) { + exist = 1; break; + } cur = cur->next; } - if (!cur) - break; - } + if (exist) { + ast_log(LOG_WARNING, "Requested parking extension already exists: %d@%s\n", x, parkinglot->parking_con); + ASTOBJ_UNLOCK(parkinglot); + parkinglot_unref(parkinglot); + free(pu); + return -1; + } + } else { + /* Select parking space within range */ + parking_range = parkinglot->parking_stop - parkinglot->parking_start + 1; + for (i = 0; i < parking_range; i++) { + x = (i + parkinglot->parking_offset) % parking_range + parkinglot->parking_start; + cur = parkinglot->occupiedlots; + while(cur) { + if (cur->parkingnum == x) + break; + cur = cur->next; + } + if (!cur) + break; + } - if (!(i < parking_range)) { - ast_log(LOG_WARNING, "No more parking spaces\n"); - free(pu); - ast_mutex_unlock(&parking_lock); - return -1; + if (!(i < parking_range)) { + ast_log(LOG_WARNING, "No more parking spaces in parking lot \"%s\"\n", parkinglot->name); + free(pu); + ASTOBJ_UNLOCK(parkinglot); + parkinglot_unref(parkinglot); + return -1; + } + /* Set pointer for next parking */ + if (parkinglot->parkfindnext) + parkinglot->parking_offset = x - parkinglot->parking_start + 1; } - /* Set pointer for next parking */ - if (parkfindnext) - parking_offset = x - parking_start + 1; } chan->appl = "Parked Call"; @@ -367,61 +432,72 @@ /* 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); + S_OR(parkinglot->parkmohclass, NULL), + !ast_strlen_zero(parkinglot->parkmohclass) ? strlen(parkinglot->parkmohclass) + 1 : 0); } pu->start = ast_tvnow(); pu->parkingnum = x; - pu->parkingtime = (timeout > 0) ? timeout : parkingtime; + pu->parkinglot = parkinglot; + pu->parkingtime = (timeout > 0) ? timeout : parkinglot->parkingtime; if (extout) *extout = x; - if (peer) + if (peer) ast_copy_string(pu->peername, peer->name, sizeof(pu->peername)); + else { + /* try to get peer name, some channel driver set this value */ + const char *peername = pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"); + if (peername) { + if (option_debug) + ast_log(LOG_DEBUG, "Got name of peer: %s.\n", peername); + ast_copy_string(pu->peername, peername, 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; + pu->next = parkinglot->occupiedlots; + parkinglot->occupiedlots = pu; /* 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); + ASTOBJ_UNLOCK(parkinglot); /* Wake up the (presumably select()ing) thread */ pthread_kill(parking_thread, SIGURG); if (option_verbose > 1) - 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)); + ast_verbose(VERBOSE_PREFIX_2 "Parked %s on %d (lot %s). Will timeout back to extension [%s] %s, %d in %d seconds\n", pu->chan->name, pu->parkingnum, parkinglot->name, pu->context, pu->exten, pu->priority, (pu->parkingtime/1000)); if (pu->parkingnum != -1) snprintf(pu->parkingexten, sizeof(pu->parkingexten), "%d", x); manager_event(EVENT_FLAG_CALL, "ParkedCall", "Exten: %s\r\n" "Channel: %s\r\n" + "Parkinglot: %s\r\n" "From: %s\r\n" "Timeout: %ld\r\n" "CallerID: %s\r\n" "CallerIDName: %s\r\n", - pu->parkingexten, pu->chan->name, peer ? peer->name : "", + pu->parkingexten, pu->chan->name, pu->parkinglot->name, peer ? peer->name : "", (long)pu->start.tv_sec + (long)(pu->parkingtime/1000) - (long)time(NULL), S_OR(pu->chan->cid.cid_num, ""), S_OR(pu->chan->cid.cid_name, "") ); - if (peer && adsipark && ast_adsi_available(peer)) { + if (peer && parkinglot->adsipark && ast_adsi_available(peer)) { adsi_announce_park(peer, pu->parkingexten); /* Only supports parking numbers */ ast_adsi_unload_session(peer); } - con = ast_context_find(parking_con); + con = ast_context_find(parkinglot->parking_con); if (!con) - con = ast_context_create(NULL, parking_con, registrar); + con = ast_context_create(NULL, parkinglot->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); + ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parkinglot->parking_con); /* Tell the peer channel the number of the parking space */ if (peer && ((pu->parkingnum != -1 && ast_strlen_zero(orig_chan_name)) || !strcasecmp(peer->name, orig_chan_name))) { /* Only say number if it's a number and the channel hasn't been masqueraded away */ /* Make sure we don't start saying digits to the channel being parked */ @@ -430,17 +506,29 @@ ast_clear_flag(peer, AST_FLAG_MASQ_NOSTREAM); } if (con) { - if (!ast_add_extension2(con, 1, pu->parkingexten, 1, NULL, NULL, parkedcall, strdup(pu->parkingexten), ast_free, registrar)) - notify_metermaids(pu->parkingexten, parking_con); + char extatlot[AST_MAX_EXTENSION*2]; + char *pextatlot; + if (!ast_strlen_zero(parkinglotname)) { + snprintf(extatlot, sizeof(extatlot), "%s@%s", pu->parkingexten, parkinglotname); + pextatlot = extatlot; + } + else + pextatlot = pu->parkingexten; + if (option_debug) + ast_log(LOG_DEBUG, "Add extension to dialplan: %s(%s).\n", parkedcall, pextatlot); + if (!ast_add_extension2(con, 1, pu->parkingexten, 1, NULL, NULL, parkedcall, strdup(pextatlot), ast_free, registrar)) + notify_metermaids(pu->parkingexten, parkinglot->parking_con); } 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); + S_OR(parkinglot->parkmohclass, NULL), + !ast_strlen_zero(parkinglot->parkmohclass) ? strlen(parkinglot->parkmohclass) + 1 : 0); pu->notquiteyet = 0; pthread_kill(parking_thread, SIGURG); } + parkinglot_unref(parkinglot); + return 0; } @@ -449,10 +537,15 @@ after these channels too */ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeout, int *extout) { - return park_call_full(chan, peer, timeout, extout, NULL); + if (option_debug) + ast_log(LOG_DEBUG, "Park '%s' without indication of parkinglot name, peer: %s.\n", + chan ? chan->name : "", peer ? peer->name : ""); + return park_call_full(chan, peer, timeout, extout, NULL, NULL, NULL); } -int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int timeout, int *extout) + +static int masq_park_call_full(struct ast_channel *rchan, struct ast_channel *peer, int timeout, int *extout, const char *parkinglotname, + const char *position) { struct ast_channel *chan; struct ast_frame *f; @@ -479,7 +572,16 @@ orig_chan_name = ast_strdupa(chan->name); - park_call_full(chan, peer, timeout, extout, orig_chan_name); + return park_call_full(chan, peer, timeout, extout, orig_chan_name, parkinglotname, position); +} + +int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int timeout, int *extout) +{ + if (option_debug) + ast_log(LOG_DEBUG, "Park '%s' without indication of parkinglot name, peer: %s.\n", + rchan ? rchan->name : "", peer ? peer->name : ""); + + masq_park_call_full(rchan, peer, timeout, extout, NULL, NULL); return 0; } @@ -512,6 +614,31 @@ } } +static struct ast_exten *find_matching_application(struct ast_context *c, const char *exten, const char *app) +{ + struct ast_exten *e; + struct ast_include *i; + struct ast_context *c2; + + for (e=ast_walk_context_extensions(c, NULL); e; e=ast_walk_context_extensions(c, e)) { + if (!(exten) || ast_extension_match(ast_get_extension_name(e), exten)) { + if (ast_extension_match(ast_get_extension_app(e), app)) + return e; + } + } + /* No match, run through includes */ + for (i=ast_walk_context_includes(c, NULL); i; i=ast_walk_context_includes(c, i)) { + for (c2=ast_walk_contexts(NULL); c2; c2=ast_walk_contexts(c2)) { + if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) { + e = find_matching_application(c2, exten, app); + if (e) + return e; + } + } + } + return NULL; +} + /*! \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, void *data) { @@ -519,6 +646,11 @@ struct ast_channel *parkee; int res = 0; struct ast_module_user *u; + const char *transferer_real_context; + struct ast_context *con; + static struct ast_exten *e; + char *parkinglotname; + struct ast_parkinglot *parkinglot = 0; u = ast_module_user_add(chan); @@ -531,27 +663,76 @@ res = ast_answer(chan); if (!res) res = ast_safe_sleep(chan, 1000); - if (!res) - res = ast_park_call(parkee, parker, 0, NULL); + if (!res) { + transferer_real_context = real_ctx(parker, parkee); - ast_module_user_remove(u); + /* Get parkinglot name from dialplan */ + con = ast_context_find(transferer_real_context); + e = find_matching_application(con, NULL, parkcall); + if (e) { + parkinglotname = ast_get_extension_app_data(e); + if (option_debug) + ast_log(LOG_DEBUG, "Park entry present in dialplan for context %s, parkinglot name: %s.\n", + transferer_real_context, parkinglotname ? parkinglotname : ""); + /* Get parkinglot */ + if (parkinglotname) { + parkinglot = find_parkinglot(parkinglotname); + if (parkinglot) + parkinglot_unref(parkinglot); + } + if (!parkinglot) { + parkinglot = default_parkinglot; + } + } else { + if (option_debug) + ast_log(LOG_DEBUG, "Park entry not present in dialplan for context \"%s\".\n", + transferer_real_context); + } - if (!res) { - if (sense == FEATURE_SENSE_CHAN) - res = AST_PBX_NO_HANGUP_PEER; - else - res = AST_PBX_KEEPALIVE; + if (parkinglot) { + if (option_debug) + ast_log(LOG_DEBUG, "Parking lot: %s, parking extension: %s.\n", + parkinglot->name, parkinglot->parking_ext); + res = finishup(parkee); + if (res) + res = -1; + else if (!park_call_full(parkee, parker, 0, NULL, NULL, parkinglotname, NULL)) { /* success */ + ast_module_user_remove(u); + /* 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 (parker == peer) ? AST_PBX_KEEPALIVE : AST_PBX_NO_HANGUP_PEER; + } else { + ast_log(LOG_WARNING, "Unable to park call %s\n", parkee->name); + } + } } - return res; + ast_module_user_remove(u); + + if (ast_stream_and_wait(parker, xferfailsound, parker->language, AST_DIGIT_ANY) < 0 ) { + finishup(parkee); + return -1; + } + ast_stopstream(parker); + res = finishup(parkee); + if (res) { + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Hungup during autoservice stop on '%s'\n", parkee->name); + return res; + } + return FEATURE_RETURN_SUCCESS; } + static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data) { char *caller_chan_id = NULL, *callee_chan_id = NULL, *args = NULL, *touch_filename = NULL; int x = 0; size_t len; struct ast_channel *caller_chan, *callee_chan; + char *courtesytone; if (!monitor_ok) { ast_log(LOG_ERROR,"Cannot record the call. The monitor application is disabled.\n"); @@ -566,6 +747,7 @@ set_peers(&caller_chan, &callee_chan, peer, chan, sense); + courtesytone = (default_parkinglot) ? default_parkinglot->courtesytone : ""; if (!ast_strlen_zero(courtesytone)) { if (ast_autoservice_start(callee_chan)) return -1; @@ -664,6 +846,10 @@ const char *transferer_real_context; char xferto[256]; int res; + struct ast_context *con; + static struct ast_exten *e; + char *parkinglotname; + struct ast_parkinglot *parkinglot = 0; set_peers(&transferer, &transferee, peer, chan, sense); transferer_real_context = real_ctx(transferer, transferee); @@ -688,11 +874,40 @@ finishup(transferee); return res; } - if (!strcmp(xferto, ast_parking_ext())) { + + if (option_debug) + ast_log(LOG_DEBUG, "Got number, xferto: %s.\n", xferto); + + /* Get parkinglot name from dialplan */ + con = ast_context_find(transferer_real_context); + e = find_matching_application(con, xferto, parkcall); + if (e) { + parkinglotname = ast_get_extension_app_data(e); + if (option_debug) + ast_log(LOG_DEBUG, "Park entry present in dialplan for context %s, parkinglot name: %s.\n", + transferer_real_context, parkinglotname ? parkinglotname : ""); + /* Get parkinglot */ + if (parkinglotname) { + parkinglot = find_parkinglot(parkinglotname); + if (parkinglot) + parkinglot_unref(parkinglot); + } + if (!parkinglot) + parkinglot = default_parkinglot; + } else { + if (option_debug) + ast_log(LOG_DEBUG, "Park entry not present in dialplan for context \"%s\" and extension \"%s\".\n", + transferer_real_context, xferto); + } + if (option_debug && parkinglot) + ast_log(LOG_DEBUG, "Parking lot: %s, parking extension: %s, xferto: %s.\n", + parkinglot->name, parkinglot->parking_ext, xferto); + + if (parkinglot && !strcmp(xferto, parkinglot->parking_ext)) { res = finishup(transferee); if (res) res = -1; - else if (!ast_park_call(transferee, transferer, 0, NULL)) { /* success */ + else if (!park_call_full(transferee, transferer, 0, NULL, NULL, parkinglotname, 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! */ @@ -1630,176 +1845,196 @@ return res; } -static void post_manager_event(const char *s, char *parkingexten, struct ast_channel *chan) +static void post_manager_event(const char *s, struct parkeduser *pu) { manager_event(EVENT_FLAG_CALL, s, "Exten: %s\r\n" "Channel: %s\r\n" + "Parkinglot: %s\r\n" "CallerID: %s\r\n" "CallerIDName: %s\r\n\r\n", - parkingexten, - chan->name, - S_OR(chan->cid.cid_num, ""), - S_OR(chan->cid.cid_name, "") + pu->parkingexten, + pu->chan->name, + pu->parkinglot->name, + S_OR(pu->chan->cid.cid_num, ""), + S_OR(pu->chan->cid.cid_name, "") ); } -/*! \brief Take care of parked calls and unpark them if needed */ -static void *do_parking_thread(void *ignore) +/*! \brief Run management on parkinglots, called once per parkinglot */ +static int manage_parkinglot(struct ast_parkinglot *curlot, fd_set *rfds, fd_set *efds, fd_set *nrfds, fd_set *nefds, int *ms, int *max) { - fd_set rfds, efds; /* results from previous select, to be preserved across loops. */ - FD_ZERO(&rfds); - FD_ZERO(&efds); - - for (;;) { - struct parkeduser *pu, *pl, *pt = NULL; - int ms = -1; /* select timeout, uninitialized */ - int max = -1; /* max fd, none there yet */ - fd_set nrfds, nefds; /* args for the next select */ - FD_ZERO(&nrfds); - FD_ZERO(&nefds); + struct parkeduser *pu, *pl = NULL, *pt = NULL; + int res = 0; - ast_mutex_lock(&parking_lock); - pl = NULL; - pu = parkinglot; - /* navigate the list with prev-cur pointers to support removals */ - while (pu) { - struct ast_channel *chan = pu->chan; /* shorthand */ - int tms; /* timeout for this item */ - int x; /* fd index in channel */ - struct ast_context *con; + pu = curlot->occupiedlots; - if (pu->notquiteyet) { /* Pretend this one isn't here yet */ - pl = pu; - pu = pu->next; - continue; - } - tms = ast_tvdiff_ms(ast_tvnow(), pu->start); - if (tms > pu->parkingtime) { - ast_indicate(chan, AST_CONTROL_UNHOLD); - /* Get chan, exten from derived kludge */ - if (pu->peername[0]) { - char *peername = ast_strdupa(pu->peername); - char *cp = strrchr(peername, '-'); - if (cp) - *cp = 0; - con = ast_context_find(parking_con_dial); - if (!con) { - con = ast_context_create(NULL, parking_con_dial, registrar); - if (!con) - ast_log(LOG_ERROR, "Parking dial context '%s' does not exist and unable to create\n", parking_con_dial); - } - if (con) { - char returnexten[AST_MAX_EXTENSION]; - snprintf(returnexten, sizeof(returnexten), "%s||t", peername); - ast_add_extension2(con, 1, peername, 1, NULL, NULL, "Dial", strdup(returnexten), ast_free, registrar); - } - set_c_e_p(chan, parking_con_dial, peername, 1); - } else { - /* They've been waiting too long, send them back to where they came. Theoretically they - should have their original extensions and such, but we copy to be on the safe side */ - set_c_e_p(chan, pu->context, pu->exten, pu->priority); + while (pu) { + struct ast_channel *chan = pu->chan; /* shorthand */ + int tms; /* timeout for this item */ + int x; /* fd index in channel */ + struct ast_context *con; + + if (pu->notquiteyet) { /* Pretend this one isn't here yet */ + pl = pu; + pu = pu->next; + continue; + } + tms = ast_tvdiff_ms(ast_tvnow(), pu->start); + if (tms > pu->parkingtime) { + /* Stop music on hold */ + ast_indicate(chan, AST_CONTROL_UNHOLD); + /* Get chan, exten from derived kludge */ + if (pu->peername[0]) { + char *peername = ast_strdupa(pu->peername); + char *cp = strrchr(peername, '-'); + if (cp) + *cp = 0; + con = ast_context_find(parking_con_dial); + if (!con) { + con = ast_context_create(NULL, parking_con_dial, registrar); + if (!con) + ast_log(LOG_ERROR, "Parking dial context '%s' does not exist and unable to create\n", parking_con_dial); + } + if (con) { + char returnexten[AST_MAX_EXTENSION]; + snprintf(returnexten, sizeof(returnexten), "%s||t", peername); + ast_add_extension2(con, 1, peername, 1, NULL, NULL, "Dial", strdup(returnexten), ast_free, registrar); } + set_c_e_p(chan, parking_con_dial, peername, 1); + } else { + /* They've been waiting too long, send them back to where they came. Theoretically they + should have their original extensions and such, but we copy to be on the safe side */ + set_c_e_p(chan, pu->context, pu->exten, pu->priority); + } - post_manager_event("ParkedCallTimeOut", pu->parkingexten, chan); + post_manager_event("ParkedCallTimeOut", pu); - if (option_verbose > 1) - ast_verbose(VERBOSE_PREFIX_2 "Timeout for %s parked on %d. Returning to %s,%s,%d\n", chan->name, pu->parkingnum, chan->context, chan->exten, chan->priority); - /* Start up the PBX, or hang them up */ - if (ast_pbx_start(chan)) { - ast_log(LOG_WARNING, "Unable to restart the PBX for user on '%s', hanging them up...\n", chan->name); - ast_hangup(chan); - } - /* And take them out of the parking lot */ - if (pl) - pl->next = pu->next; + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Timeout for %s parked on %d (%s). Returning to %s,%s,%d\n", pu->chan->name, pu->parkingnum, pu->parkinglot->name, pu->chan->context, pu->chan->exten, pu->chan->priority); + /* Start up the PBX, or hang them up */ + if (ast_pbx_start(chan)) { + ast_log(LOG_WARNING, "Unable to restart the PBX for user on '%s', hanging them up...\n", pu->chan->name); + ast_hangup(chan); + } + /* And take them out of the parking lot */ + if (pl) + pl->next = pu->next; + else + curlot->occupiedlots = pu->next; + pt = pu; + pu = pu->next; + con = ast_context_find(pt->parkinglot->parking_con); + if (con) { + if (ast_context_remove_extension2(con, pt->parkingexten, 1, NULL)) + ast_log(LOG_WARNING, "Whoa, failed to remove the parking extension!\n"); else - parkinglot = pu->next; - pt = pu; - pu = pu->next; - con = ast_context_find(parking_con); - if (con) { - if (ast_context_remove_extension2(con, pt->parkingexten, 1, NULL)) - ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n"); - else - notify_metermaids(pt->parkingexten, parking_con); - } else - ast_log(LOG_WARNING, "Whoa, no parking context?\n"); - free(pt); - } else { /* still within parking time, process descriptors */ - for (x = 0; x < AST_MAX_FDS; x++) { - struct ast_frame *f; + notify_metermaids(pt->parkingexten, curlot->parking_con); + } else + ast_log(LOG_WARNING, "Whoa, no parking context?\n"); + free(pt); + } else { /* still within parking time, process descriptors */ + for (x = 0; x < AST_MAX_FDS; x++) { + struct ast_frame *f; - if (chan->fds[x] == -1 || (!FD_ISSET(chan->fds[x], &rfds) && !FD_ISSET(chan->fds[x], &efds))) - continue; /* nothing on this descriptor */ + if (chan->fds[x] == -1 || (!FD_ISSET(chan->fds[x], rfds) && !FD_ISSET(chan->fds[x], efds))) + continue; /* nothing on this descriptor */ - if (FD_ISSET(chan->fds[x], &efds)) - ast_set_flag(chan, AST_FLAG_EXCEPTION); - else - ast_clear_flag(chan, AST_FLAG_EXCEPTION); - chan->fdno = x; + if (FD_ISSET(chan->fds[x], efds)) + ast_set_flag(chan, AST_FLAG_EXCEPTION); + else + ast_clear_flag(chan, AST_FLAG_EXCEPTION); + chan->fdno = x; - /* See if they need servicing */ - f = ast_read(chan); - if (!f || (f->frametype == AST_FRAME_CONTROL && f->subclass == AST_CONTROL_HANGUP)) { - if (f) - ast_frfree(f); - post_manager_event("ParkedCallGiveUp", pu->parkingexten, chan); + /* See if they need servicing */ + f = ast_read(pu->chan); - /* There's a problem, hang them up*/ - if (option_verbose > 1) - ast_verbose(VERBOSE_PREFIX_2 "%s got tired of being parked\n", chan->name); - ast_hangup(chan); - /* And take them out of the parking lot */ - if (pl) - pl->next = pu->next; - else - parkinglot = pu->next; - pt = pu; - pu = pu->next; - con = ast_context_find(parking_con); - if (con) { - if (ast_context_remove_extension2(con, pt->parkingexten, 1, NULL)) - ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n"); - else - notify_metermaids(pt->parkingexten, parking_con); - } else - ast_log(LOG_WARNING, "Whoa, no parking context?\n"); - free(pt); - break; - } else { - /*! \todo XXX Maybe we could do something with packets, like dial "0" for operator or something XXX */ + /* Hangup? */ + if (!f || (f->frametype == AST_FRAME_CONTROL && f->subclass == AST_CONTROL_HANGUP)) { + if (f) ast_frfree(f); - if (pu->moh_trys < 3 && !chan->generatordata) { - if (option_debug) - ast_log(LOG_DEBUG, "MOH on parked call stopped by outside source. Restarting.\n"); - ast_indicate_data(pu->chan, AST_CONTROL_HOLD, - S_OR(parkmohclass, NULL), - !ast_strlen_zero(parkmohclass) ? strlen(parkmohclass) + 1 : 0); - pu->moh_trys++; - } - goto std; /*! \todo XXX Ick: jumping into an else statement??? XXX */ + post_manager_event("ParkedCallGiveUp", pu); + + /* There's a problem, hang them up*/ + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "%s got tired of being parked\n", chan->name); + ast_hangup(chan); + /* And take them out of the parking lot */ + if (pl) + pl->next = pu->next; + else + curlot->occupiedlots = pu->next; + pt = pu; + pu = pu->next; + con = ast_context_find(curlot->parking_con); + if (con) { + if (ast_context_remove_extension2(con, pt->parkingexten, 1, NULL)) + ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n"); + else + notify_metermaids(pt->parkingexten, curlot->parking_con); + } else + ast_log(LOG_WARNING, "Whoa, no parking context for parking lot %s?\n", curlot->name); + free(pt); + break; + } else { + /*! \todo XXX Maybe we could do something with packets, like dial "0" for operator or something XXX */ + ast_frfree(f); + if (pu->moh_trys < 3 && !chan->generatordata) { + if (option_debug) + ast_log(LOG_DEBUG, "MOH on parked call stopped by outside source. Restarting on channel %s.\n", chan->name); + ast_indicate_data(chan, AST_CONTROL_HOLD, + S_OR(curlot->parkmohclass, NULL), + !ast_strlen_zero(curlot->parkmohclass) ? strlen(curlot->parkmohclass) + 1 : 0); + pu->moh_trys++; } + goto std; /*! \todo XXX Ick: jumping into an else statement??? XXX */ + } - } /* end for */ - if (x >= AST_MAX_FDS) { -std: for (x=0; xfds[x] > -1) { - FD_SET(chan->fds[x], &nrfds); - FD_SET(chan->fds[x], &nefds); - if (chan->fds[x] > max) - max = chan->fds[x]; - } + } /* End for */ + if (x >= AST_MAX_FDS) { +std: for (x=0; xfds[x] > -1) { + FD_SET(chan->fds[x], nrfds); + FD_SET(chan->fds[x], nefds); + if (chan->fds[x] > *max) + *max = chan->fds[x]; } - /* Keep track of our shortest wait */ - if (tms < ms || ms < 0) - ms = tms; - pl = pu; - pu = pu->next; } + /* Keep track of our shortest wait */ + if (tms < *ms || *ms < 0) + *ms = tms; + pl = pu; + pu = pu->next; } - } /* end while */ - ast_mutex_unlock(&parking_lock); + } + } /* end while */ + + return res; +} + +/*! \brief Take care of parked calls and unpark them if needed */ +static void *do_parking_thread(void *ignore) +{ + fd_set rfds, efds; /* results from previous select, to be preserved across loops. */ + fd_set nrfds, nefds; /* args for the next select */ + FD_ZERO(&rfds); + FD_ZERO(&efds); + + for (;;) { + int res = 0; + int ms = -1; /* select timeout, uninitialized */ + int max = -1; /* max fd, none there yet */ + FD_ZERO(&nrfds); + FD_ZERO(&nefds); + + /* We need to do this for every parking lot */ + ASTOBJ_CONTAINER_TRAVERSE(&parkinglots, 1, do { + parkinglot_addref(iterator); + ASTOBJ_WRLOCK(iterator); + res = manage_parkinglot(iterator, &rfds, &efds, &nrfds, &nefds, &ms, &max); + ASTOBJ_UNLOCK(iterator); + parkinglot_unref(iterator); + } while (0) ); rfds = nrfds; efds = nefds; { @@ -1812,6 +2047,22 @@ return NULL; /* Never reached */ } +/* Find parkinglot by name */ +static struct ast_parkinglot *find_parkinglot(const char *parkinglotname) +{ + struct ast_parkinglot *parkinglot = NULL; + + if (ast_strlen_zero(parkinglotname)) + return NULL; + + parkinglot = ASTOBJ_CONTAINER_FIND(&parkinglots, parkinglotname); + + if (parkinglot && option_debug) + ast_log(LOG_DEBUG, "Found Parkinglot: %s\n", parkinglot->name); + + return parkinglot; +} + /*! \brief Park a call */ static int park_call_exec(struct ast_channel *chan, void *data) { @@ -1822,11 +2073,15 @@ char orig_exten[AST_MAX_EXTENSION]; int orig_priority = chan->priority; - /* Data is unused at the moment but could contain a parking - lot context eventually */ + /* Data contains the name of the parking lot */ + char *parkinglotname = data; int res = 0; struct ast_module_user *u; + if (option_debug) + ast_log(LOG_DEBUG, "Park '%s', parkinglot name: '%s'.\n", + orig_chan_name, parkinglotname ? parkinglotname : ""); + u = ast_module_user_add(chan); ast_copy_string(orig_exten, chan->exten, sizeof(orig_exten)); @@ -1843,7 +2098,7 @@ res = ast_safe_sleep(chan, 1000); /* Park the call */ if (!res) { - res = park_call_full(chan, chan, 0, NULL, orig_chan_name); + res = park_call_full(chan, NULL, 0, NULL, orig_chan_name, parkinglotname, NULL); /* Continue on in the dialplan */ if (res == 1) { ast_copy_string(chan->exten, orig_exten, sizeof(chan->exten)); @@ -1866,6 +2121,9 @@ struct ast_channel *peer=NULL; struct parkeduser *pu, *pl=NULL; struct ast_context *con; + struct ast_parkinglot *parkinglot = NULL; + char *exten; + char *parkinglotname; int park; struct ast_bridge_config config; @@ -1877,39 +2135,51 @@ u = ast_module_user_add(chan); - park = atoi((char *)data); - ast_mutex_lock(&parking_lock); - pu = parkinglot; + /* data should contain something like: @ */ + parkinglotname = ast_strdupa(data); + exten = strsep(&parkinglotname, "@"); + park = atoi((char *)exten); + + if (parkinglotname) + parkinglot = find_parkinglot(parkinglotname); + if (!parkinglot) { + parkinglot = default_parkinglot; + parkinglot_addref(parkinglot); + } + + ASTOBJ_WRLOCK(parkinglot); + pu = parkinglot->occupiedlots; while(pu) { if (pu->parkingnum == park) { if (pl) pl->next = pu->next; else - parkinglot = pu->next; + parkinglot->occupiedlots = pu->next; break; } pl = pu; pu = pu->next; } - ast_mutex_unlock(&parking_lock); if (pu) { peer = pu->chan; - con = ast_context_find(parking_con); + con = ast_context_find(pu->parkinglot->parking_con); if (con) { if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL)) ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n"); else - notify_metermaids(pu->parkingexten, parking_con); + notify_metermaids(pu->parkingexten, parkinglot->parking_con); } else ast_log(LOG_WARNING, "Whoa, no parking context?\n"); manager_event(EVENT_FLAG_CALL, "UnParkedCall", "Exten: %s\r\n" "Channel: %s\r\n" + "Parkinglot: %s\r\n" + "Context: %s\r\n" "From: %s\r\n" "CallerID: %s\r\n" "CallerIDName: %s\r\n", - pu->parkingexten, pu->chan->name, chan->name, + pu->parkingexten, pu->chan->name, pu->parkinglot->name, pu->parkinglot->parking_con, chan->name, S_OR(pu->chan->cid.cid_num, ""), S_OR(pu->chan->cid.cid_name, "") ); @@ -1922,17 +2192,17 @@ if (peer) { /* Play a courtesy to the source(s) configured to prefix the bridge connecting */ - - if (!ast_strlen_zero(courtesytone)) { + + if (!ast_strlen_zero(parkinglot->courtesytone)) { int error = 0; ast_indicate(peer, AST_CONTROL_UNHOLD); - if (parkedplay == 0) { - error = ast_stream_and_wait(chan, courtesytone, chan->language, ""); - } else if (parkedplay == 1) { - error = ast_stream_and_wait(peer, courtesytone, chan->language, ""); - } else if (parkedplay == 2) { - if (!ast_streamfile(chan, courtesytone, chan->language) && - !ast_streamfile(peer, courtesytone, chan->language)) { + if (parkinglot->parkedplay == 0) { + error = ast_stream_and_wait(chan, parkinglot->courtesytone, chan->language, ""); + } else if (parkinglot->parkedplay == 1) { + error = ast_stream_and_wait(peer, parkinglot->courtesytone, chan->language, ""); + } else if (parkinglot->parkedplay == 2) { + if (!ast_streamfile(chan, parkinglot->courtesytone, chan->language) && + !ast_streamfile(peer, parkinglot->courtesytone, chan->language)) { /*! \todo XXX we would like to wait on both! */ res = ast_waitstream(chan, ""); if (res >= 0) @@ -1943,6 +2213,8 @@ } if (error) { ast_log(LOG_WARNING, "Failed to play courtesy tone!\n"); + ASTOBJ_UNLOCK(parkinglot); + parkinglot_unref(parkinglot); ast_hangup(peer); ast_module_user_remove(u); return -1; @@ -1953,6 +2225,8 @@ res = ast_channel_make_compatible(chan, peer); if (res < 0) { ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", chan->name, peer->name); + ASTOBJ_UNLOCK(parkinglot); + parkinglot_unref(parkinglot); ast_hangup(peer); ast_module_user_remove(u); return -1; @@ -1960,13 +2234,17 @@ /* This runs sorta backwards, since we give the incoming channel control, as if it were the person called. */ if (option_verbose > 2) - ast_verbose(VERBOSE_PREFIX_3 "Channel %s connected to parked call %d\n", chan->name, park); + ast_verbose(VERBOSE_PREFIX_3 "Channel %s connected to parked call %d in lot %s\n", chan->name, park, parkinglot->name); + + ASTOBJ_UNLOCK(parkinglot); + parkinglot_unref(parkinglot); pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", peer->name); ast_cdr_setdestchan(chan->cdr, peer->name); memset(&config, 0, sizeof(struct ast_bridge_config)); ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT); ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT); + res = ast_bridge_call(chan, peer, &config); pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", peer->name); @@ -2020,9 +2298,16 @@ } ast_cli(fd, "\nCall parking\n"); ast_cli(fd, "------------\n"); - ast_cli(fd,"%-20s: %s\n", "Parking extension", parking_ext); - ast_cli(fd,"%-20s: %s\n", "Parking context", parking_con); - ast_cli(fd,"%-20s: %d-%d\n", "Parked call extensions", parking_start, parking_stop); + + ASTOBJ_CONTAINER_TRAVERSE(&parkinglots, 1, do { + ASTOBJ_RDLOCK(iterator); + ast_cli(fd, "%-24s : %s\n", "Parking lot", iterator->name); + ast_cli(fd, " %-24s: %s\n", "Parking extension", iterator->parking_ext); + ast_cli(fd, " %-24s: %s\n", "Parking context", iterator->parking_con); + ast_cli(fd, " %-24s: %d-%d\n", "Parked call extensions", iterator->parking_start, iterator->parking_stop); + ASTOBJ_UNLOCK(iterator); + + } while (0) ); ast_cli(fd,"\n"); return RESULT_SUCCESS; @@ -2040,17 +2325,25 @@ ast_cli(fd, "%4s %25s (%-15s %-12s %-4s) %-6s \n", "Num", "Channel" , "Context", "Extension", "Pri", "Timeout"); - ast_mutex_lock(&parking_lock); - - for (cur = parkinglot; cur; cur = cur->next) { - ast_cli(fd, "%-10.10s %25s (%-15s %-12s %-4d) %6lds\n" - ,cur->parkingexten, cur->chan->name, cur->context, cur->exten - ,cur->priority, cur->start.tv_sec + (cur->parkingtime/1000) - time(NULL)); - - numparked++; - } - ast_mutex_unlock(&parking_lock); - ast_cli(fd, "%d parked call%s.\n", numparked, (numparked != 1) ? "s" : ""); + ASTOBJ_CONTAINER_TRAVERSE(&parkinglots, 1, do { + int lotparked = 0; + ASTOBJ_RDLOCK(iterator); + cur = iterator->occupiedlots; + if (cur) + ast_cli(fd, "*** Parking lot: %s\n", iterator->name); + while(cur) { + ast_cli(fd, "%-10.10s %25s (%-15s %-12s %-4d) %6lds\n" + ,cur->parkingexten, cur->chan->name, cur->context, cur->exten + ,cur->priority, cur->start.tv_sec + (cur->parkingtime/1000) - time(NULL)); + cur = cur->next; + lotparked++; + } + if (lotparked) + ast_cli(fd, " %d parked call%s in parking lot %s\n", lotparked, (lotparked != 1) ? "s" : "", iterator->name); + numparked += lotparked; + ASTOBJ_UNLOCK(iterator); + } while (0) ); + ast_cli(fd, "---\n%d parked call%s in total.\n", numparked, (numparked != 1) ? "s" : ""); return RESULT_SUCCESS; @@ -2060,6 +2353,43 @@ "Usage: show parkedcalls\n" " Lists currently parked calls.\n"; +static int handle_parkinglots(int fd, int argc, char *argv[]) +{ + int count = 0; + + ast_cli(fd, "Parking lots:\n"); + + ASTOBJ_CONTAINER_TRAVERSE(&parkinglots, 1, do { + ASTOBJ_RDLOCK(iterator); + ast_cli(fd, "Parking lot: \"%s\"\n", iterator->name); + ast_cli(fd, "----------------------------------\n"); + ast_cli(fd, " Extension: %s\n", iterator->parking_ext); + ast_cli(fd, " Context: %s\n", iterator->parking_con); + ast_cli(fd, " Park position: %d-%d\n", iterator->parking_start, iterator->parking_stop); + ast_cli(fd, " Findslot: %s\n", iterator->parkfindnext ? "next" : "-"); + ast_cli(fd, " Parking_offset: %d\n", iterator->parking_offset); + ast_cli(fd, " Occupiedlots: %s\n", iterator->occupiedlots ? "yes" : "no"); + ast_cli(fd, " Parkedmusicclass: %s\n", iterator->parkmohclass); + ast_cli(fd, " Parkedplay: %s\n", (iterator->parkedplay == 2) ? "both" : ((iterator->parkedplay == 1) ? "parked" : "caller")); + ast_cli(fd, " Courtesytone: %s\n", iterator->courtesytone); + ast_cli(fd, " Parkingtime (sec): %d\n\n", iterator->parkingtime/1000); +#if 0 + ast_cli(fd, " Refcount: %d\n\n", iterator->refcount); /* for debug purpose */ +#endif + count++; + ASTOBJ_UNLOCK(iterator); + } while (0) ); + if (count) + ast_cli(fd, " %d parking lot%s.\n", count, (count != 1) ? "s" : ""); + else + ast_cli(fd, " No parking lots configured.\n"); + return RESULT_SUCCESS; +} + +static char showparkinglots_help[] = +"Usage: show parkinglots\n" +" Lists all parking lots.\n"; + static struct ast_cli_entry cli_show_features_deprecated = { { "show", "features", NULL }, handle_showfeatures, NULL, @@ -2073,66 +2403,240 @@ { { "show", "parkedcalls", NULL }, handle_parkedcalls, "Lists parked calls", showparked_help }, + + { { "show", "parkinglots", NULL }, + handle_parkinglots, "Lists parkinglots", + showparkinglots_help }, }; +static char mandescr_parking_status[] = +"Description: List parked calls.\n" +"Variables: (Names marked with * are required)\n" +" Parkinglot: Only show parked calls of this parking lot.\n"; + /*! \brief Dump lot status */ static int manager_parking_status( struct mansession *s, const struct message *m) { struct parkeduser *cur; + struct ast_parkinglot *parkinglot = NULL; const char *id = astman_get_header(m, "ActionID"); + const char *parkinglotname = astman_get_header(m, "Parkinglot"); char idText[256] = ""; + if (!ast_strlen_zero(parkinglotname)) { + parkinglot = find_parkinglot(parkinglotname); + if (parkinglot) { + parkinglot_unref(parkinglot); + } else { + snprintf(idText, sizeof(idText), "Parkinglot does not exist: %s", parkinglotname); + astman_send_error(s, m, idText); + return 0; + } + } + if (!ast_strlen_zero(id)) snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); astman_send_ack(s, m, "Parked calls will follow"); - ast_mutex_lock(&parking_lock); + ASTOBJ_CONTAINER_TRAVERSE(&parkinglots, 1, do { + ASTOBJ_RDLOCK(iterator); - for (cur = parkinglot; cur; cur = cur->next) { - astman_append(s, "Event: ParkedCall\r\n" - "Exten: %d\r\n" - "Channel: %s\r\n" - "From: %s\r\n" - "Timeout: %ld\r\n" - "CallerID: %s\r\n" - "CallerIDName: %s\r\n" - "%s" - "\r\n", - cur->parkingnum, cur->chan->name, cur->peername, - (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - (long) time(NULL), - S_OR(cur->chan->cid.cid_num, ""), /* XXX in other places it is */ - S_OR(cur->chan->cid.cid_name, ""), - idText); - } + /* if Parkinglot is specified only show parked calls of this parking lot */ + if (!parkinglot || (!strcmp(parkinglot->name, iterator->name))) { + cur=iterator->occupiedlots; + while(cur) { + astman_append(s, "Event: ParkedCall\r\n" + "Exten: %d\r\n" + "Channel: %s\r\n" + "From: %s\r\n" + "Parkinglot: %s\r\n" + "Context: %s\r\n" + "Timeout: %ld\r\n" + "CallerID: %s\r\n" + "CallerIDName: %s\r\n" + "%s" + "\r\n", + cur->parkingnum, cur->chan->name, cur->peername, + iterator->name, iterator->parking_con, + (long)cur->start.tv_sec + (long)(cur->parkingtime/1000) - (long)time(NULL), + S_OR(cur->chan->cid.cid_num, ""), /* XXX in other places it is */ + S_OR(cur->chan->cid.cid_name, ""), + idText); + + cur = cur->next; + } + } + + ASTOBJ_UNLOCK(iterator); + } while (0) ); astman_append(s, "Event: ParkedCallsComplete\r\n" "%s" "\r\n",idText); - ast_mutex_unlock(&parking_lock); - return RESULT_SUCCESS; } + +struct bridge_dual { + struct ast_channel *chan1; + struct ast_channel *chan2; +}; + +static void *ast_masq_bridge_call_thread(void *stuff) +{ + struct ast_channel *chan1, *chan2; + struct bridge_dual *d; + struct ast_frame *f; + int res = 0; + struct ast_module_user *u; + struct ast_bridge_config config; + + d = stuff; + chan1 = d->chan1; + chan2 = d->chan2; + free(d); + + if (!chan1 || !chan2) { + ast_log(LOG_ERROR, "Missing channels for bridge! Ch1: %s, Ch2: %s\n", chan1 ? "" : "", chan2 ? "" : "" ); + return NULL; + } + f = ast_read(chan1); + if (f) + ast_frfree(f); + + u = ast_module_user_add(chan1); + + res = ast_channel_make_compatible(chan1, chan2); + if (res < 0) { + ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge.\n", chan1->name, chan2->name); + ast_module_user_remove(u); + return NULL; + } + + memset(&config, 0, sizeof(struct ast_bridge_config)); + ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT); + ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT); + + res = ast_bridge_call(chan2, chan1, &config); + + /* Simulate the PBX hanging up */ + if (res == AST_PBX_NO_HANGUP_PEER) { /* AST_PBX_NO_HANGUP_PEER 11 */ + ast_hangup(chan2); + } else if (res == AST_PBX_KEEPALIVE) { /* AST_PBX_KEEPALIVE 10 */ + ast_hangup(chan1); + } else { + ast_hangup(chan1); + ast_hangup(chan2); + } + + ast_module_user_remove(u); + + return NULL; +} + +/*! \brief Bridge a call via two masqueraded channels in a session thread + * \param chan1 is the first leg to be bridged + \param chan2 is the second leg to be bridged + Masquerade both channels chan1 and chan2 into + new, empty channels. Create a session thread + and bridge both channels with ast_bridge_call. + */ +static int ast_masq_bridge_call(struct ast_channel *chan1, struct ast_channel *chan2) +{ + struct bridge_dual *d; + struct ast_channel *chan1m, *chan2m; + pthread_t th; + + if (option_debug) + ast_log(LOG_DEBUG, "Bridge %s with %s.\n", chan1->name, chan2->name); + + chan1m = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, chan2->accountcode, chan1->exten, chan1->context, chan1->amaflags, "Redirected/%s", chan1->name); + chan2m = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, chan2->accountcode, chan2->exten, chan2->context, chan2->amaflags, "Redirected/%s",chan2->name); + if (chan2m && chan1m) { + /* Make formats okay */ + chan1m->readformat = chan1->readformat; + chan1m->writeformat = chan1->writeformat; + ast_channel_masquerade(chan1m, chan1); + + /* Setup the extensions and such */ + set_c_e_p(chan1m, chan1->context, chan1->exten, chan1->priority); + + /* We make a clone of the peer channel too, so we can play + back the announcement */ + /* Make formats okay */ + chan2m->readformat = chan2->readformat; + chan2m->writeformat = chan2->writeformat; + ast_channel_masquerade(chan2m, chan2); + + /* Setup the extensions and such */ + set_c_e_p(chan2m, chan2->context, chan2->exten, chan2->priority); + + if (ast_do_masquerade(chan2m)) { + ast_log(LOG_WARNING, "Masquerade failed :(\n"); + ast_hangup(chan2m); + return -1; + } + } else { + if (chan1m) + ast_hangup(chan1m); + if (chan2m) + ast_hangup(chan2m); + return -1; + } + if ((d = ast_calloc(1, sizeof(*d)))) { + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + d->chan1 = chan1m; + d->chan2 = chan2m; + if (!ast_pthread_create_background(&th, &attr, ast_masq_bridge_call_thread, d)) { + pthread_attr_destroy(&attr); + return 0; + } + pthread_attr_destroy(&attr); + free(d); + } + return -1; +} + static char mandescr_park[] = "Description: Park a channel.\n" "Variables: (Names marked with * are required)\n" " *Channel: Channel name to park\n" " *Channel2: Channel to announce park info to (and return to if timeout)\n" -" Timeout: Number of milliseconds to wait before callback.\n"; +" Timeout: Number of milliseconds to wait before callback\n" +" Parkinglot: Parking lot to park the call into\n" +" Position: Park position within the parking lot to park the call to\n" +" Exten: Extension within the dialplan to handle the 2nd leg (Channel2)\n" +" Context: Context within the dialplan to handle the 2nd leg (Channel2)\n" +" RetrieveChannel: Connect 2nd leg (Channel2) with RetrieveChannel\n" +" Variable: Channel variable to set for 2nd leg (Channel2).\n"; static int manager_park(struct mansession *s, const struct message *m) { const char *channel = astman_get_header(m, "Channel"); const char *channel2 = astman_get_header(m, "Channel2"); const char *timeout = astman_get_header(m, "Timeout"); + const char *parkinglotname = astman_get_header(m, "Parkinglot"); + const char *position = astman_get_header(m, "Position"); + const char *ch2Exten = astman_get_header(m, "Exten"); + const char *ch2Context = astman_get_header(m, "Context"); + const char *retrieveChannel = astman_get_header(m, "RetrieveChannel"); + struct ast_variable *vars = astman_get_variables(m, "Variable"); + char buf[BUFSIZ]; int to = 0; int res = 0; int parkExt = 0; + int ch2Transfered = 0; struct ast_channel *ch1, *ch2; + struct ast_channel *chRetrieve = NULL; + struct ast_parkinglot *parkinglot = NULL; if (ast_strlen_zero(channel)) { astman_send_error(s, m, "Channel not specified"); @@ -2163,20 +2667,357 @@ sscanf(timeout, "%d", &to); } - res = ast_masq_park_call(ch1, ch2, to, &parkExt); + if (!ast_strlen_zero(parkinglotname)) { + parkinglot = find_parkinglot(parkinglotname); + if (parkinglot) { +#if 0 +/* in comment because we accept all parkinglots, no additional check */ + struct ast_context *con; + static struct ast_exten *e; + + /* Get parkinglot name from dialplan */ + con = ast_context_find(ch1->context); + e = find_matching_application(con, parkinglot->parking_ext, parkcall); + if (!(e) || (strcasecmp(parkinglotname, ast_get_extension_app_data(e)))) { + snprintf(buf, sizeof(buf), "Context '%s' does not include parkinglot '%s'.", + ch1->context, parkinglotname); + astman_send_error(s, m, buf); + parkinglot_unref(parkinglot); + ast_channel_unlock(ch1); + ast_channel_unlock(ch2); + return 0; + } +#endif + } else { + snprintf(buf, sizeof(buf), "Parkinglot does not exist: %s", parkinglotname); + astman_send_error(s, m, buf); + ast_channel_unlock(ch1); + ast_channel_unlock(ch2); + return 0; + } + } + + if (!ast_strlen_zero(position)) { + if (parkinglot) { + struct parkeduser *pu; + int park = atoi(position); + if ((park < parkinglot->parking_start) || (park > parkinglot->parking_stop)) { + snprintf(buf, sizeof(buf), "Position %d out of valid range %d-%d.", + park, parkinglot->parking_start, parkinglot->parking_stop); + astman_send_error(s, m, buf); + parkinglot_unref(parkinglot); + ast_channel_unlock(ch1); + ast_channel_unlock(ch2); + return 0; + } + pu = parkinglot->occupiedlots; + while(pu) { + if (pu->parkingnum == park) { + snprintf(buf, sizeof(buf), "Requested parking position already exists: %d@%s.", + park, parkinglot->parking_con); + astman_send_error(s, m, buf); + parkinglot_unref(parkinglot); + ast_channel_unlock(ch1); + ast_channel_unlock(ch2); + return 0; + } + pu = pu->next; + } + } else { + snprintf(buf, sizeof(buf), "Position %s specified without specifying the parkinglot.", position); + astman_send_error(s, m, buf); + ast_channel_unlock(ch1); + ast_channel_unlock(ch2); + return 0; + } + } + + if (!ast_strlen_zero(retrieveChannel)) { + if (!ast_strlen_zero(ch2Context) || !ast_strlen_zero(ch2Exten)) { + astman_send_error(s, m, "Configure either RetrieveChannel or Context and Exten but not both"); + if (parkinglot) + parkinglot_unref(parkinglot); + ast_channel_unlock(ch1); + ast_channel_unlock(ch2); + return 0; + } else { + chRetrieve = ast_get_channel_by_name_locked(retrieveChannel); + if (!chRetrieve) { + snprintf(buf, sizeof(buf), "RetrieveChannel does not exist: %s", retrieveChannel); + astman_send_error(s, m, buf); + if (parkinglot) + parkinglot_unref(parkinglot); + ast_channel_unlock(ch1); + ast_channel_unlock(ch2); + return 0; + } + ast_channel_unlock(chRetrieve); + /* retrieve channel found, valid configuration */ + } + } else { + if (!ast_strlen_zero(ch2Context)) { + if (!ast_context_find(ch2Context)) { + snprintf(buf, sizeof(buf), "Context does not exist: %s", ch2Context); + astman_send_error(s, m, buf); + if (parkinglot) + parkinglot_unref(parkinglot); + ast_channel_unlock(ch1); + ast_channel_unlock(ch2); + return 0; + } else { + if (!ast_strlen_zero(ch2Exten)) { + if (!ast_exists_extension(NULL, ch2Context, ch2Exten, 1, NULL)) { + snprintf(buf, sizeof(buf), "Extension does not exist: %s@%s", ch2Exten, ch2Context); + astman_send_error(s, m, buf); + if (parkinglot) + parkinglot_unref(parkinglot); + ast_channel_unlock(ch1); + ast_channel_unlock(ch2); + return 0; + } else { + /* context and extension found, both are valid */ + } + } else { + snprintf(buf, sizeof(buf), "Context specified but missing Extension, Context: %s", ch2Context); + astman_send_error(s, m, buf); + if (parkinglot) + parkinglot_unref(parkinglot); + ast_channel_unlock(ch1); + ast_channel_unlock(ch2); + return 0; + } + } + } else { + if (!ast_strlen_zero(ch2Exten)) { + snprintf(buf, sizeof(buf), "Extension specified but missing Context, Extension: %s", ch2Exten); + astman_send_error(s, m, buf); + if (parkinglot) + parkinglot_unref(parkinglot); + ast_channel_unlock(ch1); + ast_channel_unlock(ch2); + return 0; + } else { + /* RetrieveChannel not specified, Exten and + Context are also not specified. */ + } + } + } + + res = masq_park_call_full(ch1, ch2, to, &parkExt, parkinglotname, position); if (!res) { - ast_softhangup(ch2, AST_SOFTHANGUP_EXPLICIT); + if ((vars) && ((!ast_strlen_zero(ch2Exten) && !ast_strlen_zero(ch2Context)) || (chRetrieve))) + ast_set_variables(ch2, vars); + /* Transfer second leg of call */ + if (!ast_strlen_zero(ch2Exten) && !ast_strlen_zero(ch2Context)) { + if (ast_async_goto(ch2, ch2Context, ch2Exten, 1)) { + ast_log(LOG_WARNING, "Async goto of '%s' to '%s@%s' failed.\n", ch2->name, ch2Exten, ch2Context); + } + else { + ast_log(LOG_DEBUG, "Async goto of '%s' to '%s@%s' started.\n", ch2->name, ch2Exten, ch2Context); + ch2Transfered = 1; + } + } else if (chRetrieve) { + /* Connect 2nd leg (Channel2) with RetrieveChannel. */ + if (option_debug) + ast_log(LOG_DEBUG, "Bridge %s with %s.\n", ch2->name, chRetrieve->name); + ast_masq_bridge_call(ch2, chRetrieve); + ch2Transfered = 1; + } + + if (!ch2Transfered) { + /* 2nd leg not transfered, hangup */ + ast_softhangup(ch2, AST_SOFTHANGUP_EXPLICIT); + } astman_send_ack(s, m, "Park successful"); } else { astman_send_error(s, m, "Park failure"); } + if (parkinglot) + parkinglot_unref(parkinglot); ast_channel_unlock(ch1); ast_channel_unlock(ch2); return 0; } +static char mandescr_redirect[] = +"Description: Redirect (transfer) a call.\n" +"Variables: (Names marked with * are required)\n" +" *Channel: Channel to redirect\n" +" ExtraChannel: Second call leg to transfer (optional)\n" +" *Exten: Extension to transfer to\n" +" *Context: Context to transfer to\n" +" *Priority: Priority to transfer to\n" +" Variable: Channel variable to set (Channel)\n" +" ExtraExten: Extension to transfer second call leg to\n" +" ExtraContext: Context to transfer second call leg to\n" +" ExtraPriority: Priority to transfer second call leg to\n" +" ExtraVariable: Channel variable to set for 2nd leg (ExtraChannel)\n" +" RetrieveChannel: Connect second leg with RetrieveChannel\n" +" ActionID: Optional Action id for message matching.\n"; + +/*! \brief action_redirect: The redirect manager command */ +static int action_redirect(struct mansession *s, const struct message *m) +{ + const char *name = astman_get_header(m, "Channel"); + const char *name2 = astman_get_header(m, "ExtraChannel"); + const char *exten = astman_get_header(m, "Exten"); + const char *context = astman_get_header(m, "Context"); + const char *priority = astman_get_header(m, "Priority"); + struct ast_variable *vars = astman_get_variables(m, "Variable"); + const char *exten2 = astman_get_header(m, "ExtraExten"); + const char *context2 = astman_get_header(m, "ExtraContext"); + const char *priority2 = astman_get_header(m, "ExtraPriority"); + struct ast_variable *vars2 = astman_get_variables(m, "ExtraVariable"); + const char *retrieveChannel = astman_get_header(m, "RetrieveChannel"); + char buf[BUFSIZ]; + struct ast_channel *chan, *chan2 = NULL; + struct ast_channel *chRetrieve = NULL; + int pi = 0; + int pi2 = 0; + int res; + + if (ast_strlen_zero(name)) { + astman_send_error(s, m, "Channel not specified"); + return 0; + } + if (!ast_strlen_zero(priority) && (sscanf(priority, "%d", &pi) != 1)) { + if ((pi = ast_findlabel_extension(NULL, context, exten, priority, NULL)) < 1) { + astman_send_error(s, m, "Invalid priority\n"); + return 0; + } + } + /* XXX watch out, possible deadlock!!! */ + chan = ast_get_channel_by_name_locked(name); + if (!chan) { + snprintf(buf, sizeof(buf), "Channel does not exist: %s", name); + astman_send_error(s, m, buf); + return 0; + } + if (ast_check_hangup(chan)) { + astman_send_error(s, m, "Redirect failed, channel not up.\n"); + ast_channel_unlock(chan); + return 0; + } + if (!ast_strlen_zero(name2)) + chan2 = ast_get_channel_by_name_locked(name2); + if (chan2 && ast_check_hangup(chan2)) { + astman_send_error(s, m, "Redirect failed, extra channel not up.\n"); + ast_channel_unlock(chan); + ast_channel_unlock(chan2); + return 0; + } + if (!ast_strlen_zero(retrieveChannel)) { + if (!ast_strlen_zero(context) || !ast_strlen_zero(exten) || !ast_strlen_zero(context2) || !ast_strlen_zero(exten2)) { + astman_send_error(s, m, "Parameter RetrieveChannel prohibits parameters Exten/Context"); + ast_channel_unlock(chan); + if (chan2) + ast_channel_unlock(chan2); + return 0; + } else { + chRetrieve = ast_get_channel_by_name_locked(retrieveChannel); + if (!chRetrieve) { + snprintf(buf, sizeof(buf), "RetrieveChannel does not exist: %s", retrieveChannel); + astman_send_error(s, m, buf); + ast_channel_unlock(chan); + if (chan2) + ast_channel_unlock(chan2); + return 0; + } + ast_channel_unlock(chRetrieve); + } + } else { + if (!ast_strlen_zero(context2)) { + if (!ast_context_find(context2)) { + snprintf(buf, sizeof(buf), "ExtraContext does not exist: %s", context2); + astman_send_error(s, m, buf); + ast_channel_unlock(chan); + if (chan2) + ast_channel_unlock(chan2); + return 0; + } else { + if (!ast_strlen_zero(exten2)) { + if (!ast_exists_extension(NULL, context2, exten2, 1, NULL)) { + snprintf(buf, sizeof(buf), "ExtraExtension does not exist: %s@%s", exten2, context2); + astman_send_error(s, m, buf); + ast_channel_unlock(chan); + if (chan2) + ast_channel_unlock(chan2); + return 0; + } else { + /* context2 and extension2 found, + both are valid */ + if (!ast_strlen_zero(priority2) && (sscanf(priority2, "%d", &pi2) != 1)) { + if ((pi2 = ast_findlabel_extension(NULL, context2, exten2, priority2, NULL)) < 1) { + astman_send_error(s, m, "Invalid priority for second leg\n"); + return 0; + } + } + } + } else { + snprintf(buf, sizeof(buf), "ExtraContext specified but missing ExtraExtension, ExtraContext: %s", context2); + astman_send_error(s, m, buf); + ast_channel_unlock(chan); + if (chan2) + ast_channel_unlock(chan2); + return 0; + } + } + } else { + if (!ast_strlen_zero(exten2)) { + snprintf(buf, sizeof(buf), "ExtraExtension specified but missing ExtraContext, ExtraExtension: %s", exten2); + astman_send_error(s, m, buf); + ast_channel_unlock(chan); + if (chan2) + ast_channel_unlock(chan2); + return 0; + } else { + /* RetrieveChannel not specified, ExtraExten and + ExtraContext are also not specified. */ + exten2 = exten; + context2 = context; + pi2 = pi; + } + } + } + if ((vars) && ((!ast_strlen_zero(exten) && !ast_strlen_zero(context)) || (chRetrieve))) + ast_set_variables(chan, vars); + if ((vars2) && (chan2) && ((!ast_strlen_zero(exten2) && !ast_strlen_zero(context2)))) + ast_set_variables(chan2, vars2); + if (chRetrieve) { + /* Connect Channel with RetrieveChannel. */ + if (option_debug) + ast_log(LOG_DEBUG, "Bridge %s with %s.\n", chan->name, chRetrieve->name); + ast_masq_bridge_call(chan, chRetrieve); + } else { + if (option_debug) + ast_log(LOG_DEBUG, "Transfer '%s' to %s@%s, priority %d\n", chan->name, exten, context, pi); + res = ast_async_goto(chan, context, exten, pi); + if (!res) { + if (!ast_strlen_zero(name2)) { + if (chan2) { + if (option_debug) + ast_log(LOG_DEBUG, "Transfer second call leg '%s' to %s@%s, priority %d\n", chan2->name, exten2, context2, pi2); + res = ast_async_goto(chan2, context2, exten2, pi2); + } + else + res = -1; + if (!res) + astman_send_ack(s, m, "Dual Redirect successful"); + else + astman_send_error(s, m, "Secondary redirect failed"); + } else + astman_send_ack(s, m, "Redirect successful"); + } else + astman_send_error(s, m, "Redirect failed"); + } + if (chan) + ast_channel_unlock(chan); + if (chan2) + ast_channel_unlock(chan2); + return 0; +} int ast_pickup_call(struct ast_channel *chan) { @@ -2213,6 +3054,233 @@ return res; } +/*! \brief Unreference parkinglot object. If no more references, + then go ahead and delete it */ +static void parkinglot_unref(struct ast_parkinglot *parkinglot) +{ + if (option_debug > 2) + ast_log(LOG_DEBUG, "Multiparking: %s refcount now %d\n", parkinglot->name, parkinglot->refcount - 1); + ASTOBJ_UNREF(parkinglot, parkinglot_destroy); +} + +static struct ast_parkinglot *parkinglot_addref(struct ast_parkinglot *parkinglot) +{ + if (option_debug > 2) + ast_log(LOG_DEBUG, "Multiparking: %s refcount now %d\n", parkinglot->name, parkinglot->refcount + 1); + return ASTOBJ_REF(parkinglot); +} + +/*! \brief Allocate parking lot structure */ +static struct ast_parkinglot *create_parkinglot(char *name) +{ + struct ast_parkinglot *newlot = (struct ast_parkinglot *) NULL; + + if (!name) + return NULL; + + newlot = ast_calloc(1, sizeof(*newlot)); + if (!newlot) + return NULL; + + ASTOBJ_INIT(newlot); + ast_copy_string(newlot->name, name, sizeof(newlot->name)); + + return newlot; +} + +/*! \brief Destroy a parking lot */ +static void parkinglot_destroy(struct ast_parkinglot *ruin) +{ + struct ast_context *con; + struct parkeduser *pu, *pl; + + con = ast_context_find(ruin->parking_con); + if (con) { + if (ast_context_remove_extension2(con, ruin->parking_ext, 1, NULL)) + ast_log(LOG_WARNING, "Failed to remove extension %s in context %s.\n", ruin->parking_ext, ruin->parking_con); + + /* Cleanup parked users */ + pu = ruin->occupiedlots; + while (pu) { + pl = pu; + pu = pu->next; + if (pl->chan) { + if (ast_context_remove_extension2(con, pl->parkingexten, 1, NULL)) + ast_log(LOG_WARNING, "Failed to remove extension %s in context %s.\n", pl->parkingexten, ruin->parking_con); + ast_hangup(pl->chan); + } + free(pl); + } + + /* Delete context if there are no more entries */ + if (!ast_walk_context_extensions(con, NULL)) + ast_context_destroy(con, NULL); + } + + ASTOBJ_CONTAINER_UNLINK(&parkinglots, ruin); /* Remove from parkinglot list */ + + free(ruin); +} + +/*! \brief Build parkinglot from configuration and chain it in */ +static struct ast_parkinglot *build_parkinglot(char *parkinglotname, struct ast_variable *var, int reload) +{ + struct ast_parkinglot *parkinglot; + struct ast_context *con = NULL; + + struct ast_variable *confvar = var; + int error = 0; + int start = 0, end = 0; + int res = 0; + char old_parking_ext[AST_MAX_EXTENSION]; + char old_parking_con[AST_MAX_EXTENSION] = ""; + + if (reload) { + parkinglot = find_parkinglot(parkinglotname); + if (parkinglot) { + strcpy(old_parking_ext, parkinglot->parking_ext); + strcpy(old_parking_con, parkinglot->parking_con); + } + else { + parkinglot = create_parkinglot(parkinglotname); + if (!parkinglot) { + ast_log(LOG_ERROR, "Reload, configuration of parkinglot %s failed.\n", parkinglotname); + return NULL; + } + reload = 0; + } + } + else { + parkinglot = create_parkinglot(parkinglotname); + if (!parkinglot) { + ast_log(LOG_ERROR, "Configuration of parkinglot %s failed.\n", parkinglotname); + return NULL; + } + } + + /* reload = 1 - refcount should be 2 at this point * + * = 0 - refcount should be 1 at this point */ + + ASTOBJ_WRLOCK(parkinglot); + + if (option_debug) + ast_log(LOG_DEBUG, "Building parking lot %s.\n", parkinglotname); + + /* Initialize parkinglot with default values */ + parkinglot->parking_con[0] = '\0'; /* no default value for parking context, it must be set by configuration */ + strcpy(parkinglot->parking_ext, "700"); + strcpy(parkinglot->parkmohclass, "default"); + parkinglot->courtesytone[0] = '\0'; + parkinglot->parkedplay = 0; + parkinglot->parkaddhints = 0; + parkinglot->adsipark = 0; + parkinglot->parking_start = 701; + parkinglot->parking_stop = 750; + parkinglot->parking_offset = 0; + parkinglot->parkfindnext = 0; + parkinglot->parkingtime = DEFAULT_PARK_TIME; + if (!reload) + parkinglot->occupiedlots = NULL; + + /* Do some config stuff */ + while(confvar) { + if (!strcasecmp(confvar->name, "parkext")) { + ast_copy_string(parkinglot->parking_ext, confvar->value, sizeof(parkinglot->parking_ext)); + } else if (!strcasecmp(confvar->name, "context")) { + ast_copy_string(parkinglot->parking_con, confvar->value, sizeof(parkinglot->parking_con)); + } else if (!strcasecmp(confvar->name, "parkingtime")) { + if ((sscanf(confvar->value, "%d", &parkinglot->parkingtime) != 1) || (parkinglot->parkingtime < 1)) { + ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", confvar->value); + parkinglot->parkingtime = DEFAULT_PARK_TIME; + } else + parkinglot->parkingtime = parkinglot->parkingtime * 1000; + } else if (!strcasecmp(confvar->name, "parkpos")) { + if (sscanf(confvar->value, "%d-%d", &start, &end) != 2) { + ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers at line %d of features.conf\n", confvar->lineno); + error = 1; + } else { + parkinglot->parking_start = start; + parkinglot->parking_stop = end; + } + } else if (!strcasecmp(confvar->name, "findslot")) { + parkinglot->parkfindnext = (!strcasecmp(confvar->value, "next")); + } else if (!strcasecmp(confvar->name, "parkinghints")) { + parkinglot->parkaddhints = ast_true(confvar->value); + } else if (!strcasecmp(confvar->name, "adsipark")) { + parkinglot->adsipark = ast_true(confvar->value); + } else if (!strcasecmp(confvar->name, "courtesytone")) { + ast_copy_string(parkinglot->courtesytone, confvar->value, sizeof(parkinglot->courtesytone)); + } else if (!strcasecmp(confvar->name, "parkedplay")) { + if (!strcasecmp(confvar->value, "both")) + parkinglot->parkedplay = 2; + else if (!strcasecmp(confvar->value, "parked")) + parkinglot->parkedplay = 1; + else + parkinglot->parkedplay = 0; + } else if (!strcasecmp(confvar->name, "parkedmusicclass")) { + ast_copy_string(parkinglot->parkmohclass, confvar->value, sizeof(parkinglot->parkmohclass)); + } + + confvar = confvar->next; + } + + ASTOBJ_UNLOCK(parkinglot); + + /* Check for errors */ + if (ast_strlen_zero(parkinglot->parking_con)) { + ast_log(LOG_WARNING, "Parking lot %s lacks context\n", parkinglotname); + goto failed; + } + if (error) { + ast_log(LOG_WARNING, "Wrong parameter setting for parking lot %s\n", parkinglotname); + goto failed; + } + + /* Remove the old parking extension */ + if (!ast_strlen_zero(old_parking_con) && (con = ast_context_find(old_parking_con))) { + if(ast_context_remove_extension2(con, old_parking_ext, 1, registrar)) + notify_metermaids(old_parking_ext, old_parking_con); + /* Delete context if there are no more entries */ + if (!ast_walk_context_extensions(con, NULL)) + ast_context_destroy(con, NULL); + } + + if (option_debug) + ast_log(LOG_DEBUG, "Add to dialplan: %s@%s, parkinglot: %s.\n", + parkinglot->parking_ext, parkinglot->parking_con, parkinglot->name); + + /* Create context */ + if (!(con = ast_context_find(parkinglot->parking_con)) && !(con = ast_context_create(NULL, parkinglot->parking_con, registrar))) { + ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parkinglot->parking_con); + goto failed; + } + + /* Add entry to dialplan: Park(name>) */ + res = ast_add_extension2(con, 1, parkinglot->parking_ext, 1, NULL, NULL, parkcall, strdup(parkinglot->name), ast_free, registrar); + + if (parkinglot->parkaddhints) + park_add_hints(parkinglot->parking_con, parkinglot->parking_start, parkinglot->parking_stop); + + if (!res) { + notify_metermaids(parkinglot->parking_ext, parkinglot->parking_con); + if (reload) { + parkinglot_unref(parkinglot); + } else { + /* Move it into the list */ + ASTOBJ_CONTAINER_LINK(&parkinglots, parkinglot); + parkinglot_unref(parkinglot); + } + return parkinglot; + } + +failed: + ast_log(LOG_WARNING, "Parking %s not open for business. Configuration error.\n", parkinglot->name); + parkinglot_destroy(parkinglot); + + return NULL; +} + + /*! \brief Add parking hints for all defined parking lots */ static void park_add_hints(char *context, int start, int stop) { @@ -2228,69 +3296,162 @@ } -static int load_config(void) +static void unmark_valid_parkinglots(void) +{ + struct ast_parkinglot *parkinglot; + char *parkinglotname; + char *section; + struct ast_config *cfg = NULL; + + cfg = ast_config_load("features.conf"); + if (!cfg) { + ast_log(LOG_WARNING,"Could not load features.conf\n"); + return; + } + + /* Now, find parking lot definitions */ + section = NULL; + while ((section = ast_category_browse(cfg, section))) { + if (option_debug) + ast_log(LOG_DEBUG, "*** Found section %s\n", section); + + if ( !strncasecmp(section, "featuremap", strlen("featuremap")) || + !strncasecmp(section, "applicationmap", strlen("applicationmap")) ) + continue; + + if (option_debug) + ast_log(LOG_DEBUG, "Found configuration section %s, assume parking lot.\n", section); + + parkinglotname = !strncasecmp(section, "general", strlen("general")) ? "default" : section; + parkinglot = find_parkinglot(parkinglotname); + if (parkinglot) { + ASTOBJ_UNMARK(parkinglot); + parkinglot_unref(parkinglot); + } + } + + ast_config_destroy(cfg); + + return; +} + + +static int load_config(int reload) { int start = 0, end = 0; int res; struct ast_context *con = NULL; struct ast_config *cfg = NULL; struct ast_variable *var = NULL; + char *section; char old_parking_ext[AST_MAX_EXTENSION]; char old_parking_con[AST_MAX_EXTENSION] = ""; + int reloaddpl = reload; - if (!ast_strlen_zero(parking_con)) { - strcpy(old_parking_ext, parking_ext); - strcpy(old_parking_con, parking_con); - } + if (reloaddpl && default_parkinglot) { + strcpy(old_parking_ext, default_parkinglot->parking_ext); + strcpy(old_parking_con, default_parkinglot->parking_con); + } - /* Reset to defaults */ - strcpy(parking_con, "parkedcalls"); + /* Reset general parameters to defaults */ strcpy(parking_con_dial, "park-dial"); - strcpy(parking_ext, "700"); strcpy(pickup_ext, "*8"); - strcpy(parkmohclass, "default"); - courtesytone[0] = '\0'; strcpy(xfersound, "beep"); strcpy(xferfailsound, "pbx-invalid"); - parking_start = 701; - parking_stop = 750; - parkfindnext = 0; - adsipark = 0; - parkaddhints = 0; transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT; featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT; atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER; + if (reloaddpl) { + default_parkinglot = find_parkinglot(DEFAULT_PARKINGLOT); + if (!default_parkinglot) { + default_parkinglot = create_parkinglot(DEFAULT_PARKINGLOT); + if (!default_parkinglot) { + ast_log(LOG_ERROR, "Reload, configuration of default parkinglot failed.\n"); + return -1; + } + reloaddpl = 0; + } + } + else { + default_parkinglot = create_parkinglot(DEFAULT_PARKINGLOT); + if (!default_parkinglot) { + ast_log(LOG_ERROR, "Configuration of default parkinglot failed.\n"); + return -1; + } + } + + /* reloaddpl = 1 - refcount should be 2 at this point * + * = 0 - refcount should be 1 at this point */ + cfg = ast_config_load("features.conf"); if (!cfg) { ast_log(LOG_WARNING,"Could not load features.conf\n"); + parkinglot_unref(default_parkinglot); return AST_MODULE_LOAD_DECLINE; } + + ASTOBJ_WRLOCK(default_parkinglot); + + if (option_debug) + ast_log(LOG_DEBUG, "Building default parking lot.\n"); + + /* Reset values for default parkinglot */ + strcpy(default_parkinglot->parking_con, "parkedcalls"); + strcpy(default_parkinglot->parking_ext, "700"); + strcpy(default_parkinglot->parkmohclass, "default"); + default_parkinglot->courtesytone[0] = '\0'; + default_parkinglot->parkedplay = 0; + default_parkinglot->parkaddhints = 0; + default_parkinglot->adsipark = 0; + default_parkinglot->parking_start = 701; + default_parkinglot->parking_stop = 750; + default_parkinglot->parking_offset = 0; + default_parkinglot->parkfindnext = 0; + default_parkinglot->parkingtime = DEFAULT_PARK_TIME; + if (!reloaddpl) + default_parkinglot->occupiedlots = NULL; + for (var = ast_variable_browse(cfg, "general"); var; var = var->next) { + + /* Set parameters for default parkinglot */ if (!strcasecmp(var->name, "parkext")) { - ast_copy_string(parking_ext, var->value, sizeof(parking_ext)); + ast_copy_string(default_parkinglot->parking_ext, var->value, sizeof(default_parkinglot->parking_ext)); } else if (!strcasecmp(var->name, "context")) { - ast_copy_string(parking_con, var->value, sizeof(parking_con)); + ast_copy_string(default_parkinglot->parking_con, var->value, sizeof(default_parkinglot->parking_con)); } else if (!strcasecmp(var->name, "parkingtime")) { - if ((sscanf(var->value, "%d", &parkingtime) != 1) || (parkingtime < 1)) { + if ((sscanf(var->value, "%d", &default_parkinglot->parkingtime) != 1) || (default_parkinglot->parkingtime < 1)) { ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value); - parkingtime = DEFAULT_PARK_TIME; + default_parkinglot->parkingtime = DEFAULT_PARK_TIME; } else - parkingtime = parkingtime * 1000; + default_parkinglot->parkingtime = default_parkinglot->parkingtime * 1000; } else if (!strcasecmp(var->name, "parkpos")) { if (sscanf(var->value, "%d-%d", &start, &end) != 2) { ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers at line %d of features.conf\n", var->lineno); } else { - parking_start = start; - parking_stop = end; + default_parkinglot->parking_start = start; + default_parkinglot->parking_stop = end; } } else if (!strcasecmp(var->name, "findslot")) { - parkfindnext = (!strcasecmp(var->value, "next")); + default_parkinglot->parkfindnext = (!strcasecmp(var->value, "next")); } else if (!strcasecmp(var->name, "parkinghints")) { - parkaddhints = ast_true(var->value); + default_parkinglot->parkaddhints = ast_true(var->value); } else if (!strcasecmp(var->name, "adsipark")) { - adsipark = ast_true(var->value); + default_parkinglot->adsipark = ast_true(var->value); + } else if (!strcasecmp(var->name, "courtesytone")) { + ast_copy_string(default_parkinglot->courtesytone, var->value, sizeof(default_parkinglot->courtesytone)); + } else if (!strcasecmp(var->name, "parkedplay")) { + if (!strcasecmp(var->value, "both")) + default_parkinglot->parkedplay = 2; + else if (!strcasecmp(var->value, "parked")) + default_parkinglot->parkedplay = 1; + else + default_parkinglot->parkedplay = 0; + } else if (!strcasecmp(var->name, "parkedmusicclass")) { + ast_copy_string(default_parkinglot->parkmohclass, var->value, sizeof(default_parkinglot->parkmohclass)); + + /* Set general parameters */ } else if (!strcasecmp(var->name, "transferdigittimeout")) { if ((sscanf(var->value, "%d", &transferdigittimeout) != 1) || (transferdigittimeout < 1)) { ast_log(LOG_WARNING, "%s is not a valid transferdigittimeout\n", var->value); @@ -2308,26 +3469,17 @@ atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER; } else atxfernoanswertimeout = atxfernoanswertimeout * 1000; - } else if (!strcasecmp(var->name, "courtesytone")) { - ast_copy_string(courtesytone, var->value, sizeof(courtesytone)); - } else if (!strcasecmp(var->name, "parkedplay")) { - if (!strcasecmp(var->value, "both")) - parkedplay = 2; - else if (!strcasecmp(var->value, "parked")) - parkedplay = 1; - else - parkedplay = 0; } else if (!strcasecmp(var->name, "xfersound")) { ast_copy_string(xfersound, var->value, sizeof(xfersound)); } else if (!strcasecmp(var->name, "xferfailsound")) { ast_copy_string(xferfailsound, var->value, sizeof(xferfailsound)); } else if (!strcasecmp(var->name, "pickupexten")) { ast_copy_string(pickup_ext, var->value, sizeof(pickup_ext)); - } else if (!strcasecmp(var->name, "parkedmusicclass")) { - ast_copy_string(parkmohclass, var->value, sizeof(parkmohclass)); } } + ASTOBJ_UNLOCK(default_parkinglot); + unmap_features(); for (var = ast_variable_browse(cfg, "featuremap"); var; var = var->next) { if (remap_feature(var->name, var->value)) @@ -2415,42 +3567,88 @@ if (option_verbose >= 1) ast_verbose(VERBOSE_PREFIX_2 "Mapping Feature '%s' to app '%s(%s)' with code '%s'\n", var->name, app, app_args, exten); } + + /* Now, find parking lot definitions */ + section = NULL; + while ((section = ast_category_browse(cfg, section))) { + if (option_debug) + ast_log(LOG_DEBUG, "*** Found section %s\n", section); + + /* Assume additional parkinglot, if keyword is unequal to predefined keyword */ + if ( !strncasecmp(section, "general", strlen("general")) || + !strncasecmp(section, "featuremap", strlen("featuremap")) || + !strncasecmp(section, "applicationmap", strlen("applicationmap")) ) + continue; + + if (option_debug) + ast_log(LOG_DEBUG, "Found configuration section %s, assume parking lot.\n", section); + if(!build_parkinglot(section, ast_variable_browse(cfg, section), reload)) + ast_log(LOG_ERROR, "Could not build parking lot %s. Configuration error.\n", section); + else if (option_debug) + ast_log(LOG_DEBUG, "Configured parking context %s\n", section); + } + ast_config_destroy(cfg); /* Remove the old parking extension */ - if (!ast_strlen_zero(old_parking_con) && (con = ast_context_find(old_parking_con))) { + if (!ast_strlen_zero(old_parking_con) && (con = ast_context_find(old_parking_con))) { if(ast_context_remove_extension2(con, old_parking_ext, 1, registrar)) notify_metermaids(old_parking_ext, old_parking_con); - if (option_debug) - ast_log(LOG_DEBUG, "Removed old parking extension %s@%s\n", old_parking_ext, old_parking_con); + /* Delete context if there are no more entries */ + if (!ast_walk_context_extensions(con, NULL)) + ast_context_destroy(con, NULL); } - - if (!(con = ast_context_find(parking_con)) && !(con = ast_context_create(NULL, parking_con, registrar))) { - ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con); + + /* Add default parking lot to dialplan */ + if (!(con = ast_context_find(default_parkinglot->parking_con)) && !(con = ast_context_create(NULL, default_parkinglot->parking_con, registrar))) { + ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", default_parkinglot->parking_con); + if (reloaddpl) + parkinglot_unref(default_parkinglot); return -1; } - res = ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, NULL, NULL, registrar); - if (parkaddhints) - park_add_hints(parking_con, parking_start, parking_stop); - if (!res) - notify_metermaids(ast_parking_ext(), parking_con); - return res; + res = ast_add_extension2(con, 1, default_parkinglot->parking_ext, 1, NULL, NULL, parkcall, NULL, NULL, registrar); + if (default_parkinglot->parkaddhints) + park_add_hints(default_parkinglot->parking_con, default_parkinglot->parking_start, default_parkinglot->parking_stop); + if (!res) { + notify_metermaids(default_parkinglot->parking_ext, default_parkinglot->parking_con); + if (!reloaddpl) { + /* Move it into the list */ + ASTOBJ_CONTAINER_LINK(&parkinglots, default_parkinglot); + parkinglot_unref(default_parkinglot); + } + } else { + ast_log(LOG_WARNING, "Parking %s not open for business. Configuration error.\n", default_parkinglot->name); + } + if (reloaddpl) + parkinglot_unref(default_parkinglot); + return res; } static int reload(void) { - return load_config(); + int res; + + /* Release parking lot list */ + ASTOBJ_CONTAINER_MARKALL(&parkinglots); + + unmark_valid_parkinglots(); + + ASTOBJ_CONTAINER_PRUNE_MARKED(&parkinglots, parkinglot_destroy); + + /* Reload configuration */ + res = load_config(1); + + return res; } static int load_module(void) { int res; - - memset(parking_ext, 0, sizeof(parking_ext)); - memset(parking_con, 0, sizeof(parking_con)); - if ((res = load_config())) + ASTOBJ_CONTAINER_INIT(&parkinglots); + + if ((res = load_config(0))) return res; ast_cli_register_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry)); ast_pthread_create(&parking_thread, NULL, do_parking_thread, NULL); @@ -2458,9 +3656,12 @@ if (!res) res = ast_register_application(parkcall, park_call_exec, synopsis2, descrip2); if (!res) { - ast_manager_register("ParkedCalls", 0, manager_parking_status, "List parked calls" ); + ast_manager_register2("ParkedCalls", 0, manager_parking_status, + "List parked calls", mandescr_parking_status ); ast_manager_register2("Park", EVENT_FLAG_CALL, manager_park, "Park a channel", mandescr_park); + ast_manager_register2("Redirect", EVENT_FLAG_CALL, action_redirect, + "Redirect (transfer) a call", mandescr_redirect ); } res |= ast_devstate_prov_add("Park", metermaidstate); @@ -2471,14 +3672,29 @@ static int unload_module(void) { + int res; + ast_module_user_hangup_all(); ast_manager_unregister("ParkedCalls"); ast_manager_unregister("Park"); + ast_manager_unregister("Redirect"); ast_cli_unregister_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry)); ast_unregister_application(parkcall); ast_devstate_prov_del("Park"); - return ast_unregister_application(parkedcall); + res = ast_unregister_application(parkedcall); + + ASTOBJ_CONTAINER_DESTROYALL(&parkinglots,parkinglot_destroy); + ASTOBJ_CONTAINER_DESTROY(&parkinglots); + + default_parkinglot = NULL; + + /* Drop all parkinglot contexts from dialplan. + * Already should be done by parkinglot_destroy(), + for security reason do it again. */ + ast_context_destroy(NULL, registrar); + + return res; } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Call Features Resource",