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