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