diff --git a/.cleancount b/.cleancount index bb95160cb6..a787364590 100644 --- a/.cleancount +++ b/.cleancount @@ -1 +1 @@ -33 +34 diff --git a/CHANGES b/CHANGES index c373183ce0..381dbcc7a2 100644 --- a/CHANGES +++ b/CHANGES @@ -409,6 +409,8 @@ Call Features (res_features) Changes * Updated the ParkedCall application to allow you to not specify a parking extension. If you don't specify a parking space to pick up, it will grab the first one available. + * Added cli command 'features reload' to reload call features from features.conf + * Moved into core asterisk binary. Language Support Changes ------------------------ @@ -498,4 +500,3 @@ Miscellaneous specifying which socket to use to connect to the running Asterisk daemon (-s) * Added logging to 'make update' command. See update.log - diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h index 06163cd53a..33ba835937 100644 --- a/include/asterisk/_private.h +++ b/include/asterisk/_private.h @@ -32,6 +32,7 @@ void ast_event_init(void); /*!< Provided by event.c */ int ast_device_state_engine_init(void); /*!< Provided by devicestate.c */ int astobj2_init(void); /*!< Provided by astobj2.c */ int ast_file_init(void); /*!< Provided by file.c */ +int ast_features_init(void); /*!< Provided by features.c */ /*! * \brief Reload asterisk modules. diff --git a/include/asterisk/features.h b/include/asterisk/features.h index aa145a961a..6e2ae1eaba 100644 --- a/include/asterisk/features.h +++ b/include/asterisk/features.h @@ -109,4 +109,7 @@ struct ast_call_feature *ast_find_call_feature(const char *name); void ast_rdlock_call_features(void); void ast_unlock_call_features(void); +/*! \brief Reload call features from features.conf */ +int ast_features_reload(void); + #endif /* _AST_FEATURES_H */ diff --git a/main/Makefile b/main/Makefile index 3504b547bc..15d793fb53 100644 --- a/main/Makefile +++ b/main/Makefile @@ -29,7 +29,8 @@ OBJS= tcptls.o io.o sched.o logger.o frame.o loader.o config.o channel.o \ netsock.o slinfactory.o ast_expr2.o ast_expr2f.o \ cryptostub.o sha1.o http.o fixedjitterbuf.o abstract_jb.o \ strcompat.o threadstorage.o dial.o event.o adsistub.o audiohook.o \ - astobj2.o hashtab.o global_datastores.o $(RESAMPLE_OBJS) version.o + astobj2.o hashtab.o global_datastores.o $(RESAMPLE_OBJS) version.o \ + features.o # we need to link in the objects statically, not as a library, because # otherwise modules will not have them available if none of the static diff --git a/main/asterisk.c b/main/asterisk.c index 4c903cfbef..45bfc808cd 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -96,6 +96,7 @@ int daemon(int, int); /* defined in libresolv of all places */ #include "asterisk/network.h" #include "asterisk/cli.h" #include "asterisk/channel.h" +#include "asterisk/features.h" #include "asterisk/ulaw.h" #include "asterisk/alaw.h" #include "asterisk/callerid.h" @@ -3165,6 +3166,8 @@ int main(int argc, char *argv[]) exit(1); } + ast_features_init(); + if (init_framer()) { printf(term_quit()); exit(1); diff --git a/res/res_features.c b/main/features.c similarity index 98% rename from res/res_features.c rename to main/features.c index c6f68124e8..c0c77c26d4 100644 --- a/res/res_features.c +++ b/main/features.c @@ -1,7 +1,7 @@ /* * Asterisk -- An open source telephony toolkit. * - * Copyright (C) 1999 - 2006, Digium, Inc. + * Copyright (C) 1999 - 2008, Digium, Inc. * * Mark Spencer * @@ -31,6 +31,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") +#include "asterisk/_private.h" + #include #include #include @@ -126,7 +128,7 @@ static unsigned int atxferdropcall; static unsigned int atxferloopdelay; static unsigned int atxfercallbackretries; -static char *registrar = "res_features"; /*!< Registrar for operations */ +static char *registrar = "features"; /*!< Registrar for operations */ /* module and CLI command definitions */ static char *synopsis = "Answer a parked call"; @@ -599,9 +601,6 @@ static int builtin_parkcall(struct ast_channel *chan, struct ast_channel *peer, 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 @@ -615,8 +614,6 @@ static int builtin_parkcall(struct ast_channel *chan, struct ast_channel *peer, if (!res) res = ast_park_call(parkee, parker, 0, NULL); - ast_module_user_remove(u); - if (!res) { if (sense == FEATURE_SENSE_CHAN) res = AST_PBX_NO_HANGUP_PEER; @@ -675,7 +672,7 @@ static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *pee if (callee_chan->monitor) { ast_verb(4, "User hit '%s' to stop recording call.\n", code); - ast_monitor_stop(callee_chan, 1); + callee_chan->monitor->stop(callee_chan, 1); return FEATURE_RETURN_SUCCESS; } @@ -2511,733 +2508,758 @@ static int park_exec(struct ast_channel *chan, void *data) return res; } -/*! - * \brief CLI command to list configured features - * \param e - * \param cmd - * \param a - * - * \retval CLI_SUCCESS on success. - * \retval NULL when tab completion is used. - */ -static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - int i; - struct ast_call_feature *feature; - char format[] = "%-25s %-7s %-7s\n"; - - switch (cmd) { - - case CLI_INIT: - e->command = "features show"; - e->usage = - "Usage: features show\n" - " Lists configured features\n"; - return NULL; - case CLI_GENERATE: - return NULL; - } - - ast_cli(a->fd, format, "Builtin Feature", "Default", "Current"); - ast_cli(a->fd, format, "---------------", "-------", "-------"); - - ast_cli(a->fd, format, "Pickup", "*8", ast_pickup_ext()); /* default hardcoded above, so we'll hardcode it here */ - - ast_rwlock_rdlock(&features_lock); - for (i = 0; i < FEATURES_COUNT; i++) - ast_cli(a->fd, format, builtin_features[i].fname, builtin_features[i].default_exten, builtin_features[i].exten); - ast_rwlock_unlock(&features_lock); - - ast_cli(a->fd, "\n"); - ast_cli(a->fd, format, "Dynamic Feature", "Default", "Current"); - ast_cli(a->fd, format, "---------------", "-------", "-------"); - if (AST_LIST_EMPTY(&feature_list)) - ast_cli(a->fd, "(none)\n"); - else { - AST_LIST_LOCK(&feature_list); - AST_LIST_TRAVERSE(&feature_list, feature, feature_entry) - ast_cli(a->fd, format, feature->sname, "no def", feature->exten); - AST_LIST_UNLOCK(&feature_list); - } - ast_cli(a->fd, "\nCall parking\n"); - ast_cli(a->fd, "------------\n"); - ast_cli(a->fd,"%-20s: %s\n", "Parking extension", parking_ext); - ast_cli(a->fd,"%-20s: %s\n", "Parking context", parking_con); - ast_cli(a->fd,"%-20s: %d-%d\n", "Parked call extensions", parking_start, parking_stop); - ast_cli(a->fd,"\n"); - - return CLI_SUCCESS; -} - -static char mandescr_bridge[] = -"Description: Bridge together two channels already in the PBX\n" -"Variables: ( Headers marked with * are required )\n" -" *Channel1: Channel to Bridge to Channel2\n" -" *Channel2: Channel to Bridge to Channel1\n" -" Tone: (Yes|No) Play courtesy tone to Channel 2\n" -"\n"; - -/*! - * \brief Actual bridge - * \param chan - * \param tmpchan - * - * Stop hold music, lock both channels, masq channels, - * after bridge return channel to next priority. -*/ -static void do_bridge_masquerade(struct ast_channel *chan, struct ast_channel *tmpchan) -{ - ast_moh_stop(chan); - ast_channel_lock(chan); - ast_setstate(tmpchan, chan->_state); - tmpchan->readformat = chan->readformat; - tmpchan->writeformat = chan->writeformat; - ast_channel_masquerade(tmpchan, chan); - ast_channel_lock(tmpchan); - ast_do_masquerade(tmpchan); - /* when returning from bridge, the channel will continue at the next priority */ - ast_explicit_goto(tmpchan, chan->context, chan->exten, chan->priority + 1); - ast_channel_unlock(tmpchan); - ast_channel_unlock(chan); -} - -/*! - * \brief Bridge channels together - * \param s - * \param m - * - * Make sure valid channels were specified, - * send errors if any of the channels could not be found/locked, answer channels if needed, - * create the placeholder channels and grab the other channels - * make the channels compatible, send error if we fail doing so - * setup the bridge thread object and start the bridge. - * - * \retval 0 on success or on incorrect use. - * \retval 1 on failure to bridge channels. +/*! + * \brief Add parking hints for all defined parking lots + * \param context + * \param start starting parkinglot number + * \param stop ending parkinglot number */ -static int action_bridge(struct mansession *s, const struct message *m) +static void park_add_hints(char *context, int start, int stop) { - const char *channela = astman_get_header(m, "Channel1"); - const char *channelb = astman_get_header(m, "Channel2"); - const char *playtone = astman_get_header(m, "Tone"); - struct ast_channel *chana = NULL, *chanb = NULL; - struct ast_channel *tmpchana = NULL, *tmpchanb = NULL; - struct ast_bridge_thread_obj *tobj = NULL; - - /* make sure valid channels were specified */ - if (!ast_strlen_zero(channela) && !ast_strlen_zero(channelb)) { - chana = ast_get_channel_by_name_prefix_locked(channela, strlen(channela)); - chanb = ast_get_channel_by_name_prefix_locked(channelb, strlen(channelb)); - if (chana) - ast_channel_unlock(chana); - if (chanb) - ast_channel_unlock(chanb); + int numext; + char device[AST_MAX_EXTENSION]; + char exten[10]; - /* send errors if any of the channels could not be found/locked */ - if (!chana) { - char buf[256]; - snprintf(buf, sizeof(buf), "Channel1 does not exists: %s", channela); - astman_send_error(s, m, buf); - return 0; - } - if (!chanb) { - char buf[256]; - snprintf(buf, sizeof(buf), "Channel2 does not exists: %s", channelb); - astman_send_error(s, m, buf); - return 0; - } - } else { - astman_send_error(s, m, "Missing channel parameter in request"); - return 0; + for (numext = start; numext <= stop; numext++) { + snprintf(exten, sizeof(exten), "%d", numext); + snprintf(device, sizeof(device), "park:%s@%s", exten, context); + ast_add_extension(context, 1, exten, PRIORITY_HINT, NULL, NULL, device, NULL, NULL, registrar); } +} - /* Answer the channels if needed */ - if (chana->_state != AST_STATE_UP) - ast_answer(chana); - if (chanb->_state != AST_STATE_UP) - ast_answer(chanb); - - /* create the placeholder channels and grab the other channels */ - if (!(tmpchana = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, - NULL, NULL, 0, "Bridge/%s", chana->name))) { - astman_send_error(s, m, "Unable to create temporary channel!"); - return 1; - } +static int load_config(void) +{ + int start = 0, end = 0; + int res; + int i; + struct ast_context *con = NULL; + struct ast_config *cfg = NULL; + struct ast_variable *var = NULL; + struct feature_group *fg = NULL; + struct ast_flags config_flags = { 0 }; + char old_parking_ext[AST_MAX_EXTENSION]; + char old_parking_con[AST_MAX_EXTENSION] = ""; + char *ctg; + static const char *categories[] = { + /* Categories in features.conf that are not + * to be parsed as group categories + */ + "general", + "featuremap", + "applicationmap" + }; - if (!(tmpchanb = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, - NULL, NULL, 0, "Bridge/%s", chanb->name))) { - astman_send_error(s, m, "Unable to create temporary channels!"); - ast_channel_free(tmpchana); - return 1; - } + if (!ast_strlen_zero(parking_con)) { + strcpy(old_parking_ext, parking_ext); + strcpy(old_parking_con, parking_con); + } - do_bridge_masquerade(chana, tmpchana); - do_bridge_masquerade(chanb, tmpchanb); - - /* make the channels compatible, send error if we fail doing so */ - if (ast_channel_make_compatible(tmpchana, tmpchanb)) { - ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for manager bridge\n", tmpchana->name, tmpchanb->name); - astman_send_error(s, m, "Could not make channels compatible for manager bridge"); - ast_hangup(tmpchana); - ast_hangup(tmpchanb); - return 1; - } + /* Reset to defaults */ + strcpy(parking_con, "parkedcalls"); + 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; + comebacktoorigin = 1; + parkaddhints = 0; + parkedcalltransfers = 0; + parkedcallreparking = 0; - /* setup the bridge thread object and start the bridge */ - if (!(tobj = ast_calloc(1, sizeof(*tobj)))) { - ast_log(LOG_WARNING, "Unable to spawn a new bridge thread on %s and %s: %s\n", tmpchana->name, tmpchanb->name, strerror(errno)); - astman_send_error(s, m, "Unable to spawn a new bridge thread"); - ast_hangup(tmpchana); - ast_hangup(tmpchanb); - return 1; - } + transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT; + featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT; + atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER; + atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY; + atxferdropcall = DEFAULT_ATXFER_DROP_CALL; + atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES; - tobj->chan = tmpchana; - tobj->peer = tmpchanb; - tobj->return_to_pbx = 1; - - if (ast_true(playtone)) { - if (!ast_strlen_zero(xfersound) && !ast_streamfile(tmpchanb, xfersound, tmpchanb->language)) { - if (ast_waitstream(tmpchanb, "") < 0) - ast_log(LOG_WARNING, "Failed to play a courtesy tone on chan %s\n", tmpchanb->name); - } + cfg = ast_config_load("features.conf", config_flags); + if (!cfg) { + ast_log(LOG_WARNING,"Could not load features.conf\n"); + return 0; } - - ast_bridge_call_thread_launch(tobj); - - astman_send_ack(s, m, "Launched bridge thread with success"); - - return 0; -} - + for (var = ast_variable_browse(cfg, "general"); var; var = var->next) { + if (!strcasecmp(var->name, "parkext")) { + ast_copy_string(parking_ext, var->value, sizeof(parking_ext)); + } else if (!strcasecmp(var->name, "context")) { + ast_copy_string(parking_con, var->value, sizeof(parking_con)); + } else if (!strcasecmp(var->name, "parkingtime")) { + if ((sscanf(var->value, "%d", &parkingtime) != 1) || (parkingtime < 1)) { + ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value); + parkingtime = DEFAULT_PARK_TIME; + } else + parkingtime = 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; + } + } else if (!strcasecmp(var->name, "findslot")) { + parkfindnext = (!strcasecmp(var->value, "next")); + } else if (!strcasecmp(var->name, "parkinghints")) { + parkaddhints = ast_true(var->value); + } else if (!strcasecmp(var->name, "parkedcalltransfers")) { + if (!strcasecmp(var->value, "both")) + parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH; + else if (!strcasecmp(var->value, "caller")) + parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER; + else if (!strcasecmp(var->value, "callee")) + parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE; + } else if (!strcasecmp(var->name, "parkedcallreparking")) { + if (!strcasecmp(var->value, "both")) + parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH; + else if (!strcasecmp(var->value, "caller")) + parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER; + else if (!strcasecmp(var->value, "callee")) + parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE; + } else if (!strcasecmp(var->name, "adsipark")) { + adsipark = ast_true(var->value); + } 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); + transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT; + } else + transferdigittimeout = transferdigittimeout * 1000; + } else if (!strcasecmp(var->name, "featuredigittimeout")) { + if ((sscanf(var->value, "%d", &featuredigittimeout) != 1) || (featuredigittimeout < 1)) { + ast_log(LOG_WARNING, "%s is not a valid featuredigittimeout\n", var->value); + featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT; + } + } else if (!strcasecmp(var->name, "atxfernoanswertimeout")) { + if ((sscanf(var->value, "%d", &atxfernoanswertimeout) != 1) || (atxfernoanswertimeout < 1)) { + ast_log(LOG_WARNING, "%s is not a valid atxfernoanswertimeout\n", var->value); + atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER; + } else + atxfernoanswertimeout = atxfernoanswertimeout * 1000; + } else if (!strcasecmp(var->name, "atxferloopdelay")) { + if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) { + ast_log(LOG_WARNING, "%s is not a valid atxferloopdelay\n", var->value); + atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY; + } else + atxferloopdelay *= 1000; + } else if (!strcasecmp(var->name, "atxferdropcall")) { + atxferdropcall = ast_true(var->value); + } else if (!strcasecmp(var->name, "atxfercallbackretries")) { + if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) { + ast_log(LOG_WARNING, "%s is not a valid atxfercallbackretries\n", var->value); + atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES; + } + } 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, "comebacktoorigin")) { + comebacktoorigin = ast_true(var->value); + } else if (!strcasecmp(var->name, "parkedmusicclass")) { + ast_copy_string(parkmohclass, var->value, sizeof(parkmohclass)); + } + } + + unmap_features(); + for (var = ast_variable_browse(cfg, "featuremap"); var; var = var->next) { + if (remap_feature(var->name, var->value)) + ast_log(LOG_NOTICE, "Unknown feature '%s'\n", var->name); + } + + /* Map a key combination to an application*/ + ast_unregister_features(); + for (var = ast_variable_browse(cfg, "applicationmap"); var; var = var->next) { + char *tmp_val = ast_strdupa(var->value); + char *exten, *activateon, *activatedby, *app, *app_args, *moh_class; + struct ast_call_feature *feature; + + /* strsep() sets the argument to NULL if match not found, and it + * is safe to use it with a NULL argument, so we don't check + * between calls. + */ + exten = strsep(&tmp_val,","); + activatedby = strsep(&tmp_val,","); + app = strsep(&tmp_val,","); + app_args = strsep(&tmp_val,","); + moh_class = strsep(&tmp_val,","); + + activateon = strsep(&activatedby, "/"); + + /*! \todo XXX var_name or app_args ? */ + if (ast_strlen_zero(app) || ast_strlen_zero(exten) || ast_strlen_zero(activateon) || ast_strlen_zero(var->name)) { + ast_log(LOG_NOTICE, "Please check the feature Mapping Syntax, either extension, name, or app aren't provided %s %s %s %s\n", + app, exten, activateon, var->name); + continue; + } + + AST_LIST_LOCK(&feature_list); + if ((feature = find_dynamic_feature(var->name))) { + AST_LIST_UNLOCK(&feature_list); + ast_log(LOG_WARNING, "Dynamic Feature '%s' specified more than once!\n", var->name); + continue; + } + AST_LIST_UNLOCK(&feature_list); + + if (!(feature = ast_calloc(1, sizeof(*feature)))) + continue; + + ast_copy_string(feature->sname, var->name, FEATURE_SNAME_LEN); + ast_copy_string(feature->app, app, FEATURE_APP_LEN); + ast_copy_string(feature->exten, exten, FEATURE_EXTEN_LEN); + + if (app_args) + ast_copy_string(feature->app_args, app_args, FEATURE_APP_ARGS_LEN); + + if (moh_class) + ast_copy_string(feature->moh_class, moh_class, FEATURE_MOH_LEN); + + ast_copy_string(feature->exten, exten, sizeof(feature->exten)); + feature->operation = feature_exec_app; + ast_set_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF); + + /* Allow caller and calle to be specified for backwards compatability */ + if (!strcasecmp(activateon, "self") || !strcasecmp(activateon, "caller")) + ast_set_flag(feature, AST_FEATURE_FLAG_ONSELF); + else if (!strcasecmp(activateon, "peer") || !strcasecmp(activateon, "callee")) + ast_set_flag(feature, AST_FEATURE_FLAG_ONPEER); + else { + ast_log(LOG_NOTICE, "Invalid 'ActivateOn' specification for feature '%s'," + " must be 'self', or 'peer'\n", var->name); + continue; + } + + if (ast_strlen_zero(activatedby)) + ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH); + else if (!strcasecmp(activatedby, "caller")) + ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLER); + else if (!strcasecmp(activatedby, "callee")) + ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLEE); + else if (!strcasecmp(activatedby, "both")) + ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH); + else { + ast_log(LOG_NOTICE, "Invalid 'ActivatedBy' specification for feature '%s'," + " must be 'caller', or 'callee', or 'both'\n", var->name); + continue; + } + + ast_register_feature(feature); + + ast_verb(2, "Mapping Feature '%s' to app '%s(%s)' with code '%s'\n", var->name, app, app_args, exten); + } + + ast_unregister_groups(); + AST_RWLIST_WRLOCK(&feature_groups); + + ctg = NULL; + while ((ctg = ast_category_browse(cfg, ctg))) { + for (i = 0; i < ARRAY_LEN(categories); i++) { + if (!strcasecmp(categories[i], ctg)) + break; + } + + if (i < ARRAY_LEN(categories)) + continue; + + if (!(fg = register_group(ctg))) + continue; + + for (var = ast_variable_browse(cfg, ctg); var; var = var->next) { + struct ast_call_feature *feature; + + AST_LIST_LOCK(&feature_list); + if(!(feature = find_dynamic_feature(var->name)) && + !(feature = ast_find_call_feature(var->name))) { + AST_LIST_UNLOCK(&feature_list); + ast_log(LOG_WARNING, "Feature '%s' was not found.\n", var->name); + continue; + } + AST_LIST_UNLOCK(&feature_list); + + register_group_feature(fg, var->value, feature); + } + } + + AST_RWLIST_UNLOCK(&feature_groups); + + 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_context_remove_extension2(con, old_parking_ext, 1, registrar)) + notify_metermaids(old_parking_ext, old_parking_con, AST_DEVICE_NOT_INUSE); + ast_debug(1, "Removed old parking extension %s@%s\n", old_parking_ext, old_parking_con); + } + + 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); + 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, AST_DEVICE_INUSE); + return res; + +} + /*! - * \brief CLI command to list parked calls - * \param e + * \brief CLI command to list configured features + * \param e * \param cmd * \param a - * - * Check right usage, lock parking lot, display parked calls, unlock parking lot list. + * * \retval CLI_SUCCESS on success. - * \retval CLI_SHOWUSAGE on incorrect number of arguments. * \retval NULL when tab completion is used. -*/ -static char *handle_parkedcalls(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) + */ +static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - struct parkeduser *cur; - int numparked = 0; + int i; + struct ast_call_feature *feature; + char format[] = "%-25s %-7s %-7s\n"; switch (cmd) { + case CLI_INIT: - e->command = "parkedcalls show"; + e->command = "features show"; e->usage = - "Usage: parkedcalls show\n" - " List currently parked calls\n"; + "Usage: features show\n" + " Lists configured features\n"; return NULL; case CLI_GENERATE: return NULL; - } + } - if (a->argc > e->args) - return CLI_SHOWUSAGE; + ast_cli(a->fd, format, "Builtin Feature", "Default", "Current"); + ast_cli(a->fd, format, "---------------", "-------", "-------"); - ast_cli(a->fd, "%4s %25s (%-15s %-12s %-4s) %-6s \n", "Num", "Channel" - , "Context", "Extension", "Pri", "Timeout"); + ast_cli(a->fd, format, "Pickup", "*8", ast_pickup_ext()); /* default hardcoded above, so we'll hardcode it here */ - AST_LIST_LOCK(&parkinglot); - AST_LIST_TRAVERSE(&parkinglot, cur, list) { - ast_cli(a->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)); + ast_rwlock_rdlock(&features_lock); + for (i = 0; i < FEATURES_COUNT; i++) + ast_cli(a->fd, format, builtin_features[i].fname, builtin_features[i].default_exten, builtin_features[i].exten); + ast_rwlock_unlock(&features_lock); - numparked++; + ast_cli(a->fd, "\n"); + ast_cli(a->fd, format, "Dynamic Feature", "Default", "Current"); + ast_cli(a->fd, format, "---------------", "-------", "-------"); + if (AST_LIST_EMPTY(&feature_list)) + ast_cli(a->fd, "(none)\n"); + else { + AST_LIST_LOCK(&feature_list); + AST_LIST_TRAVERSE(&feature_list, feature, feature_entry) + ast_cli(a->fd, format, feature->sname, "no def", feature->exten); + AST_LIST_UNLOCK(&feature_list); } - AST_LIST_UNLOCK(&parkinglot); - ast_cli(a->fd, "%d parked call%s.\n", numparked, ESS(numparked)); - + ast_cli(a->fd, "\nCall parking\n"); + ast_cli(a->fd, "------------\n"); + ast_cli(a->fd,"%-20s: %s\n", "Parking extension", parking_ext); + ast_cli(a->fd,"%-20s: %s\n", "Parking context", parking_con); + ast_cli(a->fd,"%-20s: %d-%d\n", "Parked call extensions", parking_start, parking_stop); + ast_cli(a->fd,"\n"); return CLI_SUCCESS; } -static char *handle_parkedcalls_deprecated(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +int ast_features_reload(void) { - char *res = handle_parkedcalls(e, cmd, a); - if (cmd == CLI_INIT) - e->command = "show parkedcalls"; - return res; + load_config(); + + return RESULT_SUCCESS; } -static struct ast_cli_entry cli_show_parkedcalls_deprecated = AST_CLI_DEFINE(handle_parkedcalls_deprecated, "List currently parked calls."); +static char *handle_features_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "features reload"; + e->usage = + "Usage: features reload\n" + " Reloads configured call features from features.conf\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + load_config(); -static struct ast_cli_entry cli_features[] = { - AST_CLI_DEFINE(handle_feature_show, "Lists configured features"), - AST_CLI_DEFINE(handle_parkedcalls, "List currently parked calls", .deprecate_cmd = &cli_show_parkedcalls_deprecated), -}; + return CLI_SUCCESS; +} -/*! - * \brief Dump parking lot status - * \param s - * \param m +static char mandescr_bridge[] = +"Description: Bridge together two channels already in the PBX\n" +"Variables: ( Headers marked with * are required )\n" +" *Channel1: Channel to Bridge to Channel2\n" +" *Channel2: Channel to Bridge to Channel1\n" +" Tone: (Yes|No) Play courtesy tone to Channel 2\n" +"\n"; + +/*! + * \brief Actual bridge + * \param chan + * \param tmpchan * - * Lock parking lot, iterate list and append parked calls status, unlock parking lot. - * \return Always RESULT_SUCCESS + * Stop hold music, lock both channels, masq channels, + * after bridge return channel to next priority. */ -static int manager_parking_status(struct mansession *s, const struct message *m) +static void do_bridge_masquerade(struct ast_channel *chan, struct ast_channel *tmpchan) { - struct parkeduser *cur; - const char *id = astman_get_header(m, "ActionID"); - char idText[256] = ""; - - if (!ast_strlen_zero(id)) - snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); - - astman_send_ack(s, m, "Parked calls will follow"); - - AST_LIST_LOCK(&parkinglot); - - AST_LIST_TRAVERSE(&parkinglot, cur, list) { - astman_append(s, "Event: ParkedCall\r\n" - "Exten: %d\r\n" - "Channel: %s\r\n" - "From: %s\r\n" - "Timeout: %ld\r\n" - "CallerIDNum: %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); - } - - astman_append(s, - "Event: ParkedCallsComplete\r\n" - "%s" - "\r\n",idText); - - AST_LIST_UNLOCK(&parkinglot); - - return RESULT_SUCCESS; + ast_moh_stop(chan); + ast_channel_lock(chan); + ast_setstate(tmpchan, chan->_state); + tmpchan->readformat = chan->readformat; + tmpchan->writeformat = chan->writeformat; + ast_channel_masquerade(tmpchan, chan); + ast_channel_lock(tmpchan); + ast_do_masquerade(tmpchan); + /* when returning from bridge, the channel will continue at the next priority */ + ast_explicit_goto(tmpchan, chan->context, chan->exten, chan->priority + 1); + ast_channel_unlock(tmpchan); + ast_channel_unlock(chan); } -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"; - /*! - * \brief Create manager event for parked calls + * \brief Bridge channels together * \param s * \param m - * - * Get channels involved in park, create event. - * \return Always 0 + * + * Make sure valid channels were specified, + * send errors if any of the channels could not be found/locked, answer channels if needed, + * create the placeholder channels and grab the other channels + * make the channels compatible, send error if we fail doing so + * setup the bridge thread object and start the bridge. + * + * \retval 0 on success or on incorrect use. + * \retval 1 on failure to bridge channels. */ -static int manager_park(struct mansession *s, const struct message *m) +static int action_bridge(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"); - char buf[BUFSIZ]; - int to = 0; - int res = 0; - int parkExt = 0; - struct ast_channel *ch1, *ch2; + const char *channela = astman_get_header(m, "Channel1"); + const char *channelb = astman_get_header(m, "Channel2"); + const char *playtone = astman_get_header(m, "Tone"); + struct ast_channel *chana = NULL, *chanb = NULL; + struct ast_channel *tmpchana = NULL, *tmpchanb = NULL; + struct ast_bridge_thread_obj *tobj = NULL; - if (ast_strlen_zero(channel)) { - astman_send_error(s, m, "Channel not specified"); + /* make sure valid channels were specified */ + if (!ast_strlen_zero(channela) && !ast_strlen_zero(channelb)) { + chana = ast_get_channel_by_name_prefix_locked(channela, strlen(channela)); + chanb = ast_get_channel_by_name_prefix_locked(channelb, strlen(channelb)); + if (chana) + ast_channel_unlock(chana); + if (chanb) + ast_channel_unlock(chanb); + + /* send errors if any of the channels could not be found/locked */ + if (!chana) { + char buf[256]; + snprintf(buf, sizeof(buf), "Channel1 does not exists: %s", channela); + astman_send_error(s, m, buf); + return 0; + } + if (!chanb) { + char buf[256]; + snprintf(buf, sizeof(buf), "Channel2 does not exists: %s", channelb); + astman_send_error(s, m, buf); + return 0; + } + } else { + astman_send_error(s, m, "Missing channel parameter in request"); return 0; } - if (ast_strlen_zero(channel2)) { - astman_send_error(s, m, "Channel2 not specified"); - return 0; + /* Answer the channels if needed */ + if (chana->_state != AST_STATE_UP) + ast_answer(chana); + if (chanb->_state != AST_STATE_UP) + ast_answer(chanb); + + /* create the placeholder channels and grab the other channels */ + if (!(tmpchana = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, + NULL, NULL, 0, "Bridge/%s", chana->name))) { + astman_send_error(s, m, "Unable to create temporary channel!"); + return 1; } - ch1 = ast_get_channel_by_name_locked(channel); - if (!ch1) { - snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel); - astman_send_error(s, m, buf); - return 0; + if (!(tmpchanb = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, + NULL, NULL, 0, "Bridge/%s", chanb->name))) { + astman_send_error(s, m, "Unable to create temporary channels!"); + ast_channel_free(tmpchana); + return 1; } - ch2 = ast_get_channel_by_name_locked(channel2); - if (!ch2) { - snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel2); - astman_send_error(s, m, buf); - ast_channel_unlock(ch1); - return 0; + do_bridge_masquerade(chana, tmpchana); + do_bridge_masquerade(chanb, tmpchanb); + + /* make the channels compatible, send error if we fail doing so */ + if (ast_channel_make_compatible(tmpchana, tmpchanb)) { + ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for manager bridge\n", tmpchana->name, tmpchanb->name); + astman_send_error(s, m, "Could not make channels compatible for manager bridge"); + ast_hangup(tmpchana); + ast_hangup(tmpchanb); + return 1; } - if (!ast_strlen_zero(timeout)) { - sscanf(timeout, "%d", &to); + /* setup the bridge thread object and start the bridge */ + if (!(tobj = ast_calloc(1, sizeof(*tobj)))) { + ast_log(LOG_WARNING, "Unable to spawn a new bridge thread on %s and %s: %s\n", tmpchana->name, tmpchanb->name, strerror(errno)); + astman_send_error(s, m, "Unable to spawn a new bridge thread"); + ast_hangup(tmpchana); + ast_hangup(tmpchanb); + return 1; } - res = ast_masq_park_call(ch1, ch2, to, &parkExt); - if (!res) { - ast_softhangup(ch2, AST_SOFTHANGUP_EXPLICIT); - astman_send_ack(s, m, "Park successful"); - } else { - astman_send_error(s, m, "Park failure"); + tobj->chan = tmpchana; + tobj->peer = tmpchanb; + tobj->return_to_pbx = 1; + + if (ast_true(playtone)) { + if (!ast_strlen_zero(xfersound) && !ast_streamfile(tmpchanb, xfersound, tmpchanb->language)) { + if (ast_waitstream(tmpchanb, "") < 0) + ast_log(LOG_WARNING, "Failed to play a courtesy tone on chan %s\n", tmpchanb->name); + } } - ast_channel_unlock(ch1); - ast_channel_unlock(ch2); + ast_bridge_call_thread_launch(tobj); + + astman_send_ack(s, m, "Launched bridge thread with success"); return 0; } /*! - * \brief Pickup a call - * \param chan channel that initiated pickup. - * - * Walk list of channels, checking it is not itself, channel is pbx one, - * check that the callgroup for both channels are the same and the channel is ringing. - * Answer calling channel, flag channel as answered on queue, masq channels together. -*/ -int ast_pickup_call(struct ast_channel *chan) -{ - struct ast_channel *cur = NULL; - int res = -1; - - while ((cur = ast_channel_walk_locked(cur)) != NULL) { - if (!cur->pbx && - (cur != chan) && - (chan->pickupgroup & cur->callgroup) && - ((cur->_state == AST_STATE_RINGING) || - (cur->_state == AST_STATE_RING))) { - break; - } - ast_channel_unlock(cur); - } - if (cur) { - ast_debug(1, "Call pickup on chan '%s' by '%s'\n",cur->name, chan->name); - res = ast_answer(chan); - if (res) - ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan->name); - res = ast_queue_control(chan, AST_CONTROL_ANSWER); - if (res) - ast_log(LOG_WARNING, "Unable to queue answer on '%s'\n", chan->name); - res = ast_channel_masquerade(cur, chan); - if (res) - ast_log(LOG_WARNING, "Unable to masquerade '%s' into '%s'\n", chan->name, cur->name); /* Done */ - ast_channel_unlock(cur); - } else { - ast_debug(1, "No call pickup possible...\n"); - } - return res; -} - -/*! - * \brief Add parking hints for all defined parking lots - * \param context - * \param start starting parkinglot number - * \param stop ending parkinglot number + * \brief CLI command to list parked calls + * \param e + * \param cmd + * \param a + * + * Check right usage, lock parking lot, display parked calls, unlock parking lot list. + * \retval CLI_SUCCESS on success. + * \retval CLI_SHOWUSAGE on incorrect number of arguments. + * \retval NULL when tab completion is used. */ -static void park_add_hints(char *context, int start, int stop) +static char *handle_parkedcalls(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - int numext; - char device[AST_MAX_EXTENSION]; - char exten[10]; + struct parkeduser *cur; + int numparked = 0; - for (numext = start; numext <= stop; numext++) { - snprintf(exten, sizeof(exten), "%d", numext); - snprintf(device, sizeof(device), "park:%s@%s", exten, context); - ast_add_extension(context, 1, exten, PRIORITY_HINT, NULL, NULL, device, NULL, NULL, registrar); + switch (cmd) { + case CLI_INIT: + e->command = "parkedcalls show"; + e->usage = + "Usage: parkedcalls show\n" + " List currently parked calls\n"; + return NULL; + case CLI_GENERATE: + return NULL; } -} - - -static int load_config(void) -{ - int start = 0, end = 0; - int res; - int i; - struct ast_context *con = NULL; - struct ast_config *cfg = NULL; - struct ast_variable *var = NULL; - struct feature_group *fg = NULL; - struct ast_flags config_flags = { 0 }; - char old_parking_ext[AST_MAX_EXTENSION]; - char old_parking_con[AST_MAX_EXTENSION] = ""; - char *ctg; - static const char *categories[] = { - /* Categories in features.conf that are not - * to be parsed as group categories - */ - "general", - "featuremap", - "applicationmap" - }; - - if (!ast_strlen_zero(parking_con)) { - strcpy(old_parking_ext, parking_ext); - strcpy(old_parking_con, parking_con); - } - /* Reset to defaults */ - strcpy(parking_con, "parkedcalls"); - 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; - comebacktoorigin = 1; - parkaddhints = 0; - parkedcalltransfers = 0; - parkedcallreparking = 0; + if (a->argc > e->args) + return CLI_SHOWUSAGE; - transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT; - featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT; - atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER; - atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY; - atxferdropcall = DEFAULT_ATXFER_DROP_CALL; - atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES; + ast_cli(a->fd, "%4s %25s (%-15s %-12s %-4s) %-6s \n", "Num", "Channel" + , "Context", "Extension", "Pri", "Timeout"); - cfg = ast_config_load("features.conf", config_flags); - if (!cfg) { - ast_log(LOG_WARNING,"Could not load features.conf\n"); - return AST_MODULE_LOAD_DECLINE; - } - for (var = ast_variable_browse(cfg, "general"); var; var = var->next) { - if (!strcasecmp(var->name, "parkext")) { - ast_copy_string(parking_ext, var->value, sizeof(parking_ext)); - } else if (!strcasecmp(var->name, "context")) { - ast_copy_string(parking_con, var->value, sizeof(parking_con)); - } else if (!strcasecmp(var->name, "parkingtime")) { - if ((sscanf(var->value, "%d", &parkingtime) != 1) || (parkingtime < 1)) { - ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value); - parkingtime = DEFAULT_PARK_TIME; - } else - parkingtime = 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; - } - } else if (!strcasecmp(var->name, "findslot")) { - parkfindnext = (!strcasecmp(var->value, "next")); - } else if (!strcasecmp(var->name, "parkinghints")) { - parkaddhints = ast_true(var->value); - } else if (!strcasecmp(var->name, "parkedcalltransfers")) { - if (!strcasecmp(var->value, "both")) - parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH; - else if (!strcasecmp(var->value, "caller")) - parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER; - else if (!strcasecmp(var->value, "callee")) - parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE; - } else if (!strcasecmp(var->name, "parkedcallreparking")) { - if (!strcasecmp(var->value, "both")) - parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH; - else if (!strcasecmp(var->value, "caller")) - parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER; - else if (!strcasecmp(var->value, "callee")) - parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE; - } else if (!strcasecmp(var->name, "adsipark")) { - adsipark = ast_true(var->value); - } 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); - transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT; - } else - transferdigittimeout = transferdigittimeout * 1000; - } else if (!strcasecmp(var->name, "featuredigittimeout")) { - if ((sscanf(var->value, "%d", &featuredigittimeout) != 1) || (featuredigittimeout < 1)) { - ast_log(LOG_WARNING, "%s is not a valid featuredigittimeout\n", var->value); - featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT; - } - } else if (!strcasecmp(var->name, "atxfernoanswertimeout")) { - if ((sscanf(var->value, "%d", &atxfernoanswertimeout) != 1) || (atxfernoanswertimeout < 1)) { - ast_log(LOG_WARNING, "%s is not a valid atxfernoanswertimeout\n", var->value); - atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER; - } else - atxfernoanswertimeout = atxfernoanswertimeout * 1000; - } else if (!strcasecmp(var->name, "atxferloopdelay")) { - if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) { - ast_log(LOG_WARNING, "%s is not a valid atxferloopdelay\n", var->value); - atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY; - } else - atxferloopdelay *= 1000; - } else if (!strcasecmp(var->name, "atxferdropcall")) { - atxferdropcall = ast_true(var->value); - } else if (!strcasecmp(var->name, "atxfercallbackretries")) { - if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) { - ast_log(LOG_WARNING, "%s is not a valid atxfercallbackretries\n", var->value); - atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES; - } - } 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, "comebacktoorigin")) { - comebacktoorigin = ast_true(var->value); - } else if (!strcasecmp(var->name, "parkedmusicclass")) { - ast_copy_string(parkmohclass, var->value, sizeof(parkmohclass)); - } - } + AST_LIST_LOCK(&parkinglot); + AST_LIST_TRAVERSE(&parkinglot, cur, list) { + ast_cli(a->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)); - unmap_features(); - for (var = ast_variable_browse(cfg, "featuremap"); var; var = var->next) { - if (remap_feature(var->name, var->value)) - ast_log(LOG_NOTICE, "Unknown feature '%s'\n", var->name); + numparked++; } + AST_LIST_UNLOCK(&parkinglot); + ast_cli(a->fd, "%d parked call%s.\n", numparked, ESS(numparked)); - /* Map a key combination to an application*/ - ast_unregister_features(); - for (var = ast_variable_browse(cfg, "applicationmap"); var; var = var->next) { - char *tmp_val = ast_strdupa(var->value); - char *exten, *activateon, *activatedby, *app, *app_args, *moh_class; - struct ast_call_feature *feature; - /* strsep() sets the argument to NULL if match not found, and it - * is safe to use it with a NULL argument, so we don't check - * between calls. - */ - exten = strsep(&tmp_val,","); - activatedby = strsep(&tmp_val,","); - app = strsep(&tmp_val,","); - app_args = strsep(&tmp_val,","); - moh_class = strsep(&tmp_val,","); + return CLI_SUCCESS; +} - activateon = strsep(&activatedby, "/"); +static char *handle_parkedcalls_deprecated(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + char *res = handle_parkedcalls(e, cmd, a); + if (cmd == CLI_INIT) + e->command = "show parkedcalls"; + return res; +} - /*! \todo XXX var_name or app_args ? */ - if (ast_strlen_zero(app) || ast_strlen_zero(exten) || ast_strlen_zero(activateon) || ast_strlen_zero(var->name)) { - ast_log(LOG_NOTICE, "Please check the feature Mapping Syntax, either extension, name, or app aren't provided %s %s %s %s\n", - app, exten, activateon, var->name); - continue; - } +static struct ast_cli_entry cli_show_parkedcalls_deprecated = AST_CLI_DEFINE(handle_parkedcalls_deprecated, "List currently parked calls."); - AST_LIST_LOCK(&feature_list); - if ((feature = find_dynamic_feature(var->name))) { - AST_LIST_UNLOCK(&feature_list); - ast_log(LOG_WARNING, "Dynamic Feature '%s' specified more than once!\n", var->name); - continue; - } - AST_LIST_UNLOCK(&feature_list); - - if (!(feature = ast_calloc(1, sizeof(*feature)))) - continue; +static struct ast_cli_entry cli_features[] = { + AST_CLI_DEFINE(handle_feature_show, "Lists configured features"), + AST_CLI_DEFINE(handle_features_reload, "Reloads configured features"), + AST_CLI_DEFINE(handle_parkedcalls, "List currently parked calls", .deprecate_cmd = &cli_show_parkedcalls_deprecated), +}; - ast_copy_string(feature->sname, var->name, FEATURE_SNAME_LEN); - ast_copy_string(feature->app, app, FEATURE_APP_LEN); - ast_copy_string(feature->exten, exten, FEATURE_EXTEN_LEN); - - if (app_args) - ast_copy_string(feature->app_args, app_args, FEATURE_APP_ARGS_LEN); +/*! + * \brief Dump parking lot status + * \param s + * \param m + * + * Lock parking lot, iterate list and append parked calls status, unlock parking lot. + * \return Always RESULT_SUCCESS +*/ +static int manager_parking_status(struct mansession *s, const struct message *m) +{ + struct parkeduser *cur; + const char *id = astman_get_header(m, "ActionID"); + char idText[256] = ""; - if (moh_class) - ast_copy_string(feature->moh_class, moh_class, FEATURE_MOH_LEN); - - ast_copy_string(feature->exten, exten, sizeof(feature->exten)); - feature->operation = feature_exec_app; - ast_set_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF); + if (!ast_strlen_zero(id)) + snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); - /* Allow caller and calle to be specified for backwards compatability */ - if (!strcasecmp(activateon, "self") || !strcasecmp(activateon, "caller")) - ast_set_flag(feature, AST_FEATURE_FLAG_ONSELF); - else if (!strcasecmp(activateon, "peer") || !strcasecmp(activateon, "callee")) - ast_set_flag(feature, AST_FEATURE_FLAG_ONPEER); - else { - ast_log(LOG_NOTICE, "Invalid 'ActivateOn' specification for feature '%s'," - " must be 'self', or 'peer'\n", var->name); - continue; - } + astman_send_ack(s, m, "Parked calls will follow"); - if (ast_strlen_zero(activatedby)) - ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH); - else if (!strcasecmp(activatedby, "caller")) - ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLER); - else if (!strcasecmp(activatedby, "callee")) - ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLEE); - else if (!strcasecmp(activatedby, "both")) - ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH); - else { - ast_log(LOG_NOTICE, "Invalid 'ActivatedBy' specification for feature '%s'," - " must be 'caller', or 'callee', or 'both'\n", var->name); - continue; - } + AST_LIST_LOCK(&parkinglot); - ast_register_feature(feature); - - ast_verb(2, "Mapping Feature '%s' to app '%s(%s)' with code '%s'\n", var->name, app, app_args, exten); + AST_LIST_TRAVERSE(&parkinglot, cur, list) { + astman_append(s, "Event: ParkedCall\r\n" + "Exten: %d\r\n" + "Channel: %s\r\n" + "From: %s\r\n" + "Timeout: %ld\r\n" + "CallerIDNum: %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); } - ast_unregister_groups(); - AST_RWLIST_WRLOCK(&feature_groups); + astman_append(s, + "Event: ParkedCallsComplete\r\n" + "%s" + "\r\n",idText); - ctg = NULL; - while ((ctg = ast_category_browse(cfg, ctg))) { - for (i = 0; i < ARRAY_LEN(categories); i++) { - if (!strcasecmp(categories[i], ctg)) - break; - } + AST_LIST_UNLOCK(&parkinglot); - if (i < ARRAY_LEN(categories)) - continue; + return RESULT_SUCCESS; +} - if (!(fg = register_group(ctg))) - continue; +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"; - for (var = ast_variable_browse(cfg, ctg); var; var = var->next) { - struct ast_call_feature *feature; +/*! + * \brief Create manager event for parked calls + * \param s + * \param m + * + * Get channels involved in park, create event. + * \return Always 0 +*/ +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"); + char buf[BUFSIZ]; + int to = 0; + int res = 0; + int parkExt = 0; + struct ast_channel *ch1, *ch2; - AST_LIST_LOCK(&feature_list); - if(!(feature = find_dynamic_feature(var->name)) && - !(feature = ast_find_call_feature(var->name))) { - AST_LIST_UNLOCK(&feature_list); - ast_log(LOG_WARNING, "Feature '%s' was not found.\n", var->name); - continue; - } - AST_LIST_UNLOCK(&feature_list); + if (ast_strlen_zero(channel)) { + astman_send_error(s, m, "Channel not specified"); + return 0; + } - register_group_feature(fg, var->value, feature); - } + if (ast_strlen_zero(channel2)) { + astman_send_error(s, m, "Channel2 not specified"); + return 0; } - AST_RWLIST_UNLOCK(&feature_groups); + ch1 = ast_get_channel_by_name_locked(channel); + if (!ch1) { + snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel); + astman_send_error(s, m, buf); + return 0; + } - ast_config_destroy(cfg); + ch2 = ast_get_channel_by_name_locked(channel2); + if (!ch2) { + snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel2); + astman_send_error(s, m, buf); + ast_channel_unlock(ch1); + return 0; + } - /* 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, AST_DEVICE_NOT_INUSE); - ast_debug(1, "Removed old parking extension %s@%s\n", old_parking_ext, old_parking_con); + if (!ast_strlen_zero(timeout)) { + sscanf(timeout, "%d", &to); } - - 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); - return -1; + + res = ast_masq_park_call(ch1, ch2, to, &parkExt); + if (!res) { + ast_softhangup(ch2, AST_SOFTHANGUP_EXPLICIT); + astman_send_ack(s, m, "Park successful"); + } else { + astman_send_error(s, m, "Park failure"); } - 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, AST_DEVICE_INUSE); - return res; + ast_channel_unlock(ch1); + ast_channel_unlock(ch2); + + return 0; +} + +/*! + * \brief Pickup a call + * \param chan channel that initiated pickup. + * + * Walk list of channels, checking it is not itself, channel is pbx one, + * check that the callgroup for both channels are the same and the channel is ringing. + * Answer calling channel, flag channel as answered on queue, masq channels together. +*/ +int ast_pickup_call(struct ast_channel *chan) +{ + struct ast_channel *cur = NULL; + int res = -1; + + while ((cur = ast_channel_walk_locked(cur)) != NULL) { + if (!cur->pbx && + (cur != chan) && + (chan->pickupgroup & cur->callgroup) && + ((cur->_state == AST_STATE_RINGING) || + (cur->_state == AST_STATE_RING))) { + break; + } + ast_channel_unlock(cur); + } + if (cur) { + ast_debug(1, "Call pickup on chan '%s' by '%s'\n",cur->name, chan->name); + res = ast_answer(chan); + if (res) + ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan->name); + res = ast_queue_control(chan, AST_CONTROL_ANSWER); + if (res) + ast_log(LOG_WARNING, "Unable to queue answer on '%s'\n", chan->name); + res = ast_channel_masquerade(cur, chan); + if (res) + ast_log(LOG_WARNING, "Unable to masquerade '%s' into '%s'\n", chan->name, cur->name); /* Done */ + ast_channel_unlock(cur); + } else { + ast_debug(1, "No call pickup possible...\n"); + } + return res; } static char *app_bridge = "Bridge"; @@ -3388,16 +3410,11 @@ static int bridge_exec(struct ast_channel *chan, void *data) return 0; } -static int reload(void) -{ - return load_config(); -} - -static int load_module(void) +int ast_features_init(void) { int res; - ast_register_application(app_bridge, bridge_exec, bridge_synopsis, bridge_descrip); + ast_register_application2(app_bridge, bridge_exec, bridge_synopsis, bridge_descrip, NULL); memset(parking_ext, 0, sizeof(parking_ext)); memset(parking_con, 0, sizeof(parking_con)); @@ -3406,9 +3423,9 @@ static int load_module(void) 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); - res = ast_register_application(parkedcall, park_exec, synopsis, descrip); + res = ast_register_application2(parkedcall, park_exec, synopsis, descrip, NULL); if (!res) - res = ast_register_application(parkcall, park_call_exec, synopsis2, descrip2); + res = ast_register_application2(parkcall, park_call_exec, synopsis2, descrip2, NULL); if (!res) { ast_manager_register("ParkedCalls", 0, manager_parking_status, "List parked calls"); ast_manager_register2("Park", EVENT_FLAG_CALL, manager_park, @@ -3420,29 +3437,3 @@ static int load_module(void) return res; } - - -static int unload_module(void) -{ - struct ast_context *con; - ast_manager_unregister("ParkedCalls"); - ast_manager_unregister("Bridge"); - ast_manager_unregister("Park"); - ast_cli_unregister_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry)); - ast_unregister_application(parkcall); - ast_unregister_application(app_bridge); - ast_devstate_prov_del("Park"); - con = ast_context_find(parking_con); - if (con) - ast_context_destroy(con, registrar); - con = ast_context_find(parking_con_dial); - if (con) - ast_context_destroy(con, registrar); - return ast_unregister_application(parkedcall); -} - -AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Call Features Resource", - .load = load_module, - .unload = unload_module, - .reload = reload, - ); diff --git a/main/loader.c b/main/loader.c index 1d07977202..45fc7be82c 100644 --- a/main/loader.c +++ b/main/loader.c @@ -46,6 +46,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/rtp.h" #include "asterisk/http.h" #include "asterisk/lock.h" +#include "asterisk/features.h" #ifdef DLFCNCOMPAT #include "asterisk/dlfcn-compat.h" @@ -247,6 +248,7 @@ static struct reload_classes { { "rtp", ast_rtp_reload }, { "http", ast_http_reload }, { "logger", logger_reload }, + { "features", ast_features_reload }, { NULL, NULL } };