PickupChan: Add ability to specify channel uniqueids as well as channel names.
[asterisk/asterisk.git] / apps / app_directed_pickup.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2005, Joshua Colp
5  *
6  * Joshua Colp <jcolp@digium.com>
7  *
8  * Portions merged from app_pickupchan, which was
9  * Copyright (C) 2008, Gary Cook
10  *
11  * See http://www.asterisk.org for more information about
12  * the Asterisk project. Please do not directly contact
13  * any of the maintainers of this project for assistance;
14  * the project provides a web site, mailing lists and IRC
15  * channels for your use.
16  *
17  * This program is free software, distributed under the terms of
18  * the GNU General Public License Version 2. See the LICENSE file
19  * at the top of the source tree.
20  */
21
22 /*! \file
23  *
24  * \brief Directed Call Pickup Support
25  *
26  * \author Joshua Colp <jcolp@digium.com>
27  * \author Gary Cook
28  *
29  * \ingroup applications
30  */
31
32 /*** MODULEINFO
33         <support_level>core</support_level>
34  ***/
35
36 #include "asterisk.h"
37
38 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
39
40 #include "asterisk/file.h"
41 #include "asterisk/channel.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/module.h"
44 #include "asterisk/lock.h"
45 #include "asterisk/app.h"
46 #include "asterisk/pickup.h"
47 #include "asterisk/manager.h"
48 #include "asterisk/callerid.h"
49
50 #define PICKUPMARK "PICKUPMARK"
51
52 /*** DOCUMENTATION
53         <application name="Pickup" language="en_US">
54                 <synopsis>
55                         Directed extension call pickup.
56                 </synopsis>
57                 <syntax>
58                         <parameter name="targets" argsep="&amp;">
59                                 <argument name="extension" argsep="@" required="true">
60                                         <para>Specification of the pickup target.</para>
61                                         <argument name="extension" required="true"/>
62                                         <argument name="context" />
63                                 </argument>
64                                 <argument name="extension2" argsep="@" multiple="true">
65                                         <para>Additional specifications of pickup targets.</para>
66                                         <argument name="extension2" required="true"/>
67                                         <argument name="context2"/>
68                                 </argument>
69                         </parameter>
70                 </syntax>
71                 <description>
72                         <para>This application can pickup a specified ringing channel.  The channel
73                         to pickup can be specified in the following ways.</para>
74                         <para>1) If no <replaceable>extension</replaceable> targets are specified,
75                         the application will pickup a channel matching the pickup group of the
76                         requesting channel.</para>
77                         <para>2) If the <replaceable>extension</replaceable> is specified with a
78                         <replaceable>context</replaceable> of the special string
79                         <literal>PICKUPMARK</literal> (for example 10@PICKUPMARK), the application
80                         will pickup a channel which has defined the channel variable
81                         <variable>PICKUPMARK</variable> with the same value as
82                         <replaceable>extension</replaceable> (in this example,
83                         <literal>10</literal>).</para>
84                         <para>3) If the <replaceable>extension</replaceable> is specified
85                         with or without a <replaceable>context</replaceable>, the channel with a
86                         matching <replaceable>extension</replaceable> and <replaceable>context</replaceable>
87                         will be picked up.  If no <replaceable>context</replaceable> is specified,
88                         the current context will be used.</para>
89                         <note><para>The <replaceable>extension</replaceable> is typically set on
90                         matching channels by the dial application that created the channel.  The
91                         <replaceable>context</replaceable> is set on matching channels by the
92                         channel driver for the device.</para></note>
93                 </description>
94         </application>
95         <application name="PickupChan" language="en_US">
96                 <synopsis>
97                         Pickup a ringing channel.
98                 </synopsis>
99                 <syntax >
100                         <parameter name="channel" argsep="&amp;" required="true">
101                                 <argument name="channel" required="true" />
102                                 <argument name="channel2" required="false" multiple="true" />
103                                 <para>List of channel names or channel uniqueids to pickup if ringing.
104                                         For example, a channel name could be <literal>SIP/bob</literal> or
105                                         <literal>SIP/bob-00000000</literal> to find
106                                         <literal>SIP/bob-00000000</literal>.
107                                 </para>
108                         </parameter>
109                         <parameter name="options" required="false">
110                                 <optionlist>
111                                         <option name="p">
112                                                 <para>Supplied channel names are prefixes.  For example,
113                                                         <literal>SIP/bob</literal> will match
114                                                         <literal>SIP/bob-00000000</literal> and
115                                                         <literal>SIP/bobby-00000000</literal>.
116                                                 </para>
117                                         </option>
118                                 </optionlist>
119                         </parameter>
120                 </syntax>
121                 <description>
122                         <para>Pickup a specified <replaceable>channel</replaceable> if ringing.</para>
123                 </description>
124         </application>
125  ***/
126
127 static const char app[] = "Pickup";
128 static const char app2[] = "PickupChan";
129
130 struct pickup_by_name_args {
131         /*! Channel attempting to pickup a call. */
132         struct ast_channel *chan;
133         /*! Channel uniqueid or partial channel name to match. */
134         const char *name;
135         /*! Length of partial channel name to match. */
136         size_t len;
137 };
138
139 static int find_by_name(void *obj, void *arg, void *data, int flags)
140 {
141         struct ast_channel *target = obj;/*!< Potential pickup target */
142         struct pickup_by_name_args *args = data;
143
144         if (args->chan == target) {
145                 /* The channel attempting to pickup a call cannot pickup itself. */
146                 return 0;
147         }
148
149         ast_channel_lock(target);
150         if (!strncasecmp(ast_channel_name(target), args->name, args->len)
151                 && ast_can_pickup(target)) {
152                 /* Return with the channel still locked on purpose */
153                 return CMP_MATCH | CMP_STOP;
154         }
155         ast_channel_unlock(target);
156
157         return 0;
158 }
159
160 static int find_by_uniqueid(void *obj, void *arg, void *data, int flags)
161 {
162         struct ast_channel *target = obj;/*!< Potential pickup target */
163         struct pickup_by_name_args *args = data;
164
165         if (args->chan == target) {
166                 /* The channel attempting to pickup a call cannot pickup itself. */
167                 return 0;
168         }
169
170         ast_channel_lock(target);
171         if (!strcasecmp(ast_channel_uniqueid(target), args->name)
172                 && ast_can_pickup(target)) {
173                 /* Return with the channel still locked on purpose */
174                 return CMP_MATCH | CMP_STOP;
175         }
176         ast_channel_unlock(target);
177
178         return 0;
179 }
180
181 /*! \brief Helper Function to walk through ALL channels checking NAME and STATE */
182 static struct ast_channel *find_by_channel(struct ast_channel *chan, const char *channame)
183 {
184         struct ast_channel *target;
185         char *chkchan;
186         struct pickup_by_name_args pickup_args;
187
188         pickup_args.chan = chan;
189
190         if (strchr(channame, '-')) {
191                 /*
192                  * Use the given channel name string as-is.  This allows a full channel
193                  * name with a typical sequence number to be used as well as still
194                  * allowing the odd partial channel name that has a '-' in it to still
195                  * work, i.e. Local/bob@en-phone.
196                  */
197                 pickup_args.len = strlen(channame);
198                 pickup_args.name = channame;
199         } else {
200                 /*
201                  * Append a '-' for the comparison so we check the channel name less
202                  * a sequence number, i.e Find SIP/bob- and not SIP/bobby.
203                  */
204                 pickup_args.len = strlen(channame) + 1;
205                 chkchan = ast_alloca(pickup_args.len + 1);
206                 strcpy(chkchan, channame);/* Safe */
207                 strcat(chkchan, "-");
208                 pickup_args.name = chkchan;
209         }
210         target = ast_channel_callback(find_by_name, NULL, &pickup_args, 0);
211         if (target) {
212                 return target;
213         }
214
215         /* Now try a search for uniqueid. */
216         pickup_args.name = channame;
217         pickup_args.len = 0;
218         return ast_channel_callback(find_by_uniqueid, NULL, &pickup_args, 0);
219 }
220
221 /*! \brief Attempt to pick up named channel. */
222 static int pickup_by_channel(struct ast_channel *chan, const char *name)
223 {
224         int res = -1;
225         struct ast_channel *target;/*!< Potential pickup target */
226
227         /* The found channel is already locked. */
228         target = find_by_channel(chan, name);
229         if (target) {
230                 res = ast_do_pickup(chan, target);
231                 ast_channel_unlock(target);
232                 target = ast_channel_unref(target);
233         }
234
235         return res;
236 }
237
238 /* Attempt to pick up specified extension with context */
239 static int pickup_by_exten(struct ast_channel *chan, const char *exten, const char *context)
240 {
241         struct ast_channel *target = NULL;/*!< Potential pickup target */
242         struct ast_channel_iterator *iter;
243         int res = -1;
244
245         if (!(iter = ast_channel_iterator_by_exten_new(exten, context))) {
246                 return -1;
247         }
248
249         while ((target = ast_channel_iterator_next(iter))) {
250                 ast_channel_lock(target);
251                 if ((chan != target) && ast_can_pickup(target)) {
252                         ast_log(LOG_NOTICE, "%s pickup by %s\n", ast_channel_name(target), ast_channel_name(chan));
253                         break;
254                 }
255                 ast_channel_unlock(target);
256                 target = ast_channel_unref(target);
257         }
258
259         ast_channel_iterator_destroy(iter);
260
261         if (target) {
262                 res = ast_do_pickup(chan, target);
263                 ast_channel_unlock(target);
264                 target = ast_channel_unref(target);
265         }
266
267         return res;
268 }
269
270 static int find_by_mark(void *obj, void *arg, void *data, int flags)
271 {
272         struct ast_channel *target = obj;/*!< Potential pickup target */
273         struct ast_channel *chan = arg;
274         const char *mark = data;
275         const char *tmp;
276
277         if (chan == target) {
278                 /* The channel attempting to pickup a call cannot pickup itself. */
279                 return 0;
280         }
281
282         ast_channel_lock(target);
283         tmp = pbx_builtin_getvar_helper(target, PICKUPMARK);
284         if (tmp && !strcasecmp(tmp, mark) && ast_can_pickup(target)) {
285                 /* Return with the channel still locked on purpose */
286                 return CMP_MATCH | CMP_STOP;
287         }
288         ast_channel_unlock(target);
289
290         return 0;
291 }
292
293 /* Attempt to pick up specified mark */
294 static int pickup_by_mark(struct ast_channel *chan, const char *mark)
295 {
296         struct ast_channel *target;/*!< Potential pickup target */
297         int res = -1;
298
299         /* The found channel is already locked. */
300         target = ast_channel_callback(find_by_mark, chan, (char *) mark, 0);
301         if (target) {
302                 res = ast_do_pickup(chan, target);
303                 ast_channel_unlock(target);
304                 target = ast_channel_unref(target);
305         }
306
307         return res;
308 }
309
310 static int pickup_by_group(struct ast_channel *chan)
311 {
312         struct ast_channel *target;/*!< Potential pickup target */
313         int res = -1;
314
315         /* The found channel is already locked. */
316         target = ast_pickup_find_by_group(chan);
317         if (target) {
318                 ast_log(LOG_NOTICE, "pickup %s attempt by %s\n", ast_channel_name(target), ast_channel_name(chan));
319                 res = ast_do_pickup(chan, target);
320                 ast_channel_unlock(target);
321                 target = ast_channel_unref(target);
322         }
323
324         return res;
325 }
326
327 /* application entry point for Pickup() */
328 static int pickup_exec(struct ast_channel *chan, const char *data)
329 {
330         char *parse;
331         char *exten;
332         char *context;
333
334         if (ast_strlen_zero(data)) {
335                 return pickup_by_group(chan) ? 0 : -1;
336         }
337
338         /* Parse extension (and context if there) */
339         parse = ast_strdupa(data);
340         for (;;) {
341                 if (ast_strlen_zero(parse)) {
342                         break;
343                 }
344                 exten = strsep(&parse, "&");
345                 if (ast_strlen_zero(exten)) {
346                         continue;
347                 }
348
349                 context = strchr(exten, '@');
350                 if (context) {
351                         *context++ = '\0';
352                 }
353                 if (!ast_strlen_zero(context) && !strcasecmp(context, PICKUPMARK)) {
354                         if (!pickup_by_mark(chan, exten)) {
355                                 /* Pickup successful.  Stop the dialplan this channel is a zombie. */
356                                 return -1;
357                         }
358                 } else {
359                         if (ast_strlen_zero(context)) {
360                                 context = (char *) ast_channel_context(chan);
361                         }
362                         if (!pickup_by_exten(chan, exten, context)) {
363                                 /* Pickup successful.  Stop the dialplan this channel is a zombie. */
364                                 return -1;
365                         }
366                 }
367                 ast_log(LOG_NOTICE, "No target channel found for %s@%s.\n", exten, context);
368         }
369
370         /* Pickup failed.  Keep going in the dialplan. */
371         return 0;
372 }
373
374 /* Find channel for pick up specified by partial channel name */
375 static struct ast_channel *find_by_part(struct ast_channel *chan, const char *part)
376 {
377         struct ast_channel *target;
378         struct pickup_by_name_args pickup_args;
379
380         pickup_args.chan = chan;
381
382         /* Try a partial channel name search. */
383         pickup_args.name = part;
384         pickup_args.len = strlen(part);
385         target = ast_channel_callback(find_by_name, NULL, &pickup_args, 0);
386         if (target) {
387                 return target;
388         }
389
390         /* Now try a search for uniqueid. */
391         pickup_args.name = part;
392         pickup_args.len = 0;
393         return ast_channel_callback(find_by_uniqueid, NULL, &pickup_args, 0);
394 }
395
396 /* Attempt to pick up specified by partial channel name */
397 static int pickup_by_part(struct ast_channel *chan, const char *part)
398 {
399         struct ast_channel *target;/*!< Potential pickup target */
400         int res = -1;
401
402         /* The found channel is already locked. */
403         target = find_by_part(chan, part);
404         if (target) {
405                 res = ast_do_pickup(chan, target);
406                 ast_channel_unlock(target);
407                 target = ast_channel_unref(target);
408         }
409
410         return res;
411 }
412
413 enum OPT_PICKUPCHAN_FLAGS {
414         OPT_PICKUPCHAN_PARTIAL =   (1 << 0),    /* Channel name is a partial name. */
415 };
416
417 AST_APP_OPTIONS(pickupchan_opts, BEGIN_OPTIONS
418         AST_APP_OPTION('p', OPT_PICKUPCHAN_PARTIAL),
419 END_OPTIONS);
420
421 /* application entry point for PickupChan() */
422 static int pickupchan_exec(struct ast_channel *chan, const char *data)
423 {
424         char *pickup = NULL;
425         char *parse = ast_strdupa(data);
426         AST_DECLARE_APP_ARGS(args,
427                 AST_APP_ARG(channel);
428                 AST_APP_ARG(options);
429                 AST_APP_ARG(other);     /* Any remining unused arguments */
430         );
431         struct ast_flags opts;
432
433         AST_STANDARD_APP_ARGS(args, parse);
434
435         if (ast_strlen_zero(args.channel)) {
436                 ast_log(LOG_WARNING, "PickupChan requires an argument (channel)!\n");
437                 /* Pickup failed.  Keep going in the dialplan. */
438                 return 0;
439         }
440         if (ast_app_parse_options(pickupchan_opts, &opts, NULL, args.options)) {
441                 /*
442                  * General invalid option syntax.
443                  * Pickup failed.  Keep going in the dialplan.
444                  */
445                 return 0;
446         }
447
448         /* Parse channel */
449         for (;;) {
450                 if (ast_strlen_zero(args.channel)) {
451                         break;
452                 }
453                 pickup = strsep(&args.channel, "&");
454                 if (ast_strlen_zero(pickup)) {
455                         continue;
456                 }
457
458                 if (ast_test_flag(&opts, OPT_PICKUPCHAN_PARTIAL)) {
459                         if (!pickup_by_part(chan, pickup)) {
460                                 /* Pickup successful.  Stop the dialplan this channel is a zombie. */
461                                 return -1;
462                         }
463                 } else if (!pickup_by_channel(chan, pickup)) {
464                         /* Pickup successful.  Stop the dialplan this channel is a zombie. */
465                         return -1;
466                 }
467                 ast_log(LOG_NOTICE, "No target channel found for %s.\n", pickup);
468         }
469
470         /* Pickup failed.  Keep going in the dialplan. */
471         return 0;
472 }
473
474 static int unload_module(void)
475 {
476         int res;
477
478         res = ast_unregister_application(app);
479         res |= ast_unregister_application(app2);
480
481         return res;
482 }
483
484 static int load_module(void)
485 {
486         int res;
487
488         res = ast_register_application_xml(app, pickup_exec);
489         res |= ast_register_application_xml(app2, pickupchan_exec);
490
491         return res;
492 }
493
494 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Directed Call Pickup Application");