77a4ffe85d134138a8047ae34c96756faede667c
[asterisk/asterisk.git] / apps / app_chanspy.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2005 Anthony Minessale II (anthmct@yahoo.com)
5  * Copyright (C) 2005 - 2006, Digium, Inc.
6  *
7  * A license has been granted to Digium (via disclaimer) for the use of
8  * this code.
9  *
10  * See http://www.asterisk.org for more information about
11  * the Asterisk project. Please do not directly contact
12  * any of the maintainers of this project for assistance;
13  * the project provides a web site, mailing lists and IRC
14  * channels for your use.
15  *
16  * This program is free software, distributed under the terms of
17  * the GNU General Public License Version 2. See the LICENSE file
18  * at the top of the source tree.
19  */
20
21 /*! \file
22  *
23  * \brief ChanSpy: Listen in on any channel.
24  *
25  * \author Anthony Minessale II <anthmct@yahoo.com>
26  *
27  * \ingroup applications
28  */
29
30 #include "asterisk.h"
31
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33
34 #include <ctype.h>
35
36 #include "asterisk/paths.h" /* use ast_config_AST_MONITOR_DIR */
37 #include "asterisk/file.h"
38 #include "asterisk/channel.h"
39 #include "asterisk/audiohook.h"
40 #include "asterisk/features.h"
41 #include "asterisk/app.h"
42 #include "asterisk/utils.h"
43 #include "asterisk/say.h"
44 #include "asterisk/pbx.h"
45 #include "asterisk/translate.h"
46 #include "asterisk/module.h"
47 #include "asterisk/lock.h"
48
49 #define AST_NAME_STRLEN 256
50
51 static const char *tdesc = "Listen to a channel, and optionally whisper into it";
52 static const char *app_chan = "ChanSpy";
53 static const char *desc_chan =
54 "  ChanSpy([chanprefix][,options]): This application is used to listen to the\n"
55 "audio from an Asterisk channel. This includes the audio coming in and\n"
56 "out of the channel being spied on. If the 'chanprefix' parameter is specified,\n"
57 "only channels beginning with this string will be spied upon.\n"
58 "  While spying, the following actions may be performed:\n"
59 "    - Dialing # cycles the volume level.\n"
60 "    - Dialing * will stop spying and look for another channel to spy on.\n"
61 "    - Dialing a series of digits followed by # builds a channel name to append\n"
62 "      to 'chanprefix'. For example, executing ChanSpy(Agent) and then dialing\n"
63 "      the digits '1234#' while spying will begin spying on the channel\n"
64 "      'Agent/1234'.\n"
65 "  Note: The X option supersedes the three features above in that if a valid\n"
66 "        single digit extension exists in the correct context ChanSpy will\n"
67 "        exit to it. This also disables choosing a channel based on 'chanprefix'\n"
68 "        and a digit sequence.\n"
69 "  Options:\n"
70 "    b             - Only spy on channels involved in a bridged call.\n"
71 "    g(grp)        - Match only channels where their SPYGROUP variable is set to\n"
72 "                    contain 'grp' in an optional : delimited list.\n"
73 "    q             - Don't play a beep when beginning to spy on a channel, or speak the\n"
74 "                    selected channel name.\n"
75 "    r[(basename)] - Record the session to the monitor spool directory. An\n"
76 "                    optional base for the filename may be specified. The\n"
77 "                    default is 'chanspy'.\n"
78 "    v([value])    - Adjust the initial volume in the range from -4 to 4. A\n"
79 "                    negative value refers to a quieter setting.\n"
80 "    w             - Enable 'whisper' mode, so the spying channel can talk to\n"
81 "                    the spied-on channel.\n"
82 "    W             - Enable 'private whisper' mode, so the spying channel can\n"
83 "                    talk to the spied-on channel but cannot listen to that\n"
84 "                    channel.\n"
85 "    o             - Only listen to audio coming from this channel.\n"
86 "    X             - Allow the user to exit ChanSpy to a valid single digit\n"
87 "                    numeric extension in the current context or the context\n"
88 "                    specified by the SPY_EXIT_CONTEXT channel variable. The\n"
89 "                    name of the last channel that was spied on will be stored\n"
90 "                    in the SPY_CHANNEL variable.\n"
91 "    e(ext)        - Enable 'enforced' mode, so the spying channel can\n"
92 "                    only monitor extensions whose name is in the 'ext' : \n"
93 "                    delimited list.\n"
94 ;
95
96 static const char *app_ext = "ExtenSpy";
97 static const char *desc_ext =
98 "  ExtenSpy(exten[@context][,options]): This application is used to listen to the\n"
99 "audio from an Asterisk channel. This includes the audio coming in and\n"
100 "out of the channel being spied on. Only channels created by outgoing calls for the\n"
101 "specified extension will be selected for spying. If the optional context is not\n"
102 "supplied, the current channel's context will be used.\n"
103 "  While spying, the following actions may be performed:\n"
104 "    - Dialing # cycles the volume level.\n"
105 "    - Dialing * will stop spying and look for another channel to spy on.\n"
106 "  Note: The X option superseeds the two features above in that if a valid\n"
107 "        single digit extension exists in the correct context it ChanSpy will\n"
108 "        exit to it.\n"
109 "  Options:\n"
110 "    b             - Only spy on channels involved in a bridged call.\n"
111 "    g(grp)        - Match only channels where their ${SPYGROUP} variable is set to\n"
112 "                    contain 'grp' in an optional : delimited list.\n"
113 "    q             - Don't play a beep when beginning to spy on a channel, or speak the\n"
114 "                    selected channel name.\n"
115 "    r[(basename)] - Record the session to the monitor spool directory. An\n"
116 "                    optional base for the filename may be specified. The\n"
117 "                    default is 'chanspy'.\n"
118 "    v([value])    - Adjust the initial volume in the range from -4 to 4. A\n"
119 "                    negative value refers to a quieter setting.\n"
120 "    w             - Enable 'whisper' mode, so the spying channel can talk to\n"
121 "                    the spied-on channel.\n"
122 "    W             - Enable 'private whisper' mode, so the spying channel can\n"
123 "                    talk to the spied-on channel but cannot listen to that\n"
124 "                    channel.\n"
125 "    o             - Only listen to audio coming from this channel.\n"
126 "    X             - Allow the user to exit ChanSpy to a valid single digit\n"
127 "                    numeric extension in the current context or the context\n"
128 "                    specified by the SPY_EXIT_CONTEXT channel variable. The\n"
129 "                    name of the last channel that was spied on will be stored\n"
130 "                    in the SPY_CHANNEL variable.\n"
131 ;
132
133 enum {
134         OPTION_QUIET     = (1 << 0),    /* Quiet, no announcement */
135         OPTION_BRIDGED   = (1 << 1),    /* Only look at bridged calls */
136         OPTION_VOLUME    = (1 << 2),    /* Specify initial volume */
137         OPTION_GROUP     = (1 << 3),    /* Only look at channels in group */
138         OPTION_RECORD    = (1 << 4),
139         OPTION_WHISPER   = (1 << 5),
140         OPTION_PRIVATE   = (1 << 6),    /* Private Whisper mode */
141         OPTION_READONLY  = (1 << 7),    /* Don't mix the two channels */
142         OPTION_EXIT      = (1 << 8),    /* Exit to a valid single digit extension */
143         OPTION_ENFORCED  = (1 << 9),    /* Enforced mode */
144 } chanspy_opt_flags;
145
146 enum {
147         OPT_ARG_VOLUME = 0,
148         OPT_ARG_GROUP,
149         OPT_ARG_RECORD,
150         OPT_ARG_ENFORCED,
151         OPT_ARG_ARRAY_SIZE,
152 } chanspy_opt_args;
153
154 AST_APP_OPTIONS(spy_opts, {
155         AST_APP_OPTION('q', OPTION_QUIET),
156         AST_APP_OPTION('b', OPTION_BRIDGED),
157         AST_APP_OPTION('w', OPTION_WHISPER),
158         AST_APP_OPTION('W', OPTION_PRIVATE),
159         AST_APP_OPTION_ARG('v', OPTION_VOLUME, OPT_ARG_VOLUME),
160         AST_APP_OPTION_ARG('g', OPTION_GROUP, OPT_ARG_GROUP),
161         AST_APP_OPTION_ARG('r', OPTION_RECORD, OPT_ARG_RECORD),
162         AST_APP_OPTION_ARG('e', OPTION_ENFORCED, OPT_ARG_ENFORCED),
163         AST_APP_OPTION('o', OPTION_READONLY),
164         AST_APP_OPTION('X', OPTION_EXIT),
165 });
166
167
168 struct chanspy_translation_helper {
169         /* spy data */
170         struct ast_audiohook spy_audiohook;
171         struct ast_audiohook whisper_audiohook;
172         int fd;
173         int volfactor;
174 };
175
176 static void *spy_alloc(struct ast_channel *chan, void *data)
177 {
178         /* just store the data pointer in the channel structure */
179         return data;
180 }
181
182 static void spy_release(struct ast_channel *chan, void *data)
183 {
184         /* nothing to do */
185 }
186
187 static int spy_generate(struct ast_channel *chan, void *data, int len, int samples)
188 {
189         struct chanspy_translation_helper *csth = data;
190         struct ast_frame *f = NULL;
191
192         ast_audiohook_lock(&csth->spy_audiohook);
193         if (csth->spy_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
194                 /* Channel is already gone more than likely */
195                 ast_audiohook_unlock(&csth->spy_audiohook);
196                 return -1;
197         }
198
199         f = ast_audiohook_read_frame(&csth->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR);
200
201         ast_audiohook_unlock(&csth->spy_audiohook);
202
203         if (!f)
204                 return 0;
205
206         if (ast_write(chan, f)) {
207                 ast_frfree(f);
208                 return -1;
209         }
210
211         if (csth->fd)
212                 write(csth->fd, f->data, f->datalen);
213
214         ast_frfree(f);
215
216         return 0;
217 }
218
219 static struct ast_generator spygen = {
220         .alloc = spy_alloc,
221         .release = spy_release,
222         .generate = spy_generate,
223 };
224
225 static int start_spying(struct ast_channel *chan, struct ast_channel *spychan, struct ast_audiohook *audiohook)
226 {
227         int res = 0;
228         struct ast_channel *peer = NULL;
229
230         ast_log(LOG_NOTICE, "Attaching %s to %s\n", spychan->name, chan->name);
231
232         res = ast_audiohook_attach(chan, audiohook);
233
234         if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
235                 ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
236
237         return res;
238 }
239
240 static int channel_spy(struct ast_channel *chan, struct ast_channel *spyee, int *volfactor, int fd,
241         const struct ast_flags *flags, char *exitcontext)
242 {
243         struct chanspy_translation_helper csth;
244         int running = 0, res, x = 0;
245         char inp[24] = {0};
246         char *name;
247         struct ast_frame *f;
248         struct ast_silence_generator *silgen = NULL;
249
250         if (ast_check_hangup(chan) || ast_check_hangup(spyee))
251                 return 0;
252
253         name = ast_strdupa(spyee->name);
254         ast_verb(2, "Spying on channel %s\n", name);
255
256         memset(&csth, 0, sizeof(csth));
257
258         ast_audiohook_init(&csth.spy_audiohook, AST_AUDIOHOOK_TYPE_SPY, "ChanSpy");
259
260         if (start_spying(spyee, chan, &csth.spy_audiohook)) {
261                 ast_audiohook_destroy(&csth.spy_audiohook);
262                 return 0;
263         }
264
265         if (ast_test_flag(flags, OPTION_WHISPER)) {
266                 ast_audiohook_init(&csth.whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "ChanSpy");
267                 start_spying(spyee, chan, &csth.whisper_audiohook);
268         }
269
270         csth.volfactor = *volfactor;
271
272         if (csth.volfactor) {
273                 csth.spy_audiohook.options.read_volume = csth.volfactor;
274                 csth.spy_audiohook.options.write_volume = csth.volfactor;
275         }
276
277         csth.fd = fd;
278
279         if (ast_test_flag(flags, OPTION_PRIVATE))
280                 silgen = ast_channel_start_silence_generator(chan);
281         else
282                 ast_activate_generator(chan, &spygen, &csth);
283
284         /* We can no longer rely on 'spyee' being an actual channel;
285            it can be hung up and freed out from under us. However, the
286            channel destructor will put NULL into our csth.spy.chan
287            field when that happens, so that is our signal that the spyee
288            channel has gone away.
289         */
290
291         /* Note: it is very important that the ast_waitfor() be the first
292            condition in this expression, so that if we wait for some period
293            of time before receiving a frame from our spying channel, we check
294            for hangup on the spied-on channel _after_ knowing that a frame
295            has arrived, since the spied-on channel could have gone away while
296            we were waiting
297         */
298         while ((res = ast_waitfor(chan, -1) > -1) && csth.spy_audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
299                 if (!(f = ast_read(chan)) || ast_check_hangup(chan)) {
300                         running = -1;
301                         break;
302                 }
303
304                 if (ast_test_flag(flags, OPTION_WHISPER) && f->frametype == AST_FRAME_VOICE) {
305                         ast_audiohook_lock(&csth.whisper_audiohook);
306                         ast_audiohook_write_frame(&csth.whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
307                         ast_audiohook_unlock(&csth.whisper_audiohook);
308                         ast_frfree(f);
309                         continue;
310                 }
311                 
312                 res = (f->frametype == AST_FRAME_DTMF) ? f->subclass : 0;
313                 ast_frfree(f);
314                 if (!res)
315                         continue;
316
317                 if (x == sizeof(inp))
318                         x = 0;
319
320                 if (res < 0) {
321                         running = -1;
322                         break;
323                 }
324
325                 if (ast_test_flag(flags, OPTION_EXIT)) {
326                         char tmp[2];
327                         tmp[0] = res;
328                         tmp[1] = '\0';
329                         if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) {
330                                 ast_debug(1, "Got DTMF %c, goto context %s\n", tmp[0], exitcontext);
331                                 pbx_builtin_setvar_helper(chan, "SPY_CHANNEL", name);
332                                 running = -2;
333                                 break;
334                         } else {
335                                 ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
336                         }
337                 } else if (res >= '0' && res <= '9') {
338                         inp[x++] = res;
339                 }
340
341                 if (res == '*') {
342                         running = 0;
343                         break;
344                 } else if (res == '#') {
345                         if (!ast_strlen_zero(inp)) {
346                                 running = atoi(inp);
347                                 break;
348                         }
349
350                         (*volfactor)++;
351                         if (*volfactor > 4)
352                                 *volfactor = -4;
353                         ast_verb(3, "Setting spy volume on %s to %d\n", chan->name, *volfactor);
354
355                         csth.volfactor = *volfactor;
356                         csth.spy_audiohook.options.read_volume = csth.volfactor;
357                         csth.spy_audiohook.options.write_volume = csth.volfactor;
358                 }
359         }
360
361         if (ast_test_flag(flags, OPTION_PRIVATE))
362                 ast_channel_stop_silence_generator(chan, silgen);
363         else
364                 ast_deactivate_generator(chan);
365
366         if (ast_test_flag(flags, OPTION_WHISPER)) {
367                 ast_audiohook_lock(&csth.whisper_audiohook);
368                 ast_audiohook_detach(&csth.whisper_audiohook);
369                 ast_audiohook_unlock(&csth.whisper_audiohook);
370                 ast_audiohook_destroy(&csth.whisper_audiohook);
371         }
372
373         ast_audiohook_lock(&csth.spy_audiohook);
374         ast_audiohook_detach(&csth.spy_audiohook);
375         ast_audiohook_unlock(&csth.spy_audiohook);
376         ast_audiohook_destroy(&csth.spy_audiohook);
377         
378         ast_verb(2, "Done Spying on channel %s\n", name);
379
380         return running;
381 }
382
383 static struct ast_channel *next_channel(const struct ast_channel *last, const char *spec,
384         const char *exten, const char *context)
385 {
386         struct ast_channel *this;
387
388         redo:
389         if (!ast_strlen_zero(spec))
390                 this = ast_walk_channel_by_name_prefix_locked(last, spec, strlen(spec));
391
392         else if (!ast_strlen_zero(exten))
393                 this = ast_walk_channel_by_exten_locked(last, exten, context);
394         else
395                 this = ast_channel_walk_locked(last);
396
397         if (this) {
398                 ast_channel_unlock(this);
399                 if (!strncmp(this->name, "Zap/pseudo", 10))
400                         goto redo;
401         }
402
403         return this;
404 }
405
406 static int common_exec(struct ast_channel *chan, const struct ast_flags *flags,
407         int volfactor, const int fd, const char *mygroup, const char *myenforced,
408         const char *spec, const char *exten, const char *context)
409 {
410         struct ast_channel *peer, *prev, *next;
411         char nameprefix[AST_NAME_STRLEN];
412         char peer_name[AST_NAME_STRLEN + 5];
413         char exitcontext[AST_MAX_CONTEXT] = "";
414         signed char zero_volume = 0;
415         int waitms;
416         int res;
417         char *ptr;
418         int num;
419         int num_spyed_upon = 1;
420
421         if (ast_test_flag(flags, OPTION_EXIT)) {
422                 const char *c;
423                 if ((c = pbx_builtin_getvar_helper(chan, "SPY_EXIT_CONTEXT")))
424                         ast_copy_string(exitcontext, c, sizeof(exitcontext));
425                 else if (!ast_strlen_zero(chan->macrocontext))
426                         ast_copy_string(exitcontext, chan->macrocontext, sizeof(exitcontext));
427                 else
428                         ast_copy_string(exitcontext, chan->context, sizeof(exitcontext));
429         }
430
431         if (chan->_state != AST_STATE_UP)
432                 ast_answer(chan);
433
434         ast_set_flag(chan, AST_FLAG_SPYING); /* so nobody can spy on us while we are spying */
435
436         waitms = 100;
437
438         for (;;) {
439                 if (!ast_test_flag(flags, OPTION_QUIET) && num_spyed_upon) {
440                         res = ast_streamfile(chan, "beep", chan->language);
441                         if (!res)
442                                 res = ast_waitstream(chan, "");
443                         else if (res < 0) {
444                                 ast_clear_flag(chan, AST_FLAG_SPYING);
445                                 break;
446                         }
447                         if (!ast_strlen_zero(exitcontext)) {
448                                 char tmp[2];
449                                 tmp[0] = res;
450                                 tmp[1] = '\0';
451                                 if (!ast_goto_if_exists(chan, exitcontext, tmp, 1))
452                                         goto exit;
453                                 else
454                                         ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
455                         }
456                 }
457
458                 res = ast_waitfordigit(chan, waitms);
459                 if (res < 0) {
460                         ast_clear_flag(chan, AST_FLAG_SPYING);
461                         break;
462                 }
463                 if (!ast_strlen_zero(exitcontext)) {
464                         char tmp[2];
465                         tmp[0] = res;
466                         tmp[1] = '\0';
467                         if (!ast_goto_if_exists(chan, exitcontext, tmp, 1))
468                                 goto exit;
469                         else
470                                 ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
471                 }
472
473                 /* reset for the next loop around, unless overridden later */
474                 waitms = 100;
475                 peer = prev = next = NULL;
476                 num_spyed_upon = 0;
477
478                 for (peer = next_channel(peer, spec, exten, context);
479                         peer;
480                         prev = peer, peer = next ? next : next_channel(peer, spec, exten, context), next = NULL) {
481                         const char *group;
482                         int igrp = !mygroup;
483                         char *groups[25];
484                         int num_groups = 0;
485                         char *dup_group;
486                         int x;
487                         char *s;
488                         char *buffer;
489                         char *end;
490                         char *ext;
491                         char *form_enforced;
492                         int ienf = !myenforced;
493
494                         if (peer == prev)
495                                 break;
496
497                         if (peer == chan)
498                                 continue;
499
500                         if (ast_test_flag(flags, OPTION_BRIDGED) && !ast_bridged_channel(peer))
501                                 continue;
502
503                         if (ast_check_hangup(peer) || ast_test_flag(peer, AST_FLAG_SPYING))
504                                 continue;
505
506                         if (mygroup) {
507                                 if ((group = pbx_builtin_getvar_helper(peer, "SPYGROUP"))) {
508                                         dup_group = ast_strdupa(group);
509                                         num_groups = ast_app_separate_args(dup_group, ':', groups,
510                                                 sizeof(groups) / sizeof(groups[0]));
511                                 }
512
513                                 for (x = 0; x < num_groups; x++) {
514                                         if (!strcmp(mygroup, groups[x])) {
515                                                 igrp = 1;
516                                                 break;
517                                         }
518                                 }
519                         }
520
521                         if (!igrp)
522                                 continue;
523
524                         if (myenforced) {
525
526                                 /* We don't need to allocate more space than just the
527                                 length of (peer->name) for ext as we will cut the
528                                 channel name's ending before copying into ext */
529
530                                 ext = alloca(strlen(peer->name));
531
532                                 form_enforced = alloca(strlen(myenforced) + 3);
533
534                                 strcpy(form_enforced, ":");
535                                 strcat(form_enforced, myenforced);
536                                 strcat(form_enforced, ":");
537
538                                 buffer = ast_strdupa(peer->name);
539                                 
540                                 if ((end = strchr(buffer, '-'))) {
541                                         *end++ = ':';
542                                         *end = '\0';
543                                 }
544
545                                 strcpy(ext, ":");
546                                 strcat(ext, buffer);
547
548                                 if (strcasestr(form_enforced, ext))
549                                         ienf = 1;
550                         }
551
552                         if (!ienf)
553                                 continue;
554
555                         strcpy(peer_name, "spy-");
556                         strncat(peer_name, peer->name, AST_NAME_STRLEN);
557                         ptr = strchr(peer_name, '/');
558                         *ptr++ = '\0';
559
560                         for (s = peer_name; s < ptr; s++)
561                                 *s = tolower(*s);
562
563                         if (!ast_test_flag(flags, OPTION_QUIET)) {
564                                 if (ast_fileexists(peer_name, NULL, NULL) != -1) {
565                                         res = ast_streamfile(chan, peer_name, chan->language);
566                                         if (!res)
567                                                 res = ast_waitstream(chan, "");
568                                         if (res)
569                                                 break;
570                                 } else
571                                         res = ast_say_character_str(chan, peer_name, "", chan->language);
572                                 if ((num = atoi(ptr)))
573                                         ast_say_digits(chan, atoi(ptr), "", chan->language);
574                         }
575
576                         waitms = 5000;
577                         res = channel_spy(chan, peer, &volfactor, fd, flags, exitcontext);
578                         num_spyed_upon++;
579
580                         if (res == -1) {
581                                 goto exit;
582                         } else if (res == -2) {
583                                 res = 0;
584                                 goto exit;
585                         } else if (res > 1 && spec) {
586                                 snprintf(nameprefix, AST_NAME_STRLEN, "%s/%d", spec, res);
587                                 if ((next = ast_get_channel_by_name_prefix_locked(nameprefix, strlen(nameprefix)))) {
588                                         ast_channel_unlock(next);
589                                 } else {
590                                         /* stay on this channel */
591                                         next = peer;
592                                 }
593                                 peer = NULL;
594                         }
595                 }
596         }
597 exit:
598
599         ast_clear_flag(chan, AST_FLAG_SPYING);
600
601         ast_channel_setoption(chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0);
602
603         return res;
604 }
605
606 static int chanspy_exec(struct ast_channel *chan, void *data)
607 {
608         char *myenforced = NULL;
609         char *mygroup = NULL;
610         char *recbase = NULL;
611         int fd = 0;
612         struct ast_flags flags;
613         int oldwf = 0;
614         int volfactor = 0;
615         int res;
616         AST_DECLARE_APP_ARGS(args,
617                 AST_APP_ARG(spec);
618                 AST_APP_ARG(options);
619         );
620         char *opts[OPT_ARG_ARRAY_SIZE];
621
622         data = ast_strdupa(data);
623         AST_STANDARD_APP_ARGS(args, data);
624
625         if (args.spec && !strcmp(args.spec, "all"))
626                 args.spec = NULL;
627
628         if (args.options) {
629                 ast_app_parse_options(spy_opts, &flags, opts, args.options);
630                 if (ast_test_flag(&flags, OPTION_GROUP))
631                         mygroup = opts[OPT_ARG_GROUP];
632
633                 if (ast_test_flag(&flags, OPTION_RECORD) &&
634                         !(recbase = opts[OPT_ARG_RECORD]))
635                         recbase = "chanspy";
636
637                 if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) {
638                         int vol;
639
640                         if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &vol) != 1) || (vol > 4) || (vol < -4))
641                                 ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
642                         else
643                                 volfactor = vol;
644                 }
645
646                 if (ast_test_flag(&flags, OPTION_PRIVATE))
647                         ast_set_flag(&flags, OPTION_WHISPER);
648
649                 if (ast_test_flag(&flags, OPTION_ENFORCED))
650                         myenforced = opts[OPT_ARG_ENFORCED];
651
652         } else
653                 ast_clear_flag(&flags, AST_FLAGS_ALL);
654
655         oldwf = chan->writeformat;
656         if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
657                 ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
658                 return -1;
659         }
660
661         if (recbase) {
662                 char filename[512];
663
664                 snprintf(filename, sizeof(filename), "%s/%s.%d.raw", ast_config_AST_MONITOR_DIR, recbase, (int) time(NULL));
665                 if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, AST_FILE_MODE)) <= 0) {
666                         ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename);
667                         fd = 0;
668                 }
669         }
670
671         res = common_exec(chan, &flags, volfactor, fd, mygroup, myenforced, args.spec, NULL, NULL);
672
673         if (fd)
674                 close(fd);
675
676         if (oldwf && ast_set_write_format(chan, oldwf) < 0)
677                 ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
678
679         return res;
680 }
681
682 static int extenspy_exec(struct ast_channel *chan, void *data)
683 {
684         char *ptr, *exten = NULL;
685         char *mygroup = NULL;
686         char *recbase = NULL;
687         int fd = 0;
688         struct ast_flags flags;
689         int oldwf = 0;
690         int volfactor = 0;
691         int res;
692         AST_DECLARE_APP_ARGS(args,
693                 AST_APP_ARG(context);
694                 AST_APP_ARG(options);
695         );
696
697         data = ast_strdupa(data);
698
699         AST_STANDARD_APP_ARGS(args, data);
700         if (!ast_strlen_zero(args.context) && (ptr = strchr(args.context, '@'))) {
701                 exten = args.context;
702                 *ptr++ = '\0';
703                 args.context = ptr;
704         }
705
706         if (ast_strlen_zero(args.context))
707                 args.context = ast_strdupa(chan->context);
708
709         if (args.options) {
710                 char *opts[OPT_ARG_ARRAY_SIZE];
711
712                 ast_app_parse_options(spy_opts, &flags, opts, args.options);
713                 if (ast_test_flag(&flags, OPTION_GROUP))
714                         mygroup = opts[OPT_ARG_GROUP];
715
716                 if (ast_test_flag(&flags, OPTION_RECORD) &&
717                         !(recbase = opts[OPT_ARG_RECORD]))
718                         recbase = "chanspy";
719
720                 if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) {
721                         int vol;
722
723                         if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &vol) != 1) || (vol > 4) || (vol < -4))
724                                 ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
725                         else
726                                 volfactor = vol;
727                 }
728
729                 if (ast_test_flag(&flags, OPTION_PRIVATE))
730                         ast_set_flag(&flags, OPTION_WHISPER);
731         } else
732                 ast_clear_flag(&flags, AST_FLAGS_ALL);
733
734         oldwf = chan->writeformat;
735         if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
736                 ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
737                 return -1;
738         }
739
740         if (recbase) {
741                 char filename[512];
742
743                 snprintf(filename, sizeof(filename), "%s/%s.%d.raw", ast_config_AST_MONITOR_DIR, recbase, (int) time(NULL));
744                 if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, AST_FILE_MODE)) <= 0) {
745                         ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename);
746                         fd = 0;
747                 }
748         }
749
750
751         res = common_exec(chan, &flags, volfactor, fd, mygroup, NULL, NULL, exten, args.context);
752
753         if (fd)
754                 close(fd);
755
756         if (oldwf && ast_set_write_format(chan, oldwf) < 0)
757                 ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
758
759         return res;
760 }
761
762 static int unload_module(void)
763 {
764         int res = 0;
765
766         res |= ast_unregister_application(app_chan);
767         res |= ast_unregister_application(app_ext);
768
769         return res;
770 }
771
772 static int load_module(void)
773 {
774         int res = 0;
775
776         res |= ast_register_application(app_chan, chanspy_exec, tdesc, desc_chan);
777         res |= ast_register_application(app_ext, extenspy_exec, tdesc, desc_ext);
778
779         return res;
780 }
781
782 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Listen to the audio of an active channel");