2 * Asterisk -- A telephony toolkit for Linux.
4 * Provide a directory of extensions
6 * Copyright (C) 1999 - 2005, Digium, Inc.
8 * Mark Spencer <markster@digium.com>
10 * This program is free software, distributed under the terms of
11 * the GNU General Public License
14 #include <asterisk/lock.h>
15 #include <asterisk/file.h>
16 #include <asterisk/logger.h>
17 #include <asterisk/channel.h>
18 #include <asterisk/pbx.h>
19 #include <asterisk/module.h>
20 #include <asterisk/config.h>
21 #include <asterisk/say.h>
22 #include <asterisk/utils.h>
27 #include "../asterisk.h"
28 #include "../astconf.h"
30 static char *tdesc = "Extension Directory";
31 static char *app = "Directory";
33 static char *synopsis = "Provide directory of voicemail extensions";
34 static char *descrip =
35 " Directory(vm-context[|dial-context[|options]]): Presents the user with a directory\n"
36 "of extensions from which they may select by name. The list of names \n"
37 "and extensions is discovered from voicemail.conf. The vm-context argument\n"
38 "is required, and specifies the context of voicemail.conf to use. The\n"
39 "dial-context is the context to use for dialing the users, and defaults to\n"
40 "the vm-context if unspecified. The 'f' option causes the directory to match\n"
41 "based on the first name in voicemail.conf instead of the last name.\n"
42 "Returns 0 unless the user hangs up. It also sets up the channel on exit\n"
43 "to enter the extension the user selected. If the user enters '0' and there\n"
44 "exists an extension 'o' in the current context, the directory will exit with 0\n"
45 "and call control will resume at that extension. Entering '*' will exit similarly,\n"
46 "but to the 'a' extension, much like app_voicemail's behavior.\n";
48 /* For simplicity, I'm keeping the format compatible with the voicemail config,
49 but i'm open to suggestions for isolating it */
51 #define DIRECTORY_CONFIG "voicemail.conf"
53 /* How many digits to read in */
60 static char *convert(char *lastname)
64 tmp = malloc(NUMDIGITS + 1);
66 while((*lastname > 32) && lcount < NUMDIGITS) {
67 switch(toupper(*lastname)) {
129 /* play name of mailbox owner.
130 * returns: -1 for bad or missing extension
131 * '1' for selected entry from directory
132 * '*' for skipped entry from directory
134 static int play_mailbox_owner(struct ast_channel *chan, char *context, char *dialcontext, char *ext, char *name) {
140 /* Check for the VoiceMail2 greeting first */
141 snprintf(fn, sizeof(fn), "%s/voicemail/%s/%s/greet",
142 (char *)ast_config_AST_SPOOL_DIR, context, ext);
144 /* Otherwise, check for an old-style Voicemail greeting */
145 snprintf(fn2, sizeof(fn2), "%s/vm/%s/greet",
146 (char *)ast_config_AST_SPOOL_DIR, ext);
148 if (ast_fileexists(fn, NULL, chan->language) > 0) {
149 res = ast_streamfile(chan, fn, chan->language);
151 res = ast_waitstream(chan, AST_DIGIT_ANY);
153 ast_stopstream(chan);
154 } else if (ast_fileexists(fn2, NULL, chan->language) > 0) {
155 res = ast_streamfile(chan, fn2, chan->language);
157 res = ast_waitstream(chan, AST_DIGIT_ANY);
159 ast_stopstream(chan);
161 res = ast_say_character_str(chan, !ast_strlen_zero(name) ? name : ext,
162 AST_DIGIT_ANY, chan->language);
167 res = ast_streamfile(chan, "dir-instr", chan->language);
170 res = ast_waitstream(chan, AST_DIGIT_ANY);
173 res = ast_waitfordigit(chan, 3000);
175 ast_stopstream(chan);
182 if (ast_exists_extension(chan,dialcontext,ext,1,chan->cid.cid_num)) {
183 strncpy(chan->exten, ext, sizeof(chan->exten)-1);
185 strncpy(chan->context, dialcontext, sizeof(chan->context)-1);
188 "Can't find extension '%s' in context '%s'. "
189 "Did you pass the wrong context to Directory?\n",
196 /* Skip to next match in list */
201 /* Not '1', or '*', so decrement number of tries */
208 /* User hungup, so jump out now */
216 static struct ast_config *realtime_directory(char *context)
218 struct ast_config *cfg = NULL;
219 struct ast_variable *rtvar = NULL;
220 struct ast_category *cat = NULL;
221 char fullname[50] = "";
222 char mailbox[50] = "";
227 /* Load flat file config. */
228 cfg = ast_config_load(DIRECTORY_CONFIG);
231 /* Loading config failed. Even if config file doesn't exist, we should still have an ast_config. */
232 ast_log(LOG_WARNING, "Loading/Creating config failed.\n");
236 /* Load RealTime voicemail users for this context. */
237 rtvar = ast_load_realtime("voicemail", "context", context, NULL);
239 /* If we got nothing from RealTime, we can just return the Flatfile. */
243 /* Does the context exist within the Flatfile? */
244 if (ast_category_exist(cfg, context)) {
245 /* If so, get a pointer to it so we can append RealTime variables to it. */
246 cat = ast_category_get(cfg, context);
248 /* If not, make a fresh one and append it to the master config. */
249 cat = ast_category_new(context);
251 ast_log(LOG_WARNING, "Ran out of memory while creating new ast_category!\n");
252 ast_config_destroy(cfg);
255 ast_category_append(cfg, cat);
258 /* We now have a category: from the Flatfile or freshly created. */
260 if (!strcasecmp(rtvar->name, "fullname")) {
261 strncpy(fullname, rtvar->value, sizeof(fullname)-1);
263 } else if (!strcasecmp(rtvar->name, "mailbox")) {
264 strncpy(mailbox, rtvar->value, sizeof(mailbox)-1);
268 /* app_directory needs only mailbox and fullname. Fill password and email with dummy values. */
269 if (havemailbox && havename) {
270 sprintf(tmp, "9999,%s,email@email.com", fullname);
271 ast_variable_append(cat, ast_variable_new(mailbox, tmp));
282 static int do_directory(struct ast_channel *chan, struct ast_config *cfg, char *context, char *dialcontext, char digit, int last)
284 /* Read in the first three digits.. "digit" is the first digit, already read */
285 char ext[NUMDIGITS + 1];
287 struct ast_variable *v;
290 int lastuserchoice = 0;
291 char *start, *pos, *conv,*stringp=NULL;
293 if (!context || ast_strlen_zero(context)) {
295 "Directory must be called with an argument "
296 "(context in which to interpret extensions)\n");
300 if (ast_exists_extension(chan,chan->context,"o",1,chan->cid.cid_num) ||
301 (!ast_strlen_zero(chan->macrocontext) &&
302 ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num))) {
303 strncpy(chan->exten, "o", sizeof(chan->exten)-1);
308 ast_log(LOG_WARNING, "Can't find extension 'o' in current context. "
309 "Not Exiting the Directory!\n");
314 if (ast_exists_extension(chan,chan->context,"a",1,chan->cid.cid_num) ||
315 (!ast_strlen_zero(chan->macrocontext) &&
316 ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num))) {
317 strncpy(chan->exten, "a", sizeof(chan->exten)-1);
322 ast_log(LOG_WARNING, "Can't find extension 'a' in current context. "
323 "Not Exiting the Directory!\n");
327 memset(ext, 0, sizeof(ext));
330 if (ast_readstring(chan, ext + 1, NUMDIGITS - 1, 3000, 3000, "#") < 0) res = -1;
332 /* Search for all names which start with those digits */
333 v = ast_variable_browse(cfg, context);
335 /* Find all candidate extensions */
337 /* Find a candidate extension */
338 start = strdup(v->value);
341 strsep(&stringp, ",");
342 pos = strsep(&stringp, ",");
344 strncpy(name, pos, sizeof(name) - 1);
345 /* Grab the last name */
346 if (last && strrchr(pos,' '))
347 pos = strrchr(pos, ' ') + 1;
350 if (!strcmp(conv, ext)) {
366 /* We have a match -- play a greeting if they have it */
367 res = play_mailbox_owner(chan, context, dialcontext, v->name, name);
370 /* user pressed '1' but extension does not exist, or
376 /* user pressed '1' and extensions exists */
377 lastuserchoice = res;
378 strncpy(chan->context, dialcontext, sizeof(chan->context) - 1);
379 strncpy(chan->exten, v->name, sizeof(chan->exten) - 1);
383 /* user pressed '*' to skip something found */
384 lastuserchoice = res;
394 if (lastuserchoice != '1') {
396 res = ast_streamfile(chan, "dir-nomore", chan->language);
398 res = ast_streamfile(chan, "dir-nomatch", chan->language);
408 static int directory_exec(struct ast_channel *chan, void *data)
412 struct ast_config *cfg;
414 char *context, *dialcontext, *dirintro, *options;
417 ast_log(LOG_WARNING, "directory requires an argument (context[,dialcontext])\n");
422 context = ast_strdupa(data);
423 dialcontext = strchr(context, '|');
427 options = strchr(dialcontext, '|');
431 if (strchr(options, 'f'))
435 dialcontext = context;
437 cfg = realtime_directory(context);
439 ast_log(LOG_WARNING, "Unable to open/create directory configuration %s\n", DIRECTORY_CONFIG);
445 dirintro = ast_variable_retrieve(cfg, context, "directoryintro");
446 if (!dirintro || ast_strlen_zero(dirintro))
447 dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
448 if (!dirintro || ast_strlen_zero(dirintro)) {
450 dirintro = "dir-intro";
452 dirintro = "dir-intro-fn";
454 if (chan->_state != AST_STATE_UP)
455 res = ast_answer(chan);
457 res = ast_streamfile(chan, dirintro, chan->language);
459 res = ast_waitstream(chan, AST_DIGIT_ANY);
460 ast_stopstream(chan);
462 res = ast_waitfordigit(chan, 5000);
464 res = do_directory(chan, cfg, context, dialcontext, res, last);
466 res = ast_waitstream(chan, AST_DIGIT_ANY);
467 ast_stopstream(chan);
473 ast_config_destroy(cfg);
474 LOCAL_USER_REMOVE(u);
478 int unload_module(void)
480 STANDARD_HANGUP_LOCALUSERS;
481 return ast_unregister_application(app);
484 int load_module(void)
486 return ast_register_application(app, directory_exec, synopsis, descrip);
489 char *description(void)
497 STANDARD_USECOUNT(res);
503 return ASTERISK_GPL_KEY;