Create a new config file status, CONFIG_STATUS_FILEINVALID for differentiating
[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 /*** MODULEINFO
29         <depend>app_voicemail</depend>
30  ***/
31 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #include <ctype.h>
36
37 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
38 #include "asterisk/file.h"
39 #include "asterisk/pbx.h"
40 #include "asterisk/module.h"
41 #include "asterisk/say.h"
42 #include "asterisk/app.h"
43 #include "asterisk/utils.h"
44
45 static char *app = "Directory";
46
47 static char *synopsis = "Provide directory of voicemail extensions";
48 static char *descrip =
49 "  Directory(vm-context[,dial-context[,options]]): This application will present\n"
50 "the calling channel with a directory of extensions from which they can search\n"
51 "by name. The list of names and corresponding extensions is retrieved from the\n"
52 "voicemail configuration file, voicemail.conf.\n"
53 "  This application will immediately exit if one of the following DTMF digits are\n"
54 "received and the extension to jump to exists:\n"
55 "    0 - Jump to the 'o' extension, if it exists.\n"
56 "    * - Jump to the 'a' extension, if it exists.\n\n"
57 "  Parameters:\n"
58 "    vm-context   - This is the context within voicemail.conf to use for the\n"
59 "                   Directory.\n"
60 "    dial-context - This is the dialplan context to use when looking for an\n"
61 "                   extension that the user has selected, or when jumping to the\n"
62 "                   'o' or 'a' extension.\n\n"
63 "  Options:\n"
64 "    e           In addition to the name, also read the extension number to the\n"
65 "              caller before presenting dialing options.\n"
66 "    f[(<n>)]    Allow the caller to enter the first name of a user in the\n"
67 "              directory instead of using the last name.  If specified, the\n"
68 "              optional number argument will be used for the number of\n"
69 "              characters the user should enter.\n"
70 "    l[(<n>)]    Allow the caller to enter the last name of a user in the\n"
71 "              directory.  This is the default.  If specified, the\n"
72 "              optional number argument will be used for the number of\n"
73 "              characters the user should enter.\n"
74 "    b[(<n>)]    Allow the caller to enter either the first or the last name\n"
75 "              of a user in the directory.  If specified, the optional number\n"
76 "              argument will be used for the number of characters the user\n"
77 "              should enter.\n"
78 "    m           Instead of reading each name sequentially and asking for\n"
79 "              confirmation, create a menu of up to 8 names.\n"
80 "    p(<n>)      Pause for n milliseconds after the digits are typed.  This is\n"
81 "              helpful for people with cellphones, who are not holding the\n"
82 "              receiver to their ear while entering DTMF.\n"
83 "\n"
84 "    Only one of the f, l, or b options may be specified.  If more than one is\n"
85 "    specified, then Directory will act as if 'b' was specified.  The number\n"
86 "    of characters for the user to type defaults to 3.\n";
87
88 /* For simplicity, I'm keeping the format compatible with the voicemail config,
89    but i'm open to suggestions for isolating it */
90
91 #define VOICEMAIL_CONFIG "voicemail.conf"
92
93 enum {
94         OPT_LISTBYFIRSTNAME = (1 << 0),
95         OPT_SAYEXTENSION =    (1 << 1),
96         OPT_FROMVOICEMAIL =   (1 << 2),
97         OPT_SELECTFROMMENU =  (1 << 3),
98         OPT_LISTBYLASTNAME =  (1 << 4),
99         OPT_LISTBYEITHER =    OPT_LISTBYFIRSTNAME | OPT_LISTBYLASTNAME,
100         OPT_PAUSE =           (1 << 5),
101 } directory_option_flags;
102
103 enum {
104         OPT_ARG_FIRSTNAME =   0,
105         OPT_ARG_LASTNAME =    1,
106         OPT_ARG_EITHER =      2,
107         OPT_ARG_PAUSE =       3,
108         /* This *must* be the last value in this enum! */
109         OPT_ARG_ARRAY_SIZE =  4,
110 };
111
112 struct directory_item {
113         char exten[AST_MAX_EXTENSION + 1];
114         char name[AST_MAX_EXTENSION + 1];
115         char key[50]; /* Text to order items. Either lastname+firstname or firstname+lastname */
116
117         AST_LIST_ENTRY(directory_item) entry;
118 };
119
120 AST_APP_OPTIONS(directory_app_options, {
121         AST_APP_OPTION_ARG('f', OPT_LISTBYFIRSTNAME, OPT_ARG_FIRSTNAME),
122         AST_APP_OPTION_ARG('l', OPT_LISTBYLASTNAME, OPT_ARG_LASTNAME),
123         AST_APP_OPTION_ARG('b', OPT_LISTBYEITHER, OPT_ARG_EITHER),
124         AST_APP_OPTION_ARG('p', OPT_PAUSE, OPT_ARG_PAUSE),
125         AST_APP_OPTION('e', OPT_SAYEXTENSION),
126         AST_APP_OPTION('v', OPT_FROMVOICEMAIL),
127         AST_APP_OPTION('m', OPT_SELECTFROMMENU),
128 });
129
130 static int compare(const char *text, const char *template)
131 {
132         char digit;
133
134         while (*template) {
135                 digit = toupper(*text++);
136                 switch (digit) {
137                 case 0:
138                         return -1;
139                 case '1':
140                         digit = '1';
141                         break;
142                 case '2':
143                 case 'A':
144                 case 'B':
145                 case 'C':
146                         digit = '2';
147                         break;
148                 case '3':
149                 case 'D':
150                 case 'E':
151                 case 'F':
152                         digit = '3';
153                         break;
154                 case '4':
155                 case 'G':
156                 case 'H':
157                 case 'I':
158                         digit = '4';
159                         break;
160                 case '5':
161                 case 'J':
162                 case 'K':
163                 case 'L':
164                         digit = '5';
165                         break;
166                 case '6':
167                 case 'M':
168                 case 'N':
169                 case 'O':
170                         digit = '6';
171                         break;
172                 case '7':
173                 case 'P':
174                 case 'Q':
175                 case 'R':
176                 case 'S':
177                         digit = '7';
178                         break;
179                 case '8':
180                 case 'T':
181                 case 'U':
182                 case 'V':
183                         digit = '8';
184                         break;
185                 case '9':
186                 case 'W':
187                 case 'X':
188                 case 'Y':
189                 case 'Z':
190                         digit = '9';
191                         break;
192
193                 default:
194                         if (digit > ' ')
195                                 return -1;
196                         continue;
197                 }
198
199                 if (*template++ != digit)
200                         return -1;
201         }
202
203         return 0;
204 }
205
206 /* play name of mailbox owner.
207  * returns:  -1 for bad or missing extension
208  *           '1' for selected entry from directory
209  *           '*' for skipped entry from directory
210  */
211 static int play_mailbox_owner(struct ast_channel *chan, const char *context,
212         const char *ext, const char *name, struct ast_flags *flags)
213 {
214         int res = 0;
215         if ((res = ast_app_sayname(chan, ext, context)) >= 0) {
216                 ast_stopstream(chan);
217                 /* If Option 'e' was specified, also read the extension number with the name */
218                 if (ast_test_flag(flags, OPT_SAYEXTENSION)) {
219                         ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
220                         res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
221                 }
222         } else {
223                 res = ast_say_character_str(chan, S_OR(name, ext), AST_DIGIT_ANY, chan->language);
224                 if (!ast_strlen_zero(name) && ast_test_flag(flags, OPT_SAYEXTENSION)) {
225                         ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
226                         res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
227                 }
228         }
229
230         return res;
231 }
232
233 static int select_entry(struct ast_channel *chan, const char *context, const char *dialcontext, const struct directory_item *item, struct ast_flags *flags)
234 {
235         ast_debug(1, "Selecting '%s' - %s@%s\n", item->name, item->exten, dialcontext);
236
237         if (ast_test_flag(flags, OPT_FROMVOICEMAIL)) {
238                 /* We still want to set the exten though */
239                 ast_copy_string(chan->exten, item->exten, sizeof(chan->exten));
240         } else if (ast_goto_if_exists(chan, dialcontext, item->exten, 1)) {
241                 ast_log(LOG_WARNING,
242                         "Can't find extension '%s' in context '%s'.  "
243                         "Did you pass the wrong context to Directory?\n",
244                         item->exten, dialcontext);
245                 return -1;
246         }
247
248         return 0;
249 }
250
251 static int select_item_seq(struct ast_channel *chan, struct directory_item **items, int count, const char *context, const char *dialcontext, struct ast_flags *flags)
252 {
253         struct directory_item *item, **ptr;
254         int i, res, loop;
255
256         for (ptr = items, i = 0; i < count; i++, ptr++) {
257                 item = *ptr;
258
259                 for (loop = 3 ; loop > 0; loop--) {
260                         res = play_mailbox_owner(chan, context, item->exten, item->name, flags);
261
262                         if (!res)
263                                 res = ast_stream_and_wait(chan, "dir-instr", AST_DIGIT_ANY);
264                         if (!res)
265                                 res = ast_waitfordigit(chan, 3000);
266                         ast_stopstream(chan);
267         
268                         if (res == '1') { /* Name selected */
269                                 return select_entry(chan, context, dialcontext, item, flags) ? -1 : 1;
270                         } else if (res == '*') {
271                                 /* Skip to next match in list */
272                                 break;
273                         }
274
275                         if (res < 0)
276                                 return -1;
277
278                         res = 0;
279                 }
280         }
281
282         /* Nothing was selected */
283         return 0;
284 }
285
286 static int select_item_menu(struct ast_channel *chan, struct directory_item **items, int count, const char *context, const char *dialcontext, struct ast_flags *flags)
287 {
288         struct directory_item **block, *item;
289         int i, limit, res = 0;
290         char buf[9];
291
292         for (block = items; count; block += limit, count -= limit) {
293                 limit = count;
294                 if (limit > 8)
295                         limit = 8;
296
297                 for (i = 0; i < limit && !res; i++) {
298                         item = block[i];
299
300                         snprintf(buf, sizeof(buf), "digits/%d", i + 1);
301                         /* Press <num> for <name>, [ extension <ext> ] */
302                         res = ast_streamfile(chan, "dir-multi1", chan->language);
303                         if (!res)
304                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
305                         if (!res)
306                                 res = ast_streamfile(chan, buf, chan->language);
307                         if (!res)
308                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
309                         if (!res)
310                                 res = ast_streamfile(chan, "dir-multi2", chan->language);
311                         if (!res)
312                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
313                         if (!res)
314                                 res = play_mailbox_owner(chan, context, item->exten, item->name, flags);
315                         if (!res)
316                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
317                         if (!res)
318                                 res = ast_waitfordigit(chan, 800);
319                 }
320
321                 /* Press "9" for more names. */
322                 if (!res && count > limit) {
323                         res = ast_streamfile(chan, "dir-multi9", chan->language);
324                         if (!res)
325                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
326                 }
327
328                 if (!res) {
329                         res = ast_waitfordigit(chan, 3000);
330                 }
331
332                 if (res && res > '0' && res < '1' + limit) {
333                         return select_entry(chan, context, dialcontext, block[res - '1'], flags) ? -1 : 1;
334                 }
335
336                 if (res < 0)
337                         return -1;
338
339                 res = 0;
340         }
341
342         /* Nothing was selected */
343         return 0;
344 }
345
346 static struct ast_config *realtime_directory(char *context)
347 {
348         struct ast_config *cfg;
349         struct ast_config *rtdata;
350         struct ast_category *cat;
351         struct ast_variable *var;
352         char *mailbox;
353         const char *fullname;
354         const char *hidefromdir;
355         char tmp[100];
356         struct ast_flags config_flags = { 0 };
357
358         /* Load flat file config. */
359         cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
360
361         if (!cfg) {
362                 /* Loading config failed. */
363                 ast_log(LOG_WARNING, "Loading config failed.\n");
364                 return NULL;
365         } else if (cfg == CONFIG_STATUS_FILEINVALID) {
366                 ast_log(LOG_ERROR, "Config file %s is in an invalid format.  Aborting.\n", VOICEMAIL_CONFIG);
367                 return NULL;
368         }
369
370         /* Get realtime entries, categorized by their mailbox number
371            and present in the requested context */
372         rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, SENTINEL);
373
374         /* if there are no results, just return the entries from the config file */
375         if (!rtdata)
376                 return cfg;
377
378         /* Does the context exist within the config file? If not, make one */
379         cat = ast_category_get(cfg, context);
380         if (!cat) {
381                 cat = ast_category_new(context, "", 99999);
382                 if (!cat) {
383                         ast_log(LOG_WARNING, "Out of memory\n");
384                         ast_config_destroy(cfg);
385                         return NULL;
386                 }
387                 ast_category_append(cfg, cat);
388         }
389
390         mailbox = NULL;
391         while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) {
392                 fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
393                 if (ast_true((hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir")))) {
394                         /* Skip hidden */
395                         continue;
396                 }
397                 snprintf(tmp, sizeof(tmp), "no-password,%s", S_OR(fullname, ""));
398                 var = ast_variable_new(mailbox, tmp, "");
399                 if (var)
400                         ast_variable_append(cat, var);
401                 else
402                         ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
403         }
404         ast_config_destroy(rtdata);
405
406         return cfg;
407 }
408
409 static int check_match(struct directory_item **result, const char *item_fullname, const char *item_ext, const char *pattern_ext, int use_first_name)
410 {
411         struct directory_item *item;
412         const char *key = NULL;
413         int namelen;
414
415
416         /* Set key to last name or first name depending on search mode */
417         if (!use_first_name)
418                 key = strchr(item_fullname, ' ');
419
420         if (key)
421                 key++;
422         else
423                 key = item_fullname;
424
425         if (compare(key, pattern_ext))
426                 return 0;
427
428         /* Match */
429         item = ast_calloc(1, sizeof(*item));
430         if (!item)
431                 return -1;
432         ast_copy_string(item->name, item_fullname, sizeof(item->name));
433         ast_copy_string(item->exten, item_ext, sizeof(item->exten));
434
435         ast_copy_string(item->key, key, sizeof(item->key));
436         if (key != item_fullname) {
437                 /* Key is the last name. Append first name to key in order to sort Last,First */
438                 namelen = key - item_fullname - 1;
439                 if (namelen > sizeof(item->key) - strlen(item->key) - 1)
440                         namelen = sizeof(item->key) - strlen(item->key) - 1;
441                 strncat(item->key, item_fullname, namelen);
442         }
443
444         *result = item;
445         return 1;
446 }
447
448 typedef AST_LIST_HEAD_NOLOCK(, directory_item) itemlist;
449
450 static int search_directory(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, struct ast_flags flags, itemlist *alist)
451 {
452         struct ast_variable *v;
453         char buf[AST_MAX_EXTENSION + 1], *pos, *bufptr, *cat;
454         struct directory_item *item;
455         int res;
456
457         ast_debug(2, "Pattern: %s\n", ext);
458
459         for (v = ast_variable_browse(vmcfg, context); v; v = v->next) {
460
461                 /* Ignore hidden */
462                 if (strcasestr(v->value, "hidefromdir=yes"))
463                         continue;
464
465                 ast_copy_string(buf, v->value, sizeof(buf));
466                 bufptr = buf;
467
468                 /* password,Full Name,email,pager,options */
469                 strsep(&bufptr, ",");
470                 pos = strsep(&bufptr, ",");
471
472                 res = 0;
473                 if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
474                         res = check_match(&item, pos, v->name, ext, 0 /* use_first_name */);
475                 }
476                 if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
477                         res = check_match(&item, pos, v->name, ext, 1 /* use_first_name */);
478                 }
479
480                 if (!res)
481                         continue;
482                 else if (res < 0)
483                         return -1;
484
485                 AST_LIST_INSERT_TAIL(alist, item, entry);
486         }
487
488         if (ucfg) {
489                 for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) {
490                         const char *position;
491                         if (!strcasecmp(cat, "general"))
492                                 continue;
493                         if (!ast_true(ast_config_option(ucfg, cat, "hasdirectory")))
494                                 continue;
495
496                         /* Find all candidate extensions */
497                         position = ast_variable_retrieve(ucfg, cat, "fullname");
498                         if (!position)
499                                 continue;
500
501                         res = 0;
502                         if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
503                                 res = check_match(&item, position, cat, ext, 0 /* use_first_name */);
504                         }
505                         if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
506                                 res = check_match(&item, position, cat, ext, 1 /* use_first_name */);
507                         }
508
509                         if (!res)
510                                 continue;
511                         else if (res < 0)
512                                 return -1;
513
514                         AST_LIST_INSERT_TAIL(alist, item, entry);
515                 }
516         }
517         return 0;
518 }
519
520 static void sort_items(struct directory_item **sorted, int count)
521 {
522         int reordered, i;
523         struct directory_item **ptr, *tmp;
524
525         if (count < 2)
526                 return;
527
528         /* Bubble-sort items by the key */
529         do {
530                 reordered = 0;
531                 for (ptr = sorted, i = 0; i < count - 1; i++, ptr++) {
532                         if (strcasecmp(ptr[0]->key, ptr[1]->key) > 0) {
533                                 tmp = ptr[0];
534                                 ptr[0] = ptr[1];
535                                 ptr[1] = tmp;
536                                 reordered++;
537                         }
538                 }
539         } while (reordered);
540 }
541
542 static int goto_exten(struct ast_channel *chan, const char *dialcontext, char *ext)
543 {
544         if (!ast_goto_if_exists(chan, dialcontext, ext, 1) ||
545                 (!ast_strlen_zero(chan->macrocontext) &&
546                 !ast_goto_if_exists(chan, chan->macrocontext, ext, 1))) {
547                 return 0;
548         } else {
549                 ast_log(LOG_WARNING, "Can't find extension '%s' in current context.  "
550                         "Not Exiting the Directory!\n", ext);
551                 return -1;
552         }
553 }
554
555 static int do_directory(struct ast_channel *chan, struct ast_config *vmcfg, struct ast_config *ucfg, char *context, char *dialcontext, char digit, int digits, struct ast_flags *flags)
556 {
557         /* Read in the first three digits..  "digit" is the first digit, already read */
558         int res = 0;
559         itemlist alist = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
560         struct directory_item *item, **ptr, **sorted = NULL;
561         int count, i;
562         char ext[10] = "";
563
564         if (ast_strlen_zero(context)) {
565                 ast_log(LOG_WARNING,
566                         "Directory must be called with an argument "
567                         "(context in which to interpret extensions)\n");
568                 return -1;
569         }
570
571         if (digit == '0' && !goto_exten(chan, dialcontext, "o")) {
572                 return 0;
573         }
574
575         if (digit == '*' && !goto_exten(chan, dialcontext, "a")) {
576                 return 0;
577         }
578
579         ext[0] = digit;
580         if (ast_readstring(chan, ext + 1, digits - 1, 3000, 3000, "#") < 0)
581                 return -1;
582
583         res = search_directory(context, vmcfg, ucfg, ext, *flags, &alist);
584         if (res)
585                 goto exit;
586
587         /* Count items in the list */
588         count = 0;
589         AST_LIST_TRAVERSE(&alist, item, entry) {
590                 count++;
591         }
592
593         if (count < 1) {
594                 res = ast_streamfile(chan, "dir-nomatch", chan->language);
595                 goto exit;
596         }
597
598
599         /* Create plain array of pointers to items (for sorting) */
600         sorted = ast_calloc(count, sizeof(*sorted));
601
602         ptr = sorted;
603         AST_LIST_TRAVERSE(&alist, item, entry) {
604                 *ptr++ = item;
605         }
606
607         /* Sort items */
608         sort_items(sorted, count);
609
610         if (option_debug) {
611                 ast_debug(2, "Listing matching entries:\n");
612                 for (ptr = sorted, i = 0; i < count; i++, ptr++) {
613                         ast_log(LOG_DEBUG, "%s: %s\n", ptr[0]->exten, ptr[0]->name);
614                 }
615         }
616
617         if (ast_test_flag(flags, OPT_SELECTFROMMENU)) {
618                 /* Offer multiple entries at the same time */
619                 res = select_item_menu(chan, sorted, count, context, dialcontext, flags);
620         } else {
621                 /* Offer entries one by one */
622                 res = select_item_seq(chan, sorted, count, context, dialcontext, flags);
623         }
624
625         if (!res) {
626                 res = ast_streamfile(chan, "dir-nomore", chan->language);
627         }
628
629 exit:
630         if (sorted)
631                 ast_free(sorted);
632
633         while ((item = AST_LIST_REMOVE_HEAD(&alist, entry)))
634                 ast_free(item);
635
636         return res;
637 }
638
639 static int directory_exec(struct ast_channel *chan, void *data)
640 {
641         int res = 0, digit = 3;
642         struct ast_config *cfg, *ucfg;
643         const char *dirintro;
644         char *parse, *opts[OPT_ARG_ARRAY_SIZE];
645         struct ast_flags flags = { 0 };
646         struct ast_flags config_flags = { 0 };
647         enum { FIRST, LAST, BOTH } which = LAST;
648         char digits[9] = "digits/3";
649         AST_DECLARE_APP_ARGS(args,
650                 AST_APP_ARG(vmcontext);
651                 AST_APP_ARG(dialcontext);
652                 AST_APP_ARG(options);
653         );
654
655         if (ast_strlen_zero(data)) {
656                 ast_log(LOG_WARNING, "Directory requires an argument (context[,dialcontext])\n");
657                 return -1;
658         }
659
660         parse = ast_strdupa(data);
661
662         AST_STANDARD_APP_ARGS(args, parse);
663
664         if (args.options && ast_app_parse_options(directory_app_options, &flags, opts, args.options))
665                 return -1;
666
667         if (ast_strlen_zero(args.dialcontext))
668                 args.dialcontext = args.vmcontext;
669
670         cfg = realtime_directory(args.vmcontext);
671         if (!cfg) {
672                 ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
673                 return -1;
674         }
675
676         if ((ucfg = ast_config_load("users.conf", config_flags)) == CONFIG_STATUS_FILEINVALID) {
677                 ast_log(LOG_ERROR, "Config file users.conf is in an invalid format.  Aborting.\n");
678                 ucfg = NULL;
679         }
680
681         dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro");
682         if (ast_strlen_zero(dirintro))
683                 dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
684
685         if (ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) && ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
686                 if (!ast_strlen_zero(opts[OPT_ARG_EITHER])) {
687                         digit = atoi(opts[OPT_ARG_EITHER]);
688                 }
689                 which = BOTH;
690         } else if (ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
691                 if (!ast_strlen_zero(opts[OPT_ARG_FIRSTNAME])) {
692                         digit = atoi(opts[OPT_ARG_FIRSTNAME]);
693                 }
694                 which = FIRST;
695         } else {
696                 if (!ast_strlen_zero(opts[OPT_ARG_LASTNAME])) {
697                         digit = atoi(opts[OPT_ARG_LASTNAME]);
698                 }
699                 which = LAST;
700         }
701
702         /* If no options specified, search by last name */
703         if (!ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) && !ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
704                 ast_set_flag(&flags, OPT_LISTBYLASTNAME);
705                 which = LAST;
706         }
707
708         if (digit > 9) {
709                 digit = 9;
710         } else if (digit < 1) {
711                 digit = 3;
712         }
713         digits[7] = digit + '0';
714
715         if (chan->_state != AST_STATE_UP)
716                 res = ast_answer(chan);
717
718         for (;;) {
719                 if (!ast_strlen_zero(dirintro) && !res) {
720                         res = ast_stream_and_wait(chan, dirintro, AST_DIGIT_ANY);
721                 } else if (!res) {
722                         /* Stop playing sounds as soon as we have a digit. */
723                         res = ast_stream_and_wait(chan, "dir-welcome", AST_DIGIT_ANY);
724                         if (!res) {
725                                 res = ast_stream_and_wait(chan, "dir-pls-enter", AST_DIGIT_ANY);
726                         }
727                         if (!res) {
728                                 res = ast_stream_and_wait(chan, digits, AST_DIGIT_ANY);
729                         }
730                         if (!res) {
731                                 res = ast_stream_and_wait(chan, 
732                                         which == FIRST ? "dir-first" :
733                                         which == LAST ? "dir-last" :
734                                         "dir-firstlast", AST_DIGIT_ANY);
735                         }
736                         if (!res) {
737                                 ast_stream_and_wait(chan, "dir-usingkeypad", AST_DIGIT_ANY);
738                         }
739                 }
740                 ast_stopstream(chan);
741                 if (!res)
742                         res = ast_waitfordigit(chan, 5000);
743
744                 if (res <= 0)
745                         break;
746
747                 res = do_directory(chan, cfg, ucfg, args.vmcontext, args.dialcontext, res, digit, &flags);
748                 if (res)
749                         break;
750
751                 res = ast_waitstream(chan, AST_DIGIT_ANY);
752                 ast_stopstream(chan);
753
754                 if (res)
755                         break;
756         }
757
758         if (ucfg)
759                 ast_config_destroy(ucfg);
760         ast_config_destroy(cfg);
761
762         return res < 0 ? -1 : 0;
763 }
764
765 static int unload_module(void)
766 {
767         int res;
768         res = ast_unregister_application(app);
769         return res;
770 }
771
772 static int load_module(void)
773 {
774         return ast_register_application(app, directory_exec, synopsis, descrip);
775 }
776
777 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Directory");