Merge changes dealing with support for Digium phones.
authorMark Michelson <mmichelson@digium.com>
Mon, 4 Jun 2012 20:26:12 +0000 (20:26 +0000)
committerMark Michelson <mmichelson@digium.com>
Mon, 4 Jun 2012 20:26:12 +0000 (20:26 +0000)
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

36 files changed:
apps/app_mixmonitor.c
apps/app_queue.c
apps/app_voicemail.c
apps/app_voicemail.exports.in
channels/chan_sip.c
channels/chan_sip.exports.in [new file with mode: 0644]
channels/chan_skinny.c
channels/sip/include/sip.h
configs/manager.conf.sample
contrib/realtime/mysql/voicemail_messages.sql
funcs/func_presencestate.c [new file with mode: 0644]
include/asterisk/app.h
include/asterisk/app_voicemail.h [new file with mode: 0644]
include/asterisk/callerid.h
include/asterisk/config.h
include/asterisk/event_defs.h
include/asterisk/file.h
include/asterisk/manager.h
include/asterisk/message.h
include/asterisk/pbx.h
include/asterisk/presencestate.h [new file with mode: 0644]
include/asterisk/sip_api.h [new file with mode: 0644]
main/app.c
main/asterisk.c
main/callerid.c
main/channel.c
main/config.c
main/event.c
main/features.c
main/file.c
main/manager.c
main/message.c
main/pbx.c
main/presencestate.c [new file with mode: 0644]
tests/test_config.c
tests/test_voicemail_api.c [new file with mode: 0644]

index c1f5ada..8eda399 100644 (file)
@@ -42,6 +42,7 @@
 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #include "asterisk/paths.h"    /* use ast_config_AST_MONITOR_DIR */
+#include "asterisk/stringfields.h"
 #include "asterisk/file.h"
 #include "asterisk/audiohook.h"
 #include "asterisk/pbx.h"
@@ -51,6 +52,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/channel.h"
 #include "asterisk/autochan.h"
 #include "asterisk/manager.h"
+#include "asterisk/callerid.h"
 #include "asterisk/mod_format.h"
 #include "asterisk/linkedlists.h"
 
@@ -112,6 +114,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                                <argument name="chanvar" required="true" />
                                                <para>Stores the MixMonitor's ID on this channel variable.</para>
                                        </option>
+                                       <option name="m">
+                                               <argument name="mailbox" required="true" />
+                                               <para>Create a copy of the recording as a voicemail in the indicated <emphasis>mailbox</emphasis>(es)
+                                               separated by commas eg. m(1111@default,2222@default,...).  Folders can be optionally specified using
+                                               the syntax: mailbox@context/folder</para>
+                                       </option>
                                </optionlist>
                        </parameter>
                        <parameter name="command">
@@ -238,6 +246,17 @@ static const char * const stop_app = "StopMixMonitor";
 
 static const char * const mixmonitor_spy_type = "MixMonitor";
 
+/*!
+ * \internal
+ * \brief This struct is a list item holds data needed to find a vm_recipient within voicemail
+ */
+struct vm_recipient {
+       char mailbox[AST_MAX_CONTEXT];
+       char context[AST_MAX_EXTENSION];
+       char folder[80];
+       AST_LIST_ENTRY(vm_recipient) list;
+};
+
 struct mixmonitor {
        struct ast_audiohook audiohook;
        struct ast_callid *callid;
@@ -249,6 +268,20 @@ struct mixmonitor {
        unsigned int flags;
        struct ast_autochan *autochan;
        struct mixmonitor_ds *mixmonitor_ds;
+
+       /* the below string fields describe data used for creating voicemails from the recording */
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(call_context);
+               AST_STRING_FIELD(call_macrocontext);
+               AST_STRING_FIELD(call_extension);
+               AST_STRING_FIELD(call_callerchan);
+               AST_STRING_FIELD(call_callerid);
+       );
+       int call_priority;
+
+       /* FUTURE DEVELOPMENT NOTICE
+        * recipient_list will need locks if we make it editable after the monitor is started */
+       AST_LIST_HEAD_NOLOCK(, vm_recipient) recipient_list;
 };
 
 enum mixmonitor_flags {
@@ -260,7 +293,8 @@ enum mixmonitor_flags {
        MUXFLAG_READ = (1 << 6),
        MUXFLAG_WRITE = (1 << 7),
        MUXFLAG_COMBINED = (1 << 8),
-        MUXFLAG_UID = (1 << 9),
+       MUXFLAG_UID = (1 << 9),
+       MUXFLAG_VMRECIPIENTS = (1 << 10),
 };
 
 enum mixmonitor_args {
@@ -269,7 +303,8 @@ enum mixmonitor_args {
        OPT_ARG_VOLUME,
        OPT_ARG_WRITENAME,
        OPT_ARG_READNAME,
-        OPT_ARG_UID,
+       OPT_ARG_UID,
+       OPT_ARG_VMRECIPIENTS,
        OPT_ARG_ARRAY_SIZE,     /* Always last element of the enum */
 };
 
@@ -282,6 +317,7 @@ AST_APP_OPTIONS(mixmonitor_opts, {
        AST_APP_OPTION_ARG('r', MUXFLAG_READ, OPT_ARG_READNAME),
        AST_APP_OPTION_ARG('t', MUXFLAG_WRITE, OPT_ARG_WRITENAME),
        AST_APP_OPTION_ARG('i', MUXFLAG_UID, OPT_ARG_UID),
+       AST_APP_OPTION_ARG('m', MUXFLAG_VMRECIPIENTS, OPT_ARG_VMRECIPIENTS),
 });
 
 struct mixmonitor_ds {
@@ -382,6 +418,70 @@ static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
        return res;
 }
 
+/*!
+ * \internal
+ * \brief adds recipients to a mixmonitor's recipient list
+ * \param mixmonitor mixmonitor being affected
+ * \param vm_recipients string containing the desired recipients to add
+ */
+static void add_vm_recipients_from_string(struct mixmonitor *mixmonitor, const char *vm_recipients)
+{
+       /* recipients are in a single string with a format format resembling "mailbox@context/INBOX,mailbox2@context2,mailbox3@context3/Work" */
+       char *cur_mailbox = ast_strdupa(vm_recipients);
+       char *cur_context;
+       char *cur_folder;
+       char *next;
+       int elements_processed = 0;
+
+       while (!ast_strlen_zero(cur_mailbox)) {
+               ast_debug(3, "attempting to add next element %d from %s\n", elements_processed, cur_mailbox);
+               if ((next = strchr(cur_mailbox, ',')) || (next = strchr(cur_mailbox, '&'))) {
+                       *(next++) = '\0';
+               }
+
+               if ((cur_folder = strchr(cur_mailbox, '/'))) {
+                       *(cur_folder++) = '\0';
+               } else {
+                       cur_folder = "INBOX";
+               }
+
+               if ((cur_context = strchr(cur_mailbox, '@'))) {
+                       *(cur_context++) = '\0';
+               } else {
+                       cur_context = "default";
+               }
+
+               if (!ast_strlen_zero(cur_mailbox) && !ast_strlen_zero(cur_context)) {
+                       struct vm_recipient *recipient;
+                       if (!(recipient = ast_malloc(sizeof(*recipient)))) {
+                               ast_log(LOG_ERROR, "Failed to allocate recipient. Aborting function.\n");
+                               return;
+                       }
+                       ast_copy_string(recipient->context, cur_context, sizeof(recipient->context));
+                       ast_copy_string(recipient->mailbox, cur_mailbox, sizeof(recipient->mailbox));
+                       ast_copy_string(recipient->folder, cur_folder, sizeof(recipient->folder));
+
+                       /* Add to list */
+                       ast_verb(5, "Adding %s@%s to recipient list\n", recipient->mailbox, recipient->context);
+                       AST_LIST_INSERT_HEAD(&mixmonitor->recipient_list, recipient, list);
+               } else {
+                       ast_log(LOG_ERROR, "Failed to properly parse extension and/or context from element %d of recipient string: %s\n", elements_processed, vm_recipients);
+               }
+
+               cur_mailbox = next;
+               elements_processed++;
+       }
+}
+
+static void clear_mixmonitor_recipient_list(struct mixmonitor *mixmonitor)
+{
+       struct vm_recipient *current;
+       while ((current = AST_LIST_REMOVE_HEAD(&mixmonitor->recipient_list, list))) {
+               /* Clear list element data */
+               ast_free(current);
+       }
+}
+
 #define SAMPLES_PER_FRAME 160
 
 static void mixmonitor_free(struct mixmonitor *mixmonitor)
@@ -397,6 +497,12 @@ static void mixmonitor_free(struct mixmonitor *mixmonitor)
                        ast_free(mixmonitor->post_process);
                }
 
+               /* Free everything in the recipient list */
+               clear_mixmonitor_recipient_list(mixmonitor);
+
+               /* clean stringfields */
+               ast_string_field_free_memory(mixmonitor);
+
                if (mixmonitor->callid) {
                        ast_callid_unref(mixmonitor->callid);
                }
@@ -404,10 +510,50 @@ static void mixmonitor_free(struct mixmonitor *mixmonitor)
        }
 }
 
-static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename, struct ast_filestream **fs, unsigned int *oflags, int *errflag)
+/*!
+ * \internal
+ * \brief Copies the mixmonitor to all voicemail recipients
+ * \param mixmonitor The mixmonitor that needs to forward its file to recipients
+ * \param ext Format of the file that was saved
+ */
+static void copy_to_voicemail(struct mixmonitor *mixmonitor, const char *ext, const char *filename)
+{
+       struct vm_recipient *recipient = NULL;
+       struct ast_vm_recording_data recording_data;
+       if (ast_string_field_init(&recording_data, 512)) {
+               ast_log(LOG_ERROR, "Failed to string_field_init, skipping copy_to_voicemail\n");
+               return;
+       }
+
+       /* Copy strings to stringfields that will be used for all recipients */
+       ast_string_field_set(&recording_data, recording_file, filename);
+       ast_string_field_set(&recording_data, recording_ext, ext);
+       ast_string_field_set(&recording_data, call_context, mixmonitor->call_context);
+       ast_string_field_set(&recording_data, call_macrocontext, mixmonitor->call_macrocontext);
+       ast_string_field_set(&recording_data, call_extension, mixmonitor->call_extension);
+       ast_string_field_set(&recording_data, call_callerchan, mixmonitor->call_callerchan);
+       ast_string_field_set(&recording_data, call_callerid, mixmonitor->call_callerid);
+       /* and call_priority gets copied too */
+       recording_data.call_priority = mixmonitor->call_priority;
+
+       AST_LIST_TRAVERSE(&mixmonitor->recipient_list, recipient, list) {
+               /* context, mailbox, and folder need to be set per recipient */
+               ast_string_field_set(&recording_data, context, recipient->context);
+               ast_string_field_set(&recording_data, mailbox, recipient->mailbox);
+               ast_string_field_set(&recording_data, folder, recipient->folder);
+
+               ast_verb(4, "MixMonitor attempting to send voicemail copy to %s@%s\n", recording_data.mailbox,
+                       recording_data.context);
+               ast_app_copy_recording_to_vm(&recording_data);
+       }
+
+       /* Free the string fields for recording_data before exiting the function. */
+       ast_string_field_free_memory(&recording_data);
+}
+
+static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename, struct ast_filestream **fs, unsigned int *oflags, int *errflag, char **ext)
 {
        /* Initialize the file if not already done so */
-       char *ext = NULL;
        char *last_slash = NULL;
        if (!ast_strlen_zero(filename)) {
                if (!*fs && !*errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
@@ -416,14 +562,19 @@ static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename,
 
                        last_slash = strrchr(filename, '/');
 
-                       if ((ext = strrchr(filename, '.')) && (ext > last_slash)) {
-                               *(ext++) = '\0';
+                       ast_log(LOG_NOTICE, "!!!!!! File name is %s\n", filename);
+
+                       if ((*ext = strrchr(filename, '.')) && (*ext > last_slash)) {
+                               ast_log(LOG_NOTICE, "Found a dot. *ext is %s\n", *ext);
+                               **ext = '\0';
+                               *ext = *ext + 1;
+                               ast_log(LOG_NOTICE, "After increment *ext is %s\n", *ext);
                        } else {
-                               ext = "raw";
+                               *ext = "raw";
                        }
 
-                       if (!(*fs = ast_writefile(filename, ext, NULL, *oflags, 0, 0666))) {
-                               ast_log(LOG_ERROR, "Cannot open %s.%s\n", filename, ext);
+                       if (!(*fs = ast_writefile(filename, *ext, NULL, *oflags, 0, 0666))) {
+                               ast_log(LOG_ERROR, "Cannot open %s.%s\n", filename, *ext);
                                *errflag = 1;
                        } else {
                                struct ast_filestream *tmp = *fs;
@@ -436,6 +587,9 @@ static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename,
 static void *mixmonitor_thread(void *obj) 
 {
        struct mixmonitor *mixmonitor = obj;
+       char *fs_ext = "";
+       char *fs_read_ext = "";
+       char *fs_write_ext = "";
 
        struct ast_filestream **fs = NULL;
        struct ast_filestream **fs_read = NULL;
@@ -457,9 +611,9 @@ static void *mixmonitor_thread(void *obj)
        fs_write = &mixmonitor->mixmonitor_ds->fs_write;
 
        ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
-       mixmonitor_save_prep(mixmonitor, mixmonitor->filename, fs, &oflags, &errflag);
-       mixmonitor_save_prep(mixmonitor, mixmonitor->filename_read, fs_read, &oflags, &errflag);
-       mixmonitor_save_prep(mixmonitor, mixmonitor->filename_write, fs_write, &oflags, &errflag);
+       mixmonitor_save_prep(mixmonitor, mixmonitor->filename, fs, &oflags, &errflag, &fs_ext);
+       mixmonitor_save_prep(mixmonitor, mixmonitor->filename_read, fs_read, &oflags, &errflag, &fs_read_ext);
+       mixmonitor_save_prep(mixmonitor, mixmonitor->filename_write, fs_write, &oflags, &errflag, &fs_write_ext);
 
        ast_format_set(&format_slin, ast_format_slin_by_rate(mixmonitor->mixmonitor_ds->samp_rate), 0);
 
@@ -554,6 +708,27 @@ static void *mixmonitor_thread(void *obj)
        }
 
        ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
+
+       if (!AST_LIST_EMPTY(&mixmonitor->recipient_list)) {
+               if (ast_strlen_zero(fs_ext)) {
+                       ast_log(LOG_ERROR, "No file extension set for Mixmonitor %s. Skipping copy to voicemail.\n",
+                               mixmonitor -> name);
+               } else {
+                       ast_verb(3, "Copying recordings for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
+                       copy_to_voicemail(mixmonitor, fs_ext, mixmonitor->filename);
+               }
+               if (!ast_strlen_zero(fs_read_ext)) {
+                       ast_verb(3, "Copying read recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
+                       copy_to_voicemail(mixmonitor, fs_read_ext, mixmonitor->filename_read);
+               }
+               if (!ast_strlen_zero(fs_write_ext)) {
+                       ast_verb(3, "Copying write recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
+                       copy_to_voicemail(mixmonitor, fs_write_ext, mixmonitor->filename_write);
+               }
+       } else {
+               ast_debug(3, "No recipients to forward monitor to, moving on.\n");
+       }
+
        mixmonitor_free(mixmonitor);
        return NULL;
 }
@@ -597,7 +772,8 @@ static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel
 static void launch_monitor_thread(struct ast_channel *chan, const char *filename,
                                  unsigned int flags, int readvol, int writevol,
                                  const char *post_process, const char *filename_write,
-                                 char *filename_read, const char *uid_channel_var)
+                                 char *filename_read, const char *uid_channel_var,
+                                 const char *recipients)
 {
        pthread_t thread;
        struct mixmonitor *mixmonitor;
@@ -623,6 +799,12 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
                return;
        }
 
+       /* Now that the struct has been calloced, go ahead and initialize the string fields. */
+       if (ast_string_field_init(mixmonitor, 512)) {
+               mixmonitor_free(mixmonitor);
+               return;
+       }
+
        /* Setup the actual spy before creating our thread */
        if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type, 0)) {
                mixmonitor_free(mixmonitor);
@@ -650,7 +832,6 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
        }
        ast_free(datastore_id);
 
-
        mixmonitor->name = ast_strdup(ast_channel_name(chan));
 
        if (!ast_strlen_zero(postprocess2)) {
@@ -669,6 +850,35 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
                mixmonitor->filename_read = ast_strdup(filename_read);
        }
 
+       if (!ast_strlen_zero(recipients)) {
+               char callerid[256];
+               struct ast_party_connected_line *connected;
+
+               ast_channel_lock(chan);
+
+               /* We use the connected line of the invoking channel for caller ID. */
+
+               connected = ast_channel_connected(chan);
+               ast_debug(3, "Connected Line CID = %d - %s : %d - %s\n", connected->id.name.valid,
+                       connected->id.name.str, connected->id.number.valid,
+                       connected->id.number.str);
+               ast_callerid_merge(callerid, sizeof(callerid),
+                       S_COR(connected->id.name.valid, connected->id.name.str, NULL),
+                       S_COR(connected->id.number.valid, connected->id.number.str, NULL),
+                       "Unknown");
+
+               ast_string_field_set(mixmonitor, call_context, ast_channel_context(chan));
+               ast_string_field_set(mixmonitor, call_macrocontext, ast_channel_macrocontext(chan));
+               ast_string_field_set(mixmonitor, call_extension, ast_channel_exten(chan));
+               ast_string_field_set(mixmonitor, call_callerchan, ast_channel_name(chan));
+               ast_string_field_set(mixmonitor, call_callerid, callerid);
+               mixmonitor->call_priority = ast_channel_priority(chan);
+
+               ast_channel_unlock(chan);
+
+               add_vm_recipients_from_string(mixmonitor, recipients);
+       }
+
        ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
 
        if (readvol)
@@ -723,6 +933,7 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
         char *uid_channel_var = NULL;
 
        struct ast_flags flags = { 0 };
+       char *recipients = NULL;
        char *parse;
        AST_DECLARE_APP_ARGS(args,
                AST_APP_ARG(filename);
@@ -774,6 +985,14 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
                        }
                }
 
+               if (ast_test_flag(&flags, MUXFLAG_VMRECIPIENTS)) {
+                       if (ast_strlen_zero(opts[OPT_ARG_VMRECIPIENTS])) {
+                               ast_log(LOG_WARNING, "No voicemail recipients were specified for the vm copy ('m') option.\n");
+                       } else {
+                               recipients = ast_strdupa(opts[OPT_ARG_VMRECIPIENTS]);
+                       }
+               }
+
                if (ast_test_flag(&flags, MUXFLAG_WRITE)) {
                        filename_write = ast_strdupa(filename_parse(opts[OPT_ARG_WRITENAME], filename_buffer, sizeof(filename_buffer)));
                }
@@ -799,7 +1018,16 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
        }
 
        pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
-       launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process, filename_write, filename_read, uid_channel_var);
+       launch_monitor_thread(chan,
+                       args.filename,
+                       flags.flags,
+                       readvol,
+                       writevol,
+                       args.post_process,
+                       filename_write,
+                       filename_read,
+                       uid_channel_var,
+                       recipients);
 
        return 0;
 }
index f19ca3f..cb96749 100644 (file)
@@ -1703,13 +1703,19 @@ static int extensionstate2devicestate(int state)
        return state;
 }
 
