app_queue: Added initialization for "context" parameter
[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_REGISTER_FILE()
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
522                 /* password,Full Name,email,pager,options */
523                 ast_str_set(&tmp, 0, "no-password,%s,,,", S_OR(fullname, ""));
524                 if (ast_variable_retrieve(rtdata, mailbox, "alias")) {
525                         for (alias = ast_variable_browse(rtdata, mailbox); alias; alias = alias->next) {
526                                 if (!strcasecmp(alias->name, "alias")) {
527                                         ast_str_append(&tmp, 0, "|alias=%s", alias->value);
528                                 }
529                         }
530                 }
531
532                 /* Does the context exist within the config file? If not, make one */
533                 if (!(cat = ast_category_get(cfg, ctx, NULL))) {
534                         if (!(cat = ast_category_new(ctx, "", 99999))) {
535                                 ast_log(LOG_WARNING, "Out of memory\n");
536                                 ast_config_destroy(cfg);
537                                 if (rtdata) {
538                                         ast_config_destroy(rtdata);
539                                 }
540                                 return NULL;
541                         }
542                         ast_category_append(cfg, cat);
543                 }
544
545                 if ((var = ast_variable_new(mailbox, ast_str_buffer(tmp), ""))) {
546                         ast_variable_append(cat, var);
547                 } else {
548                         ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
549                 }
550         }
551         ast_config_destroy(rtdata);
552
553         return cfg;
554 }
555
556 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)
557 {
558         struct directory_item *item;
559         const char *key = NULL;
560         int namelen;
561
562         if (ast_strlen_zero(item_fullname)) {
563                 return 0;
564         }
565
566         /* Set key to last name or first name depending on search mode */
567         if (!use_first_name)
568                 key = strchr(item_fullname, ' ');
569
570         if (key)
571                 key++;
572         else
573                 key = item_fullname;
574
575         if (compare(key, pattern_ext))
576                 return 0;
577
578         ast_debug(1, "Found match %s@%s\n", item_ext, item_context);
579
580         /* Match */
581         item = ast_calloc(1, sizeof(*item));
582         if (!item)
583                 return -1;
584         ast_copy_string(item->context, item_context, sizeof(item->context));
585         ast_copy_string(item->name, item_fullname, sizeof(item->name));
586         ast_copy_string(item->exten, item_ext, sizeof(item->exten));
587
588         ast_copy_string(item->key, key, sizeof(item->key));
589         if (key != item_fullname) {
590                 /* Key is the last name. Append first name to key in order to sort Last,First */
591                 namelen = key - item_fullname - 1;
592                 if (namelen > sizeof(item->key) - strlen(item->key) - 1)
593                         namelen = sizeof(item->key) - strlen(item->key) - 1;
594                 strncat(item->key, item_fullname, namelen);
595         }
596
597         *result = item;
598         return 1;
599 }
600
601 typedef AST_LIST_HEAD_NOLOCK(, directory_item) itemlist;
602
603 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)
604 {
605         struct ast_variable *v;
606         struct ast_str *buf = ast_str_thread_get(&commonbuf, 100);
607         char *name;
608         char *options;
609         char *alias;
610         char *cat;
611         struct directory_item *item;
612         int res;
613
614         if (!buf) {
615                 return -1;
616         }
617
618         ast_debug(2, "Pattern: %s\n", ext);
619
620         for (v = ast_variable_browse(vmcfg, context); v; v = v->next) {
621                 ast_str_set(&buf, 0, "%s", v->value);
622                 options = ast_str_buffer(buf);
623
624                 /* password,Full Name,email,pager,options */
625                 strsep(&options, ",");          /* Skip password */
626                 name = strsep(&options, ",");   /* Save full name */
627                 strsep(&options, ",");          /* Skip email */
628                 strsep(&options, ",");          /* Skip pager */
629                 /* options is now the options field if it exists. */
630
631                 if (options && strcasestr(options, "hidefromdir=yes")) {
632                         /* Ignore hidden */
633                         continue;
634                 }
635                 if (ast_strlen_zero(name)) {
636                         /* No name to compare against */
637                         continue;
638                 }
639
640                 res = 0;
641                 if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
642                         res = check_match(&item, context, name, v->name, ext, 0 /* use_first_name */);
643                 }
644                 if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
645                         res = check_match(&item, context, name, v->name, ext, 1 /* use_first_name */);
646                 }
647                 if (!res && ast_test_flag(&flags, OPT_ALIAS)
648                         && options && (alias = strcasestr(options, "alias="))) {
649                         char *a;
650
651                         ast_debug(1, "Found alias: %s\n", alias);
652                         while ((a = strsep(&alias, "|"))) {
653                                 if (!strncasecmp(a, "alias=", 6)) {
654                                         if ((res = check_match(&item, context, a + 6, v->name, ext, 1))) {
655                                                 break;
656                                         }
657                                 }
658                         }
659                 }
660
661                 if (!res) {
662                         continue;
663                 } else if (res < 0) {
664                         return -1;
665                 }
666
667                 AST_LIST_INSERT_TAIL(alist, item, entry);
668         }
669
670         if (ucfg) {
671                 for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) {
672                         const char *position;
673
674                         if (!strcasecmp(cat, "general")) {
675                                 continue;
676                         }
677                         if (!ast_true(ast_config_option(ucfg, cat, "hasdirectory"))) {
678                                 continue;
679                         }
680
681                         /* Find all candidate extensions */
682                         if (!(position = ast_variable_retrieve(ucfg, cat, "fullname"))) {
683                                 continue;
684                         }
685
686                         res = 0;
687                         if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
688                                 res = check_match(&item, context, position, cat, ext, 0 /* use_first_name */);
689                         }
690                         if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
691                                 res = check_match(&item, context, position, cat, ext, 1 /* use_first_name */);
692                         }
693                         if (!res && ast_test_flag(&flags, OPT_ALIAS)) {
694                                 for (v = ast_variable_browse(ucfg, cat); v; v = v->next) {
695                                         if (!strcasecmp(v->name, "alias")
696                                                 && (res = check_match(&item, context, v->value, cat, ext, 1))) {
697                                                 break;
698                                         }
699                                 }
700                         }
701
702                         if (!res) {
703                                 continue;
704                         } else if (res < 0) {
705                                 return -1;
706                         }
707
708                         AST_LIST_INSERT_TAIL(alist, item, entry);
709                 }
710         }
711         return 0;
712 }
713
714 static int search_directory(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, struct ast_flags flags, itemlist *alist)
715 {
716         const char *searchcontexts = ast_variable_retrieve(vmcfg, "general", "searchcontexts");
717         if (ast_strlen_zero(context)) {
718                 if (!ast_strlen_zero(searchcontexts) && ast_true(searchcontexts)) {
719                         /* Browse each context for a match */
720                         int res;
721                         const char *catg;
722                         for (catg = ast_category_browse(vmcfg, NULL); catg; catg = ast_category_browse(vmcfg, catg)) {
723                                 if (!strcmp(catg, "general") || !strcmp(catg, "zonemessages")) {
724                                         continue;
725                                 }
726
727                                 if ((res = search_directory_sub(catg, vmcfg, ucfg, ext, flags, alist))) {
728                                         return res;
729                                 }
730                         }
731                         return 0;
732                 } else {
733                         ast_debug(1, "Searching by category default\n");
734                         return search_directory_sub("default", vmcfg, ucfg, ext, flags, alist);
735                 }
736         } else {
737                 /* Browse only the listed context for a match */
738                 ast_debug(1, "Searching by category %s\n", context);
739                 return search_directory_sub(context, vmcfg, ucfg, ext, flags, alist);
740         }
741 }
742
743 static void sort_items(struct directory_item **sorted, int count)
744 {
745         int reordered, i;
746         struct directory_item **ptr, *tmp;
747
748         if (count < 2)
749                 return;
750
751         /* Bubble-sort items by the key */
752         do {
753                 reordered = 0;
754                 for (ptr = sorted, i = 0; i < count - 1; i++, ptr++) {
755                         if (strcasecmp(ptr[0]->key, ptr[1]->key) > 0) {
756                                 tmp = ptr[0];
757                                 ptr[0] = ptr[1];
758                                 ptr[1] = tmp;
759                                 reordered++;
760                         }
761                 }
762         } while (reordered);
763 }
764
765 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[])
766 {
767         /* Read in the first three digits..  "digit" is the first digit, already read */
768         int res = 0;
769         itemlist alist = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
770         struct directory_item *item, **ptr, **sorted = NULL;
771         int count, i;
772         char ext[10] = "";
773
774         if (digit == '0' && !goto_exten(chan, dialcontext, "o")) {
775                 pbx_builtin_setvar_helper(chan, "DIRECTORY_RESULT", "OPERATOR");
776                 return digit;
777         }
778
779         if (digit == '*' && !goto_exten(chan, dialcontext, "a")) {
780                 pbx_builtin_setvar_helper(chan, "DIRECTORY_RESULT", "ASSISTANT");
781                 return digit;
782         }
783
784         ext[0] = digit;
785         if (ast_readstring(chan, ext + 1, digits - 1, 3000, 3000, "#") < 0)
786                 return -1;
787
788         res = search_directory(context, vmcfg, ucfg, ext, *flags, &alist);
789         if (res)
790                 goto exit;
791
792         /* Count items in the list */
793         count = 0;
794         AST_LIST_TRAVERSE(&alist, item, entry) {
795                 count++;
796         }
797
798         if (count < 1) {
799                 res = ast_streamfile(chan, "dir-nomatch", ast_channel_language(chan));
800                 goto exit;
801         }
802
803
804         /* Create plain array of pointers to items (for sorting) */
805         sorted = ast_calloc(count, sizeof(*sorted));
806
807         ptr = sorted;
808         AST_LIST_TRAVERSE(&alist, item, entry) {
809                 *ptr++ = item;
810         }
811
812         /* Sort items */
813         sort_items(sorted, count);
814
815         if (option_debug) {
816                 ast_debug(2, "Listing matching entries:\n");
817                 for (ptr = sorted, i = 0; i < count; i++, ptr++) {
818                         ast_debug(2, "%s: %s\n", ptr[0]->exten, ptr[0]->name);
819                 }
820         }
821
822         if (ast_test_flag(flags, OPT_SELECTFROMMENU)) {
823                 /* Offer multiple entries at the same time */
824                 res = select_item_menu(chan, sorted, count, dialcontext, flags, opts);
825         } else {
826                 /* Offer entries one by one */
827                 res = select_item_seq(chan, sorted, count, dialcontext, flags, opts);
828         }
829
830         if (!res) {
831                 res = ast_streamfile(chan, "dir-nomore", ast_channel_language(chan));
832         }
833
834 exit:
835         if (sorted)
836                 ast_free(sorted);
837
838         while ((item = AST_LIST_REMOVE_HEAD(&alist, entry)))
839                 ast_free(item);
840
841         return res;
842 }
843
844 static int directory_exec(struct ast_channel *chan, const char *data)
845 {
846         int res = 0, digit = 3;
847         struct ast_config *cfg, *ucfg;
848         const char *dirintro;
849         char *parse, *opts[OPT_ARG_ARRAY_SIZE] = { 0, };
850         struct ast_flags flags = { 0 };
851         struct ast_flags config_flags = { 0 };
852         enum { FIRST, LAST, BOTH } which = LAST;
853         char digits[9] = "digits/3";
854         AST_DECLARE_APP_ARGS(args,
855                 AST_APP_ARG(vmcontext);
856                 AST_APP_ARG(dialcontext);
857                 AST_APP_ARG(options);
858         );
859
860         parse = ast_strdupa(data);
861
862         AST_STANDARD_APP_ARGS(args, parse);
863
864         if (args.options && ast_app_parse_options(directory_app_options, &flags, opts, args.options))
865                 return -1;
866
867         if (!(cfg = realtime_directory(args.vmcontext))) {
868                 ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
869                 return -1;
870         }
871
872         if ((ucfg = ast_config_load("users.conf", config_flags)) == CONFIG_STATUS_FILEINVALID) {
873                 ast_log(LOG_ERROR, "Config file users.conf is in an invalid format.  Aborting.\n");
874                 ucfg = NULL;
875         }
876
877         dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro");
878         if (ast_strlen_zero(dirintro))
879                 dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
880         /* the above prompts probably should be modified to include 0 for dialing operator
881            and # for exiting (continues in dialplan) */
882
883         if (ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) && ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
884                 if (!ast_strlen_zero(opts[OPT_ARG_EITHER])) {
885                         digit = atoi(opts[OPT_ARG_EITHER]);
886                 }
887                 which = BOTH;
888         } else if (ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
889                 if (!ast_strlen_zero(opts[OPT_ARG_FIRSTNAME])) {
890                         digit = atoi(opts[OPT_ARG_FIRSTNAME]);
891                 }
892                 which = FIRST;
893         } else {
894                 if (!ast_strlen_zero(opts[OPT_ARG_LASTNAME])) {
895                         digit = atoi(opts[OPT_ARG_LASTNAME]);
896                 }
897                 which = LAST;
898         }
899
900         /* If no options specified, search by last name */
901         if (!ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) && !ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
902                 ast_set_flag(&flags, OPT_LISTBYLASTNAME);
903                 which = LAST;
904         }
905
906         if (digit > 9) {
907                 digit = 9;
908         } else if (digit < 1) {
909                 digit = 3;
910         }
911         digits[7] = digit + '0';
912
913         if (ast_channel_state(chan) != AST_STATE_UP) {
914                 if (!ast_test_flag(&flags, OPT_NOANSWER)) {
915                         /* Otherwise answer unless we're supposed to read while on-hook */
916                         res = ast_answer(chan);
917                 }
918         }
919         for (;;) {
920                 if (!ast_strlen_zero(dirintro) && !res) {
921                         res = ast_stream_and_wait(chan, dirintro, AST_DIGIT_ANY);
922                 } else if (!res) {
923                         /* Stop playing sounds as soon as we have a digit. */
924                         res = ast_stream_and_wait(chan, "dir-welcome", AST_DIGIT_ANY);
925                         if (!res) {
926                                 res = ast_stream_and_wait(chan, "dir-pls-enter", AST_DIGIT_ANY);
927                         }
928                         if (!res) {
929                                 res = ast_stream_and_wait(chan, digits, AST_DIGIT_ANY);
930                         }
931                         if (!res) {
932                                 res = ast_stream_and_wait(chan, 
933                                         which == FIRST ? "dir-first" :
934                                         which == LAST ? "dir-last" :
935                                         "dir-firstlast", AST_DIGIT_ANY);
936                         }
937                         if (!res) {
938                                 res = ast_stream_and_wait(chan, "dir-usingkeypad", AST_DIGIT_ANY);
939                         }
940                 }
941                 ast_stopstream(chan);
942                 if (!res)
943                         res = ast_waitfordigit(chan, 5000);
944
945                 if (res <= 0) {
946                         if (res == 0) {
947                                 pbx_builtin_setvar_helper(chan, "DIRECTORY_RESULT", "TIMEOUT");
948                         }
949                         break;
950                 }
951
952                 res = do_directory(chan, cfg, ucfg, args.vmcontext, args.dialcontext, res, digit, &flags, opts);
953                 if (res)
954                         break;
955
956                 res = ast_waitstream(chan, AST_DIGIT_ANY);
957                 ast_stopstream(chan);
958                 if (res < 0) {
959                         break;
960                 }
961         }
962
963         if (ucfg)
964                 ast_config_destroy(ucfg);
965         ast_config_destroy(cfg);
966
967         if (ast_check_hangup(chan)) {
968                 pbx_builtin_setvar_helper(chan, "DIRECTORY_RESULT", "HANGUP");
969         } else if (res < 0) {
970                 /* If the res < 0 and we didn't hangup, an unaccounted for error must have happened. */
971                 pbx_builtin_setvar_helper(chan, "DIRECTORY_RESULT", "FAILED");
972         }
973
974         return res < 0 ? -1 : 0;
975 }
976
977 static int unload_module(void)
978 {
979         int res;
980         res = ast_unregister_application(app);
981         return res;
982 }
983
984 static int load_module(void)
985 {
986         return ast_register_application_xml(app, directory_exec);
987 }
988
989 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Directory");