00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 #include <string.h>
00027 #include <ctype.h>
00028 #include <stdlib.h>
00029 #include <stdio.h>
00030
00031 #include "asterisk.h"
00032
00033 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 40446 $")
00034
00035 #include "asterisk/lock.h"
00036 #include "asterisk/file.h"
00037 #include "asterisk/logger.h"
00038 #include "asterisk/channel.h"
00039 #include "asterisk/pbx.h"
00040 #include "asterisk/module.h"
00041 #include "asterisk/config.h"
00042 #include "asterisk/say.h"
00043 #include "asterisk/utils.h"
00044
00045 #ifdef USE_ODBC_STORAGE
00046 #include <errno.h>
00047 #include <sys/mman.h>
00048 #include "asterisk/res_odbc.h"
00049
00050 static char odbc_database[80] = "asterisk";
00051 static char odbc_table[80] = "voicemessages";
00052 static char vmfmts[80] = "wav";
00053 #endif
00054
00055 static char *tdesc = "Extension Directory";
00056 static char *app = "Directory";
00057
00058 static char *synopsis = "Provide directory of voicemail extensions";
00059 static char *descrip =
00060 " Directory(vm-context[|dial-context[|options]]): This application will present\n"
00061 "the calling channel with a directory of extensions from which they can search\n"
00062 "by name. The list of names and corresponding extensions is retrieved from the\n"
00063 "voicemail configuration file, voicemail.conf.\n"
00064 " This applicaiton will immediate exit if one of the following DTMF digits are\n"
00065 "received and the extension to jump to exists:\n"
00066 " 0 - Jump to the 'o' extension, if it exists.\n"
00067 " * - Jump to the 'a' extension, if it exists.\n\n"
00068 " Parameters:\n"
00069 " vm-context - This is the context within voicemail.conf to use for the\n"
00070 " Directory.\n"
00071 " dial-context - This is the dialplan context to use when looking for an\n"
00072 " extension that the user has selected, or when jumping to the\n"
00073 " 'o' or 'a' extension.\n\n"
00074 " Options:\n"
00075 " f - Allow the caller to enter the first name of a user in the directory\n"
00076 " instead of using the last name.\n";
00077
00078
00079
00080
00081 #define VOICEMAIL_CONFIG "voicemail.conf"
00082
00083
00084 #define NUMDIGITS 3
00085
00086 STANDARD_LOCAL_USER;
00087
00088 LOCAL_USER_DECL;
00089
00090 #ifdef USE_ODBC_STORAGE
00091 static void retrieve_file(char *dir)
00092 {
00093 int x = 0;
00094 int res;
00095 int fd=-1;
00096 size_t fdlen = 0;
00097 void *fdm=NULL;
00098 SQLHSTMT stmt;
00099 char sql[256];
00100 char fmt[80]="";
00101 char *c;
00102 SQLLEN colsize;
00103 char full_fn[256];
00104
00105 odbc_obj *obj;
00106 obj = fetch_odbc_obj(odbc_database, 0);
00107 if (obj) {
00108 do {
00109 ast_copy_string(fmt, vmfmts, sizeof(fmt));
00110 c = strchr(fmt, '|');
00111 if (c)
00112 *c = '\0';
00113 if (!strcasecmp(fmt, "wav49"))
00114 strcpy(fmt, "WAV");
00115 snprintf(full_fn, sizeof(full_fn), "%s.%s", dir, fmt);
00116 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
00117 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00118 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
00119 break;
00120 }
00121 snprintf(sql, sizeof(sql), "SELECT recording FROM %s WHERE dir=? AND msgnum=-1", odbc_table);
00122 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
00123 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00124 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
00125 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00126 break;
00127 }
00128 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
00129 res = odbc_smart_execute(obj, stmt);
00130 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00131 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
00132 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00133 break;
00134 }
00135 res = SQLFetch(stmt);
00136 if (res == SQL_NO_DATA) {
00137 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00138 break;
00139 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00140 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
00141 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00142 break;
00143 }
00144 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, 0770);
00145 if (fd < 0) {
00146 ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
00147 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00148 break;
00149 }
00150
00151 res = SQLGetData(stmt, 1, SQL_BINARY, NULL, 0, &colsize);
00152 fdlen = colsize;
00153 if (fd > -1) {
00154 char tmp[1]="";
00155 lseek(fd, fdlen - 1, SEEK_SET);
00156 if (write(fd, tmp, 1) != 1) {
00157 close(fd);
00158 fd = -1;
00159 break;
00160 }
00161 if (fd > -1)
00162 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
00163 }
00164 if (fdm) {
00165 memset(fdm, 0, fdlen);
00166 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, fdlen, &colsize);
00167 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
00168 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
00169 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00170 break;
00171 }
00172 }
00173 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
00174 } while (0);
00175 } else
00176 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
00177 if (fdm)
00178 munmap(fdm, fdlen);
00179 if (fd > -1)
00180 close(fd);
00181 return;
00182 }
00183 #endif
00184
00185 static char *convert(char *lastname)
00186 {
00187 char *tmp;
00188 int lcount = 0;
00189 tmp = malloc(NUMDIGITS + 1);
00190 if (tmp) {
00191 while((*lastname > 32) && lcount < NUMDIGITS) {
00192 switch(toupper(*lastname)) {
00193 case '1':
00194 tmp[lcount++] = '1';
00195 break;
00196 case '2':
00197 case 'A':
00198 case 'B':
00199 case 'C':
00200 tmp[lcount++] = '2';
00201 break;
00202 case '3':
00203 case 'D':
00204 case 'E':
00205 case 'F':
00206 tmp[lcount++] = '3';
00207 break;
00208 case '4':
00209 case 'G':
00210 case 'H':
00211 case 'I':
00212 tmp[lcount++] = '4';
00213 break;
00214 case '5':
00215 case 'J':
00216 case 'K':
00217 case 'L':
00218 tmp[lcount++] = '5';
00219 break;
00220 case '6':
00221 case 'M':
00222 case 'N':
00223 case 'O':
00224 tmp[lcount++] = '6';
00225 break;
00226 case '7':
00227 case 'P':
00228 case 'Q':
00229 case 'R':
00230 case 'S':
00231 tmp[lcount++] = '7';
00232 break;
00233 case '8':
00234 case 'T':
00235 case 'U':
00236 case 'V':
00237 tmp[lcount++] = '8';
00238 break;
00239 case '9':
00240 case 'W':
00241 case 'X':
00242 case 'Y':
00243 case 'Z':
00244 tmp[lcount++] = '9';
00245 break;
00246 }
00247 lastname++;
00248 }
00249 tmp[lcount] = '\0';
00250 }
00251 return tmp;
00252 }
00253
00254
00255
00256
00257
00258
00259 static int play_mailbox_owner(struct ast_channel *chan, char *context, char *dialcontext, char *ext, char *name, int fromappvm) {
00260 int res = 0;
00261 int loop = 3;
00262 char fn[256];
00263 char fn2[256];
00264
00265
00266 snprintf(fn, sizeof(fn), "%s/voicemail/%s/%s/greet",
00267 (char *)ast_config_AST_SPOOL_DIR, context, ext);
00268 #ifdef USE_ODBC_STORAGE
00269 retrieve_file(fn);
00270 #endif
00271
00272
00273 snprintf(fn2, sizeof(fn2), "%s/vm/%s/greet",
00274 (char *)ast_config_AST_SPOOL_DIR, ext);
00275 #ifdef USE_ODBC_STORAGE
00276 retrieve_file(fn2);
00277 #endif
00278
00279 if (ast_fileexists(fn, NULL, chan->language) > 0) {
00280 res = ast_streamfile(chan, fn, chan->language);
00281 if (!res) {
00282 res = ast_waitstream(chan, AST_DIGIT_ANY);
00283 }
00284 ast_stopstream(chan);
00285 } else if (ast_fileexists(fn2, NULL, chan->language) > 0) {
00286 res = ast_streamfile(chan, fn2, chan->language);
00287 if (!res) {
00288 res = ast_waitstream(chan, AST_DIGIT_ANY);
00289 }
00290 ast_stopstream(chan);
00291 } else {
00292 res = ast_say_character_str(chan, !ast_strlen_zero(name) ? name : ext,
00293 AST_DIGIT_ANY, chan->language);
00294 }
00295 #ifdef USE_ODBC_STORAGE
00296 ast_filedelete(fn, NULL);
00297 ast_filedelete(fn2, NULL);
00298 #endif
00299
00300 while (loop) {
00301 if (!res) {
00302 res = ast_streamfile(chan, "dir-instr", chan->language);
00303 }
00304 if (!res) {
00305 res = ast_waitstream(chan, AST_DIGIT_ANY);
00306 }
00307 if (!res) {
00308 res = ast_waitfordigit(chan, 3000);
00309 }
00310 ast_stopstream(chan);
00311
00312 if (res > -1) {
00313 switch (res) {
00314 case '1':
00315
00316 loop = 0;
00317 if (fromappvm) {
00318
00319 ast_copy_string(chan->exten, ext, sizeof(chan->exten));
00320 } else {
00321 if (ast_goto_if_exists(chan, dialcontext, ext, 1)) {
00322 ast_log(LOG_WARNING,
00323 "Can't find extension '%s' in context '%s'. "
00324 "Did you pass the wrong context to Directory?\n",
00325 ext, dialcontext);
00326 res = -1;
00327 }
00328 }
00329 break;
00330
00331 case '*':
00332
00333 loop = 0;
00334 break;
00335
00336 default:
00337
00338 res = 0;
00339 loop--;
00340 break;
00341 }
00342 }
00343 else {
00344
00345 loop = 0;
00346 }
00347 }
00348
00349 return(res);
00350 }
00351
00352 static struct ast_config *realtime_directory(char *context)
00353 {
00354 struct ast_config *cfg;
00355 struct ast_config *rtdata;
00356 struct ast_category *cat;
00357 struct ast_variable *var;
00358 char *mailbox;
00359 char *fullname;
00360 char *hidefromdir;
00361 char tmp[100];
00362
00363
00364 cfg = ast_config_load(VOICEMAIL_CONFIG);
00365
00366 if (!cfg) {
00367
00368 ast_log(LOG_WARNING, "Loading config failed.\n");
00369 return NULL;
00370 }
00371
00372
00373
00374 rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, NULL);
00375
00376
00377 if (!rtdata)
00378 return cfg;
00379
00380
00381 cat = ast_category_get(cfg, context);
00382 if (!cat) {
00383 cat = ast_category_new(context);
00384 if (!cat) {
00385 ast_log(LOG_WARNING, "Out of memory\n");
00386 ast_config_destroy(cfg);
00387 return NULL;
00388 }
00389 ast_category_append(cfg, cat);
00390 }
00391
00392 mailbox = ast_category_browse(rtdata, NULL);
00393 while (mailbox) {
00394 fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
00395 hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir");
00396 snprintf(tmp, sizeof(tmp), "no-password,%s,hidefromdir=%s",
00397 fullname ? fullname : "",
00398 hidefromdir ? hidefromdir : "no");
00399 var = ast_variable_new(mailbox, tmp);
00400 if (var)
00401 ast_variable_append(cat, var);
00402 else
00403 ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
00404 mailbox = ast_category_browse(rtdata, mailbox);
00405 }
00406 ast_config_destroy(rtdata);
00407
00408 return cfg;
00409 }
00410
00411 static int do_directory(struct ast_channel *chan, struct ast_config *cfg, char *context, char *dialcontext, char digit, int last, int fromappvm)
00412 {
00413
00414 char ext[NUMDIGITS + 1];
00415 char name[80] = "";
00416 struct ast_variable *v;
00417 int res;
00418 int found=0;
00419 int lastuserchoice = 0;
00420 char *start, *pos, *conv,*stringp=NULL;
00421
00422 if (ast_strlen_zero(context)) {
00423 ast_log(LOG_WARNING,
00424 "Directory must be called with an argument "
00425 "(context in which to interpret extensions)\n");
00426 return -1;
00427 }
00428 if (digit == '0') {
00429 if (!ast_goto_if_exists(chan, chan->context, "o", 1) ||
00430 (!ast_strlen_zero(chan->macrocontext) &&
00431 !ast_goto_if_exists(chan, chan->macrocontext, "o", 1))) {
00432 return 0;
00433 } else {
00434 ast_log(LOG_WARNING, "Can't find extension 'o' in current context. "
00435 "Not Exiting the Directory!\n");
00436 res = 0;
00437 }
00438 }
00439 if (digit == '*') {
00440 if (!ast_goto_if_exists(chan, chan->context, "a", 1) ||
00441 (!ast_strlen_zero(chan->macrocontext) &&
00442 !ast_goto_if_exists(chan, chan->macrocontext, "a", 1))) {
00443 return 0;
00444 } else {
00445 ast_log(LOG_WARNING, "Can't find extension 'a' in current context. "
00446 "Not Exiting the Directory!\n");
00447 res = 0;
00448 }
00449 }
00450 memset(ext, 0, sizeof(ext));
00451 ext[0] = digit;
00452 res = 0;
00453 if (ast_readstring(chan, ext + 1, NUMDIGITS - 1, 3000, 3000, "#") < 0) res = -1;
00454 if (!res) {
00455
00456 v = ast_variable_browse(cfg, context);
00457 while(v && !res) {
00458
00459 while(v) {
00460
00461 start = strdup(v->value);
00462 if (start && !strcasestr(start, "hidefromdir=yes")) {
00463 stringp=start;
00464 strsep(&stringp, ",");
00465 pos = strsep(&stringp, ",");
00466 if (pos) {
00467 ast_copy_string(name, pos, sizeof(name));
00468
00469 if (last && strrchr(pos,' '))
00470 pos = strrchr(pos, ' ') + 1;
00471 conv = convert(pos);
00472 if (conv) {
00473 if (!strcmp(conv, ext)) {
00474
00475 found++;
00476 free(conv);
00477 free(start);
00478 break;
00479 }
00480 free(conv);
00481 }
00482 }
00483 free(start);
00484 }
00485 v = v->next;
00486 }
00487
00488 if (v) {
00489
00490 res = play_mailbox_owner(chan, context, dialcontext, v->name, name, fromappvm);
00491 switch (res) {
00492 case -1:
00493
00494
00495
00496 lastuserchoice = 0;
00497 break;
00498 case '1':
00499
00500
00501
00502
00503 lastuserchoice = res;
00504 break;
00505 case '*':
00506
00507 lastuserchoice = res;
00508 res = 0;
00509 break;
00510 default:
00511 break;
00512 }
00513 v = v->next;
00514 }
00515 }
00516
00517 if (lastuserchoice != '1') {
00518 if (found)
00519 res = ast_streamfile(chan, "dir-nomore", chan->language);
00520 else
00521 res = ast_streamfile(chan, "dir-nomatch", chan->language);
00522 if (!res)
00523 res = 1;
00524 return res;
00525 }
00526 return 0;
00527 }
00528 return res;
00529 }
00530
00531 static int directory_exec(struct ast_channel *chan, void *data)
00532 {
00533 int res = 0;
00534 struct localuser *u;
00535 struct ast_config *cfg;
00536 int last = 1;
00537 int fromappvm = 0;
00538 char *context, *dialcontext, *dirintro, *options;
00539
00540 if (ast_strlen_zero(data)) {
00541 ast_log(LOG_WARNING, "Directory requires an argument (context[,dialcontext])\n");
00542 return -1;
00543 }
00544
00545 LOCAL_USER_ADD(u);
00546
00547 context = ast_strdupa(data);
00548 dialcontext = strchr(context, '|');
00549 if (dialcontext) {
00550 *dialcontext = '\0';
00551 dialcontext++;
00552 options = strchr(dialcontext, '|');
00553 if (options) {
00554 *options = '\0';
00555 options++;
00556 if (strchr(options, 'f'))
00557 last = 0;
00558 if (strchr(options, 'v'))
00559 fromappvm = 1;
00560 }
00561 } else
00562 dialcontext = context;
00563
00564 cfg = realtime_directory(context);
00565 if (!cfg) {
00566 LOCAL_USER_REMOVE(u);
00567 return -1;
00568 }
00569
00570 dirintro = ast_variable_retrieve(cfg, context, "directoryintro");
00571 if (ast_strlen_zero(dirintro))
00572 dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
00573 if (ast_strlen_zero(dirintro)) {
00574 if (last)
00575 dirintro = "dir-intro";
00576 else
00577 dirintro = "dir-intro-fn";
00578 }
00579
00580 if (chan->_state != AST_STATE_UP)
00581 res = ast_answer(chan);
00582
00583 for (;;) {
00584 if (!res)
00585 res = ast_streamfile(chan, dirintro, chan->language);
00586 if (!res)
00587 res = ast_waitstream(chan, AST_DIGIT_ANY);
00588 ast_stopstream(chan);
00589 if (!res)
00590 res = ast_waitfordigit(chan, 5000);
00591 if (res > 0) {
00592 res = do_directory(chan, cfg, context, dialcontext, res, last, fromappvm);
00593 if (res > 0) {
00594 res = ast_waitstream(chan, AST_DIGIT_ANY);
00595 ast_stopstream(chan);
00596 if (res >= 0) {
00597 continue;
00598 }
00599 }
00600 }
00601 break;
00602 }
00603 ast_config_destroy(cfg);
00604 LOCAL_USER_REMOVE(u);
00605 return res;
00606 }
00607
00608 int unload_module(void)
00609 {
00610 int res;
00611
00612 res = ast_unregister_application(app);
00613
00614 STANDARD_HANGUP_LOCALUSERS;
00615
00616 return res;
00617 }
00618
00619 int load_module(void)
00620 {
00621 #ifdef USE_ODBC_STORAGE
00622 struct ast_config *cfg = ast_config_load(VOICEMAIL_CONFIG);
00623 char *tmp;
00624
00625 if (cfg) {
00626 if ((tmp = ast_variable_retrieve(cfg, "general", "odbcstorage"))) {
00627 ast_copy_string(odbc_database, tmp, sizeof(odbc_database));
00628 }
00629 if ((tmp = ast_variable_retrieve(cfg, "general", "odbctable"))) {
00630 ast_copy_string(odbc_table, tmp, sizeof(odbc_table));
00631 }
00632 if ((tmp = ast_variable_retrieve(cfg, "general", "format"))) {
00633 ast_copy_string(vmfmts, tmp, sizeof(vmfmts));
00634 }
00635 ast_config_destroy(cfg);
00636 } else
00637 ast_log(LOG_WARNING, "Unable to load " VOICEMAIL_CONFIG " - ODBC defaults will be used\n");
00638 #endif
00639
00640 return ast_register_application(app, directory_exec, synopsis, descrip);
00641 }
00642
00643 char *description(void)
00644 {
00645 return tdesc;
00646 }
00647
00648 int usecount(void)
00649 {
00650 int res;
00651 STANDARD_USECOUNT(res);
00652 return res;
00653 }
00654
00655 char *key()
00656 {
00657 return ASTERISK_GPL_KEY;
00658 }