-static int extension_state_cb(const char *context, const char *exten, enum ast_extension_states state, void *data)
+static int extension_state_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data)
 {
        struct ao2_iterator miter, qiter;
        struct member *m;
        struct call_queue *q;
+       int state = info->exten_state;
        int found = 0, device_state = extensionstate2devicestate(state);
 
+       /* only interested in extension state updates involving device states */
+       if (info->reason != AST_HINT_UPDATE_DEVICE) {
+               return 0;
+       }
+
        qiter = ao2_iterator_init(queues, 0);
        while ((q = ao2_t_iterator_next(&qiter, "Iterate through queues"))) {
                ao2_lock(q);
index eec08a4..043d934 100644 (file)
@@ -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$")
                        </enumlist>
                </description>
        </application>
+       <application name="VoiceMailPlayMsg" language="en_US">
+               <synopsis>
+                       Play a single voice mail msg from a mailbox by msg id.
+               </synopsis>
+               <syntax>
+                       <parameter name="mailbox" required="true" argsep="@">
+                               <argument name="mailbox" />
+                               <argument name="context" />
+                       </parameter>
+                       <parameter name="msg_id" required="true">
+                               <para>The msg id of the msg to play back. </para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>This application sets the following channel variable upon completion:</para>
+                       <variablelist>
+                               <variable name="VOICEMAIL_PLAYBACKSTATUS">
+                                       <para>The status of the playback attempt as a text string.</para>
+                                       <value name="SUCCESS"/>
+                                       <value name="FAILED"/>
+                               </variable>
+                       </variablelist>
+               </description>
+       </application>
        <application name="VMSayName" language="en_US">
                <synopsis>
                        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
@@ -5665,6 +5873,301 @@ struct leave_vm_options {
 };
 
 /*!
+ * \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 <dir>/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
  * \param ext
@@ -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,
index 3520d22..e66bb35 100644 (file)
                LINKER_SYMBOL_PREFIXmm_notify;
                LINKER_SYMBOL_PREFIXmm_searched;
                LINKER_SYMBOL_PREFIXmm_status;
+               LINKER_SYMBOL_PREFIXast_vm_mailbox_snapshot_create;
+               LINKER_SYMBOL_PREFIXast_vm_mailbox_snapshot_destroy;
+               LINKER_SYMBOL_PREFIXast_vm_msg_move;
+               LINKER_SYMBOL_PREFIXast_vm_msg_remove;
+               LINKER_SYMBOL_PREFIXast_vm_msg_forward;
+               LINKER_SYMBOL_PREFIXast_vm_index_to_foldername;
+               LINKER_SYMBOL_PREFIXast_vm_msg_play;
+               LINKER_SYMBOL_PREFIXast_vm_test_create_user;
+               LINKER_SYMBOL_PREFIXast_vm_test_destroy_user;
        local:
                *;
 };
index 289a807..8456b0b 100644 (file)
@@ -230,6 +230,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
    affect the speed of the program at all. They can be considered to be documentation.
 */
 /* #define  REF_DEBUG 1 */
+
 #include "asterisk/lock.h"
 #include "asterisk/config.h"
 #include "asterisk/module.h"
@@ -277,7 +278,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "sip/include/dialog.h"
 #include "sip/include/dialplan_functions.h"
 #include "sip/include/security_events.h"
-
+#include "asterisk/sip_api.h"
 
 /*** DOCUMENTATION
        <application name="SIPDtmfMode" language="en_US">
@@ -339,6 +340,20 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <para>Always returns <literal>0</literal>.</para>
                </description>
        </application>
+       <application name="SIPSendCustomINFO" language="en_US">
+               <synopsis>
+                       Send a custom INFO frame on specified channels.
+               </synopsis>
+               <syntax>
+                       <parameter name="Data" required="true" />
+                       <parameter name="UserAgent" required="false" />
+               </syntax>
+               <description>
+                       <para>SIPSendCustomINFO() allows you to send a custom INFO message on all
+                       active SIP channels or on channels with the specified User Agent. This
+                       application is only available if TEST_FRAMEWORK is defined.</para>
+               </description>
+       </application>
        <function name="SIP_HEADER" language="en_US">
                <synopsis>
                        Gets the specified SIP header from an incoming INVITE message.
@@ -671,7 +686,8 @@ static const struct sip_reasons {
        { AST_REDIRECTING_REASON_FOLLOW_ME, "follow-me" },
        { AST_REDIRECTING_REASON_OUT_OF_ORDER, "out-of-service" },
        { AST_REDIRECTING_REASON_AWAY, "away" },
-       { AST_REDIRECTING_REASON_CALL_FWD_DTE, "unknown"}
+       { AST_REDIRECTING_REASON_CALL_FWD_DTE, "unknown"},
+       { AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm"},
 };
 
 
@@ -1090,6 +1106,13 @@ static void destroy_escs(void)
        }
 }
 
+struct state_notify_data {
+       int state;
+       int presence_state;
+       const char *presence_subtype;
+       const char *presence_message;
+};
+
 /*!
  * \details
  * Here we implement the container for dialogs which are in the
@@ -1373,7 +1396,8 @@ static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target
 static int do_magic_pickup(struct ast_channel *channel, const char *extension, const char *context);
 
 /*--- Device monitoring and Device/extension state/event handling */
-static int cb_extensionstate(const char *context, const char *exten, enum ast_extension_states state, void *data);
+static int extensionstate_update(char *context, char *exten, struct state_notify_data *data, struct sip_pvt *p);
+static int cb_extensionstate(char *context, char *exten, struct ast_state_cb_info *info, void *data);
 static int sip_poke_noanswer(const void *data);
 static int sip_poke_peer(struct sip_peer *peer, int force);
 static void sip_poke_all_peers(void);
@@ -1505,7 +1529,7 @@ static int get_rpid(struct sip_pvt *p, struct sip_request *oreq);
 static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq, char **name, char **number, int *reason);
 static enum sip_get_dest_result get_destination(struct sip_pvt *p, struct sip_request *oreq, int *cc_recall_core_id);
 static int get_msg_text(char *buf, int len, struct sip_request *req);
-static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout);
+static int transmit_state_notify(struct sip_pvt *p, struct state_notify_data *data, int full, int timeout);
 static void update_connectedline(struct sip_pvt *p, const void *data, size_t datalen);
 static void update_redirecting(struct sip_pvt *p, const void *data, size_t datalen);
 static int get_domain(const char *str, char *domain, int len);
@@ -3865,7 +3889,10 @@ static int __sip_autodestruct(const void *data)
 
        /* If this is a subscription, tell the phone that we got a timeout */
        if (p->subscribed && p->subscribed != MWI_NOTIFICATION && p->subscribed != CALL_COMPLETION) {
-               transmit_state_notify(p, AST_EXTENSION_DEACTIVATED, 1, TRUE);   /* Send last notification */
+               struct state_notify_data data = { 0, };
+               data.state = AST_EXTENSION_DEACTIVATED;
+
+               transmit_state_notify(p, &data, 1, TRUE);       /* Send last notification */
                p->subscribed = NONE;
                append_history(p, "Subscribestatus", "timeout");
                ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", p->callid ? p->callid : "<unknown>");
@@ -6928,6 +6955,52 @@ static int initialize_udptl(struct sip_pvt *p)
        return 0;
 }
 
+int ast_sipinfo_send(
+               struct ast_channel *chan,
+               struct ast_variable *headers,
+               const char *content_type,
+               const char *content,
+               const char *useragent_filter)
+{
+       struct sip_pvt *p;
+       struct ast_variable *var;
+       struct sip_request req;
+       int res = -1;
+
+       ast_channel_lock(chan);
+
+       if (ast_channel_tech(chan) != &sip_tech) {
+               ast_log(LOG_WARNING, "Attempted to send a custom INFO on a non-SIP channel %s\n", ast_channel_name(chan));
+               ast_channel_unlock(chan);
+               return res;
+       }
+
+       p = ast_channel_tech_pvt(chan);
+       sip_pvt_lock(p);
+
+       if (!(ast_strlen_zero(useragent_filter))) {
+               int match = (strstr(p->useragent, useragent_filter)) ? 1 : 0;
+               if (!match) {
+                       goto cleanup;
+               }
+       }
+
+       reqprep(&req, p, SIP_INFO, 0, 1);
+       for (var = headers; var; var = var->next) {
+               add_header(&req, var->name, var->value);
+       }
+       if (!ast_strlen_zero(content) && !ast_strlen_zero(content_type)) {
+               add_header(&req, "Content-Type", content_type);
+               add_content(&req, content);
+       }
+
+       res = send_request(p, &req, XMIT_RELIABLE, p->ocseq);
+
+cleanup:
+       sip_pvt_unlock(p);
+       ast_channel_unlock(chan);
+       return res;
+}
 /*! \brief Play indication to user
  * With SIP a lot of indications is sent as messages, letting the device play
    the indication - busy signal, congestion etc
@@ -13070,8 +13143,14 @@ static int find_calling_channel(void *obj, void *arg, void *data, int flags)
        return res ? CMP_MATCH | CMP_STOP : 0;
 }
 
+/* XXX Candidate for moving into its own file */
+static int allow_notify_user_presence(struct sip_pvt *p)
+{
+       return (strstr(p->useragent, "Digium")) ? 1 : 0;
+}
+
 /*! \brief Builds XML portion of NOTIFY messages for presence or dialog updates */
