Newer
Older
* Asterisk -- An open source telephony toolkit.
* Copyright (C) 1999 - 2006, Digium, Inc.
Mark Spencer
committed
* Mark Spencer <markster@digium.com>
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
*
* \brief Meet me conference bridge
*
* \author Mark Spencer <markster@digium.com>
*
Kevin P. Fleming
committed
/*** MODULEINFO
<depend>zaptel</depend>
***/
Kevin P. Fleming
committed
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include <errno.h>
#include <sys/ioctl.h>
#include <zaptel/zaptel.h>
Kevin P. Fleming
committed
#include "asterisk/lock.h"
#include "asterisk/file.h"
#include "asterisk/logger.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/config.h"
#include "asterisk/app.h"
#include "asterisk/dsp.h"
#include "asterisk/musiconhold.h"
#include "asterisk/manager.h"
#include "asterisk/options.h"
#include "asterisk/cli.h"
#include "asterisk/say.h"
#include "asterisk/utils.h"
#include "asterisk/translate.h"
#include "asterisk/ulaw.h"
#include "enter.h"
#include "leave.h"
#define CONFIG_FILE_NAME "meetme.conf"
/*! each buffer is 20ms, so this is 640ms total */
#define DEFAULT_AUDIO_BUFFERS 32
enum {
ADMINFLAG_MUTED = (1 << 1), /*!< User is muted */
ADMINFLAG_SELFMUTED = (1 << 2), /*!< User muted self */
ADMINFLAG_KICKME = (1 << 3) /*!< User has been kicked */
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
};
#define MEETME_DELAYDETECTTALK 300
#define MEETME_DELAYDETECTENDTALK 1000
#define AST_FRAME_BITS 32
enum volume_action {
VOL_UP,
VOL_DOWN
};
enum entrance_sound {
ENTER,
LEAVE
};
enum recording_state {
MEETME_RECORD_OFF,
MEETME_RECORD_STARTED,
MEETME_RECORD_ACTIVE,
MEETME_RECORD_TERMINATE
};
#define CONF_SIZE 320
enum {
/*! user has admin access on the conference */
CONFFLAG_ADMIN = (1 << 0),
/*! If set the user can only receive audio from the conference */
CONFFLAG_MONITOR = (1 << 1),
/*! If set asterisk will exit conference when '#' is pressed */
CONFFLAG_POUNDEXIT = (1 << 2),
/*! If set asterisk will provide a menu to the user when '*' is pressed */
CONFFLAG_STARMENU = (1 << 3),
/*! If set the use can only send audio to the conference */
CONFFLAG_TALKER = (1 << 4),
/*! If set there will be no enter or leave sounds */
CONFFLAG_QUIET = (1 << 5),
/*! If set, when user joins the conference, they will be told the number
* of users that are already in */
CONFFLAG_ANNOUNCEUSERCOUNT = (1 << 6),
/*! Set to run AGI Script in Background */
CONFFLAG_AGI = (1 << 7),
/*! Set to have music on hold when user is alone in conference */
CONFFLAG_MOH = (1 << 8),
/*! If set the MeetMe will return if all marked with this flag left */
CONFFLAG_MARKEDEXIT = (1 << 9),
/*! If set, the MeetMe will wait until a marked user enters */
CONFFLAG_WAITMARKED = (1 << 10),
/*! If set, the MeetMe will exit to the specified context */
CONFFLAG_EXIT_CONTEXT = (1 << 11),
/*! If set, the user will be marked */
CONFFLAG_MARKEDUSER = (1 << 12),
/*! If set, user will be ask record name on entry of conference */
CONFFLAG_INTROUSER = (1 << 13),
/*! If set, the MeetMe will be recorded */
CONFFLAG_RECORDCONF = (1<< 14),
/*! If set, the user will be monitored if the user is talking or not */
CONFFLAG_MONITORTALKER = (1 << 15),
CONFFLAG_DYNAMIC = (1 << 16),
CONFFLAG_DYNAMICPIN = (1 << 17),
CONFFLAG_EMPTY = (1 << 18),
CONFFLAG_EMPTYNOPIN = (1 << 19),
CONFFLAG_ALWAYSPROMPT = (1 << 20),
/*! If set, treats talking users as muted users */
CONFFLAG_OPTIMIZETALKER = (1 << 21),
/*! If set, won't speak the extra prompt when the first person
* enters the conference */
CONFFLAG_NOONLYPERSON = (1 << 22),
/*! If set, user will be asked to record name on entry of conference
* without review */
Mark Spencer
committed
CONFFLAG_INTROUSERNOREVIEW = (1 << 23),
/*! If set, the user will be initially self-muted */
Mark Spencer
committed
CONFFLAG_STARTMUTED = (1 << 24),
/*! If set, the user is a shared line appearance station */
CONFFLAG_SLA_STATION = (1 << 25),
/*! If set, the user is a shared line appearance trunk */
CONFFLAG_SLA_TRUNK = (1 << 26),
/*! If set, the user has put us on hold */
CONFFLAG_HOLD = (1 << 27)
};
enum {
OPT_ARG_WAITMARKED = 0,
OPT_ARG_ARRAY_SIZE = 1,
} meetme_option_args;
AST_APP_OPTIONS(meetme_opts, {
AST_APP_OPTION('A', CONFFLAG_MARKEDUSER ),
AST_APP_OPTION('a', CONFFLAG_ADMIN ),
AST_APP_OPTION('b', CONFFLAG_AGI ),
AST_APP_OPTION('c', CONFFLAG_ANNOUNCEUSERCOUNT ),
AST_APP_OPTION('D', CONFFLAG_DYNAMICPIN ),
AST_APP_OPTION('d', CONFFLAG_DYNAMIC ),
AST_APP_OPTION('E', CONFFLAG_EMPTYNOPIN ),
AST_APP_OPTION('e', CONFFLAG_EMPTY ),
AST_APP_OPTION('i', CONFFLAG_INTROUSER ),
AST_APP_OPTION('I', CONFFLAG_INTROUSERNOREVIEW ),
AST_APP_OPTION('M', CONFFLAG_MOH ),
AST_APP_OPTION('m', CONFFLAG_STARTMUTED ),
AST_APP_OPTION('o', CONFFLAG_OPTIMIZETALKER ),
AST_APP_OPTION('P', CONFFLAG_ALWAYSPROMPT ),
AST_APP_OPTION('p', CONFFLAG_POUNDEXIT ),
AST_APP_OPTION('q', CONFFLAG_QUIET ),
AST_APP_OPTION('r', CONFFLAG_RECORDCONF ),
AST_APP_OPTION('s', CONFFLAG_STARMENU ),
AST_APP_OPTION('T', CONFFLAG_MONITORTALKER ),
AST_APP_OPTION('l', CONFFLAG_MONITOR ),
AST_APP_OPTION('t', CONFFLAG_TALKER ),
AST_APP_OPTION_ARG('w', CONFFLAG_WAITMARKED, OPT_ARG_WAITMARKED ),
AST_APP_OPTION('X', CONFFLAG_EXIT_CONTEXT ),
AST_APP_OPTION('x', CONFFLAG_MARKEDEXIT ),
AST_APP_OPTION('1', CONFFLAG_NOONLYPERSON ),
});
AST_APP_OPTIONS(sla_opts, {
/* Just a placeholder for now */
});
static const char *app = "MeetMe";
static const char *app2 = "MeetMeCount";
static const char *app3 = "MeetMeAdmin";
Russell Bryant
committed
static const char *appslas = "SLAStation";
static const char *appslat = "SLATrunk";
static const char *synopsis = "MeetMe conference bridge";
static const char *synopsis2 = "MeetMe participant count";
static const char *synopsis3 = "MeetMe conference Administration";
static const char *synopslas = "Shared Line Appearance - Station";
static const char *synopslat = "Shared Line Appearance - Trunk";
" MeetMe([confno][,[options][,pin]]): Enters the user into a specified MeetMe\n"
"conference. If the conference number is omitted, the user will be prompted\n"
"to enter one. User can exit the conference by hangup, or if the 'p' option\n"
"is specified, by pressing '#'.\n"
"Please note: The Zaptel kernel modules and at least one hardware driver (or ztdummy)\n"
" must be present for conferencing to operate properly. In addition, the chan_zap\n"
" channel driver must be loaded for the 'i' and 'r' options to operate at all.\n\n"
"The option string may contain zero or more of the following characters:\n"
" 'a' -- set admin mode\n"
" 'A' -- set marked mode\n"
" 'b' -- run AGI script specified in ${MEETME_AGI_BACKGROUND}\n"
" Default: conf-background.agi (Note: This does not work with\n"
" non-Zap channels in the same conference)\n"
" 'c' -- announce user(s) count on joining a conference\n"
" 'd' -- dynamically add conference\n"
" 'D' -- dynamically add conference, prompting for a PIN\n"
" 'e' -- select an empty conference\n"
" 'E' -- select an empty pinless conference\n"
Joshua Colp
committed
" 'i' -- announce user join/leave with review\n"
" 'I' -- announce user join/leave without review\n"
" 'l' -- set listen only mode (Listen only, no talking)\n"
" 'M' -- enable music on hold when the conference has a single caller\n"
" 'o' -- set talker optimization - treats talkers who aren't speaking as\n"
" being muted, meaning (a) No encode is done on transmission and\n"
" (b) Received audio that is not registered as talking is omitted\n"
" causing no buildup in background noise\n"
" 'p' -- allow user to exit the conference by pressing '#'\n"
" 'P' -- always prompt for the pin even if it is specified\n"
" 'q' -- quiet mode (don't play enter/leave sounds)\n"
" 'r' -- Record conference (records as ${MEETME_RECORDINGFILE}\n"
" using format ${MEETME_RECORDINGFORMAT}). Default filename is\n"
" meetme-conf-rec-${CONFNO}-${UNIQUEID} and the default format is\n"
" wav.\n"
" 's' -- Present menu (user or admin) when '*' is received ('send' to menu)\n"
" 't' -- set talk only mode. (Talk only, no listening)\n"
" 'T' -- set talker detection (sent to manager interface and meetme list)\n"
" 'w[(<secs>)]'\n"
" -- wait until the marked user enters the conference\n"
" 'x' -- close the conference when last marked user exits\n"
" 'X' -- allow user to exit the conference by entering a valid single\n"
" digit extension ${MEETME_EXIT_CONTEXT} or the current context\n"
" if that variable is not defined.\n"
" '1' -- do not play message when first person enters\n";
" MeetMeCount(confno[|var]): Plays back the number of users in the specified\n"
"MeetMe conference. If var is specified, playback will be skipped and the value\n"
"will be returned in the variable. Upon app completion, MeetMeCount will hangup\n"
"the channel, unless priority n+1 exists, in which case priority progress will\n"
"continue.\n"
"A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING FUNCTIONALITY.\n";
" MeetMeAdmin(confno,command[,user]): Run admin command for conference\n"
" 'e' -- Eject last user that joined\n"
" 'k' -- Kick one user out of conference\n"
" 'K' -- Kick all users out of conference\n"
" 'l' -- Unlock conference\n"
" 'm' -- Unmute one user\n"
" 'M' -- Mute one user\n"
" 'n' -- Unmute all users in the conference\n"
" 'N' -- Mute all non-admin users in the conference\n"
" 'r' -- Reset one user's volume settings\n"
" 'R' -- Reset all users volume settings\n"
" 's' -- Lower entire conference speaking volume\n"
" 'S' -- Raise entire conference speaking volume\n"
" 't' -- Lower one user's talk volume\n"
" 'T' -- Lower all users talk volume\n"
" 'u' -- Lower one user's listen volume\n"
" 'U' -- Lower all users listen volume\n"
" 'v' -- Lower entire conference listening volume\n"
" 'V' -- Raise entire conference listening volume\n"
Russell Bryant
committed
" SLAStation(sla[,options]): Run Shared Line Appearance for station\n"
"Runs the share line appearance for a station calling in. If there are no\n"
"other participants in the conference, the trunk is called and is dumped into\n"
"the bridge.\n";
static const char *descripslat =
Russell Bryant
committed
" SLATrunk(sla[,options]): Run Shared Line Appearance for trunk\n"
"Runs the share line appearance for a trunk calling in. If there are no\n"
"other participants in the conference, all member stations are invited into\n"
"the bridge.\n";
#define CONFIG_FILE_NAME "meetme.conf"
#define CONFIG_FILE_NAME_SLA "sla.conf"
/*! \brief The MeetMe Conference object */
struct ast_conference {
ast_mutex_t playlock; /*!< Conference specific lock (players) */
ast_mutex_t listenlock; /*!< Conference specific lock (listeners) */
char confno[AST_MAX_EXTENSION]; /*!< Conference */
struct ast_channel *chan; /*!< Announcements channel */
struct ast_channel *lchan; /*!< Listen/Record channel */
int fd; /*!< Announcements fd */
int zapconf; /*!< Zaptel Conf # */
int users; /*!< Number of active users */
int markedusers; /*!< Number of marked users */
time_t start; /*!< Start time (s) */
int refcount; /*!< reference count of usage */
unsigned int isdynamic:1; /*!< Created on the fly? */
unsigned int locked:1; /*!< Is the conference locked? */
pthread_t recordthread; /*!< thread for recording */
pthread_attr_t attr; /*!< thread attribute */
const char *recordingfilename; /*!< Filename to record the Conference into */
const char *recordingformat; /*!< Format to record the Conference in */
char pin[AST_MAX_EXTENSION]; /*!< If protected by a PIN */
char pinadmin[AST_MAX_EXTENSION]; /*!< If protected by a admin PIN */
struct ast_frame *transframe[32];
struct ast_frame *origframe;
struct ast_trans_pvt *transpath[32];
AST_LIST_HEAD_NOLOCK(, ast_conf_user) userlist;
AST_LIST_ENTRY(ast_conference) list;
};
static AST_LIST_HEAD_STATIC(confs, ast_conference);
int desired; /*!< Desired volume adjustment */
int actual; /*!< Actual volume adjustment (for channels that can't adjust) */
struct ast_conf_user {
int user_no; /*!< User Number */
int userflags; /*!< Flags as set in the conference */
int adminflags; /*!< Flags set by the Admin */
struct ast_channel *chan; /*!< Connected channel */
int talking; /*!< Is user talking */
int zapchannel; /*!< Is a Zaptel channel */
char usrvalue[50]; /*!< Custom User Value */
Mark Spencer
committed
char namerecloc[PATH_MAX]; /*!< Name Recorded file Location */
int control; /*! Queue Control for transmission */
int dtmf; /*! Queue DTMF for transmission */
time_t jointime; /*!< Time the user joined the conference */
struct volume talk;
struct volume listen;
AST_LIST_ENTRY(ast_conf_user) list;
/*! SLA station - one device in an SLA configuration */
struct ast_sla_station {
ASTOBJ_COMPONENTS(struct ast_sla_station);
char *dest;
char tech[0];
};
struct ast_sla_station_box {
ASTOBJ_CONTAINER_COMPONENTS(struct ast_sla_station);
};
/*! SLA - Shared Line Apperance object. These consist of one trunk (outbound line)
and stations that receive incoming calls and place outbound calls over the trunk
*/
struct ast_sla {
ASTOBJ_COMPONENTS (struct ast_sla);
struct ast_sla_station_box stations; /*!< Stations connected to this SLA */
char confname[80]; /*!< Name for this SLA bridge */
char trunkdest[256]; /*!< Device (channel) identifier for the trunk line */
char trunktech[20]; /*!< Technology used for the trunk (channel driver) */
};
struct ast_sla_box {
ASTOBJ_CONTAINER_COMPONENTS(struct ast_sla);
} slas;
static int audio_buffers; /*!< The number of audio buffers to be allocated on pseudo channels
/*! The number of audio buffers to be allocated on pseudo channels
* when in a conference */
static int audio_buffers;
/*! Map 'volume' levels from -5 through +5 into
* decibel (dB) settings for channel drivers
* Note: these are not a straight linear-to-dB
* conversion... the numbers have been modified
* to give the user a better level of adjustability
*/
static signed char gain_map[] = {
-15,
-13,
-10,
-6,
0,
0,
0,
6,
10,
13,
15,
Kevin P. Fleming
committed
};
static int admin_exec(struct ast_channel *chan, void *data);
static void *recordthread(void *args);
static char *istalking(int x)
{
if (x > 0)
return "(talking)";
else if (x < 0)
return "(unmonitored)";
else
return "(not talking)";
}
static int careful_write(int fd, unsigned char *data, int len, int block)
Mark Spencer
committed
int x;
if (block) {
x = ZT_IOMUX_WRITE | ZT_IOMUX_SIGEVENT;
res = ioctl(fd, ZT_IOMUX, &x);
} else
res = 0;
Mark Spencer
committed
if (res >= 0)
res = write(fd, data, len);
if (res < 1) {
if (errno != EAGAIN) {
ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
return -1;
} else
return 0;
}
len -= res;
data += res;
}
Kevin P. Fleming
committed
static int set_talk_volume(struct ast_conf_user *user, int volume)
Kevin P. Fleming
committed
{
signed char gain_adjust;
/* attempt to make the adjustment in the channel driver;
if successful, don't adjust in the frame reading routine
*/
gain_adjust = gain_map[volume + 5];
Kevin P. Fleming
committed
return ast_channel_setoption(user->chan, AST_OPTION_RXGAIN, &gain_adjust, sizeof(gain_adjust), 0);
Kevin P. Fleming
committed
}
static int set_listen_volume(struct ast_conf_user *user, int volume)
{
signed char gain_adjust;
/* attempt to make the adjustment in the channel driver;
if successful, don't adjust in the frame reading routine
*/
gain_adjust = gain_map[volume + 5];
return ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &gain_adjust, sizeof(gain_adjust), 0);
}
static void tweak_volume(struct volume *vol, enum volume_action action)
Kevin P. Fleming
committed
{
switch (action) {
case VOL_UP:
Kevin P. Fleming
committed
case 5:
break;
case 0:
Kevin P. Fleming
committed
break;
case -2:
Kevin P. Fleming
committed
break;
default:
Kevin P. Fleming
committed
break;
}
break;
case VOL_DOWN:
Kevin P. Fleming
committed
case -5:
break;
case 2:
Kevin P. Fleming
committed
break;
case 0:
Kevin P. Fleming
committed
break;
default:
Kevin P. Fleming
committed
break;
}
}
}
static void tweak_talk_volume(struct ast_conf_user *user, enum volume_action action)
{
tweak_volume(&user->talk, action);
/* attempt to make the adjustment in the channel driver;
if successful, don't adjust in the frame reading routine
*/
if (!set_talk_volume(user, user->talk.desired))
user->talk.actual = 0;
else
user->talk.actual = user->talk.desired;
}
static void tweak_listen_volume(struct ast_conf_user *user, enum volume_action action)
{
tweak_volume(&user->listen, action);
Kevin P. Fleming
committed
/* attempt to make the adjustment in the channel driver;
if successful, don't adjust in the frame reading routine
*/
if (!set_listen_volume(user, user->listen.desired))
user->listen.actual = 0;
Kevin P. Fleming
committed
else
user->listen.actual = user->listen.desired;
Kevin P. Fleming
committed
}
static void reset_volumes(struct ast_conf_user *user)
{
signed char zero_volume = 0;
ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0);
ast_channel_setoption(user->chan, AST_OPTION_RXGAIN, &zero_volume, sizeof(zero_volume), 0);
}
static void conf_play(struct ast_channel *chan, struct ast_conference *conf, enum entrance_sound sound)
if (!chan->_softhangup)
res = ast_autoservice_start(chan);
AST_LIST_LOCK(&confs);
switch(sound) {
case ENTER:
data = enter;
len = sizeof(enter);
break;
case LEAVE:
data = leave;
len = sizeof(leave);
break;
default:
data = NULL;
len = 0;
}
careful_write(conf->fd, data, len, 1);
AST_LIST_UNLOCK(&confs);
if (!res)
ast_autoservice_stop(chan);
static void station_destroy(struct ast_sla_station *station)
{
free(station);
}
static void sla_destroy(struct ast_sla *sla)
{
ASTOBJ_CONTAINER_DESTROYALL(&sla->stations, station_destroy);
ASTOBJ_CONTAINER_DESTROY(&sla->stations);
free(sla);
}
static struct ast_conference *build_conf(char *confno, char *pin, char *pinadmin, int make, int dynamic, int refcount)
Jeremy McNamara
committed
struct ast_conference *cnf;
AST_LIST_LOCK(&confs);
AST_LIST_TRAVERSE(&confs, cnf, list) {
if (!strcmp(confno, cnf->confno))
break;
}
Jeremy McNamara
committed
if (!cnf && (make || dynamic)) {
if ((cnf = ast_calloc(1, sizeof(*cnf)))) {
ast_mutex_init(&cnf->playlock);
ast_mutex_init(&cnf->listenlock);
ast_copy_string(cnf->confno, confno, sizeof(cnf->confno));
ast_copy_string(cnf->pin, pin, sizeof(cnf->pin));
ast_copy_string(cnf->pinadmin, pinadmin, sizeof(cnf->pinadmin));
cnf->chan = ast_request("zap", AST_FORMAT_SLINEAR, "pseudo", NULL);
Mark Spencer
committed
if (cnf->chan) {
ast_set_read_format(cnf->chan, AST_FORMAT_SLINEAR);
ast_set_write_format(cnf->chan, AST_FORMAT_SLINEAR);
Mark Spencer
committed
cnf->fd = cnf->chan->fds[0]; /* for use by conf_play() */
} else {
ast_log(LOG_WARNING, "Unable to open pseudo channel - trying device\n");
cnf->fd = open("/dev/zap/pseudo", O_RDWR);
if (cnf->fd < 0) {
ast_log(LOG_WARNING, "Unable to open pseudo device\n");
free(cnf);
cnf = NULL;
goto cnfout;
}
}
memset(&ztc, 0, sizeof(ztc));
/* Setup a new zap conference */
Jeremy McNamara
committed
ztc.chan = 0;
ztc.confmode = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON;
ast_log(LOG_WARNING, "Error setting conference\n");
Mark Spencer
committed
if (cnf->chan)
ast_hangup(cnf->chan);
else
close(cnf->fd);
free(cnf);
cnf = NULL;
goto cnfout;
}
cnf->lchan = ast_request("zap", AST_FORMAT_SLINEAR, "pseudo", NULL);
if (cnf->lchan) {
ast_set_read_format(cnf->lchan, AST_FORMAT_SLINEAR);
ast_set_write_format(cnf->lchan, AST_FORMAT_SLINEAR);
ztc.chan = 0;
ztc.confmode = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON;
if (ioctl(cnf->lchan->fds[0], ZT_SETCONF, &ztc)) {
ast_log(LOG_WARNING, "Error setting conference\n");
ast_hangup(cnf->lchan);
cnf->lchan = NULL;
}
}
cnf->start = time(NULL);
cnf->zapconf = ztc.confno;
cnf->isdynamic = dynamic ? 1 : 0;
ast_verbose(VERBOSE_PREFIX_3 "Created MeetMe conference %d for conference '%s'\n", cnf->zapconf, cnf->confno);
AST_LIST_INSERT_HEAD(&confs, cnf, list);
manager_event(EVENT_FLAG_CALL, "MeetmeStart", "Meetme: %s\r\n", cnf->confno);
if (cnf){
cnf->refcount += refcount;
}
AST_LIST_UNLOCK(&confs);
/*! \brief CLI command for showing SLAs */
static int sla_show(int fd, int argc, char *argv[])
{
struct ast_sla *sla;
if (argc != 2)
return RESULT_SHOWUSAGE;
ast_cli(fd, "Shared line appearances:\n");
ASTOBJ_CONTAINER_TRAVERSE(&slas, 1, {
ASTOBJ_RDLOCK(iterator);
ast_cli(fd, "SLA %s\n", iterator->name);
if (ast_strlen_zero(iterator->trunkdest) || ast_strlen_zero(iterator->trunktech))
ast_cli(fd, " Trunk => <unspecified>\n");
ast_cli(fd, " Trunk => %s/%s\n", iterator->trunktech, iterator->trunkdest);
sla = iterator;
ASTOBJ_CONTAINER_TRAVERSE(&sla->stations, 1, {
ast_cli(fd, " Station: %s/%s\n", iterator->tech, iterator->dest);
});
ASTOBJ_UNLOCK(iterator);
});
return RESULT_SUCCESS;
}
static int meetme_cmd(int fd, int argc, char **argv)
/* Process the command */
struct ast_conference *cnf;
struct ast_conf_user *user;
char *header_format = "%-14s %-14s %-10s %-8s %-8s\n";
char *data_format = "%-12.12s %4.4d %4.4s %02d:%02d:%02d %-8s\n";
char cmdline[1024] = "";
if (argc > 8)
ast_cli(fd, "Invalid Arguments.\n");
/* Check for length so no buffer will overflow... */
for (i = 0; i < argc; i++) {
if (strlen(argv[i]) > 100)
ast_cli(fd, "Invalid Arguments.\n");
}
if (argc == 1) {
/* 'MeetMe': List all the conferences */
if (AST_LIST_EMPTY(&confs)) {
ast_cli(fd, "No active MeetMe conferences.\n");
return RESULT_SUCCESS;
}
ast_cli(fd, header_format, "Conf Num", "Parties", "Marked", "Activity", "Creation");
AST_LIST_TRAVERSE(&confs, cnf, list) {
else
snprintf(cmdline, sizeof(cmdline), "%4.4d", cnf->markedusers);
hr = (now - cnf->start) / 3600;
min = ((now - cnf->start) % 3600) / 60;
sec = (now - cnf->start) % 60;
ast_cli(fd, data_format, cnf->confno, cnf->users, cmdline, hr, min, sec, cnf->isdynamic ? "Dynamic" : "Static");
Jeremy McNamara
committed
total += cnf->users;
}
ast_cli(fd, "* Total number of MeetMe users: %d\n", total);
return RESULT_SUCCESS;
}
if (argc < 3)
return RESULT_SHOWUSAGE;
ast_copy_string(cmdline, argv[2], sizeof(cmdline)); /* Argv 2: conference number */
if (strstr(argv[1], "lock")) {
if (strcmp(argv[1], "lock") == 0) {
/* Lock */
strncat(cmdline, "|L", sizeof(cmdline) - strlen(cmdline) - 1);
} else {
/* Unlock */
strncat(cmdline, "|l", sizeof(cmdline) - strlen(cmdline) - 1);
} else if (strstr(argv[1], "mute")) {
if (argc < 4)
return RESULT_SHOWUSAGE;
if (strcmp(argv[1], "mute") == 0) {
/* Mute */
strncat(cmdline, "|N", sizeof(cmdline) - strlen(cmdline) - 1);
} else {
strncat(cmdline, "|M|", sizeof(cmdline) - strlen(cmdline) - 1);
strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
}
} else {
/* Unmute */
strncat(cmdline, "|n", sizeof(cmdline) - strlen(cmdline) - 1);
} else {
strncat(cmdline, "|m|", sizeof(cmdline) - strlen(cmdline) - 1);
strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
}
}
} else if (strcmp(argv[1], "kick") == 0) {
if (argc < 4)
return RESULT_SHOWUSAGE;
if (strcmp(argv[3], "all") == 0) {
/* Kick all */
strncat(cmdline, "|K", sizeof(cmdline) - strlen(cmdline) - 1);
} else {
/* Kick a single user */
strncat(cmdline, "|k|", sizeof(cmdline) - strlen(cmdline) - 1);
strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
}
} else if(strcmp(argv[1], "list") == 0) {
int concise = ( 4 == argc && ( !strcasecmp(argv[3], "concise") ) );
/* List all the users in a conference */
if (AST_LIST_EMPTY(&confs)) {
if ( !concise )
ast_cli(fd, "No active conferences.\n");
return RESULT_SUCCESS;
}
/* Find the right conference */
AST_LIST_TRAVERSE(&confs, cnf, list) {
if (strcmp(cnf->confno, argv[2]) == 0)
break;
}
if (!cnf) {
if ( !concise )
ast_cli(fd, "No such conference: %s.\n",argv[2]);
return RESULT_SUCCESS;
}
/* Show all the users */
AST_LIST_TRAVERSE(&cnf->userlist, user, list) {
now = time(NULL);
hr = (now - user->jointime) / 3600;
min = ((now - user->jointime) % 3600) / 60;
sec = (now - user->jointime) % 60;
Mark Spencer
committed
ast_cli(fd, "User #: %-2.2d %12.12s %-20.20s Channel: %s %s %s %s %s %s %02d:%02d:%02d\n",
user->user_no,
S_OR(user->chan->cid.cid_num, "<unknown>"),
S_OR(user->chan->cid.cid_name, "<no name>"),
user->chan->name,
user->userflags & CONFFLAG_ADMIN ? "(Admin)" : "",
user->userflags & CONFFLAG_MONITOR ? "(Listen only)" : "",
user->adminflags & ADMINFLAG_MUTED ? "(Admin Muted)" : user->adminflags & ADMINFLAG_SELFMUTED ? "(Muted)" : "",
Mark Spencer
committed
istalking(user->talking),
user->userflags & CONFFLAG_HOLD ? " (On Hold) " : "", hr, min, sec);
else
ast_cli(fd, "%d!%s!%s!%s!%s!%s!%s!%d!%02d:%02d:%02d\n",
user->user_no,
S_OR(user->chan->cid.cid_num, ""),
S_OR(user->chan->cid.cid_name, ""),
user->chan->name,
user->userflags & CONFFLAG_ADMIN ? "1" : "",
user->userflags & CONFFLAG_MONITOR ? "1" : "",
user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED) ? "1" : "",
user->talking, hr, min, sec);
if ( !concise )
ast_cli(fd,"%d users in that conference.\n",cnf->users);
return RESULT_SUCCESS;
} else
return RESULT_SHOWUSAGE;
if (option_debug)
ast_log(LOG_DEBUG, "Cmdline: %s\n", cmdline);
admin_exec(NULL, cmdline);
static char *complete_meetmecmd(const char *line, const char *word, int pos, int state)
{
static char *cmds[] = {"lock", "unlock", "mute", "unmute", "kick", "list", NULL};
int len = strlen(word);
int which = 0;
struct ast_conference *cnf = NULL;
struct ast_conf_user *usr = NULL;
char *confno = NULL;
char usrno[50] = "";
if (pos == 1) { /* Command */
return ast_cli_complete(word, cmds, state);
} else if (pos == 2) { /* Conference Number */
AST_LIST_LOCK(&confs);
AST_LIST_TRAVERSE(&confs, cnf, list) {
if (!strncasecmp(word, cnf->confno, len) && ++which > state) {
ret = cnf->confno;
break;
ret = ast_strdup(ret); /* dup before releasing the lock */
AST_LIST_UNLOCK(&confs);
} else if (pos == 3) {
/* User Number || Conf Command option*/
if (strstr(line, "mute") || strstr(line, "kick")) {
if (state == 0 && (strstr(line, "kick") || strstr(line,"mute")) && !strncasecmp(word, "all", len))
return strdup("all");
which++;
AST_LIST_LOCK(&confs);
/* TODO: Find the conf number from the cmdline (ignore spaces) <- test this and make it fail-safe! */
if (strsep(&myline, " ") && strsep(&myline, " ") && !confno) {
while((confno = strsep(&myline, " ")) && (strcmp(confno, " ") == 0))
;
}
AST_LIST_TRAVERSE(&confs, cnf, list) {
if (!strcmp(confno, cnf->confno))
break;
if (cnf) {
/* Search for the user */
AST_LIST_TRAVERSE(&cnf->userlist, usr, list) {
Kevin P. Fleming
committed
snprintf(usrno, sizeof(usrno), "%d", usr->user_no);
if (!strncasecmp(word, usrno, len) && ++which > state)
break;
AST_LIST_UNLOCK(&confs);
return usr ? strdup(usrno) : NULL;
} else if ( strstr(line, "list") && ( 0 == state ) )
return strdup("concise");
return NULL;
}
static char meetme_usage[] =
"Usage: meetme (un)lock|(un)mute|kick|list [concise] <confno> <usernumber>\n"
" Executes a command for the conference or on a conferee\n";
static char sla_show_usage[] =
"Usage: sla show\n"
" Lists status of all shared line appearances\n";
static struct ast_cli_entry cli_meetme[] = {
{ { "sla", "show", NULL },
sla_show, "Show status of Shared Line Appearances",
sla_show_usage, NULL },
{ { "meetme", NULL, NULL },
meetme_cmd, "Execute a command on a conference or conferee",
meetme_usage, complete_meetmecmd },
};
static void conf_flush(int fd, struct ast_channel *chan)
/* read any frames that may be waiting on the channel
and throw them away
*/
if (chan) {
struct ast_frame *f;
/* when no frames are available, this will wait
for 1 millisecond maximum
*/
while (ast_waitfor(chan, 1)) {
f = ast_read(chan);
if (f)
ast_frfree(f);
else /* channel was hung up or something else happened */
break;
}
}
/* flush any data sitting in the pseudo channel */
if (ioctl(fd, ZT_FLUSH, &x))
ast_log(LOG_WARNING, "Error flushing channel\n");
Kevin P. Fleming
committed
/* Remove the conference from the list and free it.
We assume that this was called while holding conflock. */
static int conf_free(struct ast_conference *conf)
{
AST_LIST_REMOVE(&confs, conf, list);
manager_event(EVENT_FLAG_CALL, "MeetmeEnd", "Meetme: %s\r\n", conf->confno);
Kevin P. Fleming
committed
if (conf->recording == MEETME_RECORD_ACTIVE) {
conf->recording = MEETME_RECORD_TERMINATE;
AST_LIST_UNLOCK(&confs);
Kevin P. Fleming
committed
while (1) {
AST_LIST_LOCK(&confs);
Kevin P. Fleming
committed
if (conf->recording == MEETME_RECORD_OFF)
break;
AST_LIST_UNLOCK(&confs);
Kevin P. Fleming
committed
}
}
for (x=0;x<AST_FRAME_BITS;x++) {
if (conf->transframe[x])
ast_frfree(conf->transframe[x]);
if (conf->transpath[x])
ast_translator_free_path(conf->transpath[x]);
}
if (conf->origframe)
ast_frfree(conf->origframe);
if (conf->lchan)
ast_hangup(conf->lchan);
Kevin P. Fleming
committed
if (conf->chan)
ast_hangup(conf->chan);
else
close(conf->fd);
free(conf);
return 0;
}
Mark Spencer
committed
static void conf_queue_dtmf(struct ast_conference *conf, int digit)
{
struct ast_conf_user *user;
AST_LIST_TRAVERSE(&conf->userlist, user, list) {
user->dtmf = digit;
}
}
static void conf_queue_control(struct ast_conference *conf, int control)
{
struct ast_conf_user *user;
AST_LIST_TRAVERSE(&conf->userlist, user, list) {
user->control = control;
}
}