]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
Add support for Digium Phones.
authorJason Parker <jparker@digium.com>
Tue, 28 Feb 2012 21:52:48 +0000 (21:52 +0000)
committerJason Parker <jparker@digium.com>
Tue, 28 Feb 2012 21:52:48 +0000 (21:52 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/1.8-digiumphones@357459 65c4cc65-6c06-0410-ace0-fbb531ad65f3

41 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_skinny.c
channels/sip/include/sip.h
configs/jabber.conf.sample
configs/manager.conf.sample
configs/sip.conf.sample
funcs/func_frame_trace.c
funcs/func_presence_state.c [new file with mode: 0644]
include/asterisk/_private.h
include/asterisk/app.h
include/asterisk/app_voicemail.h [new file with mode: 0644]
include/asterisk/channel.h
include/asterisk/config.h
include/asterisk/custom_control_frame.h [new file with mode: 0644]
include/asterisk/event_defs.h
include/asterisk/file.h
include/asterisk/frame.h
include/asterisk/jabber.h
include/asterisk/manager.h
include/asterisk/message.h [new file with mode: 0644]
include/asterisk/pbx.h
include/asterisk/presencestate.h [new file with mode: 0644]
main/app.c
main/asterisk.c
main/channel.c
main/config.c
main/custom_control_frame.c [new file with mode: 0644]
main/event.c
main/features.c
main/file.c
main/manager.c
main/message.c [new file with mode: 0644]
main/pbx.c
main/presencestate.c [new file with mode: 0644]
res/res_jabber.c
tests/test_config.c [new file with mode: 0644]
tests/test_custom_control.c [new file with mode: 0644]

index 86237b3b306f24d2df6ea204ee6fb76d0d705472..92681b46f1214cec1db80c5dc252a6403c454361 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"
 
 /*** DOCUMENTATION
        <application name="MixMonitor" language="en_US">
@@ -93,6 +95,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                                of <replaceable>x</replaceable> (range <literal>-4</literal> to <literal>4</literal>)</para>
                                                <argument name="x" required="true" />
                                        </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)</para>
+                                               separated by commas eg. m(1111@default,2222@default,...)
+                                       </option>
                                </optionlist>
                        </parameter>
                        <parameter name="command">
@@ -162,6 +169,16 @@ 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];
+       AST_LIST_ENTRY(vm_recipient) list;
+};
+
 struct mixmonitor {
        struct ast_audiohook audiohook;
        char *filename;
@@ -170,6 +187,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 {
@@ -178,12 +209,14 @@ enum mixmonitor_flags {
        MUXFLAG_VOLUME = (1 << 3),
        MUXFLAG_READVOLUME = (1 << 4),
        MUXFLAG_WRITEVOLUME = (1 << 5),
+       MUXFLAG_VMRECIPIENTS = (1 << 6),
 };
 
 enum mixmonitor_args {
        OPT_ARG_READVOLUME = 0,
        OPT_ARG_WRITEVOLUME,
        OPT_ARG_VOLUME,
+       OPT_ARG_VMRECIPIENTS,
        OPT_ARG_ARRAY_SIZE,
 };
 
@@ -193,6 +226,7 @@ AST_APP_OPTIONS(mixmonitor_opts, {
        AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
        AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
        AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
+       AST_APP_OPTION_ARG('m', MUXFLAG_VMRECIPIENTS, OPT_ARG_VMRECIPIENTS),
 });
 
 struct mixmonitor_ds {
@@ -267,6 +301,64 @@ 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,mailbox2@context2, mailbox3@context3" */
+       char *cur_mailbox = ast_strdupa(vm_recipients);
+       char *cur_context;
+       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_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));
+
+                       /* 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)
@@ -277,9 +369,56 @@ static void mixmonitor_free(struct mixmonitor *mixmonitor)
                        ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
                        ast_free(mixmonitor->mixmonitor_ds);
                }
+
+               /* Free everything in the recipient list */
+               clear_mixmonitor_recipient_list(mixmonitor);
+
+               /* clean stringfields */
+               ast_string_field_free_memory(mixmonitor);
+
                ast_free(mixmonitor);
        }
 }
+
+/*!
+ * \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, char *ext)
+{
+       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, mixmonitor->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 and mailbox 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_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_thread(void *obj) 
 {
        struct mixmonitor *mixmonitor = obj;
@@ -366,6 +505,14 @@ static void *mixmonitor_thread(void *obj)
        }
 
        ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
+
+       if (!AST_LIST_EMPTY(&mixmonitor->recipient_list)) {
+               ast_verb(3, "Copying recordings for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
+               copy_to_voicemail(mixmonitor, ext);
+       } else {
+               ast_debug(3, "No recipients to forward monitor to, moving on.\n");
+       }
+
        mixmonitor_free(mixmonitor);
        return NULL;
 }
@@ -401,7 +548,7 @@ 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
+                                 int readvol, int writevol, const char *post_process, const char *recipients)
 {
        pthread_t thread;
        struct mixmonitor *mixmonitor;
@@ -431,6 +578,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)) {
                mixmonitor_free(mixmonitor);
@@ -456,6 +609,32 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
                strcpy(mixmonitor->post_process, postprocess2);
        }
 
+       if (!ast_strlen_zero(recipients)) {
+               char callerid[256];
+
+               ast_channel_lock(chan);
+
+               /* We use the connected line of the invoking channel for caller ID. */
+               ast_debug(3, "Connected Line CID = %d - %s : %d - %s\n", chan->connected.id.name.valid,
+                       chan->connected.id.name.str, chan->connected.id.number.valid,
+                       chan->connected.id.number.str);
+               ast_callerid_merge(callerid, sizeof(callerid),
+                       S_COR(chan->connected.id.name.valid, chan->connected.id.name.str, NULL),
+                       S_COR(chan->connected.id.number.valid, chan->connected.id.number.str, NULL),
+                       "Unknown");
+
+               ast_string_field_set(mixmonitor, call_context, chan->context);
+               ast_string_field_set(mixmonitor, call_macrocontext, chan->macrocontext);
+               ast_string_field_set(mixmonitor, call_extension, chan->exten);
+               ast_string_field_set(mixmonitor, call_callerchan, chan->name);
+               ast_string_field_set(mixmonitor, call_callerid, callerid);
+               mixmonitor->call_priority = chan->priority;
+
+               ast_channel_unlock(chan);
+
+               add_vm_recipients_from_string(mixmonitor, recipients);
+       }
+
        mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
        strcpy(mixmonitor->filename, filename);
 
@@ -481,6 +660,7 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
 {
        int x, readvol = 0, writevol = 0;
        struct ast_flags flags = {0};
+       char *recipients = NULL;
        char *parse, *tmp, *slash;
        AST_DECLARE_APP_ARGS(args,
                AST_APP_ARG(filename);
@@ -536,6 +716,14 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
                                readvol = writevol = get_volfactor(x);
                        }
                }
+
+               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 not provided an absolute path, use the system-configured monitoring directory */
@@ -553,7 +741,7 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
        ast_mkdir(tmp, 0777);
 
        pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
-       launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
+       launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process, recipients);
 
        return 0;
 }
index 629623e533ee57ae72f348a3a5219f6d78277b2e..855615e4e5cb2c61e0c60804abb39327030ad9ba 100644 (file)
@@ -1548,13 +1548,19 @@ static int extensionstate2devicestate(int state)
        return state;
 }
 
-static int extension_state_cb(char *context, 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 b15c239b41412547450f57fc4c62196bb6aa62c7..b6fde02b1e266f557cd3ac10ffd0a1cfb6338d6d 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"
@@ -330,6 +332,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
@@ -491,7 +517,6 @@ static AST_LIST_HEAD_STATIC(vmstates, vmstate);
 #define ERROR_LOCK_PATH  -100
 #define OPERATOR_EXIT     300
 
-
 enum vm_box {
        NEW_FOLDER,
        OLD_FOLDER,
@@ -539,6 +564,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);
@@ -785,6 +829,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);
@@ -923,6 +969,7 @@ static int add_email_attachment(FILE *p, struct ast_vm_user *vmu, char *format,
 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 void free_user(struct ast_vm_user *vmu);
 
@@ -1699,25 +1746,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
@@ -2178,7 +2206,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;
        }
        
@@ -2186,8 +2216,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;
        }
 
@@ -2299,8 +2331,8 @@ 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(chan->caller.id.number.valid, chan->caller.id.number.str, NULL),
-               S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, NULL),
+               chan ? S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL) : NULL,
+               chan ? S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, NULL) : NULL,
                fn, introfn, fmt, duration, 1, chan, NULL, 1, flag);
        /* read mail file to memory */
        len = ftell(p);
@@ -4666,8 +4698,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, chan->priority);
-               fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan->name);
+               fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan ? chan->priority : 0);
+               fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan ? chan->name : "");
                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);
@@ -5344,7 +5376,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, chan->language)) {        
+               if (EXISTS(fromdir, msgnum, frompath, chan ? chan->language : "")) {
                        COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
                } else {
 #endif
@@ -5362,11 +5394,12 @@ 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(chan->caller.id.number.valid, chan->caller.id.number.str, NULL),
-               S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, NULL),
-               flag);
-       
+       if (chan) {
+               notify_new_message(chan, recip, NULL, recipmsgnum, duration, fmt,
+                       S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL),
+                       S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, NULL),
+                       flag);
+       }
        return res;
 }
 #endif
@@ -5568,6 +5601,296 @@ struct leave_vm_options {
        char *exitcontext;
 };
 
+/*!
+ * \internal
+ * \brief Creates a voicemail based on a specified file to a mailbox.
+ * \param recdata A vm_recording_data containing filename and voicemail txt info.
+ * \retval -1 failure
+ * \retval 0 success
+ *
+ * This is installed to the app.h voicemail functions and accommodates all voicemail
+ * storage methods. It should probably be broken out along with leave_voicemail at
+ * some point in the future.
+ *
+ * This function currently only works for a single recipient and only uses the format
+ * specified in recording_ext.
+ */
+static int msg_create_from_file(struct ast_vm_recording_data *recdata)
+{
+       /* voicemail recipient structure */
+       struct ast_vm_user *recipient; /* points to svm once it's been created */
+       struct ast_vm_user svm; /* struct storing the voicemail recipient */
+
+       /* File paths */
+       char tmpdir[PATH_MAX]; /* directory temp files are stored in */
+       char tmptxtfile[PATH_MAX]; /* tmp file for voicemail txt file */
+       char desttxtfile[PATH_MAX]; /* final destination for txt file */
+       char tmpaudiofile[PATH_MAX]; /* tmp file where audio is stored */
+       char dir[PATH_MAX]; /* destination for tmp files on completion */
+       char destination[PATH_MAX]; /* destination with msgXXXX.  Basically <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 */
+
+       /* 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);
+                       duration = (int) (framelength / ast_format_rate(ast_getformatbyname(recdata->recording_ext)));
+               }
+       }
+
+       /* 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[256];
+               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, "INBOX");
+
+#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, "");
+       }
+
+       free_user(recipient);
+       unlink(tmptxtfile);
+       return 0;
+}
+
 /*!
  * \brief Prompts the user and records a voicemail to a mailbox.
  * \param chan
@@ -5704,7 +6027,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
                        if (ast_exists_extension(chan, vmu->exit, "o", 1,
                                S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
                                strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
-                               ouseexten = 1;
+                                               ouseexten = 1;
                        }
                } else if (ast_exists_extension(chan, chan->context, "o", 1,
                        S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
@@ -5943,6 +6266,14 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
                /* Store information */
                txt = fdopen(txtdes, "w+");
                if (txt) {
+                       char msg_id[256] = "";
+                       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", chan->exten, chan->context, 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(chan->caller.id.name.valid, chan->caller.id.name.str, NULL),
@@ -5963,7 +6294,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,
                                chan->context,
                                chan->macrocontext, 
@@ -5974,7 +6306,8 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
                                chan->name,
                                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);
@@ -6179,7 +6512,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)
 {
 #ifdef IMAP_STORAGE
        /* we must use mbox(x) folder names, and copy the message there */
@@ -6254,6 +6587,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;
+       }
 #endif
        return 0;
 }
@@ -7898,6 +8235,7 @@ static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
 
        /* traverses directory using readdir (or select query for ODBC) */
        count_msg = count_messages(vmu, vms->curdir);
+
        if (count_msg < 0) {
                return count_msg;
        } else {
@@ -7977,7 +8315,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);
                        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");
@@ -7987,7 +8325,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);
                        if (res == ERROR_LOCK_PATH) {
                                /* If save failed do not delete the message */
                                vms->deleted[x] = 0;
@@ -9859,6 +10197,165 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
        return 0;
 }
 
