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