Fix dynamic user count, Take 4
[asterisk/asterisk.git] / apps / app_meetme.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Meet me conference bridge
5  * 
6  * Copyright (C) 1999, Mark Spencer
7  *
8  * Mark Spencer <markster@linux-support.net>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
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>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <stdlib.h>
31 #include <sys/ioctl.h>
32
33 #include <pthread.h>
34 #include <linux/zaptel.h>
35
36 static char *tdesc = "Simple MeetMe conference bridge";
37
38 static char *app = "MeetMe";
39 static char *app2 = "MeetMeCount";
40
41 static char *synopsis = "Simple MeetMe conference bridge";
42 static char *synopsis2 = "MeetMe participant count";
43
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.\n"
48
49 "The option string may contain zero or more of the following characters:\n"
50 "      'a' -- set admin mode\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 "      's' -- send user to admin/user menu if '*' is received\n"
55 "      'd' -- dynamically add conference\n"
56 "      'v' -- video mode\n"
57 "      'q' -- quiet mode (don't play enter/leave sounds)\n"
58 "      'M' -- enable music on hold when the conference has a single caller\n"
59 "      'b' -- run AGI script specified in ${MEETME_AGI_BACKGROUND}\n"
60 "             Default: conf-background.agi\n"
61 "             (Note: This does not work with non-Zap channels in the same conference)\n";
62
63 static char *descrip2 =
64 "  MeetMeCount(confno[|var]): Plays back the number of users in the specifiedi\n"
65 "MeetMe conference. If var is specified, playback will be skipped and the value\n"
66 "will be returned in the variable. Returns 0 on success or -1 on a hangup.\n"
67 "A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING FUNCTIONALITY.\n";
68
69 STANDARD_LOCAL_USER;
70
71 LOCAL_USER_DECL;
72
73 static struct ast_conference {
74         char confno[AST_MAX_EXTENSION];         /* Conference */
75         int fd;                         /* Announcements fd */
76         int zapconf;                    /* Zaptel Conf # */
77         int users;                      /* Number of active users */
78         time_t start;                   /* Start time (s) */
79         int isdynamic;                  /* Created on the fly? */
80         char pin[AST_MAX_EXTENSION];                    /* If protected by a PIN */
81         struct ast_conference *next;
82 } *confs;
83
84 static ast_mutex_t conflock = AST_MUTEX_INITIALIZER;
85
86 #include "enter.h"
87 #include "leave.h"
88
89 #define ENTER   0
90 #define LEAVE   1
91
92 #define CONF_SIZE 160
93
94 #define CONFFLAG_ADMIN  (1 << 1)        /* If set the user has admin access on the conference */
95 #define CONFFLAG_MONITOR (1 << 2)       /* If set the user can only receive audio from the conference */
96 #define CONFFLAG_POUNDEXIT (1 << 3)     /* If set asterisk will exit conference when '#' is pressed */
97 #define CONFFLAG_STARMENU (1 << 4)      /* If set asterisk will provide a menu to the user what '*' is pressed */
98 #define CONFFLAG_TALKER (1 << 5)        /* If set the use can only send audio to the conference */
99 #define CONFFLAG_QUIET (1 << 6)         /* If set there will be no enter or leave sounds */
100 #define CONFFLAG_VIDEO (1 << 7)         /* Set to enable video mode */
101 #define CONFFLAG_AGI (1 << 8)           /* Set to run AGI Script in Background */
102 #define CONFFLAG_MOH (1 << 9)           /* Set to have music on hold when */
103
104
105 static int careful_write(int fd, unsigned char *data, int len)
106 {
107         int res;
108         while(len) {
109                 res = write(fd, data, len);
110                 if (res < 1) {
111                         if (errno != EAGAIN) {
112                                 ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
113                                 return -1;
114                         } else
115                                 return 0;
116                 }
117                 len -= res;
118                 data += res;
119         }
120         return 0;
121 }
122
123 static void conf_play(struct ast_conference *conf, int sound)
124 {
125         unsigned char *data;
126         int len;
127         ast_mutex_lock(&conflock);
128         switch(sound) {
129         case ENTER:
130                 data = enter;
131                 len = sizeof(enter);
132                 break;
133         case LEAVE:
134                 data = leave;
135                 len = sizeof(leave);
136                 break;
137         default:
138                 data = NULL;
139                 len = 0;
140         }
141         if (data) 
142                 careful_write(conf->fd, data, len);
143         ast_mutex_unlock(&conflock);
144 }
145
146 static struct ast_conference *build_conf(char *confno, char *pin, int make, int dynamic)
147 {
148         struct ast_conference *cnf;
149         struct zt_confinfo ztc;
150         ast_mutex_lock(&conflock);
151         cnf = confs;
152         while(cnf) {
153                 if (!strcmp(confno, cnf->confno)) 
154                         break;
155                 cnf = cnf->next;
156         }
157         if (!cnf && (make || dynamic)) {
158                 cnf = malloc(sizeof(struct ast_conference));
159                 if (cnf) {
160                         /* Make a new one */
161                         memset(cnf, 0, sizeof(struct ast_conference));
162                         strncpy(cnf->confno, confno, sizeof(cnf->confno) - 1);
163                         strncpy(cnf->pin, pin, sizeof(cnf->pin) - 1);
164                         cnf->fd = open("/dev/zap/pseudo", O_RDWR);
165                         if (cnf->fd < 0) {
166                                 ast_log(LOG_WARNING, "Unable to open pseudo channel\n");
167                                 free(cnf);
168                                 cnf = NULL;
169                                 goto cnfout;
170                         }
171                         memset(&ztc, 0, sizeof(ztc));
172                         /* Setup a new zap conference */
173                         ztc.chan = 0;
174                         ztc.confno = -1;
175                         ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
176                         if (ioctl(cnf->fd, ZT_SETCONF, &ztc)) {
177                                 ast_log(LOG_WARNING, "Error setting conference\n");
178                                 close(cnf->fd);
179                                 free(cnf);
180                                 cnf = NULL;
181                                 goto cnfout;
182                         }
183                         cnf->start = time(NULL);
184                         cnf->zapconf = ztc.confno;
185                         cnf->isdynamic = dynamic;
186                         if (option_verbose > 2)
187                                 ast_verbose(VERBOSE_PREFIX_3 "Created ZapTel conference %d for conference '%s'\n", cnf->zapconf, cnf->confno);
188                         cnf->next = confs;
189                         confs = cnf;
190                 } else  
191                         ast_log(LOG_WARNING, "Out of memory\n");
192         }
193 cnfout:
194         ast_mutex_unlock(&conflock);
195         return cnf;
196 }
197
198 static int confs_show(int fd, int argc, char **argv)
199 {
200         struct ast_conference *conf;
201         int hr, min, sec;
202         time_t now;
203         char *header_format = "%14s %-14s %-8s  %-8s\n";
204         char *data_format = "%-12.12s   %4.4d          %02d:%02d:%02d  %-8s\n";
205
206         now = time(NULL);
207         if (argc != 2)
208                 return RESULT_SHOWUSAGE;
209         conf = confs;
210         if (!conf) {
211                 ast_cli(fd, "No active conferences.\n");
212                 return RESULT_SUCCESS;
213         }
214         ast_cli(fd, header_format, "Conf Num", "Parties", "Activity", "Creation");
215         while(conf) {
216                 hr = (now - conf->start) / 3600;
217                 min = ((now - conf->start) % 3600) / 60;
218                 sec = (now - conf->start) % 60;
219
220                 if (conf->isdynamic)
221                         ast_cli(fd, data_format, conf->confno, conf->users, hr, min, sec, "Dynamic");
222                 else
223                         ast_cli(fd, data_format, conf->confno, conf->users, hr, min, sec, "Static");
224
225                 conf = conf->next;
226         }
227         return RESULT_SUCCESS;
228 }
229
230 static char show_confs_usage[] = 
231 "Usage: show conferences\n"
232 "       Provides summary information on conferences with active\n"
233 "       participation.\n";
234
235 static struct ast_cli_entry cli_show_confs = {
236         { "show", "conferences", NULL }, confs_show, 
237         "Show status of conferences", show_confs_usage, NULL };
238
239 static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int confflags)
240 {
241         struct ast_conference *prev=NULL, *cur;
242         int fd;
243         struct zt_confinfo ztc;
244         struct ast_frame *f;
245         struct ast_channel *c;
246         struct ast_frame fr;
247         int outfd;
248         int ms;
249         int nfds;
250         int res;
251         int flags;
252         int retryzap;
253         int origfd;
254         int musiconhold = 0;
255         int firstpass = 0;
256         int ret = -1;
257         int x;
258
259         struct ast_app *app;
260         char *agifile;
261         char *agifiledefault = "conf-background.agi";
262
263         ZT_BUFFERINFO bi;
264         char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
265         char *buf = __buf + AST_FRIENDLY_OFFSET;
266         
267         conf->users++;
268         if (!(confflags & CONFFLAG_QUIET) && conf->users == 1) {
269                 if (!ast_streamfile(chan, "conf-onlyperson", chan->language))
270                         ast_waitstream(chan, "");
271         }
272
273         if (confflags & CONFFLAG_VIDEO) {       
274                 /* Set it into linear mode (write) */
275                 if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
276                         ast_log(LOG_WARNING, "Unable to set '%s' to write linear mode\n", chan->name);
277                         goto outrun;
278                 }
279
280                 /* Set it into linear mode (read) */
281                 if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0) {
282                         ast_log(LOG_WARNING, "Unable to set '%s' to read linear mode\n", chan->name);
283                         goto outrun;
284                 }
285         } else {
286                 /* Set it into U-law mode (write) */
287                 if (ast_set_write_format(chan, AST_FORMAT_ULAW) < 0) {
288                         ast_log(LOG_WARNING, "Unable to set '%s' to write ulaw mode\n", chan->name);
289                         goto outrun;
290                 }
291
292                 /* Set it into U-law mode (read) */
293                 if (ast_set_read_format(chan, AST_FORMAT_ULAW) < 0) {
294                         ast_log(LOG_WARNING, "Unable to set '%s' to read ulaw mode\n", chan->name);
295                         goto outrun;
296                 }
297         }
298         ast_indicate(chan, -1);
299         retryzap = strcasecmp(chan->type, "Zap");
300 zapretry:
301         origfd = chan->fds[0];
302         if (retryzap) {
303                 fd = open("/dev/zap/pseudo", O_RDWR);
304                 if (fd < 0) {
305                         ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
306                         goto outrun;
307                 }
308                 /* Make non-blocking */
309                 flags = fcntl(fd, F_GETFL);
310                 if (flags < 0) {
311                         ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
312                         close(fd);
313                         goto outrun;
314                 }
315                 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
316                         ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
317                         close(fd);
318                         goto outrun;
319                 }
320                 /* Setup buffering information */
321                 memset(&bi, 0, sizeof(bi));
322                 bi.bufsize = CONF_SIZE;
323                 bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
324                 bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
325                 bi.numbufs = 4;
326                 if (ioctl(fd, ZT_SET_BUFINFO, &bi)) {
327                         ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
328                         close(fd);
329                         goto outrun;
330                 }
331                 if (confflags & CONFFLAG_VIDEO) {       
332                         x = 1;
333                         if (ioctl(fd, ZT_SETLINEAR, &x)) {
334                                 ast_log(LOG_WARNING, "Unable to set linear mode: %s\n", strerror(errno));
335                                 close(fd);
336                                 goto outrun;
337                         }
338                 }
339                 nfds = 1;
340         } else {
341                 /* XXX Make sure we're not running on a pseudo channel XXX */
342                 fd = chan->fds[0];
343                 nfds = 0;
344         }
345         memset(&ztc, 0, sizeof(ztc));
346         /* Check to see if we're in a conference... */
347         ztc.chan = 0;   
348         if (ioctl(fd, ZT_GETCONF, &ztc)) {
349                 ast_log(LOG_WARNING, "Error getting conference\n");
350                 close(fd);
351                 goto outrun;
352         }
353         if (ztc.confmode) {
354                 /* Whoa, already in a conference...  Retry... */
355                 if (!retryzap) {
356                         ast_log(LOG_DEBUG, "Zap channel is in a conference already, retrying with pseudo\n");
357                         retryzap = 1;
358                         goto zapretry;
359                 }
360         }
361         memset(&ztc, 0, sizeof(ztc));
362         /* Add us to the conference */
363         ztc.chan = 0;   
364         ztc.confno = conf->zapconf;
365         if (confflags & CONFFLAG_MONITOR)
366                 ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER;
367         else if (confflags & CONFFLAG_TALKER)
368                 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER;
369         else 
370                 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
371
372         if (ioctl(fd, ZT_SETCONF, &ztc)) {
373                 ast_log(LOG_WARNING, "Error setting conference\n");
374                 close(fd);
375                 goto outrun;
376         }
377         ast_log(LOG_DEBUG, "Placed channel %s in ZAP conf %d\n", chan->name, conf->zapconf);
378         if (!firstpass && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN)) {
379                 firstpass = 1;
380                 if (!(confflags & CONFFLAG_QUIET))
381                         conf_play(conf, ENTER);
382         }
383
384         if (confflags & CONFFLAG_AGI) {
385
386                 /* Get name of AGI file to run from $(MEETME_AGI_BACKGROUND)
387                   or use default filename of conf-background.agi */
388
389                 agifile = pbx_builtin_getvar_helper(chan,"MEETME_AGI_BACKGROUND");
390                 if (!agifile)
391                         agifile = agifiledefault;
392
393                 if (!strcasecmp(chan->type,"Zap")) {
394                         /*  Set CONFMUTE mode on Zap channel to mute DTMF tones */
395                         x = 1;
396                         ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
397                 }
398                 /* Find a pointer to the agi app and execute the script */
399                 app = pbx_findapp("agi");
400                 if (app) {
401                         ret = pbx_exec(chan, app, agifile, 1);
402                 } else {
403                         ast_log(LOG_WARNING, "Could not find application (agi)\n");
404                         ret = -2;
405                 }
406                 if (!strcasecmp(chan->type,"Zap")) {
407                         /*  Remove CONFMUTE mode on Zap channel */
408                         x = 0;
409                         ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
410                 }
411         } else for(;;) {
412                 outfd = -1;
413                 ms = -1;
414                 c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
415                 /* trying to add moh for single person conf */
416                 if (confflags & CONFFLAG_MOH) {
417                         if (conf->users == 1) {
418                                 if (musiconhold == 0) {
419                                         ast_moh_start(chan, NULL);
420                                         musiconhold = 1;
421                                 } 
422                         } else {
423                                 if (musiconhold) {
424                                         ast_moh_stop(chan);
425                                         musiconhold = 0;
426                                 }
427                         }
428                 }
429                 /* end modifications */
430
431                 if (c) {
432                         if (c->fds[0] != origfd) {
433                                 if (retryzap) {
434                                         /* Kill old pseudo */
435                                         close(fd);
436                                 }
437                                 ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n");
438                                 retryzap = 0;
439                                 goto zapretry;
440                         }
441                         f = ast_read(c);
442                         if (!f) 
443                                 break;
444                         if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#') && (confflags & CONFFLAG_POUNDEXIT)) {
445                                 ret = 0;
446                                 break;
447                         } else if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*') && (confflags & CONFFLAG_STARMENU)) {
448                                         if ((confflags & CONFFLAG_ADMIN)) {
449                                         /* Do admin stuff here */
450                                         } else {
451                                         /* Do user menu here */
452                                         }
453
454                         } else if (fd != chan->fds[0]) {
455                                 if (f->frametype == AST_FRAME_VOICE) {
456                                         if (f->subclass == AST_FORMAT_ULAW) {
457                                                 /* Carefully write */
458                                                 careful_write(fd, f->data, f->datalen);
459                                         } else
460                                                 ast_log(LOG_WARNING, "Huh?  Got a non-ulaw (%d) frame in the conference\n", f->subclass);
461                                 }
462                         }
463                         ast_frfree(f);
464                 } else if (outfd > -1) {
465                         res = read(outfd, buf, CONF_SIZE);
466                         if (res > 0) {
467                                 memset(&fr, 0, sizeof(fr));
468                                 fr.frametype = AST_FRAME_VOICE;
469                                 fr.subclass = AST_FORMAT_ULAW;
470                                 fr.datalen = res;
471                                 fr.samples = res;
472                                 fr.data = buf;
473                                 fr.offset = AST_FRIENDLY_OFFSET;
474                                 if (ast_write(chan, &fr) < 0) {
475                                         ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno));
476                                         /* break; */
477                                 }
478                         } else 
479                                 ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno));
480                 }
481         }
482
483         if (fd != chan->fds[0])
484                 close(fd);
485         else {
486                 /* Take out of conference */
487                 /* Add us to the conference */
488                 ztc.chan = 0;   
489                 ztc.confno = 0;
490                 ztc.confmode = 0;
491                 if (ioctl(fd, ZT_SETCONF, &ztc)) {
492                         ast_log(LOG_WARNING, "Error setting conference\n");
493                 }
494         }
495
496         if (!(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN))
497                 conf_play(conf, LEAVE);
498
499 outrun:
500
501         ast_mutex_lock(&conflock);
502         /* Clean up */
503         conf->users--;
504         if (!conf->users) {
505                 /* No more users -- close this one out */
506                 cur = confs;
507                 while(cur) {
508                         if (cur == conf) {
509                                 if (prev)
510                                         prev->next = conf->next;
511                                 else
512                                         confs = conf->next;
513                                 break;
514                         }
515                         prev = cur;
516                         cur = cur->next;
517                 }
518                 if (!cur) 
519                         ast_log(LOG_WARNING, "Conference not found\n");
520                 close(conf->fd);
521                 free(conf);
522         }
523         ast_mutex_unlock(&conflock);
524         return ret;
525 }
526
527 static struct ast_conference *find_conf(char *confno, int make, int dynamic)
528 {
529         struct ast_config *cfg;
530         struct ast_variable *var;
531         struct ast_conference *cnf = confs;
532
533         /* Check first in the conference list */
534         ast_mutex_lock(&conflock);
535         while (cnf) {
536                 if (!strcmp(confno, cnf->confno)) 
537                         break;
538                 cnf = cnf->next;
539         }
540         ast_mutex_unlock(&conflock);
541
542         if (!cnf) {
543                 if (dynamic) {
544                         /* No need to parse meetme.conf */
545                         ast_log(LOG_DEBUG, "Using dynamic conference '%s'\n", confno);
546                         cnf = build_conf(confno, "", make, dynamic);
547                 } else {
548                         /* Check the config */
549                         cfg = ast_load("meetme.conf");
550                         if (!cfg) {
551                                 ast_log(LOG_WARNING, "No meetme.conf file :(\n");
552                                 return NULL;
553                         }
554                         var = ast_variable_browse(cfg, "rooms");
555                         while(var) {
556                                 if (!strcasecmp(var->name, "conf")) {
557                                         /* Separate the PIN */
558                                         char *pin, *conf;
559
560                                         if ((pin = ast_strdupa(var->value))) {
561                                                 conf = strsep(&pin, "|,");
562                                                 if (!strcasecmp(conf, confno)) {
563                                                         /* Bingo it's a valid conference */
564                                                         if (pin)
565                                                                 cnf = build_conf(confno, pin, make, dynamic);
566                                                         else
567                                                                 cnf = build_conf(confno, "", make, dynamic);
568                                                         break;
569                                                 }
570                                         }
571                                 }
572                                 var = var->next;
573                         }
574                         if (!var) {
575                                 ast_log(LOG_DEBUG, "%s isn't a valid conference\n", confno);
576                         }
577                         ast_destroy(cfg);
578                 }
579         }
580         return cnf;
581 }
582
583 static int count_exec(struct ast_channel *chan, void *data)
584 {
585         struct localuser *u;
586         int res = 0;
587         struct ast_conference *conf;
588         int count;
589         char *confnum, *localdata;
590         char val[80] = "0"; 
591
592         if (!data || !strlen(data)) {
593                 ast_log(LOG_WARNING, "MeetMeCount requires an argument (conference number)\n");
594                 return -1;
595         }
596         localdata = ast_strdupa(data);
597         LOCAL_USER_ADD(u);
598         confnum = strsep(&localdata,"|");       
599         conf = find_conf(confnum, 0, 0);
600         if (conf)
601                 count = conf->users;
602         else
603                 count = 0;
604
605         if (localdata && strlen(localdata)){
606                 /* have var so load it and exit */
607                 snprintf(val,sizeof(val), "%i",count);
608                 pbx_builtin_setvar_helper(chan, localdata,val);
609         } else {
610                 if (chan->_state != AST_STATE_UP)
611                         ast_answer(chan);
612                 res = ast_say_number(chan, count, "", chan->language);
613         }
614         LOCAL_USER_REMOVE(u);
615         return res;
616 }
617
618 static int conf_exec(struct ast_channel *chan, void *data)
619 {
620         int res=-1;
621         struct localuser *u;
622         char confno[AST_MAX_EXTENSION] = "";
623         int allowretry = 0;
624         int retrycnt = 0;
625         struct ast_conference *cnf;
626         int confflags = 0;
627         int dynamic = 0;
628         char *notdata, *info, *inflags = NULL, *inpin = NULL;
629
630         if (!data || !strlen(data)) {
631                 allowretry = 1;
632                 notdata = "";
633         } else {
634                 notdata = data;
635         }
636         LOCAL_USER_ADD(u);
637         if (chan->_state != AST_STATE_UP)
638                 ast_answer(chan);
639
640         info = ast_strdupa((char *)notdata);
641
642         if (info) {
643                 char *tmp = strsep(&info, "|");
644                 strncpy(confno, tmp, sizeof(confno));
645                 if (strlen(confno) == 0) {
646                         allowretry = 1;
647                 }
648         }
649         if (info)
650                 inflags = strsep(&info, "|");
651         if (info)
652                 inpin = strsep(&info, "|");
653
654         if (inflags) {
655                 if (strchr(inflags, 'a'))
656                         confflags |= CONFFLAG_ADMIN;
657                 if (strchr(inflags, 'm'))
658                         confflags |= CONFFLAG_MONITOR;
659                 if (strchr(inflags, 'p'))
660                         confflags |= CONFFLAG_POUNDEXIT;
661                 if (strchr(inflags, 's'))
662                         confflags |= CONFFLAG_STARMENU;
663                 if (strchr(inflags, 't'))
664                         confflags |= CONFFLAG_TALKER;
665                 if (strchr(inflags, 'q'))
666                         confflags |= CONFFLAG_QUIET;
667                 if (strchr(inflags, 'M'))
668                         confflags |= CONFFLAG_MOH;
669                 if (strchr(inflags, 'b'))
670                         confflags |= CONFFLAG_AGI;
671                 if (strchr(inflags, 'd'))
672                         dynamic = 1;
673         }
674
675         do {
676                 if (retrycnt > 3)
677                         allowretry = 0;
678                 while (allowretry && (!strlen(confno)) && (++retrycnt < 4)) {
679                         /* Prompt user for conference number */
680                         res = ast_app_getdata(chan, "conf-getconfno", confno, sizeof(confno) - 1, 0);
681                         if (res < 0) {
682                                 /* Don't try to validate when we catch an error */
683                                 strcpy(confno, "");
684                                 allowretry = 0;
685                                 break;
686                         }
687                 }
688                 if (strlen(confno)) {
689                         /* Check the validity of the conference */
690                         cnf = find_conf(confno, 1, dynamic);
691                         if (!cnf) {
692                                 res = ast_streamfile(chan, "conf-invalid", chan->language);
693                                 if (!res)
694                                         ast_waitstream(chan, "");
695                                 res = -1;
696                                 if (allowretry)
697                                         strcpy(confno, "");
698                         } else {
699                                 if (strlen(cnf->pin)) {
700                                         /* XXX Should prompt user for pin if pin is required XXX */
701
702                                 }
703                                 allowretry = 0;
704
705                                 /* Run the conference */
706                                 res = conf_run(chan, cnf, confflags);
707                         }
708                 }
709         } while (allowretry);
710         /* Do the conference */
711         LOCAL_USER_REMOVE(u);
712         return res;
713 }
714
715 int unload_module(void)
716 {
717         STANDARD_HANGUP_LOCALUSERS;
718         ast_cli_unregister(&cli_show_confs);
719         ast_unregister_application(app2);
720         return ast_unregister_application(app);
721 }
722
723 int load_module(void)
724 {
725         ast_cli_register(&cli_show_confs);
726         ast_register_application(app2, count_exec, synopsis2, descrip2);
727         return ast_register_application(app, conf_exec, synopsis, descrip);
728 }
729
730 char *description(void)
731 {
732         return tdesc;
733 }
734
735 int usecount(void)
736 {
737         int res;
738         STANDARD_USECOUNT(res);
739         return res;
740 }
741
742 char *key()
743 {
744         return ASTERISK_GPL_KEY;
745 }