+static int play_message_by_id_helper(struct ast_channel *chan,
+       struct ast_vm_user *vmu,
+       struct vm_state *vms,
+       const char *msg_id)
+{
+       struct ast_config *msg_cfg;
+       struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+       char filename[256];
+       const char *other_msg_id;
+       int found = 0;
+
+       for (vms->curmsg = 0; vms->curmsg <= vms->lastmsg && !found; vms->curmsg++) {
+               /* 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;
+               }
+
+               other_msg_id = ast_variable_retrieve(msg_cfg, "message", "msg_id");
+
+               if (!ast_strlen_zero(other_msg_id) && !strcmp(other_msg_id, msg_id)) {
+                       /* 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);
+                       found = 1;
+
+#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;
+                       }
+               }
+
+               /* cleanup configs and msg */
+               ast_config_destroy(msg_cfg);
+               DISPOSE(vms->curdir, vms->curmsg);
+       }
+
+       return found ? 0 : -1;
+}
+
+/*!
+ * \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 (chan->_state != 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
@@ -10461,7 +10958,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);
                                if (cmd == ERROR_LOCK_PATH) {
                                        res = cmd;
                                        goto out;
@@ -13026,6 +13523,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_manager_unregister("VoicemailUsersList");
@@ -13077,6 +13575,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_manager_register_xml("VoicemailUsersList", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, manager_list_voicemail_users);
@@ -13094,7 +13593,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);
 
@@ -13603,11 +14102,681 @@ 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;
+}
+
+/*!
+ * \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);
+               }
+               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", "folder_dir"))) {
+                       ast_string_field_set(msg_snapshot, folder_dir, 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;
+
+       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))) {
+               return NULL;
+       }
+
+       if (!(mailbox_snapshot = ast_calloc(1, sizeof(*mailbox_snapshot)))) {
+               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, "FOLDER DIR:   %s\n", msg_snapshot->folder_dir);
+                       ast_str_append(&str, 0, "FLAG:         %s\n", msg_snapshot->folder_dir);
+                       ast_str_append(&str, 0, "\n");
+               }
+       }
+
+       mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot);
+       return str;
+}
+
+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,
+       int *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 = get_folder_by_name(from_folder);
+       int to_folder_index = get_folder_by_name(to_folder);
+       int open = 0;
+       int res = 0;
+       int i;
+
+       memset(&vmus, 0, sizeof(vmus));
+       memset(&to_vmus, 0, sizeof(to_vmus));
+       memset(&from_vms, 0, sizeof(from_vms));
+
+       if (to_folder_index == -1 || from_folder_index == -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;
+
+       for (i = 0; i < num_msgs; i++) {
+               int cur_msg = msg_ids[i];
+               int duration = 0;
+               const char *value;
+
+               if (cur_msg >= 0 && from_vms.lastmsg < cur_msg) {
+                       /* msg does not exist */
+                       ast_log(LOG_WARNING, "msg %d does not exist to forward. Last msg is %d\n", cur_msg, from_vms.lastmsg);
+                       continue;
+               }
+               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);
+               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, "");
+
+               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,
+       int *old_msg_nums,
+       const char *newfolder,
+       int *new_msg_nums)
+{
+       struct vm_state vms;
+       struct ast_vm_user *vmu = NULL, vmus;
+       int old_folder_index = get_folder_by_name(oldfolder);
+       int new_folder_index = get_folder_by_name(newfolder);
+       int open = 0;
+       int res = 0;
+       int i;
+
+       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;
+
+       for (i = 0; i < num_msgs; i++) {
+               if (vms.lastmsg < old_msg_nums[i]) {
+                       /* msg does not exist */
+                       res = -1;
+                       goto vm_move_cleanup;
+               }
+               if (save_to_folder(vmu, &vms, old_msg_nums[i], new_folder_index, (new_msg_nums + i))) {
+                       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,
+       int *msgs)
+{
+       struct vm_state vms;
+       struct ast_vm_user *vmu = NULL, vmus;
+       int folder_index = get_folder_by_name(folder);
+       int open = 0;
+       int res = 0;
+       int i;
+
+       memset(&vmus, 0, sizeof(vmus));
+       memset(&vms, 0, sizeof(vms));
+
+       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;
+
+       for (i = 0; i < num_msgs; i++) {
+               if (vms.lastmsg < msgs[i]) {
+                       /* msg does not exist */
+                       ast_log(AST_LOG_ERROR, "Could not remove msg %d from folder %s because it does not exist.\n", msgs[i], folder);
+                       res = -1;
+                       goto vm_remove_cleanup;
+               }
+               vms.deleted[msgs[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_num,
+       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;
+
+       memset(&vmus, 0, sizeof(vmus));
+       memset(&vms, 0, sizeof(vms));
+
+       if (ast_strlen_zero(context)) {
+               context = "default";
+       }
+
+       if (!(vmu = find_user(&vmus, context, mailbox))) {
+               goto play2_msg_cleanup;
+       }
+
+       if (!ast_strlen_zero(msg_num) && !ast_strlen_zero(folder)) {
+               char filename[PATH_MAX];
+               struct ast_config *msg_cfg;
+               struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+               int duration = 0;
+               const char *value;
+
+               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);
+                       res = -1;
+                       goto play2_msg_cleanup;
+               }
+               open = 1;
+
+               vms.curmsg = atoi(msg_num);
+               if (vms.curmsg > vms.lastmsg) {
+                       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);
+
+               vms.heard[vms.curmsg] = 1;
+
+#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;
+               }
+
+               /* 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 3520d2216627b9ccbd55f547a6dc6636129bd502..6efe168e97a59ea0b541be78638f307021ba2802 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;
        local:
                *;
 };
index 379af06e78dc4296ef1d52d4f59770068bbd8947..2c94c021ec71da5c34824277a90fbea064096270 100644 (file)
@@ -264,6 +264,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/cel.h"
 #include "asterisk/data.h"
 #include "asterisk/aoc.h"
+#include "asterisk/custom_control_frame.h"
+#include "asterisk/message.h"
 #include "sip/include/sip.h"
 #include "sip/include/globals.h"
 #include "sip/include/config_parser.h"
@@ -1084,6 +1086,13 @@ static void destroy_escs(void)
        }
 }
 
+struct state_notify_data {
+       int state;
+       int presence_state;
+       const char *presence_subtype;
+       const char *presence_message;
+};
+
 /*!
  * \details
  * This container holds the dialogs that will be destroyed immediately.
@@ -1259,7 +1268,8 @@ static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int old
 static int transmit_info_with_aoc(struct sip_pvt *p, struct ast_aoc_decoded *decoded);
 static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration);
 static int transmit_info_with_vidupdate(struct sip_pvt *p);
-static int transmit_message_with_text(struct sip_pvt *p, const char *text);
+static int transmit_message_with_text(struct sip_pvt *p, const char *text, int init, int auth);
+static int transmit_message_with_msg(struct sip_pvt *p, const struct ast_msg *msg);
 static int transmit_refer(struct sip_pvt *p, const char *dest);
 static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs, const char *vmexten);
 static int transmit_notify_with_sipfrag(struct sip_pvt *p, int cseq, char *message, int terminate);
@@ -1268,7 +1278,7 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char *
 static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, uint32_t seqno);
 static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, uint32_t seqno);
 static void copy_request(struct sip_request *dst, const struct sip_request *src);
-static void receive_message(struct sip_pvt *p, struct sip_request *req);
+static void receive_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e);
 static void parse_moved_contact(struct sip_pvt *p, struct sip_request *req, char **name, char **number, int set_call_forward);
 static int sip_send_mwi_to_peer(struct sip_peer *peer, int cache_only);
 
@@ -1356,7 +1366,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(char *context, char* exten, int 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_devicestate(void *data);
 static int sip_poke_noanswer(const void *data);
 static int sip_poke_peer(struct sip_peer *peer, int force);
@@ -1489,7 +1500,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);
@@ -1541,7 +1552,7 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, int
 static int handle_request_bye(struct sip_pvt *p, struct sip_request *req);
 static int handle_request_register(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *sin, const char *e);
 static int handle_request_cancel(struct sip_pvt *p, struct sip_request *req);
-static int handle_request_message(struct sip_pvt *p, struct sip_request *req);
+static int handle_request_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e);
 static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e);
 static void handle_request_info(struct sip_pvt *p, struct sip_request *req);
 static int handle_request_options(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e);
@@ -3836,7 +3847,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>");
@@ -4487,7 +4501,7 @@ static int sip_sendtext(struct ast_channel *ast, const char *text)
                ast_verbose("Sending text %s on %s\n", text, ast->name);
        }
 
-       transmit_message_with_text(dialog, text);
+       transmit_message_with_text(dialog, text, 0, 0);
        return 0;       
 }
 
@@ -6822,6 +6836,54 @@ static int initialize_udptl(struct sip_pvt *p)
        return 0;
 }
 
+/*!
+ * \brief Sends AST_CUSTOM_FRAME of type sip info.
+ *
+ * \note pvt is expected to be locked before entering this function.
+ */
+static int sip_handle_custom_info(struct sip_pvt *pvt, struct ast_custom_payload *pl)
+{
+       struct ast_variable *headers = NULL;
+       char *content_type = NULL;
+       char *content = NULL;
+       char *useragent_filter = NULL;
+       struct ast_variable *var;
+       struct sip_request req;
+       int res = -1;
+
+       if (ast_custom_payload_sipinfo_decode(pl, &headers, &content_type, &content, &useragent_filter)) {
+               goto custom_info_cleanup;
+       }
+
+       if (!(ast_strlen_zero(useragent_filter))) {
+               int match = (strstr(pvt->useragent, useragent_filter)) ? 1 : 0;
+               if (!match) {
+                       goto custom_info_cleanup;
+               }
+       }
+
+       reqprep(&req, pvt, 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(pvt, &req, XMIT_RELIABLE, pvt->ocseq);
+
+custom_info_cleanup:
+
+       ast_free(content);
+       ast_free(content_type);
+       ast_free(useragent_filter);
+       ast_variables_destroy(headers);
+
+       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
@@ -6951,6 +7013,11 @@ static int sip_indicate(struct ast_channel *ast, int condition, const void *data
        case AST_CONTROL_REDIRECTING:
                update_redirecting(p, data, datalen);
                break;
+       case AST_CONTROL_CUSTOM:
+               if (datalen && ast_custom_payload_type((struct ast_custom_payload *) data) == AST_CUSTOM_SIP_INFO) {
+                       sip_handle_custom_info(p, (struct ast_custom_payload *) data);
+               }
+               break;
        case AST_CONTROL_AOC:
                {
                        struct ast_aoc_decoded *decoded = ast_aoc_decode((struct ast_aoc_encoded *) data, datalen, ast);
@@ -12505,8 +12572,13 @@ static int find_calling_channel(void *obj, void *arg, void *data, int flags)
        return res ? CMP_MATCH | CMP_STOP : 0;
 }
 
+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";
@@ -12514,7 +12586,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;
@@ -12559,9 +12631,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++;
 
@@ -12609,12 +12688,23 @@ 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_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);
 
@@ -12669,7 +12759,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);
@@ -12713,7 +12803,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];
@@ -12745,7 +12835,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");
@@ -12768,19 +12858,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;
@@ -13409,16 +13499,50 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char *
        return res;
 }
 
-/*! \brief Transmit text with SIP MESSAGE method */
-static int transmit_message_with_text(struct sip_pvt *p, const char *text)
+/*! \brief Transmit text with SIP MESSAGE method based on an ast_msg */
+static int transmit_message_with_msg(struct sip_pvt *p, const struct ast_msg *msg)
 {
        struct sip_request req;
-       
-       reqprep(&req, p, SIP_MESSAGE, 0, 1);
-       add_text(&req, text);
+       struct ast_msg_var_iterator *i;
+       const char *var, *val;
+
+       build_via(p);
+       initreqprep(&req, p, SIP_MESSAGE, NULL);
+       ast_string_field_set(p, msg_body, ast_msg_get_body(msg));
+       initialize_initreq(p, &req);
+
+       i = ast_msg_var_iterator_init(msg);
+       while (ast_msg_var_iterator_next(msg, i, &var, &val)) {
+               add_header(&req, var, val);
+               ast_msg_var_unref_current(i);
+       }
+       ast_msg_var_iterator_destroy(i);
+
+       add_text(&req, ast_msg_get_body(msg));
+
        return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
 }
 
+/*! \brief Transmit text with SIP MESSAGE method */
+static int transmit_message_with_text(struct sip_pvt *p, const char *text, int init, int auth)
+{
+       struct sip_request req;
+
+       if (init) {
+               initreqprep(&req, p, SIP_MESSAGE, NULL);
+               ast_string_field_set(p, msg_body, text);
+               initialize_initreq(p, &req);
+       } else {
+               reqprep(&req, p, SIP_MESSAGE, 0, 1);
+       }
+       if (auth) {
+               return transmit_request_with_auth(p, SIP_MESSAGE, p->ocseq, XMIT_RELIABLE, 0);
+       } else {
+               add_text(&req, text);
+               return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
+       }
+}
+
 /*! \brief Allocate SIP refer structure */
 static int sip_refer_allocate(struct sip_pvt *p)
 {
@@ -13649,6 +13773,10 @@ static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, uint32_t
                add_header(&resp, "X-Asterisk-HangupCauseCode", buf);
        }
 
+       if (sipmethod == SIP_MESSAGE) {
+               add_text(&resp, p->msg_body);
+       }
+
        return send_request(p, &resp, reliable, seqno ? seqno : p->ocseq);      
 }
 
@@ -14536,37 +14664,35 @@ static void cb_extensionstate_destroy(int id, void *data)
        dialog_unref(p, "the extensionstate containing this dialog ptr was destroyed");
 }
 
-/*! \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, int 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);
@@ -14574,6 +14700,27 @@ static int cb_extensionstate(char *context, char* exten, int state, void *data)
        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
  */
@@ -16033,6 +16180,9 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of,
                if (!ast_strlen_zero(peer->context)) {
                        ast_string_field_set(p, context, peer->context);
                }
+               if (!ast_strlen_zero(peer->messagecontext)) {
+                       ast_string_field_set(p, messagecontext, peer->messagecontext);
+               }
                if (!ast_strlen_zero(peer->mwi_from)) {
                        ast_string_field_set(p, mwi_from, peer->mwi_from);
                }
@@ -16247,16 +16397,54 @@ static int get_msg_text(char *buf, int len, struct sip_request *req)
        return 0;
 }
 
+static int get_msg_text2(struct ast_str **buf, struct sip_request *req)
+{
+       int i, res = 0;
+
+       ast_str_reset(*buf);
+
+       for (i = 0; res >= 0 && i < req->lines; i++) {
+               const char *line = REQ_OFFSET_TO_STR(req, line[i]);
+
+               res = ast_str_append(buf, 0, "%s\n", line);
+       }
+
+       return res < 0 ? -1 : 0;
+}
+
+static void set_message_vars_from_req(struct ast_msg *msg, struct sip_request *req)
+{
+       size_t x;
+       char name_buf[1024] = "";
+       char val_buf[1024] = "";
+       char *c;
+
+       for (x = 0; x < req->headers; x++) {
+               const char *header = REQ_OFFSET_TO_STR(req, header[x]);
+               if ((c = strchr(header, ':'))) {
+                       ast_copy_string(name_buf, header, MIN((c - header + 1), sizeof(name_buf)));
+                       ast_copy_string(val_buf, ast_skip_blanks(c + 1), sizeof(val_buf));
+                       ast_trim_blanks(name_buf);
+                       ast_msg_set_var(msg, name_buf, val_buf);
+               }
+       }
+}
+
+AST_THREADSTORAGE(sip_msg_buf);
 
 /*! \brief  Receive SIP MESSAGE method messages
 \note  We only handle messages within current calls currently
        Reference: RFC 3428 */
