Wed Jan 8 2020 09:49:39

Asterisk developer's documentation


app_directory.c
Go to the documentation of this file.
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: 368738 $")
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 {
131  OPT_SAYEXTENSION = (1 << 1),
132  OPT_FROMVOICEMAIL = (1 << 2),
133  OPT_SELECTFROMMENU = (1 << 3),
134  OPT_LISTBYLASTNAME = (1 << 4),
136  OPT_PAUSE = (1 << 5),
137  OPT_NOANSWER = (1 << 6),
138 };
139 
140 enum {
145  /* This *must* be the last value in this enum! */
147 };
148 
153  char key[50]; /* Text to order items. Either lastname+firstname or firstname+lastname */
154 
156 };
157 
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, chan->context), ext, 1) ||
252  (!ast_strlen_zero(chan->macrocontext) &&
253  !ast_goto_if_exists(chan, chan->macrocontext, 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, chan->language);
277  }
278  } else {
279  res = ast_say_character_str(chan, S_OR(name, ext), AST_DIGIT_ANY, chan->language);
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, chan->language);
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_copy_string(chan->exten, item->exten, sizeof(chan->exten));
296  } else if (ast_goto_if_exists(chan, S_OR(dialcontext, item->context), item->exten, 1)) {
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", chan->language);
387  if (!res)
388  res = ast_waitstream(chan, AST_DIGIT_ANY);
389  if (!res)
390  res = ast_streamfile(chan, buf, chan->language);
391  if (!res)
392  res = ast_waitstream(chan, AST_DIGIT_ANY);
393  if (!res)
394  res = ast_streamfile(chan, "dir-multi2", chan->language);
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", chan->language);
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 *context = 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, context))) {
487  if (!(cat = ast_category_new(context, "", 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 
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;
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", chan->language);
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", chan->language);
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";
770  AST_APP_ARG(vmcontext);
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 
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 */
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 (chan->_state != 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 {
891 }
892 
static int select_entry(struct ast_channel *chan, const char *dialcontext, const struct directory_item *item, struct ast_flags *flags)
Main Channel structure associated with a channel.
Definition: channel.h:742
#define AST_MODULE_INFO_STANDARD(keystr, desc)
Definition: module.h:396
#define AST_APP_OPTION_ARG(option, flagno, argno)
Declares an application option that accepts an argument.
Definition: app.h:732
int ast_streamfile(struct ast_channel *c, const char *filename, const char *preflang)
Streams a file.
Definition: file.c:946
Asterisk main include file. File version handling, generic pbx functions.
const char * ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
Gets a variable.
Definition: config.c:625
#define AST_APP_OPTIONS(holder, options...)
Declares an array of options for an application.
Definition: app.h:712
char * strsep(char **str, const char *delims)
#define AST_DIGIT_ANY
Definition: file.h:47
#define ast_test_flag(p, flag)
Definition: utils.h:63
int option_debug
Definition: asterisk.c:182
char context[AST_MAX_CONTEXT]
Definition: channel.h:868
#define ast_set_flag(p, flag)
Definition: utils.h:70
#define LOG_WARNING
Definition: logger.h:144
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category)
Goes through variables.
Definition: config.c:597
struct directory_item::@13 entry
int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr)
Parses a string containing application options and sets flags/arguments.
Definition: app.c:2101
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application&#39;s arguments.
Definition: app.h:572
Structure for variables, used for configurations and for channel variables.
Definition: config.h:75
static int goto_exten(struct ast_channel *chan, const char *dialcontext, char *ext)
#define var
Definition: ast_expr2f.c:606
static int select_item_seq(struct ast_channel *chan, struct directory_item **items, int count, const char *dialcontext, struct ast_flags *flags, char *opts[])
static struct ast_app_option directory_app_options[128]
char * text
Definition: app_queue.c:1091
const char * ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
Retrieve a configuration variable within the configuration set.
Definition: config.c:614
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
char context[AST_MAX_CONTEXT+1]
const char * ext
Definition: http.c:112
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx.c:7705
void ast_config_destroy(struct ast_config *config)
Destroys a config.
Definition: config.c:1037
Utility functions.
struct ast_category * ast_category_new(const char *name, const char *in_file, int lineno)
Create a category structure.
Definition: config.c:673
static int select_item_pause(struct ast_channel *chan, struct ast_flags *flags, char *opts[])
struct ast_category * ast_category_get(const struct ast_config *config, const char *category_name)
Retrieve a category if it exists.
Definition: config.c:709
static void sort_items(struct directory_item **sorted, int count)
static struct ast_config * realtime_directory(char *context)
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:236
#define SENTINEL
Definition: compiler.h:75
char name[AST_MAX_EXTENSION+1]
const char * value
Definition: config.h:79
static int unload_module(void)
static char dialcontext[AST_MAX_CONTEXT]
Asterisk file paths, configured in asterisk.conf.
#define ast_config_load(filename, flags)
Load a config file.
Definition: config.h:170
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:63
#define AST_MAX_EXTENSION
Definition: channel.h:135
char * ast_category_browse(struct ast_config *config, const char *prev)
Goes through categories.
Definition: config.c:810
const char * name
Definition: config.h:77
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:818
int ast_app_sayname(struct ast_channel *chan, const char *mailbox, const char *context)
Given a mailbox and context, play that mailbox owner&#39;s name to the channel specified.
Definition: app.c:478
Core PBX routines and definitions.
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)
#define AST_LIST_HEAD_NOLOCK(name, type)
Defines a structure to be used to hold a list of specified type (with no lock).
Definition: linkedlists.h:224
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: utils.h:663
static int search_directory(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, struct ast_flags flags, itemlist *alist)
static int select_item_menu(struct ast_channel *chan, struct directory_item **items, int count, const char *dialcontext, struct ast_flags *flags, char *opts[])
#define LOG_ERROR
Definition: logger.h:155
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:716
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is &quot;true&quot;. This function checks to see whether a string passed to it is an indication of an &quot;true&quot; value. It checks to see if the string is &quot;yes&quot;, &quot;true&quot;, &quot;y&quot;, &quot;t&quot;, &quot;on&quot; or &quot;1&quot;.
Definition: utils.c:1533
static struct @350 args
void ast_variable_append(struct ast_category *category, struct ast_variable *variable)
Definition: config.c:483
enum ast_channel_state _state
Definition: channel.h:839
void ast_log(int level, const char *file, int line, const char *function, const char *fmt,...)
Used for sending a log message This is the standard logger function. Probably the only way you will i...
Definition: logger.c:1207
int ast_goto_if_exists(struct ast_channel *chan, const char *context, const char *exten, int priority)
Definition: pbx.c:11261
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:490
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:409
static void parse(struct mgcp_request *req)
Definition: chan_mgcp.c:1858
#define AST_MAX_CONTEXT
Definition: channel.h:136
#define ast_free(a)
Definition: astmm.h:97
char exten[AST_MAX_EXTENSION+1]
char macrocontext[AST_MAX_CONTEXT]
Definition: channel.h:870
int ast_stream_and_wait(struct ast_channel *chan, const char *file, const char *digits)
stream file until digit If the file name is non-empty, try to play it.
Definition: file.c:1370
int ast_say_character_str(struct ast_channel *chan, const char *num, const char *ints, const char *lang)
Definition: channel.c:8421
static int load_module(void)
Structure used to handle boolean flags.
Definition: utils.h:200
#define AST_LIST_HEAD_NOLOCK_INIT_VALUE
Defines initial values for a declaration of AST_LIST_HEAD_NOLOCK.
Definition: linkedlists.h:251
static const char app[]
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[])
int ast_waitfordigit(struct ast_channel *c, int ms)
Waits for a digit.
Definition: channel.c:3552
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)
#define ast_calloc(a, b)
Definition: astmm.h:82
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:223
static int compare(const char *text, const char *template)
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:77
int ast_waitstream(struct ast_channel *c, const char *breakon)
Waits for a stream to stop or digit to be pressed.
Definition: file.c:1343
int ast_answer(struct ast_channel *chan)
Answer a channel.
Definition: channel.c:3086
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
static int directory_exec(struct ast_channel *chan, const char *data)
static int play_mailbox_owner(struct ast_channel *chan, const char *context, const char *ext, const char *name, struct ast_flags *flags)
#define VOICEMAIL_CONFIG
#define AST_APP_ARG(name)
Define an application argument.
Definition: app.h:555
struct ast_variable * next
Definition: config.h:82
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the &#39;standard&#39; argument separation process for an application.
Definition: app.h:604
void ast_category_append(struct ast_config *config, struct ast_category *cat)
Definition: config.c:719
#define CONFIG_STATUS_FILEINVALID
Definition: config.h:52
int ast_readstring(struct ast_channel *c, char *s, int len, int timeout, int rtimeout, char *enders)
Reads multiple digits.
Definition: channel.c:5837
Say numbers and dates (maybe words one day too)
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:38
Asterisk module definitions.
char * strcasestr(const char *, const char *)
const ast_string_field language
Definition: channel.h:787
char exten[AST_MAX_EXTENSION]
Definition: channel.h:869
int ast_stopstream(struct ast_channel *c)
Stops a stream.
Definition: file.c:128
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition: module.h:437
struct ast_variable * ast_variable_new(const char *name, const char *value, const char *filename)
Definition: config.c:278
static char mailbox[AST_MAX_EXTENSION]
Definition: chan_mgcp.c:197
#define ASTERISK_FILE_VERSION(file, version)
Register/unregister a source code file with the core.
Definition: asterisk.h:180
struct ast_config * ast_load_realtime_multientry(const char *family,...) attribute_sentinel
Retrieve realtime configuration.
Definition: config.c:2650
#define AST_APP_OPTION(option, flagno)
Declares an application option that does not accept an argument.
Definition: app.h:721