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