-static void receive_message(struct sip_pvt *p, struct sip_request *req)
+static void receive_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e)
 {
-       char buf[1400]; 
-       char *bufp;
+       struct ast_str *buf;
+       char *cbuf;
+       size_t len;
        struct ast_frame f;
        const char *content_type = get_header(req, "Content-Type");
+       struct ast_msg *msg;
+       int res;
+       char *from, *to;
 
        if (strncmp(content_type, "text/plain", strlen("text/plain"))) { /* No text/plain attachment */
                transmit_response(p, "415 Unsupported Media Type", req); /* Good enough, or? */
@@ -16265,7 +16453,15 @@ static void receive_message(struct sip_pvt *p, struct sip_request *req)
                return;
        }
 
-       if (get_msg_text(buf, sizeof(buf), req)) {
+       if (!(buf = ast_str_thread_get(&sip_msg_buf, 128))) {
+               transmit_response(p, "500 Internal Server Error", req);
+               if (!p->owner) {
+                       sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+               }
+               return;
+       }
+
+       if (get_msg_text2(&buf, req)) {
                ast_log(LOG_WARNING, "Unable to retrieve text from %s\n", p->callid);
                transmit_response(p, "500 Internal Server Error", req);
                if (!p->owner) {
@@ -16274,32 +16470,129 @@ static void receive_message(struct sip_pvt *p, struct sip_request *req)
                return;
        }
 
-       /* Strip trailing line feeds from message body. (get_msg_text may add
+       /* Strip trailing line feeds from message body. (get_msg_text2 may add
         * a trailing linefeed and we don't need any at the end) */
-       bufp = buf + strlen(buf);
-       while (--bufp >= buf && *bufp == '\n') {
-               *bufp = '\0';
+       cbuf = ast_str_buffer(buf);
+       len = ast_str_strlen(buf);
+       while (len > 0) {
+               if (cbuf[--len] != '\n') {
+                       ++len;
+                       break;
+               }
        }
+       ast_str_truncate(buf, len);
 
        if (p->owner) {
                if (sip_debug_test_pvt(p))
-                       ast_verbose("SIP Text message received: '%s'\n", buf);
+                       ast_verbose("SIP Text message received: '%s'\n", ast_str_buffer(buf));
                memset(&f, 0, sizeof(f));
                f.frametype = AST_FRAME_TEXT;
                f.subclass.integer = 0;
                f.offset = 0;
-               f.data.ptr = buf;
-               f.datalen = strlen(buf) + 1;
+               f.data.ptr = ast_str_buffer(buf);
+               f.datalen = ast_str_strlen(buf) + 1;
                ast_queue_frame(p->owner, &f);
                transmit_response(p, "202 Accepted", req); /* We respond 202 accepted, since we relay the message */
                return;
        }
 
-       /* Message outside of a call, we do not support that */
-       ast_log(LOG_WARNING, "Received message to %s from %s, dropped it...\n  Content-Type:%s\n  Message: %s\n", get_header(req, "To"), get_header(req, "From"), content_type, buf);
-       transmit_response(p, "405 Method Not Allowed", req);
+       if (!sip_cfg.accept_outofcall_message) {
+               /* Message outside of a call, we do not support that */
+               ast_debug(1, "MESSAGE outside of a call administratively disabled.\n");
+               transmit_response(p, "405 Method Not Allowed", req);
+               sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+               return;
+       }
+
+       copy_request(&p->initreq, req);
+
+       if (sip_cfg.auth_message_requests) {
+               int res;
+
+               set_pvt_allowed_methods(p, req);
+               res = check_user(p, req, SIP_MESSAGE, e, XMIT_UNRELIABLE, addr);
+               if (res == AUTH_CHALLENGE_SENT) {
+                       sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+                       return;
+               }
+               if (res < 0) { /* Something failed in authentication */
+                       if (res == AUTH_FAKE_AUTH) {
+                               ast_log(LOG_NOTICE, "Sending fake auth rejection for device %s\n", get_header(req, "From"));
+                               transmit_fake_auth_response(p, SIP_OPTIONS, req, XMIT_UNRELIABLE);
+                       } else {
+                               ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", get_header(req, "From"));
+                               transmit_response(p, "403 Forbidden", req);
+                       }
+                       sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+                       return;
+               }
+               /* Auth was successful.  Proceed. */
+       } else {
+               struct sip_peer *peer;
+
+               /*
+                * MESSAGE outside of a call, not authenticating it.
+                * Check to see if we match a peer anyway so that we can direct
+                * it to the right context.
+                */
+
+               peer = find_peer(NULL, &p->recv, TRUE, FINDPEERS, 0, p->socket.type);
+               if (peer) {
+                       /* Only if no auth is required. */
+                       if (ast_strlen_zero(peer->secret) && ast_strlen_zero(peer->md5secret)) {
+                               ast_string_field_set(p, context, peer->context);
+                       }
+                       if (!ast_strlen_zero(peer->messagecontext)) {
+                               ast_string_field_set(p, messagecontext, peer->messagecontext);
+                       }
+                       ast_string_field_set(p, peername, peer->name);
+                       peer = unref_peer(peer, "from find_peer() in receive_message");
+               }
+       }
+
+       /* Override the context with the message context _BEFORE_
+        * getting the destination.  This way we can guarantee the correct
+        * extension is used in the message context when it is present. */
+       if (!ast_strlen_zero(p->messagecontext)) {
+               ast_string_field_set(p, context, p->messagecontext);
+       } else if (!ast_strlen_zero(sip_cfg.messagecontext)) {
+               ast_string_field_set(p, context, sip_cfg.messagecontext);
+       }
+
+       get_destination(p, NULL, NULL);
+
+       if (!(msg = ast_msg_alloc())) {
+               transmit_response(p, "500 Internal Server Error", req);
+               if (!p->owner) {
+                       sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+               }
+               return;
+       }
+
+       to = ast_strdupa(REQ_OFFSET_TO_STR(req, rlPart2));
+       from = ast_strdupa(get_header(req, "From"));
+
+       res = ast_msg_set_to(msg, "%s", to);
+       res |= ast_msg_set_from(msg, "%s", get_in_brackets(from));
+       res |= ast_msg_set_body(msg, "%s", ast_str_buffer(buf));
+       res |= ast_msg_set_context(msg, "%s", p->context);
+
+       res |= ast_msg_set_var(msg, "SIP_RECVADDR", ast_sockaddr_stringify(&p->recv));
+       if (!ast_strlen_zero(p->peername)) {
+               res |= ast_msg_set_var(msg, "SIP_PEERNAME", p->peername);
+       }
+
+       res |= ast_msg_set_exten(msg, "%s", p->exten);
+
+       if (res) {
+               ast_msg_destroy(msg);
+       } else {
+               set_message_vars_from_req(msg, req);
+               ast_msg_queue(msg);
+       }
+
+       transmit_response(p, "202 Accepted", req);
        sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
-       return;
 }
 
 /*! \brief  CLI Command to show calls within limits set by call_limit */
@@ -20394,9 +20687,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;
@@ -20810,7 +21109,6 @@ static void handle_response_peerpoke(struct sip_pvt *p, int resp, struct sip_req
 }
 
 /*!
- * \internal
  * \brief Handle responses to INFO messages
  *
  * \note The INFO method MUST NOT change the state of calls or
@@ -20846,6 +21144,43 @@ static void handle_response_info(struct sip_pvt *p, int resp, const char *rest,
 
 /*!
  * \internal
+ * \brief Handle auth requests to a MESSAGE request
+ * \return TRUE if authentication failed.
+ */
+static int do_message_auth(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno)
+{
+       char *header;
+       char *respheader;
+       char digest[1024];
+
+       if (p->options) {
+               p->options->auth_type = (resp == 401 ? WWW_AUTH : PROXY_AUTH);
+       }
+
+       if (p->authtries == MAX_AUTHTRIES) {
+               ast_log(LOG_NOTICE, "Failed to authenticate MESSAGE with host '%s'\n",
+                       ast_sockaddr_stringify(&p->sa));
+               return -1;
+       }
+
+       ++p->authtries;
+       auth_headers((resp == 401 ? WWW_AUTH : PROXY_AUTH), &header, &respheader);
+       memset(digest, 0, sizeof(digest));
+       if (reply_digest(p, req, header, SIP_MESSAGE, digest, sizeof(digest))) {
+               /* There's nothing to use for authentication */
+               ast_debug(1, "Nothing to use for MESSAGE authentication\n");
+               return -1;
+       }
+
+       if (p->do_history) {
+               append_history(p, "MessageAuth", "Try: %d", p->authtries);
+       }
+
+       transmit_message_with_text(p, p->msg_body, 0, 1);
+       return 0;
+}
+
+/*!
  * \brief Handle responses to MESSAGE messages
  *
  * \note The MESSAGE method should not change the state of calls
@@ -20855,14 +21190,14 @@ static void handle_response_info(struct sip_pvt *p, int resp, const char *rest,
 static void handle_response_message(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno)
 {
        int sipmethod = SIP_MESSAGE;
-       /* Out-of-dialog MESSAGE currently not supported. */
-       //int in_dialog = ast_test_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+       int in_dialog = ast_test_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
 
        switch (resp) {
        case 401: /* Not www-authorized on SIP method */
        case 407: /* Proxy auth required */
-               ast_log(LOG_WARNING, "Host '%s' requests authentication (%d) for '%s'\n",
-                       ast_sockaddr_stringify(&p->sa), resp, sip_methods[sipmethod].text);
+               if (do_message_auth(p, resp, rest, req, seqno) && !in_dialog) {
+                       pvt_set_needdestroy(p, "MESSAGE authentication failed");
+               }
                break;
        case 405: /* Method not allowed */
        case 501: /* Not Implemented */
@@ -20872,15 +21207,25 @@ static void handle_response_message(struct sip_pvt *p, int resp, const char *res
                }
                ast_log(LOG_WARNING, "Host '%s' does not implement '%s'\n",
                        ast_sockaddr_stringify(&p->sa), sip_methods[sipmethod].text);
+               if (!in_dialog) {
+                       pvt_set_needdestroy(p, "MESSAGE not implemented or allowed");
+               }
                break;
        default:
                if (100 <= resp && resp < 200) {
                        /* Must allow provisional responses for out-of-dialog requests. */
                } else if (200 <= resp && resp < 300) {
                        p->authtries = 0;       /* Reset authentication counter */
+                       if (!in_dialog) {
+                               pvt_set_needdestroy(p, "MESSAGE delivery accepted");
+                       }
                } else if (300 <= resp && resp < 700) {
                        ast_verb(3, "Got SIP %s response %d \"%s\" back from host '%s'\n",
                                sip_methods[sipmethod].text, resp, rest, ast_sockaddr_stringify(&p->sa));
+                       if (!in_dialog) {
+                               pvt_set_needdestroy(p, (300 <= resp && resp < 600)
+                                       ? "MESSAGE delivery failed" : "MESSAGE delivery refused");
+                       }
                }
                break;
        }
@@ -21357,11 +21702,11 @@ static void *sip_park_thread(void *stuff)
 
 #ifdef WHEN_WE_KNOW_THAT_THE_CLIENT_SUPPORTS_MESSAGE
        if (res) {
-               transmit_message_with_text(transferer->tech_pvt, "Unable to park call.\n");
+               transmit_message_with_text(transferer->tech_pvt, "Unable to park call.\n", 0, 0);
        } else {
                /* Then tell the transferer what happened */
                sprintf(buf, "Call parked on extension '%d'", ext);
-               transmit_message_with_text(transferer->tech_pvt, buf);
+               transmit_message_with_text(transferer->tech_pvt, buf, 0, 0);
        }
 #endif
 
@@ -23899,17 +24244,97 @@ static int handle_request_bye(struct sip_pvt *p, struct sip_request *req)
 }
 
 /*! \brief Handle incoming MESSAGE request */
-static int handle_request_message(struct sip_pvt *p, struct sip_request *req)
+static int handle_request_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e)
 {
        if (!req->ignore) {
                if (req->debug)
                        ast_verbose("Receiving message!\n");
-               receive_message(p, req);
+               receive_message(p, req, addr, e);
        } else
                transmit_response(p, "202 Accepted", req);
        return 1;
 }
 
+static int sip_msg_send(const struct ast_msg *msg, const char *to, const char *from);
+
+static const struct ast_msg_tech sip_msg_tech = {
+       .name = "sip",
+       .msg_send = sip_msg_send,
+};
+
+static int sip_msg_send(const struct ast_msg *msg, const char *to, const char *from)
+{
+       struct sip_pvt *pvt;
+       int res;
+       char *to_uri, *to_host, *to_user;
+       struct sip_peer *peer_ptr;
+
+       if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_MESSAGE, NULL))) {
+               return -1;
+       }
+
+       to_uri = ast_strdupa(to);
+       parse_uri(to_uri, "sip:,sips:", &to_user, NULL, &to_host, NULL);
+
+       if (ast_strlen_zero(to_host)) {
+               ast_log(LOG_WARNING, "MESSAGE(to) is invalid for SIP - '%s'\n", to);
+               return -1;
+       }
+
+       if (!ast_strlen_zero(from)) {
+               if ((peer_ptr = find_peer(from, NULL, 0, 1, 0, 0))) {
+                       ast_string_field_set(pvt, fromname, S_OR(peer_ptr->cid_name, peer_ptr->name));
+                       ast_string_field_set(pvt, fromuser, S_OR(peer_ptr->cid_num, peer_ptr->name));
+                       unref_peer(peer_ptr, "sip_unref_peer, from sip_msg_send, sip_find_peer");
+               } else if (strchr(from, '<')) { /* from is callerid-style */
+                       char *sender;
+                       char *name = NULL, *location = NULL, *user = NULL, *domain = NULL;
+
+                       sender = ast_strdupa(from);
+                       ast_callerid_parse(sender, &name, &location);
+                       ast_string_field_set(pvt, fromname, name);
+                       if (strchr(location, ':')) { /* Must be a URI */
+                               parse_uri(location, "sip:,sips:", &user, NULL, &domain, NULL);
+                               SIP_PEDANTIC_DECODE(user);
+                               SIP_PEDANTIC_DECODE(domain);
+                               extract_host_from_hostport(&domain);
+                               ast_string_field_set(pvt, fromuser, user);
+                               ast_string_field_set(pvt, fromdomain, domain);
+                       } else { /* Treat it as an exten/user */
+                               ast_string_field_set(pvt, fromuser, location);
+                       }
+               } else { /* assume we just have the name, use defaults for the rest */
+                       ast_string_field_set(pvt, fromname, from);
+               }
+       }
+
+       sip_pvt_lock(pvt);
+
+       /* Look up the host to contact */
+       if (create_addr(pvt, to_host, NULL, TRUE, NULL)) {
+               sip_pvt_unlock(pvt);
+               dialog_unlink_all(pvt);
+               dialog_unref(pvt, "create_addr failed sending a MESSAGE");
+               return -1;
+       }
+
+       if (!ast_strlen_zero(to_user)) {
+               ast_string_field_set(pvt, username, to_user);
+       }
+       ast_sip_ouraddrfor(&pvt->sa, &pvt->ourip, pvt);
+       ast_set_flag(&pvt->flags[0], SIP_OUTGOING);
+
+       /* XXX Does pvt->expiry need to be set? */
+
+       res = transmit_message_with_msg(pvt, msg);
+
+       sip_pvt_unlock(pvt);
+       sip_scheddestroy(pvt, DEFAULT_TRANS_TIMEOUT);
+       dialog_unref(pvt, "sent a MESSAGE");
+
+       return res;
+}
+
 static enum sip_publish_type determine_sip_publish_type(struct sip_request *req, const char * const event, const char * const etag, const char * const expires, int *expires_int)
 {
        int etag_present = !ast_strlen_zero(etag);
@@ -24501,7 +24926,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 = get_header(req, "Event");     /* Get Event package name */
        int resubscribe = (p->subscribed != NONE) && !req->ignore;
@@ -24850,7 +25274,10 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req,
                                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);
@@ -24860,14 +25287,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");
@@ -25160,7 +25594,7 @@ static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct as
                res = handle_request_bye(p, req);
                break;
        case SIP_MESSAGE:
-               res = handle_request_message(p, req);
+               res = handle_request_message(p, req, addr, e);
                break;
        case SIP_PUBLISH:
                res = handle_request_publish(p, req, addr, seqno, e);
@@ -27037,6 +27471,7 @@ static void set_peer_defaults(struct sip_peer *peer)
        ast_copy_flags(&peer->flags[1], &global_flags[1], SIP_PAGE2_FLAGS_TO_COPY);
        ast_copy_flags(&peer->flags[2], &global_flags[2], SIP_PAGE3_FLAGS_TO_COPY);
        ast_string_field_set(peer, context, sip_cfg.default_context);
+       ast_string_field_set(peer, messagecontext, sip_cfg.messagecontext);
        ast_string_field_set(peer, subscribecontext, sip_cfg.default_subscribecontext);
        ast_string_field_set(peer, language, default_language);
        ast_string_field_set(peer, mohinterpret, default_mohinterpret);