-static void state_notify_build_xml(int state, int full, const char *exten, const char *context, struct ast_str **tmp, struct sip_pvt *p, int subscribed, const char *mfrom, const char *mto)
+static void state_notify_build_xml(struct state_notify_data *data, int full, const char *exten, const char *context, struct ast_str **tmp, struct sip_pvt *p, int subscribed, const char *mfrom, const char *mto)
 {
        enum state { NOTIFY_OPEN, NOTIFY_INUSE, NOTIFY_CLOSED } local_state = NOTIFY_OPEN;
        const char *statestring = "terminated";
@@ -13079,7 +13158,7 @@ static void state_notify_build_xml(int state, int full, const char *exten, const
        const char *pidfnote= "Ready";
        char hint[AST_MAX_EXTENSION];
 
-       switch (state) {
+       switch (data->state) {
        case (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE):
                statestring = (sip_cfg.notifyringing) ? "early" : "confirmed";
                local_state = NOTIFY_INUSE;
@@ -13124,9 +13203,16 @@ static void state_notify_build_xml(int state, int full, const char *exten, const
 
        /* Check which device/devices we are watching  and if they are registered */
        if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten)) {
-               char *hint2 = hint, *individual_hint = NULL;
+               char *hint2;
+               char *individual_hint = NULL;
                int hint_count = 0, unavailable_count = 0;
 
+               /* strip off any possible PRESENCE providers from hint */
+               if ((hint2 = strrchr(hint, ','))) {
+                       *hint2 = '\0';
+               }
+               hint2 = hint;
+
                while ((individual_hint = strsep(&hint2, "&"))) {
                        hint_count++;
 
@@ -13174,12 +13260,30 @@ static void state_notify_build_xml(int state, int full, const char *exten, const
                        ast_str_append(tmp, 0, "<status><basic>open</basic></status>\n");
                else
                        ast_str_append(tmp, 0, "<status><basic>%s</basic></status>\n", (local_state != NOTIFY_CLOSED) ? "open" : "closed");
+
+               if (allow_notify_user_presence(p) && (data->presence_state > 0)) {
+                       ast_str_append(tmp, 0, "</tuple>\n");
+                       ast_str_append(tmp, 0, "<tuple id=\"digium-presence\">\n");
+                       ast_str_append(tmp, 0, "<status>\n");
+                       ast_str_append(tmp, 0, "<digium_presence type=\"%s\" subtype=\"%s\">%s</digium_presence>\n",
+                               ast_presence_state2str(data->presence_state),
+                               S_OR(data->presence_subtype, ""),
+                               S_OR(data->presence_message, ""));
+                       ast_str_append(tmp, 0, "</status>\n");
+                       ast_test_suite_event_notify("DIGIUM_PRESENCE_SENT",
+                                       "PresenceState: %s\r\n"
+                                       "Subtype: %s\r\n"
+                                       "Message: %s",
+                                       ast_presence_state2str(data->presence_state),
+                                       S_OR(data->presence_subtype, ""),
+                                       S_OR(data->presence_message, ""));
+               }
                ast_str_append(tmp, 0, "</tuple>\n</presence>\n");
                break;
        case DIALOG_INFO_XML: /* SNOM subscribes in this format */
                ast_str_append(tmp, 0, "<?xml version=\"1.0\"?>\n");
                ast_str_append(tmp, 0, "<dialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"%u\" state=\"%s\" entity=\"%s\">\n", p->dialogver, full ? "full" : "partial", mto);
-               if ((state & AST_EXTENSION_RINGING) && sip_cfg.notifyringing) {
+               if ((data->state & AST_EXTENSION_RINGING) && sip_cfg.notifyringing) {
                        const char *local_display = exten;
                        char *local_target = ast_strdupa(mto);
                        const char *remote_display = exten;
@@ -13256,7 +13360,7 @@ static void state_notify_build_xml(int state, int full, const char *exten, const
                        ast_str_append(tmp, 0, "<dialog id=\"%s\">\n", exten);
                }
                ast_str_append(tmp, 0, "<state>%s</state>\n", statestring);
-               if (state == AST_EXTENSION_ONHOLD) {
+               if (data->state == AST_EXTENSION_ONHOLD) {
                                ast_str_append(tmp, 0, "<local>\n<target uri=\"%s\">\n"
                                                            "<param pname=\"+sip.rendering\" pvalue=\"no\"/>\n"
                                                            "</target>\n</local>\n", mto);
@@ -13300,7 +13404,7 @@ static int transmit_cc_notify(struct ast_cc_agent *agent, struct sip_pvt *subscr
 }
 
 /*! \brief Used in the SUBSCRIBE notification subsystem (RFC3265) */
-static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout)
+static int transmit_state_notify(struct sip_pvt *p, struct state_notify_data *data, int full, int timeout)
 {
        struct ast_str *tmp = ast_str_alloca(4000);
        char from[256], to[256];
@@ -13332,7 +13436,7 @@ static int transmit_state_notify(struct sip_pvt *p, int state, int full, int tim
 
        reqprep(&req, p, SIP_NOTIFY, 0, 1);
 
-       switch(state) {
+       switch(data->state) {
        case AST_EXTENSION_DEACTIVATED:
                if (timeout)
                        add_header(&req, "Subscription-State", "terminated;reason=timeout");
@@ -13355,19 +13459,19 @@ static int transmit_state_notify(struct sip_pvt *p, int state, int full, int tim
        case XPIDF_XML:
        case CPIM_PIDF_XML:
                add_header(&req, "Event", subscriptiontype->event);
-               state_notify_build_xml(state, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto);
+               state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto);
                add_header(&req, "Content-Type", subscriptiontype->mediatype);
                p->dialogver++;
                break;
        case PIDF_XML: /* Eyebeam supports this format */
                add_header(&req, "Event", subscriptiontype->event);
-               state_notify_build_xml(state, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto);
+               state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto);
                add_header(&req, "Content-Type", subscriptiontype->mediatype);
                p->dialogver++;
                break;
        case DIALOG_INFO_XML: /* SNOM subscribes in this format */
                add_header(&req, "Event", subscriptiontype->event);
-               state_notify_build_xml(state, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto);
+               state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto);
                add_header(&req, "Content-Type", subscriptiontype->mediatype);
                p->dialogver++;
                break;
@@ -15152,34 +15256,35 @@ static void cb_extensionstate_destroy(int id, void *data)
 /*! \brief Callback for the devicestate notification (SUBSCRIBE) support subsystem
 \note  If you add an "hint" priority to the extension in the dial plan,
        you will get notifications on device state changes */
-static int cb_extensionstate(const char *context, const char *exten, enum ast_extension_states state, void *data)
+static int extensionstate_update(char *context, char *exten, struct state_notify_data *data, struct sip_pvt *p)
 {
-       struct sip_pvt *p = data;
-
        sip_pvt_lock(p);
 
-       switch(state) {
+       switch (data->state) {
        case AST_EXTENSION_DEACTIVATED: /* Retry after a while */
        case AST_EXTENSION_REMOVED:     /* Extension is gone */
                sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);     /* Delete subscription in 32 secs */
-               ast_verb(2, "Extension state: Watcher for hint %s %s. Notify User %s\n", exten, state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", p->username);
+               ast_verb(2, "Extension state: Watcher for hint %s %s. Notify User %s\n", exten, data->state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", p->username);
                p->subscribed = NONE;
-               append_history(p, "Subscribestatus", "%s", state == AST_EXTENSION_REMOVED ? "HintRemoved" : "Deactivated");
+               append_history(p, "Subscribestatus", "%s", data->state == AST_EXTENSION_REMOVED ? "HintRemoved" : "Deactivated");
                break;
        default:        /* Tell user */
-               p->laststate = state;
+               p->laststate = data->state;
+               p->last_presence_state = data->presence_state;
+               ast_string_field_set(p, last_presence_subtype, S_OR(data->presence_subtype, ""));
+               ast_string_field_set(p, last_presence_message, S_OR(data->presence_message, ""));
                break;
        }
        if (p->subscribed != NONE) {    /* Only send state NOTIFY if we know the format */
                if (!p->pendinginvite) {
-                       transmit_state_notify(p, state, 1, FALSE);
+                       transmit_state_notify(p, data, 1, FALSE);
                } else {
                        /* We already have a NOTIFY sent that is not answered. Queue the state up.
                           if many state changes happen meanwhile, we will only send a notification of the last one */
                        ast_set_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE);
                }
        }
-       ast_verb(2, "Extension Changed %s[%s] new state %s for Notify User %s %s\n", exten, context, ast_extension_state2str(state), p->username,
+       ast_verb(2, "Extension Changed %s[%s] new state %s for Notify User %s %s\n", exten, context, ast_extension_state2str(data->state), p->username,
                        ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE) ? "(queued)" : "");
 
        sip_pvt_unlock(p);
@@ -15187,6 +15292,27 @@ static int cb_extensionstate(const char *context, const char *exten, enum ast_ex
        return 0;
 }
 
+/*! \brief Callback for the devicestate notification (SUBSCRIBE) support subsystem
+\note  If you add an "hint" priority to the extension in the dial plan,
+       you will get notifications on device state changes */
+static int cb_extensionstate(char *context, char *exten, struct ast_state_cb_info *info, void *data)
+{
+       struct sip_pvt *p = data;
+       struct state_notify_data notify_data = {
+               .state = info->exten_state,
+               .presence_state = info->presence_state,
+               .presence_subtype = info->presence_subtype,
+               .presence_message = info->presence_message,
+       };
+
+       if ((info->reason == AST_HINT_UPDATE_PRESENCE) && !(allow_notify_user_presence(p))) {
+               /* ignore a presence triggered update if we know the useragent doesn't care */
+               return 0;
+       }
+
+       return extensionstate_update(context, exten, &notify_data, p);
+}
+
 /*! \brief Send a fake 401 Unauthorized response when the administrator
   wants to hide the names of local devices  from fishers
  */
@@ -21342,9 +21468,15 @@ static void handle_response_notify(struct sip_pvt *p, int resp, const char *rest
                                pvt_set_needdestroy(p, "received 200 response");
                        }
                        if (ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE)) {
+                               struct state_notify_data data = {
+                                       .state = p->laststate,
+                                       .presence_state = p->last_presence_state,
+                                       .presence_subtype = p->last_presence_subtype,
+                                       .presence_message = p->last_presence_message,
+                               };
                                /* Ready to send the next state we have on queue */
                                ast_clear_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE);
-                               cb_extensionstate((char *)p->context, (char *)p->exten, p->laststate, (void *) p);
+                               extensionstate_update((char *)p->context, (char *)p->exten, &data, (void *) p);
                        }
                }
                break;
@@ -24331,6 +24463,8 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, int
        int localtransfer = 0;
        int attendedtransfer = 0;
        int res = 0;
+       struct ast_party_redirecting redirecting;
+       struct ast_set_party_redirecting update_redirecting;
 
        if (req->debug) {
                ast_verbose("Call %s got a SIP call transfer from %s: (REFER)!\n",
@@ -24635,6 +24769,16 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, int
        }
        ast_set_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER);  /* Delay hangup */
 
+       /* When a call is transferred to voicemail from a Digium phone, there may be
+        * a Diversion header present in the REFER with an appropriate reason parameter
+        * set. We need to update the redirecting information appropriately.
+        */
+       ast_party_redirecting_init(&redirecting);
+       memset(&update_redirecting, 0, sizeof(update_redirecting));
+       change_redirecting_information(p, req, &redirecting, &update_redirecting, FALSE);
+       ast_channel_update_redirecting(current.chan2, &redirecting, &update_redirecting);
+       ast_party_redirecting_free(&redirecting);
+
        /* Do not hold the pvt lock during the indicate and async_goto. Those functions
         * lock channels which will invalidate locking order if the pvt lock is held.*/
        /* For blind transfers, move the call to the new extensions. For attended transfers on multiple
@@ -25684,7 +25828,6 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req,
 {
        int gotdest = 0;
        int res = 0;
-       int firststate;
        struct sip_peer *authpeer = NULL;
        const char *eventheader = sip_get_header(req, "Event"); /* Get Event package name */
        int resubscribe = (p->subscribed != NONE) && !req->ignore;
@@ -26037,9 +26180,10 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req,
                                sip_unref_peer(peer, "release a peer ref now that MWI is sent");
                        }
                } else if (p->subscribed != CALL_COMPLETION) {
-
-                       if ((firststate = ast_extension_state(NULL, p->context, p->exten)) < 0) {
-
+                       struct state_notify_data data = { 0, };
+                       char *subtype = NULL;
+                       char *message = NULL;
+                       if ((data.state = ast_extension_state(NULL, p->context, p->exten)) < 0) {
                                ast_log(LOG_NOTICE, "Got SUBSCRIBE for extension %s@%s from %s, but there is no hint for that extension.\n", p->exten, p->context, ast_sockaddr_stringify(&p->sa));
                                transmit_response(p, "404 Not found", req);
                                pvt_set_needdestroy(p, "no extension for SUBSCRIBE");
@@ -26048,14 +26192,21 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req,
                                }
                                return 0;
                        }
+                       if (allow_notify_user_presence(p)) {
+                               data.presence_state = ast_hint_presence_state(NULL, p->context, p->exten, &subtype, &message);
+                               data.presence_subtype = subtype;
+                               data.presence_message = message;
+                       }
                        ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
                        transmit_response(p, "200 OK", req);
-                       transmit_state_notify(p, firststate, 1, FALSE); /* Send first notification */
-                       append_history(p, "Subscribestatus", "%s", ast_extension_state2str(firststate));
+                       transmit_state_notify(p, &data, 1, FALSE);      /* Send first notification */
+                       append_history(p, "Subscribestatus", "%s", ast_extension_state2str(data.state));
                        /* hide the 'complete' exten/context in the refer_to field for later display */
                        ast_string_field_build(p, subscribeuri, "%s@%s", p->exten, p->context);
                        /* Deleted the slow iteration of all sip dialogs to find old subscribes from this peer for exten@context */
 
+                       ast_free(subtype);
+                       ast_free(message);
                }
                if (!p->expiry) {
                        pvt_set_needdestroy(p, "forcing expiration");
@@ -30813,6 +30964,9 @@ static struct ast_rtp_glue sip_rtp_glue = {
 static char *app_dtmfmode = "SIPDtmfMode";
 static char *app_sipaddheader = "SIPAddHeader";
 static char *app_sipremoveheader = "SIPRemoveHeader";
+#ifdef TEST_FRAMEWORK
+static char *app_sipsendcustominfo = "SIPSendCustomINFO";
+#endif
 
 /*! \brief Set the DTMFmode for an outbound SIP call (application) */
 static int sip_dtmfmode(struct ast_channel *chan, const char *data)
@@ -30941,6 +31095,28 @@ static int sip_removeheader(struct ast_channel *chan, const char *data)
        return 0;
 }
 
+#ifdef TEST_FRAMEWORK
+/*! \brief Send a custom INFO message via AST_CONTROL_CUSTOM indication */
+static int sip_sendcustominfo(struct ast_channel *chan, const char *data)
+{
+       char *info_data, *useragent;
+
+       if (ast_strlen_zero(data)) {
+               ast_log(LOG_WARNING, "You must provide data to be sent\n");
+               return 0;
+       }
+
+       useragent = ast_strdupa(data);
+       info_data = strsep(&useragent, ",");
+
+       if (ast_sipinfo_send(chan, NULL, "text/plain", info_data, useragent)) {
+               ast_log(LOG_WARNING, "Failed to create payload for custom SIP INFO\n");
+               return 0;
+       }
+       return 0;
+}
+#endif
+
 /*! \brief Transfer call before connect with a 302 redirect
 \note  Called by the transfer() dialplan application through the sip_transfer()
        pbx interface function if the call is in ringing state
@@ -31922,6 +32098,9 @@ static int load_module(void)
        ast_register_application_xml(app_dtmfmode, sip_dtmfmode);
        ast_register_application_xml(app_sipaddheader, sip_addheader);
        ast_register_application_xml(app_sipremoveheader, sip_removeheader);
+#ifdef TEST_FRAMEWORK
+       ast_register_application_xml(app_sipsendcustominfo, sip_sendcustominfo);
+#endif
 
        /* Register dialplan functions */
        ast_custom_function_register(&sip_header_function);
@@ -32015,8 +32194,9 @@ static int unload_module(void)
        ast_unregister_application(app_dtmfmode);
        ast_unregister_application(app_sipaddheader);
        ast_unregister_application(app_sipremoveheader);
-
 #ifdef TEST_FRAMEWORK
+       ast_unregister_application(app_sipsendcustominfo);
+
        AST_TEST_UNREGISTER(test_sip_peers_get);
        AST_TEST_UNREGISTER(test_sip_mwi_subscribe_parse);
 #endif
@@ -32166,7 +32346,7 @@ static int unload_module(void)
        return 0;
 }
 
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Session Initiation Protocol (SIP)",
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Session Initiation Protocol (SIP)",
                .load = load_module,
                .unload = unload_module,
                .reload = reload,
diff --git a/channels/chan_sip.exports.in b/channels/chan_sip.exports.in
new file mode 100644 (file)
index 0000000..1e9972a
--- /dev/null
@@ -0,0 +1,6 @@
+{
+       global:
+               LINKER_SYMBOL_PREFIX*ast_sipinfo_send;
+       local:
+               *;
+};
index 753d7ae..6923f8f 100644 (file)
@@ -1510,7 +1510,7 @@ static struct ast_channel_tech skinny_tech = {
        .bridge = ast_rtp_instance_bridge, 
 };
 
-static int skinny_extensionstate_cb(const char *context, const char *exten, enum ast_extension_states state, void *data);
+static int skinny_extensionstate_cb(char *context, char *id, struct ast_state_cb_info *info, void *data);
 static int skinny_transfer(struct skinny_subchannel *sub);
 
 static struct skinny_line *skinny_line_alloc(void)
@@ -3029,11 +3029,17 @@ static void transmit_capabilitiesreq(struct skinny_device *d)
        transmit_response(d, req);
 }
 
-static int skinny_extensionstate_cb(const char *context, const char *exten, enum ast_extension_states state, void *data)
+static int skinny_extensionstate_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data)
 {
        struct skinny_container *container = data;
        struct skinny_device *d = NULL;
        char hint[AST_MAX_EXTENSION];
+       int state = info->exten_state;
+
+       /* only interested in device state here */
+       if (info->reason != AST_HINT_UPDATE_DEVICE) {
+               return 0;
+       }
 
        if (container->type == SKINNY_SDCONTAINER) {
                struct skinny_speeddial *sd = container->data;
@@ -5060,10 +5066,13 @@ static void setsubstate(struct skinny_subchannel *sub, int state)
                                        AST_LIST_TRAVERSE(&tmpline->sublines, tmpsubline, list) {
                                                if (!(subline == tmpsubline)) {
                                                        if (!strcasecmp(subline->lnname, tmpsubline->lnname)) {
+                                                               struct ast_state_cb_info info = {
+                                                                       .exten_state = tmpsubline->extenstate,
+                                                               };
                                                                tmpsubline->callid = callnums++;
                                                                transmit_callstate(tmpsubline->line->device, tmpsubline->line->instance, tmpsubline->callid, SKINNY_OFFHOOK);
                                                                push_callinfo(tmpsubline, sub);
-                                                               skinny_extensionstate_cb(NULL, NULL, tmpsubline->extenstate, tmpsubline->container);
+                                                               skinny_extensionstate_cb(NULL, NULL, &info, tmpsubline->container);
                                                        }
                                                }
                                        }
index 958fe9a..e8f079d 100644 (file)
@@ -1041,6 +1041,8 @@ struct sip_pvt {
                AST_STRING_FIELD(parkinglot);   /*!< Parkinglot */
                AST_STRING_FIELD(engine);       /*!< RTP engine to use */
                AST_STRING_FIELD(dialstring);   /*!< The dialstring used to call this SIP endpoint */
+               AST_STRING_FIELD(last_presence_subtype);   /*!< The last presence subtype sent for a subscription. */
+               AST_STRING_FIELD(last_presence_message);   /*!< The last presence message for a subscription */
                AST_STRING_FIELD(msg_body);     /*!< Text for a MESSAGE body */
        );
        char via[128];                          /*!< Via: header */
@@ -1141,6 +1143,7 @@ struct sip_pvt {
        enum subscriptiontype subscribed;   /*!< SUBSCRIBE: Is this dialog a subscription?  */
        int stateid;                        /*!< SUBSCRIBE: ID for devicestate subscriptions */
        int laststate;                      /*!< SUBSCRIBE: Last known extension state */
+       int last_presence_state;            /*!< SUBSCRIBE: Last known presence state */
        uint32_t dialogver;                 /*!< SUBSCRIBE: Version for subscription dialog-info */
 
        struct ast_dsp *dsp;                /*!< Inband DTMF or Fax CNG tone Detection dsp */
index fb44e74..5e99cf8 100644 (file)
@@ -140,7 +140,8 @@ bindaddr = 0.0.0.0
 ; test      - Ability to read TestEvent notifications sent to the Asterisk Test
 ;             Suite.  Note that this is only enabled when the TEST_FRAMEWORK
 ;             compiler flag is defined.
+; message   - Permissions to send out of call messages. Write-only
 ;
 ;read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan
-;write = system,call,agent,user,config,command,reporting,originate
+;write = system,call,agent,user,config,command,reporting,originate,message
 
index 79dc0ea..7290a9a 100644 (file)
@@ -25,5 +25,7 @@ CREATE TABLE voicemail_messages (
        mailboxuser CHAR(30),
        -- Context of the owner of the mailbox
        mailboxcontext CHAR(30),
+       -- Unique ID of the message,
+       msg_id char(40),
        PRIMARY KEY (dir, msgnum)
 );
diff --git a/funcs/func_presencestate.c b/funcs/func_presencestate.c
new file mode 100644 (file)
index 0000000..fa72112
--- /dev/null
@@ -0,0 +1,781 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2011, Digium, Inc.
+ *
+ * David Vossel <dvossel@digium.com> 
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Custom presence provider
+ * \ingroup functions
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/utils.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/presencestate.h"
+#include "asterisk/cli.h"
+#include "asterisk/astdb.h"
+#include "asterisk/app.h"
+#ifdef TEST_FRAMEWORK
+#include "asterisk/test.h"
+#include "asterisk/event.h"
+#include <semaphore.h>
+#endif
+
+/*** DOCUMENTATION
+       <function name="PRESENCE_STATE" language="en_US">
+               <synopsis>
+                       Get or Set a presence state.
+               </synopsis>
+               <syntax>
+                       <parameter name="provider" required="true">
+                         <para>The provider of the presence, such as <literal>CustomPresence</literal></para>
+                       </parameter>
+                       <parameter name="field" required="true">
+                         <para>Which field of the presence state information is wanted.</para>
+                         <optionlist>
+                               <option name="value">
+                                 <para>The current presence, such as <literal>away</literal></para>
+                               </option>
+                               <option name="subtype">
+                                 <para>Further information about the current presence</para>
+                               </option>
+                           <option name="message">
+                                 <para>A custom message that may indicate further details about the presence</para>
+                               </option>
+                         </optionlist>
+                       </parameter>
+                       <parameter name="options" required="false">
+                         <optionlist>
+                           <option name="e">
+                                 <para>Base-64 encode the data.</para>
+                               </option>
+                         </optionlist>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>The PRESENCE_STATE function can be used to retrieve the presence from any
+                       presence provider. For example:</para>
+                       <para>NoOp(SIP/mypeer has presence ${PRESENCE_STATE(SIP/mypeer,value)})</para>
+                       <para>NoOp(Conference number 1234 has presence message ${PRESENCE_STATE(MeetMe:1234,message)})</para>
+                       <para>The PRESENCE_STATE function can also be used to set custom presence state from
+                       the dialplan.  The <literal>CustomPresence:</literal> prefix must be used. For example:</para>
+                       <para>Set(PRESENCE_STATE(CustomPresence:lamp1)=away,temporary,Out to lunch)</para>
+                       <para>Set(PRESENCE_STATE(CustomPresence:lamp2)=dnd,,Trying to get work done)</para>
+                       <para>You can subscribe to the status of a custom presence state using a hint in
+                       the dialplan:</para>
+                       <para>exten => 1234,hint,CustomPresence:lamp1</para>
+                       <para>The possible values for both uses of this function are:</para>
+                       <para>not_set | unavailable | available | away | xa | chat | dnd</para>
+               </description>
+       </function>
+ ***/
+
+
+static const char astdb_family[] = "CustomPresence";
+
+static int presence_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+       int state;
+       char *message = NULL;
+       char *subtype = NULL;
+       char *parse;
+       int base64encode = 0;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(provider);
+               AST_APP_ARG(field);
+               AST_APP_ARG(options);
+       );
+
+       if (ast_strlen_zero(data)) {
+               ast_log(LOG_WARNING, "PRESENCE_STATE reading requires an argument \n");
+               return -1;
+       }
+
+       parse = ast_strdupa(data);
+
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       if (ast_strlen_zero(args.provider) || ast_strlen_zero(args.field)) {
+               ast_log(LOG_WARNING, "PRESENCE_STATE reading requires both presence provider and presence field arguments. \n");
+               return -1;
+       }
+
+       state = ast_presence_state_nocache(args.provider, &subtype, &message);
+       if (state == AST_PRESENCE_INVALID) {
+               ast_log(LOG_WARNING, "PRESENCE_STATE unknown \n");
+               return -1;
+       }
+
+       if (!(ast_strlen_zero(args.options)) && (strchr(args.options, 'e'))) {
+               base64encode = 1;
+       }
+
+       if (!ast_strlen_zero(subtype) && !strcasecmp(args.field, "subtype")) {
+               if (base64encode) {
+                       ast_base64encode(buf, (unsigned char *) subtype, strlen(subtype), len);
+               } else {
+                       ast_copy_string(buf, subtype, len);
+               }
+       } else if (!ast_strlen_zero(message) && !strcasecmp(args.field, "message")) {
+               if (base64encode) {
+                       ast_base64encode(buf, (unsigned char *) message, strlen(message), len);
+               } else {
+                       ast_copy_string(buf, message, len);
+               }
+
+       } else if (!strcasecmp(args.field, "value")) {
+               ast_copy_string(buf, ast_presence_state2str(state), len);
+       }
+
+       ast_free(message);
+       ast_free(subtype);
+
+       return 0;
+}
+
+static int parse_data(char *data, enum ast_presence_state *state, char **subtype, char **message, char **options)
+{
+       char *state_str;
+
+       /* data syntax is state,subtype,message,options */
+       *subtype = "";
+       *message = "";
+       *options = "";
+
+       state_str = strsep(&data, ",");
+       if (ast_strlen_zero(state_str)) {
+               return -1; /* state is required */
+       }
+
+       *state = ast_presence_state_val(state_str);
+
+       /* not a valid state */
+       if (*state == AST_PRESENCE_INVALID) {
+               ast_log(LOG_WARNING, "Unknown presence state value %s\n", state_str);
+               return -1;
+       }
+
+       if (!(*subtype = strsep(&data,","))) {
+               *subtype = "";
+               return 0;
+       }
+
+       if (!(*message = strsep(&data, ","))) {
+               *message = "";
+               return 0;
+       }
+
+       if (!(*options = strsep(&data, ","))) {
+               *options = "";
+               return 0;
+       }
+
+       if (!ast_strlen_zero(*options) && !(strchr(*options, 'e'))) {
+               ast_log(LOG_NOTICE, "Invalid options  '%s'\n", *options);
+               return -1;
+       }
+
+       return 0;
+}
+
+static int presence_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
+{
+       size_t len = strlen("CustomPresence:");
+       char *tmp = data;
+       char *args = ast_strdupa(value);
+       enum ast_presence_state state;
+       char *options, *message, *subtype;
+
+       if (strncasecmp(data, "CustomPresence:", len)) {
+               ast_log(LOG_WARNING, "The PRESENCE_STATE function can only set CustomPresence: presence providers.\n");
+               return -1;
+       }
+       data += len;
+       if (ast_strlen_zero(data)) {
+               ast_log(LOG_WARNING, "PRESENCE_STATE function called with no custom device name!\n");
+               return -1;
+       }
+
+       if (parse_data(args, &state, &subtype, &message, &options)) {
+               ast_log(LOG_WARNING, "Invalid arguments to PRESENCE_STATE\n");
+               return -1;
+       }
+
+       ast_db_put(astdb_family, data, value);
+
+       ast_presence_state_changed_literal(state, subtype, message, tmp);
+
+       return 0;
+}
+
+static enum ast_presence_state custom_presence_callback(const char *data, char **subtype, char **message)
+{
+       char buf[1301] = "";
+       enum ast_presence_state state;
+       char *_options;
+       char *_message;
+       char *_subtype;
+
+       ast_log(LOG_NOTICE, "TITTY BOMBS!\n");
+
+       ast_db_get(astdb_family, data, buf, sizeof(buf));
+
+       if (parse_data(buf, &state, &_subtype, &_message, &_options)) {
+               return -1;
+       }
+
+       if ((strchr(_options, 'e'))) {
+               char tmp[1301];
+               if (ast_strlen_zero(_subtype)) {
+                       *subtype = NULL;
+               } else {
+                       memset(tmp, 0, sizeof(tmp));
+                       ast_log(LOG_NOTICE, "Hey there, I'm doing some base64 decoding\n");
+                       ast_base64decode((unsigned char *) tmp, _subtype, sizeof(tmp) - 1);
+                       *subtype = ast_strdup(tmp);
+               }
+
+               if (ast_strlen_zero(_message)) {
+                       *message = NULL;
+               } else {
+                       memset(tmp, 0, sizeof(tmp));
+                       ast_log(LOG_NOTICE, "Hey there, I'm doing some more base64 decoding\n");
+                       ast_base64decode((unsigned char *) tmp, _message, sizeof(tmp) - 1);
+                       *message = ast_strdup(tmp);
+               }
+       } else {
+               ast_log(LOG_NOTICE, "Not doing any base64 decoding\n");
+               *subtype = ast_strlen_zero(_subtype) ? NULL : ast_strdup(_subtype);
+               *message = ast_strlen_zero(_message) ? NULL : ast_strdup(_message);
+       }
+       return state;
+}
+
+static struct ast_custom_function presence_function = {
+       .name = "PRESENCE_STATE",
+       .read = presence_read,
+       .write = presence_write,
+};
+
+static char *handle_cli_presencestate_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct ast_db_entry *db_entry, *db_tree;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "presencestate list";
+               e->usage =
+                       "Usage: presencestate list\n"
+                       "       List all custom presence states that have been set by using\n"
+                       "       the PRESENCE_STATE dialplan function.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
+
+       if (a->argc != e->args) {
+               return CLI_SHOWUSAGE;
+       }
+
+       ast_cli(a->fd, "\n"
+               "---------------------------------------------------------------------\n"
+               "--- Custom Presence States ------------------------------------------\n"
+               "---------------------------------------------------------------------\n"
+               "---\n");
+
+       db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
+       if (!db_entry) {
+               ast_cli(a->fd, "No custom presence states defined\n");
+               return CLI_SUCCESS;
+       }
+       for (; db_entry; db_entry = db_entry->next) {
+               const char *object_name = strrchr(db_entry->key, '/') + 1;
+               char state_info[1301];
+               enum ast_presence_state state;
+               char *subtype;
+               char *message;
+               char *options;
+
+               ast_copy_string(state_info, db_entry->data, sizeof(state_info));
+               if (parse_data(state_info, &state, &subtype, &message, &options)) {
+                       ast_log(LOG_WARNING, "Invalid CustomPresence entry %s encountered\n", db_entry->data);
+                       continue;
+               }
+
+               if (object_name <= (const char *) 1) {
+                       continue;
+               }
+               ast_cli(a->fd, "--- Name: 'CustomPresence:%s'\n"
+                                      "    --- State: '%s'\n"
+                                          "    --- Subtype: '%s'\n"
+                                          "    --- Message: '%s'\n"
+                                          "    --- Base64 Encoded: '%s'\n"
+                              "---\n",
+                                          object_name,
+                                          ast_presence_state2str(state),
+                                          subtype,
+                                          message,
+                                          AST_CLI_YESNO(strchr(options, 'e')));
+       }
+       ast_db_freetree(db_tree);
+       db_tree = NULL;
+
+       ast_cli(a->fd,
+               "---------------------------------------------------------------------\n"
+               "---------------------------------------------------------------------\n"
+               "\n");
+
+       return CLI_SUCCESS;
+}
+
+static char *handle_cli_presencestate_change(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+    size_t len;
+       const char *dev, *state, *full_dev;
+       enum ast_presence_state state_val;
+       char *message;
+       char *subtype;
+       char *options;
+       char *args;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "presencestate change";
+               e->usage =
+                       "Usage: presencestate change <entity> <state>[,<subtype>[,message[,options]]]\n"
+                       "       Change a custom presence to a new state.\n"
+                       "       The possible values for the state are:\n"
+                       "NOT_SET | UNAVAILABLE | AVAILABLE | AWAY | XA | CHAT | DND\n"
+                       "Optionally, a custom subtype and message may be provided, along with any options\n"
+                       "accepted by func_presencestate. If the subtype or message provided contain spaces,\n"
+                       "be sure to enclose the data in quotation marks (\"\")\n"
+                       "\n"
+                       "Examples:\n"
+                       "       presencestate change CustomPresence:mystate1 AWAY\n"
+                       "       presencestate change CustomPresence:mystate1 AVAILABLE\n"
+                       "       presencestate change CustomPresence:mystate1 \"Away,upstairs,eating lunch\"\n"
+                       "       \n";
+               return NULL;
+       case CLI_GENERATE:
+       {
+               static const char * const cmds[] = { "NOT_SET", "UNAVAILABLE", "AVAILABLE", "AWAY",
+                                                    "XA", "CHAT", "DND", NULL };
+
+               if (a->pos == e->args + 1) {
+                       return ast_cli_complete(a->word, cmds, a->n);
+               }
+
+               return NULL;
+       }
+       }
+
+       if (a->argc != e->args + 2) {
+               return CLI_SHOWUSAGE;
+       }
+
+       len = strlen("CustomPresence:");
+       full_dev = dev = a->argv[e->args];
+       state = a->argv[e->args + 1];
+
+       if (strncasecmp(dev, "CustomPresence:", len)) {
+               ast_cli(a->fd, "The presencestate command can only be used to set 'CustomPresence:' presence state!\n");
+               return CLI_FAILURE;
+       }
+
+       dev += len;
+       if (ast_strlen_zero(dev)) {
+               return CLI_SHOWUSAGE;
+       }
+
+       args = ast_strdupa(state);
+       if (parse_data(args, &state_val, &subtype, &message, &options)) {
+               return CLI_SHOWUSAGE;
+       }
+
+       if (state_val == AST_PRESENCE_NOT_SET) {
+               return CLI_SHOWUSAGE;
+       }
+
+       ast_cli(a->fd, "Changing %s to %s\n", dev, args);
+
+       ast_db_put(astdb_family, dev, state);
+
+       ast_presence_state_changed_literal(state_val, subtype, message, full_dev);
+
+       return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_funcpresencestate[] = {
+       AST_CLI_DEFINE(handle_cli_presencestate_list, "List currently know custom presence states"),
+       AST_CLI_DEFINE(handle_cli_presencestate_change, "Change a custom presence state"),
+};
+
+#ifdef TEST_FRAMEWORK
+
+struct test_string {
+       char *parse_string;
+       struct {
+               int value;
+               const char *subtype;
+               const char *message;
+               const char *options; 
+       } outputs;
+};
+
+AST_TEST_DEFINE(test_valid_parse_data)
+{
+       int i;
+       enum ast_presence_state state;
+       char *subtype;
+       char *message;
+       char *options;
+       enum ast_test_result_state res = AST_TEST_PASS;
+       
+       struct test_string tests [] = {
+               { "away",
+                       { AST_PRESENCE_AWAY,
+                               "",
+                               "",
+                               ""
+                       }
+               },
+               { "not_set",
+                       { AST_PRESENCE_NOT_SET,
+                               "",
+                               "",
+                               ""
+                       }
+               },
+               { "unavailable",
+                       { AST_PRESENCE_UNAVAILABLE,
+                               "",
+                               "",
+                               ""
+                       }
+               },
+               { "available",
+                       { AST_PRESENCE_AVAILABLE,
+                               "",
+                               "",
+                               ""
+                       }
+               },
+               { "xa",
+                       { AST_PRESENCE_XA,
+                               "",
+                               "",
+                               ""
+                       }
+               },
+               { "chat",
+                       { AST_PRESENCE_CHAT,
+                               "",
+                               "",
+                               ""
+                       }
+               },
+               { "dnd",
+                       { AST_PRESENCE_DND,
+                               "",
+                               "",
+                               ""
+                       }
+               },
+               { "away,down the hall",
+                       { AST_PRESENCE_AWAY,
+                               "down the hall",
+                               "",
+                               ""
+                       }
+               },
+               { "away,down the hall,Quarterly financial meeting",
+                       { AST_PRESENCE_AWAY,
+                               "down the hall",
+                               "Quarterly financial meeting",
+                               ""
+                       }
+               },
+               { "away,,Quarterly financial meeting",
+                       { AST_PRESENCE_AWAY,
+                               "",
+                               "Quarterly financial meeting",
+                               ""
+                       }
+               },
+               { "away,,,e",
+                       { AST_PRESENCE_AWAY,
+                               "",
+                               "",
+                               "e",
+                       }
+               },
+               { "away,down the hall,,e",
+                       { AST_PRESENCE_AWAY,
+                               "down the hall",
+                               "",
+                               "e"
+                       }
+               },
+               { "away,down the hall,Quarterly financial meeting,e",
+                       { AST_PRESENCE_AWAY,
+                               "down the hall",
+                               "Quarterly financial meeting",
+                               "e"
+                       }
+               },
+               { "away,,Quarterly financial meeting,e",
+                       { AST_PRESENCE_AWAY,
+                               "",
+                               "Quarterly financial meeting",
+                               "e"
+                       }
+               }
+       };
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = "parse_valid_presence_data";
+               info->category = "/funcs/func_presence";
+               info->summary = "PRESENCESTATE parsing test";
+               info->description =
+                       "Ensure that parsing function accepts proper values, and gives proper outputs";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       for (i = 0; i < ARRAY_LEN(tests); ++i) {
+               int parse_result;
+               char *parse_string = ast_strdup(tests[i].parse_string);
+               if (!parse_string) {
+                       res = AST_TEST_FAIL;
+                       break;
+               }
+               parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
+               if (parse_result == -1) {
+                       res = AST_TEST_FAIL;
+                       ast_free(parse_string);
+                       break;
+               }
+               if (tests[i].outputs.value != state ||
+                               strcmp(tests[i].outputs.subtype, subtype) ||
+                               strcmp(tests[i].outputs.message, message) ||
+                               strcmp(tests[i].outputs.options, options)) {
+                       res = AST_TEST_FAIL;
+                       ast_free(parse_string);
+                       break;
+               }
+               ast_free(parse_string);
+       }
+
+       return res;
+}
+
+AST_TEST_DEFINE(test_invalid_parse_data)
+{
+       int i;
+       enum ast_presence_state state;
+       char *subtype;
+       char *message;
+       char *options;
+       enum ast_test_result_state res = AST_TEST_PASS;
+
+       char *tests[] = {
+               "",
+               "bored",
+               "away,,,i",
+               /* XXX The following actually is parsed correctly. Should that
+                * be changed?
+                * "away,,,,e",
+                */
+       };
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = "parse_invalid_presence_data";
+               info->category = "/funcs/func_presence";
+               info->summary = "PRESENCESTATE parsing test";
+               info->description =
+                       "Ensure that parsing function rejects improper values";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       for (i = 0; i < ARRAY_LEN(tests); ++i) {
+               int parse_result;
+               char *parse_string = ast_strdup(tests[i]);
+               if (!parse_string) {
+                       res = AST_TEST_FAIL;
+                       break;
+               }
+               parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
+               if (parse_result == 0) {
+                       ast_log(LOG_WARNING, "Invalid string parsing failed on %s\n", tests[i]);
+                       res = AST_TEST_FAIL;
+                       ast_free(parse_string);
+                       break;
+               }
+               ast_free(parse_string);
+       }
+
+       return res;
+}
+
+struct test_cb_data {
+       enum ast_presence_state presence;
+       const char *provider;
+       const char *subtype;
+       const char *message;
+       /* That's right. I'm using a semaphore */
+       sem_t sem;
+};
+
+static void test_cb(const struct ast_event *event, void *userdata)
+{
+       struct test_cb_data *cb_data = userdata;
+       cb_data->presence = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
+       cb_data->provider = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER));
+       cb_data->subtype = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE));
+       cb_data->message = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE));
+       sem_post(&cb_data->sem);
+}
+
+/* XXX This test could probably stand to be moved since
+ * it does not test func_presencestate but rather code in
+ * presencestate.h and presencestate.c. However, the convenience
+ * of presence_write() makes this a nice location for this test.
+ */
+AST_TEST_DEFINE(test_presence_state_change)
+{
+       struct ast_event_sub *test_sub;
+       struct test_cb_data *cb_data;
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = "test_presence_state_change";
+               info->category = "/funcs/func_presence";
+               info->summary = "presence state change subscription";
+               info->description =
+                       "Ensure that presence state changes are communicated to subscribers";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       cb_data = ast_calloc(1, sizeof(*cb_data));
+       if (!cb_data) {
+               return AST_TEST_FAIL;
+       }
+
+       if (!(test_sub = ast_event_subscribe(AST_EVENT_PRESENCE_STATE,
+                       test_cb, "Test presence state callbacks", cb_data, AST_EVENT_IE_END))) {
+               return AST_TEST_FAIL;
+       }
+
+       if (sem_init(&cb_data->sem, 0, 0)) {
+               return AST_TEST_FAIL;
+       }
+
+       presence_write(NULL, "PRESENCESTATE", "CustomPresence:TestPresenceStateChange", "away,down the hall,Quarterly financial meeting");
+       sem_wait(&cb_data->sem);
+       if (cb_data->presence != AST_PRESENCE_AWAY ||
+                       strcmp(cb_data->provider, "CustomPresence:TestPresenceStateChange") ||
+                       strcmp(cb_data->subtype, "down the hall") ||
+                       strcmp(cb_data->message, "Quarterly financial meeting")) {
+               return AST_TEST_FAIL;
+       }
+
+       ast_free((char *)cb_data->provider);
+       ast_free((char *)cb_data->subtype);
+       ast_free((char *)cb_data->message);
+       ast_free((char *)cb_data);
+
+       ast_db_del("CustomPresence", "TestPresenceStateChange");
+
+       return AST_TEST_PASS;
+}
+
+#endif
+
+static int unload_module(void)
+{
+       int res = 0;
+
+       res |= ast_custom_function_unregister(&presence_function);
+       res |= ast_presence_state_prov_del("CustomPresence");
+       res |= ast_cli_unregister_multiple(cli_funcpresencestate, ARRAY_LEN(cli_funcpresencestate));
+#ifdef TEST_FRAMEWORK
+       AST_TEST_UNREGISTER(test_valid_parse_data);
+       AST_TEST_UNREGISTER(test_invalid_parse_data);
+       AST_TEST_UNREGISTER(test_presence_state_change);
+#endif
+       return res;
+}
+
+static int load_module(void)
+{
+       int res = 0;
+       struct ast_db_entry *db_entry, *db_tree;
+
+       /* Populate the presence state cache on the system with all of the currently
+        * known custom presence states. */
+       db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
+       for (; db_entry; db_entry = db_entry->next) {
+               const char *dev_name = strrchr(db_entry->key, '/') + 1;
+               char state_info[1301];
+               enum ast_presence_state state;
+               char *message;
+               char *subtype;
+               char *options;
+               if (dev_name <= (const char *) 1) {
+                       continue;
+               }
+               ast_copy_string(state_info, db_entry->data, sizeof(state_info));
+               if (parse_data(state_info, &state, &subtype, &message, &options)) {
+                       ast_log(LOG_WARNING, "Invalid CustomPresence entry %s encountered\n", db_entry->data);
+                       continue;
+               }
+               ast_presence_state_changed(state, subtype, message, "CustomPresence:%s", dev_name);
+       }
+       ast_db_freetree(db_tree);
+       db_tree = NULL;
+
+       res |= ast_custom_function_register(&presence_function);
+       res |= ast_presence_state_prov_add("CustomPresence", custom_presence_callback);
+       res |= ast_cli_register_multiple(cli_funcpresencestate, ARRAY_LEN(cli_funcpresencestate));
+#ifdef TEST_FRAMEWORK
+       AST_TEST_REGISTER(test_valid_parse_data);
+       AST_TEST_REGISTER(test_invalid_parse_data);
+       AST_TEST_REGISTER(test_presence_state_change);
+#endif
+
+       return res;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Gets or sets a presence state in the dialplan",
+       .load = load_module,
+       .unload = unload_module,
+       .load_pri = AST_MODPRI_DEVSTATE_PROVIDER,
+);
+
index edf1c1c..d438790 100644 (file)
 #ifndef _ASTERISK_APP_H
 #define _ASTERISK_APP_H
 
