code simplifications
[asterisk/asterisk.git] / apps / app_directory.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Provide a directory of extensions
22  *
23  * \author Mark Spencer <markster@digium.com>
24  * 
25  * \ingroup applications
26  */
27  
28 #include <string.h>
29 #include <ctype.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32
33 #include "asterisk.h"
34
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36
37 #include "asterisk/lock.h"
38 #include "asterisk/file.h"
39 #include "asterisk/logger.h"
40 #include "asterisk/channel.h"
41 #include "asterisk/pbx.h"
42 #include "asterisk/module.h"
43 #include "asterisk/config.h"
44 #include "asterisk/say.h"
45 #include "asterisk/utils.h"
46 #include "asterisk/app.h"
47
48 static char *app = "Directory";
49
50 static char *synopsis = "Provide directory of voicemail extensions";
51 static char *descrip =
52 "  Directory(vm-context[|dial-context[|options]]): This application will present\n"
53 "the calling channel with a directory of extensions from which they can search\n"
54 "by name. The list of names and corresponding extensions is retrieved from the\n"
55 "voicemail configuration file, voicemail.conf.\n"
56 "  This applicaiton will immediate exit if one of the following DTMF digits are\n"
57 "received and the extension to jump to exists:\n"
58 "    0 - Jump to the 'o' extension, if it exists.\n"
59 "    * - Jump to the 'a' extension, if it exists.\n\n"
60 "  Parameters:\n"
61 "    vm-context   - This is the context within voicemail.conf to use for the\n"
62 "                   Directory.\n"
63 "    dial-context - This is the dialplan context to use when looking for an\n"
64 "                   extension that the user has selected, or when jumping to the\n"
65 "                   'o' or 'a' extension.\n\n"
66 "  Options:\n"
67 "    e - In addition to the name, also read the extension number to the\n"
68 "        caller before presenting dialing options.\n"
69 "    f - Allow the caller to enter the first name of a user in the directory\n"
70 "        instead of using the last name.\n";
71
72 /* For simplicity, I'm keeping the format compatible with the voicemail config,
73    but i'm open to suggestions for isolating it */
74
75 #define VOICEMAIL_CONFIG "voicemail.conf"
76
77 /* How many digits to read in */
78 #define NUMDIGITS 3
79
80 LOCAL_USER_DECL;
81
82 static char *convert(char *lastname)
83 {
84         char *tmp;
85         int lcount = 0;
86         tmp = ast_malloc(NUMDIGITS + 1);
87         if (tmp) {
88                 while((*lastname > 32) && lcount < NUMDIGITS) {
89                         switch(toupper(*lastname)) {
90                         case '1':
91                                 tmp[lcount++] = '1';
92                                 break;
93                         case '2':
94                         case 'A':
95                         case 'B':
96                         case 'C':
97                                 tmp[lcount++] = '2';
98                                 break;
99                         case '3':
100                         case 'D':
101                         case 'E':
102                         case 'F':
103                                 tmp[lcount++] = '3';
104                                 break;
105                         case '4':
106                         case 'G':
107                         case 'H':
108                         case 'I':
109                                 tmp[lcount++] = '4';
110                                 break;
111                         case '5':
112                         case 'J':
113                         case 'K':
114                         case 'L':
115                                 tmp[lcount++] = '5';
116                                 break;
117                         case '6':
118                         case 'M':
119                         case 'N':
120                         case 'O':
121                                 tmp[lcount++] = '6';
122                                 break;
123                         case '7':
124                         case 'P':
125                         case 'Q':
126                         case 'R':
127                         case 'S':
128                                 tmp[lcount++] = '7';
129                                 break;
130                         case '8':
131                         case 'T':
132                         case 'U':
133                         case 'V':
134                                 tmp[lcount++] = '8';
135                                 break;
136                         case '9':
137                         case 'W':
138                         case 'X':
139                         case 'Y':
140                         case 'Z':
141                                 tmp[lcount++] = '9';
142                                 break;
143                         }
144                         lastname++;
145                 }
146                 tmp[lcount] = '\0';
147         }
148         return tmp;
149 }
150
151 /* play name of mailbox owner.
152  * returns:  -1 for bad or missing extension
153  *           '1' for selected entry from directory
154  *           '*' for skipped entry from directory
155  */
156 static int play_mailbox_owner(struct ast_channel *chan, char *context, char *dialcontext, char *ext, char *name, int readext) {
157         int res = 0;
158         int loop = 3;
159         char fn[256];
160         char fn2[256];
161
162         /* Check for the VoiceMail2 greeting first */
163         snprintf(fn, sizeof(fn), "%s/voicemail/%s/%s/greet",
164                 (char *)ast_config_AST_SPOOL_DIR, context, ext);
165
166         /* Otherwise, check for an old-style Voicemail greeting */
167         snprintf(fn2, sizeof(fn2), "%s/vm/%s/greet",
168                 (char *)ast_config_AST_SPOOL_DIR, ext);
169
170         if (ast_fileexists(fn, NULL, chan->language) > 0) {
171                 res = ast_streamfile(chan, fn, chan->language);
172                 if (!res) {
173                         res = ast_waitstream(chan, AST_DIGIT_ANY);
174                 }
175                 ast_stopstream(chan);
176                 /* If Option 'e' was specified, also read the extension number with the name */
177                 if (readext) {
178                         res = ast_streamfile(chan, "vm-extension", chan->language);
179                         if (!res)
180                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
181                         res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
182                 }
183         } else if (ast_fileexists(fn2, NULL, chan->language) > 0) {
184                 res = ast_streamfile(chan, fn2, chan->language);
185                 if (!res)
186                         res = ast_waitstream(chan, AST_DIGIT_ANY);
187                 ast_stopstream(chan);
188                 /* If Option 'e' was specified, also read the extension number with the name */
189                 if (readext) {
190                         res = ast_streamfile(chan, "vm-extension", chan->language);
191                         if (!res) {
192                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
193                         }
194                         res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
195                 }
196         } else {
197                 res = ast_say_character_str(chan, S_OR(name, ext),
198                                         AST_DIGIT_ANY, chan->language);
199                 if (!ast_strlen_zero(name) && readext) {
200                         res = ast_streamfile(chan, "vm-extension", chan->language);
201                         if (!res)
202                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
203                         res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
204                 }
205         }
206
207         while (loop) {
208                 if (!res)
209                         res = ast_streamfile(chan, "dir-instr", chan->language);
210                 if (!res)
211                         res = ast_waitstream(chan, AST_DIGIT_ANY);
212                 if (!res)
213                         res = ast_waitfordigit(chan, 3000);
214                 ast_stopstream(chan);
215         
216                 if (res > -1) {
217                         switch (res) {
218                                 case '1':
219                                         /* Name selected */
220                                         loop = 0;
221                                         if (ast_goto_if_exists(chan, dialcontext, ext, 1)) {
222                                                 ast_log(LOG_WARNING,
223                                                         "Can't find extension '%s' in context '%s'.  "
224                                                         "Did you pass the wrong context to Directory?\n",
225                                                         ext, dialcontext);
226                                                 res = -1;
227                                         }
228                                         break;
229         
230                                 case '*':   
231                                         /* Skip to next match in list */
232                                         loop = 0;
233                                         break;
234         
235                                 default:
236                                         /* Not '1', or '*', so decrement number of tries */
237                                         res = 0;
238                                         loop--;
239                                         break;
240                         } /* end switch */
241                 } /* end if */
242                 else {
243                         /* User hungup, so jump out now */
244                         loop = 0;
245                 }
246         } /* end while */
247
248         return(res);
249 }
250
251 static struct ast_config *realtime_directory(char *context)
252 {
253         struct ast_config *cfg;
254         struct ast_config *rtdata;
255         struct ast_category *cat;
256         struct ast_variable *var;
257         char *mailbox;
258         char *fullname;
259         char *hidefromdir;
260         char tmp[100];
261
262         /* Load flat file config. */
263         cfg = ast_config_load(VOICEMAIL_CONFIG);
264
265         if (!cfg) {
266                 /* Loading config failed. */
267                 ast_log(LOG_WARNING, "Loading config failed.\n");
268                 return NULL;
269         }
270
271         /* Get realtime entries, categorized by their mailbox number
272            and present in the requested context */
273         rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, NULL);
274
275         /* if there are no results, just return the entries from the config file */
276         if (!rtdata)
277                 return cfg;
278
279         /* Does the context exist within the config file? If not, make one */
280         cat = ast_category_get(cfg, context);
281         if (!cat) {
282                 cat = ast_category_new(context);
283                 if (!cat) {
284                         ast_log(LOG_WARNING, "Out of memory\n");
285                         ast_config_destroy(cfg);
286                         return NULL;
287                 }
288                 ast_category_append(cfg, cat);
289         }
290
291         mailbox = NULL;
292         while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) {
293                 fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
294                 hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir");
295                 snprintf(tmp, sizeof(tmp), "no-password,%s,hidefromdir=%s",
296                          fullname ? fullname : "",
297                          hidefromdir ? hidefromdir : "no");
298                 var = ast_variable_new(mailbox, tmp);
299                 if (var)
300                         ast_variable_append(cat, var);
301                 else
302                         ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
303         }
304         ast_config_destroy(rtdata);
305
306         return cfg;
307 }
308
309 static int do_directory(struct ast_channel *chan, struct ast_config *cfg, char *context, char *dialcontext, char digit, int last, int readext)
310 {
311         /* Read in the first three digits..  "digit" is the first digit, already read */
312         char ext[NUMDIGITS + 1];
313         char name[80] = "";
314         struct ast_variable *v;
315         int res;
316         int found=0;
317         int lastuserchoice = 0;
318         char *start, *pos, *conv,*stringp=NULL;
319
320         if (ast_strlen_zero(context)) {
321                 ast_log(LOG_WARNING,
322                         "Directory must be called with an argument "
323                         "(context in which to interpret extensions)\n");
324                 return -1;
325         }
326         if (digit == '0') {
327                 if (!ast_goto_if_exists(chan, chan->context, "o", 1) ||
328                     (!ast_strlen_zero(chan->macrocontext) &&
329                      !ast_goto_if_exists(chan, chan->macrocontext, "o", 1))) {
330                         return 0;
331                 } else {
332                         ast_log(LOG_WARNING, "Can't find extension 'o' in current context.  "
333                                 "Not Exiting the Directory!\n");
334                         res = 0;
335                 }
336         }       
337         if (digit == '*') {
338                 if (!ast_goto_if_exists(chan, chan->context, "a", 1) ||
339                     (!ast_strlen_zero(chan->macrocontext) &&
340                      !ast_goto_if_exists(chan, chan->macrocontext, "a", 1))) {
341                         return 0;
342                 } else {
343                         ast_log(LOG_WARNING, "Can't find extension 'a' in current context.  "
344                                 "Not Exiting the Directory!\n");
345                         res = 0;
346                 }
347         }       
348         memset(ext, 0, sizeof(ext));
349         ext[0] = digit;
350         res = 0;
351         if (ast_readstring(chan, ext + 1, NUMDIGITS - 1, 3000, 3000, "#") < 0) res = -1;
352         if (!res) {
353                 /* Search for all names which start with those digits */
354                 v = ast_variable_browse(cfg, context);
355                 while(v && !res) {
356                         /* Find all candidate extensions */
357                         while(v) {
358                                 /* Find a candidate extension */
359                                 start = strdup(v->value);
360                                 if (start && !strcasestr(start, "hidefromdir=yes")) {
361                                         stringp=start;
362                                         strsep(&stringp, ",");
363                                         pos = strsep(&stringp, ",");
364                                         if (pos) {
365                                                 ast_copy_string(name, pos, sizeof(name));
366                                                 /* Grab the last name */
367                                                 if (last && strrchr(pos,' '))
368                                                         pos = strrchr(pos, ' ') + 1;
369                                                 conv = convert(pos);
370                                                 if (conv) {
371                                                         if (!strcmp(conv, ext)) {
372                                                                 /* Match! */
373                                                                 found++;
374                                                                 free(conv);
375                                                                 free(start);
376                                                                 break;
377                                                         }
378                                                         free(conv);
379                                                 }
380                                         }
381                                         free(start);
382                                 }
383                                 v = v->next;
384                         }
385
386                         if (v) {
387                                 /* We have a match -- play a greeting if they have it */
388                                 res = play_mailbox_owner(chan, context, dialcontext, v->name, name, readext);
389                                 switch (res) {
390                                         case -1:
391                                                 /* user pressed '1' but extension does not exist, or
392                                                  * user hungup
393                                                  */
394                                                 lastuserchoice = 0;
395                                                 break;
396                                         case '1':
397                                                 /* user pressed '1' and extensions exists;
398                                                    play_mailbox_owner will already have done
399                                                    a goto() on the channel
400                                                  */
401                                                 lastuserchoice = res;
402                                                 break;
403                                         case '*':
404                                                 /* user pressed '*' to skip something found */
405                                                 lastuserchoice = res;
406                                                 res = 0;
407                                                 break;
408                                         default:
409                                                 break;
410                                 }
411                                 v = v->next;
412                         }
413                 }
414
415                 if (lastuserchoice != '1') {
416                         res = ast_streamfile(chan, found ? "dir-nomore" : "dir-nomatch", chan->language);
417                         if (!res)
418                                 res = 1;
419                         return res;
420                 }
421                 return 0;
422         }
423         return res;
424 }
425
426 static int directory_exec(struct ast_channel *chan, void *data)
427 {
428         int res = 0;
429         struct localuser *u;
430         struct ast_config *cfg;
431         int last = 1;
432         int readext = 0;
433         char *dirintro, *parse;
434         AST_DECLARE_APP_ARGS(args,
435                 AST_APP_ARG(vmcontext);
436                 AST_APP_ARG(dialcontext);
437                 AST_APP_ARG(options);
438         );
439
440         if (ast_strlen_zero(data)) {
441                 ast_log(LOG_WARNING, "Directory requires an argument (context[,dialcontext])\n");
442                 return -1;
443         }
444
445         LOCAL_USER_ADD(u);
446
447         if (!(parse = ast_strdupa(data))) {
448                 LOCAL_USER_REMOVE(u);
449                 return -1; 
450         }
451
452         AST_STANDARD_APP_ARGS(args, parse);
453                 
454         if (args.options) {
455                 if (strchr(args.options, 'f'))
456                         last = 0;
457                 if (strchr(args.options, 'e'))
458                         readext = 1;
459         }
460
461         if (ast_strlen_zero(args.dialcontext))  
462                 args.dialcontext = args.vmcontext;
463
464         cfg = realtime_directory(args.vmcontext);
465         if (!cfg) {
466                 ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
467                 LOCAL_USER_REMOVE(u);
468                 return -1;
469         }
470
471         dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro");
472         if (ast_strlen_zero(dirintro))
473                 dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
474         if (ast_strlen_zero(dirintro))
475                 dirintro = last ? "dir-intro" : "dir-intro-fn";
476
477         if (chan->_state != AST_STATE_UP) 
478                 res = ast_answer(chan);
479
480         for (;;) {
481                 if (!res)
482                         res = ast_streamfile(chan, dirintro, chan->language);
483                 if (!res)
484                         res = ast_waitstream(chan, AST_DIGIT_ANY);
485                 ast_stopstream(chan);
486                 if (!res)
487                         res = ast_waitfordigit(chan, 5000);
488                 if (res > 0) {
489                         res = do_directory(chan, cfg, args.vmcontext, args.dialcontext, res, last, readext);
490                         if (res > 0) {
491                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
492                                 ast_stopstream(chan);
493                                 if (res >= 0)
494                                         continue;
495                         }
496                 }
497                 break;
498         }
499         ast_config_destroy(cfg);
500         LOCAL_USER_REMOVE(u);
501         return res;
502 }
503
504 static int unload_module(void *mod)
505 {
506         int res;
507         res = ast_unregister_application(app);
508         return res;
509 }
510
511 static int load_module(void *mod)
512 {
513         __mod_desc = mod;
514         return ast_register_application(app, directory_exec, synopsis, descrip);
515 }
516
517 static const char *description(void)
518 {
519         return "Extension Directory";
520 }
521
522 static const char *key(void)
523 {
524         return ASTERISK_GPL_KEY;
525 }
526
527 STD_MOD1;