app_directory.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2005, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  *
00021  * \brief Provide a directory of extensions
00022  *
00023  * \author Mark Spencer <markster@digium.com>
00024  *
00025  * \ingroup applications
00026  */
00027 
00028 /*** MODULEINFO
00029    <support_level>core</support_level>
00030  ***/
00031 #include "asterisk.h"
00032 
00033 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 425385 $")
00034 
00035 #include <ctype.h>
00036 
00037 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
00038 #include "asterisk/file.h"
00039 #include "asterisk/pbx.h"
00040 #include "asterisk/module.h"
00041 #include "asterisk/say.h"
00042 #include "asterisk/app.h"
00043 #include "asterisk/utils.h"
00044 
00045 /*** DOCUMENTATION
00046    <application name="Directory" language="en_US">
00047       <synopsis>
00048          Provide directory of voicemail extensions.
00049       </synopsis>
00050       <syntax>
00051          <parameter name="vm-context">
00052             <para>This is the context within voicemail.conf to use for the Directory. If not 
00053             specified and <literal>searchcontexts=no</literal> in 
00054             <filename>voicemail.conf</filename>, then <literal>default</literal> 
00055             will be assumed.</para>
00056          </parameter>
00057          <parameter name="dial-context" required="false">
00058             <para>This is the dialplan context to use when looking for an
00059             extension that the user has selected, or when jumping to the
00060             <literal>o</literal> or <literal>a</literal> extension. If not
00061             specified, the current context will be used.</para>
00062          </parameter>
00063          <parameter name="options" required="false">
00064             <optionlist>
00065                <option name="e">
00066                   <para>In addition to the name, also read the extension number to the
00067                   caller before presenting dialing options.</para>
00068                </option>
00069                <option name="f">
00070                   <para>Allow the caller to enter the first name of a user in the
00071                   directory instead of using the last name.  If specified, the
00072                   optional number argument will be used for the number of
00073                   characters the user should enter.</para>
00074                   <argument name="n" required="true" />
00075                </option>
00076                <option name="l">
00077                   <para>Allow the caller to enter the last name of a user in the
00078                   directory.  This is the default.  If specified, the
00079                   optional number argument will be used for the number of
00080                   characters the user should enter.</para>
00081                   <argument name="n" required="true" />
00082                </option>
00083                <option name="b">
00084                   <para> Allow the caller to enter either the first or the last name
00085                   of a user in the directory.  If specified, the optional number
00086                   argument will be used for the number of characters the user should enter.</para>
00087                   <argument name="n" required="true" />
00088                </option>
00089                <option name="a">
00090                   <para>Allow the caller to additionally enter an alias for a user in the
00091                   directory.  This option must be specified in addition to the
00092                   <literal>f</literal>, <literal>l</literal>, or <literal>b</literal>
00093                   option.</para>
00094                </option>
00095                <option name="m">
00096                   <para>Instead of reading each name sequentially and asking for
00097                   confirmation, create a menu of up to 8 names.</para>
00098                </option>
00099                <option name="n">
00100                   <para>Read digits even if the channel is not answered.</para>
00101                </option>
00102                <option name="p">
00103                   <para>Pause for n milliseconds after the digits are typed.  This is
00104                   helpful for people with cellphones, who are not holding the
00105                   receiver to their ear while entering DTMF.</para>
00106                   <argument name="n" required="true" />
00107                </option>
00108             </optionlist>
00109             <note><para>Only one of the <replaceable>f</replaceable>, <replaceable>l</replaceable>, or <replaceable>b</replaceable>
00110             options may be specified. <emphasis>If more than one is specified</emphasis>, then Directory will act as 
00111             if <replaceable>b</replaceable> was specified.  The number
00112             of characters for the user to type defaults to <literal>3</literal>.</para></note>
00113 
00114          </parameter>
00115       </syntax>
00116       <description>
00117          <para>This application will present the calling channel with a directory of extensions from which they can search
00118          by name. The list of names and corresponding extensions is retrieved from the
00119          voicemail configuration file, <filename>voicemail.conf</filename>.</para>
00120          <para>This application will immediately exit if one of the following DTMF digits are
00121          received and the extension to jump to exists:</para>
00122          <para><literal>0</literal> - Jump to the 'o' extension, if it exists.</para>
00123          <para><literal>*</literal> - Jump to the 'a' extension, if it exists.</para>
00124          <para>This application will set the following channel variable before completion:</para>
00125          <variablelist>
00126             <variable name="DIRECTORY_RESULT">
00127                <para>Reason Directory application exited.</para>
00128                <value name="OPERATOR">User requested operator</value>
00129                <value name="ASSISTANT">User requested assistant</value>
00130                <value name="TIMEOUT">User allowed DTMF wait duration to pass without sending DTMF</value>
00131                <value name="HANGUP">The channel hung up before the application finished</value>
00132                <value name="SELECTED">User selected a user to call from the directory</value>
00133                <value name="USEREXIT">User exited with '#' during selection</value>
00134                <value name="FAILED">The application failed</value>
00135             </variable>
00136          </variablelist>
00137       </description>
00138    </application>
00139 
00140  ***/
00141 static const char app[] = "Directory";
00142 
00143 /* For simplicity, I'm keeping the format compatible with the voicemail config,
00144    but i'm open to suggestions for isolating it */
00145 
00146 #define VOICEMAIL_CONFIG "voicemail.conf"
00147 
00148 enum {
00149    OPT_LISTBYFIRSTNAME = (1 << 0),
00150    OPT_SAYEXTENSION =    (1 << 1),
00151    OPT_FROMVOICEMAIL =   (1 << 2),
00152    OPT_SELECTFROMMENU =  (1 << 3),
00153    OPT_LISTBYLASTNAME =  (1 << 4),
00154    OPT_LISTBYEITHER =    OPT_LISTBYFIRSTNAME | OPT_LISTBYLASTNAME,
00155    OPT_PAUSE =           (1 << 5),
00156    OPT_NOANSWER =        (1 << 6),
00157    OPT_ALIAS =           (1 << 7),
00158 };
00159 
00160 enum {
00161    OPT_ARG_FIRSTNAME =   0,
00162    OPT_ARG_LASTNAME =    1,
00163    OPT_ARG_EITHER =      2,
00164    OPT_ARG_PAUSE =       3,
00165    /* This *must* be the last value in this enum! */
00166    OPT_ARG_ARRAY_SIZE =  4,
00167 };
00168 
00169 struct directory_item {
00170    char exten[AST_MAX_EXTENSION + 1];
00171    char name[AST_MAX_EXTENSION + 1];
00172    char context[AST_MAX_CONTEXT + 1];
00173    char key[50]; /* Text to order items. Either lastname+firstname or firstname+lastname */
00174 
00175    AST_LIST_ENTRY(directory_item) entry;
00176 };
00177 
00178 AST_APP_OPTIONS(directory_app_options, {
00179    AST_APP_OPTION_ARG('f', OPT_LISTBYFIRSTNAME, OPT_ARG_FIRSTNAME),
00180    AST_APP_OPTION_ARG('l', OPT_LISTBYLASTNAME, OPT_ARG_LASTNAME),
00181    AST_APP_OPTION_ARG('b', OPT_LISTBYEITHER, OPT_ARG_EITHER),
00182    AST_APP_OPTION_ARG('p', OPT_PAUSE, OPT_ARG_PAUSE),
00183    AST_APP_OPTION('e', OPT_SAYEXTENSION),
00184    AST_APP_OPTION('v', OPT_FROMVOICEMAIL),
00185    AST_APP_OPTION('m', OPT_SELECTFROMMENU),
00186    AST_APP_OPTION('n', OPT_NOANSWER),
00187    AST_APP_OPTION('a', OPT_ALIAS),
00188 });
00189 
00190 static int compare(const char *text, const char *template)
00191 {
00192    char digit;
00193 
00194    if (ast_strlen_zero(text)) {
00195       return -1;
00196    }
00197 
00198    while (*template) {
00199       digit = toupper(*text++);
00200       switch (digit) {
00201       case 0:
00202          return -1;
00203       case '1':
00204          digit = '1';
00205          break;
00206       case '2':
00207       case 'A':
00208       case 'B':
00209       case 'C':
00210          digit = '2';
00211          break;
00212       case '3':
00213       case 'D':
00214       case 'E':
00215       case 'F':
00216          digit = '3';
00217          break;
00218       case '4':
00219       case 'G':
00220       case 'H':
00221       case 'I':
00222          digit = '4';
00223          break;
00224       case '5':
00225       case 'J':
00226       case 'K':
00227       case 'L':
00228          digit = '5';
00229          break;
00230       case '6':
00231       case 'M':
00232       case 'N':
00233       case 'O':
00234          digit = '6';
00235          break;
00236       case '7':
00237       case 'P':
00238       case 'Q':
00239       case 'R':
00240       case 'S':
00241          digit = '7';
00242          break;
00243       case '8':
00244       case 'T':
00245       case 'U':
00246       case 'V':
00247          digit = '8';
00248          break;
00249       case '9':
00250       case 'W':
00251       case 'X':
00252       case 'Y':
00253       case 'Z':
00254          digit = '9';
00255          break;
00256 
00257       default:
00258          if (digit > ' ')
00259             return -1;
00260          continue;
00261       }
00262 
00263       if (*template++ != digit)
00264          return -1;
00265    }
00266 
00267    return 0;
00268 }
00269 
00270 static int goto_exten(struct ast_channel *chan, const char *dialcontext, char *ext)
00271 {
00272    if (!ast_goto_if_exists(chan, S_OR(dialcontext, ast_channel_context(chan)), ext, 1) ||
00273       (!ast_strlen_zero(ast_channel_macrocontext(chan)) &&
00274       !ast_goto_if_exists(chan, ast_channel_macrocontext(chan), ext, 1))) {
00275       return 0;
00276    } else {
00277       ast_log(LOG_WARNING, "Can't find extension '%s' in current context.  "
00278          "Not Exiting the Directory!\n", ext);
00279       return -1;
00280    }
00281 }
00282 
00283 /* play name of mailbox owner.
00284  * returns:  -1 for bad or missing extension
00285  *           '1' for selected entry from directory
00286  *           '*' for skipped entry from directory
00287  */
00288 static int play_mailbox_owner(struct ast_channel *chan, const char *context,
00289    const char *ext, const char *name, struct ast_flags *flags)
00290 {
00291    int res = 0;
00292    char *mailbox_id;
00293 
00294    mailbox_id = ast_alloca(strlen(ext) + strlen(context) + 2);
00295    sprintf(mailbox_id, "%s@%s", ext, context); /* Safe */
00296 
00297    res = ast_app_sayname(chan, mailbox_id);
00298    if (res >= 0) {
00299       ast_stopstream(chan);
00300       /* If Option 'e' was specified, also read the extension number with the name */
00301       if (ast_test_flag(flags, OPT_SAYEXTENSION)) {
00302          ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
00303          res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, ast_channel_language(chan), AST_SAY_CASE_NONE);
00304       }
00305    } else {
00306       res = ast_say_character_str(chan, S_OR(name, ext), AST_DIGIT_ANY, ast_channel_language(chan), AST_SAY_CASE_NONE);
00307       if (!ast_strlen_zero(name) && ast_test_flag(flags, OPT_SAYEXTENSION)) {
00308          ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
00309          res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, ast_channel_language(chan), AST_SAY_CASE_NONE);
00310       }
00311    }
00312 
00313    return res;
00314 }
00315 
00316 static int select_entry(struct ast_channel *chan, const char *dialcontext, const struct directory_item *item, struct ast_flags *flags)
00317 {
00318    ast_debug(1, "Selecting '%s' - %s@%s\n", item->name, item->exten, S_OR(dialcontext, item->context));
00319 
00320    if (ast_test_flag(flags, OPT_FROMVOICEMAIL)) {
00321       /* We still want to set the exten though */
00322       ast_channel_exten_set(chan, item->exten);
00323    } else if (ast_goto_if_exists(chan, S_OR(dialcontext, item->context), item->exten, 1)) {
00324       ast_log(LOG_WARNING,
00325          "Can't find extension '%s' in context '%s'.  "
00326          "Did you pass the wrong context to Directory?\n",
00327          item->exten, S_OR(dialcontext, item->context));
00328       return -1;
00329    }
00330 
00331    pbx_builtin_setvar_helper(chan, "DIRECTORY_RESULT", "SELECTED");
00332    return 0;
00333 }
00334 
00335 static int select_item_pause(struct ast_channel *chan, struct ast_flags *flags, char *opts[])
00336 {
00337    int res = 0, opt_pause = 0;
00338 
00339    if (ast_test_flag(flags, OPT_PAUSE) && !ast_strlen_zero(opts[OPT_ARG_PAUSE])) {
00340       opt_pause = atoi(opts[OPT_ARG_PAUSE]);
00341       if (opt_pause > 3000) {
00342          opt_pause = 3000;
00343       }
00344       res = ast_waitfordigit(chan, opt_pause);
00345    }
00346    return res;
00347 }
00348 
00349 static int select_item_seq(struct ast_channel *chan, struct directory_item **items, int count, const char *dialcontext, struct ast_flags *flags, char *opts[])
00350 {
00351    struct directory_item *item, **ptr;
00352    int i, res, loop;
00353 
00354    /* option p(n): cellphone pause option */
00355    /* allow early press of selection key */
00356    res = select_item_pause(chan, flags, opts);
00357 
00358    for (ptr = items, i = 0; i < count; i++, ptr++) {
00359       item = *ptr;
00360 
00361       for (loop = 3 ; loop > 0; loop--) {
00362          if (!res)
00363             res = play_mailbox_owner(chan, item->context, item->exten, item->name, flags);
00364          if (!res)
00365             res = ast_stream_and_wait(chan, "dir-instr", AST_DIGIT_ANY);
00366          if (!res)
00367             res = ast_waitfordigit(chan, 3000);
00368          ast_stopstream(chan);
00369    
00370          if (res == '0') { /* operator selected */
00371             goto_exten(chan, dialcontext, "o");
00372             pbx_builtin_setvar_helper(chan, "DIRECTORY_RESULT", "OPERATOR");
00373             return '0';
00374          } else if (res == '1') { /* Name selected */
00375             return select_entry(chan, dialcontext, item, flags) ? -1 : 1;
00376          } else if (res == '*') {
00377             /* Skip to next match in list */
00378             break;
00379          } else if (res == '#') {
00380             /* Exit reading, continue in dialplan */
00381             pbx_builtin_setvar_helper(chan, "DIRECTORY_RESULT", "USEREXIT");
00382             return res;
00383          }
00384 
00385          if (res < 0)
00386             return -1;
00387 
00388          res = 0;
00389       }
00390       res = 0;
00391    }
00392 
00393    /* Nothing was selected */
00394    return 0;
00395 }
00396 
00397 static int select_item_menu(struct ast_channel *chan, struct directory_item **items, int count, const char *dialcontext, struct ast_flags *flags, char *opts[])
00398 {
00399    struct directory_item **block, *item;
00400    int i, limit, res = 0;
00401    char buf[9];
00402 
00403    /* option p(n): cellphone pause option */
00404    select_item_pause(chan, flags, opts);
00405 
00406    for (block = items; count; block += limit, count -= limit) {
00407       limit = count;
00408       if (limit > 8)
00409          limit = 8;
00410 
00411       for (i = 0; i < limit && !res; i++) {
00412          item = block[i];
00413 
00414          snprintf(buf, sizeof(buf), "digits/%d", i + 1);
00415          /* Press <num> for <name>, [ extension <ext> ] */
00416          res = ast_streamfile(chan, "dir-multi1", ast_channel_language(chan));
00417          if (!res)
00418             res = ast_waitstream(chan, AST_DIGIT_ANY);
00419          if (!res)
00420             res = ast_streamfile(chan, buf, ast_channel_language(chan));
00421          if (!res)
00422             res = ast_waitstream(chan, AST_DIGIT_ANY);
00423          if (!res)
00424             res = ast_streamfile(chan, "dir-multi2", ast_channel_language(chan));
00425          if (!res)
00426             res = ast_waitstream(chan, AST_DIGIT_ANY);
00427          if (!res)
00428             res = play_mailbox_owner(chan, item->context, item->exten, item->name, flags);
00429          if (!res)
00430             res = ast_waitstream(chan, AST_DIGIT_ANY);
00431          if (!res)
00432             res = ast_waitfordigit(chan, 800);
00433       }
00434 
00435       /* Press "9" for more names. */
00436       if (!res && count > limit) {
00437          res = ast_streamfile(chan, "dir-multi9", ast_channel_language(chan));
00438          if (!res)
00439             res = ast_waitstream(chan, AST_DIGIT_ANY);
00440       }
00441 
00442       if (!res) {
00443          res = ast_waitfordigit(chan, 3000);
00444       }
00445 
00446       if (res && res > '0' && res < '1' + limit) {
00447          pbx_builtin_setvar_helper(chan, "DIRECTORY_RESULT", "SELECTED");
00448          return select_entry(chan, dialcontext, block[res - '1'], flags) ? -1 : 1;
00449       }
00450 
00451       if (res < 0)
00452          return -1;
00453 
00454       res = 0;
00455    }
00456 
00457    /* Nothing was selected */
00458    return 0;
00459 }
00460 
00461 AST_THREADSTORAGE(commonbuf);
00462 
00463 static struct ast_config *realtime_directory(char *context)
00464 {
00465    struct ast_config *cfg;
00466    struct ast_config *rtdata = NULL;
00467    struct ast_category *cat;
00468    struct ast_variable *var;
00469    char *mailbox;
00470    const char *fullname;
00471    const char *hidefromdir, *searchcontexts = NULL;
00472    struct ast_flags config_flags = { 0 };
00473    struct ast_str *tmp = ast_str_thread_get(&commonbuf, 100);
00474 
00475    if (!tmp) {
00476       return NULL;
00477    }
00478 
00479    /* Load flat file config. */
00480    cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
00481 
00482    if (!cfg) {
00483       /* Loading config failed. */
00484       ast_log(LOG_WARNING, "Loading config failed.\n");
00485       return NULL;
00486    } else if (cfg == CONFIG_STATUS_FILEINVALID) {
00487       ast_log(LOG_ERROR, "Config file %s is in an invalid format.  Aborting.\n", VOICEMAIL_CONFIG);
00488       return NULL;
00489    }
00490 
00491    /* Get realtime entries, categorized by their mailbox number
00492       and present in the requested context */
00493    if (ast_strlen_zero(context) && (searchcontexts = ast_variable_retrieve(cfg, "general", "searchcontexts"))) {
00494       if (ast_true(searchcontexts)) {
00495          rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", SENTINEL);
00496          context = NULL;
00497       } else {
00498          rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", "default", SENTINEL);
00499          context = "default";
00500       }
00501    } else if (!ast_strlen_zero(context)) {
00502       rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, SENTINEL);
00503    }
00504 
00505    /* if there are no results, just return the entries from the config file */
00506    if (!rtdata) {
00507       return cfg;
00508    }
00509 
00510    mailbox = NULL;
00511    while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) {
00512       struct ast_variable *alias;
00513       const char *ctx = ast_variable_retrieve(rtdata, mailbox, "context");
00514 
00515       fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
00516       hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir");
00517       if (ast_true(hidefromdir)) {
00518          /* Skip hidden */
00519          continue;
00520       }
00521       ast_str_set(&tmp, 0, "no-password,%s", S_OR(fullname, ""));
00522       if (ast_variable_retrieve(rtdata, mailbox, "alias")) {
00523          for (alias = ast_variable_browse(rtdata, mailbox); alias; alias = alias->next) {
00524             if (!strcasecmp(alias->name, "alias")) {
00525                ast_str_append(&tmp, 0, "|alias=%s", alias->value);
00526             }
00527          }
00528       }
00529 
00530       /* Does the context exist within the config file? If not, make one */
00531       if (!(cat = ast_category_get(cfg, ctx, NULL))) {
00532          if (!(cat = ast_category_new(ctx, "", 99999))) {
00533             ast_log(LOG_WARNING, "Out of memory\n");
00534             ast_config_destroy(cfg);
00535             if (rtdata) {
00536                ast_config_destroy(rtdata);
00537             }
00538             return NULL;
00539          }
00540          ast_category_append(cfg, cat);
00541       }
00542 
00543       if ((var = ast_variable_new(mailbox, ast_str_buffer(tmp), ""))) {
00544          ast_variable_append(cat, var);
00545       } else {
00546          ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
00547       }
00548    }
00549    ast_config_destroy(rtdata);
00550 
00551    return cfg;
00552 }
00553 
00554 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)
00555 {
00556    struct directory_item *item;
00557    const char *key = NULL;
00558    int namelen;
00559 
00560    if (ast_strlen_zero(item_fullname)) {
00561       return 0;
00562    }
00563 
00564    /* Set key to last name or first name depending on search mode */
00565    if (!use_first_name)
00566       key = strchr(item_fullname, ' ');
00567 
00568    if (key)
00569       key++;
00570    else
00571       key = item_fullname;
00572 
00573    if (compare(key, pattern_ext))
00574       return 0;
00575 
00576    ast_debug(1, "Found match %s@%s\n", item_ext, item_context);
00577 
00578    /* Match */
00579    item = ast_calloc(1, sizeof(*item));
00580    if (!item)
00581       return -1;
00582    ast_copy_string(item->context, item_context, sizeof(item->context));
00583    ast_copy_string(item->name, item_fullname, sizeof(item->name));
00584    ast_copy_string(item->exten, item_ext, sizeof(item->exten));
00585 
00586    ast_copy_string(item->key, key, sizeof(item->key));
00587    if (key != item_fullname) {
00588       /* Key is the last name. Append first name to key in order to sort Last,First */
00589       namelen = key - item_fullname - 1;
00590       if (namelen > sizeof(item->key) - strlen(item->key) - 1)
00591          namelen = sizeof(item->key) - strlen(item->key) - 1;
00592       strncat(item->key, item_fullname, namelen);
00593    }
00594 
00595    *result = item;
00596    return 1;
00597 }
00598 
00599 typedef AST_LIST_HEAD_NOLOCK(, directory_item) itemlist;
00600 
00601 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)
00602 {
00603    struct ast_variable *v;
00604    struct ast_str *buf = ast_str_thread_get(&commonbuf, 100);
00605    char *pos, *bufptr, *cat, *alias;
00606    struct directory_item *item;
00607    int res;
00608 
00609    if (!buf) {
00610       return -1;
00611    }
00612 
00613    ast_debug(2, "Pattern: %s\n", ext);
00614 
00615    for (v = ast_variable_browse(vmcfg, context); v; v = v->next) {
00616 
00617       /* Ignore hidden */
00618       if (strcasestr(v->value, "hidefromdir=yes")) {
00619          continue;
00620       }
00621 
00622       ast_str_set(&buf, 0, "%s", v->value);
00623       bufptr = ast_str_buffer(buf);
00624 
00625       /* password,Full Name,email,pager,options */
00626       strsep(&bufptr, ",");
00627       pos = strsep(&bufptr, ",");
00628 
00629       /* No name to compare against */
00630       if (ast_strlen_zero(pos)) {
00631          continue;
00632       }
00633 
00634       res = 0;
00635       if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
00636          res = check_match(&item, context, pos, v->name, ext, 0 /* use_first_name */);
00637       }
00638       if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
00639          res = check_match(&item, context, pos, v->name, ext, 1 /* use_first_name */);
00640       }
00641       if (!res && ast_test_flag(&flags, OPT_ALIAS) && (alias = strcasestr(bufptr, "alias="))) {
00642          char *a;
00643          ast_debug(1, "Found alias: %s\n", alias);
00644          while ((a = strsep(&alias, "|"))) {
00645             if (!strncasecmp(a, "alias=", 6)) {
00646                if ((res = check_match(&item, context, a + 6, v->name, ext, 1))) {
00647                   break;
00648                }
00649             }
00650          }
00651       }
00652 
00653       if (!res) {
00654          continue;
00655       } else if (res < 0) {
00656          return -1;
00657       }
00658 
00659       AST_LIST_INSERT_TAIL(alist, item, entry);
00660    }
00661 
00662    if (ucfg) {
00663       for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) {
00664          const char *position;
00665 
00666          if (!strcasecmp(cat, "general")) {
00667             continue;
00668          }
00669          if (!ast_true(ast_config_option(ucfg, cat, "hasdirectory"))) {
00670             continue;
00671          }
00672 
00673          /* Find all candidate extensions */
00674          if (!(position = ast_variable_retrieve(ucfg, cat, "fullname"))) {
00675             continue;
00676          }
00677 
00678          res = 0;
00679          if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
00680             res = check_match(&item, context, position, cat, ext, 0 /* use_first_name */);
00681          }
00682          if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
00683             res = check_match(&item, context, position, cat, ext, 1 /* use_first_name */);
00684          }
00685          if (!res && ast_test_flag(&flags, OPT_ALIAS)) {
00686             struct ast_variable *alias;
00687             for (alias = ast_variable_browse(ucfg, cat); alias; alias = alias->next) {
00688                if (!strcasecmp(v->name, "alias") && (res = check_match(&item, context, v->value, cat, ext, 1))) {
00689                   break;
00690                }
00691             }
00692          }
00693 
00694          if (!res) {
00695             continue;
00696          } else if (res < 0) {
00697             return -1;
00698          }
00699 
00700          AST_LIST_INSERT_TAIL(alist, item, entry);
00701       }
00702    }
00703    return 0;
00704 }
00705 
00706 static int search_directory(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, struct ast_flags flags, itemlist *alist)
00707 {
00708    const char *searchcontexts = ast_variable_retrieve(vmcfg, "general", "searchcontexts");
00709    if (ast_strlen_zero(context)) {
00710       if (!ast_strlen_zero(searchcontexts) && ast_true(searchcontexts)) {
00711          /* Browse each context for a match */
00712          int res;
00713          const char *catg;
00714          for (catg = ast_category_browse(vmcfg, NULL); catg; catg = ast_category_browse(vmcfg, catg)) {
00715             if (!strcmp(catg, "general") || !strcmp(catg, "zonemessages")) {
00716                continue;
00717             }
00718 
00719             if ((res = search_directory_sub(catg, vmcfg, ucfg, ext, flags, alist))) {
00720                return res;
00721             }
00722          }
00723          return 0;
00724       } else {
00725          ast_debug(1, "Searching by category default\n");
00726          return search_directory_sub("default", vmcfg, ucfg, ext, flags, alist);
00727       }
00728    } else {
00729       /* Browse only the listed context for a match */
00730       ast_debug(1, "Searching by category %s\n", context);
00731       return search_directory_sub(context, vmcfg, ucfg, ext, flags, alist);
00732    }
00733 }
00734 
00735 static void sort_items(struct directory_item **sorted, int count)
00736 {
00737    int reordered, i;
00738    struct directory_item **ptr, *tmp;
00739 
00740    if (count < 2)
00741       return;
00742 
00743    /* Bubble-sort items by the key */
00744    do {
00745       reordered = 0;
00746       for (ptr = sorted, i = 0; i < count - 1; i++, ptr++) {
00747          if (strcasecmp(ptr[0]->key, ptr[1]->key) > 0) {
00748             tmp = ptr[0];
00749             ptr[0] = ptr[1];
00750             ptr[1] = tmp;
00751             reordered++;
00752          }
00753       }
00754    } while (reordered);
00755 }
00756 
00757 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[])
00758 {
00759    /* Read in the first three digits..  "digit" is the first digit, already read */
00760    int res = 0;
00761    itemlist alist = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
00762    struct directory_item *item, **ptr, **sorted = NULL;
00763    int count, i;
00764    char ext[10] = "";
00765 
00766    if (digit == '0' && !goto_exten(chan, dialcontext, "o")) {
00767       pbx_builtin_setvar_helper(chan, "DIRECTORY_RESULT", "OPERATOR");
00768       return digit;
00769    }
00770 
00771    if (digit == '*' && !goto_exten(chan, dialcontext, "a")) {
00772       pbx_builtin_setvar_helper(chan, "DIRECTORY_RESULT", "ASSISTANT");
00773       return digit;
00774    }
00775 
00776    ext[0] = digit;
00777    if (ast_readstring(chan, ext + 1, digits - 1, 3000, 3000, "#") < 0)
00778       return -1;
00779 
00780    res = search_directory(context, vmcfg, ucfg, ext, *flags, &alist);
00781    if (res)
00782       goto exit;
00783 
00784    /* Count items in the list */
00785    count = 0;
00786    AST_LIST_TRAVERSE(&alist, item, entry) {
00787       count++;
00788    }
00789 
00790    if (count < 1) {
00791       res = ast_streamfile(chan, "dir-nomatch", ast_channel_language(chan));
00792       goto exit;
00793    }
00794 
00795 
00796    /* Create plain array of pointers to items (for sorting) */
00797    sorted = ast_calloc(count, sizeof(*sorted));
00798 
00799    ptr = sorted;
00800    AST_LIST_TRAVERSE(&alist, item, entry) {
00801       *ptr++ = item;
00802    }
00803 
00804    /* Sort items */
00805    sort_items(sorted, count);
00806 
00807    if (option_debug) {
00808       ast_debug(2, "Listing matching entries:\n");
00809       for (ptr = sorted, i = 0; i < count; i++, ptr++) {
00810          ast_debug(2, "%s: %s\n", ptr[0]->exten, ptr[0]->name);
00811       }
00812    }
00813 
00814    if (ast_test_flag(flags, OPT_SELECTFROMMENU)) {
00815       /* Offer multiple entries at the same time */
00816       res = select_item_menu(chan, sorted, count, dialcontext, flags, opts);
00817    } else {
00818       /* Offer entries one by one */
00819       res = select_item_seq(chan, sorted, count, dialcontext, flags, opts);
00820    }
00821 
00822    if (!res) {
00823       res = ast_streamfile(chan, "dir-nomore", ast_channel_language(chan));
00824    }
00825 
00826 exit:
00827    if (sorted)
00828       ast_free(sorted);
00829 
00830    while ((item = AST_LIST_REMOVE_HEAD(&alist, entry)))
00831       ast_free(item);
00832 
00833    return res;
00834 }
00835 
00836 static int directory_exec(struct ast_channel *chan, const char *data)
00837 {
00838    int res = 0, digit = 3;
00839    struct ast_config *cfg, *ucfg;
00840    const char *dirintro;
00841    char *parse, *opts[OPT_ARG_ARRAY_SIZE] = { 0, };
00842    struct ast_flags flags = { 0 };
00843    struct ast_flags config_flags = { 0 };
00844    enum { FIRST, LAST, BOTH } which = LAST;
00845    char digits[9] = "digits/3";
00846    AST_DECLARE_APP_ARGS(args,
00847       AST_APP_ARG(vmcontext);
00848       AST_APP_ARG(dialcontext);
00849       AST_APP_ARG(options);
00850    );
00851 
00852    parse = ast_strdupa(data);
00853 
00854    AST_STANDARD_APP_ARGS(args, parse);
00855 
00856    if (args.options && ast_app_parse_options(directory_app_options, &flags, opts, args.options))
00857       return -1;
00858 
00859    if (!(cfg = realtime_directory(args.vmcontext))) {
00860       ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
00861       return -1;
00862    }
00863 
00864    if ((ucfg = ast_config_load("users.conf", config_flags)) == CONFIG_STATUS_FILEINVALID) {
00865       ast_log(LOG_ERROR, "Config file users.conf is in an invalid format.  Aborting.\n");
00866       ucfg = NULL;
00867    }
00868 
00869    dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro");
00870    if (ast_strlen_zero(dirintro))
00871       dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
00872    /* the above prompts probably should be modified to include 0 for dialing operator
00873       and # for exiting (continues in dialplan) */
00874 
00875    if (ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) && ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
00876       if (!ast_strlen_zero(opts[OPT_ARG_EITHER])) {
00877          digit = atoi(opts[OPT_ARG_EITHER]);
00878       }
00879       which = BOTH;
00880    } else if (ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
00881       if (!ast_strlen_zero(opts[OPT_ARG_FIRSTNAME])) {
00882          digit = atoi(opts[OPT_ARG_FIRSTNAME]);
00883       }
00884       which = FIRST;
00885    } else {
00886       if (!ast_strlen_zero(opts[OPT_ARG_LASTNAME])) {
00887          digit = atoi(opts[OPT_ARG_LASTNAME]);
00888       }
00889       which = LAST;
00890    }
00891 
00892    /* If no options specified, search by last name */
00893    if (!ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) && !ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
00894       ast_set_flag(&flags, OPT_LISTBYLASTNAME);
00895       which = LAST;
00896    }
00897 
00898    if (digit > 9) {
00899       digit = 9;
00900    } else if (digit < 1) {
00901       digit = 3;
00902    }
00903    digits[7] = digit + '0';
00904 
00905    if (ast_channel_state(chan) != AST_STATE_UP) {
00906       if (!ast_test_flag(&flags, OPT_NOANSWER)) {
00907          /* Otherwise answer unless we're supposed to read while on-hook */
00908          res = ast_answer(chan);
00909       }
00910    }
00911    for (;;) {
00912       if (!ast_strlen_zero(dirintro) && !res) {
00913          res = ast_stream_and_wait(chan, dirintro, AST_DIGIT_ANY);
00914       } else if (!res) {
00915          /* Stop playing sounds as soon as we have a digit. */
00916          res = ast_stream_and_wait(chan, "dir-welcome", AST_DIGIT_ANY);
00917          if (!res) {
00918             res = ast_stream_and_wait(chan, "dir-pls-enter", AST_DIGIT_ANY);
00919          }
00920          if (!res) {
00921             res = ast_stream_and_wait(chan, digits, AST_DIGIT_ANY);
00922          }
00923          if (!res) {
00924             res = ast_stream_and_wait(chan, 
00925                which == FIRST ? "dir-first" :
00926                which == LAST ? "dir-last" :
00927                "dir-firstlast", AST_DIGIT_ANY);
00928          }
00929          if (!res) {
00930             res = ast_stream_and_wait(chan, "dir-usingkeypad", AST_DIGIT_ANY);
00931          }
00932       }
00933       ast_stopstream(chan);
00934       if (!res)
00935          res = ast_waitfordigit(chan, 5000);
00936 
00937       if (res <= 0) {
00938          if (res == 0) {
00939             pbx_builtin_setvar_helper(chan, "DIRECTORY_RESULT", "TIMEOUT");
00940          }
00941          break;
00942       }
00943 
00944       res = do_directory(chan, cfg, ucfg, args.vmcontext, args.dialcontext, res, digit, &flags, opts);
00945       if (res)
00946          break;
00947 
00948       res = ast_waitstream(chan, AST_DIGIT_ANY);
00949       ast_stopstream(chan);
00950       if (res < 0) {
00951          break;
00952       }
00953    }
00954 
00955    if (ucfg)
00956       ast_config_destroy(ucfg);
00957    ast_config_destroy(cfg);
00958 
00959    if (ast_check_hangup(chan)) {
00960       pbx_builtin_setvar_helper(chan, "DIRECTORY_RESULT", "HANGUP");
00961    } else if (res < 0) {
00962       /* If the res < 0 and we didn't hangup, an unaccounted for error must have happened. */
00963       pbx_builtin_setvar_helper(chan, "DIRECTORY_RESULT", "FAILED");
00964    }
00965 
00966    return res < 0 ? -1 : 0;
00967 }
00968 
00969 static int unload_module(void)
00970 {
00971    int res;
00972    res = ast_unregister_application(app);
00973    return res;
00974 }
00975 
00976 static int load_module(void)
00977 {
00978    return ast_register_application_xml(app, directory_exec);
00979 }
00980 
00981 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Directory");

Generated on Thu Apr 16 06:27:09 2015 for Asterisk - The Open Source Telephony Project by  doxygen 1.5.6