+#include "asterisk/stringfields.h"
 #include "asterisk/strings.h"
 #include "asterisk/threadstorage.h"
+#include "asterisk/file.h"
 
 struct ast_flags64;
 
@@ -77,6 +79,27 @@ struct ast_ivr_menu {
        unsigned int flags;     /*!< Flags */
        struct ast_ivr_option *options; /*!< All options */
 };
+/*!
+ * \brief Structure used for ast_copy_recording_to_vm in order to cleanly supply
+ * data needed for making the recording from the recorded file.
+ */
+struct ast_vm_recording_data {
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(context);
+               AST_STRING_FIELD(mailbox);
+               AST_STRING_FIELD(folder);
+               AST_STRING_FIELD(recording_file);
+               AST_STRING_FIELD(recording_ext);
+
+               AST_STRING_FIELD(call_context);
+               AST_STRING_FIELD(call_macrocontext);
+               AST_STRING_FIELD(call_extension);
+               AST_STRING_FIELD(call_callerchan);
+               AST_STRING_FIELD(call_callerid);
+               );
+       int call_priority;
+};
 
 #define AST_IVR_FLAG_AUTORESTART (1 << 0)
 
@@ -219,11 +242,21 @@ void ast_install_vm_functions(int (*has_voicemail_func)(const char *mailbox, con
                              int (*inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsgs),
                              int (*inboxcount2_func)(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs),
                              int (*messagecount_func)(const char *context, const char *mailbox, const char *folder),
-                             int (*sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context));
+                             int (*sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context),
+                             int (*copy_recording_to_vm_func)(struct ast_vm_recording_data *vm_rec_data));
+
 
 void ast_uninstall_vm_functions(void);
 
 /*!
+ * \brief
+ * param[in] vm_rec_data Contains data needed to make the recording.
+ * retval 0 voicemail successfully created from recording.
+ * retval -1 Failure
+ */
+int ast_app_copy_recording_to_vm(struct ast_vm_recording_data *vm_rec_data);
+
+/*!
  * \brief Determine if a given mailbox has any voicemail
  * If folder is NULL, defaults to "INBOX".  If folder is "INBOX", includes the
  * number of messages in the "Urgent" folder.
@@ -339,6 +372,29 @@ int ast_linear_stream(struct ast_channel *chan, const char *filename, int fd, in
  */
 int ast_control_streamfile(struct ast_channel *chan, const char *file, const char *fwd, const char *rev, const char *stop, const char *pause, const char *restart, int skipms, long *offsetms);
 
+/*!
+ * \brief Stream a file with fast forward, pause, reverse, restart.
+ * \param chan
+ * \param file filename
+ * \param fwd, rev, stop, pause, restart, skipms, offsetms
+ * \param waitstream callback to invoke when fastforward or rewind occurrs.
+ *
+ * Before calling this function, set this to be the number
+ * of ms to start from the beginning of the file.  When the function
+ * returns, it will be the number of ms from the beginning where the
+ * playback stopped.  Pass NULL if you don't care.
+ */
+int ast_control_streamfile_w_cb(struct ast_channel *chan,
+       const char *file,
+       const char *fwd,
+       const char *rev,
+       const char *stop,
+       const char *pause,
+       const char *restart,
+       int skipms,
+       long *offsetms,
+       ast_waitstream_fr_cb cb);
+
 /*! \brief Play a stream and wait for a digit, returning the digit that was pressed */
 int ast_play_and_wait(struct ast_channel *chan, const char *fn);
 
