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/manager.h>
24 #include <asterisk/options.h>
25 #include <asterisk/cli.h>
26 #include <asterisk/say.h>
27 #include <asterisk/utils.h>
33 #include <sys/ioctl.h>
36 #include <linux/zaptel.h>
39 #endif /* __linux__ */
41 static char *tdesc = "MeetMe conference bridge";
43 static char *app = "MeetMe";
44 static char *app2 = "MeetMeCount";
45 static char *app3 = "MeetMeAdmin";
47 static char *synopsis = "MeetMe conference bridge";
48 static char *synopsis2 = "MeetMe participant count";
49 static char *synopsis3 = "MeetMe conference Administration";
51 static char *descrip =
52 " MeetMe([confno][,[options][,pin]]): Enters the user into a specified MeetMe conference.\n"
53 "If the conference number is omitted, the user will be prompted to enter\n"
55 "MeetMe returns 0 if user pressed # to exit (see option 'p'), otherwise -1.\n"
56 "Please note: A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING TO WORK!\n\n"
58 "The option string may contain zero or more of the following characters:\n"
59 " 'm' -- set monitor only mode (Listen only, no talking)\n"
60 " 't' -- set talk only mode. (Talk only, no listening)\n"
61 " 'p' -- allow user to exit the conference by pressing '#'\n"
62 " 'd' -- dynamically add conference\n"
63 " 'D' -- dynamically add conference, prompting for a PIN\n"
64 " 'e' -- select an empty conference\n"
65 " 'E' -- select an empty pinless conference\n"
66 " 'v' -- video mode\n"
67 " 'q' -- quiet mode (don't play enter/leave sounds)\n"
68 " 'M' -- enable music on hold when the conference has a single caller\n"
69 " 'x' -- exit the conference if the last marked user left\n"
70 " 'w' -- wait until a marked user has entered the conference\n"
71 " 'b' -- run AGI script specified in ${MEETME_AGI_BACKGROUND}\n"
72 " Default: conf-background.agi\n"
73 " (Note: This does not work with non-Zap channels in the same conference)\n"
74 " 's' -- Present menu (user or admin) when '*' is received ('send' to menu)\n"
75 " 'a' -- set admin mode\n";
77 static char *descrip2 =
78 " MeetMeCount(confno[|var]): Plays back the number of users in the specifiedi\n"
79 "MeetMe conference. If var is specified, playback will be skipped and the value\n"
80 "will be returned in the variable. Returns 0 on success or -1 on a hangup.\n"
81 "A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING FUNCTIONALITY.\n";
83 static char *descrip3 =
84 " MeetMeAdmin(confno,command[,user]): Run admin command for conference\n"
85 " 'K' -- Kick all users out of conference\n"
86 " 'k' -- Kick one user out of conference\n"
87 " 'L' -- Lock conference\n"
88 " 'l' -- Unlock conference\n"
89 " 'M' -- Mute conference\n"
90 " 'm' -- Unmute conference\n"
97 static struct ast_conference {
98 char confno[AST_MAX_EXTENSION]; /* Conference */
99 struct ast_channel *chan; /* Announcements channel */
100 int fd; /* Announcements fd */
101 int zapconf; /* Zaptel Conf # */
102 int users; /* Number of active users */
103 int markedusers; /* Number of marked users */
104 struct ast_conf_user *firstuser; /* Pointer to the first user struct */
105 struct ast_conf_user *lastuser; /* Pointer to the last user struct */
106 time_t start; /* Start time (s) */
107 int isdynamic; /* Created on the fly? */
108 int locked; /* Is the conference locked? */
109 char pin[AST_MAX_EXTENSION]; /* If protected by a PIN */
110 struct ast_conference *next;
113 struct ast_conf_user {
114 int user_no; /* User Number */
115 struct ast_conf_user *prevuser; /* Pointer to the previous user */
116 struct ast_conf_user *nextuser; /* Pointer to the next user */
117 int userflags; /* Flags as set in the conference */
118 int adminflags; /* Flags set by the Admin */
119 struct ast_channel *chan; /* Connected channel */
120 char usrvalue[50]; /* Custom User Value */
121 time_t jointime; /* Time the user joined the conference */
124 #define ADMINFLAG_MUTED (1 << 1) /* User is muted */
125 #define ADMINFLAG_KICKME (1 << 2) /* User is kicked */
128 AST_MUTEX_DEFINE_STATIC(conflock);
130 static int admin_exec(struct ast_channel *chan, void *data);
138 #define CONF_SIZE 320
140 #define CONFFLAG_ADMIN (1 << 1) /* If set the user has admin access on the conference */
141 #define CONFFLAG_MONITOR (1 << 2) /* If set the user can only receive audio from the conference */
142 #define CONFFLAG_POUNDEXIT (1 << 3) /* If set asterisk will exit conference when '#' is pressed */
143 #define CONFFLAG_STARMENU (1 << 4) /* If set asterisk will provide a menu to the user what '*' is pressed */
144 #define CONFFLAG_TALKER (1 << 5) /* If set the use can only send audio to the conference */
145 #define CONFFLAG_QUIET (1 << 6) /* If set there will be no enter or leave sounds */
146 #define CONFFLAG_VIDEO (1 << 7) /* Set to enable video mode */
147 #define CONFFLAG_AGI (1 << 8) /* Set to run AGI Script in Background */
148 #define CONFFLAG_MOH (1 << 9) /* Set to have music on hold when user is alone in conference */
149 #define CONFFLAG_ADMINEXIT (1 << 10) /* If set the MeetMe will return if all marked with this flag left */
150 #define CONFFLAG_WAITMARKED (1 << 11) /* If set, the MeetMe will wait until a marked user enters */
153 static int careful_write(int fd, unsigned char *data, int len)
158 x = ZT_IOMUX_WRITE | ZT_IOMUX_SIGEVENT;
159 res = ioctl(fd, ZT_IOMUX, &x);
161 res = write(fd, data, len);
163 if (errno != EAGAIN) {
164 ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
175 static void conf_play(struct ast_conference *conf, int sound)
179 ast_mutex_lock(&conflock);
194 careful_write(conf->fd, data, len);
195 ast_mutex_unlock(&conflock);
198 static struct ast_conference *build_conf(char *confno, char *pin, int make, int dynamic)
200 struct ast_conference *cnf;
201 struct zt_confinfo ztc;
202 ast_mutex_lock(&conflock);
205 if (!strcmp(confno, cnf->confno))
209 if (!cnf && (make || dynamic)) {
210 cnf = malloc(sizeof(struct ast_conference));
213 memset(cnf, 0, sizeof(struct ast_conference));
214 strncpy(cnf->confno, confno, sizeof(cnf->confno) - 1);
215 strncpy(cnf->pin, pin, sizeof(cnf->pin) - 1);
216 cnf->markedusers = -1;
217 cnf->chan = ast_request("zap", AST_FORMAT_ULAW, "pseudo");
219 cnf->fd = cnf->chan->fds[0]; /* for use by conf_play() */
221 ast_log(LOG_WARNING, "Unable to open pseudo channel - trying device\n");
222 cnf->fd = open("/dev/zap/pseudo", O_RDWR);
224 ast_log(LOG_WARNING, "Unable to open pseudo device\n");
230 memset(&ztc, 0, sizeof(ztc));
231 /* Setup a new zap conference */
234 ztc.confmode = ZT_CONF_CONFANN;
235 if (ioctl(cnf->fd, ZT_SETCONF, &ztc)) {
236 ast_log(LOG_WARNING, "Error setting conference\n");
238 ast_hangup(cnf->chan);
245 /* Fill the conference struct */
246 cnf->start = time(NULL);
247 cnf->zapconf = ztc.confno;
248 cnf->isdynamic = dynamic;
249 cnf->firstuser = NULL;
250 cnf->lastuser = NULL;
252 if (option_verbose > 2)
253 ast_verbose(VERBOSE_PREFIX_3 "Created MeetMe conference %d for conference '%s'\n", cnf->zapconf, cnf->confno);
257 ast_log(LOG_WARNING, "Out of memory\n");
260 ast_mutex_unlock(&conflock);
264 static int confs_show(int fd, int argc, char **argv)
266 ast_cli(fd, "Deprecated! Please use 'meetme' instead.\n");
267 return RESULT_SUCCESS;
270 static char show_confs_usage[] =
271 "Deprecated! Please use 'meetme' instead.\n";
273 static struct ast_cli_entry cli_show_confs = {
274 { "show", "conferences", NULL }, confs_show,
275 "Show status of conferences", show_confs_usage, NULL };
277 static int conf_cmd(int fd, int argc, char **argv) {
278 /* Process the command */
279 struct ast_conference *cnf;
280 struct ast_conf_user *user;
282 int i = 0, total = 0;
284 char *header_format = "%-14s %-14s %-10s %-8s %-8s\n";
285 char *data_format = "%-12.12s %4.4d %4.4s %02d:%02d:%02d %-8s\n";
286 char cmdline[1024] = "";
289 ast_cli(fd, "Invalid Arguments.\n");
290 /* Check for length so no buffer will overflow... */
291 for (i = 0; i < argc; i++) {
292 if (strlen(argv[i]) > 100)
293 ast_cli(fd, "Invalid Arguments.\n");
296 /* 'MeetMe': List all the conferences */
300 ast_cli(fd, "No active MeetMe conferences.\n");
301 return RESULT_SUCCESS;
303 ast_cli(fd, header_format, "Conf Num", "Parties", "Marked", "Activity", "Creation");
305 if (cnf->markedusers < 0)
306 strcpy(cmdline, "N/A ");
308 snprintf(cmdline, sizeof(cmdline), "%4.4d", cnf->markedusers);
309 hr = (now - cnf->start) / 3600;
310 min = ((now - cnf->start) % 3600) / 60;
311 sec = (now - cnf->start) % 60;
313 ast_cli(fd, data_format, cnf->confno, cnf->users, cmdline, hr, min, sec, cnf->isdynamic ? "Dynamic" : "Static");
318 ast_cli(fd, "* Total number of MeetMe users: %d\n", total);
319 return RESULT_SUCCESS;
322 return RESULT_SHOWUSAGE;
323 strncpy(cmdline, argv[2], 100); /* Argv 2: conference number */
324 if (strstr(argv[1], "lock")) {
325 if (strcmp(argv[1], "lock") == 0) {
327 strcat(cmdline, "|L");
330 strcat(cmdline, "|l");
332 } else if (strstr(argv[1], "mute")) {
334 return RESULT_SHOWUSAGE;
335 if (strcmp(argv[1], "mute") == 0) {
337 strcat(cmdline, "|M|");
338 strcat(cmdline, argv[3]);
341 strcat(cmdline, "|m|");
342 strcat(cmdline, argv[3]);
344 } else if (strcmp(argv[1], "kick") == 0) {
346 return RESULT_SHOWUSAGE;
347 if (strcmp(argv[3], "all") == 0) {
349 strcat(cmdline, "|K");
351 /* Kick a single user */
352 strcat(cmdline, "|k|");
353 strcat(cmdline, argv[3]);
355 } else if(strcmp(argv[1], "list") == 0) {
356 /* List all the users in a conference */
358 ast_cli(fd, "No active conferences.\n");
359 return RESULT_SUCCESS;
362 /* Find the right conference */
364 if (strcmp(cnf->confno, argv[2]) == 0)
369 ast_cli(fd, "No such conference: %s.\n",argv[2]);
370 return RESULT_SUCCESS;
373 /* Show all the users */
374 user = cnf->firstuser;
376 ast_cli(fd, "User #: %i Channel: %s %s %s\n", user->user_no, user->chan->name, (user->userflags & CONFFLAG_ADMIN) ? "(Admin)" : "", (user->userflags & CONFFLAG_MONITOR) ? "(Listen only)" : "" );
377 user = user->nextuser;
379 return RESULT_SUCCESS;
381 return RESULT_SHOWUSAGE;
382 ast_log(LOG_DEBUG, "Cmdline: %s\n", cmdline);
383 admin_exec(NULL, cmdline);
387 static char *complete_confcmd(char *line, char *word, int pos, int state) {
388 #define CONF_COMMANDS 6
389 int which = 0, x = 0;
390 struct ast_conference *cnf = NULL;
391 struct ast_conf_user *usr = NULL;
394 char cmds[CONF_COMMANDS][20] = {"lock", "unlock", "mute", "unmute", "kick", "list"};
399 for (x = 0;x < CONF_COMMANDS; x++) {
400 if (!strncasecmp(cmds[x], word, strlen(word))) {
401 if (++which > state) {
402 return strdup(cmds[x]);
406 } else if (pos == 2) {
407 /* Conference Number */
408 ast_mutex_lock(&conflock);
411 if (!strncasecmp(word, cnf->confno, strlen(word))) {
417 ast_mutex_unlock(&conflock);
418 return cnf ? strdup(cnf->confno) : NULL;
419 } else if (pos == 3) {
420 /* User Number || Conf Command option*/
421 if (strstr(line, "mute") || strstr(line, "kick")) {
422 if ((state == 0) && (strstr(line, "kick")) && !(strncasecmp(word, "all", strlen(word)))) {
423 return strdup("all");
426 ast_mutex_lock(&conflock);
429 /* TODO: Find the conf number from the cmdline (ignore spaces) <- test this and make it fail-safe! */
430 myline = ast_strdupa(line);
431 if (strsep(&myline, " ") && strsep(&myline, " ") && !confno) {
432 while((confno = strsep(&myline, " ")) && (strcmp(confno, " ") == 0))
437 if (strcmp(confno, cnf->confno) == 0) {
443 /* Search for the user */
444 usr = cnf->firstuser;
446 sprintf(usrno, "%i", usr->user_no);
447 if (!strncasecmp(word, usrno, strlen(word))) {
454 ast_mutex_unlock(&conflock);
455 return usr ? strdup(usrno) : NULL;
461 static char conf_usage[] =
462 "Usage: meetme (un)lock|(un)mute|kick|list <confno> <usernumber>\n"
463 " Executes a command for the conference or on a conferee\n";
465 static struct ast_cli_entry cli_conf = {
466 { "meetme", NULL, NULL }, conf_cmd,
467 "Execute a command on a conference or conferee", conf_usage, complete_confcmd };
469 static int confnonzero(void *ptr)
471 struct ast_conference *conf = ptr;
473 ast_mutex_lock(&conflock);
474 res = (conf->markedusers < 0);
475 ast_mutex_unlock(&conflock);
479 static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int confflags)
481 struct ast_conference *prev=NULL, *cur;
482 struct ast_conf_user *user = malloc(sizeof(struct ast_conf_user));
484 struct zt_confinfo ztc;
486 struct ast_channel *c;
501 int using_pseudo = 0;
505 char *agifiledefault = "conf-background.agi";
509 char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
510 char *buf = __buf + AST_FRIENDLY_OFFSET;
512 user->user_no = 0; /* User number 0 means starting up user! (dead - not in the list!) */
515 /* Sorry, but this confernce is locked! */
516 if (!ast_streamfile(chan, "conf-locked", chan->language))
517 ast_waitstream(chan, "");
521 if (confflags & CONFFLAG_ADMINEXIT) {
522 if (conf->markedusers == -1) {
523 conf->markedusers = 1;
529 ast_mutex_lock(&conflock);
530 if (conf->firstuser == NULL) {
531 /* Fill the first new User struct */
533 user->nextuser = NULL;
534 user->prevuser = NULL;
535 conf->firstuser = user;
536 conf->lastuser = user;
538 /* Fill the new user struct */
539 user->user_no = conf->lastuser->user_no + 1;
540 user->prevuser = conf->lastuser;
541 user->nextuser = NULL;
542 if (conf->lastuser->nextuser != NULL) {
543 ast_log(LOG_WARNING, "Error in User Management!\n");
544 ast_mutex_unlock(&conflock);
547 conf->lastuser->nextuser = user;
548 conf->lastuser = user;
551 strncpy(user->usrvalue, "test", sizeof(user->usrvalue));
553 user->userflags = confflags;
554 user->adminflags = 0;
555 ast_mutex_unlock(&conflock);
556 origquiet = confflags & CONFFLAG_QUIET;
557 while((confflags & CONFFLAG_WAITMARKED) && (conf->markedusers < 0)) {
558 confflags &= ~CONFFLAG_QUIET;
559 confflags |= origquiet;
560 /* XXX Announce that we're waiting on the conference lead to join */
561 if (!(confflags & CONFFLAG_QUIET)) {
562 res = ast_streamfile(chan, "vm-dialout", chan->language);
564 res = ast_waitstream(chan, "");
567 /* If we're waiting with hold music, set to silent mode */
569 confflags |= CONFFLAG_QUIET;
570 ast_moh_start(chan, NULL);
571 res = ast_safe_sleep_conditional(chan, 60000, confnonzero, conf);
575 ast_log(LOG_DEBUG, "Got hangup on '%s' already\n", chan->name);
580 if (!(confflags & CONFFLAG_QUIET) && conf->users == 1) {
581 if (!ast_streamfile(chan, "conf-onlyperson", chan->language)) {
582 if (ast_waitstream(chan, "") < 0)
588 /* Set it into linear mode (write) */
589 if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
590 ast_log(LOG_WARNING, "Unable to set '%s' to write linear mode\n", chan->name);
594 /* Set it into linear mode (read) */
595 if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0) {
596 ast_log(LOG_WARNING, "Unable to set '%s' to read linear mode\n", chan->name);
599 ast_indicate(chan, -1);
600 retryzap = strcasecmp(chan->type, "Zap");
602 origfd = chan->fds[0];
604 fd = open("/dev/zap/pseudo", O_RDWR);
606 ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
610 /* Make non-blocking */
611 flags = fcntl(fd, F_GETFL);
613 ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
617 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
618 ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
622 /* Setup buffering information */
623 memset(&bi, 0, sizeof(bi));
624 bi.bufsize = CONF_SIZE/2;
625 bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
626 bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
628 if (ioctl(fd, ZT_SET_BUFINFO, &bi)) {
629 ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
634 if (ioctl(fd, ZT_SETLINEAR, &x)) {
635 ast_log(LOG_WARNING, "Unable to set linear mode: %s\n", strerror(errno));
641 /* XXX Make sure we're not running on a pseudo channel XXX */
645 memset(&ztc, 0, sizeof(ztc));
646 /* Check to see if we're in a conference... */
648 if (ioctl(fd, ZT_GETCONF, &ztc)) {
649 ast_log(LOG_WARNING, "Error getting conference\n");
654 /* Whoa, already in a conference... Retry... */
656 ast_log(LOG_DEBUG, "Zap channel is in a conference already, retrying with pseudo\n");
661 memset(&ztc, 0, sizeof(ztc));
662 /* Add us to the conference */
664 ztc.confno = conf->zapconf;
665 if (confflags & CONFFLAG_MONITOR)
666 ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER;
667 else if (confflags & CONFFLAG_TALKER)
668 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER;
670 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
672 if (ioctl(fd, ZT_SETCONF, &ztc)) {
673 ast_log(LOG_WARNING, "Error setting conference\n");
677 ast_log(LOG_DEBUG, "Placed channel %s in ZAP conf %d\n", chan->name, conf->zapconf);
679 manager_event(EVENT_FLAG_CALL, "MeetmeJoin",
683 chan->name, chan->uniqueid, conf->confno);
685 if (!firstpass && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN)) {
687 if (!(confflags & CONFFLAG_QUIET))
688 conf_play(conf, ENTER);
691 if (confflags & CONFFLAG_AGI) {
693 /* Get name of AGI file to run from $(MEETME_AGI_BACKGROUND)
694 or use default filename of conf-background.agi */
696 agifile = pbx_builtin_getvar_helper(chan,"MEETME_AGI_BACKGROUND");
698 agifile = agifiledefault;
700 if (!strcasecmp(chan->type,"Zap")) {
701 /* Set CONFMUTE mode on Zap channel to mute DTMF tones */
703 ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
705 /* Find a pointer to the agi app and execute the script */
706 app = pbx_findapp("agi");
708 ret = pbx_exec(chan, app, agifile, 1);
710 ast_log(LOG_WARNING, "Could not find application (agi)\n");
713 if (!strcasecmp(chan->type,"Zap")) {
714 /* Remove CONFMUTE mode on Zap channel */
716 ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
719 if (!strcasecmp(chan->type,"Zap") && (confflags & CONFFLAG_STARMENU)) {
720 /* Set CONFMUTE mode on Zap channel to mute DTMF tones when the menu is enabled */
722 ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
727 c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
729 /* Update the struct with the actual confflags */
730 user->userflags = confflags;
732 /* trying to add moh for single person conf */
733 if (confflags & CONFFLAG_MOH) {
734 if (conf->users == 1) {
735 if (musiconhold == 0) {
736 ast_moh_start(chan, NULL);
747 /* Leave if the last marked user left */
748 if (conf->markedusers == 0) {
753 /* Check if the admin changed my modes */
754 if (user->adminflags) {
755 /* Set the new modes */
756 if ((user->adminflags & ADMINFLAG_MUTED) && (ztc.confmode & ZT_CONF_TALKER)) {
757 ztc.confmode ^= ZT_CONF_TALKER;
758 if (ioctl(fd, ZT_SETCONF, &ztc)) {
759 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
764 if (!(user->adminflags & ADMINFLAG_MUTED) && !(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & ZT_CONF_TALKER)) {
765 ztc.confmode |= ZT_CONF_TALKER;
766 if (ioctl(fd, ZT_SETCONF, &ztc)) {
767 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
772 if (user->adminflags & ADMINFLAG_KICKME) {
773 //You have been kicked.
774 if (!ast_streamfile(chan, "conf-kicked", chan->language))
775 ast_waitstream(chan, "");
779 } else if (!(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & ZT_CONF_TALKER)) {
780 ztc.confmode |= ZT_CONF_TALKER;
781 if (ioctl(fd, ZT_SETCONF, &ztc)) {
782 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
789 if (c->fds[0] != origfd) {
791 /* Kill old pseudo */
794 ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n");
802 if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#') && (confflags & CONFFLAG_POUNDEXIT)) {
805 } else if (((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*') && (confflags & CONFFLAG_STARMENU)) || ((f->frametype == AST_FRAME_DTMF) && menu_active)) {
809 if ((confflags & CONFFLAG_ADMIN)) {
813 /* Record this sound! */
814 if (!ast_streamfile(chan, "conf-adminmenu", chan->language))
815 ast_waitstream(chan, "");
817 switch(f->subclass - 48) {
818 case 1: /* Un/Mute */
820 if (ztc.confmode & ZT_CONF_TALKER) {
821 ztc.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER;
822 confflags |= CONFFLAG_MONITOR ^ CONFFLAG_TALKER;
824 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
825 confflags ^= CONFFLAG_MONITOR | CONFFLAG_TALKER;
827 if (ioctl(fd, ZT_SETCONF, &ztc)) {
828 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
832 if (ztc.confmode & ZT_CONF_TALKER) {
833 if (!ast_streamfile(chan, "conf-unmuted", chan->language))
834 ast_waitstream(chan, "");
836 if (!ast_streamfile(chan, "conf-muted", chan->language))
837 ast_waitstream(chan, "");
840 case 2: /* Un/Lock the Conference */
844 if (!ast_streamfile(chan, "conf-unlockednow", chan->language))
845 ast_waitstream(chan, "");
848 if (!ast_streamfile(chan, "conf-lockednow", chan->language))
849 ast_waitstream(chan, "");
854 /* Play an error message! */
855 if (!ast_streamfile(chan, "conf-errormenu", chan->language))
856 ast_waitstream(chan, "");
864 /* Record this sound! */
865 if (!ast_streamfile(chan, "conf-usermenu", chan->language))
866 ast_waitstream(chan, "");
868 switch(f->subclass - 48) {
869 case 1: /* Un/Mute */
871 if (ztc.confmode & ZT_CONF_TALKER) {
872 ztc.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER;
873 confflags |= CONFFLAG_MONITOR ^ CONFFLAG_TALKER;
874 } else if (!(user->adminflags & ADMINFLAG_MUTED)) {
875 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
876 confflags ^= CONFFLAG_MONITOR | CONFFLAG_TALKER;
878 if (ioctl(fd, ZT_SETCONF, &ztc)) {
879 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
883 if (ztc.confmode & ZT_CONF_TALKER) {
884 if (!ast_streamfile(chan, "conf-unmuted", chan->language))
885 ast_waitstream(chan, "");
887 if (!ast_streamfile(chan, "conf-muted", chan->language))
888 ast_waitstream(chan, "");
893 /* Play an error message! */
894 if (!ast_streamfile(chan, "conf-errormenu", chan->language))
895 ast_waitstream(chan, "");
901 ast_moh_start(chan, NULL);
903 } else if (using_pseudo) {
904 if (f->frametype == AST_FRAME_VOICE) {
905 if (f->subclass == AST_FORMAT_SLINEAR) {
906 /* Carefully write */
907 careful_write(fd, f->data, f->datalen);
909 ast_log(LOG_WARNING, "Huh? Got a non-linear (%d) frame in the conference\n", f->subclass);
913 } else if (outfd > -1) {
914 res = read(outfd, buf, CONF_SIZE);
916 memset(&fr, 0, sizeof(fr));
917 fr.frametype = AST_FRAME_VOICE;
918 fr.subclass = AST_FORMAT_SLINEAR;
922 fr.offset = AST_FRIENDLY_OFFSET;
923 if (ast_write(chan, &fr) < 0) {
924 ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno));
928 ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno));
935 /* Take out of conference */
936 /* Add us to the conference */
940 if (ioctl(fd, ZT_SETCONF, &ztc)) {
941 ast_log(LOG_WARNING, "Error setting conference\n");
944 if (!(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN))
945 conf_play(conf, LEAVE);
948 ast_mutex_lock(&conflock);
949 if (user->user_no) { /* Only cleanup users who really joined! */
950 manager_event(EVENT_FLAG_CALL, "MeetmeLeave",
954 chan->name, chan->uniqueid, conf->confno);
957 if (confflags & CONFFLAG_ADMINEXIT)
961 /* No more users -- close this one out */
965 prev->next = conf->next;
974 ast_log(LOG_WARNING, "Conference not found\n");
976 ast_hangup(conf->chan);
981 /* Remove the user struct */
982 if (user == conf->firstuser) {
983 if (user->nextuser) {
984 /* There is another entry */
985 user->nextuser->prevuser = NULL;
987 /* We are the only entry */
988 conf->lastuser = NULL;
991 conf->firstuser = user->nextuser;
992 } else if (user == conf->lastuser){
994 user->prevuser->nextuser = NULL;
996 ast_log(LOG_ERROR, "Bad bad bad! We're the last, not the first, but nobody before us??\n");
997 conf->lastuser = user->prevuser;
1000 user->nextuser->prevuser = user->prevuser;
1002 ast_log(LOG_ERROR, "Bad! Bad! Bad! user->nextuser is NULL but we're not the end!\n");
1004 user->prevuser->nextuser = user->nextuser;
1006 ast_log(LOG_ERROR, "Bad! Bad! Bad! user->prevuser is NULL but we're not the beginning!\n");
1008 /* Return the number of seconds the user was in the conf */
1009 sprintf(meetmesecs, "%i", (int) (user->jointime - time(NULL)));
1010 pbx_builtin_setvar_helper(chan, "MEETMESECS", meetmesecs);
1014 ast_mutex_unlock(&conflock);
1018 static struct ast_conference *find_conf(struct ast_channel *chan, char *confno, int make, int dynamic, char *dynamic_pin)
1020 struct ast_config *cfg;
1021 struct ast_variable *var;
1022 struct ast_conference *cnf;
1024 /* Check first in the conference list */
1025 ast_mutex_lock(&conflock);
1028 if (!strcmp(confno, cnf->confno))
1032 ast_mutex_unlock(&conflock);
1036 /* No need to parse meetme.conf */
1037 ast_log(LOG_DEBUG, "Building dynamic conference '%s'\n", confno);
1039 if (dynamic_pin[0] == 'q') {
1040 /* Query the user to enter a PIN */
1041 ast_app_getdata(chan, "conf-getpin", dynamic_pin, AST_MAX_EXTENSION - 1, 0);
1043 cnf = build_conf(confno, dynamic_pin, make, dynamic);
1045 cnf = build_conf(confno, "", make, dynamic);
1048 /* Check the config */
1049 cfg = ast_load("meetme.conf");
1051 ast_log(LOG_WARNING, "No meetme.conf file :(\n");
1054 var = ast_variable_browse(cfg, "rooms");
1056 if (!strcasecmp(var->name, "conf")) {
1057 /* Separate the PIN */
1060 if ((pin = ast_strdupa(var->value))) {
1061 conf = strsep(&pin, "|,");
1062 if (!strcasecmp(conf, confno)) {
1063 /* Bingo it's a valid conference */
1065 cnf = build_conf(confno, pin, make, dynamic);
1067 cnf = build_conf(confno, "", make, dynamic);
1075 ast_log(LOG_DEBUG, "%s isn't a valid conference\n", confno);
1083 /*--- count_exec: The MeetmeCount application */
1084 static int count_exec(struct ast_channel *chan, void *data)
1086 struct localuser *u;
1088 struct ast_conference *conf;
1090 char *confnum, *localdata;
1093 if (!data || ast_strlen_zero(data)) {
1094 ast_log(LOG_WARNING, "MeetMeCount requires an argument (conference number)\n");
1097 localdata = ast_strdupa(data);
1099 confnum = strsep(&localdata,"|");
1100 conf = find_conf(chan, confnum, 0, 0, NULL);
1102 count = conf->users;
1106 if (localdata && !ast_strlen_zero(localdata)){
1107 /* have var so load it and exit */
1108 snprintf(val,sizeof(val), "%i",count);
1109 pbx_builtin_setvar_helper(chan, localdata,val);
1111 if (chan->_state != AST_STATE_UP)
1113 res = ast_say_number(chan, count, "", chan->language, (char *) NULL); /* Needs gender */
1115 LOCAL_USER_REMOVE(u);
1119 /*--- conf_exec: The meetme() application */
1120 static int conf_exec(struct ast_channel *chan, void *data)
1123 struct localuser *u;
1124 char confno[AST_MAX_EXTENSION] = "";
1127 struct ast_conference *cnf;
1130 int empty = 0, empty_no_pin = 0;
1131 char *notdata, *info, *inflags = NULL, *inpin = NULL, the_pin[AST_MAX_EXTENSION] = "";
1133 if (!data || ast_strlen_zero(data)) {
1140 if (chan->_state != AST_STATE_UP)
1143 info = ast_strdupa((char *)notdata);
1146 char *tmp = strsep(&info, "|");
1147 strncpy(confno, tmp, sizeof(confno));
1148 if (ast_strlen_zero(confno)) {
1153 inflags = strsep(&info, "|");
1155 inpin = strsep(&info, "|");
1157 strncpy(the_pin, inpin, sizeof(the_pin) - 1);
1160 if (strchr(inflags, 'a'))
1161 confflags |= CONFFLAG_ADMIN;
1162 if (strchr(inflags, 'm'))
1163 confflags |= CONFFLAG_MONITOR;
1164 if (strchr(inflags, 'p'))
1165 confflags |= CONFFLAG_POUNDEXIT;
1166 if (strchr(inflags, 's'))
1167 confflags |= CONFFLAG_STARMENU;
1168 if (strchr(inflags, 't'))
1169 confflags |= CONFFLAG_TALKER;
1170 if (strchr(inflags, 'q'))
1171 confflags |= CONFFLAG_QUIET;
1172 if (strchr(inflags, 'M'))
1173 confflags |= CONFFLAG_MOH;
1174 if (strchr(inflags, 'x'))
1175 confflags |= CONFFLAG_ADMINEXIT;
1176 if (strchr(inflags, 'b'))
1177 confflags |= CONFFLAG_AGI;
1178 if (strchr(inflags, 'w'))
1179 confflags |= CONFFLAG_WAITMARKED;
1180 if (strchr(inflags, 'd'))
1182 if (strchr(inflags, 'D')) {
1185 strncpy(the_pin, "q", sizeof(the_pin) - 1);
1188 if (strchr(inflags, 'e'))
1190 if (strchr(inflags, 'E')) {
1201 struct ast_config *cfg;
1202 struct ast_variable *var;
1205 memset(map, 0, sizeof(map));
1207 ast_mutex_lock(&conflock);
1210 if (sscanf(cnf->confno, "%d", &confno_int) == 1) {
1211 /* Disqualify in use conference */
1212 if (confno_int >= 0 && confno_int < 1024)
1217 ast_mutex_unlock(&conflock);
1219 /* We only need to load the config file for static and empty_no_pin (otherwise we don't care) */
1220 if ((empty_no_pin) || (!dynamic)) {
1221 cfg = ast_load("meetme.conf");
1223 var = ast_variable_browse(cfg, "rooms");
1225 if (!strcasecmp(var->name, "conf")) {
1226 char *stringp = ast_strdupa(var->value);
1228 char *confno_tmp = strsep(&stringp, "|,");
1230 if (sscanf(confno_tmp, "%d", &confno_int) == 1) {
1231 if ((confno_int >= 0) && (confno_int < 1024)) {
1232 if (stringp && empty_no_pin) {
1238 /* For static: run through the list and see if this conference is empty */
1239 ast_mutex_lock(&conflock);
1242 if (!strcmp(confno_tmp, cnf->confno)) {
1243 /* The conference exists, therefore it's not empty */
1249 ast_mutex_unlock(&conflock);
1251 /* At this point, we have a confno_tmp (static conference) that is empty */
1252 if ((empty_no_pin && ((!stringp) || (stringp && (stringp[0] == '\0')))) || (!empty_no_pin)) {
1253 /* Case 1: empty_no_pin and pin is nonexistant (NULL)
1254 * Case 2: empty_no_pin and pin is blank (but not NULL)
1255 * Case 3: not empty_no_pin
1257 strncpy(confno, confno_tmp, sizeof(confno) - 1);
1259 /* XXX the map is not complete (but we do have a confno) */
1264 ast_log(LOG_ERROR, "Out of memory\n");
1272 /* Select first conference number not in use */
1273 if (ast_strlen_zero(confno) && dynamic) {
1274 for (i=0;i<1024;i++) {
1276 snprintf(confno, sizeof(confno) - 1, "%d", i);
1283 if (ast_strlen_zero(confno)) {
1284 res = ast_streamfile(chan, "conf-noempty", chan->language);
1286 ast_waitstream(chan, "");
1288 if (sscanf(confno, "%d", &confno_int) == 1) {
1289 res = ast_streamfile(chan, "conf-enteringno", chan->language);
1291 ast_waitstream(chan, "");
1292 res = ast_say_digits(chan, confno_int, "", chan->language);
1295 ast_log(LOG_ERROR, "Could not scan confno '%s'\n", confno);
1299 while (allowretry && (ast_strlen_zero(confno)) && (++retrycnt < 4)) {
1300 /* Prompt user for conference number */
1301 res = ast_app_getdata(chan, "conf-getconfno", confno, sizeof(confno) - 1, 0);
1303 /* Don't try to validate when we catch an error */
1309 if (!ast_strlen_zero(confno)) {
1310 /* Check the validity of the conference */
1311 cnf = find_conf(chan, confno, 1, dynamic, the_pin);
1313 res = ast_streamfile(chan, "conf-invalid", chan->language);
1315 ast_waitstream(chan, "");
1320 if (!ast_strlen_zero(cnf->pin)) {
1321 char pin[AST_MAX_EXTENSION];
1324 strncpy(pin, the_pin, sizeof(pin) - 1);
1327 /* Prompt user for pin if pin is required */
1328 res = ast_app_getdata(chan, "conf-getpin", pin, sizeof(pin) - 1, 0);
1331 if (!strcasecmp(pin, cnf->pin)) {
1334 /* Run the conference */
1335 res = conf_run(chan, cnf, confflags);
1338 res = ast_streamfile(chan, "conf-invalidpin", chan->language);
1340 ast_waitstream(chan, "");
1350 /* No pin required */
1353 /* Run the conference */
1354 res = conf_run(chan, cnf, confflags);
1358 } while (allowretry);
1359 /* Do the conference */
1360 LOCAL_USER_REMOVE(u);
1364 static struct ast_conf_user* find_user(struct ast_conference *conf, char *callerident) {
1365 struct ast_conf_user *user = NULL;
1366 char usrno[1024] = "";
1367 if (conf && callerident) {
1368 user = conf->firstuser;
1370 sprintf(usrno, "%i", user->user_no);
1371 if (strcmp(usrno, callerident) == 0)
1373 user = user->nextuser;
1379 /*--- admin_exec: The MeetMeadmin application */
1380 /* MeetMeAdmin(confno, command, caller) */
1381 static int admin_exec(struct ast_channel *chan, void *data) {
1382 char *params, *command = NULL, *caller = NULL, *conf = NULL;
1383 struct ast_conference *cnf;
1384 struct ast_conf_user *user = NULL;
1386 ast_mutex_lock(&conflock);
1387 /* The param has the conference number the user and the command to execute */
1388 if (data && !ast_strlen_zero(data)) {
1389 params = ast_strdupa((char *) data);
1390 conf = strsep(¶ms, "|");
1391 command = strsep(¶ms, "|");
1392 caller = strsep(¶ms, "|");
1394 ast_mutex_lock(&conflock);
1397 if (strcmp(cnf->confno, conf) == 0)
1401 ast_mutex_unlock(&conflock);
1404 user = find_user(cnf, caller);
1407 switch((int) (*command)) {
1408 case 76: /* L: Lock */
1411 case 108: /* l: Unlock */
1414 case 75: /* K: kick all users*/
1415 user = cnf->firstuser;
1417 user->adminflags |= ADMINFLAG_KICKME;
1418 if (user->nextuser) {
1419 user = user->nextuser;
1425 case 77: /* M: Mute */
1427 user->adminflags |= ADMINFLAG_MUTED;
1429 ast_log(LOG_NOTICE, "Specified User not found!");
1432 case 109: /* m: Unmute */
1433 if (user && (user->adminflags & ADMINFLAG_MUTED)) {
1434 user->adminflags ^= ADMINFLAG_MUTED;
1436 ast_log(LOG_NOTICE, "Specified User not found or he muted himself!");
1439 case 107: /* k: Kick user */
1441 user->adminflags |= ADMINFLAG_KICKME;
1443 ast_log(LOG_NOTICE, "Specified User not found!");
1448 ast_log(LOG_NOTICE, "Conference Number not found\n");
1451 ast_mutex_unlock(&conflock);
1455 int unload_module(void)
1457 STANDARD_HANGUP_LOCALUSERS;
1458 ast_cli_unregister(&cli_show_confs);
1459 ast_cli_unregister(&cli_conf);
1460 ast_unregister_application(app3);
1461 ast_unregister_application(app2);
1462 return ast_unregister_application(app);
1465 int load_module(void)
1467 ast_cli_register(&cli_show_confs);
1468 ast_cli_register(&cli_conf);
1469 ast_register_application(app3, admin_exec, synopsis3, descrip3);
1470 ast_register_application(app2, count_exec, synopsis2, descrip2);
1471 return ast_register_application(app, conf_exec, synopsis, descrip);
1474 char *description(void)
1482 STANDARD_USECOUNT(res);
1488 return ASTERISK_GPL_KEY;