From 48c6e3fb1da06b8e76a73a4d9099f33664b4b372 Mon Sep 17 00:00:00 2001 From: Mike Bradeen Date: Tue, 20 Jun 2023 10:32:14 -0600 Subject: [PATCH] app_voicemail: add CLI commands for message manipulation Adds CLI commands to allow move/remove/forward individual messages from a particular mailbox folder. The forward command can be used to copy a message within a mailbox or to another mailbox. Also adds a show mailbox, required to retrieve message ID's. Resolves: #170 UserNote: The following CLI commands have been added to app_voicemail voicemail show mailbox Show contents of mailbox @ voicemail remove Remove message from in mailbox @ voicemail move Move message in mailbox & from to voicemail forward Forward message in mailbox @ to mailbox @ --- apps/app_voicemail.c | 391 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 389 insertions(+), 2 deletions(-) diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c index 4699e73058..263348a794 100644 --- a/apps/app_voicemail.c +++ b/apps/app_voicemail.c @@ -11418,6 +11418,383 @@ static int vm_playmsgexec(struct ast_channel *chan, const char *data) return 0; } +static int show_mailbox_details(struct ast_cli_args *a) +{ +#define VMBOX_STRING_HEADER_FORMAT "%-32.32s %-32.32s %-16.16s %-16.16s %-16.16s %-16.16s\n" +#define VMBOX_STRING_DATA_FORMAT "%-32.32s %-32.32s %-16.16s %-16.16s %-16.16s %-16.16s\n" + + const char *mailbox = a->argv[3]; + const char *context = a->argv[4]; + struct vm_state vms; + struct ast_vm_user *vmu = NULL, vmus; + memset(&vmus, 0, sizeof(vmus)); + memset(&vms, 0, sizeof(vms)); + + if (!(vmu = find_user(&vmus, context, mailbox))) { + ast_cli(a->fd, "Can't find voicemail user %s@%s\n", mailbox, context); + return -1; + } + + ast_cli(a->fd, VMBOX_STRING_HEADER_FORMAT, "Full Name", "Email", "Pager", "Language", "Locale", "Time Zone"); + ast_cli(a->fd, VMBOX_STRING_DATA_FORMAT, vmu->fullname, vmu->email, vmu->pager, vmu->language, vmu->locale, vmu->zonetag); + + return 0; +} + +static int show_mailbox_snapshot(struct ast_cli_args *a) +{ +#define VM_STRING_HEADER_FORMAT "%-8.8s %-32.32s %-32.32s %-9.9s %-6.6s %-30.30s\n" + const char *mailbox = a->argv[3]; + const char *context = a->argv[4]; + struct ast_vm_mailbox_snapshot *mailbox_snapshot; + struct ast_vm_msg_snapshot *msg; + + /* Take a snapshot of the mailbox and walk through each folder's contents */ + mailbox_snapshot = ast_vm_mailbox_snapshot_create(mailbox, context, NULL, 0, AST_VM_SNAPSHOT_SORT_BY_ID, 0); + if (!mailbox_snapshot) { + ast_cli(a->fd, "Can't create snapshot for voicemail user %s@%s\n", mailbox, context); + return -1; + } + + ast_cli(a->fd, VM_STRING_HEADER_FORMAT, "Folder", "Caller ID", "Date", "Duration", "Flag", "ID"); + + for (int i = 0; i < mailbox_snapshot->folders; i++) { + AST_LIST_TRAVERSE(&((mailbox_snapshot)->snapshots[i]), msg, msg) { + ast_cli(a->fd, VM_STRING_HEADER_FORMAT, msg->folder_name, msg->callerid, msg->origdate, msg->duration, + msg->flag, msg->msg_id); + } + } + + ast_cli(a->fd, "%d Message%s Total\n", mailbox_snapshot->total_msg_num, ESS(mailbox_snapshot->total_msg_num)); + /* done, destroy. */ + mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot); + + return 0; +} + +static int show_messages_for_mailbox(struct ast_cli_args *a) +{ + if (show_mailbox_details(a)){ + return -1; + } + ast_cli(a->fd, "\n"); + return show_mailbox_snapshot(a); +} + +static int forward_message_from_mailbox(struct ast_cli_args *a) +{ + const char *from_mailbox = a->argv[2]; + const char *from_context = a->argv[3]; + const char *from_folder = a->argv[4]; + const char *id[] = { a->argv[5] }; + const char *to_mailbox = a->argv[6]; + const char *to_context = a->argv[7]; + const char *to_folder = a->argv[8]; + int ret = vm_msg_forward(from_mailbox, from_context, from_folder, to_mailbox, to_context, to_folder, 1, id, 0); + if (ret) { + ast_cli(a->fd, "Error forwarding message %s from mailbox %s@%s %s to mailbox %s@%s %s\n", + id[0], from_mailbox, from_context, from_folder, to_mailbox, to_context, to_folder); + } else { + ast_cli(a->fd, "Forwarded message %s from mailbox %s@%s %s to mailbox %s@%s %s\n", + id[0], from_mailbox, from_context, from_folder, to_mailbox, to_context, to_folder); + } + return ret; +} + +static int move_message_from_mailbox(struct ast_cli_args *a) +{ + const char *mailbox = a->argv[2]; + const char *context = a->argv[3]; + const char *from_folder = a->argv[4]; + const char *id[] = { a->argv[5] }; + const char *to_folder = a->argv[6]; + int ret = vm_msg_move(mailbox, context, 1, from_folder, id, to_folder); + if (ret) { + ast_cli(a->fd, "Error moving message %s from mailbox %s@%s %s to %s\n", + id[0], mailbox, context, from_folder, to_folder); + } else { + ast_cli(a->fd, "Moved message %s from mailbox %s@%s %s to %s\n", + id[0], mailbox, context, from_folder, to_folder); + } + return ret; +} + +static int remove_message_from_mailbox(struct ast_cli_args *a) +{ + const char *mailbox = a->argv[2]; + const char *context = a->argv[3]; + const char *folder = a->argv[4]; + const char *id[] = { a->argv[5] }; + int ret = vm_msg_remove(mailbox, context, 1, folder, id); + if (ret) { + ast_cli(a->fd, "Error removing message %s from mailbox %s@%s %s\n", + id[0], mailbox, context, folder); + } else { + ast_cli(a->fd, "Removed message %s from mailbox %s@%s %s\n", + id[0], mailbox, context, folder); + } + return ret; +} + +static char *complete_voicemail_show_mailbox(struct ast_cli_args *a) +{ + const char *word = a->word; + int pos = a->pos; + int state = a->n; + int which = 0; + int wordlen; + struct ast_vm_user *vmu; + const char *context = "", *mailbox = ""; + char *ret = NULL; + + /* 0 - voicemail; 1 - show; 2 - mailbox; 3 - ; 4 - */ + if (pos == 3) { + wordlen = strlen(word); + AST_LIST_LOCK(&users); + AST_LIST_TRAVERSE(&users, vmu, list) { + if (!strncasecmp(word, vmu->mailbox , wordlen)) { + if (mailbox && strcmp(mailbox, vmu->mailbox) && ++which > state) { + ret = ast_strdup(vmu->mailbox); + AST_LIST_UNLOCK(&users); + return ret; + } + mailbox = vmu->mailbox; + } + } + AST_LIST_UNLOCK(&users); + } else if (pos == 4) { + /* Only display contexts that match the user in pos 3 */ + const char *box = a->argv[3]; + wordlen = strlen(word); + AST_LIST_LOCK(&users); + AST_LIST_TRAVERSE(&users, vmu, list) { + if (!strncasecmp(word, vmu->context, wordlen) && !strcasecmp(box, vmu->mailbox)) { + if (context && strcmp(context, vmu->context) && ++which > state) { + ret = ast_strdup(vmu->context); + AST_LIST_UNLOCK(&users); + return ret; + } + context = vmu->context; + } + } + AST_LIST_UNLOCK(&users); + } + + return ret; +} + +static char *handle_voicemail_show_mailbox(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "voicemail show mailbox"; + e->usage = + "Usage: voicemail show mailbox \n" + " Show contents of mailbox @\n"; + return NULL; + case CLI_GENERATE: + return complete_voicemail_show_mailbox(a); + case CLI_HANDLER: + break; + } + + if (a->argc != 5) { + return CLI_SHOWUSAGE; + } + + if (show_messages_for_mailbox(a)) { + return CLI_FAILURE; + } + + return CLI_SUCCESS; +} + +/* Handles filling in data for one of the following three formats (based on maxpos = 5|6|8): + + maxpos = 5 + 0 - voicemail; 1 - forward; 2 - ; 3 - ; 4 - ; 5 - ; + maxpos = 6 + 0 - voicemail; 1 - forward; 2 - ; 3 - ; 4 - ; 5 - ; + 6 - ; + maxpos = 8 + 0 - voicemail; 1 - forward; 2 - ; 3 - ; 4 - ; 5 - ; + 6 - ; 7 - ; 8 - ; + + Passing in the maximum expected position 'maxpos' helps us fill in the missing entries in one function + instead of three by taking advantage of the overlap in the command sequence between forward, move and + remove as each of these use nearly the same syntax up until their maximum number of arguments. + The value of pos = 6 changes to be either or based on maxpos being 6 or 8. +*/ + +static char *complete_voicemail_move_message(struct ast_cli_args *a, int maxpos) +{ + const char *word = a->word; + int pos = a->pos; + int state = a->n; + int which = 0; + int wordlen; + struct ast_vm_user *vmu; + const char *context = "", *mailbox = "", *folder = "", *id = ""; + char *ret = NULL; + + if (pos > maxpos) { + /* If the passed in pos is above the max, return NULL to avoid 'over-filling' the cli */ + return NULL; + } + + /* if we are in pos 2 or pos 6 in 'forward' mode */ + if (pos == 2 || (pos == 6 && maxpos == 8)) { + /* find users */ + wordlen = strlen(word); + AST_LIST_LOCK(&users); + AST_LIST_TRAVERSE(&users, vmu, list) { + if (!strncasecmp(word, vmu->mailbox , wordlen)) { + if (mailbox && strcmp(mailbox, vmu->mailbox) && ++which > state) { + ret = ast_strdup(vmu->mailbox); + AST_LIST_UNLOCK(&users); + return ret; + } + mailbox = vmu->mailbox; + } + } + AST_LIST_UNLOCK(&users); + } else if (pos == 3 || pos == 7) { + /* find contexts that match the user */ + mailbox = (pos == 3) ? a->argv[2] : a->argv[6]; + wordlen = strlen(word); + AST_LIST_LOCK(&users); + AST_LIST_TRAVERSE(&users, vmu, list) { + if (!strncasecmp(word, vmu->context, wordlen) && !strcasecmp(mailbox, vmu->mailbox)) { + if (context && strcmp(context, vmu->context) && ++which > state) { + ret = ast_strdup(vmu->context); + AST_LIST_UNLOCK(&users); + return ret; + } + context = vmu->context; + } + } + AST_LIST_UNLOCK(&users); + } else if (pos == 4 || pos == 8 || (pos == 6 && maxpos == 6) ) { + /* Walk through the standard folders */ + wordlen = strlen(word); + for (int i = 0; i < ARRAY_LEN(mailbox_folders); i++) { + if (folder && !strncasecmp(word, mailbox_folders[i], wordlen) && ++which > state) { + return ast_strdup(mailbox_folders[i]); + } + folder = mailbox_folders[i]; + } + } else if (pos == 5) { + /* find messages in the folder */ + struct ast_vm_mailbox_snapshot *mailbox_snapshot; + struct ast_vm_msg_snapshot *msg; + int i = 0; + mailbox = a->argv[2]; + context = a->argv[3]; + folder = a->argv[4]; + wordlen = strlen(word); + + /* Take a snapshot of the mailbox and snag the individual info */ + if ((mailbox_snapshot = ast_vm_mailbox_snapshot_create(mailbox, context, folder, 0, AST_VM_SNAPSHOT_SORT_BY_ID, 0))) { + /* we are only requesting the one folder, but we still need to know it's index */ + for (i = 0; i < ARRAY_LEN(mailbox_folders); i++) { + if (!strcasecmp(mailbox_folders[i], folder)) { + break; + } + } + AST_LIST_TRAVERSE(&((mailbox_snapshot)->snapshots[i]), msg, msg) { + if (id && !strncasecmp(word, msg->msg_id, wordlen) && ++which > state) { + ret = ast_strdup(msg->msg_id); + break; + } + id = msg->msg_id; + } + /* done, destroy. */ + mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot); + } + } + + return ret; +} + +static char *handle_voicemail_forward_message(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "voicemail forward"; + e->usage = + "Usage: voicemail forward \n" + " Forward message in mailbox @ \n" + " to mailbox @ \n"; + return NULL; + case CLI_GENERATE: + return complete_voicemail_move_message(a, 8); + case CLI_HANDLER: + break; + } + + if (a->argc != 9) { + return CLI_SHOWUSAGE; + } + + if (forward_message_from_mailbox(a)) { + return CLI_FAILURE; + } + + return CLI_SUCCESS; +} + +static char *handle_voicemail_move_message(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "voicemail move"; + e->usage = + "Usage: voicemail move \n" + " Move message in mailbox & from to \n"; + return NULL; + case CLI_GENERATE: + return complete_voicemail_move_message(a, 6); + case CLI_HANDLER: + break; + } + + if (a->argc != 7) { + return CLI_SHOWUSAGE; + } + + if (move_message_from_mailbox(a)) { + return CLI_FAILURE; + } + + return CLI_SUCCESS; +} + +static char *handle_voicemail_remove_message(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "voicemail remove"; + e->usage = + "Usage: voicemail remove \n" + " Remove message from in mailbox @\n"; + return NULL; + case CLI_GENERATE: + return complete_voicemail_move_message(a, 5); + case CLI_HANDLER: + break; + } + + if (a->argc != 6) { + return CLI_SHOWUSAGE; + } + + if (remove_message_from_mailbox(a)) { + return CLI_FAILURE; + } + + return CLI_SUCCESS; +} + static int vm_execmain(struct ast_channel *chan, const char *data) { /* XXX This is, admittedly, some pretty horrendous code. For some @@ -12776,19 +13153,25 @@ static char *complete_voicemail_show_users(const char *line, const char *word, i int wordlen; struct ast_vm_user *vmu; const char *context = ""; + char *ret; /* 0 - voicemail; 1 - show; 2 - users; 3 - for; 4 - */ if (pos > 4) return NULL; wordlen = strlen(word); + AST_LIST_LOCK(&users); AST_LIST_TRAVERSE(&users, vmu, list) { if (!strncasecmp(word, vmu->context, wordlen)) { - if (context && strcmp(context, vmu->context) && ++which > state) - return ast_strdup(vmu->context); + if (context && strcmp(context, vmu->context) && ++which > state) { + ret = ast_strdup(vmu->context); + AST_LIST_UNLOCK(&users); + return ret; + } /* ignore repeated contexts ? */ context = vmu->context; } } + AST_LIST_UNLOCK(&users); return NULL; } @@ -12972,6 +13355,10 @@ static struct ast_cli_entry cli_voicemail[] = { 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"), + AST_CLI_DEFINE(handle_voicemail_show_mailbox, "Display a mailbox's content details"), + AST_CLI_DEFINE(handle_voicemail_forward_message, "Forward message to another folder"), + AST_CLI_DEFINE(handle_voicemail_move_message, "Move message to another folder"), + AST_CLI_DEFINE(handle_voicemail_remove_message, "Remove message"), }; static int poll_subscribed_mailbox(struct ast_mwi_state *mwi_state, void *data)