diff --git a/include/asterisk/app_voicemail.h b/include/asterisk/app_voicemail.h
new file mode 100644 (file)
index 0000000..8a42bd7
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2011, Digium, Inc.
+ *
+ * David Vossel <dvossel@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ * \brief Voice Mail API
+ * \author David Vossel <dvossel@digium.com>
+ */
+
+#ifndef _ASTERISK_VM_H
+#define _ASTERISK_VM_H
+
+#include "asterisk/stringfields.h"
+#include "asterisk/linkedlists.h"
+
+#define AST_VM_FOLDER_NUMBER 12
+
+enum ast_vm_snapshot_sort_val {
+       AST_VM_SNAPSHOT_SORT_BY_ID = 0,
+       AST_VM_SNAPSHOT_SORT_BY_TIME,
+};
+
+struct ast_vm_msg_snapshot {
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(msg_id);
+               AST_STRING_FIELD(callerid);
+               AST_STRING_FIELD(callerchan);
+               AST_STRING_FIELD(exten);
+               AST_STRING_FIELD(origdate);
+               AST_STRING_FIELD(origtime);
+               AST_STRING_FIELD(duration);
+               AST_STRING_FIELD(folder_name);
+               AST_STRING_FIELD(flag);
+       );
+       unsigned int msg_number;
+
+       AST_LIST_ENTRY(ast_vm_msg_snapshot) msg;
+};
+
+struct ast_vm_mailbox_snapshot {
+       int total_msg_num;
+       AST_LIST_HEAD_NOLOCK(, ast_vm_msg_snapshot) snapshots[AST_VM_FOLDER_NUMBER];
+};
+
+/*
+ * \brief Create a snapshot of a mailbox which contains information about every msg.
+ *
+ * \param mailbox, the mailbox to look for
+ * \param context, the context to look for the mailbox in
+ * \param folder, OPTIONAL.  When not NULL only msgs from the specified folder will be included.
+ * \param desending, list the msgs in descending order rather than ascending order.
+ * \param combine_INBOX_and_OLD, When this argument is set, The OLD folder will be represented
+ *        in the INBOX folder of the snapshot. This allows the snapshot to represent the
+ *        OLD and INBOX messages in sorted order merged together.
+ *
+ * \retval snapshot on success
+ * \retval NULL on failure
+ */
+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);
+
+/*
+ * \brief destroy a snapshot
+ *
+ * \param mailbox_snapshot The snapshot to destroy.
+ * \retval NULL
+ */
+struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_destroy(struct ast_vm_mailbox_snapshot *mailbox_snapshot);
+
+/*!
+ * \brief Move messages from one folder to another
+ *
+ * \param mailbox The mailbox to which the folders belong
+ * \param context The voicemail context for the mailbox
+ * \param num_msgs The number of messages to move
+ * \param oldfolder The folder from where messages should be moved
+ * \param old_msg_nums The message IDs of the messages to move
+ * \param newfolder The folder to which messages should be moved
+ * \param new_msg_ids[out] An array of message IDs for the messages as they are in the
+ * new folder. This array must be num_msgs sized.
+ *
+ * \retval -1 Failure
+ * \retval 0 Success
+ */
+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);
+
+/*!
+ * \brief Remove/delete messages from a mailbox folder.
+ *
+ * \param mailbox The mailbox from which to delete messages
+ * \param context The voicemail context for the mailbox
+ * \param num_msgs The number of messages to delete
+ * \param folder The folder from which to remove messages
+ * \param msgs The message IDs of the messages to delete
+ * 
+ * \retval -1 Failure
+ * \retval 0 Success
+ */
+int ast_vm_msg_remove(const char *mailbox,
+       const char *context,
+       size_t num_msgs,
+       const char *folder,
+       const char *msgs []);
+
+/*!
+ * \brief forward a message from one mailbox to another.
+ *
+ * \brief from_mailbox The original mailbox the message is being forwarded from
+ * \brief from_context The voicemail context of the from_mailbox
+ * \brief from_folder The folder from which the message is being forwarded
+ * \brief to_mailbox The mailbox to forward the message to
+ * \brief to_context The voicemail context of the to_mailbox
+ * \brief to_folder The voicemail folder to forward the message to
+ * \brief num_msgs The number of messages being forwarded
+ * \brief msg_ids The message IDs of the messages in from_mailbox to forward
+ * \brief delete_old If non-zero, the forwarded messages are also deleted from from_mailbox.
+ * Otherwise, the messages will remain in the from_mailbox.
+ *
+ * \retval -1 Failure
+ * \retval 0 Success
+ */
+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);
+
+/*!
+ * \brief Voicemail playback callback function definition
+ *
+ * \param channel to play the file back on.
+ * \param location of file on disk
+ * \param duration of file in seconds. This will be zero if msg is very short or
+ * has an unknown duration.
+ */
+typedef void (ast_vm_msg_play_cb)(struct ast_channel *chan, const char *playfile, int duration);
+
+/*!
+ * \brief Play a voicemail msg back on a channel.
+ *
+ * \param mailbox msg is in.
+ * \param context of mailbox.
+ * \param voicemail folder to look in.
+ * \param message number in the voicemailbox to playback to the channel.
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+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);
+
+/*!
+ * \brief Get the name of a folder given its numeric index
+ *
+ * \param index The integer value of the mailbox.
+ * \retval "" Invalid index provided
+ * \retval other The name of the mailbox
+ */
+const char *ast_vm_index_to_foldername(unsigned int index);
+
+#ifdef TEST_FRAMEWORK
+/*!
+ * \brief Add a user to the voicemail system for test purposes
+ * \param context The context of the mailbox
+ * \param mailbox The mailbox for the user
+ * \retval 0 success
+ * \retval other failure
+ */
+int ast_vm_test_create_user(const char *context, const char *mailbox);
+
+/*!
+ * \brief Dispose of a user.  This should be used to destroy a user that was
+ * previously created using ast_vm_test_create_user
+ * \param context The context of the mailbox
+ * \param mailbox The mailbox for the user to destroy
+ */
+int ast_vm_test_destroy_user(const char *context, const char *mailbox);
+
+#endif
+
+#endif
index c047632..7c4905e 100644 (file)
@@ -400,6 +400,7 @@ enum AST_REDIRECTING_REASON {
        AST_REDIRECTING_REASON_OUT_OF_ORDER,
        AST_REDIRECTING_REASON_AWAY,
        AST_REDIRECTING_REASON_CALL_FWD_DTE,           /* This is something defined in Q.931, and no I don't know what it means */
+       AST_REDIRECTING_REASON_SEND_TO_VM,
 };
 
 /*!
index 5e71d80..b2ffcf5 100644 (file)
@@ -589,6 +589,63 @@ int ast_config_text_file_save(const char *filename, const struct ast_config *cfg
 int config_text_file_save(const char *filename, const struct ast_config *cfg, const char *generator) __attribute__((deprecated));
 
 struct ast_config *ast_config_internal_load(const char *configfile, struct ast_config *cfg, struct ast_flags flags, const char *suggested_incl_file, const char *who_asked);
+/*!
+ * \brief
+ * Copies the contents of one ast_config into another
+ *
+ * \note
+ * This creates a config on the heap. The caller of this must
+ * be prepared to free the memory returned.
+ *
+ * \param orig the config to copy
+ * \return The new config on success, NULL on failure.
+ */
+struct ast_config *ast_config_copy(const struct ast_config *orig);
+
+/*!
+ * \brief
+ * Flags that affect the behaviour of config hooks.
+ */
+enum config_hook_flags {
+       butt,
+};
+
+/*
+ * \brief Callback when configuration is updated
+ *
+ * \param cfg A copy of the configuration that is being changed.
+ *            This MUST be freed by the callback before returning.
+ */
+typedef int (*config_hook_cb)(struct ast_config *cfg);
+
+/*!
+ * \brief
+ * Register a config hook for a particular file and module
+ *
+ * \param name The name of the hook you are registering.
+ * \param filename The file whose config you wish to hook into.
+ * \param module The module that is reloading the config. This
+ *               can be useful if multiple modules may possibly
+ *               reload the same file, but you are only interested
+ *               when a specific module reloads the file
+ * \param flags Flags that affect the way hooks work.
+ * \param hook The callback to be called when config is loaded.
+ * return 0 Success
+ * return -1 Unsuccess, also known as UTTER AND COMPLETE FAILURE
+ */
+int ast_config_hook_register(const char *name,
+               const char *filename,
+               const char *module,
+               enum config_hook_flags flags,
+               config_hook_cb hook);
+
+/*!
+ * \brief
+ * Unregister a config hook
+ *
+ * \param name The name of the hook to unregister
+ */
+void ast_config_hook_unregister(const char *name);
 
 /*!
  * \brief Support code to parse config file arguments
index 4d18922..b2bf0e4 100644 (file)
@@ -54,8 +54,10 @@ enum ast_event_type {
        AST_EVENT_SECURITY            = 0x08,
        /*! Used by res_stun_monitor to alert listeners to an exernal network address change. */
        AST_EVENT_NETWORK_CHANGE      = 0x09,
+       /*! The presence state for a presence provider */
+       AST_EVENT_PRESENCE_STATE      = 0x0a,
        /*! Number of event types.  This should be the last event type + 1 */
-       AST_EVENT_TOTAL               = 0x0a,
+       AST_EVENT_TOTAL               = 0x0b,
 };
 
 /*! \brief Event Information Element types */
@@ -287,9 +289,13 @@ enum ast_event_ie_type {
        AST_EVENT_IE_RECEIVED_HASH       = 0x0036,
        AST_EVENT_IE_USING_PASSWORD      = 0x0037,
        AST_EVENT_IE_ATTEMPTED_TRANSPORT = 0x0038,
+       AST_EVENT_IE_PRESENCE_PROVIDER   = 0x0039,
+       AST_EVENT_IE_PRESENCE_STATE      = 0x003a,
+       AST_EVENT_IE_PRESENCE_SUBTYPE    = 0x003b,
+       AST_EVENT_IE_PRESENCE_MESSAGE    = 0x003c,
 
        /*! \brief Must be the last IE value +1 */
-       AST_EVENT_IE_TOTAL               = 0x0039,
+       AST_EVENT_IE_TOTAL               = 0x003d,
 };
 
 /*!
index e7817b3..ec2a38e 100644 (file)
@@ -49,7 +49,21 @@ struct ast_format;
 #define AST_DIGIT_ANYNUM "0123456789"
 
 #define SEEK_FORCECUR  10
-       
+
+/*! The type of event associated with a ast_waitstream_fr_cb invocation */
+enum ast_waitstream_fr_cb_values {
+       AST_WAITSTREAM_CB_REWIND = 1,
+       AST_WAITSTREAM_CB_FASTFORWARD,
+       AST_WAITSTREAM_CB_START
+};
+
+/*!
+ * \brief callback used during dtmf controlled file playback to indicate
+ * location of playback in a file after rewinding or fastfowarding
+ * a file.
+ */
+typedef void (ast_waitstream_fr_cb)(struct ast_channel *chan, long ms, enum ast_waitstream_fr_cb_values val);
+
 /*! 
  * \brief Streams a file 
  * \param c channel to stream the file to
@@ -162,6 +176,28 @@ int ast_waitstream_exten(struct ast_channel *c, const char *context);
  */
 int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *forward, const char *rewind, int ms);
 
+/*! 
+ * \brief Same as waitstream_fr but allows a callback to be alerted when a user
+ * fastforwards or rewinds the file.
+ * \param c channel to waitstream on
+ * \param breakon string of DTMF digits to break upon
+ * \param forward DTMF digit to fast forward upon
+ * \param rewind DTMF digit to rewind upon
+ * \param ms How many milliseconds to skip forward/back
+ * \param cb to call when rewind or fastfoward occurs. 
+ * Begins playback of a stream...
+ * Wait for a stream to stop or for any one of a given digit to arrive,  
+ * \retval 0 if the stream finishes.
+ * \retval the character if it was interrupted.
+ * \retval -1 on error.
+ */
+int ast_waitstream_fr_w_cb(struct ast_channel *c,
+       const char *breakon,
+       const char *forward,
+       const char *rewind,
+       int ms,
+       ast_waitstream_fr_cb cb);
+
 /*!
  * Same as waitstream, but with audio output to fd and monitored fd checking.  
  *
index fac8c2b..257e939 100644 (file)
@@ -86,6 +86,8 @@
 #define EVENT_FLAG_CC                  (1 << 15) /* Call Completion events */
 #define EVENT_FLAG_AOC                 (1 << 16) /* Advice Of Charge events */
 #define EVENT_FLAG_TEST                        (1 << 17) /* Test event used to signal the Asterisk Test Suite */
+/*XXX Why shifted by 30? XXX */
+#define EVENT_FLAG_MESSAGE             (1 << 30) /* MESSAGE events. */
 /*@} */
 
 /*! \brief Export manager structures */
index d989563..31ed0b2 100644 (file)
@@ -114,6 +114,11 @@ struct ast_msg *ast_msg_alloc(void);
 struct ast_msg *ast_msg_destroy(struct ast_msg *msg);
 
 /*!
+ * \brief Bump a msg's ref count
+ */
+struct ast_msg *ast_msg_ref(struct ast_msg *msg);
+
+/*!
  * \brief Set the 'to' URI of a message
  *
  * \retval 0 success
@@ -159,7 +164,7 @@ int __attribute__((format(printf, 2, 3)))
                ast_msg_set_exten(struct ast_msg *msg, const char *fmt, ...);
        
 /*!
- * \brief Set a variable on the message
+ * \brief Set a variable on the message going to the dialplan.
  * \note Setting a variable that already exists overwrites the existing variable value
  *
  * \param name Name of variable to set
@@ -171,6 +176,18 @@ int __attribute__((format(printf, 2, 3)))
 int ast_msg_set_var(struct ast_msg *msg, const char *name, const char *value);
 
 /*!
+ * \brief Set a variable on the message being sent to a message tech directly.
+ * \note Setting a variable that already exists overwrites the existing variable value
+ *
+ * \param name Name of variable to set
+ * \param value Value of variable to set
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_msg_set_var_outbound(struct ast_msg *msg, const char *name, const char *value);
+
+/*!
  * \brief Get the specified variable on the message
  * \note The return value is valid only as long as the ast_message is valid. Hold a reference
  *       to the message if you plan on storing the return value. Do re-set the same
@@ -201,6 +218,17 @@ const char *ast_msg_get_body(const struct ast_msg *msg);
 int ast_msg_queue(struct ast_msg *msg);
 
 /*!
+ * \brief Send a msg directly to an endpoint.
+ *
+ * Regardless of the return value of this function, this funciton will take
+ * care of ensuring that the message object is properly destroyed when needed.
+ *
+ * \retval 0 message successfully queued to be sent out
+ * \retval non-zero failure, message not get sent out.
+ */
+int ast_msg_send(struct ast_msg *msg, const char *to, const char *from);
+
+/*!
  * \brief Opaque iterator for msg variables
  */
 struct ast_msg_var_iterator;
index f7dc7b9..2305f39 100644 (file)
@@ -26,6 +26,7 @@
 #include "asterisk/channel.h"
 #include "asterisk/sched.h"
 #include "asterisk/devicestate.h"
+#include "asterisk/presencestate.h"
 #include "asterisk/chanvars.h"
 #include "asterisk/hashtab.h"
 #include "asterisk/stringfields.h"
@@ -74,9 +75,24 @@ struct ast_exten;
 struct ast_include;
 struct ast_ignorepat;
 struct ast_sw;
+enum ast_state_cb_update_reason {
+       /*! The extension state update is a result of a device state changing on the extension. */
+       AST_HINT_UPDATE_DEVICE = 1,
+       /*! The extension state update is a result of presence state changing on the extension. */
+       AST_HINT_UPDATE_PRESENCE = 2,
+};
+
+struct ast_state_cb_info {
+       enum ast_state_cb_update_reason reason;
+       enum ast_extension_states exten_state;
+       enum ast_presence_state presence_state;
+       const char *presence_subtype;
+       const char *presence_message;
+};
 
 /*! \brief Typedef for devicestate and hint callbacks */
-typedef int (*ast_state_cb_type)(const char *context, const char *exten, enum ast_extension_states state, void *data);
+typedef int (*ast_state_cb_type)(char *context, char *id, struct ast_state_cb_info *info, void *data);
 
 /*! \brief Typedef for devicestate and hint callback removal indication callback */
 typedef void (*ast_state_cb_destroy_type)(int id, void *data);
