From 14a985560ed5830aa3e1b5880890a59a5d0f0c2f Mon Sep 17 00:00:00 2001 From: Mark Michelson Date: Mon, 4 Jun 2012 20:26:12 +0000 Subject: Merge changes dealing with support for Digium phones. Presence support has been added. This is accomplished by allowing for presence hints in addition to device state hints. A dialplan function called PRESENCE_STATE has been added to allow for setting and reading presence. Presence can be transmitted to Digium phones using custom XML elements in a PIDF presence document. Voicemail has new APIs that allow for moving, removing, forwarding, and playing messages. Messages have had a new unique message ID added to them so that the APIs will work reliably. The state of a voicemail mailbox can be obtained using an API that allows one to get a snapshot of the mailbox. A voicemail Dialplan App called VoiceMailPlayMsg has been added to be able to play back a specific message. Configuration hooks have been added. Configuration hooks allow for a piece of code to be executed when a specific configuration file is loaded by a specific module. This is useful for modules that are dependent on the configuration of other modules. chan_sip now has a public method that allows for a custom SIP INFO request to be sent mid-dialog. Digium phones use this in order to display progress bars when files are played. Messaging support has been expanded a bit. The main visible difference is the addition of an AMI action MessageSend. Finally, a ParkingLots manager action has been added in order to get a list of parking lots. git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@368435 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- apps/app_voicemail.c | 1844 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 1731 insertions(+), 113 deletions(-) (limited to 'apps/app_voicemail.c') diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c index eec08a484..043d93475 100644 --- a/apps/app_voicemail.c +++ b/apps/app_voicemail.c @@ -113,12 +113,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/module.h" #include "asterisk/adsi.h" #include "asterisk/app.h" +#include "asterisk/app_voicemail.h" #include "asterisk/manager.h" #include "asterisk/dsp.h" #include "asterisk/localtime.h" #include "asterisk/cli.h" #include "asterisk/utils.h" #include "asterisk/stringfields.h" +#include "asterisk/strings.h" #include "asterisk/smdi.h" #include "asterisk/astobj2.h" #include "asterisk/event.h" @@ -334,6 +336,30 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + + + Play a single voice mail msg from a mailbox by msg id. + + + + + + + + The msg id of the msg to play back. + + + + This application sets the following channel variable upon completion: + + + The status of the playback attempt as a text string. + + + + + + Play the name of a voicemail user @@ -469,7 +495,8 @@ static int save_body(BODY *body, struct vm_state *vms, char *section, char *form static void get_mailbox_delimiter(struct vm_state *vms, MAILSTREAM *stream); static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota); static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target); -static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag); +static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag, const char *msg_id); +static void vm_imap_update_msg_id(char *dir, int msgnum, const char *msg_id, struct ast_vm_user *vmu, struct ast_config *msg_cfg, int folder); static void update_messages_by_imapuser(const char *user, unsigned long number); static int vm_delete(char *file); @@ -552,7 +579,6 @@ static AST_LIST_HEAD_STATIC(vmstates, vmstate); #define ERROR_LOCK_PATH -100 #define OPERATOR_EXIT 300 - enum vm_box { NEW_FOLDER, OLD_FOLDER, @@ -600,6 +626,25 @@ AST_APP_OPTIONS(vm_app_options, { AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY) }); +static const char * const mailbox_folders[] = { +#ifdef IMAP_STORAGE + imapfolder, +#else + "INBOX", +#endif + "Old", + "Work", + "Family", + "Friends", + "Cust1", + "Cust2", + "Cust3", + "Cust4", + "Cust5", + "Deleted", + "Urgent", +}; + static int load_config(int reload); #ifdef TEST_FRAMEWORK static int load_config_from_memory(int reload, struct ast_config *cfg, struct ast_config *ucfg); @@ -793,28 +838,31 @@ static char odbc_database[80]; static char odbc_table[80]; #define RETRIEVE(a,b,c,d) retrieve_file(a,b) #define DISPOSE(a,b) remove_file(a,b) -#define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d) +#define STORE(a,b,c,d,e,f,g,h,i,j,k) store_file(a,b,c,d) #define EXISTS(a,b,c,d) (message_exists(a,b)) #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f)) #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f)) #define DELETE(a,b,c,d) (delete_file(a,b)) +#define UPDATE_MSG_ID(a, b, c, d, e, f) (odbc_update_msg_id((a), (b), (c))) #else #ifdef IMAP_STORAGE #define DISPOSE(a,b) (imap_remove_file(a,b)) -#define STORE(a,b,c,d,e,f,g,h,i,j) (imap_store_file(a,b,c,d,e,f,g,h,i,j)) +#define STORE(a,b,c,d,e,f,g,h,i,j,k) (imap_store_file(a,b,c,d,e,f,g,h,i,j,k)) #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d) #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0) #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h)); #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h)); #define DELETE(a,b,c,d) (vm_imap_delete(a,b,d)) +#define UPDATE_MSG_ID(a, b, c, d, e, f) (vm_imap_update_msg_id((a), (b), (c), (d), (e), (f))) #else #define RETRIEVE(a,b,c,d) #define DISPOSE(a,b) -#define STORE(a,b,c,d,e,f,g,h,i,j) +#define STORE(a,b,c,d,e,f,g,h,i,j,k) #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0) #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h)); #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h)); #define DELETE(a,b,c,d) (vm_delete(c)) +#define UPDATE_MSG_ID(a, b, c, d, e, f) #endif #endif @@ -852,6 +900,8 @@ static char *app2 = "VoiceMailMain"; static char *app3 = "MailboxExists"; static char *app4 = "VMAuthenticate"; +static char *playmsg_app = "VoiceMailPlayMsg"; + static char *sayname_app = "VMSayName"; static AST_LIST_HEAD_STATIC(users, ast_vm_user); @@ -976,21 +1026,39 @@ static char pagerdateformat[32] = "%A, %B %d, %Y at %r"; /* Forward declarations - generic */ static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box); +static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu); static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg, int option, signed char record_gain); static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context); static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, int *sound_duration, const char *unlockdir, - signed char record_gain, struct vm_state *vms, char *flag); + signed char record_gain, struct vm_state *vms, char *flag, const char *msg_id); static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain); static int vm_play_folder_name(struct ast_channel *chan, char *mbox); static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msgnum, long duration, char *fmt, char *cidnum, char *cidname, const char *flag); -static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap, const char *flag); +static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap, const char *flag, const char *msg_id); static void apply_options(struct ast_vm_user *vmu, const char *options); static int add_email_attachment(FILE *p, struct ast_vm_user *vmu, char *format, char *attach, char *greeting_attachment, char *mailbox, char *bound, char *filename, int last, int msgnum); static int is_valid_dtmf(const char *key); static void read_password_from_file(const char *secretfn, char *password, int passwordlen); static int write_password_to_file(const char *secretfn, const char *password); +struct ast_str *vm_mailbox_snapshot_str(const char *mailbox, const char *context); static const char *substitute_escapes(const char *value); +static int message_range_and_existence_check(struct vm_state *vms, const char *msg_ids [], size_t num_msgs, int *msg_nums, struct ast_vm_user *vmu); +/*! + * Place a message in the indicated folder + * + * \param vmu Voicemail user + * \param vms Current voicemail state for the user + * \param msg The message number to save + * \param box The folder into which the message should be saved + * \param[out] newmsg The new message number of the saved message + * \param move Tells whether to copy or to move the message + * + * \note the "move" parameter is only honored for IMAP voicemail presently + * \retval 0 Success + * \revval other Failure + */ +static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box, int *newmsg, int move); struct ao2_container *inprocess_container; @@ -1790,25 +1858,6 @@ static int create_dirpath(char *dest, int len, const char *context, const char * return 0; } -static const char * const mailbox_folders[] = { -#ifdef IMAP_STORAGE - imapfolder, -#else - "INBOX", -#endif - "Old", - "Work", - "Family", - "Friends", - "Cust1", - "Cust2", - "Cust3", - "Cust4", - "Cust5", - "Deleted", - "Urgent", -}; - static const char *mbox(struct ast_vm_user *vmu, int id) { #ifdef IMAP_STORAGE @@ -1909,6 +1958,79 @@ static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu) ast_mutex_unlock(&vms->lock); } +static void vm_imap_update_msg_id(char *dir, int msgnum, const char *msg_id, struct ast_vm_user *vmu, struct ast_config *msg_cfg, int folder) +{ + struct ast_channel *chan; + char *cid; + char *cid_name; + char *cid_num; + struct vm_state *vms; + const char *duration_str; + int duration = 0; + + /* + * First, get things initially set up. If any of this fails, then + * back out before doing anything substantial + */ + vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0); + if (!vms) { + return; + } + + if (open_mailbox(vms, vmu, folder)) { + return; + } + + chan = ast_dummy_channel_alloc(); + if (!chan) { + close_mailbox(vms, vmu); + return; + } + + /* + * We need to make sure the new message we save has the same + * callerid, flag, and duration as the original message + */ + cid = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "callerid")); + + if (!ast_strlen_zero(cid)) { + ast_callerid_parse(cid, &cid_name, &cid_num); + ast_party_caller_init(ast_channel_caller(chan)); + if (!ast_strlen_zero(cid_name)) { + ast_channel_caller(chan)->id.name.valid = 1; + ast_channel_caller(chan)->id.name.str = ast_strdup(cid_name); + } + if (!ast_strlen_zero(cid_num)) { + ast_channel_caller(chan)->id.number.valid = 1; + ast_channel_caller(chan)->id.number.str = ast_strdup(cid_num); + } + } + + duration_str = ast_variable_retrieve(msg_cfg, "message", "duration"); + + if (!ast_strlen_zero(duration_str)) { + sscanf(duration_str, "%30d", &duration); + } + + /* + * IMAP messages cannot be altered once delivered. So we have to delete the + * current message and then re-add it with the updated message ID. + * + * Furthermore, there currently is no atomic way to create a new message and to + * store it in an arbitrary folder. So we have to save it to the INBOX and then + * move to the appropriate folder. + */ + if (!imap_store_file(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, vmfmts, + duration, vms, ast_variable_retrieve(msg_cfg, "message", "flag"), msg_id)) { + if (folder != NEW_FOLDER) { + save_to_folder(vmu, vms, msgnum, folder, NULL, 1); + } + vm_imap_delete(dir, msgnum, vmu); + } + close_mailbox(vms, vmu); + ast_channel_unref(chan); +} + static int imap_retrieve_greeting(const char *dir, const int msgnum, struct ast_vm_user *vmu) { struct vm_state *vms_p; @@ -2086,20 +2208,30 @@ static int imap_retrieve_file(const char *dir, const int msgnum, const char *mai fprintf(text_file_ptr, "%s\n", "[message]"); - get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf)); - fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, "")); - get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf)); - fprintf(text_file_ptr, "<%s>\n", S_OR(buf, "")); - get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf)); - fprintf(text_file_ptr, "context=%s\n", S_OR(buf, "")); - get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf)); - fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, "")); - get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf)); - fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, "")); - get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf)); - fprintf(text_file_ptr, "category=%s\n", S_OR(buf, "")); - get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf)); - fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, "")); + if (get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf))) { + fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, "")); + } + if (get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf))) { + fprintf(text_file_ptr, "<%s>\n", S_OR(buf, "")); + } + if (get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf))) { + fprintf(text_file_ptr, "context=%s\n", S_OR(buf, "")); + } + if (get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf))) { + fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, "")); + } + if (get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf))) { + fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, "")); + } + if (get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf))) { + fprintf(text_file_ptr, "category=%s\n", S_OR(buf, "")); + } + if (get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf))) { + fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, "")); + } + if (get_header_by_tag(header_content, "X-Asterisk-VM-Message-ID:", buf, sizeof(buf))) { + fprintf(text_file_ptr, "msg_id=%s\n", S_OR(buf, "")); + } fclose(text_file_ptr); exit: @@ -2266,7 +2398,9 @@ static int imap_check_limits(struct ast_channel *chan, struct vm_state *vms, str check_quota(vms, vmu->imapfolder); if (vms->quota_limit && vms->quota_usage >= vms->quota_limit) { ast_debug(1, "*** QUOTA EXCEEDED!! %u >= %u\n", vms->quota_usage, vms->quota_limit); - ast_play_and_wait(chan, "vm-mailboxfull"); + if (chan) { + ast_play_and_wait(chan, "vm-mailboxfull"); + } return -1; } @@ -2274,8 +2408,10 @@ static int imap_check_limits(struct ast_channel *chan, struct vm_state *vms, str ast_debug(3, "Checking message number quota: mailbox has %d messages, maximum is set to %d, current messages %d\n", msgnum, vmu->maxmsg, inprocess_count(vmu->mailbox, vmu->context, 0)); if (msgnum >= vmu->maxmsg - inprocess_count(vmu->mailbox, vmu->context, +1)) { ast_log(LOG_WARNING, "Unable to leave message since we will exceed the maximum number of messages allowed (%u >= %u)\n", msgnum, vmu->maxmsg); - ast_play_and_wait(chan, "vm-mailboxfull"); - pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED"); + if (chan) { + ast_play_and_wait(chan, "vm-mailboxfull"); + pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED"); + } return -1; } @@ -2300,7 +2436,7 @@ static int messagecount(const char *context, const char *mailbox, const char *fo } } -static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag) +static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag, const char *msg_id) { char *myserveremail = serveremail; char fn[PATH_MAX]; @@ -2389,7 +2525,7 @@ static int imap_store_file(const char *dir, const char *mailboxuser, const char make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, "INBOX", S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL), S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL), - fn, introfn, fmt, duration, 1, chan, NULL, 1, flag); + fn, introfn, fmt, duration, 1, chan, NULL, 1, flag, msg_id); /* read mail file to memory */ len = ftell(p); rewind(p); @@ -2567,7 +2703,7 @@ static int has_voicemail(const char *mailbox, const char *folder) * * \return zero on success, -1 on error. */ -static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, char *flag) +static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, char *flag, const char *dest_folder) { struct vm_state *sendvms = NULL; char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/ @@ -3826,6 +3962,7 @@ struct insert_data { const char *mailboxcontext; const char *category; const char *flag; + const char *msg_id; }; static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata) @@ -3852,8 +3989,9 @@ static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata) SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *) data->mailboxuser, 0, NULL); SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *) data->mailboxcontext, 0, NULL); SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *) data->flag, 0, NULL); + SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msg_id), 0, (void *) data->msg_id, 0, NULL); if (!ast_strlen_zero(data->category)) { - SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL); + SQLBindParameter(stmt, 13, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL); } res = SQLExecDirect(stmt, (unsigned char *) data->sql, SQL_NTS); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { @@ -3894,7 +4032,7 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail struct ast_config *cfg = NULL; struct odbc_obj *obj; struct insert_data idata = { .sql = sql, .msgnums = msgnums, .dir = dir, .mailboxuser = mailboxuser, .mailboxcontext = mailboxcontext, - .context = "", .macrocontext = "", .callerid = "", .origtime = "", .duration = "", .category = "", .flag = "" }; + .context = "", .macrocontext = "", .callerid = "", .origtime = "", .duration = "", .category = "", .flag = "", .msg_id = "" }; struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE }; delete_file(dir, msgnum); @@ -3946,6 +4084,9 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail if (!(idata.flag = ast_variable_retrieve(cfg, "message", "flag"))) { idata.flag = ""; } + if (!(idata.msg_id = ast_variable_retrieve(cfg, "message", "msg_id"))) { + idata.msg_id = ""; + } } fdlen = lseek(fd, 0, SEEK_END); if (fdlen < 0 || lseek(fd, 0, SEEK_SET) < 0) { @@ -3963,9 +4104,9 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail idata.datalen = idata.indlen = fdlen; if (!ast_strlen_zero(idata.category)) - snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table); + snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table); else - snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag) VALUES (?,?,?,?,?,?,?,?,?,?,?)", odbc_table); + snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table); if ((stmt = ast_odbc_direct_execute(obj, insert_data_cb, &idata))) { SQLFreeHandle (SQL_HANDLE_STMT, stmt); @@ -3986,6 +4127,33 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail return res; } +static void odbc_update_msg_id(char *dir, int msg_num, char *msg_id) +{ + SQLHSTMT stmt; + char sql[PATH_MAX]; + struct odbc_obj *obj; + char msg_num_str[20]; + char *argv[] = { msg_id, dir, msg_num_str }; + struct generic_prepare_struct gps = { .sql = sql, .argc = 3, .argv = argv }; + + obj = ast_odbc_request_obj(odbc_database, 0); + if (!obj) { + ast_log(LOG_WARNING, "Unable to update message ID for message %d in %s\n", msg_num, dir); + return; + } + + snprintf(msg_num_str, sizeof(msg_num_str), "%d", msg_num); + snprintf(sql, sizeof(sql), "UPDATE %s SET msg_id=? WHERE dir=? AND msgnum=?", odbc_table); + stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps); + if (!stmt) { + ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql); + } else { + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + } + ast_odbc_release_obj(obj); + return; +} + /*! * \brief Renames a message in a mailbox folder. * \param sdir The folder of the message to be renamed. @@ -4614,7 +4782,25 @@ static const char *ast_str_encode_mime(struct ast_str **end, ssize_t maxlen, con * * The email body, and base 64 encoded attachement (if any) are stored to the file identified by *p. This method does not actually send the email. That is done by invoking the configure 'mailcmd' and piping this generated file into it, or with the sendemail() function. */ -static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap, const char *flag) +static void make_email_file(FILE *p, + char *srcemail, + struct ast_vm_user *vmu, + int msgnum, + char *context, + char *mailbox, + const char *fromfolder, + char *cidnum, + char *cidname, + char *attach, + char *attach2, + char *format, + int duration, + int attach_user_voicemail, + struct ast_channel *chan, + const char *category, + int imap, + const char *flag, + const char *msg_id) { char date[256]; char host[MAXHOSTNAMELEN] = ""; @@ -4758,8 +4944,8 @@ static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, in #endif /* flag added for Urgent */ fprintf(p, "X-Asterisk-VM-Flag: %s" ENDL, flag); - fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, ast_channel_priority(chan)); - fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, ast_channel_name(chan)); + fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan ? ast_channel_priority(chan) : 0); + fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan ? ast_channel_name(chan) : ""); fprintf(p, "X-Asterisk-VM-Caller-ID-Num: %s" ENDL, enc_cidnum); fprintf(p, "X-Asterisk-VM-Caller-ID-Name: %s" ENDL, enc_cidname); fprintf(p, "X-Asterisk-VM-Duration: %d" ENDL, duration); @@ -4771,6 +4957,7 @@ static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, in fprintf(p, "X-Asterisk-VM-Message-Type: %s" ENDL, msgnum > -1 ? "Message" : greeting_attachment); fprintf(p, "X-Asterisk-VM-Orig-date: %s" ENDL, date); fprintf(p, "X-Asterisk-VM-Orig-time: %ld" ENDL, (long) time(NULL)); + fprintf(p, "X-Asterisk-VM-Message-ID: %s" ENDL, msg_id); } if (!ast_strlen_zero(cidnum)) { fprintf(p, "X-Asterisk-CallerID: %s" ENDL, enc_cidnum); @@ -4937,7 +5124,23 @@ static int add_email_attachment(FILE *p, struct ast_vm_user *vmu, char *format, return 0; } -static int sendmail(char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, const char *flag) +static int sendmail(char *srcemail, + struct ast_vm_user *vmu, + int msgnum, + char *context, + char *mailbox, + const char *fromfolder, + char *cidnum, + char *cidname, + char *attach, + char *attach2, + char *format, + int duration, + int attach_user_voicemail, + struct ast_channel *chan, + const char *category, + const char *flag, + const char *msg_id) { FILE *p = NULL; char tmp[80] = "/tmp/astmail-XXXXXX"; @@ -4963,7 +5166,7 @@ static int sendmail(char *srcemail, struct ast_vm_user *vmu, int msgnum, char *c ast_log(AST_LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd); return -1; } else { - make_email_file(p, srcemail, vmu, msgnum, context, mailbox, fromfolder, cidnum, cidname, attach, attach2, format, duration, attach_user_voicemail, chan, category, 0, flag); + make_email_file(p, srcemail, vmu, msgnum, context, mailbox, fromfolder, cidnum, cidname, attach, attach2, format, duration, attach_user_voicemail, chan, category, 0, flag, msg_id); fclose(p); snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp); ast_safe_system(tmp2); @@ -5407,7 +5610,7 @@ static int has_voicemail(const char *mailbox, const char *folder) * * \return zero on success, -1 on error. */ -static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, const char *flag) +static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, const char *flag, const char *dest_folder) { char fromdir[PATH_MAX], todir[PATH_MAX], frompath[PATH_MAX], topath[PATH_MAX]; const char *frombox = mbox(vmu, imbox); @@ -5419,6 +5622,8 @@ static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int i if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) { /* If urgent, copy to Urgent folder */ userfolder = "Urgent"; + } else if (!ast_strlen_zero(dest_folder)) { + userfolder = dest_folder; } else { userfolder = "INBOX"; } @@ -5440,7 +5645,7 @@ static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int i if (recipmsgnum < recip->maxmsg - (imbox ? 0 : inprocess_count(vmu->mailbox, vmu->context, 0))) { make_file(topath, sizeof(topath), todir, recipmsgnum); #ifndef ODBC_STORAGE - if (EXISTS(fromdir, msgnum, frompath, ast_channel_language(chan))) { + if (EXISTS(fromdir, msgnum, frompath, chan ? ast_channel_language(chan) : "")) { COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath); } else { #endif @@ -5448,7 +5653,7 @@ static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int i * exists in the database, but we want to force copying from the * filesystem (since only the FS contains the prepend). */ copy_plain_file(frompath, topath); - STORE(todir, recip->mailbox, recip->context, recipmsgnum, chan, recip, fmt, duration, NULL, NULL); + STORE(todir, recip->mailbox, recip->context, recipmsgnum, chan, recip, fmt, duration, NULL, NULL, NULL); vm_delete(topath); #ifndef ODBC_STORAGE } @@ -5458,11 +5663,14 @@ static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int i res = -1; } ast_unlock_path(todir); - notify_new_message(chan, recip, NULL, recipmsgnum, duration, fmt, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL), - S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL), - flag); - + if (chan) { + struct ast_party_caller *caller = ast_channel_caller(chan); + notify_new_message(chan, recip, NULL, recipmsgnum, duration, fmt, + S_COR(caller->id.number.valid, caller->id.number.str, NULL), + S_COR(caller->id.name.valid, caller->id.name.str, NULL), + flag); + } + return res; } #endif @@ -5664,6 +5872,301 @@ struct leave_vm_options { char *exitcontext; }; +/*! + * \internal + * \brief Creates a voicemail based on a specified file to a mailbox. + * \param recdata A vm_recording_data containing filename and voicemail txt info. + * \retval -1 failure + * \retval 0 success + * + * This is installed to the app.h voicemail functions and accommodates all voicemail + * storage methods. It should probably be broken out along with leave_voicemail at + * some point in the future. + * + * This function currently only works for a single recipient and only uses the format + * specified in recording_ext. + */ +static int msg_create_from_file(struct ast_vm_recording_data *recdata) +{ + /* voicemail recipient structure */ + struct ast_vm_user *recipient; /* points to svm once it's been created */ + struct ast_vm_user svm; /* struct storing the voicemail recipient */ + + /* File paths */ + char tmpdir[PATH_MAX]; /* directory temp files are stored in */ + char tmptxtfile[PATH_MAX]; /* tmp file for voicemail txt file */ + char desttxtfile[PATH_MAX]; /* final destination for txt file */ + char tmpaudiofile[PATH_MAX]; /* tmp file where audio is stored */ + char dir[PATH_MAX]; /* destination for tmp files on completion */ + char destination[PATH_MAX]; /* destination with msgXXXX. Basically /msgXXXX */ + + /* stuff that only seems to be needed for IMAP */ + #ifdef IMAP_STORAGE + struct vm_state *vms = NULL; + char ext_context[256] = ""; + char *fmt = ast_strdupa(recdata->recording_ext); + int newmsgs = 0; + int oldmsgs = 0; + #endif + + /* miscellaneous operational variables */ + int res = 0; /* Used to store error codes from functions */ + int txtdes /* File descriptor for the text file used to write the voicemail info */; + FILE *txt; /* FILE pointer to text file used to write the voicemail info */ + char date[256]; /* string used to hold date of the voicemail (only used for ODBC) */ + int msgnum; /* the 4 digit number designated to the voicemail */ + int duration = 0; /* Length of the audio being recorded in seconds */ + struct ast_filestream *recording_fs; /*used to read the recording to get duration data */ + + /* We aren't currently doing anything with category, since it comes from a channel variable and + * this function doesn't use channels, but this function could add that as an argument later. */ + const char *category = NULL; /* pointless for now */ + char msg_id[256]; + + /* Start by checking to see if the file actually exists... */ + if (!(ast_fileexists(recdata->recording_file, recdata->recording_ext, NULL))) { + ast_log(LOG_ERROR, "File: %s not found.\n", recdata->recording_file); + return -1; + } + + if (!(recipient = find_user(&svm, recdata->context, recdata->mailbox))) { + ast_log(LOG_ERROR, "No entry in voicemail config file for '%s@%s'\n", recdata->mailbox, recdata->context); + return -1; + } + + /* determine duration in seconds */ + if ((recording_fs = ast_readfile(recdata->recording_file, recdata->recording_ext, NULL, 0, 0, VOICEMAIL_DIR_MODE))) { + if (!ast_seekstream(recording_fs, 0, SEEK_END)) { + long framelength = ast_tellstream(recording_fs); + struct ast_format result; + /* XXX This use of ast_getformatbyname seems incorrect here. The file extension does not necessarily correspond + * to the name of the format. For instance, if "raw" were passed in, I don't think ast_getformatbyname would + * find the slinear format + */ + duration = (int) (framelength / ast_format_rate(ast_getformatbyname(recdata->recording_ext, &result))); + } + } + + /* If the duration was below the minimum duration for the user, let's just drop the whole thing now */ + if (duration < recipient->minsecs) { + ast_log(LOG_NOTICE, "Copying recording to voicemail %s@%s skipped because duration was shorter than " + "minmessage of recipient\n", recdata->mailbox, recdata->context); + return -1; + } + + /* Note that this number must be dropped back to a net sum of zero before returning from this function */ + + if ((res = create_dirpath(tmpdir, sizeof(tmpdir), recipient->context, recdata->mailbox, "tmp"))) { + ast_log(LOG_ERROR, "Failed to make directory.\n"); + } + + snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir); + txtdes = mkstemp(tmptxtfile); + if (txtdes < 0) { + chmod(tmptxtfile, VOICEMAIL_FILE_MODE & ~my_umask); + /* Something screwed up. Abort. */ + ast_log(AST_LOG_ERROR, "Unable to create message file: %s\n", strerror(errno)); + free_user(recipient); + return -1; + } + + /* Store information */ + txt = fdopen(txtdes, "w+"); + if (txt) { + char msg_id_hash[256]; + + /* Every voicemail msg gets its own unique msg id. The msg id is the originate time + * plus a hash of the extension, context, and callerid of the channel leaving the msg */ + + snprintf(msg_id_hash, sizeof(msg_id_hash), "%s%s%s", recdata->call_extension, + recdata->call_context, recdata->call_callerid); + snprintf(msg_id, sizeof(msg_id), "%ld-%d", (long) time(NULL), ast_str_hash(msg_id_hash)); + + get_date(date, sizeof(date)); + fprintf(txt, + ";\n" + "; Message Information file\n" + ";\n" + "[message]\n" + "origmailbox=%s\n" + "context=%s\n" + "macrocontext=%s\n" + "exten=%s\n" + "rdnis=Unknown\n" + "priority=%d\n" + "callerchan=%s\n" + "callerid=%s\n" + "origdate=%s\n" + "origtime=%ld\n" + "category=%s\n" + "msg_id=%s\n" + "flag=\n" /* flags not supported in copy from file yet */ + "duration=%d\n", /* Don't have any reliable way to get duration of file. */ + + recdata->mailbox, + S_OR(recdata->call_context, ""), + S_OR(recdata->call_macrocontext, ""), + S_OR(recdata->call_extension, ""), + recdata->call_priority, + S_OR(recdata->call_callerchan, "Unknown"), + S_OR(recdata->call_callerid, "Unknown"), + date, (long) time(NULL), + S_OR(category, ""), + msg_id, + duration); + + /* Since we are recording from a file, we shouldn't need to do anything else with + * this txt file */ + fclose(txt); + + } else { + ast_log(LOG_WARNING, "Error opening text file for output\n"); + if (ast_check_realtime("voicemail_data")) { + ast_destroy_realtime("voicemail_data", "filename", tmptxtfile, SENTINEL); + } + free_user(recipient); + return -1; + } + + /* At this point, the actual creation of a voicemail message should be finished. + * Now we just need to copy the files being recorded into the receiving folder. */ + + create_dirpath(dir, sizeof(dir), recipient->context, recipient->mailbox, recdata->folder); + +#ifdef IMAP_STORAGE + /* make recipient info into an inboxcount friendly string */ + snprintf(ext_context, sizeof(ext_context), "%s@%s", recipient->mailbox, recipient->context); + + /* Is ext a mailbox? */ + /* must open stream for this user to get info! */ + res = inboxcount(ext_context, &newmsgs, &oldmsgs); + if (res < 0) { + ast_log(LOG_NOTICE, "Can not leave voicemail, unable to count messages\n"); + free_user(recipient); + unlink(tmptxtfile); + return -1; + } + if (!(vms = get_vm_state_by_mailbox(recipient->mailbox, recipient->context, 0))) { + /* It is possible under certain circumstances that inboxcount did not + * create a vm_state when it was needed. This is a catchall which will + * rarely be used. + */ + if (!(vms = create_vm_state_from_user(recipient))) { + ast_log(LOG_ERROR, "Couldn't allocate necessary space\n"); + free_user(recipient); + unlink(tmptxtfile); + return -1; + } + } + vms->newmessages++; + + /* here is a big difference! We add one to it later */ + msgnum = newmsgs + oldmsgs; + ast_debug(3, "Messagecount set to %d\n", msgnum); + snprintf(destination, sizeof(destination), "%simap/msg%s%04d", VM_SPOOL_DIR, recipient->mailbox, msgnum); + + /* Check to see if we have enough room in the mailbox. If not, spit out an error and end + * Note that imap_check_limits raises inprocess_count if successful */ + if ((res = imap_check_limits(NULL, vms, recipient, msgnum))) { + ast_log(LOG_NOTICE, "Didn't copy to voicemail. Mailbox for %s@%s is full.\n", recipient->mailbox, recipient->context); + inprocess_count(recipient->mailbox, recipient->context, -1); + free_user(recipient); + unlink(tmptxtfile); + return -1; + } + +#else + + /* Check to see if the mailbox is full for ODBC/File storage */ + ast_debug(3, "mailbox = %d : inprocess = %d\n", count_messages(recipient, dir), + inprocess_count(recipient->mailbox, recipient->context, 0)); + if (count_messages(recipient, dir) > recipient->maxmsg - inprocess_count(recipient->mailbox, recipient->context, +1)) { + ast_log(AST_LOG_WARNING, "Didn't copy to voicemail. Mailbox for %s@%s is full.\n", recipient->mailbox, recipient->context); + inprocess_count(recipient->mailbox, recipient->context, -1); + free_user(recipient); + unlink(tmptxtfile); + return -1; + } + + msgnum = last_message_index(recipient, dir) + 1; +#endif + + /* Lock the directory receiving the voicemail since we want it to still exist when we attempt to copy the voicemail. + * We need to unlock it before we return. */ + if (vm_lock_path(dir)) { + ast_log(LOG_ERROR, "Couldn't lock directory %s. Voicemail will be lost.\n", dir); + /* Delete files */ + ast_filedelete(tmptxtfile, NULL); + unlink(tmptxtfile); + free_user(recipient); + return -1; + } + + make_file(destination, sizeof(destination), dir, msgnum); + + make_file(tmpaudiofile, sizeof(tmpaudiofile), tmpdir, msgnum); + + if (ast_filecopy(recdata->recording_file, tmpaudiofile, recdata->recording_ext)) { + ast_log(LOG_ERROR, "Audio file failed to copy to tmp dir. Probably low disk space.\n"); + + inprocess_count(recipient->mailbox, recipient->context, -1); + ast_unlock_path(dir); + free_user(recipient); + unlink(tmptxtfile); + return -1; + } + + /* Alright, try to copy to the destination folder now. */ + if (ast_filerename(tmpaudiofile, destination, recdata->recording_ext)) { + ast_log(LOG_ERROR, "Audio file failed to move to destination directory. Permissions/Overlap?\n"); + inprocess_count(recipient->mailbox, recipient->context, -1); + ast_unlock_path(dir); + free_user(recipient); + unlink(tmptxtfile); + return -1; + } + + snprintf(desttxtfile, sizeof(desttxtfile), "%s.txt", destination); + rename(tmptxtfile, desttxtfile); + + if (chmod(desttxtfile, VOICEMAIL_FILE_MODE) < 0) { + ast_log(AST_LOG_ERROR, "Couldn't set permissions on voicemail text file %s: %s", desttxtfile, strerror(errno)); + } + + + ast_unlock_path(dir); + inprocess_count(recipient->mailbox, recipient->context, -1); + + /* If we copied something, we should store it either to ODBC or IMAP if we are using those. The STORE macro allows us + * to do both with one line and is also safe to use with file storage mode. Also, if we are using ODBC, now is a good + * time to create the voicemail database entry. */ + if (ast_fileexists(destination, NULL, NULL) > 0) { + if (ast_check_realtime("voicemail_data")) { + get_date(date, sizeof(date)); + ast_store_realtime("voicemail_data", + "origmailbox", recdata->mailbox, + "context", S_OR(recdata->context, ""), + "macrocontext", S_OR(recdata->call_macrocontext, ""), + "exten", S_OR(recdata->call_extension, ""), + "priority", recdata->call_priority, + "callerchan", S_OR(recdata->call_callerchan, "Unknown"), + "callerid", S_OR(recdata->call_callerid, "Unknown"), + "origdate", date, + "origtime", time(NULL), + "category", S_OR(category, ""), + "filename", tmptxtfile, + "duration", duration, + SENTINEL); + } + + STORE(dir, recipient->mailbox, recipient->context, msgnum, NULL, recipient, fmt, 0, vms, "", msg_id); + } + + free_user(recipient); + unlink(tmptxtfile); + return 0; +} + /*! * \brief Prompts the user and records a voicemail to a mailbox. * \param chan @@ -5946,6 +6449,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_ /* The meat of recording the message... All the announcements and beeps have been played*/ ast_copy_string(fmt, vmfmts, sizeof(fmt)); if (!ast_strlen_zero(fmt)) { + char msg_id[256] = ""; msgnum = 0; #ifdef IMAP_STORAGE @@ -6038,6 +6542,13 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_ /* Store information */ txt = fdopen(txtdes, "w+"); if (txt) { + char msg_id_hash[256] = ""; + + /* Every voicemail msg gets its own unique msg id. The msg id is the originate time + * plus a hash of the extension, context, and callerid of the channel leaving the msg */ + snprintf(msg_id_hash, sizeof(msg_id_hash), "%s%s%s", ast_channel_exten(chan), ast_channel_context(chan), callerid); + snprintf(msg_id, sizeof(msg_id), "%ld-%d", (long) time(NULL), ast_str_hash(msg_id_hash)); + get_date(date, sizeof(date)); ast_callerid_merge(callerid, sizeof(callerid), S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL), @@ -6058,7 +6569,8 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_ "callerid=%s\n" "origdate=%s\n" "origtime=%ld\n" - "category=%s\n", + "category=%s\n" + "msg_id=%s\n", ext, ast_channel_context(chan), ast_channel_macrocontext(chan), @@ -6069,7 +6581,8 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_ ast_channel_name(chan), callerid, date, (long) time(NULL), - category ? category : ""); + category ? category : "", + msg_id); } else { ast_log(AST_LOG_WARNING, "Error opening text file for output\n"); inprocess_count(vmu->mailbox, vmu->context, -1); @@ -6079,7 +6592,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_ res = ast_streamfile(chan, "vm-mailboxfull", ast_channel_language(chan)); goto leave_vm_out; } - res = play_record_review(chan, NULL, tmptxtfile, vmu->maxsecs, fmt, 1, vmu, &duration, &sound_duration, NULL, options->record_gain, vms, flag); + res = play_record_review(chan, NULL, tmptxtfile, vmu->maxsecs, fmt, 1, vmu, &duration, &sound_duration, NULL, options->record_gain, vms, flag, msg_id); if (txt) { fprintf(txt, "flag=%s\n", flag); @@ -6141,7 +6654,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_ * ODBC storage does the entire copy with SQL. */ if (ast_fileexists(fn, NULL, NULL) > 0) { - STORE(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, fmt, duration, vms, flag); + STORE(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, fmt, duration, vms, flag, msg_id); } /* Are there to be more recipients of this message? */ @@ -6156,7 +6669,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_ cntx++; } if ((recip = find_user(&recipu, cntx, exten))) { - copy_message(chan, vmu, 0, msgnum, duration, recip, fmt, dir, flag); + copy_message(chan, vmu, 0, msgnum, duration, recip, fmt, dir, flag, NULL); free_user(recip); } } @@ -6274,7 +6787,7 @@ static int say_and_wait(struct ast_channel *chan, int num, const char *language) return d; } -static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box) +static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box, int *newmsg, int move) { #ifdef IMAP_STORAGE /* we must use mbox(x) folder names, and copy the message there */ @@ -6307,7 +6820,11 @@ static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg ast_debug(5, "Folder exists.\n"); else ast_log(AST_LOG_NOTICE, "Folder %s created!\n", mbox(vmu, box)); - res = !mail_copy(vms->mailstream, sequence, (char *) mbox(vmu, box)); + if (move) { + res = !mail_move(vms->mailstream, sequence, (char *) mbox(vmu, box)); + } else { + res = !mail_copy(vms->mailstream, sequence, (char *) mbox(vmu, box)); + } ast_mutex_unlock(&vms->lock); return res; #else @@ -6349,6 +6866,10 @@ static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg COPY(dir, msg, ddir, x, username, context, sfn, dfn); } ast_unlock_path(ddir); + + if (newmsg) { + *newmsg = x; + } return 0; #endif } @@ -6953,6 +7474,8 @@ static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu, struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE }; #ifndef IMAP_STORAGE signed char zero_gain = 0; +#else + const char *msg_id = NULL; #endif const char *duration_str; @@ -6979,11 +7502,14 @@ static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu, #ifdef IMAP_STORAGE /* Record new intro file */ + if (msg_cfg && msg_cfg != CONFIG_STATUS_FILEINVALID) { + msg_id = ast_variable_retrieve(msg_cfg, "message", "msg_id"); + } make_file(vms->introfn, sizeof(vms->introfn), curdir, curmsg); strncat(vms->introfn, "intro", sizeof(vms->introfn)); ast_play_and_wait(chan, INTRO); ast_play_and_wait(chan, "beep"); - cmd = play_record_review(chan, NULL, vms->introfn, vmu->maxsecs, vm_fmts, 1, vmu, (int *) duration, NULL, NULL, record_gain, vms, flag); + cmd = play_record_review(chan, NULL, vms->introfn, vmu->maxsecs, vm_fmts, 1, vmu, (int *) duration, NULL, NULL, record_gain, vms, flag, msg_id); if (cmd == -1) { break; } @@ -7173,12 +7699,25 @@ static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, if (!ast_strlen_zero(vmu->email)) { int attach_user_voicemail = ast_test_flag(vmu, VM_ATTACH); + char *msg_id = NULL; +#ifdef IMAP_STORAGE + struct ast_config *msg_cfg; + struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE }; + char filename[PATH_MAX]; + + snprintf(filename, sizeof(filename), "%s.txt", fn); + msg_cfg = ast_config_load(filename, config_flags); + if (msg_cfg && msg_cfg != CONFIG_STATUS_FILEINVALID) { + msg_id = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "msg_id")); + ast_config_destroy(msg_cfg); + } +#endif if (attach_user_voicemail) RETRIEVE(todir, msgnum, vmu->mailbox, vmu->context); /* XXX possible imap issue, should category be NULL XXX */ - sendmail(myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, mbox(vmu, 0), cidnum, cidname, fn, NULL, fmt, duration, attach_user_voicemail, chan, category, flag); + sendmail(myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, mbox(vmu, 0), cidnum, cidname, fn, NULL, fmt, duration, attach_user_voicemail, chan, category, flag, msg_id); if (attach_user_voicemail) DISPOSE(todir, msgnum); @@ -7438,9 +7977,24 @@ static int forward_message(struct ast_channel *chan, char *context, struct vm_st long duration = 0; struct vm_state vmstmp; int copy_msg_result = 0; +#ifdef IMAP_STORAGE + char filename[PATH_MAX]; + struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE }; + const char *msg_id = NULL; + struct ast_config *msg_cfg; +#endif memcpy(&vmstmp, vms, sizeof(vmstmp)); - RETRIEVE(dir, curmsg, sender->mailbox, sender->context); + RETRIEVE(dir, curmsg, sender->mailbox, sender->context); +#ifdef IMAP_STORAGE + make_file(filename, sizeof(filename), dir, curmsg); + strncat(filename, ".txt", sizeof(filename) - strlen(filename) - 1); + msg_cfg = ast_config_load(filename, config_flags); + if (msg_cfg && msg_cfg == CONFIG_STATUS_FILEINVALID) { + msg_id = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "msg_id")); + ast_config_destroy(msg_cfg); + } +#endif cmd = vm_forwardoptions(chan, sender, vmstmp.curdir, curmsg, vmfmts, S_OR(context, "default"), record_gain, &duration, &vmstmp, urgent_str); if (!cmd) { @@ -7459,7 +8013,7 @@ static int forward_message(struct ast_channel *chan, char *context, struct vm_st if (!dstvms->mailstream) { ast_log(AST_LOG_ERROR, "IMAP mailstream for %s is NULL\n", vmtmp->mailbox); } else { - copy_msg_result = STORE(vmstmp.curdir, vmtmp->mailbox, vmtmp->context, dstvms->curmsg, chan, vmtmp, fmt, duration, dstvms, urgent_str); + copy_msg_result = STORE(vmstmp.curdir, vmtmp->mailbox, vmtmp->context, dstvms->curmsg, chan, vmtmp, fmt, duration, dstvms, urgent_str, msg_id); run_externnotify(vmtmp->context, vmtmp->mailbox, urgent_str); } } else { @@ -7474,9 +8028,9 @@ static int forward_message(struct ast_channel *chan, char *context, struct vm_st S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL), S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL), vmstmp.fn, vmstmp.introfn, fmt, duration, attach_user_voicemail, chan, - NULL, urgent_str); + NULL, urgent_str, msg_id); #else - copy_msg_result = copy_message(chan, sender, 0, curmsg, duration, vmtmp, fmt, dir, urgent_str); + copy_msg_result = copy_message(chan, sender, 0, curmsg, duration, vmtmp, fmt, dir, urgent_str, NULL); #endif saved_messages++; AST_LIST_REMOVE_CURRENT(list); @@ -8088,7 +8642,7 @@ static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu) } } else if ((!strcasecmp(vms->curbox, "INBOX") || !strcasecmp(vms->curbox, "Urgent")) && vms->heard[x] && ast_test_flag(vmu, VM_MOVEHEARD) && !vms->deleted[x]) { /* Move to old folder before deleting */ - res = save_to_folder(vmu, vms, x, 1); + res = save_to_folder(vmu, vms, x, 1, NULL, 0); if (res == ERROR_LOCK_PATH) { /* If save failed do not delete the message */ ast_log(AST_LOG_WARNING, "Save failed. Not moving message: %s.\n", res == ERROR_LOCK_PATH ? "unable to lock path" : "destination folder full"); @@ -8098,7 +8652,7 @@ static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu) } } else if (vms->deleted[x] && vmu->maxdeletedmsg) { /* Move to deleted folder */ - res = save_to_folder(vmu, vms, x, 10); + res = save_to_folder(vmu, vms, x, 10, NULL, 0); if (res == ERROR_LOCK_PATH) { /* If save failed do not delete the message */ vms->deleted[x] = 0; @@ -9323,7 +9877,7 @@ static int vm_newuser(struct ast_channel *chan, struct ast_vm_user *vmu, struct if (ast_test_flag(vmu, VM_FORCENAME)) { snprintf(prefile, sizeof(prefile), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, vms->username); if (ast_fileexists(prefile, NULL, NULL) < 1) { - cmd = play_record_review(chan, "vm-rec-name", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL); + cmd = play_record_review(chan, "vm-rec-name", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL); if (cmd < 0 || cmd == 't' || cmd == '#') return cmd; } @@ -9333,14 +9887,14 @@ static int vm_newuser(struct ast_channel *chan, struct ast_vm_user *vmu, struct if (ast_test_flag(vmu, VM_FORCEGREET)) { snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, vms->username); if (ast_fileexists(prefile, NULL, NULL) < 1) { - cmd = play_record_review(chan, "vm-rec-unv", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL); + cmd = play_record_review(chan, "vm-rec-unv", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL); if (cmd < 0 || cmd == 't' || cmd == '#') return cmd; } snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, vms->username); if (ast_fileexists(prefile, NULL, NULL) < 1) { - cmd = play_record_review(chan, "vm-rec-busy", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL); + cmd = play_record_review(chan, "vm-rec-busy", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL); if (cmd < 0 || cmd == 't' || cmd == '#') return cmd; } @@ -9422,15 +9976,15 @@ static int vm_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct switch (cmd) { case '1': /* Record your unavailable message */ snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, vms->username); - cmd = play_record_review(chan, "vm-rec-unv", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL); + cmd = play_record_review(chan, "vm-rec-unv", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL); break; case '2': /* Record your busy message */ snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, vms->username); - cmd = play_record_review(chan, "vm-rec-busy", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL); + cmd = play_record_review(chan, "vm-rec-busy", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL); break; case '3': /* Record greeting */ snprintf(prefile, sizeof(prefile), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, vms->username); - cmd = play_record_review(chan, "vm-rec-name", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL); + cmd = play_record_review(chan, "vm-rec-name", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL); break; case '4': /* manage the temporary greeting */ cmd = vm_tempgreeting(chan, vmu, vms, fmtc, record_gain); @@ -9564,7 +10118,7 @@ static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, st retries = 0; RETRIEVE(prefile, -1, vmu->mailbox, vmu->context); if (ast_fileexists(prefile, NULL, NULL) <= 0) { - cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL); + cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL); if (cmd == -1) { break; } @@ -9572,7 +10126,7 @@ static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, st } else { switch (cmd) { case '1': - cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL); + cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL); break; case '2': DELETE(prefile, -1, prefile, vmu); @@ -9973,27 +10527,162 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_ return 0; } -static int vm_execmain(struct ast_channel *chan, const char *data) +static int play_message_by_id_helper(struct ast_channel *chan, + struct ast_vm_user *vmu, + struct vm_state *vms, + const char *msg_id) { - /* XXX This is, admittedly, some pretty horrendous code. For some - reason it just seemed a lot easier to do with GOTO's. I feel - like I'm back in my GWBASIC days. XXX */ - int res = -1; - int cmd = 0; - int valid = 0; - char prefixstr[80] =""; - char ext_context[256]=""; - int box; - int useadsi = 0; - int skipuser = 0; - struct vm_state vms; - struct ast_vm_user *vmu = NULL, vmus; - char *context = NULL; - int silentexit = 0; - struct ast_flags flags = { 0 }; - signed char record_gain = 0; - int play_auto = 0; - int play_folder = 0; + if (message_range_and_existence_check(vms, &msg_id, 1, &vms->curmsg, vmu)) { + return -1; + } + /* Found the msg, so play it back */ + + make_file(vms->fn, sizeof(vms->fn), vms->curdir, vms->curmsg); + make_file(vms->fn, sizeof(vms->fn), vms->curdir, vms->curmsg); + +#ifdef IMAP_STORAGE + /*IMAP storage stores any prepended message from a forward + * as a separate file from the rest of the message + */ + if (!ast_strlen_zero(vms->introfn) && ast_fileexists(vms->introfn, NULL, NULL) > 0) { + wait_file(chan, vms, vms->introfn); + } +#endif + if ((wait_file(chan, vms, vms->fn)) < 0) { + ast_log(AST_LOG_WARNING, "Playback of message %s failed\n", vms->fn); + } else { + vms->heard[vms->curmsg] = 1; + } + + return 0; +} + +/*! + * \brief Finds a message in a specific mailbox by msg_id and plays it to the channel + * + * \retval 0 Success + * \retval -1 Failure + */ +static int play_message_by_id(struct ast_channel *chan, const char *mailbox, const char *context, const char *msg_id) +{ + struct vm_state vms; + struct ast_vm_user *vmu = NULL, vmus; + int res = 0; + int open = 0; + int played = 0; + int i; + + memset(&vmus, 0, sizeof(vmus)); + memset(&vms, 0, sizeof(vms)); + + if (!(vmu = find_user(&vmus, context, mailbox))) { + goto play_msg_cleanup; + } + + /* Iterate through every folder, find the msg, and play it */ + for (i = 0; i < AST_VM_FOLDER_NUMBER && !played; i++) { + ast_copy_string(vms.username, mailbox, sizeof(vms.username)); + vms.lastmsg = -1; + + /* open the mailbox state */ + if ((res = open_mailbox(&vms, vmu, i)) < 0) { + ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox); + res = -1; + goto play_msg_cleanup; + } + open = 1; + + /* play msg if it exists in this mailbox */ + if ((vms.lastmsg != -1) && !(play_message_by_id_helper(chan, vmu, &vms, msg_id))) { + played = 1; + } + + /* close mailbox */ + if ((res = close_mailbox(&vms, vmu) == ERROR_LOCK_PATH)) { + res = -1; + goto play_msg_cleanup; + } + open = 0; + } + +play_msg_cleanup: + if (!played) { + res = -1; + } + + if (vmu && open) { + close_mailbox(&vms, vmu); + } + +#ifdef IMAP_STORAGE + if (vmu) { + vmstate_delete(&vms); + } +#endif + + return res; +} + +static int vm_playmsgexec(struct ast_channel *chan, const char *data) +{ + char *parse; + char *mailbox = NULL; + char *context = NULL; + int res; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(mailbox); + AST_APP_ARG(msg_id); + ); + + if (ast_channel_state(chan) != AST_STATE_UP) { + ast_debug(1, "Before ast_answer\n"); + ast_answer(chan); + } + + if (ast_strlen_zero(data)) { + return -1; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.mailbox) || ast_strlen_zero(args.msg_id)) { + return -1; + } + + if ((context = strchr(args.mailbox, '@'))) { + *context++ = '\0'; + } + mailbox = args.mailbox; + + res = play_message_by_id(chan, mailbox, context, args.msg_id); + pbx_builtin_setvar_helper(chan, "VOICEMAIL_PLAYBACKSTATUS", res ? "FAILED" : "SUCCESS"); + + return 0; +} + +static int vm_execmain(struct ast_channel *chan, const char *data) +{ + /* XXX This is, admittedly, some pretty horrendous code. For some + reason it just seemed a lot easier to do with GOTO's. I feel + like I'm back in my GWBASIC days. XXX */ + int res = -1; + int cmd = 0; + int valid = 0; + char prefixstr[80] =""; + char ext_context[256]=""; + int box; + int useadsi = 0; + int skipuser = 0; + struct vm_state vms; + struct ast_vm_user *vmu = NULL, vmus; + char *context = NULL; + int silentexit = 0; + struct ast_flags flags = { 0 }; + signed char record_gain = 0; + int play_auto = 0; + int play_folder = 0; int in_urgent = 0; #ifdef IMAP_STORAGE int deleted = 0; @@ -10575,7 +11264,7 @@ static int vm_execmain(struct ast_channel *chan, const char *data) break; } else if (cmd > 0) { box = cmd = cmd - '0'; - cmd = save_to_folder(vmu, &vms, vms.curmsg, cmd); + cmd = save_to_folder(vmu, &vms, vms.curmsg, cmd, NULL, 0); if (cmd == ERROR_LOCK_PATH) { res = cmd; goto out; @@ -10792,6 +11481,45 @@ static int vm_exec(struct ast_channel *chan, const char *data) return res; } +static void generate_random_string(char *buf, size_t size) +{ + long val[4]; + int x; + + for (x=0; x<4; x++) + val[x] = ast_random(); + snprintf(buf, size, "%08lx%08lx%08lx%08lx", val[0], val[1], val[2], val[3]); +} + +static int add_message_id(struct ast_config *msg_cfg, char *dir, int msg, char *filename, char *id, size_t id_size, struct ast_vm_user *vmu, int folder) +{ + struct ast_variable *var; + struct ast_category *cat; + generate_random_string(id, id_size); + + var = ast_variable_new("msg_id", id, ""); + if (!var) { + return -1; + } + + cat = ast_category_get(msg_cfg, "message"); + if (!cat) { + ast_log(LOG_ERROR, "Voicemail data file %s/%d.txt has no [message] category?\n", dir, msg); + ast_variables_destroy(var); + return -1; + } + + ast_variable_append(cat, var); + + if (ast_config_text_file_save(filename, msg_cfg, "app_voicemail")) { + ast_log(LOG_WARNING, "Unable to update %s to have a message ID\n", filename); + return -1; + } + + UPDATE_MSG_ID(dir, msg, id, vmu, msg_cfg, folder); + return 0; +} + static struct ast_vm_user *find_or_create(const char *context, const char *box) { struct ast_vm_user *vmu; @@ -12998,7 +13726,7 @@ AST_TEST_DEFINE(test_voicemail_msgcount) break; } open_mailbox(&vms, vmu, folder2mbox[i]); - STORE(tmp[i].dir, testmailbox, testcontext, 0, chan, vmu, "gsm", 600, &vms, strcmp(folders[i], "Urgent") ? "" : "Urgent"); + STORE(tmp[i].dir, testmailbox, testcontext, 0, chan, vmu, "gsm", 600, &vms, strcmp(folders[i], "Urgent") ? "" : "Urgent", NULL); /* hasvm-old, hasvm-urgent, hasvm-new, ic-old, ic-urgent, ic-new, ic2-old, ic2-urgent, ic2-new, mc-old, mc-urgent, mc-new */ for (j = 0; j < 3; j++) { @@ -13160,7 +13888,7 @@ AST_TEST_DEFINE(test_voicemail_notify_endl) test_items[which].location = test_items[which].u.strval; } - make_email_file(file, from, vmu, 0, testcontext, testmailbox, "INBOX", cidnum, cidname, attach, attach2, format, 999, 1, chan, NULL, 0, NULL); + make_email_file(file, from, vmu, 0, testcontext, testmailbox, "INBOX", cidnum, cidname, attach, attach2, format, 999, 1, chan, NULL, 0, NULL, NULL); rewind(file); while (fgets(buf, sizeof(buf), file)) { if ( @@ -13352,6 +14080,7 @@ static int unload_module(void) res |= ast_unregister_application(app2); res |= ast_unregister_application(app3); res |= ast_unregister_application(app4); + res |= ast_unregister_application(playmsg_app); res |= ast_unregister_application(sayname_app); res |= ast_custom_function_unregister(&mailbox_exists_acf); res |= ast_custom_function_unregister(&vm_info_acf); @@ -13405,6 +14134,7 @@ static int load_module(void) res |= ast_register_application_xml(app2, vm_execmain); res |= ast_register_application_xml(app3, vm_box_exists); res |= ast_register_application_xml(app4, vmauthenticate); + res |= ast_register_application_xml(playmsg_app, vm_playmsgexec); res |= ast_register_application_xml(sayname_app, vmsayname_exec); res |= ast_custom_function_register(&mailbox_exists_acf); res |= ast_custom_function_register(&vm_info_acf); @@ -13424,7 +14154,7 @@ static int load_module(void) ast_cli_register_multiple(cli_voicemail, ARRAY_LEN(cli_voicemail)); ast_data_register_multiple(vm_data_providers, ARRAY_LEN(vm_data_providers)); - ast_install_vm_functions(has_voicemail, inboxcount, inboxcount2, messagecount, sayname); + ast_install_vm_functions(has_voicemail, inboxcount, inboxcount2, messagecount, sayname, msg_create_from_file); ast_realtime_require_field("voicemail", "uniqueid", RQ_UINTEGER3, 11, "password", RQ_CHAR, 10, SENTINEL); ast_realtime_require_field("voicemail_data", "filename", RQ_CHAR, 30, "duration", RQ_UINTEGER3, 5, SENTINEL); @@ -13698,7 +14428,7 @@ static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, s static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, int *sound_duration, const char *unlockdir, - signed char record_gain, struct vm_state *vms, char *flag) + signed char record_gain, struct vm_state *vms, char *flag, const char *msg_id) { /* Record message & let caller review or re-record it, or set options if applicable */ int res = 0; @@ -13743,7 +14473,7 @@ static int play_record_review(struct ast_channel *chan, char *playfile, char *re ast_stream_and_wait(chan, "vm-msgsaved", ""); if (!outsidecaller) { /* Saves to IMAP server only if imapgreeting=yes */ - STORE(recordfile, vmu->mailbox, vmu->context, -1, chan, vmu, fmt, *duration, vms, flag); + STORE(recordfile, vmu->mailbox, vmu->context, -1, chan, vmu, fmt, *duration, vms, flag, msg_id); DISPOSE(recordfile, -1); } cmd = 't'; @@ -13933,11 +14663,899 @@ static int play_record_review(struct ast_channel *chan, char *playfile, char *re return cmd; } +static struct ast_vm_msg_snapshot *vm_msg_snapshot_alloc(void) +{ + struct ast_vm_msg_snapshot *msg_snapshot; + + if (!(msg_snapshot = ast_calloc(1, sizeof(*msg_snapshot)))) { + return NULL; + } + + if (ast_string_field_init(msg_snapshot, 512)) { + ast_free(msg_snapshot); + return NULL; + } + + return msg_snapshot; +} + +static struct ast_vm_msg_snapshot *vm_msg_snapshot_destroy(struct ast_vm_msg_snapshot *msg_snapshot) +{ + ast_string_field_free_memory(msg_snapshot); + ast_free(msg_snapshot); + + return NULL; +} + +#ifdef TEST_FRAMEWORK + +int ast_vm_test_destroy_user(const char *context, const char *mailbox) +{ + struct ast_vm_user *vmu; + + AST_LIST_LOCK(&users); + AST_LIST_TRAVERSE_SAFE_BEGIN(&users, vmu, list) { + if (!strncmp(context, vmu->context, sizeof(context)) + && !strncmp(mailbox, vmu->mailbox, sizeof(mailbox))) { + AST_LIST_REMOVE_CURRENT(list); + ast_free(vmu); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + AST_LIST_UNLOCK(&users); + return 0; +} + +int ast_vm_test_create_user(const char *context, const char *mailbox) +{ + struct ast_vm_user *vmu; + + if (!(vmu = find_or_create(context, mailbox))) { + return -1; + } + populate_defaults(vmu); + return 0; +} + +#endif + +/*! + * \brief Create and store off all the msgs in an open mailbox + * + * \note TODO XXX This function should work properly for all + * voicemail storage options, but is far more expensive for + * ODBC at the moment. This is because the RETRIEVE macro + * not only pulls out the message's meta data file from the + * database, but also the actual audio for each message, temporarily + * writing it to the file system. This is an area that needs + * to be made more efficient. + */ +static int vm_msg_snapshot_create(struct ast_vm_user *vmu, + struct vm_state *vms, + struct ast_vm_mailbox_snapshot *mailbox_snapshot, + int snapshot_index, + int mailbox_index, + int descending, + enum ast_vm_snapshot_sort_val sort_val) +{ + struct ast_vm_msg_snapshot *msg_snapshot; + struct ast_vm_msg_snapshot *msg_snapshot_tmp; + struct ast_config *msg_cfg; + struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE }; + char filename[PATH_MAX]; + const char *value; + + for (vms->curmsg = 0; vms->curmsg <= vms->lastmsg; vms->curmsg++) { + int inserted = 0; + /* Find the msg */ + make_file(vms->fn, sizeof(vms->fn), vms->curdir, vms->curmsg); + snprintf(filename, sizeof(filename), "%s.txt", vms->fn); + RETRIEVE(vms->curdir, vms->curmsg, vmu->mailbox, vmu->context); + msg_cfg = ast_config_load(filename, config_flags); + if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) { + DISPOSE(vms->curdir, vms->curmsg); + continue; + } + + /* Create the snapshot object */ + if (!(msg_snapshot = vm_msg_snapshot_alloc())) { + ast_config_destroy(msg_cfg); + return -1; + } + + /* Fill in the snapshot object */ + if ((value = ast_variable_retrieve(msg_cfg, "message", "msg_id"))) { + ast_string_field_set(msg_snapshot, msg_id, value); + } else { + /* Message snapshots *really* should have a + * message ID. Add one to the message config + * if it does not already exist + */ + char id[33]; + if (!(add_message_id(msg_cfg, vms->curdir, vms->curmsg, + filename, id, sizeof(id), vmu, mailbox_index))) { + ast_string_field_set(msg_snapshot, msg_id, id); + } else { + ast_log(LOG_WARNING, "Unable to create a message ID for message %s/%d\n", vms->curdir, vms->curmsg); + } + } + if ((value = ast_variable_retrieve(msg_cfg, "message", "callerid"))) { + ast_string_field_set(msg_snapshot, callerid, value); + } + if ((value = ast_variable_retrieve(msg_cfg, "message", "callerchan"))) { + ast_string_field_set(msg_snapshot, callerchan, value); + } + if ((value = ast_variable_retrieve(msg_cfg, "message", "exten"))) { + ast_string_field_set(msg_snapshot, exten, value); + } + if ((value = ast_variable_retrieve(msg_cfg, "message", "origdate"))) { + ast_string_field_set(msg_snapshot, origdate, value); + } + if ((value = ast_variable_retrieve(msg_cfg, "message", "origtime"))) { + ast_string_field_set(msg_snapshot, origtime, value); + } + if ((value = ast_variable_retrieve(msg_cfg, "message", "duration"))) { + ast_string_field_set(msg_snapshot, duration, value); + } + if ((value = ast_variable_retrieve(msg_cfg, "message", "flag"))) { + ast_string_field_set(msg_snapshot, flag, value); + } + msg_snapshot->msg_number = vms->curmsg; + ast_string_field_set(msg_snapshot, folder_name, mailbox_folders[mailbox_index]); + + /* store msg snapshot in mailbox snapshot */ + switch (sort_val) { + default: + case AST_VM_SNAPSHOT_SORT_BY_ID: + if (descending) { + AST_LIST_INSERT_HEAD(&mailbox_snapshot->snapshots[snapshot_index], msg_snapshot, msg); + } else { + AST_LIST_INSERT_TAIL(&mailbox_snapshot->snapshots[snapshot_index], msg_snapshot, msg); + } + inserted = 1; + break; + case AST_VM_SNAPSHOT_SORT_BY_TIME: + AST_LIST_TRAVERSE_SAFE_BEGIN(&mailbox_snapshot->snapshots[snapshot_index], msg_snapshot_tmp, msg) { + int val = strcmp(msg_snapshot->origtime, msg_snapshot_tmp->origtime); + if (descending && val >= 0) { + AST_LIST_INSERT_BEFORE_CURRENT(msg_snapshot, msg); + inserted = 1; + break; + } else if (!descending && val <= 0) { + AST_LIST_INSERT_BEFORE_CURRENT(msg_snapshot, msg); + inserted = 1; + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + break; + } + + if (!inserted) { + AST_LIST_INSERT_TAIL(&mailbox_snapshot->snapshots[snapshot_index], msg_snapshot, msg); + } + + mailbox_snapshot->total_msg_num++; + + /* cleanup configs and msg */ + ast_config_destroy(msg_cfg); + DISPOSE(vms->curdir, vms->curmsg); + } + + return 0; +} + +struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_create(const char *mailbox, + const char *context, + const char *folder, + int descending, + enum ast_vm_snapshot_sort_val sort_val, + int combine_INBOX_and_OLD) +{ + struct ast_vm_mailbox_snapshot *mailbox_snapshot; + struct vm_state vms; + struct ast_vm_user *vmu = NULL, vmus; + int res; + int i; + int this_index_only = -1; + int open = 0; + int inbox_index = 0; + int old_index = 1; + + if (ast_strlen_zero(mailbox)) { + ast_log(LOG_WARNING, "Cannot create a mailbox snapshot since no mailbox was specified\n"); + return NULL; + } + + memset(&vmus, 0, sizeof(vmus)); + + if (!(ast_strlen_zero(folder))) { + /* find the folder index */ + for (i = 0; i < AST_VM_FOLDER_NUMBER; i++) { + if (!strcasecmp(mailbox_folders[i], folder)) { + this_index_only = i; + break; + } + } + if (this_index_only == -1) { + /* Folder was specified and it did not match any folder in our list */ + return NULL; + } + } + + if (!(vmu = find_user(&vmus, context, mailbox))) { + ast_log(AST_LOG_WARNING, "Failed to create mailbox snapshot for unknown voicemail user %s@%s\n", mailbox, context); + return NULL; + } + + if (!(mailbox_snapshot = ast_calloc(1, sizeof(*mailbox_snapshot)))) { + ast_log(AST_LOG_ERROR, "Failed to allocate memory for mailbox snapshot\n"); + return NULL; + } + + for (i = 0; i < AST_VM_FOLDER_NUMBER; i++) { + int combining_old = 0; + if ((i == old_index) && (combine_INBOX_and_OLD)) { + combining_old = 1; + } + + /* This if statement is confusing looking. Here is what it means in english. + * - If a folder is given to the function and that folder's index is not the one we are iterating over, skip it... + * - Unless the folder provided is the INBOX folder and the current index is the OLD folder and we are combining OLD and INBOX msgs. + */ + if ((this_index_only != -1) && (this_index_only != i) && !(combining_old && i == old_index && this_index_only == inbox_index)) { + continue; + } + + memset(&vms, 0, sizeof(vms)); + ast_copy_string(vms.username, mailbox, sizeof(vms.username)); + vms.lastmsg = -1; + open = 0; + + /* open the mailbox state */ + if ((res = open_mailbox(&vms, vmu, i)) < 0) { + ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox); + goto snapshot_cleanup; + } + open = 1; + + /* Iterate through each msg, storing off info */ + if (vms.lastmsg != -1) { + if ((vm_msg_snapshot_create(vmu, &vms, mailbox_snapshot, combining_old ? inbox_index : i, i, descending, sort_val))) { + ast_log(LOG_WARNING, "Failed to create msg snapshots for %s@%s\n", mailbox, context); + goto snapshot_cleanup; + } + } + + /* close mailbox */ + if ((res = close_mailbox(&vms, vmu) == ERROR_LOCK_PATH)) { + goto snapshot_cleanup; + } + open = 0; + } + +snapshot_cleanup: + if (vmu && open) { + close_mailbox(&vms, vmu); + } + +#ifdef IMAP_STORAGE + if (vmu) { + vmstate_delete(&vms); + } +#endif + + return mailbox_snapshot; +} + +struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_destroy(struct ast_vm_mailbox_snapshot *mailbox_snapshot) +{ + int i; + struct ast_vm_msg_snapshot *msg_snapshot; + + for (i = 0; i < AST_VM_FOLDER_NUMBER; i++) { + while ((msg_snapshot = AST_LIST_REMOVE_HEAD(&mailbox_snapshot->snapshots[i], msg))) { + msg_snapshot = vm_msg_snapshot_destroy(msg_snapshot); + } + } + ast_free(mailbox_snapshot); + return NULL; +} + +struct ast_str *vm_mailbox_snapshot_str(const char *mailbox, const char *context) +{ + struct ast_vm_mailbox_snapshot *mailbox_snapshot = ast_vm_mailbox_snapshot_create(mailbox, context, NULL, 0, AST_VM_SNAPSHOT_SORT_BY_ID, 0); + struct ast_vm_msg_snapshot *msg_snapshot; + int i; + struct ast_str *str; + + if (!mailbox_snapshot) { + return NULL; + } + + if (!(str = ast_str_create(512))) { + return NULL; + mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot); + } + + for (i = 0; i < AST_VM_FOLDER_NUMBER; i++) { + ast_str_append(&str, 0, "FOLDER: %s\n", mailbox_folders[i]); + AST_LIST_TRAVERSE(&mailbox_snapshot->snapshots[i], msg_snapshot, msg) { + ast_str_append(&str, 0, "MSG Number: %d\n", msg_snapshot->msg_number); + ast_str_append(&str, 0, "MSG ID: %s\n", msg_snapshot->msg_id); + ast_str_append(&str, 0, "CALLER ID: %s\n", msg_snapshot->callerid); + ast_str_append(&str, 0, "CALLER CHAN: %s\n", msg_snapshot->callerchan); + ast_str_append(&str, 0, "CALLER EXTEN: %s\n", msg_snapshot->exten); + ast_str_append(&str, 0, "DATE: %s\n", msg_snapshot->origdate); + ast_str_append(&str, 0, "TIME: %s\n", msg_snapshot->origtime); + ast_str_append(&str, 0, "DURATION: %s\n", msg_snapshot->duration); + ast_str_append(&str, 0, "FOLDER NAME: %s\n", msg_snapshot->folder_name); + ast_str_append(&str, 0, "FLAG: %s\n", msg_snapshot->flag); + ast_str_append(&str, 0, "\n"); + } + } + + mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot); + return str; +} + +/*! + * \brief common bounds checking and existence check for Voicemail API functions. + * + * \details + * This is called by ast_vm_msg_move, ast_vm_msg_remove, and ast_vm_msg_forward to + * ensure that data passed in are valid. This ensures that given the + * desired message IDs, they can be found. + * + * \param vms The voicemail state corresponding to an open mailbox + * \param msg_ids An array of message identifiers + * \param num_msgs The number of identifiers in msg_ids + * \param msg_nums [out] The message indexes corresponding to the given + * message IDs + * \pre vms must have open_mailbox() called on it prior to this function. + * + * \retval -1 Failure + * \retval 0 Success + */ +static int message_range_and_existence_check(struct vm_state *vms, const char *msg_ids [], size_t num_msgs, int *msg_nums, struct ast_vm_user *vmu) +{ + int i; + int res = 0; + for (i = 0; i < num_msgs; ++i) { + const char *msg_id = msg_ids[i]; + int found = 0; + for (vms->curmsg = 0; vms->curmsg <= vms->lastmsg; vms->curmsg++) { + const char *other_msg_id; + char filename[PATH_MAX]; + struct ast_config *msg_cfg; + struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE }; + + make_file(vms->fn, sizeof(vms->fn), vms->curdir, vms->curmsg); + snprintf(filename, sizeof(filename), "%s.txt", vms->fn); + RETRIEVE(vms->curdir, vms->curmsg, vmu->mailbox, vmu->context); + msg_cfg = ast_config_load(filename, config_flags); + if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) { + DISPOSE(vms->curdir, vms->curmsg); + res = -1; + goto done; + } + + other_msg_id = ast_variable_retrieve(msg_cfg, "message", "msg_id"); + + if (!ast_strlen_zero(other_msg_id) && !strcmp(other_msg_id, msg_id)) { + /* Message found. We can get out of this inner loop + * and move on to the next message to find + */ + found = 1; + msg_nums[i] = vms->curmsg; + ast_config_destroy(msg_cfg); + DISPOSE(vms->curdir, vms->curmsg); + break; + } + DISPOSE(vms->curdir, vms->curmsg); + } + if (!found) { + /* If we can't find one of the message IDs requested, then OH NO! */ + res = -1; + goto done; + } + } + +done: + return res; +} + +static void notify_new_state(struct ast_vm_user *vmu) +{ + int new = 0, old = 0, urgent = 0; + char ext_context[1024]; + + snprintf(ext_context, sizeof(ext_context), "%s@%s", vmu->mailbox, vmu->context); + run_externnotify(vmu->context, vmu->mailbox, NULL); + ast_app_inboxcount2(ext_context, &urgent, &new, &old); + queue_mwi_event(ext_context, urgent, new, old); +} + +int ast_vm_msg_forward(const char *from_mailbox, + const char *from_context, + const char *from_folder, + const char *to_mailbox, + const char *to_context, + const char *to_folder, + size_t num_msgs, + const char *msg_ids [], + int delete_old) +{ + struct vm_state from_vms; + struct ast_vm_user *vmu = NULL, vmus; + struct ast_vm_user *to_vmu = NULL, to_vmus; + struct ast_config *msg_cfg; + struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE }; + char filename[PATH_MAX]; + int from_folder_index; + int open = 0; + int res = 0; + int i; + int *msg_nums; + + if (ast_strlen_zero(from_mailbox) || ast_strlen_zero(to_mailbox)) { + ast_log(LOG_WARNING, "Cannot forward message because either the from or to mailbox was not specified\n"); + return -1; + } + + if (!num_msgs) { + ast_log(LOG_WARNING, "Invalid number of messages specified to forward: %zu\n", num_msgs); + return -1; + } + + if (ast_strlen_zero(from_folder) || ast_strlen_zero(to_folder)) { + ast_log(LOG_WARNING, "Cannot forward message because the from_folder or to_folder was not specified\n"); + return -1; + } + + memset(&vmus, 0, sizeof(vmus)); + memset(&to_vmus, 0, sizeof(to_vmus)); + memset(&from_vms, 0, sizeof(from_vms)); + + from_folder_index = get_folder_by_name(from_folder); + if (from_folder_index == -1) { + return -1; + } + + if (get_folder_by_name(to_folder) == -1) { + return -1; + } + + if (!(vmu = find_user(&vmus, from_context, from_mailbox))) { + ast_log(LOG_WARNING, "Can't find voicemail user to forward from (%s@%s)\n", from_mailbox, from_context); + return -1; + } + + if (!(to_vmu = find_user(&to_vmus, to_context, to_mailbox))) { + ast_log(LOG_WARNING, "Can't find voicemail user to forward to (%s@%s)\n", to_mailbox, to_context); + return -1; + } + + ast_copy_string(from_vms.username, from_mailbox, sizeof(from_vms.username)); + from_vms.lastmsg = -1; + open = 0; + + /* open the mailbox state */ + if ((res = open_mailbox(&from_vms, vmu, from_folder_index)) < 0) { + ast_log(LOG_WARNING, "Could not open mailbox %s\n", from_mailbox); + res = -1; + goto vm_forward_cleanup; + } + + open = 1; + + if ((from_vms.lastmsg + 1) < num_msgs) { + ast_log(LOG_WARNING, "Folder %s has less than %zu messages\n", from_folder, num_msgs); + res = -1; + goto vm_forward_cleanup; + } + + if (!(msg_nums = alloca(sizeof(int) * num_msgs))) + { + ast_log(LOG_ERROR, "Unable to allocate stack space! Expect awful things!\n"); + res = -1; + goto vm_forward_cleanup; + } + + if ((res = message_range_and_existence_check(&from_vms, msg_ids, num_msgs, msg_nums, vmu) < 0)) { + goto vm_forward_cleanup; + } + + /* Now we actually forward the messages */ + for (i = 0; i < num_msgs; i++) { + int cur_msg = msg_nums[i]; + int duration = 0; + const char *value; + + make_file(from_vms.fn, sizeof(from_vms.fn), from_vms.curdir, cur_msg); + snprintf(filename, sizeof(filename), "%s.txt", from_vms.fn); + RETRIEVE(from_vms.curdir, cur_msg, vmu->mailbox, vmu->context); + msg_cfg = ast_config_load(filename, config_flags); + /* XXX This likely will not fail since we previously ensured that the + * message we are looking for exists. However, there still could be some + * circumstance where this fails, so atomicity is not guaranteed. + */ + if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) { + DISPOSE(from_vms.curdir, cur_msg); + continue; + } + if ((value = ast_variable_retrieve(msg_cfg, "message", "duration"))) { + duration = atoi(value); + } + + copy_message(NULL, vmu, from_folder_index, cur_msg, duration, to_vmu, vmfmts, from_vms.curdir, "", to_folder); + + if (delete_old) { + from_vms.deleted[cur_msg] = 1; + } + ast_config_destroy(msg_cfg); + DISPOSE(from_vms.curdir, cur_msg); + } + + /* close mailbox */ + if ((res = close_mailbox(&from_vms, vmu) == ERROR_LOCK_PATH)) { + res = -1; + goto vm_forward_cleanup; + } + open = 0; + +vm_forward_cleanup: + if (vmu && open) { + close_mailbox(&from_vms, vmu); + } +#ifdef IMAP_STORAGE + if (vmu) { + vmstate_delete(&from_vms); + } +#endif + + if (!res) { + notify_new_state(to_vmu); + } + + return res; +} + +int ast_vm_msg_move(const char *mailbox, + const char *context, + size_t num_msgs, + const char *oldfolder, + const char *old_msg_ids [], + const char *newfolder) +{ + struct vm_state vms; + struct ast_vm_user *vmu = NULL, vmus; + int old_folder_index; + int new_folder_index; + int open = 0; + int res = 0; + int i; + int *old_msg_nums; + + if (ast_strlen_zero(mailbox)) { + ast_log(LOG_WARNING, "Cannot move message because no mailbox was specified\n"); + return -1; + } + + if (!num_msgs) { + ast_log(LOG_WARNING, "Invalid number of messages specified to move: %zu\n", num_msgs); + return -1; + } + + if (ast_strlen_zero(oldfolder) || ast_strlen_zero(newfolder)) { + ast_log(LOG_WARNING, "Cannot move message because either oldfolder or newfolder was not specified\n"); + return -1; + } + + old_folder_index = get_folder_by_name(oldfolder); + new_folder_index = get_folder_by_name(newfolder); + + memset(&vmus, 0, sizeof(vmus)); + memset(&vms, 0, sizeof(vms)); + + if (old_folder_index == -1 || new_folder_index == -1) { + return -1; + } + + if (!(vmu = find_user(&vmus, context, mailbox))) { + return -1; + } + + ast_copy_string(vms.username, mailbox, sizeof(vms.username)); + vms.lastmsg = -1; + open = 0; + + /* open the mailbox state */ + if ((res = open_mailbox(&vms, vmu, old_folder_index)) < 0) { + ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox); + res = -1; + goto vm_move_cleanup; + } + + open = 1; + + if ((vms.lastmsg + 1) < num_msgs) { + ast_log(LOG_WARNING, "Folder %s has less than %zu messages\n", oldfolder, num_msgs); + res = -1; + goto vm_move_cleanup; + } + + if (!(old_msg_nums = alloca(sizeof(int) * num_msgs))) { + ast_log(LOG_ERROR, "Unable to allocate stack space! Expect awful things!\n"); + res = -1; + goto vm_move_cleanup; + } + + if ((res = message_range_and_existence_check(&vms, old_msg_ids, num_msgs, old_msg_nums, vmu)) < 0) { + goto vm_move_cleanup; + } + + /* Now actually move the message */ + for (i = 0; i < num_msgs; ++i) { + if (save_to_folder(vmu, &vms, old_msg_nums[i], new_folder_index, NULL, 0)) { + res = -1; + goto vm_move_cleanup; + } + vms.deleted[old_msg_nums[i]] = 1; + } + + /* close mailbox */ + if ((res = close_mailbox(&vms, vmu) == ERROR_LOCK_PATH)) { + res = -1; + goto vm_move_cleanup; + } + open = 0; + +vm_move_cleanup: + if (vmu && open) { + close_mailbox(&vms, vmu); + } +#ifdef IMAP_STORAGE + if (vmu) { + vmstate_delete(&vms); + } +#endif + + if (!res) { + notify_new_state(vmu); + } + + return res; +} + +int ast_vm_msg_remove(const char *mailbox, + const char *context, + size_t num_msgs, + const char *folder, + const char *msgs[]) +{ + struct vm_state vms; + struct ast_vm_user *vmu = NULL, vmus; + int folder_index; + int open = 0; + int res = 0; + int i; + int *msg_nums; + + if (ast_strlen_zero(mailbox)) { + ast_log(LOG_WARNING, "Cannot remove message because no mailbox was specified\n"); + return -1; + } + + if (!num_msgs) { + ast_log(LOG_WARNING, "Invalid number of messages specified to remove: %zu\n", num_msgs); + return -1; + } + + if (ast_strlen_zero(folder)) { + ast_log(LOG_WARNING, "Cannot remove message because no folder was specified\n"); + return -1; + } + + memset(&vmus, 0, sizeof(vmus)); + memset(&vms, 0, sizeof(vms)); + + folder_index = get_folder_by_name(folder); + if (folder_index == -1) { + ast_log(LOG_WARNING, "Could not remove msgs from unknown folder %s\n", folder); + return -1; + } + + if (!(vmu = find_user(&vmus, context, mailbox))) { + ast_log(LOG_WARNING, "Can't find voicemail user to remove msg from (%s@%s)\n", mailbox, context); + return -1; + } + + ast_copy_string(vms.username, mailbox, sizeof(vms.username)); + vms.lastmsg = -1; + open = 0; + + /* open the mailbox state */ + if ((res = open_mailbox(&vms, vmu, folder_index)) < 0) { + ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox); + res = -1; + goto vm_remove_cleanup; + } + + open = 1; + + if ((vms.lastmsg + 1) < num_msgs) { + ast_log(LOG_WARNING, "Folder %s has less than %zu messages\n", folder, num_msgs); + res = -1; + goto vm_remove_cleanup; + } + + if (!(msg_nums = alloca(sizeof(int) * num_msgs))) { + ast_log(LOG_ERROR, "Unable to allocate stack space! Expect awful things\n"); + res = -1; + goto vm_remove_cleanup; + } + + if ((res = message_range_and_existence_check(&vms, msgs, num_msgs, msg_nums, vmu)) < 0) { + goto vm_remove_cleanup; + } + + for (i = 0; i < num_msgs; i++) { + vms.deleted[msg_nums[i]] = 1; + } + + /* close mailbox */ + if ((res = close_mailbox(&vms, vmu) == ERROR_LOCK_PATH)) { + res = -1; + ast_log(AST_LOG_ERROR, "Failed to close mailbox folder %s while removing msgs\n", folder); + goto vm_remove_cleanup; + } + open = 0; + +vm_remove_cleanup: + if (vmu && open) { + close_mailbox(&vms, vmu); + } +#ifdef IMAP_STORAGE + if (vmu) { + vmstate_delete(&vms); + } +#endif + + if (!res) { + notify_new_state(vmu); + } + + return res; +} + +const char *ast_vm_index_to_foldername(unsigned int index) +{ + if (index >= AST_VM_FOLDER_NUMBER) { + return ""; + } + return mailbox_folders[index]; +} + +int ast_vm_msg_play(struct ast_channel *chan, + const char *mailbox, + const char *context, + const char *folder, + const char *msg_id, + ast_vm_msg_play_cb cb) +{ + struct vm_state vms; + struct ast_vm_user *vmu = NULL, vmus; + int res = 0; + int open = 0; + int i; + char filename[PATH_MAX]; + struct ast_config *msg_cfg; + struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE }; + int duration = 0; + const char *value; + + if (ast_strlen_zero(mailbox)) { + ast_log(LOG_WARNING, "Cannot play message because no mailbox was specified\n"); + return -1; + } + + if (ast_strlen_zero(folder)) { + ast_log(LOG_WARNING, "Cannot play message because no folder was specified\n"); + return -1; + } + + if (ast_strlen_zero(msg_id)) { + ast_log(LOG_WARNING, "Cannot play message because no message number was specified\n"); + return -1; + } + + memset(&vmus, 0, sizeof(vmus)); + memset(&vms, 0, sizeof(vms)); + + if (ast_strlen_zero(context)) { + context = "default"; + } + + if (!(vmu = find_user(&vmus, context, mailbox))) { + return -1; + } + + i = get_folder_by_name(folder); + ast_copy_string(vms.username, mailbox, sizeof(vms.username)); + vms.lastmsg = -1; + if ((res = open_mailbox(&vms, vmu, i)) < 0) { + ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox); + goto play2_msg_cleanup; + } + open = 1; + + if (message_range_and_existence_check(&vms, &msg_id, 1, &vms.curmsg, vmu)) { + res = -1; + goto play2_msg_cleanup; + } + + /* Find the msg */ + make_file(vms.fn, sizeof(vms.fn), vms.curdir, vms.curmsg); + snprintf(filename, sizeof(filename), "%s.txt", vms.fn); + RETRIEVE(vms.curdir, vms.curmsg, vmu->mailbox, vmu->context); + + msg_cfg = ast_config_load(filename, config_flags); + if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) { + DISPOSE(vms.curdir, vms.curmsg); + res = -1; + goto play2_msg_cleanup; + } + if ((value = ast_variable_retrieve(msg_cfg, "message", "duration"))) { + duration = atoi(value); + } + ast_config_destroy(msg_cfg); + +#ifdef IMAP_STORAGE + /*IMAP storage stores any prepended message from a forward + * as a separate file from the rest of the message + */ + if (!ast_strlen_zero(vms.introfn) && ast_fileexists(vms.introfn, NULL, NULL) > 0) { + wait_file(chan, &vms, vms.introfn); + } +#endif + if (cb) { + cb(chan, vms.fn, duration); + } else if ((wait_file(chan, &vms, vms.fn)) < 0) { + ast_log(AST_LOG_WARNING, "Playback of message %s failed\n", vms.fn); + } else { + res = 0; + } + + vms.heard[vms.curmsg] = 1; + + /* cleanup configs and msg */ + DISPOSE(vms.curdir, vms.curmsg); + +play2_msg_cleanup: + if (vmu && open) { + close_mailbox(&vms, vmu); + } + +#ifdef IMAP_STORAGE + if (vmu) { + vmstate_delete(&vms); + } +#endif + + if (!res) { + notify_new_state(vmu); + } + + return res; +} + /* This is a workaround so that menuselect displays a proper description * AST_MODULE_INFO(, , "Comedian Mail (Voicemail System)" */ -AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, tdesc, +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, tdesc, .load = load_module, .unload = unload_module, .reload = reload, -- cgit v1.2.3