Newer
Older
* Asterisk -- An open source telephony toolkit.
* Copyright (C) 1999 - 2007, Digium, Inc.
Mark Spencer
committed
* Mark Spencer <markster@digium.com>
* SLA Implementation by:
* Russell Bryant <russell@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 and Shared Line Appearances
*
* \author Mark Spencer <markster@digium.com>
* \author (SLA) Russell Bryant <russell@digium.com>
*
Kevin P. Fleming
committed
/*** MODULEINFO
<depend>zaptel</depend>
***/
Kevin P. Fleming
committed
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/zapata.h"
Kevin P. Fleming
committed
#include "asterisk/lock.h"
#include "asterisk/file.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/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"
#define SLA_CONFIG_FILE "sla.conf"
/*! each buffer is 20ms, so this is 640ms total */
#define DEFAULT_AUDIO_BUFFERS 32
/*! String format for scheduled conferences */
#define DATE_FORMAT "%Y-%m-%d %H:%M:%S"
enum {
ADMINFLAG_MUTED = (1 << 1), /*!< User is muted */
ADMINFLAG_SELFMUTED = (1 << 2), /*!< User muted self */
Russell Bryant
committed
ADMINFLAG_KICKME = (1 << 3), /*!< User has been kicked */
/*! User has requested to speak */
ADMINFLAG_T_REQUEST = (1 << 4),
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
};
#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 key defined in p() option is pressed */
CONFFLAG_KEYEXIT = (1 << 2),
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
148
149
150
/*! 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, 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),
/*! Pass DTMF through the conference */
CONFFLAG_PASS_DTMF = (1 << 25),
CONFFLAG_SLA_STATION = (1 << 26),
CONFFLAG_SLA_TRUNK = (1 << 27),
/*! If set, the user should continue in the dialplan if kicked out */
CONFFLAG_KICK_CONTINUE = (1 << 28),
CONFFLAG_DURATION_STOP = (1 << 29),
CONFFLAG_DURATION_LIMIT = (1 << 30),
};
enum {
OPT_ARG_WAITMARKED = 0,
OPT_ARG_EXITKEYS = 1,
OPT_ARG_DURATION_STOP = 2,
OPT_ARG_DURATION_LIMIT = 3,
OPT_ARG_MOH_CLASS = 4,
OPT_ARG_ARRAY_SIZE = 5,
AST_APP_OPTIONS(meetme_opts, BEGIN_OPTIONS
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('C', CONFFLAG_KICK_CONTINUE),
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('F', CONFFLAG_PASS_DTMF ),
AST_APP_OPTION('i', CONFFLAG_INTROUSER ),
AST_APP_OPTION('I', CONFFLAG_INTROUSERNOREVIEW ),
AST_APP_OPTION_ARG('M', CONFFLAG_MOH, OPT_ARG_MOH_CLASS ),
AST_APP_OPTION('m', CONFFLAG_STARTMUTED ),
AST_APP_OPTION('P', CONFFLAG_ALWAYSPROMPT ),
AST_APP_OPTION_ARG('p', CONFFLAG_KEYEXIT, OPT_ARG_EXITKEYS ),
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_OPTION_ARG('S', CONFFLAG_DURATION_STOP, OPT_ARG_DURATION_STOP),
AST_APP_OPTION_ARG('L', CONFFLAG_DURATION_LIMIT, OPT_ARG_DURATION_LIMIT),
static const char *app = "MeetMe";
static const char *app2 = "MeetMeCount";
static const char *app3 = "MeetMeAdmin";
Russell Bryant
committed
static const char *app4 = "MeetMeChannelAdmin";
static const char *slastation_app = "SLAStation";
static const char *slatrunk_app = "SLATrunk";
static const char *synopsis = "MeetMe conference bridge";
static const char *synopsis2 = "MeetMe participant count";
static const char *synopsis3 = "MeetMe conference Administration";
Russell Bryant
committed
static const char *synopsis4 = "MeetMe conference Administration (channel specific)";
static const char *slastation_synopsis = "Shared Line Appearance Station";
static const char *slatrunk_synopsis = "Shared Line Appearance Trunk";
/* Lookup RealTime conferences based on confno and current time */
static int rt_schedule;
static int fuzzystart;
static int earlyalert;
static int endalert;
/* Log participant count to the RealTime backend */
static int rt_log_members;
" 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"
" 'C' -- continue in dialplan when kicked out of 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"
" 'F' -- Pass DTMF through the 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[(<class>]\n"
" ' -- enable music on hold when the conference has a single caller.\n"
" Optionally, specify a musiconhold class to use. If one is not\n"
" provided, it will use the channel's currently set music class,\n"
" or \"default\".\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[(<keys>)]'\n"
" -- allow user to exit the conference by pressing '#' (default)\n"
" or any of the defined keys. If keys contain '*' this will override\n"
" option 's'. The key used is set to channel variable MEETME_EXIT_KEY.\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"
" 'S(x)' -- Kick the user 'x' seconds *after* he entered into the conference.\n"
" 'L(x[:y][:z])' - Limit the conference to 'x' ms. Play a warning when 'y' ms are\n"
" left. Repeat the warning every 'z' ms. The following special\n"
" variables can be used with this option:\n"
" * CONF_LIMIT_TIMEOUT_FILE File to play when time is up.\n"
" * CONF_LIMIT_WARNING_FILE File to play as warning if 'y' is defined.\n"
" The default is to say the time remaining.\n"
"";
Tilghman Lesher
committed
" 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' -- Raise one user's talk volume\n"
" 'u' -- Lower one user's listen volume\n"
" 'U' -- Raise one user's listen volume\n"
" 'v' -- Lower entire conference listening volume\n"
" 'V' -- Raise entire conference listening volume\n"
Russell Bryant
committed
static const char *descrip4 =
Tilghman Lesher
committed
" MeetMeChannelAdmin(channel,command): Run admin command for a specific\n"
Russell Bryant
committed
"channel in any coference.\n"
" 'k' -- Kick the specified user out of the conference he is in\n"
" 'm' -- Unmute the specified user\n"
" 'M' -- Mute the specified user\n"
"";
static const char *slastation_desc =
" SLAStation(<station name>):\n"
"This application should be executed by an SLA station. The argument depends\n"
"on how the call was initiated. If the phone was just taken off hook, then\n"
"the argument \"station\" should be just the station name. If the call was\n"
"initiated by pressing a line key, then the station name should be preceded\n"
"by an underscore and the trunk name associated with that line button.\n"
"For example: \"station1_line1\"."
" On exit, this application will set the variable SLASTATION_STATUS to\n"
"one of the following values:\n"
" FAILURE | CONGESTION | SUCCESS\n"
"";
static const char *slatrunk_desc =
" SLATrunk(<trunk name>[,options]):\n"
"This application should be executed by an SLA trunk on an inbound call.\n"
"The channel calling this application should correspond to the SLA trunk\n"
"with the name \"trunk\" that is being passed as an argument.\n"
" On exit, this application will set the variable SLATRUNK_STATUS to\n"
"one of the following values:\n"
" FAILURE | SUCCESS | UNANSWERED | RINGTIMEOUT\n"
" The available options are:\n"
" M[(<class>)] - Play back the specified MOH class instead of ringing\n"
#define MAX_CONFNUM 80
#define MAX_PIN 80
/*! \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[MAX_CONFNUM]; /*!< 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 */
int maxusers; /*!< Participant limit if scheduled */
int endalert; /*!< When to play conf ending message */
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 */
ast_mutex_t recordthreadlock; /*!< control threads trying to start recordthread */
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[MAX_PIN]; /*!< If protected by a PIN */
char pinadmin[MAX_PIN]; /*!< If protected by a admin PIN */
Russell Bryant
committed
char uniqueid[32];
char endtime[19]; /*!< When to end the conf if scheduled */
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);
static unsigned int conf_map[1024] = {0, };
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 */
time_t jointime; /*!< Time the user joined the conference */
time_t kicktime; /*!< Time the user will be kicked from the conference */
struct timeval start_time; /*!< Time the user entered into the conference */
long timelimit; /*!< Time limit for the user to be in the conference L(x:y:z) */
long play_warning; /*!< Play a warning when 'y' ms are left */
long warning_freq; /*!< Repeat the warning every 'z' ms */
const char *warning_sound; /*!< File to play as warning if 'y' is defined */
const char *end_sound; /*!< File to play when time is up. */
struct volume talk;
struct volume listen;
AST_LIST_ENTRY(ast_conf_user) list;
enum sla_which_trunk_refs {
ALL_TRUNK_REFS,
INACTIVE_TRUNK_REFS,
};
enum sla_trunk_state {
SLA_TRUNK_STATE_IDLE,
SLA_TRUNK_STATE_RINGING,
SLA_TRUNK_STATE_UP,
SLA_TRUNK_STATE_ONHOLD,
enum sla_hold_access {
/*! This means that any station can put it on hold, and any station
* can retrieve the call from hold. */
SLA_HOLD_OPEN,
/*! This means that only the station that put the call on hold may
* retrieve it from hold. */
SLA_HOLD_PRIVATE,
};
struct sla_trunk_ref;
struct sla_station {
AST_RWLIST_ENTRY(sla_station) entry;
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(name);
AST_STRING_FIELD(device);
AST_STRING_FIELD(autocontext);
);
AST_LIST_HEAD_NOLOCK(, sla_trunk_ref) trunks;
struct ast_dial *dial;
/*! Ring timeout for this station, for any trunk. If a ring timeout
* is set for a specific trunk on this station, that will take
* priority over this value. */
unsigned int ring_timeout;
/*! Ring delay for this station, for any trunk. If a ring delay
* is set for a specific trunk on this station, that will take
* priority over this value. */
unsigned int ring_delay;
/*! This option uses the values in the sla_hold_access enum and sets the
* access control type for hold on this station. */
unsigned int hold_access:1;
/*! Use count for inside sla_station_exec */
unsigned int ref_count;
};
struct sla_station_ref {
AST_LIST_ENTRY(sla_station_ref) entry;
struct sla_station *station;
};
struct sla_trunk {
AST_RWLIST_ENTRY(sla_trunk) entry;
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(name);
AST_STRING_FIELD(device);
AST_STRING_FIELD(autocontext);
);
AST_LIST_HEAD_NOLOCK(, sla_station_ref) stations;
/*! Number of stations that use this trunk */
unsigned int num_stations;
/*! Number of stations currently on a call with this trunk */
unsigned int active_stations;
/*! Number of stations that have this trunk on hold. */
unsigned int hold_stations;
struct ast_channel *chan;
/*! If set to 1, no station will be able to join an active call with
* this trunk. */
unsigned int barge_disabled:1;
/*! This option uses the values in the sla_hold_access enum and sets the
* access control type for hold on this trunk. */
unsigned int hold_access:1;
/*! Whether this trunk is currently on hold, meaning that once a station
* connects to it, the trunk channel needs to have UNHOLD indicated to it. */
unsigned int on_hold:1;
/*! Use count for inside sla_trunk_exec */
unsigned int ref_count;
};
struct sla_trunk_ref {
AST_LIST_ENTRY(sla_trunk_ref) entry;
struct sla_trunk *trunk;
enum sla_trunk_state state;
struct ast_channel *chan;
/*! Ring timeout to use when this trunk is ringing on this specific
* station. This takes higher priority than a ring timeout set at
* the station level. */
/*! Ring delay to use when this trunk is ringing on this specific
* station. This takes higher priority than a ring delay set at
* the station level. */
unsigned int ring_delay;
};
static AST_RWLIST_HEAD_STATIC(sla_stations, sla_station);
static AST_RWLIST_HEAD_STATIC(sla_trunks, sla_trunk);
static const char sla_registrar[] = "SLA";
/*! \brief Event types that can be queued up for the SLA thread */
/*! A station has put the call on hold */
/*! The state of a dial has changed */
SLA_EVENT_DIAL_STATE,
/*! The state of a ringing trunk has changed */
SLA_EVENT_RINGING_TRUNK,
/*! A reload of configuration has been requested */
SLA_EVENT_RELOAD,
/*! Poke the SLA thread so it can check if it can perform a reload */
SLA_EVENT_CHECK_RELOAD,
struct sla_event {
enum sla_event_type type;
struct sla_station *station;
struct sla_trunk_ref *trunk_ref;
AST_LIST_ENTRY(sla_event) entry;
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
/*! \brief A station that failed to be dialed
* \note Only used by the SLA thread. */
struct sla_failed_station {
struct sla_station *station;
struct timeval last_try;
AST_LIST_ENTRY(sla_failed_station) entry;
};
/*! \brief A trunk that is ringing */
struct sla_ringing_trunk {
struct sla_trunk *trunk;
/*! The time that this trunk started ringing */
struct timeval ring_begin;
AST_LIST_HEAD_NOLOCK(, sla_station_ref) timed_out_stations;
AST_LIST_ENTRY(sla_ringing_trunk) entry;
};
enum sla_station_hangup {
SLA_STATION_HANGUP_NORMAL,
SLA_STATION_HANGUP_TIMEOUT,
};
/*! \brief A station that is ringing */
struct sla_ringing_station {
struct sla_station *station;
/*! The time that this station started ringing */
struct timeval ring_begin;
AST_LIST_ENTRY(sla_ringing_station) entry;
};
/*!
* \brief A structure for data used by the sla thread
*/
/*! The SLA thread ID */
pthread_t thread;
ast_cond_t cond;
ast_mutex_t lock;
AST_LIST_HEAD_NOLOCK(, sla_ringing_trunk) ringing_trunks;
AST_LIST_HEAD_NOLOCK(, sla_ringing_station) ringing_stations;
AST_LIST_HEAD_NOLOCK(, sla_failed_station) failed_stations;
AST_LIST_HEAD_NOLOCK(, sla_event) event_q;
unsigned int stop:1;
/*! Attempt to handle CallerID, even though it is known not to work
* properly in some situations. */
unsigned int attempt_callerid:1;
/*! A reload has been requested */
unsigned int reload:1;
} sla = {
.thread = AST_PTHREADT_NULL,
};
/*! 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
*/
-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
{
Kevin P. Fleming
committed
/* 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)
{
/* 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)
Russell Bryant
committed
if (!ast_check_hangup(chan))
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);
/*!
* \brief Find or create a conference
*
* \param confno The conference name/number
* \param pin The regular user pin
* \param pinadmin The admin pin
* \param make Make the conf if it doesn't exist
* \param dynamic Mark the newly created conference as dynamic
* \param refcount How many references to mark on the conference
* \param chan The asterisk channel
*
* \return A pointer to the conference struct, or NULL if it wasn't found and
* make or dynamic were not set.
*/
Russell Bryant
committed
static struct ast_conference *build_conf(char *confno, char *pin, char *pinadmin, int make, int dynamic, int refcount, const struct ast_channel *chan)
Jeremy McNamara
committed
struct ast_conference *cnf;
struct zt_confinfo ztc = { 0, };
AST_LIST_LOCK(&confs);
AST_LIST_TRAVERSE(&confs, cnf, list) {
if (!strcmp(confno, cnf->confno))
break;
}
if (cnf || (!make && !dynamic))
goto cnfout;
/* Make a new one */
if (!(cnf = ast_calloc(1, sizeof(*cnf))))
goto cnfout;
ast_mutex_init(&cnf->playlock);
ast_mutex_init(&cnf->listenlock);
cnf->recordthread = AST_PTHREADT_NULL;
ast_mutex_init(&cnf->recordthreadlock);
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));
Russell Bryant
committed
ast_copy_string(cnf->uniqueid, chan->uniqueid, sizeof(cnf->uniqueid));
/* Setup a new zap conference */
ztc.confno = -1;
ztc.confmode = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON;
cnf->fd = open("/dev/zap/pseudo", O_RDWR);
if (cnf->fd < 0 || ioctl(cnf->fd, ZT_SETCONF, &ztc)) {
ast_log(LOG_WARNING, "Unable to open pseudo device\n");
if (cnf->fd >= 0)
close(cnf->fd);
ast_free(cnf);
cnf = NULL;
goto cnfout;
}
cnf->zapconf = ztc.confno;
/* Setup a new channel for playback of audio files */
cnf->chan = ast_request("zap", AST_FORMAT_SLINEAR, "pseudo", NULL);
if (cnf->chan) {
ast_set_read_format(cnf->chan, AST_FORMAT_SLINEAR);
ast_set_write_format(cnf->chan, AST_FORMAT_SLINEAR);
ztc.chan = 0;
ztc.confno = cnf->zapconf;
ztc.confmode = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON;
if (ioctl(cnf->chan->fds[0], ZT_SETCONF, &ztc)) {
ast_log(LOG_WARNING, "Error setting conference\n");
if (cnf->chan)
ast_hangup(cnf->chan);
else
close(cnf->fd);
Tilghman Lesher
committed
ast_free(cnf);
cnf = NULL;
goto cnfout;
}
/* Fill the conference struct */
cnf->start = time(NULL);
cnf->maxusers = 0x7fffffff;
cnf->isdynamic = dynamic ? 1 : 0;
ast_verb(3, "Created MeetMe conference %d for conference '%s'\n", cnf->zapconf, cnf->confno);
AST_LIST_INSERT_HEAD(&confs, cnf, list);
/* Reserve conference number in map */
if ((sscanf(cnf->confno, "%d", &confno_int) == 1) && (confno_int >= 0 && confno_int < 1024))
conf_map[confno_int] = 1;
ast_atomic_fetchadd_int(&cnf->refcount, refcount);
AST_LIST_UNLOCK(&confs);
return cnf;
static char *complete_meetmecmd(const char *line, const char *word, int pos, int state)
{
static char *cmds[] = {"concise", "lock", "unlock", "mute", "unmute", "kick", "list", NULL};
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
int len = strlen(word);
int which = 0;
struct ast_conference *cnf = NULL;
struct ast_conf_user *usr = NULL;
char *confno = NULL;
char usrno[50] = "";
char *myline, *ret = NULL;
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);
return ret;
} 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 ast_strdup("all");
which++;
AST_LIST_LOCK(&confs);
/* TODO: Find the conf number from the cmdline (ignore spaces) <- test this and make it fail-safe! */
myline = ast_strdupa(line);
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) {
snprintf(usrno, sizeof(usrno), "%d", usr->user_no);
if (!strncasecmp(word, usrno, len) && ++which > state)
break;
}
}
AST_LIST_UNLOCK(&confs);
return usr ? ast_strdup(usrno) : NULL;
} else if ( strstr(line, "list") && ( 0 == state ) )
return ast_strdup("concise");
}
return NULL;
}
static char *meetme_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
/* Process the command */
struct ast_conference *cnf;
struct ast_conf_user *user;
char *header_format = "%-14s %-14s %-10s %-8s %-8s %-6s\n";
char *data_format = "%-12.12s %4.4d %4.4s %02d:%02d:%02d %-8s %-6s\n";
char cmdline[1024] = "";
switch (cmd) {
case CLI_INIT:
e->command = "meetme";
e->usage =
"Usage: meetme (un)lock|(un)mute|kick|list [concise] <confno> <usernumber>\n"
" Executes a command for the conference or on a conferee\n";
return NULL;
case CLI_GENERATE:
return complete_meetmecmd(a->line, a->word, a->pos, a->n);
}
if (a->argc > 8)
ast_cli(a->fd, "Invalid Arguments.\n");
/* Check for length so no buffer will overflow... */
for (i = 0; i < a->argc; i++) {
if (strlen(a->argv[i]) > 100)
ast_cli(a->fd, "Invalid Arguments.\n");
if (a->argc == 1 || ( a->argc == 2 && !strcasecmp(a->argv[1], "concise") )) {
/* 'MeetMe': List all the conferences */
int concise = ( a->argc == 2 && !strcasecmp(a->argv[1], "concise") );
if (AST_LIST_EMPTY(&confs)) {
if (!concise)
ast_cli(a->fd, "No active MeetMe conferences.\n");
if (!concise)
ast_cli(a->fd, header_format, "Conf Num", "Parties", "Marked", "Activity", "Creation", "Locked");
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;