@@ -27333,6 +27768,8 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
                        } else if (!strcasecmp(v->name, "context")) {
                                ast_string_field_set(peer, context, v->value);
                                ast_set_flag(&peer->flags[1], SIP_PAGE2_HAVEPEERCONTEXT);
+                       } else if (!strcasecmp(v->name, "outofcall_message_context")) {
+                               ast_string_field_set(peer, messagecontext, v->value);
                        } else if (!strcasecmp(v->name, "subscribecontext")) {
                                ast_string_field_set(peer, subscribecontext, v->value);
                        } else if (!strcasecmp(v->name, "fromdomain")) {
@@ -28035,6 +28472,9 @@ static int reload_config(enum channelreloadreason reason)
        sip_cfg.directrtpsetup = FALSE;         /* Experimental feature, disabled by default */
        sip_cfg.alwaysauthreject = DEFAULT_ALWAYSAUTHREJECT;
        sip_cfg.auth_options_requests = DEFAULT_AUTH_OPTIONS;
+       sip_cfg.auth_message_requests = DEFAULT_AUTH_MESSAGE;
+       sip_cfg.messagecontext[0] = '\0';
+       sip_cfg.accept_outofcall_message = DEFAULT_ACCEPT_OUTOFCALL_MESSAGE;
        sip_cfg.allowsubscribe = FALSE;
        sip_cfg.disallowed_methods = SIP_UNKNOWN;
        sip_cfg.contact_ha = NULL;              /* Reset the contact ACL */
@@ -28284,6 +28724,12 @@ static int reload_config(enum channelreloadreason reason)
                        if (ast_true(v->value)) {
                                sip_cfg.auth_options_requests = 1;
                        }
+               } else if (!strcasecmp(v->name, "auth_message_requests")) {
+                       sip_cfg.auth_message_requests = ast_true(v->value) ? 1 : 0;
+               } else if (!strcasecmp(v->name, "accept_outofcall_message")) {
+                       sip_cfg.accept_outofcall_message = ast_true(v->value) ? 1 : 0;
+               } else if (!strcasecmp(v->name, "outofcall_message_context")) {
+                       ast_copy_string(sip_cfg.messagecontext, v->value, sizeof(sip_cfg.messagecontext));
                } else if (!strcasecmp(v->name, "mohinterpret")) {
                        ast_copy_string(default_mohinterpret, v->value, sizeof(default_mohinterpret));
                } else if (!strcasecmp(v->name, "mohsuggest")) {
@@ -30270,6 +30716,11 @@ static int load_module(void)
        memcpy(&sip_tech_info, &sip_tech, sizeof(sip_tech));
        memset((void *) &sip_tech_info.send_digit_begin, 0, sizeof(sip_tech_info.send_digit_begin));
 
+       if (ast_msg_tech_register(&sip_msg_tech)) {
+               /* LOAD_FAILURE stops Asterisk, so cleanup is a moot point. */
+               return AST_MODULE_LOAD_FAILURE;
+       }
+
        /* Make sure we can register our sip channel type */
        if (ast_channel_register(&sip_tech)) {
                ast_log(LOG_ERROR, "Unable to register channel type 'SIP'\n");
@@ -30379,6 +30830,8 @@ static int unload_module(void)
        /* First, take us out of the channel type list */
        ast_channel_unregister(&sip_tech);
 
+       ast_msg_tech_unregister(&sip_msg_tech);
+
        /* Unregister dial plan functions */
        ast_custom_function_unregister(&sipchaninfo_function);
        ast_custom_function_unregister(&sippeer_function);
index c967ca8344cff9e666e6b796d8dda30f95c92b71..2d1447f4d212760dd3e9168f766ea89b1918403c 100644 (file)
@@ -1408,7 +1408,7 @@ static const struct ast_channel_tech skinny_tech = {
        .bridge = ast_rtp_instance_bridge, 
 };
 
-static int skinny_extensionstate_cb(char *context, char* exten, int 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 void *get_button_template(struct skinnysession *s, struct button_definition_template *btn)
@@ -2623,11 +2623,17 @@ static void transmit_softkeytemplateres(struct skinny_device *d)
 }
 
 
-static int skinny_extensionstate_cb(char *context, char *exten, int state, void *data)
+static int skinny_extensionstate_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data)
 {
        struct skinny_speeddial *sd = data;
        struct skinny_device *d = sd->parent;
        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 (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, sd->context, sd->exten)) {
                /* If they are not registered, we will override notification and show no availability */
index 79102b78c71941314e97f65ef9c35dc7330abeaa..b69fa0aaa5de6df129701d2c1717017bfedc447e 100644 (file)
 #define DEFAULT_CALLEVENTS     FALSE    /*!< Extra manager SIP call events */
 #define DEFAULT_ALWAYSAUTHREJECT  TRUE  /*!< Don't reject authentication requests always */
 #define DEFAULT_AUTH_OPTIONS  FALSE
+#define DEFAULT_AUTH_MESSAGE  TRUE
+#define DEFAULT_ACCEPT_OUTOFCALL_MESSAGE TRUE
 #define DEFAULT_REGEXTENONQUALIFY FALSE
 #define DEFAULT_LEGACY_USEROPTION_PARSING FALSE
 #define DEFAULT_T1MIN             100   /*!< 100 MS for minimal roundtrip time */
@@ -686,6 +688,8 @@ struct sip_settings {
        int allowguest;             /*!< allow unauthenticated peers to connect? */
        int alwaysauthreject;       /*!< Send 401 Unauthorized for all failing requests */
        int auth_options_requests;  /*!< Authenticate OPTIONS requests */
+       int auth_message_requests;  /*!< Authenticate MESSAGE requests */
+       int accept_outofcall_message; /*!< Accept MESSAGE outside of a call */
        int compactheaders;         /*!< send compact sip headers */
        int allow_external_domains; /*!< Accept calls to external SIP domains? */
        int callevents;             /*!< Whether we send manager events or not */
@@ -704,6 +708,7 @@ struct sip_settings {
        int domainsasrealm;         /*!< Use domains lists as realms */
        struct sip_proxy outboundproxy; /*!< Outbound proxy */
        char default_context[AST_MAX_CONTEXT];
+       char messagecontext[AST_MAX_CONTEXT];
        char default_subscribecontext[AST_MAX_CONTEXT];
        struct ast_ha *contact_ha;  /*! \brief Global list of addresses dynamic peers are not allowed to use */
        format_t capability;        /*!< Supported codecs */
@@ -946,6 +951,7 @@ struct sip_pvt {
                AST_STRING_FIELD(useragent);    /*!< User agent in SIP request */
                AST_STRING_FIELD(exten);        /*!< Extension where to start */
                AST_STRING_FIELD(context);      /*!< Context for this call */
+               AST_STRING_FIELD(messagecontext);/*!< Default context for outofcall messages*/
                AST_STRING_FIELD(subscribecontext); /*!< Subscribecontext */
                AST_STRING_FIELD(subscribeuri); /*!< Subscribecontext */
                AST_STRING_FIELD(fromdomain);   /*!< Domain to show in the from field */
@@ -977,6 +983,9 @@ 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 */
        int maxforwards;                        /*!< SIP Loop prevention */
@@ -1074,6 +1083,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 */
@@ -1178,6 +1188,7 @@ struct sip_peer {
                AST_STRING_FIELD(md5secret);    /*!< Password in MD5 */
                AST_STRING_FIELD(remotesecret); /*!< Remote secret (trunks, remote devices) */
                AST_STRING_FIELD(context);      /*!< Default context for incoming calls */
+               AST_STRING_FIELD(messagecontext);/*!< Default context for outofcall messages*/
                AST_STRING_FIELD(subscribecontext); /*!< Default context for subscriptions */
                AST_STRING_FIELD(username);     /*!< Temporary username until registration */
                AST_STRING_FIELD(accountcode);  /*!< Account code */
index 098122d9161d8cc060204e147653650ae5abe993..a8385686784acd67613faeef1e2fc2aeac767374 100644 (file)
@@ -34,3 +34,6 @@
                                        ; Messages stored longer than this value will be deleted by Asterisk.
                                        ; This option applies to incoming messages only, which are intended to
                                        ; be processed by the JABBER_RECEIVE dialplan function.
+;sendtodialplan=yes                    ; Send incoming messages into the dialplan.  Off by default.
+;context=messages                      ; Dialplan context to send incoming messages to.  If not set,
+                                       ; "default" will be used.
index fb44e74d4d3958cb76d5ae27f7d45078d3c4d6ac..c48f643bbd258bff0b7c99c84cc9ddebd919a7cc 100644 (file)
@@ -140,7 +140,9 @@ 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 6c29b9da47a5bec2f3099bf599184d9df0ac70a3..a10c224a9d448b233612fea84c96d917746feb0e 100644 (file)
@@ -396,6 +396,21 @@ srvlookup=yes                   ; Enable DNS SRV lookups on outbound calls
 ;auth_options_requests = yes    ; Enabling this option will authenticate OPTIONS requests just like
                                 ; INVITE requests are.  By default this option is disabled.
 
+;accept_outofcall_message = no  ; Disable this option to reject all MESSAGE requests outside of a
+                                ; call.  By default, this option is enabled.  When enabled, MESSAGE
+                                ; requests are passed in to the dialplan.
+
+;outofcall_message_context = messages ; Context all out of dialog msgs are sent to. When this
+                                      ; option is not set, the context used during peer matching
+                                      ; is used. This option can be defined at both the peer and
+                                      ; global level.
+
+;auth_message_requests = yes    ; Enabling this option will authenticate MESSAGE requests.
+                                ; By default this option is enabled.  However, it can be disabled
+                                ; should an application desire to not load the Asterisk server with
+                                ; doing authentication and implement end to end security in the
+                                ; message body.
+
 ;g726nonstandard = yes          ; If the peer negotiates G726-32 audio, use AAL2 packing
                                 ; order instead of RFC3551 packing order (this is required
                                 ; for Sipura and Grandstream ATAs, among others). This is
index 0c1bc81cf6866b74c6fd41dd7419de91c3111b6b..a983663307fcd7a0423ca1d28d303b1c7338bbb2 100644 (file)
@@ -318,6 +318,9 @@ static void print_frame(struct ast_frame *frame)
                case AST_CONTROL_END_OF_Q:
                        ast_verbose("SubClass: END_OF_Q\n");
                        break;
+               case AST_CONTROL_CUSTOM:
+                       ast_verbose("Subclass: Custom");
+                       break;
                case AST_CONTROL_UPDATE_RTP_PEER:
                        ast_verbose("SubClass: UPDATE_RTP_PEER\n");
                        break;
diff --git a/funcs/func_presence_state.c b/funcs/func_presence_state.c
new file mode 100644 (file)
index 0000000..69fbf6b
--- /dev/null
@@ -0,0 +1,293 @@
+/*
+ * 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"
+
+/*** 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(args.provider, &subtype, &message);
+       if (state < 0) {
+               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, int *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 < 0) {
+               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);
+       int 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(tmp);
+
+       return 0;
+}
+
+static enum ast_presence_state custom_presence_callback(const char *data, char **subtype, char **message)
+{
+       char buf[1301] = "";
+       int state;
+       char *_options;
+       char *_message;
+       char *_subtype;
+
+       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_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_base64decode((unsigned char *) tmp, _message, sizeof(tmp) - 1);
+                       *message = ast_strdup(tmp);
+               }
+       } else {
+               *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 int unload_module(void)
+{
+       int res = 0;
+
+       res |= ast_custom_function_unregister(&presence_function);
+       res |= ast_presence_state_prov_del("CustomPresence");
+       return res;
+}
+
+static int load_module(void)
+{
+       int res = 0;
+
+       res |= ast_custom_function_register(&presence_function);
+       res |= ast_presence_state_prov_add("CustomPresence", custom_presence_callback);
+
+       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 560c8c1699b500c7f3007277211c748d439a48ed..057255fa6bf9b461c9277db9ba3dc467fbfe93dc 100644 (file)
@@ -47,6 +47,7 @@ int ast_cel_engine_init(void);                /*!< Provided by cel.c */
 int ast_cel_engine_reload(void);       /*!< Provided by cel.c */
 int ast_ssl_init(void);                 /*!< Provided by ssl.c */
 int ast_test_init(void);            /*!< Provided by test.c */
+int ast_msg_init(void);             /*!< Provided by message.c */
 
 /*!
  * \brief Reload asterisk modules.
index 87f7c80edea4b1c440c42694be8dda39523d98c6..54bc93f6722bd7d5cae27bff40ef09e8e67ca93c 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;
 
@@ -78,6 +80,27 @@ struct ast_ivr_menu {
        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)
 
 #define AST_IVR_DECLARE_MENU(holder, title, flags, foo...) \
@@ -147,10 +170,19 @@ 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
@@ -267,6 +299,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..3550b02
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * 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(folder_dir);
+               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,
+       int *old_msg_ids,
+       const char *newfolder,
+       int *new_msg_ids);
+
+/*!
+ * \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,
+       int *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 folder to which the message is being forwarded
+ * \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,
+       int *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_num,
+       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);
+#endif
index d9c261a30066b013afa70d35110a86af49b2e4bc..9c005bb4624b6e0d548fe7f73423b901ff9c3fbd 100644 (file)
@@ -3500,4 +3500,14 @@ int ast_channel_get_cc_agent_type(struct ast_channel *chan, char *agent_type, si
 }
 #endif
 
+/*!
+ * \brief Remove a channel from the global channels container
+ *
+ * \param chan channel to remove
+ *
+ * In a case where it is desired that a channel not be available in any lookups
+ * in the global channels conatiner, use this function.
+ */
+void ast_channel_unlink(struct ast_channel *chan);
+
 #endif /* _ASTERISK_CHANNEL_H */
index 86c2bb5dd487692284143552a6408318f799180c..b2224415c17024d9b0d6451fb74142df500a3b68 100644 (file)
@@ -590,6 +590,65 @@ int config_text_file_save(const char *filename, const struct ast_config *cfg, co
 
 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
  *
diff --git a/include/asterisk/custom_control_frame.h b/include/asterisk/custom_control_frame.h
new file mode 100644 (file)
index 0000000..e96d710
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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 Defines the use of the AST_CONTROL_CUSTOM control frame subclass.
+ */
+
+#ifndef _ASTERISK_CUSTOM_FRAME_H
+#define _ASTERISK_CUSTOM_FRAME_H
+
+#include "asterisk/config.h"
+
+/*! \brief this is the payload structure used in every AST_CONTROL_CUSTOM frame. */
+struct ast_custom_payload;
+
+enum ast_custom_payload_type {
+       /*! Custom SIP INFO payload type, used only in the sip channel driver. */
+       AST_CUSTOM_SIP_INFO,
+};
+
+/*!
+ * \brief returns the type of payload the custom payload represents
+ *
+ * \retval payload type, on success
+ * \retval -1, on failure
+ */
+enum ast_custom_payload_type ast_custom_payload_type(struct ast_custom_payload *type);
+
+/*!
+ * \brief returns the length of a custom payload
+ *
+ * \retval len on success
+ * \retval -1 on failure
+ */
+size_t ast_custom_payload_len(struct ast_custom_payload *type);
+
+/*!
+ * \brief Encodes and allocates a sip info custom payload type
+ *
+ * \retval encoded custom payload on success
+ * \retval NULL on failure.
+ */
+struct ast_custom_payload *ast_custom_payload_sipinfo_encode(struct ast_variable *headers,
+       const char *content_type,
+       const char *content,
+       const char *useragent_filter);
+
+/*!
+ * \brief Decodes a sip info custom payload type, returns results in parameters.
+ * 
+ * \note This is the reverse of the encode function.  Pass in a payload, get the headers
+ * content type and content variables back out.  Make sure to free all the variables
+ * this function returns.
+ *
+ * \retval 0, variables allocated and returned in output parameters
+ * \retval -1, failure no variables were allocated.
+ */
+int ast_custom_payload_sipinfo_decode(struct ast_custom_payload *pl,
+       struct ast_variable **headers,
+       char **content_type,
+       char **content,
+       char **useragent_filter);
+
+#endif
index 073d67bc6b9a14756fb36d8cd387e68a00533069..9401961a74d953e01c6f84957ee7a2e1f89dc703 100644 (file)
@@ -54,8 +54,11 @@ 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 */
@@ -283,8 +286,12 @@ enum ast_event_ie_type {
        AST_EVENT_IE_CHALLENGE           = 0x0032,
        AST_EVENT_IE_RESPONSE            = 0x0033,
        AST_EVENT_IE_EXPECTED_RESPONSE   = 0x0034,
+       AST_EVENT_IE_PRESENCE_PROVIDER   = 0x0035,
+       AST_EVENT_IE_PRESENCE_STATE      = 0x0036,
+       AST_EVENT_IE_PRESENCE_SUBTYPE    = 0x0037,
+       AST_EVENT_IE_PRESENCE_MESSAGE    = 0x0038,
        /*! \brief Must be the last IE value +1 */
-       AST_EVENT_IE_TOTAL               = 0x0035,
+       AST_EVENT_IE_TOTAL               = 0x0039,
 };
 
 /*!
index 69de811652abc41ccc6ace7b44e7fd8ddc916f29..8e4d8469164a85c270d4bd0df894ba1eb14fc7ba 100644 (file)
@@ -48,7 +48,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
@@ -160,6 +174,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 6dcabda1f5c7414372fb803874a43f262c5f2ac1..b9515f85896b790fb20ca2cfd4fd7ea52e6a1e58 100644 (file)
@@ -334,6 +334,7 @@ enum ast_control_frame_type {
        AST_CONTROL_READ_ACTION = 27,   /*!< Tell ast_read to take a specific action */
        AST_CONTROL_AOC = 28,                   /*!< Advice of Charge with encoded generic AOC payload */
        AST_CONTROL_END_OF_Q = 29,              /*!< Indicate that this position was the end of the channel queue for a softhangup. */
+       AST_CONTROL_CUSTOM = 200,               /*!< Indicate a custom channel driver specific payload.  Look in custom_control_frame.h for how to define and use this frame. */
        AST_CONTROL_INCOMPLETE = 30,    /*!< Indication that the extension dialed is incomplete */
        AST_CONTROL_UPDATE_RTP_PEER = 31, /*!< Interrupt the bridge and have it update the peer */
 };
index 6d7139ed8c4b9e2c56d54a268d90a6e5c3ee3e7e..8c627466cccfc410a68cc7404b6271f72942d363 100644 (file)
@@ -157,6 +157,7 @@ struct aji_client {
        char name_space[256];
        char sid[10]; /* Session ID */
        char mid[6]; /* Message ID */
+       char context[AST_MAX_CONTEXT];
        iksid *jid;
        iksparser *p;
        iksfilter *f;
@@ -179,6 +180,7 @@ struct aji_client {
        int message_timeout;
        int authorized;
        int distribute_events;
+       int send_to_dialplan;
        struct ast_flags flags;
        int component; /* 0 client,  1 component */
        struct aji_buddy_container buddies;
index f0168ef4d2e16551a5b321eff59bf33bd72e5f14..3b77a8caebadafa9a8749314caa3a38e831f22dd 100644 (file)
@@ -86,6 +86,7 @@
 #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 */
+#define EVENT_FLAG_MESSAGE             (1 << 30) /* MESSAGE events. */
 /*@} */
 
 /*! \brief Export manager structures */
diff --git a/include/asterisk/message.h b/include/asterisk/message.h
new file mode 100644 (file)
index 0000000..8564a6f
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * Russell Bryant <russell@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 Out-of-call text message support
+ *
+ * \author Russell Bryant <russell@digium.com>
+ *
+ * The purpose of this API is to provide support for text messages that
+ * are not session based.  The messages are passed into the Asterisk core
+ * to be routed through the dialplan and potentially sent back out through
+ * a message technology that has been registered through this API.
+ */
+
+#ifndef __AST_MESSAGE_H__
+#define __AST_MESSAGE_H__
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+/*!
+ * \brief A text message.
+ *
+ * This is an opaque type that represents a text message.
+ */
+struct ast_msg;
+
+/*!
+ * \brief A message technology
+ *
+ * A message technology is capable of transmitting text messages.
+ */
+struct ast_msg_tech {
+        /*!
+         * \brief Name of this message technology
+         *
+         * This is the name that comes at the beginning of a URI for messages
+         * that should be sent to this message technology implementation.
+         * For example, messages sent to "xmpp:rbryant@digium.com" would be
+         * passed to the ast_msg_tech with a name of "xmpp".
+         */
+        const char * const name;
+        /*!
+         * \brief Send a message.
+        *
+        * \param msg the message to send
+        * \param to the URI of where the message is being sent
+        * \param from the URI of where the message was sent from
+        *
+        * The fields of the ast_msg are guaranteed not to change during the
+        * duration of this function call.
+        *
+        * \retval 0 success
+        * \retval non-zero failure
+         */
+        int (* const msg_send)(const struct ast_msg *msg, const char *to, const char *from);
+};
+
+/*!
+ * \brief Register a message technology
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_msg_tech_register(const struct ast_msg_tech *tech);
+
+/*!
+ * \brief Unregister a message technology.
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_msg_tech_unregister(const struct ast_msg_tech *tech);
+
+/*!
+ * \brief Allocate a message.
+ *
+ * Allocate a message for the purposes of passing it into the Asterisk core
+ * to be routed through the dialplan.  If ast_msg_queue() is not called, this
+ * message must be destroyed using ast_msg_destroy().  Otherwise, the message
+ * core code will take care of it.
+ *
+ * \return A message object. This function will return NULL if an allocation
+ *         error occurs.
+ */
+struct ast_msg *ast_msg_alloc(void);
+
+/*!
+ * \brief Destroy an ast_msg
+ *
+ * This should only be called on a message if it was not
+ * passed on to ast_msg_queue().
+ *
+ * \return NULL, always.
+ */
+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
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_msg_set_to(struct ast_msg *msg, const char *fmt, ...);
+
+/*!
+ * \brief Set the 'from' URI of a message
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_msg_set_from(struct ast_msg *msg, const char *fmt, ...);
+
+/*!
+ * \brief Set the 'body' text of a message (in UTF-8)
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_msg_set_body(struct ast_msg *msg, const char *fmt, ...);
+
+/*!
+ * \brief Set the dialplan context for this message
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_msg_set_context(struct ast_msg *msg, const char *fmt, ...);
+
+/*!
+ * \brief Set the dialplan extension for this message
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_msg_set_exten(struct ast_msg *msg, const char *fmt, ...);
+
+/*!
+ * \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
+ * \param value Value of variable to set
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+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
+ *       message var name while holding a pointer to the result of this function.
+ *
+ * \return The value associated with variable "name". NULL if variable not found.
+ */
+const char *ast_msg_get_var(struct ast_msg *msg, const char *name);
+
+/*!
+ * \brief Get the body of a 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. 
+ *
+ * \return The body of the messsage, encoded in UTF-8.
+ */
+const char *ast_msg_get_body(const struct ast_msg *msg);
+
+/*!
+ * \brief Queue a message for routing through the dialplan.
+ *
+ * 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
+ * \retval non-zero failure, message not sent to dialplan
+ */
+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;
+
+/*!
+ * \brief Create a new message variable iterator
+ * \param msg A message whose variables are to be iterated over
+ *
+ * \return An opaque pointer to the new iterator
+ */
+struct ast_msg_var_iterator *ast_msg_var_iterator_init(const struct ast_msg *msg);
+
+/*!
+ * \brief Get the next variable name and value that is set for sending outbound
+ * \param msg The message with the variables
+ * \param i An iterator created with ast_msg_var_iterator_init
+ * \param name A pointer to the name result pointer
+ * \param value A pointer to the value result pointer
+ *
+ * \retval 0 No more entries
+ * \retval 1 Valid entry
+ */
+int ast_msg_var_iterator_next(const struct ast_msg *msg, struct ast_msg_var_iterator *i, const char **name, const char **value);
+
+/*!
+ * \brief Destroy a message variable iterator
+ * \param i Iterator to be destroyed
+ */
+void ast_msg_var_iterator_destroy(struct ast_msg_var_iterator *i);
+
+/*!
+ * \brief Unref a message var from inside an iterator loop
+ */
+void ast_msg_var_unref_current(struct ast_msg_var_iterator *i);
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* __AST_MESSAGE_H__ */
index 623451ef5d36c920b4793fad8334a8ab85d6629d..d2d05c5b7b53a39ddd94bccf3f56c49697694e44 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"
@@ -75,8 +76,23 @@ 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)(char *context, char *id, 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);
@@ -401,6 +417,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
  *
diff --git a/include/asterisk/presencestate.h b/include/asterisk/presencestate.h
new file mode 100644 (file)
index 0000000..0304423
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * 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,
+};
+
+/*! \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 Notify the world that a presence provider state changed.
+ */
+int ast_presence_state_changed(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
index b48d2bfde45e464d28ae207cde0ab5585c061cab..7626b512eb0b7a8d5f68c5752c6fc27e636200f9 100644 (file)
@@ -273,18 +273,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)
@@ -294,6 +297,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)
@@ -309,6 +313,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)
 {
@@ -558,10 +584,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;
@@ -632,7 +664,11 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file,
                                ast_seekstream(chan->stream, 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) {
@@ -696,6 +732,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 f5f176bc4d51f9a56b72aa536ec53ee416bd368a..da32c84e136988a390cb0d5273b30a0dc65889fd 100644 (file)
@@ -135,6 +135,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"
@@ -3811,6 +3812,11 @@ int main(int argc, char *argv[])
        ast_xmldoc_load_documentation();
 #endif
 
+       if (ast_msg_init()) {
+               printf("%s", term_quit());
+               exit(1);
+       }
+
        /* initialize the data retrieval API */
        if (ast_data_init()) {
                printf ("%s", term_quit());
@@ -3851,6 +3857,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 273f2c9b347ad4ef9e793f41602361d3e42bfaf3..e21e4a8c80e252a81811675e2ee53ebaf6abd48b 100644 (file)
@@ -4295,6 +4295,7 @@ static int attribute_const is_visible_indication(enum ast_control_frame_type con
        case AST_CONTROL_CC:
        case AST_CONTROL_READ_ACTION:
        case AST_CONTROL_AOC:
+       case AST_CONTROL_CUSTOM:
        case AST_CONTROL_END_OF_Q:
        case AST_CONTROL_UPDATE_RTP_PEER:
                break;
@@ -4483,6 +4484,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
        case AST_CONTROL_CC:
        case AST_CONTROL_READ_ACTION:
        case AST_CONTROL_AOC:
+       case AST_CONTROL_CUSTOM:
        case AST_CONTROL_END_OF_Q:
        case AST_CONTROL_UPDATE_RTP_PEER:
                /* Nothing left to do for these. */
@@ -9537,3 +9539,8 @@ struct ast_channel *ast_channel_alloc(int needqueue, int state, const char *cid_
 
        return result;
 }
+
+void ast_channel_unlink(struct ast_channel *chan)
+{
+       ao2_unlink(channels, chan);
+}
index 1608e31f1b97484f1e568540a626d0da4dc9952b..bf7eb946a9247fccc993ddb4011d2db382f93922 100644 (file)
@@ -65,6 +65,9 @@ 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 {
        struct ast_comment *next;
@@ -2265,12 +2268,44 @@ 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];
        char table[256];
        struct ast_config_engine *loader = &text_file_engine;
-       struct ast_config *result; 
+       struct ast_config *result;
 
        /* The config file itself bumps include_level by 1 */
        if (cfg->max_include_level > 0 && cfg->include_level == cfg->max_include_level + 1) {
@@ -2297,10 +2332,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;
 }
@@ -2894,6 +2931,89 @@ static struct ast_cli_entry cli_config[] = {
        AST_CLI_DEFINE(handle_cli_config_list, "Show all files that have loaded a configuration file"),
 };
 
+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;
+}
+
+
 int register_config_cli(void)
 {
        ast_cli_register_multiple(cli_config, ARRAY_LEN(cli_config));
diff --git a/main/custom_control_frame.c b/main/custom_control_frame.c
new file mode 100644 (file)
index 0000000..b72ee5d
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2011, Digium, Inc.
+ *
+ * David Vossel <dvosse@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 Encode and Decode custom control frame payload types.
+ *
+ * \author David Vossel <dvossel@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+
+#include "asterisk/custom_control_frame.h"
+
+struct ast_custom_payload {
+       enum ast_custom_payload_type type;
+       /*! length of data portion only */
+       size_t datalen;
+       char *data;
+};
+
+enum ast_custom_payload_type ast_custom_payload_type(struct ast_custom_payload *type)
+{
+       return type->type;
+}
+
+size_t ast_custom_payload_len(struct ast_custom_payload *type)
+{
+       return type->datalen + sizeof(struct ast_custom_payload);
+}
+
+struct custom_sipinfo {
+       size_t num_headers;
+       int content_present;
+       int useragent_filter_present;
+       char *data;
+};
+
+struct ast_custom_payload *ast_custom_payload_sipinfo_encode(struct ast_variable *headers,
+       const char *content_type,
+       const char *content,
+       const char *useragent_filter)
+{
+       int num_headers = 0;
+       int content_present = 0;
+       int content_strlen = 0;
+       int content_type_strlen = 0;
+       int useragent_filter_present = 0;
+       int useragent_filter_len = 0;
+       size_t datalen = 0;
+       struct ast_variable *var;
+       struct ast_custom_payload *payload;
+       struct custom_sipinfo *sipinfo;
+       char *data;
+
+       datalen += sizeof(struct custom_sipinfo);
+
+       for (var = headers; var; var = var->next) {
+               datalen += strlen(var->name) + 1;
+               datalen += strlen(var->value) + 1;
+               num_headers++;
+       }
+
+       if (!ast_strlen_zero(content_type) && !ast_strlen_zero(content)) {
+               content_type_strlen = strlen(content_type);
+               content_strlen = strlen(content);
+               datalen += content_type_strlen + 1;
+               datalen += content_strlen + 1;
+               content_present = 1;
+       }
+
+       if (!ast_strlen_zero(useragent_filter)) {
+               useragent_filter_len = strlen(useragent_filter);
+               datalen += useragent_filter_len + 1;
+               useragent_filter_present = 1;
+       }
+
+       if (!(payload = ast_calloc(1, datalen + sizeof(*payload)))) {
+               return NULL;
+       }
+
+       payload->type = AST_CUSTOM_SIP_INFO;
+       payload->datalen = datalen;
+       payload->data = (char *) payload + sizeof(struct ast_custom_payload);
+       sipinfo = (struct custom_sipinfo *) payload->data;
+       sipinfo->num_headers = num_headers;
+       sipinfo->content_present = content_present;
+       sipinfo->useragent_filter_present = useragent_filter_present;
+       sipinfo->data = (char *) sipinfo + sizeof(struct custom_sipinfo);
+
+       /* store string buffers in payload data
+        * headers are put in first, followed by content type and then content body. */
+       data = sipinfo->data;
+
+       for (var = headers; var; var = var->next) {
+               int namelen = strlen(var->name);
+               int vallen = strlen(var->value);
+
+               /*! we already know we have enough room for each of these */
+               ast_copy_string(data, var->name, namelen+1);
+               data += namelen + 1; /* skip over the '\0' character */
+               ast_copy_string(data, var->value, vallen+1);
+               data += vallen + 1; /* skip over the '\0' character */
+       }
+
+       if (content_present) {
+               ast_copy_string(data, content_type, content_type_strlen+1);
+               data += content_type_strlen + 1;
+               ast_copy_string(data, content, content_strlen+1);
+               data += content_strlen + 1;
+       }
+
+       if (useragent_filter_present) {
+               ast_copy_string(data, useragent_filter, useragent_filter_len+1);
+       }
+
+       return payload;
+}
+
+int ast_custom_payload_sipinfo_decode(struct ast_custom_payload *pl,
+       struct ast_variable **headers,
+       char **content_type,
+       char **content,
+       char **useragent_filter)
+{
+       struct custom_sipinfo *sipinfo;
+       struct ast_variable *cur = NULL;
+       char *data;
+       int i;
+
+       *headers = NULL;
+       *content_type = NULL;
+       *content = NULL;
+       *useragent_filter = NULL;
+
+       if (pl->type != AST_CUSTOM_SIP_INFO) {
+               return -1;
+       }
+
+       sipinfo = (struct custom_sipinfo *) pl->data;
+       data = sipinfo->data;
+       for (i = 0; i < sipinfo->num_headers; i++) {
+               const char *name;
+               const char *value;
+
+               name = data;
+               data += strlen(name) + 1;
+               value = data;
+               data += strlen(value) + 1;
+
+               if (*headers) {
+                       if ((cur->next = ast_variable_new(name, value, ""))) {
+                               cur = cur->next;
+                       }
+               } else {
+                       *headers = cur = ast_variable_new(name, value, "");
+               }
+       }
+
+       if (sipinfo->content_present) {
+               *content_type = ast_strdup(data);
+               data += strlen(data) + 1;
+               *content = ast_strdup(data);
+               data += strlen(data) + 1;
+       }
+
+       if (sipinfo->useragent_filter_present) {
+               *useragent_filter = ast_strdup(data);
+       }
+       return 0;
+}
index a62e736376abc17971e36856fef9ef7ed4b49143..e22e30e883fdfbe47c88970214c1838884901d5d 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, },
+       },
+
 };
 
 /*!
@@ -1550,6 +1556,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 41fdfb088268d0cfac4444cef5014b686e33ddfe..a79aab2229ae38a18d095bbee8cc70fa092098b2 100644 (file)
@@ -49,6 +49,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/app.h"
 #include "asterisk/say.h"
 #include "asterisk/features.h"
+#include "asterisk/custom_control_frame.h"
 #include "asterisk/musiconhold.h"
 #include "asterisk/config.h"
 #include "asterisk/cli.h"
@@ -378,6 +379,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>
  ***/
 
 #define DEFAULT_PARK_TIME                                                      45000   /*!< ms */
@@ -6986,6 +6998,41 @@ 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
@@ -7002,6 +7049,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);
@@ -7018,6 +7066,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"
@@ -7026,7 +7075,8 @@ static int manager_parking_status(struct mansession *s, const struct message *m)
                                "\r\n",
                                curlot->name,
                                cur->parkingnum, cur->chan->name, 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(cur->chan->caller.id.number.valid, cur->chan->caller.id.number.str, ""),  /* XXX in other places it is <unknown> */
                                S_COR(cur->chan->caller.id.name.valid, cur->chan->caller.id.name.str, ""),
                                S_COR(cur->chan->connected.id.number.valid, cur->chan->connected.id.number.str, ""),    /* XXX in other places it is <unknown> */
@@ -8116,6 +8166,7 @@ int ast_features_init(void)
                res = ast_register_application2(parkcall, park_call_exec, NULL, NULL, NULL);
        if (!res) {
                ast_manager_register_xml("ParkedCalls", 0, manager_parking_status);
+               ast_manager_register_xml("Parkinglots", 0, manager_parkinglot_list);
                ast_manager_register_xml("Park", EVENT_FLAG_CALL, manager_park);
                ast_manager_register_xml("Bridge", EVENT_FLAG_CALL, action_bridge);
        }
index 18ef8ca944b990141c61303190b9d8c604e63a30..b71334498a2543b74dfcf5f898ca9cde7cc53365 100644 (file)
@@ -1177,11 +1177,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)
@@ -1197,6 +1204,11 @@ static int waitstream_core(struct ast_channel *c, const char *breakon,
        if (ast_test_flag(c, AST_FLAG_MASQ_NOSTREAM))
                orig_chan_name = ast_strdupa(c->name);
 
+       if (c->stream && cb) {
+               long ms_len = ast_tellstream(c->stream) / (ast_format_rate(c->stream->fmt->format) / 1000);
+               cb(c, ms_len, AST_WAITSTREAM_CB_START);
+       }
+
        while (c->stream) {
                int res;
                int ms;
@@ -1258,6 +1270,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;
@@ -1268,13 +1281,19 @@ static int waitstream_core(struct ast_channel *c, const char *breakon,
                                                } else {
                                                        ungetc(eoftest, c->stream->f);
                                                }
+                                               cb_val = AST_WAITSTREAM_CB_FASTFORWARD;
                                        } else if (strchr(reverse, res)) {
                                                ast_stream_rewind(c->stream, skip_ms);
+                                               cb_val = AST_WAITSTREAM_CB_REWIND;
                                        } else if (strchr(breakon, res)) {
                                                ast_frfree(fr);
                                                ast_clear_flag(c, AST_FLAG_END_DTMF_ONLY);
                                                return res;
-                                       }                                       
+                                       }
+                                       if (cb_val && cb) {
+                                               long ms_len = ast_tellstream(c->stream) / (ast_format_rate(c->stream->fmt->format) / 1000);
+                                               cb(c, ms_len, cb_val);
+                                       }
                                }
                                break;
                        case AST_FRAME_CONTROL:
@@ -1324,21 +1343,32 @@ static int waitstream_core(struct ast_channel *c, const char *breakon,
        return (err || c->_softhangup) ? -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)
@@ -1349,7 +1379,7 @@ int ast_waitstream_exten(struct ast_channel *c, const char *context)
        if (!context)
                context = c->context;
        return waitstream_core(c, NULL, NULL, NULL, 0,
-               -1, -1, context);
+               -1, -1, context, NULL /* no callback */);
 }
 
 /*
index 77ed06f1fa0718fd3391d06269e35d3b611a2235..da8fdba2be6efe28e9e6b6442831f953a339efc8 100644 (file)
@@ -1175,6 +1175,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" },
 };
@@ -4023,7 +4024,7 @@ static int action_originate(struct mansession *s, const struct message *m)
                format = 0;
                ast_parse_allow_disallow(NULL, &format, codecs, 1);
        }
-       if (!ast_strlen_zero(app)) {
+       if (!ast_strlen_zero(app) && s->session) {
                /* To run the System application (or anything else that goes to
                 * shell), you must have the additional System privilege */
                if (!(s->session->writeperm & EVENT_FLAG_SYSTEM)
@@ -5159,10 +5160,17 @@ int ast_manager_unregister(char *action)
        return 0;
 }
 
