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