Skip to content
Snippets Groups Projects
app_meetme.c 91.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    /*
    
     * Asterisk -- An open source telephony toolkit.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Copyright (C) 1999 - 2006, Digium, Inc.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * 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.
     *
    
    Mark Spencer's avatar
    Mark Spencer committed
     * 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>
    
    Russell Bryant's avatar
    Russell Bryant committed
     * \ingroup applications
    
    #include "asterisk.h"
    
    ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
    
    
    #include <stdlib.h>
    
    #include <stdio.h>
    
    #include <string.h>
    
    #include <unistd.h>
    
    #include <errno.h>
    #include <sys/ioctl.h>
    
    #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"
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include "asterisk/astobj.h"
    
    Olle Johansson's avatar
    Olle Johansson committed
    #include "asterisk/devicestate.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 */
    
    };
    
    #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 */
    
    	CONFFLAG_INTROUSERNOREVIEW = (1 << 23),
    
    	/*! If set, the user will be initially self-muted */
    
    	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 ),
    });
    
    Mark Spencer's avatar
    Mark Spencer committed
    AST_APP_OPTIONS(sla_opts, {
    	/* Just a placeholder for now */
    });
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    static const char *app = "MeetMe";
    static const char *app2 = "MeetMeCount";
    static const char *app3 = "MeetMeAdmin";
    
    static const char *appslas = "SLAStation";
    static const char *appslat = "SLATrunk";
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    static const char *synopsis = "MeetMe conference bridge";
    static const char *synopsis2 = "MeetMe participant count";
    static const char *synopsis3 = "MeetMe conference Administration";
    
    Mark Spencer's avatar
    Mark Spencer committed
    static const char *synopslas = "Shared Line Appearance - Station";
    static const char *synopslat = "Shared Line Appearance - Trunk";
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    static const char *descrip =
    
    "  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"
    
    Mark Spencer's avatar
    Mark Spencer committed
    "The option string may contain zero or more of the following characters:\n"
    
    Russell Bryant's avatar
    Russell Bryant committed
    "      '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"
    
    Russell Bryant's avatar
    Russell Bryant committed
    "      '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"
    
    "      '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' -- set initially muted\n"
    
    Russell Bryant's avatar
    Russell Bryant committed
    "      '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"
    
    Russell Bryant's avatar
    Russell Bryant committed
    "      '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"
    
    Russell Bryant's avatar
    Russell Bryant committed
    "      '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"
    
    Russell Bryant's avatar
    Russell Bryant committed
    "      '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";
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    static const char *descrip2 =
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming 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";
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    static const char *descrip3 = 
    
    "  MeetMeAdmin(confno,command[,user]): Run admin command for conference\n"
    
    "      'e' -- Eject last user that joined\n"
    
    Russell Bryant's avatar
    Russell Bryant committed
    "      'k' -- Kick one user out of conference\n"
    "      'K' -- Kick all users out of conference\n"
    
    "      'l' -- Unlock conference\n"
    
    Russell Bryant's avatar
    Russell Bryant committed
    "      'L' -- Lock 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"
    
    Mark Spencer's avatar
    Mark Spencer committed
    static const char *descripslas =
    
    "  SLAStation(sla[,options]): Run Shared Line Appearance for station\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    "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 =
    
    "  SLATrunk(sla[,options]): Run Shared Line Appearance for trunk\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    "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 */
    
    	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 */
    
    Olle Johansson's avatar
    Olle Johansson committed
    	enum recording_state recording:2;       /*!< recording status */
    
    	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);
    
    struct volume {
    
    	int desired;                            /*!< Desired volume adjustment */
    	int actual;                             /*!< Actual volume adjustment (for channels that can't adjust) */
    
    	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 */
    
    	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 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    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 
    */
    
    Mark Spencer's avatar
    Mark Spencer committed
    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) */
    
    Mark Spencer's avatar
    Mark Spencer committed
    };
    
    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
    
    Mark Spencer's avatar
    Mark Spencer committed
    						   when in a conference
    						*/
    
    /*! 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,
    
    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's avatar
    Mark Spencer committed
    {
    	int res;
    
    		if (block) {
    			x = ZT_IOMUX_WRITE | ZT_IOMUX_SIGEVENT;
    			res = ioctl(fd, ZT_IOMUX, &x);
    		} else
    			res = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		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;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    
    static int set_talk_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_RXGAIN, &gain_adjust, sizeof(gain_adjust), 0);
    
    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)
    
    		switch (vol->desired) {
    
    			vol->desired = 2;
    
    			vol->desired = 0;
    
    			vol->desired++;
    
    		switch (vol->desired) {
    
    			vol->desired = 0;
    
    			vol->desired = -2;
    
    			vol->desired--;
    
    }
    
    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);
    
    	/* 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;
    
    		user->listen.actual = user->listen.desired;
    
    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)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	unsigned char *data;
    	int len;
    
    	if (!chan->_softhangup)
    		res = ast_autoservice_start(chan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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);
    
    	if (!res) 
    		ast_autoservice_stop(chan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    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)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct zt_confinfo ztc;
    
    	AST_LIST_TRAVERSE(&confs, cnf, list) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (!strcmp(confno, cnf->confno)) 
    			break;
    	}
    
    		/* Make a new one */
    
    		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->markedusers = 0;
    
    			cnf->chan = ast_request("zap", AST_FORMAT_SLINEAR, "pseudo", NULL);
    
    				ast_set_read_format(cnf->chan, AST_FORMAT_SLINEAR);
    				ast_set_write_format(cnf->chan, AST_FORMAT_SLINEAR);
    
    				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;
    				}
    
    Mark Spencer's avatar
    Mark Spencer committed
    			}
    			memset(&ztc, 0, sizeof(ztc));
    			/* Setup a new zap conference */
    
    Mark Spencer's avatar
    Mark Spencer committed
    			ztc.confno = -1;
    
    			ztc.confmode = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON;
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if (ioctl(cnf->fd, ZT_SETCONF, &ztc)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				ast_log(LOG_WARNING, "Error setting conference\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    				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;
    				}
    			}
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* Fill the conference struct */
    
    Mark Spencer's avatar
    Mark Spencer committed
    			cnf->start = time(NULL);
    			cnf->zapconf = ztc.confno;
    
    			cnf->isdynamic = dynamic ? 1 : 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if (option_verbose > 2)
    
    				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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	if (cnf){ 
    		cnf->refcount += refcount;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return cnf;
    }
    
    
    /*! \brief CLI command for showing SLAs */
    
    Mark Spencer's avatar
    Mark Spencer committed
    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");
    
    Mark Spencer's avatar
    Mark Spencer committed
    		else
    
    			ast_cli(fd, "   Trunk => %s/%s\n", iterator->trunktech, iterator->trunkdest);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		sla = iterator;
    		ASTOBJ_CONTAINER_TRAVERSE(&sla->stations, 1, {
    
    			ast_cli(fd, "   Station: %s/%s\n", iterator->tech, iterator->dest);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		});
    		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;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int hr, min, sec;
    
    	int i = 0, total = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	time_t now;
    
    	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 */	
    
    		now = time(NULL);
    
    		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) {
    
    			if (cnf->markedusers == 0)
    
    				strcpy(cmdline, "N/A ");
    
    			else 
    				snprintf(cmdline, sizeof(cmdline), "%4.4d", cnf->markedusers);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			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");
    
    			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);
    
    			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 */
    
    			if (strcmp(argv[3], "all") == 0) {
    
    				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);
    			}
    
    			if (strcmp(argv[3], "all") == 0) {
    
    				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 ( !concise )
    				ast_cli(fd, "No such conference: %s.\n",argv[2]);
    
    		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;
    
    				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)" : "",
    
    					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] = "";
    
    	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 */
    
    		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 strdup("all");
    			which++;
    
    			/* TODO: Find the conf number from the cmdline (ignore spaces) <- test this and make it fail-safe! */
    
    Mark Spencer's avatar
    Mark Spencer committed
    			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) {
    
    					if (!strncasecmp(word, usrno, len) && ++which > state)
    						break;
    
    			return usr ? strdup(usrno) : NULL;
    
    		} else if ( strstr(line, "list") && ( 0 == state ) )
    			return strdup("concise");
    
    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 },
    };
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    static void conf_flush(int fd, struct ast_channel *chan)
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    	/* 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;
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		}
    	}
    
    	/* flush any data sitting in the pseudo channel */
    
    	x = ZT_FLUSH_ALL;
    
    	if (ioctl(fd, ZT_FLUSH, &x))
    
    		ast_log(LOG_WARNING, "Error flushing channel\n");
    
    /* 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);
    
    
    	if (conf->recording == MEETME_RECORD_ACTIVE) {
    		conf->recording = MEETME_RECORD_TERMINATE;
    
    	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]);
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (conf->origframe)
    		ast_frfree(conf->origframe);
    
    	if (conf->lchan)
    		ast_hangup(conf->lchan);
    
    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;
    	}
    }