-static int manager_state_cb(char *context, char *exten, int 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);
diff --git a/main/message.c b/main/message.c
new file mode 100644 (file)
index 0000000..3c36268
--- /dev/null
@@ -0,0 +1,1288 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * Russell Bryant <russell@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 Out-of-call text message support
+ *
+ * \author Russell Bryant <russell@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+
+#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"
+#include "asterisk/taskprocessor.h"
+#include "asterisk/message.h"
+
+/*** DOCUMENTATION
+       <function name="MESSAGE" language="en_US">
+               <synopsis>
+                       Create a message or read fields from a message.
+               </synopsis>
+               <syntax argsep="/">
+                       <parameter name="argument" required="true">
+                       <para>Field of the message to get or set.</para>
+                       <enumlist>
+                               <enum name="to">
+                                       <para>Read-only.  The destination of the message.  When processing an
+                                       incoming message, this will be set to the destination listed as
+                                       the recipient of the message that was received by Asterisk.</para>
+                               </enum>
+                               <enum name="from">
+                                       <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="body">
+                                       <para>Read/Write.  The message body.  When processing an incoming
+                                       message, this includes the body of the message that Asterisk
+                                       received.  When MessageSend() is executed, the contents of this
+                                       field are used as the body of the outgoing message.  The body
+                                       will always be UTF-8.</para>
+                               </enum>
+                       </enumlist>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>This function will read from or write a value to a text message.
+                       It is used both to read the data out of an incoming message, as well as
+                       modify or create a message that will be sent outbound.</para>
+               </description>
+               <see-also>
+                       <ref type="application">MessageSend</ref>
+               </see-also>
+       </function>
+       <function name="MESSAGE_DATA" language="en_US">
+               <synopsis>
+                       Read or write custom data attached to a message.
+               </synopsis>
+               <syntax argsep="/">
+                       <parameter name="argument" required="true">
+                       <para>Field of the message to get or set.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>This function will read from or write a value to a text message.
+                       It is used both to read the data out of an incoming message, as well as
+                       modify a message that will be sent outbound.</para>
+                       <para>NOTE: If you want to set an outbound message to carry data in the
+                       current message, do Set(MESSAGE_DATA(key)=${MESSAGE_DATA(key)}).</para>
+               </description>
+               <see-also>
+                       <ref type="application">MessageSend</ref>
+               </see-also>
+       </function>
+       <application name="MessageSend" language="en_US">
+               <synopsis>
+                       Send a text message.
+               </synopsis>
+               <syntax>
+                       <parameter name="to" required="true">
+                               <para>A To URI for the message.</para>
+                       </parameter>
+                       <parameter name="from" required="false">
+                               <para>A From URI for the message if needed for the
+                               message technology being used to send this message.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Send a text message.  The body of the message that will be
+                       sent is what is currently set to <literal>MESSAGE(body)</literal>.</para>
+
+                       <para>This application sets the following channel variables:</para>
+                       <variablelist>
+                               <variable name="MESSAGE_SEND_STATUS">
+                                       <para>This is the time from dialing a channel until when it is disconnected.</para>
+                                       <value name="INVALID_PROTOCOL">
+                                               No handler for the technology part of the URI was found.
+                                       </value>
+                                       <value name="INVALID_URI">
+                                               The protocol handler reported that the URI was not valid.
+                                       </value>
+                                       <value name="SUCCESS">
+                                               Successfully passed on to the protocol handler, but delivery has not necessarily been guaranteed.
+                                       </value>
+                                       <value name="FAILURE">
+                                               The protocol handler reported that it was unabled to deliver the message for some reason.
+                                       </value>
+                               </variable>
+                       </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>The URI the message is from. Not required.</para>
+                       </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.  The Base64Body will be decoded before being sent out.</para>
+                       </parameter>
+                       <parameter name="Variable">
+                               <para>Message variable to set, multiple Variable: headers are allowed.</para>
+                       </parameter>
+               </syntax>
+       </manager>
+ ***/
+
+struct msg_data {
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(name);
+               AST_STRING_FIELD(value);
+       );
+       unsigned int send:1; /* Whether to send out on outbound messages */
+};
+
+AST_LIST_HEAD_NOLOCK(outhead, msg_data);
+
+/*!
+ * \brief A message.
+ *
+ * \todo Consider whether stringfields would be an appropriate optimization here.
+ */
+struct ast_msg {
+       struct ast_str *to;
+       struct ast_str *from;
+       struct ast_str *body;
+       struct ast_str *context;
+       struct ast_str *exten;
+       struct ao2_container *vars;
+};
+
+struct ast_msg_tech_holder {
+       const struct ast_msg_tech *tech;
+       /*! 
+        * \brief A rwlock for this object
+        *
+        * a read/write lock must be used to protect the wrapper instead
+        * of the ao2 lock. A rdlock must be held to read tech_holder->tech.
+        */
+       ast_rwlock_t tech_lock;
+};
+
+static struct ao2_container *msg_techs;
+
+static struct ast_taskprocessor *msg_q_tp;
+
+static const char app_msg_send[] = "MessageSend";
+
+static void msg_ds_destroy(void *data);
+
+static const struct ast_datastore_info msg_datastore = {
+       .type = "message",
+       .destroy = msg_ds_destroy,
+};
+
+static int msg_func_read(struct ast_channel *chan, const char *function,
+               char *data, char *buf, size_t len);
+static int msg_func_write(struct ast_channel *chan, const char *function,
+               char *data, const char *value);
+
+static struct ast_custom_function msg_function = {
+       .name = "MESSAGE",
+       .read = msg_func_read,
+       .write = msg_func_write,
+};
+
+static int msg_data_func_read(struct ast_channel *chan, const char *function,
+               char *data, char *buf, size_t len);
+static int msg_data_func_write(struct ast_channel *chan, const char *function,
+               char *data, const char *value);
+
+static struct ast_custom_function msg_data_function = {
+       .name = "MESSAGE_DATA",
+       .read = msg_data_func_read,
+       .write = msg_data_func_write,
+};
+
+static struct ast_frame *chan_msg_read(struct ast_channel *chan);
+static int chan_msg_write(struct ast_channel *chan, struct ast_frame *fr);
+static int chan_msg_indicate(struct ast_channel *chan, int condition,
+               const void *data, size_t datalen);
+static int chan_msg_send_digit_begin(struct ast_channel *chan, char digit);
+static int chan_msg_send_digit_end(struct ast_channel *chan, char digit,
+               unsigned int duration);
+
+/*!
+ * \internal
+ * \brief A bare minimum channel technology
+ *
+ * This will not be registered as we never want anything to try
+ * to create Message channels other than internally in this file.
+ */
+static const struct ast_channel_tech msg_chan_tech_hack = {
+       .type             = "Message",
+       .description      = "Internal Text Message Processing",
+       .read             = chan_msg_read,
+       .write            = chan_msg_write,
+       .indicate         = chan_msg_indicate,
+       .send_digit_begin = chan_msg_send_digit_begin,
+       .send_digit_end   = chan_msg_send_digit_end,
+};
+
+/*!
+ * \internal
+ * \brief ast_channel_tech read callback
+ *
+ * This should never be called.  However, we say that about chan_iax2's
+ * read callback, too, and it seems to randomly get called for some
+ * reason.  If it does, a simple NULL frame will suffice.
+ */
+static struct ast_frame *chan_msg_read(struct ast_channel *chan)
+{
+       return &ast_null_frame;
+}
+
+/*!
+ * \internal
+ * \brief ast_channel_tech write callback
+ *
+ * Throw all frames away.  We don't care about any of them.
+ */
+static int chan_msg_write(struct ast_channel *chan, struct ast_frame *fr)
+{
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief ast_channel_tech indicate callback
+ *
+ * The indicate callback is here just so it can return success.
+ * We don't want any callers of ast_indicate() to think something
+ * has failed.  We also don't want ast_indicate() itself to try
+ * to generate inband tones since we didn't tell it that we took
+ * care of it ourselves.
+ */
+static int chan_msg_indicate(struct ast_channel *chan, int condition,
+               const void *data, size_t datalen)
+{
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief ast_channel_tech send_digit_begin callback
+ *
+ * This is here so that just in case a digit comes at a message channel
+ * that the Asterisk core doesn't waste any time trying to generate
+ * inband DTMF in audio.  It's a waste of resources.
+ */
+static int chan_msg_send_digit_begin(struct ast_channel *chan, char digit)
+{
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief ast_channel_tech send_digit_end callback
+ *
+ * This is here so that just in case a digit comes at a message channel
+ * that the Asterisk core doesn't waste any time trying to generate
+ * inband DTMF in audio.  It's a waste of resources.
+ */
+static int chan_msg_send_digit_end(struct ast_channel *chan, char digit,
+               unsigned int duration)
+{
+       return 0;
+}
+
+static void msg_ds_destroy(void *data)
+{
+       struct ast_msg *msg = data;
+
+       ao2_ref(msg, -1);
+}
+
+static int msg_data_hash_fn(const void *obj, const int flags)
+{
+       const struct msg_data *data = obj;
+       return ast_str_case_hash(data->name);
+}
+
+static int msg_data_cmp_fn(void *obj, void *arg, int flags)
+{
+       const struct msg_data *one = obj, *two = arg;
+       return !strcasecmp(one->name, two->name) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static void msg_data_destructor(void *obj)
+{
+       struct msg_data *data = obj;
+       ast_string_field_free_memory(data);
+}
+
+static void msg_destructor(void *obj)
+{
+       struct ast_msg *msg = obj;
+
+       ast_free(msg->to);
+       msg->to = NULL;
+
+       ast_free(msg->from);
+       msg->from = NULL;
+
+       ast_free(msg->body);
+       msg->body = NULL;
+
+       ast_free(msg->context);
+       msg->context = NULL;
+
+       ast_free(msg->exten);
+       msg->exten = NULL;
+
+       ao2_ref(msg->vars, -1);
+}
+
+struct ast_msg *ast_msg_alloc(void)
+{
+       struct ast_msg *msg;
+
+       if (!(msg = ao2_alloc(sizeof(*msg), msg_destructor))) {
+               return NULL;
+       }
+
+       if (!(msg->to = ast_str_create(32))) {
+               ao2_ref(msg, -1);
+               return NULL;
+       }
+
+       if (!(msg->from = ast_str_create(32))) {
+               ao2_ref(msg, -1);
+               return NULL;
+       }
+
+       if (!(msg->body = ast_str_create(128))) {
+               ao2_ref(msg, -1);
+               return NULL;
+       }
+
+       if (!(msg->context = ast_str_create(16))) {
+               ao2_ref(msg, -1);
+               return NULL;
+       }
+
+       if (!(msg->exten = ast_str_create(16))) {
+               ao2_ref(msg, -1);
+               return NULL;
+       }
+
+       if (!(msg->vars = ao2_container_alloc(1, msg_data_hash_fn, msg_data_cmp_fn))) {
+               ao2_ref(msg, -1);
+               return NULL;
+       }
+
+       ast_str_set(&msg->context, 0, "default");
+
+       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);
+
+       return NULL;
+}
+
+int ast_msg_set_to(struct ast_msg *msg, const char *fmt, ...)
+{
+       va_list ap;
+       int res;
+
+       va_start(ap, fmt);
+       res = ast_str_set_va(&msg->to, 0, fmt, ap);
+       va_end(ap);
+
+       return res < 0 ? -1 : 0;
+}
+
+int ast_msg_set_from(struct ast_msg *msg, const char *fmt, ...)
+{
+       va_list ap;
+       int res;
+
+       va_start(ap, fmt);
+       res = ast_str_set_va(&msg->from, 0, fmt, ap);
+       va_end(ap);
+
+       return res < 0 ? -1 : 0;
+}
+
+int ast_msg_set_body(struct ast_msg *msg, const char *fmt, ...)
+{
+       va_list ap;
+       int res;
+
+       va_start(ap, fmt);
+       res = ast_str_set_va(&msg->body, 0, fmt, ap);
+       va_end(ap);
+
+       return res < 0 ? -1 : 0;
+}
+
+int ast_msg_set_context(struct ast_msg *msg, const char *fmt, ...)
+{
+       va_list ap;
+       int res;
+
+       va_start(ap, fmt);
+       res = ast_str_set_va(&msg->context, 0, fmt, ap);
+       va_end(ap);
+
+       return res < 0 ? -1 : 0;
+}
+
+int ast_msg_set_exten(struct ast_msg *msg, const char *fmt, ...)
+{
+       va_list ap;
+       int res;
+
+       va_start(ap, fmt);
+       res = ast_str_set_va(&msg->exten, 0, fmt, ap);
+       va_end(ap);
+
+       return res < 0 ? -1 : 0;
+}
+
+const char *ast_msg_get_body(const struct ast_msg *msg)
+{
+       return ast_str_buffer(msg->body);
+}
+
+static struct msg_data *msg_data_alloc(void)
+{
+       struct msg_data *data;
+
+       if (!(data = ao2_alloc(sizeof(*data), msg_data_destructor))) {
+               return NULL;
+       }
+
+       if (ast_string_field_init(data, 32)) {
+               ao2_ref(data, -1);
+               return NULL;
+       }
+
+       return data;
+}
+
+static struct msg_data *msg_data_find(struct ao2_container *vars, const char *name)
+{
+       struct msg_data tmp = {
+               .name = name,
+       };
+       return ao2_find(vars, &tmp, OBJ_POINTER);
+}
+
+static int msg_set_var_full(struct ast_msg *msg, const char *name, const char *value, unsigned int outbound)
+{
+       struct msg_data *data;
+
+       if (!(data = msg_data_find(msg->vars, name))) {
+               if (!(data = msg_data_alloc())) {
+                       return -1;
+               };
+
+               ast_string_field_set(data, name, name);
+               ast_string_field_set(data, value, value);
+               data->send = outbound;
+               ao2_link(msg->vars, data);
+       } else {
+               if (ast_strlen_zero(value)) {
+                       ao2_unlink(msg->vars, data);
+               } else {
+                       ast_string_field_set(data, value, value);
+                       data->send = outbound;
+               }
+       }
+
+       ao2_ref(data, -1);
+
+       return 0;
+}
+
+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);
+}
+
+int ast_msg_set_var(struct ast_msg *msg, const char *name, const char *value)
+{
+       return msg_set_var_full(msg, name, value, 0);
+}
+
+const char *ast_msg_get_var(struct ast_msg *msg, const char *name)
+{
+       struct msg_data *data;
+       const char *val = NULL;
+
+       if (!(data = msg_data_find(msg->vars, name))) {
+               return NULL;
+       }
+
+       /* Yep, this definitely looks like val would be a dangling pointer
+        * after the ref count is decremented.  As long as the message structure
+        * is used in a thread safe manner, this will not be the case though.
+        * The ast_msg holds a reference to this object in the msg->vars container. */
+       val = data->value;
+       ao2_ref(data, -1);
+
+       return val;
+}
+
+struct ast_msg_var_iterator {
+       struct ao2_iterator i;
+       struct msg_data *current_used;
+};
+
+struct ast_msg_var_iterator *ast_msg_var_iterator_init(const struct ast_msg *msg)
+{
+       struct ast_msg_var_iterator *i;
+       if (!(i = ast_calloc(1, sizeof(*i)))) {
+               return NULL;
+       }
+
+       i->i = ao2_iterator_init(msg->vars, 0);
+
+       return i;
+}
+
+int ast_msg_var_iterator_next(const struct ast_msg *msg, struct ast_msg_var_iterator *i, const char **name, const char **value)
+{
+       struct msg_data *data;
+
+       /* Skip any that aren't marked for sending out */
+       while ((data = ao2_iterator_next(&i->i)) && !data->send) {
+               ao2_ref(data, -1);
+       }
+
+       if (!data) {
+               return 0;
+       }
+
+       if (data->send) {
+               *name = data->name;
+               *value = data->value;
+       }
+
+       /* Leave the refcount to be cleaned up by the caller with
+        * ast_msg_var_unref_current after they finish with the pointers to the data */
+       i->current_used = data;
+
+       return 1;
+}
+
+void ast_msg_var_unref_current(struct ast_msg_var_iterator *i) {
+       if (i->current_used) {
+               ao2_ref(i->current_used, -1);
+       }
+       i->current_used = NULL;
+}
+
+void ast_msg_var_iterator_destroy(struct ast_msg_var_iterator *i)
+{
+       ao2_iterator_destroy(&i->i);
+       ast_free(i);
+}
+
+static struct ast_channel *create_msg_q_chan(void)
+{
+       struct ast_channel *chan;
+       struct ast_datastore *ds;
+
+       chan = ast_channel_alloc(1, AST_STATE_UP,
+                       NULL, NULL, NULL,
+                       NULL, NULL, NULL, 0,
+                       "%s", "Message/ast_msg_queue");
+
+       if (!chan) {
+               return NULL;
+       }
+
+       ast_channel_unlink(chan);
+
+       chan->tech = &msg_chan_tech_hack;
+
+       if (!(ds = ast_datastore_alloc(&msg_datastore, NULL))) {
+               ast_hangup(chan);
+               return NULL;
+       }
+
+       ast_channel_lock(chan);
+       ast_channel_datastore_add(chan, ds);
+       ast_channel_unlock(chan);
+
+       return chan;
+}
+
+/*!
+ * \internal
+ * \brief Run the dialplan for message processing
+ *
+ * \pre The message has already been set up on the msg datastore
+ *      on this channel.
+ */
+static void msg_route(struct ast_channel *chan, struct ast_msg *msg)
+{
+       struct ast_pbx_args pbx_args;
+
+       ast_explicit_goto(chan, ast_str_buffer(msg->context), AS_OR(msg->exten, "s"), 1);
+
+       memset(&pbx_args, 0, sizeof(pbx_args));
+       pbx_args.no_hangup_chan = 1,
+       ast_pbx_run_args(chan, &pbx_args);
+}
+
+/*!
+ * \internal
+ * \brief Clean up ast_channel after each message
+ *
+ * Reset various bits of state after routing each message so the same ast_channel
+ * can just be reused.
+ */
+static void chan_cleanup(struct ast_channel *chan)
+{
+       struct ast_datastore *msg_ds, *ds;
+       struct varshead *headp;
+       struct ast_var_t *vardata;
+
+       ast_channel_lock(chan);
+
+       /*
+        * Remove the msg datastore.  Free its data but keep around the datastore
+        * object and just reuse it.
+        */
+       if ((msg_ds = ast_channel_datastore_find(chan, &msg_datastore, NULL)) && msg_ds->data) {
+               ast_channel_datastore_remove(chan, msg_ds);
+               ao2_ref(msg_ds->data, -1);
+               msg_ds->data = NULL;
+       }
+
+       /*
+        * Destroy all other datastores.
+        */
+       while ((ds = AST_LIST_REMOVE_HEAD(&chan->datastores, entry))) {
+               ast_datastore_free(ds);
+       }
+
+       /*
+        * Destroy all channel variables.
+        */
+       headp = &chan->varshead;
+       while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries))) {
+               ast_var_delete(vardata);
+       }
+
+       /*
+        * Restore msg datastore.
+        */
+       if (msg_ds) {
+               ast_channel_datastore_add(chan, msg_ds);
+       }
+
+       ast_channel_unlock(chan);
+}
+
+AST_THREADSTORAGE(msg_q_chan);
+
+/*!
+ * \internal
+ * \brief Message queue task processor callback
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note Even though this returns a value, the taskprocessor code ignores the value.
+ */
+static int msg_q_cb(void *data)
+{
+       struct ast_msg *msg = data;
+       struct ast_channel **chan_p, *chan;
+       struct ast_datastore *ds;
+
+       if (!(chan_p = ast_threadstorage_get(&msg_q_chan, sizeof(struct ast_channel *)))) {
+               return -1;
+       }
+       if (!*chan_p) {
+               if (!(*chan_p = create_msg_q_chan())) {
+                       return -1;
+               }
+       }
+       chan = *chan_p;
+
+       ast_channel_lock(chan);
+       if (!(ds = ast_channel_datastore_find(chan, &msg_datastore, NULL))) {
+               ast_channel_unlock(chan);
+               return -1;
+       }
+       ao2_ref(msg, +1);
+       ds->data = msg;
+       ast_channel_unlock(chan);
+
+       msg_route(chan, msg);
+       chan_cleanup(chan);
+
+       ao2_ref(msg, -1);
+
+       return 0;
+}
+
+int ast_msg_queue(struct ast_msg *msg)
+{
+       int res;
+
+       res = ast_taskprocessor_push(msg_q_tp, msg_q_cb, msg);
+       if (res == -1) {
+               ao2_ref(msg, -1);
+       }
+
+       return res;
+}
+
+/*!
+ * \internal
+ * \brief Find or create a message datastore on a channel
+ * 
+ * \pre chan is locked
+ *
+ * \param chan the relevant channel
+ *
+ * \return the channel's message datastore, or NULL on error
+ */
+static struct ast_datastore *msg_datastore_find_or_create(struct ast_channel *chan)
+{
+       struct ast_datastore *ds;
+
+       if ((ds = ast_channel_datastore_find(chan, &msg_datastore, NULL))) {
+               return ds;
+       }
+
+       if (!(ds = ast_datastore_alloc(&msg_datastore, NULL))) {
+               return NULL;
+       }
+
+       if (!(ds->data = ast_msg_alloc())) {
+               ast_datastore_free(ds);
+               return NULL;
+       }
+
+       ast_channel_datastore_add(chan, ds);
+
+       return ds;
+}
+
+static int msg_func_read(struct ast_channel *chan, const char *function,
+               char *data, char *buf, size_t len)
+{
+       struct ast_datastore *ds;
+       struct ast_msg *msg;
+
+       ast_channel_lock(chan);
+
+       if (!(ds = ast_channel_datastore_find(chan, &msg_datastore, NULL))) {
+               ast_channel_unlock(chan);
+               ast_log(LOG_ERROR, "No MESSAGE data found on the channel to read.\n");
+               return -1;
+       }
+
+       msg = ds->data;
+       ao2_ref(msg, +1);
+       ast_channel_unlock(chan);
+
+       ao2_lock(msg);
+
+       if (!strcasecmp(data, "to")) {
+               ast_copy_string(buf, ast_str_buffer(msg->to), len);
+       } else if (!strcasecmp(data, "from")) {
+               ast_copy_string(buf, ast_str_buffer(msg->from), len);
+       } else if (!strcasecmp(data, "body")) {
+               ast_copy_string(buf, ast_msg_get_body(msg), len);
+       } else {
+               ast_log(LOG_WARNING, "Invalid argument to MESSAGE(): '%s'\n", data);
+       }
+
+       ao2_unlock(msg);
+       ao2_ref(msg, -1);
+
+       return 0;
+}
+
+static int msg_func_write(struct ast_channel *chan, const char *function,
+               char *data, const char *value)
+{
+       struct ast_datastore *ds;
+       struct ast_msg *msg;
+
+       ast_channel_lock(chan);
+
+       if (!(ds = msg_datastore_find_or_create(chan))) {
+               ast_channel_unlock(chan);
+               return -1;
+       }
+
+       msg = ds->data;
+       ao2_ref(msg, +1);
+       ast_channel_unlock(chan);
+
+       ao2_lock(msg);
+
+       if (!strcasecmp(data, "to")) {
+               ast_msg_set_to(msg, "%s", value);
+       } else if (!strcasecmp(data, "from")) {
+               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 *data;
+                       struct ao2_iterator iter = ao2_iterator_init(msg->vars, 0);
+
+                       while ((data= ao2_iterator_next(&iter))) {
+                               data->send = outbound;
+                               ao2_ref(data, -1);
+                       }
+                       ao2_iterator_destroy(&iter);
+               }
+
+       } else {
+               ast_log(LOG_WARNING, "'%s' is not a valid write argument.\n", data);
+       }
+
+       ao2_unlock(msg);
+       ao2_ref(msg, -1);
+
+       return 0;
+}
+
+static int msg_data_func_read(struct ast_channel *chan, const char *function,
+               char *data, char *buf, size_t len)
+{
+       struct ast_datastore *ds;
+       struct ast_msg *msg;
+       const char *val;
+
+       ast_channel_lock(chan);
+
+       if (!(ds = ast_channel_datastore_find(chan, &msg_datastore, NULL))) {
+               ast_channel_unlock(chan);
+               ast_log(LOG_ERROR, "No MESSAGE data found on the channel to read.\n");
+               return -1;
+       }
+
+       msg = ds->data;
+       ao2_ref(msg, +1);
+       ast_channel_unlock(chan);
+
+       ao2_lock(msg);
+
+       if ((val = ast_msg_get_var(msg, data))) {
+               ast_copy_string(buf, val, len);
+       }
+
+       ao2_unlock(msg);
+       ao2_ref(msg, -1);
+
+       return 0;
+}
+
+static int msg_data_func_write(struct ast_channel *chan, const char *function,
+               char *data, const char *value)
+{
+       struct ast_datastore *ds;
+       struct ast_msg *msg;
+
+       ast_channel_lock(chan);
+
+       if (!(ds = msg_datastore_find_or_create(chan))) {
+               ast_channel_unlock(chan);
+               return -1;
+       }
+
+       msg = ds->data;
+       ao2_ref(msg, +1);
+       ast_channel_unlock(chan);
+
+       ao2_lock(msg);
+
+       ast_msg_set_var_outbound(msg, data, value);
+
+       ao2_unlock(msg);
+       ao2_ref(msg, -1);
+
+       return 0;
+}
+static int msg_tech_hash(const void *obj, const int flags)
+{
+       struct ast_msg_tech_holder *tech_holder = (struct ast_msg_tech_holder *) obj;
+       int res = 0;
+
+       ast_rwlock_rdlock(&tech_holder->tech_lock);
+       if (tech_holder->tech) {
+               res = ast_str_case_hash(tech_holder->tech->name);
+       }
+       ast_rwlock_unlock(&tech_holder->tech_lock);
+
+       return res;
+}
+
+static int msg_tech_cmp(void *obj, void *arg, int flags)
+{
+       struct ast_msg_tech_holder *tech_holder = obj;
+       const struct ast_msg_tech_holder *tech_holder2 = arg;
+       int res = 1;
+
+       ast_rwlock_rdlock(&tech_holder->tech_lock);
+       /*
+        * tech_holder2 is a temporary fake tech_holder.
+        */
+       if (tech_holder->tech) {
+               res = strcasecmp(tech_holder->tech->name, tech_holder2->tech->name) ? 0 : CMP_MATCH | CMP_STOP;
+       }
+       ast_rwlock_unlock(&tech_holder->tech_lock);
+
+       return res;
+}
+
+/*!
+ * \internal
+ * \brief MessageSend() application
+ */
+static int msg_send_exec(struct ast_channel *chan, const char *data)
+{
+       struct ast_datastore *ds;
+       struct ast_msg *msg;
+       char *tech_name;
+       struct ast_msg_tech_holder *tech_holder = NULL;
+       char *parse;
+       int res = -1;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(to);
+               AST_APP_ARG(from);
+       );
+
+       if (ast_strlen_zero(data)) {
+               ast_log(LOG_WARNING, "An argument is required to MessageSend()\n");
+               pbx_builtin_setvar_helper(chan, "MESSAGE_SEND_STATUS", "INVALID_URI");
+               return 0;
+       }
+
+       parse = ast_strdupa(data);
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       if (ast_strlen_zero(args.to)) {
+               ast_log(LOG_WARNING, "A 'to' URI is required for MessageSend()\n");
+               pbx_builtin_setvar_helper(chan, "MESSAGE_SEND_STATUS", "INVALID_URI");
+               return 0;
+       }
+
+       ast_channel_lock(chan);
+
+       if (!(ds = ast_channel_datastore_find(chan, &msg_datastore, NULL))) {
+               ast_channel_unlock(chan);
+               ast_log(LOG_WARNING, "No message data found on channel to send.\n");
+               pbx_builtin_setvar_helper(chan, "MESSAGE_SEND_STATUS", "FAILURE");
+               return 0;
+       }
+
+       msg = ds->data;
+       ao2_ref(msg, +1);
+       ast_channel_unlock(chan);
+
+       tech_name = ast_strdupa(args.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) {
+               ast_log(LOG_WARNING, "No message technology '%s' found.\n", tech_name);
+               pbx_builtin_setvar_helper(chan, "MESSAGE_SEND_STATUS", "INVALID_PROTOCOL");
+               goto exit_cleanup;
+       }
+
+       /*
+        * The message lock is held here to safely allow the technology
+        * implementation to access the message fields without worrying
+        * that they could change.
+        */
+       ao2_lock(msg);
+       ast_rwlock_rdlock(&tech_holder->tech_lock);
+       if (tech_holder->tech) {
+               res = tech_holder->tech->msg_send(msg, S_OR(args.to, ""),
+                                                       S_OR(args.from, ""));
+       }
+       ast_rwlock_unlock(&tech_holder->tech_lock);
+       ao2_unlock(msg);
+
+       pbx_builtin_setvar_helper(chan, "MESSAGE_SEND_STATUS", res ? "FAILURE" : "SUCCESS");
+
+exit_cleanup:
+       if (tech_holder) {
+               ao2_ref(tech_holder, -1);
+               tech_holder = NULL;
+       }
+
+       ao2_ref(msg, -1);
+
+       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 = {
+               .tech = tech,
+       };
+       struct ast_msg_tech_holder *tech_holder;
+
+       if ((tech_holder = ao2_find(msg_techs, &tmp_tech_holder, OBJ_POINTER))) {
+               ao2_ref(tech_holder, -1);
+               ast_log(LOG_ERROR, "Message technology already registered for '%s'\n",
+                               tech->name);
+               return -1;
+       }
+
+       if (!(tech_holder = ao2_alloc(sizeof(*tech_holder), NULL))) {
+               return -1;
+       }
+
+       ast_rwlock_init(&tech_holder->tech_lock);
+       tech_holder->tech = tech;
+
+       ao2_link(msg_techs, tech_holder);
+
+       ao2_ref(tech_holder, -1);
+       tech_holder = NULL;
+
+       ast_verb(3, "Message technology handler '%s' registered.\n", tech->name);
+
+       return 0;
+}
+
+int ast_msg_tech_unregister(const struct ast_msg_tech *tech)
+{
+       struct ast_msg_tech_holder tmp_tech_holder = {
+               .tech = tech,
+       };
+       struct ast_msg_tech_holder *tech_holder;
+
+       tech_holder = ao2_find(msg_techs, &tmp_tech_holder, OBJ_POINTER | OBJ_UNLINK);
+
+       if (!tech_holder) {
+               ast_log(LOG_ERROR, "No '%s' message technology found.\n", tech->name);
+               return -1;
+       }
+
+       ast_rwlock_wrlock(&tech_holder->tech_lock);
+       tech_holder->tech = NULL;
+       ast_rwlock_unlock(&tech_holder->tech_lock);
+
+       ao2_ref(tech_holder, -1);
+       tech_holder = NULL;
+
+       ast_verb(3, "Message technology handler '%s' unregistered.\n", tech->name);
+
+       return 0;
+}
+
+/*
+ * \internal
+ * \brief Initialize stuff during Asterisk startup.
+ *
+ * Cleanup isn't a big deal in this function.  If we return non-zero,
+ * Asterisk is going to exit.
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_msg_init(void)
+{
+       int res;
+
+       msg_q_tp = ast_taskprocessor_get("ast_msg_queue", TPS_REF_DEFAULT);
+       if (!msg_q_tp) {
+               return -1;
+       }
+
+       msg_techs = ao2_container_alloc(17, msg_tech_hash, msg_tech_cmp);
+       if (!msg_techs) {
+               return -1;
+       }
+
+       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("MessageSend", EVENT_FLAG_MESSAGE, action_messagesend);
+
+       return res;
+}
index 44b4a3f92195a6a3ebf89a2e38b4c1dcba357e9b..4457dead7db2df2519a4d6c9644a5e520d94b54c 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. */
 };
