86a2c842aa4aa401cf6e1d284964e0f27bcdcf83
[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 #include "asterisk.h"
29
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
31
32 #include <ctype.h>
33
34 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
35 #include "asterisk/file.h"
36 #include "asterisk/pbx.h"
37 #include "asterisk/module.h"
38 #include "asterisk/say.h"
39 #include "asterisk/app.h"
40
41 #ifdef ODBC_STORAGE
42 #include <sys/mman.h>
43 #include "asterisk/res_odbc.h"
44
45 static char odbc_database[80] = "asterisk";
46 static char odbc_table[80] = "voicemessages";
47 static char vmfmts[80] = "wav";
48 #endif
49
50 static char *app = "Directory";
51
52 static char *synopsis = "Provide directory of voicemail extensions";
53 static char *descrip =
54 "  Directory(vm-context[,dial-context[,options]]): This application will present\n"
55 "the calling channel with a directory of extensions from which they can search\n"
56 "by name. The list of names and corresponding extensions is retrieved from the\n"
57 "voicemail configuration file, voicemail.conf.\n"
58 "  This application will immediately exit if one of the following DTMF digits are\n"
59 "received and the extension to jump to exists:\n"
60 "    0 - Jump to the 'o' extension, if it exists.\n"
61 "    * - Jump to the 'a' extension, if it exists.\n\n"
62 "  Parameters:\n"
63 "    vm-context   - This is the context within voicemail.conf to use for the\n"
64 "                   Directory.\n"
65 "    dial-context - This is the dialplan context to use when looking for an\n"
66 "                   extension that the user has selected, or when jumping to the\n"
67 "                   'o' or 'a' extension.\n\n"
68 "  Options:\n"
69 "    e - In addition to the name, also read the extension number to the\n"
70 "        caller before presenting dialing options.\n"
71 "    f - Allow the caller to enter the first name of a user in the directory\n"
72 "        instead of using the last name.\n"
73 "    m - Instead of reading each name sequentially and asking for confirmation,\n"
74 "        create a menu of up to 8 names.\n";
75
76 /* For simplicity, I'm keeping the format compatible with the voicemail config,
77    but i'm open to suggestions for isolating it */
78
79 #define VOICEMAIL_CONFIG "voicemail.conf"
80
81 /* How many digits to read in */
82 #define NUMDIGITS 3
83
84 enum {
85         OPT_LISTBYFIRSTNAME = (1 << 0),
86         OPT_SAYEXTENSION =    (1 << 1),
87         OPT_FROMVOICEMAIL =   (1 << 2),
88         OPT_SELECTFROMMENU =  (1 << 3),
89 } directory_option_flags;
90
91 struct directory_item {
92         char exten[AST_MAX_EXTENSION + 1];
93         char name[AST_MAX_EXTENSION + 1];
94         char key[50]; /* Text to order items. Either lastname+firstname or firstname+lastname */
95
96         AST_LIST_ENTRY(directory_item) entry;
97 };
98
99 AST_APP_OPTIONS(directory_app_options, {
100         AST_APP_OPTION('f', OPT_LISTBYFIRSTNAME),
101         AST_APP_OPTION('e', OPT_SAYEXTENSION),
102         AST_APP_OPTION('v', OPT_FROMVOICEMAIL),
103         AST_APP_OPTION('m', OPT_SELECTFROMMENU),
104 });
105
106 #ifdef ODBC_STORAGE
107 struct generic_prepare_struct {
108         const char *sql;
109         const char *param;
110 };
111
112 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
113 {
114         struct generic_prepare_struct *gps = data;
115         SQLHSTMT stmt;
116         int res;
117
118         res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
119         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
120                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
121                 return NULL;
122         }
123
124         res = SQLPrepare(stmt, (unsigned char *)gps->sql, SQL_NTS);
125         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
126                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", (char *)gps->sql);
127                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
128                 return NULL;
129         }
130
131         if (!ast_strlen_zero(gps->param))
132                 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->param), 0, (void *)gps->param, 0, NULL);
133
134         return stmt;
135 }
136
137 static void retrieve_file(char *dir)
138 {
139         int x = 0;
140         int res;
141         int fd=-1;
142         size_t fdlen = 0;
143         void *fdm = MAP_FAILED;
144         SQLHSTMT stmt;
145         char sql[256];
146         char fmt[80]="", empty[10] = "";
147         char *c;
148         SQLLEN colsize;
149         char full_fn[256];
150         struct odbc_obj *obj;
151         struct generic_prepare_struct gps = { .sql = sql, .param = dir };
152
153         obj = ast_odbc_request_obj(odbc_database, 1);
154         if (obj) {
155                 do {
156                         ast_copy_string(fmt, vmfmts, sizeof(fmt));
157                         c = strchr(fmt, '|');
158                         if (c)
159                                 *c = '\0';
160                         if (!strcasecmp(fmt, "wav49"))
161                                 strcpy(fmt, "WAV");
162                         snprintf(full_fn, sizeof(full_fn), "%s.%s", dir, fmt);
163                         snprintf(sql, sizeof(sql), "SELECT recording FROM %s WHERE dir=? AND msgnum=-1", odbc_table);
164                         stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
165
166                         if (!stmt) {
167                                 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
168                                 break;
169                         }
170                         res = SQLFetch(stmt);
171                         if (res == SQL_NO_DATA) {
172                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
173                                 break;
174                         } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
175                                 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
176                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
177                                 break;
178                         }
179                         fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, AST_FILE_MODE);
180                         if (fd < 0) {
181                                 ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
182                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
183                                 break;
184                         }
185
186                         res = SQLGetData(stmt, 1, SQL_BINARY, empty, 0, &colsize);
187                         fdlen = colsize;
188                         if (fd > -1) {
189                                 char tmp[1]="";
190                                 lseek(fd, fdlen - 1, SEEK_SET);
191                                 if (write(fd, tmp, 1) != 1) {
192                                         close(fd);
193                                         fd = -1;
194                                         break;
195                                 }
196                                 if (fd > -1)
197                                         fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
198                         }
199                         if (fdm != MAP_FAILED) {
200                                 memset(fdm, 0, fdlen);
201                                 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, fdlen, &colsize);
202                                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
203                                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
204                                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
205                                         break;
206                                 }
207                         }
208                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
209                 } while (0);
210                 ast_odbc_release_obj(obj);
211         } else
212                 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
213         if (fdm != MAP_FAILED)
214                 munmap(fdm, fdlen);
215         if (fd > -1)
216                 close(fd);
217         return;
218 }
219 #endif
220
221 static int compare(const char *text, const char *template)
222 {
223         char digit;
224
225         while (*template) {
226                 digit = toupper(*text++);
227                 switch (digit) {
228                 case 0:
229                         return -1;
230                 case '1':
231                         digit = '1';
232                         break;
233                 case '2':
234                 case 'A':
235                 case 'B':
236                 case 'C':
237                         digit = '2';
238                         break;
239                 case '3':
240                 case 'D':
241                 case 'E':
242                 case 'F':
243                         digit = '3';
244                         break;
245                 case '4':
246                 case 'G':
247                 case 'H':
248                 case 'I':
249                         digit = '4';
250                         break;
251                 case '5':
252                 case 'J':
253                 case 'K':
254                 case 'L':
255                         digit = '5';
256                         break;
257                 case '6':
258                 case 'M':
259                 case 'N':
260                 case 'O':
261                         digit = '6';
262                         break;
263                 case '7':
264                 case 'P':
265                 case 'Q':
266                 case 'R':
267                 case 'S':
268                         digit = '7';
269                         break;
270                 case '8':
271                 case 'T':
272                 case 'U':
273                 case 'V':
274                         digit = '8';
275                         break;
276                 case '9':
277                 case 'W':
278                 case 'X':
279                 case 'Y':
280                 case 'Z':
281                         digit = '9';
282                         break;
283
284                 default:
285                         if (digit > ' ')
286                                 return -1;
287                         continue;
288                 }
289
290                 if (*template++ != digit)
291                         return -1;
292         }
293
294         return 0;
295 }
296
297 /* play name of mailbox owner.
298  * returns:  -1 for bad or missing extension
299  *           '1' for selected entry from directory
300  *           '*' for skipped entry from directory
301  */
302 static int play_mailbox_owner(struct ast_channel *chan, const char *context,
303         const char *ext, const char *name, struct ast_flags *flags)
304 {
305         int res = 0;
306         char fn[256];
307
308         /* Check for the VoiceMail2 greeting first */
309         snprintf(fn, sizeof(fn), "%s/voicemail/%s/%s/greet",
310                 ast_config_AST_SPOOL_DIR, context, ext);
311 #ifdef ODBC_STORAGE
312         retrieve_file(fn);
313 #endif
314
315         if (ast_fileexists(fn, NULL, chan->language) <= 0) {
316                 /* no file, check for an old-style Voicemail greeting */
317                 snprintf(fn, sizeof(fn), "%s/vm/%s/greet",
318                         ast_config_AST_SPOOL_DIR, ext);
319         }
320 #ifdef ODBC_STORAGE
321         retrieve_file(fn);
322 #endif
323
324         if (ast_fileexists(fn, NULL, chan->language) > 0) {
325                 res = ast_stream_and_wait(chan, fn, AST_DIGIT_ANY);
326                 ast_stopstream(chan);
327                 /* If Option 'e' was specified, also read the extension number with the name */
328                 if (ast_test_flag(flags, OPT_SAYEXTENSION)) {
329                         ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
330                         res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
331                 }
332         } else {
333                 res = ast_say_character_str(chan, S_OR(name, ext), AST_DIGIT_ANY, chan->language);
334                 if (!ast_strlen_zero(name) && ast_test_flag(flags, OPT_SAYEXTENSION)) {
335                         ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
336                         res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
337                 }
338         }
339 #ifdef ODBC_STORAGE
340         ast_filedelete(fn, NULL);
341 #endif
342
343         return res;
344 }
345
346 static int select_entry(struct ast_channel *chan, const char *context, const char *dialcontext, const struct directory_item *item, struct ast_flags *flags)
347 {
348         ast_debug(1, "Selecting '%s' - %s@%s\n", item->name, item->exten, dialcontext);
349
350         if (ast_test_flag(flags, OPT_FROMVOICEMAIL)) {
351                 /* We still want to set the exten though */
352                 ast_copy_string(chan->exten, item->exten, sizeof(chan->exten));
353         } else if (ast_goto_if_exists(chan, dialcontext, item->exten, 1)) {
354                 ast_log(LOG_WARNING,
355                         "Can't find extension '%s' in context '%s'.  "
356                         "Did you pass the wrong context to Directory?\n",
357                         item->exten, dialcontext);
358                 return -1;
359         }
360
361         return 0;
362 }
363
364 static int select_item_seq(struct ast_channel *chan, struct directory_item **items, int count, const char *context, const char *dialcontext, struct ast_flags *flags)
365 {
366         struct directory_item *item, **ptr;
367         int i, res, loop;
368
369         for (ptr = items, i = 0; i < count; i++, ptr++) {
370                 item = *ptr;
371
372                 for (loop = 3 ; loop > 0; loop--) {
373                         res = play_mailbox_owner(chan, context, item->exten, item->name, flags);
374
375                         if (!res)
376                                 res = ast_stream_and_wait(chan, "dir-instr", AST_DIGIT_ANY);
377                         if (!res)
378                                 res = ast_waitfordigit(chan, 3000);
379                         ast_stopstream(chan);
380         
381                         if (res == '1') { /* Name selected */
382                                 return select_entry(chan, context, dialcontext, item, flags) ? -1 : 1;
383                         } else if (res == '*') {
384                                 /* Skip to next match in list */
385                                 break;
386                         }
387
388                         if (res < 0)
389                                 return -1;
390
391                         res = 0;
392                 }
393         }
394
395         /* Nothing was selected */
396         return 0;
397 }
398
399 static int select_item_menu(struct ast_channel *chan, struct directory_item **items, int count, const char *context, const char *dialcontext, struct ast_flags *flags)
400 {
401         struct directory_item **block, *item;
402         int i, limit, res = 0;
403         char buf[9];
404
405         for (block = items; count; block += limit, count -= limit) {
406                 limit = count;
407                 if (limit > 8)
408                         limit = 8;
409
410                 for (i = 0; i < limit && !res; i++) {
411                         item = block[i];
412
413                         snprintf(buf, sizeof(buf), "digits/%d", i + 1);
414                         /* Press <num> for <name>, [ extension <ext> ] */
415                         res = ast_streamfile(chan, "dir-multi1", chan->language);
416                         if (!res)
417                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
418                         if (!res)
419                                 res = ast_streamfile(chan, buf, chan->language);
420                         if (!res)
421                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
422                         if (!res)
423                                 res = ast_streamfile(chan, "dir-multi2", chan->language);
424                         if (!res)
425                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
426                         if (!res)
427                                 res = play_mailbox_owner(chan, context, item->exten, item->name, flags);
428                         if (!res)
429                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
430                         if (!res)
431                                 res = ast_waitfordigit(chan, 800);
432                 }
433
434                 /* Press "9" for more names. */
435                 if (!res && count > limit) {
436                         res = ast_streamfile(chan, "dir-multi9", chan->language);
437                         if (!res)
438                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
439                 }
440
441                 if (!res) {
442                         res = ast_waitfordigit(chan, 3000);
443                 }
444
445                 if (res && res > '0' && res < '1' + limit) {
446                         return select_entry(chan, context, 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 static struct ast_config *realtime_directory(char *context)
460 {
461         struct ast_config *cfg;
462         struct ast_config *rtdata;
463         struct ast_category *cat;
464         struct ast_variable *var;
465         char *mailbox;
466         const char *fullname;
467         const char *hidefromdir;
468         char tmp[100];
469         struct ast_flags config_flags = { 0 };
470
471         /* Load flat file config. */
472         cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
473
474         if (!cfg) {
475                 /* Loading config failed. */
476                 ast_log(LOG_WARNING, "Loading config failed.\n");
477                 return NULL;
478         }
479
480         /* Get realtime entries, categorized by their mailbox number
481            and present in the requested context */
482         rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, NULL);
483
484         /* if there are no results, just return the entries from the config file */
485         if (!rtdata)
486                 return cfg;
487
488         /* Does the context exist within the config file? If not, make one */
489         cat = ast_category_get(cfg, context);
490         if (!cat) {
491                 cat = ast_category_new(context, "", 99999);
492                 if (!cat) {
493                         ast_log(LOG_WARNING, "Out of memory\n");
494                         ast_config_destroy(cfg);
495                         return NULL;
496                 }
497                 ast_category_append(cfg, cat);
498         }
499
500         mailbox = NULL;
501         while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) {
502                 fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
503                 hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir");
504                 snprintf(tmp, sizeof(tmp), "no-password,%s,hidefromdir=%s",
505                         fullname ? fullname : "",
506                         hidefromdir ? hidefromdir : "no");
507                 var = ast_variable_new(mailbox, tmp, "");
508                 if (var)
509                         ast_variable_append(cat, var);
510                 else
511                         ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
512         }
513         ast_config_destroy(rtdata);
514
515         return cfg;
516 }
517
518 static int check_match(struct directory_item **result, const char *item_fullname, const char *item_ext, const char *pattern_ext, int use_first_name)
519 {
520         struct directory_item *item;
521         const char *key = NULL;
522         int namelen;
523
524
525         /* Set key to last name or first name depending on search mode */
526         if (!use_first_name)
527                 key = strchr(item_fullname, ' ');
528
529         if (key)
530                 key++;
531         else
532                 key = item_fullname;
533
534         if (compare(key, pattern_ext))
535                 return 0;
536
537         /* Match */
538         item = ast_calloc(1, sizeof(*item));
539         if (!item)
540                 return -1;
541         ast_copy_string(item->name, item_fullname, sizeof(item->name));
542         ast_copy_string(item->exten, item_ext, sizeof(item->exten));
543
544         ast_copy_string(item->key, key, sizeof(item->key));
545         if (key != item_fullname) {
546                 /* Key is the last name. Append first name to key in order to sort Last,First */
547                 namelen = key - item_fullname - 1;
548                 if (namelen > sizeof(item->key) - strlen(item->key) - 1)
549                         namelen = sizeof(item->key) - strlen(item->key) - 1;
550                 strncat(item->key, item_fullname, namelen);
551         }
552
553         *result = item;
554         return 1;
555 }
556
557 typedef AST_LIST_HEAD_NOLOCK(, directory_item) itemlist;
558
559 static int search_directory(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, int use_first_name, itemlist *alist)
560 {
561         struct ast_variable *v;
562         char buf[AST_MAX_EXTENSION + 1], *pos, *bufptr, *cat;
563         struct directory_item *item;
564         int res;
565
566         ast_debug(2, "Pattern: %s\n", ext);
567
568         for (v = ast_variable_browse(vmcfg, context); v; v = v->next) {
569
570                 /* Ignore hidden */
571                 if (strcasestr(v->value, "hidefromdir=yes"))
572                         continue;
573
574                 ast_copy_string(buf, v->value, sizeof(buf));
575                 bufptr = buf;
576
577                 /* password,Full Name,email,pager,options */
578                 strsep(&bufptr, ",");
579                 pos = strsep(&bufptr, ",");
580
581                 res = check_match(&item, pos, v->name, ext, use_first_name);
582                 if (!res)
583                         continue;
584                 else if (res < 0)
585                         return -1;
586
587                 AST_LIST_INSERT_TAIL(alist, item, entry);
588         }
589
590
591         if (ucfg) {
592                 for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) {
593                         const char *pos;
594                         if (!strcasecmp(cat, "general"))
595                                 continue;
596                         if (!ast_true(ast_config_option(ucfg, cat, "hasdirectory")))
597                                 continue;
598
599                         /* Find all candidate extensions */
600                         pos = ast_variable_retrieve(ucfg, cat, "fullname");
601                         if (!pos)
602                                 continue;
603
604                         res = check_match(&item, pos, cat, ext, use_first_name);
605                         if (!res)
606                                 continue;
607                         else if (res < 0)
608                                 return -1;
609
610                         AST_LIST_INSERT_TAIL(alist, item, entry);
611                 }
612         }
613
614         return 0;
615 }
616
617 static void sort_items(struct directory_item **sorted, int count)
618 {
619         int reordered, i;
620         struct directory_item **ptr, *tmp;
621
622         if (count < 2)
623                 return;
624
625         /* Bubble-sort items by the key */
626         do {
627                 reordered = 0;
628                 for (ptr = sorted, i = 0; i < count - 1; i++, ptr++) {
629                         if (strcasecmp(ptr[0]->key, ptr[1]->key) > 0) {
630                                 tmp = ptr[0];
631                                 ptr[0] = ptr[1];
632                                 ptr[1] = tmp;
633                                 reordered++;
634                         }
635                 }
636         } while (reordered);
637 }
638
639 static int goto_exten(struct ast_channel *chan, const char *dialcontext, char *ext)
640 {
641         if (!ast_goto_if_exists(chan, dialcontext, ext, 1) ||
642                 (!ast_strlen_zero(chan->macrocontext) &&
643                 !ast_goto_if_exists(chan, chan->macrocontext, ext, 1))) {
644                 return 0;
645         } else {
646                 ast_log(LOG_WARNING, "Can't find extension '%s' in current context.  "
647                         "Not Exiting the Directory!\n", ext);
648                 return -1;
649         }
650 }
651
652 static int do_directory(struct ast_channel *chan, struct ast_config *vmcfg, struct ast_config *ucfg, char *context, char *dialcontext, char digit, struct ast_flags *flags)
653 {
654         /* Read in the first three digits..  "digit" is the first digit, already read */
655         int res = 0;
656         itemlist alist = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
657         struct directory_item *item, **ptr, **sorted = NULL;
658         int count, i;
659         char ext[NUMDIGITS + 1] = "";
660
661         if (ast_strlen_zero(context)) {
662                 ast_log(LOG_WARNING,
663                         "Directory must be called with an argument "
664                         "(context in which to interpret extensions)\n");
665                 return -1;
666         }
667
668         if (digit == '0' && !goto_exten(chan, dialcontext, "o")) {
669                 return 0;
670         }
671
672         if (digit == '*' && !goto_exten(chan, dialcontext, "a")) {
673                 return 0;
674         }
675
676         ext[0] = digit;
677         if (ast_readstring(chan, ext + 1, NUMDIGITS - 1, 3000, 3000, "#") < 0)
678                 return -1;
679
680         res = search_directory(context, vmcfg, ucfg, ext, ast_test_flag(flags, OPT_LISTBYFIRSTNAME), &alist);
681         if (res)
682                 goto exit;
683
684         /* Count items in the list */
685         count = 0;
686         AST_LIST_TRAVERSE(&alist, item, entry) {
687                 count++;
688         }
689
690         if (count < 1) {
691                 res = ast_streamfile(chan, "dir-nomatch", chan->language);
692                 goto exit;
693         }
694
695
696         /* Create plain array of pointers to items (for sorting) */
697         sorted = ast_calloc(count, sizeof(*sorted));
698
699         ptr = sorted;
700         AST_LIST_TRAVERSE(&alist, item, entry) {
701                 *ptr++ = item;
702         }
703
704         /* Sort items */
705         sort_items(sorted, count);
706
707         if (option_debug) {
708                 ast_debug(2, "Listing matching entries:\n");
709                 for (ptr = sorted, i = 0; i < count; i++, ptr++) {
710                         ast_log(LOG_DEBUG, "%s: %s\n", ptr[0]->exten, ptr[0]->name);
711                 }
712         }
713
714         if (ast_test_flag(flags, OPT_SELECTFROMMENU)) {
715                 /* Offer multiple entries at the same time */
716                 res = select_item_menu(chan, sorted, count, context, dialcontext, flags);
717         } else {
718                 /* Offer entries one by one */
719                 res = select_item_seq(chan, sorted, count, context, dialcontext, flags);
720         }
721
722         if (!res) {
723                 res = ast_streamfile(chan, "dir-nomore", chan->language);
724         }
725
726 exit:
727         if (sorted)
728                 ast_free(sorted);
729
730         while ((item = AST_LIST_REMOVE_HEAD(&alist, entry)))
731                 ast_free(item);
732
733         return res;
734 }
735
736 static int directory_exec(struct ast_channel *chan, void *data)
737 {
738         int res = 0;
739         struct ast_config *cfg, *ucfg;
740         const char *dirintro;
741         char *parse, *opts[0];
742         struct ast_flags flags = { 0 };
743         struct ast_flags config_flags = { 0 };
744         AST_DECLARE_APP_ARGS(args,
745                 AST_APP_ARG(vmcontext);
746                 AST_APP_ARG(dialcontext);
747                 AST_APP_ARG(options);
748         );
749
750         if (ast_strlen_zero(data)) {
751                 ast_log(LOG_WARNING, "Directory requires an argument (context[,dialcontext])\n");
752                 return -1;
753         }
754
755         parse = ast_strdupa(data);
756
757         AST_STANDARD_APP_ARGS(args, parse);
758
759         if (args.options && ast_app_parse_options(directory_app_options, &flags, opts, args.options))
760                 return -1;
761
762         if (ast_strlen_zero(args.dialcontext))
763                 args.dialcontext = args.vmcontext;
764
765         cfg = realtime_directory(args.vmcontext);
766         if (!cfg) {
767                 ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
768                 return -1;
769         }
770
771         ucfg = ast_config_load("users.conf", config_flags);
772
773         dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro");
774         if (ast_strlen_zero(dirintro))
775                 dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
776         if (ast_strlen_zero(dirintro))
777                 dirintro = ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) ? "dir-intro-fn" : "dir-intro";
778
779         if (chan->_state != AST_STATE_UP)
780                 res = ast_answer(chan);
781
782         for (;;) {
783                 if (!res)
784                         res = ast_stream_and_wait(chan, dirintro, AST_DIGIT_ANY);
785                 ast_stopstream(chan);
786                 if (!res)
787                         res = ast_waitfordigit(chan, 5000);
788
789                 if (res <= 0)
790                         break;
791
792                 res = do_directory(chan, cfg, ucfg, args.vmcontext, args.dialcontext, res, &flags);
793                 if (res)
794                         break;
795
796                 res = ast_waitstream(chan, AST_DIGIT_ANY);
797                 ast_stopstream(chan);
798
799                 if (res)
800                         break;
801         }
802
803         if (ucfg)
804                 ast_config_destroy(ucfg);
805         ast_config_destroy(cfg);
806
807         return res < 0 ? -1 : 0;
808 }
809
810 static int unload_module(void)
811 {
812         int res;
813         res = ast_unregister_application(app);
814         return res;
815 }
816
817 static int load_module(void)
818 {
819 #ifdef ODBC_STORAGE
820         struct ast_flags config_flags = { 0 };
821         struct ast_config *cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
822         const char *tmp;
823
824         if (cfg) {
825                 if ((tmp = ast_variable_retrieve(cfg, "general", "odbcstorage"))) {
826                         ast_copy_string(odbc_database, tmp, sizeof(odbc_database));
827                 }
828                 if ((tmp = ast_variable_retrieve(cfg, "general", "odbctable"))) {
829                         ast_copy_string(odbc_table, tmp, sizeof(odbc_table));
830                 }
831                 if ((tmp = ast_variable_retrieve(cfg, "general", "format"))) {
832                         ast_copy_string(vmfmts, tmp, sizeof(vmfmts));
833                 }
834                 ast_config_destroy(cfg);
835         } else
836                 ast_log(LOG_WARNING, "Unable to load " VOICEMAIL_CONFIG " - ODBC defaults will be used\n");
837 #endif
838
839         return ast_register_application(app, directory_exec, synopsis, descrip);
840 }
841
842 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Directory");