Merged revisions 263769 via svnmerge from
[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 static int goto_exten(struct ast_channel *chan, const char *dialcontext, char *ext)
248 {
249         if (!ast_goto_if_exists(chan, dialcontext, ext, 1) ||
250                 (!ast_strlen_zero(chan->macrocontext) &&
251                 !ast_goto_if_exists(chan, chan->macrocontext, ext, 1))) {
252                 return 0;
253         } else {
254                 ast_log(LOG_WARNING, "Can't find extension '%s' in current context.  "
255                         "Not Exiting the Directory!\n", ext);
256                 return -1;
257         }
258 }
259
260 /* play name of mailbox owner.
261  * returns:  -1 for bad or missing extension
262  *           '1' for selected entry from directory
263  *           '*' for skipped entry from directory
264  */
265 static int play_mailbox_owner(struct ast_channel *chan, const char *context,
266         const char *ext, const char *name, struct ast_flags *flags)
267 {
268         int res = 0;
269         if ((res = ast_app_sayname(chan, ext, context)) >= 0) {
270                 ast_stopstream(chan);
271                 /* If Option 'e' was specified, also read the extension number with the name */
272                 if (ast_test_flag(flags, OPT_SAYEXTENSION)) {
273                         ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
274                         res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
275                 }
276         } else {
277                 res = ast_say_character_str(chan, S_OR(name, ext), AST_DIGIT_ANY, chan->language);
278                 if (!ast_strlen_zero(name) && ast_test_flag(flags, OPT_SAYEXTENSION)) {
279                         ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
280                         res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
281                 }
282         }
283
284         return res;
285 }
286
287 static int select_entry(struct ast_channel *chan, const char *dialcontext, const struct directory_item *item, struct ast_flags *flags)
288 {
289         ast_debug(1, "Selecting '%s' - %s@%s\n", item->name, item->exten, S_OR(dialcontext, item->context));
290
291         if (ast_test_flag(flags, OPT_FROMVOICEMAIL)) {
292                 /* We still want to set the exten though */
293                 ast_copy_string(chan->exten, item->exten, sizeof(chan->exten));
294         } else if (ast_goto_if_exists(chan, S_OR(dialcontext, item->context), item->exten, 1)) {
295                 ast_log(LOG_WARNING,
296                         "Can't find extension '%s' in context '%s'.  "
297                         "Did you pass the wrong context to Directory?\n",
298                         item->exten, S_OR(dialcontext, item->context));
299                 return -1;
300         }
301
302         return 0;
303 }
304
305 static int select_item_pause(struct ast_channel *chan, struct ast_flags *flags, char *opts[])
306 {
307         int res = 0, opt_pause = 0;
308
309         if (ast_test_flag(flags, OPT_PAUSE) && !ast_strlen_zero(opts[OPT_ARG_PAUSE])) {
310                 opt_pause = atoi(opts[OPT_ARG_PAUSE]);
311                 if (opt_pause > 3000) {
312                         opt_pause = 3000;
313                 }
314                 res = ast_waitfordigit(chan, opt_pause);
315         }
316         return res;
317 }
318
319 static int select_item_seq(struct ast_channel *chan, struct directory_item **items, int count, const char *dialcontext, struct ast_flags *flags, char *opts[])
320 {
321         struct directory_item *item, **ptr;
322         int i, res, loop;
323
324         /* option p(n): cellphone pause option */
325         /* allow early press of selection key */
326         res = select_item_pause(chan, flags, opts);
327
328         for (ptr = items, i = 0; i < count; i++, ptr++) {
329                 item = *ptr;
330
331                 for (loop = 3 ; loop > 0; loop--) {
332                         if (!res)
333                                 res = play_mailbox_owner(chan, item->context, item->exten, item->name, flags);
334                         if (!res)
335                                 res = ast_stream_and_wait(chan, "dir-instr", AST_DIGIT_ANY);
336                         if (!res)
337                                 res = ast_waitfordigit(chan, 3000);
338                         ast_stopstream(chan);
339         
340                         if (res == '0') { /* operator selected */
341                                 goto_exten(chan, dialcontext, "o");
342                                 return '0';
343                         } else if (res == '1') { /* Name selected */
344                                 return select_entry(chan, dialcontext, item, flags) ? -1 : 1;
345                         } else if (res == '*') {
346                                 /* Skip to next match in list */
347                                 break;
348                         } else if (res == '#') {
349                                 /* Exit reading, continue in dialplan */
350                                 return res;
351                         }
352
353                         if (res < 0)
354                                 return -1;
355
356                         res = 0;
357                 }
358                 res = 0;
359         }
360
361         /* Nothing was selected */
362         return 0;
363 }
364
365 static int select_item_menu(struct ast_channel *chan, struct directory_item **items, int count, const char *dialcontext, struct ast_flags *flags, char *opts[])
366 {
367         struct directory_item **block, *item;
368         int i, limit, res = 0;
369         char buf[9];
370
371         /* option p(n): cellphone pause option */
372         select_item_pause(chan, flags, opts);
373
374         for (block = items; count; block += limit, count -= limit) {
375                 limit = count;
376                 if (limit > 8)
377                         limit = 8;
378
379                 for (i = 0; i < limit && !res; i++) {
380                         item = block[i];
381
382                         snprintf(buf, sizeof(buf), "digits/%d", i + 1);
383                         /* Press <num> for <name>, [ extension <ext> ] */
384                         res = ast_streamfile(chan, "dir-multi1", chan->language);
385                         if (!res)
386                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
387                         if (!res)
388                                 res = ast_streamfile(chan, buf, chan->language);
389                         if (!res)
390                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
391                         if (!res)
392                                 res = ast_streamfile(chan, "dir-multi2", chan->language);
393                         if (!res)
394                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
395                         if (!res)
396                                 res = play_mailbox_owner(chan, item->context, item->exten, item->name, flags);
397                         if (!res)
398                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
399                         if (!res)
400                                 res = ast_waitfordigit(chan, 800);
401                 }
402
403                 /* Press "9" for more names. */
404                 if (!res && count > limit) {
405                         res = ast_streamfile(chan, "dir-multi9", chan->language);
406                         if (!res)
407                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
408                 }
409
410                 if (!res) {
411                         res = ast_waitfordigit(chan, 3000);
412                 }
413
414                 if (res && res > '0' && res < '1' + limit) {
415                         return select_entry(chan, dialcontext, block[res - '1'], flags) ? -1 : 1;
416                 }
417
418                 if (res < 0)
419                         return -1;
420
421                 res = 0;
422         }
423
424         /* Nothing was selected */
425         return 0;
426 }
427
428 static struct ast_config *realtime_directory(char *context)
429 {
430         struct ast_config *cfg;
431         struct ast_config *rtdata;
432         struct ast_category *cat;
433         struct ast_variable *var;
434         char *mailbox;
435         const char *fullname;
436         const char *hidefromdir, *searchcontexts = NULL;
437         char tmp[100];
438         struct ast_flags config_flags = { 0 };
439
440         /* Load flat file config. */
441         cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
442
443         if (!cfg) {
444                 /* Loading config failed. */
445                 ast_log(LOG_WARNING, "Loading config failed.\n");
446                 return NULL;
447         } else if (cfg == CONFIG_STATUS_FILEINVALID) {
448                 ast_log(LOG_ERROR, "Config file %s is in an invalid format.  Aborting.\n", VOICEMAIL_CONFIG);
449                 return NULL;
450         }
451
452         /* Get realtime entries, categorized by their mailbox number
453            and present in the requested context */
454         if (ast_strlen_zero(context) && (searchcontexts = ast_variable_retrieve(cfg, "general", "searchcontexts"))) {
455                 if (ast_true(searchcontexts)) {
456                         rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", SENTINEL);
457                         context = NULL;
458                 } else {
459                         rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", "default", SENTINEL);
460                         context = "default";
461                 }
462         } else {
463                 rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, SENTINEL);
464         }
465
466         /* if there are no results, just return the entries from the config file */
467         if (!rtdata) {
468                 return cfg;
469         }
470
471         mailbox = NULL;
472         while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) {
473                 const char *context = ast_variable_retrieve(rtdata, mailbox, "context");
474
475                 fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
476                 if (ast_true((hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir")))) {
477                         /* Skip hidden */
478                         continue;
479                 }
480                 snprintf(tmp, sizeof(tmp), "no-password,%s", S_OR(fullname, ""));
481
482                 /* Does the context exist within the config file? If not, make one */
483                 if (!(cat = ast_category_get(cfg, context))) {
484                         if (!(cat = ast_category_new(context, "", 99999))) {
485                                 ast_log(LOG_WARNING, "Out of memory\n");
486                                 ast_config_destroy(cfg);
487                                 if (rtdata) {
488                                         ast_config_destroy(rtdata);
489                                 }
490                                 return NULL;
491                         }
492                         ast_category_append(cfg, cat);
493                 }
494
495                 if ((var = ast_variable_new(mailbox, tmp, ""))) {
496                         ast_variable_append(cat, var);
497                 } else {
498                         ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
499                 }
500         }
501         ast_config_destroy(rtdata);
502
503         return cfg;
504 }
505
506 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)
507 {
508         struct directory_item *item;
509         const char *key = NULL;
510         int namelen;
511
512         if (ast_strlen_zero(item_fullname)) {
513                 return 0;
514         }
515
516         /* Set key to last name or first name depending on search mode */
517         if (!use_first_name)
518                 key = strchr(item_fullname, ' ');
519
520         if (key)
521                 key++;
522         else
523                 key = item_fullname;
524
525         if (compare(key, pattern_ext))
526                 return 0;
527
528         ast_debug(1, "Found match %s@%s\n", item_ext, item_context);
529
530         /* Match */
531         item = ast_calloc(1, sizeof(*item));
532         if (!item)
533                 return -1;
534         ast_copy_string(item->context, item_context, sizeof(item->context));
535         ast_copy_string(item->name, item_fullname, sizeof(item->name));
536         ast_copy_string(item->exten, item_ext, sizeof(item->exten));
537
538         ast_copy_string(item->key, key, sizeof(item->key));
539         if (key != item_fullname) {
540                 /* Key is the last name. Append first name to key in order to sort Last,First */
541                 namelen = key - item_fullname - 1;
542                 if (namelen > sizeof(item->key) - strlen(item->key) - 1)
543                         namelen = sizeof(item->key) - strlen(item->key) - 1;
544                 strncat(item->key, item_fullname, namelen);
545         }
546
547         *result = item;
548         return 1;
549 }
550
551 typedef AST_LIST_HEAD_NOLOCK(, directory_item) itemlist;
552
553 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)
554 {
555         struct ast_variable *v;
556         char buf[AST_MAX_EXTENSION + 1], *pos, *bufptr, *cat;
557         struct directory_item *item;
558         int res;
559
560         ast_debug(2, "Pattern: %s\n", ext);
561
562         for (v = ast_variable_browse(vmcfg, context); v; v = v->next) {
563
564                 /* Ignore hidden */
565                 if (strcasestr(v->value, "hidefromdir=yes"))
566                         continue;
567
568                 ast_copy_string(buf, v->value, sizeof(buf));
569                 bufptr = buf;
570
571                 /* password,Full Name,email,pager,options */
572                 strsep(&bufptr, ",");
573                 pos = strsep(&bufptr, ",");
574
575                 /* No name to compare against */
576                 if (ast_strlen_zero(pos)) {
577                         continue;
578                 }
579
580                 res = 0;
581                 if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
582                         res = check_match(&item, context, pos, v->name, ext, 0 /* use_first_name */);
583                 }
584                 if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
585                         res = check_match(&item, context, pos, v->name, ext, 1 /* use_first_name */);
586                 }
587
588                 if (!res)
589                         continue;
590                 else if (res < 0)
591                         return -1;
592
593                 AST_LIST_INSERT_TAIL(alist, item, entry);
594         }
595
596         if (ucfg) {
597                 for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) {
598                         const char *position;
599                         if (!strcasecmp(cat, "general"))
600                                 continue;
601                         if (!ast_true(ast_config_option(ucfg, cat, "hasdirectory")))
602                                 continue;
603
604                         /* Find all candidate extensions */
605                         position = ast_variable_retrieve(ucfg, cat, "fullname");
606                         if (!position)
607                                 continue;
608
609                         res = 0;
610                         if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
611                                 res = check_match(&item, context, position, cat, ext, 0 /* use_first_name */);
612                         }
613                         if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
614                                 res = check_match(&item, context, position, cat, ext, 1 /* use_first_name */);
615                         }
616
617                         if (!res)
618                                 continue;
619                         else if (res < 0)
620                                 return -1;
621
622                         AST_LIST_INSERT_TAIL(alist, item, entry);
623                 }
624         }
625         return 0;
626 }
627
628 static int search_directory(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, struct ast_flags flags, itemlist *alist)
629 {
630         const char *searchcontexts = ast_variable_retrieve(vmcfg, "general", "searchcontexts");
631         if (ast_strlen_zero(context)) {
632                 if (!ast_strlen_zero(searchcontexts) && ast_true(searchcontexts)) {
633                         /* Browse each context for a match */
634                         int res;
635                         const char *catg;
636                         for (catg = ast_category_browse(vmcfg, NULL); catg; catg = ast_category_browse(vmcfg, catg)) {
637                                 if (!strcmp(catg, "general") || !strcmp(catg, "zonemessages")) {
638                                         continue;
639                                 }
640
641                                 if ((res = search_directory_sub(catg, vmcfg, ucfg, ext, flags, alist))) {
642                                         return res;
643                                 }
644                         }
645                         return 0;
646                 } else {
647                         ast_debug(1, "Searching by category default\n");
648                         return search_directory_sub("default", vmcfg, ucfg, ext, flags, alist);
649                 }
650         } else {
651                 /* Browse only the listed context for a match */
652                 ast_debug(1, "Searching by category %s\n", context);
653                 return search_directory_sub(context, vmcfg, ucfg, ext, flags, alist);
654         }
655 }
656
657 static void sort_items(struct directory_item **sorted, int count)
658 {
659         int reordered, i;
660         struct directory_item **ptr, *tmp;
661
662         if (count < 2)
663                 return;
664
665         /* Bubble-sort items by the key */
666         do {
667                 reordered = 0;
668                 for (ptr = sorted, i = 0; i < count - 1; i++, ptr++) {
669                         if (strcasecmp(ptr[0]->key, ptr[1]->key) > 0) {
670                                 tmp = ptr[0];
671                                 ptr[0] = ptr[1];
672                                 ptr[1] = tmp;
673                                 reordered++;
674                         }
675                 }
676         } while (reordered);
677 }
678
679 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[])
680 {
681         /* Read in the first three digits..  "digit" is the first digit, already read */
682         int res = 0;
683         itemlist alist = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
684         struct directory_item *item, **ptr, **sorted = NULL;
685         int count, i;
686         char ext[10] = "";
687
688         if (digit == '0' && !goto_exten(chan, S_OR(dialcontext, "default"), "o")) {
689                 return digit;
690         }
691
692         if (digit == '*' && !goto_exten(chan, S_OR(dialcontext, "default"), "a")) {
693                 return digit;
694         }
695
696         ext[0] = digit;
697         if (ast_readstring(chan, ext + 1, digits - 1, 3000, 3000, "#") < 0)
698                 return -1;
699
700         res = search_directory(context, vmcfg, ucfg, ext, *flags, &alist);
701         if (res)
702                 goto exit;
703
704         /* Count items in the list */
705         count = 0;
706         AST_LIST_TRAVERSE(&alist, item, entry) {
707                 count++;
708         }
709
710         if (count < 1) {
711                 res = ast_streamfile(chan, "dir-nomatch", chan->language);
712                 goto exit;
713         }
714
715
716         /* Create plain array of pointers to items (for sorting) */
717         sorted = ast_calloc(count, sizeof(*sorted));
718
719         ptr = sorted;
720         AST_LIST_TRAVERSE(&alist, item, entry) {
721                 *ptr++ = item;
722         }
723
724         /* Sort items */
725         sort_items(sorted, count);
726
727         if (option_debug) {
728                 ast_debug(2, "Listing matching entries:\n");
729                 for (ptr = sorted, i = 0; i < count; i++, ptr++) {
730                         ast_debug(2, "%s: %s\n", ptr[0]->exten, ptr[0]->name);
731                 }
732         }
733
734         if (ast_test_flag(flags, OPT_SELECTFROMMENU)) {
735                 /* Offer multiple entries at the same time */
736                 res = select_item_menu(chan, sorted, count, dialcontext, flags, opts);
737         } else {
738                 /* Offer entries one by one */
739                 res = select_item_seq(chan, sorted, count, dialcontext, flags, opts);
740         }
741
742         if (!res) {
743                 res = ast_streamfile(chan, "dir-nomore", chan->language);
744         }
745
746 exit:
747         if (sorted)
748                 ast_free(sorted);
749
750         while ((item = AST_LIST_REMOVE_HEAD(&alist, entry)))
751                 ast_free(item);
752
753         return res;
754 }
755
756 static int directory_exec(struct ast_channel *chan, const char *data)
757 {
758         int res = 0, digit = 3;
759         struct ast_config *cfg, *ucfg;
760         const char *dirintro;
761         char *parse, *opts[OPT_ARG_ARRAY_SIZE] = { 0, };
762         struct ast_flags flags = { 0 };
763         struct ast_flags config_flags = { 0 };
764         enum { FIRST, LAST, BOTH } which = LAST;
765         char digits[9] = "digits/3";
766         AST_DECLARE_APP_ARGS(args,
767                 AST_APP_ARG(vmcontext);
768                 AST_APP_ARG(dialcontext);
769                 AST_APP_ARG(options);
770         );
771
772         parse = ast_strdupa(data);
773
774         AST_STANDARD_APP_ARGS(args, parse);
775
776         if (args.options && ast_app_parse_options(directory_app_options, &flags, opts, args.options))
777                 return -1;
778
779         if (!(cfg = realtime_directory(args.vmcontext))) {
780                 ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
781                 return -1;
782         }
783
784         if ((ucfg = ast_config_load("users.conf", config_flags)) == CONFIG_STATUS_FILEINVALID) {
785                 ast_log(LOG_ERROR, "Config file users.conf is in an invalid format.  Aborting.\n");
786                 ucfg = NULL;
787         }
788
789         dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro");
790         if (ast_strlen_zero(dirintro))
791                 dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
792         /* the above prompts probably should be modified to include 0 for dialing operator
793            and # for exiting (continues in dialplan) */
794
795         if (ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) && ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
796                 if (!ast_strlen_zero(opts[OPT_ARG_EITHER])) {
797                         digit = atoi(opts[OPT_ARG_EITHER]);
798                 }
799                 which = BOTH;
800         } else if (ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
801                 if (!ast_strlen_zero(opts[OPT_ARG_FIRSTNAME])) {
802                         digit = atoi(opts[OPT_ARG_FIRSTNAME]);
803                 }
804                 which = FIRST;
805         } else {
806                 if (!ast_strlen_zero(opts[OPT_ARG_LASTNAME])) {
807                         digit = atoi(opts[OPT_ARG_LASTNAME]);
808                 }
809                 which = LAST;
810         }
811
812         /* If no options specified, search by last name */
813         if (!ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) && !ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
814                 ast_set_flag(&flags, OPT_LISTBYLASTNAME);
815                 which = LAST;
816         }
817
818         if (digit > 9) {
819                 digit = 9;
820         } else if (digit < 1) {
821                 digit = 3;
822         }
823         digits[7] = digit + '0';
824
825         if (chan->_state != AST_STATE_UP) {
826                 if (!ast_test_flag(&flags, OPT_NOANSWER)) {
827                         /* Otherwise answer unless we're supposed to read while on-hook */
828                         res = ast_answer(chan);
829                 }
830         }
831         for (;;) {
832                 if (!ast_strlen_zero(dirintro) && !res) {
833                         res = ast_stream_and_wait(chan, dirintro, AST_DIGIT_ANY);
834                 } else if (!res) {
835                         /* Stop playing sounds as soon as we have a digit. */
836                         res = ast_stream_and_wait(chan, "dir-welcome", AST_DIGIT_ANY);
837                         if (!res) {
838                                 res = ast_stream_and_wait(chan, "dir-pls-enter", AST_DIGIT_ANY);
839                         }
840                         if (!res) {
841                                 res = ast_stream_and_wait(chan, digits, AST_DIGIT_ANY);
842                         }
843                         if (!res) {
844                                 res = ast_stream_and_wait(chan, 
845                                         which == FIRST ? "dir-first" :
846                                         which == LAST ? "dir-last" :
847                                         "dir-firstlast", AST_DIGIT_ANY);
848                         }
849                         if (!res) {
850                                 res = ast_stream_and_wait(chan, "dir-usingkeypad", AST_DIGIT_ANY);
851                         }
852                 }
853                 ast_stopstream(chan);
854                 if (!res)
855                         res = ast_waitfordigit(chan, 5000);
856
857                 if (res <= 0)
858                         break;
859
860                 res = do_directory(chan, cfg, ucfg, args.vmcontext, args.dialcontext, res, digit, &flags, opts);
861                 if (res)
862                         break;
863
864                 res = ast_waitstream(chan, AST_DIGIT_ANY);
865                 ast_stopstream(chan);
866
867                 if (res)
868                         break;
869         }
870
871         if (ucfg)
872                 ast_config_destroy(ucfg);
873         ast_config_destroy(cfg);
874
875         return res < 0 ? -1 : 0;
876 }
877
878 static int unload_module(void)
879 {
880         int res;
881         res = ast_unregister_application(app);
882         return res;
883 }
884
885 static int load_module(void)
886 {
887         return ast_register_application_xml(app, directory_exec);
888 }
889
890 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Directory");