@@ -973,6 +982,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];
@@ -1143,6 +1159,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;
@@ -4331,6 +4349,39 @@ 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++;
+       }
+       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)
 {
@@ -4339,7 +4390,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, "&"))) {
@@ -4397,6 +4448,195 @@ 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;
+
+       if (!e || !hint_app) {
+               return -1;
+       }
+
+       ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(e));
+       presence_provider = parse_hint_presence(hint_app);
+
+       if (ast_strlen_zero(presence_provider)) {
+               return -1;
+       }
+
+       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;
+               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? */
+               ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(hint->exten));
+               parse = parse_hint_presence(hint_app);
+               if (ast_strlen_zero(parse)) {
+                       /* The hint does not monitor presence at all. */
+                       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;
@@ -4429,7 +4669,8 @@ static int handle_statechange(void *datap)
 
                /* Does this hint monitor the device that changed state? */
                ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(hint->exten));
-               parse = ast_str_buffer(hint_app);
+               parse = parse_hint_device(hint_app);
+
                while ((cur = strsep(&parse, "&"))) {
                        if (!strcasecmp(cur, sc->dev)) {
                                /* The hint monitors the device. */
@@ -4472,14 +4713,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);
        }
@@ -4651,7 +4902,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;
@@ -4686,15 +4936,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 */
-                       /* NOTE: The casts will not be needed for v1.10 and later */
-                       state_cb->change_cb((char *) context_name, (char *) 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 */
@@ -4735,6 +4991,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;
@@ -4758,6 +5017,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);
@@ -7243,6 +7508,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];
 };
