2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2013, Digium, Inc.
6 * Jonathan Rose <jrose@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief Call Parking Resource
23 * \author Jonathan Rose <jrose@digium.com>
27 <depend>bridge_holding</depend>
28 <support_level>core</support_level>
32 <configInfo name="res_parking" language="en_US">
33 <configFile name="res_parking.conf">
34 <configObject name="globals">
35 <synopsis>Options that apply to every parking lot</synopsis>
37 <configObject name="parking_lot">
38 <synopsis>Defined parking lots for res_parking to use to park calls on</synopsis>
39 <configOption name="context" default="parkedcalls">
40 <synopsis>The name of the context where calls are parked and picked up from.</synopsis>
41 <description><para>This option is only used if parkext is set.</para></description>
43 <configOption name="parkext">
44 <synopsis>Extension to park calls to this parking lot.</synopsis>
45 <description><para>If this option is used, this extension will automatically be created to place calls into
46 parking lots. In addition, if parkext_exclusive is set for this parking lot, the name of the parking lot
47 will be included in the application's arguments so that it only parks to this parking lot. The extension
48 will be created in <literal>context</literal>. Using this option also creates extensions for retrieving
49 parked calls from the parking spaces in the same context.</para></description>
51 <configOption name="parkext_exclusive" default="no">
52 <synopsis>If yes, the extension registered as parkext will park exclusively to this parking lot.</synopsis>
54 <configOption name="parkpos" default="701-750">
55 <synopsis>Numerical range of parking spaces which can be used to retrieve parked calls.</synopsis>
56 <description><para>If parkext is set, these extensions will automatically be mapped in <literal>context</literal>
57 in order to pick up calls parked to these parking spaces.</para></description>
59 <configOption name="parkinghints" default="no">
60 <synopsis>If yes, this parking lot will add hints automatically for parking spaces.</synopsis>
62 <configOption name="parkingtime" default="45">
63 <synopsis>Amount of time a call will remain parked before giving up (in seconds).</synopsis>
65 <configOption name="parkedmusicclass">
66 <synopsis>Which music class to use for parked calls. They will use the default if unspecified.</synopsis>
68 <configOption name="comebacktoorigin" default="yes">
69 <synopsis>Determines what should be done with the parked channel if no one picks it up before it times out.</synopsis>
70 <description><para>Valid Options:</para>
73 <para>Automatically have the parked channel dial the device that parked the call with dial
74 timeout set by the <literal>parkingtime</literal> option. When the call times out an extension
75 to dial the PARKER will automatically be created in the <literal>park-dial</literal> context with
76 an extension of the flattened parker device name. If the call is not answered, the parked channel
77 that is timing out will continue in the dial plan at that point if there are more priorities in
78 the extension (which won't be the case unless the dialplan deliberately includes such priorities
79 in the <literal>park-dial</literal> context through pattern matching or deliberately written
80 flattened peer extensions).</para>
83 <para>Place the call into the PBX at <literal>comebackcontext</literal> instead. The extension will
84 still be set as the flattened peer name. If an extension the flattened peer name isn't available
85 then it will fall back to the <literal>s</literal> extension. If that also is unavailable it will
86 attempt to fall back to <literal>s@default</literal>. The normal dial extension will still be
87 created in the <literal>park-dial</literal> context with the extension also being the flattened
91 <note><para>Flattened Peer Names - Extensions can not include slash characters since those are used for pattern
92 matching. When a peer name is flattened, slashes become underscores. For example if the parker of a call
93 is called <literal>SIP/0004F2040001</literal> then flattened peer name and therefor the extensions created
94 and used on timeouts will be <literal>SIP_0004F204001</literal>.</para></note>
95 <note><para>When parking times out and the channel returns to the dial plan, the following variables are set:
98 <variable name="PARKINGSLOT">
99 <para>extension that the call was parked in prior to timing out.</para>
101 <variable name="PARKEDLOT">
102 <para>name of the lot that the call was parked in prior to timing out.</para>
104 <variable name="PARKER">
105 <para>The device that parked the call</para>
110 <configOption name="comebackdialtime" default="30">
111 <synopsis>Timeout for the Dial extension created to call back the parker when a parked call times out.</synopsis>
113 <configOption name="comebackcontext" default="parkedcallstimeout">
114 <synopsis>Context where parked calls will enter the PBX on timeout when comebacktoorigin=no</synopsis>
115 <description><para>The extension the call enters will prioritize the flattened peer name in this context.
116 If the flattened peer name extension is unavailable, then the 's' extension in this context will be
117 used. If that also is unavailable, the 's' extension in the 'default' context will be used.</para>
120 <configOption name="courtesytone">
121 <synopsis>If the name of a sound file is provided, use this as the courtesy tone</synopsis>
122 <description><para>By default, this tone is only played to the caller of a parked call. Who receives the tone
123 can be changed using the <literal>parkedplay</literal> option.</para>
126 <configOption name="parkedplay" default="caller">
127 <synopsis>Who we should play the courtesytone to on the pickup of a parked call from this lot</synopsis>
130 <enum name="no"><para>Apply to neither side.</para></enum>
131 <enum name="caller"><para>Apply to only to the caller picking up the parked call.</para></enum>
132 <enum name="callee"><para>Apply to only to the parked call being picked up.</para></enum>
133 <enum name="both"><para>Apply to both the caller and the callee.</para></enum>
135 <note><para>If courtesy tone is not specified then this option will be ignored.</para></note>
138 <configOption name="parkedcalltransfers" default="no">
139 <synopsis>Apply the DTMF transfer features to the caller and/or callee when parked calls are picked up.</synopsis>
141 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
144 <configOption name="parkedcallreparking" default="no">
145 <synopsis>Apply the DTMF parking feature to the caller and/or callee when parked calls are picked up.</synopsis>
147 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
150 <configOption name="parkedcallhangup" default="no">
151 <synopsis>Apply the DTMF Hangup feature to the caller and/or callee when parked calls are picked up.</synopsis>
153 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
156 <configOption name="parkedcallrecording" default="no">
157 <synopsis>Apply the DTMF recording features to the caller and/or callee when parked calls are picked up</synopsis>
159 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
162 <configOption name="findslot" default="first">
163 <synopsis>Rule to use when trying to figure out which parking space a call should be parked with.</synopsis>
166 <enum name="first"><para>Always try to place in the lowest available space in the parking lot</para></enum>
167 <enum name="next"><para>Track the last parking space used and always attempt to use the one immediately after.
172 <configOption name="courtesytone">
173 <synopsis>If set, the sound set will be played to whomever is set by parkedplay</synopsis>
180 #include "asterisk.h"
182 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
184 #include "parking/res_parking.h"
185 #include "asterisk/config.h"
186 #include "asterisk/config_options.h"
187 #include "asterisk/event.h"
188 #include "asterisk/utils.h"
189 #include "asterisk/module.h"
190 #include "asterisk/cli.h"
191 #include "asterisk/astobj2.h"
192 #include "asterisk/features.h"
193 #include "asterisk/manager.h"
194 #include "asterisk/pbx.h"
196 #define PARKED_CALL_APPLICATION "ParkedCall"
197 #define PARK_AND_ANNOUNCE_APPLICATION "ParkAndAnnounce"
199 /* TODO Add unit tests for parking */
201 static int parking_lot_sort_fn(const void *obj_left, const void *obj_right, int flags)
203 const struct parking_lot *left = obj_left;
204 const struct parking_lot *right = obj_right;
205 const char *right_key = obj_right;
208 switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
211 right_key = right->name;
214 cmp = strcmp(left->name, right_key);
216 case OBJ_PARTIAL_KEY:
217 cmp = strncmp(left->name, right_key, strlen(right_key));
222 /*! All parking lots that are currently alive in some fashion can be obtained from here */
223 static struct ao2_container *parking_lot_container;
225 static void *parking_config_alloc(void);
227 static void *parking_lot_cfg_alloc(const char *cat);
228 static void *named_item_find(struct ao2_container *container, const char *name); /* XXX This is really just a generic string find. Move to astobj2.c? */
230 static int config_parking_preapply(void);
231 static void link_configured_disable_marked_lots(void);
233 struct parking_global_config {
237 struct parking_config {
238 struct parking_global_config *global;
239 struct ao2_container *parking_lots;
242 static struct aco_type global_option = {
245 .item_offset = offsetof(struct parking_config, global),
246 .category_match = ACO_WHITELIST,
247 .category = "^general$",
250 struct aco_type *global_options[] = ACO_TYPES(&global_option);
252 static struct aco_type parking_lot_type = {
254 .name = "parking_lot",
255 .category_match = ACO_BLACKLIST,
256 .category = "^(general)$",
257 .item_alloc = parking_lot_cfg_alloc,
258 .item_find = named_item_find,
259 .item_offset = offsetof(struct parking_config, parking_lots),
262 struct aco_type *parking_lot_types[] = ACO_TYPES(&parking_lot_type);
264 struct aco_file parking_lot_conf = {
265 .filename = "res_parking.conf",
266 .types = ACO_TYPES(&global_option, &parking_lot_type),
269 static AO2_GLOBAL_OBJ_STATIC(globals);
271 CONFIG_INFO_STANDARD(cfg_info, globals, parking_config_alloc,
272 .files = ACO_FILES(&parking_lot_conf),
273 .pre_apply_config = config_parking_preapply,
274 .post_apply_config = link_configured_disable_marked_lots,
277 static int parking_lot_cfg_hash_fn(const void *obj, const int flags)
279 const struct parking_lot_cfg *entry;
282 switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
285 return ast_str_hash(key);
286 case OBJ_PARTIAL_KEY:
291 return ast_str_hash(entry->name);
295 static int parking_lot_cfg_cmp_fn(void *obj, void *arg, const int flags)
297 struct parking_lot_cfg *entry1 = obj;
301 struct parking_lot_cfg *entry2;
303 switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
306 return (!strcmp(entry1->name, key)) ? CMP_MATCH : 0;
307 case OBJ_PARTIAL_KEY:
309 key_size = strlen(key);
310 return (!strncmp(entry1->name, key, key_size)) ? CMP_MATCH : 0;
313 return (!strcmp(entry1->name, entry2->name)) ? CMP_MATCH : 0;
319 /*! \brief destructor for parking_config */
320 static void parking_config_destructor(void *obj)
322 struct parking_config *cfg = obj;
323 ao2_cleanup(cfg->parking_lots);
324 ao2_cleanup(cfg->global);
327 /*! \brief destructor for parking_global_config */
328 static void parking_global_config_destructor(void *obj)
330 /* For now, do nothing. */
333 /*! \brief allocator callback for parking_config. Notice it returns void * since it is only used by the backend config code */
334 static void *parking_config_alloc(void)
336 RAII_VAR(struct parking_config *, cfg, NULL, ao2_cleanup);
338 if (!(cfg = ao2_alloc(sizeof(*cfg), parking_config_destructor))) {
342 if (!(cfg->parking_lots = ao2_container_alloc(37, parking_lot_cfg_hash_fn, parking_lot_cfg_cmp_fn))) {
346 if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), parking_global_config_destructor))) {
350 /* Bump the ref count since RAII_VAR is going to eat one */
355 int parking_lot_remove_if_unused(struct parking_lot *lot)
357 if (lot->mode != PARKINGLOT_DISABLED) {
361 if (!ao2_container_count(lot->parked_users)) {
362 ao2_unlink(parking_lot_container, lot);
369 static void parking_lot_disable(struct parking_lot *lot)
371 /* If a dynamic lot wasn't removed, we need to restore it to full functionality afterwards. */
372 int was_dynamic = (lot->mode == PARKINGLOT_DYNAMIC);
374 lot->mode = PARKINGLOT_DISABLED;
375 if (parking_lot_remove_if_unused(lot) && was_dynamic) {
376 lot->mode = PARKINGLOT_DYNAMIC;
377 lot->disable_mark = 0;
381 /*! \brief Destroy a parking lot cfg object */
382 static void parking_lot_cfg_destructor(void *obj)
384 struct parking_lot_cfg *lot_cfg = obj;
385 parking_lot_cfg_remove_extensions(lot_cfg);
386 ast_string_field_free_memory(lot_cfg);
389 /* The arg just needs to have the parking space with it */
390 static int parked_user_cmp_fn(void *obj, void *arg, int flags)
392 int *search_space = arg;
393 struct parked_user *user = obj;
394 int object_space = user->parking_space;
396 if (*search_space == object_space) {
402 static int parked_user_sort_fn(const void *obj_left, const void *obj_right, int flags)
404 const struct parked_user *left = obj_left;
405 const struct parked_user *right = obj_right;
407 return left->parking_space - right->parking_space;
411 * \brief create a parking lot structure
412 * \param cat name given to the parking lot
413 * \retval NULL failure
414 * \retval non-NULL successfully allocated parking lot
416 static void *parking_lot_cfg_alloc(const char *cat)
418 struct parking_lot_cfg *lot_cfg;
420 lot_cfg = ao2_alloc(sizeof(*lot_cfg), parking_lot_cfg_destructor);
425 if (ast_string_field_init(lot_cfg, 32)) {
426 ao2_cleanup(lot_cfg);
430 ast_string_field_set(lot_cfg, name, cat);
436 * XXX This is actually incredibly generic and might be better placed in something like astobj2 if there isn't already an equivalent
437 * \brief find an item in a container by its name
439 * \param container ao2container where we want the item from
440 * \param key name of the item wanted to be found
442 * \retval pointer to the parking lot if available. NULL if not found.
444 static void *named_item_find(struct ao2_container *container, const char *name)
446 return ao2_find(container, name, OBJ_KEY);
450 * \brief Custom field handler for parking positions
452 static int option_handler_parkpos(const struct aco_option *opt, struct ast_variable *var, void *obj)
454 struct parking_lot_cfg *lot_cfg = obj;
458 if (sscanf(var->value, "%30d-%30d", &low, &high) != 2) {
459 ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers\n");
460 } else if (high < low || low <= 0 || high <= 0) {
461 ast_log(LOG_WARNING, "Format for parking positions is a-b, where a <= b\n");
463 lot_cfg->parking_start = low;
464 lot_cfg->parking_stop = high;
471 * \brief Custom field handler for the findslot option
473 static int option_handler_findslot(const struct aco_option *opt, struct ast_variable *var, void *obj)
475 struct parking_lot_cfg *lot_cfg = obj;
477 if (!strcmp(var->value, "first")) {
478 lot_cfg->parkfindnext = 0;
479 } else if (!strcmp(var->value, "next")) {
480 lot_cfg->parkfindnext = 1;
482 ast_log(LOG_WARNING, "value '%s' is not valid for findslot option.\n", var->value);
490 * \brief Maps string values for option_handler_parkedfeature to their ENUM values
492 static int parking_feature_flag_cfg(int *param, const char *var)
494 if (ast_false(var)) {
496 } else if (!strcasecmp(var, "both")) {
497 *param = AST_FEATURE_FLAG_BYBOTH;
498 } else if (!strcasecmp(var, "caller")) {
499 *param = AST_FEATURE_FLAG_BYCALLER;
500 } else if (!strcasecmp(var, "callee")) {
501 *param = AST_FEATURE_FLAG_BYCALLEE;
510 * \brief Custom field handler for feature mapping on parked call pickup options
512 static int option_handler_parkedfeature(const struct aco_option *opt, struct ast_variable *var, void *obj)
514 struct parking_lot_cfg *cfg = obj;
515 enum parked_call_feature_options option = aco_option_get_flags(opt);
516 int *parameter = NULL;
520 parameter = &cfg->parkedplay;
522 case OPT_PARKEDTRANSFERS:
523 parameter = &cfg->parkedcalltransfers;
525 case OPT_PARKEDREPARKING:
526 parameter = &cfg->parkedcallreparking;
528 case OPT_PARKEDHANGUP:
529 parameter = &cfg->parkedcallhangup;
531 case OPT_PARKEDRECORDING:
532 parameter = &cfg->parkedcallrecording;
536 ast_assert(parameter != NULL);
537 if (!parameter || parking_feature_flag_cfg(parameter, var->value)) {
544 struct ao2_container *get_parking_lot_container(void)
546 return parking_lot_container;
549 struct parking_lot *parking_lot_find_by_name(const char *lot_name)
551 struct parking_lot *lot = named_item_find(parking_lot_container, lot_name);
555 const char *find_channel_parking_lot_name(struct ast_channel *chan)
559 /* The channel variable overrides everything */
560 name = pbx_builtin_getvar_helper(chan, "PARKINGLOT");
561 if (ast_strlen_zero(name) && !ast_strlen_zero(ast_channel_parkinglot(chan))) {
562 /* Use the channel's parking lot. */
563 name = ast_channel_parkinglot(chan);
566 /* If the name couldn't be pulled from that either, use the default parking lot name. */
567 if (ast_strlen_zero(name)) {
568 name = DEFAULT_PARKING_LOT;
574 static void parking_lot_destructor(void *obj)
576 struct parking_lot *lot = obj;
578 if (lot->parking_bridge) {
579 ast_bridge_destroy(lot->parking_bridge);
581 ao2_cleanup(lot->parked_users);
582 ao2_cleanup(lot->cfg);
583 ast_string_field_free_memory(lot);
586 static struct parking_lot *alloc_new_parking_lot(struct parking_lot_cfg *lot_cfg)
588 struct parking_lot *lot;
589 if (!(lot = ao2_alloc(sizeof(*lot), parking_lot_destructor))) {
593 if (ast_string_field_init(lot, 32)) {
597 /* Create parked user ordered list */
598 lot->parked_users = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_RWLOCK,
599 AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
603 if (!lot->parked_users) {
608 ast_string_field_set(lot, name, lot_cfg->name);
612 void parking_lot_cfg_remove_extensions(struct parking_lot_cfg *lot_cfg)
614 if (!ast_strlen_zero(lot_cfg->registrar)) {
615 /* Although the function is called ast_context_destroy, the use of this funtion is
616 * intended only to remove extensions, hints, etc registered by the parking lot's registrar.
617 * It won't actually destroy the context unless that context is empty afterwards and it is
620 ast_context_destroy(NULL, lot_cfg->registrar);
624 static void remove_all_configured_parking_lot_extensions(void)
626 RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
627 struct parking_lot_cfg *lot_cfg;
628 struct ao2_iterator iter;
634 for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
635 parking_lot_cfg_remove_extensions(lot_cfg);
638 ast_context_destroy(NULL, BASE_REGISTRAR);
640 ao2_iterator_destroy(&iter);
646 * \brief Create an extension using ast_add_extension2_nolock. This function automatically allocates a duplicate
647 * of the data string so that whatever calls it doesn't have to deal with freeing it if the ast_add_extension2_nolock
650 * \param context a write locked ast_context. Make certain it is write locked prior to calling this function
651 * \param replace whether the extension should replace existing extensions
652 * \param extension name of the extension desired
653 * \param priority priority of the extension we are registering
654 * \param application name of the application being used for the extension
655 * \param data application arguments
656 * \param registrar name of the registrar you should use for the extension.
657 * Make sure this string doesn't go anywhere while there are still extensions using it.
659 static int parking_add_extension(struct ast_context *context, int replace, const char *extension,
660 int priority, const char *application, const char *data, const char *registrar)
662 char *data_duplicate = ast_strdup(data);
664 if (!data_duplicate) {
668 if (ast_add_extension2_nolock(context, replace, extension, priority, NULL, NULL,
669 application, data_duplicate, ast_free_ptr, registrar)) {
670 ast_free(data_duplicate);
677 static int extension_is_compatible(struct parking_lot_cfg *lot_cfg, const char *app_type, struct ast_exten *extension)
679 RAII_VAR(struct parking_lot_cfg *, owner, NULL, ao2_cleanup);
680 const char *extension_registrar = ast_get_extension_registrar(extension);
681 const char *extension_context = ast_get_context_name(ast_get_extension_context(extension));
682 const char *extension_name = ast_get_extension_name(extension);
683 const char *extension_application = ast_get_extension_app(extension);
685 ast_assert(extension_registrar && extension_context && extension_name && extension_application);
687 if (strcmp(extension_registrar, BASE_REGISTRAR)) {
688 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs an extension '%s@%s', but that extension is already owned by %s.\n",
689 lot_cfg->name, extension_name, extension_context, extension_registrar);
693 if (strcmp(extension_application, app_type)) {
694 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs an extension '%s@%s' with a non-exclusive %s application, "
695 "but a/an %s application is already registered to that extension by %s.\n",
696 lot_cfg->name, extension_name, extension_context, app_type,
697 extension_application, BASE_REGISTRAR);
701 ast_debug(3, "Parking lot '%s' -- extension '%s@%s' with application %s is compatible.\n",
702 lot_cfg->name, extension_name, extension_context, app_type);
706 int parking_lot_cfg_create_extensions(struct parking_lot_cfg *lot_cfg)
709 struct ast_exten *existing_exten;
710 struct ast_context *lot_context;
711 struct pbx_find_info find_info = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
712 const char *parkext_registrar_pointer; /* Used for park extension */
713 const char *parkedcall_registrar_pointer; /* Used for parkedcall extensions/hints */
715 if (ast_strlen_zero(lot_cfg->parkext)) {
719 ast_string_field_build(lot_cfg, registrar, "%s/%s", BASE_REGISTRAR, lot_cfg->name);
720 parkedcall_registrar_pointer = lot_cfg->registrar;
722 if (lot_cfg->parkext_exclusive) {
723 parkext_registrar_pointer = lot_cfg->registrar;
725 parkext_registrar_pointer = BASE_REGISTRAR;
728 /* We need the contexts list locked to safely be able to both read and lock the specific context within */
729 if (ast_wrlock_contexts()) {
730 ast_log(LOG_ERROR, "Failed to lock the contexts list.\n");
734 if (!(lot_context = ast_context_find_or_create(NULL, NULL, lot_cfg->parking_con, parkext_registrar_pointer))) {
735 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs a context '%s' which does not exist and Asterisk was unable to create\n",
736 lot_cfg->name, lot_cfg->parking_con);
737 if (ast_unlock_contexts()) {
743 /* Once we know what context we will be modifying, we need to write lock it because we will be reading extensions
744 * and we don't want something else to destroy them while we are looking at them.
746 if (ast_wrlock_context(lot_context)) {
747 ast_log(LOG_ERROR, "failed to obtain write lock on context\n");
751 if (ast_unlock_contexts()) {
755 /* Handle generation/confirmation for the Park extension */
756 if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, lot_cfg->parkext, 1, NULL, NULL, E_MATCH))) {
757 if (lot_cfg->parkext_exclusive || !extension_is_compatible(lot_cfg, PARK_APPLICATION, existing_exten)) {
758 ast_unlock_context(lot_context);
761 } else if (parking_add_extension(lot_context, 0, lot_cfg->parkext, 1, PARK_APPLICATION,
762 lot_cfg->parkext_exclusive ? lot_cfg->name : "", parkext_registrar_pointer)) {
763 ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add %s extension '%s@%s' to the PBX.\n",
764 lot_cfg->name, PARK_APPLICATION, lot_cfg->parkext, lot_cfg->parking_con);
765 ast_unlock_context(lot_context);
769 /* Handle generation/confirmation for the ParkedCall extensions and hints */
770 for (parkingspace = lot_cfg->parking_start; parkingspace <= lot_cfg->parking_stop; parkingspace++) {
771 char space[AST_MAX_EXTENSION];
772 RAII_VAR(struct ast_str *, arguments_string, NULL, ast_free);
773 find_info.stacklen = 0; /* reset for pbx_find_exten */
775 snprintf(space, sizeof(space), "%d", parkingspace);
777 /* Unlike the Park extensions, ParkedCall extensions and their hints may never be shared for any reason. */
778 if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, space, 1, NULL, NULL, E_MATCH))) {
779 ast_unlock_context(lot_context);
783 arguments_string = ast_str_create(32);
784 if (!arguments_string) {
785 ast_unlock_context(lot_context);
789 ast_str_set(&arguments_string, 0, "%s,%s", lot_cfg->name, space);
790 if (parking_add_extension(lot_context, 0, space, 1, PARKED_CALL_APPLICATION,
791 ast_str_buffer(arguments_string), parkedcall_registrar_pointer)) {
792 ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add %s extension '%s@%s' to the PBX.\n",
793 lot_cfg->name, PARKED_CALL_APPLICATION, space, lot_cfg->parking_con);
794 ast_unlock_context(lot_context);
798 find_info.stacklen = 0; /* reset for pbx_find_exten */
800 if (lot_cfg->parkaddhints) {
801 char hint_device[AST_MAX_EXTENSION];
803 snprintf(hint_device, sizeof(hint_device), "park:%s@%s", space, lot_cfg->parking_con);
805 if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, space, PRIORITY_HINT, NULL, NULL, E_MATCH))) {
806 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs to add a hint '%s' at '%s@%s' but one already exists owned by %s\n",
807 lot_cfg->name, hint_device, space, lot_cfg->parking_con, ast_get_extension_registrar(existing_exten));
808 ast_unlock_context(lot_context);
812 if (parking_add_extension(lot_context, 0, space, PRIORITY_HINT, hint_device, "", parkedcall_registrar_pointer)) {
813 ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add hint '%s@%s' to the PBX.\n",
814 lot_cfg->name, space, lot_cfg->parking_con);
815 ast_unlock_context(lot_context);
821 if (ast_unlock_context(lot_context)) {
828 struct parking_lot *parking_lot_build_or_update(struct parking_lot_cfg *lot_cfg, int dynamic)
830 struct parking_lot *lot;
831 struct parking_lot_cfg *replaced_cfg = NULL;
834 /* Start by trying to find it. If that works we can skip the rest. */
835 lot = named_item_find(parking_lot_container, lot_cfg->name);
837 lot = alloc_new_parking_lot(lot_cfg);
839 /* If we still don't have a lot, we failed to alloc one. */
847 ast_log(LOG_ERROR, "Tried to create dynamic parking lot with name '%s' but a lot with that name already exists.\n", lot_cfg->name);
853 /* Set the configuration reference. Unref the one currently in the lot if it's there. */
855 replaced_cfg = lot->cfg;
858 ao2_ref(lot_cfg, +1);
861 ao2_cleanup(replaced_cfg);
863 /* Set the operating mode to normal since the parking lot has a configuration. */
864 lot->disable_mark = 0;
865 lot->mode = dynamic ? PARKINGLOT_DYNAMIC : PARKINGLOT_NORMAL;
868 /* Link after configuration is set since a lot without configuration will cause all kinds of trouble. */
869 ao2_link(parking_lot_container, lot);
875 static void generate_or_link_lots_to_configs(void)
877 RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
878 struct parking_lot_cfg *lot_cfg;
879 struct ao2_iterator iter;
881 for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
882 RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
883 lot = parking_lot_build_or_update(lot_cfg, 0);
886 ao2_iterator_destroy(&iter);
889 int parking_dynamic_lots_enabled(void)
891 RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
897 return cfg->global->parkeddynamic;
900 static struct parking_lot_cfg *clone_parkinglot_cfg(struct parking_lot_cfg *source, const char *name)
902 struct parking_lot_cfg *cfg = parking_lot_cfg_alloc(name);
908 ast_string_fields_copy(cfg, source);
910 /* Needs to be reset after being copied */
911 ast_string_field_set(cfg, name, name);
913 /* Stuff that should be cloned that isn't hit by string field copy */
914 cfg->parking_start = source->parking_start;
915 cfg->parking_stop = source->parking_stop;
916 cfg->parkingtime = source->parkingtime;
917 cfg->comebackdialtime = source->comebackdialtime;
918 cfg->parkfindnext = source->parkfindnext;
919 cfg->parkext_exclusive = source->parkext_exclusive;
920 cfg->parkaddhints = source->parkaddhints;
921 cfg->comebacktoorigin = source->comebacktoorigin;
922 cfg->parkedplay = source->parkedplay;
923 cfg->parkedcalltransfers = source->parkedcalltransfers;
924 cfg->parkedcallreparking = source->parkedcallreparking;
925 cfg->parkedcallhangup = source->parkedcallhangup;
926 cfg->parkedcallrecording = source->parkedcallrecording;
931 struct parking_lot *parking_create_dynamic_lot(const char *name, struct ast_channel *chan)
933 RAII_VAR(struct parking_lot_cfg *, cfg, NULL, ao2_cleanup);
934 RAII_VAR(struct parking_lot *, template_lot, NULL, ao2_cleanup);
936 struct parking_lot *lot;
937 const char *dyn_context;
938 const char *dyn_exten;
939 const char *dyn_range;
940 const char *template_name;
941 const char *chan_template_name;
945 if (!parking_dynamic_lots_enabled()) {
949 ast_channel_lock(chan);
950 chan_template_name = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNAMIC"), ""));
951 dyn_context = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNCONTEXT"), ""));
952 dyn_exten = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNEXTEN"), ""));
953 dyn_range = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNPOS"), ""));
954 ast_channel_unlock(chan);
956 template_name = S_OR(chan_template_name, DEFAULT_PARKING_LOT);
958 template_lot = parking_lot_find_by_name(template_name);
960 ast_log(LOG_ERROR, "Lot %s does not exist. Can not use it as a dynamic parking lot template.\n",
965 cfg = clone_parkinglot_cfg(template_lot->cfg, name);
968 ast_log(LOG_ERROR, "Failed to allocate dynamic parking lot configuration.\n");
972 if (!ast_strlen_zero(dyn_exten)) {
973 ast_string_field_set(cfg, parkext, dyn_exten);
976 if (!ast_strlen_zero(dyn_context)) {
977 ast_string_field_set(cfg, parking_con, dyn_context);
980 if (!ast_strlen_zero(dyn_range)) {
981 if (sscanf(dyn_range, "%30d-%30d", &dyn_start, &dyn_end) != 2) {
983 "Invalid parking range %s specified in PARKINGDYNPOS: could not parse minimum/maximum parking space range\n", dyn_range);
986 if (dyn_end < dyn_start || dyn_start < 0) {
988 "Invalid parking range %s specified for PARKINGDYNPOS: end parking space must be greater than starting parking space.\n", dyn_range);
992 cfg->parking_start = dyn_start;
993 cfg->parking_stop = dyn_end;
996 if (parking_lot_cfg_create_extensions(cfg)) {
997 ast_log(LOG_ERROR, "Extensions for dynamic parking lot '%s' could not be registered. Dynamic lot creation failed.\n", name);
1001 ao2_lock(parking_lot_container);
1003 if ((lot = parking_lot_find_by_name(name))) {
1004 ao2_unlock(parking_lot_container);
1005 ast_log(LOG_ERROR, "Started creating dynamic parking lot '%s', but a parking lot with that name already exists.\n", name);
1010 lot = parking_lot_build_or_update(cfg, 1);
1011 ao2_unlock(parking_lot_container);
1014 ast_log(LOG_NOTICE, "Failed to build dynamic parking lot '%s'\n", name);
1022 static int verify_default_parking_lot(void)
1024 struct parking_config *cfg = aco_pending_config(&cfg_info);
1025 RAII_VAR(struct parking_lot_cfg *, lot_cfg, NULL, ao2_cleanup);
1031 lot_cfg = ao2_find(cfg->parking_lots, DEFAULT_PARKING_LOT, OBJ_KEY);
1033 lot_cfg = parking_lot_cfg_alloc(DEFAULT_PARKING_LOT);
1037 ast_log(AST_LOG_NOTICE, "Adding %s profile to res_parking\n", DEFAULT_PARKING_LOT);
1038 aco_set_defaults(&parking_lot_type, DEFAULT_PARKING_LOT, lot_cfg);
1039 ast_string_field_set(lot_cfg, parkext, DEFAULT_PARKING_EXTEN);
1040 ao2_link(cfg->parking_lots, lot_cfg);
1046 static void remove_pending_parking_lot_extensions(struct parking_config *cfg_pending)
1048 struct parking_lot_cfg *lot_cfg;
1049 struct ao2_iterator iter;
1051 for (iter = ao2_iterator_init(cfg_pending->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
1052 parking_lot_cfg_remove_extensions(lot_cfg);
1055 ao2_iterator_destroy(&iter);
1057 ast_context_destroy(NULL, BASE_REGISTRAR);
1061 static int configure_parking_extensions(void)
1063 struct parking_config *cfg = aco_pending_config(&cfg_info);
1064 struct ao2_iterator iter;
1065 RAII_VAR(struct parking_lot_cfg *, lot_cfg, NULL, ao2_cleanup);
1072 /* Clear existing extensions */
1073 remove_all_configured_parking_lot_extensions();
1075 /* Attempt to build new extensions for each lot */
1076 for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
1077 if (parking_lot_cfg_create_extensions(lot_cfg)) {
1078 ao2_cleanup(lot_cfg);
1084 ao2_iterator_destroy(&iter);
1087 remove_pending_parking_lot_extensions(cfg);
1088 ast_log(LOG_ERROR, "Extension registration failed. Previously configured lot extensions were removed and can not be safely restored.\n");
1094 static void mark_lots_as_disabled(void)
1096 struct ao2_iterator iter;
1097 struct parking_lot *lot;
1099 for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
1100 lot->disable_mark = 1;
1103 ao2_iterator_destroy(&iter);
1106 static int config_parking_preapply(void)
1108 mark_lots_as_disabled();
1110 if (verify_default_parking_lot()) {
1114 if (configure_parking_extensions()) {
1121 static void disable_marked_lots(void)
1123 struct ao2_iterator iter;
1124 struct parking_lot *lot;
1126 for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
1127 if (lot->disable_mark) {
1128 parking_lot_disable(lot);
1132 ao2_iterator_destroy(&iter);
1135 static void link_configured_disable_marked_lots(void)
1137 generate_or_link_lots_to_configs();
1138 disable_marked_lots();
1141 static int load_module(void)
1143 if (aco_info_init(&cfg_info)) {
1147 parking_lot_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX,
1148 AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
1149 parking_lot_sort_fn,
1152 if (!parking_lot_container) {
1156 /* Global options */
1157 aco_option_register(&cfg_info, "parkeddynamic", ACO_EXACT, global_options, "no", OPT_BOOL_T, 1, FLDSET(struct parking_global_config, parkeddynamic));
1159 /* Register the per parking lot options. */
1160 aco_option_register(&cfg_info, "parkext", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parkext));
1161 aco_option_register(&cfg_info, "context", ACO_EXACT, parking_lot_types, "parkedcalls", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parking_con));
1162 aco_option_register(&cfg_info, "parkingtime", ACO_EXACT, parking_lot_types, "45", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, parkingtime));
1163 aco_option_register(&cfg_info, "comebacktoorigin", ACO_EXACT, parking_lot_types, "yes", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, comebacktoorigin));
1164 aco_option_register(&cfg_info, "comebackcontext", ACO_EXACT, parking_lot_types, "parkedcallstimeout", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, comebackcontext));
1165 aco_option_register(&cfg_info, "comebackdialtime", ACO_EXACT, parking_lot_types, "30", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, comebackdialtime));
1166 aco_option_register(&cfg_info, "parkedmusicclass", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, mohclass));
1167 aco_option_register(&cfg_info, "parkext_exclusive", ACO_EXACT, parking_lot_types, "no", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, parkext_exclusive));
1168 aco_option_register(&cfg_info, "parkinghints", ACO_EXACT, parking_lot_types, "no", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, parkaddhints));
1169 aco_option_register(&cfg_info, "courtesytone", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, courtesytone));
1171 /* More complicated parking lot options that require special handling */
1172 aco_option_register_custom(&cfg_info, "parkpos", ACO_EXACT, parking_lot_types, "701-750", option_handler_parkpos, 0);
1173 aco_option_register_custom(&cfg_info, "findslot", ACO_EXACT, parking_lot_types, "first", option_handler_findslot, 0);
1174 aco_option_register_custom(&cfg_info, "parkedplay", ACO_EXACT, parking_lot_types, "caller", option_handler_parkedfeature, OPT_PARKEDPLAY);
1175 aco_option_register_custom(&cfg_info, "parkedcalltransfers", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDTRANSFERS);
1176 aco_option_register_custom(&cfg_info, "parkedcallreparking", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDREPARKING);
1177 aco_option_register_custom(&cfg_info, "parkedcallhangup", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDHANGUP);
1178 aco_option_register_custom(&cfg_info, "parkedcallrecording", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDRECORDING);
1180 if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
1184 if (ast_register_application_xml(PARK_APPLICATION, park_app_exec)) {
1188 if (ast_register_application_xml(PARKED_CALL_APPLICATION, parked_call_app_exec)) {
1192 if (ast_register_application_xml(PARK_AND_ANNOUNCE_APPLICATION, park_and_announce_app_exec)) {
1196 if (load_parking_ui()) {
1200 if (load_parking_manager()) {
1204 if (load_parking_bridge_features()) {
1208 if (load_parking_devstate()) {
1212 return AST_MODULE_LOAD_SUCCESS;
1215 ao2_cleanup(parking_lot_container);
1216 aco_info_destroy(&cfg_info);
1217 return AST_MODULE_LOAD_DECLINE;
1220 static int reload_module(void)
1222 if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
1223 return AST_MODULE_LOAD_DECLINE;
1229 static int unload_module(void)
1231 /* XXX Parking is currently not unloadable due to the fact that it loads features which could cause
1232 * significant problems if they disappeared while a channel still had access to them.
1236 /* TODO Things we will need to do here:
1238 * destroy existing parking lots
1239 * uninstall parking related bridge features
1240 * remove extensions owned by the parking registrar
1244 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Call Parking Resource",
1245 .load = load_module,
1246 .unload = unload_module,
1247 .reload = reload_module,