Asterisk.org: Developer Documentation (28 Oct 2009)

Wed Oct 28 15:47:48 2009

## Asterisk developer's documentation

# app_meetme.c

Go to the documentation of this file.

    00001 /*
    00002  * Asterisk -- An open source telephony toolkit.
    00003  *
    00004  * Copyright (C) 1999 - 2006, Digium, Inc.
    00005  *
    00006  * Mark Spencer <markster@digium.com>
    00007  *
    00008  * See http://www.asterisk.org for more information about
    00009  * the Asterisk project. Please do not directly contact
    00010  * any of the maintainers of this project for assistance;
    00011  * the project provides a web site, mailing lists and IRC
    00012  * channels for your use.
    00013  *
    00014  * This program is free software, distributed under the terms of
    00015  * the GNU General Public License Version 2. See the LICENSE file
    00016  * at the top of the source tree.
    00017  */
    00018
    00019 /*! \file
    00020  *
    00021  * \brief Meet me conference bridge
    00022  *
    00023  * \ingroup applications
    00024  */
    00025
    00026 #include <stdlib.h>
    00027 #include <stdio.h>
    00028 #include <string.h>
    00029 #include <unistd.h>
    00030 #include <errno.h>
    00031 #include <sys/ioctl.h>
    00032 #ifdef __linux__
    00033 #include <linux/zaptel.h>
    00034 #else
    00035 #include <zaptel.h>
    00036 #endif /* __linux__ */
    00037
    00038 #include "asterisk.h"
    00039
    00040 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 211526 $")
    00041
    00042 #include "asterisk/lock.h"
    00043 #include "asterisk/file.h"
    00044 #include "asterisk/logger.h"
    00045 #include "asterisk/channel.h"
    00046 #include "asterisk/pbx.h"
    00047 #include "asterisk/module.h"
    00048 #include "asterisk/config.h"
    00049 #include "asterisk/app.h"
    00050 #include "asterisk/dsp.h"
    00051 #include "asterisk/musiconhold.h"
    00052 #include "asterisk/manager.h"
    00053 #include "asterisk/options.h"
    00054 #include "asterisk/cli.h"
    00055 #include "asterisk/say.h"
    00056 #include "asterisk/utils.h"
    00057 #include "asterisk/linkedlists.h"
    00058
    00059 static const char *tdesc = "MeetMe conference bridge";
    00060
    00061 static const char *app = "MeetMe";
    00062 static const char *app2 = "MeetMeCount";
    00063 static const char *app3 = "MeetMeAdmin";
    00064
    00065 static const char *synopsis = "MeetMe conference bridge";
    00066 static const char *synopsis2 = "MeetMe participant count";
    00067 static const char *synopsis3 = "MeetMe conference Administration";
    00068
    00069 static const char *descrip =
    00070 "  MeetMe([confno][,[options][,pin]]): Enters the user into a specified MeetMe conference.\n"
    00071 "If the conference number is omitted, the user will be prompted to enter\n"
    00072 "one. \n"
    00073 "User can exit the conference by hangup, or if the 'p' option is specified, by pressing '#'.\n"
    00074 "Please note: The Zaptel kernel modules and at least one hardware driver (or ztdummy)\n"
    00075 "             must be present for conferencing to operate properly. In addition, the chan_zap\n"
    00076 "             channel driver must be loaded for the 'i' and 'r' options to operate at all.\n\n"
    00077 "The option string may contain zero or more of the following characters:\n"
    00078 "      'a' -- set admin mode\n"
    00079 "      'A' -- set marked mode\n"
    00080 "      'b' -- run AGI script specified in ${MEETME_AGI_BACKGROUND}\n"
    00081 "             Default: conf-background.agi\n"
    00082 "             (Note: This does not work with non-Zap channels in the same conference)\n"
    00083 "      'c' -- announce user(s) count on joining a conference\n"
    00084 "      'd' -- dynamically add conference\n"
    00085 "      'D' -- dynamically add conference, prompting for a PIN\n"
    00086 "      'e' -- select an empty conference\n"
    00087 "      'E' -- select an empty pinless conference\n"
    00088 "      'i' -- announce user join/leave\n"
    00089 "      'm' -- set monitor only mode (Listen only, no talking)\n"
    00090 "      'M' -- enable music on hold when the conference has a single caller\n"
    00091 "      'p' -- allow user to exit the conference by pressing '#'\n"
    00092 "      'P' -- always prompt for the pin even if it is specified\n"
    00093 "      'q' -- quiet mode (don't play enter/leave sounds)\n"
    00094 "      'r' -- Record conference (records as ${MEETME_RECORDINGFILE}\n"
    00095 "             using format ${MEETME_RECORDINGFORMAT}). Default filename is\n"
    00096 "             meetme-conf-rec-${CONFNO}-${UNIQUEID} and the default format is wav.\n"
    00097 "      's' -- Present menu (user or admin) when '*' is received ('send' to menu)\n"
    00098 "      't' -- set talk only mode. (Talk only, no listening)\n"
    00099 "      'T' -- set talker detection (sent to manager interface and meetme list)\n"
    00100 "      'w[(<secs>)]'\n"
    00101 "          -- wait until the marked user enters the conference\n"
    00102 "      'x' -- close the conference when last marked user exits\n"
    00103 "      'X' -- allow user to exit the conference by entering a valid single\n"
    00104 "             digit extension ${MEETME_EXIT_CONTEXT} or the current context\n"
    00105 "             if that variable is not defined.\n";
    00106
    00107 static const char *descrip2 =
    00108 "  MeetMeCount(confno[|var]): Plays back the number of users in the specified\n"
    00109 "MeetMe conference. If var is specified, playback will be skipped and the value\n"
    00110 "will be returned in the variable. Upon app completion, MeetMeCount will hangup the\n"
    00111 "channel, unless priority n+1 exists, in which case priority progress will continue.\n"
    00112 "A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING FUNCTIONALITY.\n";
    00113
    00114 static const char *descrip3 =
    00115 "  MeetMeAdmin(confno,command[,user]): Run admin command for conference\n"
    00116 "      'e' -- Eject last user that joined\n"
    00117 "      'k' -- Kick one user out of conference\n"
    00118 "      'K' -- Kick all users out of conference\n"
    00119 "      'l' -- Unlock conference\n"
    00120 "      'L' -- Lock conference\n"
    00121 "      'm' -- Unmute conference\n"
    00122 "      'M' -- Mute conference\n"
    00123 "      'n' -- Unmute entire conference (except admin)\n"
    00124 "      'N' -- Mute entire conference (except admin)\n"
    00125 "";
    00126
    00127 #define CONFIG_FILE_NAME "meetme.conf"
    00128
    00129 STANDARD_LOCAL_USER;
    00130
    00131 LOCAL_USER_DECL;
    00132
    00133 struct ast_conference {
    00134    char confno[AST_MAX_EXTENSION];     /* Conference */
    00135    struct ast_channel *chan;     /* Announcements channel */
    00136    int fd;              /* Announcements fd */
    00137    int zapconf;            /* Zaptel Conf # */
    00138    int users;           /* Number of active users */
    00139    int markedusers;        /* Number of marked users */
    00140    AST_LIST_HEAD_NOLOCK(, ast_conf_user) userlist;
    00141    time_t start;           /* Start time (s) */
    00142    int refcount;
    00143    int recording;          /* recording status */
    00144    int isdynamic;          /* Created on the fly? */
    00145    int locked;          /* Is the conference locked? */
    00146    pthread_t recordthread;       /* thread for recording */
    00147    pthread_attr_t attr;       /* thread attribute */
    00148    char *recordingfilename;      /* Filename to record the Conference into */
    00149    char *recordingformat;        /* Format to record the Conference in */
    00150    char pin[AST_MAX_EXTENSION];     /* If protected by a PIN */
    00151    char pinadmin[AST_MAX_EXTENSION];   /* If protected by a admin PIN */
    00152    AST_LIST_ENTRY(ast_conference) list;
    00153 };
    00154
    00155 static AST_LIST_HEAD_STATIC(confs, ast_conference);
    00156
    00157 static unsigned int conf_map[1024] = {0, };
    00158
    00159 struct volume {
    00160    int desired;            /* Desired volume adjustment */
    00161    int actual;          /* Actual volume adjustment (for channels that can't adjust) */
    00162 };
    00163
    00164 struct ast_conf_user {
    00165    int user_no;            /* User Number */
    00166    AST_LIST_ENTRY(ast_conf_user) list;
    00167    int userflags;          /* Flags as set in the conference */
    00168    int adminflags;            /* Flags set by the Admin */
    00169    struct ast_channel *chan;     /* Connected channel */
    00170    int talking;            /* Is user talking */
    00171    int zapchannel;            /* Is a Zaptel channel */
    00172    char usrvalue[50];         /* Custom User Value */
    00173    char namerecloc[PATH_MAX]; /* Name Recorded file Location */
    00174    time_t jointime;        /* Time the user joined the conference */
    00175    struct volume talk;
    00176    struct volume listen;
    00177 };
    00178
    00179 static int audio_buffers;        /* The number of audio buffers to be allocated on pseudo channels
    00180                      when in a conference
    00181                   */
    00182
    00183 #define DEFAULT_AUDIO_BUFFERS 32    /* each buffer is 20ms, so this is 640ms total */
    00184
    00185 #define ADMINFLAG_MUTED (1 << 1)    /* User is muted */
    00186 #define ADMINFLAG_KICKME (1 << 2)      /* User is kicked */
    00187 #define MEETME_DELAYDETECTTALK      300
    00188 #define MEETME_DELAYDETECTENDTALK   1000
    00189
    00190 enum volume_action {
    00191    VOL_UP,
    00192    VOL_DOWN,
    00193 };
    00194
    00195 static int admin_exec(struct ast_channel *chan, void *data);
    00196
    00197 static void *recordthread(void *args);
    00198
    00199 #include "enter.h"
    00200 #include "leave.h"
    00201
    00202 #define ENTER  0
    00203 #define LEAVE  1
    00204
    00205 #define MEETME_RECORD_OFF  0
    00206 #define MEETME_RECORD_ACTIVE  1
    00207 #define MEETME_RECORD_TERMINATE  2
    00208
    00209 #define CONF_SIZE 320
    00210
    00211 #define CONFFLAG_ADMIN  (1 << 1)    /* If set the user has admin access on the conference */
    00212 #define CONFFLAG_MONITOR (1 << 2)      /* If set the user can only receive audio from the conference */
    00213 #define CONFFLAG_POUNDEXIT (1 << 3)    /* If set asterisk will exit conference when '#' is pressed */
    00214 #define CONFFLAG_STARMENU (1 << 4)     /* If set asterisk will provide a menu to the user when '*' is pressed */
    00215 #define CONFFLAG_TALKER (1 << 5)    /* If set the use can only send audio to the conference */
    00216 #define CONFFLAG_QUIET (1 << 6)        /* If set there will be no enter or leave sounds */
    00217 #define CONFFLAG_ANNOUNCEUSERCOUNT (1 << 7)  /* If set, when user joins the conference, they will be told the number of users that are already in */
    00218 #define CONFFLAG_AGI (1 << 8)       /* Set to run AGI Script in Background */
    00219 #define CONFFLAG_MOH (1 << 9)       /* Set to have music on hold when user is alone in conference */
    00220 #define CONFFLAG_MARKEDEXIT (1 << 10)     /* If set the MeetMe will return if all marked with this flag left */
    00221 #define CONFFLAG_WAITMARKED (1 << 11)     /* If set, the MeetMe will wait until a marked user enters */
    00222 #define CONFFLAG_EXIT_CONTEXT (1 << 12)      /* If set, the MeetMe will exit to the specified context */
    00223 #define CONFFLAG_MARKEDUSER (1 << 13)     /* If set, the user will be marked */
    00224 #define CONFFLAG_INTROUSER (1 << 14)      /* If set, user will be ask record name on entry of conference */
    00225 #define CONFFLAG_RECORDCONF (1<< 15)      /* If set, the MeetMe will be recorded */
    00226 #define CONFFLAG_MONITORTALKER (1 << 16)  /* If set, the user will be monitored if the user is talking or not */
    00227 #define CONFFLAG_DYNAMIC (1 << 17)
    00228 #define CONFFLAG_DYNAMICPIN (1 << 18)
    00229 #define CONFFLAG_EMPTY (1 << 19)
    00230 #define CONFFLAG_EMPTYNOPIN (1 << 20)
    00231 #define CONFFLAG_ALWAYSPROMPT (1 << 21)
    00232
    00233 enum {
    00234    OPT_ARG_WAITMARKED = 0,
    00235    OPT_ARG_ARRAY_SIZE = 1,
    00236 } meetme_option_args;
    00237
    00238 AST_APP_OPTIONS(meetme_opts, {
    00239    AST_APP_OPTION('a', CONFFLAG_ADMIN ),
    00240    AST_APP_OPTION('c', CONFFLAG_ANNOUNCEUSERCOUNT ),
    00241    AST_APP_OPTION('T', CONFFLAG_MONITORTALKER ),
    00242    AST_APP_OPTION('i', CONFFLAG_INTROUSER ),
    00243    AST_APP_OPTION('m', CONFFLAG_MONITOR ),
    00244    AST_APP_OPTION('p', CONFFLAG_POUNDEXIT ),
    00245    AST_APP_OPTION('s', CONFFLAG_STARMENU ),
    00246    AST_APP_OPTION('t', CONFFLAG_TALKER ),
    00247    AST_APP_OPTION('q', CONFFLAG_QUIET ),
    00248    AST_APP_OPTION('M', CONFFLAG_MOH ),
    00249    AST_APP_OPTION('x', CONFFLAG_MARKEDEXIT ),
    00250    AST_APP_OPTION('X', CONFFLAG_EXIT_CONTEXT ),
    00251    AST_APP_OPTION('A', CONFFLAG_MARKEDUSER ),
    00252    AST_APP_OPTION('b', CONFFLAG_AGI ),
    00253    AST_APP_OPTION_ARG('w', CONFFLAG_WAITMARKED, OPT_ARG_WAITMARKED ),
    00254    AST_APP_OPTION('r', CONFFLAG_RECORDCONF ),
    00255    AST_APP_OPTION('d', CONFFLAG_DYNAMIC ),
    00256    AST_APP_OPTION('D', CONFFLAG_DYNAMICPIN ),
    00257    AST_APP_OPTION('e', CONFFLAG_EMPTY ),
    00258    AST_APP_OPTION('E', CONFFLAG_EMPTYNOPIN ),
    00259    AST_APP_OPTION('P', CONFFLAG_ALWAYSPROMPT ),
    00260 });
    00261
    00262 static char *istalking(int x)
    00263 {
    00264    if (x > 0)
    00265       return "(talking)";
    00266    else if (x < 0)
    00267       return "(unmonitored)";
    00268    else
    00269       return "(not talking)";
    00270 }
    00271
    00272 static int careful_write(int fd, unsigned char *data, int len, int block)
    00273 {
    00274    int res;
    00275    int x;
    00276
    00277    while (len) {
    00278       if (block) {
    00279          x = ZT_IOMUX_WRITE | ZT_IOMUX_SIGEVENT;
    00280          res = ioctl(fd, ZT_IOMUX, &x);
    00281       } else
    00282          res = 0;
    00283       if (res >= 0)
    00284          res = write(fd, data, len);
    00285       if (res < 1) {
    00286          if (errno != EAGAIN) {
    00287             ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
    00288             return -1;
    00289          } else
    00290             return 0;
    00291       }
    00292       len -= res;
    00293       data += res;
    00294    }
    00295
    00296    return 0;
    00297 }
    00298
    00299 /* Map 'volume' levels from -5 through +5 into
    00300    decibel (dB) settings for channel drivers
    00301    Note: these are not a straight linear-to-dB
    00302    conversion... the numbers have been modified
    00303    to give the user a better level of adjustability
    00304 */
    00305 static signed char gain_map[] = {
    00306    -15,
    00307    -13,
    00308    -10,
    00309    -6,
    00310    0,
    00311    0,
    00312    0,
    00313    6,
    00314    10,
    00315    13,
    00316    15,
    00317 };
    00318
    00319 static int set_talk_volume(struct ast_conf_user *user, int volume)
    00320 {
    00321    signed char gain_adjust;
    00322
    00323    /* attempt to make the adjustment in the channel driver;
    00324       if successful, don't adjust in the frame reading routine
    00325    */
    00326    gain_adjust = gain_map[volume + 5];
    00327
    00328    return ast_channel_setoption(user->chan, AST_OPTION_RXGAIN, &gain_adjust, sizeof(gain_adjust), 0);
    00329 }
    00330
    00331 static int set_listen_volume(struct ast_conf_user *user, int volume)
    00332 {
    00333    signed char gain_adjust;
    00334
    00335    /* attempt to make the adjustment in the channel driver;
    00336       if successful, don't adjust in the frame reading routine
    00337    */
    00338    gain_adjust = gain_map[volume + 5];
    00339
    00340    return ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &gain_adjust, sizeof(gain_adjust), 0);
    00341 }
    00342
    00343 static void tweak_volume(struct volume *vol, enum volume_action action)
    00344 {
    00345    switch (action) {
    00346    case VOL_UP:
    00347       switch (vol->desired) {
    00348       case 5:
    00349          break;
    00350       case 0:
    00351          vol->desired = 2;
    00352          break;
    00353       case -2:
    00354          vol->desired = 0;
    00355          break;
    00356       default:
    00357          vol->desired++;
    00358          break;
    00359       }
    00360       break;
    00361    case VOL_DOWN:
    00362       switch (vol->desired) {
    00363       case -5:
    00364          break;
    00365       case 2:
    00366          vol->desired = 0;
    00367          break;
    00368       case 0:
    00369          vol->desired = -2;
    00370          break;
    00371       default:
    00372          vol->desired--;
    00373          break;
    00374       }
    00375    }
    00376 }
    00377
    00378 static void tweak_talk_volume(struct ast_conf_user *user, enum volume_action action)
    00379 {
    00380    tweak_volume(&user->talk, action);
    00381    /* attempt to make the adjustment in the channel driver;
    00382       if successful, don't adjust in the frame reading routine
    00383    */
    00384    if (!set_talk_volume(user, user->talk.desired))
    00385       user->talk.actual = 0;
    00386    else
    00387       user->talk.actual = user->talk.desired;
    00388 }
    00389
    00390 static void tweak_listen_volume(struct ast_conf_user *user, enum volume_action action)
    00391 {
    00392    tweak_volume(&user->listen, action);
    00393    /* attempt to make the adjustment in the channel driver;
    00394       if successful, don't adjust in the frame reading routine
    00395    */
    00396    if (!set_listen_volume(user, user->listen.desired))
    00397       user->listen.actual = 0;
    00398    else
    00399       user->listen.actual = user->listen.desired;
    00400 }
    00401
    00402 static void reset_volumes(struct ast_conf_user *user)
    00403 {
    00404    signed char zero_volume = 0;
    00405
    00406    ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0);
    00407    ast_channel_setoption(user->chan, AST_OPTION_RXGAIN, &zero_volume, sizeof(zero_volume), 0);
    00408 }
    00409
    00410 static void conf_play(struct ast_channel *chan, struct ast_conference *conf, int sound)
    00411 {
    00412    unsigned char *data;
    00413    int len;
    00414    int res = -1;
    00415
    00416    if (!chan->_softhangup)
    00417       res = ast_autoservice_start(chan);
    00418
    00419    AST_LIST_LOCK(&confs);
    00420
    00421    switch(sound) {
    00422    case ENTER:
    00423       data = enter;
    00424       len = sizeof(enter);
    00425       break;
    00426    case LEAVE:
    00427       data = leave;
    00428       len = sizeof(leave);
    00429       break;
    00430    default:
    00431       data = NULL;
    00432       len = 0;
    00433    }
    00434    if (data)
    00435       careful_write(conf->fd, data, len, 1);
    00436
    00437    AST_LIST_UNLOCK(&confs);
    00438
    00439    if (!res)
    00440       ast_autoservice_stop(chan);
    00441 }
    00442
    00443 static struct ast_conference *build_conf(char *confno, char *pin, char *pinadmin, int make, int dynamic, int refcount)
    00444 {
    00445    struct ast_conference *cnf;
    00446    struct zt_confinfo ztc;
    00447    int confno_int = 0;
    00448
    00449    AST_LIST_LOCK(&confs);
    00450    AST_LIST_TRAVERSE(&confs, cnf, list) {
    00451       if (!strcmp(confno, cnf->confno))
    00452          break;
    00453    }
    00454
    00455    if (!cnf && (make || dynamic)) {
    00456       /* Make a new one */
    00457       cnf = calloc(1, sizeof(*cnf));
    00458       if (cnf) {
    00459          ast_copy_string(cnf->confno, confno, sizeof(cnf->confno));
    00460          ast_copy_string(cnf->pin, pin, sizeof(cnf->pin));
    00461          ast_copy_string(cnf->pinadmin, pinadmin, sizeof(cnf->pinadmin));
    00462          cnf->markedusers = 0;
    00463          cnf->chan = ast_request("zap", AST_FORMAT_ULAW, "pseudo", NULL);
    00464          if (cnf->chan) {
    00465             cnf->fd = cnf->chan->fds[0];  /* for use by conf_play() */
    00466          } else {
    00467             ast_log(LOG_WARNING, "Unable to open pseudo channel - trying device\n");
    00468             cnf->fd = open("/dev/zap/pseudo", O_RDWR);
    00469             if (cnf->fd < 0) {
    00470                ast_log(LOG_WARNING, "Unable to open pseudo device\n");
    00471                free(cnf);
    00472                cnf = NULL;
    00473                goto cnfout;
    00474             }
    00475          }
    00476          memset(&ztc, 0, sizeof(ztc));
    00477          /* Setup a new zap conference */
    00478          ztc.chan = 0;
    00479          ztc.confno = -1;
    00480          ztc.confmode = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON;
    00481          if (ioctl(cnf->fd, ZT_SETCONF, &ztc)) {
    00482             ast_log(LOG_WARNING, "Error setting conference\n");
    00483             if (cnf->chan)
    00484                ast_hangup(cnf->chan);
    00485             else
    00486                close(cnf->fd);
    00487             free(cnf);
    00488             cnf = NULL;
    00489             goto cnfout;
    00490          }
    00491          /* Fill the conference struct */
    00492          cnf->start = time(NULL);
    00493          cnf->zapconf = ztc.confno;
    00494          cnf->isdynamic = dynamic;
    00495          cnf->locked = 0;
    00496          if (option_verbose > 2)
    00497             ast_verbose(VERBOSE_PREFIX_3 "Created MeetMe conference %d for conference '%s'\n", cnf->zapconf, cnf->confno);
    00498          AST_LIST_INSERT_HEAD(&confs, cnf, list);
    00499          /* Reserve conference number in map */
    00500          if ((sscanf(cnf->confno, "%30d", &confno_int) == 1) && (confno_int >= 0 && confno_int < 1024))
    00501             conf_map[confno_int] = 1;
    00502       } else
    00503          ast_log(LOG_WARNING, "Out of memory\n");
    00504    }
    00505  cnfout:
    00506    if (cnf)
    00507       ast_atomic_fetchadd_int(&cnf->refcount, refcount);
    00508    AST_LIST_UNLOCK(&confs);
    00509    return cnf;
    00510 }
    00511
    00512 static int confs_show(int fd, int argc, char **argv)
    00513 {
    00514    ast_cli(fd, "Deprecated! Please use 'meetme' instead.\n");
    00515
    00516    return RESULT_SUCCESS;
    00517 }
    00518
    00519 static char show_confs_usage[] =
    00520 "Deprecated! Please use 'meetme' instead.\n";
    00521
    00522 static struct ast_cli_entry cli_show_confs = {
    00523    { "show", "conferences", NULL }, confs_show,
    00524    "Show status of conferences", show_confs_usage, NULL };
    00525
    00526 static int conf_cmd(int fd, int argc, char **argv) {
    00527    /* Process the command */
    00528    struct ast_conference *cnf;
    00529    struct ast_conf_user *user;
    00530    int hr, min, sec;
    00531    int i = 0, total = 0;
    00532    time_t now;
    00533    char *header_format = "%-14s %-14s %-10s %-8s  %-8s\n";
    00534    char *data_format = "%-12.12s   %4.4d        %4.4s       %02d:%02d:%02d  %-8s\n";
    00535    char cmdline[1024] = "";
    00536
    00537    if (argc > 8)
    00538       ast_cli(fd, "Invalid Arguments.\n");
    00539    /* Check for length so no buffer will overflow... */
    00540    for (i = 0; i < argc; i++) {
    00541       if (strlen(argv[i]) > 100)
    00542          ast_cli(fd, "Invalid Arguments.\n");
    00543    }
    00544    if (argc == 1) {
    00545       /* 'MeetMe': List all the conferences */
    00546       now = time(NULL);
    00547       AST_LIST_LOCK(&confs);
    00548       if (!AST_LIST_FIRST(&confs)) {
    00549          ast_cli(fd, "No active MeetMe conferences.\n");
    00550          AST_LIST_UNLOCK(&confs);
    00551          return RESULT_SUCCESS;
    00552       }
    00553       ast_cli(fd, header_format, "Conf Num", "Parties", "Marked", "Activity", "Creation");
    00554       AST_LIST_TRAVERSE(&confs, cnf, list) {
    00555          if (cnf->markedusers == 0)
    00556             strcpy(cmdline, "N/A ");
    00557          else
    00558             snprintf(cmdline, sizeof(cmdline), "%4.4d", cnf->markedusers);
    00559          hr = (now - cnf->start) / 3600;
    00560          min = ((now - cnf->start) % 3600) / 60;
    00561          sec = (now - cnf->start) % 60;
    00562
    00563          ast_cli(fd, data_format, cnf->confno, cnf->users, cmdline, hr, min, sec, cnf->isdynamic ? "Dynamic" : "Static");
    00564
    00565          total += cnf->users;
    00566       }
    00567       ast_cli(fd, "* Total number of MeetMe users: %d\n", total);
    00568       AST_LIST_UNLOCK(&confs);
    00569       return RESULT_SUCCESS;
    00570    }
    00571    if (argc < 3)
    00572       return RESULT_SHOWUSAGE;
    00573    ast_copy_string(cmdline, argv[2], sizeof(cmdline));   /* Argv 2: conference number */
    00574    if (strstr(argv[1], "lock")) {
    00575       if (strcmp(argv[1], "lock") == 0) {
    00576          /* Lock */
    00577          strncat(cmdline, "|L", sizeof(cmdline) - strlen(cmdline) - 1);
    00578       } else {
    00579          /* Unlock */
    00580          strncat(cmdline, "|l", sizeof(cmdline) - strlen(cmdline) - 1);
    00581       }
    00582    } else if (strstr(argv[1], "mute")) {
    00583       if (argc < 4)
    00584          return RESULT_SHOWUSAGE;
    00585       if (strcmp(argv[1], "mute") == 0) {
    00586          /* Mute */
    00587          if (strcmp(argv[3], "all") == 0) {
    00588             strncat(cmdline, "|N", sizeof(cmdline) - strlen(cmdline) - 1);
    00589          } else {
    00590             strncat(cmdline, "|M|", sizeof(cmdline) - strlen(cmdline) - 1);
    00591             strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
    00592          }
    00593       } else {
    00594          /* Unmute */
    00595          if (strcmp(argv[3], "all") == 0) {
    00596             strncat(cmdline, "|n", sizeof(cmdline) - strlen(cmdline) - 1);
    00597          } else {
    00598             strncat(cmdline, "|m|", sizeof(cmdline) - strlen(cmdline) - 1);
    00599             strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
    00600          }
    00601       }
    00602    } else if (strcmp(argv[1], "kick") == 0) {
    00603       if (argc < 4)
    00604          return RESULT_SHOWUSAGE;
    00605       if (strcmp(argv[3], "all") == 0) {
    00606          /* Kick all */
    00607          strncat(cmdline, "|K", sizeof(cmdline) - strlen(cmdline) - 1);
    00608       } else {
    00609          /* Kick a single user */
    00610          strncat(cmdline, "|k|", sizeof(cmdline) - strlen(cmdline) - 1);
    00611          strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
    00612       }
    00613    } else if(strcmp(argv[1], "list") == 0) {
    00614       /* List all the users in a conference */
    00615       if (!AST_LIST_FIRST(&confs)) {
    00616          ast_cli(fd, "No active conferences.\n");
    00617          return RESULT_SUCCESS;
    00618       }
    00619       AST_LIST_LOCK(&confs);
    00620       AST_LIST_TRAVERSE(&confs, cnf, list) {
    00621          if (strcmp(cnf->confno, argv[2]) == 0)
    00622             break;
    00623       }
    00624       if (!cnf) {
    00625          ast_cli(fd, "No such conference: %s.\n", argv[2]);
    00626          AST_LIST_UNLOCK(&confs);
    00627          return RESULT_SUCCESS;
    00628       }
    00629       /* Show all the users */
    00630       AST_LIST_TRAVERSE(&cnf->userlist, user, list) {
    00631          ast_cli(fd, "User #: %-2.2d %12.12s %-20.20s Channel: %s %s %s %s %s\n",
    00632             user->user_no,
    00633             user->chan->cid.cid_num ? user->chan->cid.cid_num : "<unknown>",
    00634             user->chan->cid.cid_name ? user->chan->cid.cid_name : "<no name>",
    00635             user->chan->name,
    00636             user->userflags & CONFFLAG_ADMIN ? "(Admin)" : "",
    00637             user->userflags & CONFFLAG_MONITOR ? "(Listen only)" : "",
    00638             user->adminflags & ADMINFLAG_MUTED ? "(Admin Muted)" : "",
    00639             istalking(user->talking));
    00640       }
    00641       ast_cli(fd,"%d users in that conference.\n",cnf->users);
    00642       AST_LIST_UNLOCK(&confs);
    00643
    00644       return RESULT_SUCCESS;
    00645    } else
    00646       return RESULT_SHOWUSAGE;
    00647    ast_log(LOG_DEBUG, "Cmdline: %s\n", cmdline);
    00648    admin_exec(NULL, cmdline);
    00649
    00650    return 0;
    00651 }
    00652
    00653 static char *complete_confcmd(char *line, char *word, int pos, int state) {
    00654 #define CONF_COMMANDS 6
    00655    int which = 0, x = 0;
    00656    struct ast_conference *cnf = NULL;
    00657    struct ast_conf_user *usr = NULL;
    00658    char *confno = NULL;
    00659    char usrno[50] = "";
    00660    char cmds[CONF_COMMANDS][20] = {"lock", "unlock", "mute", "unmute", "kick", "list"};
    00661    char *myline;
    00662    char *res = NULL;
    00663
    00664    if (pos == 1) {
    00665       /* Command */
    00666       for (x = 0;x < CONF_COMMANDS; x++) {
    00667          if (!strncasecmp(cmds[x], word, strlen(word))) {
    00668             if (++which > state) {
    00669                return strdup(cmds[x]);
    00670             }
    00671          }
    00672       }
    00673    } else if (pos == 2) {
    00674       /* Conference Number */
    00675       AST_LIST_LOCK(&confs);
    00676       AST_LIST_TRAVERSE(&confs, cnf, list) {
    00677          if (!strncasecmp(word, cnf->confno, strlen(word))) {
    00678             if (++which > state)
    00679                break;
    00680          }
    00681       }
    00682       res = cnf ? strdup(cnf->confno) : NULL;
    00683       AST_LIST_UNLOCK(&confs);
    00684       return res;
    00685    } else if (pos == 3) {
    00686       /* User Number || Conf Command option*/
    00687       if (strstr(line, "mute") || strstr(line, "kick")) {
    00688          if ((state == 0) && (strstr(line, "kick") || strstr(line,"mute")) && !(strncasecmp(word, "all", strlen(word)))) {
    00689             return strdup("all");
    00690          }
    00691          which++;
    00692          AST_LIST_LOCK(&confs);
    00693
    00694          /* TODO: Find the conf number from the cmdline (ignore spaces) <- test this and make it fail-safe! */
    00695          myline = ast_strdupa(line);
    00696          if (strsep(&myline, " ") && strsep(&myline, " ") && !confno) {
    00697             while((confno = strsep(&myline, " ")) && (strcmp(confno, " ") == 0))
    00698                ;
    00699          }
    00700
    00701          AST_LIST_TRAVERSE(&confs, cnf, list) {
    00702             if (!strcmp(confno, cnf->confno))
    00703                 break;
    00704          }
    00705
    00706          if (cnf) {
    00707             /* Search for the user */
    00708             AST_LIST_TRAVERSE(&cnf->userlist, usr, list) {
    00709                snprintf(usrno, sizeof(usrno), "%d", usr->user_no);
    00710                if (!strncasecmp(word, usrno, strlen(word))) {
    00711                   if (++which > state)
    00712                      break;
    00713                }
    00714             }
    00715          }
    00716          AST_LIST_UNLOCK(&confs);
    00717          return usr ? strdup(usrno) : NULL;
    00718       }
    00719    }
    00720
    00721    return NULL;
    00722 }
    00723
    00724 static char conf_usage[] =
    00725 "Usage: meetme  (un)lock|(un)mute|kick|list <confno> <usernumber>\n"
    00726 "       Executes a command for the conference or on a conferee\n";
    00727
    00728 static struct ast_cli_entry cli_conf = {
    00729    {"meetme", NULL, NULL }, conf_cmd,
    00730    "Execute a command on a conference or conferee", conf_usage, complete_confcmd};
    00731
    00732 static void conf_flush(int fd, struct ast_channel *chan)
    00733 {
    00734    int x;
    00735
    00736    /* read any frames that may be waiting on the channel
    00737       and throw them away
    00738    */
    00739    if (chan) {
    00740       struct ast_frame *f;
    00741
    00742       /* when no frames are available, this will wait
    00743          for 1 millisecond maximum
    00744       */
    00745       while (ast_waitfor(chan, 1)) {
    00746          f = ast_read(chan);
    00747          if (f)
    00748             ast_frfree(f);
    00749          else /* channel was hung up or something else happened */
    00750             break;
    00751       }
    00752    }
    00753
    00754    /* flush any data sitting in the pseudo channel */
    00755    x = ZT_FLUSH_ALL;
    00756    if (ioctl(fd, ZT_FLUSH, &x))
    00757       ast_log(LOG_WARNING, "Error flushing channel\n");
    00758
    00759 }
    00760
    00761 /* Remove the conference from the list and free it.
    00762    XXX We assume that this was called while holding the confs list lock. */
    00763 static int conf_free(struct ast_conference *conf)
    00764 {
    00765    struct ast_conference *cur;
    00766
    00767    AST_LIST_TRAVERSE_SAFE_BEGIN(&confs, cur, list) {
    00768       if (cur == conf) {
    00769          AST_LIST_REMOVE_CURRENT(&confs, list);
    00770          break;
    00771       }
    00772    }
    00773    AST_LIST_TRAVERSE_SAFE_END;
    00774
    00775    if (!cur)
    00776       ast_log(LOG_WARNING, "Conference not found\n");
    00777
    00778    if (conf->recording == MEETME_RECORD_ACTIVE) {
    00779       conf->recording = MEETME_RECORD_TERMINATE;
    00780       AST_LIST_UNLOCK(&confs);
    00781       while (1) {
    00782          usleep(1);
    00783          AST_LIST_LOCK(&confs);
    00784          if (conf->recording == MEETME_RECORD_OFF)
    00785             break;
    00786          AST_LIST_UNLOCK(&confs);
    00787       }
    00788    }
    00789
    00790    if (conf->chan)
    00791       ast_hangup(conf->chan);
    00792    else
    00793       close(conf->fd);
    00794
    00795    free(conf);
    00796
    00797    return 0;
    00798 }
    00799
    00800 /* Decrement reference counts, as incremented by find_conf() */
    00801 static int dispose_conf(struct ast_conference *conf)
    00802 {
    00803    int res = 0;
    00804    int confno_int = 0;
    00805
    00806    AST_LIST_LOCK(&confs);
    00807    if (ast_atomic_dec_and_test(&conf->refcount)) {
    00808       /* Take the conference room number out of an inuse state */
    00809       if ((sscanf(conf->confno, "%30d", &confno_int) == 1) && (confno_int >= 0 && confno_int < 1024))
    00810          conf_map[confno_int] = 0;
    00811       conf_free(conf);
    00812       res = 1;
    00813    }
    00814    AST_LIST_UNLOCK(&confs);
    00815
    00816    return res;
    00817 }
    00818
    00819 static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int confflags, char *optargs[])
    00820 {
    00821    struct ast_conf_user *user = calloc(1, sizeof(*user));
    00822    struct ast_conf_user *usr = NULL;
    00823    int fd;
    00824    struct zt_confinfo ztc, ztc_empty;
    00825    struct ast_frame *f;
    00826    struct ast_channel *c;
    00827    struct ast_frame fr;
    00828    int outfd;
    00829    int ms;
    00830    int nfds;
    00831    int res;
    00832    int flags;
    00833    int retryzap;
    00834    int origfd;
    00835    int musiconhold = 0;
    00836    int firstpass = 0;
    00837    int lastmarked = 0;
    00838    int currentmarked = 0;
    00839    int ret = -1;
    00840    int x;
    00841    int menu_active = 0;
    00842    int using_pseudo = 0;
    00843    int duration=20;
    00844    struct ast_dsp *dsp=NULL;
    00845    struct ast_app *app;
    00846    char *agifile;
    00847    char *agifiledefault = "conf-background.agi";
    00848    char meetmesecs[30] = "";
    00849    char exitcontext[AST_MAX_CONTEXT] = "";
    00850    char recordingtmp[AST_MAX_EXTENSION] = "";
    00851    int dtmf, opt_waitmarked_timeout = 0;
    00852    time_t timeout = 0;
    00853    ZT_BUFFERINFO bi;
    00854    char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
    00855    char *buf = __buf + AST_FRIENDLY_OFFSET;
    00856
    00857    if (!user) {
    00858       ast_log(LOG_ERROR, "Out of memory\n");
    00859       return ret;
    00860    }
    00861
    00862    /* Possible timeout waiting for marked user */
    00863    if ((confflags & CONFFLAG_WAITMARKED) &&
    00864       !ast_strlen_zero(optargs[OPT_ARG_WAITMARKED]) &&
    00865       (sscanf(optargs[OPT_ARG_WAITMARKED], "%30d", &opt_waitmarked_timeout) == 1) &&
    00866       (opt_waitmarked_timeout > 0)) {
    00867       timeout = time(NULL) + opt_waitmarked_timeout;
    00868    }
    00869
    00870    AST_LIST_LOCK(&confs);
    00871
    00872    if (confflags & CONFFLAG_RECORDCONF && conf->recording !=MEETME_RECORD_ACTIVE) {
    00873       conf->recordingfilename = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFILE");
    00874       if (!conf->recordingfilename) {
    00875          snprintf(recordingtmp, sizeof(recordingtmp), "meetme-conf-rec-%s-%s", conf->confno, chan->uniqueid);
    00876          conf->recordingfilename = ast_strdupa(recordingtmp);
    00877       }
    00878       conf->recordingformat = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFORMAT");
    00879       if (!conf->recordingformat) {
    00880          snprintf(recordingtmp, sizeof(recordingtmp), "wav");
    00881          conf->recordingformat = ast_strdupa(recordingtmp);
    00882       }
    00883       pthread_attr_init(&conf->attr);
    00884       pthread_attr_setdetachstate(&conf->attr, PTHREAD_CREATE_DETACHED);
    00885       ast_verbose(VERBOSE_PREFIX_4 "Starting recording of MeetMe Conference %s into file %s.%s.\n",
    00886              conf->confno, conf->recordingfilename, conf->recordingformat);
    00887       ast_pthread_create(&conf->recordthread, &conf->attr, recordthread, conf);
    00888       pthread_attr_destroy(&conf->attr);
    00889    }
    00890
    00891    time(&user->jointime);
    00892
    00893    if (conf->locked && (!(confflags & CONFFLAG_ADMIN))) {
    00894       /* Sorry, but this confernce is locked! */
    00895       if (!ast_streamfile(chan, "conf-locked", chan->language))
    00896          ast_waitstream(chan, "");
    00897       AST_LIST_UNLOCK(&confs);
    00898       goto outrun;
    00899    }
    00900
    00901    if (confflags & CONFFLAG_MARKEDUSER)
    00902       conf->markedusers++;
    00903
    00904    if (AST_LIST_LAST(&conf->userlist))
    00905       user->user_no = AST_LIST_LAST(&conf->userlist)->user_no + 1;
    00906    else
    00907       user->user_no = 1;
    00908
    00909    user->chan = chan;
    00910    user->userflags = confflags;
    00911    user->adminflags = 0;
    00912    user->talking = -1;
    00913    conf->users++;
    00914
    00915    AST_LIST_INSERT_TAIL(&conf->userlist, user, list);
    00916
    00917    /* Since we control a user in the userlist, our conference should never go away now. */
    00918    AST_LIST_UNLOCK(&confs);
    00919
    00920    if (confflags & CONFFLAG_EXIT_CONTEXT) {
    00921       if ((agifile = pbx_builtin_getvar_helper(chan, "MEETME_EXIT_CONTEXT")))
    00922          ast_copy_string(exitcontext, agifile, sizeof(exitcontext));
    00923       else if (!ast_strlen_zero(chan->macrocontext))
    00924          ast_copy_string(exitcontext, chan->macrocontext, sizeof(exitcontext));
    00925       else
    00926          ast_copy_string(exitcontext, chan->context, sizeof(exitcontext));
    00927    }
    00928
    00929    if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER)) {
    00930       snprintf(user->namerecloc, sizeof(user->namerecloc),
    00931           "%s/meetme/meetme-username-%s-%d", ast_config_AST_SPOOL_DIR,
    00932           conf->confno, user->user_no);
    00933       res = ast_record_review(chan, "vm-rec-name", user->namerecloc, 10, "sln", &duration, NULL);
    00934       if (res == -1)
    00935          goto outrun;
    00936    }
    00937
    00938    if (!(confflags & CONFFLAG_QUIET)) {
    00939       if (conf->users == 1 && !(confflags & CONFFLAG_WAITMARKED))
    00940          if (!ast_streamfile(chan, "conf-onlyperson", chan->language))
    00941             ast_waitstream(chan, "");
    00942       if ((confflags & CONFFLAG_WAITMARKED) && conf->markedusers == 0)
    00943          if (!ast_streamfile(chan, "conf-waitforleader", chan->language))
    00944             ast_waitstream(chan, "");
    00945    }
    00946
    00947    if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_ANNOUNCEUSERCOUNT) && conf->users > 1) {
    00948       int keepplaying = 1;
    00949
    00950       if (conf->users == 2) {
    00951          if (!ast_streamfile(chan,"conf-onlyone",chan->language)) {
    00952             res = ast_waitstream(chan, AST_DIGIT_ANY);
    00953             ast_stopstream(chan);
    00954             if (res > 0)
    00955                keepplaying=0;
    00956             else if (res == -1)
    00957                goto outrun;
    00958          }
    00959       } else {
    00960          if (!ast_streamfile(chan, "conf-thereare", chan->language)) {
    00961             res = ast_waitstream(chan, AST_DIGIT_ANY);
    00962             ast_stopstream(chan);
    00963             if (res > 0)
    00964                keepplaying=0;
    00965             else if (res == -1)
    00966                goto outrun;
    00967          }
    00968          if (keepplaying) {
    00969             res = ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL);
    00970             if (res > 0)
    00971                keepplaying=0;
    00972             else if (res == -1)
    00973                goto outrun;
    00974          }
    00975          if (keepplaying && !ast_streamfile(chan, "conf-otherinparty", chan->language)) {
    00976             res = ast_waitstream(chan, AST_DIGIT_ANY);
    00977             ast_stopstream(chan);
    00978             if (res > 0)
    00979                keepplaying=0;
    00980             else if (res == -1)
    00981                goto outrun;
    00982          }
    00983       }
    00984    }
    00985
    00986    ast_indicate(chan, -1);
    00987
    00988    if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
    00989       ast_log(LOG_WARNING, "Unable to set '%s' to write linear mode\n", chan->name);
    00990       goto outrun;
    00991    }
    00992
    00993    if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0) {
    00994       ast_log(LOG_WARNING, "Unable to set '%s' to read linear mode\n", chan->name);
    00995       goto outrun;
    00996    }
    00997
    00998    retryzap = strcasecmp(chan->type, "Zap");
    00999    user->zapchannel = !retryzap;
    01000
    01001  zapretry:
    01002    origfd = chan->fds[0];
    01003    if (retryzap) {
    01004       fd = open("/dev/zap/pseudo", O_RDWR);
    01005       if (fd < 0) {
    01006          ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
    01007          goto outrun;
    01008       }
    01009       using_pseudo = 1;
    01010       /* Make non-blocking */
    01011       flags = fcntl(fd, F_GETFL);
    01012       if (flags < 0) {
    01013          ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
    01014          close(fd);
    01015          goto outrun;
    01016       }
    01017       if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
    01018          ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
    01019          close(fd);
    01020          goto outrun;
    01021       }
    01022       /* Setup buffering information */
    01023       memset(&bi, 0, sizeof(bi));
    01024       bi.bufsize = CONF_SIZE/2;
    01025       bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
    01026       bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
    01027       bi.numbufs = audio_buffers;
    01028       if (ioctl(fd, ZT_SET_BUFINFO, &bi)) {
    01029          ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
    01030          close(fd);
    01031          goto outrun;
    01032       }
    01033       x = 1;
    01034       if (ioctl(fd, ZT_SETLINEAR, &x)) {
    01035          ast_log(LOG_WARNING, "Unable to set linear mode: %s\n", strerror(errno));
    01036          close(fd);
    01037          goto outrun;
    01038       }
    01039       nfds = 1;
    01040    } else {
    01041       /* XXX Make sure we're not running on a pseudo channel XXX */
    01042       fd = chan->fds[0];
    01043       nfds = 0;
    01044    }
    01045    memset(&ztc, 0, sizeof(ztc));
    01046    memset(&ztc_empty, 0, sizeof(ztc_empty));
    01047    /* Check to see if we're in a conference... */
    01048    ztc.chan = 0;
    01049    if (ioctl(fd, ZT_GETCONF, &ztc)) {
    01050       ast_log(LOG_WARNING, "Error getting conference\n");
    01051       close(fd);
    01052       goto outrun;
    01053    }
    01054    if (ztc.confmode) {
    01055       /* Whoa, already in a conference...  Retry... */
    01056       if (!retryzap) {
    01057          ast_log(LOG_DEBUG, "Zap channel is in a conference already, retrying with pseudo\n");
    01058          retryzap = 1;
    01059          goto zapretry;
    01060       }
    01061    }
    01062    memset(&ztc, 0, sizeof(ztc));
    01063    /* Add us to the conference */
    01064    ztc.chan = 0;
    01065    ztc.confno = conf->zapconf;
    01066
    01067    AST_LIST_LOCK(&confs);
    01068
    01069    if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER) && conf->users > 1) {
    01070       if (conf->chan && ast_fileexists(user->namerecloc, NULL, NULL)) {
    01071          if (!ast_streamfile(conf->chan, user->namerecloc, chan->language))
    01072             ast_waitstream(conf->chan, "");
    01073          if (!ast_streamfile(conf->chan, "conf-hasjoin", chan->language))
    01074             ast_waitstream(conf->chan, "");
    01075       }
    01076    }
    01077
    01078    if (confflags & CONFFLAG_MONITOR)
    01079       ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER;
    01080    else if (confflags & CONFFLAG_TALKER)
    01081       ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER;
    01082    else
    01083       ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
    01084
    01085    if (ioctl(fd, ZT_SETCONF, &ztc)) {
    01086       ast_log(LOG_WARNING, "Error setting conference\n");
    01087       close(fd);
    01088       AST_LIST_UNLOCK(&confs);
    01089       goto outrun;
    01090    }
    01091    ast_log(LOG_DEBUG, "Placed channel %s in ZAP conf %d\n", chan->name, conf->zapconf);
    01092
    01093    manager_event(EVENT_FLAG_CALL, "MeetmeJoin",
    01094             "Channel: %s\r\n"
    01095             "Uniqueid: %s\r\n"
    01096             "Meetme: %s\r\n"
    01097             "Usernum: %d\r\n",
    01098             chan->name, chan->uniqueid, conf->confno, user->user_no);
    01099
    01100    if (!firstpass && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN)) {
    01101       firstpass = 1;
    01102       if (!(confflags & CONFFLAG_QUIET))
    01103          if (!(confflags & CONFFLAG_WAITMARKED) || ((confflags & CONFFLAG_MARKEDUSER) && (conf->markedusers >= 1)))
    01104             conf_play(chan, conf, ENTER);
    01105    }
    01106
    01107    AST_LIST_UNLOCK(&confs);
    01108
    01109    conf_flush(fd, chan);
    01110
    01111    if (confflags & CONFFLAG_AGI) {
    01112       /* Get name of AGI file to run from $(MEETME_AGI_BACKGROUND)
    01113          or use default filename of conf-background.agi */
    01114
    01115       agifile = pbx_builtin_getvar_helper(chan, "MEETME_AGI_BACKGROUND");
    01116       if (!agifile)
    01117          agifile = agifiledefault;
    01118
    01119       if (user->zapchannel) {
    01120          /*  Set CONFMUTE mode on Zap channel to mute DTMF tones */
    01121          x = 1;
    01122          ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
    01123       }
    01124       /* Find a pointer to the agi app and execute the script */
    01125       app = pbx_findapp("agi");
    01126       if (app) {
    01127          ret = pbx_exec(chan, app, agifile, 1);
    01128       } else {
    01129          ast_log(LOG_WARNING, "Could not find application (agi)\n");
    01130          ret = -2;
    01131       }
    01132       if (user->zapchannel) {
    01133          /*  Remove CONFMUTE mode on Zap channel */
    01134          x = 0;
    01135          ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
    01136       }
    01137    } else {
    01138       if (user->zapchannel && (confflags & CONFFLAG_STARMENU)) {
    01139          /*  Set CONFMUTE mode on Zap channel to mute DTMF tones when the menu is enabled */
    01140          x = 1;
    01141          ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
    01142       }
    01143       if (confflags & CONFFLAG_MONITORTALKER && !(dsp = ast_dsp_new())) {
    01144          ast_log(LOG_WARNING, "Unable to allocate DSP!\n");
    01145          res = -1;
    01146       }
    01147       for(;;) {
    01148          int menu_was_active = 0;
    01149
    01150          outfd = -1;
    01151          ms = -1;
    01152
    01153          if (timeout && time(NULL) >= timeout)
    01154             break;
    01155
    01156          /* if we have just exited from the menu, and the user had a channel-driver
    01157             volume adjustment, restore it
    01158          */
    01159          if (!menu_active && menu_was_active && user->listen.desired && !user->listen.actual)
    01160             set_talk_volume(user, user->listen.desired);
    01161
    01162          menu_was_active = menu_active;
    01163
    01164          currentmarked = conf->markedusers;
    01165          if (!(confflags & CONFFLAG_QUIET) &&
    01166              (confflags & CONFFLAG_MARKEDUSER) &&
    01167              (confflags & CONFFLAG_WAITMARKED) &&
    01168              lastmarked == 0) {
    01169             if (currentmarked == 1 && conf->users > 1) {
    01170                ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL);
    01171                if (conf->users - 1 == 1) {
    01172                   if (!ast_streamfile(chan, "conf-userwilljoin", chan->language))
    01173                      ast_waitstream(chan, "");
    01174                } else {
    01175                   if (!ast_streamfile(chan, "conf-userswilljoin", chan->language))
    01176                      ast_waitstream(chan, "");
    01177                }
    01178             }
    01179             if (conf->users == 1 && ! (confflags & CONFFLAG_MARKEDUSER))
    01180                if (!ast_streamfile(chan, "conf-onlyperson", chan->language))
    01181                   ast_waitstream(chan, "");
    01182          }
    01183
    01184          c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
    01185
    01186          /* Update the struct with the actual confflags */
    01187          user->userflags = confflags;
    01188
    01189          if (confflags & CONFFLAG_WAITMARKED) {
    01190             if(currentmarked == 0) {
    01191                if (lastmarked != 0) {
    01192                   if (!(confflags & CONFFLAG_QUIET))
    01193                      if (!ast_streamfile(chan, "conf-leaderhasleft", chan->language))
    01194                         ast_waitstream(chan, "");
    01195                   if(confflags & CONFFLAG_MARKEDEXIT)
    01196                      break;
    01197                   else {
    01198                      ztc.confmode = ZT_CONF_CONF;
    01199                      if (ioctl(fd, ZT_SETCONF, &ztc)) {
    01200                         ast_log(LOG_WARNING, "Error setting conference\n");
    01201                         close(fd);
    01202                         goto outrun;
    01203                      }
    01204                   }
    01205                }
    01206                if (musiconhold == 0 && (confflags & CONFFLAG_MOH)) {
    01207                   ast_moh_start(chan, NULL);
    01208                   musiconhold = 1;
    01209                } else {
    01210                   ztc.confmode = ZT_CONF_CONF;
    01211                   if (ioctl(fd, ZT_SETCONF, &ztc)) {
    01212                      ast_log(LOG_WARNING, "Error setting conference\n");
    01213                      close(fd);
    01214                      goto outrun;
    01215                   }
    01216                }
    01217             } else if(currentmarked >= 1 && lastmarked == 0) {
    01218                /* Marked user entered, so cancel timeout */
    01219                timeout = 0;
    01220                if (confflags & CONFFLAG_MONITOR)
    01221                   ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER;
    01222                else if (confflags & CONFFLAG_TALKER)
    01223                   ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER;
    01224                else
    01225                   ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
    01226                if (ioctl(fd, ZT_SETCONF, &ztc)) {
    01227                   ast_log(LOG_WARNING, "Error setting conference\n");
    01228                   close(fd);
    01229                   goto outrun;
    01230                }
    01231                if (musiconhold && (confflags & CONFFLAG_MOH)) {
    01232                   ast_moh_stop(chan);
    01233                   musiconhold = 0;
    01234                }
    01235                if ( !(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MARKEDUSER)) {
    01236                   if (!ast_streamfile(chan, "conf-placeintoconf", chan->language))
    01237                      ast_waitstream(chan, "");
    01238                   conf_play(chan, conf, ENTER);
    01239                }
    01240             }
    01241          }
    01242
    01243          /* trying to add moh for single person conf */
    01244          if ((confflags & CONFFLAG_MOH) && !(confflags & CONFFLAG_WAITMARKED)) {
    01245             if (conf->users == 1) {
    01246                if (musiconhold == 0) {
    01247                   ast_moh_start(chan, NULL);
    01248                   musiconhold = 1;
    01249                }
    01250             } else {
    01251                if (musiconhold) {
    01252                   ast_moh_stop(chan);
    01253                   musiconhold = 0;
    01254                }
    01255             }
    01256          }
    01257
    01258          /* Leave if the last marked user left */
    01259          if (currentmarked == 0 && lastmarked != 0 && (confflags & CONFFLAG_MARKEDEXIT)) {
    01260             ret = -1;
    01261             break;
    01262          }
    01263
    01264          /* Check if the admin changed my modes */
    01265          if (user->adminflags) {
    01266             /* Set the new modes */
    01267             if ((user->adminflags & ADMINFLAG_MUTED) && (ztc.confmode & ZT_CONF_TALKER)) {
    01268                ztc.confmode ^= ZT_CONF_TALKER;
    01269                if (ioctl(fd, ZT_SETCONF, &ztc)) {
    01270                   ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
    01271                   ret = -1;
    01272                   break;
    01273                }
    01274             }
    01275             if (!(user->adminflags & ADMINFLAG_MUTED) && !(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & ZT_CONF_TALKER)) {
    01276                ztc.confmode |= ZT_CONF_TALKER;
    01277                if (ioctl(fd, ZT_SETCONF, &ztc)) {
    01278                   ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
    01279                   ret = -1;
    01280                   break;
    01281                }
    01282             }
    01283             if (user->adminflags & ADMINFLAG_KICKME) {
    01284                /* You have been kicked. */
    01285                if (!ast_streamfile(chan, "conf-kicked", chan->language))
    01286                   ast_waitstream(chan, "");
    01287                ret = 0;
    01288                break;
    01289             }
    01290          } else if (!(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & ZT_CONF_TALKER)) {
    01291             ztc.confmode |= ZT_CONF_TALKER;
    01292             if (ioctl(fd, ZT_SETCONF, &ztc)) {
    01293                ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
    01294                ret = -1;
    01295                break;
    01296             }
    01297          }
    01298
    01299          /* If the channel wants to be hung up, hang it up */
    01300          if (ast_check_hangup(chan))
    01301             break;
    01302
    01303          if (c) {
    01304             if (c->fds[0] != origfd) {
    01305                if (using_pseudo) {
    01306                   /* Kill old pseudo */
    01307                   close(fd);
    01308                   using_pseudo = 0;
    01309                }
    01310                ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n");
    01311                retryzap = strcasecmp(c->type, "Zap");
    01312                user->zapchannel = !retryzap;
    01313                goto zapretry;
    01314             }
    01315             f = ast_read(c);
    01316             if (!f)
    01317                break;
    01318             if ((f->frametype == AST_FRAME_VOICE) && (f->subclass == AST_FORMAT_SLINEAR)) {
    01319                if (user->talk.actual)
    01320                   ast_frame_adjust_volume(f, user->talk.actual);
    01321
    01322                if (confflags &  CONFFLAG_MONITORTALKER) {
    01323                   int totalsilence;
    01324
    01325                   if (user->talking == -1)
    01326                      user->talking = 0;
    01327
    01328                   res = ast_dsp_silence(dsp, f, &totalsilence);
    01329                   if (!user->talking && totalsilence < MEETME_DELAYDETECTTALK) {
    01330                      user->talking = 1;
    01331                      manager_event(EVENT_FLAG_CALL, "MeetmeTalking",
    01332                               "Channel: %s\r\n"
    01333                               "Uniqueid: %s\r\n"
    01334                               "Meetme: %s\r\n"
    01335                               "Usernum: %d\r\n",
    01336                               chan->name, chan->uniqueid, conf->confno, user->user_no);
    01337                   }
    01338                   if (user->talking && totalsilence > MEETME_DELAYDETECTENDTALK) {
    01339                      user->talking = 0;
    01340                      manager_event(EVENT_FLAG_CALL, "MeetmeStopTalking",
    01341                               "Channel: %s\r\n"
    01342                               "Uniqueid: %s\r\n"
    01343                               "Meetme: %s\r\n"
    01344                               "Usernum: %d\r\n",
    01345                               chan->name, chan->uniqueid, conf->confno, user->user_no);
    01346                   }
    01347                }
    01348                if (using_pseudo) {
    01349                   /* Absolutely do _not_ use careful_write here...
    01350                      it is important that we read data from the channel
    01351                      as fast as it arrives, and feed it into the conference.
    01352                      The buffering in the pseudo channel will take care of any
    01353                      timing differences, unless they are so drastic as to lose
    01354                      audio frames (in which case carefully writing would only
    01355                      have delayed the audio even further).
    01356                   */
    01357                   /* As it turns out, we do want to use careful write.  We just
    01358                      don't want to block, but we do want to at least *try*
    01359                      to write out all the samples.
    01360                    */
    01361                   careful_write(fd, f->data, f->datalen, 0);
    01362                }
    01363             } else if ((f->frametype == AST_FRAME_DTMF) && (confflags & CONFFLAG_EXIT_CONTEXT)) {
    01364                char tmp[2];
    01365
    01366                tmp[0] = f->subclass;
    01367                tmp[1] = '\0';
    01368                if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) {
    01369                   ret = 0;
    01370                   ast_frfree(f);
    01371                   break;
    01372                } else if (option_debug > 1)
    01373                   ast_log(LOG_DEBUG, "Exit by single digit did not work in meetme. Extension %s does not exist in context %s\n", tmp, exitcontext);
    01374             } else if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#') && (confflags & CONFFLAG_POUNDEXIT)) {
    01375                ret = 0;
    01376                ast_frfree(f);
    01377                break;
    01378             } else if (((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*') && (confflags & CONFFLAG_STARMENU)) || ((f->frametype == AST_FRAME_DTMF) && menu_active)) {
    01379                if (ioctl(fd, ZT_SETCONF, &ztc_empty)) {
    01380                   ast_log(LOG_WARNING, "Error setting conference\n");
    01381                   close(fd);
    01382                   ast_frfree(f);
    01383                   goto outrun;
    01384                }
    01385
    01386                /* if we are entering the menu, and the user has a channel-driver
    01387                   volume adjustment, clear it
    01388                */
    01389                if (!menu_active && user->talk.desired && !user->talk.actual)
    01390                   set_talk_volume(user, 0);
    01391
    01392                if (musiconhold) {
    01393                      ast_moh_stop(chan);
    01394                }
    01395                if ((confflags & CONFFLAG_ADMIN)) {
    01396                   /* Admin menu */
    01397                   if (!menu_active) {
    01398                      menu_active = 1;
    01399                      /* Record this sound! */
    01400                      if (!ast_streamfile(chan, "conf-adminmenu", chan->language)) {
    01401                         dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
    01402                         ast_stopstream(chan);
    01403                      } else
    01404                         dtmf = 0;
    01405                   } else
    01406                      dtmf = f->subclass;
    01407                   if (dtmf) {
    01408                      switch(dtmf) {
    01409                      case '1': /* Un/Mute */
    01410                         menu_active = 0;
    01411                         if (ztc.confmode & ZT_CONF_TALKER) {
    01412                                  ztc.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER;
    01413                                  confflags |= CONFFLAG_MONITOR ^ CONFFLAG_TALKER;
    01414                         } else {
    01415                            ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
    01416                            confflags ^= CONFFLAG_MONITOR | CONFFLAG_TALKER;
    01417                         }
    01418                         if (ioctl(fd, ZT_SETCONF, &ztc)) {
    01419                            ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
    01420                            ret = -1;
    01421                            break;
    01422                         }
    01423                         if (ztc.confmode & ZT_CONF_TALKER) {
    01424                            if (!ast_streamfile(chan, "conf-unmuted", chan->language))
    01425                               ast_waitstream(chan, "");
    01426                         } else {
    01427                            if (!ast_streamfile(chan, "conf-muted", chan->language))
    01428                               ast_waitstream(chan, "");
    01429                         }
    01430                         break;
    01431                      case '2': /* Un/Lock the Conference */
    01432                         menu_active = 0;
    01433                         if (conf->locked) {
    01434                            conf->locked = 0;
    01435                            if (!ast_streamfile(chan, "conf-unlockednow", chan->language))
    01436                               ast_waitstream(chan, "");
    01437                         } else {
    01438                            conf->locked = 1;
    01439                            if (!ast_streamfile(chan, "conf-lockednow", chan->language))
    01440                               ast_waitstream(chan, "");
    01441                         }
    01442                         break;
    01443                      case '3': /* Eject last user */
    01444                         menu_active = 0;
    01445                         usr = AST_LIST_LAST(&conf->userlist);
    01446                         if ((usr->chan->name == chan->name)||(usr->userflags & CONFFLAG_ADMIN)) {
    01447                            if(!ast_streamfile(chan, "conf-errormenu", chan->language))
    01448                               ast_waitstream(chan, "");
    01449                         } else
    01450                            usr->adminflags |= ADMINFLAG_KICKME;
    01451                         ast_stopstream(chan);
    01452                         break;
    01453                      case '4':
    01454                         tweak_listen_volume(user, VOL_DOWN);
    01455                         break;
    01456                      case '6':
    01457                         tweak_listen_volume(user, VOL_UP);
    01458                         break;
    01459                      case '7':
    01460                         tweak_talk_volume(user, VOL_DOWN);
    01461                         break;
    01462                      case '8':
    01463                         menu_active = 0;
    01464                         break;
    01465                      case '9':
    01466                         tweak_talk_volume(user, VOL_UP);
    01467                         break;
    01468                      default:
    01469                         menu_active = 0;
    01470                         /* Play an error message! */
    01471                         if (!ast_streamfile(chan, "conf-errormenu", chan->language))
    01472                            ast_waitstream(chan, "");
    01473                         break;
    01474                      }
    01475                   }
    01476                } else {
    01477                   /* User menu */
    01478                   if (!menu_active) {
    01479                      menu_active = 1;
    01480                      if (!ast_streamfile(chan, "conf-usermenu", chan->language)) {
    01481                         dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
    01482                         ast_stopstream(chan);
    01483                      } else
    01484                         dtmf = 0;
    01485                   } else
    01486                      dtmf = f->subclass;
    01487                   if (dtmf) {
    01488                      switch(dtmf) {
    01489                      case '1': /* Un/Mute */
    01490                         menu_active = 0;
    01491                         if (ztc.confmode & ZT_CONF_TALKER) {
    01492                                  ztc.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER;
    01493                                  confflags |= CONFFLAG_MONITOR ^ CONFFLAG_TALKER;
    01494                         } else if (!(user->adminflags & ADMINFLAG_MUTED)) {
    01495                            ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
    01496                            confflags ^= CONFFLAG_MONITOR | CONFFLAG_TALKER;
    01497                         }
    01498                         if (ioctl(fd, ZT_SETCONF, &ztc)) {
    01499                            ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
    01500                            ret = -1;
    01501                            break;
    01502                         }
    01503                         if (ztc.confmode & ZT_CONF_TALKER) {
    01504                            if (!ast_streamfile(chan, "conf-unmuted", chan->language))
    01505                               ast_waitstream(chan, "");
    01506                         } else {
    01507                            if (!ast_streamfile(chan, "conf-muted", chan->language))
    01508                               ast_waitstream(chan, "");
    01509                         }
    01510                         break;
    01511                      case '4':
    01512                         tweak_listen_volume(user, VOL_DOWN);
    01513                         break;
    01514                      case '6':
    01515                         tweak_listen_volume(user, VOL_UP);
    01516                         break;
    01517                      case '7':
    01518                         tweak_talk_volume(user, VOL_DOWN);
    01519                         break;
    01520                      case '8':
    01521                         menu_active = 0;
    01522                         break;
    01523                      case '9':
    01524                         tweak_talk_volume(user, VOL_UP);
    01525                         break;
    01526                      default:
    01527                         menu_active = 0;
    01528                         if (!ast_streamfile(chan, "conf-errormenu", chan->language))
    01529                            ast_waitstream(chan, "");
    01530                         break;
    01531                      }
    01532                   }
    01533                }
    01534                if (musiconhold)
    01535                      ast_moh_start(chan, NULL);
    01536
    01537                if (ioctl(fd, ZT_SETCONF, &ztc)) {
    01538                   ast_log(LOG_WARNING, "Error setting conference\n");
    01539                   close(fd);
    01540                   ast_frfree(f);
    01541                   goto outrun;
    01542                }
    01543
    01544                conf_flush(fd, chan);
    01545             } else if (option_debug) {
    01546                ast_log(LOG_DEBUG,
    01547                   "Got unrecognized frame on channel %s, f->frametype=%d,f->subclass=%d\n",
    01548                   chan->name, f->frametype, f->subclass);
    01549             }
    01550             ast_frfree(f);
    01551          } else if (outfd > -1) {
    01552             res = read(outfd, buf, CONF_SIZE);
    01553             if (res > 0) {
    01554                memset(&fr, 0, sizeof(fr));
    01555                fr.frametype = AST_FRAME_VOICE;
    01556                fr.subclass = AST_FORMAT_SLINEAR;
    01557                fr.datalen = res;
    01558                fr.samples = res/2;
    01559                fr.data = buf;
    01560                fr.offset = AST_FRIENDLY_OFFSET;
    01561                if (user->listen.actual)
    01562                   ast_frame_adjust_volume(&fr, user->listen.actual);
    01563                if (ast_write(chan, &fr) < 0) {
    01564                   ast_log(LOG_WARNING, "Unable to write frame to channel %s\n", chan->name);
    01565                }
    01566             } else
    01567                ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno));
    01568          }
    01569          lastmarked = currentmarked;
    01570       }
    01571    }
    01572
    01573    if (musiconhold)
    01574       ast_moh_stop(chan);
    01575
    01576    if (using_pseudo)
    01577       close(fd);
    01578    else {
    01579       /* Take out of conference */
    01580       ztc.chan = 0;
    01581       ztc.confno = 0;
    01582       ztc.confmode = 0;
    01583       if (ioctl(fd, ZT_SETCONF, &ztc)) {
    01584          ast_log(LOG_WARNING, "Error setting conference\n");
    01585       }
    01586    }
    01587
    01588    reset_volumes(user);
    01589
    01590    AST_LIST_LOCK(&confs);
    01591    if (!(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN))
    01592       conf_play(chan, conf, LEAVE);
    01593
    01594    if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER)) {
    01595       if (ast_fileexists(user->namerecloc, NULL, NULL)) {
    01596          if ((conf->chan) && (conf->users > 1)) {
    01597             if (!ast_streamfile(conf->chan, user->namerecloc, chan->language))
    01598                ast_waitstream(conf->chan, "");
    01599             if (!ast_streamfile(conf->chan, "conf-hasleft", chan->language))
    01600                ast_waitstream(conf->chan, "");
    01601          }
    01602          ast_filedelete(user->namerecloc, NULL);
    01603       }
    01604    }
    01605    AST_LIST_UNLOCK(&confs);
    01606
    01607  outrun:
    01608    AST_LIST_LOCK(&confs);
    01609
    01610    if (confflags & CONFFLAG_MONITORTALKER && dsp)
    01611       ast_dsp_free(dsp);
    01612
    01613    if (user->user_no) { /* Only cleanup users who really joined! */
    01614       manager_event(EVENT_FLAG_CALL, "MeetmeLeave",
    01615                "Channel: %s\r\n"
    01616                "Uniqueid: %s\r\n"
    01617                "Meetme: %s\r\n"
    01618                "Usernum: %d\r\n",
    01619                chan->name, chan->uniqueid, conf->confno, user->user_no);
    01620       conf->users--;
    01621       if (confflags & CONFFLAG_MARKEDUSER)
    01622          conf->markedusers--;
    01623       AST_LIST_REMOVE(&conf->userlist, user, list);
    01624       /* Return the number of seconds the user was in the conf */
    01625       snprintf(meetmesecs, sizeof(meetmesecs), "%d", (int) (time(NULL) - user->jointime));
    01626       pbx_builtin_setvar_helper(chan, "MEETMESECS", meetmesecs);
    01627    }
    01628    free(user);
    01629    AST_LIST_UNLOCK(&confs);
    01630
    01631    return ret;
    01632 }
    01633
    01634 static struct ast_conference *find_conf(struct ast_channel *chan, char *confno, int make, int dynamic, char *dynamic_pin,
    01635    int refcount, struct ast_flags *confflags)
    01636 {
    01637    struct ast_config *cfg;
    01638    struct ast_variable *var;
    01639    struct ast_conference *cnf;
    01640
    01641    /* Check first in the conference list */
    01642    AST_LIST_LOCK(&confs);
    01643    AST_LIST_TRAVERSE(&confs, cnf, list) {
    01644       if (!strcmp(confno, cnf->confno))
    01645          break;
    01646    }
    01647    if (cnf)
    01648       ast_atomic_fetchadd_int(&cnf->refcount, refcount);
    01649    AST_LIST_UNLOCK(&confs);
    01650
    01651    if (!cnf) {
    01652       if (dynamic) {
    01653          /* No need to parse meetme.conf */
    01654          ast_log(LOG_DEBUG, "Building dynamic conference '%s'\n", confno);
    01655          if (dynamic_pin) {
    01656             if (dynamic_pin[0] == 'q') {
    01657                /* Query the user to enter a PIN */
    01658                if (ast_app_getdata(chan, "conf-getpin", dynamic_pin, AST_MAX_EXTENSION - 1, 0) < 0)
    01659                   return NULL;
    01660             }
    01661             cnf = build_conf(confno, dynamic_pin, "", make, dynamic, refcount);
    01662          } else {
    01663             cnf = build_conf(confno, "", "", make, dynamic, refcount);
    01664          }
    01665       } else {
    01666          /* Check the config */
    01667          cfg = ast_config_load(CONFIG_FILE_NAME);
    01668          if (!cfg) {
    01669             ast_log(LOG_WARNING, "No %s file :(\n", CONFIG_FILE_NAME);
    01670             return NULL;
    01671          }
    01672          var = ast_variable_browse(cfg, "rooms");
    01673          while (var) {
    01674             if (!strcasecmp(var->name, "conf")) {
    01675                /* Separate the PIN */
    01676                char *pin, *pinadmin, *conf;
    01677
    01678                if ((pinadmin = ast_strdupa(var->value))) {
    01679                   conf = strsep(&pinadmin, "|,");
    01680                   pin = strsep(&pinadmin, "|,");
    01681                   if (!strcasecmp(conf, confno)) {
    01682                      /* Bingo it's a valid conference */
    01683                      if (pin)
    01684                         if (pinadmin)
    01685                            cnf = build_conf(confno, pin, pinadmin, make, dynamic, refcount);
    01686                         else
    01687                            cnf = build_conf(confno, pin, "", make, dynamic, refcount);
    01688                      else
    01689                         if (pinadmin)
    01690                            cnf = build_conf(confno, "", pinadmin, make, dynamic, refcount);
    01691                         else
    01692                            cnf = build_conf(confno, "", "", make, dynamic, refcount);
    01693                      break;
    01694                   }
    01695                }
    01696             }
    01697             var = var->next;
    01698          }
    01699          if (!var) {
    01700             ast_log(LOG_DEBUG, "%s isn't a valid conference\n", confno);
    01701          }
    01702          ast_config_destroy(cfg);
    01703       }
    01704    } else if (dynamic_pin) {
    01705       /* Correct for the user selecting 'D' instead of 'd' to have
    01706          someone join into a conference that has already been created
    01707          with a pin. */
    01708       if (dynamic_pin[0] == 'q')
    01709          dynamic_pin[0] = '\0';
    01710    }
    01711
    01712    if (cnf) {
    01713       if (confflags && !cnf->chan &&
    01714           !ast_test_flag(confflags, CONFFLAG_QUIET) &&
    01715           ast_test_flag(confflags, CONFFLAG_INTROUSER)) {
    01716          ast_log(LOG_WARNING, "No Zap channel available for conference, user introduction disabled (is chan_zap loaded?)\n");
    01717          ast_clear_flag(confflags, CONFFLAG_INTROUSER);
    01718       }
    01719
    01720       if (confflags && !cnf->chan &&
    01721           ast_test_flag(confflags, CONFFLAG_RECORDCONF)) {
    01722          ast_log(LOG_WARNING, "No Zap channel available for conference, conference recording disabled (is chan_zap loaded?)\n");
    01723          ast_clear_flag(confflags, CONFFLAG_RECORDCONF);
    01724       }
    01725    }
    01726
    01727    return cnf;
    01728 }
    01729
    01730 /*--- count_exec: The MeetmeCount application */
    01731 static int count_exec(struct ast_channel *chan, void *data)
    01732 {
    01733    struct localuser *u;
    01734    int res = 0;
    01735    struct ast_conference *conf;
    01736    int count;
    01737    char *confnum, *localdata;
    01738    char val[80] = "0";
    01739
    01740    if (ast_strlen_zero(data)) {
    01741       ast_log(LOG_WARNING, "MeetMeCount requires an argument (conference number)\n");
    01742       return -1;
    01743    }
    01744
    01745    LOCAL_USER_ADD(u);
    01746
    01747    localdata = ast_strdupa(data);
    01748    if (!localdata) {
    01749       ast_log(LOG_ERROR, "Out of memory!\n");
    01750       LOCAL_USER_REMOVE(u);
    01751       return -1;
    01752    }
    01753
    01754    confnum = strsep(&localdata,"|");
    01755    conf = find_conf(chan, confnum, 0, 0, NULL, 1, NULL);
    01756    if (conf) {
    01757       count = conf->users;
    01758       dispose_conf(conf);
    01759       conf = NULL;
    01760    } else
    01761       count = 0;
    01762
    01763    if (!ast_strlen_zero(localdata)){
    01764       /* have var so load it and exit */
    01765       snprintf(val, sizeof(val), "%d",count);
    01766       pbx_builtin_setvar_helper(chan, localdata, val);
    01767    } else {
    01768       if (chan->_state != AST_STATE_UP)
    01769          ast_answer(chan);
    01770       res = ast_say_number(chan, count, "", chan->language, (char *) NULL); /* Needs gender */
    01771    }
    01772    LOCAL_USER_REMOVE(u);
    01773
    01774    return res;
    01775 }
    01776
    01777 /*--- conf_exec: The meetme() application */
    01778 static int conf_exec(struct ast_channel *chan, void *data)
    01779 {
    01780    int res=-1;
    01781    struct localuser *u;
    01782    char confno[AST_MAX_EXTENSION] = "";
    01783    int allowretry = 0;
    01784    int retrycnt = 0;
    01785    struct ast_conference *cnf = NULL;
    01786    struct ast_flags confflags = {0};
    01787    int dynamic = 0;
    01788    int empty = 0, empty_no_pin = 0;
    01789    int always_prompt = 0;
    01790    char *notdata, *info, *inflags = NULL, *inpin = NULL, the_pin[AST_MAX_EXTENSION] = "";
    01791    char *optargs[OPT_ARG_ARRAY_SIZE] = { NULL, };
    01792
    01793    LOCAL_USER_ADD(u);
    01794
    01795    if (ast_strlen_zero(data)) {
    01796       allowretry = 1;
    01797       notdata = "";
    01798    } else {
    01799       notdata = data;
    01800    }
    01801
    01802    if (chan->_state != AST_STATE_UP)
    01803       ast_answer(chan);
    01804
    01805    info = ast_strdupa(notdata);
    01806
    01807    if (info) {
    01808       char *tmp = strsep(&info, "|");
    01809       ast_copy_string(confno, tmp, sizeof(confno));
    01810       if (ast_strlen_zero(confno)) {
    01811          allowretry = 1;
    01812       }
    01813    }
    01814    if (info)
    01815       inflags = strsep(&info, "|");
    01816    if (info)
    01817       inpin = strsep(&info, "|");
    01818    if (inpin)
    01819       ast_copy_string(the_pin, inpin, sizeof(the_pin));
    01820
    01821    if (inflags) {
    01822       ast_app_parse_options(meetme_opts, &confflags, optargs, inflags);
    01823       dynamic = ast_test_flag(&confflags, CONFFLAG_DYNAMIC | CONFFLAG_DYNAMICPIN);
    01824       if (ast_test_flag(&confflags, CONFFLAG_DYNAMICPIN) && !inpin)
    01825          strcpy(the_pin, "q");
    01826
    01827       empty = ast_test_flag(&confflags, CONFFLAG_EMPTY | CONFFLAG_EMPTYNOPIN);
    01828       empty_no_pin = ast_test_flag(&confflags, CONFFLAG_EMPTYNOPIN);
    01829       always_prompt = ast_test_flag(&confflags, CONFFLAG_ALWAYSPROMPT);
    01830    }
    01831
    01832    do {
    01833       if (retrycnt > 3)
    01834          allowretry = 0;
    01835       if (empty) {
    01836          struct ast_config *cfg;
    01837          struct ast_variable *var;
    01838          int i = 0, confno_int = 0;
    01839
    01840          /* We only need to load the config file for static and empty_no_pin (otherwise we don't care) */
    01841          if ((empty_no_pin) || (!dynamic)) {
    01842             cfg = ast_config_load(CONFIG_FILE_NAME);
    01843             if (cfg) {
    01844                var = ast_variable_browse(cfg, "rooms");
    01845                while (var) {
    01846                   if (!strcasecmp(var->name, "conf")) {
    01847                      char *stringp = ast_strdupa(var->value);
    01848                      if (stringp) {
    01849                         char *confno_tmp = strsep(&stringp, "|,");
    01850                         int found = 0;
    01851                         if (!dynamic) {
    01852                            /* For static:  run through the list and see if this conference is empty */
    01853                            AST_LIST_LOCK(&confs);
    01854                            AST_LIST_TRAVERSE(&confs, cnf, list) {
    01855                               if (!strcmp(confno_tmp, cnf->confno)) {
    01856                                  /* The conference exists, therefore it's not empty */
    01857                                  found = 1;
    01858                                  break;
    01859                               }
    01860                            }
    01861                            AST_LIST_UNLOCK(&confs);
    01862                            if (!found) {
    01863                               /* At this point, we have a confno_tmp (static conference) that is empty */
    01864                               if ((empty_no_pin && ((!stringp) || (stringp && (stringp[0] == '\0')))) || (!empty_no_pin)) {
    01865                                  /* Case 1:  empty_no_pin and pin is nonexistent (NULL)
    01866                                   * Case 2:  empty_no_pin and pin is blank (but not NULL)
    01867                                   * Case 3:  not empty_no_pin
    01868                                   */
    01869                                  ast_copy_string(confno, confno_tmp, sizeof(confno));
    01870                                  break;
    01871                                  /* XXX the map is not complete (but we do have a confno) */
    01872                               }
    01873                            }
    01874                         }
    01875                      } else {
    01876                         ast_log(LOG_ERROR, "Out of memory\n");
    01877                      }
    01878                   }
    01879                   var = var->next;
    01880                }
    01881                ast_config_destroy(cfg);
    01882             }
    01883          }
    01884
    01885          /* Select first conference number not in use */
    01886          if (ast_strlen_zero(confno) && dynamic) {
    01887             AST_LIST_LOCK(&confs);
    01888             for (i = 0; i < sizeof(conf_map) / sizeof(conf_map[0]); i++) {
    01889                if (!conf_map[i]) {
    01890                   snprintf(confno, sizeof(confno), "%d", i);
    01891                   conf_map[i] = 1;
    01892                   break;
    01893                }
    01894             }
    01895             AST_LIST_UNLOCK(&confs);
    01896          }
    01897
    01898          /* Not found? */
    01899          if (ast_strlen_zero(confno)) {
    01900             res = ast_streamfile(chan, "conf-noempty", chan->language);
    01901             if (!res)
    01902                ast_waitstream(chan, "");
    01903          } else {
    01904             if (sscanf(confno, "%30d", &confno_int) == 1) {
    01905                res = ast_streamfile(chan, "conf-enteringno", chan->language);
    01906                if (!res) {
    01907                   ast_waitstream(chan, "");
    01908                   res = ast_say_digits(chan, confno_int, "", chan->language);
    01909                }
    01910             } else {
    01911                ast_log(LOG_ERROR, "Could not scan confno '%s'\n", confno);
    01912             }
    01913          }
    01914       }
    01915
    01916       while (allowretry && (ast_strlen_zero(confno)) && (++retrycnt < 4)) {
    01917          /* Prompt user for conference number */
    01918          res = ast_app_getdata(chan, "conf-getconfno", confno, sizeof(confno) - 1, 0);
    01919          if (res < 0) {
    01920             /* Don't try to validate when we catch an error */
    01921             confno[0] = '\0';
    01922             allowretry = 0;
    01923             break;
    01924          }
    01925       }
    01926       if (!ast_strlen_zero(confno)) {
    01927          /* Check the validity of the conference */
    01928          cnf = find_conf(chan, confno, 1, dynamic, the_pin, 1, &confflags);
    01929          if (!cnf) {
    01930             res = ast_streamfile(chan, "conf-invalid", chan->language);
    01931             if (!res)
    01932                ast_waitstream(chan, "");
    01933             res = -1;
    01934             if (allowretry)
    01935                confno[0] = '\0';
    01936          } else {
    01937             if ((!ast_strlen_zero(cnf->pin) &&
    01938                  !ast_test_flag(&confflags, CONFFLAG_ADMIN)) ||
    01939                 (!ast_strlen_zero(cnf->pinadmin) &&
    01940                  ast_test_flag(&confflags, CONFFLAG_ADMIN))) {
    01941                char pin[AST_MAX_EXTENSION]="";
    01942                int j;
    01943
    01944                /* Allow the pin to be retried up to 3 times */
    01945                for (j = 0; j < 3; j++) {
    01946                   if (*the_pin && (always_prompt == 0)) {
    01947                      ast_copy_string(pin, the_pin, sizeof(pin));
    01948                      res = 0;
    01949                   } else {
    01950                      /* Prompt user for pin if pin is required */
    01951                      res = ast_app_getdata(chan, "conf-getpin", pin + strlen(pin), sizeof(pin) - 1 - strlen(pin), 0);
    01952                   }
    01953                   if (res >= 0) {
    01954                      if (!strcasecmp(pin, cnf->pin) ||
    01955                          (!ast_strlen_zero(cnf->pinadmin) &&
    01956                           !strcasecmp(pin, cnf->pinadmin))) {
    01957                         /* Pin correct */
    01958                         allowretry = 0;
    01959                         if (!ast_strlen_zero(cnf->pinadmin) && !strcasecmp(pin, cnf->pinadmin))
    01960                            ast_set_flag(&confflags, CONFFLAG_ADMIN);
    01961                         /* Run the conference */
    01962                         res = conf_run(chan, cnf, confflags.flags, optargs);
    01963                         break;
    01964                      } else {
    01965                         /* Pin invalid */
    01966                         if (!ast_streamfile(chan, "conf-invalidpin", chan->language)) {
    01967                            res = ast_waitstream(chan, AST_DIGIT_ANY);
    01968                            ast_stopstream(chan);
    01969                         }
    01970                         else {
    01971                            ast_log(LOG_WARNING, "Couldn't play invalid pin msg!\n");
    01972                            break;
    01973                         }
    01974                         if (res < 0)
    01975                            break;
    01976                         pin[0] = res;
    01977                         pin[1] = '\0';
    01978                         res = -1;
    01979                         if (allowretry)
    01980                            confno[0] = '\0';
    01981                      }
    01982                   } else {
    01983                      /* failed when getting the pin */
    01984                      res = -1;
    01985                      allowretry = 0;
    01986                      break;
    01987                   }
    01988
    01989                   /* Don't retry pin with a static pin */
    01990                   if (*the_pin && !always_prompt)
    01991                      break;
    01992                }
    01993             } else {
    01994                /* No pin required */
    01995                allowretry = 0;
    01996
    01997                /* Run the conference */
    01998                res = conf_run(chan, cnf, confflags.flags, optargs);
    01999             }
    02000             dispose_conf(cnf);
    02001             cnf = NULL;
    02002          }
    02003       }
    02004    } while (allowretry);
    02005
    02006    if (cnf)
    02007       dispose_conf(cnf);
    02008
    02009    LOCAL_USER_REMOVE(u);
    02010
    02011    return res;
    02012 }
    02013
    02014 static struct ast_conf_user* find_user(struct ast_conference *conf, char *callerident)
    02015 {
    02016    struct ast_conf_user *user = NULL;
    02017    int cid;
    02018
    02019    if (!conf || !callerident) {
    02020       return NULL;
    02021    }
    02022
    02023    sscanf(callerident, "%30i", &cid);
    02024
    02025    AST_LIST_TRAVERSE(&conf->userlist, user, list) {
    02026       if (user->user_no == cid)
    02027          break;
    02028    }
    02029
    02030    return user;
    02031 }
    02032
    02033 /*--- admin_exec: The MeetMeadmin application */
    02034 /* MeetMeAdmin(confno, command, caller) */
    02035 static int admin_exec(struct ast_channel *chan, void *data) {
    02036    char *params, *command = NULL, *caller = NULL, *conf = NULL;
    02037    struct ast_conference *cnf;
    02038    struct ast_conf_user *user = NULL;
    02039    struct localuser *u;
    02040
    02041    LOCAL_USER_ADD(u);
    02042
    02043    /* The param has the conference number the user and the command to execute */
    02044    if (!ast_strlen_zero(data)) {
    02045       params = ast_strdupa((char *) data);
    02046       conf = strsep(&params, "|");
    02047       command = strsep(&params, "|");
    02048       caller = strsep(&params, "|");
    02049
    02050       if (!command) {
    02051          ast_log(LOG_WARNING, "MeetmeAdmin requires a command!\n");
    02052          LOCAL_USER_REMOVE(u);
    02053          return -1;
    02054       }
    02055
    02056       cnf = find_conf(chan, conf, 0, 0, NULL, 1, NULL);
    02057
    02058       if (caller)
    02059          user = find_user(cnf, caller);
    02060
    02061       if (cnf) {
    02062          switch((int) (*command)) {
    02063          case 76: /* L: Lock */
    02064             cnf->locked = 1;
    02065             break;
    02066          case 108: /* l: Unlock */
    02067             cnf->locked = 0;
    02068             break;
    02069          case 75: /* K: kick all users*/
    02070             AST_LIST_TRAVERSE(&cnf->userlist, user, list) {
    02071                user->adminflags |= ADMINFLAG_KICKME;
    02072             }
    02073             break;
    02074          case 101: /* e: Eject last user*/
    02075             user = AST_LIST_LAST(&cnf->userlist);
    02076             if (!(user->userflags & CONFFLAG_ADMIN)) {
    02077                user->adminflags |= ADMINFLAG_KICKME;
    02078                break;
    02079             } else
    02080                ast_log(LOG_NOTICE, "Not kicking last user, is an Admin!\n");
    02081             break;
    02082          case 77: /* M: Mute */
    02083             if (user) {
    02084                user->adminflags |= ADMINFLAG_MUTED;
    02085             } else {
    02086                ast_log(LOG_NOTICE, "Specified User not found!\n");
    02087             }
    02088             break;
    02089          case 78: /* N: Mute all users */
    02090             AST_LIST_TRAVERSE(&cnf->userlist, user, list) {
    02091                if (user && !(user->userflags & CONFFLAG_ADMIN))
    02092                   user->adminflags |= ADMINFLAG_MUTED;
    02093             }
    02094             break;
    02095          case 109: /* m: Unmute */
    02096             if (user && (user->adminflags & ADMINFLAG_MUTED)) {
    02097                user->adminflags ^= ADMINFLAG_MUTED;
    02098             } else {
    02099                ast_log(LOG_NOTICE, "Specified User not found or he muted himself!\n");
    02100             }
    02101             break;
    02102          case  110: /* n: Unmute all users */
    02103             AST_LIST_TRAVERSE(&cnf->userlist, user, list) {
    02104                if (user && (user-> adminflags & ADMINFLAG_MUTED)) {
    02105                   user->adminflags ^= ADMINFLAG_MUTED;
    02106                }
    02107             }
    02108             break;
    02109          case 107: /* k: Kick user */
    02110             if (user) {
    02111                user->adminflags |= ADMINFLAG_KICKME;
    02112             } else {
    02113                ast_log(LOG_NOTICE, "Specified User not found!\n");
    02114             }
    02115             break;
    02116          }
    02117
    02118          dispose_conf(cnf);
    02119          cnf = NULL;
    02120       } else {
    02121          ast_log(LOG_NOTICE, "Conference Number not found\n");
    02122       }
    02123    }
    02124
    02125    LOCAL_USER_REMOVE(u);
    02126
    02127    return 0;
    02128 }
    02129
    02130 static void *recordthread(void *args)
    02131 {
    02132    struct ast_conference *cnf = args;
    02133    struct ast_frame *f=NULL;
    02134    int flags;
    02135    struct ast_filestream *s;
    02136    int res=0;
    02137
    02138    if (!cnf || !cnf->chan) {
    02139       pthread_exit(0);
    02140    }
    02141    ast_stopstream(cnf->chan);
    02142    flags = O_CREAT|O_TRUNC|O_WRONLY;
    02143    s = ast_writefile(cnf->recordingfilename, cnf->recordingformat, NULL, flags, 0, 0644);
    02144
    02145    if (s) {
    02146       cnf->recording = MEETME_RECORD_ACTIVE;
    02147       while (ast_waitfor(cnf->chan, -1) > -1) {
    02148          f = ast_read(cnf->chan);
    02149          if (!f) {
    02150             res = -1;
    02151             break;
    02152          }
    02153          if (f->frametype == AST_FRAME_VOICE) {
    02154             res = ast_writestream(s, f);
    02155             if (res) {
    02156                ast_frfree(f);
    02157                break;
    02158             }
    02159          }
    02160          ast_frfree(f);
    02161          if (cnf->recording == MEETME_RECORD_TERMINATE) {
    02162             break;
    02163          }
    02164       }
    02165       cnf->recording = MEETME_RECORD_OFF;
    02166       ast_closestream(s);
    02167    }
    02168    pthread_exit(0);
    02169 }
    02170
    02171 static void load_config(void)
    02172 {
    02173    struct ast_config *cfg;
    02174    char *val;
    02175
    02176    audio_buffers = DEFAULT_AUDIO_BUFFERS;
    02177
    02178    if (!(cfg = ast_config_load(CONFIG_FILE_NAME)))
    02179       return;
    02180
    02181    if ((val = ast_variable_retrieve(cfg, "general", "audiobuffers"))) {
    02182       if ((sscanf(val, "%30d", &audio_buffers) != 1)) {
    02183          ast_log(LOG_WARNING, "audiobuffers setting must be a number, not '%s'\n", val);
    02184          audio_buffers = DEFAULT_AUDIO_BUFFERS;
    02185       } else if ((audio_buffers < ZT_DEFAULT_NUM_BUFS) || (audio_buffers > ZT_MAX_NUM_BUFS)) {
    02186          ast_log(LOG_WARNING, "audiobuffers setting must be between %d and %d\n",
    02187             ZT_DEFAULT_NUM_BUFS, ZT_MAX_NUM_BUFS);
    02188          audio_buffers = DEFAULT_AUDIO_BUFFERS;
    02189       }
    02190       if (audio_buffers != DEFAULT_AUDIO_BUFFERS)
    02191          ast_log(LOG_NOTICE, "Audio buffers per channel set to %d\n", audio_buffers);
    02192    }
    02193
    02194    ast_config_destroy(cfg);
    02195 }
    02196
    02197 int unload_module(void)
    02198 {
    02199    int res;
    02200
    02201    res = ast_cli_unregister(&cli_show_confs);
    02202    res |= ast_cli_unregister(&cli_conf);
    02203    res |= ast_unregister_application(app3);
    02204    res |= ast_unregister_application(app2);
    02205    res |= ast_unregister_application(app);
    02206
    02207    STANDARD_HANGUP_LOCALUSERS;
    02208
    02209    return res;
    02210 }
    02211
    02212 int load_module(void)
    02213 {
    02214    int res;
    02215
    02216    load_config();
    02217
    02218    res = ast_cli_register(&cli_show_confs);
    02219    res |= ast_cli_register(&cli_conf);
    02220    res |= ast_register_application(app3, admin_exec, synopsis3, descrip3);
    02221    res |= ast_register_application(app2, count_exec, synopsis2, descrip2);
    02222    res |= ast_register_application(app, conf_exec, synopsis, descrip);
    02223
    02224    return res;
    02225 }
    02226
    02227 int reload(void)
    02228 {
    02229    load_config();
    02230
    02231    return 0;
    02232 }
    02233
    02234 char *description(void)
    02235 {
    02236    return (char *) tdesc;
    02237 }
    02238
    02239 int usecount(void)
    02240 {
    02241    int res;
    02242
    02243    STANDARD_USECOUNT(res);
    02244
    02245    return res;
    02246 }
    02247
    02248 char *key()
    02249 {
    02250    return ASTERISK_GPL_KEY;
    02251 }
    02252