Wed Aug 18 22:33:40 2010

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: 228192 $")
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(&csth->spy_audiohook, 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    ast_copy_flags(&csth.spy_audiohook, flags, AST_FLAGS_ALL);
00367 
00368    ast_audiohook_init(&csth.spy_audiohook, AST_AUDIOHOOK_TYPE_SPY, "ChanSpy");
00369 
00370    if (start_spying(spyee, spyer_name, &csth.spy_audiohook)) {
00371       ast_audiohook_destroy(&csth.spy_audiohook);
00372       ast_channel_unlock(spyee);
00373       return 0;
00374    }
00375 
00376    ast_audiohook_init(&csth.whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "ChanSpy");
00377    ast_audiohook_init(&csth.bridge_whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "Chanspy");
00378    if (start_spying(spyee, spyer_name, &csth.whisper_audiohook)) {
00379       ast_log(LOG_WARNING, "Unable to attach whisper audiohook to spyee %s. Whisper mode disabled!\n", spyee->name);
00380    }
00381    if ((spyee_bridge = ast_bridged_channel(spyee))) {
00382       ast_channel_lock(spyee_bridge);
00383       if (start_spying(spyee_bridge, spyer_name, &csth.bridge_whisper_audiohook)) {
00384          ast_log(LOG_WARNING, "Unable to attach barge audiohook on spyee %s. Barge mode disabled!\n", spyee->name);
00385       }
00386       ast_channel_unlock(spyee_bridge);
00387    }
00388    ast_channel_unlock(spyee);
00389    spyee = NULL;
00390 
00391    ast_channel_lock(chan);
00392    ast_set_flag(chan, AST_FLAG_END_DTMF_ONLY);
00393    ast_channel_unlock(chan);
00394 
00395    csth.volfactor = *volfactor;
00396 
00397    if (csth.volfactor) {
00398       csth.spy_audiohook.options.read_volume = csth.volfactor;
00399       csth.spy_audiohook.options.write_volume = csth.volfactor;
00400    }
00401 
00402    csth.fd = fd;
00403 
00404    if (ast_test_flag(flags, OPTION_PRIVATE))
00405       silgen = ast_channel_start_silence_generator(chan);
00406    else
00407       ast_activate_generator(chan, &spygen, &csth);
00408 
00409    /* We can no longer rely on 'spyee' being an actual channel;
00410       it can be hung up and freed out from under us. However, the
00411       channel destructor will put NULL into our csth.spy.chan
00412       field when that happens, so that is our signal that the spyee
00413       channel has gone away.
00414    */
00415 
00416    /* Note: it is very important that the ast_waitfor() be the first
00417       condition in this expression, so that if we wait for some period
00418       of time before receiving a frame from our spying channel, we check
00419       for hangup on the spied-on channel _after_ knowing that a frame
00420       has arrived, since the spied-on channel could have gone away while
00421       we were waiting
00422    */
00423    while ((res = ast_waitfor(chan, -1) > -1) && csth.spy_audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
00424       if (!(f = ast_read(chan)) || ast_check_hangup(chan)) {
00425          running = -1;
00426          break;
00427       }
00428 
00429       if (ast_test_flag(flags, OPTION_BARGE) && f->frametype == AST_FRAME_VOICE) {
00430          ast_audiohook_lock(&csth.whisper_audiohook);
00431          ast_audiohook_lock(&csth.bridge_whisper_audiohook);
00432          ast_audiohook_write_frame(&csth.whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
00433          ast_audiohook_write_frame(&csth.bridge_whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
00434          ast_audiohook_unlock(&csth.whisper_audiohook);
00435          ast_audiohook_unlock(&csth.bridge_whisper_audiohook);
00436          ast_frfree(f);
00437          continue;
00438       } else if (ast_test_flag(flags, OPTION_WHISPER) && f->frametype == AST_FRAME_VOICE) {
00439          ast_audiohook_lock(&csth.whisper_audiohook);
00440          ast_audiohook_write_frame(&csth.whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
00441          ast_audiohook_unlock(&csth.whisper_audiohook);
00442          ast_frfree(f);
00443          continue;
00444       }
00445       
00446       res = (f->frametype == AST_FRAME_DTMF) ? f->subclass : 0;
00447       ast_frfree(f);
00448       if (!res)
00449          continue;
00450 
00451       if (x == sizeof(inp))
00452          x = 0;
00453 
00454       if (res < 0) {
00455          running = -1;
00456          break;
00457       }
00458 
00459       if (ast_test_flag(flags, OPTION_EXIT)) {
00460          char tmp[2];
00461          tmp[0] = res;
00462          tmp[1] = '\0';
00463          if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) {
00464             ast_debug(1, "Got DTMF %c, goto context %s\n", tmp[0], exitcontext);
00465             pbx_builtin_setvar_helper(chan, "SPY_CHANNEL", name);
00466             running = -2;
00467             break;
00468          } else {
00469             ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
00470          }
00471       } else if (res >= '0' && res <= '9') {
00472          if (ast_test_flag(flags, OPTION_DTMF_SWITCH_MODES)) {
00473             change_spy_mode(res, flags);
00474          } else {
00475             inp[x++] = res;
00476          }
00477       }
00478 
00479       if (res == '*') {
00480          running = 0;
00481          break;
00482       } else if (res == '#') {
00483          if (!ast_strlen_zero(inp)) {
00484             running = atoi(inp);
00485             break;
00486          }
00487 
00488          (*volfactor)++;
00489          if (*volfactor > 4)
00490             *volfactor = -4;
00491          ast_verb(3, "Setting spy volume on %s to %d\n", chan->name, *volfactor);
00492 
00493          csth.volfactor = *volfactor;
00494          csth.spy_audiohook.options.read_volume = csth.volfactor;
00495          csth.spy_audiohook.options.write_volume = csth.volfactor;
00496       }
00497    }
00498 
00499    if (ast_test_flag(flags, OPTION_PRIVATE))
00500       ast_channel_stop_silence_generator(chan, silgen);
00501    else
00502       ast_deactivate_generator(chan);
00503 
00504    ast_channel_lock(chan);
00505    ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
00506    ast_channel_unlock(chan);
00507 
00508    ast_audiohook_lock(&csth.whisper_audiohook);
00509    ast_audiohook_detach(&csth.whisper_audiohook);
00510    ast_audiohook_unlock(&csth.whisper_audiohook);
00511    ast_audiohook_destroy(&csth.whisper_audiohook);
00512    
00513    ast_audiohook_lock(&csth.bridge_whisper_audiohook);
00514    ast_audiohook_detach(&csth.bridge_whisper_audiohook);
00515    ast_audiohook_unlock(&csth.bridge_whisper_audiohook);
00516    ast_audiohook_destroy(&csth.bridge_whisper_audiohook);
00517 
00518    ast_audiohook_lock(&csth.spy_audiohook);
00519    ast_audiohook_detach(&csth.spy_audiohook);
00520    ast_audiohook_unlock(&csth.spy_audiohook);
00521    ast_audiohook_destroy(&csth.spy_audiohook);
00522    
00523    ast_verb(2, "Done Spying on channel %s\n", name);
00524 
00525    return running;
00526 }
00527 
00528 /*!
00529  * \note This relies on the embedded lock to be recursive, as it may be called
00530  * due to a call to chanspy_ds_free with the lock held there.
00531  */
00532 static void chanspy_ds_destroy(void *data)
00533 {
00534    struct chanspy_ds *chanspy_ds = data;
00535 
00536    /* Setting chan to be NULL is an atomic operation, but we don't want this
00537     * value to change while this lock is held.  The lock is held elsewhere
00538     * while it performs non-atomic operations with this channel pointer */
00539 
00540    ast_mutex_lock(&chanspy_ds->lock);
00541    chanspy_ds->chan = NULL;
00542    ast_mutex_unlock(&chanspy_ds->lock);
00543 }
00544 
00545 static void chanspy_ds_chan_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
00546 {
00547    struct chanspy_ds *chanspy_ds = data;
00548    
00549    ast_mutex_lock(&chanspy_ds->lock);
00550    chanspy_ds->chan = new_chan;
00551    ast_mutex_unlock(&chanspy_ds->lock);
00552 }
00553 
00554 static const struct ast_datastore_info chanspy_ds_info = {
00555    .type = "chanspy",
00556    .destroy = chanspy_ds_destroy,
00557    .chan_fixup = chanspy_ds_chan_fixup,
00558 };
00559 
00560 static struct chanspy_ds *chanspy_ds_free(struct chanspy_ds *chanspy_ds)
00561 {
00562    struct ast_channel *chan;
00563 
00564    if (!chanspy_ds) {
00565       return NULL;
00566    }
00567 
00568    ast_mutex_lock(&chanspy_ds->lock);
00569    while ((chan = chanspy_ds->chan)) {
00570       struct ast_datastore *datastore;
00571 
00572       if (ast_channel_trylock(chan)) {
00573          DEADLOCK_AVOIDANCE(&chanspy_ds->lock);
00574          continue;
00575       }
00576       if ((datastore = ast_channel_datastore_find(chan, &chanspy_ds_info, chanspy_ds->unique_id))) {
00577          ast_channel_datastore_remove(chan, datastore);
00578          /* chanspy_ds->chan is NULL after this call */
00579          chanspy_ds_destroy(datastore->data);
00580          datastore->data = NULL;
00581          ast_datastore_free(datastore);
00582       }
00583       ast_channel_unlock(chan);
00584       break;
00585    }
00586    ast_mutex_unlock(&chanspy_ds->lock);
00587 
00588    return NULL;
00589 }
00590 
00591 /*! \note Returns the channel in the chanspy_ds locked as well as the chanspy_ds locked */
00592 static struct chanspy_ds *setup_chanspy_ds(struct ast_channel *chan, struct chanspy_ds *chanspy_ds)
00593 {
00594    struct ast_datastore *datastore = NULL;
00595 
00596    ast_mutex_lock(&chanspy_ds->lock);
00597 
00598    if (!(datastore = ast_datastore_alloc(&chanspy_ds_info, chanspy_ds->unique_id))) {
00599       ast_mutex_unlock(&chanspy_ds->lock);
00600       chanspy_ds = chanspy_ds_free(chanspy_ds);
00601       ast_channel_unlock(chan);
00602       return NULL;
00603    }
00604    
00605    chanspy_ds->chan = chan;
00606    datastore->data = chanspy_ds;
00607    ast_channel_datastore_add(chan, datastore);
00608 
00609    return chanspy_ds;
00610 }
00611 
00612 static struct chanspy_ds *next_channel(struct ast_channel *chan,
00613    const struct ast_channel *last, const char *spec,
00614    const char *exten, const char *context, struct chanspy_ds *chanspy_ds)
00615 {
00616    struct ast_channel *next;
00617    const size_t pseudo_len = strlen("DAHDI/pseudo");
00618 
00619 redo:
00620    if (!ast_strlen_zero(spec))
00621       next = ast_walk_channel_by_name_prefix_locked(last, spec, strlen(spec));
00622    else if (!ast_strlen_zero(exten))
00623       next = ast_walk_channel_by_exten_locked(last, exten, context);
00624    else
00625       next = ast_channel_walk_locked(last);
00626 
00627    if (!next)
00628       return NULL;
00629 
00630    if (!strncmp(next->name, "DAHDI/pseudo", pseudo_len)) {
00631       last = next;
00632       ast_channel_unlock(next);
00633       goto redo;
00634    } else if (next == chan) {
00635       last = next;
00636       ast_channel_unlock(next);
00637       goto redo;
00638    }
00639 
00640    return setup_chanspy_ds(next, chanspy_ds);
00641 }
00642 
00643 static int common_exec(struct ast_channel *chan, struct ast_flags *flags,
00644    int volfactor, const int fd, const char *mygroup, const char *myenforced,
00645    const char *spec, const char *exten, const char *context, const char *mailbox,
00646    const char *name_context)
00647 {
00648    char nameprefix[AST_NAME_STRLEN];
00649    char peer_name[AST_NAME_STRLEN + 5];
00650    char exitcontext[AST_MAX_CONTEXT] = "";
00651    signed char zero_volume = 0;
00652    int waitms;
00653    int res;
00654    char *ptr;
00655    int num;
00656    int num_spyed_upon = 1;
00657    struct chanspy_ds chanspy_ds = { 0, };
00658 
00659    if (ast_test_flag(flags, OPTION_EXIT)) {
00660       const char *c;
00661       ast_channel_lock(chan);
00662       if ((c = pbx_builtin_getvar_helper(chan, "SPY_EXIT_CONTEXT"))) {
00663          ast_copy_string(exitcontext, c, sizeof(exitcontext));
00664       } else if (!ast_strlen_zero(chan->macrocontext)) {
00665          ast_copy_string(exitcontext, chan->macrocontext, sizeof(exitcontext));
00666       } else {
00667          ast_copy_string(exitcontext, chan->context, sizeof(exitcontext));
00668       }
00669       ast_channel_unlock(chan);
00670    }
00671 
00672    ast_mutex_init(&chanspy_ds.lock);
00673 
00674    snprintf(chanspy_ds.unique_id, sizeof(chanspy_ds.unique_id), "%d", ast_atomic_fetchadd_int(&next_unique_id_to_use, +1));
00675 
00676    if (chan->_state != AST_STATE_UP)
00677       ast_answer(chan);
00678 
00679    ast_set_flag(chan, AST_FLAG_SPYING); /* so nobody can spy on us while we are spying */
00680 
00681    waitms = 100;
00682 
00683    for (;;) {
00684       struct chanspy_ds *peer_chanspy_ds = NULL, *next_chanspy_ds = NULL;
00685       struct ast_channel *prev = NULL, *peer = NULL;
00686 
00687       if (!ast_test_flag(flags, OPTION_QUIET) && num_spyed_upon) {
00688          res = ast_streamfile(chan, "beep", chan->language);
00689          if (!res)
00690             res = ast_waitstream(chan, "");
00691          else if (res < 0) {
00692             ast_clear_flag(chan, AST_FLAG_SPYING);
00693             break;
00694          }
00695          if (!ast_strlen_zero(exitcontext)) {
00696             char tmp[2];
00697             tmp[0] = res;
00698             tmp[1] = '\0';
00699             if (!ast_goto_if_exists(chan, exitcontext, tmp, 1))
00700                goto exit;
00701             else
00702                ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
00703          }
00704       }
00705 
00706       res = ast_waitfordigit(chan, waitms);
00707       if (res < 0) {
00708          ast_clear_flag(chan, AST_FLAG_SPYING);
00709          break;
00710       }
00711       if (!ast_strlen_zero(exitcontext)) {
00712          char tmp[2];
00713          tmp[0] = res;
00714          tmp[1] = '\0';
00715          if (!ast_goto_if_exists(chan, exitcontext, tmp, 1))
00716             goto exit;
00717          else
00718             ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
00719       }
00720 
00721       /* reset for the next loop around, unless overridden later */
00722       waitms = 100;
00723       num_spyed_upon = 0;
00724 
00725       for (peer_chanspy_ds = next_channel(chan, prev, spec, exten, context, &chanspy_ds);
00726            peer_chanspy_ds;
00727           chanspy_ds_free(peer_chanspy_ds), prev = peer,
00728            peer_chanspy_ds = next_chanspy_ds ? next_chanspy_ds : 
00729             next_channel(chan, prev, spec, exten, context, &chanspy_ds), next_chanspy_ds = NULL) {
00730          int igrp = !mygroup;
00731          int ienf = !myenforced;
00732          char *s;
00733 
00734          peer = peer_chanspy_ds->chan;
00735 
00736          ast_mutex_unlock(&peer_chanspy_ds->lock);
00737 
00738          if (peer == prev) {
00739             ast_channel_unlock(peer);
00740             chanspy_ds_free(peer_chanspy_ds);
00741             break;
00742          }
00743 
00744          if (ast_check_hangup(chan)) {
00745             ast_channel_unlock(peer);
00746             chanspy_ds_free(peer_chanspy_ds);
00747             break;
00748          }
00749 
00750          if (ast_test_flag(flags, OPTION_BRIDGED) && !ast_bridged_channel(peer)) {
00751             ast_channel_unlock(peer);
00752             continue;
00753          }
00754 
00755          if (ast_check_hangup(peer) || ast_test_flag(peer, AST_FLAG_SPYING)) {
00756             ast_channel_unlock(peer);
00757             continue;
00758          }
00759 
00760          if (mygroup) {
00761             int num_groups = 0;
00762             int num_mygroups = 0;
00763             char dup_group[512];
00764             char dup_mygroup[512];
00765             char *groups[NUM_SPYGROUPS];
00766             char *mygroups[NUM_SPYGROUPS];
00767             const char *group;
00768             int x;
00769             int y;
00770             ast_copy_string(dup_mygroup, mygroup, sizeof(dup_mygroup));
00771             num_mygroups = ast_app_separate_args(dup_mygroup, ':', mygroups,
00772                ARRAY_LEN(mygroups));
00773 
00774             if ((group = pbx_builtin_getvar_helper(peer, "SPYGROUP"))) {
00775                ast_copy_string(dup_group, group, sizeof(dup_group));
00776                num_groups = ast_app_separate_args(dup_group, ':', groups,
00777                   ARRAY_LEN(groups));
00778             }
00779 
00780             for (y = 0; y < num_mygroups; y++) {
00781                for (x = 0; x < num_groups; x++) {
00782                   if (!strcmp(mygroups[y], groups[x])) {
00783                      igrp = 1;
00784                      break;
00785                   }
00786                }
00787             }
00788          }
00789 
00790          if (!igrp) {
00791             ast_channel_unlock(peer);
00792             continue;
00793          }
00794 
00795          if (myenforced) {
00796             char ext[AST_CHANNEL_NAME + 3];
00797             char buffer[512];
00798             char *end;
00799 
00800             snprintf(buffer, sizeof(buffer) - 1, ":%s:", myenforced);
00801 
00802             ast_copy_string(ext + 1, peer->name, sizeof(ext) - 1);
00803             if ((end = strchr(ext, '-'))) {
00804                *end++ = ':';
00805                *end = '\0';
00806             }
00807 
00808             ext[0] = ':';
00809 
00810             if (strcasestr(buffer, ext)) {
00811                ienf = 1;
00812             }
00813          }
00814 
00815          if (!ienf) {
00816             continue;
00817          }
00818 
00819          strcpy(peer_name, "spy-");
00820          strncat(peer_name, peer->name, AST_NAME_STRLEN - 4 - 1);
00821          ptr = strchr(peer_name, '/');
00822          *ptr++ = '\0';
00823          ptr = strsep(&ptr, "-");
00824 
00825          for (s = peer_name; s < ptr; s++)
00826             *s = tolower(*s);
00827          /* We have to unlock the peer channel here to avoid a deadlock.
00828           * So, when we need to dereference it again, we have to lock the 
00829           * datastore and get the pointer from there to see if the channel 
00830           * is still valid. */
00831          ast_channel_unlock(peer);
00832 
00833          if (!ast_test_flag(flags, OPTION_QUIET)) {
00834             if (ast_test_flag(flags, OPTION_NAME)) {
00835                const char *local_context = S_OR(name_context, "default");
00836                const char *local_mailbox = S_OR(mailbox, ptr);
00837                res = ast_app_sayname(chan, local_mailbox, local_context);
00838             }
00839             if (!ast_test_flag(flags, OPTION_NAME) || res < 0) {
00840                if (!ast_test_flag(flags, OPTION_NOTECH)) {
00841                   if (ast_fileexists(peer_name, NULL, NULL) != -1) {
00842                      res = ast_streamfile(chan, peer_name, chan->language);
00843                      if (!res) {
00844                         res = ast_waitstream(chan, "");
00845                      }
00846                      if (res) {
00847                         chanspy_ds_free(peer_chanspy_ds);
00848                         break;
00849                      }
00850                   } else {
00851                      res = ast_say_character_str(chan, peer_name, "", chan->language);
00852                   }
00853                }
00854                if ((num = atoi(ptr)))
00855                   ast_say_digits(chan, atoi(ptr), "", chan->language);
00856             }
00857          }
00858 
00859          res = channel_spy(chan, peer_chanspy_ds, &volfactor, fd, flags, exitcontext);
00860          num_spyed_upon++; 
00861 
00862          if (res == -1) {
00863             chanspy_ds_free(peer_chanspy_ds);
00864             goto exit;
00865          } else if (res == -2) {
00866             res = 0;
00867             chanspy_ds_free(peer_chanspy_ds);
00868             goto exit;
00869          } else if (res > 1 && spec) {
00870             struct ast_channel *next;
00871 
00872             snprintf(nameprefix, AST_NAME_STRLEN, "%s/%d", spec, res);
00873 
00874             if ((next = ast_get_channel_by_name_prefix_locked(nameprefix, strlen(nameprefix)))) {
00875                peer_chanspy_ds = chanspy_ds_free(peer_chanspy_ds);
00876                next_chanspy_ds = setup_chanspy_ds(next, &chanspy_ds);
00877             } else {
00878                /* stay on this channel, if it is still valid */
00879 
00880                ast_mutex_lock(&peer_chanspy_ds->lock);
00881                if (peer_chanspy_ds->chan) {
00882                   ast_channel_lock(peer_chanspy_ds->chan);
00883                   next_chanspy_ds = peer_chanspy_ds;
00884                   peer_chanspy_ds = NULL;
00885                } else {
00886                   /* the channel is gone */
00887                   ast_mutex_unlock(&peer_chanspy_ds->lock);
00888                   next_chanspy_ds = NULL;
00889                }
00890             }
00891 
00892             peer = NULL;
00893          }
00894       }
00895       if (res == -1 || ast_check_hangup(chan))
00896          break;
00897    }
00898 exit:
00899 
00900    ast_clear_flag(chan, AST_FLAG_SPYING);
00901 
00902    ast_channel_setoption(chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0);
00903 
00904    ast_mutex_lock(&chanspy_ds.lock);
00905    ast_mutex_unlock(&chanspy_ds.lock);
00906    ast_mutex_destroy(&chanspy_ds.lock);
00907 
00908    return res;
00909 }
00910 
00911 static int chanspy_exec(struct ast_channel *chan, void *data)
00912 {
00913    char *myenforced = NULL;
00914    char *mygroup = NULL;
00915    char *recbase = NULL;
00916    int fd = 0;
00917    struct ast_flags flags;
00918    int oldwf = 0;
00919    int volfactor = 0;
00920    int res;
00921    char *mailbox = NULL;
00922    char *name_context = NULL;
00923    AST_DECLARE_APP_ARGS(args,
00924       AST_APP_ARG(spec);
00925       AST_APP_ARG(options);
00926    );
00927    char *opts[OPT_ARG_ARRAY_SIZE];
00928 
00929    data = ast_strdupa(data);
00930    AST_STANDARD_APP_ARGS(args, data);
00931 
00932    if (args.spec && !strcmp(args.spec, "all"))
00933       args.spec = NULL;
00934 
00935    if (args.options) {
00936       ast_app_parse_options(spy_opts, &flags, opts, args.options);
00937       if (ast_test_flag(&flags, OPTION_GROUP))
00938          mygroup = opts[OPT_ARG_GROUP];
00939 
00940       if (ast_test_flag(&flags, OPTION_RECORD) &&
00941          !(recbase = opts[OPT_ARG_RECORD]))
00942          recbase = "chanspy";
00943 
00944       if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) {
00945          int vol;
00946 
00947          if ((sscanf(opts[OPT_ARG_VOLUME], "%30d", &vol) != 1) || (vol > 4) || (vol < -4))
00948             ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
00949          else
00950             volfactor = vol;
00951       }
00952 
00953       if (ast_test_flag(&flags, OPTION_PRIVATE))
00954          ast_set_flag(&flags, OPTION_WHISPER);
00955 
00956       if (ast_test_flag(&flags, OPTION_ENFORCED))
00957          myenforced = opts[OPT_ARG_ENFORCED];
00958       
00959       if (ast_test_flag(&flags, OPTION_NAME)) {
00960          if (!ast_strlen_zero(opts[OPT_ARG_NAME])) {
00961             char *delimiter;
00962             if ((delimiter = strchr(opts[OPT_ARG_NAME], '@'))) {
00963                mailbox = opts[OPT_ARG_NAME];
00964                *delimiter++ = '\0';
00965                name_context = delimiter;
00966             } else {
00967                mailbox = opts[OPT_ARG_NAME];
00968             }
00969          }
00970       }
00971 
00972 
00973    } else
00974       ast_clear_flag(&flags, AST_FLAGS_ALL);
00975 
00976    oldwf = chan->writeformat;
00977    if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
00978       ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
00979       return -1;
00980    }
00981 
00982    if (recbase) {
00983       char filename[PATH_MAX];
00984 
00985       snprintf(filename, sizeof(filename), "%s/%s.%d.raw", ast_config_AST_MONITOR_DIR, recbase, (int) time(NULL));
00986       if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, AST_FILE_MODE)) <= 0) {
00987          ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename);
00988          fd = 0;
00989       }
00990    }
00991 
00992    res = common_exec(chan, &flags, volfactor, fd, mygroup, myenforced, args.spec, NULL, NULL, mailbox, name_context);
00993 
00994    if (fd)
00995       close(fd);
00996 
00997    if (oldwf && ast_set_write_format(chan, oldwf) < 0)
00998       ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
00999 
01000    return res;
01001 }
01002 
01003 static int extenspy_exec(struct ast_channel *chan, void *data)
01004 {
01005    char *ptr, *exten = NULL;
01006    char *mygroup = NULL;
01007    char *recbase = NULL;
01008    int fd = 0;
01009    struct ast_flags flags;
01010    int oldwf = 0;
01011    int volfactor = 0;
01012    int res;
01013    char *mailbox = NULL;
01014    char *name_context = NULL;
01015    AST_DECLARE_APP_ARGS(args,
01016       AST_APP_ARG(context);
01017       AST_APP_ARG(options);
01018    );
01019 
01020    data = ast_strdupa(data);
01021 
01022    AST_STANDARD_APP_ARGS(args, data);
01023    if (!ast_strlen_zero(args.context) && (ptr = strchr(args.context, '@'))) {
01024       exten = args.context;
01025       *ptr++ = '\0';
01026       args.context = ptr;
01027    }
01028 
01029    if (ast_strlen_zero(args.context))
01030       args.context = ast_strdupa(chan->context);
01031 
01032    if (args.options) {
01033       char *opts[OPT_ARG_ARRAY_SIZE];
01034 
01035       ast_app_parse_options(spy_opts, &flags, opts, args.options);
01036       if (ast_test_flag(&flags, OPTION_GROUP))
01037          mygroup = opts[OPT_ARG_GROUP];
01038 
01039       if (ast_test_flag(&flags, OPTION_RECORD) &&
01040          !(recbase = opts[OPT_ARG_RECORD]))
01041          recbase = "chanspy";
01042 
01043       if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) {
01044          int vol;
01045 
01046          if ((sscanf(opts[OPT_ARG_VOLUME], "%30d", &vol) != 1) || (vol > 4) || (vol < -4))
01047             ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
01048          else
01049             volfactor = vol;
01050       }
01051 
01052       if (ast_test_flag(&flags, OPTION_PRIVATE))
01053          ast_set_flag(&flags, OPTION_WHISPER);
01054 
01055       
01056       if (ast_test_flag(&flags, OPTION_NAME)) {
01057          if (!ast_strlen_zero(opts[OPT_ARG_NAME])) {
01058             char *delimiter;
01059             if ((delimiter = strchr(opts[OPT_ARG_NAME], '@'))) {
01060                mailbox = opts[OPT_ARG_NAME];
01061                *delimiter++ = '\0';
01062                name_context = delimiter;
01063             } else {
01064                mailbox = opts[OPT_ARG_NAME];
01065             }
01066          }
01067       }
01068 
01069    } else
01070       ast_clear_flag(&flags, AST_FLAGS_ALL);
01071 
01072    oldwf = chan->writeformat;
01073    if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
01074       ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
01075       return -1;
01076    }
01077 
01078    if (recbase) {
01079       char filename[PATH_MAX];
01080 
01081       snprintf(filename, sizeof(filename), "%s/%s.%d.raw", ast_config_AST_MONITOR_DIR, recbase, (int) time(NULL));
01082       if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, AST_FILE_MODE)) <= 0) {
01083          ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename);
01084          fd = 0;
01085       }
01086    }
01087 
01088 
01089    res = common_exec(chan, &flags, volfactor, fd, mygroup, NULL, NULL, exten, args.context, mailbox, name_context);
01090 
01091    if (fd)
01092       close(fd);
01093 
01094    if (oldwf && ast_set_write_format(chan, oldwf) < 0)
01095       ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
01096 
01097    return res;
01098 }
01099 
01100 static int unload_module(void)
01101 {
01102    int res = 0;
01103 
01104    res |= ast_unregister_application(app_chan);
01105    res |= ast_unregister_application(app_ext);
01106 
01107    return res;
01108 }
01109 
01110 static int load_module(void)
01111 {
01112    int res = 0;
01113 
01114    res |= ast_register_application(app_chan, chanspy_exec, tdesc, desc_chan);
01115    res |= ast_register_application(app_ext, extenspy_exec, tdesc, desc_ext);
01116 
01117    return res;
01118 }
01119 
01120 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Listen to the audio of an active channel");

Generated on Wed Aug 18 22:33:40 2010 for Asterisk - the Open Source PBX by  doxygen 1.4.7