2 * Asterisk -- A telephony toolkit for Linux.
4 * Meet me conference bridge
6 * Copyright (C) 1999, Mark Spencer
8 * Mark Spencer <markster@linux-support.net>
10 * This program is free software, distributed under the terms of
11 * the GNU General Public License
14 #include <asterisk/lock.h>
15 #include <asterisk/file.h>
16 #include <asterisk/logger.h>
17 #include <asterisk/channel.h>
18 #include <asterisk/pbx.h>
19 #include <asterisk/module.h>
20 #include <asterisk/config.h>
21 #include <asterisk/app.h>
22 #include <asterisk/musiconhold.h>
23 #include <asterisk/options.h>
24 #include <asterisk/cli.h>
25 #include <asterisk/say.h>
31 #include <sys/ioctl.h>
34 #include <linux/zaptel.h>
36 static char *tdesc = "Simple MeetMe conference bridge";
38 static char *app = "MeetMe";
39 static char *app2 = "MeetMeCount";
41 static char *synopsis = "Simple MeetMe conference bridge";
42 static char *synopsis2 = "MeetMe participant count";
44 static char *descrip =
45 " MeetMe(confno[|options]): Enters the user into a specified MeetMe conference.\n"
46 "If the conference number is omitted, the user will be prompted to enter\n"
47 "one. This application always returns -1. A ZAPTEL INTERFACE MUST BE INSTALLED\n"
48 "FOR CONFERENCING TO WORK!\n\n"
50 "The option string may contain zero or more of the following characters:\n"
51 " 'm' -- set monitor only mode (Listen only, no talking\n"
52 " 't' -- set talk only mode. (Talk only, no listening)\n"
53 " 'p' -- allow user to exit the conference by pressing '#'\n"
54 " 'd' -- dynamically add conference\n"
55 " 'v' -- video mode\n"
56 " 'q' -- quiet mode (don't play enter/leave sounds)\n"
57 " 'M' -- enable music on hold when the conference has a single caller\n"
58 " 'b' -- run AGI script specified in ${MEETME_AGI_BACKGROUND}\n"
59 " Default: conf-background.agi\n"
60 " (Note: This does not work with non-Zap channels in the same conference)\n"
61 " Not implemented yet:\n"
62 " 's' -- send user to admin/user menu if '*' is received\n"
63 " 'a' -- set admin mode\n";
65 static char *descrip2 =
66 " MeetMeCount(confno[|var]): Plays back the number of users in the specifiedi\n"
67 "MeetMe conference. If var is specified, playback will be skipped and the value\n"
68 "will be returned in the variable. Returns 0 on success or -1 on a hangup.\n"
69 "A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING FUNCTIONALITY.\n";
75 static struct ast_conference {
76 char confno[AST_MAX_EXTENSION]; /* Conference */
77 int fd; /* Announcements fd */
78 int zapconf; /* Zaptel Conf # */
79 int users; /* Number of active users */
80 time_t start; /* Start time (s) */
81 int isdynamic; /* Created on the fly? */
82 char pin[AST_MAX_EXTENSION]; /* If protected by a PIN */
83 struct ast_conference *next;
86 static ast_mutex_t conflock = AST_MUTEX_INITIALIZER;
96 #define CONFFLAG_ADMIN (1 << 1) /* If set the user has admin access on the conference */
97 #define CONFFLAG_MONITOR (1 << 2) /* If set the user can only receive audio from the conference */
98 #define CONFFLAG_POUNDEXIT (1 << 3) /* If set asterisk will exit conference when '#' is pressed */
99 #define CONFFLAG_STARMENU (1 << 4) /* If set asterisk will provide a menu to the user what '*' is pressed */
100 #define CONFFLAG_TALKER (1 << 5) /* If set the use can only send audio to the conference */
101 #define CONFFLAG_QUIET (1 << 6) /* If set there will be no enter or leave sounds */
102 #define CONFFLAG_VIDEO (1 << 7) /* Set to enable video mode */
103 #define CONFFLAG_AGI (1 << 8) /* Set to run AGI Script in Background */
104 #define CONFFLAG_MOH (1 << 9) /* Set to have music on hold when */
107 static int careful_write(int fd, unsigned char *data, int len)
111 res = write(fd, data, len);
113 if (errno != EAGAIN) {
114 ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
125 static void conf_play(struct ast_conference *conf, int sound)
129 ast_mutex_lock(&conflock);
144 careful_write(conf->fd, data, len);
145 ast_mutex_unlock(&conflock);
148 static struct ast_conference *build_conf(char *confno, char *pin, int make, int dynamic)
150 struct ast_conference *cnf;
151 struct zt_confinfo ztc;
152 ast_mutex_lock(&conflock);
155 if (!strcmp(confno, cnf->confno))
159 if (!cnf && (make || dynamic)) {
160 cnf = malloc(sizeof(struct ast_conference));
163 memset(cnf, 0, sizeof(struct ast_conference));
164 strncpy(cnf->confno, confno, sizeof(cnf->confno) - 1);
165 strncpy(cnf->pin, pin, sizeof(cnf->pin) - 1);
166 cnf->fd = open("/dev/zap/pseudo", O_RDWR);
168 ast_log(LOG_WARNING, "Unable to open pseudo channel\n");
173 memset(&ztc, 0, sizeof(ztc));
174 /* Setup a new zap conference */
177 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
178 if (ioctl(cnf->fd, ZT_SETCONF, &ztc)) {
179 ast_log(LOG_WARNING, "Error setting conference\n");
185 cnf->start = time(NULL);
186 cnf->zapconf = ztc.confno;
187 cnf->isdynamic = dynamic;
188 if (option_verbose > 2)
189 ast_verbose(VERBOSE_PREFIX_3 "Created ZapTel conference %d for conference '%s'\n", cnf->zapconf, cnf->confno);
193 ast_log(LOG_WARNING, "Out of memory\n");
196 ast_mutex_unlock(&conflock);
200 static int confs_show(int fd, int argc, char **argv)
202 struct ast_conference *conf;
205 char *header_format = "%14s %-14s %-8s %-8s\n";
206 char *data_format = "%-12.12s %4.4d %02d:%02d:%02d %-8s\n";
210 return RESULT_SHOWUSAGE;
213 ast_cli(fd, "No active conferences.\n");
214 return RESULT_SUCCESS;
216 ast_cli(fd, header_format, "Conf Num", "Parties", "Activity", "Creation");
218 hr = (now - conf->start) / 3600;
219 min = ((now - conf->start) % 3600) / 60;
220 sec = (now - conf->start) % 60;
223 ast_cli(fd, data_format, conf->confno, conf->users, hr, min, sec, "Dynamic");
225 ast_cli(fd, data_format, conf->confno, conf->users, hr, min, sec, "Static");
229 return RESULT_SUCCESS;
232 static char show_confs_usage[] =
233 "Usage: show conferences\n"
234 " Provides summary information on conferences with active\n"
237 static struct ast_cli_entry cli_show_confs = {
238 { "show", "conferences", NULL }, confs_show,
239 "Show status of conferences", show_confs_usage, NULL };
241 static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int confflags)
243 struct ast_conference *prev=NULL, *cur;
245 struct zt_confinfo ztc;
247 struct ast_channel *c;
263 char *agifiledefault = "conf-background.agi";
266 char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
267 char *buf = __buf + AST_FRIENDLY_OFFSET;
270 if (!(confflags & CONFFLAG_QUIET) && conf->users == 1) {
271 if (!ast_streamfile(chan, "conf-onlyperson", chan->language))
272 ast_waitstream(chan, "");
275 if (confflags & CONFFLAG_VIDEO) {
276 /* Set it into linear mode (write) */
277 if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
278 ast_log(LOG_WARNING, "Unable to set '%s' to write linear mode\n", chan->name);
282 /* Set it into linear mode (read) */
283 if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0) {
284 ast_log(LOG_WARNING, "Unable to set '%s' to read linear mode\n", chan->name);
288 /* Set it into U-law mode (write) */
289 if (ast_set_write_format(chan, AST_FORMAT_ULAW) < 0) {
290 ast_log(LOG_WARNING, "Unable to set '%s' to write ulaw mode\n", chan->name);
294 /* Set it into U-law mode (read) */
295 if (ast_set_read_format(chan, AST_FORMAT_ULAW) < 0) {
296 ast_log(LOG_WARNING, "Unable to set '%s' to read ulaw mode\n", chan->name);
300 ast_indicate(chan, -1);
301 retryzap = strcasecmp(chan->type, "Zap");
303 origfd = chan->fds[0];
305 fd = open("/dev/zap/pseudo", O_RDWR);
307 ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
310 /* Make non-blocking */
311 flags = fcntl(fd, F_GETFL);
313 ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
317 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
318 ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
322 /* Setup buffering information */
323 memset(&bi, 0, sizeof(bi));
324 bi.bufsize = CONF_SIZE;
325 bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
326 bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
328 if (ioctl(fd, ZT_SET_BUFINFO, &bi)) {
329 ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
333 if (confflags & CONFFLAG_VIDEO) {
335 if (ioctl(fd, ZT_SETLINEAR, &x)) {
336 ast_log(LOG_WARNING, "Unable to set linear mode: %s\n", strerror(errno));
343 /* XXX Make sure we're not running on a pseudo channel XXX */
347 memset(&ztc, 0, sizeof(ztc));
348 /* Check to see if we're in a conference... */
350 if (ioctl(fd, ZT_GETCONF, &ztc)) {
351 ast_log(LOG_WARNING, "Error getting conference\n");
356 /* Whoa, already in a conference... Retry... */
358 ast_log(LOG_DEBUG, "Zap channel is in a conference already, retrying with pseudo\n");
363 memset(&ztc, 0, sizeof(ztc));
364 /* Add us to the conference */
366 ztc.confno = conf->zapconf;
367 if (confflags & CONFFLAG_MONITOR)
368 ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER;
369 else if (confflags & CONFFLAG_TALKER)
370 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER;
372 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
374 if (ioctl(fd, ZT_SETCONF, &ztc)) {
375 ast_log(LOG_WARNING, "Error setting conference\n");
379 ast_log(LOG_DEBUG, "Placed channel %s in ZAP conf %d\n", chan->name, conf->zapconf);
380 if (!firstpass && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN)) {
382 if (!(confflags & CONFFLAG_QUIET))
383 conf_play(conf, ENTER);
386 if (confflags & CONFFLAG_AGI) {
388 /* Get name of AGI file to run from $(MEETME_AGI_BACKGROUND)
389 or use default filename of conf-background.agi */
391 agifile = pbx_builtin_getvar_helper(chan,"MEETME_AGI_BACKGROUND");
393 agifile = agifiledefault;
395 if (!strcasecmp(chan->type,"Zap")) {
396 /* Set CONFMUTE mode on Zap channel to mute DTMF tones */
398 ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
400 /* Find a pointer to the agi app and execute the script */
401 app = pbx_findapp("agi");
403 ret = pbx_exec(chan, app, agifile, 1);
405 ast_log(LOG_WARNING, "Could not find application (agi)\n");
408 if (!strcasecmp(chan->type,"Zap")) {
409 /* Remove CONFMUTE mode on Zap channel */
411 ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
416 c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
417 /* trying to add moh for single person conf */
418 if (confflags & CONFFLAG_MOH) {
419 if (conf->users == 1) {
420 if (musiconhold == 0) {
421 ast_moh_start(chan, NULL);
431 /* end modifications */
434 if (c->fds[0] != origfd) {
436 /* Kill old pseudo */
439 ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n");
446 if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#') && (confflags & CONFFLAG_POUNDEXIT)) {
449 } else if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*') && (confflags & CONFFLAG_STARMENU)) {
450 if ((confflags & CONFFLAG_ADMIN)) {
451 /* Do admin stuff here */
453 /* Do user menu here */
456 } else if (fd != chan->fds[0]) {
457 if (f->frametype == AST_FRAME_VOICE) {
458 if (f->subclass == AST_FORMAT_ULAW) {
459 /* Carefully write */
460 careful_write(fd, f->data, f->datalen);
462 ast_log(LOG_WARNING, "Huh? Got a non-ulaw (%d) frame in the conference\n", f->subclass);
466 } else if (outfd > -1) {
467 res = read(outfd, buf, CONF_SIZE);
469 memset(&fr, 0, sizeof(fr));
470 fr.frametype = AST_FRAME_VOICE;
471 fr.subclass = AST_FORMAT_ULAW;
475 fr.offset = AST_FRIENDLY_OFFSET;
476 if (ast_write(chan, &fr) < 0) {
477 ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno));
481 ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno));
485 if (fd != chan->fds[0])
488 /* Take out of conference */
489 /* Add us to the conference */
493 if (ioctl(fd, ZT_SETCONF, &ztc)) {
494 ast_log(LOG_WARNING, "Error setting conference\n");
498 if (!(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN))
499 conf_play(conf, LEAVE);
503 ast_mutex_lock(&conflock);
507 /* No more users -- close this one out */
512 prev->next = conf->next;
521 ast_log(LOG_WARNING, "Conference not found\n");
525 ast_mutex_unlock(&conflock);
529 static struct ast_conference *find_conf(char *confno, int make, int dynamic)
531 struct ast_config *cfg;
532 struct ast_variable *var;
533 struct ast_conference *cnf = confs;
535 /* Check first in the conference list */
536 ast_mutex_lock(&conflock);
538 if (!strcmp(confno, cnf->confno))
542 ast_mutex_unlock(&conflock);
546 /* No need to parse meetme.conf */
547 ast_log(LOG_DEBUG, "Using dynamic conference '%s'\n", confno);
548 cnf = build_conf(confno, "", make, dynamic);
550 /* Check the config */
551 cfg = ast_load("meetme.conf");
553 ast_log(LOG_WARNING, "No meetme.conf file :(\n");
556 var = ast_variable_browse(cfg, "rooms");
558 if (!strcasecmp(var->name, "conf")) {
559 /* Separate the PIN */
562 if ((pin = ast_strdupa(var->value))) {
563 conf = strsep(&pin, "|,");
564 if (!strcasecmp(conf, confno)) {
565 /* Bingo it's a valid conference */
567 cnf = build_conf(confno, pin, make, dynamic);
569 cnf = build_conf(confno, "", make, dynamic);
577 ast_log(LOG_DEBUG, "%s isn't a valid conference\n", confno);
585 static int count_exec(struct ast_channel *chan, void *data)
589 struct ast_conference *conf;
591 char *confnum, *localdata;
594 if (!data || !strlen(data)) {
595 ast_log(LOG_WARNING, "MeetMeCount requires an argument (conference number)\n");
598 localdata = ast_strdupa(data);
600 confnum = strsep(&localdata,"|");
601 conf = find_conf(confnum, 0, 0);
607 if (localdata && strlen(localdata)){
608 /* have var so load it and exit */
609 snprintf(val,sizeof(val), "%i",count);
610 pbx_builtin_setvar_helper(chan, localdata,val);
612 if (chan->_state != AST_STATE_UP)
614 res = ast_say_number(chan, count, "", chan->language);
616 LOCAL_USER_REMOVE(u);
620 static int conf_exec(struct ast_channel *chan, void *data)
624 char confno[AST_MAX_EXTENSION] = "";
627 struct ast_conference *cnf;
630 char *notdata, *info, *inflags = NULL, *inpin = NULL;
632 if (!data || !strlen(data)) {
639 if (chan->_state != AST_STATE_UP)
642 info = ast_strdupa((char *)notdata);
645 char *tmp = strsep(&info, "|");
646 strncpy(confno, tmp, sizeof(confno));
647 if (strlen(confno) == 0) {
652 inflags = strsep(&info, "|");
654 inpin = strsep(&info, "|");
657 if (strchr(inflags, 'a'))
658 confflags |= CONFFLAG_ADMIN;
659 if (strchr(inflags, 'm'))
660 confflags |= CONFFLAG_MONITOR;
661 if (strchr(inflags, 'p'))
662 confflags |= CONFFLAG_POUNDEXIT;
663 if (strchr(inflags, 's'))
664 confflags |= CONFFLAG_STARMENU;
665 if (strchr(inflags, 't'))
666 confflags |= CONFFLAG_TALKER;
667 if (strchr(inflags, 'q'))
668 confflags |= CONFFLAG_QUIET;
669 if (strchr(inflags, 'M'))
670 confflags |= CONFFLAG_MOH;
671 if (strchr(inflags, 'b'))
672 confflags |= CONFFLAG_AGI;
673 if (strchr(inflags, 'd'))
680 while (allowretry && (!strlen(confno)) && (++retrycnt < 4)) {
681 /* Prompt user for conference number */
682 res = ast_app_getdata(chan, "conf-getconfno", confno, sizeof(confno) - 1, 0);
684 /* Don't try to validate when we catch an error */
690 if (strlen(confno)) {
691 /* Check the validity of the conference */
692 cnf = find_conf(confno, 1, dynamic);
694 res = ast_streamfile(chan, "conf-invalid", chan->language);
696 ast_waitstream(chan, "");
701 if (strlen(cnf->pin)) {
702 /* XXX Should prompt user for pin if pin is required XXX */
707 /* Run the conference */
708 res = conf_run(chan, cnf, confflags);
711 } while (allowretry);
712 /* Do the conference */
713 LOCAL_USER_REMOVE(u);
717 int unload_module(void)
719 STANDARD_HANGUP_LOCALUSERS;
720 ast_cli_unregister(&cli_show_confs);
721 ast_unregister_application(app2);
722 return ast_unregister_application(app);
725 int load_module(void)
727 ast_cli_register(&cli_show_confs);
728 ast_register_application(app2, count_exec, synopsis2, descrip2);
729 return ast_register_application(app, conf_exec, synopsis, descrip);
732 char *description(void)
740 STANDARD_USECOUNT(res);
746 return ASTERISK_GPL_KEY;