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