@@ -7442,6 +7711,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);
                }
@@ -7495,8 +7771,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);
                }
        }
@@ -7511,11 +7794,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);
        }
 
@@ -10210,6 +10499,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;
@@ -10224,7 +10558,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);
        }
 }
@@ -10256,6 +10590,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)) {
@@ -10282,7 +10619,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");
        }
 
@@ -10309,6 +10646,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..967276e
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+ * 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
+ */
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+#include "asterisk/utils.h"
+#include "asterisk/lock.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/presencestate.h"
+#include "asterisk/pbx.h"
+#include "asterisk/app.h"
+#include "asterisk/event.h"
+
+/*! \brief Device state strings for printing */
+static const struct {
+       const char *string;
+       enum ast_presence_state state;
+
+} state2string[] = {
+       { "not_set", AST_PRESENCE_NOT_SET},
+       { "unavailable", AST_PRESENCE_UNAVAILABLE },
+       { "available", AST_PRESENCE_AVAILABLE},
+       { "away", AST_PRESENCE_AWAY},
+       { "xa", AST_PRESENCE_XA},
+       { "chat", AST_PRESENCE_CHAT},
+       { "dnd", AST_PRESENCE_DND},
+};
+
+/*! \brief Flag for the queue */
+static ast_cond_t change_pending;
+
+struct state_change {
+       AST_LIST_ENTRY(state_change) list;
+       char provider[1];
+};
+
+/*! \brief  A presence state provider */
+struct presence_state_provider {
+       char label[40];
+       ast_presence_state_prov_cb_type callback;
+       AST_RWLIST_ENTRY(presence_state_provider) list;
+};
+
+/*! \brief A list of providers */
+static AST_RWLIST_HEAD_STATIC(presence_state_providers, presence_state_provider);
+
+/*! \brief The state change queue. State changes are queued
+       for processing by a separate thread */
+static AST_LIST_HEAD_STATIC(state_changes, state_change);
+
+/*! \brief The presence state change notification thread */
+static pthread_t change_thread = AST_PTHREADT_NULL;
+
+const char *ast_presence_state2str(enum ast_presence_state state)
+{
+       int i;
+       for (i = 0; i < ARRAY_LEN(state2string); i++) {
+               if (state == state2string[i].state) {
+                       return state2string[i].string;
+               }
+       }
+       return "";
+}
+
+enum ast_presence_state ast_presence_state_val(const char *val)
+{
+       int i;
+       for (i = 0; i < ARRAY_LEN(state2string); i++) {
+               if (!strcasecmp(val, state2string[i].string)) {
+                       return state2string[i].state;
+               }
+       }
+       return -1;
+}
+
+static enum ast_presence_state presence_state_cached(const char *presence_provider, char **subtype, char **message)
+{
+       enum ast_presence_state res = -1;
+       struct ast_event *event;
+       const char *_subtype;
+       const char *_message;
+
+       event = ast_event_get_cached(AST_EVENT_PRESENCE_STATE,
+               AST_EVENT_IE_PRESENCE_PROVIDER, AST_EVENT_IE_PLTYPE_STR, presence_provider,
+               AST_EVENT_IE_END);
+
+       if (!event) {
+               return res;
+       }
+
+       res = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
+       _subtype = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE);
+       _message = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE);
+
+       *subtype = !ast_strlen_zero(_subtype) ? ast_strdup(_subtype) : NULL;
+       *message = !ast_strlen_zero(_message) ? ast_strdup(_message) : NULL;
+       ast_event_destroy(event);
+
+       return res;
+}
+
+static enum ast_presence_state ast_presence_state_helper(const char *presence_provider, char **subtype, char **message, int check_cache)
+{
+       struct presence_state_provider *provider;
+       char *address;
+       char *label = ast_strdupa(presence_provider);
+       int res = -1;
+
+       if (check_cache) {
+               res = presence_state_cached(presence_provider, subtype, message);
+               if (res > 0) {
+                       return res;
+               }
+       }
+
+       if ((address = strchr(label, ':'))) {
+               *address = '\0';
+               address++;
+       } else {
+               ast_log(LOG_WARNING, "No label found for presence state provider: %s\n", presence_provider);
+               return res;
+       }
+
+       AST_RWLIST_RDLOCK(&presence_state_providers);
+       AST_RWLIST_TRAVERSE(&presence_state_providers, provider, list) {
+               ast_debug(5, "Checking provider %s with %s\n", provider->label, label);
+
+               if (!strcasecmp(provider->label, label)) {
+                       res = provider->callback(address, subtype, message);
+                       break;
+               }
+       }
+       AST_RWLIST_UNLOCK(&presence_state_providers);
+
+
+       return res;
+}
+
+enum ast_presence_state ast_presence_state(const char *presence_provider, char **subtype, char **message)
+{
+       return ast_presence_state_helper(presence_provider, subtype, message, 1);
+}
+
+int ast_presence_state_prov_add(const char *label, ast_presence_state_prov_cb_type callback)
+{
+       struct presence_state_provider *provider;
+
+       if (!callback || !(provider = ast_calloc(1, sizeof(*provider)))) {
+               return -1;
+       }
+
+       provider->callback = callback;
+       ast_copy_string(provider->label, label, sizeof(provider->label));
+
+       AST_RWLIST_WRLOCK(&presence_state_providers);
+       AST_RWLIST_INSERT_HEAD(&presence_state_providers, provider, list);
+       AST_RWLIST_UNLOCK(&presence_state_providers);
+
+       return 0;
+}
+int ast_presence_state_prov_del(const char *label)
+{
+       struct presence_state_provider *provider;
+       int res = -1;
+
+       AST_RWLIST_WRLOCK(&presence_state_providers);
+       AST_RWLIST_TRAVERSE_SAFE_BEGIN(&presence_state_providers, provider, list) {
+               if (!strcasecmp(provider->label, label)) {
+                       AST_RWLIST_REMOVE_CURRENT(list);
+                       ast_free(provider);
+                       res = 0;
+                       break;
+               }
+       }
+       AST_RWLIST_TRAVERSE_SAFE_END;
+       AST_RWLIST_UNLOCK(&presence_state_providers);
+
+       return res;
+}
+
+static void do_presence_state_change(const char *provider)
+{
+       struct ast_event *event;
+       enum ast_event_type event_type;
+       char *subtype = NULL;
+       char *message = NULL;
+       int state;
+
+       state = ast_presence_state_helper(provider, &subtype, &message, 0);
+
+       if (state < 0) {
+               return;
+       }
+
+       event_type = AST_EVENT_PRESENCE_STATE;
+
+       if (!(event = ast_event_new(event_type,
+                       AST_EVENT_IE_PRESENCE_PROVIDER, AST_EVENT_IE_PLTYPE_STR, provider,
+                       AST_EVENT_IE_PRESENCE_STATE, AST_EVENT_IE_PLTYPE_UINT, state,
+                       AST_EVENT_IE_PRESENCE_SUBTYPE, AST_EVENT_IE_PLTYPE_STR, S_OR(subtype, ""),
+                       AST_EVENT_IE_PRESENCE_MESSAGE, AST_EVENT_IE_PLTYPE_STR, S_OR(message, ""),
+                       AST_EVENT_IE_END))) {
+               return;
+       }
+
+       ast_event_queue_and_cache(event);
+       ast_free(subtype);
+       ast_free(message);
+}
+
+int ast_presence_state_changed(const char *presence_provider)
+{
+       struct state_change *change;
+
+       if ((change_thread == AST_PTHREADT_NULL) ||
+               !(change = ast_calloc(1, sizeof(*change) + strlen(presence_provider)))) {
+               do_presence_state_change(presence_provider);
+       } else {
+               strcpy(change->provider, presence_provider);
+               AST_LIST_LOCK(&state_changes);
+               AST_LIST_INSERT_TAIL(&state_changes, change, list);
+               ast_cond_signal(&change_pending);
+               AST_LIST_UNLOCK(&state_changes);
+       }
+       return 0;
+}
+
+/*! \brief Go through the presence state change queue and update changes in the presence state thread */
+static void *do_presence_changes(void *data)
+{
+       struct state_change *next, *current;
+
+       for (;;) {
+               /* This basically pops off any state change entries, resets the list back to NULL, unlocks, and processes each state change */
+               AST_LIST_LOCK(&state_changes);
+               if (AST_LIST_EMPTY(&state_changes))
+                       ast_cond_wait(&change_pending, &state_changes.lock);
+               next = AST_LIST_FIRST(&state_changes);
+               AST_LIST_HEAD_INIT_NOLOCK(&state_changes);
+               AST_LIST_UNLOCK(&state_changes);
+
+               /* Process each state change */
+               while ((current = next)) {
+                       next = AST_LIST_NEXT(current, list);
+                       do_presence_state_change(current->provider);
+                       ast_free(current);
+               }
+       }
+
+       return NULL;
+}
+
+int ast_presence_state_engine_init(void)
+{
+       ast_cond_init(&change_pending, NULL);
+       if (ast_pthread_create_background(&change_thread, NULL, do_presence_changes, NULL) < 0) {
+               ast_log(LOG_ERROR, "Unable to start presence state change thread.\n");
+               return -1;
+       }
+
+       return 0;
+}
+
index 5da2ee31ba19649ff40e00e88967231834e3718d..656f323f6c66db0822506f8b34c48428394fc4df 100644 (file)
@@ -61,6 +61,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/manager.h"
 #include "asterisk/event.h"
 #include "asterisk/devicestate.h"
