Merge config updates (bug #3406)
[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 - 2005, Digium, Inc.
7  *
8  * Mark Spencer <markster@digium.com>
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/manager.h>
24 #include <asterisk/options.h>
25 #include <asterisk/cli.h>
26 #include <asterisk/say.h>
27 #include <asterisk/utils.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <errno.h>
32 #include <stdlib.h>
33 #include <sys/ioctl.h>
34 #include "../asterisk.h"
35 #include "../astconf.h"
36
37 #ifdef __linux__
38 #include <linux/zaptel.h>
39 #else
40 #include <zaptel.h>
41 #endif /* __linux__ */
42
43 static char *tdesc = "MeetMe conference bridge";
44
45 static char *app = "MeetMe";
46 static char *app2 = "MeetMeCount";
47 static char *app3 = "MeetMeAdmin";
48
49 static char *synopsis = "MeetMe conference bridge";
50 static char *synopsis2 = "MeetMe participant count";
51 static char *synopsis3 = "MeetMe conference Administration";
52
53 static char *descrip =
54 "  MeetMe([confno][,[options][,pin]]): Enters the user into a specified MeetMe conference.\n"
55 "If the conference number is omitted, the user will be prompted to enter\n"
56 "one. \n"
57 "MeetMe returns 0 if user pressed # to exit (see option 'p'), otherwise -1.\n"
58 "Please note: A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING TO WORK!\n\n"
59
60 "The option string may contain zero or more of the following characters:\n"
61 "      'm' -- set monitor only mode (Listen only, no talking)\n"
62 "      't' -- set talk only mode. (Talk only, no listening)\n"
63 "      'i' -- announce user join/leave\n"
64 "      'p' -- allow user to exit the conference by pressing '#'\n"
65 "      'X' -- allow user to exit the conference by entering a valid single\n"
66 "             digit extension ${MEETME_EXIT_CONTEXT} or the current context\n"
67 "             if that variable is not defined.\n"
68 "      'd' -- dynamically add conference\n"
69 "      'D' -- dynamically add conference, prompting for a PIN\n"
70 "      'e' -- select an empty conference\n"
71 "      'E' -- select an empty pinless conference\n"
72 "      'v' -- video mode\n"
73 "      'r' -- Record conference (records as ${MEETME_RECORDINGFILE}\n"
74 "             using format ${MEETME_RECORDINGFORMAT}). Default filename is\n"
75 "             meetme-conf-rec-${CONFNO}-${UNIQUEID} and the default format is wav.\n"
76 "      'q' -- quiet mode (don't play enter/leave sounds)\n"
77 "      'M' -- enable music on hold when the conference has a single caller\n"
78 "      'x' -- close the conference when last marked user exits\n"
79 "      'w' -- wait until the marked user enters the conference\n"
80 "      'b' -- run AGI script specified in ${MEETME_AGI_BACKGROUND}\n"
81 "         Default: conf-background.agi\n"
82 "        (Note: This does not work with non-Zap channels in the same conference)\n"
83 "      's' -- Present menu (user or admin) when '*' is received ('send' to menu)\n"
84 "      'a' -- set admin mode\n"
85 "      'A' -- set marked mode\n";
86
87 static char *descrip2 =
88 "  MeetMeCount(confno[|var]): Plays back the number of users in the specifiedi\n"
89 "MeetMe conference. If var is specified, playback will be skipped and the value\n"
90 "will be returned in the variable. Returns 0 on success or -1 on a hangup.\n"
91 "A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING FUNCTIONALITY.\n";
92
93 static char *descrip3 = 
94 "  MeetMeAdmin(confno,command[,user]): Run admin command for conference\n"
95 "      'K' -- Kick all users out of conference\n"
96 "      'k' -- Kick one user out of conference\n"
97 "      'e' -- Eject last user that joined\n"
98 "      'L' -- Lock conference\n"
99 "      'l' -- Unlock conference\n"
100 "      'M' -- Mute conference\n"
101 "      'm' -- Unmute conference\n"
102 "      'N' -- Mute entire conference (except admin)\n"
103 "      'n' -- Unmute entire conference (except admin)\n"
104 "";
105
106 STANDARD_LOCAL_USER;
107
108 LOCAL_USER_DECL;
109
110 static struct ast_conference {
111         char confno[AST_MAX_EXTENSION];         /* Conference */
112         struct ast_channel *chan;       /* Announcements channel */
113         int fd;                         /* Announcements fd */
114         int zapconf;                    /* Zaptel Conf # */
115         int users;                      /* Number of active users */
116         int markedusers;                  /* Number of marked users */
117         struct ast_conf_user *firstuser;  /* Pointer to the first user struct */
118         struct ast_conf_user *lastuser;   /* Pointer to the last user struct */
119         time_t start;                   /* Start time (s) */
120         int recording;                  /* recording status */
121         int isdynamic;                  /* Created on the fly? */
122         int locked;                       /* Is the conference locked? */
123         pthread_t recordthread;         /* thread for recording */
124         pthread_attr_t attr;            /* thread attribute */
125         char *recordingfilename;        /* Filename to record the Conference into */
126         char *recordingformat;          /* Format to record the Conference in */
127         char pin[AST_MAX_EXTENSION];                    /* If protected by a PIN */
128         struct ast_conference *next;
129 } *confs;
130
131 struct ast_conf_user {
132         int user_no;                 /* User Number */
133         struct ast_conf_user *prevuser;  /* Pointer to the previous user */
134         struct ast_conf_user *nextuser;  /* Pointer to the next user */
135         int userflags;                   /* Flags as set in the conference */
136         int adminflags;                  /* Flags set by the Admin */
137         struct ast_channel *chan;        /* Connected channel */
138         char usrvalue[50];               /* Custom User Value */
139         char namerecloc[AST_MAX_EXTENSION]; /* Name Recorded file Location */
140         time_t jointime;                 /* Time the user joined the conference */
141 };
142
143 #define ADMINFLAG_MUTED (1 << 1)        /* User is muted */
144 #define ADMINFLAG_KICKME (1 << 2)       /* User is kicked */
145
146
147 AST_MUTEX_DEFINE_STATIC(conflock);
148
149 static int admin_exec(struct ast_channel *chan, void *data);
150
151 static void *recordthread(void *args);
152
153 #include "enter.h"
154 #include "leave.h"
155
156 #define ENTER   0
157 #define LEAVE   1
158
159 #define MEETME_RECORD_OFF       0
160 #define MEETME_RECORD_ACTIVE    1
161 #define MEETME_RECORD_TERMINATE 2
162
163 #define CONF_SIZE 320
164
165 #define CONFFLAG_ADMIN  (1 << 1)        /* If set the user has admin access on the conference */
166 #define CONFFLAG_MONITOR (1 << 2)       /* If set the user can only receive audio from the conference */
167 #define CONFFLAG_POUNDEXIT (1 << 3)     /* If set asterisk will exit conference when '#' is pressed */
168 #define CONFFLAG_STARMENU (1 << 4)      /* If set asterisk will provide a menu to the user what '*' is pressed */
169 #define CONFFLAG_TALKER (1 << 5)        /* If set the use can only send audio to the conference */
170 #define CONFFLAG_QUIET (1 << 6)         /* If set there will be no enter or leave sounds */
171 #define CONFFLAG_VIDEO (1 << 7)         /* Set to enable video mode */
172 #define CONFFLAG_AGI (1 << 8)           /* Set to run AGI Script in Background */
173 #define CONFFLAG_MOH (1 << 9)           /* Set to have music on hold when user is alone in conference */
174 #define CONFFLAG_MARKEDEXIT (1 << 10)    /* If set the MeetMe will return if all marked with this flag left */
175 #define CONFFLAG_WAITMARKED (1 << 11)   /* If set, the MeetMe will wait until a marked user enters */
176 #define CONFFLAG_EXIT_CONTEXT (1 << 12) /* If set, the MeetMe will exit to the specified context */
177 #define CONFFLAG_MARKEDUSER (1 << 13)   /* If set, the user will be marked */
178 #define CONFFLAG_INTROUSER (1 << 14)    /* If set, user will be ask record name on entry of conference */
179 #define CONFFLAG_RECORDCONF (1<< 15)    /* If set, the MeetMe will be recorded */
180
181 static int careful_write(int fd, unsigned char *data, int len)
182 {
183         int res;
184         int x;
185         while(len) {
186                 x = ZT_IOMUX_WRITE | ZT_IOMUX_SIGEVENT;
187                 res = ioctl(fd, ZT_IOMUX, &x);
188                 if (res >= 0)
189                         res = write(fd, data, len);
190                 if (res < 1) {
191                         if (errno != EAGAIN) {
192                                 ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
193                                 return -1;
194                         } else
195                                 return 0;
196                 }
197                 len -= res;
198                 data += res;
199         }
200         return 0;
201 }
202
203 static void conf_play(struct ast_conference *conf, int sound)
204 {
205         unsigned char *data;
206         int len;
207         ast_mutex_lock(&conflock);
208         switch(sound) {
209         case ENTER:
210                 data = enter;
211                 len = sizeof(enter);
212                 break;
213         case LEAVE:
214                 data = leave;
215                 len = sizeof(leave);
216                 break;
217         default:
218                 data = NULL;
219                 len = 0;
220         }
221         if (data) 
222                 careful_write(conf->fd, data, len);
223         ast_mutex_unlock(&conflock);
224 }
225
226 static struct ast_conference *build_conf(char *confno, char *pin, int make, int dynamic)
227 {
228         struct ast_conference *cnf;
229         struct zt_confinfo ztc;
230         ast_mutex_lock(&conflock);
231         cnf = confs;
232         while(cnf) {
233                 if (!strcmp(confno, cnf->confno)) 
234                         break;
235                 cnf = cnf->next;
236         }
237         if (!cnf && (make || dynamic)) {
238                 cnf = malloc(sizeof(struct ast_conference));
239                 if (cnf) {
240                         /* Make a new one */
241                         memset(cnf, 0, sizeof(struct ast_conference));
242                         strncpy(cnf->confno, confno, sizeof(cnf->confno) - 1);
243                         strncpy(cnf->pin, pin, sizeof(cnf->pin) - 1);
244                         cnf->markedusers = 0;
245                         cnf->chan = ast_request("zap", AST_FORMAT_ULAW, "pseudo", NULL);
246                         if (cnf->chan) {
247                                 cnf->fd = cnf->chan->fds[0];    /* for use by conf_play() */
248                         } else {
249                                 ast_log(LOG_WARNING, "Unable to open pseudo channel - trying device\n");
250                                 cnf->fd = open("/dev/zap/pseudo", O_RDWR);
251                                 if (cnf->fd < 0) {
252                                         ast_log(LOG_WARNING, "Unable to open pseudo device\n");
253                                         free(cnf);
254                                         cnf = NULL;
255                                         goto cnfout;
256                                 }
257                         }
258                         memset(&ztc, 0, sizeof(ztc));
259                         /* Setup a new zap conference */
260                         ztc.chan = 0;
261                         ztc.confno = -1;
262                         ztc.confmode = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON;
263                         if (ioctl(cnf->fd, ZT_SETCONF, &ztc)) {
264                                 ast_log(LOG_WARNING, "Error setting conference\n");
265                                 if (cnf->chan)
266                                         ast_hangup(cnf->chan);
267                                 else
268                                         close(cnf->fd);
269                                 free(cnf);
270                                 cnf = NULL;
271                                 goto cnfout;
272                         }
273                         /* Fill the conference struct */
274                         cnf->start = time(NULL);
275                         cnf->zapconf = ztc.confno;
276                         cnf->isdynamic = dynamic;
277                         cnf->firstuser = NULL;
278                         cnf->lastuser = NULL;
279                         cnf->locked = 0;
280                         if (option_verbose > 2)
281                                 ast_verbose(VERBOSE_PREFIX_3 "Created MeetMe conference %d for conference '%s'\n", cnf->zapconf, cnf->confno);
282                         cnf->next = confs;
283                         confs = cnf;
284                 } else  
285                         ast_log(LOG_WARNING, "Out of memory\n");
286         }
287 cnfout:
288         ast_mutex_unlock(&conflock);
289         return cnf;
290 }
291
292 static int confs_show(int fd, int argc, char **argv)
293 {
294         ast_cli(fd, "Deprecated! Please use 'meetme' instead.\n");
295         return RESULT_SUCCESS;
296 }
297
298 static char show_confs_usage[] =
299 "Deprecated! Please use 'meetme' instead.\n";
300
301 static struct ast_cli_entry cli_show_confs = {
302         { "show", "conferences", NULL }, confs_show,
303         "Show status of conferences", show_confs_usage, NULL };
304         
305 static int conf_cmd(int fd, int argc, char **argv) {
306         /* Process the command */
307         struct ast_conference *cnf;
308         struct ast_conf_user *user;
309         int hr, min, sec;
310         int i = 0, total = 0;
311         time_t now;
312         char *header_format = "%-14s %-14s %-10s %-8s  %-8s\n";
313         char *data_format = "%-12.12s   %4.4d         %4.4s       %02d:%02d:%02d  %-8s\n";
314         char cmdline[1024] = "";
315
316         if (argc > 8)
317                 ast_cli(fd, "Invalid Arguments.\n");
318         /* Check for length so no buffer will overflow... */
319         for (i = 0; i < argc; i++) {
320                 if (strlen(argv[i]) > 100)
321                         ast_cli(fd, "Invalid Arguments.\n");
322         }
323         if (argc == 1) {
324                 /* 'MeetMe': List all the conferences */        
325         now = time(NULL);
326                 cnf = confs;
327                 if (!cnf) {
328                 ast_cli(fd, "No active MeetMe conferences.\n");
329                 return RESULT_SUCCESS;
330         }
331         ast_cli(fd, header_format, "Conf Num", "Parties", "Marked", "Activity", "Creation");
332                 while(cnf) {
333                         if (cnf->markedusers == 0)
334                                 strncpy(cmdline, "N/A ", sizeof(cmdline) - 1);
335                         else 
336                                 snprintf(cmdline, sizeof(cmdline), "%4.4d", cnf->markedusers);
337                         hr = (now - cnf->start) / 3600;
338                         min = ((now - cnf->start) % 3600) / 60;
339                         sec = (now - cnf->start) % 60;
340
341                         ast_cli(fd, data_format, cnf->confno, cnf->users, cmdline, hr, min, sec, cnf->isdynamic ? "Dynamic" : "Static");
342
343                         total += cnf->users;    
344                         cnf = cnf->next;
345                 }
346                 ast_cli(fd, "* Total number of MeetMe users: %d\n", total);
347                 return RESULT_SUCCESS;
348         }
349         if (argc < 3)
350                 return RESULT_SHOWUSAGE;
351         strncpy(cmdline, argv[2], sizeof(cmdline) - 1); /* Argv 2: conference number */
352         if (strstr(argv[1], "lock")) {  
353                 if (strcmp(argv[1], "lock") == 0) {
354                         /* Lock */
355                         strncat(cmdline, "|L", sizeof(cmdline) - strlen(cmdline) - 1);
356                 } else {
357                         /* Unlock */
358                         strncat(cmdline, "|l", sizeof(cmdline) - strlen(cmdline) - 1);
359                 }
360         } else if (strstr(argv[1], "mute")) { 
361                 if (argc < 4)
362                         return RESULT_SHOWUSAGE;
363                 if (strcmp(argv[1], "mute") == 0) {
364                         /* Mute */
365                         if (strcmp(argv[3], "all") == 0) {
366                                  strncat(cmdline, "|N", sizeof(cmdline) - strlen(cmdline) - 1);
367                         } else {
368                                 strncat(cmdline, "|M|", sizeof(cmdline) - strlen(cmdline) - 1); 
369                                 strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
370                         }
371                 } else {
372                         /* Unmute */
373                         if (strcmp(argv[3], "all") == 0) {
374                                  strncat(cmdline, "|n", sizeof(cmdline) - strlen(cmdline) - 1);
375                         } else {
376                                 strncat(cmdline, "|m|", sizeof(cmdline) - strlen(cmdline) - 1);
377                                 strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
378                         }
379                 }
380         } else if (strcmp(argv[1], "kick") == 0) {
381                 if (argc < 4)
382                         return RESULT_SHOWUSAGE;
383                 if (strcmp(argv[3], "all") == 0) {
384                         /* Kick all */
385                         strncat(cmdline, "|K", sizeof(cmdline) - strlen(cmdline) - 1);
386                 } else {
387                         /* Kick a single user */
388                         strncat(cmdline, "|k|", sizeof(cmdline) - strlen(cmdline) - 1);
389                         strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
390                 }       
391         } else if(strcmp(argv[1], "list") == 0) {
392                 /* List all the users in a conference */
393                 if (!confs) {
394                         ast_cli(fd, "No active conferences.\n");
395                         return RESULT_SUCCESS;  
396                 }
397                 cnf = confs;
398                 /* Find the right conference */
399                 while(cnf) {
400                         if (strcmp(cnf->confno, argv[2]) == 0)
401                                 break;
402                         if (cnf->next) {
403                                 cnf = cnf->next;        
404                         } else {
405                                 ast_cli(fd, "No such conference: %s.\n",argv[2]);
406                                 return RESULT_SUCCESS;
407                         }
408                 }
409                 /* Show all the users */
410                 user = cnf->firstuser;
411                 while(user) {
412                         ast_cli(fd, "User #: %i  Channel: %s %s %s %s\n", user->user_no, user->chan->name, (user->userflags & CONFFLAG_ADMIN) ? "(Admin)" : "", (user->userflags & CONFFLAG_MONITOR) ? "(Listen only)" : "", (user->adminflags & ADMINFLAG_MUTED) ? "(Admn Muted)" : "" );
413                         user = user->nextuser;
414                 }
415                 return RESULT_SUCCESS;
416         } else 
417                 return RESULT_SHOWUSAGE;
418         ast_log(LOG_DEBUG, "Cmdline: %s\n", cmdline);
419         admin_exec(NULL, cmdline);
420         return 0;
421 }
422
423 static char *complete_confcmd(char *line, char *word, int pos, int state) {
424         #define CONF_COMMANDS 6
425         int which = 0, x = 0;
426         struct ast_conference *cnf = NULL;
427         struct ast_conf_user *usr = NULL;
428         char *confno = NULL;
429         char usrno[50] = "";
430         char cmds[CONF_COMMANDS][20] = {"lock", "unlock", "mute", "unmute", "kick", "list"};
431         char *myline;
432         
433         if (pos == 1) {
434                 /* Command */
435                 for (x = 0;x < CONF_COMMANDS; x++) {
436                         if (!strncasecmp(cmds[x], word, strlen(word))) {
437                                 if (++which > state) {
438                                         return strdup(cmds[x]);
439                                 }
440                         }
441                 }
442         } else if (pos == 2) {
443                 /* Conference Number */
444                 ast_mutex_lock(&conflock);
445                 cnf = confs;
446                 while(cnf) {
447                         if (!strncasecmp(word, cnf->confno, strlen(word))) {
448                                 if (++which > state)
449                                         break;
450                         }
451                         cnf = cnf->next;
452                 }
453                 ast_mutex_unlock(&conflock);
454                 return cnf ? strdup(cnf->confno) : NULL;
455         } else if (pos == 3) {
456                 /* User Number || Conf Command option*/
457                 if (strstr(line, "mute") || strstr(line, "kick")) {
458                         if ((state == 0) && (strstr(line, "kick") || strstr(line,"mute")) && !(strncasecmp(word, "all", strlen(word)))) {
459                                 return strdup("all");
460                         }
461                         which++;
462                         ast_mutex_lock(&conflock);
463                         cnf = confs;
464
465                         /* TODO: Find the conf number from the cmdline (ignore spaces) <- test this and make it fail-safe! */
466                         myline = ast_strdupa(line);
467                         if (strsep(&myline, " ") && strsep(&myline, " ") && !confno) {
468                                 while((confno = strsep(&myline, " ")) && (strcmp(confno, " ") == 0))
469                                         ;
470                         }
471                         
472                         while(cnf) {
473                                 if (strcmp(confno, cnf->confno) == 0) {
474                                         break;
475                                 }
476                                 cnf = cnf->next;
477                         }
478                         if (cnf) {
479                                 /* Search for the user */
480                                 usr = cnf->firstuser;
481                                 while(usr) {
482                                         snprintf(usrno, sizeof(usrno), "%i", usr->user_no);
483                                         if (!strncasecmp(word, usrno, strlen(word))) {
484                                                 if (++which > state)
485                                                         break;
486                                         }
487                                         usr = usr->nextuser;
488                                 }
489                         }
490                         ast_mutex_unlock(&conflock);
491                         return usr ? strdup(usrno) : NULL;
492                 }
493         }
494         return NULL;
495 }
496         
497 static char conf_usage[] =
498 "Usage: meetme  (un)lock|(un)mute|kick|list <confno> <usernumber>\n"
499 "       Executes a command for the conference or on a conferee\n";
500
501 static struct ast_cli_entry cli_conf = {
502         { "meetme", NULL, NULL }, conf_cmd,
503         "Execute a command on a conference or conferee", conf_usage, complete_confcmd };
504
505 static int confnonzero(void *ptr)
506 {
507         struct ast_conference *conf = ptr;
508         int res;
509         ast_mutex_lock(&conflock);
510         res = (conf->markedusers == 0);
511         ast_mutex_unlock(&conflock);
512         return res;
513 }
514
515 static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int confflags)
516 {
517         struct ast_conference *prev=NULL, *cur;
518         struct ast_conf_user *user = malloc(sizeof(struct ast_conf_user));
519         struct ast_conf_user *usr = NULL;
520         int fd;
521         struct zt_confinfo ztc;
522         struct ast_frame *f;
523         struct ast_channel *c;
524         struct ast_frame fr;
525         int outfd;
526         int ms;
527         int nfds;
528         int res;
529         int flags;
530         int retryzap;
531         int origfd;
532         int musiconhold = 0;
533         int firstpass = 0;
534         int origquiet;
535         int ret = -1;
536         int x;
537         int menu_active = 0;
538         int using_pseudo = 0;
539         int duration=20;
540
541         struct ast_app *app;
542         char *agifile;
543         char *agifiledefault = "conf-background.agi";
544         char meetmesecs[30] = "";
545         char exitcontext[AST_MAX_EXTENSION] = "";
546         char recordingtmp[AST_MAX_EXTENSION] = "";
547         int dtmf;
548
549         ZT_BUFFERINFO bi;
550         char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
551         char *buf = __buf + AST_FRIENDLY_OFFSET;
552         
553         if (!user) {
554                 ast_log(LOG_ERROR, "Out of memory\n");
555                 return(ret);
556         }
557         memset(user, 0, sizeof(struct ast_conf_user));
558
559         if (confflags & CONFFLAG_RECORDCONF && conf->recording !=MEETME_RECORD_ACTIVE) {
560                 conf->recordingfilename = pbx_builtin_getvar_helper(chan,"MEETME_RECORDINGFILE");
561                 if (!conf->recordingfilename) {
562                         snprintf(recordingtmp,sizeof(recordingtmp),"meetme-conf-rec-%s-%s",conf->confno,chan->uniqueid);
563                         conf->recordingfilename = ast_strdupa(recordingtmp);
564                 }
565                 conf->recordingformat = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFORMAT");
566                 if (!conf->recordingformat) {
567                         snprintf(recordingtmp,sizeof(recordingtmp), "wav");
568                         conf->recordingformat = ast_strdupa(recordingtmp);
569                 }
570                 pthread_attr_init(&conf->attr);
571                 pthread_attr_setdetachstate(&conf->attr, PTHREAD_CREATE_DETACHED);
572                 ast_verbose(VERBOSE_PREFIX_4 "Starting recording of MeetMe Conference %s into file %s.%s.\n", conf->confno, conf->recordingfilename, conf->recordingformat);
573                 ast_pthread_create(&conf->recordthread, &conf->attr, recordthread, conf);
574         }
575
576         user->user_no = 0; /* User number 0 means starting up user! (dead - not in the list!) */
577
578         time(&user->jointime);
579
580         if (conf->locked) {
581                 /* Sorry, but this confernce is locked! */      
582                 if (!ast_streamfile(chan, "conf-locked", chan->language))
583                         ast_waitstream(chan, "");
584                 goto outrun;
585         }
586         conf->users++;
587
588         if (confflags & CONFFLAG_MARKEDUSER)
589                 conf->markedusers++;
590       
591         ast_mutex_lock(&conflock);
592         if (conf->firstuser == NULL) {
593                 /* Fill the first new User struct */
594                 user->user_no = 1;
595                 user->nextuser = NULL;
596                 user->prevuser = NULL;
597                 conf->firstuser = user;
598                 conf->lastuser = user;
599         } else {
600                 /* Fill the new user struct */  
601                 user->user_no = conf->lastuser->user_no + 1; 
602                 user->prevuser = conf->lastuser;
603                 user->nextuser = NULL;
604                 if (conf->lastuser->nextuser != NULL) {
605                         ast_log(LOG_WARNING, "Error in User Management!\n");
606                         ast_mutex_unlock(&conflock);
607                         goto outrun;
608                 } else {
609                         conf->lastuser->nextuser = user;
610                         conf->lastuser = user;
611                 }
612         }
613         user->chan = chan;
614         user->userflags = confflags;
615         user->adminflags = 0;
616         ast_mutex_unlock(&conflock);
617         origquiet = confflags & CONFFLAG_QUIET;
618         if (confflags & CONFFLAG_EXIT_CONTEXT) {
619                 if ((agifile = pbx_builtin_getvar_helper(chan, "MEETME_EXIT_CONTEXT"))) 
620                         strncpy(exitcontext, agifile, sizeof(exitcontext) - 1);
621                 else if (!ast_strlen_zero(chan->macrocontext)) 
622                         strncpy(exitcontext, chan->macrocontext, sizeof(exitcontext) - 1);
623                 else
624                         strncpy(exitcontext, chan->context, sizeof(exitcontext) - 1);
625         }
626         snprintf(user->namerecloc,sizeof(user->namerecloc),"%s/meetme-username-%s-%d",AST_SPOOL_DIR,conf->confno,user->user_no);
627
628         if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER))
629                 ast_record_review(chan,"vm-rec-name",user->namerecloc, 10,"sln", &duration, NULL);
630
631         while((confflags & CONFFLAG_WAITMARKED) && (conf->markedusers == 0)) {
632                 confflags &= ~CONFFLAG_QUIET;
633                 confflags |= origquiet;
634                 /* XXX Announce that we're waiting on the conference lead to join */
635                 if (!(confflags & CONFFLAG_QUIET)) {
636                         res = ast_streamfile(chan, "vm-dialout", chan->language);
637                         if (!res)
638                                 res = ast_waitstream(chan, "");
639                 } else
640                         res = 0;
641                 /* If we're waiting with hold music, set to silent mode */
642                 if (!res) {
643                         confflags |= CONFFLAG_QUIET;
644                         ast_moh_start(chan, NULL);
645                         res = ast_safe_sleep_conditional(chan, 60000, confnonzero, conf);
646                         ast_moh_stop(chan);
647                 }
648                 if (res < 0) {
649                         ast_log(LOG_DEBUG, "Got hangup on '%s' already\n", chan->name);
650                         goto outrun;
651                 }
652         }
653         
654         if (!(confflags & CONFFLAG_QUIET) && conf->users == 1) {
655                 if (!ast_streamfile(chan, "conf-onlyperson", chan->language)) {
656                         if (ast_waitstream(chan, "") < 0)
657                                 goto outrun;
658                 } else
659                         goto outrun;
660         }
661
662         /* Set it into linear mode (write) */
663         if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
664                 ast_log(LOG_WARNING, "Unable to set '%s' to write linear mode\n", chan->name);
665                 goto outrun;
666         }
667
668         /* Set it into linear mode (read) */
669         if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0) {
670                 ast_log(LOG_WARNING, "Unable to set '%s' to read linear mode\n", chan->name);
671                 goto outrun;
672         }
673         ast_indicate(chan, -1);
674         retryzap = strcasecmp(chan->type, "Zap");
675 zapretry:
676         origfd = chan->fds[0];
677         if (retryzap) {
678                 fd = open("/dev/zap/pseudo", O_RDWR);
679                 if (fd < 0) {
680                         ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
681                         goto outrun;
682                 }
683                 using_pseudo = 1;
684                 /* Make non-blocking */
685                 flags = fcntl(fd, F_GETFL);
686                 if (flags < 0) {
687                         ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
688                         close(fd);
689                         goto outrun;
690                 }
691                 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
692                         ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
693                         close(fd);
694                         goto outrun;
695                 }
696                 /* Setup buffering information */
697                 memset(&bi, 0, sizeof(bi));
698                 bi.bufsize = CONF_SIZE/2;
699                 bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
700                 bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
701                 bi.numbufs = 4;
702                 if (ioctl(fd, ZT_SET_BUFINFO, &bi)) {
703                         ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
704                         close(fd);
705                         goto outrun;
706                 }
707                 x = 1;
708                 if (ioctl(fd, ZT_SETLINEAR, &x)) {
709                         ast_log(LOG_WARNING, "Unable to set linear mode: %s\n", strerror(errno));
710                         close(fd);
711                         goto outrun;
712                 }
713                 nfds = 1;
714         } else {
715                 /* XXX Make sure we're not running on a pseudo channel XXX */
716                 fd = chan->fds[0];
717                 nfds = 0;
718         }
719         memset(&ztc, 0, sizeof(ztc));
720         /* Check to see if we're in a conference... */
721         ztc.chan = 0;   
722         if (ioctl(fd, ZT_GETCONF, &ztc)) {
723                 ast_log(LOG_WARNING, "Error getting conference\n");
724                 close(fd);
725                 goto outrun;
726         }
727         if (ztc.confmode) {
728                 /* Whoa, already in a conference...  Retry... */
729                 if (!retryzap) {
730                         ast_log(LOG_DEBUG, "Zap channel is in a conference already, retrying with pseudo\n");
731                         retryzap = 1;
732                         goto zapretry;
733                 }
734         }
735         memset(&ztc, 0, sizeof(ztc));
736         /* Add us to the conference */
737         ztc.chan = 0;   
738         ztc.confno = conf->zapconf;
739         ast_mutex_lock(&conflock);
740         if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER) && conf->users > 1) {
741                 if (ast_fileexists(user->namerecloc, NULL, NULL)) {
742                         if (!ast_streamfile(conf->chan, user->namerecloc, chan->language))
743                                 ast_waitstream(conf->chan, "");
744                         if (!ast_streamfile(conf->chan, "conf-hasjoin", chan->language))
745                                 ast_waitstream(conf->chan, "");
746                 }
747         }
748
749         if (confflags & CONFFLAG_MONITOR)
750                 ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER;
751         else if (confflags & CONFFLAG_TALKER)
752                 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER;
753         else 
754                 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
755
756         if (ioctl(fd, ZT_SETCONF, &ztc)) {
757                 ast_log(LOG_WARNING, "Error setting conference\n");
758                 close(fd);
759                 ast_mutex_unlock(&conflock);
760                 goto outrun;
761         }
762         ast_log(LOG_DEBUG, "Placed channel %s in ZAP conf %d\n", chan->name, conf->zapconf);
763
764         manager_event(EVENT_FLAG_CALL, "MeetmeJoin", 
765                         "Channel: %s\r\n"
766                         "Uniqueid: %s\r\n"
767                         "Meetme: %s\r\n"
768                         "Usernum: %i\r\n",
769                         chan->name, chan->uniqueid, conf->confno, user->user_no);
770
771         if (!firstpass && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN)) {
772                 firstpass = 1;
773                 if (!(confflags & CONFFLAG_QUIET))
774                         conf_play(conf, ENTER);
775         }
776         ast_mutex_unlock(&conflock);
777         if (confflags & CONFFLAG_AGI) {
778
779                 /* Get name of AGI file to run from $(MEETME_AGI_BACKGROUND)
780                   or use default filename of conf-background.agi */
781
782                 agifile = pbx_builtin_getvar_helper(chan,"MEETME_AGI_BACKGROUND");
783                 if (!agifile)
784                         agifile = agifiledefault;
785
786                 if (!strcasecmp(chan->type,"Zap")) {
787                         /*  Set CONFMUTE mode on Zap channel to mute DTMF tones */
788                         x = 1;
789                         ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
790                 }
791                 /* Find a pointer to the agi app and execute the script */
792                 app = pbx_findapp("agi");
793                 if (app) {
794                         ret = pbx_exec(chan, app, agifile, 1);
795                 } else {
796                         ast_log(LOG_WARNING, "Could not find application (agi)\n");
797                         ret = -2;
798                 }
799                 if (!strcasecmp(chan->type,"Zap")) {
800                         /*  Remove CONFMUTE mode on Zap channel */
801                         x = 0;
802                         ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
803                 }
804         } else {
805                 if (!strcasecmp(chan->type,"Zap") && (confflags & CONFFLAG_STARMENU)) {
806                         /*  Set CONFMUTE mode on Zap channel to mute DTMF tones when the menu is enabled */
807                         x = 1;
808                         ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
809                 }       
810                 for(;;) {
811                         outfd = -1;
812                         ms = -1;
813                         c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
814                         
815                         /* Update the struct with the actual confflags */
816                         user->userflags = confflags;
817                         
818                         /* trying to add moh for single person conf */
819                         if (confflags & CONFFLAG_MOH) {
820                                 if (conf->users == 1) {
821                                         if (musiconhold == 0) {
822                                                 ast_moh_start(chan, NULL);
823                                                 musiconhold = 1;
824                                         } 
825                                 } else {
826                                         if (musiconhold) {
827                                                 ast_moh_stop(chan);
828                                                 musiconhold = 0;
829                                         }
830                                 }
831                         }
832                         
833                         /* Leave if the last marked user left */
834                         if (conf->markedusers == 0 && confflags & CONFFLAG_MARKEDEXIT) {
835                                 ret = -1;
836                                 break;
837                         }
838         
839                         /* Check if the admin changed my modes */
840                         if (user->adminflags) {                 
841                                 /* Set the new modes */
842                                 if ((user->adminflags & ADMINFLAG_MUTED) && (ztc.confmode & ZT_CONF_TALKER)) {
843                                         ztc.confmode ^= ZT_CONF_TALKER;
844                                         if (ioctl(fd, ZT_SETCONF, &ztc)) {
845                                                 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
846                                                 ret = -1;
847                                                 break;
848                                         }
849                                 }
850                                 if (!(user->adminflags & ADMINFLAG_MUTED) && !(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & ZT_CONF_TALKER)) {
851                                         ztc.confmode |= ZT_CONF_TALKER;
852                                         if (ioctl(fd, ZT_SETCONF, &ztc)) {
853                                                 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
854                                                 ret = -1;
855                                                 break;
856                                         }
857                                 }
858                                 if (user->adminflags & ADMINFLAG_KICKME) {
859                                         /* You have been kicked. */
860                                         if (!ast_streamfile(chan, "conf-kicked", chan->language))
861                                                 ast_waitstream(chan, "");
862                                         ret = 0;
863                                         break;
864                                 }
865                         } else if (!(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & ZT_CONF_TALKER)) {
866                                 ztc.confmode |= ZT_CONF_TALKER;
867                                 if (ioctl(fd, ZT_SETCONF, &ztc)) {
868                                         ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
869                                         ret = -1;
870                                         break;
871                                 }
872                         }
873
874                         if (c) {
875                                 if (c->fds[0] != origfd) {
876                                         if (using_pseudo) {
877                                                 /* Kill old pseudo */
878                                                 close(fd);
879                                         }
880                                         ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n");
881                                         retryzap = 0;
882                                         using_pseudo = 0;
883                                         goto zapretry;
884                                 }
885                                 f = ast_read(c);
886                                 if (!f) 
887                                         break;
888                                 if ((f->frametype == AST_FRAME_DTMF) && (confflags & CONFFLAG_EXIT_CONTEXT)) {
889                                         char tmp[2];
890                                         tmp[0] = f->subclass;
891                                         tmp[1] = '\0';
892                                         if (ast_exists_extension(chan, exitcontext, tmp, 1, chan->cid.cid_num)) {
893                                                 strncpy(chan->context, exitcontext, sizeof(chan->context) - 1);
894                                                 strncpy(chan->exten, tmp, sizeof(chan->exten) - 1);
895                                                 chan->priority = 0;
896                                                 ret = 0;
897                                                 break;
898                                         }
899                                 } else if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#') && (confflags & CONFFLAG_POUNDEXIT)) {
900                                         ret = 0;
901                                         break;
902                                 } else if (((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*') && (confflags & CONFFLAG_STARMENU)) || ((f->frametype == AST_FRAME_DTMF) && menu_active)) {
903                                         if (musiconhold) {
904                                                 ast_moh_stop(chan);
905                                         }
906                                         if ((confflags & CONFFLAG_ADMIN)) {
907                                                 /* Admin menu */
908                                                 if (!menu_active) {
909                                                         menu_active = 1;
910                                                         /* Record this sound! */
911                                                          if (!ast_streamfile(chan, "conf-adminmenu", chan->language))
912                                                                 dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
913                                                         else 
914                                                                 dtmf = 0;
915                                                 } else 
916                                                         dtmf = f->subclass;
917                                                 if (dtmf) {
918                                                         switch(dtmf) {
919                                                                 case '1': /* Un/Mute */
920                                                                         menu_active = 0;
921                                                                         if (ztc.confmode & ZT_CONF_TALKER) {
922                                                                         ztc.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER;
923                                                                         confflags |= CONFFLAG_MONITOR ^ CONFFLAG_TALKER;
924                                                                         } else {
925                                                                                 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
926                                                                                 confflags ^= CONFFLAG_MONITOR | CONFFLAG_TALKER;
927                                                                         }
928                                                                         if (ioctl(fd, ZT_SETCONF, &ztc)) {
929                                                                                 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
930                                                                                 ret = -1;
931                                                                                 break;
932                                                                         }
933                                                                         if (ztc.confmode & ZT_CONF_TALKER) {
934                                                                                 if (!ast_streamfile(chan, "conf-unmuted", chan->language))
935                                                                                         ast_waitstream(chan, "");
936                                                                         } else {
937                                                                                 if (!ast_streamfile(chan, "conf-muted", chan->language))
938                                                                                         ast_waitstream(chan, "");
939                                                                         }
940                                                                         break;
941                                                                 case '2': /* Un/Lock the Conference */
942                                                                         menu_active = 0;
943                                                                         if (conf->locked) {
944                                                                                 conf->locked = 0;
945                                                                                 if (!ast_streamfile(chan, "conf-unlockednow", chan->language))
946                                                                                         ast_waitstream(chan, "");
947                                                                         } else {
948                                                                                 conf->locked = 1;
949                                                                                 if (!ast_streamfile(chan, "conf-lockednow", chan->language))
950                                                                                         ast_waitstream(chan, "");
951                                                                         }
952                                                                         break;
953                                                                 case '3': /* Eject last user */
954                                                                         menu_active = 0;
955                                                                         usr = conf->lastuser;
956                                                                         if ((usr->chan->name == chan->name)||(usr->userflags & CONFFLAG_ADMIN)) {
957                                                                                 if(!ast_streamfile(chan, "conf-errormenu", chan->language))
958                                                                                         ast_waitstream(chan, "");
959                                                                         } else 
960                                                                                 usr->adminflags |= ADMINFLAG_KICKME;
961                                                                         ast_stopstream(chan);
962                                                                         break;  
963                                                                 default:
964                                                                         menu_active = 0;
965                                                                         /* Play an error message! */
966                                                                         if (!ast_streamfile(chan, "conf-errormenu", chan->language))
967                                                                                 ast_waitstream(chan, "");
968                                                                         break;
969                                                         }
970                                                 }
971                                         } else {
972                                                 /* User menu */
973                                                 if (!menu_active) {
974                                                         menu_active = 1;
975                                                         /* Record this sound! */
976                                                         if (!ast_streamfile(chan, "conf-usermenu", chan->language))
977                                                                 dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
978                                                         else
979                                                                 dtmf = 0;
980                                                 } else 
981                                                         dtmf = f->subclass;
982                                                 if (dtmf) {
983                                                         switch(dtmf) {
984                                                                 case '1': /* Un/Mute */
985                                                                         menu_active = 0;
986                                                                         if (ztc.confmode & ZT_CONF_TALKER) {
987                                                                         ztc.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER;
988                                                                         confflags |= CONFFLAG_MONITOR ^ CONFFLAG_TALKER;
989                                                                         } else if (!(user->adminflags & ADMINFLAG_MUTED)) {
990                                                                                 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
991                                                                                 confflags ^= CONFFLAG_MONITOR | CONFFLAG_TALKER;
992                                                                         }
993                                                                         if (ioctl(fd, ZT_SETCONF, &ztc)) {
994                                                                                 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
995                                                                                 ret = -1;
996                                                                                 break;
997                                                                         }
998                                                                         if (ztc.confmode & ZT_CONF_TALKER) {
999                                                                                 if (!ast_streamfile(chan, "conf-unmuted", chan->language))
1000                                                                                         ast_waitstream(chan, "");
1001                                                                         } else {
1002                                                                                 if (!ast_streamfile(chan, "conf-muted", chan->language))
1003                                                                                         ast_waitstream(chan, "");
1004                                                                         }
1005                                                                         break;
1006                                                                 default:
1007                                                                         menu_active = 0;
1008                                                                         /* Play an error message! */
1009                                                                         if (!ast_streamfile(chan, "conf-errormenu", chan->language))
1010                                                                                 ast_waitstream(chan, "");
1011                                                                         break;
1012                                                         }
1013                                                 }
1014                                         }
1015                                         if (musiconhold) {
1016                                                 ast_moh_start(chan, NULL);
1017                                         }
1018                                 } else if (using_pseudo) {
1019                                         if (f->frametype == AST_FRAME_VOICE) {
1020                                                 if (f->subclass == AST_FORMAT_SLINEAR) {
1021                                                         /* Carefully write */
1022                                                         careful_write(fd, f->data, f->datalen);
1023                                                 } else
1024                                                         ast_log(LOG_WARNING, "Huh?  Got a non-linear (%d) frame in the conference\n", f->subclass);
1025                                         }
1026                                 }
1027                                 ast_frfree(f);
1028                         } else if (outfd > -1) {
1029                                 res = read(outfd, buf, CONF_SIZE);
1030                                 if (res > 0) {
1031                                         memset(&fr, 0, sizeof(fr));
1032                                         fr.frametype = AST_FRAME_VOICE;
1033                                         fr.subclass = AST_FORMAT_SLINEAR;
1034                                         fr.datalen = res;
1035                                         fr.samples = res/2;
1036                                         fr.data = buf;
1037                                         fr.offset = AST_FRIENDLY_OFFSET;
1038                                         if (ast_write(chan, &fr) < 0) {
1039                                                 ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno));
1040                                                 /* break; */
1041                                         }
1042                                 } else 
1043                                         ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno));
1044                         }
1045                 }
1046         }
1047         if (using_pseudo)
1048                 close(fd);
1049         else {
1050                 /* Take out of conference */
1051                 /* Add us to the conference */
1052                 ztc.chan = 0;   
1053                 ztc.confno = 0;
1054                 ztc.confmode = 0;
1055                 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1056                         ast_log(LOG_WARNING, "Error setting conference\n");
1057                 }
1058         }
1059
1060         ast_mutex_lock(&conflock);
1061         if (!(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN))
1062                 conf_play(conf, LEAVE);
1063
1064         if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER) && conf->users > 1) {
1065                 if (ast_fileexists(user->namerecloc, NULL, NULL)) {
1066                         if (!ast_streamfile(conf->chan, user->namerecloc, chan->language))
1067                                 ast_waitstream(conf->chan, "");
1068                         if (!ast_streamfile(conf->chan, "conf-hasleft", chan->language))
1069                                 ast_waitstream(conf->chan, "");
1070                         ast_filedelete(user->namerecloc, NULL);
1071
1072                 }
1073         }
1074         ast_mutex_unlock(&conflock);
1075
1076
1077 outrun:
1078         ast_mutex_lock(&conflock);
1079         if (user->user_no) { /* Only cleanup users who really joined! */
1080                 manager_event(EVENT_FLAG_CALL, "MeetmeLeave", 
1081                         "Channel: %s\r\n"
1082                         "Uniqueid: %s\r\n"
1083                         "Meetme: %s\r\n"
1084                         "Usernum: %i\r\n",
1085                         chan->name, chan->uniqueid, conf->confno, user->user_no);
1086                 prev = NULL;
1087                 conf->users--;
1088                 if (confflags & CONFFLAG_MARKEDUSER) 
1089                         conf->markedusers--;
1090                 cur = confs;
1091                 if (!conf->users) {
1092                         /* No more users -- close this one out */
1093                         while(cur) {
1094                                 if (cur == conf) {
1095                                         if (prev)
1096                                                 prev->next = conf->next;
1097                                         else
1098                                                 confs = conf->next;
1099                                         break;
1100                                 }
1101                                 prev = cur;
1102                                 cur = cur->next;
1103                         }
1104                         if (!cur) 
1105                                 ast_log(LOG_WARNING, "Conference not found\n");
1106                         if (conf->recording == MEETME_RECORD_ACTIVE) {
1107                                 conf->recording = MEETME_RECORD_TERMINATE;
1108                                 ast_mutex_unlock(&conflock);
1109                                 while (1) {
1110                                         ast_mutex_lock(&conflock);
1111                                         if (conf->recording == MEETME_RECORD_OFF)
1112                                                 break;
1113                                         ast_mutex_unlock(&conflock);
1114                                 }
1115                         }
1116                         if (conf->chan)
1117                                 ast_hangup(conf->chan);
1118                         else
1119                                 close(conf->fd);
1120                         free(conf);
1121                 } else {
1122                         /* Remove the user struct */ 
1123                         if (user == conf->firstuser) {
1124                                 if (user->nextuser) {
1125                                         /* There is another entry */
1126                                         user->nextuser->prevuser = NULL;
1127                                 } else {
1128                                         /* We are the only entry */
1129                                         conf->lastuser = NULL;
1130                                 }
1131                                 /* In either case */
1132                                 conf->firstuser = user->nextuser;
1133                         } else if (user == conf->lastuser){
1134                                 if (user->prevuser)
1135                                         user->prevuser->nextuser = NULL;
1136                                 else
1137                                         ast_log(LOG_ERROR, "Bad bad bad!  We're the last, not the first, but nobody before us??\n");
1138                                 conf->lastuser = user->prevuser;
1139                         } else {
1140                                 if (user->nextuser)
1141                                         user->nextuser->prevuser = user->prevuser;
1142                                 else
1143                                         ast_log(LOG_ERROR, "Bad! Bad! Bad! user->nextuser is NULL but we're not the end!\n");
1144                                 if (user->prevuser)
1145                                         user->prevuser->nextuser = user->nextuser;
1146                                 else
1147                                         ast_log(LOG_ERROR, "Bad! Bad! Bad! user->prevuser is NULL but we're not the beginning!\n");
1148                         }
1149                 }
1150                 /* Return the number of seconds the user was in the conf */
1151                 snprintf(meetmesecs, sizeof(meetmesecs), "%i", (int) (time(NULL) - user->jointime));
1152                 pbx_builtin_setvar_helper(chan, "MEETMESECS", meetmesecs);
1153         }
1154         free(user);
1155         ast_mutex_unlock(&conflock);
1156         return ret;
1157 }
1158
1159 static struct ast_conference *find_conf(struct ast_channel *chan, char *confno, int make, int dynamic, char *dynamic_pin)
1160 {
1161         struct ast_config *cfg;
1162         struct ast_variable *var;
1163         struct ast_conference *cnf;
1164
1165         /* Check first in the conference list */
1166         ast_mutex_lock(&conflock);
1167         cnf = confs;
1168         while (cnf) {
1169                 if (!strcmp(confno, cnf->confno)) 
1170                         break;
1171                 cnf = cnf->next;
1172         }
1173         ast_mutex_unlock(&conflock);
1174
1175         if (!cnf) {
1176                 if (dynamic) {
1177                         /* No need to parse meetme.conf */
1178                         ast_log(LOG_DEBUG, "Building dynamic conference '%s'\n", confno);
1179                         if (dynamic_pin) {
1180                                 if (dynamic_pin[0] == 'q') {
1181                                         /* Query the user to enter a PIN */
1182                                         ast_app_getdata(chan, "conf-getpin", dynamic_pin, AST_MAX_EXTENSION - 1, 0);
1183                                 }
1184                                 cnf = build_conf(confno, dynamic_pin, make, dynamic);
1185                         } else {
1186                                 cnf = build_conf(confno, "", make, dynamic);
1187                         }
1188                 } else {
1189                         /* Check the config */
1190                         cfg = ast_config_load("meetme.conf");
1191                         if (!cfg) {
1192                                 ast_log(LOG_WARNING, "No meetme.conf file :(\n");
1193                                 return NULL;
1194                         }
1195                         var = ast_variable_browse(cfg, "rooms");
1196                         while(var) {
1197                                 if (!strcasecmp(var->name, "conf")) {
1198                                         /* Separate the PIN */
1199                                         char *pin, *conf;
1200
1201                                         if ((pin = ast_strdupa(var->value))) {
1202                                                 conf = strsep(&pin, "|,");
1203                                                 if (!strcasecmp(conf, confno)) {
1204                                                         /* Bingo it's a valid conference */
1205                                                         if (pin)
1206                                                                 cnf = build_conf(confno, pin, make, dynamic);
1207                                                         else
1208                                                                 cnf = build_conf(confno, "", make, dynamic);
1209                                                         break;
1210                                                 }
1211                                         }
1212                                 }
1213                                 var = var->next;
1214                         }
1215                         if (!var) {
1216                                 ast_log(LOG_DEBUG, "%s isn't a valid conference\n", confno);
1217                         }
1218                         ast_config_destroy(cfg);
1219                 }
1220         } else if (dynamic_pin) {
1221                 /* Correct for the user selecting 'D' instead of 'd' to have
1222                    someone join into a conference that has already been created
1223                    with a pin. */
1224                 if (dynamic_pin[0] == 'q')
1225                         dynamic_pin[0] = '\0';
1226         }
1227         return cnf;
1228 }
1229
1230 /*--- count_exec: The MeetmeCount application */
1231 static int count_exec(struct ast_channel *chan, void *data)
1232 {
1233         struct localuser *u;
1234         int res = 0;
1235         struct ast_conference *conf;
1236         int count;
1237         char *confnum, *localdata;
1238         char val[80] = "0"; 
1239
1240         if (!data || ast_strlen_zero(data)) {
1241                 ast_log(LOG_WARNING, "MeetMeCount requires an argument (conference number)\n");
1242                 return -1;
1243         }
1244         localdata = ast_strdupa(data);
1245         LOCAL_USER_ADD(u);
1246         confnum = strsep(&localdata,"|");       
1247         conf = find_conf(chan, confnum, 0, 0, NULL);
1248         if (conf)
1249                 count = conf->users;
1250         else
1251                 count = 0;
1252
1253         if (localdata && !ast_strlen_zero(localdata)){
1254                 /* have var so load it and exit */
1255                 snprintf(val,sizeof(val), "%i",count);
1256                 pbx_builtin_setvar_helper(chan, localdata,val);
1257         } else {
1258                 if (chan->_state != AST_STATE_UP)
1259                         ast_answer(chan);
1260                 res = ast_say_number(chan, count, "", chan->language, (char *) NULL); /* Needs gender */
1261         }
1262         LOCAL_USER_REMOVE(u);
1263         return res;
1264 }
1265
1266 /*--- conf_exec: The meetme() application */
1267 static int conf_exec(struct ast_channel *chan, void *data)
1268 {
1269         int res=-1;
1270         struct localuser *u;
1271         char confno[AST_MAX_EXTENSION] = "";
1272         int allowretry = 0;
1273         int retrycnt = 0;
1274         struct ast_conference *cnf;
1275         int confflags = 0;
1276         int dynamic = 0;
1277         int empty = 0, empty_no_pin = 0;
1278         char *notdata, *info, *inflags = NULL, *inpin = NULL, the_pin[AST_MAX_EXTENSION] = "";
1279
1280         if (!data || ast_strlen_zero(data)) {
1281                 allowretry = 1;
1282                 notdata = "";
1283         } else {
1284                 notdata = data;
1285         }
1286         LOCAL_USER_ADD(u);
1287         if (chan->_state != AST_STATE_UP)
1288                 ast_answer(chan);
1289
1290         info = ast_strdupa((char *)notdata);
1291
1292         if (info) {
1293                 char *tmp = strsep(&info, "|");
1294                 strncpy(confno, tmp, sizeof(confno) - 1);
1295                 if (ast_strlen_zero(confno)) {
1296                         allowretry = 1;
1297                 }
1298         }
1299         if (info)
1300                 inflags = strsep(&info, "|");
1301         if (info)
1302                 inpin = strsep(&info, "|");
1303         if (inpin)
1304                 strncpy(the_pin, inpin, sizeof(the_pin) - 1);
1305
1306         if (inflags) {
1307                 if (strchr(inflags, 'a'))
1308                         confflags |= CONFFLAG_ADMIN;
1309                 if (strchr(inflags, 'i'))
1310                         confflags |= CONFFLAG_INTROUSER;
1311                 if (strchr(inflags, 'm'))
1312                         confflags |= CONFFLAG_MONITOR;
1313                 if (strchr(inflags, 'p'))
1314                         confflags |= CONFFLAG_POUNDEXIT;
1315                 if (strchr(inflags, 's'))
1316                         confflags |= CONFFLAG_STARMENU;
1317                 if (strchr(inflags, 't'))
1318                         confflags |= CONFFLAG_TALKER;
1319                 if (strchr(inflags, 'q'))
1320                         confflags |= CONFFLAG_QUIET;
1321                 if (strchr(inflags, 'M'))
1322                         confflags |= CONFFLAG_MOH;
1323                 if (strchr(inflags, 'x'))
1324                         confflags |= CONFFLAG_MARKEDEXIT;
1325                 if (strchr(inflags, 'X'))
1326                         confflags |= CONFFLAG_EXIT_CONTEXT;
1327                 if (strchr(inflags, 'A'))
1328                         confflags |= CONFFLAG_MARKEDUSER;
1329                 if (strchr(inflags, 'b'))
1330                         confflags |= CONFFLAG_AGI;
1331                 if (strchr(inflags, 'w'))
1332                         confflags |= CONFFLAG_WAITMARKED;
1333                 if (strchr(inflags, 'r'))
1334                         confflags |= CONFFLAG_RECORDCONF;       
1335                 if (strchr(inflags, 'd'))
1336                         dynamic = 1;
1337                 if (strchr(inflags, 'D')) {
1338                         dynamic = 1;
1339                         if (! inpin) {
1340                                 strncpy(the_pin, "q", sizeof(the_pin) - 1);
1341                         }
1342                 }
1343                 if (strchr(inflags, 'e'))
1344                         empty = 1;
1345                 if (strchr(inflags, 'E')) {
1346                         empty = 1;
1347                         empty_no_pin = 1;
1348                 }
1349         }
1350
1351         do {
1352                 if (retrycnt > 3)
1353                         allowretry = 0;
1354                 if (empty) {
1355                         int i, map[1024];
1356                         struct ast_config *cfg;
1357                         struct ast_variable *var;
1358                         int confno_int;
1359
1360                         memset(map, 0, sizeof(map));
1361
1362                         ast_mutex_lock(&conflock);
1363                         cnf = confs;
1364                         while (cnf) {
1365                                 if (sscanf(cnf->confno, "%d", &confno_int) == 1) {
1366                                         /* Disqualify in use conference */
1367                                         if (confno_int >= 0 && confno_int < 1024)
1368                                                 map[confno_int]++;
1369                                 }
1370                                 cnf = cnf->next;
1371                         }
1372                         ast_mutex_unlock(&conflock);
1373
1374                         /* We only need to load the config file for static and empty_no_pin (otherwise we don't care) */
1375                         if ((empty_no_pin) || (!dynamic)) {
1376                                 cfg = ast_config_load("meetme.conf");
1377                                 if (cfg) {
1378                                         var = ast_variable_browse(cfg, "rooms");
1379                                         while(var) {
1380                                                 if (!strcasecmp(var->name, "conf")) {
1381                                                         char *stringp = ast_strdupa(var->value);
1382                                                         if (stringp) {
1383                                                                 char *confno_tmp = strsep(&stringp, "|,");
1384                                                                 int found = 0;
1385                                                                 if (sscanf(confno_tmp, "%d", &confno_int) == 1) {
1386                                                                         if ((confno_int >= 0) && (confno_int < 1024)) {
1387                                                                                 if (stringp && empty_no_pin) {
1388                                                                                         map[confno_int]++;
1389                                                                                 }
1390                                                                         }
1391                                                                 }
1392                                                                 if (! dynamic) {
1393                                                                         /* For static:  run through the list and see if this conference is empty */
1394                                                                         ast_mutex_lock(&conflock);
1395                                                                         cnf = confs;
1396                                                                         while (cnf) {
1397                                                                                 if (!strcmp(confno_tmp, cnf->confno)) {
1398                                                                                         /* The conference exists, therefore it's not empty */
1399                                                                                         found = 1;
1400                                                                                         break;
1401                                                                                 }
1402                                                                                 cnf = cnf->next;
1403                                                                         }
1404                                                                         ast_mutex_unlock(&conflock);
1405                                                                         if (!found) {
1406                                                                                 /* At this point, we have a confno_tmp (static conference) that is empty */
1407                                                                                 if ((empty_no_pin && ((!stringp) || (stringp && (stringp[0] == '\0')))) || (!empty_no_pin)) {
1408                                                                                 /* Case 1:  empty_no_pin and pin is nonexistant (NULL)
1409                                                                                  * Case 2:  empty_no_pin and pin is blank (but not NULL)
1410                                                                                  * Case 3:  not empty_no_pin
1411                                                                                  */
1412                                                                                         strncpy(confno, confno_tmp, sizeof(confno) - 1);
1413                                                                                         break;
1414                                                                                         /* XXX the map is not complete (but we do have a confno) */
1415                                                                                 }
1416                                                                         }
1417                                                                 }
1418                                                         } else {
1419                                                                 ast_log(LOG_ERROR, "Out of memory\n");
1420                                                         }
1421                                                 }
1422                                                 var = var->next;
1423                                         }
1424                                         ast_config_destroy(cfg);
1425                                 }
1426                         }
1427                         /* Select first conference number not in use */
1428                         if (ast_strlen_zero(confno) && dynamic) {
1429                                 for (i=0;i<1024;i++) {
1430                                         if (!map[i]) {
1431                                                 snprintf(confno, sizeof(confno), "%d", i);
1432                                                 break;
1433                                         }
1434                                 }
1435                         }
1436
1437                         /* Not found? */
1438                         if (ast_strlen_zero(confno)) {
1439                                 res = ast_streamfile(chan, "conf-noempty", chan->language);
1440                                 if (!res)
1441                                         ast_waitstream(chan, "");
1442                         } else {
1443                                 if (sscanf(confno, "%d", &confno_int) == 1) {
1444                                         res = ast_streamfile(chan, "conf-enteringno", chan->language);
1445                                         if (!res) {
1446                                                 ast_waitstream(chan, "");
1447                                                 res = ast_say_digits(chan, confno_int, "", chan->language);
1448                                         }
1449                                 } else {
1450                                         ast_log(LOG_ERROR, "Could not scan confno '%s'\n", confno);
1451                                 }
1452                         }
1453                 }
1454                 while (allowretry && (ast_strlen_zero(confno)) && (++retrycnt < 4)) {
1455                         /* Prompt user for conference number */
1456                         res = ast_app_getdata(chan, "conf-getconfno", confno, sizeof(confno) - 1, 0);
1457                         if (res < 0) {
1458                                 /* Don't try to validate when we catch an error */
1459                                 confno[0] = '\0';
1460                                 allowretry = 0;
1461                                 break;
1462                         }
1463                 }
1464                 if (!ast_strlen_zero(confno)) {
1465                         /* Check the validity of the conference */
1466                         cnf = find_conf(chan, confno, 1, dynamic, the_pin);
1467                         if (!cnf) {
1468                                 res = ast_streamfile(chan, "conf-invalid", chan->language);
1469                                 if (!res)
1470                                         ast_waitstream(chan, "");
1471                                 res = -1;
1472                                 if (allowretry)
1473                                         confno[0] = '\0';
1474                         } else {
1475                                 if (!ast_strlen_zero(cnf->pin)) {
1476                                         char pin[AST_MAX_EXTENSION]="";
1477                                         int j;
1478
1479                                         /* Allow the pin to be retried up to 3 times */
1480                                         for (j=0; j<3; j++) {
1481                                                 if (*the_pin) {
1482                                                         strncpy(pin, the_pin, sizeof(pin) - 1);
1483                                                         res = 0;
1484                                                 } else {
1485                                                         /* Prompt user for pin if pin is required */
1486                                                         res = ast_app_getdata(chan, "conf-getpin", pin + strlen(pin), sizeof(pin) - 1 - strlen(pin), 0);
1487                                                 }
1488                                                 if (res >= 0) {
1489                                                         if (!strcasecmp(pin, cnf->pin)) {
1490                                                                 /* Pin correct */
1491                                                                 allowretry = 0;
1492                                                                 /* Run the conference */
1493                                                                 res = conf_run(chan, cnf, confflags);
1494                                                                 break;
1495                                                         } else {
1496                                                                 /* Pin invalid */
1497                                                                 res = ast_streamfile(chan, "conf-invalidpin", chan->language);
1498                                                                 if (!res)
1499                                                                         ast_waitstream(chan, AST_DIGIT_ANY);
1500                                                                 if (res < 0)
1501                                                                         break;
1502                                                                 pin[0] = res;
1503                                                                 pin[1] = '\0';
1504                                                                 res = -1;
1505                                                                 if (allowretry)
1506                                                                         confno[0] = '\0';
1507                                                         }
1508                                                 } else {
1509                                                         res = -1;
1510                                                         allowretry = 0;
1511                                                         break;
1512                                                 }
1513
1514                                                 /* Don't retry pin with a static pin */
1515                                                 if (*the_pin) {
1516                                                         break;
1517                                                 }
1518                                         }
1519                                 } else {
1520                                         /* No pin required */
1521                                         allowretry = 0;
1522
1523                                         /* Run the conference */
1524                                         res = conf_run(chan, cnf, confflags);
1525                                 }
1526                         }
1527                 }
1528         } while (allowretry);
1529         /* Do the conference */
1530         LOCAL_USER_REMOVE(u);
1531         return res;
1532 }
1533
1534 static struct ast_conf_user* find_user(struct ast_conference *conf, char *callerident) {
1535         struct ast_conf_user *user = NULL;
1536         char usrno[1024] = "";
1537         if (conf && callerident) {
1538                 user = conf->firstuser;
1539                 while(user) {
1540                         snprintf(usrno, sizeof(usrno), "%i", user->user_no);
1541                         if (strcmp(usrno, callerident) == 0)
1542                                 return user;
1543                         user = user->nextuser;
1544                 }
1545         }
1546         return NULL;
1547 }
1548
1549 /*--- admin_exec: The MeetMeadmin application */
1550 /* MeetMeAdmin(confno, command, caller) */
1551 static int admin_exec(struct ast_channel *chan, void *data) {
1552         char *params, *command = NULL, *caller = NULL, *conf = NULL;
1553         struct ast_conference *cnf;
1554         struct ast_conf_user *user = NULL;
1555
1556         ast_mutex_lock(&conflock);
1557         /* The param has the conference number the user and the command to execute */
1558         if (data && !ast_strlen_zero(data)) {           
1559                 params = ast_strdupa((char *) data);
1560                 conf = strsep(&params, "|");
1561                 command = strsep(&params, "|");
1562                 caller = strsep(&params, "|");
1563                 
1564                 if (!command) {
1565                         ast_log(LOG_WARNING, "MeetmeAdmin requires a command!\n");
1566                         ast_mutex_unlock(&conflock);
1567                         return -1;
1568                 }
1569                 cnf = confs;
1570                 while (cnf) {
1571                         if (strcmp(cnf->confno, conf) == 0) 
1572                                 break;
1573                         cnf = cnf->next;
1574                 }
1575                 
1576                 if (caller)
1577                         user = find_user(cnf, caller);
1578                 
1579                 if (cnf) {
1580                         switch((int) (*command)) {
1581                                 case 76: /* L: Lock */ 
1582                                         cnf->locked = 1;
1583                                         break;
1584                                 case 108: /* l: Unlock */ 
1585                                         cnf->locked = 0;
1586                                         break;
1587                                 case 75: /* K: kick all users*/
1588                                         user = cnf->firstuser;
1589                                         while(user) {
1590                                                 user->adminflags |= ADMINFLAG_KICKME;
1591                                                 if (user->nextuser) {
1592                                                         user = user->nextuser;
1593                                                 } else {
1594                                                         break;
1595                                                 }
1596                                         }
1597                                         break;
1598                                 case 101: /* e: Eject last user*/
1599                                         user = cnf->lastuser;
1600                                         if (!(user->userflags & CONFFLAG_ADMIN)) {
1601                                                 user->adminflags |= ADMINFLAG_KICKME;
1602                                                 break;
1603                                         } else
1604                                                 ast_log(LOG_NOTICE, "Not kicking last user, is an Admin!\n");
1605                                         break;
1606                                 case 77: /* M: Mute */ 
1607                                         if (user) {
1608                                                 user->adminflags |= ADMINFLAG_MUTED;
1609                                         } else {
1610                                                 ast_log(LOG_NOTICE, "Specified User not found!\n");
1611                                         }
1612                                         break;
1613                                 case 78: /* N: Mute all users */
1614                                         user = cnf->firstuser;
1615                                         while(user) {
1616                                                 if (user && !(user->userflags & CONFFLAG_ADMIN))
1617                                                         user->adminflags |= ADMINFLAG_MUTED;
1618                                                 if (user->nextuser) {
1619                                                         user = user->nextuser;
1620                                                 } else {
1621                                                         break;
1622                                                 }
1623                                         }
1624                                         break;                                  
1625                                 case 109: /* m: Unmute */ 
1626                                         if (user && (user->adminflags & ADMINFLAG_MUTED)) {
1627                                                 user->adminflags ^= ADMINFLAG_MUTED;
1628                                         } else {
1629                                                 ast_log(LOG_NOTICE, "Specified User not found or he muted himself!");
1630                                         }
1631                                         break;
1632                                 case  110: /* n: Unmute all users */
1633                                         user = cnf->firstuser;
1634                                         while(user) {
1635                                                 if (user && (user-> adminflags & ADMINFLAG_MUTED)) {
1636                                                         user->adminflags ^= ADMINFLAG_MUTED;
1637                                                 }
1638                                                 if (user->nextuser) {
1639                                                         user = user->nextuser;
1640                                                 } else {
1641                                                         break;
1642                                                 }
1643                                         }
1644                                         break;
1645                                 case 107: /* k: Kick user */ 
1646                                         if (user) {
1647                                                 user->adminflags |= ADMINFLAG_KICKME;
1648                                         } else {
1649                                                 ast_log(LOG_NOTICE, "Specified User not found!");
1650                                         }
1651                                         break;
1652                         }
1653                 } else {
1654                         ast_log(LOG_NOTICE, "Conference Number not found\n");
1655                 }
1656         }
1657         ast_mutex_unlock(&conflock);
1658         return 0;
1659 }
1660
1661 static void *recordthread(void *args)
1662 {
1663         struct ast_conference *cnf;
1664         struct ast_frame *f=NULL;
1665         int flags;
1666         struct ast_filestream *s;
1667         int res=0;
1668
1669         cnf = (struct ast_conference *)args;
1670         ast_stopstream(cnf->chan);
1671         flags = O_CREAT|O_TRUNC|O_WRONLY;
1672         s = ast_writefile(cnf->recordingfilename, cnf->recordingformat, NULL, flags, 0, 0644);
1673
1674         if (s) {
1675                 cnf->recording = MEETME_RECORD_ACTIVE;
1676                 while (ast_waitfor(cnf->chan, -1) > -1) {
1677                         f = ast_read(cnf->chan);
1678                         if (!f) {
1679                                 res = -1;
1680                                 break;
1681                         }
1682                         if (f->frametype == AST_FRAME_VOICE) {
1683                                 res = ast_writestream(s, f);
1684                                 if (res) 
1685                                         break;
1686                         }
1687                         ast_frfree(f);
1688                         if (cnf->recording == MEETME_RECORD_TERMINATE) {
1689                                 ast_mutex_lock(&conflock);
1690                                 ast_mutex_unlock(&conflock);
1691                                 break;
1692                         }
1693                 }
1694                 cnf->recording = MEETME_RECORD_OFF;
1695                 ast_closestream(s);
1696         }
1697         pthread_exit(0);
1698 }
1699
1700 int unload_module(void)
1701 {
1702         STANDARD_HANGUP_LOCALUSERS;
1703         ast_cli_unregister(&cli_show_confs);
1704         ast_cli_unregister(&cli_conf);
1705         ast_unregister_application(app3);
1706         ast_unregister_application(app2);
1707         return ast_unregister_application(app);
1708 }
1709
1710 int load_module(void)
1711 {
1712         ast_cli_register(&cli_show_confs);
1713         ast_cli_register(&cli_conf);
1714         ast_register_application(app3, admin_exec, synopsis3, descrip3);
1715         ast_register_application(app2, count_exec, synopsis2, descrip2);
1716         return ast_register_application(app, conf_exec, synopsis, descrip);
1717 }
1718
1719
1720 char *description(void)
1721 {
1722         return tdesc;
1723 }
1724
1725 int usecount(void)
1726 {
1727         int res;
1728         STANDARD_USECOUNT(res);
1729         return res;
1730 }
1731
1732 char *key()
1733 {
1734         return ASTERISK_GPL_KEY;
1735 }
1736