2 * Asterisk -- A telephony toolkit for Linux.
4 * Meet me conference bridge
6 * Copyright (C) 1999-2004, Digium, Inc.
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 " 'X' -- allow user to exit the conference by entering a valid single\n"
63 " digit extension ${MEETME_EXIT_CONTEXT} or the current context\n"
64 " if that variable is not defined.\n"
65 " 'd' -- dynamically add conference\n"
66 " 'D' -- dynamically add conference, prompting for a PIN\n"
67 " 'e' -- select an empty conference\n"
68 " 'E' -- select an empty pinless conference\n"
69 " 'v' -- video mode\n"
70 " 'q' -- quiet mode (don't play enter/leave sounds)\n"
71 " 'M' -- enable music on hold when the conference has a single caller\n"
72 " 'x' -- close the conference when last marked user exits\n"
73 " 'w' -- wait until the marked user enters the conference\n"
74 " 'b' -- run AGI script specified in ${MEETME_AGI_BACKGROUND}\n"
75 " Default: conf-background.agi\n"
76 " (Note: This does not work with non-Zap channels in the same conference)\n"
77 " 's' -- Present menu (user or admin) when '*' is received ('send' to menu)\n"
78 " 'a' -- set admin mode\n"
79 " 'A' -- set marked mode\n";
81 static char *descrip2 =
82 " MeetMeCount(confno[|var]): Plays back the number of users in the specifiedi\n"
83 "MeetMe conference. If var is specified, playback will be skipped and the value\n"
84 "will be returned in the variable. Returns 0 on success or -1 on a hangup.\n"
85 "A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING FUNCTIONALITY.\n";
87 static char *descrip3 =
88 " MeetMeAdmin(confno,command[,user]): Run admin command for conference\n"
89 " 'K' -- Kick all users out of conference\n"
90 " 'k' -- Kick one user out of conference\n"
91 " 'L' -- Lock conference\n"
92 " 'l' -- Unlock conference\n"
93 " 'M' -- Mute conference\n"
94 " 'm' -- Unmute conference\n"
101 static struct ast_conference {
102 char confno[AST_MAX_EXTENSION]; /* Conference */
103 struct ast_channel *chan; /* Announcements channel */
104 int fd; /* Announcements fd */
105 int zapconf; /* Zaptel Conf # */
106 int users; /* Number of active users */
107 int markedusers; /* Number of marked users */
108 struct ast_conf_user *firstuser; /* Pointer to the first user struct */
109 struct ast_conf_user *lastuser; /* Pointer to the last user struct */
110 time_t start; /* Start time (s) */
111 int isdynamic; /* Created on the fly? */
112 int locked; /* Is the conference locked? */
113 char pin[AST_MAX_EXTENSION]; /* If protected by a PIN */
114 struct ast_conference *next;
117 struct ast_conf_user {
118 int user_no; /* User Number */
119 struct ast_conf_user *prevuser; /* Pointer to the previous user */
120 struct ast_conf_user *nextuser; /* Pointer to the next user */
121 int userflags; /* Flags as set in the conference */
122 int adminflags; /* Flags set by the Admin */
123 struct ast_channel *chan; /* Connected channel */
124 char usrvalue[50]; /* Custom User Value */
125 time_t jointime; /* Time the user joined the conference */
128 #define ADMINFLAG_MUTED (1 << 1) /* User is muted */
129 #define ADMINFLAG_KICKME (1 << 2) /* User is kicked */
132 AST_MUTEX_DEFINE_STATIC(conflock);
134 static int admin_exec(struct ast_channel *chan, void *data);
142 #define CONF_SIZE 320
144 #define CONFFLAG_ADMIN (1 << 1) /* If set the user has admin access on the conference */
145 #define CONFFLAG_MONITOR (1 << 2) /* If set the user can only receive audio from the conference */
146 #define CONFFLAG_POUNDEXIT (1 << 3) /* If set asterisk will exit conference when '#' is pressed */
147 #define CONFFLAG_STARMENU (1 << 4) /* If set asterisk will provide a menu to the user what '*' is pressed */
148 #define CONFFLAG_TALKER (1 << 5) /* If set the use can only send audio to the conference */
149 #define CONFFLAG_QUIET (1 << 6) /* If set there will be no enter or leave sounds */
150 #define CONFFLAG_VIDEO (1 << 7) /* Set to enable video mode */
151 #define CONFFLAG_AGI (1 << 8) /* Set to run AGI Script in Background */
152 #define CONFFLAG_MOH (1 << 9) /* Set to have music on hold when user is alone in conference */
153 #define CONFFLAG_MARKEDEXIT (1 << 10) /* If set the MeetMe will return if all marked with this flag left */
154 #define CONFFLAG_WAITMARKED (1 << 11) /* If set, the MeetMe will wait until a marked user enters */
155 #define CONFFLAG_EXIT_CONTEXT (1 << 12) /* If set, the MeetMe will exit to the specified context */
156 #define CONFFLAG_MARKEDUSER (1 << 13) /* If set, the user will be marked */
159 static int careful_write(int fd, unsigned char *data, int len)
164 x = ZT_IOMUX_WRITE | ZT_IOMUX_SIGEVENT;
165 res = ioctl(fd, ZT_IOMUX, &x);
167 res = write(fd, data, len);
169 if (errno != EAGAIN) {
170 ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
181 static void conf_play(struct ast_conference *conf, int sound)
185 ast_mutex_lock(&conflock);
200 careful_write(conf->fd, data, len);
201 ast_mutex_unlock(&conflock);
204 static struct ast_conference *build_conf(char *confno, char *pin, int make, int dynamic)
206 struct ast_conference *cnf;
207 struct zt_confinfo ztc;
208 ast_mutex_lock(&conflock);
211 if (!strcmp(confno, cnf->confno))
215 if (!cnf && (make || dynamic)) {
216 cnf = malloc(sizeof(struct ast_conference));
219 memset(cnf, 0, sizeof(struct ast_conference));
220 strncpy(cnf->confno, confno, sizeof(cnf->confno) - 1);
221 strncpy(cnf->pin, pin, sizeof(cnf->pin) - 1);
222 cnf->markedusers = 0;
223 cnf->chan = ast_request("zap", AST_FORMAT_ULAW, "pseudo");
225 cnf->fd = cnf->chan->fds[0]; /* for use by conf_play() */
227 ast_log(LOG_WARNING, "Unable to open pseudo channel - trying device\n");
228 cnf->fd = open("/dev/zap/pseudo", O_RDWR);
230 ast_log(LOG_WARNING, "Unable to open pseudo device\n");
236 memset(&ztc, 0, sizeof(ztc));
237 /* Setup a new zap conference */
240 ztc.confmode = ZT_CONF_CONFANN;
241 if (ioctl(cnf->fd, ZT_SETCONF, &ztc)) {
242 ast_log(LOG_WARNING, "Error setting conference\n");
244 ast_hangup(cnf->chan);
251 /* Fill the conference struct */
252 cnf->start = time(NULL);
253 cnf->zapconf = ztc.confno;
254 cnf->isdynamic = dynamic;
255 cnf->firstuser = NULL;
256 cnf->lastuser = NULL;
258 if (option_verbose > 2)
259 ast_verbose(VERBOSE_PREFIX_3 "Created MeetMe conference %d for conference '%s'\n", cnf->zapconf, cnf->confno);
263 ast_log(LOG_WARNING, "Out of memory\n");
266 ast_mutex_unlock(&conflock);
270 static int confs_show(int fd, int argc, char **argv)
272 ast_cli(fd, "Deprecated! Please use 'meetme' instead.\n");
273 return RESULT_SUCCESS;
276 static char show_confs_usage[] =
277 "Deprecated! Please use 'meetme' instead.\n";
279 static struct ast_cli_entry cli_show_confs = {
280 { "show", "conferences", NULL }, confs_show,
281 "Show status of conferences", show_confs_usage, NULL };
283 static int conf_cmd(int fd, int argc, char **argv) {
284 /* Process the command */
285 struct ast_conference *cnf;
286 struct ast_conf_user *user;
288 int i = 0, total = 0;
290 char *header_format = "%-14s %-14s %-10s %-8s %-8s\n";
291 char *data_format = "%-12.12s %4.4d %4.4s %02d:%02d:%02d %-8s\n";
292 char cmdline[1024] = "";
295 ast_cli(fd, "Invalid Arguments.\n");
296 /* Check for length so no buffer will overflow... */
297 for (i = 0; i < argc; i++) {
298 if (strlen(argv[i]) > 100)
299 ast_cli(fd, "Invalid Arguments.\n");
302 /* 'MeetMe': List all the conferences */
306 ast_cli(fd, "No active MeetMe conferences.\n");
307 return RESULT_SUCCESS;
309 ast_cli(fd, header_format, "Conf Num", "Parties", "Marked", "Activity", "Creation");
311 if (cnf->markedusers == 0)
312 strncpy(cmdline, "N/A ", sizeof(cmdline) - 1);
314 snprintf(cmdline, sizeof(cmdline), "%4.4d", cnf->markedusers);
315 hr = (now - cnf->start) / 3600;
316 min = ((now - cnf->start) % 3600) / 60;
317 sec = (now - cnf->start) % 60;
319 ast_cli(fd, data_format, cnf->confno, cnf->users, cmdline, hr, min, sec, cnf->isdynamic ? "Dynamic" : "Static");
324 ast_cli(fd, "* Total number of MeetMe users: %d\n", total);
325 return RESULT_SUCCESS;
328 return RESULT_SHOWUSAGE;
329 strncpy(cmdline, argv[2], sizeof(cmdline) - 1); /* Argv 2: conference number */
330 if (strstr(argv[1], "lock")) {
331 if (strcmp(argv[1], "lock") == 0) {
333 strncat(cmdline, "|L", sizeof(cmdline) - strlen(cmdline) - 1);
336 strncat(cmdline, "|l", sizeof(cmdline) - strlen(cmdline) - 1);
338 } else if (strstr(argv[1], "mute")) {
340 return RESULT_SHOWUSAGE;
341 if (strcmp(argv[1], "mute") == 0) {
343 strncat(cmdline, "|M|", sizeof(cmdline) - strlen(cmdline) - 1);
344 strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
347 strncat(cmdline, "|m|", sizeof(cmdline) - strlen(cmdline) - 1);
348 strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
350 } else if (strcmp(argv[1], "kick") == 0) {
352 return RESULT_SHOWUSAGE;
353 if (strcmp(argv[3], "all") == 0) {
355 strncat(cmdline, "|K", sizeof(cmdline) - strlen(cmdline) - 1);
357 /* Kick a single user */
358 strncat(cmdline, "|k|", sizeof(cmdline) - strlen(cmdline) - 1);
359 strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
361 } else if(strcmp(argv[1], "list") == 0) {
362 /* List all the users in a conference */
364 ast_cli(fd, "No active conferences.\n");
365 return RESULT_SUCCESS;
368 /* Find the right conference */
370 if (strcmp(cnf->confno, argv[2]) == 0)
375 ast_cli(fd, "No such conference: %s.\n",argv[2]);
376 return RESULT_SUCCESS;
379 /* Show all the users */
380 user = cnf->firstuser;
382 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)" : "" );
383 user = user->nextuser;
385 return RESULT_SUCCESS;
387 return RESULT_SHOWUSAGE;
388 ast_log(LOG_DEBUG, "Cmdline: %s\n", cmdline);
389 admin_exec(NULL, cmdline);
393 static char *complete_confcmd(char *line, char *word, int pos, int state) {
394 #define CONF_COMMANDS 6
395 int which = 0, x = 0;
396 struct ast_conference *cnf = NULL;
397 struct ast_conf_user *usr = NULL;
400 char cmds[CONF_COMMANDS][20] = {"lock", "unlock", "mute", "unmute", "kick", "list"};
405 for (x = 0;x < CONF_COMMANDS; x++) {
406 if (!strncasecmp(cmds[x], word, strlen(word))) {
407 if (++which > state) {
408 return strdup(cmds[x]);
412 } else if (pos == 2) {
413 /* Conference Number */
414 ast_mutex_lock(&conflock);
417 if (!strncasecmp(word, cnf->confno, strlen(word))) {
423 ast_mutex_unlock(&conflock);
424 return cnf ? strdup(cnf->confno) : NULL;
425 } else if (pos == 3) {
426 /* User Number || Conf Command option*/
427 if (strstr(line, "mute") || strstr(line, "kick")) {
428 if ((state == 0) && (strstr(line, "kick")) && !(strncasecmp(word, "all", strlen(word)))) {
429 return strdup("all");
432 ast_mutex_lock(&conflock);
435 /* TODO: Find the conf number from the cmdline (ignore spaces) <- test this and make it fail-safe! */
436 myline = ast_strdupa(line);
437 if (strsep(&myline, " ") && strsep(&myline, " ") && !confno) {
438 while((confno = strsep(&myline, " ")) && (strcmp(confno, " ") == 0))
443 if (strcmp(confno, cnf->confno) == 0) {
449 /* Search for the user */
450 usr = cnf->firstuser;
452 snprintf(usrno, sizeof(usrno), "%i", usr->user_no);
453 if (!strncasecmp(word, usrno, strlen(word))) {
460 ast_mutex_unlock(&conflock);
461 return usr ? strdup(usrno) : NULL;
467 static char conf_usage[] =
468 "Usage: meetme (un)lock|(un)mute|kick|list <confno> <usernumber>\n"
469 " Executes a command for the conference or on a conferee\n";
471 static struct ast_cli_entry cli_conf = {
472 { "meetme", NULL, NULL }, conf_cmd,
473 "Execute a command on a conference or conferee", conf_usage, complete_confcmd };
475 static int confnonzero(void *ptr)
477 struct ast_conference *conf = ptr;
479 ast_mutex_lock(&conflock);
480 res = (conf->markedusers == 0);
481 ast_mutex_unlock(&conflock);
485 static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int confflags)
487 struct ast_conference *prev=NULL, *cur;
488 struct ast_conf_user *user = malloc(sizeof(struct ast_conf_user));
490 struct zt_confinfo ztc;
492 struct ast_channel *c;
507 int using_pseudo = 0;
511 char *agifiledefault = "conf-background.agi";
512 char meetmesecs[30] = "";
513 char exitcontext[AST_MAX_EXTENSION] = "";
516 char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
517 char *buf = __buf + AST_FRIENDLY_OFFSET;
520 ast_log(LOG_ERROR, "Out of memory\n");
523 memset(user, 0, sizeof(struct ast_conf_user));
525 user->user_no = 0; /* User number 0 means starting up user! (dead - not in the list!) */
528 /* Sorry, but this confernce is locked! */
529 if (!ast_streamfile(chan, "conf-locked", chan->language))
530 ast_waitstream(chan, "");
534 if (confflags & CONFFLAG_MARKEDUSER)
537 ast_mutex_lock(&conflock);
538 if (conf->firstuser == NULL) {
539 /* Fill the first new User struct */
541 user->nextuser = NULL;
542 user->prevuser = NULL;
543 conf->firstuser = user;
544 conf->lastuser = user;
546 /* Fill the new user struct */
547 user->user_no = conf->lastuser->user_no + 1;
548 user->prevuser = conf->lastuser;
549 user->nextuser = NULL;
550 if (conf->lastuser->nextuser != NULL) {
551 ast_log(LOG_WARNING, "Error in User Management!\n");
552 ast_mutex_unlock(&conflock);
555 conf->lastuser->nextuser = user;
556 conf->lastuser = user;
559 strncpy(user->usrvalue, "test", sizeof(user->usrvalue) - 1);
561 user->userflags = confflags;
562 user->adminflags = 0;
563 ast_mutex_unlock(&conflock);
564 origquiet = confflags & CONFFLAG_QUIET;
565 if (confflags & CONFFLAG_EXIT_CONTEXT) {
566 if ((agifile = pbx_builtin_getvar_helper(chan, "MEETME_EXIT_CONTEXT")))
567 strncpy(exitcontext, agifile, sizeof(exitcontext) - 1);
568 else if (!ast_strlen_zero(chan->macrocontext))
569 strncpy(exitcontext, chan->macrocontext, sizeof(exitcontext) - 1);
571 strncpy(exitcontext, chan->context, sizeof(exitcontext) - 1);
573 while((confflags & CONFFLAG_WAITMARKED) && (conf->markedusers == 0)) {
574 confflags &= ~CONFFLAG_QUIET;
575 confflags |= origquiet;
576 /* XXX Announce that we're waiting on the conference lead to join */
577 if (!(confflags & CONFFLAG_QUIET)) {
578 res = ast_streamfile(chan, "vm-dialout", chan->language);
580 res = ast_waitstream(chan, "");
583 /* If we're waiting with hold music, set to silent mode */
585 confflags |= CONFFLAG_QUIET;
586 ast_moh_start(chan, NULL);
587 res = ast_safe_sleep_conditional(chan, 60000, confnonzero, conf);
591 ast_log(LOG_DEBUG, "Got hangup on '%s' already\n", chan->name);
596 if (!(confflags & CONFFLAG_QUIET) && conf->users == 1) {
597 if (!ast_streamfile(chan, "conf-onlyperson", chan->language)) {
598 if (ast_waitstream(chan, "") < 0)
604 /* Set it into linear mode (write) */
605 if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
606 ast_log(LOG_WARNING, "Unable to set '%s' to write linear mode\n", chan->name);
610 /* Set it into linear mode (read) */
611 if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0) {
612 ast_log(LOG_WARNING, "Unable to set '%s' to read linear mode\n", chan->name);
615 ast_indicate(chan, -1);
616 retryzap = strcasecmp(chan->type, "Zap");
618 origfd = chan->fds[0];
620 fd = open("/dev/zap/pseudo", O_RDWR);
622 ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
626 /* Make non-blocking */
627 flags = fcntl(fd, F_GETFL);
629 ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
633 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
634 ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
638 /* Setup buffering information */
639 memset(&bi, 0, sizeof(bi));
640 bi.bufsize = CONF_SIZE/2;
641 bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
642 bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
644 if (ioctl(fd, ZT_SET_BUFINFO, &bi)) {
645 ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
650 if (ioctl(fd, ZT_SETLINEAR, &x)) {
651 ast_log(LOG_WARNING, "Unable to set linear mode: %s\n", strerror(errno));
657 /* XXX Make sure we're not running on a pseudo channel XXX */
661 memset(&ztc, 0, sizeof(ztc));
662 /* Check to see if we're in a conference... */
664 if (ioctl(fd, ZT_GETCONF, &ztc)) {
665 ast_log(LOG_WARNING, "Error getting conference\n");
670 /* Whoa, already in a conference... Retry... */
672 ast_log(LOG_DEBUG, "Zap channel is in a conference already, retrying with pseudo\n");
677 memset(&ztc, 0, sizeof(ztc));
678 /* Add us to the conference */
680 ztc.confno = conf->zapconf;
681 if (confflags & CONFFLAG_MONITOR)
682 ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER;
683 else if (confflags & CONFFLAG_TALKER)
684 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER;
686 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
688 if (ioctl(fd, ZT_SETCONF, &ztc)) {
689 ast_log(LOG_WARNING, "Error setting conference\n");
693 ast_log(LOG_DEBUG, "Placed channel %s in ZAP conf %d\n", chan->name, conf->zapconf);
695 manager_event(EVENT_FLAG_CALL, "MeetmeJoin",
700 chan->name, chan->uniqueid, conf->confno, user->user_no);
702 if (!firstpass && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN)) {
704 if (!(confflags & CONFFLAG_QUIET))
705 conf_play(conf, ENTER);
708 if (confflags & CONFFLAG_AGI) {
710 /* Get name of AGI file to run from $(MEETME_AGI_BACKGROUND)
711 or use default filename of conf-background.agi */
713 agifile = pbx_builtin_getvar_helper(chan,"MEETME_AGI_BACKGROUND");
715 agifile = agifiledefault;
717 if (!strcasecmp(chan->type,"Zap")) {
718 /* Set CONFMUTE mode on Zap channel to mute DTMF tones */
720 ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
722 /* Find a pointer to the agi app and execute the script */
723 app = pbx_findapp("agi");
725 ret = pbx_exec(chan, app, agifile, 1);
727 ast_log(LOG_WARNING, "Could not find application (agi)\n");
730 if (!strcasecmp(chan->type,"Zap")) {
731 /* Remove CONFMUTE mode on Zap channel */
733 ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
736 if (!strcasecmp(chan->type,"Zap") && (confflags & CONFFLAG_STARMENU)) {
737 /* Set CONFMUTE mode on Zap channel to mute DTMF tones when the menu is enabled */
739 ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
744 c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
746 /* Update the struct with the actual confflags */
747 user->userflags = confflags;
749 /* trying to add moh for single person conf */
750 if (confflags & CONFFLAG_MOH) {
751 if (conf->users == 1) {
752 if (musiconhold == 0) {
753 ast_moh_start(chan, NULL);
764 /* Leave if the last marked user left */
765 if (conf->markedusers == 0 && confflags & CONFFLAG_MARKEDEXIT) {
770 /* Check if the admin changed my modes */
771 if (user->adminflags) {
772 /* Set the new modes */
773 if ((user->adminflags & ADMINFLAG_MUTED) && (ztc.confmode & ZT_CONF_TALKER)) {
774 ztc.confmode ^= ZT_CONF_TALKER;
775 if (ioctl(fd, ZT_SETCONF, &ztc)) {
776 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
781 if (!(user->adminflags & ADMINFLAG_MUTED) && !(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & ZT_CONF_TALKER)) {
782 ztc.confmode |= ZT_CONF_TALKER;
783 if (ioctl(fd, ZT_SETCONF, &ztc)) {
784 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
789 if (user->adminflags & ADMINFLAG_KICKME) {
790 //You have been kicked.
791 if (!ast_streamfile(chan, "conf-kicked", chan->language))
792 ast_waitstream(chan, "");
796 } else if (!(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & ZT_CONF_TALKER)) {
797 ztc.confmode |= ZT_CONF_TALKER;
798 if (ioctl(fd, ZT_SETCONF, &ztc)) {
799 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
806 if (c->fds[0] != origfd) {
808 /* Kill old pseudo */
811 ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n");
819 if ((f->frametype == AST_FRAME_DTMF) && (confflags & CONFFLAG_EXIT_CONTEXT)) {
821 tmp[0] = f->subclass;
823 if (ast_exists_extension(chan, exitcontext, tmp, 1, chan->callerid)) {
824 strncpy(chan->context, exitcontext, sizeof(chan->context) - 1);
825 strncpy(chan->exten, tmp, sizeof(chan->exten) - 1);
830 } else if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#') && (confflags & CONFFLAG_POUNDEXIT)) {
833 } else if (((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*') && (confflags & CONFFLAG_STARMENU)) || ((f->frametype == AST_FRAME_DTMF) && menu_active)) {
837 if ((confflags & CONFFLAG_ADMIN)) {
841 /* Record this sound! */
842 if (!ast_streamfile(chan, "conf-adminmenu", chan->language))
843 ast_waitstream(chan, "");
845 switch(f->subclass - 48) {
846 case 1: /* Un/Mute */
848 if (ztc.confmode & ZT_CONF_TALKER) {
849 ztc.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER;
850 confflags |= CONFFLAG_MONITOR ^ CONFFLAG_TALKER;
852 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
853 confflags ^= CONFFLAG_MONITOR | CONFFLAG_TALKER;
855 if (ioctl(fd, ZT_SETCONF, &ztc)) {
856 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
860 if (ztc.confmode & ZT_CONF_TALKER) {
861 if (!ast_streamfile(chan, "conf-unmuted", chan->language))
862 ast_waitstream(chan, "");
864 if (!ast_streamfile(chan, "conf-muted", chan->language))
865 ast_waitstream(chan, "");
868 case 2: /* Un/Lock the Conference */
872 if (!ast_streamfile(chan, "conf-unlockednow", chan->language))
873 ast_waitstream(chan, "");
876 if (!ast_streamfile(chan, "conf-lockednow", chan->language))
877 ast_waitstream(chan, "");
882 /* Play an error message! */
883 if (!ast_streamfile(chan, "conf-errormenu", chan->language))
884 ast_waitstream(chan, "");
892 /* Record this sound! */
893 if (!ast_streamfile(chan, "conf-usermenu", chan->language))
894 ast_waitstream(chan, "");
896 switch(f->subclass - 48) {
897 case 1: /* Un/Mute */
899 if (ztc.confmode & ZT_CONF_TALKER) {
900 ztc.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER;
901 confflags |= CONFFLAG_MONITOR ^ CONFFLAG_TALKER;
902 } else if (!(user->adminflags & ADMINFLAG_MUTED)) {
903 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
904 confflags ^= CONFFLAG_MONITOR | CONFFLAG_TALKER;
906 if (ioctl(fd, ZT_SETCONF, &ztc)) {
907 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
911 if (ztc.confmode & ZT_CONF_TALKER) {
912 if (!ast_streamfile(chan, "conf-unmuted", chan->language))
913 ast_waitstream(chan, "");
915 if (!ast_streamfile(chan, "conf-muted", chan->language))
916 ast_waitstream(chan, "");
921 /* Play an error message! */
922 if (!ast_streamfile(chan, "conf-errormenu", chan->language))
923 ast_waitstream(chan, "");
929 ast_moh_start(chan, NULL);
931 } else if (using_pseudo) {
932 if (f->frametype == AST_FRAME_VOICE) {
933 if (f->subclass == AST_FORMAT_SLINEAR) {
934 /* Carefully write */
935 careful_write(fd, f->data, f->datalen);
937 ast_log(LOG_WARNING, "Huh? Got a non-linear (%d) frame in the conference\n", f->subclass);
941 } else if (outfd > -1) {
942 res = read(outfd, buf, CONF_SIZE);
944 memset(&fr, 0, sizeof(fr));
945 fr.frametype = AST_FRAME_VOICE;
946 fr.subclass = AST_FORMAT_SLINEAR;
950 fr.offset = AST_FRIENDLY_OFFSET;
951 if (ast_write(chan, &fr) < 0) {
952 ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno));
956 ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno));
963 /* Take out of conference */
964 /* Add us to the conference */
968 if (ioctl(fd, ZT_SETCONF, &ztc)) {
969 ast_log(LOG_WARNING, "Error setting conference\n");
972 if (!(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN))
973 conf_play(conf, LEAVE);
976 ast_mutex_lock(&conflock);
977 if (user->user_no) { /* Only cleanup users who really joined! */
978 manager_event(EVENT_FLAG_CALL, "MeetmeLeave",
983 chan->name, chan->uniqueid, conf->confno, user->user_no);
986 if (confflags & CONFFLAG_MARKEDUSER)
990 /* No more users -- close this one out */
994 prev->next = conf->next;
1003 ast_log(LOG_WARNING, "Conference not found\n");
1005 ast_hangup(conf->chan);
1010 /* Remove the user struct */
1011 if (user == conf->firstuser) {
1012 if (user->nextuser) {
1013 /* There is another entry */
1014 user->nextuser->prevuser = NULL;
1016 /* We are the only entry */
1017 conf->lastuser = NULL;
1019 /* In either case */
1020 conf->firstuser = user->nextuser;
1021 } else if (user == conf->lastuser){
1023 user->prevuser->nextuser = NULL;
1025 ast_log(LOG_ERROR, "Bad bad bad! We're the last, not the first, but nobody before us??\n");
1026 conf->lastuser = user->prevuser;
1029 user->nextuser->prevuser = user->prevuser;
1031 ast_log(LOG_ERROR, "Bad! Bad! Bad! user->nextuser is NULL but we're not the end!\n");
1033 user->prevuser->nextuser = user->nextuser;
1035 ast_log(LOG_ERROR, "Bad! Bad! Bad! user->prevuser is NULL but we're not the beginning!\n");
1037 /* Return the number of seconds the user was in the conf */
1038 snprintf(meetmesecs, sizeof(meetmesecs), "%i", (int) (user->jointime - time(NULL)));
1039 pbx_builtin_setvar_helper(chan, "MEETMESECS", meetmesecs);
1043 ast_mutex_unlock(&conflock);
1047 static struct ast_conference *find_conf(struct ast_channel *chan, char *confno, int make, int dynamic, char *dynamic_pin)
1049 struct ast_config *cfg;
1050 struct ast_variable *var;
1051 struct ast_conference *cnf;
1053 /* Check first in the conference list */
1054 ast_mutex_lock(&conflock);
1057 if (!strcmp(confno, cnf->confno))
1061 ast_mutex_unlock(&conflock);
1065 /* No need to parse meetme.conf */
1066 ast_log(LOG_DEBUG, "Building dynamic conference '%s'\n", confno);
1068 if (dynamic_pin[0] == 'q') {
1069 /* Query the user to enter a PIN */
1070 ast_app_getdata(chan, "conf-getpin", dynamic_pin, AST_MAX_EXTENSION - 1, 0);
1072 cnf = build_conf(confno, dynamic_pin, make, dynamic);
1074 cnf = build_conf(confno, "", make, dynamic);
1077 /* Check the config */
1078 cfg = ast_load("meetme.conf");
1080 ast_log(LOG_WARNING, "No meetme.conf file :(\n");
1083 var = ast_variable_browse(cfg, "rooms");
1085 if (!strcasecmp(var->name, "conf")) {
1086 /* Separate the PIN */
1089 if ((pin = ast_strdupa(var->value))) {
1090 conf = strsep(&pin, "|,");
1091 if (!strcasecmp(conf, confno)) {
1092 /* Bingo it's a valid conference */
1094 cnf = build_conf(confno, pin, make, dynamic);
1096 cnf = build_conf(confno, "", make, dynamic);
1104 ast_log(LOG_DEBUG, "%s isn't a valid conference\n", confno);
1108 } else if (dynamic_pin) {
1109 /* Correct for the user selecting 'D' instead of 'd' to have
1110 someone join into a conference that has already been created
1112 if (dynamic_pin[0] == 'q')
1113 dynamic_pin[0] = '\0';
1118 /*--- count_exec: The MeetmeCount application */
1119 static int count_exec(struct ast_channel *chan, void *data)
1121 struct localuser *u;
1123 struct ast_conference *conf;
1125 char *confnum, *localdata;
1128 if (!data || ast_strlen_zero(data)) {
1129 ast_log(LOG_WARNING, "MeetMeCount requires an argument (conference number)\n");
1132 localdata = ast_strdupa(data);
1134 confnum = strsep(&localdata,"|");
1135 conf = find_conf(chan, confnum, 0, 0, NULL);
1137 count = conf->users;
1141 if (localdata && !ast_strlen_zero(localdata)){
1142 /* have var so load it and exit */
1143 snprintf(val,sizeof(val), "%i",count);
1144 pbx_builtin_setvar_helper(chan, localdata,val);
1146 if (chan->_state != AST_STATE_UP)
1148 res = ast_say_number(chan, count, "", chan->language, (char *) NULL); /* Needs gender */
1150 LOCAL_USER_REMOVE(u);
1154 /*--- conf_exec: The meetme() application */
1155 static int conf_exec(struct ast_channel *chan, void *data)
1158 struct localuser *u;
1159 char confno[AST_MAX_EXTENSION] = "";
1162 struct ast_conference *cnf;
1165 int empty = 0, empty_no_pin = 0;
1166 char *notdata, *info, *inflags = NULL, *inpin = NULL, the_pin[AST_MAX_EXTENSION] = "";
1168 if (!data || ast_strlen_zero(data)) {
1175 if (chan->_state != AST_STATE_UP)
1178 info = ast_strdupa((char *)notdata);
1181 char *tmp = strsep(&info, "|");
1182 strncpy(confno, tmp, sizeof(confno) - 1);
1183 if (ast_strlen_zero(confno)) {
1188 inflags = strsep(&info, "|");
1190 inpin = strsep(&info, "|");
1192 strncpy(the_pin, inpin, sizeof(the_pin) - 1);
1195 if (strchr(inflags, 'a'))
1196 confflags |= CONFFLAG_ADMIN;
1197 if (strchr(inflags, 'm'))
1198 confflags |= CONFFLAG_MONITOR;
1199 if (strchr(inflags, 'p'))
1200 confflags |= CONFFLAG_POUNDEXIT;
1201 if (strchr(inflags, 's'))
1202 confflags |= CONFFLAG_STARMENU;
1203 if (strchr(inflags, 't'))
1204 confflags |= CONFFLAG_TALKER;
1205 if (strchr(inflags, 'q'))
1206 confflags |= CONFFLAG_QUIET;
1207 if (strchr(inflags, 'M'))
1208 confflags |= CONFFLAG_MOH;
1209 if (strchr(inflags, 'x'))
1210 confflags |= CONFFLAG_MARKEDEXIT;
1211 if (strchr(inflags, 'X'))
1212 confflags |= CONFFLAG_EXIT_CONTEXT;
1213 if (strchr(inflags, 'A'))
1214 confflags |= CONFFLAG_MARKEDUSER;
1215 if (strchr(inflags, 'b'))
1216 confflags |= CONFFLAG_AGI;
1217 if (strchr(inflags, 'w'))
1218 confflags |= CONFFLAG_WAITMARKED;
1219 if (strchr(inflags, 'd'))
1221 if (strchr(inflags, 'D')) {
1224 strncpy(the_pin, "q", sizeof(the_pin) - 1);
1227 if (strchr(inflags, 'e'))
1229 if (strchr(inflags, 'E')) {
1240 struct ast_config *cfg;
1241 struct ast_variable *var;
1244 memset(map, 0, sizeof(map));
1246 ast_mutex_lock(&conflock);
1249 if (sscanf(cnf->confno, "%d", &confno_int) == 1) {
1250 /* Disqualify in use conference */
1251 if (confno_int >= 0 && confno_int < 1024)
1256 ast_mutex_unlock(&conflock);
1258 /* We only need to load the config file for static and empty_no_pin (otherwise we don't care) */
1259 if ((empty_no_pin) || (!dynamic)) {
1260 cfg = ast_load("meetme.conf");
1262 var = ast_variable_browse(cfg, "rooms");
1264 if (!strcasecmp(var->name, "conf")) {
1265 char *stringp = ast_strdupa(var->value);
1267 char *confno_tmp = strsep(&stringp, "|,");
1269 if (sscanf(confno_tmp, "%d", &confno_int) == 1) {
1270 if ((confno_int >= 0) && (confno_int < 1024)) {
1271 if (stringp && empty_no_pin) {
1277 /* For static: run through the list and see if this conference is empty */
1278 ast_mutex_lock(&conflock);
1281 if (!strcmp(confno_tmp, cnf->confno)) {
1282 /* The conference exists, therefore it's not empty */
1288 ast_mutex_unlock(&conflock);
1290 /* At this point, we have a confno_tmp (static conference) that is empty */
1291 if ((empty_no_pin && ((!stringp) || (stringp && (stringp[0] == '\0')))) || (!empty_no_pin)) {
1292 /* Case 1: empty_no_pin and pin is nonexistant (NULL)
1293 * Case 2: empty_no_pin and pin is blank (but not NULL)
1294 * Case 3: not empty_no_pin
1296 strncpy(confno, confno_tmp, sizeof(confno) - 1);
1298 /* XXX the map is not complete (but we do have a confno) */
1303 ast_log(LOG_ERROR, "Out of memory\n");
1311 /* Select first conference number not in use */
1312 if (ast_strlen_zero(confno) && dynamic) {
1313 for (i=0;i<1024;i++) {
1315 snprintf(confno, sizeof(confno), "%d", i);
1322 if (ast_strlen_zero(confno)) {
1323 res = ast_streamfile(chan, "conf-noempty", chan->language);
1325 ast_waitstream(chan, "");
1327 if (sscanf(confno, "%d", &confno_int) == 1) {
1328 res = ast_streamfile(chan, "conf-enteringno", chan->language);
1330 ast_waitstream(chan, "");
1331 res = ast_say_digits(chan, confno_int, "", chan->language);
1334 ast_log(LOG_ERROR, "Could not scan confno '%s'\n", confno);
1338 while (allowretry && (ast_strlen_zero(confno)) && (++retrycnt < 4)) {
1339 /* Prompt user for conference number */
1340 res = ast_app_getdata(chan, "conf-getconfno", confno, sizeof(confno) - 1, 0);
1342 /* Don't try to validate when we catch an error */
1348 if (!ast_strlen_zero(confno)) {
1349 /* Check the validity of the conference */
1350 cnf = find_conf(chan, confno, 1, dynamic, the_pin);
1352 res = ast_streamfile(chan, "conf-invalid", chan->language);
1354 ast_waitstream(chan, "");
1359 if (!ast_strlen_zero(cnf->pin)) {
1360 char pin[AST_MAX_EXTENSION]="";
1363 /* Allow the pin to be retried up to 3 times */
1364 for (j=0; j<3; j++) {
1366 strncpy(pin, the_pin, sizeof(pin) - 1);
1369 /* Prompt user for pin if pin is required */
1370 res = ast_app_getdata(chan, "conf-getpin", pin + strlen(pin), sizeof(pin) - 1 - strlen(pin), 0);
1373 if (!strcasecmp(pin, cnf->pin)) {
1376 /* Run the conference */
1377 res = conf_run(chan, cnf, confflags);
1381 res = ast_streamfile(chan, "conf-invalidpin", chan->language);
1383 ast_waitstream(chan, AST_DIGIT_ANY);
1398 /* Don't retry pin with a static pin */
1404 /* No pin required */
1407 /* Run the conference */
1408 res = conf_run(chan, cnf, confflags);
1412 } while (allowretry);
1413 /* Do the conference */
1414 LOCAL_USER_REMOVE(u);
1418 static struct ast_conf_user* find_user(struct ast_conference *conf, char *callerident) {
1419 struct ast_conf_user *user = NULL;
1420 char usrno[1024] = "";
1421 if (conf && callerident) {
1422 user = conf->firstuser;
1424 snprintf(usrno, sizeof(usrno), "%i", user->user_no);
1425 if (strcmp(usrno, callerident) == 0)
1427 user = user->nextuser;
1433 /*--- admin_exec: The MeetMeadmin application */
1434 /* MeetMeAdmin(confno, command, caller) */
1435 static int admin_exec(struct ast_channel *chan, void *data) {
1436 char *params, *command = NULL, *caller = NULL, *conf = NULL;
1437 struct ast_conference *cnf;
1438 struct ast_conf_user *user = NULL;
1440 ast_mutex_lock(&conflock);
1441 /* The param has the conference number the user and the command to execute */
1442 if (data && !ast_strlen_zero(data)) {
1443 params = ast_strdupa((char *) data);
1444 conf = strsep(¶ms, "|");
1445 command = strsep(¶ms, "|");
1446 caller = strsep(¶ms, "|");
1448 ast_mutex_lock(&conflock);
1451 if (strcmp(cnf->confno, conf) == 0)
1455 ast_mutex_unlock(&conflock);
1458 user = find_user(cnf, caller);
1461 switch((int) (*command)) {
1462 case 76: /* L: Lock */
1465 case 108: /* l: Unlock */
1468 case 75: /* K: kick all users*/
1469 user = cnf->firstuser;
1471 user->adminflags |= ADMINFLAG_KICKME;
1472 if (user->nextuser) {
1473 user = user->nextuser;
1479 case 77: /* M: Mute */
1481 user->adminflags |= ADMINFLAG_MUTED;
1483 ast_log(LOG_NOTICE, "Specified User not found!");
1486 case 109: /* m: Unmute */
1487 if (user && (user->adminflags & ADMINFLAG_MUTED)) {
1488 user->adminflags ^= ADMINFLAG_MUTED;
1490 ast_log(LOG_NOTICE, "Specified User not found or he muted himself!");
1493 case 107: /* k: Kick user */
1495 user->adminflags |= ADMINFLAG_KICKME;
1497 ast_log(LOG_NOTICE, "Specified User not found!");
1502 ast_log(LOG_NOTICE, "Conference Number not found\n");
1505 ast_mutex_unlock(&conflock);
1509 int unload_module(void)
1511 STANDARD_HANGUP_LOCALUSERS;
1512 ast_cli_unregister(&cli_show_confs);
1513 ast_cli_unregister(&cli_conf);
1514 ast_unregister_application(app3);
1515 ast_unregister_application(app2);
1516 return ast_unregister_application(app);
1519 int load_module(void)
1521 ast_cli_register(&cli_show_confs);
1522 ast_cli_register(&cli_conf);
1523 ast_register_application(app3, admin_exec, synopsis3, descrip3);
1524 ast_register_application(app2, count_exec, synopsis2, descrip2);
1525 return ast_register_application(app, conf_exec, synopsis, descrip);
1528 char *description(void)
1536 STANDARD_USECOUNT(res);
1542 return ASTERISK_GPL_KEY;