Wed Oct 28 11:45:25 2009

Asterisk developer's documentation


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 #include "asterisk.h"
00029 
00030 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 165322 $")
00031 
00032 #include <ctype.h>
00033 
00034 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
00035 #include "asterisk/file.h"
00036 #include "asterisk/pbx.h"
00037 #include "asterisk/module.h"
00038 #include "asterisk/say.h"
00039 #include "asterisk/app.h"
00040 
00041 #ifdef ODBC_STORAGE
00042 #include <sys/mman.h>
00043 #include "asterisk/res_odbc.h"
00044 
00045 static char odbc_database[80] = "asterisk";
00046 static char odbc_table[80] = "voicemessages";
00047 static char vmfmts[80] = "wav";
00048 #endif
00049 
00050 static char *app = "Directory";
00051 
00052 static char *synopsis = "Provide directory of voicemail extensions";
00053 static char *descrip =
00054 "  Directory(vm-context[,dial-context[,options]]): This application will present\n"
00055 "the calling channel with a directory of extensions from which they can search\n"
00056 "by name. The list of names and corresponding extensions is retrieved from the\n"
00057 "voicemail configuration file, voicemail.conf.\n"
00058 "  This application will immediately exit if one of the following DTMF digits are\n"
00059 "received and the extension to jump to exists:\n"
00060 "    0 - Jump to the 'o' extension, if it exists.\n"
00061 "    * - Jump to the 'a' extension, if it exists.\n\n"
00062 "  Parameters:\n"
00063 "    vm-context   - This is the context within voicemail.conf to use for the\n"
00064 "                   Directory.\n"
00065 "    dial-context - This is the dialplan context to use when looking for an\n"
00066 "                   extension that the user has selected, or when jumping to the\n"
00067 "                   'o' or 'a' extension.\n\n"
00068 "  Options:\n"
00069 "    e - In addition to the name, also read the extension number to the\n"
00070 "        caller before presenting dialing options.\n"
00071 "    f - Allow the caller to enter the first name of a user in the directory\n"
00072 "        instead of using the last name.\n"
00073 "    m - Instead of reading each name sequentially and asking for confirmation,\n"
00074 "        create a menu of up to 8 names.\n";
00075 
00076 /* For simplicity, I'm keeping the format compatible with the voicemail config,
00077    but i'm open to suggestions for isolating it */
00078 
00079 #define VOICEMAIL_CONFIG "voicemail.conf"
00080 
00081 /* How many digits to read in */
00082 #define NUMDIGITS 3
00083 
00084 enum {
00085    OPT_LISTBYFIRSTNAME = (1 << 0),
00086    OPT_SAYEXTENSION =    (1 << 1),
00087    OPT_FROMVOICEMAIL =   (1 << 2),
00088    OPT_SELECTFROMMENU =  (1 << 3),
00089 } directory_option_flags;
00090 
00091 struct directory_item {
00092    char exten[AST_MAX_EXTENSION + 1];
00093    char name[AST_MAX_EXTENSION + 1];
00094    char key[50]; /* Text to order items. Either lastname+firstname or firstname+lastname */
00095 
00096    AST_LIST_ENTRY(directory_item) entry;
00097 };
00098 
00099 AST_APP_OPTIONS(directory_app_options, {
00100    AST_APP_OPTION('f', OPT_LISTBYFIRSTNAME),
00101    AST_APP_OPTION('e', OPT_SAYEXTENSION),
00102    AST_APP_OPTION('v', OPT_FROMVOICEMAIL),
00103    AST_APP_OPTION('m', OPT_SELECTFROMMENU),
00104 });
00105 
00106 #ifdef ODBC_STORAGE
00107 struct generic_prepare_struct {
00108    const char *sql;
00109    const char *param;
00110 };
00111 
00112 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
00113 {
00114    struct generic_prepare_struct *gps = data;
00115    SQLHSTMT stmt;
00116    int res;
00117 
00118    res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
00119    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00120       ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
00121       return NULL;
00122    }
00123 
00124    res = SQLPrepare(stmt, (unsigned char *)gps->sql, SQL_NTS);
00125    if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00126       ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", (char *)gps->sql);
00127       SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00128       return NULL;
00129    }
00130 
00131    if (!ast_strlen_zero(gps->param))
00132       SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->param), 0, (void *)gps->param, 0, NULL);
00133 
00134    return stmt;
00135 }
00136 
00137 static void retrieve_file(char *dir)
00138 {
00139    int x = 0;
00140    int res;
00141    int fd=-1;
00142    size_t fdlen = 0;
00143    void *fdm = MAP_FAILED;
00144    SQLHSTMT stmt;
00145    char sql[256];
00146    char fmt[80]="", empty[10] = "";
00147    char *c;
00148    SQLLEN colsize;
00149    char full_fn[256];
00150    struct odbc_obj *obj;
00151    struct generic_prepare_struct gps = { .sql = sql, .param = dir };
00152 
00153    obj = ast_odbc_request_obj(odbc_database, 1);
00154    if (obj) {
00155       do {
00156          ast_copy_string(fmt, vmfmts, sizeof(fmt));
00157          c = strchr(fmt, '|');
00158          if (c)
00159             *c = '\0';
00160          if (!strcasecmp(fmt, "wav49"))
00161             strcpy(fmt, "WAV");
00162          snprintf(full_fn, sizeof(full_fn), "%s.%s", dir, fmt);
00163          snprintf(sql, sizeof(sql), "SELECT recording FROM %s WHERE dir=? AND msgnum=-1", odbc_table);
00164          stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
00165 
00166          if (!stmt) {
00167             ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
00168             break;
00169          }
00170          res = SQLFetch(stmt);
00171          if (res == SQL_NO_DATA) {
00172             SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00173             break;
00174          } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00175             ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
00176             SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00177             break;
00178          }
00179          fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, AST_FILE_MODE);
00180          if (fd < 0) {
00181             ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
00182             SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00183             break;
00184          }
00185 
00186          res = SQLGetData(stmt, 1, SQL_BINARY, empty, 0, &colsize);
00187          fdlen = colsize;
00188          if (fd > -1) {
00189             char tmp[1]="";
00190             lseek(fd, fdlen - 1, SEEK_SET);
00191             if (write(fd, tmp, 1) != 1) {
00192                close(fd);
00193                fd = -1;
00194                break;
00195             }
00196             if (fd > -1)
00197                fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
00198          }
00199          if (fdm != MAP_FAILED) {
00200             memset(fdm, 0, fdlen);
00201             res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, fdlen, &colsize);
00202             if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00203                ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
00204                SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00205                break;
00206             }
00207          }
00208          SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00209       } while (0);
00210       ast_odbc_release_obj(obj);
00211    } else
00212       ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
00213    if (fdm != MAP_FAILED)
00214       munmap(fdm, fdlen);
00215    if (fd > -1)
00216       close(fd);
00217    return;
00218 }
00219 #endif
00220 
00221 static int compare(const char *text, const char *template)
00222 {
00223    char digit;
00224 
00225    if (ast_strlen_zero(text)) {
00226       return -1;
00227    }
00228 
00229    while (*template) {
00230       digit = toupper(*text++);
00231       switch (digit) {
00232       case 0:
00233          return -1;
00234       case '1':
00235          digit = '1';
00236          break;
00237       case '2':
00238       case 'A':
00239       case 'B':
00240       case 'C':
00241          digit = '2';
00242          break;
00243       case '3':
00244       case 'D':
00245       case 'E':
00246       case 'F':
00247          digit = '3';
00248          break;
00249       case '4':
00250       case 'G':
00251       case 'H':
00252       case 'I':
00253          digit = '4';
00254          break;
00255       case '5':
00256       case 'J':
00257       case 'K':
00258       case 'L':
00259          digit = '5';
00260          break;
00261       case '6':
00262       case 'M':
00263       case 'N':
00264       case 'O':
00265          digit = '6';
00266          break;
00267       case '7':
00268       case 'P':
00269       case 'Q':
00270       case 'R':
00271       case 'S':
00272          digit = '7';
00273          break;
00274       case '8':
00275       case 'T':
00276       case 'U':
00277       case 'V':
00278          digit = '8';
00279          break;
00280       case '9':
00281       case 'W':
00282       case 'X':
00283       case 'Y':
00284       case 'Z':
00285          digit = '9';
00286          break;
00287 
00288       default:
00289          if (digit > ' ')
00290             return -1;
00291          continue;
00292       }
00293 
00294       if (*template++ != digit)
00295          return -1;
00296    }
00297 
00298    return 0;
00299 }
00300 
00301 /* play name of mailbox owner.
00302  * returns:  -1 for bad or missing extension
00303  *           '1' for selected entry from directory
00304  *           '*' for skipped entry from directory
00305  */
00306 static int play_mailbox_owner(struct ast_channel *chan, const char *context,
00307    const char *ext, const char *name, struct ast_flags *flags)
00308 {
00309    int res = 0;
00310    char fn[256];
00311 
00312    /* Check for the VoiceMail2 greeting first */
00313    snprintf(fn, sizeof(fn), "%s/voicemail/%s/%s/greet",
00314       ast_config_AST_SPOOL_DIR, context, ext);
00315 #ifdef ODBC_STORAGE
00316    retrieve_file(fn);
00317 #endif
00318 
00319    if (ast_fileexists(fn, NULL, chan->language) <= 0) {
00320       /* no file, check for an old-style Voicemail greeting */
00321       snprintf(fn, sizeof(fn), "%s/vm/%s/greet",
00322          ast_config_AST_SPOOL_DIR, ext);
00323    }
00324 #ifdef ODBC_STORAGE
00325    retrieve_file(fn);
00326 #endif
00327 
00328    if (ast_fileexists(fn, NULL, chan->language) > 0) {
00329       res = ast_stream_and_wait(chan, fn, AST_DIGIT_ANY);
00330       ast_stopstream(chan);
00331       /* If Option 'e' was specified, also read the extension number with the name */
00332       if (ast_test_flag(flags, OPT_SAYEXTENSION)) {
00333          ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
00334          res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
00335       }
00336    } else {
00337       res = ast_say_character_str(chan, S_OR(name, ext), AST_DIGIT_ANY, chan->language);
00338       if (!ast_strlen_zero(name) && ast_test_flag(flags, OPT_SAYEXTENSION)) {
00339          ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
00340          res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
00341       }
00342    }
00343 #ifdef ODBC_STORAGE
00344    ast_filedelete(fn, NULL);
00345 #endif
00346 
00347    return res;
00348 }
00349 
00350 static int select_entry(struct ast_channel *chan, const char *context, const char *dialcontext, const struct directory_item *item, struct ast_flags *flags)
00351 {
00352    ast_debug(1, "Selecting '%s' - %s@%s\n", item->name, item->exten, dialcontext);
00353 
00354    if (ast_test_flag(flags, OPT_FROMVOICEMAIL)) {
00355       /* We still want to set the exten though */
00356       ast_copy_string(chan->exten, item->exten, sizeof(chan->exten));
00357    } else if (ast_goto_if_exists(chan, dialcontext, item->exten, 1)) {
00358       ast_log(LOG_WARNING,
00359          "Can't find extension '%s' in context '%s'.  "
00360          "Did you pass the wrong context to Directory?\n",
00361          item->exten, dialcontext);
00362       return -1;
00363    }
00364 
00365    return 0;
00366 }
00367 
00368 static int select_item_seq(struct ast_channel *chan, struct directory_item **items, int count, const char *context, const char *dialcontext, struct ast_flags *flags)
00369 {
00370    struct directory_item *item, **ptr;
00371    int i, res, loop;
00372 
00373    for (ptr = items, i = 0; i < count; i++, ptr++) {
00374       item = *ptr;
00375 
00376       for (loop = 3 ; loop > 0; loop--) {
00377          res = play_mailbox_owner(chan, context, item->exten, item->name, flags);
00378 
00379          if (!res)
00380             res = ast_stream_and_wait(chan, "dir-instr", AST_DIGIT_ANY);
00381          if (!res)
00382             res = ast_waitfordigit(chan, 3000);
00383          ast_stopstream(chan);
00384    
00385          if (res == '1') { /* Name selected */
00386             return select_entry(chan, context, dialcontext, item, flags) ? -1 : 1;
00387          } else if (res == '*') {
00388             /* Skip to next match in list */
00389             break;
00390          }
00391 
00392          if (res < 0)
00393             return -1;
00394 
00395          res = 0;
00396       }
00397    }
00398 
00399    /* Nothing was selected */
00400    return 0;
00401 }
00402 
00403 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)
00404 {
00405    struct directory_item **block, *item;
00406    int i, limit, res = 0;
00407    char buf[9];
00408 
00409    for (block = items; count; block += limit, count -= limit) {
00410       limit = count;
00411       if (limit > 8)
00412          limit = 8;
00413 
00414       for (i = 0; i < limit && !res; i++) {
00415          item = block[i];
00416 
00417          snprintf(buf, sizeof(buf), "digits/%d", i + 1);
00418          /* Press <num> for <name>, [ extension <ext> ] */
00419          res = ast_streamfile(chan, "dir-multi1", chan->language);
00420          if (!res)
00421             res = ast_waitstream(chan, AST_DIGIT_ANY);
00422          if (!res)
00423             res = ast_streamfile(chan, buf, chan->language);
00424          if (!res)
00425             res = ast_waitstream(chan, AST_DIGIT_ANY);
00426          if (!res)
00427             res = ast_streamfile(chan, "dir-multi2", chan->language);
00428          if (!res)
00429             res = ast_waitstream(chan, AST_DIGIT_ANY);
00430          if (!res)
00431             res = play_mailbox_owner(chan, context, item->exten, item->name, flags);
00432          if (!res)
00433             res = ast_waitstream(chan, AST_DIGIT_ANY);
00434          if (!res)
00435             res = ast_waitfordigit(chan, 800);
00436       }
00437 
00438       /* Press "9" for more names. */
00439       if (!res && count > limit) {
00440          res = ast_streamfile(chan, "dir-multi9", chan->language);
00441          if (!res)
00442             res = ast_waitstream(chan, AST_DIGIT_ANY);
00443       }
00444 
00445       if (!res) {
00446          res = ast_waitfordigit(chan, 3000);
00447       }
00448 
00449       if (res && res > '0' && res < '1' + limit) {
00450          return select_entry(chan, context, dialcontext, block[res - '1'], flags) ? -1 : 1;
00451       }
00452 
00453       if (res < 0)
00454          return -1;
00455 
00456       res = 0;
00457    }
00458 
00459    /* Nothing was selected */
00460    return 0;
00461 }
00462 
00463 static struct ast_config *realtime_directory(char *context)
00464 {
00465    struct ast_config *cfg;
00466    struct ast_config *rtdata;
00467    struct ast_category *cat;
00468    struct ast_variable *var;
00469    char *mailbox;
00470    const char *fullname;
00471    const char *hidefromdir;
00472    char tmp[100];
00473    struct ast_flags config_flags = { 0 };
00474 
00475    /* Load flat file config. */
00476    cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
00477 
00478    if (!cfg) {
00479       /* Loading config failed. */
00480       ast_log(LOG_WARNING, "Loading config failed.\n");
00481       return NULL;
00482    }
00483 
00484    /* Get realtime entries, categorized by their mailbox number
00485       and present in the requested context */
00486    rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, NULL);
00487 
00488    /* if there are no results, just return the entries from the config file */
00489    if (!rtdata)
00490       return cfg;
00491 
00492    /* Does the context exist within the config file? If not, make one */
00493    cat = ast_category_get(cfg, context);
00494    if (!cat) {
00495       cat = ast_category_new(context, "", 99999);
00496       if (!cat) {
00497          ast_log(LOG_WARNING, "Out of memory\n");
00498          ast_config_destroy(cfg);
00499          if (rtdata) {
00500             ast_config_destroy(rtdata);
00501          }
00502          return NULL;
00503       }
00504       ast_category_append(cfg, cat);
00505    }
00506 
00507    mailbox = NULL;
00508    while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) {
00509       fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
00510       hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir");
00511       snprintf(tmp, sizeof(tmp), "no-password,%s,hidefromdir=%s",
00512          fullname ? fullname : "",
00513          hidefromdir ? hidefromdir : "no");
00514       var = ast_variable_new(mailbox, tmp, "");
00515       if (var)
00516          ast_variable_append(cat, var);
00517       else
00518          ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
00519    }
00520    ast_config_destroy(rtdata);
00521 
00522    return cfg;
00523 }
00524 
00525 static int check_match(struct directory_item **result, const char *item_fullname, const char *item_ext, const char *pattern_ext, int use_first_name)
00526 {
00527    struct directory_item *item;
00528    const char *key = NULL;
00529    int namelen;
00530 
00531 
00532    /* Set key to last name or first name depending on search mode */
00533    if (!use_first_name)
00534       key = strchr(item_fullname, ' ');
00535 
00536    if (key)
00537       key++;
00538    else
00539       key = item_fullname;
00540 
00541    if (compare(key, pattern_ext))
00542       return 0;
00543 
00544    /* Match */
00545    item = ast_calloc(1, sizeof(*item));
00546    if (!item)
00547       return -1;
00548    ast_copy_string(item->name, item_fullname, sizeof(item->name));
00549    ast_copy_string(item->exten, item_ext, sizeof(item->exten));
00550 
00551    ast_copy_string(item->key, key, sizeof(item->key));
00552    if (key != item_fullname) {
00553       /* Key is the last name. Append first name to key in order to sort Last,First */
00554       namelen = key - item_fullname - 1;
00555       if (namelen > sizeof(item->key) - strlen(item->key) - 1)
00556          namelen = sizeof(item->key) - strlen(item->key) - 1;
00557       strncat(item->key, item_fullname, namelen);
00558    }
00559 
00560    *result = item;
00561    return 1;
00562 }
00563 
00564 typedef AST_LIST_HEAD_NOLOCK(, directory_item) itemlist;
00565 
00566 static int search_directory(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, int use_first_name, itemlist *alist)
00567 {
00568    struct ast_variable *v;
00569    char buf[AST_MAX_EXTENSION + 1], *pos, *bufptr, *cat;
00570    struct directory_item *item;
00571    int res;
00572 
00573    ast_debug(2, "Pattern: %s\n", ext);
00574 
00575    for (v = ast_variable_browse(vmcfg, context); v; v = v->next) {
00576 
00577       /* Ignore hidden */
00578       if (strcasestr(v->value, "hidefromdir=yes"))
00579          continue;
00580 
00581       ast_copy_string(buf, v->value, sizeof(buf));
00582       bufptr = buf;
00583 
00584       /* password,Full Name,email,pager,options */
00585       strsep(&bufptr, ",");
00586       pos = strsep(&bufptr, ",");
00587 
00588       res = check_match(&item, S_OR(pos, ""), v->name, ext, use_first_name);
00589       if (!res)
00590          continue;
00591       else if (res < 0)
00592          return -1;
00593 
00594       AST_LIST_INSERT_TAIL(alist, item, entry);
00595    }
00596 
00597 
00598    if (ucfg) {
00599       for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) {
00600          const char *pos;
00601          if (!strcasecmp(cat, "general"))
00602             continue;
00603          if (!ast_true(ast_config_option(ucfg, cat, "hasdirectory")))
00604             continue;
00605 
00606          /* Find all candidate extensions */
00607          pos = ast_variable_retrieve(ucfg, cat, "fullname");
00608          if (!pos)
00609             continue;
00610 
00611          res = check_match(&item, pos, cat, ext, use_first_name);
00612          if (!res)
00613             continue;
00614          else if (res < 0)
00615             return -1;
00616 
00617          AST_LIST_INSERT_TAIL(alist, item, entry);
00618       }
00619    }
00620 
00621    return 0;
00622 }
00623 
00624 static void sort_items(struct directory_item **sorted, int count)
00625 {
00626    int reordered, i;
00627    struct directory_item **ptr, *tmp;
00628 
00629    if (count < 2)
00630       return;
00631 
00632    /* Bubble-sort items by the key */
00633    do {
00634       reordered = 0;
00635       for (ptr = sorted, i = 0; i < count - 1; i++, ptr++) {
00636          if (strcasecmp(ptr[0]->key, ptr[1]->key) > 0) {
00637             tmp = ptr[0];
00638             ptr[0] = ptr[1];
00639             ptr[1] = tmp;
00640             reordered++;
00641          }
00642       }
00643    } while (reordered);
00644 }
00645 
00646 static int goto_exten(struct ast_channel *chan, const char *dialcontext, char *ext)
00647 {
00648    if (!ast_goto_if_exists(chan, dialcontext, ext, 1) ||
00649       (!ast_strlen_zero(chan->macrocontext) &&
00650       !ast_goto_if_exists(chan, chan->macrocontext, ext, 1))) {
00651       return 0;
00652    } else {
00653       ast_log(LOG_WARNING, "Can't find extension '%s' in current context.  "
00654          "Not Exiting the Directory!\n", ext);
00655       return -1;
00656    }
00657 }
00658 
00659 static int do_directory(struct ast_channel *chan, struct ast_config *vmcfg, struct ast_config *ucfg, char *context, char *dialcontext, char digit, struct ast_flags *flags)
00660 {
00661    /* Read in the first three digits..  "digit" is the first digit, already read */
00662    int res = 0;
00663    itemlist alist = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
00664    struct directory_item *item, **ptr, **sorted = NULL;
00665    int count, i;
00666    char ext[NUMDIGITS + 1] = "";
00667 
00668    if (ast_strlen_zero(context)) {
00669       ast_log(LOG_WARNING,
00670          "Directory must be called with an argument "
00671          "(context in which to interpret extensions)\n");
00672       return -1;
00673    }
00674 
00675    if (digit == '0' && !goto_exten(chan, dialcontext, "o")) {
00676       return 0;
00677    }
00678 
00679    if (digit == '*' && !goto_exten(chan, dialcontext, "a")) {
00680       return 0;
00681    }
00682 
00683    ext[0] = digit;
00684    if (ast_readstring(chan, ext + 1, NUMDIGITS - 1, 3000, 3000, "#") < 0)
00685       return -1;
00686 
00687    res = search_directory(context, vmcfg, ucfg, ext, ast_test_flag(flags, OPT_LISTBYFIRSTNAME), &alist);
00688    if (res)
00689       goto exit;
00690 
00691    /* Count items in the list */
00692    count = 0;
00693    AST_LIST_TRAVERSE(&alist, item, entry) {
00694       count++;
00695    }
00696 
00697    if (count < 1) {
00698       res = ast_streamfile(chan, "dir-nomatch", chan->language);
00699       goto exit;
00700    }
00701 
00702 
00703    /* Create plain array of pointers to items (for sorting) */
00704    sorted = ast_calloc(count, sizeof(*sorted));
00705 
00706    ptr = sorted;
00707    AST_LIST_TRAVERSE(&alist, item, entry) {
00708       *ptr++ = item;
00709    }
00710 
00711    /* Sort items */
00712    sort_items(sorted, count);
00713 
00714    if (option_debug) {
00715       ast_debug(2, "Listing matching entries:\n");
00716       for (ptr = sorted, i = 0; i < count; i++, ptr++) {
00717          ast_log(LOG_DEBUG, "%s: %s\n", ptr[0]->exten, ptr[0]->name);
00718       }
00719    }
00720 
00721    if (ast_test_flag(flags, OPT_SELECTFROMMENU)) {
00722       /* Offer multiple entries at the same time */
00723       res = select_item_menu(chan, sorted, count, context, dialcontext, flags);
00724    } else {
00725       /* Offer entries one by one */
00726       res = select_item_seq(chan, sorted, count, context, dialcontext, flags);
00727    }
00728 
00729    if (!res) {
00730       res = ast_streamfile(chan, "dir-nomore", chan->language);
00731    }
00732 
00733 exit:
00734    if (sorted)
00735       ast_free(sorted);
00736 
00737    while ((item = AST_LIST_REMOVE_HEAD(&alist, entry)))
00738       ast_free(item);
00739 
00740    return res;
00741 }
00742 
00743 static int directory_exec(struct ast_channel *chan, void *data)
00744 {
00745    int res = 0;
00746    struct ast_config *cfg, *ucfg;
00747    const char *dirintro;
00748    char *parse, *opts[0];
00749    struct ast_flags flags = { 0 };
00750    struct ast_flags config_flags = { 0 };
00751    AST_DECLARE_APP_ARGS(args,
00752       AST_APP_ARG(vmcontext);
00753       AST_APP_ARG(dialcontext);
00754       AST_APP_ARG(options);
00755    );
00756 
00757    if (ast_strlen_zero(data)) {
00758       ast_log(LOG_WARNING, "Directory requires an argument (context[,dialcontext])\n");
00759       return -1;
00760    }
00761 
00762    parse = ast_strdupa(data);
00763 
00764    AST_STANDARD_APP_ARGS(args, parse);
00765 
00766    if (args.options && ast_app_parse_options(directory_app_options, &flags, opts, args.options))
00767       return -1;
00768 
00769    if (ast_strlen_zero(args.dialcontext))
00770       args.dialcontext = args.vmcontext;
00771 
00772    cfg = realtime_directory(args.vmcontext);
00773    if (!cfg) {
00774       ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
00775       return -1;
00776    }
00777 
00778    ucfg = ast_config_load("users.conf", config_flags);
00779 
00780    dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro");
00781    if (ast_strlen_zero(dirintro))
00782       dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
00783    if (ast_strlen_zero(dirintro))
00784       dirintro = ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) ? "dir-intro-fn" : "dir-intro";
00785 
00786    if (chan->_state != AST_STATE_UP)
00787       res = ast_answer(chan);
00788 
00789    for (;;) {
00790       if (!res)
00791          res = ast_stream_and_wait(chan, dirintro, AST_DIGIT_ANY);
00792       ast_stopstream(chan);
00793       if (!res)
00794          res = ast_waitfordigit(chan, 5000);
00795 
00796       if (res <= 0)
00797          break;
00798 
00799       res = do_directory(chan, cfg, ucfg, args.vmcontext, args.dialcontext, res, &flags);
00800       if (res)
00801          break;
00802 
00803       res = ast_waitstream(chan, AST_DIGIT_ANY);
00804       ast_stopstream(chan);
00805 
00806       if (res)
00807          break;
00808    }
00809 
00810    if (ucfg)
00811       ast_config_destroy(ucfg);
00812    ast_config_destroy(cfg);
00813 
00814    return res < 0 ? -1 : 0;
00815 }
00816 
00817 static int unload_module(void)
00818 {
00819    int res;
00820    res = ast_unregister_application(app);
00821    return res;
00822 }
00823 
00824 static int load_module(void)
00825 {
00826 #ifdef ODBC_STORAGE
00827    struct ast_flags config_flags = { 0 };
00828    struct ast_config *cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
00829    const char *tmp;
00830 
00831    if (cfg) {
00832       if ((tmp = ast_variable_retrieve(cfg, "general", "odbcstorage"))) {
00833          ast_copy_string(odbc_database, tmp, sizeof(odbc_database));
00834       }
00835       if ((tmp = ast_variable_retrieve(cfg, "general", "odbctable"))) {
00836          ast_copy_string(odbc_table, tmp, sizeof(odbc_table));
00837       }
00838       if ((tmp = ast_variable_retrieve(cfg, "general", "format"))) {
00839          ast_copy_string(vmfmts, tmp, sizeof(vmfmts));
00840       }
00841       ast_config_destroy(cfg);
00842    } else
00843       ast_log(LOG_WARNING, "Unable to load " VOICEMAIL_CONFIG " - ODBC defaults will be used\n");
00844 #endif
00845 
00846    return ast_register_application(app, directory_exec, synopsis, descrip);
00847 }
00848 
00849 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Directory");

Generated on Wed Oct 28 11:45:25 2009 for Asterisk - the Open Source PBX by  doxygen 1.5.6