668ed63a4dcf4d3270ca40ff782cad6dd50e7299
[asterisk/asterisk.git] / res / res_parking.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Jonathan Rose <jrose@digium.com>
7  *
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.
13  *
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.
17  */
18
19 /*! \file
20  *
21  * \brief Call Parking Resource
22  *
23  * \author Jonathan Rose <jrose@digium.com>
24  */
25
26 /*** MODULEINFO
27         <depend>bridge_holding</depend>
28         <support_level>core</support_level>
29  ***/
30
31 /*** DOCUMENTATION
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>
36                         </configObject>
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>
42                                 </configOption>
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>
50                                 </configOption>
51                                 <configOption name="parkext_exclusive" default="no">
52                                         <synopsis>If yes, the extension registered as parkext will park exclusively to this parking lot.</synopsis>
53                                 </configOption>
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>
58                                 </configOption>
59                                 <configOption name="parkinghints" default="no">
60                                         <synopsis>If yes, this parking lot will add hints automatically for parking spaces.</synopsis>
61                                 </configOption>
62                                 <configOption name="parkingtime" default="45">
63                                         <synopsis>Amount of time a call will remain parked before giving up (in seconds).</synopsis>
64                                 </configOption>
65                                 <configOption name="parkedmusicclass">
66                                         <synopsis>Which music class to use for parked calls. They will use the default if unspecified.</synopsis>
67                                 </configOption>
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>
71                                                 <enumlist>
72                                                         <enum name="yes">
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>
81                                                         </enum>
82                                                         <enum name="no">
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
88                                                                         peer name.</para>
89                                                         </enum>
90                                                 </enumlist>
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:
96                                                 </para></note>
97                                                 <variablelist>
98                                                         <variable name="PARKINGSLOT">
99                                                                 <para>extension that the call was parked in prior to timing out.</para>
100                                                         </variable>
101                                                         <variable name="PARKEDLOT">
102                                                                 <para>name of the lot that the call was parked in prior to timing out.</para>
103                                                         </variable>
104                                                         <variable name="PARKER">
105                                                                 <para>The device that parked the call</para>
106                                                         </variable>
107                                                 </variablelist>
108                                         </description>
109                                 </configOption>
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>
112                                 </configOption>
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>
118                                         </description>
119                                 </configOption>
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>
124                                         </description>
125                                 </configOption>
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>
128                                         <description>
129                                                 <enumlist>
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>
134                                                 </enumlist>
135                                                 <note><para>If courtesy tone is not specified then this option will be ignored.</para></note>
136                                         </description>
137                                 </configOption>
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>
140                                         <description>
141                                                 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
142                                         </description>
143                                 </configOption>
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>
146                                         <description>
147                                                 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
148                                         </description>
149                                 </configOption>
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>
152                                         <description>
153                                                 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
154                                         </description>
155                                 </configOption>
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>
158                                         <description>
159                                                 <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
160                                         </description>
161                                 </configOption>
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>
164                                         <description>
165                                                 <enumlist>
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.
168                                                         </para></enum>
169                                                 </enumlist>
170                                         </description>
171                                 </configOption>
172                                 <configOption name="courtesytone">
173                                         <synopsis>If set, the sound set will be played to whomever is set by parkedplay</synopsis>
174                                 </configOption>
175                         </configObject>
176                 </configFile>
177         </configInfo>
178  ***/
179
180 #include "asterisk.h"
181
182 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
183
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"
195
196 #define PARKED_CALL_APPLICATION "ParkedCall"
197 #define PARK_AND_ANNOUNCE_APPLICATION "ParkAndAnnounce"
198
199 /* TODO Add unit tests for parking */
200
201 static int parking_lot_sort_fn(const void *obj_left, const void *obj_right, int flags)
202 {
203         const struct parking_lot *left = obj_left;
204         const struct parking_lot *right = obj_right;
205         const char *right_key = obj_right;
206         int cmp;
207
208         switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
209         default:
210         case OBJ_POINTER:
211                 right_key = right->name;
212                 /* Fall through */
213         case OBJ_KEY:
214                 cmp = strcmp(left->name, right_key);
215                 break;
216         case OBJ_PARTIAL_KEY:
217                 cmp = strncmp(left->name, right_key, strlen(right_key));
218         }
219         return cmp;
220 }
221
222 /*! All parking lots that are currently alive in some fashion can be obtained from here */
223 static struct ao2_container *parking_lot_container;
224
225 static void *parking_config_alloc(void);
226
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? */
229
230 static int config_parking_preapply(void);
231 static void link_configured_disable_marked_lots(void);
232
233 struct parking_global_config {
234         int parkeddynamic;
235 };
236
237 struct parking_config {
238         struct parking_global_config *global;
239         struct ao2_container *parking_lots;
240 };
241
242 static struct aco_type global_option = {
243         .type = ACO_GLOBAL,
244         .name = "globals",
245         .item_offset = offsetof(struct parking_config, global),
246         .category_match = ACO_WHITELIST,
247         .category = "^general$",
248 };
249
250 struct aco_type *global_options[] = ACO_TYPES(&global_option);
251
252 static struct aco_type parking_lot_type = {
253         .type = ACO_ITEM,
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),
260 };
261
262 struct aco_type *parking_lot_types[] = ACO_TYPES(&parking_lot_type);
263
264 struct aco_file parking_lot_conf = {
265         .filename = "res_parking.conf",
266         .types = ACO_TYPES(&global_option, &parking_lot_type),
267 };
268
269 static AO2_GLOBAL_OBJ_STATIC(globals);
270
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,
275 );
276
277 static int parking_lot_cfg_hash_fn(const void *obj, const int flags)
278 {
279         const struct parking_lot_cfg *entry;
280         const char *key;
281
282         switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
283         case OBJ_KEY:
284                 key = obj;
285                 return ast_str_hash(key);
286         case OBJ_PARTIAL_KEY:
287                 ast_assert(0);
288                 return 0;
289         default:
290                 entry = obj;
291                 return ast_str_hash(entry->name);
292         }
293 }
294
295 static int parking_lot_cfg_cmp_fn(void *obj, void *arg, const int flags)
296 {
297         struct parking_lot_cfg *entry1 = obj;
298
299         char *key;
300         size_t key_size;
301         struct parking_lot_cfg *entry2;
302
303         switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
304         case OBJ_KEY:
305                 key = arg;
306                 return (!strcmp(entry1->name, key)) ? CMP_MATCH : 0;
307         case OBJ_PARTIAL_KEY:
308                 key = arg;
309                 key_size = strlen(key);
310                 return (!strncmp(entry1->name, key, key_size)) ? CMP_MATCH : 0;
311         case OBJ_POINTER:
312                 entry2 = arg;
313                 return (!strcmp(entry1->name, entry2->name)) ? CMP_MATCH : 0;
314         default:
315                 return CMP_STOP;
316         }
317 }
318
319 /*! \brief destructor for parking_config */
320 static void parking_config_destructor(void *obj)
321 {
322         struct parking_config *cfg = obj;
323         ao2_cleanup(cfg->parking_lots);
324         ao2_cleanup(cfg->global);
325 }
326
327 /*! \brief destructor for parking_global_config */
328 static void parking_global_config_destructor(void *obj)
329 {
330         /* For now, do nothing. */
331 }
332
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)
335 {
336         RAII_VAR(struct parking_config *, cfg, NULL, ao2_cleanup);
337
338         if (!(cfg = ao2_alloc(sizeof(*cfg), parking_config_destructor))) {
339                 return NULL;
340         }
341
342         if (!(cfg->parking_lots = ao2_container_alloc(37, parking_lot_cfg_hash_fn, parking_lot_cfg_cmp_fn))) {
343                 return NULL;
344         }
345
346         if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), parking_global_config_destructor))) {
347                 return NULL;
348         }
349
350         /* Bump the ref count since RAII_VAR is going to eat one */
351         ao2_ref(cfg, +1);
352         return cfg;
353 }
354
355 int parking_lot_remove_if_unused(struct parking_lot *lot)
356 {
357         if (lot->mode != PARKINGLOT_DISABLED) {
358                 return -1;
359         }
360
361         if (!ao2_container_count(lot->parked_users)) {
362                 ao2_unlink(parking_lot_container, lot);
363                 return 0;
364         }
365
366         return -1;
367 }
368
369 static void parking_lot_disable(struct parking_lot *lot)
370 {
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);
373
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;
378         }
379 }
380
381 /*! \brief Destroy a parking lot cfg object */
382 static void parking_lot_cfg_destructor(void *obj)
383 {
384         struct parking_lot_cfg *lot_cfg = obj;
385         parking_lot_cfg_remove_extensions(lot_cfg);
386         ast_string_field_free_memory(lot_cfg);
387 }
388
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)
391 {
392         int *search_space = arg;
393         struct parked_user *user = obj;
394         int object_space = user->parking_space;
395
396         if (*search_space == object_space) {
397                 return CMP_MATCH;
398         }
399         return 0;
400 }
401
402 static int parked_user_sort_fn(const void *obj_left, const void *obj_right, int flags)
403 {
404         const struct parked_user *left = obj_left;
405         const struct parked_user *right = obj_right;
406
407         return left->parking_space - right->parking_space;
408 }
409
410 /*!
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
415  */
416 static void *parking_lot_cfg_alloc(const char *cat)
417 {
418         struct parking_lot_cfg *lot_cfg;
419
420         lot_cfg = ao2_alloc(sizeof(*lot_cfg), parking_lot_cfg_destructor);
421         if (!lot_cfg) {
422                 return NULL;
423         }
424
425         if (ast_string_field_init(lot_cfg, 32)) {
426                 ao2_cleanup(lot_cfg);
427                 return NULL;
428         }
429
430         ast_string_field_set(lot_cfg, name, cat);
431
432         return lot_cfg;
433 }
434
435 #if defined(TEST_FRAMEWORK)
436 struct parking_lot_cfg *parking_lot_cfg_create(const char *cat)
437 {
438         return parking_lot_cfg_alloc(cat);
439 }
440 #endif
441
442 /*!
443  * XXX This is actually incredibly generic and might be better placed in something like astobj2 if there isn't already an equivalent
444  * \brief find an item in a container by its name
445  *
446  * \param container ao2container where we want the item from
447  * \param key name of the item wanted to be found
448  *
449  * \retval pointer to the parking lot if available. NULL if not found.
450  */
451 static void *named_item_find(struct ao2_container *container, const char *name)
452 {
453         return ao2_find(container, name, OBJ_KEY);
454 }
455
456 /*!
457  * \brief Custom field handler for parking positions
458  */
459 static int option_handler_parkpos(const struct aco_option *opt, struct ast_variable *var, void *obj)
460 {
461         struct parking_lot_cfg *lot_cfg = obj;
462         int low;
463         int high;
464
465         if (sscanf(var->value, "%30d-%30d", &low, &high) != 2) {
466                 ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers\n");
467         } else if (high < low || low <= 0 || high <= 0) {
468                 ast_log(LOG_WARNING, "Format for parking positions is a-b, where a <= b\n");
469         } else {
470                 lot_cfg->parking_start = low;
471                 lot_cfg->parking_stop = high;
472                 return 0;
473         }
474         return -1;
475 }
476
477 /*!
478  * \brief Custom field handler for the findslot option
479  */
480 static int option_handler_findslot(const struct aco_option *opt, struct ast_variable *var, void *obj)
481 {
482         struct parking_lot_cfg *lot_cfg = obj;
483
484         if (!strcmp(var->value, "first")) {
485                 lot_cfg->parkfindnext = 0;
486         } else if (!strcmp(var->value, "next")) {
487                 lot_cfg->parkfindnext = 1;
488         } else {
489                 ast_log(LOG_WARNING, "value '%s' is not valid for findslot option.\n", var->value);
490                 return -1;
491         }
492
493         return 0;
494 }
495
496 /*!
497  * \brief Maps string values for option_handler_parkedfeature to their ENUM values
498  */
499 static int parking_feature_flag_cfg(int *param, const char *var)
500 {
501         if (ast_false(var)) {
502                 *param = 0;
503         } else if (!strcasecmp(var, "both")) {
504                 *param = AST_FEATURE_FLAG_BYBOTH;
505         } else if (!strcasecmp(var, "caller")) {
506                 *param = AST_FEATURE_FLAG_BYCALLER;
507         } else if (!strcasecmp(var, "callee")) {
508                 *param = AST_FEATURE_FLAG_BYCALLEE;
509         } else {
510                 return -1;
511         }
512
513         return 0;
514 }
515
516 /*!
517  * \brief Custom field handler for feature mapping on parked call pickup options
518  */
519 static int option_handler_parkedfeature(const struct aco_option *opt, struct ast_variable *var, void *obj)
520 {
521         struct parking_lot_cfg *cfg = obj;
522         enum parked_call_feature_options option = aco_option_get_flags(opt);
523         int *parameter = NULL;
524
525         switch (option) {
526         case OPT_PARKEDPLAY:
527                 parameter = &cfg->parkedplay;
528                 break;
529         case OPT_PARKEDTRANSFERS:
530                 parameter = &cfg->parkedcalltransfers;
531                 break;
532         case OPT_PARKEDREPARKING:
533                 parameter = &cfg->parkedcallreparking;
534                 break;
535         case OPT_PARKEDHANGUP:
536                 parameter = &cfg->parkedcallhangup;
537                 break;
538         case OPT_PARKEDRECORDING:
539                 parameter = &cfg->parkedcallrecording;
540                 break;
541         }
542
543         ast_assert(parameter != NULL);
544         if (!parameter || parking_feature_flag_cfg(parameter, var->value)) {
545                 return -1;
546         }
547
548         return 0;
549 }
550
551 struct ao2_container *get_parking_lot_container(void)
552 {
553         return parking_lot_container;
554 }
555
556 struct parking_lot *parking_lot_find_by_name(const char *lot_name)
557 {
558         struct parking_lot *lot = named_item_find(parking_lot_container, lot_name);
559         return lot;
560 }
561
562 const char *find_channel_parking_lot_name(struct ast_channel *chan)
563 {
564         const char *name;
565
566         /* The channel variable overrides everything */
567         name = pbx_builtin_getvar_helper(chan, "PARKINGLOT");
568         if (ast_strlen_zero(name) && !ast_strlen_zero(ast_channel_parkinglot(chan))) {
569                 /* Use the channel's parking lot. */
570                 name = ast_channel_parkinglot(chan);
571         }
572
573         /* If the name couldn't be pulled from that either, use the default parking lot name. */
574         if (ast_strlen_zero(name)) {
575                 name = DEFAULT_PARKING_LOT;
576         }
577
578         return name;
579 }
580
581 static void parking_lot_destructor(void *obj)
582 {
583         struct parking_lot *lot = obj;
584
585         if (lot->parking_bridge) {
586                 ast_bridge_destroy(lot->parking_bridge);
587         }
588         ao2_cleanup(lot->parked_users);
589         ao2_cleanup(lot->cfg);
590         ast_string_field_free_memory(lot);
591 }
592
593 static struct parking_lot *alloc_new_parking_lot(struct parking_lot_cfg *lot_cfg)
594 {
595         struct parking_lot *lot;
596         if (!(lot = ao2_alloc(sizeof(*lot), parking_lot_destructor))) {
597                 return NULL;
598         }
599
600         if (ast_string_field_init(lot, 32)) {
601                 return NULL;
602         }
603
604         /* Create parked user ordered list */
605         lot->parked_users = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_RWLOCK,
606                 AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
607                 parked_user_sort_fn,
608                 parked_user_cmp_fn);
609
610         if (!lot->parked_users) {
611                 ao2_cleanup(lot);
612                 return NULL;
613         }
614
615         ast_string_field_set(lot, name, lot_cfg->name);
616         return lot;
617 }
618
619 void parking_lot_cfg_remove_extensions(struct parking_lot_cfg *lot_cfg)
620 {
621         if (!ast_strlen_zero(lot_cfg->registrar)) {
622                 /* Although the function is called ast_context_destroy, the use of this funtion is
623                  * intended only to remove extensions, hints, etc registered by the parking lot's registrar.
624                  * It won't actually destroy the context unless that context is empty afterwards and it is
625                  * unreferenced.
626                  */
627                 ast_context_destroy(NULL, lot_cfg->registrar);
628         }
629 }
630
631 static void remove_all_configured_parking_lot_extensions(void)
632 {
633         RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
634         struct parking_lot_cfg *lot_cfg;
635         struct ao2_iterator iter;
636
637         if (!cfg) {
638                 return;
639         }
640
641         for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
642                 parking_lot_cfg_remove_extensions(lot_cfg);
643         }
644
645         ast_context_destroy(NULL, BASE_REGISTRAR);
646
647         ao2_iterator_destroy(&iter);
648 }
649
650 /*!
651  * \internal
652  * \since 12
653  * \brief Create an extension using ast_add_extension2_nolock. This function automatically allocates a duplicate
654  *        of the data string so that whatever calls it doesn't have to deal with freeing it if the ast_add_extension2_nolock
655  *        fails.
656  *
657  * \param context a write locked ast_context. Make certain it is write locked prior to calling this function
658  * \param replace whether the extension should replace existing extensions
659  * \param extension name of the extension desired
660  * \param priority priority of the extension we are registering
661  * \param application name of the application being used for the extension
662  * \param data application arguments
663  * \param registrar name of the registrar you should use for the extension.
664  *        Make sure this string doesn't go anywhere while there are still extensions using it.
665  */
666 static int parking_add_extension(struct ast_context *context, int replace, const char *extension,
667         int priority, const char *application, const char *data, const char *registrar)
668 {
669         char *data_duplicate = ast_strdup(data);
670
671         if (!data_duplicate) {
672                 return -1;
673         }
674
675         if (ast_add_extension2_nolock(context, replace, extension, priority, NULL, NULL,
676                         application, data_duplicate, ast_free_ptr, registrar)) {
677                 ast_free(data_duplicate);
678                 return -1;
679         }
680
681         return 0;
682 }
683
684 static int extension_is_compatible(struct parking_lot_cfg *lot_cfg, const char *app_type, struct ast_exten *extension)
685 {
686         RAII_VAR(struct parking_lot_cfg *, owner, NULL, ao2_cleanup);
687         const char *extension_registrar = ast_get_extension_registrar(extension);
688         const char *extension_context = ast_get_context_name(ast_get_extension_context(extension));
689         const char *extension_name = ast_get_extension_name(extension);
690         const char *extension_application = ast_get_extension_app(extension);
691
692         ast_assert(extension_registrar && extension_context && extension_name && extension_application);
693
694         if (strcmp(extension_registrar, BASE_REGISTRAR)) {
695                 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs an extension '%s@%s', but that extension is already owned by %s.\n",
696                         lot_cfg->name, extension_name, extension_context, extension_registrar);
697                 return 0;
698         }
699
700         if (strcmp(extension_application, app_type)) {
701                 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs an extension '%s@%s' with a non-exclusive %s application, "
702                         "but a/an %s application is already registered to that extension by %s.\n",
703                         lot_cfg->name, extension_name, extension_context, app_type,
704                         extension_application, BASE_REGISTRAR);
705                 return 0;
706         }
707
708         ast_debug(3, "Parking lot '%s' -- extension '%s@%s' with application %s is compatible.\n",
709                   lot_cfg->name, extension_name, extension_context, app_type);
710         return 1;
711 }
712
713 int parking_lot_cfg_create_extensions(struct parking_lot_cfg *lot_cfg)
714 {
715         int parkingspace;
716         struct ast_exten *existing_exten;
717         struct ast_context *lot_context;
718         struct pbx_find_info find_info = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
719         const char *parkext_registrar_pointer; /* Used for park extension */
720         const char *parkedcall_registrar_pointer; /* Used for parkedcall extensions/hints */
721
722         if (ast_strlen_zero(lot_cfg->parkext)) {
723                 return 0;
724         }
725
726         ast_string_field_build(lot_cfg, registrar, "%s/%s", BASE_REGISTRAR, lot_cfg->name);
727         parkedcall_registrar_pointer = lot_cfg->registrar;
728
729         if (lot_cfg->parkext_exclusive) {
730                 parkext_registrar_pointer = lot_cfg->registrar;
731         } else {
732                 parkext_registrar_pointer = BASE_REGISTRAR;
733         }
734
735         /* We need the contexts list locked to safely be able to both read and lock the specific context within */
736         if (ast_wrlock_contexts()) {
737                 ast_log(LOG_ERROR, "Failed to lock the contexts list.\n");
738                 return -1;
739         }
740
741         if (!(lot_context = ast_context_find_or_create(NULL, NULL, lot_cfg->parking_con, parkext_registrar_pointer))) {
742                 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs a context '%s' which does not exist and Asterisk was unable to create\n",
743                         lot_cfg->name, lot_cfg->parking_con);
744                 if (ast_unlock_contexts()) {
745                         ast_assert(0);
746                 }
747                 return -1;
748         }
749
750         /* Once we know what context we will be modifying, we need to write lock it because we will be reading extensions
751          * and we don't want something else to destroy them while we are looking at them.
752          */
753         if (ast_wrlock_context(lot_context)) {
754                 ast_log(LOG_ERROR, "failed to obtain write lock on context\n");
755                 return -1;
756         }
757
758         if (ast_unlock_contexts()) {
759                 ast_assert(0);
760         }
761
762         /* Handle generation/confirmation for the Park extension */
763         if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, lot_cfg->parkext, 1, NULL, NULL, E_MATCH))) {
764                 if (lot_cfg->parkext_exclusive || !extension_is_compatible(lot_cfg, PARK_APPLICATION, existing_exten)) {
765                         ast_unlock_context(lot_context);
766                         return -1;
767                 }
768         } else if (parking_add_extension(lot_context, 0, lot_cfg->parkext, 1, PARK_APPLICATION,
769                    lot_cfg->parkext_exclusive ? lot_cfg->name : "", parkext_registrar_pointer)) {
770                 ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add %s extension '%s@%s' to the PBX.\n",
771                         lot_cfg->name, PARK_APPLICATION, lot_cfg->parkext, lot_cfg->parking_con);
772                 ast_unlock_context(lot_context);
773                 return -1;
774         }
775
776         /* Handle generation/confirmation for the ParkedCall extensions and hints */
777         for (parkingspace = lot_cfg->parking_start; parkingspace <= lot_cfg->parking_stop; parkingspace++) {
778                 char space[AST_MAX_EXTENSION];
779                 RAII_VAR(struct ast_str *, arguments_string, NULL, ast_free);
780                 find_info.stacklen = 0; /* reset for pbx_find_exten */
781
782                 snprintf(space, sizeof(space), "%d", parkingspace);
783
784                 /* Unlike the Park extensions, ParkedCall extensions and their hints may never be shared for any reason. */
785                 if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, space, 1, NULL, NULL, E_MATCH))) {
786                         ast_unlock_context(lot_context);
787                         return -1;
788                 }
789
790                 arguments_string = ast_str_create(32);
791                 if (!arguments_string) {
792                         ast_unlock_context(lot_context);
793                         return -1;
794                 }
795
796                 ast_str_set(&arguments_string, 0, "%s,%s", lot_cfg->name, space);
797                 if (parking_add_extension(lot_context, 0, space, 1, PARKED_CALL_APPLICATION,
798                     ast_str_buffer(arguments_string), parkedcall_registrar_pointer)) {
799                         ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add %s extension '%s@%s' to the PBX.\n",
800                                 lot_cfg->name, PARKED_CALL_APPLICATION, space, lot_cfg->parking_con);
801                         ast_unlock_context(lot_context);
802                         return -1;
803                 }
804
805                 find_info.stacklen = 0; /* reset for pbx_find_exten */
806
807                 if (lot_cfg->parkaddhints) {
808                         char hint_device[AST_MAX_EXTENSION];
809
810                         snprintf(hint_device, sizeof(hint_device), "park:%s@%s", space, lot_cfg->parking_con);
811
812                         if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, space, PRIORITY_HINT, NULL, NULL, E_MATCH))) {
813                                 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs to add a hint '%s' at '%s@%s' but one already exists owned by %s\n",
814                                 lot_cfg->name, hint_device, space, lot_cfg->parking_con, ast_get_extension_registrar(existing_exten));
815                                         ast_unlock_context(lot_context);
816                                         return -1;
817                         }
818
819                         if (parking_add_extension(lot_context, 0, space, PRIORITY_HINT, hint_device, "", parkedcall_registrar_pointer)) {
820                                 ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add hint '%s@%s' to the PBX.\n",
821                                         lot_cfg->name, space, lot_cfg->parking_con);
822                                 ast_unlock_context(lot_context);
823                                 return -1;
824                         }
825                 }
826         }
827
828         if (ast_unlock_context(lot_context)) {
829                 ast_assert(0);
830         }
831
832         return 0;
833 }
834
835 struct parking_lot *parking_lot_build_or_update(struct parking_lot_cfg *lot_cfg, int dynamic)
836 {
837         struct parking_lot *lot;
838         struct parking_lot_cfg *replaced_cfg = NULL;
839         int found = 0;
840
841         /* Start by trying to find it. If that works we can skip the rest. */
842         lot = named_item_find(parking_lot_container, lot_cfg->name);
843         if (!lot) {
844                 lot = alloc_new_parking_lot(lot_cfg);
845
846                 /* If we still don't have a lot, we failed to alloc one. */
847                 if (!lot) {
848                         return NULL;
849                 }
850         } else {
851                 found = 1;
852
853                 if (dynamic) {
854                         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);
855                         ao2_cleanup(lot);
856                         return NULL;
857                 }
858         }
859
860         /* Set the configuration reference. Unref the one currently in the lot if it's there. */
861         if (lot->cfg) {
862                 replaced_cfg = lot->cfg;
863         }
864
865         ao2_ref(lot_cfg, +1);
866         lot->cfg = lot_cfg;
867
868         ao2_cleanup(replaced_cfg);
869
870         /* Set the operating mode to normal since the parking lot has a configuration. */
871         lot->disable_mark = 0;
872         lot->mode = dynamic ? PARKINGLOT_DYNAMIC : PARKINGLOT_NORMAL;
873
874         if (!found) {
875                 /* Link after configuration is set since a lot without configuration will cause all kinds of trouble. */
876                 ao2_link(parking_lot_container, lot);
877         };
878
879         return lot;
880 }
881
882 static void generate_or_link_lots_to_configs(void)
883 {
884         RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
885         struct parking_lot_cfg *lot_cfg;
886         struct ao2_iterator iter;
887
888         for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
889                 RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
890                 lot = parking_lot_build_or_update(lot_cfg, 0);
891         }
892
893         ao2_iterator_destroy(&iter);
894 }
895
896 int parking_dynamic_lots_enabled(void)
897 {
898         RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
899
900         if (!cfg) {
901                 return 0;
902         }
903
904         return cfg->global->parkeddynamic;
905 }
906
907 static struct parking_lot_cfg *clone_parkinglot_cfg(struct parking_lot_cfg *source, const char *name)
908 {
909         struct parking_lot_cfg *cfg = parking_lot_cfg_alloc(name);
910
911         if (!cfg) {
912                 return NULL;
913         }
914
915         ast_string_fields_copy(cfg, source);
916
917         /* Needs to be reset after being copied */
918         ast_string_field_set(cfg, name, name);
919
920         /* Stuff that should be cloned that isn't hit by string field copy */
921         cfg->parking_start = source->parking_start;
922         cfg->parking_stop = source->parking_stop;
923         cfg->parkingtime = source->parkingtime;
924         cfg->comebackdialtime = source->comebackdialtime;
925         cfg->parkfindnext = source->parkfindnext;
926         cfg->parkext_exclusive = source->parkext_exclusive;
927         cfg->parkaddhints = source->parkaddhints;
928         cfg->comebacktoorigin = source->comebacktoorigin;
929         cfg->parkedplay = source->parkedplay;
930         cfg->parkedcalltransfers = source->parkedcalltransfers;
931         cfg->parkedcallreparking = source->parkedcallreparking;
932         cfg->parkedcallhangup = source->parkedcallhangup;
933         cfg->parkedcallrecording = source->parkedcallrecording;
934
935         return cfg;
936 }
937
938 static struct parking_lot *create_dynamic_lot_full(const char *name, struct ast_channel *chan, int forced)
939 {
940         RAII_VAR(struct parking_lot_cfg *, cfg, NULL, ao2_cleanup);
941         RAII_VAR(struct parking_lot *, template_lot, NULL, ao2_cleanup);
942
943         struct parking_lot *lot;
944         const char *dyn_context;
945         const char *dyn_exten;
946         const char *dyn_range;
947         const char *template_name;
948         const char *chan_template_name;
949         int dyn_start;
950         int dyn_end;
951
952         if (!forced && !parking_dynamic_lots_enabled()) {
953                 return NULL;
954         }
955
956         ast_channel_lock(chan);
957         chan_template_name = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNAMIC"), ""));
958         dyn_context = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNCONTEXT"), ""));
959         dyn_exten = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNEXTEN"), ""));
960         dyn_range = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "PARKINGDYNPOS"), ""));
961         ast_channel_unlock(chan);
962
963         template_name = S_OR(chan_template_name, DEFAULT_PARKING_LOT);
964
965         template_lot = parking_lot_find_by_name(template_name);
966         if (!template_lot) {
967                 ast_log(LOG_ERROR, "Lot %s does not exist. Can not use it as a dynamic parking lot template.\n",
968                         template_name);
969                 return NULL;
970         }
971
972         cfg = clone_parkinglot_cfg(template_lot->cfg, name);
973
974         if (!cfg) {
975                 ast_log(LOG_ERROR, "Failed to allocate dynamic parking lot configuration.\n");
976                 return NULL;
977         }
978
979         if (!ast_strlen_zero(dyn_exten)) {
980                 ast_string_field_set(cfg, parkext, dyn_exten);
981         }
982
983         if (!ast_strlen_zero(dyn_context)) {
984                 ast_string_field_set(cfg, parking_con, dyn_context);
985         }
986
987         if (!ast_strlen_zero(dyn_range)) {
988                 if (sscanf(dyn_range, "%30d-%30d", &dyn_start, &dyn_end) != 2) {
989                         ast_log(LOG_ERROR,
990                                 "Invalid parking range %s specified in PARKINGDYNPOS: could not parse minimum/maximum parking space range\n", dyn_range);
991                                 return NULL;
992                 }
993                 if (dyn_end < dyn_start || dyn_start < 0) {
994                         ast_log(LOG_ERROR,
995                                 "Invalid parking range %s specified for PARKINGDYNPOS: end parking space must be greater than starting parking space.\n", dyn_range);
996                                 return NULL;
997                 }
998
999                 cfg->parking_start = dyn_start;
1000                 cfg->parking_stop = dyn_end;
1001         }
1002
1003         if (parking_lot_cfg_create_extensions(cfg)) {
1004                 ast_log(LOG_ERROR, "Extensions for dynamic parking lot '%s' could not be registered. Dynamic lot creation failed.\n", name);
1005                 return NULL;
1006         }
1007
1008         ao2_lock(parking_lot_container);
1009
1010         if ((lot = parking_lot_find_by_name(name))) {
1011                 ao2_unlock(parking_lot_container);
1012                 ast_log(LOG_ERROR, "Started creating dynamic parking lot '%s', but a parking lot with that name already exists.\n", name);
1013                 ao2_ref(lot, -1);
1014                 return NULL;
1015         }
1016
1017         lot = parking_lot_build_or_update(cfg, 1);
1018         ao2_unlock(parking_lot_container);
1019
1020         if (!lot) {
1021                 ast_log(LOG_NOTICE, "Failed to build dynamic parking lot '%s'\n", name);
1022         }
1023
1024         return lot;
1025 }
1026
1027 struct parking_lot *parking_create_dynamic_lot(const char *name, struct ast_channel *chan){
1028         return create_dynamic_lot_full(name, chan, 0);
1029 }
1030
1031 #if defined(TEST_FRAMEWORK)
1032 struct parking_lot *parking_create_dynamic_lot_forced(const char *name, struct ast_channel *chan) {
1033         return create_dynamic_lot_full(name, chan, 1);
1034 }
1035 #endif
1036
1037 /* Preapply */
1038
1039 static int verify_default_parking_lot(void)
1040 {
1041         struct parking_config *cfg = aco_pending_config(&cfg_info);
1042         RAII_VAR(struct parking_lot_cfg *, lot_cfg, NULL, ao2_cleanup);
1043
1044         if (!cfg) {
1045                 return 0;
1046         }
1047
1048         lot_cfg = ao2_find(cfg->parking_lots, DEFAULT_PARKING_LOT, OBJ_KEY);
1049         if (!lot_cfg) {
1050                 lot_cfg = parking_lot_cfg_alloc(DEFAULT_PARKING_LOT);
1051                 if (!lot_cfg) {
1052                         return -1;
1053                 }
1054                 ast_log(AST_LOG_NOTICE, "Adding %s profile to res_parking\n", DEFAULT_PARKING_LOT);
1055                 aco_set_defaults(&parking_lot_type, DEFAULT_PARKING_LOT, lot_cfg);
1056                 ast_string_field_set(lot_cfg, parkext, DEFAULT_PARKING_EXTEN);
1057                 ao2_link(cfg->parking_lots, lot_cfg);
1058         }
1059
1060         return 0;
1061 }
1062
1063 static void remove_pending_parking_lot_extensions(struct parking_config *cfg_pending)
1064 {
1065         struct parking_lot_cfg *lot_cfg;
1066         struct ao2_iterator iter;
1067
1068         for (iter = ao2_iterator_init(cfg_pending->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
1069                 parking_lot_cfg_remove_extensions(lot_cfg);
1070         }
1071
1072         ao2_iterator_destroy(&iter);
1073
1074         ast_context_destroy(NULL, BASE_REGISTRAR);
1075
1076 }
1077
1078 static int configure_parking_extensions(void)
1079 {
1080         struct parking_config *cfg = aco_pending_config(&cfg_info);
1081         struct ao2_iterator iter;
1082         RAII_VAR(struct parking_lot_cfg *, lot_cfg, NULL, ao2_cleanup);
1083         int res = 0;
1084
1085         if (!cfg) {
1086                 return 0;
1087         }
1088
1089         /* Clear existing extensions */
1090         remove_all_configured_parking_lot_extensions();
1091
1092         /* Attempt to build new extensions for each lot */
1093         for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
1094                 if (parking_lot_cfg_create_extensions(lot_cfg)) {
1095                         ao2_cleanup(lot_cfg);
1096                         lot_cfg = NULL;
1097                         res = -1;
1098                         break;
1099                 }
1100         }
1101         ao2_iterator_destroy(&iter);
1102
1103         if (res) {
1104                 remove_pending_parking_lot_extensions(cfg);
1105                 ast_log(LOG_ERROR, "Extension registration failed. Previously configured lot extensions were removed and can not be safely restored.\n");
1106         }
1107
1108         return res;
1109 }
1110
1111 static void mark_lots_as_disabled(void)
1112 {
1113         struct ao2_iterator iter;
1114         struct parking_lot *lot;
1115
1116         for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
1117                 lot->disable_mark = 1;
1118         }
1119
1120         ao2_iterator_destroy(&iter);
1121 }
1122
1123 static int config_parking_preapply(void)
1124 {
1125         mark_lots_as_disabled();
1126
1127         if (verify_default_parking_lot()) {
1128                 return -1;
1129         }
1130
1131         if (configure_parking_extensions()) {
1132                 return -1;
1133         }
1134
1135         return 0;
1136 }
1137
1138 static void disable_marked_lots(void)
1139 {
1140         struct ao2_iterator iter;
1141         struct parking_lot *lot;
1142
1143         for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
1144                 if (lot->disable_mark) {
1145                         parking_lot_disable(lot);
1146                 }
1147         }
1148
1149         ao2_iterator_destroy(&iter);
1150 }
1151
1152 static void link_configured_disable_marked_lots(void)
1153 {
1154         generate_or_link_lots_to_configs();
1155         disable_marked_lots();
1156 }
1157
1158 static int load_module(void)
1159 {
1160         parking_lot_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX,
1161                 AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
1162                 parking_lot_sort_fn,
1163                 NULL);
1164         if (!parking_lot_container) {
1165                 goto error;
1166         }
1167
1168         if (aco_info_init(&cfg_info)) {
1169                 goto error;
1170         }
1171
1172         /* Global options */
1173         aco_option_register(&cfg_info, "parkeddynamic", ACO_EXACT, global_options, "no", OPT_BOOL_T, 1, FLDSET(struct parking_global_config, parkeddynamic));
1174
1175         /* Register the per parking lot options. */
1176         aco_option_register(&cfg_info, "parkext", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parkext));
1177         aco_option_register(&cfg_info, "context", ACO_EXACT, parking_lot_types, "parkedcalls", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parking_con));
1178         aco_option_register(&cfg_info, "parkingtime", ACO_EXACT, parking_lot_types, "45", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, parkingtime));
1179         aco_option_register(&cfg_info, "comebacktoorigin", ACO_EXACT, parking_lot_types, "yes", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, comebacktoorigin));
1180         aco_option_register(&cfg_info, "comebackcontext", ACO_EXACT, parking_lot_types, "parkedcallstimeout", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, comebackcontext));
1181         aco_option_register(&cfg_info, "comebackdialtime", ACO_EXACT, parking_lot_types, "30", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, comebackdialtime));
1182         aco_option_register(&cfg_info, "parkedmusicclass", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, mohclass));
1183         aco_option_register(&cfg_info, "parkext_exclusive", ACO_EXACT, parking_lot_types, "no", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, parkext_exclusive));
1184         aco_option_register(&cfg_info, "parkinghints", ACO_EXACT, parking_lot_types, "no", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, parkaddhints));
1185         aco_option_register(&cfg_info, "courtesytone", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, courtesytone));
1186
1187         /* More complicated parking lot options that require special handling */
1188         aco_option_register_custom(&cfg_info, "parkpos", ACO_EXACT, parking_lot_types, "701-750", option_handler_parkpos, 0);
1189         aco_option_register_custom(&cfg_info, "findslot", ACO_EXACT, parking_lot_types, "first", option_handler_findslot, 0);
1190         aco_option_register_custom(&cfg_info, "parkedplay", ACO_EXACT, parking_lot_types, "caller", option_handler_parkedfeature, OPT_PARKEDPLAY);
1191         aco_option_register_custom(&cfg_info, "parkedcalltransfers", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDTRANSFERS);
1192         aco_option_register_custom(&cfg_info, "parkedcallreparking", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDREPARKING);
1193         aco_option_register_custom(&cfg_info, "parkedcallhangup", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDHANGUP);
1194         aco_option_register_custom(&cfg_info, "parkedcallrecording", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDRECORDING);
1195
1196         if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
1197                 goto error;
1198         }
1199
1200         if (ast_register_application_xml(PARK_APPLICATION, park_app_exec)) {
1201                 goto error;
1202         }
1203
1204         if (ast_register_application_xml(PARKED_CALL_APPLICATION, parked_call_app_exec)) {
1205                 goto error;
1206         }
1207
1208         if (ast_register_application_xml(PARK_AND_ANNOUNCE_APPLICATION, park_and_announce_app_exec)) {
1209                 goto error;
1210         }
1211
1212         if (load_parking_ui()) {
1213                 goto error;
1214         }
1215
1216         if (load_parking_manager()) {
1217                 goto error;
1218         }
1219
1220         if (load_parking_bridge_features()) {
1221                 goto error;
1222         }
1223
1224         if (load_parking_devstate()) {
1225                 goto error;
1226         }
1227
1228         if (load_parking_tests()) {
1229                 goto error;
1230         }
1231
1232         return AST_MODULE_LOAD_SUCCESS;
1233
1234 error:
1235         /* XXX errored loads don't currently do a good job of cleaning up after themselves */
1236         ao2_cleanup(parking_lot_container);
1237         aco_info_destroy(&cfg_info);
1238         return AST_MODULE_LOAD_DECLINE;
1239 }
1240
1241 static int reload_module(void)
1242 {
1243         if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
1244                 return AST_MODULE_LOAD_DECLINE;
1245         }
1246
1247         return 0;
1248 }
1249
1250 static int unload_module(void)
1251 {
1252
1253         /*ast_parking_unregister_bridge_features(parking_provider.module_name);*/
1254
1255         /* XXX Parking is currently not unloadable due to the fact that it loads features which could cause
1256          *     significant problems if they disappeared while a channel still had access to them.
1257          */
1258         return -1;
1259
1260         /* TODO Things we will need to do here:
1261          *
1262          *  destroy existing parking lots
1263          *  uninstall parking related bridge features
1264          *  remove extensions owned by the parking registrar
1265          *  unload currently loaded unit tests, CLI/AMI commands, etc.
1266          */
1267 }
1268
1269 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Call Parking Resource",
1270         .load = load_module,
1271         .unload = unload_module,
1272         .reload = reload_module,
1273 );