/* * Asterisk -- A telephony toolkit for Linux. * * Provide a directory of extensions * * Copyright (C) 1999 - 2005, Digium, Inc. * * Mark Spencer * * This program is free software, distributed under the terms of * the GNU General Public License */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../asterisk.h" #include "../astconf.h" static char *tdesc = "Extension Directory"; static char *app = "Directory"; static char *synopsis = "Provide directory of voicemail extensions"; static char *descrip = " Directory(vm-context[|dial-context[|options]]): Presents the user with a directory\n" "of extensions from which they may select by name. The list of names \n" "and extensions is discovered from voicemail.conf. The vm-context argument\n" "is required, and specifies the context of voicemail.conf to use. The\n" "dial-context is the context to use for dialing the users, and defaults to\n" "the vm-context if unspecified. The 'f' option causes the directory to match\n" "based on the first name in voicemail.conf instead of the last name.\n" "Returns 0 unless the user hangs up. It also sets up the channel on exit\n" "to enter the extension the user selected. If the user enters '0' and there\n" "exists an extension 'o' in the current context, the directory will exit with 0\n" "and call control will resume at that extension. Entering '*' will exit similarly,\n" "but to the 'a' extension, much like app_voicemail's behavior.\n"; /* For simplicity, I'm keeping the format compatible with the voicemail config, but i'm open to suggestions for isolating it */ #define DIRECTORY_CONFIG "voicemail.conf" /* How many digits to read in */ #define NUMDIGITS 3 STANDARD_LOCAL_USER; LOCAL_USER_DECL; static char *convert(char *lastname) { char *tmp; int lcount = 0; tmp = malloc(NUMDIGITS + 1); if (tmp) { while((*lastname > 32) && lcount < NUMDIGITS) { switch(toupper(*lastname)) { case '1': tmp[lcount++] = '1'; break; case '2': case 'A': case 'B': case 'C': tmp[lcount++] = '2'; break; case '3': case 'D': case 'E': case 'F': tmp[lcount++] = '3'; break; case '4': case 'G': case 'H': case 'I': tmp[lcount++] = '4'; break; case '5': case 'J': case 'K': case 'L': tmp[lcount++] = '5'; break; case '6': case 'M': case 'N': case 'O': tmp[lcount++] = '6'; break; case '7': case 'P': case 'Q': case 'R': case 'S': tmp[lcount++] = '7'; break; case '8': case 'T': case 'U': case 'V': tmp[lcount++] = '8'; break; case '9': case 'W': case 'X': case 'Y': case 'Z': tmp[lcount++] = '9'; break; } lastname++; } tmp[lcount] = '\0'; } return tmp; } /* play name of mailbox owner. * returns: -1 for bad or missing extension * '1' for selected entry from directory * '*' for skipped entry from directory */ static int play_mailbox_owner(struct ast_channel *chan, char *context, char *dialcontext, char *ext, char *name) { int res = 0; int loop = 3; char fn[256]; char fn2[256]; /* Check for the VoiceMail2 greeting first */ snprintf(fn, sizeof(fn), "%s/voicemail/%s/%s/greet", (char *)ast_config_AST_SPOOL_DIR, context, ext); /* Otherwise, check for an old-style Voicemail greeting */ snprintf(fn2, sizeof(fn2), "%s/vm/%s/greet", (char *)ast_config_AST_SPOOL_DIR, ext); if (ast_fileexists(fn, NULL, chan->language) > 0) { res = ast_streamfile(chan, fn, chan->language); if (!res) { res = ast_waitstream(chan, AST_DIGIT_ANY); } ast_stopstream(chan); } else if (ast_fileexists(fn2, NULL, chan->language) > 0) { res = ast_streamfile(chan, fn2, chan->language); if (!res) { res = ast_waitstream(chan, AST_DIGIT_ANY); } ast_stopstream(chan); } else { res = ast_say_character_str(chan, !ast_strlen_zero(name) ? name : ext, AST_DIGIT_ANY, chan->language); } while (loop) { if (!res) { res = ast_streamfile(chan, "dir-instr", chan->language); } if (!res) { res = ast_waitstream(chan, AST_DIGIT_ANY); } if (!res) { res = ast_waitfordigit(chan, 3000); } ast_stopstream(chan); if (res > -1) { switch (res) { case '1': /* Name selected */ loop = 0; if (ast_exists_extension(chan,dialcontext,ext,1,chan->cid.cid_num)) { strncpy(chan->exten, ext, sizeof(chan->exten)-1); chan->priority = 0; strncpy(chan->context, dialcontext, sizeof(chan->context)-1); } else { ast_log(LOG_WARNING, "Can't find extension '%s' in context '%s'. " "Did you pass the wrong context to Directory?\n", ext, dialcontext); res = -1; } break; case '*': /* Skip to next match in list */ loop = 0; break; default: /* Not '1', or '*', so decrement number of tries */ res = 0; loop--; break; } /* end switch */ } /* end if */ else { /* User hungup, so jump out now */ loop = 0; } } /* end while */ return(res); } static struct ast_config *realtime_directory(char *context) { struct ast_config *cfg = NULL; struct ast_variable *rtvar = NULL; struct ast_category *cat = NULL; char fullname[50] = ""; char mailbox[50] = ""; char tmp[100] = ""; int havename = 0; int havemailbox = 0; /* Load flat file config. */ cfg = ast_config_load(DIRECTORY_CONFIG); if (!cfg) { /* Loading config failed. Even if config file doesn't exist, we should still have an ast_config. */ ast_log(LOG_WARNING, "Loading/Creating config failed.\n"); return NULL; } /* Load RealTime voicemail users for this context. */ rtvar = ast_load_realtime("voicemail", "context", context, NULL); /* If we got nothing from RealTime, we can just return the Flatfile. */ if (!rtvar) return cfg; /* Does the context exist within the Flatfile? */ if (ast_category_exist(cfg, context)) { /* If so, get a pointer to it so we can append RealTime variables to it. */ cat = ast_category_get(cfg, context); } else { /* If not, make a fresh one and append it to the master config. */ cat = ast_category_new(context); if (!cat) { ast_log(LOG_WARNING, "Ran out of memory while creating new ast_category!\n"); ast_config_destroy(cfg); return NULL; } ast_category_append(cfg, cat); } /* We now have a category: from the Flatfile or freshly created. */ while (rtvar) { if (!strcasecmp(rtvar->name, "fullname")) { strncpy(fullname, rtvar->value, sizeof(fullname)-1); havename = 1; } else if (!strcasecmp(rtvar->name, "mailbox")) { strncpy(mailbox, rtvar->value, sizeof(mailbox)-1); havemailbox = 1; } /* app_directory needs only mailbox and fullname. Fill password and email with dummy values. */ if (havemailbox && havename) { sprintf(tmp, "9999,%s,email@email.com", fullname); ast_variable_append(cat, ast_variable_new(mailbox, tmp)); havemailbox = 0; havename = 0; } rtvar = rtvar->next; } return cfg; } static int do_directory(struct ast_channel *chan, struct ast_config *cfg, char *context, char *dialcontext, char digit, int last) { /* Read in the first three digits.. "digit" is the first digit, already read */ char ext[NUMDIGITS + 1]; char name[80] = ""; struct ast_variable *v; int res; int found=0; int lastuserchoice = 0; char *start, *pos, *conv,*stringp=NULL; if (!context || ast_strlen_zero(context)) { ast_log(LOG_WARNING, "Directory must be called with an argument " "(context in which to interpret extensions)\n"); return -1; } if (digit == '0') { if (ast_exists_extension(chan,chan->context,"o",1,chan->cid.cid_num) || (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num))) { strncpy(chan->exten, "o", sizeof(chan->exten)-1); chan->priority = 0; return 0; } else { ast_log(LOG_WARNING, "Can't find extension 'o' in current context. " "Not Exiting the Directory!\n"); res = 0; } } if (digit == '*') { if (ast_exists_extension(chan,chan->context,"a",1,chan->cid.cid_num) || (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num))) { strncpy(chan->exten, "a", sizeof(chan->exten)-1); chan->priority = 0; return 0; } else { ast_log(LOG_WARNING, "Can't find extension 'a' in current context. " "Not Exiting the Directory!\n"); res = 0; } } memset(ext, 0, sizeof(ext)); ext[0] = digit; res = 0; if (ast_readstring(chan, ext + 1, NUMDIGITS - 1, 3000, 3000, "#") < 0) res = -1; if (!res) { /* Search for all names which start with those digits */ v = ast_variable_browse(cfg, context); while(v && !res) { /* Find all candidate extensions */ while(v) { /* Find a candidate extension */ start = strdup(v->value); if (start) { stringp=start; strsep(&stringp, ","); pos = strsep(&stringp, ","); if (pos) { strncpy(name, pos, sizeof(name) - 1); /* Grab the last name */ if (last && strrchr(pos,' ')) pos = strrchr(pos, ' ') + 1; conv = convert(pos); if (conv) { if (!strcmp(conv, ext)) { /* Match! */ found++; free(conv); free(start); break; } free(conv); } } free(start); } v = v->next; } if (v) { /* We have a match -- play a greeting if they have it */ res = play_mailbox_owner(chan, context, dialcontext, v->name, name); switch (res) { case -1: /* user pressed '1' but extension does not exist, or * user hungup */ lastuserchoice = 0; break; case '1': /* user pressed '1' and extensions exists */ lastuserchoice = res; strncpy(chan->context, dialcontext, sizeof(chan->context) - 1); strncpy(chan->exten, v->name, sizeof(chan->exten) - 1); chan->priority = 0; break; case '*': /* user pressed '*' to skip something found */ lastuserchoice = res; res = 0; break; default: break; } v = v->next; } } if (lastuserchoice != '1') { if (found) res = ast_streamfile(chan, "dir-nomore", chan->language); else res = ast_streamfile(chan, "dir-nomatch", chan->language); if (!res) res = 1; return res; } return 0; } return res; } static int directory_exec(struct ast_channel *chan, void *data) { int res = 0; struct localuser *u; struct ast_config *cfg; int last = 1; char *context, *dialcontext, *dirintro, *options; if (!data) { ast_log(LOG_WARNING, "directory requires an argument (context[,dialcontext])\n"); return -1; } top: context = ast_strdupa(data); dialcontext = strchr(context, '|'); if (dialcontext) { *dialcontext = '\0'; dialcontext++; options = strchr(dialcontext, '|'); if (options) { *options = '\0'; options++; if (strchr(options, 'f')) last = 0; } } else dialcontext = context; cfg = realtime_directory(context); if (!cfg) { ast_log(LOG_WARNING, "Unable to open/create directory configuration %s\n", DIRECTORY_CONFIG); return -1; } LOCAL_USER_ADD(u); dirintro = ast_variable_retrieve(cfg, context, "directoryintro"); if (!dirintro || ast_strlen_zero(dirintro)) dirintro = ast_variable_retrieve(cfg, "general", "directoryintro"); if (!dirintro || ast_strlen_zero(dirintro)) { if (last) dirintro = "dir-intro"; else dirintro = "dir-intro-fn"; } if (chan->_state != AST_STATE_UP) res = ast_answer(chan); if (!res) res = ast_streamfile(chan, dirintro, chan->language); if (!res) res = ast_waitstream(chan, AST_DIGIT_ANY); ast_stopstream(chan); if (!res) res = ast_waitfordigit(chan, 5000); if (res > 0) { res = do_directory(chan, cfg, context, dialcontext, res, last); if (res > 0) { res = ast_waitstream(chan, AST_DIGIT_ANY); ast_stopstream(chan); if (res >= 0) { goto top; } } } ast_config_destroy(cfg); LOCAL_USER_REMOVE(u); return res; } int unload_module(void) { STANDARD_HANGUP_LOCALUSERS; return ast_unregister_application(app); } int load_module(void) { return ast_register_application(app, directory_exec, synopsis, descrip); } char *description(void) { return tdesc; } int usecount(void) { int res; STANDARD_USECOUNT(res); return res; } char *key() { return ASTERISK_GPL_KEY; }