app_voicemail: Add Mailbox Aliases
authorGeorge Joseph <gjoseph@digium.com>
Mon, 10 Dec 2018 13:20:06 +0000 (06:20 -0700)
committerSean Bright <sean.bright@gmail.com>
Tue, 22 Jan 2019 19:32:04 +0000 (13:32 -0600)
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 <extension>@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
apps/app_voicemail.c
configs/samples/voicemail.conf.sample

diff --git a/CHANGES b/CHANGES
index 3459b47..23612fb 100644 (file)
--- 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 ------------
 ------------------------------------------------------------------------------
index d132e2b..3223af6 100644 (file)
@@ -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);
 
index e4130d3..30054b5 100644 (file)
@@ -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 <mailbox>=<password>,<name>,<email>,<pager_email>,<options>
 ; 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