From c6980e32aed6c0d0b9910b25cb4b17534aee911e Mon Sep 17 00:00:00 2001 From: George Joseph Date: Mon, 10 Dec 2018 06:20:06 -0700 Subject: [PATCH] app_voicemail: Add Mailbox Aliases You can now define an "aliases" context in voicemail.conf whose entries point to actual mailboxes. These can be used anywhere the mailbox is specified. Example: [general] aliasescontext = myaliases [default] 1234 = yadayada [myaliases] 4321@devices = 1234@default Now you can use 4321@devices to refer to the 1234@default mailbox. This can be useful to provide channel drivers with constant mailbox specifications such as @devices leaving app_voicemail to control exactly which mailbox the alias points to. Now, only voicemail has to be reloaded to make changes instead of individual channel drivers which are usually more expensive to reload. Change-Id: I395b9205c91523a334fe971be0d1de4522067b04 --- CHANGES | 6 + apps/app_voicemail.c | 315 ++++++++++++++++++++++---- configs/samples/voicemail.conf.sample | 12 +- 3 files changed, 288 insertions(+), 45 deletions(-) diff --git a/CHANGES b/CHANGES index 3459b47e5a..23612fb685 100644 --- a/CHANGES +++ b/CHANGES @@ -93,6 +93,12 @@ Features The previous behavior has been restored so both channels receive the channel variable when one of these features is invoked. +app_voicemail +------------------ + * You can now specify a special context with the "aliasescontext" parameter + in voicemail.conf which will allow you to create aliases for physical + mailboxes. + ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 16.0.0 to Asterisk 16.1.0 ------------ ------------------------------------------------------------------------------ diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c index d132e2b613..3223af67b1 100644 --- a/apps/app_voicemail.c +++ b/apps/app_voicemail.c @@ -999,6 +999,7 @@ static int skipms; static int maxlogins; static int minpassword; static int passwordlocation; +static char aliasescontext[MAX_VM_CONTEXT_LEN]; /*! Poll mailboxes for changes since there is something external to * app_voicemail that may change them. */ @@ -1051,6 +1052,27 @@ static struct ast_taskprocessor *mwi_subscription_tps; static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub); +struct alias_mailbox_mapping { + char *alias; + char *mailbox; + char buf[0]; +}; + +struct mailbox_alias_mapping { + char *alias; + char *mailbox; + char buf[0]; +}; + +#define MAPPING_BUCKETS 511 +static struct ao2_container *alias_mailbox_mappings; +AO2_STRING_FIELD_HASH_FN(alias_mailbox_mapping, alias); +AO2_STRING_FIELD_CMP_FN(alias_mailbox_mapping, alias); + +static struct ao2_container *mailbox_alias_mappings; +AO2_STRING_FIELD_HASH_FN(mailbox_alias_mapping, mailbox); +AO2_STRING_FIELD_CMP_FN(mailbox_alias_mapping, mailbox); + /* custom audio control prompts for voicemail playback */ static char listen_control_forward_key[12]; static char listen_control_reverse_key[12]; @@ -1765,9 +1787,31 @@ static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *contex ast_set2_flag(vmu, !ivm, VM_ALLOCED); AST_LIST_NEXT(vmu, list) = NULL; } - } else - vmu = find_user_realtime(ivm, context, mailbox); + } AST_LIST_UNLOCK(&users); + if (!vmu) { + vmu = find_user_realtime(ivm, context, mailbox); + } + if (!vmu && !ast_strlen_zero(aliasescontext)) { + struct alias_mailbox_mapping *mapping; + char *search_string = ast_alloca(MAX_VM_MAILBOX_LEN); + + snprintf(search_string, MAX_VM_MAILBOX_LEN, "%s%s%s", + mailbox, + ast_strlen_zero(context) ? "" : "@", + S_OR(context, "")); + + mapping = ao2_find(alias_mailbox_mappings, search_string, OBJ_SEARCH_KEY); + if (mapping) { + char *search_mailbox = NULL; + char *search_context = NULL; + + separate_mailbox(ast_strdupa(mapping->mailbox), &search_mailbox, &search_context); + ao2_ref(mapping, -1); + vmu = find_user(ivm, search_mailbox, search_context); + } + } + return vmu; } @@ -6056,6 +6100,9 @@ static int __has_voicemail(const char *context, const char *mailbox, const char struct dirent *de; char fn[256]; int ret = 0; + struct alias_mailbox_mapping *mapping; + char *c; + char *m; /* If no mailbox, return immediately */ if (ast_strlen_zero(mailbox)) @@ -6066,7 +6113,21 @@ static int __has_voicemail(const char *context, const char *mailbox, const char if (ast_strlen_zero(context)) context = "default"; - snprintf(fn, sizeof(fn), "%s%s/%s/%s", VM_SPOOL_DIR, context, mailbox, folder); + c = (char *)context; + m = (char *)mailbox; + + if (!ast_strlen_zero(aliasescontext)) { + char tmp[MAX_VM_MAILBOX_LEN]; + + snprintf(tmp, MAX_VM_MAILBOX_LEN, "%s@%s", mailbox, context); + mapping = ao2_find(alias_mailbox_mappings, tmp, OBJ_SEARCH_KEY); + if (mapping) { + separate_mailbox(ast_strdupa(mapping->mailbox), &m, &c); + ao2_ref(mapping, -1); + } + } + + snprintf(fn, sizeof(fn), "%s%s/%s/%s", VM_SPOOL_DIR, c, m, folder); if (!(dir = opendir(fn))) return 0; @@ -8096,7 +8157,24 @@ static void queue_mwi_event(const char *channel_id, const char *box, int urgent, return; } + ast_debug(3, "Queueing event for mailbox %s New: %d Old: %d\n", box, new + urgent, old); ast_publish_mwi_state_channel(mailbox, context, new + urgent, old, channel_id); + + if (!ast_strlen_zero(aliasescontext)) { + struct ao2_iterator *aliases; + struct mailbox_alias_mapping *mapping; + + aliases = ao2_find(mailbox_alias_mappings, box, OBJ_SEARCH_KEY | OBJ_MULTIPLE); + while ((mapping = ao2_iterator_next(aliases))) { + mailbox = NULL; + context = NULL; + ast_debug(3, "Found alias mapping: %s -> %s\n", mapping->alias, box); + separate_mailbox(ast_strdupa(mapping->alias), &mailbox, &context); + ast_publish_mwi_state_channel(mailbox, context, new + urgent, old, channel_id); + ao2_ref(mapping, -1); + } + ao2_iterator_destroy(aliases); + } } /*! @@ -13000,6 +13078,46 @@ static char *handle_voicemail_show_zones(struct ast_cli_entry *e, int cmd, struc return res; } +/*! \brief Show a list of voicemail zones in the CLI */ +static char *handle_voicemail_show_aliases(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ao2_iterator aliases; + struct alias_mailbox_mapping *mapping; +#define ALIASES_OUTPUT_FORMAT "%-32s %-32s\n" + char *res = CLI_SUCCESS; + + switch (cmd) { + case CLI_INIT: + e->command = "voicemail show aliases"; + e->usage = + "Usage: voicemail show aliases\n" + " Lists mailbox aliases\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 3) + return CLI_SHOWUSAGE; + + if (ast_strlen_zero(aliasescontext)) { + ast_cli(a->fd, "Aliases are not enabled\n"); + return res; + } + + ast_cli(a->fd, "Aliases context: %s\n", aliasescontext); + ast_cli(a->fd, ALIASES_OUTPUT_FORMAT, "Alias", "Mailbox"); + + aliases = ao2_iterator_init(alias_mailbox_mappings, 0); + while ((mapping = ao2_iterator_next(&aliases))) { + ast_cli(a->fd, ALIASES_OUTPUT_FORMAT, mapping->alias, mapping->mailbox); + ao2_ref(mapping, -1); + } + ao2_iterator_destroy(&aliases); + + return res; +} + /*! \brief Reload voicemail configuration from the CLI */ static char *handle_voicemail_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { @@ -13026,6 +13144,7 @@ static char *handle_voicemail_reload(struct ast_cli_entry *e, int cmd, struct as static struct ast_cli_entry cli_voicemail[] = { AST_CLI_DEFINE(handle_voicemail_show_users, "List defined voicemail boxes"), AST_CLI_DEFINE(handle_voicemail_show_zones, "List zone message formats"), + AST_CLI_DEFINE(handle_voicemail_show_aliases, "List mailbox aliases"), AST_CLI_DEFINE(handle_voicemail_reload, "Reload voicemail configuration"), }; @@ -13244,7 +13363,6 @@ static void mwi_event_cb(void *userdata, struct stasis_subscription *sub, struct if (stasis_message_type(msg) != stasis_subscription_change_type()) { return; } - change = stasis_message_data(msg); if (change->topic == ast_mwi_topic_all()) { return; @@ -13662,11 +13780,98 @@ static int load_config_from_memory(int reload, struct ast_config *cfg, struct as } #endif +static struct alias_mailbox_mapping *alias_mailbox_mapping_create(const char *alias, const char *mailbox) +{ + struct alias_mailbox_mapping *mapping; + size_t from_len = strlen(alias) + 1; + size_t to_len = strlen(mailbox) + 1; + + mapping = ao2_alloc(sizeof(*mapping) + from_len + to_len, NULL); + if (!mapping) { + return NULL; + } + mapping->alias = mapping->buf; + mapping->mailbox = mapping->buf + from_len; + strcpy(mapping->alias, alias); /* Safe */ + strcpy(mapping->mailbox, mailbox); /* Safe */ + + return mapping; +} + +static void load_aliases(struct ast_config *cfg) +{ + struct ast_variable *var; + + if (ast_strlen_zero(aliasescontext)) { + return; + } + var = ast_variable_browse(cfg, aliasescontext); + while (var) { + struct alias_mailbox_mapping *mapping = alias_mailbox_mapping_create(var->name, var->value); + if (mapping) { + ao2_link(alias_mailbox_mappings, mapping); + ao2_link(mailbox_alias_mappings, mapping); + ao2_ref(mapping, -1); + } + var = var->next; + } +} + +static void load_zonemessages(struct ast_config *cfg) +{ + struct ast_variable *var; + + var = ast_variable_browse(cfg, "zonemessages"); + while (var) { + struct vm_zone *z; + char *msg_format, *tzone; + + z = ast_malloc(sizeof(*z)); + if (!z) { + return; + } + + msg_format = ast_strdupa(var->value); + tzone = strsep(&msg_format, "|,"); + if (msg_format) { + ast_copy_string(z->name, var->name, sizeof(z->name)); + ast_copy_string(z->timezone, tzone, sizeof(z->timezone)); + ast_copy_string(z->msg_format, msg_format, sizeof(z->msg_format)); + AST_LIST_LOCK(&zones); + AST_LIST_INSERT_HEAD(&zones, z, list); + AST_LIST_UNLOCK(&zones); + } else { + ast_log(AST_LOG_WARNING, "Invalid timezone definition at line %d\n", var->lineno); + ast_free(z); + } + var = var->next; + } +} + +static void load_users(struct ast_config *cfg) +{ + struct ast_variable *var; + char *cat = NULL; + + while ((cat = ast_category_browse(cfg, cat))) { + if (strcasecmp(cat, "general") == 0 + || strcasecmp(cat, aliasescontext) == 0 + || strcasecmp(cat, "zonemessages") == 0) { + continue; + } + + var = ast_variable_browse(cfg, cat); + while (var) { + append_mailbox(cat, var->name, var->value); + var = var->next; + } + } +} + static int actual_load_config(int reload, struct ast_config *cfg, struct ast_config *ucfg) { struct ast_vm_user *current; char *cat; - struct ast_variable *var; const char *val; char *q, *stringp, *tmp; int x; @@ -13695,6 +13900,10 @@ static int actual_load_config(int reload, struct ast_config *cfg, struct ast_con /* Free all the zones structure */ free_vm_zones(); + /* Remove all aliases */ + ao2_callback(alias_mailbox_mappings, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL); + ao2_callback(mailbox_alias_mappings, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL); + AST_LIST_LOCK(&users); memset(ext_pass_cmd, 0, sizeof(ext_pass_cmd)); @@ -13706,6 +13915,11 @@ static int actual_load_config(int reload, struct ast_config *cfg, struct ast_con if (!(val = ast_variable_retrieve(cfg, "general", "userscontext"))) val = "default"; ast_copy_string(userscontext, val, sizeof(userscontext)); + + aliasescontext[0] = '\0'; + val = ast_variable_retrieve(cfg, "general", "aliasescontext"); + ast_copy_string(aliasescontext, S_OR(val, ""), sizeof(aliasescontext)); + /* Attach voice message to mail message ? */ if (!(val = ast_variable_retrieve(cfg, "general", "attach"))) val = "yes"; @@ -14307,45 +14521,16 @@ static int actual_load_config(int reload, struct ast_config *cfg, struct ast_con } /* load mailboxes from voicemail.conf */ - cat = ast_category_browse(cfg, NULL); - while (cat) { - if (strcasecmp(cat, "general")) { - var = ast_variable_browse(cfg, cat); - if (strcasecmp(cat, "zonemessages")) { - /* Process mailboxes in this context */ - while (var) { - append_mailbox(cat, var->name, var->value); - var = var->next; - } - } else { - /* Timezones in this context */ - while (var) { - struct vm_zone *z; - if ((z = ast_malloc(sizeof(*z)))) { - char *msg_format, *tzone; - msg_format = ast_strdupa(var->value); - tzone = strsep(&msg_format, "|,"); - if (msg_format) { - ast_copy_string(z->name, var->name, sizeof(z->name)); - ast_copy_string(z->timezone, tzone, sizeof(z->timezone)); - ast_copy_string(z->msg_format, msg_format, sizeof(z->msg_format)); - AST_LIST_LOCK(&zones); - AST_LIST_INSERT_HEAD(&zones, z, list); - AST_LIST_UNLOCK(&zones); - } else { - ast_log(AST_LOG_WARNING, "Invalid timezone definition at line %d\n", var->lineno); - ast_free(z); - } - } else { - AST_LIST_UNLOCK(&users); - return -1; - } - var = var->next; - } - } - } - cat = ast_category_browse(cfg, cat); - } + + /* + * Aliases must be loaded before users or the aliases won't be notified + * if there's existing voicemail in the user mailbox. + */ + load_aliases(cfg); + + load_zonemessages(cfg); + + load_users(cfg); AST_LIST_UNLOCK(&users); @@ -15096,6 +15281,16 @@ static int unload_module(void) return res; } +static void print_mappings(void *v_obj, void *where, ao2_prnt_fn *prnt) +{ + struct alias_mailbox_mapping *mapping = v_obj; + + if (!mapping) { + return; + } + prnt(where, "Alias: %s Mailbox: %s", mapping->alias, mapping->mailbox); +} + /*! * \brief Load the module * @@ -15122,6 +15317,38 @@ static int load_module(void) return AST_MODULE_LOAD_DECLINE; } + alias_mailbox_mappings = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, MAPPING_BUCKETS, + alias_mailbox_mapping_hash_fn, NULL, alias_mailbox_mapping_cmp_fn); + if (!alias_mailbox_mappings) { + ast_log(LOG_ERROR, "Unable to create alias_mailbox_mappings container\n"); + ao2_cleanup(inprocess_container); + return AST_MODULE_LOAD_DECLINE; + } + res = ao2_container_register("voicemail_alias_mailbox_mappings", alias_mailbox_mappings, print_mappings); + if (res) { + ast_log(LOG_ERROR, "Unable to register alias_mailbox_mappings container\n"); + ao2_cleanup(inprocess_container); + ao2_cleanup(alias_mailbox_mappings); + return AST_MODULE_LOAD_DECLINE; + } + + mailbox_alias_mappings = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, MAPPING_BUCKETS, + mailbox_alias_mapping_hash_fn, NULL, mailbox_alias_mapping_cmp_fn); + if (!mailbox_alias_mappings) { + ast_log(LOG_ERROR, "Unable to create mailbox_alias_mappings container\n"); + ao2_cleanup(inprocess_container); + ao2_cleanup(alias_mailbox_mappings); + return AST_MODULE_LOAD_DECLINE; + } + res = ao2_container_register("voicemail_mailbox_alias_mappings", mailbox_alias_mappings, print_mappings); + if (res) { + ast_log(LOG_ERROR, "Unable to register mailbox_alias_mappings container\n"); + ao2_cleanup(inprocess_container); + ao2_cleanup(alias_mailbox_mappings); + ao2_cleanup(mailbox_alias_mappings); + return AST_MODULE_LOAD_DECLINE; + } + /* compute the location of the voicemail spool directory */ snprintf(VM_SPOOL_DIR, sizeof(VM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR); diff --git a/configs/samples/voicemail.conf.sample b/configs/samples/voicemail.conf.sample index e4130d356f..30054b59c7 100644 --- a/configs/samples/voicemail.conf.sample +++ b/configs/samples/voicemail.conf.sample @@ -73,6 +73,10 @@ maxlogins=3 ; ;userscontext=default ; +; Aliases allow a mailbox to be referenced by an alias. The aliases are +; specified in the special context named here. There is no default. +;aliasescontext=myaliases +; ; If you need to have an external program, i.e. /usr/bin/myapp ; called when a voicemail is left, delivered, or your voicemailbox ; is checked, uncomment this. @@ -233,7 +237,6 @@ pagerdateformat=%A, %B %d, %Y at %r ; Default: no ; ----------------------------------------------------------------------------- -; ; Each mailbox is listed in the form =,,,, ; If email is specified, a message will be sent when a voicemail is received, to @@ -451,6 +454,13 @@ european=Europe/Copenhagen|'vm-received' a d b 'digits/at' HM ;4110 => 3443,Rob Flynn,rflynn@blueridge.net ;4235 => 1234,Jim Holmes,jim@astricon.ips,,Tz=european +; +; Aliases allow alternate references to mailboxes. See the "aliasescontext" +; parameter in the "general" section. +; +[myaliases] +1234@devices => 1234@default +;6200@devices => 4200@default ; ; Mailboxes may be organized into multiple contexts for