res_parking: Dynamic Parking Lots
[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 /*!
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
438  *
439  * \param container ao2container where we want the item from
440  * \param key name of the item wanted to be found
441  *
442  * \retval pointer to the parking lot if available. NULL if not found.
443  */
444 static void *named_item_find(struct ao2_container *container, const char *name)
445 {
446         return ao2_find(container, name, OBJ_KEY);
447 }
448
449 /*!
450  * \brief Custom field handler for parking positions
451  */
452 static int option_handler_parkpos(const struct aco_option *opt, struct ast_variable *var, void *obj)
453 {
454         struct parking_lot_cfg *lot_cfg = obj;
455         int low;
456         int high;
457
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");
462         } else {
463                 lot_cfg->parking_start = low;
464                 lot_cfg->parking_stop = high;
465                 return 0;
466         }
467         return -1;
468 }
469
470 /*!
471  * \brief Custom field handler for the findslot option
472  */
473 static int option_handler_findslot(const struct aco_option *opt, struct ast_variable *var, void *obj)
474 {
475         struct parking_lot_cfg *lot_cfg = obj;
476
477         if (!strcmp(var->value, "first")) {
478                 lot_cfg->parkfindnext = 0;
479         } else if (!strcmp(var->value, "next")) {
480                 lot_cfg->parkfindnext = 1;
481         } else {
482                 ast_log(LOG_WARNING, "value '%s' is not valid for findslot option.\n", var->value);
483                 return -1;
484         }
485
486         return 0;
487 }
488
489 /*!
490  * \brief Maps string values for option_handler_parkedfeature to their ENUM values
491  */
492 static int parking_feature_flag_cfg(int *param, const char *var)
493 {
494         if (ast_false(var)) {
495                 *param = 0;
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;
502         } else {
503                 return -1;
504         }
505
506         return 0;
507 }
508
509 /*!
510  * \brief Custom field handler for feature mapping on parked call pickup options
511  */
512 static int option_handler_parkedfeature(const struct aco_option *opt, struct ast_variable *var, void *obj)
513 {
514         struct parking_lot_cfg *cfg = obj;
515         enum parked_call_feature_options option = aco_option_get_flags(opt);
516         int *parameter = NULL;
517
518         switch (option) {
519         case OPT_PARKEDPLAY:
520                 parameter = &cfg->parkedplay;
521                 break;
522         case OPT_PARKEDTRANSFERS:
523                 parameter = &cfg->parkedcalltransfers;
524                 break;
525         case OPT_PARKEDREPARKING:
526                 parameter = &cfg->parkedcallreparking;
527                 break;
528         case OPT_PARKEDHANGUP:
529                 parameter = &cfg->parkedcallhangup;
530                 break;
531         case OPT_PARKEDRECORDING:
532                 parameter = &cfg->parkedcallrecording;
533                 break;
534         }
535
536         ast_assert(parameter != NULL);
537         if (!parameter || parking_feature_flag_cfg(parameter, var->value)) {
538                 return -1;
539         }
540
541         return 0;
542 }
543
544 struct ao2_container *get_parking_lot_container(void)
545 {
546         return parking_lot_container;
547 }
548
549 struct parking_lot *parking_lot_find_by_name(const char *lot_name)
550 {
551         struct parking_lot *lot = named_item_find(parking_lot_container, lot_name);
552         return lot;
553 }
554
555 const char *find_channel_parking_lot_name(struct ast_channel *chan)
556 {
557         const char *name;
558
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);
564         }
565
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;
569         }
570
571         return name;
572 }
573
574 static void parking_lot_destructor(void *obj)
575 {
576         struct parking_lot *lot = obj;
577
578         if (lot->parking_bridge) {
579                 ast_bridge_destroy(lot->parking_bridge);
580         }
581         ao2_cleanup(lot->parked_users);
582         ao2_cleanup(lot->cfg);
583         ast_string_field_free_memory(lot);
584 }
585
586 static struct parking_lot *alloc_new_parking_lot(struct parking_lot_cfg *lot_cfg)
587 {
588         struct parking_lot *lot;
589         if (!(lot = ao2_alloc(sizeof(*lot), parking_lot_destructor))) {
590                 return NULL;
591         }
592
593         if (ast_string_field_init(lot, 32)) {
594                 return NULL;
595         }
596
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,
600                 parked_user_sort_fn,
601                 parked_user_cmp_fn);
602
603         if (!lot->parked_users) {
604                 ao2_cleanup(lot);
605                 return NULL;
606         }
607
608         ast_string_field_set(lot, name, lot_cfg->name);
609         return lot;
610 }
611
612 void parking_lot_cfg_remove_extensions(struct parking_lot_cfg *lot_cfg)
613 {
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
618                  * unreferenced.
619                  */
620                 ast_context_destroy(NULL, lot_cfg->registrar);
621         }
622 }
623
624 static void remove_all_configured_parking_lot_extensions(void)
625 {
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;
629
630         if (!cfg) {
631                 return;
632         }
633
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);
636         }
637
638         ast_context_destroy(NULL, BASE_REGISTRAR);
639
640         ao2_iterator_destroy(&iter);
641 }
642
643 /*!
644  * \internal
645  * \since 12
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
648  *        fails.
649  *
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.
658  */
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)
661 {
662         char *data_duplicate = ast_strdup(data);
663
664         if (!data_duplicate) {
665                 return -1;
666         }
667
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);
671                 return -1;
672         }
673
674         return 0;
675 }
676
677 static int extension_is_compatible(struct parking_lot_cfg *lot_cfg, const char *app_type, struct ast_exten *extension)
678 {
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);
684
685         ast_assert(extension_registrar && extension_context && extension_name && extension_application);
686
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);
690                 return 0;
691         }
692
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);
698                 return 0;
699         }
700
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);
703         return 1;
704 }
705
706 int parking_lot_cfg_create_extensions(struct parking_lot_cfg *lot_cfg)
707 {
708         int parkingspace;
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 */
714
715         if (ast_strlen_zero(lot_cfg->parkext)) {
716                 return 0;
717         }
718
719         ast_string_field_build(lot_cfg, registrar, "%s/%s", BASE_REGISTRAR, lot_cfg->name);
720         parkedcall_registrar_pointer = lot_cfg->registrar;
721
722         if (lot_cfg->parkext_exclusive) {
723                 parkext_registrar_pointer = lot_cfg->registrar;
724         } else {
725                 parkext_registrar_pointer = BASE_REGISTRAR;
726         }
727
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");
731                 return -1;
732         }
733
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()) {
738                         ast_assert(0);
739                 }
740                 return -1;
741         }
742
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.
745          */
746         if (ast_wrlock_context(lot_context)) {
747                 ast_log(LOG_ERROR, "failed to obtain write lock on context\n");
748                 return -1;
749         }
750
751         if (ast_unlock_contexts()) {
752                 ast_assert(0);
753         }
754
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);
759                         return -1;
760                 }
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);
766                 return -1;
767         }
768
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 */
774
775                 snprintf(space, sizeof(space), "%d", parkingspace);
776
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);
780                         return -1;
781                 }
782
783                 arguments_string = ast_str_create(32);
784                 if (!arguments_string) {
785                         ast_unlock_context(lot_context);
786                         return -1;
787                 }
788
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);
795                         return -1;
796                 }
797
798                 find_info.stacklen = 0; /* reset for pbx_find_exten */
799
800                 if (lot_cfg->parkaddhints) {
801                         char hint_device[AST_MAX_EXTENSION];
802
803                         snprintf(hint_device, sizeof(hint_device), "park:%s@%s", space, lot_cfg->parking_con);
804
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);
809                                         return -1;
810                         }
811
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);
816                                 return -1;
817                         }
818                 }
819         }
820
821         if (ast_unlock_context(lot_context)) {
822                 ast_assert(0);
823         }
824
825         return 0;
826 }
827
828 struct parking_lot *parking_lot_build_or_update(struct parking_lot_cfg *lot_cfg, int dynamic)
829 {
830         struct parking_lot *lot;
831         struct parking_lot_cfg *replaced_cfg = NULL;
832         int found = 0;
833
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);
836         if (!lot) {
837                 lot = alloc_new_parking_lot(lot_cfg);
838
839                 /* If we still don't have a lot, we failed to alloc one. */
840                 if (!lot) {
841                         return NULL;
842                 }
843         } else {
844                 found = 1;
845
846                 if (dynamic) {
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);
848                         ao2_cleanup(lot);
849                         return NULL;
850                 }
851         }
852
853         /* Set the configuration reference. Unref the one currently in the lot if it's there. */
854         if (lot->cfg) {
855                 replaced_cfg = lot->cfg;
856         }
857
858         ao2_ref(lot_cfg, +1);
859         lot->cfg = lot_cfg;
860
861         ao2_cleanup(replaced_cfg);
862
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;
866
867         if (!found) {
868                 /* Link after configuration is set since a lot without configuration will cause all kinds of trouble. */
869                 ao2_link(parking_lot_container, lot);
870         };
871
872         return lot;
873 }
874
875 static void generate_or_link_lots_to_configs(void)
876 {
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;
880
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);
884         }
885
886         ao2_iterator_destroy(&iter);
887 }
888
889 int parking_dynamic_lots_enabled(void)
890 {
891         RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
892
893         if (!cfg) {
894                 return 0;
895         }
896
897         return cfg->global->parkeddynamic;
898 }
899
900 static struct parking_lot_cfg *clone_parkinglot_cfg(struct parking_lot_cfg *source, const char *name)
901 {
902         struct parking_lot_cfg *cfg = parking_lot_cfg_alloc(name);
903
904         if (!cfg) {
905                 return NULL;
906         }
907
908         ast_string_fields_copy(cfg, source);
909
910         /* Needs to be reset after being copied */
911         ast_string_field_set(cfg, name, name);
912
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;
927
928         return cfg;
929 }
930
931 struct parking_lot *parking_create_dynamic_lot(const char *name, struct ast_channel *chan)
932 {
933         RAII_VAR(struct parking_lot_cfg *, cfg, NULL, ao2_cleanup);
934         RAII_VAR(struct parking_lot *, template_lot, NULL, ao2_cleanup);
935
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;
942         int dyn_start;
943         int dyn_end;
944
945         if (!parking_dynamic_lots_enabled()) {
946                 return NULL;
947         }
948
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);
955
956         template_name = S_OR(chan_template_name, DEFAULT_PARKING_LOT);
957
958         template_lot = parking_lot_find_by_name(template_name);
959         if (!template_lot) {
960                 ast_log(LOG_ERROR, "Lot %s does not exist. Can not use it as a dynamic parking lot template.\n",
961                         template_name);
962                 return NULL;
963         }
964
965         cfg = clone_parkinglot_cfg(template_lot->cfg, name);
966
967         if (!cfg) {
968                 ast_log(LOG_ERROR, "Failed to allocate dynamic parking lot configuration.\n");
969                 return NULL;
970         }
971
972         if (!ast_strlen_zero(dyn_exten)) {
973                 ast_string_field_set(cfg, parkext, dyn_exten);
974         }
975
976         if (!ast_strlen_zero(dyn_context)) {
977                 ast_string_field_set(cfg, parking_con, dyn_context);
978         }
979
980         if (!ast_strlen_zero(dyn_range)) {
981                 if (sscanf(dyn_range, "%30d-%30d", &dyn_start, &dyn_end) != 2) {
982                         ast_log(LOG_ERROR,
983                                 "Invalid parking range %s specified in PARKINGDYNPOS: could not parse minimum/maximum parking space range\n", dyn_range);
984                                 return NULL;
985                 }
986                 if (dyn_end < dyn_start || dyn_start < 0) {
987                         ast_log(LOG_ERROR,
988                                 "Invalid parking range %s specified for PARKINGDYNPOS: end parking space must be greater than starting parking space.\n", dyn_range);
989                                 return NULL;
990                 }
991
992                 cfg->parking_start = dyn_start;
993                 cfg->parking_stop = dyn_end;
994         }
995
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);
998                 return NULL;
999         }
1000
1001         ao2_lock(parking_lot_container);
1002
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);
1006                 ao2_ref(lot, -1);
1007                 return NULL;
1008         }
1009
1010         lot = parking_lot_build_or_update(cfg, 1);
1011         ao2_unlock(parking_lot_container);
1012
1013         if (!lot) {
1014                 ast_log(LOG_NOTICE, "Failed to build dynamic parking lot '%s'\n", name);
1015         }
1016
1017         return lot;
1018 }
1019
1020 /* Preapply */
1021
1022 static int verify_default_parking_lot(void)
1023 {
1024         struct parking_config *cfg = aco_pending_config(&cfg_info);
1025         RAII_VAR(struct parking_lot_cfg *, lot_cfg, NULL, ao2_cleanup);
1026
1027         if (!cfg) {
1028                 return 0;
1029         }
1030
1031         lot_cfg = ao2_find(cfg->parking_lots, DEFAULT_PARKING_LOT, OBJ_KEY);
1032         if (!lot_cfg) {
1033                 lot_cfg = parking_lot_cfg_alloc(DEFAULT_PARKING_LOT);
1034                 if (!lot_cfg) {
1035                         return -1;
1036                 }
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);
1041         }
1042
1043         return 0;
1044 }
1045
1046 static void remove_pending_parking_lot_extensions(struct parking_config *cfg_pending)
1047 {
1048         struct parking_lot_cfg *lot_cfg;
1049         struct ao2_iterator iter;
1050
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);
1053         }
1054
1055         ao2_iterator_destroy(&iter);
1056
1057         ast_context_destroy(NULL, BASE_REGISTRAR);
1058
1059 }
1060
1061 static int configure_parking_extensions(void)
1062 {
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);
1066         int res = 0;
1067
1068         if (!cfg) {
1069                 return 0;
1070         }
1071
1072         /* Clear existing extensions */
1073         remove_all_configured_parking_lot_extensions();
1074
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);
1079                         lot_cfg = NULL;
1080                         res = -1;
1081                         break;
1082                 }
1083         }
1084         ao2_iterator_destroy(&iter);
1085
1086         if (res) {
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");
1089         }
1090
1091         return res;
1092 }
1093
1094 static void mark_lots_as_disabled(void)
1095 {
1096         struct ao2_iterator iter;
1097         struct parking_lot *lot;
1098
1099         for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
1100                 lot->disable_mark = 1;
1101         }
1102
1103         ao2_iterator_destroy(&iter);
1104 }
1105
1106 static int config_parking_preapply(void)
1107 {
1108         mark_lots_as_disabled();
1109
1110         if (verify_default_parking_lot()) {
1111                 return -1;
1112         }
1113
1114         if (configure_parking_extensions()) {
1115                 return -1;
1116         }
1117
1118         return 0;
1119 }
1120
1121 static void disable_marked_lots(void)
1122 {
1123         struct ao2_iterator iter;
1124         struct parking_lot *lot;
1125
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);
1129                 }
1130         }
1131
1132         ao2_iterator_destroy(&iter);
1133 }
1134
1135 static void link_configured_disable_marked_lots(void)
1136 {
1137         generate_or_link_lots_to_configs();
1138         disable_marked_lots();
1139 }
1140
1141 static int load_module(void)
1142 {
1143         if (aco_info_init(&cfg_info)) {
1144                 goto error;
1145         }
1146
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,
1150                 NULL);
1151
1152         if (!parking_lot_container) {
1153                 goto error;
1154         }
1155
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));
1158
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));
1170
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);
1179
1180         if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
1181                 goto error;
1182         }
1183
1184         if (ast_register_application_xml(PARK_APPLICATION, park_app_exec)) {
1185                 goto error;
1186         }
1187
1188         if (ast_register_application_xml(PARKED_CALL_APPLICATION, parked_call_app_exec)) {
1189                 goto error;
1190         }
1191
1192         if (ast_register_application_xml(PARK_AND_ANNOUNCE_APPLICATION, park_and_announce_app_exec)) {
1193                 goto error;
1194         }
1195
1196         if (load_parking_ui()) {
1197                 goto error;
1198         }
1199
1200         if (load_parking_manager()) {
1201                 goto error;
1202         }
1203
1204         if (load_parking_bridge_features()) {
1205                 goto error;
1206         }
1207
1208         if (load_parking_devstate()) {
1209                 goto error;
1210         }
1211
1212         return AST_MODULE_LOAD_SUCCESS;
1213
1214 error:
1215         ao2_cleanup(parking_lot_container);
1216         aco_info_destroy(&cfg_info);
1217         return AST_MODULE_LOAD_DECLINE;
1218 }
1219
1220 static int reload_module(void)
1221 {
1222         if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
1223                 return AST_MODULE_LOAD_DECLINE;
1224         }
1225
1226         return 0;
1227 }
1228
1229 static int unload_module(void)
1230 {
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.
1233          */
1234         return -1;
1235
1236         /* TODO Things we will need to do here:
1237          *
1238          *  destroy existing parking lots
1239          *  uninstall parking related bridge features
1240          *  remove extensions owned by the parking registrar
1241          */
1242 }
1243
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,
1248 );