Asterisk - The Open Source Telephony Project  21.4.1
app_chanspy.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2005 Anthony Minessale II (anthmct@yahoo.com)
5  * Copyright (C) 2005 - 2008, 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  * \author Joshua Colp <jcolp@digium.com>
27  * \author Russell Bryant <russell@digium.com>
28  *
29  * \ingroup applications
30  */
31 
32 /*** MODULEINFO
33  <support_level>core</support_level>
34  ***/
35 
36 #include "asterisk.h"
37 
38 #include <ctype.h>
39 #include <errno.h>
40 
41 #include "asterisk/paths.h" /* use ast_config_AST_MONITOR_DIR */
42 #include "asterisk/file.h"
43 #include "asterisk/channel.h"
44 #include "asterisk/audiohook.h"
45 #include "asterisk/features.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/manager.h"
52 #include "asterisk/module.h"
53 #include "asterisk/lock.h"
54 #include "asterisk/options.h"
55 #include "asterisk/autochan.h"
56 #include "asterisk/stasis_channels.h"
57 #include "asterisk/json.h"
58 #include "asterisk/format_cache.h"
59 
60 #define AST_NAME_STRLEN 256
61 #define NUM_SPYGROUPS 128
62 
63 /*** DOCUMENTATION
64  <application name="ChanSpy" language="en_US">
65  <synopsis>
66  Listen to a channel, and optionally whisper into it.
67  </synopsis>
68  <syntax>
69  <parameter name="chanprefix" />
70  <parameter name="options">
71  <optionlist>
72  <option name="b">
73  <para>Only spy on channels involved in a bridged call.</para>
74  </option>
75  <option name="B">
76  <para>Instead of whispering on a single channel barge in on both
77  channels involved in the call.</para>
78  </option>
79  <option name="c">
80  <argument name="digit" required="true">
81  <para>Specify a DTMF digit that can be used to spy on the next available channel.</para>
82  </argument>
83  </option>
84  <option name="d">
85  <para>Override the typical numeric DTMF functionality and instead
86  use DTMF to switch between spy modes.</para>
87  <enumlist>
88  <enum name="4">
89  <para>spy mode</para>
90  </enum>
91  <enum name="5">
92  <para>whisper mode</para>
93  </enum>
94  <enum name="6">
95  <para>barge mode</para>
96  </enum>
97  </enumlist>
98  </option>
99  <option name="e">
100  <argument name="ext" required="true" />
101  <para>Enable <emphasis>enforced</emphasis> mode, so the spying channel can
102  only monitor extensions whose name is in the <replaceable>ext</replaceable> : delimited
103  list.</para>
104  </option>
105  <option name="E">
106  <para>Exit when the spied-on channel hangs up.</para>
107  </option>
108  <option name="g">
109  <argument name="grp" required="true">
110  <para>Only spy on channels in which one or more of the groups
111  listed in <replaceable>grp</replaceable> matches one or more groups from the
112  <variable>SPYGROUP</variable> variable set on the channel to be spied upon.</para>
113  </argument>
114  <note><para>both <replaceable>grp</replaceable> and <variable>SPYGROUP</variable> can contain
115  either a single group or a colon-delimited list of groups, such
116  as <literal>sales:support:accounting</literal>.</para></note>
117  </option>
118  <option name="l">
119  <para>Allow usage of a long queue to store audio frames.</para>
120  <note><para>This may introduce some delay in the received audio feed, but will improve the audio quality.</para></note>
121  </option>
122  <option name="n" argsep="@">
123  <para>Say the name of the person being spied on if that person has recorded
124  his/her name. If a context is specified, then that voicemail context will
125  be searched when retrieving the name, otherwise the <literal>default</literal> context
126  be used when searching for the name (i.e. if SIP/1000 is the channel being
127  spied on and no mailbox is specified, then <literal>1000</literal> will be used when searching
128  for the name).</para>
129  <argument name="mailbox" />
130  <argument name="context" />
131  </option>
132  <option name="o">
133  <para>Only listen to audio coming from this channel.</para>
134  </option>
135  <option name="q">
136  <para>Don't play a beep when beginning to spy on a channel, or speak the
137  selected channel name.</para>
138  </option>
139  <option name="r">
140  <para>Record the session to the monitor spool directory. An optional base for the filename
141  may be specified. The default is <literal>chanspy</literal>.</para>
142  <argument name="basename" />
143  </option>
144  <option name="s">
145  <para>Skip the playback of the channel type (i.e. SIP, IAX, etc) when
146  speaking the selected channel name.</para>
147  </option>
148  <option name="S">
149  <para>Stop when no more channels are left to spy on.</para>
150  </option>
151  <option name="u">
152  <para>The <literal>chanprefix</literal> parameter is a channel uniqueid
153  or fully specified channel name.</para>
154  </option>
155  <option name="v">
156  <argument name="value" />
157  <para>Adjust the initial volume in the range from <literal>-4</literal>
158  to <literal>4</literal>. A negative value refers to a quieter setting.</para>
159  </option>
160  <option name="w">
161  <para>Enable <literal>whisper</literal> mode, so the spying channel can talk to
162  the spied-on channel.</para>
163  </option>
164  <option name="W">
165  <para>Enable <literal>private whisper</literal> mode, so the spying channel can
166  talk to the spied-on channel but cannot listen to that channel.</para>
167  </option>
168  <option name="x">
169  <argument name="digit" required="true">
170  <para>Specify a DTMF digit that can be used to exit the application while actively
171  spying on a channel. If there is no channel being spied on, the DTMF digit will be
172  ignored.</para>
173  </argument>
174  </option>
175  <option name="X">
176  <para>Allow the user to exit ChanSpy to a valid single digit
177  numeric extension in the current context or the context
178  specified by the <variable>SPY_EXIT_CONTEXT</variable> channel variable. The
179  name of the last channel that was spied on will be stored
180  in the <variable>SPY_CHANNEL</variable> variable.</para>
181  </option>
182  </optionlist>
183  </parameter>
184  </syntax>
185  <description>
186  <para>This application is used to listen to the audio from an Asterisk channel. This includes the audio
187  coming in and out of the channel being spied on. If the <literal>chanprefix</literal> parameter is specified,
188  only channels beginning with this string will be spied upon.</para>
189  <para>While spying, the following actions may be performed:</para>
190  <para> - Dialing <literal>#</literal> cycles the volume level.</para>
191  <para> - Dialing <literal>*</literal> will stop spying and look for another channel to spy on.</para>
192  <para> - Dialing a series of digits followed by <literal>#</literal> builds a channel name to append
193  to <literal>chanprefix</literal>. For example, executing ChanSpy(Agent) and then dialing the digits '1234#'
194  while spying will begin spying on the channel 'Agent/1234'. Note that this feature will be overridden
195  if the 'd' or 'u' options are used.</para>
196  <note><para>The <replaceable>X</replaceable> option supersedes the three features above in that if a valid
197  single digit extension exists in the correct context ChanSpy will exit to it.
198  This also disables choosing a channel based on <literal>chanprefix</literal> and a digit sequence.</para></note>
199  </description>
200  <see-also>
201  <ref type="application">ExtenSpy</ref>
202  <ref type="managerEvent">ChanSpyStart</ref>
203  <ref type="managerEvent">ChanSpyStop</ref>
204  </see-also>
205  </application>
206  <application name="ExtenSpy" language="en_US">
207  <synopsis>
208  Listen to a channel, and optionally whisper into it.
209  </synopsis>
210  <syntax>
211  <parameter name="exten" required="true" argsep="@">
212  <argument name="exten" required="true">
213  <para>Specify extension.</para>
214  </argument>
215  <argument name="context">
216  <para>Optionally specify a context, defaults to <literal>default</literal>.</para>
217  </argument>
218  </parameter>
219  <parameter name="options">
220  <optionlist>
221  <option name="b">
222  <para>Only spy on channels involved in a bridged call.</para>
223  </option>
224  <option name="B">
225  <para>Instead of whispering on a single channel barge in on both
226  channels involved in the call.</para>
227  </option>
228  <option name="c">
229  <argument name="digit" required="true">
230  <para>Specify a DTMF digit that can be used to spy on the next available channel.</para>
231  </argument>
232  </option>
233  <option name="d">
234  <para>Override the typical numeric DTMF functionality and instead
235  use DTMF to switch between spy modes.</para>
236  <enumlist>
237  <enum name="4">
238  <para>spy mode</para>
239  </enum>
240  <enum name="5">
241  <para>whisper mode</para>
242  </enum>
243  <enum name="6">
244  <para>barge mode</para>
245  </enum>
246  </enumlist>
247  </option>
248  <option name="D">
249  <para>Interleave the audio coming from the channel and the audio coming to the channel in
250  the output audio as a dual channel stream, rather than mix it. Does nothing if 'o'
251  is also set.</para>
252  </option>
253  <option name="e">
254  <argument name="ext" required="true" />
255  <para>Enable <emphasis>enforced</emphasis> mode, so the spying channel can
256  only monitor extensions whose name is in the <replaceable>ext</replaceable> : delimited
257  list.</para>
258  </option>
259  <option name="E">
260  <para>Exit when the spied-on channel hangs up.</para>
261  </option>
262  <option name="g">
263  <argument name="grp" required="true">
264  <para>Only spy on channels in which one or more of the groups
265  listed in <replaceable>grp</replaceable> matches one or more groups from the
266  <variable>SPYGROUP</variable> variable set on the channel to be spied upon.</para>
267  </argument>
268  <note><para>both <replaceable>grp</replaceable> and <variable>SPYGROUP</variable> can contain
269  either a single group or a colon-delimited list of groups, such
270  as <literal>sales:support:accounting</literal>.</para></note>
271  </option>
272  <option name="l">
273  <para>Allow usage of a long queue to store audio frames.</para>
274  <note><para>This may introduce some delay in the received audio feed, but will improve the audio quality.</para></note>
275  </option>
276  <option name="n" argsep="@">
277  <para>Say the name of the person being spied on if that person has recorded
278  his/her name. If a context is specified, then that voicemail context will
279  be searched when retrieving the name, otherwise the <literal>default</literal> context
280  be used when searching for the name (i.e. if SIP/1000 is the channel being
281  spied on and no mailbox is specified, then <literal>1000</literal> will be used when searching
282  for the name).</para>
283  <argument name="mailbox" />
284  <argument name="context" />
285  </option>
286  <option name="o">
287  <para>Only listen to audio coming from this channel.</para>
288  </option>
289  <option name="q">
290  <para>Don't play a beep when beginning to spy on a channel, or speak the
291  selected channel name.</para>
292  </option>
293  <option name="r">
294  <para>Record the session to the monitor spool directory. An optional base for the filename
295  may be specified. The default is <literal>chanspy</literal>.</para>
296  <argument name="basename" />
297  </option>
298  <option name="s">
299  <para>Skip the playback of the channel type (i.e. SIP, IAX, etc) when
300  speaking the selected channel name.</para>
301  </option>
302  <option name="S">
303  <para>Stop when there are no more extensions left to spy on.</para>
304  </option>
305  <option name="v">
306  <argument name="value" />
307  <para>Adjust the initial volume in the range from <literal>-4</literal>
308  to <literal>4</literal>. A negative value refers to a quieter setting.</para>
309  </option>
310  <option name="w">
311  <para>Enable <literal>whisper</literal> mode, so the spying channel can talk to
312  the spied-on channel.</para>
313  </option>
314  <option name="W">
315  <para>Enable <literal>private whisper</literal> mode, so the spying channel can
316  talk to the spied-on channel but cannot listen to that channel.</para>
317  </option>
318  <option name="x">
319  <argument name="digit" required="true">
320  <para>Specify a DTMF digit that can be used to exit the application while actively
321  spying on a channel. If there is no channel being spied on, the DTMF digit will be
322  ignored.</para>
323  </argument>
324  </option>
325  <option name="X">
326  <para>Allow the user to exit ChanSpy to a valid single digit
327  numeric extension in the current context or the context
328  specified by the <variable>SPY_EXIT_CONTEXT</variable> channel variable. The
329  name of the last channel that was spied on will be stored
330  in the <variable>SPY_CHANNEL</variable> variable.</para>
331  </option>
332  </optionlist>
333  </parameter>
334  </syntax>
335  <description>
336  <para>This application is used to listen to the audio from an Asterisk channel. This includes
337  the audio coming in and out of the channel being spied on. Only channels created by outgoing calls for the
338  specified extension will be selected for spying. If the optional context is not supplied,
339  the current channel's context will be used.</para>
340  <para>While spying, the following actions may be performed:</para>
341  <para> - Dialing <literal>#</literal> cycles the volume level.</para>
342  <para> - Dialing <literal>*</literal> will stop spying and look for another channel to spy on.</para>
343  <note><para>The <replaceable>X</replaceable> option supersedes the three features above in that if a valid
344  single digit extension exists in the correct context ChanSpy will exit to it.
345  This also disables choosing a channel based on <literal>chanprefix</literal> and a digit sequence.</para></note>
346  </description>
347  <see-also>
348  <ref type="application">ChanSpy</ref>
349  <ref type="managerEvent">ChanSpyStart</ref>
350  <ref type="managerEvent">ChanSpyStop</ref>
351  </see-also>
352  </application>
353  <application name="DAHDIScan" language="en_US">
354  <synopsis>
355  Scan DAHDI channels to monitor calls.
356  </synopsis>
357  <syntax>
358  <parameter name="group">
359  <para>Limit scanning to a channel <replaceable>group</replaceable> by setting this option.</para>
360  </parameter>
361  </syntax>
362  <description>
363  <para>Allows a call center manager to monitor DAHDI channels in a
364  convenient way. Use <literal>#</literal> to select the next channel and use <literal>*</literal> to exit.</para>
365  </description>
366  <see-also>
367  <ref type="managerEvent">ChanSpyStart</ref>
368  <ref type="managerEvent">ChanSpyStop</ref>
369  </see-also>
370  </application>
371  ***/
372 
373 static const char app_chan[] = "ChanSpy";
374 
375 static const char app_ext[] = "ExtenSpy";
376 
377 static const char app_dahdiscan[] = "DAHDIScan";
378 
379 enum {
380  OPTION_QUIET = (1 << 0), /* Quiet, no announcement */
381  OPTION_BRIDGED = (1 << 1), /* Only look at bridged calls */
382  OPTION_VOLUME = (1 << 2), /* Specify initial volume */
383  OPTION_GROUP = (1 << 3), /* Only look at channels in group */
384  OPTION_RECORD = (1 << 4),
385  OPTION_WHISPER = (1 << 5),
386  OPTION_PRIVATE = (1 << 6), /* Private Whisper mode */
387  OPTION_READONLY = (1 << 7), /* Don't mix the two channels */
388  OPTION_EXIT = (1 << 8), /* Exit to a valid single digit extension */
389  OPTION_ENFORCED = (1 << 9), /* Enforced mode */
390  OPTION_NOTECH = (1 << 10), /* Skip technology name playback */
391  OPTION_BARGE = (1 << 11), /* Barge mode (whisper to both channels) */
392  OPTION_NAME = (1 << 12), /* Say the name of the person on whom we will spy */
393  OPTION_DTMF_SWITCH_MODES = (1 << 13), /* Allow numeric DTMF to switch between chanspy modes */
394  OPTION_DTMF_EXIT = (1 << 14), /* Set DTMF to exit, added for DAHDIScan integration */
395  OPTION_DTMF_CYCLE = (1 << 15), /* Custom DTMF for cycling next available channel, (default is '*') */
396  OPTION_DAHDI_SCAN = (1 << 16), /* Scan groups in DAHDIScan mode */
397  OPTION_STOP = (1 << 17),
398  OPTION_EXITONHANGUP = (1 << 18), /* Hang up when the spied-on channel hangs up. */
399  OPTION_UNIQUEID = (1 << 19), /* The chanprefix is a channel uniqueid or fully specified channel name. */
400  OPTION_LONG_QUEUE = (1 << 20), /* Allow usage of a long queue to store audio frames. */
401  OPTION_INTERLEAVED = (1 << 21), /* Interleave the Read and Write frames in the output frame. */
402 };
403 
404 enum {
405  OPT_ARG_VOLUME = 0,
406  OPT_ARG_GROUP,
407  OPT_ARG_RECORD,
408  OPT_ARG_ENFORCED,
409  OPT_ARG_NAME,
410  OPT_ARG_EXIT,
411  OPT_ARG_CYCLE,
412  OPT_ARG_ARRAY_SIZE,
413 };
414 
415 AST_APP_OPTIONS(spy_opts, {
416  AST_APP_OPTION('b', OPTION_BRIDGED),
417  AST_APP_OPTION('B', OPTION_BARGE),
418  AST_APP_OPTION_ARG('c', OPTION_DTMF_CYCLE, OPT_ARG_CYCLE),
419  AST_APP_OPTION('d', OPTION_DTMF_SWITCH_MODES),
420  AST_APP_OPTION('D', OPTION_INTERLEAVED),
421  AST_APP_OPTION_ARG('e', OPTION_ENFORCED, OPT_ARG_ENFORCED),
422  AST_APP_OPTION('E', OPTION_EXITONHANGUP),
423  AST_APP_OPTION_ARG('g', OPTION_GROUP, OPT_ARG_GROUP),
424  AST_APP_OPTION('l', OPTION_LONG_QUEUE),
425  AST_APP_OPTION_ARG('n', OPTION_NAME, OPT_ARG_NAME),
426  AST_APP_OPTION('o', OPTION_READONLY),
427  AST_APP_OPTION('q', OPTION_QUIET),
428  AST_APP_OPTION_ARG('r', OPTION_RECORD, OPT_ARG_RECORD),
429  AST_APP_OPTION('s', OPTION_NOTECH),
430  AST_APP_OPTION('S', OPTION_STOP),
431  AST_APP_OPTION('u', OPTION_UNIQUEID),
432  AST_APP_OPTION_ARG('v', OPTION_VOLUME, OPT_ARG_VOLUME),
433  AST_APP_OPTION('w', OPTION_WHISPER),
434  AST_APP_OPTION('W', OPTION_PRIVATE),
435  AST_APP_OPTION_ARG('x', OPTION_DTMF_EXIT, OPT_ARG_EXIT),
436  AST_APP_OPTION('X', OPTION_EXIT),
437 });
438 
440  /* spy data */
441  struct ast_audiohook spy_audiohook;
442  struct ast_audiohook whisper_audiohook;
443  struct ast_audiohook bridge_whisper_audiohook;
444  int fd;
445  int volfactor;
446  struct ast_flags flags;
447 };
448 
450  char exit;
451  char cycle;
452  char volume;
453 };
454 
455 static void *spy_alloc(struct ast_channel *chan, void *data)
456 {
457  /* just store the data pointer in the channel structure */
458  return data;
459 }
460 
461 static void spy_release(struct ast_channel *chan, void *data)
462 {
463  /* nothing to do */
464 }
465 
466 static int spy_generate(struct ast_channel *chan, void *data, int len, int samples)
467 {
468  struct chanspy_translation_helper *csth = data;
469  struct ast_frame *f, *cur;
470 
471  ast_audiohook_lock(&csth->spy_audiohook);
472  if (csth->spy_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
473  /* Channel is already gone more than likely */
474  ast_audiohook_unlock(&csth->spy_audiohook);
475  return -1;
476  }
477 
478  if (ast_test_flag(&csth->flags, OPTION_READONLY)) {
479  /* Option 'o' was set, so don't mix channel audio */
480  f = ast_audiohook_read_frame(&csth->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_READ, ast_format_slin);
481  } else if (ast_test_flag(&csth->flags, OPTION_INTERLEAVED)) {
482  /* Option 'D' was set, so mix the spy frame as an interleaved dual channel frame. */
483  int i;
484  struct ast_frame *fr_read = NULL;
485  struct ast_frame *fr_write = NULL;
486  short read_buf[samples];
487  short write_buf[samples];
488  short stereo_buf[samples * 2];
489  struct ast_frame stereo_frame = {
491  .datalen = sizeof(stereo_buf),
492  .samples = samples,
493  };
494 
495  f = ast_audiohook_read_frame_all(&csth->spy_audiohook, samples, ast_format_slin, &fr_read, &fr_write);
496  if (f) {
497  ast_frame_free(f, 0);
498  f = NULL;
499  }
500 
501  if (fr_read) {
502  memcpy(read_buf, fr_read->data.ptr, sizeof(read_buf));
503  } else {
504  /* silent out the output frame if we can't read the input */
505  memset(read_buf, 0, sizeof(read_buf));
506  }
507 
508  if (fr_write) {
509  memcpy(write_buf, fr_write->data.ptr, sizeof(write_buf));
510  } else {
511  memset(write_buf, 0, sizeof(write_buf));
512  }
513 
514  for (i = 0; i < samples; i++) {
515  stereo_buf[i*2] = read_buf[i];
516  stereo_buf[i*2+1] = write_buf[i];
517  }
518 
519  stereo_frame.data.ptr = stereo_buf;
520  stereo_frame.subclass.format = ast_format_cache_get_slin_by_rate(samples);
521 
522  f = ast_frdup(&stereo_frame);
523 
524  if (fr_read) {
525  ast_frame_free(fr_read, 0);
526  }
527  if (fr_write) {
528  ast_frame_free(fr_write, 0);
529  }
530 
531  } else {
532  f = ast_audiohook_read_frame(&csth->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_BOTH, ast_format_slin);
533  }
534 
535  ast_audiohook_unlock(&csth->spy_audiohook);
536 
537  if (!f)
538  return 0;
539 
540  for (cur = f; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
541  if (ast_write(chan, cur)) {
542  ast_frfree(f);
543  return -1;
544  }
545 
546  if (csth->fd) {
547  if (write(csth->fd, cur->data.ptr, cur->datalen) < 0) {
548  ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
549  }
550  }
551  }
552 
553  ast_frfree(f);
554 
555  return 0;
556 }
557 
558 static struct ast_generator spygen = {
559  .alloc = spy_alloc,
560  .release = spy_release,
561  .generate = spy_generate,
562 };
563 
564 static int start_spying(struct ast_autochan *autochan, const char *spychan_name, struct ast_audiohook *audiohook, struct ast_flags *flags)
565 {
566  int res;
567 
568  ast_autochan_channel_lock(autochan);
569  ast_verb(3, "Attaching spy channel %s to %s\n",
570  spychan_name, ast_channel_name(autochan->chan));
571 
572  if (ast_test_flag(flags, OPTION_READONLY)) {
573  ast_set_flag(audiohook, AST_AUDIOHOOK_MUTE_WRITE);
574  } else {
575  ast_set_flag(audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
576  }
577  if (ast_test_flag(flags, OPTION_LONG_QUEUE)) {
578  ast_debug(9, "Using a long queue to store audio frames in spy audiohook\n");
579  } else {
580  ast_set_flag(audiohook, AST_AUDIOHOOK_SMALL_QUEUE);
581  }
582  res = ast_audiohook_attach(autochan->chan, audiohook);
583  ast_autochan_channel_unlock(autochan);
584  return res;
585 }
586 
587 static void change_spy_mode(const char digit, struct ast_flags *flags)
588 {
589  if (digit == '4') {
590  ast_clear_flag(flags, OPTION_WHISPER);
591  ast_clear_flag(flags, OPTION_BARGE);
592  } else if (digit == '5') {
593  ast_clear_flag(flags, OPTION_BARGE);
594  ast_set_flag(flags, OPTION_WHISPER);
595  } else if (digit == '6') {
596  ast_clear_flag(flags, OPTION_WHISPER);
597  ast_set_flag(flags, OPTION_BARGE);
598  }
599 }
600 
601 static int pack_channel_into_message(struct ast_channel *chan, const char *role,
602  struct ast_multi_channel_blob *payload)
603 {
604  RAII_VAR(struct ast_channel_snapshot *, snapshot,
605  ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan)),
606  ao2_cleanup);
607 
608  if (!snapshot) {
609  return -1;
610  }
611  ast_multi_channel_blob_add_channel(payload, role, snapshot);
612  return 0;
613 }
614 
615 /*! \internal
616  * \brief Publish the chanspy message over Stasis-Core
617  * \param spyer The channel doing the spying
618  * \param spyee Who is being spied upon
619  * \param start If non-zero, the spying is starting. Otherwise, the spyer is
620  * finishing
621  */
622 static void publish_chanspy_message(struct ast_channel *spyer,
623  struct ast_channel *spyee,
624  int start)
625 {
626  RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
627  RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
628  RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
630 
631  if (!spyer) {
632  ast_log(AST_LOG_WARNING, "Attempt to publish ChanSpy message for NULL spyer channel\n");
633  return;
634  }
635  blob = ast_json_null();
636  if (!blob || !type) {
637  return;
638  }
639 
640  payload = ast_multi_channel_blob_create(blob);
641  if (!payload) {
642  return;
643  }
644 
645  if (pack_channel_into_message(spyer, "spyer_channel", payload)) {
646  return;
647  }
648 
649  if (spyee) {
650  if (pack_channel_into_message(spyee, "spyee_channel", payload)) {
651  return;
652  }
653  }
654 
655  message = stasis_message_create(type, payload);
656  if (!message) {
657  return;
658  }
660 }
661 
662 static int attach_barge(struct ast_autochan *spyee_autochan,
663  struct ast_autochan **spyee_bridge_autochan, struct ast_audiohook *bridge_whisper_audiohook,
664  const char *spyer_name, const char *name, struct ast_flags *flags)
665 {
666  int retval = 0;
667  struct ast_autochan *internal_bridge_autochan;
668  struct ast_channel *spyee_chan;
669  RAII_VAR(struct ast_channel *, bridged, NULL, ast_channel_cleanup);
670 
671  ast_autochan_channel_lock(spyee_autochan);
672  spyee_chan = ast_channel_ref(spyee_autochan->chan);
673  ast_autochan_channel_unlock(spyee_autochan);
674  bridged = ast_channel_bridge_peer(spyee_chan);
675  ast_channel_unref(spyee_chan);
676  if (!bridged) {
677  return -1;
678  }
679 
680  ast_audiohook_init(bridge_whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "Chanspy", 0);
681 
682  internal_bridge_autochan = ast_autochan_setup(bridged);
683  if (!internal_bridge_autochan) {
684  return -1;
685  }
686 
687  if (start_spying(internal_bridge_autochan, spyer_name, bridge_whisper_audiohook, flags)) {
688  ast_log(LOG_WARNING, "Unable to attach barge audiohook on spyee '%s'. Barge mode disabled.\n", name);
689  retval = -1;
690  }
691 
692  *spyee_bridge_autochan = internal_bridge_autochan;
693 
694  return retval;
695 }
696 
697 static int channel_spy(struct ast_channel *chan, struct ast_autochan *spyee_autochan,
698  int *volfactor, int fd, struct spy_dtmf_options *user_options, struct ast_flags *flags,
699  char *exitcontext)
700 {
701  struct chanspy_translation_helper csth;
702  int running = 0, bridge_connected = 0, res, x = 0;
703  char inp[24] = {0};
704  char *name;
705  struct ast_frame *f;
706  struct ast_silence_generator *silgen = NULL;
707  struct ast_autochan *spyee_bridge_autochan = NULL;
708  const char *spyer_name;
709 
710  ast_channel_lock(chan);
711  if (ast_check_hangup(chan)) {
712  ast_channel_unlock(chan);
713  return 0;
714  }
715  spyer_name = ast_strdupa(ast_channel_name(chan));
716  ast_channel_unlock(chan);
717 
718  ast_autochan_channel_lock(spyee_autochan);
719  if (ast_check_hangup(spyee_autochan->chan)
720  || ast_test_flag(ast_channel_flags(spyee_autochan->chan), AST_FLAG_ZOMBIE)) {
721  ast_autochan_channel_unlock(spyee_autochan);
722  return 0;
723  }
724  name = ast_strdupa(ast_channel_name(spyee_autochan->chan));
725 
726  ast_verb(2, "Spying on channel %s\n", name);
727  publish_chanspy_message(chan, spyee_autochan->chan, 1);
728  ast_autochan_channel_unlock(spyee_autochan);
729 
730  memset(&csth, 0, sizeof(csth));
731  ast_copy_flags(&csth.flags, flags, AST_FLAGS_ALL);
732 
733  /* This is the audiohook which gives us the audio off the channel we are
734  spying on.
735  */
736  ast_audiohook_init(&csth.spy_audiohook, AST_AUDIOHOOK_TYPE_SPY, "ChanSpy", 0);
737 
738  if (start_spying(spyee_autochan, spyer_name, &csth.spy_audiohook, flags)) {
739  ast_audiohook_destroy(&csth.spy_audiohook);
740  return 0;
741  }
742 
743  if (ast_test_flag(flags, OPTION_WHISPER | OPTION_BARGE | OPTION_DTMF_SWITCH_MODES)) {
744  /* This audiohook will let us inject audio from our channel into the
745  channel we are currently spying on.
746  */
747  ast_audiohook_init(&csth.whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "ChanSpy", 0);
748 
749  if (start_spying(spyee_autochan, spyer_name, &csth.whisper_audiohook, flags)) {
750  ast_log(LOG_WARNING, "Unable to attach whisper audiohook to spyee %s. Whisper mode disabled!\n", name);
751  }
752  }
753 
755 
756  csth.volfactor = *volfactor;
757 
758  if (csth.volfactor) {
759  csth.spy_audiohook.options.read_volume = csth.volfactor;
760  csth.spy_audiohook.options.write_volume = csth.volfactor;
761  }
762 
763  csth.fd = fd;
764 
765  if (ast_test_flag(flags, OPTION_PRIVATE))
767  else
768  ast_activate_generator(chan, &spygen, &csth);
769 
770  /* We can no longer rely on 'spyee' being an actual channel;
771  it can be hung up and freed out from under us. However, the
772  channel destructor will put NULL into our csth.spy.chan
773  field when that happens, so that is our signal that the spyee
774  channel has gone away.
775  */
776 
777  /* Note: it is very important that the ast_waitfor() be the first
778  condition in this expression, so that if we wait for some period
779  of time before receiving a frame from our spying channel, we check
780  for hangup on the spied-on channel _after_ knowing that a frame
781  has arrived, since the spied-on channel could have gone away while
782  we were waiting
783  */
784  while (ast_waitfor(chan, -1) > -1 && csth.spy_audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
785  if (!(f = ast_read(chan)) || ast_check_hangup(chan)) {
786  running = -1;
787  if (f) {
788  ast_frfree(f);
789  }
790  break;
791  }
792 
793  if (ast_test_flag(flags, OPTION_BARGE) && f->frametype == AST_FRAME_VOICE) {
794  /* This hook lets us inject audio into the channel that the spyee is currently
795  * bridged with. If the spyee isn't bridged with anything yet, nothing will
796  * be attached and we'll need to continue attempting to attach the barge
797  * audio hook. */
798  if (!bridge_connected && attach_barge(spyee_autochan, &spyee_bridge_autochan,
799  &csth.bridge_whisper_audiohook, spyer_name, name, flags) == 0) {
800  bridge_connected = 1;
801  }
802 
803  ast_audiohook_lock(&csth.whisper_audiohook);
804  ast_audiohook_write_frame(&csth.whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
805  ast_audiohook_unlock(&csth.whisper_audiohook);
806 
807  if (bridge_connected) {
808  ast_audiohook_lock(&csth.bridge_whisper_audiohook);
809  ast_audiohook_write_frame(&csth.bridge_whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
810  ast_audiohook_unlock(&csth.bridge_whisper_audiohook);
811  }
812 
813  ast_frfree(f);
814  continue;
815  } else if (ast_test_flag(flags, OPTION_WHISPER) && f->frametype == AST_FRAME_VOICE) {
816  ast_audiohook_lock(&csth.whisper_audiohook);
817  ast_audiohook_write_frame(&csth.whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
818  ast_audiohook_unlock(&csth.whisper_audiohook);
819  ast_frfree(f);
820  continue;
821  }
822 
823  res = (f->frametype == AST_FRAME_DTMF) ? f->subclass.integer : 0;
824  ast_frfree(f);
825  if (!res)
826  continue;
827 
828  if (x == sizeof(inp))
829  x = 0;
830 
831  if (res < 0) {
832  running = -1;
833  break;
834  }
835 
836  if (ast_test_flag(flags, OPTION_EXIT)) {
837  char tmp[2];
838  tmp[0] = res;
839  tmp[1] = '\0';
840  if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) {
841  ast_debug(1, "Got DTMF %c, goto context %s\n", tmp[0], exitcontext);
842  pbx_builtin_setvar_helper(chan, "SPY_CHANNEL", name);
843  running = -2;
844  break;
845  } else {
846  ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
847  }
848  } else if (res >= '0' && res <= '9') {
849  if (ast_test_flag(flags, OPTION_DTMF_SWITCH_MODES)) {
850  change_spy_mode(res, flags);
851  } else {
852  inp[x++] = res;
853  }
854  }
855 
856  if (res == user_options->cycle) {
857  running = 0;
858  break;
859  } else if (res == user_options->exit) {
860  running = -2;
861  break;
862  } else if (res == user_options->volume) {
863  if (!ast_strlen_zero(inp)) {
864  running = atoi(inp);
865  break;
866  }
867 
868  (*volfactor)++;
869  if (*volfactor > 4)
870  *volfactor = -4;
871  ast_verb(3, "Setting spy volume on %s to %d\n", ast_channel_name(chan), *volfactor);
872 
873  csth.volfactor = *volfactor;
874  csth.spy_audiohook.options.read_volume = csth.volfactor;
875  csth.spy_audiohook.options.write_volume = csth.volfactor;
876  }
877  }
878 
879  if (ast_test_flag(flags, OPTION_PRIVATE))
881  else
883 
885 
886  if (ast_test_flag(flags, OPTION_WHISPER | OPTION_BARGE | OPTION_DTMF_SWITCH_MODES)) {
887  ast_audiohook_lock(&csth.whisper_audiohook);
888  ast_audiohook_detach(&csth.whisper_audiohook);
889  ast_audiohook_unlock(&csth.whisper_audiohook);
890  ast_audiohook_destroy(&csth.whisper_audiohook);
891  }
892 
893  if (ast_test_flag(flags, OPTION_BARGE | OPTION_DTMF_SWITCH_MODES)) {
894  ast_audiohook_lock(&csth.bridge_whisper_audiohook);
895  ast_audiohook_detach(&csth.bridge_whisper_audiohook);
896  ast_audiohook_unlock(&csth.bridge_whisper_audiohook);
897  ast_audiohook_destroy(&csth.bridge_whisper_audiohook);
898  }
899 
900  ast_audiohook_lock(&csth.spy_audiohook);
901  ast_audiohook_detach(&csth.spy_audiohook);
902  ast_audiohook_unlock(&csth.spy_audiohook);
903  ast_audiohook_destroy(&csth.spy_audiohook);
904 
905  ast_verb(2, "Done Spying on channel %s\n", name);
906  publish_chanspy_message(chan, spyee_autochan->chan, 0);
907 
908  if (spyee_bridge_autochan) {
909  ast_autochan_destroy(spyee_bridge_autochan);
910  }
911 
912  return running;
913 }
914 
915 static struct ast_autochan *next_channel(struct ast_channel_iterator *iter,
916  struct ast_channel *chan)
917 {
918  struct ast_channel *next;
919  struct ast_autochan *autochan_store;
920  const size_t pseudo_len = strlen("DAHDI/pseudo");
921 
922  if (!iter) {
923  return NULL;
924  }
925 
926  for (; (next = ast_channel_iterator_next(iter)); ast_channel_unref(next)) {
927  if (!strncmp(ast_channel_name(next), "DAHDI/pseudo", pseudo_len)
928  || next == chan) {
929  continue;
930  }
931 
932  autochan_store = ast_autochan_setup(next);
933  ast_channel_unref(next);
934 
935  return autochan_store;
936  }
937  return NULL;
938 }
939 
940 static int spy_sayname(struct ast_channel *chan, const char *mailbox, const char *context)
941 {
942  char *mailbox_id;
943 
944  mailbox_id = ast_alloca(strlen(mailbox) + strlen(context) + 2);
945  sprintf(mailbox_id, "%s@%s", mailbox, context); /* Safe */
946  return ast_app_sayname(chan, mailbox_id);
947 }
948 
949 static int common_exec(struct ast_channel *chan, struct ast_flags *flags,
950  int volfactor, const int fd, struct spy_dtmf_options *user_options,
951  const char *mygroup, const char *myenforced, const char *spec, const char *exten,
952  const char *context, const char *mailbox, const char *name_context)
953 {
954  char nameprefix[AST_NAME_STRLEN];
955  char exitcontext[AST_MAX_CONTEXT] = "";
956  signed char zero_volume = 0;
957  int waitms;
958  int res;
959  int num_spied_upon = 1;
960  struct ast_channel_iterator *iter = NULL;
961 
962  if (ast_test_flag(flags, OPTION_EXIT)) {
963  const char *c;
964  ast_channel_lock(chan);
965  if ((c = pbx_builtin_getvar_helper(chan, "SPY_EXIT_CONTEXT"))) {
966  ast_copy_string(exitcontext, c, sizeof(exitcontext));
967  } else {
968  ast_copy_string(exitcontext, ast_channel_context(chan), sizeof(exitcontext));
969  }
970  ast_channel_unlock(chan);
971  }
972 
973  if (ast_channel_state(chan) != AST_STATE_UP)
974  ast_answer(chan);
975 
977 
978  waitms = 100;
979 
980  for (;;) {
981  struct ast_autochan *autochan = NULL, *next_autochan = NULL;
982  struct ast_channel *prev = NULL;
983 
984  if (!ast_test_flag(flags, OPTION_QUIET) && num_spied_upon) {
985  res = ast_streamfile(chan, "beep", ast_channel_language(chan));
986  if (!res)
987  res = ast_waitstream(chan, "");
988  else if (res < 0) {
990  break;
991  }
992  if (!ast_strlen_zero(exitcontext)) {
993  char tmp[2];
994  tmp[0] = res;
995  tmp[1] = '\0';
996  if (!ast_goto_if_exists(chan, exitcontext, tmp, 1))
997  goto exit;
998  else
999  ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
1000  }
1001  }
1002 
1003  /* Set up the iterator we'll be using during this call */
1004  if (!ast_strlen_zero(spec)) {
1005  if (ast_test_flag(flags, OPTION_UNIQUEID)) {
1006  struct ast_channel *unique_chan;
1007 
1008  unique_chan = ast_channel_get_by_name(spec);
1009  if (!unique_chan) {
1010  res = -1;
1011  goto exit;
1012  }
1013  iter = ast_channel_iterator_by_name_new(ast_channel_name(unique_chan), 0);
1014  ast_channel_unref(unique_chan);
1015  } else {
1016  iter = ast_channel_iterator_by_name_new(spec, strlen(spec));
1017  }
1018  } else if (!ast_strlen_zero(exten)) {
1019  iter = ast_channel_iterator_by_exten_new(exten, context);
1020  } else {
1022  }
1023 
1024  if (!iter) {
1025  res = -1;
1026  goto exit;
1027  }
1028 
1029  res = ast_waitfordigit(chan, waitms);
1030  if (res < 0) {
1031  iter = ast_channel_iterator_destroy(iter);
1033  break;
1034  }
1035  if (!ast_strlen_zero(exitcontext)) {
1036  char tmp[2];
1037  tmp[0] = res;
1038  tmp[1] = '\0';
1039  if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) {
1040  iter = ast_channel_iterator_destroy(iter);
1041  goto exit;
1042  } else {
1043  ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
1044  }
1045  }
1046 
1047  /* reset for the next loop around, unless overridden later */
1048  waitms = 100;
1049  num_spied_upon = 0;
1050 
1051  for (autochan = next_channel(iter, chan);
1052  autochan;
1053  prev = autochan->chan,
1054  ast_autochan_destroy(autochan),
1055  autochan = next_autochan ?: next_channel(iter, chan),
1056  next_autochan = NULL) {
1057  int igrp = !mygroup;
1058  int ienf = !myenforced;
1059 
1060  if (autochan->chan == prev) {
1061  ast_autochan_destroy(autochan);
1062  break;
1063  }
1064 
1065  if (ast_check_hangup(chan)) {
1066  ast_autochan_destroy(autochan);
1067  break;
1068  }
1069 
1070  ast_autochan_channel_lock(autochan);
1071  if (ast_test_flag(flags, OPTION_BRIDGED)
1072  && !ast_channel_is_bridged(autochan->chan)) {
1073  ast_autochan_channel_unlock(autochan);
1074  continue;
1075  }
1076 
1077  if (ast_check_hangup(autochan->chan)
1078  || ast_test_flag(ast_channel_flags(autochan->chan), AST_FLAG_SPYING)) {
1079  ast_autochan_channel_unlock(autochan);
1080  continue;
1081  }
1082  ast_autochan_channel_unlock(autochan);
1083 
1084  if (mygroup) {
1085  int num_groups = 0;
1086  int num_mygroups = 0;
1087  char dup_group[512];
1088  char dup_mygroup[512];
1089  char *groups[NUM_SPYGROUPS];
1090  char *mygroups[NUM_SPYGROUPS];
1091  const char *group = NULL;
1092  int x;
1093  int y;
1094  ast_copy_string(dup_mygroup, mygroup, sizeof(dup_mygroup));
1095  num_mygroups = ast_app_separate_args(dup_mygroup, ':', mygroups,
1096  ARRAY_LEN(mygroups));
1097 
1098  /* Before dahdi scan was part of chanspy, it would use the "GROUP" variable
1099  * rather than "SPYGROUP", this check is done to preserve expected behavior */
1100  ast_autochan_channel_lock(autochan);
1101  if (ast_test_flag(flags, OPTION_DAHDI_SCAN)) {
1102  group = pbx_builtin_getvar_helper(autochan->chan, "GROUP");
1103  } else {
1104  group = pbx_builtin_getvar_helper(autochan->chan, "SPYGROUP");
1105  }
1106  ast_autochan_channel_unlock(autochan);
1107 
1108  if (!ast_strlen_zero(group)) {
1109  ast_copy_string(dup_group, group, sizeof(dup_group));
1110  num_groups = ast_app_separate_args(dup_group, ':', groups,
1111  ARRAY_LEN(groups));
1112  }
1113 
1114  for (y = 0; y < num_mygroups; y++) {
1115  for (x = 0; x < num_groups; x++) {
1116  if (!strcmp(mygroups[y], groups[x])) {
1117  igrp = 1;
1118  break;
1119  }
1120  }
1121  }
1122  }
1123 
1124  if (!igrp) {
1125  continue;
1126  }
1127  if (myenforced) {
1128  char ext[AST_CHANNEL_NAME + 3];
1129  char buffer[512];
1130  char *end;
1131 
1132  snprintf(buffer, sizeof(buffer) - 1, ":%s:", myenforced);
1133 
1134  ast_autochan_channel_lock(autochan);
1135  ast_copy_string(ext + 1, ast_channel_name(autochan->chan), sizeof(ext) - 1);
1136  ast_autochan_channel_unlock(autochan);
1137  if ((end = strchr(ext, '-'))) {
1138  *end++ = ':';
1139  *end = '\0';
1140  }
1141 
1142  ext[0] = ':';
1143 
1144  if (strcasestr(buffer, ext)) {
1145  ienf = 1;
1146  }
1147  }
1148 
1149  if (!ienf) {
1150  continue;
1151  }
1152 
1153  if (!ast_test_flag(flags, OPTION_QUIET)) {
1154  char peer_name[AST_NAME_STRLEN + 5];
1155  char *ptr, *s;
1156 
1157  strcpy(peer_name, "spy-");
1158  ast_autochan_channel_lock(autochan);
1159  strncat(peer_name, ast_channel_name(autochan->chan), AST_NAME_STRLEN - 4 - 1);
1160  ast_autochan_channel_unlock(autochan);
1161  if ((ptr = strchr(peer_name, '/'))) {
1162  *ptr++ = '\0';
1163  for (s = peer_name; s < ptr; s++) {
1164  *s = tolower(*s);
1165  }
1166  if ((s = strchr(ptr, '-'))) {
1167  *s = '\0';
1168  }
1169  }
1170 
1171  if (ast_test_flag(flags, OPTION_NAME)) {
1172  const char *local_context = S_OR(name_context, "default");
1173  const char *local_mailbox = S_OR(mailbox, ptr);
1174 
1175  if (local_mailbox) {
1176  res = spy_sayname(chan, local_mailbox, local_context);
1177  } else {
1178  res = -1;
1179  }
1180  }
1181  if (!ast_test_flag(flags, OPTION_NAME) || res < 0) {
1182  int num;
1183  if (!ast_test_flag(flags, OPTION_NOTECH)) {
1184  if (ast_fileexists(peer_name, NULL, NULL) > 0) {
1185  res = ast_streamfile(chan, peer_name, ast_channel_language(chan));
1186  if (!res) {
1187  res = ast_waitstream(chan, "");
1188  }
1189  if (res) {
1190  ast_autochan_destroy(autochan);
1191  break;
1192  }
1193  } else {
1194  res = ast_say_character_str(chan, peer_name, "", ast_channel_language(chan), AST_SAY_CASE_NONE);
1195  }
1196  }
1197  if (ptr && (num = atoi(ptr))) {
1198  ast_say_digits(chan, num, "", ast_channel_language(chan));
1199  }
1200  }
1201  }
1202 
1203  res = channel_spy(chan, autochan, &volfactor, fd, user_options, flags, exitcontext);
1204  num_spied_upon++;
1205 
1206  if (res == -1) {
1207  ast_autochan_destroy(autochan);
1208  iter = ast_channel_iterator_destroy(iter);
1209  goto exit;
1210  } else if (res == -2) {
1211  res = 0;
1212  ast_autochan_destroy(autochan);
1213  iter = ast_channel_iterator_destroy(iter);
1214  goto exit;
1215  } else if (res > 1 && spec && !ast_test_flag(flags, OPTION_UNIQUEID)) {
1216  struct ast_channel *next;
1217 
1218  snprintf(nameprefix, AST_NAME_STRLEN, "%s/%d", spec, res);
1219 
1220  if ((next = ast_channel_get_by_name_prefix(nameprefix, strlen(nameprefix)))) {
1221  next_autochan = ast_autochan_setup(next);
1222  next = ast_channel_unref(next);
1223  } else {
1224  /* stay on this channel, if it is still valid */
1225  ast_autochan_channel_lock(autochan);
1226  if (!ast_check_hangup(autochan->chan)) {
1227  next_autochan = ast_autochan_setup(autochan->chan);
1228  } else {
1229  /* the channel is gone */
1230  next_autochan = NULL;
1231  }
1232  ast_autochan_channel_unlock(autochan);
1233  }
1234  } else if (res == 0 && ast_test_flag(flags, OPTION_EXITONHANGUP)) {
1235  ast_autochan_destroy(autochan);
1236  iter = ast_channel_iterator_destroy(iter);
1237  goto exit;
1238  }
1239  }
1240 
1241  iter = ast_channel_iterator_destroy(iter);
1242 
1243  if (res == -1 || ast_check_hangup(chan))
1244  break;
1245  if (ast_test_flag(flags, OPTION_STOP) && !next_autochan) {
1246  break;
1247  }
1248  }
1249 exit:
1250 
1252 
1253  ast_channel_setoption(chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0);
1254 
1255  return res;
1256 }
1257 
1258 static int chanspy_exec(struct ast_channel *chan, const char *data)
1259 {
1260  char *myenforced = NULL;
1261  char *mygroup = NULL;
1262  char *recbase = NULL;
1263  int fd = 0;
1264  struct ast_flags flags;
1265  struct spy_dtmf_options user_options = {
1266  .cycle = '*',
1267  .volume = '#',
1268  .exit = '\0',
1269  };
1270  RAII_VAR(struct ast_format *, oldwf, NULL, ao2_cleanup);
1271  int volfactor = 0;
1272  int res;
1273  char *mailbox = NULL;
1274  char *name_context = NULL;
1275  AST_DECLARE_APP_ARGS(args,
1276  AST_APP_ARG(spec);
1277  AST_APP_ARG(options);
1278  );
1279  char *opts[OPT_ARG_ARRAY_SIZE];
1280  char *parse = ast_strdupa(data);
1281 
1282  AST_STANDARD_APP_ARGS(args, parse);
1283 
1284  if (args.spec && !strcmp(args.spec, "all"))
1285  args.spec = NULL;
1286 
1287  if (args.options) {
1288  char tmp;
1289  ast_app_parse_options(spy_opts, &flags, opts, args.options);
1290  if (ast_test_flag(&flags, OPTION_GROUP))
1291  mygroup = opts[OPT_ARG_GROUP];
1292 
1293  if (ast_test_flag(&flags, OPTION_RECORD) &&
1294  !(recbase = opts[OPT_ARG_RECORD]))
1295  recbase = "chanspy";
1296 
1297  if (ast_test_flag(&flags, OPTION_DTMF_EXIT) && opts[OPT_ARG_EXIT]) {
1298  tmp = opts[OPT_ARG_EXIT][0];
1299  if (strchr("0123456789*#", tmp) && tmp != '\0') {
1300  user_options.exit = tmp;
1301  } else {
1302  ast_log(LOG_NOTICE, "Argument for option 'x' must be a valid DTMF digit.\n");
1303  }
1304  }
1305 
1306  if (ast_test_flag(&flags, OPTION_DTMF_CYCLE) && opts[OPT_ARG_CYCLE]) {
1307  tmp = opts[OPT_ARG_CYCLE][0];
1308  if (strchr("0123456789*#", tmp) && tmp != '\0') {
1309  user_options.cycle = tmp;
1310  } else {
1311  ast_log(LOG_NOTICE, "Argument for option 'c' must be a valid DTMF digit.\n");
1312  }
1313  }
1314 
1315  if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) {
1316  int vol;
1317 
1318  if ((sscanf(opts[OPT_ARG_VOLUME], "%30d", &vol) != 1) || (vol > 4) || (vol < -4))
1319  ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
1320  else
1321  volfactor = vol;
1322  }
1323 
1324  if (ast_test_flag(&flags, OPTION_PRIVATE))
1325  ast_set_flag(&flags, OPTION_WHISPER);
1326 
1327  if (ast_test_flag(&flags, OPTION_ENFORCED))
1328  myenforced = opts[OPT_ARG_ENFORCED];
1329 
1330  if (ast_test_flag(&flags, OPTION_NAME)) {
1331  if (!ast_strlen_zero(opts[OPT_ARG_NAME])) {
1332  char *delimiter;
1333  if ((delimiter = strchr(opts[OPT_ARG_NAME], '@'))) {
1334  mailbox = opts[OPT_ARG_NAME];
1335  *delimiter++ = '\0';
1336  name_context = delimiter;
1337  } else {
1338  mailbox = opts[OPT_ARG_NAME];
1339  }
1340  }
1341  }
1342  } else {
1343  ast_clear_flag(&flags, AST_FLAGS_ALL);
1344  }
1345 
1346  oldwf = ao2_bump(ast_channel_writeformat(chan));
1347  if (ast_set_write_format(chan, ast_format_slin) < 0) {
1348  ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
1349  return -1;
1350  }
1351 
1352  if (recbase) {
1353  char filename[PATH_MAX];
1354 
1355  snprintf(filename, sizeof(filename), "%s/%s.%d.raw", ast_config_AST_MONITOR_DIR, recbase, (int) time(NULL));
1356  if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, AST_FILE_MODE)) <= 0) {
1357  ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename);
1358  fd = 0;
1359  }
1360  }
1361 
1362  res = common_exec(chan, &flags, volfactor, fd, &user_options, mygroup, myenforced, args.spec, NULL, NULL, mailbox, name_context);
1363 
1364  if (fd)
1365  close(fd);
1366 
1367  if (oldwf && ast_set_write_format(chan, oldwf) < 0)
1368  ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
1369 
1370  if (ast_test_flag(&flags, OPTION_EXITONHANGUP)) {
1371  ast_verb(3, "Stopped spying due to the spied-on channel hanging up.\n");
1372  }
1373 
1374  return res;
1375 }
1376 
1377 static int extenspy_exec(struct ast_channel *chan, const char *data)
1378 {
1379  char *ptr, *exten = NULL;
1380  char *mygroup = NULL;
1381  char *recbase = NULL;
1382  int fd = 0;
1383  struct ast_flags flags;
1384  struct spy_dtmf_options user_options = {
1385  .cycle = '*',
1386  .volume = '#',
1387  .exit = '\0',
1388  };
1389  RAII_VAR(struct ast_format *, oldwf, NULL, ao2_cleanup);
1390  int volfactor = 0;
1391  int res;
1392  char *mailbox = NULL;
1393  char *name_context = NULL;
1394  AST_DECLARE_APP_ARGS(args,
1395  AST_APP_ARG(context);
1396  AST_APP_ARG(options);
1397  );
1398  char *parse = ast_strdupa(data);
1399 
1400  AST_STANDARD_APP_ARGS(args, parse);
1401 
1402  if (!ast_strlen_zero(args.context) && (ptr = strchr(args.context, '@'))) {
1403  exten = args.context;
1404  *ptr++ = '\0';
1405  args.context = ptr;
1406  }
1407  if (ast_strlen_zero(args.context))
1408  args.context = ast_strdupa(ast_channel_context(chan));
1409 
1410  if (args.options) {
1411  char *opts[OPT_ARG_ARRAY_SIZE];
1412  char tmp;
1413 
1414  ast_app_parse_options(spy_opts, &flags, opts, args.options);
1415  if (ast_test_flag(&flags, OPTION_GROUP))
1416  mygroup = opts[OPT_ARG_GROUP];
1417 
1418  if (ast_test_flag(&flags, OPTION_RECORD) &&
1419  !(recbase = opts[OPT_ARG_RECORD]))
1420  recbase = "chanspy";
1421 
1422  if (ast_test_flag(&flags, OPTION_DTMF_EXIT) && opts[OPT_ARG_EXIT]) {
1423  tmp = opts[OPT_ARG_EXIT][0];
1424  if (strchr("0123456789*#", tmp) && tmp != '\0') {
1425  user_options.exit = tmp;
1426  } else {
1427  ast_log(LOG_NOTICE, "Argument for option 'x' must be a valid DTMF digit.\n");
1428  }
1429  }
1430 
1431  if (ast_test_flag(&flags, OPTION_DTMF_CYCLE) && opts[OPT_ARG_CYCLE]) {
1432  tmp = opts[OPT_ARG_CYCLE][0];
1433  if (strchr("0123456789*#", tmp) && tmp != '\0') {
1434  user_options.cycle = tmp;
1435  } else {
1436  ast_log(LOG_NOTICE, "Argument for option 'c' must be a valid DTMF digit.\n");
1437  }
1438  }
1439 
1440  if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) {
1441  int vol;
1442 
1443  if ((sscanf(opts[OPT_ARG_VOLUME], "%30d", &vol) != 1) || (vol > 4) || (vol < -4))
1444  ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
1445  else
1446  volfactor = vol;
1447  }
1448 
1449  if (ast_test_flag(&flags, OPTION_PRIVATE))
1450  ast_set_flag(&flags, OPTION_WHISPER);
1451 
1452  if (ast_test_flag(&flags, OPTION_NAME)) {
1453  if (!ast_strlen_zero(opts[OPT_ARG_NAME])) {
1454  char *delimiter;
1455  if ((delimiter = strchr(opts[OPT_ARG_NAME], '@'))) {
1456  mailbox = opts[OPT_ARG_NAME];
1457  *delimiter++ = '\0';
1458  name_context = delimiter;
1459  } else {
1460  mailbox = opts[OPT_ARG_NAME];
1461  }
1462  }
1463  }
1464 
1465  } else {
1466  /* Coverity - This uninit_use should be ignored since this macro initializes the flags */
1467  ast_clear_flag(&flags, AST_FLAGS_ALL);
1468  }
1469 
1470  oldwf = ao2_bump(ast_channel_writeformat(chan));
1471  if (ast_set_write_format(chan, ast_format_slin) < 0) {
1472  ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
1473  return -1;
1474  }
1475 
1476  if (recbase) {
1477  char filename[PATH_MAX];
1478 
1479  snprintf(filename, sizeof(filename), "%s/%s.%d.raw", ast_config_AST_MONITOR_DIR, recbase, (int) time(NULL));
1480  if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, AST_FILE_MODE)) <= 0) {
1481  ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename);
1482  fd = 0;
1483  }
1484  }
1485 
1486 
1487  res = common_exec(chan, &flags, volfactor, fd, &user_options, mygroup, NULL, NULL, exten, args.context, mailbox, name_context);
1488 
1489  if (fd)
1490  close(fd);
1491 
1492  if (oldwf && ast_set_write_format(chan, oldwf) < 0)
1493  ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
1494 
1495  return res;
1496 }
1497 
1498 static int dahdiscan_exec(struct ast_channel *chan, const char *data)
1499 {
1500  const char *spec = "DAHDI";
1501  struct ast_flags flags = {0};
1502  struct spy_dtmf_options user_options = {
1503  .cycle = '#',
1504  .volume = '\0',
1505  .exit = '*',
1506  };
1507  struct ast_format *oldwf;
1508  int res;
1509  char *mygroup = NULL;
1510 
1511  /* Coverity - This uninit_use should be ignored since this macro initializes the flags */
1512  ast_clear_flag(&flags, AST_FLAGS_ALL);
1513 
1514  if (!ast_strlen_zero(data)) {
1515  mygroup = ast_strdupa(data);
1516  }
1517  ast_set_flag(&flags, OPTION_DTMF_EXIT);
1518  ast_set_flag(&flags, OPTION_DTMF_CYCLE);
1519  ast_set_flag(&flags, OPTION_DAHDI_SCAN);
1520 
1521  oldwf = ao2_bump(ast_channel_writeformat(chan));
1522  if (ast_set_write_format(chan, ast_format_slin) < 0) {
1523  ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
1524  ao2_cleanup(oldwf);
1525  return -1;
1526  }
1527 
1528  res = common_exec(chan, &flags, 0, 0, &user_options, mygroup, NULL, spec, NULL, NULL, NULL, NULL);
1529 
1530  if (oldwf && ast_set_write_format(chan, oldwf) < 0)
1531  ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
1532  ao2_cleanup(oldwf);
1533 
1534  return res;
1535 }
1536 
1537 static int unload_module(void)
1538 {
1539  int res = 0;
1540 
1541  res |= ast_unregister_application(app_chan);
1542  res |= ast_unregister_application(app_ext);
1543  res |= ast_unregister_application(app_dahdiscan);
1544 
1545  return res;
1546 }
1547 
1548 static int load_module(void)
1549 {
1550  int res = 0;
1551 
1552  res |= ast_register_application_xml(app_chan, chanspy_exec);
1553  res |= ast_register_application_xml(app_ext, extenspy_exec);
1554  res |= ast_register_application_xml(app_dahdiscan, dahdiscan_exec);
1555 
1556  return res;
1557 }
1558 
1559 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Listen to the audio of an active channel");
Main Channel structure associated with a channel.
#define ast_frdup(fr)
Copies a frame.
int ast_streamfile(struct ast_channel *c, const char *filename, const char *preflang)
Streams a file.
Definition: file.c:1293
Asterisk locking-related definitions:
struct ast_channel * ast_channel_iterator_next(struct ast_channel_iterator *i)
Get the next channel for a channel iterator.
Definition: channel.c:1422
Asterisk main include file. File version handling, generic pbx functions.
int ast_audiohook_write_frame(struct ast_audiohook *audiohook, enum ast_audiohook_direction direction, struct ast_frame *frame)
Writes a frame into the audiohook structure.
Definition: audiohook.c:167
#define AST_OPTION_TXGAIN
#define ast_autochan_channel_lock(autochan)
Lock the autochan's channel lock.
Definition: autochan.h:75
#define ast_channel_unref(c)
Decrease channel reference count.
Definition: channel.h:2958
int ast_app_sayname(struct ast_channel *chan, const char *mailbox_id)
Play a recorded user name for the mailbox to the specified channel.
Definition: main/app.c:637
int ast_activate_generator(struct ast_channel *chan, struct ast_generator *gen, void *params)
Definition: channel.c:2951
Support for translation of data formats. translate.c.
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Definition: json.c:73
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
Audiohooks Architecture.
Structure representing a snapshot of channel state.
void ast_channel_clear_flag(struct ast_channel *chan, unsigned int flag)
Clear a flag on a channel.
Definition: channel.c:11034
#define AST_LIST_NEXT(elm, field)
Returns the next entry in the list after the given entry.
Definition: linkedlists.h:439
struct ast_frame * ast_read(struct ast_channel *chan)
Reads a frame.
Definition: channel.c:4257
ast_channel_state
ast_channel states
Definition: channelstate.h:35
int ast_say_digits(struct ast_channel *chan, int num, const char *ints, const char *lang)
says digits
Definition: channel.c:8253
Definition of a media format.
Definition: format.c:43
int ast_audiohook_attach(struct ast_channel *chan, struct ast_audiohook *audiohook)
Attach audiohook to channel.
Definition: audiohook.c:484
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
struct stasis_message_type * ast_channel_chanspy_start_type(void)
Message type for when a channel starts spying on another channel.
int ast_channel_setoption(struct ast_channel *channel, int option, void *data, int datalen, int block)
Sets an option on a channel.
Definition: channel.c:7422
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx_app.c:392
int ast_audiohook_destroy(struct ast_audiohook *audiohook)
Destroys an audiohook structure.
Definition: audiohook.c:124
const char * pbx_builtin_getvar_helper(struct ast_channel *chan, const char *name)
Return a pointer to the value of the corresponding channel variable.
struct ast_frame_subclass subclass
Utility functions.
struct ast_channel * ast_channel_get_by_name_prefix(const char *name, size_t name_len)
Find a channel by a name prefix.
Definition: channel.c:1434
#define AST_APP_OPTIONS(holder, options...)
Declares an array of options for an application.
struct ast_json * ast_json_null(void)
Get the JSON null value.
Definition: json.c:248
#define ao2_bump(obj)
Bump refcount on an AO2 object by one, returning the object.
Definition: astobj2.h:480
int ast_audiohook_init(struct ast_audiohook *audiohook, enum ast_audiohook_type type, const char *source, enum ast_audiohook_init_flags flags)
Initialize an audiohook structure.
Definition: audiohook.c:100
#define ast_audiohook_unlock(ah)
Unlock an audiohook.
Definition: audiohook.h:318
General Asterisk PBX channel definitions.
Asterisk JSON abstraction layer.
Asterisk file paths, configured in asterisk.conf.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
void ast_multi_channel_blob_add_channel(struct ast_multi_channel_blob *obj, const char *role, struct ast_channel_snapshot *snapshot)
Add a ast_channel_snapshot to a ast_multi_channel_blob object.
#define ast_channel_cleanup(c)
Cleanup a channel reference.
Definition: channel.h:2969
#define ast_debug(level,...)
Log a DEBUG message.
int ast_set_write_format(struct ast_channel *chan, struct ast_format *format)
Sets write format on channel chan.
Definition: channel.c:5803
int ast_say_character_str(struct ast_channel *chan, const char *num, const char *ints, const char *lang, enum ast_say_case_sensitivity sensitivity)
function to pronounce character and phonetic strings
Definition: channel.c:8271
int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr)
Parses a string containing application options and sets flags/arguments.
Definition: main/app.c:3066
Core PBX routines and definitions.
int ast_check_hangup(struct ast_channel *chan)
Check to see if a channel is needing hang up.
Definition: channel.c:445
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
struct ast_silence_generator * ast_channel_start_silence_generator(struct ast_channel *chan)
Starts a silence generator on the given channel.
Definition: channel.c:8164
struct stasis_topic * ast_channel_topic(struct ast_channel *chan)
A topic which publishes the events for a particular channel.
The AMI - Asterisk Manager Interface - is a TCP protocol created to manage Asterisk with third-party ...
"smart" channels that update automatically if a channel is masqueraded
int ast_audiohook_detach(struct ast_audiohook *audiohook)
Detach audiohook from channel.
Definition: audiohook.c:550
#define AST_APP_OPTION_ARG(option, flagno, argno)
Declares an application option that accepts an argument.
void ast_channel_set_flag(struct ast_channel *chan, unsigned int flag)
Set a flag on a channel.
Definition: channel.c:11027
struct stasis_message * stasis_message_create(struct stasis_message_type *type, void *data)
Create a new message.
struct ast_channel_iterator * ast_channel_iterator_by_exten_new(const char *exten, const char *context)
Create a new channel iterator based on extension.
Definition: channel.c:1368
int ast_goto_if_exists(struct ast_channel *chan, const char *context, const char *exten, int priority)
Definition: pbx.c:8781
int ast_channel_is_bridged(const struct ast_channel *chan)
Determine if a channel is in a bridge.
Definition: channel.c:10545
void(* digit)(struct ast_channel *chan, char digit)
Definition: channel.h:235
void ast_channel_stop_silence_generator(struct ast_channel *chan, struct ast_silence_generator *state)
Stops a previously-started silence generator on the given channel.
Definition: channel.c:8210
void stasis_publish(struct stasis_topic *topic, struct stasis_message *message)
Publish a message to a topic's subscribers.
Definition: stasis.c:1511
#define AST_MAX_CONTEXT
Definition: channel.h:135
union ast_frame::@224 data
#define AST_CHANNEL_NAME
Definition: channel.h:171
int ast_write(struct ast_channel *chan, struct ast_frame *frame)
Write a frame to a channel This function writes the given frame to the indicated channel.
Definition: channel.c:5144
Structure used to handle boolean flags.
Definition: utils.h:199
struct ast_channel_iterator * ast_channel_iterator_by_name_new(const char *name, size_t name_len)
Create a new channel iterator based on name.
Definition: channel.c:1388
void ast_autochan_destroy(struct ast_autochan *autochan)
destroy an ast_autochan structure
Definition: autochan.c:64
int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
Add a variable to the channel variable stack, removing the most recently set value for the same name...
int ast_waitfordigit(struct ast_channel *c, int ms)
Waits for a digit.
Definition: channel.c:3175
struct ast_autochan * ast_autochan_setup(struct ast_channel *chan)
set up a new ast_autochan structure
Definition: autochan.c:38
void ast_deactivate_generator(struct ast_channel *chan)
Definition: channel.c:2893
struct ast_audiohook_options options
Definition: audiohook.h:119
struct ast_frame * ast_audiohook_read_frame_all(struct ast_audiohook *audiohook, size_t samples, struct ast_format *format, struct ast_frame **read_frame, struct ast_frame **write_frame)
Reads a frame in from the audiohook structure in mixed audio mode and copies read and write frame dat...
Definition: audiohook.c:451
#define ast_channel_ref(c)
Increase channel reference count.
Definition: channel.h:2947
int ast_waitfor(struct ast_channel *chan, int ms)
Wait for input on a channel.
Definition: channel.c:3162
struct ast_channel_snapshot * ast_channel_snapshot_get_latest(const char *uniqueid)
Obtain the latest ast_channel_snapshot from the Stasis Message Bus API cache. This is an ao2 object...
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:80
struct ast_channel * ast_channel_bridge_peer(struct ast_channel *chan)
Get the channel's bridge peer only if the bridge is two-party.
Definition: channel.c:10564
int ast_waitstream(struct ast_channel *c, const char *breakon)
Waits for a stream to stop or digit to be pressed.
Definition: file.c:1840
int ast_fileexists(const char *filename, const char *fmt, const char *preflang)
Checks for the existence of a given file.
Definition: file.c:1129
A multi channel blob data structure for multi_channel_blob stasis messages.
struct ast_channel_iterator * ast_channel_iterator_destroy(struct ast_channel_iterator *i)
Destroy a channel iterator.
Definition: channel.c:1360
int ast_answer(struct ast_channel *chan)
Answer a channel.
Definition: channel.c:2805
Data structure associated with a single frame of data.
Abstract JSON element (object, array, string, int, ...).
Options provided by main asterisk program.
enum ast_audiohook_status status
Definition: audiohook.h:108
enum ast_frame_type frametype
Call Parking and Pickup API Includes code and algorithms from the Zapata library. ...
#define AST_APP_OPTION(option, flagno)
Declares an application option that does not accept an argument.
struct ast_format * format
struct ast_channel_iterator * ast_channel_iterator_all_new(void)
Create a new channel iterator.
Definition: channel.c:1408
Say numbers and dates (maybe words one day too)
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
struct ast_channel * ast_channel_get_by_name(const char *name)
Find a channel by name.
Definition: channel.c:1454
#define ast_audiohook_lock(ah)
Lock an audiohook.
Definition: audiohook.h:313
struct ast_format * ast_format_slin
Built-in cached signed linear 8kHz format.
Definition: format_cache.c:41
Asterisk module definitions.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
struct ast_frame * ast_audiohook_read_frame(struct ast_audiohook *audiohook, size_t samples, enum ast_audiohook_direction direction, struct ast_format *format)
Reads a frame in from the audiohook structure.
Definition: audiohook.c:446
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition: module.h:640
struct ast_multi_channel_blob * ast_multi_channel_blob_create(struct ast_json *blob)
Create a ast_multi_channel_blob suitable for a stasis_message.
struct stasis_message_type * ast_channel_chanspy_stop_type(void)
Message type for when a channel stops spying on another channel.
struct ast_format * ast_format_cache_get_slin_by_rate(unsigned int rate)
Retrieve the best signed linear format given a sample rate.
Definition: format_cache.c:512
void ast_frame_free(struct ast_frame *frame, int cache)
Frees a frame or list of frames.
Definition: main/frame.c:176
Media Format Cache API.
#define AST_APP_ARG(name)
Define an application argument.