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