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");
497 if (!(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN))
498 conf_play(conf, LEAVE);
502 ast_mutex_lock(&conflock);
506 /* No more users -- close this one out */
511 prev->next = conf->next;
520 ast_log(LOG_WARNING, "Conference not found\n");
524 ast_mutex_unlock(&conflock);
528 static struct ast_conference *find_conf(char *confno, int make, int dynamic)
530 struct ast_config *cfg;
531 struct ast_variable *var;
532 struct ast_conference *cnf = confs;
534 /* Check first in the conference list */
535 ast_mutex_lock(&conflock);
537 if (!strcmp(confno, cnf->confno))
541 ast_mutex_unlock(&conflock);
545 /* No need to parse meetme.conf */
546 ast_log(LOG_DEBUG, "Using dynamic conference '%s'\n", confno);
547 cnf = build_conf(confno, "", make, dynamic);
549 /* Check the config */
550 cfg = ast_load("meetme.conf");
552 ast_log(LOG_WARNING, "No meetme.conf file :(\n");
555 var = ast_variable_browse(cfg, "rooms");
557 if (!strcasecmp(var->name, "conf")) {
558 /* Separate the PIN */
561 if ((pin = ast_strdupa(var->value))) {
562 conf = strsep(&pin, "|,");
563 if (!strcasecmp(conf, confno)) {
564 /* Bingo it's a valid conference */
566 cnf = build_conf(confno, pin, make, dynamic);
568 cnf = build_conf(confno, "", make, dynamic);
576 ast_log(LOG_DEBUG, "%s isn't a valid conference\n", confno);
584 static int count_exec(struct ast_channel *chan, void *data)
588 struct ast_conference *conf;
590 char *confnum, *localdata;
593 if (!data || !strlen(data)) {
594 ast_log(LOG_WARNING, "MeetMeCount requires an argument (conference number)\n");
597 localdata = ast_strdupa(data);
599 confnum = strsep(&localdata,"|");
600 conf = find_conf(confnum, 0, 0);
606 if (localdata && strlen(localdata)){
607 /* have var so load it and exit */
608 snprintf(val,sizeof(val), "%i",count);
609 pbx_builtin_setvar_helper(chan, localdata,val);
611 if (chan->_state != AST_STATE_UP)
613 res = ast_say_number(chan, count, "", chan->language);
615 LOCAL_USER_REMOVE(u);
619 static int conf_exec(struct ast_channel *chan, void *data)
623 char confno[AST_MAX_EXTENSION] = "";
626 struct ast_conference *cnf;
629 char *notdata, *info, *inflags = NULL, *inpin = NULL;
631 if (!data || !strlen(data)) {
638 if (chan->_state != AST_STATE_UP)
641 info = ast_strdupa((char *)notdata);
644 char *tmp = strsep(&info, "|");
645 strncpy(confno, tmp, sizeof(confno));
646 if (strlen(confno) == 0) {
651 inflags = strsep(&info, "|");
653 inpin = strsep(&info, "|");
656 if (strchr(inflags, 'a'))
657 confflags |= CONFFLAG_ADMIN;
658 if (strchr(inflags, 'm'))
659 confflags |= CONFFLAG_MONITOR;
660 if (strchr(inflags, 'p'))
661 confflags |= CONFFLAG_POUNDEXIT;
662 if (strchr(inflags, 's'))
663 confflags |= CONFFLAG_STARMENU;
664 if (strchr(inflags, 't'))
665 confflags |= CONFFLAG_TALKER;
666 if (strchr(inflags, 'q'))
667 confflags |= CONFFLAG_QUIET;
668 if (strchr(inflags, 'M'))
669 confflags |= CONFFLAG_MOH;
670 if (strchr(inflags, 'b'))
671 confflags |= CONFFLAG_AGI;
672 if (strchr(inflags, 'd'))
679 while (allowretry && (!strlen(confno)) && (++retrycnt < 4)) {
680 /* Prompt user for conference number */
681 res = ast_app_getdata(chan, "conf-getconfno", confno, sizeof(confno) - 1, 0);
683 /* Don't try to validate when we catch an error */
689 if (strlen(confno)) {
690 /* Check the validity of the conference */
691 cnf = find_conf(confno, 1, dynamic);
693 res = ast_streamfile(chan, "conf-invalid", chan->language);
695 ast_waitstream(chan, "");
700 if (strlen(cnf->pin)) {
701 char pin[AST_MAX_EXTENSION];
703 if (inpin && *inpin) {
704 strncpy(pin, inpin, sizeof(pin) - 1);
706 /* Prompt user for pin if pin is required */
707 res = ast_app_getdata(chan, "conf-getpin", pin, sizeof(pin) - 1, 0);
710 if (!strcasecmp(pin, cnf->pin)) {
713 /* Run the conference */
714 res = conf_run(chan, cnf, confflags);
717 res = ast_streamfile(chan, "conf-invalidpin", chan->language);
719 ast_waitstream(chan, "");
729 /* No pin required */
732 /* Run the conference */
733 res = conf_run(chan, cnf, confflags);
737 } while (allowretry);
738 /* Do the conference */
739 LOCAL_USER_REMOVE(u);
743 int unload_module(void)
745 STANDARD_HANGUP_LOCALUSERS;
746 ast_cli_unregister(&cli_show_confs);
747 ast_unregister_application(app2);
748 return ast_unregister_application(app);
751 int load_module(void)
753 ast_cli_register(&cli_show_confs);
754 ast_register_application(app2, count_exec, synopsis2, descrip2);
755 return ast_register_application(app, conf_exec, synopsis, descrip);
758 char *description(void)
766 STANDARD_USECOUNT(res);
772 return ASTERISK_GPL_KEY;