@@ -402,6 +418,22 @@ enum ast_extension_states ast_devstate_to_extenstate(enum ast_device_state devst
 int ast_extension_state(struct ast_channel *c, const char *context, const char *exten);
 
 /*!
+ * \brief Uses hint and presence state callback to get the presence state of an extension
+ *
+ * \param c this is not important
+ * \param context which context to look in
+ * \param exten which extension to get state
+ * \param[out] subtype Further information regarding the presence returned
+ * \param[out] message Custom message further describing current presence
+ *
+ * \note The subtype and message are dynamically allocated and must be freed by
+ * the caller of this function.
+ *
+ * \return returns the presence state value.
+ */
+int ast_hint_presence_state(struct ast_channel *c, const char *context, const char *exten, char **subtype, char **message);
+
+/*!
  * \brief Return string representation of the state of an extension
  *
  * \param extension_state is the numerical state delivered by ast_extension_state
diff --git a/include/asterisk/presencestate.h b/include/asterisk/presencestate.h
new file mode 100644 (file)
index 0000000..dbbe5dc
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2011, Digium, Inc.
+ *
+ * David Vossel <dvossel@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ * \brief Presence state management
+ */
+
+#ifndef _ASTERISK_PRESSTATE_H
+#define _ASTERISK_PRESSTATE_H
+
+enum ast_presence_state {
+       AST_PRESENCE_NOT_SET = 0,
+       AST_PRESENCE_UNAVAILABLE,
+       AST_PRESENCE_AVAILABLE,
+       AST_PRESENCE_AWAY,
+       AST_PRESENCE_XA,
+       AST_PRESENCE_CHAT,
+       AST_PRESENCE_DND,
+       /* This is not something that a user can
+        * set his presence to. Rather, this is returned
+        * to indicate that presence is in some invalid
+        * state
+        */
+       AST_PRESENCE_INVALID,
+};
+
+/*! \brief Presence state provider call back */
+typedef enum ast_presence_state (*ast_presence_state_prov_cb_type)(const char *data, char **subtype, char **message);
+
+/*!
+ * \brief Convert presence state to text string for output
+ *
+ * \param state Current presence state
+ */
+const char *ast_presence_state2str(enum ast_presence_state state);
+
+/*!
+ * \brief Convert presence state from text to integer value
+ *
+ * \param val The text representing the presence state.  Valid values are anything
+ *        that comes after AST_PRESENCE_ in one of the defined values.
+ *
+ * \return The AST_PRESENCE_ integer value
+ */
+enum ast_presence_state ast_presence_state_val(const char *val);
+
+/*!
+ * \brief Asks a presence state provider for the current presence state.
+ *
+ * \param presence_provider, The presence provider to retrieve the state from.
+ * \param subtype, The output paramenter to store the subtype string in. Must be freed if returned
+ * \param message, The output paramenter to store the message string in. Must be freed if returned
+ *
+ * \retval presence state value on success,
+ * \retval -1 on failure.
+ */
+enum ast_presence_state ast_presence_state(const char *presence_provider, char **subtype, char **message);
+
+/*!
+ * \brief Asks a presence state provider for the current presence state, bypassing the event cache
+ *
+ * \details Some presence state providers may perform transformations on presence data when it is
+ * requested (such as a base64 decode). In such instances, use of the event cache is not suitable
+ * and should be bypassed.
+ *
+ * \param presence_provider, The presence provider to retrieve the state from.
+ * \param subtype, The output paramenter to store the subtype string in. Must be freed if returned
+ * \param message, The output paramenter to store the message string in. Must be freed if returned
+ *
+ * \retval presence state value on success,
+ * \retval -1 on failure.
+ */
+enum ast_presence_state ast_presence_state_nocache(const char *presence_provider, char **subtype, char **message);
+
+/*!
+ * \brief Notify the world that a presence provider state changed.
+ *
+ * \param state the new presence state
+ * \param subtype the new presence subtype
+ * \param message the new presence message
+ * \param fmt Presence entity whose state has changed
+ *
+ * The new state of the entity will be sent off to any subscribers
+ * of the presence state. It will also be stored in the internal event
+ * cache.
+ *
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+int ast_presence_state_changed(enum ast_presence_state state,
+               const char *subtype,
+               const char *message,
+               const char *fmt, ...)
+       __attribute__((format(printf, 4, 5)));
+
+/*!
+ * \brief Notify the world that a presence provider state changed.
+ *
+ * \param state the new presence state
+ * \param subtype the new presence subtype
+ * \param message the new presence message
+ * \param presence_provider Presence entity whose state has changed
+ *
+ * The new state of the entity will be sent off to any subscribers
+ * of the presence state. It will also be stored in the internal event
+ * cache.
+ *
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+int ast_presence_state_changed_literal(enum ast_presence_state state,
+               const char *subtype,
+               const char *message,
+               const char *presence_provider);
+
+/*!
+ * \brief Add presence state provider
+ *
+ * \param label to use in hint, like label:object
+ * \param callback Callback
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_presence_state_prov_add(const char *label, ast_presence_state_prov_cb_type callback);
+
+/*!
+ * \brief Remove presence state provider
+ *
+ * \param label to use in hint, like label:object
+ *
+ * \retval -1 on failure
+ * \retval 0 on success
+ */
+int ast_presence_state_prov_del(const char *label);
+
+int ast_presence_state_engine_init(void);
+#endif
+
diff --git a/include/asterisk/sip_api.h b/include/asterisk/sip_api.h
new file mode 100644 (file)
index 0000000..018785a
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef __ASTERISK_SIP_H
+#define __ASTERISK_SIP_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+#include "asterisk/optional_api.h"
+#include "asterisk/config.h"
+
+/*!
+ * \brief Send a customized SIP INFO request
+ *
+ * \param headers The headers to add to the INFO request
+ * \param content_type The content type header to add
+ * \param conten The body of the INFO request
+ * \param useragent_filter If non-NULL, only send the INFO if the
+ * recipient's User-Agent contains useragent_filter as a substring
+ *
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+int ast_sipinfo_send(struct ast_channel *chan,
+               struct ast_variable *headers,
+               const char *content_type,
+               const char *content,
+               const char *useragent_filter);
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* __ASTERISK_SIP_H */
index 3d2fa52..31484a1 100644 (file)
@@ -423,18 +423,21 @@ static int (*ast_inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsg
 static int (*ast_inboxcount2_func)(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs) = NULL;
 static int (*ast_sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context) = NULL;
 static int (*ast_messagecount_func)(const char *context, const char *mailbox, const char *folder) = NULL;
+static int (*ast_copy_recording_to_vm_func)(struct ast_vm_recording_data *vm_rec_data) = NULL;
 
 void ast_install_vm_functions(int (*has_voicemail_func)(const char *mailbox, const char *folder),
                              int (*inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsgs),
                              int (*inboxcount2_func)(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs),
                              int (*messagecount_func)(const char *context, const char *mailbox, const char *folder),
-                             int (*sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context))
+                             int (*sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context),
+                             int (*copy_recording_to_vm_func)(struct ast_vm_recording_data *vm_rec_data))
 {
        ast_has_voicemail_func = has_voicemail_func;
        ast_inboxcount_func = inboxcount_func;
        ast_inboxcount2_func = inboxcount2_func;
        ast_messagecount_func = messagecount_func;
        ast_sayname_func = sayname_func;
+       ast_copy_recording_to_vm_func = copy_recording_to_vm_func;
 }
 
 void ast_uninstall_vm_functions(void)
@@ -444,6 +447,7 @@ void ast_uninstall_vm_functions(void)
        ast_inboxcount2_func = NULL;
        ast_messagecount_func = NULL;
        ast_sayname_func = NULL;
+       ast_copy_recording_to_vm_func = NULL;
 }
 
 int ast_app_has_voicemail(const char *mailbox, const char *folder)
@@ -459,6 +463,28 @@ int ast_app_has_voicemail(const char *mailbox, const char *folder)
        return 0;
 }
 
+/*!
+ * \internal
+ * \brief Function used as a callback for ast_copy_recording_to_vm when a real one isn't installed.
+ * \param vm_rec_data Stores crucial information about the voicemail that will basically just be used
+ * to figure out what the name of the recipient was supposed to be
+ */
+int ast_app_copy_recording_to_vm(struct ast_vm_recording_data *vm_rec_data)
+{
+       static int warned = 0;
+
+       if (ast_copy_recording_to_vm_func) {
+               return ast_copy_recording_to_vm_func(vm_rec_data);
+       }
+
+       if (warned++ % 10 == 0) {
+               ast_verb(3, "copy recording to voicemail called to copy %s.%s to %s@%s, but voicemail not loaded.\n",
+                       vm_rec_data->recording_file, vm_rec_data->recording_ext,
+                       vm_rec_data->mailbox, vm_rec_data->context);
+       }
+
+       return -1;
+}
 
 int ast_app_inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
 {
@@ -709,10 +735,16 @@ int ast_linear_stream(struct ast_channel *chan, const char *filename, int fd, in
        return res;
 }
 
-int ast_control_streamfile(struct ast_channel *chan, const char *file,
-                          const char *fwd, const char *rev,
-                          const char *stop, const char *suspend,
-                          const char *restart, int skipms, long *offsetms)
+static int control_streamfile(struct ast_channel *chan,
+       const char *file,
+       const char *fwd,
+       const char *rev,
+       const char *stop,
+       const char *suspend,
+       const char *restart,
+       int skipms,
+       long *offsetms,
+       ast_waitstream_fr_cb cb)
 {
        char *breaks = NULL;
        char *end = NULL;
@@ -784,7 +816,11 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file,
                                ast_seekstream(ast_channel_stream(chan), offset, SEEK_SET);
                                offset = 0;
                        }
-                       res = ast_waitstream_fr(chan, breaks, fwd, rev, skipms);
+                       if (cb) {
+                               res = ast_waitstream_fr_w_cb(chan, breaks, fwd, rev, skipms, cb);
+                       } else {
+                               res = ast_waitstream_fr(chan, breaks, fwd, rev, skipms);
+                       }
                }
 
                if (res < 1) {
@@ -848,6 +884,28 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file,
        return res;
 }
 
+int ast_control_streamfile_w_cb(struct ast_channel *chan,
+       const char *file,
+       const char *fwd,
+       const char *rev,
+       const char *stop,
+       const char *suspend,
+       const char *restart,
+       int skipms,
+       long *offsetms,
+       ast_waitstream_fr_cb cb)
+{
+       return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, cb);
+}
+
+int ast_control_streamfile(struct ast_channel *chan, const char *file,
+                          const char *fwd, const char *rev,
+                          const char *stop, const char *suspend,
+                          const char *restart, int skipms, long *offsetms)
+{
+       return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, NULL);
+}
+
 int ast_play_and_wait(struct ast_channel *chan, const char *fn)
 {
        int d = 0;
index 849cbdd..6122028 100644 (file)
@@ -136,6 +136,7 @@ int daemon(int, int);  /* defined in libresolv of all places */
 #include "asterisk/ast_version.h"
 #include "asterisk/linkedlists.h"
 #include "asterisk/devicestate.h"
+#include "asterisk/presencestate.h"
 #include "asterisk/module.h"
 #include "asterisk/dsp.h"
 #include "asterisk/buildinfo.h"
@@ -4028,6 +4029,11 @@ int main(int argc, char *argv[])
                exit(1);
        }
 
+       if (ast_presence_state_engine_init()) {
+               printf("%s", term_quit());
+               exit(1);
+       }
+
        ast_dsp_init();
        ast_udptl_init();
 
index dc3a910..37edd99 100644 (file)
@@ -1203,6 +1203,7 @@ static const struct ast_value_translation redirecting_reason_types[] = {
        { AST_REDIRECTING_REASON_OUT_OF_ORDER,   "out_of_order", "Called DTE Out-Of-Order" },
        { AST_REDIRECTING_REASON_AWAY,           "away",         "Callee is Away" },
        { AST_REDIRECTING_REASON_CALL_FWD_DTE,   "cf_dte",       "Call Forwarding By The Called DTE" },
+       { AST_REDIRECTING_REASON_SEND_TO_VM,     "send_to_vm",   "Call is being redirected to user's voicemail"},
 /* *INDENT-ON* */
 };
 
index 8bd9735..320947d 100644 (file)
@@ -2514,6 +2514,7 @@ void ast_channel_clear_softhangup(struct ast_channel *chan, int flag)
 int ast_softhangup_nolock(struct ast_channel *chan, int cause)
 {
        ast_debug(1, "Soft-Hanging up channel '%s'\n", ast_channel_name(chan));
+       ast_backtrace();
        /* Inform channel driver that we need to be hung up, if it cares */
        ast_channel_softhangup_internal_flag_add(chan, cause);
        ast_queue_frame(chan, &ast_null_frame);
index 9d9eefc..127d89b 100644 (file)
@@ -64,6 +64,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 static char *extconfig_conf = "extconfig.conf";
 
+static struct ao2_container *cfg_hooks;
+static void config_hook_exec(const char *filename, const char *module, struct ast_config *cfg);
 
 /*! \brief Structure to keep comments for rewriting configuration files */
 struct ast_comment {
@@ -2278,6 +2280,39 @@ static struct ast_config_engine text_file_engine = {
        .load_func = config_text_file_load,
 };
 
+struct ast_config *ast_config_copy(const struct ast_config *old)
+{
+       struct ast_config *new_config = ast_config_new();
+       struct ast_category *cat_iter;
+
+       if (!new_config) {
+               return NULL;
+       }
+
+       for (cat_iter = old->root; cat_iter; cat_iter = cat_iter->next) {
+               struct ast_category *new_cat =
+                       ast_category_new(cat_iter->name, cat_iter->file, cat_iter->lineno);
+               if (!new_cat) {
+                       goto fail;
+               }
+               ast_category_append(new_config, new_cat);
+               if (cat_iter->root) {
+                       new_cat->root = ast_variables_dup(cat_iter->root);
+                       if (!new_cat->root) {
+                               goto fail;
+                       }
+                       new_cat->last = cat_iter->last;
+               }
+       }
+
+       return new_config;
+
+fail:
+       ast_config_destroy(new_config);
+       return NULL;
+}
+
+
 struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, struct ast_flags flags, const char *suggested_include_file, const char *who_asked)
 {
        char db[256];
@@ -2310,10 +2345,12 @@ struct ast_config *ast_config_internal_load(const char *filename, struct ast_con
 
        result = loader->load_func(db, table, filename, cfg, flags, suggested_include_file, who_asked);
 
-       if (result && result != CONFIG_STATUS_FILEINVALID && result != CONFIG_STATUS_FILEUNCHANGED)
+       if (result && result != CONFIG_STATUS_FILEINVALID && result != CONFIG_STATUS_FILEUNCHANGED) {
                result->include_level--;
-       else if (result != CONFIG_STATUS_FILEINVALID)
+               config_hook_exec(filename, who_asked, result);
+       } else if (result != CONFIG_STATUS_FILEINVALID) {
                cfg->include_level--;
+       }
 
        return result;
 }
@@ -2968,3 +3005,85 @@ int register_config_cli(void)
        ast_cli_register_multiple(cli_config, ARRAY_LEN(cli_config));
        return 0;
 }
+
+struct cfg_hook {
+       const char *name;
+       const char *filename;
+       const char *module;
+       config_hook_cb hook_cb;
+};
+
+static void hook_destroy(void *obj)
+{
+       struct cfg_hook *hook = obj;
+       ast_free((void *) hook->name);
+       ast_free((void *) hook->filename);
+       ast_free((void *) hook->module);
+}
+
+static int hook_cmp(void *obj, void *arg, int flags)
+{
+       struct cfg_hook *hook1 = obj;
+       struct cfg_hook *hook2 = arg;
+
+       return !(strcasecmp(hook1->name, hook2->name)) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static int hook_hash(const void *obj, const int flags)
+{
+       const struct cfg_hook *hook = obj;
+
+       return ast_str_hash(hook->name);
+}
+
+void ast_config_hook_unregister(const char *name)
+{
+       struct cfg_hook tmp;
+
+       tmp.name = ast_strdupa(name);
+
+       ao2_find(cfg_hooks, &tmp, OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
+}
+
+static void config_hook_exec(const char *filename, const char *module, struct ast_config *cfg)
+{
+       struct ao2_iterator it;
+       struct cfg_hook *hook;
+       if (!(cfg_hooks)) {
+               return;
+       }
+       it = ao2_iterator_init(cfg_hooks, 0);
+       while ((hook = ao2_iterator_next(&it))) {
+               if (!strcasecmp(hook->filename, filename) &&
+                               !strcasecmp(hook->module, module)) {
+                       struct ast_config *copy = ast_config_copy(cfg);
+                       hook->hook_cb(copy);
+               }
+               ao2_ref(hook, -1);
+       }
+       ao2_iterator_destroy(&it);
+}
+
+int ast_config_hook_register(const char *name,
+               const char *filename,
+               const char *module,
+               enum config_hook_flags flags,
+               config_hook_cb hook_cb)
+{
+       struct cfg_hook *hook;
+       if (!cfg_hooks && !(cfg_hooks = ao2_container_alloc(17, hook_hash, hook_cmp))) {
+               return -1;
+       }
+
+       if (!(hook = ao2_alloc(sizeof(*hook), hook_destroy))) {
+               return -1;
+       }
+
+       hook->hook_cb = hook_cb;
+       hook->filename = ast_strdup(filename);
+       hook->name = ast_strdup(name);
+       hook->module = ast_strdup(module);
+
+       ao2_link(cfg_hooks, hook);
+       return 0;
+}
index 9ecf710..d79b07a 100644 (file)
@@ -137,6 +137,7 @@ static int ast_event_cmp(void *obj, void *arg, int flags);
 static int ast_event_hash_mwi(const void *obj, const int flags);
 static int ast_event_hash_devstate(const void *obj, const int flags);
 static int ast_event_hash_devstate_change(const void *obj, const int flags);
+static int ast_event_hash_presence_state_change(const void *obj, const int flags);
 
 #ifdef LOW_MEMORY
 #define NUM_CACHE_BUCKETS 17
@@ -181,6 +182,11 @@ static struct {
                .hash_fn = ast_event_hash_devstate_change,
                .cache_args = { AST_EVENT_IE_DEVICE, AST_EVENT_IE_EID, },
        },
+       [AST_EVENT_PRESENCE_STATE] = {
+               .hash_fn = ast_event_hash_presence_state_change,
+               .cache_args = { AST_EVENT_IE_PRESENCE_STATE, },
+       },
+
 };
 
 /*!
@@ -1587,6 +1593,22 @@ static int ast_event_hash_devstate_change(const void *obj, const int flags)
        return ast_str_hash(ast_event_get_ie_str(event, AST_EVENT_IE_DEVICE));
 }
 
+/*!
+ * \internal
+ * \brief Hash function for AST_EVENT_PRESENCE_STATE
+ *
+ * \param[in] obj an ast_event
+ * \param[in] flags unused
+ *
+ * \return hash value
+ */
+static int ast_event_hash_presence_state_change(const void *obj, const int flags)
+{
+       const struct ast_event *event = obj;
+
+       return ast_str_hash(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER));
+}
+
 static int ast_event_hash(const void *obj, const int flags)
 {
        const struct ast_event_ref *event_ref;
index 8b69d13..04bc532 100644 (file)
@@ -409,6 +409,17 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <para>Bridge together two channels already in the PBX.</para>
                </description>
        </manager>
+       <manager name="Parkinglots" language="en_US">
+               <synopsis>
+                       Get a list of parking lots
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+               </syntax>
+               <description>
+                       <para>List all parking lots as a series of AMI events</para>
+               </description>
+       </manager>
        <function name="FEATURE" language="en_US">
                <synopsis>
                        Get or set a feature option on a channel.
@@ -7347,7 +7358,42 @@ static struct ast_cli_entry cli_features[] = {
        AST_CLI_DEFINE(handle_parkedcalls, "List currently parked calls"),
 };
 
-/*!
+static int manager_parkinglot_list(struct mansession *s, const struct message *m)
+{
+       const char *id = astman_get_header(m, "ActionID");
+       char idText[256] = "";
+       struct ao2_iterator iter;
+       struct ast_parkinglot *curlot;
+
+       if (!ast_strlen_zero(id))
+               snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
+
+       astman_send_ack(s, m, "Parking lots will follow");
+
+       iter = ao2_iterator_init(parkinglots, 0);
+       while ((curlot = ao2_iterator_next(&iter))) {
+               astman_append(s, "Event: Parkinglot\r\n"
+                       "Name: %s\r\n"
+                       "StartExten: %d\r\n"
+                       "StopExten: %d\r\n"
+                       "Timeout: %d\r\n"
+                       "\r\n",
+                       curlot->name,
+                       curlot->cfg.parking_start,
+                       curlot->cfg.parking_stop,
+                       curlot->cfg.parkingtime ? curlot->cfg.parkingtime / 1000 : curlot->cfg.parkingtime);
+               ao2_ref(curlot, -1);
+       }
+
+       astman_append(s,
+               "Event: ParkinglotsComplete\r\n"
+               "%s"
+               "\r\n",idText);
+
+       return RESULT_SUCCESS;
+}
+
+/*! 
  * \brief Dump parking lot status
  * \param s
  * \param m
@@ -7363,6 +7409,7 @@ static int manager_parking_status(struct mansession *s, const struct message *m)
        struct ao2_iterator iter;
        struct ast_parkinglot *curlot;
        int numparked = 0;
+       long now = time(NULL);
 
        if (!ast_strlen_zero(id))
                snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
@@ -7379,6 +7426,7 @@ static int manager_parking_status(struct mansession *s, const struct message *m)
                                "Channel: %s\r\n"
                                "From: %s\r\n"
                                "Timeout: %ld\r\n"
+                               "Duration: %ld\r\n"
                                "CallerIDNum: %s\r\n"
                                "CallerIDName: %s\r\n"
                                "ConnectedLineNum: %s\r\n"
@@ -7387,7 +7435,8 @@ static int manager_parking_status(struct mansession *s, const struct message *m)
                                "\r\n",
                                curlot->name,
                                cur->parkingnum, ast_channel_name(cur->chan), cur->peername,
-                               (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - (long) time(NULL),
+                               (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - now,
+                               now - (long) cur->start.tv_sec,
                                S_COR(ast_channel_caller(cur->chan)->id.number.valid, ast_channel_caller(cur->chan)->id.number.str, ""),        /* XXX in other places it is <unknown> */
                                S_COR(ast_channel_caller(cur->chan)->id.name.valid, ast_channel_caller(cur->chan)->id.name.str, ""),
                                S_COR(ast_channel_connected(cur->chan)->id.number.valid, ast_channel_connected(cur->chan)->id.number.str, ""),  /* XXX in other places it is <unknown> */
@@ -8669,6 +8718,7 @@ int ast_features_init(void)
                res = ast_register_application2(parkcall, park_call_exec, NULL, NULL, NULL);
        if (!res) {
                ast_manager_register_xml_core("ParkedCalls", 0, manager_parking_status);
+               ast_manager_register_xml_core("Parkinglots", 0, manager_parkinglot_list);
                ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park);
                ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge);
        }
