app_directory.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  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. /*! \file
  19. *
  20. * \brief Provide a directory of extensions
  21. *
  22. * \author Mark Spencer <markster@digium.com>
  23. *
  24. * \ingroup applications
  25. */
  26. /*** MODULEINFO
  27. <depend>app_voicemail</depend>
  28. ***/
  29. #include "asterisk.h"
  30. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  31. #include <ctype.h>
  32. #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
  33. #include "asterisk/file.h"
  34. #include "asterisk/pbx.h"
  35. #include "asterisk/module.h"
  36. #include "asterisk/say.h"
  37. #include "asterisk/app.h"
  38. #include "asterisk/utils.h"
  39. static char *app = "Directory";
  40. static char *synopsis = "Provide directory of voicemail extensions";
  41. static char *descrip =
  42. " Directory(vm-context[,dial-context[,options]]): This application will present\n"
  43. "the calling channel with a directory of extensions from which they can search\n"
  44. "by name. The list of names and corresponding extensions is retrieved from the\n"
  45. "voicemail configuration file, voicemail.conf.\n"
  46. " This application will immediately exit if one of the following DTMF digits are\n"
  47. "received and the extension to jump to exists:\n"
  48. " 0 - Jump to the 'o' extension, if it exists.\n"
  49. " * - Jump to the 'a' extension, if it exists.\n\n"
  50. " Parameters:\n"
  51. " vm-context - This is the context within voicemail.conf to use for the\n"
  52. " Directory.\n"
  53. " dial-context - This is the dialplan context to use when looking for an\n"
  54. " extension that the user has selected, or when jumping to the\n"
  55. " 'o' or 'a' extension.\n\n"
  56. " Options:\n"
  57. " e In addition to the name, also read the extension number to the\n"
  58. " caller before presenting dialing options.\n"
  59. " f[(<n>)] Allow the caller to enter the first name of a user in the\n"
  60. " directory instead of using the last name. If specified, the\n"
  61. " optional number argument will be used for the number of\n"
  62. " characters the user should enter.\n"
  63. " l[(<n>)] Allow the caller to enter the last name of a user in the\n"
  64. " directory. This is the default. If specified, the\n"
  65. " optional number argument will be used for the number of\n"
  66. " characters the user should enter.\n"
  67. " b[(<n>)] Allow the caller to enter either the first or the last name\n"
  68. " of a user in the directory. If specified, the optional number\n"
  69. " argument will be used for the number of characters the user\n"
  70. " should enter.\n"
  71. " m Instead of reading each name sequentially and asking for\n"
  72. " confirmation, create a menu of up to 8 names.\n"
  73. " p(<n>) Pause for n milliseconds after the digits are typed. This is\n"
  74. " helpful for people with cellphones, who are not holding the\n"
  75. " receiver to their ear while entering DTMF.\n"
  76. "\n"
  77. " Only one of the f, l, or b options may be specified. If more than one is\n"
  78. " specified, then Directory will act as if 'b' was specified. The number\n"
  79. " of characters for the user to type defaults to 3.\n";
  80. /* For simplicity, I'm keeping the format compatible with the voicemail config,
  81. but i'm open to suggestions for isolating it */
  82. #define VOICEMAIL_CONFIG "voicemail.conf"
  83. enum {
  84. OPT_LISTBYFIRSTNAME = (1 << 0),
  85. OPT_SAYEXTENSION = (1 << 1),
  86. OPT_FROMVOICEMAIL = (1 << 2),
  87. OPT_SELECTFROMMENU = (1 << 3),
  88. OPT_LISTBYLASTNAME = (1 << 4),
  89. OPT_LISTBYEITHER = OPT_LISTBYFIRSTNAME | OPT_LISTBYLASTNAME,
  90. OPT_PAUSE = (1 << 5),
  91. } directory_option_flags;
  92. enum {
  93. OPT_ARG_FIRSTNAME = 0,
  94. OPT_ARG_LASTNAME = 1,
  95. OPT_ARG_EITHER = 2,
  96. OPT_ARG_PAUSE = 3,
  97. /* This *must* be the last value in this enum! */
  98. OPT_ARG_ARRAY_SIZE = 4,
  99. };
  100. struct directory_item {
  101. char exten[AST_MAX_EXTENSION + 1];
  102. char name[AST_MAX_EXTENSION + 1];
  103. char key[50]; /* Text to order items. Either lastname+firstname or firstname+lastname */
  104. AST_LIST_ENTRY(directory_item) entry;
  105. };
  106. AST_APP_OPTIONS(directory_app_options, {
  107. AST_APP_OPTION_ARG('f', OPT_LISTBYFIRSTNAME, OPT_ARG_FIRSTNAME),
  108. AST_APP_OPTION_ARG('l', OPT_LISTBYLASTNAME, OPT_ARG_LASTNAME),
  109. AST_APP_OPTION_ARG('b', OPT_LISTBYEITHER, OPT_ARG_EITHER),
  110. AST_APP_OPTION_ARG('p', OPT_PAUSE, OPT_ARG_PAUSE),
  111. AST_APP_OPTION('e', OPT_SAYEXTENSION),
  112. AST_APP_OPTION('v', OPT_FROMVOICEMAIL),
  113. AST_APP_OPTION('m', OPT_SELECTFROMMENU),
  114. });
  115. static int compare(const char *text, const char *template)
  116. {
  117. char digit;
  118. if (ast_strlen_zero(text)) {
  119. return -1;
  120. }
  121. while (*template) {
  122. digit = toupper(*text++);
  123. switch (digit) {
  124. case 0:
  125. return -1;
  126. case '1':
  127. digit = '1';
  128. break;
  129. case '2':
  130. case 'A':
  131. case 'B':
  132. case 'C':
  133. digit = '2';
  134. break;
  135. case '3':
  136. case 'D':
  137. case 'E':
  138. case 'F':
  139. digit = '3';
  140. break;
  141. case '4':
  142. case 'G':
  143. case 'H':
  144. case 'I':
  145. digit = '4';
  146. break;
  147. case '5':
  148. case 'J':
  149. case 'K':
  150. case 'L':
  151. digit = '5';
  152. break;
  153. case '6':
  154. case 'M':
  155. case 'N':
  156. case 'O':
  157. digit = '6';
  158. break;
  159. case '7':
  160. case 'P':
  161. case 'Q':
  162. case 'R':
  163. case 'S':
  164. digit = '7';
  165. break;
  166. case '8':
  167. case 'T':
  168. case 'U':
  169. case 'V':
  170. digit = '8';
  171. break;
  172. case '9':
  173. case 'W':
  174. case 'X':
  175. case 'Y':
  176. case 'Z':
  177. digit = '9';
  178. break;
  179. default:
  180. if (digit > ' ')
  181. return -1;
  182. continue;
  183. }
  184. if (*template++ != digit)
  185. return -1;
  186. }
  187. return 0;
  188. }
  189. /* play name of mailbox owner.
  190. * returns: -1 for bad or missing extension
  191. * '1' for selected entry from directory
  192. * '*' for skipped entry from directory
  193. */
  194. static int play_mailbox_owner(struct ast_channel *chan, const char *context,
  195. const char *ext, const char *name, struct ast_flags *flags)
  196. {
  197. int res = 0;
  198. if ((res = ast_app_sayname(chan, ext, context)) >= 0) {
  199. ast_stopstream(chan);
  200. /* If Option 'e' was specified, also read the extension number with the name */
  201. if (ast_test_flag(flags, OPT_SAYEXTENSION)) {
  202. ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
  203. res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
  204. }
  205. } else {
  206. res = ast_say_character_str(chan, S_OR(name, ext), AST_DIGIT_ANY, chan->language);
  207. if (!ast_strlen_zero(name) && ast_test_flag(flags, OPT_SAYEXTENSION)) {
  208. ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
  209. res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
  210. }
  211. }
  212. return res;
  213. }
  214. static int select_entry(struct ast_channel *chan, const char *context, const char *dialcontext, const struct directory_item *item, struct ast_flags *flags)
  215. {
  216. ast_debug(1, "Selecting '%s' - %s@%s\n", item->name, item->exten, dialcontext);
  217. if (ast_test_flag(flags, OPT_FROMVOICEMAIL)) {
  218. /* We still want to set the exten though */
  219. ast_copy_string(chan->exten, item->exten, sizeof(chan->exten));
  220. } else if (ast_goto_if_exists(chan, dialcontext, item->exten, 1)) {
  221. ast_log(LOG_WARNING,
  222. "Can't find extension '%s' in context '%s'. "
  223. "Did you pass the wrong context to Directory?\n",
  224. item->exten, dialcontext);
  225. return -1;
  226. }
  227. return 0;
  228. }
  229. static int select_item_pause(struct ast_channel *chan, struct ast_flags *flags, char *opts[])
  230. {
  231. int res = 0, opt_pause = 0;
  232. if (ast_test_flag(flags, OPT_PAUSE) && !ast_strlen_zero(opts[OPT_ARG_PAUSE])) {
  233. opt_pause = atoi(opts[OPT_ARG_PAUSE]);
  234. if (opt_pause > 3000) {
  235. opt_pause = 3000;
  236. }
  237. res = ast_waitfordigit(chan, opt_pause);
  238. }
  239. return res;
  240. }
  241. static int select_item_seq(struct ast_channel *chan, struct directory_item **items, int count,
  242. const char *context, const char *dialcontext, struct ast_flags *flags, char *opts[])
  243. {
  244. struct directory_item *item, **ptr;
  245. int i, res, loop;
  246. /* option p(n): cellphone pause option */
  247. /* allow early press of selection key */
  248. res = select_item_pause(chan, flags, opts);
  249. for (ptr = items, i = 0; i < count; i++, ptr++) {
  250. item = *ptr;
  251. for (loop = 3 ; loop > 0; loop--) {
  252. if (!res)
  253. res = play_mailbox_owner(chan, context, item->exten, item->name, flags);
  254. if (!res)
  255. res = ast_stream_and_wait(chan, "dir-instr", AST_DIGIT_ANY);
  256. if (!res)
  257. res = ast_waitfordigit(chan, 3000);
  258. ast_stopstream(chan);
  259. if (res == '1') { /* Name selected */
  260. return select_entry(chan, context, dialcontext, item, flags) ? -1 : 1;
  261. } else if (res == '*') {
  262. /* Skip to next match in list */
  263. break;
  264. }
  265. if (res < 0)
  266. return -1;
  267. res = 0;
  268. }
  269. res = 0;
  270. }
  271. /* Nothing was selected */
  272. return 0;
  273. }
  274. 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, char *opts[])
  275. {
  276. struct directory_item **block, *item;
  277. int i, limit, res = 0;
  278. char buf[9];
  279. /* option p(n): cellphone pause option */
  280. select_item_pause(chan, flags, opts);
  281. for (block = items; count; block += limit, count -= limit) {
  282. limit = count;
  283. if (limit > 8)
  284. limit = 8;
  285. for (i = 0; i < limit && !res; i++) {
  286. item = block[i];
  287. snprintf(buf, sizeof(buf), "digits/%d", i + 1);
  288. /* Press <num> for <name>, [ extension <ext> ] */
  289. res = ast_streamfile(chan, "dir-multi1", chan->language);
  290. if (!res)
  291. res = ast_waitstream(chan, AST_DIGIT_ANY);
  292. if (!res)
  293. res = ast_streamfile(chan, buf, chan->language);
  294. if (!res)
  295. res = ast_waitstream(chan, AST_DIGIT_ANY);
  296. if (!res)
  297. res = ast_streamfile(chan, "dir-multi2", chan->language);
  298. if (!res)
  299. res = ast_waitstream(chan, AST_DIGIT_ANY);
  300. if (!res)
  301. res = play_mailbox_owner(chan, context, item->exten, item->name, flags);
  302. if (!res)
  303. res = ast_waitstream(chan, AST_DIGIT_ANY);
  304. if (!res)
  305. res = ast_waitfordigit(chan, 800);
  306. }
  307. /* Press "9" for more names. */
  308. if (!res && count > limit) {
  309. res = ast_streamfile(chan, "dir-multi9", chan->language);
  310. if (!res)
  311. res = ast_waitstream(chan, AST_DIGIT_ANY);
  312. }
  313. if (!res) {
  314. res = ast_waitfordigit(chan, 3000);
  315. }
  316. if (res && res > '0' && res < '1' + limit) {
  317. return select_entry(chan, context, dialcontext, block[res - '1'], flags) ? -1 : 1;
  318. }
  319. if (res < 0)
  320. return -1;
  321. res = 0;
  322. }
  323. /* Nothing was selected */
  324. return 0;
  325. }
  326. static struct ast_config *realtime_directory(char *context)
  327. {
  328. struct ast_config *cfg;
  329. struct ast_config *rtdata;
  330. struct ast_category *cat;
  331. struct ast_variable *var;
  332. char *mailbox;
  333. const char *fullname;
  334. const char *hidefromdir;
  335. char tmp[100];
  336. struct ast_flags config_flags = { 0 };
  337. /* Load flat file config. */
  338. cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
  339. if (!cfg) {
  340. /* Loading config failed. */
  341. ast_log(LOG_WARNING, "Loading config failed.\n");
  342. return NULL;
  343. }
  344. /* Get realtime entries, categorized by their mailbox number
  345. and present in the requested context */
  346. rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, SENTINEL);
  347. /* if there are no results, just return the entries from the config file */
  348. if (!rtdata)
  349. return cfg;
  350. /* Does the context exist within the config file? If not, make one */
  351. cat = ast_category_get(cfg, context);
  352. if (!cat) {
  353. cat = ast_category_new(context, "", 99999);
  354. if (!cat) {
  355. ast_log(LOG_WARNING, "Out of memory\n");
  356. ast_config_destroy(cfg);
  357. if (rtdata) {
  358. ast_config_destroy(rtdata);
  359. }
  360. return NULL;
  361. }
  362. ast_category_append(cfg, cat);
  363. }
  364. mailbox = NULL;
  365. while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) {
  366. fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
  367. if (ast_true((hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir")))) {
  368. /* Skip hidden */
  369. continue;
  370. }
  371. snprintf(tmp, sizeof(tmp), "no-password,%s", S_OR(fullname, ""));
  372. var = ast_variable_new(mailbox, tmp, "");
  373. if (var)
  374. ast_variable_append(cat, var);
  375. else
  376. ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
  377. }
  378. ast_config_destroy(rtdata);
  379. return cfg;
  380. }
  381. static int check_match(struct directory_item **result, const char *item_fullname, const char *item_ext, const char *pattern_ext, int use_first_name)
  382. {
  383. struct directory_item *item;
  384. const char *key = NULL;
  385. int namelen;
  386. /* Set key to last name or first name depending on search mode */
  387. if (!use_first_name)
  388. key = strchr(item_fullname, ' ');
  389. if (key)
  390. key++;
  391. else
  392. key = item_fullname;
  393. if (compare(key, pattern_ext))
  394. return 0;
  395. /* Match */
  396. item = ast_calloc(1, sizeof(*item));
  397. if (!item)
  398. return -1;
  399. ast_copy_string(item->name, item_fullname, sizeof(item->name));
  400. ast_copy_string(item->exten, item_ext, sizeof(item->exten));
  401. ast_copy_string(item->key, key, sizeof(item->key));
  402. if (key != item_fullname) {
  403. /* Key is the last name. Append first name to key in order to sort Last,First */
  404. namelen = key - item_fullname - 1;
  405. if (namelen > sizeof(item->key) - strlen(item->key) - 1)
  406. namelen = sizeof(item->key) - strlen(item->key) - 1;
  407. strncat(item->key, item_fullname, namelen);
  408. }
  409. *result = item;
  410. return 1;
  411. }
  412. typedef AST_LIST_HEAD_NOLOCK(, directory_item) itemlist;
  413. static int search_directory(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, struct ast_flags flags, itemlist *alist)
  414. {
  415. struct ast_variable *v;
  416. char buf[AST_MAX_EXTENSION + 1], *pos, *bufptr, *cat;
  417. struct directory_item *item;
  418. int res;
  419. ast_debug(2, "Pattern: %s\n", ext);
  420. for (v = ast_variable_browse(vmcfg, context); v; v = v->next) {
  421. /* Ignore hidden */
  422. if (strcasestr(v->value, "hidefromdir=yes"))
  423. continue;
  424. ast_copy_string(buf, v->value, sizeof(buf));
  425. bufptr = buf;
  426. /* password,Full Name,email,pager,options */
  427. strsep(&bufptr, ",");
  428. pos = strsep(&bufptr, ",");
  429. /* No name to compare against */
  430. if (ast_strlen_zero(pos)) {
  431. continue;
  432. }
  433. res = 0;
  434. if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
  435. res = check_match(&item, pos, v->name, ext, 0 /* use_first_name */);
  436. }
  437. if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
  438. res = check_match(&item, pos, v->name, ext, 1 /* use_first_name */);
  439. }
  440. if (!res)
  441. continue;
  442. else if (res < 0)
  443. return -1;
  444. AST_LIST_INSERT_TAIL(alist, item, entry);
  445. }
  446. if (ucfg) {
  447. for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) {
  448. const char *position;
  449. if (!strcasecmp(cat, "general"))
  450. continue;
  451. if (!ast_true(ast_config_option(ucfg, cat, "hasdirectory")))
  452. continue;
  453. /* Find all candidate extensions */
  454. position = ast_variable_retrieve(ucfg, cat, "fullname");
  455. if (!position)
  456. continue;
  457. res = 0;
  458. if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
  459. res = check_match(&item, position, cat, ext, 0 /* use_first_name */);
  460. }
  461. if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
  462. res = check_match(&item, position, cat, ext, 1 /* use_first_name */);
  463. }
  464. if (!res)
  465. continue;
  466. else if (res < 0)
  467. return -1;
  468. AST_LIST_INSERT_TAIL(alist, item, entry);
  469. }
  470. }
  471. return 0;
  472. }
  473. static void sort_items(struct directory_item **sorted, int count)
  474. {
  475. int reordered, i;
  476. struct directory_item **ptr, *tmp;
  477. if (count < 2)
  478. return;
  479. /* Bubble-sort items by the key */
  480. do {
  481. reordered = 0;
  482. for (ptr = sorted, i = 0; i < count - 1; i++, ptr++) {
  483. if (strcasecmp(ptr[0]->key, ptr[1]->key) > 0) {
  484. tmp = ptr[0];
  485. ptr[0] = ptr[1];
  486. ptr[1] = tmp;
  487. reordered++;
  488. }
  489. }
  490. } while (reordered);
  491. }
  492. static int goto_exten(struct ast_channel *chan, const char *dialcontext, char *ext)
  493. {
  494. if (!ast_goto_if_exists(chan, dialcontext, ext, 1) ||
  495. (!ast_strlen_zero(chan->macrocontext) &&
  496. !ast_goto_if_exists(chan, chan->macrocontext, ext, 1))) {
  497. return 0;
  498. } else {
  499. ast_log(LOG_WARNING, "Can't find extension '%s' in current context. "
  500. "Not Exiting the Directory!\n", ext);
  501. return -1;
  502. }
  503. }
  504. 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[])
  505. {
  506. /* Read in the first three digits.. "digit" is the first digit, already read */
  507. int res = 0;
  508. itemlist alist = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
  509. struct directory_item *item, **ptr, **sorted = NULL;
  510. int count, i;
  511. char ext[10] = "";
  512. if (ast_strlen_zero(context)) {
  513. ast_log(LOG_WARNING,
  514. "Directory must be called with an argument "
  515. "(context in which to interpret extensions)\n");
  516. return -1;
  517. }
  518. if (digit == '0' && !goto_exten(chan, dialcontext, "o")) {
  519. return digit;
  520. }
  521. if (digit == '*' && !goto_exten(chan, dialcontext, "a")) {
  522. return digit;
  523. }
  524. ext[0] = digit;
  525. if (ast_readstring(chan, ext + 1, digits - 1, 3000, 3000, "#") < 0)
  526. return -1;
  527. res = search_directory(context, vmcfg, ucfg, ext, *flags, &alist);
  528. if (res)
  529. goto exit;
  530. /* Count items in the list */
  531. count = 0;
  532. AST_LIST_TRAVERSE(&alist, item, entry) {
  533. count++;
  534. }
  535. if (count < 1) {
  536. res = ast_streamfile(chan, "dir-nomatch", chan->language);
  537. goto exit;
  538. }
  539. /* Create plain array of pointers to items (for sorting) */
  540. sorted = ast_calloc(count, sizeof(*sorted));
  541. ptr = sorted;
  542. AST_LIST_TRAVERSE(&alist, item, entry) {
  543. *ptr++ = item;
  544. }
  545. /* Sort items */
  546. sort_items(sorted, count);
  547. if (option_debug) {
  548. ast_debug(2, "Listing matching entries:\n");
  549. for (ptr = sorted, i = 0; i < count; i++, ptr++) {
  550. ast_log(LOG_DEBUG, "%s: %s\n", ptr[0]->exten, ptr[0]->name);
  551. }
  552. }
  553. if (ast_test_flag(flags, OPT_SELECTFROMMENU)) {
  554. /* Offer multiple entries at the same time */
  555. res = select_item_menu(chan, sorted, count, context, dialcontext, flags, opts);
  556. } else {
  557. /* Offer entries one by one */
  558. res = select_item_seq(chan, sorted, count, context, dialcontext, flags, opts);
  559. }
  560. if (!res) {
  561. res = ast_streamfile(chan, "dir-nomore", chan->language);
  562. }
  563. exit:
  564. if (sorted)
  565. ast_free(sorted);
  566. while ((item = AST_LIST_REMOVE_HEAD(&alist, entry)))
  567. ast_free(item);
  568. return res;
  569. }
  570. static int directory_exec(struct ast_channel *chan, void *data)
  571. {
  572. int res = 0, digit = 3;
  573. struct ast_config *cfg, *ucfg;
  574. const char *dirintro;
  575. char *parse, *opts[OPT_ARG_ARRAY_SIZE] = { "", };
  576. struct ast_flags flags = { 0 };
  577. struct ast_flags config_flags = { 0 };
  578. enum { FIRST, LAST, BOTH } which = LAST;
  579. char digits[9] = "digits/3";
  580. AST_DECLARE_APP_ARGS(args,
  581. AST_APP_ARG(vmcontext);
  582. AST_APP_ARG(dialcontext);
  583. AST_APP_ARG(options);
  584. );
  585. if (ast_strlen_zero(data)) {
  586. ast_log(LOG_WARNING, "Directory requires an argument (context[,dialcontext])\n");
  587. return -1;
  588. }
  589. parse = ast_strdupa(data);
  590. AST_STANDARD_APP_ARGS(args, parse);
  591. if (args.options && ast_app_parse_options(directory_app_options, &flags, opts, args.options))
  592. return -1;
  593. if (ast_strlen_zero(args.dialcontext))
  594. args.dialcontext = args.vmcontext;
  595. cfg = realtime_directory(args.vmcontext);
  596. if (!cfg) {
  597. ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
  598. return -1;
  599. }
  600. ucfg = ast_config_load("users.conf", config_flags);
  601. dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro");
  602. if (ast_strlen_zero(dirintro))
  603. dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
  604. if (ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) && ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
  605. if (!ast_strlen_zero(opts[OPT_ARG_EITHER])) {
  606. digit = atoi(opts[OPT_ARG_EITHER]);
  607. }
  608. which = BOTH;
  609. } else if (ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
  610. if (!ast_strlen_zero(opts[OPT_ARG_FIRSTNAME])) {
  611. digit = atoi(opts[OPT_ARG_FIRSTNAME]);
  612. }
  613. which = FIRST;
  614. } else {
  615. if (!ast_strlen_zero(opts[OPT_ARG_LASTNAME])) {
  616. digit = atoi(opts[OPT_ARG_LASTNAME]);
  617. }
  618. which = LAST;
  619. }
  620. /* If no options specified, search by last name */
  621. if (!ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) && !ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
  622. ast_set_flag(&flags, OPT_LISTBYLASTNAME);
  623. which = LAST;
  624. }
  625. if (digit > 9) {
  626. digit = 9;
  627. } else if (digit < 1) {
  628. digit = 3;
  629. }
  630. digits[7] = digit + '0';
  631. if (chan->_state != AST_STATE_UP)
  632. res = ast_answer(chan);
  633. for (;;) {
  634. if (!ast_strlen_zero(dirintro) && !res) {
  635. res = ast_stream_and_wait(chan, dirintro, AST_DIGIT_ANY);
  636. } else if (!res) {
  637. /* Stop playing sounds as soon as we have a digit. */
  638. res = ast_stream_and_wait(chan, "dir-welcome", AST_DIGIT_ANY);
  639. if (!res) {
  640. res = ast_stream_and_wait(chan, "dir-pls-enter", AST_DIGIT_ANY);
  641. }
  642. if (!res) {
  643. res = ast_stream_and_wait(chan, digits, AST_DIGIT_ANY);
  644. }
  645. if (!res) {
  646. res = ast_stream_and_wait(chan,
  647. which == FIRST ? "dir-first" :
  648. which == LAST ? "dir-last" :
  649. "dir-firstlast", AST_DIGIT_ANY);
  650. }
  651. if (!res) {
  652. res = ast_stream_and_wait(chan, "dir-usingkeypad", AST_DIGIT_ANY);
  653. }
  654. }
  655. ast_stopstream(chan);
  656. if (!res)
  657. res = ast_waitfordigit(chan, 5000);
  658. if (res <= 0)
  659. break;
  660. res = do_directory(chan, cfg, ucfg, args.vmcontext, args.dialcontext, res, digit, &flags, opts);
  661. if (res)
  662. break;
  663. res = ast_waitstream(chan, AST_DIGIT_ANY);
  664. ast_stopstream(chan);
  665. if (res)
  666. break;
  667. }
  668. if (ucfg)
  669. ast_config_destroy(ucfg);
  670. ast_config_destroy(cfg);
  671. return res < 0 ? -1 : 0;
  672. }
  673. static int unload_module(void)
  674. {
  675. int res;
  676. res = ast_unregister_application(app);
  677. return res;
  678. }
  679. static int load_module(void)
  680. {
  681. return ast_register_application(app, directory_exec, synopsis, descrip);
  682. }
  683. AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Directory");