Wed Oct 28 11:50:52 2009

Asterisk developer's documentation


app_chanspy.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2005 Anthony Minessale II (anthmct@yahoo.com)
00005  * Copyright (C) 2005 - 2008, Digium, Inc.
00006  *
00007  * A license has been granted to Digium (via disclaimer) for the use of
00008  * this code.
00009  *
00010  * See http://www.asterisk.org for more information about
00011  * the Asterisk project. Please do not directly contact
00012  * any of the maintainers of this project for assistance;
00013  * the project provides a web site, mailing lists and IRC
00014  * channels for your use.
00015  *
00016  * This program is free software, distributed under the terms of
00017  * the GNU General Public License Version 2. See the LICENSE file
00018  * at the top of the source tree.
00019  */
00020 
00021 /*! \file
00022  *
00023  * \brief ChanSpy: Listen in on any channel.
00024  *
00025  * \author Anthony Minessale II <anthmct@yahoo.com>
00026  * \author Joshua Colp <jcolp@digium.com>
00027  * \author Russell Bryant <russell@digium.com>
00028  *
00029  * \ingroup applications
00030  */
00031 
00032 #include "asterisk.h"
00033 
00034 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 224180 $")
00035 
00036 #include <ctype.h>
00037 #include <errno.h>
00038 
00039 #include "asterisk/paths.h" /* use ast_config_AST_MONITOR_DIR */
00040 #include "asterisk/file.h"
00041 #include "asterisk/channel.h"
00042 #include "asterisk/audiohook.h"
00043 #include "asterisk/features.h"
00044 #include "asterisk/app.h"
00045 #include "asterisk/utils.h"
00046 #include "asterisk/say.h"
00047 #include "asterisk/pbx.h"
00048 #include "asterisk/translate.h"
00049 #include "asterisk/module.h"
00050 #include "asterisk/lock.h"
00051 #include "asterisk/options.h"
00052 
00053 #define AST_NAME_STRLEN 256
00054 #define NUM_SPYGROUPS 128
00055 
00056 static const char *tdesc = "Listen to a channel, and optionally whisper into it";
00057 static const char *app_chan = "ChanSpy";
00058 static const char *desc_chan =
00059 "  ChanSpy([chanprefix][,options]): This application is used to listen to the\n"
00060 "audio from an Asterisk channel. This includes the audio coming in and\n"
00061 "out of the channel being spied on. If the 'chanprefix' parameter is specified,\n"
00062 "only channels beginning with this string will be spied upon.\n"
00063 "  While spying, the following actions may be performed:\n"
00064 "    - Dialing # cycles the volume level.\n"
00065 "    - Dialing * will stop spying and look for another channel to spy on.\n"
00066 "    - Dialing a series of digits followed by # builds a channel name to append\n"
00067 "      to 'chanprefix'. For example, executing ChanSpy(Agent) and then dialing\n"
00068 "      the digits '1234#' while spying will begin spying on the channel\n"
00069 "      'Agent/1234'. Note that this feature will be overriden if the 'd' option\n"
00070 "       is used\n"
00071 "  Note: The X option supersedes the three features above in that if a valid\n"
00072 "        single digit extension exists in the correct context ChanSpy will\n"
00073 "        exit to it. This also disables choosing a channel based on 'chanprefix'\n"
00074 "        and a digit sequence.\n"
00075 "  Options:\n"
00076 "    b                      - Only spy on channels involved in a bridged call.\n"
00077 "    B                      - Instead of whispering on a single channel barge in on both\n"
00078 "                             channels involved in the call.\n"
00079 "    d                      - Override the typical numeric DTMF functionality and instead\n"
00080 "                             use DTMF to switch between spy modes.\n"
00081 "                                     4 = spy mode\n"
00082 "                                     5 = whisper mode\n"
00083 "                                     6 = barge mode\n"
00084 "    g(grp)                 - Only spy on channels in which one or more of the groups \n"
00085 "                             listed in 'grp' matches one or more groups from the\n"
00086 "                             SPYGROUP variable set on the channel to be spied upon.\n"
00087 "                             Note that both 'grp' and SPYGROUP can contain either a\n"
00088 "                             single group or a colon-delimited list of groups, such\n"
00089 "                             as 'sales:support:accounting'.\n"
00090 "    n([mailbox][@context]) - Say the name of the person being spied on if that person has recorded\n"
00091 "                             his/her name. If a context is specified, then that voicemail context will\n"
00092 "                             be searched when retrieving the name, otherwise the \"default\" context\n"
00093 "                             will be searched. If no mailbox is specified, then the channel name will\n"
00094 "                             be used when searching for the name (i.e. if SIP/1000 is the channel being\n"
00095 "                             spied on and no mailbox is specified, then \"1000\" will be used when searching\n"
00096 "                             for the name).\n"
00097 "    q                      - Don't play a beep when beginning to spy on a channel, or speak the\n"
00098 "                             selected channel name.\n"
00099 "    r[(basename)]          - Record the session to the monitor spool directory. An\n"
00100 "                             optional base for the filename may be specified. The\n"
00101 "                             default is 'chanspy'.\n"
00102 "    s                      - Skip the playback of the channel type (i.e. SIP, IAX, etc) when\n"
00103 "                             speaking the selected channel name.\n"
00104 "    v([value])             - Adjust the initial volume in the range from -4 to 4. A\n"
00105 "                             negative value refers to a quieter setting.\n"
00106 "    w                      - Enable 'whisper' mode, so the spying channel can talk to\n"
00107 "                             the spied-on channel.\n"
00108 "    W                      - Enable 'private whisper' mode, so the spying channel can\n"
00109 "                             talk to the spied-on channel but cannot listen to that\n"
00110 "                             channel.\n"
00111 "    o                      - Only listen to audio coming from this channel.\n"
00112 "    X                      - Allow the user to exit ChanSpy to a valid single digit\n"
00113 "                             numeric extension in the current context or the context\n"
00114 "                             specified by the SPY_EXIT_CONTEXT channel variable. The\n"
00115 "                             name of the last channel that was spied on will be stored\n"
00116 "                             in the SPY_CHANNEL variable.\n"
00117 "    e(ext)                 - Enable 'enforced' mode, so the spying channel can\n"
00118 "                             only monitor extensions whose name is in the 'ext' : \n"
00119 "                             delimited list.\n"
00120 ;
00121 
00122 static const char *app_ext = "ExtenSpy";
00123 static const char *desc_ext =
00124 "  ExtenSpy(exten[@context][,options]): This application is used to listen to the\n"
00125 "audio from an Asterisk channel. This includes the audio coming in and\n"
00126 "out of the channel being spied on. Only channels created by outgoing calls for the\n"
00127 "specified extension will be selected for spying. If the optional context is not\n"
00128 "supplied, the current channel's context will be used.\n"
00129 "  While spying, the following actions may be performed:\n"
00130 "    - Dialing # cycles the volume level.\n"
00131 "    - Dialing * will stop spying and look for another channel to spy on.\n"
00132 "  Note: The X option superseeds the two features above in that if a valid\n"
00133 "        single digit extension exists in the correct context it ChanSpy will\n"
00134 "        exit to it.\n"
00135 "  Options:\n"
00136 "    b                      - Only spy on channels involved in a bridged call.\n"
00137 "    B                      - Instead of whispering on a single channel barge in on both\n"
00138 "                             channels involved in the call.\n"
00139 "    d                      - Override the typical numeric DTMF functionality and instead\n"
00140 "                             use DTMF to switch between spy modes.\n"
00141 "                                     4 = spy mode\n"
00142 "                                     5 = whisper mode\n"
00143 "                                     6 = barge mode\n"
00144 "    g(grp)                 - Only spy on channels in which one or more of the groups \n"
00145 "                             listed in 'grp' matches one or more groups from the\n"
00146 "                             SPYGROUP variable set on the channel to be spied upon.\n"
00147 "                             Note that both 'grp' and SPYGROUP can contain either a\n"
00148 "                             single group or a colon-delimited list of groups, such\n"
00149 "                             as 'sales:support:accounting'.\n"
00150 "    n([mailbox][@context]) - Say the name of the person being spied on if that person has recorded\n"
00151 "                             his/her name. If a context is specified, then that voicemail context will\n"
00152 "                             be searched when retrieving the name, otherwise the \"default\" context\n"
00153 "                             will be searched. If no mailbox is specified, then the channel name will\n"
00154 "                             be used when searching for the name (i.e. if SIP/1000 is the channel being\n"
00155 "                             spied on and no mailbox is specified, then \"1000\" will be used when searching\n"
00156 "                             for the name).\n"
00157 "    q                      - Don't play a beep when beginning to spy on a channel, or speak the\n"
00158 "                             selected channel name.\n"
00159 "    r[(basename)]          - Record the session to the monitor spool directory. An\n"
00160 "                             optional base for the filename may be specified. The\n"
00161 "                             default is 'chanspy'.\n"
00162 "    s                      - Skip the playback of the channel type (i.e. SIP, IAX, etc) when\n"
00163 "                             speaking the selected channel name.\n"
00164 "    v([value])             - Adjust the initial volume in the range from -4 to 4. A\n"
00165 "                             negative value refers to a quieter setting.\n"
00166 "    w                      - Enable 'whisper' mode, so the spying channel can talk to\n"
00167 "                             the spied-on channel.\n"
00168 "    W                      - Enable 'private whisper' mode, so the spying channel can\n"
00169 "                             talk to the spied-on channel but cannot listen to that\n"
00170 "                             channel.\n"
00171 "    o                      - Only listen to audio coming from this channel.\n"
00172 "    X                      - Allow the user to exit ChanSpy to a valid single digit\n"
00173 "                             numeric extension in the current context or the context\n"
00174 "                             specified by the SPY_EXIT_CONTEXT channel variable. The\n"
00175 "                             name of the last channel that was spied on will be stored\n"
00176 "                             in the SPY_CHANNEL variable.\n"
00177 ;
00178 
00179 enum {
00180    OPTION_QUIET             = (1 << 0),    /* Quiet, no announcement */
00181    OPTION_BRIDGED           = (1 << 1),    /* Only look at bridged calls */
00182    OPTION_VOLUME            = (1 << 2),    /* Specify initial volume */
00183    OPTION_GROUP             = (1 << 3),    /* Only look at channels in group */
00184    OPTION_RECORD            = (1 << 4),
00185    OPTION_WHISPER           = (1 << 5),
00186    OPTION_PRIVATE           = (1 << 6),    /* Private Whisper mode */
00187    OPTION_READONLY          = (1 << 7),    /* Don't mix the two channels */
00188    OPTION_EXIT              = (1 << 8),    /* Exit to a valid single digit extension */
00189    OPTION_ENFORCED          = (1 << 9),    /* Enforced mode */
00190    OPTION_NOTECH            = (1 << 10),   /* Skip technology name playback */
00191    OPTION_BARGE             = (1 << 11),   /* Barge mode (whisper to both channels) */
00192    OPTION_NAME              = (1 << 12),   /* Say the name of the person on whom we will spy */
00193    OPTION_DTMF_SWITCH_MODES = (1 << 13),   /*Allow numeric DTMF to switch between chanspy modes */
00194 } chanspy_opt_flags;
00195 
00196 enum {
00197    OPT_ARG_VOLUME = 0,
00198    OPT_ARG_GROUP,
00199    OPT_ARG_RECORD,
00200    OPT_ARG_ENFORCED,
00201    OPT_ARG_NAME,
00202    OPT_ARG_ARRAY_SIZE,
00203 } chanspy_opt_args;
00204 
00205 AST_APP_OPTIONS(spy_opts, {
00206    AST_APP_OPTION('q', OPTION_QUIET),
00207    AST_APP_OPTION('b', OPTION_BRIDGED),
00208    AST_APP_OPTION('B', OPTION_BARGE),
00209    AST_APP_OPTION('w', OPTION_WHISPER),
00210    AST_APP_OPTION('W', OPTION_PRIVATE),
00211    AST_APP_OPTION_ARG('v', OPTION_VOLUME, OPT_ARG_VOLUME),
00212    AST_APP_OPTION_ARG('g', OPTION_GROUP, OPT_ARG_GROUP),
00213    AST_APP_OPTION_ARG('r', OPTION_RECORD, OPT_ARG_RECORD),
00214    AST_APP_OPTION_ARG('e', OPTION_ENFORCED, OPT_ARG_ENFORCED),
00215    AST_APP_OPTION('o', OPTION_READONLY),
00216    AST_APP_OPTION('X', OPTION_EXIT),
00217    AST_APP_OPTION('s', OPTION_NOTECH),
00218    AST_APP_OPTION_ARG('n', OPTION_NAME, OPT_ARG_NAME),
00219    AST_APP_OPTION('d', OPTION_DTMF_SWITCH_MODES),
00220 });
00221 
00222 static int next_unique_id_to_use = 0;
00223 
00224 struct chanspy_translation_helper {
00225    /* spy data */
00226    struct ast_audiohook spy_audiohook;
00227    struct ast_audiohook whisper_audiohook;
00228    struct ast_audiohook bridge_whisper_audiohook;
00229    int fd;
00230    int volfactor;
00231 };
00232 
00233 static void *spy_alloc(struct ast_channel *chan, void *data)
00234 {
00235    /* just store the data pointer in the channel structure */
00236    return data;
00237 }
00238 
00239 static void spy_release(struct ast_channel *chan, void *data)
00240 {
00241    /* nothing to do */
00242 }
00243 
00244 static int spy_generate(struct ast_channel *chan, void *data, int len, int samples)
00245 {
00246    struct chanspy_translation_helper *csth = data;
00247    struct ast_frame *f, *cur;
00248 
00249    ast_audiohook_lock(&csth->spy_audiohook);
00250    if (csth->spy_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
00251       /* Channel is already gone more than likely */
00252       ast_audiohook_unlock(&csth->spy_audiohook);
00253       return -1;
00254    }
00255 
00256    if (ast_test_flag(chan, OPTION_READONLY)) {
00257       /* Option 'o' was set, so don't mix channel audio */
00258       f = ast_audiohook_read_frame(&csth->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_READ, AST_FORMAT_SLINEAR);
00259    } else {
00260       f = ast_audiohook_read_frame(&csth->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR);
00261    }
00262 
00263    ast_audiohook_unlock(&csth->spy_audiohook);
00264 
00265    if (!f)
00266       return 0;
00267 
00268    for (cur = f; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
00269       if (ast_write(chan, cur)) {
00270          ast_frfree(f);
00271          return -1;
00272       }
00273 
00274       if (csth->fd) {
00275          if (write(csth->fd, cur->data.ptr, cur->datalen) < 0) {
00276             ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
00277          }
00278       }
00279    }
00280 
00281    ast_frfree(f);
00282 
00283    return 0;
00284 }
00285 
00286 static struct ast_generator spygen = {
00287    .alloc = spy_alloc,
00288    .release = spy_release,
00289    .generate = spy_generate,
00290 };
00291 
00292 static int start_spying(struct ast_channel *chan, const char *spychan_name, struct ast_audiohook *audiohook)
00293 {
00294    int res = 0;
00295    struct ast_channel *peer = NULL;
00296 
00297    ast_log(LOG_NOTICE, "Attaching %s to %s\n", spychan_name, chan->name);
00298 
00299    ast_set_flag(audiohook, AST_AUDIOHOOK_TRIGGER_SYNC | AST_AUDIOHOOK_SMALL_QUEUE);
00300    res = ast_audiohook_attach(chan, audiohook);
00301 
00302    if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan))) { 
00303       ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
00304    }
00305    return res;
00306 }
00307 
00308 struct chanspy_ds {
00309    struct ast_channel *chan;
00310    char unique_id[20];
00311    ast_mutex_t lock;
00312 };
00313 
00314 static void change_spy_mode(const char digit, struct ast_flags *flags)
00315 {
00316    if (digit == '4') {
00317       ast_clear_flag(flags, OPTION_WHISPER);
00318       ast_clear_flag(flags, OPTION_BARGE);
00319    } else if (digit == '5') {
00320       ast_clear_flag(flags, OPTION_BARGE);
00321       ast_set_flag(flags, OPTION_WHISPER);
00322    } else if (digit == '6') {
00323       ast_clear_flag(flags, OPTION_WHISPER);
00324       ast_set_flag(flags, OPTION_BARGE);
00325    }
00326 }
00327 
00328 static int channel_spy(struct ast_channel *chan, struct chanspy_ds *spyee_chanspy_ds, 
00329    int *volfactor, int fd, struct ast_flags *flags, char *exitcontext) 
00330 {
00331    struct chanspy_translation_helper csth;
00332    int running = 0, res, x = 0;
00333    char inp[24] = {0};
00334    char *name;
00335    struct ast_frame *f;
00336    struct ast_silence_generator *silgen = NULL;
00337    struct ast_channel *spyee = NULL, *spyee_bridge = NULL;
00338    const char *spyer_name;
00339 
00340    ast_channel_lock(chan);
00341    spyer_name = ast_strdupa(chan->name);
00342    ast_channel_unlock(chan);
00343 
00344    ast_mutex_lock(&spyee_chanspy_ds->lock);
00345    while ((spyee = spyee_chanspy_ds->chan) && ast_channel_trylock(spyee)) {
00346       /* avoid a deadlock here, just in case spyee is masqueraded and
00347        * chanspy_ds_chan_fixup() is called with the channel locked */
00348       DEADLOCK_AVOIDANCE(&spyee_chanspy_ds->lock);
00349    }
00350    ast_mutex_unlock(&spyee_chanspy_ds->lock);
00351 
00352    if (!spyee)
00353       return 0;
00354 
00355    /* We now hold the channel lock on spyee */
00356 
00357    if (ast_check_hangup(chan) || ast_check_hangup(spyee)) {
00358       ast_channel_unlock(spyee);
00359       return 0;
00360    }
00361 
00362    name = ast_strdupa(spyee->name);
00363    ast_verb(2, "Spying on channel %s\n", name);
00364 
00365    memset(&csth, 0, sizeof(csth));
00366 
00367    ast_audiohook_init(&csth.spy_audiohook, AST_AUDIOHOOK_TYPE_SPY, "ChanSpy");
00368 
00369    if (start_spying(spyee, spyer_name, &csth.spy_audiohook)) {
00370       ast_audiohook_destroy(&csth.spy_audiohook);
00371       ast_channel_unlock(spyee);
00372       return 0;
00373    }
00374 
00375    ast_audiohook_init(&csth.whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "ChanSpy");
00376    ast_audiohook_init(&csth.bridge_whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "Chanspy");
00377    if (start_spying(spyee, spyer_name, &csth.whisper_audiohook)) {
00378       ast_log(LOG_WARNING, "Unable to attach whisper audiohook to spyee %s. Whisper mode disabled!\n", spyee->name);
00379    }
00380    if ((spyee_bridge = ast_bridged_channel(spyee))) {
00381       ast_channel_lock(spyee_bridge);
00382       if (start_spying(spyee_bridge, spyer_name, &csth.bridge_whisper_audiohook)) {
00383          ast_log(LOG_WARNING, "Unable to attach barge audiohook on spyee %s. Barge mode disabled!\n", spyee->name);
00384       }
00385       ast_channel_unlock(spyee_bridge);
00386    }
00387    ast_channel_unlock(spyee);
00388    spyee = NULL;
00389 
00390    ast_channel_lock(chan);
00391    ast_set_flag(chan, AST_FLAG_END_DTMF_ONLY);
00392    ast_channel_unlock(chan);
00393 
00394    csth.volfactor = *volfactor;
00395 
00396    if (csth.volfactor) {
00397       csth.spy_audiohook.options.read_volume = csth.volfactor;
00398       csth.spy_audiohook.options.write_volume = csth.volfactor;
00399    }
00400 
00401    csth.fd = fd;
00402 
00403    if (ast_test_flag(flags, OPTION_PRIVATE))
00404       silgen = ast_channel_start_silence_generator(chan);
00405    else
00406       ast_activate_generator(chan, &spygen, &csth);
00407 
00408    /* We can no longer rely on 'spyee' being an actual channel;
00409       it can be hung up and freed out from under us. However, the
00410       channel destructor will put NULL into our csth.spy.chan
00411       field when that happens, so that is our signal that the spyee
00412       channel has gone away.
00413    */
00414 
00415    /* Note: it is very important that the ast_waitfor() be the first
00416       condition in this expression, so that if we wait for some period
00417       of time before receiving a frame from our spying channel, we check
00418       for hangup on the spied-on channel _after_ knowing that a frame
00419       has arrived, since the spied-on channel could have gone away while
00420       we were waiting
00421    */
00422    while ((res = ast_waitfor(chan, -1) > -1) && csth.spy_audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
00423       if (!(f = ast_read(chan)) || ast_check_hangup(chan)) {
00424          running = -1;
00425          break;
00426       }
00427 
00428       if (ast_test_flag(flags, OPTION_BARGE) && f->frametype == AST_FRAME_VOICE) {
00429          ast_audiohook_lock(&csth.whisper_audiohook);
00430          ast_audiohook_lock(&csth.bridge_whisper_audiohook);
00431          ast_audiohook_write_frame(&csth.whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
00432          ast_audiohook_write_frame(&csth.bridge_whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
00433          ast_audiohook_unlock(&csth.whisper_audiohook);
00434          ast_audiohook_unlock(&csth.bridge_whisper_audiohook);
00435          ast_frfree(f);
00436          continue;
00437       } else if (ast_test_flag(flags, OPTION_WHISPER) && f->frametype == AST_FRAME_VOICE) {
00438          ast_audiohook_lock(&csth.whisper_audiohook);
00439          ast_audiohook_write_frame(&csth.whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
00440          ast_audiohook_unlock(&csth.whisper_audiohook);
00441          ast_frfree(f);
00442          continue;
00443       }
00444       
00445       res = (f->frametype == AST_FRAME_DTMF) ? f->subclass : 0;
00446       ast_frfree(f);
00447       if (!res)
00448          continue;
00449 
00450       if (x == sizeof(inp))
00451          x = 0;
00452 
00453       if (res < 0) {
00454          running = -1;
00455          break;
00456       }
00457 
00458       if (ast_test_flag(flags, OPTION_EXIT)) {
00459          char tmp[2];
00460          tmp[0] = res;
00461          tmp[1] = '\0';
00462          if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) {
00463             ast_debug(1, "Got DTMF %c, goto context %s\n", tmp[0], exitcontext);
00464             pbx_builtin_setvar_helper(chan, "SPY_CHANNEL", name);
00465             running = -2;
00466             break;
00467          } else {
00468             ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
00469          }
00470       } else if (res >= '0' && res <= '9') {
00471          if (ast_test_flag(flags, OPTION_DTMF_SWITCH_MODES)) {
00472             change_spy_mode(res, flags);
00473          } else {
00474             inp[x++] = res;
00475          }
00476       }
00477 
00478       if (res == '*') {
00479          running = 0;
00480          break;
00481       } else if (res == '#') {
00482          if (!ast_strlen_zero(inp)) {
00483             running = atoi(inp);
00484             break;
00485          }
00486 
00487          (*volfactor)++;
00488          if (*volfactor > 4)
00489             *volfactor = -4;
00490          ast_verb(3, "Setting spy volume on %s to %d\n", chan->name, *volfactor);
00491 
00492          csth.volfactor = *volfactor;
00493          csth.spy_audiohook.options.read_volume = csth.volfactor;
00494          csth.spy_audiohook.options.write_volume = csth.volfactor;
00495       }
00496    }
00497 
00498    if (ast_test_flag(flags, OPTION_PRIVATE))
00499       ast_channel_stop_silence_generator(chan, silgen);
00500    else
00501       ast_deactivate_generator(chan);
00502 
00503    ast_channel_lock(chan);
00504    ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
00505    ast_channel_unlock(chan);
00506 
00507    ast_audiohook_lock(&csth.whisper_audiohook);
00508    ast_audiohook_detach(&csth.whisper_audiohook);
00509    ast_audiohook_unlock(&csth.whisper_audiohook);
00510    ast_audiohook_destroy(&csth.whisper_audiohook);
00511    
00512    ast_audiohook_lock(&csth.bridge_whisper_audiohook);
00513    ast_audiohook_detach(&csth.bridge_whisper_audiohook);
00514    ast_audiohook_unlock(&csth.bridge_whisper_audiohook);
00515    ast_audiohook_destroy(&csth.bridge_whisper_audiohook);
00516 
00517    ast_audiohook_lock(&csth.spy_audiohook);
00518    ast_audiohook_detach(&csth.spy_audiohook);
00519    ast_audiohook_unlock(&csth.spy_audiohook);
00520    ast_audiohook_destroy(&csth.spy_audiohook);
00521    
00522    ast_verb(2, "Done Spying on channel %s\n", name);
00523 
00524    return running;
00525 }
00526 
00527 /*!
00528  * \note This relies on the embedded lock to be recursive, as it may be called
00529  * due to a call to chanspy_ds_free with the lock held there.
00530  */
00531 static void chanspy_ds_destroy(void *data)
00532 {
00533    struct chanspy_ds *chanspy_ds = data;
00534 
00535    /* Setting chan to be NULL is an atomic operation, but we don't want this
00536     * value to change while this lock is held.  The lock is held elsewhere
00537     * while it performs non-atomic operations with this channel pointer */
00538 
00539    ast_mutex_lock(&chanspy_ds->lock);
00540    chanspy_ds->chan = NULL;
00541    ast_mutex_unlock(&chanspy_ds->lock);
00542 }
00543 
00544 static void chanspy_ds_chan_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
00545 {
00546    struct chanspy_ds *chanspy_ds = data;
00547    
00548    ast_mutex_lock(&chanspy_ds->lock);
00549    chanspy_ds->chan = new_chan;
00550    ast_mutex_unlock(&chanspy_ds->lock);
00551 }
00552 
00553 static const struct ast_datastore_info chanspy_ds_info = {
00554    .type = "chanspy",
00555    .destroy = chanspy_ds_destroy,
00556    .chan_fixup = chanspy_ds_chan_fixup,
00557 };
00558 
00559 static struct chanspy_ds *chanspy_ds_free(struct chanspy_ds *chanspy_ds)
00560 {
00561    struct ast_channel *chan;
00562 
00563    if (!chanspy_ds) {
00564       return NULL;
00565    }
00566 
00567    ast_mutex_lock(&chanspy_ds->lock);
00568    while ((chan = chanspy_ds->chan)) {
00569       struct ast_datastore *datastore;
00570 
00571       if (ast_channel_trylock(chan)) {
00572          DEADLOCK_AVOIDANCE(&chanspy_ds->lock);
00573          continue;
00574       }
00575       if ((datastore = ast_channel_datastore_find(chan, &chanspy_ds_info, chanspy_ds->unique_id))) {
00576          ast_channel_datastore_remove(chan, datastore);
00577          /* chanspy_ds->chan is NULL after this call */
00578          chanspy_ds_destroy(datastore->data);
00579          datastore->data = NULL;
00580          ast_datastore_free(datastore);
00581       }
00582       ast_channel_unlock(chan);
00583       break;
00584    }
00585    ast_mutex_unlock(&chanspy_ds->lock);
00586 
00587    return NULL;
00588 }
00589 
00590 /*! \note Returns the channel in the chanspy_ds locked as well as the chanspy_ds locked */
00591 static struct chanspy_ds *setup_chanspy_ds(struct ast_channel *chan, struct chanspy_ds *chanspy_ds)
00592 {
00593    struct ast_datastore *datastore = NULL;
00594 
00595    ast_mutex_lock(&chanspy_ds->lock);
00596 
00597    if (!(datastore = ast_datastore_alloc(&chanspy_ds_info, chanspy_ds->unique_id))) {
00598       ast_mutex_unlock(&chanspy_ds->lock);
00599       chanspy_ds = chanspy_ds_free(chanspy_ds);
00600       ast_channel_unlock(chan);
00601       return NULL;
00602    }
00603    
00604    chanspy_ds->chan = chan;
00605    datastore->data = chanspy_ds;
00606    ast_channel_datastore_add(chan, datastore);
00607 
00608    return chanspy_ds;
00609 }
00610 
00611 static struct chanspy_ds *next_channel(struct ast_channel *chan,
00612    const struct ast_channel *last, const char *spec,
00613    const char *exten, const char *context, struct chanspy_ds *chanspy_ds)
00614 {
00615    struct ast_channel *next;
00616    const size_t pseudo_len = strlen("DAHDI/pseudo");
00617 
00618 redo:
00619    if (!ast_strlen_zero(spec))
00620       next = ast_walk_channel_by_name_prefix_locked(last, spec, strlen(spec));
00621    else if (!ast_strlen_zero(exten))
00622       next = ast_walk_channel_by_exten_locked(last, exten, context);
00623    else
00624       next = ast_channel_walk_locked(last);
00625 
00626    if (!next)
00627       return NULL;
00628 
00629    if (!strncmp(next->name, "DAHDI/pseudo", pseudo_len)) {
00630       last = next;
00631       ast_channel_unlock(next);
00632       goto redo;
00633    } else if (next == chan) {
00634       last = next;
00635       ast_channel_unlock(next);
00636       goto redo;
00637    }
00638 
00639    return setup_chanspy_ds(next, chanspy_ds);
00640 }
00641 
00642 static int common_exec(struct ast_channel *chan, struct ast_flags *flags,
00643    int volfactor, const int fd, const char *mygroup, const char *myenforced,
00644    const char *spec, const char *exten, const char *context, const char *mailbox,
00645    const char *name_context)
00646 {
00647    char nameprefix[AST_NAME_STRLEN];
00648    char peer_name[AST_NAME_STRLEN + 5];
00649    char exitcontext[AST_MAX_CONTEXT] = "";
00650    signed char zero_volume = 0;
00651    int waitms;
00652    int res;
00653    char *ptr;
00654    int num;
00655    int num_spyed_upon = 1;
00656    struct chanspy_ds chanspy_ds = { 0, };
00657 
00658    if (ast_test_flag(flags, OPTION_EXIT)) {
00659       const char *c;
00660       ast_channel_lock(chan);
00661       if ((c = pbx_builtin_getvar_helper(chan, "SPY_EXIT_CONTEXT"))) {
00662          ast_copy_string(exitcontext, c, sizeof(exitcontext));
00663       } else if (!ast_strlen_zero(chan->macrocontext)) {
00664          ast_copy_string(exitcontext, chan->macrocontext, sizeof(exitcontext));
00665       } else {
00666          ast_copy_string(exitcontext, chan->context, sizeof(exitcontext));
00667       }
00668       ast_channel_unlock(chan);
00669    }
00670 
00671    ast_mutex_init(&chanspy_ds.lock);
00672 
00673    snprintf(chanspy_ds.unique_id, sizeof(chanspy_ds.unique_id), "%d", ast_atomic_fetchadd_int(&next_unique_id_to_use, +1));
00674 
00675    if (chan->_state != AST_STATE_UP)
00676       ast_answer(chan);
00677 
00678    ast_set_flag(chan, AST_FLAG_SPYING); /* so nobody can spy on us while we are spying */
00679 
00680    waitms = 100;
00681 
00682    for (;;) {
00683       struct chanspy_ds *peer_chanspy_ds = NULL, *next_chanspy_ds = NULL;
00684       struct ast_channel *prev = NULL, *peer = NULL;
00685 
00686       if (!ast_test_flag(flags, OPTION_QUIET) && num_spyed_upon) {
00687          res = ast_streamfile(chan, "beep", chan->language);
00688          if (!res)
00689             res = ast_waitstream(chan, "");
00690          else if (res < 0) {
00691             ast_clear_flag(chan, AST_FLAG_SPYING);
00692             break;
00693          }
00694          if (!ast_strlen_zero(exitcontext)) {
00695             char tmp[2];
00696             tmp[0] = res;
00697             tmp[1] = '\0';
00698             if (!ast_goto_if_exists(chan, exitcontext, tmp, 1))
00699                goto exit;
00700             else
00701                ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
00702          }
00703       }
00704 
00705       res = ast_waitfordigit(chan, waitms);
00706       if (res < 0) {
00707          ast_clear_flag(chan, AST_FLAG_SPYING);
00708          break;
00709       }
00710       if (!ast_strlen_zero(exitcontext)) {
00711          char tmp[2];
00712          tmp[0] = res;
00713          tmp[1] = '\0';
00714          if (!ast_goto_if_exists(chan, exitcontext, tmp, 1))
00715             goto exit;
00716          else
00717             ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
00718       }
00719 
00720       /* reset for the next loop around, unless overridden later */
00721       waitms = 100;
00722       num_spyed_upon = 0;
00723 
00724       for (peer_chanspy_ds = next_channel(chan, prev, spec, exten, context, &chanspy_ds);
00725            peer_chanspy_ds;
00726           chanspy_ds_free(peer_chanspy_ds), prev = peer,
00727            peer_chanspy_ds = next_chanspy_ds ? next_chanspy_ds : 
00728             next_channel(chan, prev, spec, exten, context, &chanspy_ds), next_chanspy_ds = NULL) {
00729          int igrp = !mygroup;
00730          int ienf = !myenforced;
00731          char *s;
00732 
00733          peer = peer_chanspy_ds->chan;
00734 
00735          ast_mutex_unlock(&peer_chanspy_ds->lock);
00736 
00737          if (peer == prev) {
00738             ast_channel_unlock(peer);
00739             chanspy_ds_free(peer_chanspy_ds);
00740             break;
00741          }
00742 
00743          if (ast_check_hangup(chan)) {
00744             ast_channel_unlock(peer);
00745             chanspy_ds_free(peer_chanspy_ds);
00746             break;
00747          }
00748 
00749          if (ast_test_flag(flags, OPTION_BRIDGED) && !ast_bridged_channel(peer)) {
00750             ast_channel_unlock(peer);
00751             continue;
00752          }
00753 
00754          if (ast_check_hangup(peer) || ast_test_flag(peer, AST_FLAG_SPYING)) {
00755             ast_channel_unlock(peer);
00756             continue;
00757          }
00758 
00759          if (mygroup) {
00760             int num_groups = 0;
00761             int num_mygroups = 0;
00762             char dup_group[512];
00763             char dup_mygroup[512];
00764             char *groups[NUM_SPYGROUPS];
00765             char *mygroups[NUM_SPYGROUPS];
00766             const char *group;
00767             int x;
00768             int y;
00769             ast_copy_string(dup_mygroup, mygroup, sizeof(dup_mygroup));
00770             num_mygroups = ast_app_separate_args(dup_mygroup, ':', mygroups,
00771                ARRAY_LEN(mygroups));
00772 
00773             if ((group = pbx_builtin_getvar_helper(peer, "SPYGROUP"))) {
00774                ast_copy_string(dup_group, group, sizeof(dup_group));
00775                num_groups = ast_app_separate_args(dup_group, ':', groups,
00776                   ARRAY_LEN(groups));
00777             }
00778 
00779             for (y = 0; y < num_mygroups; y++) {
00780                for (x = 0; x < num_groups; x++) {
00781                   if (!strcmp(mygroups[y], groups[x])) {
00782                      igrp = 1;
00783                      break;
00784                   }
00785                }
00786             }
00787          }
00788 
00789          if (!igrp) {
00790             ast_channel_unlock(peer);
00791             continue;
00792          }
00793 
00794          if (myenforced) {
00795             char ext[AST_CHANNEL_NAME + 3];
00796             char buffer[512];
00797             char *end;
00798 
00799             snprintf(buffer, sizeof(buffer) - 1, ":%s:", myenforced);
00800 
00801             ast_copy_string(ext + 1, peer->name, sizeof(ext) - 1);
00802             if ((end = strchr(ext, '-'))) {
00803                *end++ = ':';
00804                *end = '\0';
00805             }
00806 
00807             ext[0] = ':';
00808 
00809             if (strcasestr(buffer, ext)) {
00810                ienf = 1;
00811             }
00812          }
00813 
00814          if (!ienf) {
00815             continue;
00816          }
00817 
00818          strcpy(peer_name, "spy-");
00819          strncat(peer_name, peer->name, AST_NAME_STRLEN - 4 - 1);
00820          ptr = strchr(peer_name, '/');
00821          *ptr++ = '\0';
00822          ptr = strsep(&ptr, "-");
00823 
00824          for (s = peer_name; s < ptr; s++)
00825             *s = tolower(*s);
00826          /* We have to unlock the peer channel here to avoid a deadlock.
00827           * So, when we need to dereference it again, we have to lock the 
00828           * datastore and get the pointer from there to see if the channel 
00829           * is still valid. */
00830          ast_channel_unlock(peer);
00831 
00832          if (!ast_test_flag(flags, OPTION_QUIET)) {
00833             if (ast_test_flag(flags, OPTION_NAME)) {
00834                const char *local_context = S_OR(name_context, "default");
00835                const char *local_mailbox = S_OR(mailbox, ptr);
00836                res = ast_app_sayname(chan, local_mailbox, local_context);
00837             }
00838             if (!ast_test_flag(flags, OPTION_NAME) || res < 0) {
00839                if (!ast_test_flag(flags, OPTION_NOTECH)) {
00840                   if (ast_fileexists(peer_name, NULL, NULL) != -1) {
00841                      res = ast_streamfile(chan, peer_name, chan->language);
00842                      if (!res) {
00843                         res = ast_waitstream(chan, "");
00844                      }
00845                      if (res) {
00846                         chanspy_ds_free(peer_chanspy_ds);
00847                         break;
00848                      }
00849                   } else {
00850                      res = ast_say_character_str(chan, peer_name, "", chan->language);
00851                   }
00852                }
00853                if ((num = atoi(ptr)))
00854                   ast_say_digits(chan, atoi(ptr), "", chan->language);
00855             }
00856          }
00857 
00858          res = channel_spy(chan, peer_chanspy_ds, &volfactor, fd, flags, exitcontext);
00859          num_spyed_upon++; 
00860 
00861          if (res == -1) {
00862             chanspy_ds_free(peer_chanspy_ds);
00863             goto exit;
00864          } else if (res == -2) {
00865             res = 0;
00866             chanspy_ds_free(peer_chanspy_ds);
00867             goto exit;
00868          } else if (res > 1 && spec) {
00869             struct ast_channel *next;
00870 
00871             snprintf(nameprefix, AST_NAME_STRLEN, "%s/%d", spec, res);
00872 
00873             if ((next = ast_get_channel_by_name_prefix_locked(nameprefix, strlen(nameprefix)))) {
00874                peer_chanspy_ds = chanspy_ds_free(peer_chanspy_ds);
00875                next_chanspy_ds = setup_chanspy_ds(next, &chanspy_ds);
00876             } else {
00877                /* stay on this channel, if it is still valid */
00878 
00879                ast_mutex_lock(&peer_chanspy_ds->lock);
00880                if (peer_chanspy_ds->chan) {
00881                   ast_channel_lock(peer_chanspy_ds->chan);
00882                   next_chanspy_ds = peer_chanspy_ds;
00883                   peer_chanspy_ds = NULL;
00884                } else {
00885                   /* the channel is gone */
00886                   ast_mutex_unlock(&peer_chanspy_ds->lock);
00887                   next_chanspy_ds = NULL;
00888                }
00889             }
00890 
00891             peer = NULL;
00892          }
00893       }
00894       if (res == -1 || ast_check_hangup(chan))
00895          break;
00896    }
00897 exit:
00898 
00899    ast_clear_flag(chan, AST_FLAG_SPYING);
00900 
00901    ast_channel_setoption(chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0);
00902 
00903    ast_mutex_lock(&chanspy_ds.lock);
00904    ast_mutex_unlock(&chanspy_ds.lock);
00905    ast_mutex_destroy(&chanspy_ds.lock);
00906 
00907    return res;
00908 }
00909 
00910 static int chanspy_exec(struct ast_channel *chan, void *data)
00911 {
00912    char *myenforced = NULL;
00913    char *mygroup = NULL;
00914    char *recbase = NULL;
00915    int fd = 0;
00916    struct ast_flags flags;
00917    int oldwf = 0;
00918    int volfactor = 0;
00919    int res;
00920    char *mailbox = NULL;
00921    char *name_context = NULL;
00922    AST_DECLARE_APP_ARGS(args,
00923       AST_APP_ARG(spec);
00924       AST_APP_ARG(options);
00925    );
00926    char *opts[OPT_ARG_ARRAY_SIZE];
00927 
00928    data = ast_strdupa(data);
00929    AST_STANDARD_APP_ARGS(args, data);
00930 
00931    if (args.spec && !strcmp(args.spec, "all"))
00932       args.spec = NULL;
00933 
00934    if (args.options) {
00935       ast_app_parse_options(spy_opts, &flags, opts, args.options);
00936       if (ast_test_flag(&flags, OPTION_GROUP))
00937          mygroup = opts[OPT_ARG_GROUP];
00938 
00939       if (ast_test_flag(&flags, OPTION_RECORD) &&
00940          !(recbase = opts[OPT_ARG_RECORD]))
00941          recbase = "chanspy";
00942 
00943       if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) {
00944          int vol;
00945 
00946          if ((sscanf(opts[OPT_ARG_VOLUME], "%30d", &vol) != 1) || (vol > 4) || (vol < -4))
00947             ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
00948          else
00949             volfactor = vol;
00950       }
00951 
00952       if (ast_test_flag(&flags, OPTION_PRIVATE))
00953          ast_set_flag(&flags, OPTION_WHISPER);
00954 
00955       if (ast_test_flag(&flags, OPTION_ENFORCED))
00956          myenforced = opts[OPT_ARG_ENFORCED];
00957       
00958       if (ast_test_flag(&flags, OPTION_NAME)) {
00959          if (!ast_strlen_zero(opts[OPT_ARG_NAME])) {
00960             char *delimiter;
00961             if ((delimiter = strchr(opts[OPT_ARG_NAME], '@'))) {
00962                mailbox = opts[OPT_ARG_NAME];
00963                *delimiter++ = '\0';
00964                name_context = delimiter;
00965             } else {
00966                mailbox = opts[OPT_ARG_NAME];
00967             }
00968          }
00969       }
00970 
00971 
00972    } else
00973       ast_clear_flag(&flags, AST_FLAGS_ALL);
00974 
00975    oldwf = chan->writeformat;
00976    if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
00977       ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
00978       return -1;
00979    }
00980 
00981    if (recbase) {
00982       char filename[PATH_MAX];
00983 
00984       snprintf(filename, sizeof(filename), "%s/%s.%d.raw", ast_config_AST_MONITOR_DIR, recbase, (int) time(NULL));
00985       if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, AST_FILE_MODE)) <= 0) {
00986          ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename);
00987          fd = 0;
00988       }
00989    }
00990 
00991    res = common_exec(chan, &flags, volfactor, fd, mygroup, myenforced, args.spec, NULL, NULL, mailbox, name_context);
00992 
00993    if (fd)
00994       close(fd);
00995 
00996    if (oldwf && ast_set_write_format(chan, oldwf) < 0)
00997       ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
00998 
00999    return res;
01000 }
01001 
01002 static int extenspy_exec(struct ast_channel *chan, void *data)
01003 {
01004    char *ptr, *exten = NULL;
01005    char *mygroup = NULL;
01006    char *recbase = NULL;
01007    int fd = 0;
01008    struct ast_flags flags;
01009    int oldwf = 0;
01010    int volfactor = 0;
01011    int res;
01012    char *mailbox = NULL;
01013    char *name_context = NULL;
01014    AST_DECLARE_APP_ARGS(args,
01015       AST_APP_ARG(context);
01016       AST_APP_ARG(options);
01017    );
01018 
01019    data = ast_strdupa(data);
01020 
01021    AST_STANDARD_APP_ARGS(args, data);
01022    if (!ast_strlen_zero(args.context) && (ptr = strchr(args.context, '@'))) {
01023       exten = args.context;
01024       *ptr++ = '\0';
01025       args.context = ptr;
01026    }
01027 
01028    if (ast_strlen_zero(args.context))
01029       args.context = ast_strdupa(chan->context);
01030 
01031    if (args.options) {
01032       char *opts[OPT_ARG_ARRAY_SIZE];
01033 
01034       ast_app_parse_options(spy_opts, &flags, opts, args.options);
01035       if (ast_test_flag(&flags, OPTION_GROUP))
01036          mygroup = opts[OPT_ARG_GROUP];
01037 
01038       if (ast_test_flag(&flags, OPTION_RECORD) &&
01039          !(recbase = opts[OPT_ARG_RECORD]))
01040          recbase = "chanspy";
01041 
01042       if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) {
01043          int vol;
01044 
01045          if ((sscanf(opts[OPT_ARG_VOLUME], "%30d", &vol) != 1) || (vol > 4) || (vol < -4))
01046             ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
01047          else
01048             volfactor = vol;
01049       }
01050 
01051       if (ast_test_flag(&flags, OPTION_PRIVATE))
01052          ast_set_flag(&flags, OPTION_WHISPER);
01053 
01054       
01055       if (ast_test_flag(&flags, OPTION_NAME)) {
01056          if (!ast_strlen_zero(opts[OPT_ARG_NAME])) {
01057             char *delimiter;
01058             if ((delimiter = strchr(opts[OPT_ARG_NAME], '@'))) {
01059                mailbox = opts[OPT_ARG_NAME];
01060                *delimiter++ = '\0';
01061                name_context = delimiter;
01062             } else {
01063                mailbox = opts[OPT_ARG_NAME];
01064             }
01065          }
01066       }
01067 
01068    } else
01069       ast_clear_flag(&flags, AST_FLAGS_ALL);
01070 
01071    oldwf = chan->writeformat;
01072    if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
01073       ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
01074       return -1;
01075    }
01076 
01077    if (recbase) {
01078       char filename[PATH_MAX];
01079 
01080       snprintf(filename, sizeof(filename), "%s/%s.%d.raw", ast_config_AST_MONITOR_DIR, recbase, (int) time(NULL));
01081       if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, AST_FILE_MODE)) <= 0) {
01082          ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename);
01083          fd = 0;
01084       }
01085    }
01086 
01087 
01088    res = common_exec(chan, &flags, volfactor, fd, mygroup, NULL, NULL, exten, args.context, mailbox, name_context);
01089 
01090    if (fd)
01091       close(fd);
01092 
01093    if (oldwf && ast_set_write_format(chan, oldwf) < 0)
01094       ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
01095 
01096    return res;
01097 }
01098 
01099 static int unload_module(void)
01100 {
01101    int res = 0;
01102 
01103    res |= ast_unregister_application(app_chan);
01104    res |= ast_unregister_application(app_ext);
01105 
01106    return res;
01107 }
01108 
01109 static int load_module(void)
01110 {
01111    int res = 0;
01112 
01113    res |= ast_register_application(app_chan, chanspy_exec, tdesc, desc_chan);
01114    res |= ast_register_application(app_ext, extenspy_exec, tdesc, desc_ext);
01115 
01116    return res;
01117 }
01118 
01119 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Listen to the audio of an active channel");

Generated on Wed Oct 28 11:50:52 2009 for Asterisk - the Open Source PBX by  doxygen 1.5.6