Add realtime support to app_directory (bug #2475)
[asterisk/asterisk.git] / apps / app_directory.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Provide a directory of extensions
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/say.h>
22 #include <asterisk/utils.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include "../asterisk.h"
28 #include "../astconf.h"
29
30 static char *tdesc = "Extension Directory";
31 static char *app = "Directory";
32
33 static char *synopsis = "Provide directory of voicemail extensions";
34 static char *descrip =
35 "  Directory(vm-context[|dial-context[|options]]): Presents the user with a directory\n"
36 "of extensions from which they  may  select  by name. The  list  of  names \n"
37 "and  extensions  is discovered from  voicemail.conf. The  vm-context  argument\n"
38 "is required, and specifies  the  context  of voicemail.conf to use.  The\n"
39 "dial-context is the context to use for dialing the users, and defaults to\n"
40 "the vm-context if unspecified. The 'f' option causes the directory to match\n"
41 "based on the first name in voicemail.conf instead of the last name.\n"
42 "Returns 0 unless the user hangs up. It  also sets up the channel on exit\n"
43 "to enter the extension the user selected.  If the user enters '0' and there\n"
44 "exists an extension 'o' in the current context, the directory will exit with 0\n"
45 "and call control will resume at that extension.  Entering '*' will exit similarly,\n"
46 "but to the 'a' extension, much like app_voicemail's behavior.\n";
47
48 /* For simplicity, I'm keeping the format compatible with the voicemail config,
49    but i'm open to suggestions for isolating it */
50
51 #define DIRECTORY_CONFIG "voicemail.conf"
52
53 /* How many digits to read in */
54 #define NUMDIGITS 3
55
56 STANDARD_LOCAL_USER;
57
58 LOCAL_USER_DECL;
59
60 static char *convert(char *lastname)
61 {
62         char *tmp;
63         int lcount = 0;
64         tmp = malloc(NUMDIGITS + 1);
65         if (tmp) {
66                 while((*lastname > 32) && lcount < NUMDIGITS) {
67                         switch(toupper(*lastname)) {
68                         case '1':
69                                 tmp[lcount++] = '1';
70                                 break;
71                         case '2':
72                         case 'A':
73                         case 'B':
74                         case 'C':
75                                 tmp[lcount++] = '2';
76                                 break;
77                         case '3':
78                         case 'D':
79                         case 'E':
80                         case 'F':
81                                 tmp[lcount++] = '3';
82                                 break;
83                         case '4':
84                         case 'G':
85                         case 'H':
86                         case 'I':
87                                 tmp[lcount++] = '4';
88                                 break;
89                         case '5':
90                         case 'J':
91                         case 'K':
92                         case 'L':
93                                 tmp[lcount++] = '5';
94                                 break;
95                         case '6':
96                         case 'M':
97                         case 'N':
98                         case 'O':
99                                 tmp[lcount++] = '6';
100                                 break;
101                         case '7':
102                         case 'P':
103                         case 'Q':
104                         case 'R':
105                         case 'S':
106                                 tmp[lcount++] = '7';
107                                 break;
108                         case '8':
109                         case 'T':
110                         case 'U':
111                         case 'V':
112                                 tmp[lcount++] = '8';
113                                 break;
114                         case '9':
115                         case 'W':
116                         case 'X':
117                         case 'Y':
118                         case 'Z':
119                                 tmp[lcount++] = '9';
120                                 break;
121                         }
122                         lastname++;
123                 }
124                 tmp[lcount] = '\0';
125         }
126         return tmp;
127 }
128
129 /* play name of mailbox owner.
130  * returns:  -1 for bad or missing extension
131  *           '1' for selected entry from directory
132  *           '*' for skipped entry from directory
133  */
134 static int play_mailbox_owner(struct ast_channel *chan, char *context, char *dialcontext, char *ext, char *name) {
135         int res = 0;
136         int loop = 3;
137         char fn[256];
138         char fn2[256];
139
140         /* Check for the VoiceMail2 greeting first */
141         snprintf(fn, sizeof(fn), "%s/voicemail/%s/%s/greet",
142                 (char *)ast_config_AST_SPOOL_DIR, context, ext);
143
144         /* Otherwise, check for an old-style Voicemail greeting */
145         snprintf(fn2, sizeof(fn2), "%s/vm/%s/greet",
146                 (char *)ast_config_AST_SPOOL_DIR, ext);
147
148         if (ast_fileexists(fn, NULL, chan->language) > 0) {
149                 res = ast_streamfile(chan, fn, chan->language);
150                 if (!res) {
151                         res = ast_waitstream(chan, AST_DIGIT_ANY);
152                 }
153                 ast_stopstream(chan);
154         } else if (ast_fileexists(fn2, NULL, chan->language) > 0) {
155                 res = ast_streamfile(chan, fn2, chan->language);
156                 if (!res) {
157                         res = ast_waitstream(chan, AST_DIGIT_ANY);
158                 }
159                 ast_stopstream(chan);
160         } else {
161                 res = ast_say_character_str(chan, !ast_strlen_zero(name) ? name : ext,
162                                         AST_DIGIT_ANY, chan->language);
163         }
164
165         while (loop) {
166                 if (!res) {
167                         res = ast_streamfile(chan, "dir-instr", chan->language);
168                 }
169                 if (!res) {
170                         res = ast_waitstream(chan, AST_DIGIT_ANY);
171                 }
172                 if (!res) {
173                         res = ast_waitfordigit(chan, 3000);
174                 }
175                 ast_stopstream(chan);
176         
177                 if (res > -1) {
178                         switch (res) {
179                                 case '1':
180                                         /* Name selected */
181                                         loop = 0;
182                                         if (ast_exists_extension(chan,dialcontext,ext,1,chan->cid.cid_num)) {
183                                                 strncpy(chan->exten, ext, sizeof(chan->exten)-1);
184                                                 chan->priority = 0;
185                                                 strncpy(chan->context, dialcontext, sizeof(chan->context)-1);
186                                         } else {
187                                                 ast_log(LOG_WARNING,
188                                                         "Can't find extension '%s' in context '%s'.  "
189                                                         "Did you pass the wrong context to Directory?\n",
190                                                         ext, dialcontext);
191                                                 res = -1;
192                                         }
193                                         break;
194         
195                                 case '*':   
196                                         /* Skip to next match in list */
197                                         loop = 0;
198                                         break;
199         
200                                 default:
201                                         /* Not '1', or '*', so decrement number of tries */
202                                         res = 0;
203                                         loop--;
204                                         break;
205                         } /* end switch */
206                 } /* end if */
207                 else {
208                         /* User hungup, so jump out now */
209                         loop = 0;
210                 }
211         } /* end while */
212
213         return(res);
214 }
215
216 static struct ast_config *realtime_directory(char *context)
217 {
218         struct ast_config *cfg = NULL;
219         struct ast_variable *rtvar = NULL;
220         struct ast_category *cat = NULL;
221         char fullname[50] = "";
222         char mailbox[50] = "";
223         char tmp[100] = "";
224         int havename = 0;
225         int havemailbox = 0;
226
227         /* Load flat file config. */
228         cfg = ast_config_load(DIRECTORY_CONFIG);
229
230         if (!cfg) {
231                 /* Loading config failed. Even if config file doesn't exist, we should still have an ast_config. */
232                 ast_log(LOG_WARNING, "Loading/Creating config failed.\n");
233                 return NULL;
234         }
235
236         /* Load RealTime voicemail users for this context. */
237         rtvar = ast_load_realtime("voicemail", "context", context, NULL);
238
239         /* If we got nothing from RealTime, we can just return the Flatfile. */
240         if (!rtvar)
241                 return cfg;
242
243         /* Does the context exist within the Flatfile? */
244         if (ast_category_exist(cfg, context)) {
245                 /* If so, get a pointer to it so we can append RealTime variables to it. */
246                 cat = ast_category_get(cfg, context);
247         } else {
248                 /* If not, make a fresh one and append it to the master config. */
249                 cat = ast_category_new(context);
250                 if (!cat) {
251                         ast_log(LOG_WARNING, "Ran out of memory while creating new ast_category!\n");
252                         ast_config_destroy(cfg);
253                         return NULL;
254                 }
255                 ast_category_append(cfg, cat);
256         }
257
258         /* We now have a category: from the Flatfile or freshly created. */
259         while (rtvar) {
260                 if (!strcasecmp(rtvar->name, "fullname")) {
261                         strncpy(fullname, rtvar->value, sizeof(fullname)-1);
262                         havename = 1;
263                 } else if (!strcasecmp(rtvar->name, "mailbox")) {
264                         strncpy(mailbox, rtvar->value, sizeof(mailbox)-1);
265                         havemailbox = 1;
266                 }
267
268                 /* app_directory needs only mailbox and fullname. Fill password and email with dummy values. */
269                 if (havemailbox && havename) {
270                         sprintf(tmp, "9999,%s,email@email.com", fullname);
271                         ast_variable_append(cat, ast_variable_new(mailbox, tmp));
272                         havemailbox = 0;
273                         havename = 0;
274                 }
275
276                 rtvar = rtvar->next;
277         }
278
279         return cfg;
280 }
281
282 static int do_directory(struct ast_channel *chan, struct ast_config *cfg, char *context, char *dialcontext, char digit, int last)
283 {
284         /* Read in the first three digits..  "digit" is the first digit, already read */
285         char ext[NUMDIGITS + 1];
286         char name[80] = "";
287         struct ast_variable *v;
288         int res;
289         int found=0;
290         int lastuserchoice = 0;
291         char *start, *pos, *conv,*stringp=NULL;
292
293         if (!context || ast_strlen_zero(context)) {
294                 ast_log(LOG_WARNING,
295                         "Directory must be called with an argument "
296                         "(context in which to interpret extensions)\n");
297                 return -1;
298         }
299         if (digit == '0') {
300                 if (ast_exists_extension(chan,chan->context,"o",1,chan->cid.cid_num) || 
301                         (!ast_strlen_zero(chan->macrocontext) &&
302                      ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num))) {
303                         strncpy(chan->exten, "o", sizeof(chan->exten)-1);
304                         chan->priority = 0;
305                         return 0;
306                 } else {
307
308                         ast_log(LOG_WARNING, "Can't find extension 'o' in current context.  "
309                                 "Not Exiting the Directory!\n");
310                         res = 0;
311                 }
312         }       
313         if (digit == '*') {
314                 if (ast_exists_extension(chan,chan->context,"a",1,chan->cid.cid_num) || 
315                         (!ast_strlen_zero(chan->macrocontext) &&
316                      ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num))) {
317                         strncpy(chan->exten, "a", sizeof(chan->exten)-1);
318                         chan->priority = 0;
319                         return 0;
320                 } else {
321
322                         ast_log(LOG_WARNING, "Can't find extension 'a' in current context.  "
323                                 "Not Exiting the Directory!\n");
324                         res = 0;
325                 }
326         }       
327         memset(ext, 0, sizeof(ext));
328         ext[0] = digit;
329         res = 0;
330         if (ast_readstring(chan, ext + 1, NUMDIGITS - 1, 3000, 3000, "#") < 0) res = -1;
331         if (!res) {
332                 /* Search for all names which start with those digits */
333                 v = ast_variable_browse(cfg, context);
334                 while(v && !res) {
335                         /* Find all candidate extensions */
336                         while(v) {
337                                 /* Find a candidate extension */
338                                 start = strdup(v->value);
339                                 if (start) {
340                                         stringp=start;
341                                         strsep(&stringp, ",");
342                                         pos = strsep(&stringp, ",");
343                                         if (pos) {
344                                                 strncpy(name, pos, sizeof(name) - 1);
345                                                 /* Grab the last name */
346                                                 if (last && strrchr(pos,' '))
347                                                         pos = strrchr(pos, ' ') + 1;
348                                                 conv = convert(pos);
349                                                 if (conv) {
350                                                         if (!strcmp(conv, ext)) {
351                                                                 /* Match! */
352                                                                 found++;
353                                                                 free(conv);
354                                                                 free(start);
355                                                                 break;
356                                                         }
357                                                         free(conv);
358                                                 }
359                                         }
360                                         free(start);
361                                 }
362                                 v = v->next;
363                         }
364
365                         if (v) {
366                                 /* We have a match -- play a greeting if they have it */
367                                 res = play_mailbox_owner(chan, context, dialcontext, v->name, name);
368                                 switch (res) {
369                                         case -1:
370                                                 /* user pressed '1' but extension does not exist, or
371                                                  * user hungup
372                                                  */
373                                                 lastuserchoice = 0;
374                                                 break;
375                                         case '1':
376                                                 /* user pressed '1' and extensions exists */
377                                                 lastuserchoice = res;
378                                                 strncpy(chan->context, dialcontext, sizeof(chan->context) - 1);
379                                                 strncpy(chan->exten, v->name, sizeof(chan->exten) - 1);
380                                                 chan->priority = 0;
381                                                 break;
382                                         case '*':
383                                                 /* user pressed '*' to skip something found */
384                                                 lastuserchoice = res;
385                                                 res = 0;
386                                                 break;
387                                         default:
388                                                 break;
389                                 }
390                                 v = v->next;
391                         }
392                 }
393
394                 if (lastuserchoice != '1') {
395                         if (found) 
396                                 res = ast_streamfile(chan, "dir-nomore", chan->language);
397                         else
398                                 res = ast_streamfile(chan, "dir-nomatch", chan->language);
399                         if (!res)
400                                 res = 1;
401                         return res;
402                 }
403                 return 0;
404         }
405         return res;
406 }
407
408 static int directory_exec(struct ast_channel *chan, void *data)
409 {
410         int res = 0;
411         struct localuser *u;
412         struct ast_config *cfg;
413         int last = 1;
414         char *context, *dialcontext, *dirintro, *options;
415
416         if (!data) {
417                 ast_log(LOG_WARNING, "directory requires an argument (context[,dialcontext])\n");
418                 return -1;
419         }
420
421 top:
422         context = ast_strdupa(data);
423         dialcontext = strchr(context, '|');
424         if (dialcontext) {
425                 *dialcontext = '\0';
426                 dialcontext++;
427                 options = strchr(dialcontext, '|');
428                 if (options) {
429                         *options = '\0';
430                         options++; 
431                         if (strchr(options, 'f'))
432                                 last = 0;
433                 }
434         } else  
435                 dialcontext = context;
436
437         cfg = realtime_directory(context);
438         if (!cfg) {
439                 ast_log(LOG_WARNING, "Unable to open/create directory configuration %s\n", DIRECTORY_CONFIG);
440                 return -1;
441         }
442
443         LOCAL_USER_ADD(u);
444
445         dirintro = ast_variable_retrieve(cfg, context, "directoryintro");
446         if (!dirintro || ast_strlen_zero(dirintro))
447                 dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
448         if (!dirintro || ast_strlen_zero(dirintro)) {
449                 if (last)
450                         dirintro = "dir-intro"; 
451                 else
452                         dirintro = "dir-intro-fn";
453         }
454         if (chan->_state != AST_STATE_UP) 
455                 res = ast_answer(chan);
456         if (!res)
457                 res = ast_streamfile(chan, dirintro, chan->language);
458         if (!res)
459                 res = ast_waitstream(chan, AST_DIGIT_ANY);
460         ast_stopstream(chan);
461         if (!res)
462                 res = ast_waitfordigit(chan, 5000);
463         if (res > 0) {
464                 res = do_directory(chan, cfg, context, dialcontext, res, last);
465                 if (res > 0) {
466                         res = ast_waitstream(chan, AST_DIGIT_ANY);
467                         ast_stopstream(chan);
468                         if (res >= 0) {
469                                 goto top;
470                         }
471                 }
472         }
473         ast_config_destroy(cfg);
474         LOCAL_USER_REMOVE(u);
475         return res;
476 }
477
478 int unload_module(void)
479 {
480         STANDARD_HANGUP_LOCALUSERS;
481         return ast_unregister_application(app);
482 }
483
484 int load_module(void)
485 {
486         return ast_register_application(app, directory_exec, synopsis, descrip);
487 }
488
489 char *description(void)
490 {
491         return tdesc;
492 }
493
494 int usecount(void)
495 {
496         int res;
497         STANDARD_USECOUNT(res);
498         return res;
499 }
500
501 char *key()
502 {
503         return ASTERISK_GPL_KEY;
504 }