|
|
|
@ -46,7 +46,7 @@ static char *app = "Directory";
|
|
|
|
|
|
|
|
|
|
static char *synopsis = "Provide directory of voicemail extensions";
|
|
|
|
|
static char *descrip =
|
|
|
|
|
" Directory(vm-context[,dial-context[,options]]): This application will present\n"
|
|
|
|
|
" Directory([vm-context][,dial-context[,options]]): This application will present\n"
|
|
|
|
|
"the calling channel with a directory of extensions from which they can search\n"
|
|
|
|
|
"by name. The list of names and corresponding extensions is retrieved from the\n"
|
|
|
|
|
"voicemail configuration file, voicemail.conf.\n"
|
|
|
|
@ -56,7 +56,9 @@ static char *descrip =
|
|
|
|
|
" * - Jump to the 'a' extension, if it exists.\n\n"
|
|
|
|
|
" Parameters:\n"
|
|
|
|
|
" vm-context - This is the context within voicemail.conf to use for the\n"
|
|
|
|
|
" Directory.\n"
|
|
|
|
|
" Directory. If not specified and searchcontexts=no in\n"
|
|
|
|
|
" voicemail.conf, then \"default\" will be assumed.\n"
|
|
|
|
|
" Otherwise, in not specified, all contexts will be searched.\n"
|
|
|
|
|
" dial-context - This is the dialplan context to use when looking for an\n"
|
|
|
|
|
" extension that the user has selected, or when jumping to the\n"
|
|
|
|
|
" 'o' or 'a' extension.\n\n"
|
|
|
|
@ -112,6 +114,7 @@ enum {
|
|
|
|
|
struct directory_item {
|
|
|
|
|
char exten[AST_MAX_EXTENSION + 1];
|
|
|
|
|
char name[AST_MAX_EXTENSION + 1];
|
|
|
|
|
char context[AST_MAX_CONTEXT + 1];
|
|
|
|
|
char key[50]; /* Text to order items. Either lastname+firstname or firstname+lastname */
|
|
|
|
|
|
|
|
|
|
AST_LIST_ENTRY(directory_item) entry;
|
|
|
|
@ -234,25 +237,25 @@ static int play_mailbox_owner(struct ast_channel *chan, const char *context,
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int select_entry(struct ast_channel *chan, const char *context, const char *dialcontext, const struct directory_item *item, struct ast_flags *flags)
|
|
|
|
|
static int select_entry(struct ast_channel *chan, const char *dialcontext, const struct directory_item *item, struct ast_flags *flags)
|
|
|
|
|
{
|
|
|
|
|
ast_debug(1, "Selecting '%s' - %s@%s\n", item->name, item->exten, dialcontext);
|
|
|
|
|
ast_debug(1, "Selecting '%s' - %s@%s\n", item->name, item->exten, S_OR(dialcontext, item->context));
|
|
|
|
|
|
|
|
|
|
if (ast_test_flag(flags, OPT_FROMVOICEMAIL)) {
|
|
|
|
|
/* We still want to set the exten though */
|
|
|
|
|
ast_copy_string(chan->exten, item->exten, sizeof(chan->exten));
|
|
|
|
|
} else if (ast_goto_if_exists(chan, dialcontext, item->exten, 1)) {
|
|
|
|
|
} else if (ast_goto_if_exists(chan, S_OR(dialcontext, item->context), item->exten, 1)) {
|
|
|
|
|
ast_log(LOG_WARNING,
|
|
|
|
|
"Can't find extension '%s' in context '%s'. "
|
|
|
|
|
"Did you pass the wrong context to Directory?\n",
|
|
|
|
|
item->exten, dialcontext);
|
|
|
|
|
item->exten, S_OR(dialcontext, item->context));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
static int select_item_seq(struct ast_channel *chan, struct directory_item **items, int count, const char *dialcontext, struct ast_flags *flags)
|
|
|
|
|
{
|
|
|
|
|
struct directory_item *item, **ptr;
|
|
|
|
|
int i, res, loop;
|
|
|
|
@ -261,7 +264,7 @@ static int select_item_seq(struct ast_channel *chan, struct directory_item **ite
|
|
|
|
|
item = *ptr;
|
|
|
|
|
|
|
|
|
|
for (loop = 3 ; loop > 0; loop--) {
|
|
|
|
|
res = play_mailbox_owner(chan, context, item->exten, item->name, flags);
|
|
|
|
|
res = play_mailbox_owner(chan, item->context, item->exten, item->name, flags);
|
|
|
|
|
|
|
|
|
|
if (!res)
|
|
|
|
|
res = ast_stream_and_wait(chan, "dir-instr", AST_DIGIT_ANY);
|
|
|
|
@ -270,7 +273,7 @@ static int select_item_seq(struct ast_channel *chan, struct directory_item **ite
|
|
|
|
|
ast_stopstream(chan);
|
|
|
|
|
|
|
|
|
|
if (res == '1') { /* Name selected */
|
|
|
|
|
return select_entry(chan, context, dialcontext, item, flags) ? -1 : 1;
|
|
|
|
|
return select_entry(chan, dialcontext, item, flags) ? -1 : 1;
|
|
|
|
|
} else if (res == '*') {
|
|
|
|
|
/* Skip to next match in list */
|
|
|
|
|
break;
|
|
|
|
@ -287,7 +290,7 @@ static int select_item_seq(struct ast_channel *chan, struct directory_item **ite
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
static int select_item_menu(struct ast_channel *chan, struct directory_item **items, int count, const char *dialcontext, struct ast_flags *flags)
|
|
|
|
|
{
|
|
|
|
|
struct directory_item **block, *item;
|
|
|
|
|
int i, limit, res = 0;
|
|
|
|
@ -315,7 +318,7 @@ static int select_item_menu(struct ast_channel *chan, struct directory_item **it
|
|
|
|
|
if (!res)
|
|
|
|
|
res = ast_waitstream(chan, AST_DIGIT_ANY);
|
|
|
|
|
if (!res)
|
|
|
|
|
res = play_mailbox_owner(chan, context, item->exten, item->name, flags);
|
|
|
|
|
res = play_mailbox_owner(chan, item->context, item->exten, item->name, flags);
|
|
|
|
|
if (!res)
|
|
|
|
|
res = ast_waitstream(chan, AST_DIGIT_ANY);
|
|
|
|
|
if (!res)
|
|
|
|
@ -334,7 +337,7 @@ static int select_item_menu(struct ast_channel *chan, struct directory_item **it
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (res && res > '0' && res < '1' + limit) {
|
|
|
|
|
return select_entry(chan, context, dialcontext, block[res - '1'], flags) ? -1 : 1;
|
|
|
|
|
return select_entry(chan, dialcontext, block[res - '1'], flags) ? -1 : 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (res < 0)
|
|
|
|
@ -355,7 +358,7 @@ static struct ast_config *realtime_directory(char *context)
|
|
|
|
|
struct ast_variable *var;
|
|
|
|
|
char *mailbox;
|
|
|
|
|
const char *fullname;
|
|
|
|
|
const char *hidefromdir;
|
|
|
|
|
const char *hidefromdir, *searchcontexts = NULL;
|
|
|
|
|
char tmp[100];
|
|
|
|
|
struct ast_flags config_flags = { 0 };
|
|
|
|
|
|
|
|
|
@ -373,49 +376,64 @@ static struct ast_config *realtime_directory(char *context)
|
|
|
|
|
|
|
|
|
|
/* Get realtime entries, categorized by their mailbox number
|
|
|
|
|
and present in the requested context */
|
|
|
|
|
rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, SENTINEL);
|
|
|
|
|
if (ast_strlen_zero(context) && (searchcontexts = ast_variable_retrieve(cfg, "general", "searchcontexts"))) {
|
|
|
|
|
if (ast_true(searchcontexts)) {
|
|
|
|
|
rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", SENTINEL);
|
|
|
|
|
context = NULL;
|
|
|
|
|
} else {
|
|
|
|
|
rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", "default", SENTINEL);
|
|
|
|
|
context = "default";
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, SENTINEL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* if there are no results, just return the entries from the config file */
|
|
|
|
|
if (!rtdata)
|
|
|
|
|
if (!rtdata) {
|
|
|
|
|
return cfg;
|
|
|
|
|
|
|
|
|
|
/* Does the context exist within the config file? If not, make one */
|
|
|
|
|
cat = ast_category_get(cfg, context);
|
|
|
|
|
if (!cat) {
|
|
|
|
|
cat = ast_category_new(context, "", 99999);
|
|
|
|
|
if (!cat) {
|
|
|
|
|
ast_log(LOG_WARNING, "Out of memory\n");
|
|
|
|
|
ast_config_destroy(cfg);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
ast_category_append(cfg, cat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mailbox = NULL;
|
|
|
|
|
while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) {
|
|
|
|
|
const char *context = ast_variable_retrieve(rtdata, mailbox, "context");
|
|
|
|
|
|
|
|
|
|
fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
|
|
|
|
|
if (ast_true((hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir")))) {
|
|
|
|
|
/* Skip hidden */
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
snprintf(tmp, sizeof(tmp), "no-password,%s", S_OR(fullname, ""));
|
|
|
|
|
var = ast_variable_new(mailbox, tmp, "");
|
|
|
|
|
if (var)
|
|
|
|
|
|
|
|
|
|
/* Does the context exist within the config file? If not, make one */
|
|
|
|
|
if (!(cat = ast_category_get(cfg, context))) {
|
|
|
|
|
if (!(cat = ast_category_new(context, "", 99999))) {
|
|
|
|
|
ast_log(LOG_WARNING, "Out of memory\n");
|
|
|
|
|
ast_config_destroy(cfg);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
ast_category_append(cfg, cat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((var = ast_variable_new(mailbox, tmp, ""))) {
|
|
|
|
|
ast_variable_append(cat, var);
|
|
|
|
|
else
|
|
|
|
|
} else {
|
|
|
|
|
ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ast_config_destroy(rtdata);
|
|
|
|
|
|
|
|
|
|
return cfg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int check_match(struct directory_item **result, const char *item_fullname, const char *item_ext, const char *pattern_ext, int use_first_name)
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
struct directory_item *item;
|
|
|
|
|
const char *key = NULL;
|
|
|
|
|
int namelen;
|
|
|
|
|
|
|
|
|
|
if (ast_strlen_zero(item_fullname)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Set key to last name or first name depending on search mode */
|
|
|
|
|
if (!use_first_name)
|
|
|
|
@ -429,10 +447,13 @@ static int check_match(struct directory_item **result, const char *item_fullname
|
|
|
|
|
if (compare(key, pattern_ext))
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
ast_debug(1, "Found match %s@%s\n", item_ext, item_context);
|
|
|
|
|
|
|
|
|
|
/* Match */
|
|
|
|
|
item = ast_calloc(1, sizeof(*item));
|
|
|
|
|
if (!item)
|
|
|
|
|
return -1;
|
|
|
|
|
ast_copy_string(item->context, item_context, sizeof(item->context));
|
|
|
|
|
ast_copy_string(item->name, item_fullname, sizeof(item->name));
|
|
|
|
|
ast_copy_string(item->exten, item_ext, sizeof(item->exten));
|
|
|
|
|
|
|
|
|
@ -451,7 +472,7 @@ static int check_match(struct directory_item **result, const char *item_fullname
|
|
|
|
|
|
|
|
|
|
typedef AST_LIST_HEAD_NOLOCK(, directory_item) itemlist;
|
|
|
|
|
|
|
|
|
|
static int search_directory(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, struct ast_flags flags, itemlist *alist)
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
struct ast_variable *v;
|
|
|
|
|
char buf[AST_MAX_EXTENSION + 1], *pos, *bufptr, *cat;
|
|
|
|
@ -475,10 +496,10 @@ static int search_directory(const char *context, struct ast_config *vmcfg, struc
|
|
|
|
|
|
|
|
|
|
res = 0;
|
|
|
|
|
if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
|
|
|
|
|
res = check_match(&item, pos, v->name, ext, 0 /* use_first_name */);
|
|
|
|
|
res = check_match(&item, context, pos, v->name, ext, 0 /* use_first_name */);
|
|
|
|
|
}
|
|
|
|
|
if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
|
|
|
|
|
res = check_match(&item, pos, v->name, ext, 1 /* use_first_name */);
|
|
|
|
|
res = check_match(&item, context, pos, v->name, ext, 1 /* use_first_name */);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!res)
|
|
|
|
@ -504,10 +525,10 @@ static int search_directory(const char *context, struct ast_config *vmcfg, struc
|
|
|
|
|
|
|
|
|
|
res = 0;
|
|
|
|
|
if (ast_test_flag(&flags, OPT_LISTBYLASTNAME)) {
|
|
|
|
|
res = check_match(&item, position, cat, ext, 0 /* use_first_name */);
|
|
|
|
|
res = check_match(&item, context, position, cat, ext, 0 /* use_first_name */);
|
|
|
|
|
}
|
|
|
|
|
if (!res && ast_test_flag(&flags, OPT_LISTBYFIRSTNAME)) {
|
|
|
|
|
res = check_match(&item, position, cat, ext, 1 /* use_first_name */);
|
|
|
|
|
res = check_match(&item, context, position, cat, ext, 1 /* use_first_name */);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!res)
|
|
|
|
@ -521,6 +542,35 @@ static int search_directory(const char *context, struct ast_config *vmcfg, struc
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int search_directory(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, struct ast_flags flags, itemlist *alist)
|
|
|
|
|
{
|
|
|
|
|
const char *searchcontexts = ast_variable_retrieve(vmcfg, "general", "searchcontexts");
|
|
|
|
|
if (ast_strlen_zero(context)) {
|
|
|
|
|
if (!ast_strlen_zero(searchcontexts) && ast_true(searchcontexts)) {
|
|
|
|
|
/* Browse each context for a match */
|
|
|
|
|
int res;
|
|
|
|
|
const char *catg;
|
|
|
|
|
for (catg = ast_category_browse(vmcfg, NULL); catg; catg = ast_category_browse(vmcfg, catg)) {
|
|
|
|
|
if (!strcmp(catg, "general") || !strcmp(catg, "zonemessages")) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((res = search_directory_sub(catg, vmcfg, ucfg, ext, flags, alist))) {
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
} else {
|
|
|
|
|
ast_debug(1, "Searching by category default\n");
|
|
|
|
|
return search_directory_sub("default", vmcfg, ucfg, ext, flags, alist);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/* Browse only the listed context for a match */
|
|
|
|
|
ast_debug(1, "Searching by category %s\n", context);
|
|
|
|
|
return search_directory_sub(context, vmcfg, ucfg, ext, flags, alist);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void sort_items(struct directory_item **sorted, int count)
|
|
|
|
|
{
|
|
|
|
|
int reordered, i;
|
|
|
|
@ -565,18 +615,11 @@ static int do_directory(struct ast_channel *chan, struct ast_config *vmcfg, stru
|
|
|
|
|
int count, i;
|
|
|
|
|
char ext[10] = "";
|
|
|
|
|
|
|
|
|
|
if (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' && !goto_exten(chan, dialcontext, "o")) {
|
|
|
|
|
if (digit == '0' && !goto_exten(chan, S_OR(dialcontext, "default"), "o")) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (digit == '*' && !goto_exten(chan, dialcontext, "a")) {
|
|
|
|
|
if (digit == '*' && !goto_exten(chan, S_OR(dialcontext, "default"), "a")) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -614,16 +657,16 @@ static int do_directory(struct ast_channel *chan, struct ast_config *vmcfg, stru
|
|
|
|
|
if (option_debug) {
|
|
|
|
|
ast_debug(2, "Listing matching entries:\n");
|
|
|
|
|
for (ptr = sorted, i = 0; i < count; i++, ptr++) {
|
|
|
|
|
ast_log(LOG_DEBUG, "%s: %s\n", ptr[0]->exten, ptr[0]->name);
|
|
|
|
|
ast_debug(2, "%s: %s\n", ptr[0]->exten, ptr[0]->name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ast_test_flag(flags, OPT_SELECTFROMMENU)) {
|
|
|
|
|
/* Offer multiple entries at the same time */
|
|
|
|
|
res = select_item_menu(chan, sorted, count, context, dialcontext, flags);
|
|
|
|
|
res = select_item_menu(chan, sorted, count, dialcontext, flags);
|
|
|
|
|
} else {
|
|
|
|
|
/* Offer entries one by one */
|
|
|
|
|
res = select_item_seq(chan, sorted, count, context, dialcontext, flags);
|
|
|
|
|
res = select_item_seq(chan, sorted, count, dialcontext, flags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!res) {
|
|
|
|
@ -645,7 +688,7 @@ static int directory_exec(struct ast_channel *chan, void *data)
|
|
|
|
|
int res = 0, digit = 3;
|
|
|
|
|
struct ast_config *cfg, *ucfg;
|
|
|
|
|
const char *dirintro;
|
|
|
|
|
char *parse, *opts[OPT_ARG_ARRAY_SIZE];
|
|
|
|
|
char *parse, *opts[OPT_ARG_ARRAY_SIZE] = { 0, };
|
|
|
|
|
struct ast_flags flags = { 0 };
|
|
|
|
|
struct ast_flags config_flags = { 0 };
|
|
|
|
|
enum { FIRST, LAST, BOTH } which = LAST;
|
|
|
|
@ -656,11 +699,6 @@ static int directory_exec(struct ast_channel *chan, void *data)
|
|
|
|
|
AST_APP_ARG(options);
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (ast_strlen_zero(data)) {
|
|
|
|
|
ast_log(LOG_WARNING, "Directory requires an argument (context[,dialcontext])\n");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parse = ast_strdupa(data);
|
|
|
|
|
|
|
|
|
|
AST_STANDARD_APP_ARGS(args, parse);
|
|
|
|
@ -668,11 +706,7 @@ static int directory_exec(struct ast_channel *chan, void *data)
|
|
|
|
|
if (args.options && ast_app_parse_options(directory_app_options, &flags, opts, args.options))
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
if (ast_strlen_zero(args.dialcontext))
|
|
|
|
|
args.dialcontext = args.vmcontext;
|
|
|
|
|
|
|
|
|
|
cfg = realtime_directory(args.vmcontext);
|
|
|
|
|
if (!cfg) {
|
|
|
|
|
if (!(cfg = realtime_directory(args.vmcontext))) {
|
|
|
|
|
ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|