Implement temporary work around for pseudo channels with SMP
[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 #ifndef NO_ZAPTEL_PANIC_WORKAROUND
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         usleep(1);
495         if (fd != chan->fds[0])
496                 close(fd);
497 #else           
498         if (fd != chan->fds[0])
499                 close(fd);
500         else {
501                 /* Take out of conference */
502                 /* Add us to the conference */
503                 ztc.chan = 0;   
504                 ztc.confno = 0;
505                 ztc.confmode = 0;
506                 if (ioctl(fd, ZT_SETCONF, &ztc)) {
507                         ast_log(LOG_WARNING, "Error setting conference\n");
508                 }
509         }
510 #endif
511         if (!(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN))
512                 conf_play(conf, LEAVE);
513
514 outrun:
515
516         ast_mutex_lock(&conflock);
517         /* Clean up */
518         conf->users--;
519         if (!conf->users) {
520                 /* No more users -- close this one out */
521                 cur = confs;
522                 while(cur) {
523                         if (cur == conf) {
524                                 if (prev)
525                                         prev->next = conf->next;
526                                 else
527                                         confs = conf->next;
528                                 break;
529                         }
530                         prev = cur;
531                         cur = cur->next;
532                 }
533                 if (!cur) 
534                         ast_log(LOG_WARNING, "Conference not found\n");
535                 close(conf->fd);
536                 free(conf);
537         }
538         ast_mutex_unlock(&conflock);
539         return ret;
540 }
541
542 static struct ast_conference *find_conf(char *confno, int make, int dynamic)
543 {
544         struct ast_config *cfg;
545         struct ast_variable *var;
546         struct ast_conference *cnf = confs;
547
548         /* Check first in the conference list */
549         ast_mutex_lock(&conflock);
550         while (cnf) {
551                 if (!strcmp(confno, cnf->confno)) 
552                         break;
553                 cnf = cnf->next;
554         }
555         ast_mutex_unlock(&conflock);
556
557         if (!cnf) {
558                 if (dynamic) {
559                         /* No need to parse meetme.conf */
560                         ast_log(LOG_DEBUG, "Using dynamic conference '%s'\n", confno);
561                         cnf = build_conf(confno, "", make, dynamic);
562                 } else {
563                         /* Check the config */
564                         cfg = ast_load("meetme.conf");
565                         if (!cfg) {
566                                 ast_log(LOG_WARNING, "No meetme.conf file :(\n");
567                                 return NULL;
568                         }
569                         var = ast_variable_browse(cfg, "rooms");
570                         while(var) {
571                                 if (!strcasecmp(var->name, "conf")) {
572                                         /* Separate the PIN */
573                                         char *pin, *conf;
574
575                                         if ((pin = ast_strdupa(var->value))) {
576                                                 conf = strsep(&pin, "|,");
577                                                 if (!strcasecmp(conf, confno)) {
578                                                         /* Bingo it's a valid conference */
579                                                         if (pin)
580                                                                 cnf = build_conf(confno, pin, make, dynamic);
581                                                         else
582                                                                 cnf = build_conf(confno, "", make, dynamic);
583                                                         break;
584                                                 }
585                                         }
586                                 }
587                                 var = var->next;
588                         }
589                         if (!var) {
590                                 ast_log(LOG_DEBUG, "%s isn't a valid conference\n", confno);
591                         }
592                         ast_destroy(cfg);
593                 }
594         }
595         return cnf;
596 }
597
598 static int count_exec(struct ast_channel *chan, void *data)
599 {
600         struct localuser *u;
601         int res = 0;
602         struct ast_conference *conf;
603         int count;
604         char *confnum, *localdata;
605         char val[80] = "0"; 
606
607         if (!data || !strlen(data)) {
608                 ast_log(LOG_WARNING, "MeetMeCount requires an argument (conference number)\n");
609                 return -1;
610         }
611         localdata = ast_strdupa(data);
612         LOCAL_USER_ADD(u);
613         confnum = strsep(&localdata,"|");       
614         conf = find_conf(confnum, 0, 0);
615         if (conf)
616                 count = conf->users;
617         else
618                 count = 0;
619
620         if (localdata && strlen(localdata)){
621                 /* have var so load it and exit */
622                 snprintf(val,sizeof(val), "%i",count);
623                 pbx_builtin_setvar_helper(chan, localdata,val);
624         } else {
625                 if (chan->_state != AST_STATE_UP)
626                         ast_answer(chan);
627                 res = ast_say_number(chan, count, "", chan->language);
628         }
629         LOCAL_USER_REMOVE(u);
630         return res;
631 }
632
633 static int conf_exec(struct ast_channel *chan, void *data)
634 {
635         int res=-1;
636         struct localuser *u;
637         char confno[AST_MAX_EXTENSION] = "";
638         int allowretry = 0;
639         int retrycnt = 0;
640         struct ast_conference *cnf;
641         int confflags = 0;
642         int dynamic = 0;
643         char *notdata, *info, *inflags = NULL, *inpin = NULL;
644
645         if (!data || !strlen(data)) {
646                 allowretry = 1;
647                 notdata = "";
648         } else {
649                 notdata = data;
650         }
651         LOCAL_USER_ADD(u);
652         if (chan->_state != AST_STATE_UP)
653                 ast_answer(chan);
654
655         info = ast_strdupa((char *)notdata);
656
657         if (info) {
658                 char *tmp = strsep(&info, "|");
659                 strncpy(confno, tmp, sizeof(confno));
660                 if (strlen(confno) == 0) {
661                         allowretry = 1;
662                 }
663         }
664         if (info)
665                 inflags = strsep(&info, "|");
666         if (info)
667                 inpin = strsep(&info, "|");
668
669         if (inflags) {
670                 if (strchr(inflags, 'a'))
671                         confflags |= CONFFLAG_ADMIN;
672                 if (strchr(inflags, 'm'))
673                         confflags |= CONFFLAG_MONITOR;
674                 if (strchr(inflags, 'p'))
675                         confflags |= CONFFLAG_POUNDEXIT;
676                 if (strchr(inflags, 's'))
677                         confflags |= CONFFLAG_STARMENU;
678                 if (strchr(inflags, 't'))
679                         confflags |= CONFFLAG_TALKER;
680                 if (strchr(inflags, 'q'))
681                         confflags |= CONFFLAG_QUIET;
682                 if (strchr(inflags, 'M'))
683                         confflags |= CONFFLAG_MOH;
684                 if (strchr(inflags, 'b'))
685                         confflags |= CONFFLAG_AGI;
686                 if (strchr(inflags, 'd'))
687                         dynamic = 1;
688         }
689
690         do {
691                 if (retrycnt > 3)
692                         allowretry = 0;
693                 while (allowretry && (!strlen(confno)) && (++retrycnt < 4)) {
694                         /* Prompt user for conference number */
695                         res = ast_app_getdata(chan, "conf-getconfno", confno, sizeof(confno) - 1, 0);
696                         if (res < 0) {
697                                 /* Don't try to validate when we catch an error */
698                                 strcpy(confno, "");
699                                 allowretry = 0;
700                                 break;
701                         }
702                 }
703                 if (strlen(confno)) {
704                         /* Check the validity of the conference */
705                         cnf = find_conf(confno, 1, dynamic);
706                         if (!cnf) {
707                                 res = ast_streamfile(chan, "conf-invalid", chan->language);
708                                 if (!res)
709                                         ast_waitstream(chan, "");
710                                 res = -1;
711                                 if (allowretry)
712                                         strcpy(confno, "");
713                         } else {
714                                 if (strlen(cnf->pin)) {
715                                         char pin[AST_MAX_EXTENSION];
716
717                                         if (inpin && *inpin) {
718                                                 strncpy(pin, inpin, sizeof(pin) - 1);
719                                         } else {
720                                                 /* Prompt user for pin if pin is required */
721                                                 res = ast_app_getdata(chan, "conf-getpin", pin, sizeof(pin) - 1, 0);
722                                         }
723                                         if (res >= 0) {
724                                                 if (!strcasecmp(pin, cnf->pin)) {
725                                                         /* Pin correct */
726                                                         allowretry = 0;
727                                                         /* Run the conference */
728                                                         res = conf_run(chan, cnf, confflags);
729                                                 } else {
730                                                         /* Pin invalid */
731                                                         res = ast_streamfile(chan, "conf-invalidpin", chan->language);
732                                                         if (!res)
733                                                                 ast_waitstream(chan, "");
734                                                         res = -1;
735                                                         if (allowretry)
736                                                                 strcpy(confno, "");
737                                                 }
738                                         } else {
739                                                 res = -1;
740                                                 allowretry = 0;
741                                         }
742                                 } else {
743                                         /* No pin required */
744                                         allowretry = 0;
745
746                                         /* Run the conference */
747                                         res = conf_run(chan, cnf, confflags);
748                                 }
749                         }
750                 }
751         } while (allowretry);
752         /* Do the conference */
753         LOCAL_USER_REMOVE(u);
754         return res;
755 }
756
757 int unload_module(void)
758 {
759         STANDARD_HANGUP_LOCALUSERS;
760         ast_cli_unregister(&cli_show_confs);
761         ast_unregister_application(app2);
762         return ast_unregister_application(app);
763 }
764
765 int load_module(void)
766 {
767         ast_cli_register(&cli_show_confs);
768         ast_register_application(app2, count_exec, synopsis2, descrip2);
769         return ast_register_application(app, conf_exec, synopsis, descrip);
770 }
771
772 char *description(void)
773 {
774         return tdesc;
775 }
776
777 int usecount(void)
778 {
779         int res;
780         STANDARD_USECOUNT(res);
781         return res;
782 }
783
784 char *key()
785 {
786         return ASTERISK_GPL_KEY;
787 }