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