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