+#include "asterisk/message.h"
 
 /*** DOCUMENTATION
        <application name="JabberSend" language="en_US">
@@ -372,6 +373,13 @@ static int aji_register_transport(void *data, ikspak *pak);
 static int aji_register_transport2(void *data, ikspak *pak);
 */
 
+static int msg_send_cb(const struct ast_msg *msg, const char *to, const char *from);
+
+static const struct ast_msg_tech msg_tech = {
+       .name = "xmpp",
+       .msg_send = msg_send_cb,
+};
+
 static struct ast_cli_entry aji_cli[] = {
        AST_CLI_DEFINE(aji_do_set_debug, "Enable/Disable Jabber debug"),
        AST_CLI_DEFINE(aji_do_reload, "Reload Jabber configuration"),
@@ -1140,6 +1148,44 @@ static int aji_send_exec(struct ast_channel *chan, const char *data)
        return 0;
 }
 
+static int msg_send_cb(const struct ast_msg *msg, const char *to, const char *from)
+{
+       struct aji_client *client;
+       char *sender;
+       char *dest;
+       int res;
+
+       sender = ast_strdupa(from);
+       strsep(&sender, ":");
+       dest = ast_strdupa(to);
+       strsep(&dest, ":");
+
+       if (ast_strlen_zero(sender)) {
+               ast_log(LOG_ERROR, "MESSAGE(from) of '%s' invalid for xmpp\n", from);
+               return -1;
+       }
+
+       if (!(client = ast_aji_get_client(sender))) {
+               ast_log(LOG_WARNING, "Could not finder account to send from as '%s'\n", sender);
+               return -1;
+       }
+
+
+       ast_debug(1, "Sending message to '%s' from '%s'\n", dest, client->name);
+
+       res = ast_aji_send_chat(client, dest, ast_msg_get_body(msg));
+       if (res != IKS_OK) {
+               ast_log(LOG_WARNING, "Failed to send xmpp message (%d).\n", res);
+       }
+
+       /* 
+        * XXX Reference leak here.  See note with ast_aji_get_client() about the problems
+        * with that function.
+        */
+
+       return res == IKS_OK ? 0 : -1;
+}
+
 /*!
 * \brief Application to send a message to a groupchat.
 * \param chan ast_channel
@@ -2228,6 +2274,7 @@ static void aji_handle_message(struct aji_client *client, ikspak *pak)
 {
        struct aji_message *insert;
        int deleted = 0;
+       struct ast_msg *msg;
 
        ast_debug(3, "client %s received a message\n", client->name);
 
@@ -2258,6 +2305,25 @@ static void aji_handle_message(struct aji_client *client, ikspak *pak)
                ast_debug(3, "message comes from %s\n", insert->from);
        }
 
+       if (client->send_to_dialplan) {
+               if ((msg = ast_msg_alloc())) {
+                       int res;
+
+                       res = ast_msg_set_to(msg, "xmpp:%s", client->user);
+                       res |= ast_msg_set_from(msg, "xmpp:%s", insert->from);
+                       res |= ast_msg_set_body(msg, "%s", insert->message);
+                       res |= ast_msg_set_context(msg, "%s", client->context);
+
+                       if (res) {
+                               ast_msg_destroy(msg);
+                       } else {
+                               ast_msg_queue(msg);
+                       }
+
+                       msg = NULL;
+               }
+       }
+
        /* remove old messages received from this JID
         * and insert received message */
        deleted = delete_old_messages(client, pak->from->partial);
@@ -4276,6 +4342,7 @@ static int aji_create_client(char *label, struct ast_variable *var, int debug)
        ASTOBJ_CONTAINER_MARKALL(&client->buddies);
        ast_copy_string(client->name, label, sizeof(client->name));
        ast_copy_string(client->mid, "aaaaa", sizeof(client->mid));
+       ast_copy_string(client->context, "default", sizeof(client->context));
 
        /* Set default values for the client object */
        client->debug = debug;
@@ -4293,6 +4360,7 @@ static int aji_create_client(char *label, struct ast_variable *var, int debug)
        ast_copy_string(client->statusmessage, "Online and Available", sizeof(client->statusmessage));
        client->priority = 0;
        client->status = IKS_SHOW_AVAILABLE;
+       client->send_to_dialplan = 0;
 
        if (flag) {
                client->authorized = 0;
@@ -4384,6 +4452,10 @@ static int aji_create_client(char *label, struct ast_variable *var, int debug)
                        } else {
                                ast_log(LOG_WARNING, "Unknown presence status: %s\n", var->value);
                        }
+               } else if (!strcasecmp(var->name, "context")) {
+                       ast_copy_string(client->context, var->value, sizeof(client->context));
+               } else if (!strcasecmp(var->name, "sendtodialplan")) {
+                       client->send_to_dialplan = ast_true(var->value) ? 1 : 0;
                }
        /* no transport support in this version */
        /*      else if (!strcasecmp(var->name, "transport"))
@@ -4591,6 +4663,13 @@ static int aji_load_config(int reload)
  * (without the resource string)
  * \param name label or JID
  * \return aji_client.
+ *
+ * XXX \bug This function leads to reference leaks all over the place.
+ *          ASTOBJ_CONTAINER_FIND() returns a reference, but if the
+ *          client is found via the traversal, no reference is returned.
+ *          None of the calling code releases references.  This code needs
+ *          to be changed to always return a reference, and all of the users
+ *          need to be fixed to release them.
  */
 struct aji_client *ast_aji_get_client(const char *name)
 {
@@ -4707,7 +4786,7 @@ static int aji_reload(int reload)
  */
 static int unload_module(void)
 {
-
+       ast_msg_tech_unregister(&msg_tech);
        ast_cli_unregister_multiple(aji_cli, ARRAY_LEN(aji_cli));
        ast_unregister_application(app_ajisend);
        ast_unregister_application(app_ajisendgroup);
@@ -4760,6 +4839,7 @@ static int load_module(void)
        ast_cli_register_multiple(aji_cli, ARRAY_LEN(aji_cli));
        ast_custom_function_register(&jabberstatus_function);
        ast_custom_function_register(&jabberreceive_function);
+       ast_msg_tech_register(&msg_tech);
 
        ast_mutex_init(&messagelock);
        ast_cond_init(&message_received_condition, NULL);
diff --git a/tests/test_config.c b/tests/test_config.c
new file mode 100644 (file)
index 0000000..512dde5
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2010, 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.
+ */
+
+/*!
+ * \file
+ * \brief Configuration unit tests
+ *
+ * \author Mark Michelson <mmichelson@digium.com>
+ *
+ */
+
+/*** MODULEINFO
+       <depend>TEST_FRAMEWORK</depend>
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
+
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+
+const char cat1[] = "Capitals";
+const char cat1varname1[] = "Germany";
+const char cat1varvalue1[] = "Berlin";
+const char cat1varname2[] = "China";
+const char cat1varvalue2[] = "Beijing";
+const char cat1varname3[] = "Canada";
+const char cat1varvalue3[] = "Ottawa";
+
+const char cat2[] = "Protagonists";
+const char cat2varname1[] = "1984";
+const char cat2varvalue1[] = "Winston Smith";
+const char cat2varname2[] = "Green Eggs And Ham";
+const char cat2varvalue2[] = "Sam I Am";
+const char cat2varname3[] = "The Kalevala";
+const char cat2varvalue3[] = "Vainamoinen";
+
+struct pair {
+       const char *name;
+       const char *val;
+};
+
+struct association {
+       const char *category;
+       struct pair vars[3];
+} categories [] = {
+       { cat1,
+               {
+                       { cat1varname1, cat1varvalue1 },
+                       { cat1varname2, cat1varvalue2 },
+                       { cat1varname3, cat1varvalue3 },
+               }
+       },
+       { cat2,
+               {
+                       { cat2varname1, cat2varvalue1 },
+                       { cat2varname2, cat2varvalue2 },
+                       { cat2varname3, cat2varvalue3 },
+               }
+       },
+};
+
+static struct ast_config *build_cfg(void)
+{
+       struct ast_config *cfg;
+       struct association *cat_iter;
+       struct pair *var_iter;
+       size_t i;
+       size_t j;
+
+       cfg = ast_config_new();
+       if (!cfg) {
+               goto fail;
+       }
+
+       for (i = 0; i < ARRAY_LEN(categories); ++i) {
+               struct ast_category *cat;
+               cat_iter = &categories[i];
+
+               cat = ast_category_new(cat_iter->category, "", 999999);
+               if (!cat) {
+                       goto fail;
+               }
+               ast_category_append(cfg, cat);
+
+               for (j = 0; j < ARRAY_LEN(cat_iter->vars); ++j) {
+                       struct ast_variable *var;
+                       var_iter = &cat_iter->vars[j];
+
+                       var = ast_variable_new(var_iter->name, var_iter->val, "");
+                       if (!var) {
+                               goto fail;
+                       }
+                       ast_variable_append(cat, var);
+               }
+       }
+
+       return cfg;
+
+fail:
+       ast_config_destroy(cfg);
+       return NULL;
+}
+
+AST_TEST_DEFINE(copy_config)
+{
+       enum ast_test_result_state res = AST_TEST_FAIL;
+       struct ast_config *cfg = NULL;
+       struct ast_config *copy = NULL;
+       const char *cat_iter = NULL;
+       size_t i;
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = "copy_config";
+               info->category = "/main/config/";
+               info->summary = "Test copying configuration";
+               info->description =
+                       "Ensure that variables and categories are copied correctly";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       cfg = build_cfg();
+       if (!cfg) {
+               goto out;
+       }
+
+       copy = ast_config_copy(cfg);
+       if (!copy) {
+               goto out;
+       }
+
+       /* Okay, let's see if the correct content is there */
+       for (i = 0; i < ARRAY_LEN(categories); ++i) {
+               struct ast_variable *var = NULL;
+               size_t j;
+               cat_iter = ast_category_browse(copy, cat_iter);
+               if (strcmp(cat_iter, categories[i].category)) {
+                       ast_log(LOG_ERROR, "Category name mismatch, %s does not match %s\n", cat_iter, categories[i].category);
+                       goto out;
+               }
+               for (j = 0; j < ARRAY_LEN(categories[i].vars); ++j) {
+                       var = var ? var->next : ast_variable_browse(copy, cat_iter);
+                       if (strcmp(var->name, categories[i].vars[j].name)) {
+                               ast_log(LOG_ERROR, "Variable name mismatch, %s does not match %s\n", var->name, categories[i].vars[j].name);
+                               goto out;
+                       }
+                       if (strcmp(var->value, categories[i].vars[j].val)) {
+                               ast_log(LOG_ERROR, "Variable value mismatch, %s does not match %s\n", var->value, categories[i].vars[j].val);
+                               goto out;
+                       }
+               }
+       }
+
+       res = AST_TEST_PASS;
+
+out:
+       ast_config_destroy(cfg);
+       ast_config_destroy(copy);
+       return res;
+}
+
+static int unload_module(void)
+{
+       AST_TEST_UNREGISTER(copy_config);
+       return 0;
+}
+
+static int load_module(void)
+{
+       AST_TEST_REGISTER(copy_config);
+       return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Config test module");
diff --git a/tests/test_custom_control.c b/tests/test_custom_control.c
new file mode 100644 (file)
index 0000000..605c129
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * 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 Test custom control frame encode and decode functions.
+ *
+ * \author David Vossel <dvossel@digium.com>
+ */
+
+/*** MODULEINFO
+       <depend>TEST_FRAMEWORK</depend>
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "asterisk/custom_control_frame.h"
+#include "asterisk/test.h"
+
+AST_TEST_DEFINE(sipinfo_encode_decode_test)
+{
+       struct ast_variable *headers = NULL;
+       struct ast_variable *var = NULL;
+       struct ast_variable **cur = NULL;
+       struct ast_custom_payload *pl = NULL;
+       char *out_content = NULL;
+       char *out_content_type = NULL;
+       char *useragent_filter = NULL;
+       int res = AST_TEST_FAIL;
+       struct {
+               int num_headers_set;
+               char *header1;
+               char *header_val1;
+               char *header2;
+               char *header_val2;
+               char *header3;
+               char *header_val3;
+               char *content;
+               char *content_type;
+               char *useragent_filter;
+       } test_cases[] = {
+               {
+                       3,
+                       "X-blah-header",
+                       "blah-value",
+                       "X-blah2-header",
+                       "blah2-value",
+                       "X-blah3-header",
+                       "blah3-value",
+                       "{ 'jsonjunk': hooray }",
+                       "application/json",
+                       NULL,
+               },
+               {
+                       2,
+                       "X-blah-header",
+                       "blah-value",
+                       "X-blah2-header",
+                       "blah2-value",
+                       NULL,
+                       NULL,
+                       "{ 'jsonjunk': hooray }",
+                       "application/json",
+                       NULL,
+               },
+               {
+                       2,
+                       "X-blah-header",
+                       "blah-value",
+                       "X-blah2-header",
+                       "blah2-value",
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+               },
+               {
+                       3,
+                       "X-blah-header",
+                       "blah-value",
+                       "X-blah2-header",
+                       "blah2-value",
+                       "X-blah3-header",
+                       "blah3-value",
+                       "{ 'jsonjunk': hooray }",
+                       "application/json",
+                       "Digium",
+               },
+               {
+                       2,
+                       "X-blah-header",
+                       "blah-value",
+                       "X-blah2-header",
+                       "blah2-value",
+                       NULL,
+                       NULL,
+                       "{ 'jsonjunk': hooray }",
+                       "application/json",
+                       "Digium",
+               },
+               {
+                       2,
+                       "X-blah-header",
+                       "blah-value",
+                       "X-blah2-header",
+                       "blah2-value",
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       "Digium",
+               },
+       };
+       int i;
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = "sipinfo_encode_decode_test";
+               info->category = "/main/custom_control_frame/";
+               info->summary = "encode and decode sip info custom control frames.";
+               info->description = "Verifies the encode and decode routines for AST_CONTROL_CUSTOM sip info payloads.";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       for (i = 0; i < ARRAY_LEN(test_cases); i++) {
+               int num_headers = 0;
+               cur = &headers;
+               if (test_cases[i].header1) {
+                       *cur = ast_variable_new(test_cases[i].header1, test_cases[i].header_val1, "");
+                       cur = &(*cur)->next;
+               }
+               if (test_cases[i].header2) {
+                       *cur = ast_variable_new(test_cases[i].header2, test_cases[i].header_val2, "");
+                       cur = &(*cur)->next;
+               }
+               if (test_cases[i].header3) {
+                       *cur = ast_variable_new(test_cases[i].header3, test_cases[i].header_val3, "");
+                       cur = &(*cur)->next;
+               }
+               if (!(pl = ast_custom_payload_sipinfo_encode(headers, test_cases[i].content, test_cases[i].content_type, test_cases[i].useragent_filter))) {
+                       goto sipinfo_cleanup;
+               }
+               ast_variables_destroy(headers);
+               headers = NULL;
+
+               if (ast_custom_payload_sipinfo_decode(pl, &headers, &out_content, &out_content_type, &useragent_filter)) {
+                       goto sipinfo_cleanup;
+               }
+
+               for (var = headers; var; var = var->next) {
+                       num_headers++;
+                       if (num_headers == 1) {
+                               if (strcmp(var->name, test_cases[i].header1) || strcmp(var->value, test_cases[i].header_val1)) {
+                                       goto sipinfo_cleanup;
+                               }
+                       } else if (num_headers == 2) {
+                               if (strcmp(var->name, test_cases[i].header2) || strcmp(var->value, test_cases[i].header_val2)) {
+                                       goto sipinfo_cleanup;
+                               }
+
+                       } else if (num_headers == 3) {
+                               if (strcmp(var->name, test_cases[i].header3) || strcmp(var->value, test_cases[i].header_val3)) {
+                                       goto sipinfo_cleanup;
+                               }
+                       }
+               }
+               if (num_headers != test_cases[i].num_headers_set) {
+                       goto sipinfo_cleanup;
+               }
+               if (test_cases[i].content && strcmp(test_cases[i].content, out_content)) {
+                       goto sipinfo_cleanup;
+               }
+               if (test_cases[i].content_type && strcmp(test_cases[i].content_type, out_content_type)) {
+                       goto sipinfo_cleanup;
+               }
+               if (test_cases[i].useragent_filter && strcmp(test_cases[i].useragent_filter, useragent_filter)) {
+                       goto sipinfo_cleanup;
+               }
+               ast_variables_destroy(headers);
+               ast_free(pl);
+               ast_free(out_content);
+               ast_free(out_content_type);
+               ast_free(useragent_filter);
+               headers = NULL;
+               pl = NULL;
+               out_content = out_content_type = useragent_filter = NULL;
+       }
+       res = AST_TEST_PASS;
+
+sipinfo_cleanup:
+
+       ast_free(pl);
+       ast_free(out_content);
+       ast_free(out_content_type);
+       ast_variables_destroy(headers);
+       return res;
+}
+
+static int unload_module(void)
+{
+       AST_TEST_UNREGISTER(sipinfo_encode_decode_test);
+       return 0;
+}
+
+static int load_module(void)
+{
+
+       AST_TEST_REGISTER(sipinfo_encode_decode_test);
+
+       return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Custom control frames test module");