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