index 52f5af5..076b31d 100644 (file)
@@ -1237,11 +1237,18 @@ struct ast_filestream *ast_writefile(const char *filename, const char *type, con
 /*!
  * \brief the core of all waitstream() functions
  */
-static int waitstream_core(struct ast_channel *c, const char *breakon,
-       const char *forward, const char *reverse, int skip_ms,
-       int audiofd, int cmdfd,  const char *context)
+static int waitstream_core(struct ast_channel *c,
+       const char *breakon,
+       const char *forward,
+       const char *reverse,
+       int skip_ms,
+       int audiofd,
+       int cmdfd,
+       const char *context,
+       ast_waitstream_fr_cb cb)
 {
        const char *orig_chan_name = NULL;
+
        int err = 0;
 
        if (!breakon)
@@ -1257,6 +1264,11 @@ static int waitstream_core(struct ast_channel *c, const char *breakon,
        if (ast_test_flag(ast_channel_flags(c), AST_FLAG_MASQ_NOSTREAM))
                orig_chan_name = ast_strdupa(ast_channel_name(c));
 
+       if (ast_channel_stream(c) && cb) {
+               long ms_len = ast_tellstream(ast_channel_stream(c)) / (ast_format_rate(&ast_channel_stream(c)->fmt->format) / 1000);
+               cb(c, ms_len, AST_WAITSTREAM_CB_START);
+       }
+
        while (ast_channel_stream(c)) {
                int res;
                int ms;
@@ -1318,6 +1330,7 @@ static int waitstream_core(struct ast_channel *c, const char *breakon,
                                                return res;
                                        }
                                } else {
+                                       enum ast_waitstream_fr_cb_values cb_val = 0;
                                        res = fr->subclass.integer;
                                        if (strchr(forward, res)) {
                                                int eoftest;
@@ -1328,13 +1341,19 @@ static int waitstream_core(struct ast_channel *c, const char *breakon,
                                                } else {
                                                        ungetc(eoftest, ast_channel_stream(c)->f);
                                                }
+                                               cb_val = AST_WAITSTREAM_CB_FASTFORWARD;
                                        } else if (strchr(reverse, res)) {
                                                ast_stream_rewind(ast_channel_stream(c), skip_ms);
+                                               cb_val = AST_WAITSTREAM_CB_REWIND;
                                        } else if (strchr(breakon, res)) {
                                                ast_frfree(fr);
                                                ast_clear_flag(ast_channel_flags(c), AST_FLAG_END_DTMF_ONLY);
                                                return res;
                                        }
+                                       if (cb_val && cb) {
+                                               long ms_len = ast_tellstream(ast_channel_stream(c)) / (ast_format_rate(&ast_channel_stream(c)->fmt->format) / 1000);
+                                               cb(c, ms_len, cb_val);
+                                       }
                                }
                                break;
                        case AST_FRAME_CONTROL:
@@ -1385,21 +1404,32 @@ static int waitstream_core(struct ast_channel *c, const char *breakon,
        return (err || ast_channel_softhangup_internal_flag(c)) ? -1 : 0;
 }
 
+int ast_waitstream_fr_w_cb(struct ast_channel *c,
+       const char *breakon,
+       const char *forward,
+       const char *reverse,
+       int ms,
+       ast_waitstream_fr_cb cb)
+{
+       return waitstream_core(c, breakon, forward, reverse, ms,
+               -1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */, cb);
+}
+
 int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *forward, const char *reverse, int ms)
 {
        return waitstream_core(c, breakon, forward, reverse, ms,
-               -1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */);
+               -1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */, NULL /* no callback */);
 }
 
 int ast_waitstream(struct ast_channel *c, const char *breakon)
 {
-       return waitstream_core(c, breakon, NULL, NULL, 0, -1, -1, NULL);
+       return waitstream_core(c, breakon, NULL, NULL, 0, -1, -1, NULL, NULL /* no callback */);
 }
 
 int ast_waitstream_full(struct ast_channel *c, const char *breakon, int audiofd, int cmdfd)
 {
        return waitstream_core(c, breakon, NULL, NULL, 0,
-               audiofd, cmdfd, NULL /* no context */);
+               audiofd, cmdfd, NULL /* no context */, NULL /* no callback */);
 }
 
 int ast_waitstream_exten(struct ast_channel *c, const char *context)
@@ -1410,7 +1440,7 @@ int ast_waitstream_exten(struct ast_channel *c, const char *context)
        if (!context)
                context = ast_channel_context(c);
        return waitstream_core(c, NULL, NULL, NULL, 0,
-               -1, -1, context);
+               -1, -1, context, NULL /* no callback */);
 }
 
 /*
index 2861c6d..4bf859e 100644 (file)
@@ -82,6 +82,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/security_events.h"
 #include "asterisk/aoc.h"
 #include "asterisk/stringfields.h"
+#include "asterisk/presencestate.h"
 
 /*** DOCUMENTATION
        <manager name="Ping" language="en_US">
@@ -510,6 +511,22 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        the hint for the extension and the status.</para>
                </description>
        </manager>
+       <manager name="PresenceState" language="en_US">
+               <synopsis>
+                       Check Presence State
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                       <parameter name="Provider" required="true">
+                               <para>Presence Provider to check the state of</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Report the presence state for the given presence provider.</para>
+                       <para>Will return a <literal>Presence State</literal> message. The response will include the
+                       presence state and, if set, a presence subtype and custom message.</para>
+               </description>
+       </manager>
        <manager name="AbsoluteTimeout" language="en_US">
                <synopsis>
                        Set absolute timeout.
@@ -1218,6 +1235,7 @@ static const struct permalias {
        { EVENT_FLAG_CC, "cc" },
        { EVENT_FLAG_AOC, "aoc" },
        { EVENT_FLAG_TEST, "test" },
+       { EVENT_FLAG_MESSAGE, "message" },
        { INT_MAX, "all" },
        { 0, "none" },
 };
@@ -3211,6 +3229,7 @@ static int action_hangup(struct mansession *s, const struct message *m)
 
        if (name_or_regex[0] != '/') {
                if (!(c = ast_channel_get_by_name(name_or_regex))) {
+                       ast_log(LOG_NOTICE, "!!!!!!!!!! Can't find channel to hang up!\n");
                        astman_send_error(s, m, "No such channel");
                        return 0;
                }
@@ -4384,6 +4403,43 @@ static int action_extensionstate(struct mansession *s, const struct message *m)
        return 0;
 }
 
+static int action_presencestate(struct mansession *s, const struct message *m)
+{
+       const char *provider = astman_get_header(m, "Provider");
+       enum ast_presence_state state;
+       char *subtype;
+       char *message;
+       char subtype_header[256] = "";
+       char message_header[256] = "";
+
+       if (ast_strlen_zero(provider)) {
+               astman_send_error(s, m, "No provider specified");
+               return 0;
+       }
+
+       state = ast_presence_state(provider, &subtype, &message);
+
+       if (!ast_strlen_zero(subtype)) {
+               snprintf(subtype_header, sizeof(subtype_header),
+                               "Subtype: %s\r\n", subtype);
+       }
+
+       if (!ast_strlen_zero(message)) {
+               snprintf(message_header, sizeof(message_header),
+                               "Message: %s\r\n", message);
+       }
+
+       astman_append(s, "Message: Presence State\r\n"
+                       "State: %s\r\n"
+                       "%s"
+                       "%s"
+                       "\r\n",
+                       ast_presence_state2str(state),
+                       subtype_header,
+                       message_header);
+       return 0;
+}
+
 static int action_timeout(struct mansession *s, const struct message *m)
 {
        struct ast_channel *c;
@@ -5485,10 +5541,17 @@ int ast_manager_unregister(const char *action)
        return 0;
 }
 
-static int manager_state_cb(const char *context, const char *exten, enum ast_extension_states state, void *data)
+static int manager_state_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data)
 {
        /* Notify managers of change */
        char hint[512];
+       int state = info->exten_state;
+
+       /* only interested in device state for this right now */
+       if (info->reason !=  AST_HINT_UPDATE_DEVICE) {
+               return 0;
+       }
+
        ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten);
 
        manager_event(EVENT_FLAG_CALL, "ExtensionStatus", "Exten: %s\r\nContext: %s\r\nHint: %s\r\nStatus: %d\r\n", exten, context, hint, state);
@@ -6850,6 +6913,7 @@ static int __init_manager(int reload)
                ast_manager_register_xml_core("Originate", EVENT_FLAG_ORIGINATE, action_originate);
                ast_manager_register_xml_core("Command", EVENT_FLAG_COMMAND, action_command);
                ast_manager_register_xml_core("ExtensionState", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_extensionstate);
+               ast_manager_register_xml_core("PresenceState", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_presencestate);
                ast_manager_register_xml_core("AbsoluteTimeout", EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL, action_timeout);
                ast_manager_register_xml_core("MailboxStatus", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_mailboxstatus);
                ast_manager_register_xml_core("MailboxCount", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_mailboxcount);
index 5722969..e26b8c3 100644 (file)
@@ -32,6 +32,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/module.h"
 #include "asterisk/datastore.h"
 #include "asterisk/pbx.h"
+#include "asterisk/manager.h"
 #include "asterisk/strings.h"
 #include "asterisk/astobj2.h"
 #include "asterisk/app.h"
@@ -56,6 +57,18 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        <para>Read-only.  The source of the message.  When processing an
                                        incoming message, this will be set to the source of the message.</para>
                                </enum>
+                               <enum name="custom_data">
+                                       <para>Write-only.  Mark or unmark all message headers for an outgoing
+                                       message.  The following values can be set:</para>
+                                       <enumlist>
+                                               <enum name="mark_all_outbound">
+                                                       <para>Mark all headers for an outgoing message.</para>
+                                               </enum>
+                                               <enum name="clear_all_outbound">
+                                                       <para>Unmark all headers for an outgoing message.</para>
+                                               </enum>
+                                       </enumlist>
+                               </enum>
                                <enum name="body">
                                        <para>Read/Write.  The message body.  When processing an incoming
                                        message, this includes the body of the message that Asterisk
@@ -139,6 +152,39 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        </variablelist>
                </description>
        </application>
+       <manager name="MessageSend" language="en_US">
+               <synopsis>
+                       Send an out of call message to an endpoint.
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                       <parameter name="To" required="true">
+                               <para>The URI the message is to be sent to.</para>
+                       </parameter>
+                       <parameter name="From">
+                               <para>A From URI for the message if needed for the
+                               message technology being used to send this message.</para>
+                               <note>
+                                       <para>For SIP the from parameter can be a configured peer name
+                                       or in the form of "display-name" &lt;URI&gt;.</para>
+                               </note>
+                       </parameter>
+                       <parameter name="Body">
+                               <para>The message body text.  This must not contain any newlines as that
+                               conflicts with the AMI protocol.</para>
+                       </parameter>
+                       <parameter name="Base64Body">
+                               <para>Text bodies requiring the use of newlines have to be base64 encoded
+                               in this field.  Base64Body will be decoded before being sent out.
+                               Base64Body takes precedence over Body.</para>
+                       </parameter>
+                       <parameter name="Variable">
+                               <para>Message variable to set, multiple Variable: headers are
+                               allowed.  The header value is a comma separated list of
+                               name=value pairs.</para>
+                       </parameter>
+               </syntax>
+       </manager>
  ***/
 
 struct msg_data {
@@ -393,6 +439,12 @@ struct ast_msg *ast_msg_alloc(void)
        return msg;
 }
 
