Skip to content
Snippets Groups Projects
app_meetme.c 167 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 - 2007, Digium, Inc.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * 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.
     *
    
    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 and Shared Line Appearances
    
     *
     * \author Mark Spencer <markster@digium.com>
    
     * \author (SLA) Russell Bryant <russell@digium.com>
    
    Russell Bryant's avatar
    Russell Bryant committed
     * \ingroup applications
    
    #include "asterisk.h"
    
    ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
    
    
    #include "asterisk/zapata.h"
    
    #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"
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include "asterisk/astobj.h"
    
    Olle Johansson's avatar
    Olle Johansson committed
    #include "asterisk/devicestate.h"
    
    #include "asterisk/dial.h"
    
    #include "asterisk/causes.h"
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    #include "asterisk/paths.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"
    
    
    	ADMINFLAG_MUTED =     (1 << 1), /*!< User is muted */
    	ADMINFLAG_SELFMUTED = (1 << 2), /*!< User muted self */
    
    	ADMINFLAG_KICKME =    (1 << 3),  /*!< User has been kicked */
    	/*! User has requested to speak */
    	ADMINFLAG_T_REQUEST = (1 << 4),
    
    };
    
    #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),
    
    	/*! 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 */
    
    	CONFFLAG_INTROUSERNOREVIEW = (1 << 23),
    
    	/*! If set, the user will be initially self-muted */
    
    	/*! 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_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),
    
    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 *app4 = "MeetMeChannelAdmin";
    
    static const char *slastation_app = "SLAStation";
    static const char *slatrunk_app = "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";
    
    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;
    
    
    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"
    
    "      '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"
    
    "      '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"
    
    "      '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"
    
    Russell Bryant's avatar
    Russell Bryant committed
    "      '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"
    "      '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"
    "";
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    static const char *descrip2 =
    
    "  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' -- 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"
    
    "  MeetMeChannelAdmin(channel,command): Run admin command for a specific\n"
    
    "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 */
    
    	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 */
    
    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 */
    
    	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 */
    
    	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, };
    
    
    struct volume {
    
    	int desired;                            /*!< Desired volume adjustment */
    	int actual;                             /*!< Actual volume adjustment (for channels that can't adjust) */
    
    /*! \brief The MeetMe User object */
    
    	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 */
    
    	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,
    
    	SLA_TRUNK_STATE_ONHOLD_BYME,
    
    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;
    
    	unsigned int ring_timeout;
    
    	/*! 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. */
    
    	unsigned int ring_timeout;
    
    	/*! 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 */
    
    enum sla_event_type {
    
    	/*! 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;
    
    /*! \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
     */
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    static char const gain_map[] = {
    
    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)
    
    	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)
    {
    
    	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;
    
    		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);
    
    /*!
     * \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.
     */
    
    static struct ast_conference *build_conf(char *confno, char *pin, char *pinadmin, int make, int dynamic, int refcount, const struct ast_channel *chan)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct zt_confinfo ztc = { 0, };
    
    	int confno_int = 0;
    
    	AST_LIST_TRAVERSE(&confs, cnf, list) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		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));
    
    	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);
    
    		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);
    
    
    			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};
    
    
    	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;
    
    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;
    
    Joshua Colp's avatar
    Joshua Colp committed
    	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") );
    
    		now = time(NULL);
    
    		AST_LIST_LOCK(&confs);
    
    		if (AST_LIST_EMPTY(&confs)) {
    
    			if (!concise)
    				ast_cli(a->fd, "No active MeetMe conferences.\n");
    
    			AST_LIST_UNLOCK(&confs);
    
    			return CLI_SUCCESS;
    
    		if (!concise)
    			ast_cli(a->fd, header_format, "Conf Num", "Parties", "Marked", "Activity", "Creation", "Locked");
    
    		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;