res_parking: Automatically generate extensions, hints, etc.
[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         /* TODO Implement dynamic parking lots. Entirely. */
235         int parkeddynamic;
236 };
237
238 struct parking_config {
239         struct parking_global_config *global;
240         struct ao2_container *parking_lots;
241 };
242
243 static struct aco_type global_option = {
244         .type = ACO_GLOBAL,
245         .name = "globals",
246         .item_offset = offsetof(struct parking_config, global),
247         .category_match = ACO_WHITELIST,
248         .category = "^general$",
249 };
250
251 struct aco_type *global_options[] = ACO_TYPES(&global_option);
252
253 static struct aco_type parking_lot_type = {
254         .type = ACO_ITEM,
255         .name = "parking_lot",
256         .category_match = ACO_BLACKLIST,
257         .category = "^(general)$",
258         .item_alloc = parking_lot_cfg_alloc,
259         .item_find = named_item_find,
260         .item_offset = offsetof(struct parking_config, parking_lots),
261 };
262
263 struct aco_type *parking_lot_types[] = ACO_TYPES(&parking_lot_type);
264
265 struct aco_file parking_lot_conf = {
266         .filename = "res_parking.conf",
267         .types = ACO_TYPES(&global_option, &parking_lot_type),
268 };
269
270 static AO2_GLOBAL_OBJ_STATIC(globals);
271
272 CONFIG_INFO_STANDARD(cfg_info, globals, parking_config_alloc,
273         .files = ACO_FILES(&parking_lot_conf),
274         .pre_apply_config = config_parking_preapply,
275         .post_apply_config = link_configured_disable_marked_lots,
276 );
277
278 static int parking_lot_cfg_hash_fn(const void *obj, const int flags)
279 {
280         const struct parking_lot_cfg *entry;
281         const char *key;
282
283         switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
284         case OBJ_KEY:
285                 key = obj;
286                 return ast_str_hash(key);
287         case OBJ_PARTIAL_KEY:
288                 ast_assert(0);
289                 return 0;
290         default:
291                 entry = obj;
292                 return ast_str_hash(entry->name);
293         }
294 }
295
296 static int parking_lot_cfg_cmp_fn(void *obj, void *arg, const int flags)
297 {
298         struct parking_lot_cfg *entry1 = obj;
299
300         char *key;
301         size_t key_size;
302         struct parking_lot_cfg *entry2;
303
304         switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
305         case OBJ_KEY:
306                 key = arg;
307                 return (!strcmp(entry1->name, key)) ? CMP_MATCH : 0;
308         case OBJ_PARTIAL_KEY:
309                 key = arg;
310                 key_size = strlen(key);
311                 return (!strncmp(entry1->name, key, key_size)) ? CMP_MATCH : 0;
312         case OBJ_POINTER:
313                 entry2 = arg;
314                 return (!strcmp(entry1->name, entry2->name)) ? CMP_MATCH : 0;
315         default:
316                 return CMP_STOP;
317         }
318 }
319
320 /*! \brief destructor for parking_config */
321 static void parking_config_destructor(void *obj)
322 {
323         struct parking_config *cfg = obj;
324         ao2_cleanup(cfg->parking_lots);
325         ao2_cleanup(cfg->global);
326 }
327
328 /*! \brief destructor for parking_global_config */
329 static void parking_global_config_destructor(void *obj)
330 {
331         /* For now, do nothing. */
332 }
333
334 /*! \brief allocator callback for parking_config. Notice it returns void * since it is only used by the backend config code */
335 static void *parking_config_alloc(void)
336 {
337         RAII_VAR(struct parking_config *, cfg, NULL, ao2_cleanup);
338
339         if (!(cfg = ao2_alloc(sizeof(*cfg), parking_config_destructor))) {
340                 return NULL;
341         }
342
343         if (!(cfg->parking_lots = ao2_container_alloc(37, parking_lot_cfg_hash_fn, parking_lot_cfg_cmp_fn))) {
344                 return NULL;
345         }
346
347         if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), parking_global_config_destructor))) {
348                 return NULL;
349         }
350
351         /* Bump the ref count since RAII_VAR is going to eat one */
352         ao2_ref(cfg, +1);
353         return cfg;
354 }
355
356 void parking_lot_remove_if_unused(struct parking_lot *lot)
357 {
358
359         if (lot->mode != PARKINGLOT_DISABLED) {
360                 return;
361         }
362
363
364         if (!ao2_container_count(lot->parked_users)) {
365                 ao2_unlink(parking_lot_container, lot);
366         }
367 }
368
369 static void parking_lot_disable(struct parking_lot *lot)
370 {
371         lot->mode = PARKINGLOT_DISABLED;
372         parking_lot_remove_if_unused(lot);
373 }
374
375 /*! \brief Destroy a parking lot cfg object */
376 static void parking_lot_cfg_destructor(void *obj)
377 {
378         struct parking_lot_cfg *lot_cfg = obj;
379         parking_lot_cfg_remove_extensions(lot_cfg);
380         ast_string_field_free_memory(lot_cfg);
381 }
382
383 /* The arg just needs to have the parking space with it */
384 static int parked_user_cmp_fn(void *obj, void *arg, int flags)
385 {
386         int *search_space = arg;
387         struct parked_user *user = obj;
388         int object_space = user->parking_space;
389
390         if (*search_space == object_space) {
391                 return CMP_MATCH;
392         }
393         return 0;
394 }
395
396 static int parked_user_sort_fn(const void *obj_left, const void *obj_right, int flags)
397 {
398         const struct parked_user *left = obj_left;
399         const struct parked_user *right = obj_right;
400
401         return left->parking_space - right->parking_space;
402 }
403
404 /*!
405  * \brief create a parking lot structure
406  * \param cat name given to the parking lot
407  * \retval NULL failure
408  * \retval non-NULL successfully allocated parking lot
409  */
410 static void *parking_lot_cfg_alloc(const char *cat)
411 {
412         struct parking_lot_cfg *lot_cfg;
413
414         lot_cfg = ao2_alloc(sizeof(*lot_cfg), parking_lot_cfg_destructor);
415         if (!lot_cfg) {
416                 return NULL;
417         }
418
419         if (ast_string_field_init(lot_cfg, 32)) {
420                 ao2_cleanup(lot_cfg);
421                 return NULL;
422         }
423
424         ast_string_field_set(lot_cfg, name, cat);
425
426         return lot_cfg;
427 }
428
429 /*!
430  * XXX This is actually incredibly generic and might be better placed in something like astobj2 if there isn't already an equivalent
431  * \brief find an item in a container by its name
432  *
433  * \param container ao2container where we want the item from
434  * \param key name of the item wanted to be found
435  *
436  * \retval pointer to the parking lot if available. NULL if not found.
437  */
438 static void *named_item_find(struct ao2_container *container, const char *name)
439 {
440         return ao2_find(container, name, OBJ_KEY);
441 }
442
443 /*!
444  * \brief Custom field handler for parking positions
445  */
446 static int option_handler_parkpos(const struct aco_option *opt, struct ast_variable *var, void *obj)
447 {
448         struct parking_lot_cfg *lot_cfg = obj;
449         int low;
450         int high;
451
452         if (sscanf(var->value, "%30d-%30d", &low, &high) != 2) {
453                 ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers\n");
454         } else if (high < low || low <= 0 || high <= 0) {
455                 ast_log(LOG_WARNING, "Format for parking positions is a-b, where a <= b\n");
456         } else {
457                 lot_cfg->parking_start = low;
458                 lot_cfg->parking_stop = high;
459                 return 0;
460         }
461         return -1;
462 }
463
464 /*!
465  * \brief Custom field handler for the findslot option
466  */
467 static int option_handler_findslot(const struct aco_option *opt, struct ast_variable *var, void *obj)
468 {
469         struct parking_lot_cfg *lot_cfg = obj;
470
471         if (!strcmp(var->value, "first")) {
472                 lot_cfg->parkfindnext = 0;
473         } else if (!strcmp(var->value, "next")) {
474                 lot_cfg->parkfindnext = 1;
475         } else {
476                 ast_log(LOG_WARNING, "value '%s' is not valid for findslot option.\n", var->value);
477                 return -1;
478         }
479
480         return 0;
481 }
482
483 /*!
484  * \brief Maps string values for option_handler_parkedfeature to their ENUM values
485  */
486 static int parking_feature_flag_cfg(int *param, const char *var)
487 {
488         if (ast_false(var)) {
489                 *param = 0;
490         } else if (!strcasecmp(var, "both")) {
491                 *param = AST_FEATURE_FLAG_BYBOTH;
492         } else if (!strcasecmp(var, "caller")) {
493                 *param = AST_FEATURE_FLAG_BYCALLER;
494         } else if (!strcasecmp(var, "callee")) {
495                 *param = AST_FEATURE_FLAG_BYCALLEE;
496         } else {
497                 return -1;
498         }
499
500         return 0;
501 }
502
503 /*!
504  * \brief Custom field handler for feature mapping on parked call pickup options
505  */
506 static int option_handler_parkedfeature(const struct aco_option *opt, struct ast_variable *var, void *obj)
507 {
508         struct parking_lot_cfg *cfg = obj;
509         enum parked_call_feature_options option = aco_option_get_flags(opt);
510         int *parameter = NULL;
511
512         switch (option) {
513         case OPT_PARKEDPLAY:
514                 parameter = &cfg->parkedplay;
515                 break;
516         case OPT_PARKEDTRANSFERS:
517                 parameter = &cfg->parkedcalltransfers;
518                 break;
519         case OPT_PARKEDREPARKING:
520                 parameter = &cfg->parkedcallreparking;
521                 break;
522         case OPT_PARKEDHANGUP:
523                 parameter = &cfg->parkedcallhangup;
524                 break;
525         case OPT_PARKEDRECORDING:
526                 parameter = &cfg->parkedcallrecording;
527                 break;
528         }
529
530         if (!parameter) {
531                 ast_log(LOG_ERROR, "Unable to handle option '%s'\n", var->name);
532                 return -1;
533         }
534
535         if (parking_feature_flag_cfg(parameter, var->value)) {
536                 ast_log(LOG_ERROR, "'%s' is not a valid value for parking lot option '%s'\n", var->value, var->name);
537                 return -1;
538         }
539
540         return 0;
541 }
542
543 struct ao2_container *get_parking_lot_container(void)
544 {
545         return parking_lot_container;
546 }
547
548 struct parking_lot *parking_lot_find_by_name(const char *lot_name)
549 {
550         struct parking_lot *lot = named_item_find(parking_lot_container, lot_name);
551         return lot;
552 }
553
554 const char *find_channel_parking_lot_name(struct ast_channel *chan)
555 {
556         const char *name;
557
558         /* The channel variable overrides everything */
559         name = pbx_builtin_getvar_helper(chan, "PARKINGLOT");
560         if (ast_strlen_zero(name) && !ast_strlen_zero(ast_channel_parkinglot(chan))) {
561                 /* Use the channel's parking lot. */
562                 name = ast_channel_parkinglot(chan);
563         }
564
565         /* If the name couldn't be pulled from that either, use the default parking lot name. */
566         if (ast_strlen_zero(name)) {
567                 name = DEFAULT_PARKING_LOT;
568         }
569
570         return name;
571 }
572
573 static void parking_lot_destructor(void *obj)
574 {
575         struct parking_lot *lot = obj;
576
577         if (lot->parking_bridge) {
578                 ast_bridge_destroy(lot->parking_bridge);
579         }
580         ao2_cleanup(lot->parked_users);
581         ao2_cleanup(lot->cfg);
582         ast_string_field_free_memory(lot);
583 }
584
585 static struct parking_lot *alloc_new_parking_lot(struct parking_lot_cfg *lot_cfg)
586 {
587         struct parking_lot *lot;
588         if (!(lot = ao2_alloc(sizeof(*lot), parking_lot_destructor))) {
589                 return NULL;
590         }
591
592         if (ast_string_field_init(lot, 32)) {
593                 return NULL;
594         }
595
596         /* Create parked user ordered list */
597         lot->parked_users = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_RWLOCK,
598                 AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
599                 parked_user_sort_fn,
600                 parked_user_cmp_fn);
601
602         if (!lot->parked_users) {
603                 ao2_cleanup(lot);
604                 return NULL;
605         }
606
607         ast_string_field_set(lot, name, lot_cfg->name);
608         return lot;
609 }
610
611 void parking_lot_cfg_remove_extensions(struct parking_lot_cfg *lot_cfg)
612 {
613         if (!ast_strlen_zero(lot_cfg->registrar)) {
614                 /* Although the function is called ast_context_destroy, the use of this funtion is
615                  * intended only to remove extensions, hints, etc registered by the parking lot's registrar.
616                  * It won't actually destroy the context unless that context is empty afterwards and it is
617                  * unreferenced.
618                  */
619                 ast_context_destroy(NULL, lot_cfg->registrar);
620         }
621 }
622
623 static void remove_all_configured_parking_lot_extensions(void)
624 {
625         RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
626         struct parking_lot_cfg *lot_cfg;
627         struct ao2_iterator iter;
628
629         if (!cfg) {
630                 return;
631         }
632
633         for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
634                 parking_lot_cfg_remove_extensions(lot_cfg);
635         }
636
637         ast_context_destroy(NULL, BASE_REGISTRAR);
638
639         ao2_iterator_destroy(&iter);
640 }
641
642 /*!
643  * \internal
644  * \since 12
645  * \brief Create an extension using ast_add_extension2_nolock. This function automatically allocates a duplicate
646  *        of the data string so that whatever calls it doesn't have to deal with freeing it if the ast_add_extension2_nolock
647  *        fails.
648  *
649  * \param context a write locked ast_context. Make certain it is write locked prior to calling this function
650  * \param replace whether the extension should replace existing extensions
651  * \param extension name of the extension desired
652  * \param priority priority of the extension we are registering
653  * \param application name of the application being used for the extension
654  * \param data application arguments
655  * \param registrar name of the registrar you should use for the extension.
656  *        Make sure this string doesn't go anywhere while there are still extensions using it.
657  */
658 static int parking_add_extension(struct ast_context *context, int replace, const char *extension,
659         int priority, const char *application, const char *data, const char *registrar)
660 {
661         char *data_duplicate = ast_strdup(data);
662
663         if (!data_duplicate) {
664                 return -1;
665         }
666
667         if (ast_add_extension2_nolock(context, replace, extension, priority, NULL, NULL,
668                         application, data_duplicate, ast_free_ptr, registrar)) {
669                 ast_free(data_duplicate);
670                 return -1;
671         }
672
673         return 0;
674 }
675
676 static int extension_is_compatible(struct parking_lot_cfg *lot_cfg, const char *app_type, struct ast_exten *extension)
677 {
678         RAII_VAR(struct parking_lot_cfg *, owner, NULL, ao2_cleanup);
679         const char *extension_registrar = ast_get_extension_registrar(extension);
680         const char *extension_context = ast_get_context_name(ast_get_extension_context(extension));
681         const char *extension_name = ast_get_extension_name(extension);
682         const char *extension_application = ast_get_extension_app(extension);
683
684         ast_assert(extension_registrar && extension_context && extension_name && extension_application);
685
686         if (strcmp(extension_registrar, BASE_REGISTRAR)) {
687                 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs an extension '%s@%s', but that extension is already owned by %s.\n",
688                         lot_cfg->name, extension_name, extension_context, extension_registrar);
689                 return 0;
690         }
691
692         if (strcmp(extension_application, app_type)) {
693                 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs an extension '%s@%s' with a non-exclusive %s application, "
694                         "but a/an %s application is already registered to that extension by %s.\n",
695                         lot_cfg->name, extension_name, extension_context, app_type,
696                         extension_application, BASE_REGISTRAR);
697                 return 0;
698         }
699
700         ast_debug(3, "Parking lot '%s' -- extension '%s@%s' with application %s is compatible.\n",
701                   lot_cfg->name, extension_name, extension_context, app_type);
702         return 1;
703 }
704
705 int parking_lot_cfg_create_extensions(struct parking_lot_cfg *lot_cfg)
706 {
707         int parkingspace;
708         struct ast_exten *existing_exten;
709         struct ast_context *lot_context;
710         struct pbx_find_info find_info = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
711         const char *registrar_pointer;
712
713         if (ast_strlen_zero(lot_cfg->parkext)) {
714                 return 0;
715         }
716
717         if (lot_cfg->parkext_exclusive) {
718                 ast_string_field_build(lot_cfg, registrar, "%s/%s", BASE_REGISTRAR, lot_cfg->name);
719                 registrar_pointer = lot_cfg->registrar;
720         } else {
721                 registrar_pointer = BASE_REGISTRAR;
722         }
723
724         /* We need the contexts list locked to safely be able to both read and lock the specific context within */
725         if (ast_wrlock_contexts()) {
726                 ast_log(LOG_ERROR, "Failed to lock the contexts list.\n");
727                 return -1;
728         }
729
730         if (!(lot_context = ast_context_find_or_create(NULL, NULL, lot_cfg->parking_con, registrar_pointer))) {
731                 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs a context '%s' which does not exist and Asterisk was unable to create\n",
732                         lot_cfg->name, lot_cfg->parking_con);
733                 if (ast_unlock_contexts()) {
734                         ast_assert(0);
735                 }
736                 return -1;
737         }
738
739         /* Once we know what context we will be modifying, we need to write lock it because we will be reading extensions
740          * and we don't want something else to destroy them while we are looking at them.
741          */
742         if (ast_wrlock_context(lot_context)) {
743                 ast_log(LOG_ERROR, "failed to obtain write lock on context\n");
744                 return -1;
745         }
746
747         if (ast_unlock_contexts()) {
748                 ast_assert(0);
749         }
750
751         /* Handle generation/confirmation for the Park extension */
752         if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, lot_cfg->parkext, 1, NULL, NULL, E_MATCH))) {
753                 if (lot_cfg->parkext_exclusive || !extension_is_compatible(lot_cfg, PARK_APPLICATION, existing_exten)) {
754                         ast_unlock_context(lot_context);
755                         return -1;
756                 }
757         } else if (parking_add_extension(lot_context, 0, lot_cfg->parkext, 1, PARK_APPLICATION,
758                    lot_cfg->parkext_exclusive ? lot_cfg->name : "", registrar_pointer)) {
759                 ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add %s extension '%s@%s' to the PBX.\n",
760                         lot_cfg->name, PARK_APPLICATION, lot_cfg->parkext, lot_cfg->parking_con);
761                 ast_unlock_context(lot_context);
762                 return -1;
763         }
764
765         /* Handle generation/confirmation for the ParkedCall extensions and hints */
766         for (parkingspace = lot_cfg->parking_start; parkingspace <= lot_cfg->parking_stop; parkingspace++) {
767                 char space[AST_MAX_EXTENSION];
768                 RAII_VAR(struct ast_str *, arguments_string, NULL, ast_free);
769                 find_info.stacklen = 0; /* reset for pbx_find_exten */
770
771                 snprintf(space, sizeof(space), "%d", parkingspace);
772
773                 /* Unlike the Park extensions, ParkedCall extensions and their hints may never be shared for any reason. */
774                 if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, space, 1, NULL, NULL, E_MATCH))) {
775                         ast_unlock_context(lot_context);
776                         return -1;
777                 }
778
779                 arguments_string = ast_str_create(32);
780                 if (!arguments_string) {
781                         ast_unlock_context(lot_context);
782                         return -1;
783                 }
784
785                 ast_str_set(&arguments_string, 0, "%s,%s", lot_cfg->name, space);
786                 if (parking_add_extension(lot_context, 0, space, 1, PARKED_CALL_APPLICATION,
787                     ast_str_buffer(arguments_string), registrar_pointer)) {
788                         ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add %s extension '%s@%s' to the PBX.\n",
789                                 lot_cfg->name, PARKED_CALL_APPLICATION, space, lot_cfg->parking_con);
790                         ast_unlock_context(lot_context);
791                         return -1;
792                 }
793
794                 find_info.stacklen = 0; /* reset for pbx_find_exten */
795
796                 if (lot_cfg->parkaddhints) {
797                         char hint_device[AST_MAX_EXTENSION];
798
799                         snprintf(hint_device, sizeof(hint_device), "park:%s@%s", space, lot_cfg->parking_con);
800
801                         if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, lot_cfg->parking_con, space, PRIORITY_HINT, NULL, NULL, E_MATCH))) {
802                                 ast_log(LOG_ERROR, "Parking lot '%s' -- Needs to add a hint '%s' at '%s@%s' but one already exists owned by %s\n",
803                                 lot_cfg->name, hint_device, space, lot_cfg->parking_con, ast_get_extension_registrar(existing_exten));
804                                         ast_unlock_context(lot_context);
805                                         return -1;
806                         }
807
808                         if (parking_add_extension(lot_context, 0, space, PRIORITY_HINT, hint_device, "", registrar_pointer)) {
809                                 ast_log(LOG_ERROR, "Parking lot '%s' -- Failed to add hint '%s@%s' to the PBX.\n",
810                                         lot_cfg->name, space, lot_cfg->parking_con);
811                                 ast_unlock_context(lot_context);
812                                 return -1;
813                         }
814                 }
815         }
816
817         if (ast_unlock_context(lot_context)) {
818                 ast_assert(0);
819         }
820
821         return 0;
822 }
823
824 struct parking_lot *parking_lot_build_or_update(struct parking_lot_cfg *lot_cfg)
825 {
826         struct parking_lot *lot;
827         struct parking_lot_cfg *replaced_cfg = NULL;
828         int found = 0;
829
830         /* Start by trying to find it. If that works we can skip the rest. */
831         lot = named_item_find(parking_lot_container, lot_cfg->name);
832         if (!lot) {
833                 lot = alloc_new_parking_lot(lot_cfg);
834
835                 /* If we still don't have a lot, we failed to alloc one. */
836                 if (!lot) {
837                         return NULL;
838                 }
839         } else {
840                 found = 1;
841         }
842
843         /* Set the configuration reference. Unref the one currently in the lot if it's there. */
844         if (lot->cfg) {
845                 replaced_cfg = lot->cfg;
846         }
847
848         ao2_ref(lot_cfg, +1);
849         lot->cfg = lot_cfg;
850
851         ao2_cleanup(replaced_cfg);
852
853         /* Set the operating mode to normal since the parking lot has a configuration. */
854         lot->disable_mark = 0;
855         lot->mode = PARKINGLOT_NORMAL;
856
857         if (!found) {
858                 /* Link after configuration is set since a lot without configuration will cause all kinds of trouble. */
859                 ao2_link(parking_lot_container, lot);
860         };
861
862         return lot;
863 }
864
865 static void generate_or_link_lots_to_configs(void)
866 {
867         RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
868         struct parking_lot_cfg *lot_cfg;
869         struct ao2_iterator iter;
870
871         for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
872                 RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
873                 lot = parking_lot_build_or_update(lot_cfg);
874         }
875
876         ao2_iterator_destroy(&iter);
877 }
878
879 /* Preapply */
880
881 static int verify_default_parking_lot(void)
882 {
883         struct parking_config *cfg = aco_pending_config(&cfg_info);
884         RAII_VAR(struct parking_lot_cfg *, lot_cfg, NULL, ao2_cleanup);
885
886         if (!cfg) {
887                 return 0;
888         }
889
890         lot_cfg = ao2_find(cfg->parking_lots, DEFAULT_PARKING_LOT, OBJ_KEY);
891         if (!lot_cfg) {
892                 lot_cfg = parking_lot_cfg_alloc(DEFAULT_PARKING_LOT);
893                 if (!lot_cfg) {
894                         return -1;
895                 }
896                 ast_log(AST_LOG_NOTICE, "Adding %s profile to res_parking\n", DEFAULT_PARKING_LOT);
897                 aco_set_defaults(&parking_lot_type, DEFAULT_PARKING_LOT, lot_cfg);
898                 ast_string_field_set(lot_cfg, parkext, DEFAULT_PARKING_EXTEN);
899                 ao2_link(cfg->parking_lots, lot_cfg);
900         }
901
902         return 0;
903 }
904
905 static void remove_pending_parking_lot_extensions(struct parking_config *cfg_pending)
906 {
907         struct parking_lot_cfg *lot_cfg;
908         struct ao2_iterator iter;
909
910         for (iter = ao2_iterator_init(cfg_pending->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
911                 parking_lot_cfg_remove_extensions(lot_cfg);
912         }
913
914         ao2_iterator_destroy(&iter);
915
916         ast_context_destroy(NULL, BASE_REGISTRAR);
917
918 }
919
920 static int configure_parking_extensions(void)
921 {
922         struct parking_config *cfg = aco_pending_config(&cfg_info);
923         struct ao2_iterator iter;
924         RAII_VAR(struct parking_lot_cfg *, lot_cfg, NULL, ao2_cleanup);
925         int res = 0;
926
927         if (!cfg) {
928                 return 0;
929         }
930
931         /* Clear existing extensions */
932         remove_all_configured_parking_lot_extensions();
933
934         /* Attempt to build new extensions for each lot */
935         for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
936                 if (parking_lot_cfg_create_extensions(lot_cfg)) {
937                         ao2_cleanup(lot_cfg);
938                         lot_cfg = NULL;
939                         res = -1;
940                         break;
941                 }
942         }
943         ao2_iterator_destroy(&iter);
944
945         if (res) {
946                 remove_pending_parking_lot_extensions(cfg);
947                 ast_log(LOG_ERROR, "Extension registration failed. Previously configured lot extensions were removed and can not be safely restored.\n");
948         }
949
950         return res;
951 }
952
953 static void mark_lots_as_disabled(void)
954 {
955         struct ao2_iterator iter;
956         struct parking_lot *lot;
957
958         for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
959                 /* We aren't concerned with dynamic lots */
960                 if (lot->mode == PARKINGLOT_DYNAMIC) {
961                         continue;
962                 }
963
964                 lot->disable_mark = 1;
965         }
966
967         ao2_iterator_destroy(&iter);
968 }
969
970 static int config_parking_preapply(void)
971 {
972         mark_lots_as_disabled();
973
974         if (verify_default_parking_lot()) {
975                 return -1;
976         }
977
978         if (configure_parking_extensions()) {
979                 return -1;
980         }
981
982         return 0;
983 }
984
985 static void disable_marked_lots(void)
986 {
987         struct ao2_iterator iter;
988         struct parking_lot *lot;
989
990         for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
991                 if (lot->disable_mark) {
992                         parking_lot_disable(lot);
993                 }
994         }
995
996         ao2_iterator_destroy(&iter);
997 }
998
999 static void link_configured_disable_marked_lots(void)
1000 {
1001         generate_or_link_lots_to_configs();
1002         disable_marked_lots();
1003 }
1004
1005 static int load_module(void)
1006 {
1007         if (aco_info_init(&cfg_info)) {
1008                 goto error;
1009         }
1010
1011         parking_lot_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_RWLOCK,
1012                 AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
1013                 parking_lot_sort_fn,
1014                 NULL);
1015
1016         if (!parking_lot_container) {
1017                 goto error;
1018         }
1019
1020         /* Global options */
1021
1022         /* Register the per parking lot options. */
1023         aco_option_register(&cfg_info, "parkext", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parkext));
1024         aco_option_register(&cfg_info, "context", ACO_EXACT, parking_lot_types, "parkedcalls", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parking_con));
1025         aco_option_register(&cfg_info, "parkingtime", ACO_EXACT, parking_lot_types, "45", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, parkingtime));
1026         aco_option_register(&cfg_info, "comebacktoorigin", ACO_EXACT, parking_lot_types, "yes", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, comebacktoorigin));
1027         aco_option_register(&cfg_info, "comebackcontext", ACO_EXACT, parking_lot_types, "parkedcallstimeout", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, comebackcontext));
1028         aco_option_register(&cfg_info, "comebackdialtime", ACO_EXACT, parking_lot_types, "30", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, comebackdialtime));
1029         aco_option_register(&cfg_info, "parkedmusicclass", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, mohclass));
1030         aco_option_register(&cfg_info, "parkext_exclusive", ACO_EXACT, parking_lot_types, "no", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, parkext_exclusive));
1031         aco_option_register(&cfg_info, "parkinghints", ACO_EXACT, parking_lot_types, "no", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, parkaddhints));
1032         aco_option_register(&cfg_info, "courtesytone", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, courtesytone));
1033
1034         /* More complicated parking lot options that require special handling */
1035         aco_option_register_custom(&cfg_info, "parkpos", ACO_EXACT, parking_lot_types, "701-750", option_handler_parkpos, 0);
1036         aco_option_register_custom(&cfg_info, "findslot", ACO_EXACT, parking_lot_types, "first", option_handler_findslot, 0);
1037         aco_option_register_custom(&cfg_info, "parkedplay", ACO_EXACT, parking_lot_types, "caller", option_handler_parkedfeature, OPT_PARKEDPLAY);
1038         aco_option_register_custom(&cfg_info, "parkedcalltransfers", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDTRANSFERS);
1039         aco_option_register_custom(&cfg_info, "parkedcallreparking", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDREPARKING);
1040         aco_option_register_custom(&cfg_info, "parkedcallhangup", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDHANGUP);
1041         aco_option_register_custom(&cfg_info, "parkedcallrecording", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDRECORDING);
1042
1043         if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
1044                 goto error;
1045         }
1046
1047         if (ast_register_application_xml(PARK_APPLICATION, park_app_exec)) {
1048                 goto error;
1049         }
1050
1051         if (ast_register_application_xml(PARKED_CALL_APPLICATION, parked_call_app_exec)) {
1052                 goto error;
1053         }
1054
1055         if (ast_register_application_xml(PARK_AND_ANNOUNCE_APPLICATION, park_and_announce_app_exec)) {
1056                 goto error;
1057         }
1058
1059         if (load_parking_ui()) {
1060                 goto error;
1061         }
1062
1063         if (load_parking_manager()) {
1064                 goto error;
1065         }
1066
1067         if (load_parking_bridge_features()) {
1068                 goto error;
1069         }
1070
1071         if (load_parking_devstate()) {
1072                 goto error;
1073         }
1074
1075         return AST_MODULE_LOAD_SUCCESS;
1076
1077 error:
1078         ao2_cleanup(parking_lot_container);
1079         aco_info_destroy(&cfg_info);
1080         return AST_MODULE_LOAD_DECLINE;
1081 }
1082
1083 static int reload_module(void)
1084 {
1085         if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
1086                 return AST_MODULE_LOAD_DECLINE;
1087         }
1088
1089         return 0;
1090 }
1091
1092 static int unload_module(void)
1093 {
1094         /* XXX Parking is currently not unloadable due to the fact that it loads features which could cause
1095          *     significant problems if they disappeared while a channel still had access to them.
1096          */
1097         return -1;
1098
1099         /* TODO Things we will need to do here:
1100          *
1101          *  destroy existing parking lots
1102          *  uninstall parking related bridge features
1103          *  remove extensions owned by the parking registrar
1104          */
1105 }
1106
1107 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Call Parking Resource",
1108         .load = load_module,
1109         .unload = unload_module,
1110         .reload = reload_module,
1111 );