+struct ast_msg *ast_msg_ref(struct ast_msg *msg)
+{
+       ao2_ref(msg, 1);
+       return msg;
+}
+
 struct ast_msg *ast_msg_destroy(struct ast_msg *msg)
 {
        ao2_ref(msg, -1);
@@ -519,7 +571,7 @@ static int msg_set_var_full(struct ast_msg *msg, const char *name, const char *v
        return 0;
 }
 
-static int msg_set_var_outbound(struct ast_msg *msg, const char *name, const char *value)
+int ast_msg_set_var_outbound(struct ast_msg *msg, const char *name, const char *value)
 {
        return msg_set_var_full(msg, name, value, 1);
 }
@@ -850,6 +902,26 @@ static int msg_func_write(struct ast_channel *chan, const char *function,
                ast_msg_set_from(msg, "%s", value);
        } else if (!strcasecmp(data, "body")) {
                ast_msg_set_body(msg, "%s", value);
+       } else if (!strcasecmp(data, "custom_data")) {
+               int outbound = -1;
+               if (!strcasecmp(value, "mark_all_outbound")) {
+                       outbound = 1;
+               } else if (!strcasecmp(value, "clear_all_outbound")) {
+                       outbound = 0;
+               } else {
+                       ast_log(LOG_WARNING, "'%s' is not a valid value for custom_data\n", value);
+               }
+
+               if (outbound != -1) {
+                       struct msg_data *hdr_data;
+                       struct ao2_iterator iter = ao2_iterator_init(msg->vars, 0);
+
+                       while ((hdr_data = ao2_iterator_next(&iter))) {
+                               hdr_data->send = outbound;
+                               ao2_ref(hdr_data, -1);
+                       }
+                       ao2_iterator_destroy(&iter);
+               }
        } else {
                ast_log(LOG_WARNING, "'%s' is not a valid write argument.\n", data);
        }
@@ -910,7 +982,7 @@ static int msg_data_func_write(struct ast_channel *chan, const char *function,
 
        ao2_lock(msg);
 
-       msg_set_var_outbound(msg, data, value);
+       ast_msg_set_var_outbound(msg, data, value);
 
        ao2_unlock(msg);
        ao2_ref(msg, -1);
@@ -1041,6 +1113,120 @@ exit_cleanup:
        return 0;
 }
 
+static int action_messagesend(struct mansession *s, const struct message *m)
+{
+       const char *to = ast_strdupa(astman_get_header(m, "To"));
+       const char *from = astman_get_header(m, "From");
+       const char *body = astman_get_header(m, "Body");
+       const char *base64body = astman_get_header(m, "Base64Body");
+       char base64decoded[1301] = { 0, };
+       char *tech_name = NULL;
+       struct ast_variable *vars = NULL;
+       struct ast_variable *data = NULL;
+       struct ast_msg_tech_holder *tech_holder = NULL;
+       struct ast_msg *msg;
+       int res = -1;
+
+       if (ast_strlen_zero(to)) {
+               astman_send_error(s, m, "No 'To' address specified.");
+               return -1;
+       }
+
+       if (!ast_strlen_zero(base64body)) {
+               ast_base64decode((unsigned char *) base64decoded, base64body, sizeof(base64decoded) - 1);
+               body = base64decoded;
+       }
+
+       tech_name = ast_strdupa(to);
+       tech_name = strsep(&tech_name, ":");
+       {
+               struct ast_msg_tech tmp_msg_tech = {
+                       .name = tech_name,
+               };
+               struct ast_msg_tech_holder tmp_tech_holder = {
+                       .tech = &tmp_msg_tech,
+               };
+
+               tech_holder = ao2_find(msg_techs, &tmp_tech_holder, OBJ_POINTER);
+       }
+
+       if (!tech_holder) {
+               astman_send_error(s, m, "Message technology not found.");
+               return -1;
+       }
+
+       if (!(msg = ast_msg_alloc())) {
+               ao2_ref(tech_holder, -1);
+               astman_send_error(s, m, "Internal failure\n");
+               return -1;
+       }
+
+       data = astman_get_variables(m);
+       for (vars = data; vars; vars = vars->next) {
+               ast_msg_set_var_outbound(msg, vars->name, vars->value);
+       }
+
+       ast_msg_set_body(msg, "%s", body);
+
+       ast_rwlock_rdlock(&tech_holder->tech_lock);
+       if (tech_holder->tech) {
+               res = tech_holder->tech->msg_send(msg, S_OR(to, ""), S_OR(from, ""));
+       }
+       ast_rwlock_unlock(&tech_holder->tech_lock);
+
+       ast_variables_destroy(vars);
+       ao2_ref(tech_holder, -1);
+       ao2_ref(msg, -1);
+
+       if (res) {
+               astman_send_error(s, m, "Message failed to send.");
+       } else {
+               astman_send_ack(s, m, "Message successfully sent");
+       }
+       return res;
+}
+
+int ast_msg_send(struct ast_msg *msg, const char *to, const char *from)
+{
+       char *tech_name = NULL;
+       struct ast_msg_tech_holder *tech_holder = NULL;
+       int res = -1;
+
+       if (ast_strlen_zero(to)) {
+               ao2_ref(msg, -1);
+               return -1;
+       }
+
+       tech_name = ast_strdupa(to);
+       tech_name = strsep(&tech_name, ":");
+       {
+               struct ast_msg_tech tmp_msg_tech = {
+                       .name = tech_name,
+               };
+               struct ast_msg_tech_holder tmp_tech_holder = {
+                       .tech = &tmp_msg_tech,
+               };
+
+               tech_holder = ao2_find(msg_techs, &tmp_tech_holder, OBJ_POINTER);
+       }
+
+       if (!tech_holder) {
+               ao2_ref(msg, -1);
+               return -1;
+       }
+
+       ast_rwlock_rdlock(&tech_holder->tech_lock);
+       if (tech_holder->tech) {
+               res = tech_holder->tech->msg_send(msg, S_OR(to, ""), S_OR(from, ""));
+       }
+       ast_rwlock_unlock(&tech_holder->tech_lock);
+
+       ao2_ref(tech_holder, -1);
+       ao2_ref(msg, -1);
+
+       return res;
+}
+
 int ast_msg_tech_register(const struct ast_msg_tech *tech)
 {
        struct ast_msg_tech_holder tmp_tech_holder = {
@@ -1125,6 +1311,7 @@ int ast_msg_init(void)
        res = __ast_custom_function_register(&msg_function, NULL);
        res |= __ast_custom_function_register(&msg_data_function, NULL);
        res |= ast_register_application2(app_msg_send, msg_send_exec, NULL, NULL, NULL);
+       res |= ast_manager_register_xml_core("MessageSend", EVENT_FLAG_MESSAGE, action_messagesend);
 
        return res;
 }
index bdaea72..3ed7df4 100644 (file)
@@ -59,6 +59,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/musiconhold.h"
 #include "asterisk/app.h"
 #include "asterisk/devicestate.h"
+#include "asterisk/presencestate.h"
 #include "asterisk/event.h"
 #include "asterisk/hashtab.h"
 #include "asterisk/module.h"
@@ -801,7 +802,7 @@ AST_APP_OPTIONS(waitexten_opts, {
 struct ast_context;
 struct ast_app;
 
-static struct ast_taskprocessor *device_state_tps;
+static struct ast_taskprocessor *extension_state_tps;
 
 AST_THREADSTORAGE(switch_data);
 AST_THREADSTORAGE(extensionstate_buf);
@@ -946,8 +947,16 @@ struct ast_hint {
         * Will never be NULL while the hint is in the hints container.
         */
        struct ast_exten *exten;
-       struct ao2_container *callbacks; /*!< Callback container for this extension */
-       int laststate;                  /*!< Last known state */
+       struct ao2_container *callbacks; /*!< Device state callback container for this extension */
+
+       /*! Dev state variables */
+       int laststate;                  /*!< Last known device state */
+
+       /*! Presence state variables */
+       int last_presence_state;     /*!< Last known presence state */
+       char *last_presence_subtype; /*!< Last known presence subtype string */
+       char *last_presence_message; /*!< Last known presence message string */
+
        char context_name[AST_MAX_CONTEXT];/*!< Context of destroyed hint extension. */
        char exten_name[AST_MAX_EXTENSION];/*!< Extension of destroyed hint extension. */
 };
@@ -1024,6 +1033,7 @@ static int remove_hintdevice(struct ast_hint *hint)
        return 0;
 }
 
+static char *parse_hint_device(struct ast_str *hint_args);
 /*!
  * \internal
  * \brief Destroy the given hintdevice object.
@@ -1060,7 +1070,7 @@ static int add_hintdevice(struct ast_hint *hint, const char *devicelist)
                return -1;
        }
        ast_str_set(&str, 0, "%s", devicelist);
-       parse = ast_str_buffer(str);
+       parse = parse_hint_device(str);
 
        while ((cur = strsep(&parse, "&"))) {
                devicelength = strlen(cur);
@@ -1094,6 +1104,13 @@ static const struct cfextension_states {
        { AST_EXTENSION_INUSE | AST_EXTENSION_ONHOLD,  "InUse&Hold" }
 };
 
+struct presencechange {
+       char *provider;
+       int state;
+       char *subtype;
+       char *message;
+};
+
 struct statechange {
        AST_LIST_ENTRY(statechange) entry;
        char dev[0];
@@ -1264,6 +1281,8 @@ static char *overrideswitch = NULL;
 
 /*! \brief Subscription for device state change events */
 static struct ast_event_sub *device_state_sub;
+/*! \brief Subscription for presence state change events */
+static struct ast_event_sub *presence_state_sub;
 
 AST_MUTEX_DEFINE_STATIC(maxcalllock);
 static int countcalls;
@@ -4474,6 +4493,42 @@ enum ast_extension_states ast_devstate_to_extenstate(enum ast_device_state devst
        return AST_EXTENSION_NOT_INUSE;
 }
 
+/*!
+ * \internal
+ * \brief Parse out the presence portion of the hint string
+ */
+static char *parse_hint_presence(struct ast_str *hint_args)
+{
+       char *copy = ast_strdupa(ast_str_buffer(hint_args));
+       char *tmp = "";
+
+       if ((tmp = strrchr(copy, ','))) {
+               *tmp = '\0';
+               tmp++;
+       } else {
+               return NULL;
+       }
+       ast_str_set(&hint_args, 0, "%s", tmp);
+       return ast_str_buffer(hint_args);
+}
+
+/*!
+ * \internal
+ * \brief Parse out the device portion of the hint string
+ */
+static char *parse_hint_device(struct ast_str *hint_args)
+{
+       char *copy = ast_strdupa(ast_str_buffer(hint_args));
+       char *tmp;
+
+       if ((tmp = strrchr(copy, ','))) {
+               *tmp = '\0';
+       }
+
+       ast_str_set(&hint_args, 0, "%s", copy);
+       return ast_str_buffer(hint_args);
+}
+
 static int ast_extension_state3(struct ast_str *hint_app)
 {
        char *cur;
@@ -4481,7 +4536,7 @@ static int ast_extension_state3(struct ast_str *hint_app)
        struct ast_devstate_aggregate agg;
 
        /* One or more devices separated with a & character */
-       rest = ast_str_buffer(hint_app);
+       rest = parse_hint_device(hint_app);
 
        ast_devstate_aggregate_init(&agg);
        while ((cur = strsep(&rest, "&"))) {
@@ -4539,6 +4594,209 @@ int ast_extension_state(struct ast_channel *c, const char *context, const char *
        return ast_extension_state2(e);  /* Check all devices in the hint */
 }
 
+static int extension_presence_state_helper(struct ast_exten *e, char **subtype, char **message)
+{
+       struct ast_str *hint_app = ast_str_thread_get(&extensionstate_buf, 32);
+       char *presence_provider;
+       const char *app;
+
+       if (!e || !hint_app) {
+               return -1;
+       }
+
+       app = ast_get_extension_app(e);
+       if (ast_strlen_zero(app)) {
+               return -1;
+       }
+
+       ast_str_set(&hint_app, 0, "%s", app);
+       presence_provider = parse_hint_presence(hint_app);
+
+       if (ast_strlen_zero(presence_provider)) {
+               /* No presence string in the hint */
+               return 0;
+       }
+
+       return ast_presence_state(presence_provider, subtype, message);
+}
+
+int ast_hint_presence_state(struct ast_channel *c, const char *context, const char *exten, char **subtype, char **message)
+{
+       struct ast_exten *e;
+
+       if (!(e = ast_hint_extension(c, context, exten))) {  /* Do we have a hint for this extension ? */
+               return -1;                   /* No hint, return -1 */
+       }
+
+       if (e->exten[0] == '_') {
+               /* Create this hint on-the-fly */
+               ast_add_extension(e->parent->name, 0, exten, e->priority, e->label,
+                       e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr,
+                       e->registrar);
+               if (!(e = ast_hint_extension(c, context, exten))) {
+                       /* Improbable, but not impossible */
+                       return -1;
+               }
+       }
+
+       return extension_presence_state_helper(e, subtype, message);
+}
+
+static int execute_state_callback(ast_state_cb_type cb,
+       const char *context,
+       const char *exten,
+       void *data,
+       enum ast_state_cb_update_reason reason,
+       struct ast_hint *hint)
+{
+       int res = 0;
+       struct ast_state_cb_info info = { 0, };
+
+       info.reason = reason;
+
+       /* Copy over current hint data */
+       if (hint) {
+               ao2_lock(hint);
+               info.exten_state = hint->laststate;
+               info.presence_state = hint->last_presence_state;
+               if (!(ast_strlen_zero(hint->last_presence_subtype))) {
+                       info.presence_subtype = ast_strdupa(hint->last_presence_subtype);
+               } else {
+                       info.presence_subtype = "";
+               }
+               if (!(ast_strlen_zero(hint->last_presence_message))) {
+                       info.presence_message = ast_strdupa(hint->last_presence_message);
+               } else {
+                       info.presence_message = "";
+               }
+               ao2_unlock(hint);
+       } else {
+               info.exten_state = AST_EXTENSION_REMOVED;
+       }
+
+       /* NOTE: The casts will not be needed for v10 and later */
+       res = cb((char *) context, (char *) exten, &info, data);
+
+       return res;
+}
+
+static int handle_presencechange(void *datap)
+{
+       struct ast_hint *hint;
+       struct ast_str *hint_app = NULL;
+       struct presencechange *pc = datap;
+       struct ao2_iterator i;
+       struct ao2_iterator cb_iter;
+       char context_name[AST_MAX_CONTEXT];
+       char exten_name[AST_MAX_EXTENSION];
+       int res = -1;
+
+       hint_app = ast_str_create(1024);
+       if (!hint_app) {
+               goto presencechange_cleanup;
+       }
+
+       ast_mutex_lock(&context_merge_lock);/* Hold off ast_merge_contexts_and_delete */
+       i = ao2_iterator_init(hints, 0);
+       for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) {
+               struct ast_state_cb *state_cb;
+               const char *app;
+               char *parse;
+
+               ao2_lock(hint);
+
+               if (!hint->exten) {
+                       /* The extension has already been destroyed */
+                       ao2_unlock(hint);
+                       continue;
+               }
+
+               /* Does this hint monitor the device that changed state? */
+               app = ast_get_extension_app(hint->exten);
+               if (ast_strlen_zero(app)) {
+                       /* The hint does not monitor presence at all. */
+                       ao2_unlock(hint);
+                       continue;
+               }
+
+               ast_str_set(&hint_app, 0, "%s", app);
+               parse = parse_hint_presence(hint_app);
+               if (ast_strlen_zero(parse)) {
+                       ao2_unlock(hint);
+                       continue;
+               }
+               if (strcasecmp(parse, pc->provider)) {
+                       /* The hint does not monitor the presence provider. */
+                       ao2_unlock(hint);
+                       continue;
+               }
+
+               /*
+                * Save off strings in case the hint extension gets destroyed
+                * while we are notifying the watchers.
+                */
+               ast_copy_string(context_name,
+                       ast_get_context_name(ast_get_extension_context(hint->exten)),
+                       sizeof(context_name));
+               ast_copy_string(exten_name, ast_get_extension_name(hint->exten),
+                       sizeof(exten_name));
+               ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(hint->exten));
+
+               /* Check to see if update is necessary */
+               if ((hint->last_presence_state == pc->state) &&
+                       ((hint->last_presence_subtype && pc->subtype && !strcmp(hint->last_presence_subtype, pc->subtype)) || (!hint->last_presence_subtype && !pc->subtype)) &&
+                       ((hint->last_presence_message && pc->message && !strcmp(hint->last_presence_message, pc->message)) || (!hint->last_presence_message && !pc->message))) {
+
+                       /* this update is the same as the last, do nothing */
+                       ao2_unlock(hint);
+                       continue;
+               }
+
+               /* update new values */
+               ast_free(hint->last_presence_subtype);
+               ast_free(hint->last_presence_message);
+               hint->last_presence_state = pc->state;
+               hint->last_presence_subtype = pc->subtype ? ast_strdup(pc->subtype) : NULL;
+               hint->last_presence_message = pc->message ? ast_strdup(pc->message) : NULL;
+
+               ao2_unlock(hint);
+
+               /* For general callbacks */
+               cb_iter = ao2_iterator_init(statecbs, 0);
+               for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+                       execute_state_callback(state_cb->change_cb,
+                               context_name,
+                               exten_name,
+                               state_cb->data,
+                               AST_HINT_UPDATE_PRESENCE,
+                               hint);
+               }
+               ao2_iterator_destroy(&cb_iter);
+
+               /* For extension callbacks */
+               cb_iter = ao2_iterator_init(hint->callbacks, 0);
+               for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+                       execute_state_callback(state_cb->change_cb,
+                               context_name,
+                               exten_name,
+                               state_cb->data,
+                               AST_HINT_UPDATE_PRESENCE,
+                               hint);
+               }
+               ao2_iterator_destroy(&cb_iter);
+       }
+       ao2_iterator_destroy(&i);
+       ast_mutex_unlock(&context_merge_lock);
+
+       res = 0;
+
+presencechange_cleanup:
+       ast_free(hint_app);
+       ao2_ref(pc, -1);
+
+       return res;
+}
+
 static int handle_statechange(void *datap)
 {
        struct ast_hint *hint;
@@ -4626,14 +4884,24 @@ static int handle_statechange(void *datap)
                /* For general callbacks */
                cb_iter = ao2_iterator_init(statecbs, 0);
                for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
-                       state_cb->change_cb(context_name, exten_name, state, state_cb->data);
+                       execute_state_callback(state_cb->change_cb,
+                               context_name,
+                               exten_name,
+                               state_cb->data,
+                               AST_HINT_UPDATE_DEVICE,
+                               hint);
                }
                ao2_iterator_destroy(&cb_iter);
 
                /* For extension callbacks */
                cb_iter = ao2_iterator_init(hint->callbacks, 0);
                for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
-                       state_cb->change_cb(context_name, exten_name, state, state_cb->data);
+                       execute_state_callback(state_cb->change_cb,
+                               context_name,
+                               exten_name,
+                               state_cb->data,
+                               AST_HINT_UPDATE_DEVICE,
+                               hint);
                }
                ao2_iterator_destroy(&cb_iter);
        }
@@ -4805,7 +5073,6 @@ int ast_extension_state_del(int id, ast_state_cb_type change_cb)
        return ret;
 }
 
-
 static int hint_id_cmp(void *obj, void *arg, int flags)
 {
        const struct ast_state_cb *cb = obj;
@@ -4840,14 +5107,21 @@ static void destroy_hint(void *obj)
                        context_name = hint->context_name;
                        exten_name = hint->exten_name;
                }
+               hint->laststate = AST_EXTENSION_DEACTIVATED;
                while ((state_cb = ao2_callback(hint->callbacks, OBJ_UNLINK, NULL, NULL))) {
                        /* Notify with -1 and remove all callbacks */
-                       state_cb->change_cb(context_name, exten_name, AST_EXTENSION_DEACTIVATED,
-                               state_cb->data);
+                       execute_state_callback(state_cb->change_cb,
+                               context_name,
+                               exten_name,
+                               state_cb->data,
+                               AST_HINT_UPDATE_DEVICE,
+                               hint);
                        ao2_ref(state_cb, -1);
                }
                ao2_ref(hint->callbacks, -1);
        }
+       ast_free(hint->last_presence_subtype);
+       ast_free(hint->last_presence_message);
 }
 
 /*! \brief Remove hint from extension */
@@ -4890,6 +5164,9 @@ static int ast_add_hint(struct ast_exten *e)
 {
        struct ast_hint *hint_new;
        struct ast_hint *hint_found;
+       char *message = NULL;
+       char *subtype = NULL;
+       int presence_state;
 
        if (!e) {
                return -1;
@@ -4913,6 +5190,12 @@ static int ast_add_hint(struct ast_exten *e)
        }
        hint_new->exten = e;
        hint_new->laststate = ast_extension_state2(e);
+       if ((presence_state = extension_presence_state_helper(e, &subtype, &message)) > 0) {
+               hint_new->last_presence_state = presence_state;
+               hint_new->last_presence_subtype = subtype;
+               hint_new->last_presence_message = message;
+               message = subtype = NULL;
+       }
 
        /* Prevent multiple add hints from adding the same hint at the same time. */
        ao2_lock(hints);
@@ -7432,6 +7715,10 @@ struct store_hint {
        char *exten;
        AST_LIST_HEAD_NOLOCK(, ast_state_cb) callbacks;
        int laststate;
+       int last_presence_state;
+       char *last_presence_subtype;
+       char *last_presence_message;
+
        AST_LIST_ENTRY(store_hint) list;
        char data[1];
 };
@@ -7633,6 +7920,13 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
                        strcpy(saved_hint->data, hint->exten->parent->name);
                        saved_hint->exten = saved_hint->data + strlen(saved_hint->context) + 1;
                        strcpy(saved_hint->exten, hint->exten->exten);
+                       if (hint->last_presence_subtype) {
+                               saved_hint->last_presence_subtype = ast_strdup(hint->last_presence_subtype);
+                       }
+                       if (hint->last_presence_message) {
+                               saved_hint->last_presence_message = ast_strdup(hint->last_presence_message);
+                       }
+                       saved_hint->last_presence_state = hint->last_presence_state;
                        ao2_unlock(hint);
                        AST_LIST_INSERT_HEAD(&hints_stored, saved_hint, list);
                }
@@ -7686,8 +7980,15 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
                                ao2_ref(thiscb, -1);
                        }
                        hint->laststate = saved_hint->laststate;
+                       hint->last_presence_state = saved_hint->last_presence_state;
+                       hint->last_presence_subtype = saved_hint->last_presence_subtype;
+                       hint->last_presence_message = saved_hint->last_presence_message;
                        ao2_unlock(hint);
                        ao2_ref(hint, -1);
+                       /*
+                        * The free of saved_hint->last_presence_subtype and
+                        * saved_hint->last_presence_message is not necessary here.
+                        */
                        ast_free(saved_hint);
                }
        }
@@ -7702,11 +8003,17 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
        while ((saved_hint = AST_LIST_REMOVE_HEAD(&hints_removed, list))) {
                /* this hint has been removed, notify the watchers */
                while ((thiscb = AST_LIST_REMOVE_HEAD(&saved_hint->callbacks, entry))) {
-                       thiscb->change_cb(saved_hint->context, saved_hint->exten,
-                               AST_EXTENSION_REMOVED, thiscb->data);
+                       execute_state_callback(thiscb->change_cb,
+                               saved_hint->context,
+                               saved_hint->exten,
+                               thiscb->data,
+                               AST_HINT_UPDATE_DEVICE,
+                               NULL);
                        /* Ref that we added when putting into saved_hint->callbacks */
                        ao2_ref(thiscb, -1);
                }
+               ast_free(saved_hint->last_presence_subtype);
+               ast_free(saved_hint->last_presence_message);
                ast_free(saved_hint);
        }
 
@@ -10469,6 +10776,51 @@ static int pbx_builtin_sayphonetic(struct ast_channel *chan, const char *data)
        return res;
 }
 
+static void presencechange_destroy(void *data)
+{
+       struct presencechange *pc = data;
+       ast_free(pc->provider);
+       ast_free(pc->subtype);
+       ast_free(pc->message);
+}
+
+static void presence_state_cb(const struct ast_event *event, void *unused)
+{
+       struct presencechange *pc;
+       const char *tmp;
+
+       if (!(pc = ao2_alloc(sizeof(*pc), presencechange_destroy))) {
+               return;
+       }
+
+       tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER);
+       if (ast_strlen_zero(tmp)) {
+               ast_log(LOG_ERROR, "Received invalid event that had no presence provider IE\n");
+               ao2_ref(pc, -1);
+               return;
+       }
+       pc->provider = ast_strdup(tmp);
+
+       pc->state = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
+       if (pc->state < 0) {
+               ao2_ref(pc, -1);
+               return;
+       }
+
+       if ((tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE))) {
+               pc->subtype = ast_strdup(tmp);
+       }
+
+       if ((tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE))) {
+               pc->message = ast_strdup(tmp);
+       }
+
+       /* The task processor thread is taking our reference to the presencechange object. */
+       if (ast_taskprocessor_push(extension_state_tps, handle_presencechange, pc) < 0) {
+               ao2_ref(pc, -1);
+       }
+}
+
 static void device_state_cb(const struct ast_event *event, void *unused)
 {
        const char *device;
@@ -10483,7 +10835,7 @@ static void device_state_cb(const struct ast_event *event, void *unused)
        if (!(sc = ast_calloc(1, sizeof(*sc) + strlen(device) + 1)))
                return;
        strcpy(sc->dev, device);
-       if (ast_taskprocessor_push(device_state_tps, handle_statechange, sc) < 0) {
+       if (ast_taskprocessor_push(extension_state_tps, handle_statechange, sc) < 0) {
                ast_free(sc);
        }
 }
@@ -10515,6 +10867,9 @@ static int hints_data_provider_get(const struct ast_data_search *search,
                ast_data_add_str(data_hint, "context", ast_get_context_name(ast_get_extension_context(hint->exten)));
                ast_data_add_str(data_hint, "application", ast_get_extension_app(hint->exten));
                ast_data_add_str(data_hint, "state", ast_extension_state2str(hint->laststate));
+               ast_data_add_str(data_hint, "presence_state", ast_presence_state2str(hint->last_presence_state));
+               ast_data_add_str(data_hint, "presence_subtype", S_OR(hint->last_presence_subtype, ""));
+               ast_data_add_str(data_hint, "presence_subtype", S_OR(hint->last_presence_message, ""));
                ast_data_add_int(data_hint, "watchers", watchers);
 
                if (!ast_data_search_match(search, data_hint)) {
@@ -10541,7 +10896,7 @@ int load_pbx(void)
 
        /* Initialize the PBX */
        ast_verb(1, "Asterisk PBX Core Initializing\n");
-       if (!(device_state_tps = ast_taskprocessor_get("pbx-core", 0))) {
+       if (!(extension_state_tps = ast_taskprocessor_get("pbx-core", 0))) {
                ast_log(LOG_WARNING, "failed to create pbx-core taskprocessor\n");
        }
 
@@ -10568,6 +10923,11 @@ int load_pbx(void)
                return -1;
        }
 
+       if (!(presence_state_sub = ast_event_subscribe(AST_EVENT_PRESENCE_STATE, presence_state_cb, "pbx Presence State Change", NULL,
+                       AST_EVENT_IE_END))) {
+               return -1;
+       }
+
        return 0;
 }
 
diff --git a/main/presencestate.c b/main/presencestate.c
new file mode 100644 (file)
index 0000000..df64dab
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2011-2012, Digium, Inc.
+ *
+ * David Vossel <dvossel@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Presence state management
+ */
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+#include "asterisk/utils.h"
+#include "asterisk/lock