summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/app_mixmonitor.c258
-rw-r--r--apps/app_queue.c8
-rw-r--r--apps/app_voicemail.c1804
-rw-r--r--apps/app_voicemail.exports.in9
-rw-r--r--channels/chan_sip.c246
-rw-r--r--channels/chan_sip.exports.in6
-rw-r--r--channels/chan_skinny.c15
-rw-r--r--channels/sip/include/sip.h3
-rw-r--r--configs/manager.conf.sample3
-rw-r--r--contrib/realtime/mysql/voicemail_messages.sql2
-rw-r--r--funcs/func_presencestate.c781
-rw-r--r--include/asterisk/app.h58
-rw-r--r--include/asterisk/app_voicemail.h212
-rw-r--r--include/asterisk/callerid.h1
-rw-r--r--include/asterisk/config.h57
-rw-r--r--include/asterisk/event_defs.h10
-rw-r--r--include/asterisk/file.h38
-rw-r--r--include/asterisk/manager.h2
-rw-r--r--include/asterisk/message.h30
-rw-r--r--include/asterisk/pbx.h34
-rw-r--r--include/asterisk/presencestate.h154
-rw-r--r--include/asterisk/sip_api.h51
-rw-r--r--main/app.c70
-rw-r--r--main/asterisk.c6
-rw-r--r--main/callerid.c1
-rw-r--r--main/channel.c1
-rw-r--r--main/config.c123
-rw-r--r--main/event.c22
-rw-r--r--main/features.c54
-rw-r--r--main/file.c44
-rw-r--r--main/manager.c66
-rw-r--r--main/message.c191
-rw-r--r--main/pbx.c388
-rw-r--r--main/presencestate.c317
-rw-r--r--tests/test_config.c359
-rw-r--r--tests/test_voicemail_api.c1443
36 files changed, 6672 insertions, 195 deletions
diff --git a/apps/app_mixmonitor.c b/apps/app_mixmonitor.c
index c1f5ada72..8eda3997f 100644
--- a/apps/app_mixmonitor.c
+++ b/apps/app_mixmonitor.c
@@ -42,6 +42,7 @@
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/paths.h" /* use ast_config_AST_MONITOR_DIR */
+#include "asterisk/stringfields.h"
#include "asterisk/file.h"
#include "asterisk/audiohook.h"
#include "asterisk/pbx.h"
@@ -51,6 +52,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/channel.h"
#include "asterisk/autochan.h"
#include "asterisk/manager.h"
+#include "asterisk/callerid.h"
#include "asterisk/mod_format.h"
#include "asterisk/linkedlists.h"
@@ -112,6 +114,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<argument name="chanvar" required="true" />
<para>Stores the MixMonitor's ID on this channel variable.</para>
</option>
+ <option name="m">
+ <argument name="mailbox" required="true" />
+ <para>Create a copy of the recording as a voicemail in the indicated <emphasis>mailbox</emphasis>(es)
+ separated by commas eg. m(1111@default,2222@default,...). Folders can be optionally specified using
+ the syntax: mailbox@context/folder</para>
+ </option>
</optionlist>
</parameter>
<parameter name="command">
@@ -238,6 +246,17 @@ static const char * const stop_app = "StopMixMonitor";
static const char * const mixmonitor_spy_type = "MixMonitor";
+/*!
+ * \internal
+ * \brief This struct is a list item holds data needed to find a vm_recipient within voicemail
+ */
+struct vm_recipient {
+ char mailbox[AST_MAX_CONTEXT];
+ char context[AST_MAX_EXTENSION];
+ char folder[80];
+ AST_LIST_ENTRY(vm_recipient) list;
+};
+
struct mixmonitor {
struct ast_audiohook audiohook;
struct ast_callid *callid;
@@ -249,6 +268,20 @@ struct mixmonitor {
unsigned int flags;
struct ast_autochan *autochan;
struct mixmonitor_ds *mixmonitor_ds;
+
+ /* the below string fields describe data used for creating voicemails from the recording */
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(call_context);
+ AST_STRING_FIELD(call_macrocontext);
+ AST_STRING_FIELD(call_extension);
+ AST_STRING_FIELD(call_callerchan);
+ AST_STRING_FIELD(call_callerid);
+ );
+ int call_priority;
+
+ /* FUTURE DEVELOPMENT NOTICE
+ * recipient_list will need locks if we make it editable after the monitor is started */
+ AST_LIST_HEAD_NOLOCK(, vm_recipient) recipient_list;
};
enum mixmonitor_flags {
@@ -260,7 +293,8 @@ enum mixmonitor_flags {
MUXFLAG_READ = (1 << 6),
MUXFLAG_WRITE = (1 << 7),
MUXFLAG_COMBINED = (1 << 8),
- MUXFLAG_UID = (1 << 9),
+ MUXFLAG_UID = (1 << 9),
+ MUXFLAG_VMRECIPIENTS = (1 << 10),
};
enum mixmonitor_args {
@@ -269,7 +303,8 @@ enum mixmonitor_args {
OPT_ARG_VOLUME,
OPT_ARG_WRITENAME,
OPT_ARG_READNAME,
- OPT_ARG_UID,
+ OPT_ARG_UID,
+ OPT_ARG_VMRECIPIENTS,
OPT_ARG_ARRAY_SIZE, /* Always last element of the enum */
};
@@ -282,6 +317,7 @@ AST_APP_OPTIONS(mixmonitor_opts, {
AST_APP_OPTION_ARG('r', MUXFLAG_READ, OPT_ARG_READNAME),
AST_APP_OPTION_ARG('t', MUXFLAG_WRITE, OPT_ARG_WRITENAME),
AST_APP_OPTION_ARG('i', MUXFLAG_UID, OPT_ARG_UID),
+ AST_APP_OPTION_ARG('m', MUXFLAG_VMRECIPIENTS, OPT_ARG_VMRECIPIENTS),
});
struct mixmonitor_ds {
@@ -382,6 +418,70 @@ static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
return res;
}
+/*!
+ * \internal
+ * \brief adds recipients to a mixmonitor's recipient list
+ * \param mixmonitor mixmonitor being affected
+ * \param vm_recipients string containing the desired recipients to add
+ */
+static void add_vm_recipients_from_string(struct mixmonitor *mixmonitor, const char *vm_recipients)
+{
+ /* recipients are in a single string with a format format resembling "mailbox@context/INBOX,mailbox2@context2,mailbox3@context3/Work" */
+ char *cur_mailbox = ast_strdupa(vm_recipients);
+ char *cur_context;
+ char *cur_folder;
+ char *next;
+ int elements_processed = 0;
+
+ while (!ast_strlen_zero(cur_mailbox)) {
+ ast_debug(3, "attempting to add next element %d from %s\n", elements_processed, cur_mailbox);
+ if ((next = strchr(cur_mailbox, ',')) || (next = strchr(cur_mailbox, '&'))) {
+ *(next++) = '\0';
+ }
+
+ if ((cur_folder = strchr(cur_mailbox, '/'))) {
+ *(cur_folder++) = '\0';
+ } else {
+ cur_folder = "INBOX";
+ }
+
+ if ((cur_context = strchr(cur_mailbox, '@'))) {
+ *(cur_context++) = '\0';
+ } else {
+ cur_context = "default";
+ }
+
+ if (!ast_strlen_zero(cur_mailbox) && !ast_strlen_zero(cur_context)) {
+ struct vm_recipient *recipient;
+ if (!(recipient = ast_malloc(sizeof(*recipient)))) {
+ ast_log(LOG_ERROR, "Failed to allocate recipient. Aborting function.\n");
+ return;
+ }
+ ast_copy_string(recipient->context, cur_context, sizeof(recipient->context));
+ ast_copy_string(recipient->mailbox, cur_mailbox, sizeof(recipient->mailbox));
+ ast_copy_string(recipient->folder, cur_folder, sizeof(recipient->folder));
+
+ /* Add to list */
+ ast_verb(5, "Adding %s@%s to recipient list\n", recipient->mailbox, recipient->context);
+ AST_LIST_INSERT_HEAD(&mixmonitor->recipient_list, recipient, list);
+ } else {
+ ast_log(LOG_ERROR, "Failed to properly parse extension and/or context from element %d of recipient string: %s\n", elements_processed, vm_recipients);
+ }
+
+ cur_mailbox = next;
+ elements_processed++;
+ }
+}
+
+static void clear_mixmonitor_recipient_list(struct mixmonitor *mixmonitor)
+{
+ struct vm_recipient *current;
+ while ((current = AST_LIST_REMOVE_HEAD(&mixmonitor->recipient_list, list))) {
+ /* Clear list element data */
+ ast_free(current);
+ }
+}
+
#define SAMPLES_PER_FRAME 160
static void mixmonitor_free(struct mixmonitor *mixmonitor)
@@ -397,6 +497,12 @@ static void mixmonitor_free(struct mixmonitor *mixmonitor)
ast_free(mixmonitor->post_process);
}
+ /* Free everything in the recipient list */
+ clear_mixmonitor_recipient_list(mixmonitor);
+
+ /* clean stringfields */
+ ast_string_field_free_memory(mixmonitor);
+
if (mixmonitor->callid) {
ast_callid_unref(mixmonitor->callid);
}
@@ -404,10 +510,50 @@ static void mixmonitor_free(struct mixmonitor *mixmonitor)
}
}
-static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename, struct ast_filestream **fs, unsigned int *oflags, int *errflag)
+/*!
+ * \internal
+ * \brief Copies the mixmonitor to all voicemail recipients
+ * \param mixmonitor The mixmonitor that needs to forward its file to recipients
+ * \param ext Format of the file that was saved
+ */
+static void copy_to_voicemail(struct mixmonitor *mixmonitor, const char *ext, const char *filename)
+{
+ struct vm_recipient *recipient = NULL;
+ struct ast_vm_recording_data recording_data;
+ if (ast_string_field_init(&recording_data, 512)) {
+ ast_log(LOG_ERROR, "Failed to string_field_init, skipping copy_to_voicemail\n");
+ return;
+ }
+
+ /* Copy strings to stringfields that will be used for all recipients */
+ ast_string_field_set(&recording_data, recording_file, filename);
+ ast_string_field_set(&recording_data, recording_ext, ext);
+ ast_string_field_set(&recording_data, call_context, mixmonitor->call_context);
+ ast_string_field_set(&recording_data, call_macrocontext, mixmonitor->call_macrocontext);
+ ast_string_field_set(&recording_data, call_extension, mixmonitor->call_extension);
+ ast_string_field_set(&recording_data, call_callerchan, mixmonitor->call_callerchan);
+ ast_string_field_set(&recording_data, call_callerid, mixmonitor->call_callerid);
+ /* and call_priority gets copied too */
+ recording_data.call_priority = mixmonitor->call_priority;
+
+ AST_LIST_TRAVERSE(&mixmonitor->recipient_list, recipient, list) {
+ /* context, mailbox, and folder need to be set per recipient */
+ ast_string_field_set(&recording_data, context, recipient->context);
+ ast_string_field_set(&recording_data, mailbox, recipient->mailbox);
+ ast_string_field_set(&recording_data, folder, recipient->folder);
+
+ ast_verb(4, "MixMonitor attempting to send voicemail copy to %s@%s\n", recording_data.mailbox,
+ recording_data.context);
+ ast_app_copy_recording_to_vm(&recording_data);
+ }
+
+ /* Free the string fields for recording_data before exiting the function. */
+ ast_string_field_free_memory(&recording_data);
+}
+
+static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename, struct ast_filestream **fs, unsigned int *oflags, int *errflag, char **ext)
{
/* Initialize the file if not already done so */
- char *ext = NULL;
char *last_slash = NULL;
if (!ast_strlen_zero(filename)) {
if (!*fs && !*errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
@@ -416,14 +562,19 @@ static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename,
last_slash = strrchr(filename, '/');
- if ((ext = strrchr(filename, '.')) && (ext > last_slash)) {
- *(ext++) = '\0';
+ ast_log(LOG_NOTICE, "!!!!!! File name is %s\n", filename);
+
+ if ((*ext = strrchr(filename, '.')) && (*ext > last_slash)) {
+ ast_log(LOG_NOTICE, "Found a dot. *ext is %s\n", *ext);
+ **ext = '\0';
+ *ext = *ext + 1;
+ ast_log(LOG_NOTICE, "After increment *ext is %s\n", *ext);
} else {
- ext = "raw";
+ *ext = "raw";
}
- if (!(*fs = ast_writefile(filename, ext, NULL, *oflags, 0, 0666))) {
- ast_log(LOG_ERROR, "Cannot open %s.%s\n", filename, ext);
+ if (!(*fs = ast_writefile(filename, *ext, NULL, *oflags, 0, 0666))) {
+ ast_log(LOG_ERROR, "Cannot open %s.%s\n", filename, *ext);
*errflag = 1;
} else {
struct ast_filestream *tmp = *fs;
@@ -436,6 +587,9 @@ static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename,
static void *mixmonitor_thread(void *obj)
{
struct mixmonitor *mixmonitor = obj;
+ char *fs_ext = "";
+ char *fs_read_ext = "";
+ char *fs_write_ext = "";
struct ast_filestream **fs = NULL;
struct ast_filestream **fs_read = NULL;
@@ -457,9 +611,9 @@ static void *mixmonitor_thread(void *obj)
fs_write = &mixmonitor->mixmonitor_ds->fs_write;
ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
- mixmonitor_save_prep(mixmonitor, mixmonitor->filename, fs, &oflags, &errflag);
- mixmonitor_save_prep(mixmonitor, mixmonitor->filename_read, fs_read, &oflags, &errflag);
- mixmonitor_save_prep(mixmonitor, mixmonitor->filename_write, fs_write, &oflags, &errflag);
+ mixmonitor_save_prep(mixmonitor, mixmonitor->filename, fs, &oflags, &errflag, &fs_ext);
+ mixmonitor_save_prep(mixmonitor, mixmonitor->filename_read, fs_read, &oflags, &errflag, &fs_read_ext);
+ mixmonitor_save_prep(mixmonitor, mixmonitor->filename_write, fs_write, &oflags, &errflag, &fs_write_ext);
ast_format_set(&format_slin, ast_format_slin_by_rate(mixmonitor->mixmonitor_ds->samp_rate), 0);
@@ -554,6 +708,27 @@ static void *mixmonitor_thread(void *obj)
}
ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
+
+ if (!AST_LIST_EMPTY(&mixmonitor->recipient_list)) {
+ if (ast_strlen_zero(fs_ext)) {
+ ast_log(LOG_ERROR, "No file extension set for Mixmonitor %s. Skipping copy to voicemail.\n",
+ mixmonitor -> name);
+ } else {
+ ast_verb(3, "Copying recordings for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
+ copy_to_voicemail(mixmonitor, fs_ext, mixmonitor->filename);
+ }
+ if (!ast_strlen_zero(fs_read_ext)) {
+ ast_verb(3, "Copying read recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
+ copy_to_voicemail(mixmonitor, fs_read_ext, mixmonitor->filename_read);
+ }
+ if (!ast_strlen_zero(fs_write_ext)) {
+ ast_verb(3, "Copying write recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
+ copy_to_voicemail(mixmonitor, fs_write_ext, mixmonitor->filename_write);
+ }
+ } else {
+ ast_debug(3, "No recipients to forward monitor to, moving on.\n");
+ }
+
mixmonitor_free(mixmonitor);
return NULL;
}
@@ -597,7 +772,8 @@ static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel
static void launch_monitor_thread(struct ast_channel *chan, const char *filename,
unsigned int flags, int readvol, int writevol,
const char *post_process, const char *filename_write,
- char *filename_read, const char *uid_channel_var)
+ char *filename_read, const char *uid_channel_var,
+ const char *recipients)
{
pthread_t thread;
struct mixmonitor *mixmonitor;
@@ -623,6 +799,12 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
return;
}
+ /* Now that the struct has been calloced, go ahead and initialize the string fields. */
+ if (ast_string_field_init(mixmonitor, 512)) {
+ mixmonitor_free(mixmonitor);
+ return;
+ }
+
/* Setup the actual spy before creating our thread */
if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type, 0)) {
mixmonitor_free(mixmonitor);
@@ -650,7 +832,6 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
}
ast_free(datastore_id);
-
mixmonitor->name = ast_strdup(ast_channel_name(chan));
if (!ast_strlen_zero(postprocess2)) {
@@ -669,6 +850,35 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
mixmonitor->filename_read = ast_strdup(filename_read);
}
+ if (!ast_strlen_zero(recipients)) {
+ char callerid[256];
+ struct ast_party_connected_line *connected;
+
+ ast_channel_lock(chan);
+
+ /* We use the connected line of the invoking channel for caller ID. */
+
+ connected = ast_channel_connected(chan);
+ ast_debug(3, "Connected Line CID = %d - %s : %d - %s\n", connected->id.name.valid,
+ connected->id.name.str, connected->id.number.valid,
+ connected->id.number.str);
+ ast_callerid_merge(callerid, sizeof(callerid),
+ S_COR(connected->id.name.valid, connected->id.name.str, NULL),
+ S_COR(connected->id.number.valid, connected->id.number.str, NULL),
+ "Unknown");
+
+ ast_string_field_set(mixmonitor, call_context, ast_channel_context(chan));
+ ast_string_field_set(mixmonitor, call_macrocontext, ast_channel_macrocontext(chan));
+ ast_string_field_set(mixmonitor, call_extension, ast_channel_exten(chan));
+ ast_string_field_set(mixmonitor, call_callerchan, ast_channel_name(chan));
+ ast_string_field_set(mixmonitor, call_callerid, callerid);
+ mixmonitor->call_priority = ast_channel_priority(chan);
+
+ ast_channel_unlock(chan);
+
+ add_vm_recipients_from_string(mixmonitor, recipients);
+ }
+
ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
if (readvol)
@@ -723,6 +933,7 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
char *uid_channel_var = NULL;
struct ast_flags flags = { 0 };
+ char *recipients = NULL;
char *parse;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(filename);
@@ -774,6 +985,14 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
}
}
+ if (ast_test_flag(&flags, MUXFLAG_VMRECIPIENTS)) {
+ if (ast_strlen_zero(opts[OPT_ARG_VMRECIPIENTS])) {
+ ast_log(LOG_WARNING, "No voicemail recipients were specified for the vm copy ('m') option.\n");
+ } else {
+ recipients = ast_strdupa(opts[OPT_ARG_VMRECIPIENTS]);
+ }
+ }
+
if (ast_test_flag(&flags, MUXFLAG_WRITE)) {
filename_write = ast_strdupa(filename_parse(opts[OPT_ARG_WRITENAME], filename_buffer, sizeof(filename_buffer)));
}
@@ -799,7 +1018,16 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
}
pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
- launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process, filename_write, filename_read, uid_channel_var);
+ launch_monitor_thread(chan,
+ args.filename,
+ flags.flags,
+ readvol,
+ writevol,
+ args.post_process,
+ filename_write,
+ filename_read,
+ uid_channel_var,
+ recipients);
return 0;
}
diff --git a/apps/app_queue.c b/apps/app_queue.c
index f19ca3fdb..cb9674959 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -1703,13 +1703,19 @@ static int extensionstate2devicestate(int state)
return state;
}
-static int extension_state_cb(const char *context, const char *exten, enum ast_extension_states state, void *data)
+static int extension_state_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data)
{
struct ao2_iterator miter, qiter;
struct member *m;
struct call_queue *q;
+ int state = info->exten_state;
int found = 0, device_state = extensionstate2devicestate(state);
+ /* only interested in extension state updates involving device states */
+ if (info->reason != AST_HINT_UPDATE_DEVICE) {
+ return 0;
+ }
+
qiter = ao2_iterator_init(queues, 0);
while ((q = ao2_t_iterator_next(&qiter, "Iterate through queues"))) {
ao2_lock(q);
diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c
index eec08a484..043d93475 100644
--- a/apps/app_voicemail.c
+++ b/apps/app_voicemail.c
@@ -113,12 +113,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/module.h"
#include "asterisk/adsi.h"
#include "asterisk/app.h"
+#include "asterisk/app_voicemail.h"
#include "asterisk/manager.h"
#include "asterisk/dsp.h"
#include "asterisk/localtime.h"
#include "asterisk/cli.h"
#include "asterisk/utils.h"
#include "asterisk/stringfields.h"
+#include "asterisk/strings.h"
#include "asterisk/smdi.h"
#include "asterisk/astobj2.h"
#include "asterisk/event.h"
@@ -334,6 +336,30 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
</enumlist>
</description>
</application>
+ <application name="VoiceMailPlayMsg" language="en_US">
+ <synopsis>
+ Play a single voice mail msg from a mailbox by msg id.
+ </synopsis>
+ <syntax>
+ <parameter name="mailbox" required="true" argsep="@">
+ <argument name="mailbox" />
+ <argument name="context" />
+ </parameter>
+ <parameter name="msg_id" required="true">
+ <para>The msg id of the msg to play back. </para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>This application sets the following channel variable upon completion:</para>
+ <variablelist>
+ <variable name="VOICEMAIL_PLAYBACKSTATUS">
+ <para>The status of the playback attempt as a text string.</para>
+ <value name="SUCCESS"/>
+ <value name="FAILED"/>
+ </variable>
+ </variablelist>
+ </description>
+ </application>
<application name="VMSayName" language="en_US">
<synopsis>
Play the name of a voicemail user
@@ -469,7 +495,8 @@ static int save_body(BODY *body, struct vm_state *vms, char *section, char *form
static void get_mailbox_delimiter(struct vm_state *vms, MAILSTREAM *stream);
static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
-static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag);
+static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag, const char *msg_id);
+static void vm_imap_update_msg_id(char *dir, int msgnum, const char *msg_id, struct ast_vm_user *vmu, struct ast_config *msg_cfg, int folder);
static void update_messages_by_imapuser(const char *user, unsigned long number);
static int vm_delete(char *file);
@@ -552,7 +579,6 @@ static AST_LIST_HEAD_STATIC(vmstates, vmstate);
#define ERROR_LOCK_PATH -100
#define OPERATOR_EXIT 300
-
enum vm_box {
NEW_FOLDER,
OLD_FOLDER,
@@ -600,6 +626,25 @@ AST_APP_OPTIONS(vm_app_options, {
AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
});
+static const char * const mailbox_folders[] = {
+#ifdef IMAP_STORAGE
+ imapfolder,
+#else
+ "INBOX",
+#endif
+ "Old",
+ "Work",
+ "Family",
+ "Friends",
+ "Cust1",
+ "Cust2",
+ "Cust3",
+ "Cust4",
+ "Cust5",
+ "Deleted",
+ "Urgent",
+};
+
static int load_config(int reload);
#ifdef TEST_FRAMEWORK
static int load_config_from_memory(int reload, struct ast_config *cfg, struct ast_config *ucfg);
@@ -793,28 +838,31 @@ static char odbc_database[80];
static char odbc_table[80];
#define RETRIEVE(a,b,c,d) retrieve_file(a,b)
#define DISPOSE(a,b) remove_file(a,b)
-#define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
+#define STORE(a,b,c,d,e,f,g,h,i,j,k) store_file(a,b,c,d)
#define EXISTS(a,b,c,d) (message_exists(a,b))
#define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
#define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
#define DELETE(a,b,c,d) (delete_file(a,b))
+#define UPDATE_MSG_ID(a, b, c, d, e, f) (odbc_update_msg_id((a), (b), (c)))
#else
#ifdef IMAP_STORAGE
#define DISPOSE(a,b) (imap_remove_file(a,b))
-#define STORE(a,b,c,d,e,f,g,h,i,j) (imap_store_file(a,b,c,d,e,f,g,h,i,j))
+#define STORE(a,b,c,d,e,f,g,h,i,j,k) (imap_store_file(a,b,c,d,e,f,g,h,i,j,k))
#define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
#define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
#define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
#define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
#define DELETE(a,b,c,d) (vm_imap_delete(a,b,d))
+#define UPDATE_MSG_ID(a, b, c, d, e, f) (vm_imap_update_msg_id((a), (b), (c), (d), (e), (f)))
#else
#define RETRIEVE(a,b,c,d)
#define DISPOSE(a,b)
-#define STORE(a,b,c,d,e,f,g,h,i,j)
+#define STORE(a,b,c,d,e,f,g,h,i,j,k)
#define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
#define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
#define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
#define DELETE(a,b,c,d) (vm_delete(c))
+#define UPDATE_MSG_ID(a, b, c, d, e, f)
#endif
#endif
@@ -852,6 +900,8 @@ static char *app2 = "VoiceMailMain";
static char *app3 = "MailboxExists";
static char *app4 = "VMAuthenticate";
+static char *playmsg_app = "VoiceMailPlayMsg";
+
static char *sayname_app = "VMSayName";
static AST_LIST_HEAD_STATIC(users, ast_vm_user);
@@ -976,21 +1026,39 @@ static char pagerdateformat[32] = "%A, %B %d, %Y at %r";
/* Forward declarations - generic */
static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
+static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu);
static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg, int option, signed char record_gain);
static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, int *sound_duration, const char *unlockdir,
- signed char record_gain, struct vm_state *vms, char *flag);
+ signed char record_gain, struct vm_state *vms, char *flag, const char *msg_id);
static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msgnum, long duration, char *fmt, char *cidnum, char *cidname, const char *flag);
-static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap, const char *flag);
+static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap, const char *flag, const char *msg_id);
static void apply_options(struct ast_vm_user *vmu, const char *options);
static int add_email_attachment(FILE *p, struct ast_vm_user *vmu, char *format, char *attach, char *greeting_attachment, char *mailbox, char *bound, char *filename, int last, int msgnum);
static int is_valid_dtmf(const char *key);
static void read_password_from_file(const char *secretfn, char *password, int passwordlen);
static int write_password_to_file(const char *secretfn, const char *password);
+struct ast_str *vm_mailbox_snapshot_str(const char *mailbox, const char *context);
static const char *substitute_escapes(const char *value);
+static int message_range_and_existence_check(struct vm_state *vms, const char *msg_ids [], size_t num_msgs, int *msg_nums, struct ast_vm_user *vmu);
+/*!
+ * Place a message in the indicated folder
+ *
+ * \param vmu Voicemail user
+ * \param vms Current voicemail state for the user
+ * \param msg The message number to save
+ * \param box The folder into which the message should be saved
+ * \param[out] newmsg The new message number of the saved message
+ * \param move Tells whether to copy or to move the message
+ *
+ * \note the "move" parameter is only honored for IMAP voicemail presently
+ * \retval 0 Success
+ * \revval other Failure
+ */
+static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box, int *newmsg, int move);
struct ao2_container *inprocess_container;
@@ -1790,25 +1858,6 @@ static int create_dirpath(char *dest, int len, const char *context, const char *
return 0;
}
-static const char * const mailbox_folders[] = {
-#ifdef IMAP_STORAGE
- imapfolder,
-#else
- "INBOX",
-#endif
- "Old",
- "Work",
- "Family",
- "Friends",
- "Cust1",
- "Cust2",
- "Cust3",
- "Cust4",
- "Cust5",
- "Deleted",
- "Urgent",
-};
-
static const char *mbox(struct ast_vm_user *vmu, int id)
{
#ifdef IMAP_STORAGE
@@ -1909,6 +1958,79 @@ static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu)
ast_mutex_unlock(&vms->lock);
}
+static void vm_imap_update_msg_id(char *dir, int msgnum, const char *msg_id, struct ast_vm_user *vmu, struct ast_config *msg_cfg, int folder)
+{
+ struct ast_channel *chan;
+ char *cid;
+ char *cid_name;
+ char *cid_num;
+ struct vm_state *vms;
+ const char *duration_str;
+ int duration = 0;
+
+ /*
+ * First, get things initially set up. If any of this fails, then
+ * back out before doing anything substantial
+ */
+ vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0);
+ if (!vms) {
+ return;
+ }
+
+ if (open_mailbox(vms, vmu, folder)) {
+ return;
+ }
+
+ chan = ast_dummy_channel_alloc();
+ if (!chan) {
+ close_mailbox(vms, vmu);
+ return;
+ }
+
+ /*
+ * We need to make sure the new message we save has the same
+ * callerid, flag, and duration as the original message
+ */
+ cid = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "callerid"));
+
+ if (!ast_strlen_zero(cid)) {
+ ast_callerid_parse(cid, &cid_name, &cid_num);
+ ast_party_caller_init(ast_channel_caller(chan));
+ if (!ast_strlen_zero(cid_name)) {
+ ast_channel_caller(chan)->id.name.valid = 1;
+ ast_channel_caller(chan)->id.name.str = ast_strdup(cid_name);
+ }
+ if (!ast_strlen_zero(cid_num)) {
+ ast_channel_caller(chan)->id.number.valid = 1;
+ ast_channel_caller(chan)->id.number.str = ast_strdup(cid_num);
+ }
+ }
+
+ duration_str = ast_variable_retrieve(msg_cfg, "message", "duration");
+
+ if (!ast_strlen_zero(duration_str)) {
+ sscanf(duration_str, "%30d", &duration);
+ }
+
+ /*
+ * IMAP messages cannot be altered once delivered. So we have to delete the
+ * current message and then re-add it with the updated message ID.
+ *
+ * Furthermore, there currently is no atomic way to create a new message and to
+ * store it in an arbitrary folder. So we have to save it to the INBOX and then
+ * move to the appropriate folder.
+ */
+ if (!imap_store_file(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, vmfmts,
+ duration, vms, ast_variable_retrieve(msg_cfg, "message", "flag"), msg_id)) {
+ if (folder != NEW_FOLDER) {
+ save_to_folder(vmu, vms, msgnum, folder, NULL, 1);
+ }
+ vm_imap_delete(dir, msgnum, vmu);
+ }
+ close_mailbox(vms, vmu);
+ ast_channel_unref(chan);
+}
+
static int imap_retrieve_greeting(const char *dir, const int msgnum, struct ast_vm_user *vmu)
{
struct vm_state *vms_p;
@@ -2086,20 +2208,30 @@ static int imap_retrieve_file(const char *dir, const int msgnum, const char *mai
fprintf(text_file_ptr, "%s\n", "[message]");
- get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
- fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
- get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
- fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
- get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
- fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
- get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
- fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
- get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
- fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
- get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
- fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
- get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf));
- fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
+ }
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
+ }
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
+ }
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
+ }
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
+ }
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
+ }
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
+ }
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Message-ID:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "msg_id=%s\n", S_OR(buf, ""));
+ }
fclose(text_file_ptr);
exit:
@@ -2266,7 +2398,9 @@ static int imap_check_limits(struct ast_channel *chan, struct vm_state *vms, str
check_quota(vms, vmu->imapfolder);
if (vms->quota_limit && vms->quota_usage >= vms->quota_limit) {
ast_debug(1, "*** QUOTA EXCEEDED!! %u >= %u\n", vms->quota_usage, vms->quota_limit);
- ast_play_and_wait(chan, "vm-mailboxfull");
+ if (chan) {
+ ast_play_and_wait(chan, "vm-mailboxfull");
+ }
return -1;
}
@@ -2274,8 +2408,10 @@ static int imap_check_limits(struct ast_channel *chan, struct vm_state *vms, str
ast_debug(3, "Checking message number quota: mailbox has %d messages, maximum is set to %d, current messages %d\n", msgnum, vmu->maxmsg, inprocess_count(vmu->mailbox, vmu->context, 0));
if (msgnum >= vmu->maxmsg - inprocess_count(vmu->mailbox, vmu->context, +1)) {
ast_log(LOG_WARNING, "Unable to leave message since we will exceed the maximum number of messages allowed (%u >= %u)\n", msgnum, vmu->maxmsg);
- ast_play_and_wait(chan, "vm-mailboxfull");
- pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
+ if (chan) {
+ ast_play_and_wait(chan, "vm-mailboxfull");
+ pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
+ }
return -1;
}
@@ -2300,7 +2436,7 @@ static int messagecount(const char *context, const char *mailbox, const char *fo
}
}
-static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag)
+static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag, const char *msg_id)
{
char *myserveremail = serveremail;
char fn[PATH_MAX];
@@ -2389,7 +2525,7 @@ static int imap_store_file(const char *dir, const char *mailboxuser, const char
make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, "INBOX",
S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
- fn, introfn, fmt, duration, 1, chan, NULL, 1, flag);
+ fn, introfn, fmt, duration, 1, chan, NULL, 1, flag, msg_id);
/* read mail file to memory */
len = ftell(p);
rewind(p);
@@ -2567,7 +2703,7 @@ static int has_voicemail(const char *mailbox, const char *folder)
*
* \return zero on success, -1 on error.
*/
-static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, char *flag)
+static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, char *flag, const char *dest_folder)
{
struct vm_state *sendvms = NULL;
char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
@@ -3826,6 +3962,7 @@ struct insert_data {
const char *mailboxcontext;
const char *category;
const char *flag;
+ const char *msg_id;
};
static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
@@ -3852,8 +3989,9 @@ static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *) data->mailboxuser, 0, NULL);
SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *) data->mailboxcontext, 0, NULL);
SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *) data->flag, 0, NULL);
+ SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msg_id), 0, (void *) data->msg_id, 0, NULL);
if (!ast_strlen_zero(data->category)) {
- SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL);
+ SQLBindParameter(stmt, 13, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL);
}
res = SQLExecDirect(stmt, (unsigned char *) data->sql, SQL_NTS);
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
@@ -3894,7 +4032,7 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail
struct ast_config *cfg = NULL;
struct odbc_obj *obj;
struct insert_data idata = { .sql = sql, .msgnums = msgnums, .dir = dir, .mailboxuser = mailboxuser, .mailboxcontext = mailboxcontext,
- .context = "", .macrocontext = "", .callerid = "", .origtime = "", .duration = "", .category = "", .flag = "" };
+ .context = "", .macrocontext = "", .callerid = "", .origtime = "", .duration = "", .category = "", .flag = "", .msg_id = "" };
struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
delete_file(dir, msgnum);
@@ -3946,6 +4084,9 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail
if (!(idata.flag = ast_variable_retrieve(cfg, "message", "flag"))) {
idata.flag = "";
}
+ if (!(idata.msg_id = ast_variable_retrieve(cfg, "message", "msg_id"))) {
+ idata.msg_id = "";
+ }
}
fdlen = lseek(fd, 0, SEEK_END);
if (fdlen < 0 || lseek(fd, 0, SEEK_SET) < 0) {
@@ -3963,9 +4104,9 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail
idata.datalen = idata.indlen = fdlen;
if (!ast_strlen_zero(idata.category))
- snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
+ snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
else
- snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag) VALUES (?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
+ snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
if ((stmt = ast_odbc_direct_execute(obj, insert_data_cb, &idata))) {
SQLFreeHandle (SQL_HANDLE_STMT, stmt);
@@ -3986,6 +4127,33 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail
return res;
}
+static void odbc_update_msg_id(char *dir, int msg_num, char *msg_id)
+{
+ SQLHSTMT stmt;
+ char sql[PATH_MAX];
+ struct odbc_obj *obj;
+ char msg_num_str[20];
+ char *argv[] = { msg_id, dir, msg_num_str };
+ struct generic_prepare_struct gps = { .sql = sql, .argc = 3, .argv = argv };
+
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (!obj) {
+ ast_log(LOG_WARNING, "Unable to update message ID for message %d in %s\n", msg_num, dir);
+ return;
+ }
+
+ snprintf(msg_num_str, sizeof(msg_num_str), "%d", msg_num);
+ snprintf(sql, sizeof(sql), "UPDATE %s SET msg_id=? WHERE dir=? AND msgnum=?", odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ } else {
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ }
+ ast_odbc_release_obj(obj);
+ return;
+}
+
/*!
* \brief Renames a message in a mailbox folder.
* \param sdir The folder of the message to be renamed.
@@ -4614,7 +4782,25 @@ static const char *ast_str_encode_mime(struct ast_str **end, ssize_t maxlen, con
*
* The email body, and base 64 encoded attachement (if any) are stored to the file identified by *p. This method does not actually send the email. That is done by invoking the configure 'mailcmd' and piping this generated file into it, or with the sendemail() function.
*/
-static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap, const char *flag)
+static void make_email_file(FILE *p,
+ char *srcemail,
+ struct ast_vm_user *vmu,
+ int msgnum,
+ char *context,
+ char *mailbox,
+ const char *fromfolder,
+ char *cidnum,
+ char *cidname,
+ char *attach,
+ char *attach2,
+ char *format,
+ int duration,
+ int attach_user_voicemail,
+ struct ast_channel *chan,
+ const char *category,
+ int imap,
+ const char *flag,
+ const char *msg_id)
{
char date[256];
char host[MAXHOSTNAMELEN] = "";
@@ -4758,8 +4944,8 @@ static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, in
#endif
/* flag added for Urgent */
fprintf(p, "X-Asterisk-VM-Flag: %s" ENDL, flag);
- fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, ast_channel_priority(chan));
- fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, ast_channel_name(chan));
+ fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan ? ast_channel_priority(chan) : 0);
+ fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan ? ast_channel_name(chan) : "");
fprintf(p, "X-Asterisk-VM-Caller-ID-Num: %s" ENDL, enc_cidnum);
fprintf(p, "X-Asterisk-VM-Caller-ID-Name: %s" ENDL, enc_cidname);
fprintf(p, "X-Asterisk-VM-Duration: %d" ENDL, duration);
@@ -4771,6 +4957,7 @@ static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, in
fprintf(p, "X-Asterisk-VM-Message-Type: %s" ENDL, msgnum > -1 ? "Message" : greeting_attachment);
fprintf(p, "X-Asterisk-VM-Orig-date: %s" ENDL, date);
fprintf(p, "X-Asterisk-VM-Orig-time: %ld" ENDL, (long) time(NULL));
+ fprintf(p, "X-Asterisk-VM-Message-ID: %s" ENDL, msg_id);
}
if (!ast_strlen_zero(cidnum)) {
fprintf(p, "X-Asterisk-CallerID: %s" ENDL, enc_cidnum);
@@ -4937,7 +5124,23 @@ static int add_email_attachment(FILE *p, struct ast_vm_user *vmu, char *format,
return 0;
}
-static int sendmail(char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, const char *flag)
+static int sendmail(char *srcemail,
+ struct ast_vm_user *vmu,
+ int msgnum,
+ char *context,
+ char *mailbox,
+ const char *fromfolder,
+ char *cidnum,
+ char *cidname,
+ char *attach,
+ char *attach2,
+ char *format,
+ int duration,
+ int attach_user_voicemail,
+ struct ast_channel *chan,
+ const char *category,
+ const char *flag,
+ const char *msg_id)
{
FILE *p = NULL;
char tmp[80] = "/tmp/astmail-XXXXXX";
@@ -4963,7 +5166,7 @@ static int sendmail(char *srcemail, struct ast_vm_user *vmu, int msgnum, char *c
ast_log(AST_LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
return -1;
} else {
- make_email_file(p, srcemail, vmu, msgnum, context, mailbox, fromfolder, cidnum, cidname, attach, attach2, format, duration, attach_user_voicemail, chan, category, 0, flag);
+ make_email_file(p, srcemail, vmu, msgnum, context, mailbox, fromfolder, cidnum, cidname, attach, attach2, format, duration, attach_user_voicemail, chan, category, 0, flag, msg_id);
fclose(p);
snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
ast_safe_system(tmp2);
@@ -5407,7 +5610,7 @@ static int has_voicemail(const char *mailbox, const char *folder)
*
* \return zero on success, -1 on error.
*/
-static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, const char *flag)
+static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, const char *flag, const char *dest_folder)
{
char fromdir[PATH_MAX], todir[PATH_MAX], frompath[PATH_MAX], topath[PATH_MAX];
const char *frombox = mbox(vmu, imbox);
@@ -5419,6 +5622,8 @@ static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int i
if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) { /* If urgent, copy to Urgent folder */
userfolder = "Urgent";
+ } else if (!ast_strlen_zero(dest_folder)) {
+ userfolder = dest_folder;
} else {
userfolder = "INBOX";
}
@@ -5440,7 +5645,7 @@ static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int i
if (recipmsgnum < recip->maxmsg - (imbox ? 0 : inprocess_count(vmu->mailbox, vmu->context, 0))) {
make_file(topath, sizeof(topath), todir, recipmsgnum);
#ifndef ODBC_STORAGE
- if (EXISTS(fromdir, msgnum, frompath, ast_channel_language(chan))) {
+ if (EXISTS(fromdir, msgnum, frompath, chan ? ast_channel_language(chan) : "")) {
COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
} else {
#endif
@@ -5448,7 +5653,7 @@ static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int i
* exists in the database, but we want to force copying from the
* filesystem (since only the FS contains the prepend). */
copy_plain_file(frompath, topath);
- STORE(todir, recip->mailbox, recip->context, recipmsgnum, chan, recip, fmt, duration, NULL, NULL);
+ STORE(todir, recip->mailbox, recip->context, recipmsgnum, chan, recip, fmt, duration, NULL, NULL, NULL);
vm_delete(topath);
#ifndef ODBC_STORAGE
}
@@ -5458,11 +5663,14 @@ static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int i
res = -1;
}
ast_unlock_path(todir);
- notify_new_message(chan, recip, NULL, recipmsgnum, duration, fmt,
- S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
- S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
- flag);
-
+ if (chan) {
+ struct ast_party_caller *caller = ast_channel_caller(chan);
+ notify_new_message(chan, recip, NULL, recipmsgnum, duration, fmt,
+ S_COR(caller->id.number.valid, caller->id.number.str, NULL),
+ S_COR(caller->id.name.valid, caller->id.name.str, NULL),
+ flag);
+ }
+
return res;
}
#endif
@@ -5665,6 +5873,301 @@ struct leave_vm_options {
};
/*!
+ * \internal
+ * \brief Creates a voicemail based on a specified file to a mailbox.
+ * \param recdata A vm_recording_data containing filename and voicemail txt info.
+ * \retval -1 failure
+ * \retval 0 success
+ *
+ * This is installed to the app.h voicemail functions and accommodates all voicemail
+ * storage methods. It should probably be broken out along with leave_voicemail at
+ * some point in the future.
+ *
+ * This function currently only works for a single recipient and only uses the format
+ * specified in recording_ext.
+ */
+static int msg_create_from_file(struct ast_vm_recording_data *recdata)
+{
+ /* voicemail recipient structure */
+ struct ast_vm_user *recipient; /* points to svm once it's been created */
+ struct ast_vm_user svm; /* struct storing the voicemail recipient */
+
+ /* File paths */
+ char tmpdir[PATH_MAX]; /* directory temp files are stored in */
+ char tmptxtfile[PATH_MAX]; /* tmp file for voicemail txt file */
+ char desttxtfile[PATH_MAX]; /* final destination for txt file */
+ char tmpaudiofile[PATH_MAX]; /* tmp file where audio is stored */
+ char dir[PATH_MAX]; /* destination for tmp files on completion */
+ char destination[PATH_MAX]; /* destination with msgXXXX. Basically <dir>/msgXXXX */
+
+ /* stuff that only seems to be needed for IMAP */
+ #ifdef IMAP_STORAGE
+ struct vm_state *vms = NULL;
+ char ext_context[256] = "";
+ char *fmt = ast_strdupa(recdata->recording_ext);
+ int newmsgs = 0;
+ int oldmsgs = 0;
+ #endif
+
+ /* miscellaneous operational variables */
+ int res = 0; /* Used to store error codes from functions */
+ int txtdes /* File descriptor for the text file used to write the voicemail info */;
+ FILE *txt; /* FILE pointer to text file used to write the voicemail info */
+ char date[256]; /* string used to hold date of the voicemail (only used for ODBC) */
+ int msgnum; /* the 4 digit number designated to the voicemail */
+ int duration = 0; /* Length of the audio being recorded in seconds */
+ struct ast_filestream *recording_fs; /*used to read the recording to get duration data */
+
+ /* We aren't currently doing anything with category, since it comes from a channel variable and
+ * this function doesn't use channels, but this function could add that as an argument later. */
+ const char *category = NULL; /* pointless for now */
+ char msg_id[256];
+
+ /* Start by checking to see if the file actually exists... */
+ if (!(ast_fileexists(recdata->recording_file, recdata->recording_ext, NULL))) {
+ ast_log(LOG_ERROR, "File: %s not found.\n", recdata->recording_file);
+ return -1;
+ }
+
+ if (!(recipient = find_user(&svm, recdata->context, recdata->mailbox))) {
+ ast_log(LOG_ERROR, "No entry in voicemail config file for '%s@%s'\n", recdata->mailbox, recdata->context);
+ return -1;
+ }
+
+ /* determine duration in seconds */
+ if ((recording_fs = ast_readfile(recdata->recording_file, recdata->recording_ext, NULL, 0, 0, VOICEMAIL_DIR_MODE))) {
+ if (!ast_seekstream(recording_fs, 0, SEEK_END)) {
+ long framelength = ast_tellstream(recording_fs);
+ struct ast_format result;
+ /* XXX This use of ast_getformatbyname seems incorrect here. The file extension does not necessarily correspond
+ * to the name of the format. For instance, if "raw" were passed in, I don't think ast_getformatbyname would
+ * find the slinear format
+ */
+ duration = (int) (framelength / ast_format_rate(ast_getformatbyname(recdata->recording_ext, &result)));
+ }
+ }
+
+ /* If the duration was below the minimum duration for the user, let's just drop the whole thing now */
+ if (duration < recipient->minsecs) {
+ ast_log(LOG_NOTICE, "Copying recording to voicemail %s@%s skipped because duration was shorter than "
+ "minmessage of recipient\n", recdata->mailbox, recdata->context);
+ return -1;
+ }
+
+ /* Note that this number must be dropped back to a net sum of zero before returning from this function */
+
+ if ((res = create_dirpath(tmpdir, sizeof(tmpdir), recipient->context, recdata->mailbox, "tmp"))) {
+ ast_log(LOG_ERROR, "Failed to make directory.\n");
+ }
+
+ snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
+ txtdes = mkstemp(tmptxtfile);
+ if (txtdes < 0) {
+ chmod(tmptxtfile, VOICEMAIL_FILE_MODE & ~my_umask);
+ /* Something screwed up. Abort. */
+ ast_log(AST_LOG_ERROR, "Unable to create message file: %s\n", strerror(errno));
+ free_user(recipient);
+ return -1;
+ }
+
+ /* Store information */
+ txt = fdopen(txtdes, "w+");
+ if (txt) {
+ char msg_id_hash[256];
+
+ /* Every voicemail msg gets its own unique msg id. The msg id is the originate time
+ * plus a hash of the extension, context, and callerid of the channel leaving the msg */
+
+ snprintf(msg_id_hash, sizeof(msg_id_hash), "%s%s%s", recdata->call_extension,
+ recdata->call_context, recdata->call_callerid);
+ snprintf(msg_id, sizeof(msg_id), "%ld-%d", (long) time(NULL), ast_str_hash(msg_id_hash));
+
+ get_date(date, sizeof(date));
+ fprintf(txt,
+ ";\n"
+ "; Message Information file\n"
+ ";\n"
+ "[message]\n"
+ "origmailbox=%s\n"
+ "context=%s\n"
+ "macrocontext=%s\n"
+ "exten=%s\n"
+ "rdnis=Unknown\n"
+ "priority=%d\n"
+ "callerchan=%s\n"
+ "callerid=%s\n"
+ "origdate=%s\n"
+ "origtime=%ld\n"
+ "category=%s\n"
+ "msg_id=%s\n"
+ "flag=\n" /* flags not supported in copy from file yet */
+ "duration=%d\n", /* Don't have any reliable way to get duration of file. */
+
+ recdata->mailbox,
+ S_OR(recdata->call_context, ""),
+ S_OR(recdata->call_macrocontext, ""),
+ S_OR(recdata->call_extension, ""),
+ recdata->call_priority,
+ S_OR(recdata->call_callerchan, "Unknown"),
+ S_OR(recdata->call_callerid, "Unknown"),
+ date, (long) time(NULL),
+ S_OR(category, ""),
+ msg_id,
+ duration);
+
+ /* Since we are recording from a file, we shouldn't need to do anything else with
+ * this txt file */
+ fclose(txt);
+
+ } else {
+ ast_log(LOG_WARNING, "Error opening text file for output\n");
+ if (ast_check_realtime("voicemail_data")) {
+ ast_destroy_realtime("voicemail_data", "filename", tmptxtfile, SENTINEL);
+ }
+ free_user(recipient);
+ return -1;
+ }
+
+ /* At this point, the actual creation of a voicemail message should be finished.
+ * Now we just need to copy the files being recorded into the receiving folder. */
+
+ create_dirpath(dir, sizeof(dir), recipient->context, recipient->mailbox, recdata->folder);
+
+#ifdef IMAP_STORAGE
+ /* make recipient info into an inboxcount friendly string */
+ snprintf(ext_context, sizeof(ext_context), "%s@%s", recipient->mailbox, recipient->context);
+
+ /* Is ext a mailbox? */
+ /* must open stream for this user to get info! */
+ res = inboxcount(ext_context, &newmsgs, &oldmsgs);
+ if (res < 0) {
+ ast_log(LOG_NOTICE, "Can not leave voicemail, unable to count messages\n");
+ free_user(recipient);
+ unlink(tmptxtfile);
+ return -1;
+ }
+ if (!(vms = get_vm_state_by_mailbox(recipient->mailbox, recipient->context, 0))) {
+ /* It is possible under certain circumstances that inboxcount did not
+ * create a vm_state when it was needed. This is a catchall which will
+ * rarely be used.
+ */
+ if (!(vms = create_vm_state_from_user(recipient))) {
+ ast_log(LOG_ERROR, "Couldn't allocate necessary space\n");
+ free_user(recipient);
+ unlink(tmptxtfile);
+ return -1;
+ }
+ }
+ vms->newmessages++;
+
+ /* here is a big difference! We add one to it later */
+ msgnum = newmsgs + oldmsgs;
+ ast_debug(3, "Messagecount set to %d\n", msgnum);
+ snprintf(destination, sizeof(destination), "%simap/msg%s%04d", VM_SPOOL_DIR, recipient->mailbox, msgnum);
+
+ /* Check to see if we have enough room in the mailbox. If not, spit out an error and end
+ * Note that imap_check_limits raises inprocess_count if successful */
+ if ((res = imap_check_limits(NULL, vms, recipient, msgnum))) {
+ ast_log(LOG_NOTICE, "Didn't copy to voicemail. Mailbox for %s@%s is full.\n", recipient->mailbox, recipient->context);
+ inprocess_count(recipient->mailbox, recipient->context, -1);
+ free_user(recipient);
+ unlink(tmptxtfile);
+ return -1;
+ }
+
+#else
+
+ /* Check to see if the mailbox is full for ODBC/File storage */
+ ast_debug(3, "mailbox = %d : inprocess = %d\n", count_messages(recipient, dir),
+ inprocess_count(recipient->mailbox, recipient->context, 0));
+ if (count_messages(recipient, dir) > recipient->maxmsg - inprocess_count(recipient->mailbox, recipient->context, +1)) {
+ ast_log(AST_LOG_WARNING, "Didn't copy to voicemail. Mailbox for %s@%s is full.\n", recipient->mailbox, recipient->context);
+ inprocess_count(recipient->mailbox, recipient->context, -1);
+ free_user(recipient);
+ unlink(tmptxtfile);
+ return -1;
+ }
+
+ msgnum = last_message_index(recipient, dir) + 1;
+#endif
+
+ /* Lock the directory receiving the voicemail since we want it to still exist when we attempt to copy the voicemail.
+ * We need to unlock it before we return. */
+ if (vm_lock_path(dir)) {
+ ast_log(LOG_ERROR, "Couldn't lock directory %s. Voicemail will be lost.\n", dir);
+ /* Delete files */
+ ast_filedelete(tmptxtfile, NULL);
+ unlink(tmptxtfile);
+ free_user(recipient);
+ return -1;
+ }
+
+ make_file(destination, sizeof(destination), dir, msgnum);
+
+ make_file(tmpaudiofile, sizeof(tmpaudiofile), tmpdir, msgnum);
+
+ if (ast_filecopy(recdata->recording_file, tmpaudiofile, recdata->recording_ext)) {
+ ast_log(LOG_ERROR, "Audio file failed to copy to tmp dir. Probably low disk space.\n");
+
+ inprocess_count(recipient->mailbox, recipient->context, -1);
+ ast_unlock_path(dir);
+ free_user(recipient);
+ unlink(tmptxtfile);
+ return -1;
+ }
+
+ /* Alright, try to copy to the destination folder now. */
+ if (ast_filerename(tmpaudiofile, destination, recdata->recording_ext)) {
+ ast_log(LOG_ERROR, "Audio file failed to move to destination directory. Permissions/Overlap?\n");
+ inprocess_count(recipient->mailbox, recipient->context, -1);
+ ast_unlock_path(dir);
+ free_user(recipient);
+ unlink(tmptxtfile);
+ return -1;
+ }
+
+ snprintf(desttxtfile, sizeof(desttxtfile), "%s.txt", destination);
+ rename(tmptxtfile, desttxtfile);
+
+ if (chmod(desttxtfile, VOICEMAIL_FILE_MODE) < 0) {
+ ast_log(AST_LOG_ERROR, "Couldn't set permissions on voicemail text file %s: %s", desttxtfile, strerror(errno));
+ }
+
+
+ ast_unlock_path(dir);
+ inprocess_count(recipient->mailbox, recipient->context, -1);
+
+ /* If we copied something, we should store it either to ODBC or IMAP if we are using those. The STORE macro allows us
+ * to do both with one line and is also safe to use with file storage mode. Also, if we are using ODBC, now is a good
+ * time to create the voicemail database entry. */
+ if (ast_fileexists(destination, NULL, NULL) > 0) {
+ if (ast_check_realtime("voicemail_data")) {
+ get_date(date, sizeof(date));
+ ast_store_realtime("voicemail_data",
+ "origmailbox", recdata->mailbox,
+ "context", S_OR(recdata->context, ""),
+ "macrocontext", S_OR(recdata->call_macrocontext, ""),
+ "exten", S_OR(recdata->call_extension, ""),
+ "priority", recdata->call_priority,
+ "callerchan", S_OR(recdata->call_callerchan, "Unknown"),
+ "callerid", S_OR(recdata->call_callerid, "Unknown"),
+ "origdate", date,
+ "origtime", time(NULL),
+ "category", S_OR(category, ""),
+ "filename", tmptxtfile,
+ "duration", duration,
+ SENTINEL);
+ }
+
+ STORE(dir, recipient->mailbox, recipient->context, msgnum, NULL, recipient, fmt, 0, vms, "", msg_id);
+ }
+
+ free_user(recipient);
+ unlink(tmptxtfile);
+ return 0;
+}
+
+/*!
* \brief Prompts the user and records a voicemail to a mailbox.
* \param chan
* \param ext
@@ -5946,6 +6449,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
/* The meat of recording the message... All the announcements and beeps have been played*/
ast_copy_string(fmt, vmfmts, sizeof(fmt));
if (!ast_strlen_zero(fmt)) {
+ char msg_id[256] = "";
msgnum = 0;
#ifdef IMAP_STORAGE
@@ -6038,6 +6542,13 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
/* Store information */
txt = fdopen(txtdes, "w+");
if (txt) {
+ char msg_id_hash[256] = "";
+
+ /* Every voicemail msg gets its own unique msg id. The msg id is the originate time
+ * plus a hash of the extension, context, and callerid of the channel leaving the msg */
+ snprintf(msg_id_hash, sizeof(msg_id_hash), "%s%s%s", ast_channel_exten(chan), ast_channel_context(chan), callerid);
+ snprintf(msg_id, sizeof(msg_id), "%ld-%d", (long) time(NULL), ast_str_hash(msg_id_hash));
+
get_date(date, sizeof(date));
ast_callerid_merge(callerid, sizeof(callerid),
S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
@@ -6058,7 +6569,8 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
"callerid=%s\n"
"origdate=%s\n"
"origtime=%ld\n"
- "category=%s\n",
+ "category=%s\n"
+ "msg_id=%s\n",
ext,
ast_channel_context(chan),
ast_channel_macrocontext(chan),
@@ -6069,7 +6581,8 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
ast_channel_name(chan),
callerid,
date, (long) time(NULL),
- category ? category : "");
+ category ? category : "",
+ msg_id);
} else {
ast_log(AST_LOG_WARNING, "Error opening text file for output\n");
inprocess_count(vmu->mailbox, vmu->context, -1);
@@ -6079,7 +6592,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
res = ast_streamfile(chan, "vm-mailboxfull", ast_channel_language(chan));
goto leave_vm_out;
}
- res = play_record_review(chan, NULL, tmptxtfile, vmu->maxsecs, fmt, 1, vmu, &duration, &sound_duration, NULL, options->record_gain, vms, flag);
+ res = play_record_review(chan, NULL, tmptxtfile, vmu->maxsecs, fmt, 1, vmu, &duration, &sound_duration, NULL, options->record_gain, vms, flag, msg_id);
if (txt) {
fprintf(txt, "flag=%s\n", flag);
@@ -6141,7 +6654,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
* ODBC storage does the entire copy with SQL.
*/
if (ast_fileexists(fn, NULL, NULL) > 0) {
- STORE(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, fmt, duration, vms, flag);
+ STORE(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, fmt, duration, vms, flag, msg_id);
}
/* Are there to be more recipients of this message? */
@@ -6156,7 +6669,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
cntx++;
}
if ((recip = find_user(&recipu, cntx, exten))) {
- copy_message(chan, vmu, 0, msgnum, duration, recip, fmt, dir, flag);
+ copy_message(chan, vmu, 0, msgnum, duration, recip, fmt, dir, flag, NULL);
free_user(recip);
}
}
@@ -6274,7 +6787,7 @@ static int say_and_wait(struct ast_channel *chan, int num, const char *language)
return d;
}
-static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box)
+static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box, int *newmsg, int move)
{
#ifdef IMAP_STORAGE
/* we must use mbox(x) folder names, and copy the message there */
@@ -6307,7 +6820,11 @@ static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg
ast_debug(5, "Folder exists.\n");
else
ast_log(AST_LOG_NOTICE, "Folder %s created!\n", mbox(vmu, box));
- res = !mail_copy(vms->mailstream, sequence, (char *) mbox(vmu, box));
+ if (move) {
+ res = !mail_move(vms->mailstream, sequence, (char *) mbox(vmu, box));
+ } else {
+ res = !mail_copy(vms->mailstream, sequence, (char *) mbox(vmu, box));
+ }
ast_mutex_unlock(&vms->lock);
return res;
#else
@@ -6349,6 +6866,10 @@ static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg
COPY(dir, msg, ddir, x, username, context, sfn, dfn);
}
ast_unlock_path(ddir);
+
+ if (newmsg) {
+ *newmsg = x;
+ }
return 0;
#endif
}
@@ -6953,6 +7474,8 @@ static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu,
struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
#ifndef IMAP_STORAGE
signed char zero_gain = 0;
+#else
+ const char *msg_id = NULL;
#endif
const char *duration_str;
@@ -6979,11 +7502,14 @@ static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu,
#ifdef IMAP_STORAGE
/* Record new intro file */
+ if (msg_cfg && msg_cfg != CONFIG_STATUS_FILEINVALID) {
+ msg_id = ast_variable_retrieve(msg_cfg, "message", "msg_id");
+ }
make_file(vms->introfn, sizeof(vms->introfn), curdir, curmsg);
strncat(vms->introfn, "intro", sizeof(vms->introfn));
ast_play_and_wait(chan, INTRO);
ast_play_and_wait(chan, "beep");
- cmd = play_record_review(chan, NULL, vms->introfn, vmu->maxsecs, vm_fmts, 1, vmu, (int *) duration, NULL, NULL, record_gain, vms, flag);
+ cmd = play_record_review(chan, NULL, vms->introfn, vmu->maxsecs, vm_fmts, 1, vmu, (int *) duration, NULL, NULL, record_gain, vms, flag, msg_id);
if (cmd == -1) {
break;
}
@@ -7173,12 +7699,25 @@ static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu,
if (!ast_strlen_zero(vmu->email)) {
int attach_user_voicemail = ast_test_flag(vmu, VM_ATTACH);
+ char *msg_id = NULL;
+#ifdef IMAP_STORAGE
+ struct ast_config *msg_cfg;
+ struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+ char filename[PATH_MAX];
+
+ snprintf(filename, sizeof(filename), "%s.txt", fn);
+ msg_cfg = ast_config_load(filename, config_flags);
+ if (msg_cfg && msg_cfg != CONFIG_STATUS_FILEINVALID) {
+ msg_id = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "msg_id"));
+ ast_config_destroy(msg_cfg);
+ }
+#endif
if (attach_user_voicemail)
RETRIEVE(todir, msgnum, vmu->mailbox, vmu->context);
/* XXX possible imap issue, should category be NULL XXX */
- sendmail(myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, mbox(vmu, 0), cidnum, cidname, fn, NULL, fmt, duration, attach_user_voicemail, chan, category, flag);
+ sendmail(myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, mbox(vmu, 0), cidnum, cidname, fn, NULL, fmt, duration, attach_user_voicemail, chan, category, flag, msg_id);
if (attach_user_voicemail)
DISPOSE(todir, msgnum);
@@ -7438,9 +7977,24 @@ static int forward_message(struct ast_channel *chan, char *context, struct vm_st
long duration = 0;
struct vm_state vmstmp;
int copy_msg_result = 0;
+#ifdef IMAP_STORAGE
+ char filename[PATH_MAX];
+ struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+ const char *msg_id = NULL;
+ struct ast_config *msg_cfg;
+#endif
memcpy(&vmstmp, vms, sizeof(vmstmp));
- RETRIEVE(dir, curmsg, sender->mailbox, sender->context);
+ RETRIEVE(dir, curmsg, sender->mailbox, sender->context);
+#ifdef IMAP_STORAGE
+ make_file(filename, sizeof(filename), dir, curmsg);
+ strncat(filename, ".txt", sizeof(filename) - strlen(filename) - 1);
+ msg_cfg = ast_config_load(filename, config_flags);
+ if (msg_cfg && msg_cfg == CONFIG_STATUS_FILEINVALID) {
+ msg_id = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "msg_id"));
+ ast_config_destroy(msg_cfg);
+ }
+#endif
cmd = vm_forwardoptions(chan, sender, vmstmp.curdir, curmsg, vmfmts, S_OR(context, "default"), record_gain, &duration, &vmstmp, urgent_str);
if (!cmd) {
@@ -7459,7 +8013,7 @@ static int forward_message(struct ast_channel *chan, char *context, struct vm_st
if (!dstvms->mailstream) {
ast_log(AST_LOG_ERROR, "IMAP mailstream for %s is NULL\n", vmtmp->mailbox);
} else {
- copy_msg_result = STORE(vmstmp.curdir, vmtmp->mailbox, vmtmp->context, dstvms->curmsg, chan, vmtmp, fmt, duration, dstvms, urgent_str);
+ copy_msg_result = STORE(vmstmp.curdir, vmtmp->mailbox, vmtmp->context, dstvms->curmsg, chan, vmtmp, fmt, duration, dstvms, urgent_str, msg_id);
run_externnotify(vmtmp->context, vmtmp->mailbox, urgent_str);
}
} else {
@@ -7474,9 +8028,9 @@ static int forward_message(struct ast_channel *chan, char *context, struct vm_st
S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
vmstmp.fn, vmstmp.introfn, fmt, duration, attach_user_voicemail, chan,
- NULL, urgent_str);
+ NULL, urgent_str, msg_id);
#else
- copy_msg_result = copy_message(chan, sender, 0, curmsg, duration, vmtmp, fmt, dir, urgent_str);
+ copy_msg_result = copy_message(chan, sender, 0, curmsg, duration, vmtmp, fmt, dir, urgent_str, NULL);
#endif
saved_messages++;
AST_LIST_REMOVE_CURRENT(list);
@@ -8088,7 +8642,7 @@ static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu)
}
} else if ((!strcasecmp(vms->curbox, "INBOX") || !strcasecmp(vms->curbox, "Urgent")) && vms->heard[x] && ast_test_flag(vmu, VM_MOVEHEARD) && !vms->deleted[x]) {
/* Move to old folder before deleting */
- res = save_to_folder(vmu, vms, x, 1);
+ res = save_to_folder(vmu, vms, x, 1, NULL, 0);
if (res == ERROR_LOCK_PATH) {
/* If save failed do not delete the message */
ast_log(AST_LOG_WARNING, "Save failed. Not moving message: %s.\n", res == ERROR_LOCK_PATH ? "unable to lock path" : "destination folder full");
@@ -8098,7 +8652,7 @@ static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu)
}
} else if (vms->deleted[x] && vmu->maxdeletedmsg) {
/* Move to deleted folder */
- res = save_to_folder(vmu, vms, x, 10);
+ res = save_to_folder(vmu, vms, x, 10, NULL, 0);
if (res == ERROR_LOCK_PATH) {
/* If save failed do not delete the message */
vms->deleted[x] = 0;
@@ -9323,7 +9877,7 @@ static int vm_newuser(struct ast_channel *chan, struct ast_vm_user *vmu, struct
if (ast_test_flag(vmu, VM_FORCENAME)) {
snprintf(prefile, sizeof(prefile), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, vms->username);
if (ast_fileexists(prefile, NULL, NULL) < 1) {
- cmd = play_record_review(chan, "vm-rec-name", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-name", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
if (cmd < 0 || cmd == 't' || cmd == '#')
return cmd;
}
@@ -9333,14 +9887,14 @@ static int vm_newuser(struct ast_channel *chan, struct ast_vm_user *vmu, struct
if (ast_test_flag(vmu, VM_FORCEGREET)) {
snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, vms->username);
if (ast_fileexists(prefile, NULL, NULL) < 1) {
- cmd = play_record_review(chan, "vm-rec-unv", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-unv", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
if (cmd < 0 || cmd == 't' || cmd == '#')
return cmd;
}
snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, vms->username);
if (ast_fileexists(prefile, NULL, NULL) < 1) {
- cmd = play_record_review(chan, "vm-rec-busy", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-busy", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
if (cmd < 0 || cmd == 't' || cmd == '#')
return cmd;
}
@@ -9422,15 +9976,15 @@ static int vm_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct
switch (cmd) {
case '1': /* Record your unavailable message */
snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, vms->username);
- cmd = play_record_review(chan, "vm-rec-unv", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-unv", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
break;
case '2': /* Record your busy message */
snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, vms->username);
- cmd = play_record_review(chan, "vm-rec-busy", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-busy", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
break;
case '3': /* Record greeting */
snprintf(prefile, sizeof(prefile), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, vms->username);
- cmd = play_record_review(chan, "vm-rec-name", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-name", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
break;
case '4': /* manage the temporary greeting */
cmd = vm_tempgreeting(chan, vmu, vms, fmtc, record_gain);
@@ -9564,7 +10118,7 @@ static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, st
retries = 0;
RETRIEVE(prefile, -1, vmu->mailbox, vmu->context);
if (ast_fileexists(prefile, NULL, NULL) <= 0) {
- cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
if (cmd == -1) {
break;
}
@@ -9572,7 +10126,7 @@ static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, st
} else {
switch (cmd) {
case '1':
- cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
break;
case '2':
DELETE(prefile, -1, prefile, vmu);
@@ -9973,6 +10527,141 @@ 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)
+{
+ if (message_range_and_existence_check(vms, &msg_id, 1, &vms->curmsg, vmu)) {
+ return -1;
+ }
+ /* Found the msg, so play it back */
+
+ make_file(vms->fn, sizeof(vms->fn), vms->curdir, vms->curmsg);
+ make_file(vms->fn, sizeof(vms->fn), vms->curdir, vms->curmsg);
+
+#ifdef IMAP_STORAGE
+ /*IMAP storage stores any prepended message from a forward
+ * as a separate file from the rest of the message
+ */
+ if (!ast_strlen_zero(vms->introfn) && ast_fileexists(vms->introfn, NULL, NULL) > 0) {
+ wait_file(chan, vms, vms->introfn);
+ }
+#endif
+ if ((wait_file(chan, vms, vms->fn)) < 0) {
+ ast_log(AST_LOG_WARNING, "Playback of message %s failed\n", vms->fn);
+ } else {
+ vms->heard[vms->curmsg] = 1;
+ }
+
+ return 0;
+}
+
+/*!
+ * \brief Finds a message in a specific mailbox by msg_id and plays it to the channel
+ *
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+static int play_message_by_id(struct ast_channel *chan, const char *mailbox, const char *context, const char *msg_id)
+{
+ struct vm_state vms;
+ struct ast_vm_user *vmu = NULL, vmus;
+ int res = 0;
+ int open = 0;
+ int played = 0;
+ int i;
+
+ memset(&vmus, 0, sizeof(vmus));
+ memset(&vms, 0, sizeof(vms));
+
+ if (!(vmu = find_user(&vmus, context, mailbox))) {
+ goto play_msg_cleanup;
+ }
+
+ /* Iterate through every folder, find the msg, and play it */
+ for (i = 0; i < AST_VM_FOLDER_NUMBER && !played; i++) {
+ ast_copy_string(vms.username, mailbox, sizeof(vms.username));
+ vms.lastmsg = -1;
+
+ /* open the mailbox state */
+ if ((res = open_mailbox(&vms, vmu, i)) < 0) {
+ ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox);
+ res = -1;
+ goto play_msg_cleanup;
+ }
+ open = 1;
+
+ /* play msg if it exists in this mailbox */
+ if ((vms.lastmsg != -1) && !(play_message_by_id_helper(chan, vmu, &vms, msg_id))) {
+ played = 1;
+ }
+
+ /* close mailbox */
+ if ((res = close_mailbox(&vms, vmu) == ERROR_LOCK_PATH)) {
+ res = -1;
+ goto play_msg_cleanup;
+ }
+ open = 0;
+ }
+
+play_msg_cleanup:
+ if (!played) {
+ res = -1;
+ }
+
+ if (vmu && open) {
+ close_mailbox(&vms, vmu);
+ }
+
+#ifdef IMAP_STORAGE
+ if (vmu) {
+ vmstate_delete(&vms);
+ }
+#endif
+
+ return res;
+}
+
+static int vm_playmsgexec(struct ast_channel *chan, const char *data)
+{
+ char *parse;
+ char *mailbox = NULL;
+ char *context = NULL;
+ int res;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(mailbox);
+ AST_APP_ARG(msg_id);
+ );
+
+ if (ast_channel_state(chan) != AST_STATE_UP) {
+ ast_debug(1, "Before ast_answer\n");
+ ast_answer(chan);
+ }
+
+ if (ast_strlen_zero(data)) {
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (ast_strlen_zero(args.mailbox) || ast_strlen_zero(args.msg_id)) {
+ return -1;
+ }
+
+ if ((context = strchr(args.mailbox, '@'))) {
+ *context++ = '\0';
+ }
+ mailbox = args.mailbox;
+
+ res = play_message_by_id(chan, mailbox, context, args.msg_id);
+ pbx_builtin_setvar_helper(chan, "VOICEMAIL_PLAYBACKSTATUS", res ? "FAILED" : "SUCCESS");
+
+ return 0;
+}
+
static int vm_execmain(struct ast_channel *chan, const char *data)
{
/* XXX This is, admittedly, some pretty horrendous code. For some
@@ -10575,7 +11264,7 @@ static int vm_execmain(struct ast_channel *chan, const char *data)
break;
} else if (cmd > 0) {
box = cmd = cmd - '0';
- cmd = save_to_folder(vmu, &vms, vms.curmsg, cmd);
+ cmd = save_to_folder(vmu, &vms, vms.curmsg, cmd, NULL, 0);
if (cmd == ERROR_LOCK_PATH) {
res = cmd;
goto out;
@@ -10792,6 +11481,45 @@ static int vm_exec(struct ast_channel *chan, const char *data)
return res;
}
+static void generate_random_string(char *buf, size_t size)
+{
+ long val[4];
+ int x;
+
+ for (x=0; x<4; x++)
+ val[x] = ast_random();
+ snprintf(buf, size, "%08lx%08lx%08lx%08lx", val[0], val[1], val[2], val[3]);
+}
+
+static int add_message_id(struct ast_config *msg_cfg, char *dir, int msg, char *filename, char *id, size_t id_size, struct ast_vm_user *vmu, int folder)
+{
+ struct ast_variable *var;
+ struct ast_category *cat;
+ generate_random_string(id, id_size);
+
+ var = ast_variable_new("msg_id", id, "");
+ if (!var) {
+ return -1;
+ }
+
+ cat = ast_category_get(msg_cfg, "message");
+ if (!cat) {
+ ast_log(LOG_ERROR, "Voicemail data file %s/%d.txt has no [message] category?\n", dir, msg);
+ ast_variables_destroy(var);
+ return -1;
+ }
+
+ ast_variable_append(cat, var);
+
+ if (ast_config_text_file_save(filename, msg_cfg, "app_voicemail")) {
+ ast_log(LOG_WARNING, "Unable to update %s to have a message ID\n", filename);
+ return -1;
+ }
+
+ UPDATE_MSG_ID(dir, msg, id, vmu, msg_cfg, folder);
+ return 0;
+}
+
static struct ast_vm_user *find_or_create(const char *context, const char *box)
{
struct ast_vm_user *vmu;
@@ -12998,7 +13726,7 @@ AST_TEST_DEFINE(test_voicemail_msgcount)
break;
}
open_mailbox(&vms, vmu, folder2mbox[i]);
- STORE(tmp[i].dir, testmailbox, testcontext, 0, chan, vmu, "gsm", 600, &vms, strcmp(folders[i], "Urgent") ? "" : "Urgent");
+ STORE(tmp[i].dir, testmailbox, testcontext, 0, chan, vmu, "gsm", 600, &vms, strcmp(folders[i], "Urgent") ? "" : "Urgent", NULL);
/* hasvm-old, hasvm-urgent, hasvm-new, ic-old, ic-urgent, ic-new, ic2-old, ic2-urgent, ic2-new, mc-old, mc-urgent, mc-new */
for (j = 0; j < 3; j++) {
@@ -13160,7 +13888,7 @@ AST_TEST_DEFINE(test_voicemail_notify_endl)
test_items[which].location = test_items[which].u.strval;
}
- make_email_file(file, from, vmu, 0, testcontext, testmailbox, "INBOX", cidnum, cidname, attach, attach2, format, 999, 1, chan, NULL, 0, NULL);
+ make_email_file(file, from, vmu, 0, testcontext, testmailbox, "INBOX", cidnum, cidname, attach, attach2, format, 999, 1, chan, NULL, 0, NULL, NULL);
rewind(file);
while (fgets(buf, sizeof(buf), file)) {
if (
@@ -13352,6 +14080,7 @@ static int unload_module(void)
res |= ast_unregister_application(app2);
res |= ast_unregister_application(app3);
res |= ast_unregister_application(app4);
+ res |= ast_unregister_application(playmsg_app);
res |= ast_unregister_application(sayname_app);
res |= ast_custom_function_unregister(&mailbox_exists_acf);
res |= ast_custom_function_unregister(&vm_info_acf);
@@ -13405,6 +14134,7 @@ static int load_module(void)
res |= ast_register_application_xml(app2, vm_execmain);
res |= ast_register_application_xml(app3, vm_box_exists);
res |= ast_register_application_xml(app4, vmauthenticate);
+ res |= ast_register_application_xml(playmsg_app, vm_playmsgexec);
res |= ast_register_application_xml(sayname_app, vmsayname_exec);
res |= ast_custom_function_register(&mailbox_exists_acf);
res |= ast_custom_function_register(&vm_info_acf);
@@ -13424,7 +14154,7 @@ static int load_module(void)
ast_cli_register_multiple(cli_voicemail, ARRAY_LEN(cli_voicemail));
ast_data_register_multiple(vm_data_providers, ARRAY_LEN(vm_data_providers));
- ast_install_vm_functions(has_voicemail, inboxcount, inboxcount2, messagecount, sayname);
+ ast_install_vm_functions(has_voicemail, inboxcount, inboxcount2, messagecount, sayname, msg_create_from_file);
ast_realtime_require_field("voicemail", "uniqueid", RQ_UINTEGER3, 11, "password", RQ_CHAR, 10, SENTINEL);
ast_realtime_require_field("voicemail_data", "filename", RQ_CHAR, 30, "duration", RQ_UINTEGER3, 5, SENTINEL);
@@ -13698,7 +14428,7 @@ static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, s
static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
int outsidecaller, struct ast_vm_user *vmu, int *duration, int *sound_duration, const char *unlockdir,
- signed char record_gain, struct vm_state *vms, char *flag)
+ signed char record_gain, struct vm_state *vms, char *flag, const char *msg_id)
{
/* Record message & let caller review or re-record it, or set options if applicable */
int res = 0;
@@ -13743,7 +14473,7 @@ static int play_record_review(struct ast_channel *chan, char *playfile, char *re
ast_stream_and_wait(chan, "vm-msgsaved", "");
if (!outsidecaller) {
/* Saves to IMAP server only if imapgreeting=yes */
- STORE(recordfile, vmu->mailbox, vmu->context, -1, chan, vmu, fmt, *duration, vms, flag);
+ STORE(recordfile, vmu->mailbox, vmu->context, -1, chan, vmu, fmt, *duration, vms, flag, msg_id);
DISPOSE(recordfile, -1);
}
cmd = 't';
@@ -13933,11 +14663,899 @@ static int play_record_review(struct ast_channel *chan, char *playfile, char *re
return cmd;
}
+static struct ast_vm_msg_snapshot *vm_msg_snapshot_alloc(void)
+{
+ struct ast_vm_msg_snapshot *msg_snapshot;
+
+ if (!(msg_snapshot = ast_calloc(1, sizeof(*msg_snapshot)))) {
+ return NULL;
+ }
+
+ if (ast_string_field_init(msg_snapshot, 512)) {
+ ast_free(msg_snapshot);
+ return NULL;
+ }
+
+ return msg_snapshot;
+}
+
+static struct ast_vm_msg_snapshot *vm_msg_snapshot_destroy(struct ast_vm_msg_snapshot *msg_snapshot)
+{
+ ast_string_field_free_memory(msg_snapshot);
+ ast_free(msg_snapshot);
+
+ return NULL;
+}
+
+#ifdef TEST_FRAMEWORK
+
+int ast_vm_test_destroy_user(const char *context, const char *mailbox)
+{
+ struct ast_vm_user *vmu;
+
+ AST_LIST_LOCK(&users);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&users, vmu, list) {
+ if (!strncmp(context, vmu->context, sizeof(context))
+ && !strncmp(mailbox, vmu->mailbox, sizeof(mailbox))) {
+ AST_LIST_REMOVE_CURRENT(list);
+ ast_free(vmu);
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+ AST_LIST_UNLOCK(&users);
+ return 0;
+}
+
+int ast_vm_test_create_user(const char *context, const char *mailbox)
+{
+ struct ast_vm_user *vmu;
+
+ if (!(vmu = find_or_create(context, mailbox))) {
+ return -1;
+ }
+ populate_defaults(vmu);
+ return 0;
+}
+
+#endif
+
+/*!
+ * \brief Create and store off all the msgs in an open mailbox
+ *
+ * \note TODO XXX This function should work properly for all
+ * voicemail storage options, but is far more expensive for
+ * ODBC at the moment. This is because the RETRIEVE macro
+ * not only pulls out the message's meta data file from the
+ * database, but also the actual audio for each message, temporarily
+ * writing it to the file system. This is an area that needs
+ * to be made more efficient.
+ */
+static int vm_msg_snapshot_create(struct ast_vm_user *vmu,
+ struct vm_state *vms,
+ struct ast_vm_mailbox_snapshot *mailbox_snapshot,
+ int snapshot_index,
+ int mailbox_index,
+ int descending,
+ enum ast_vm_snapshot_sort_val sort_val)
+{
+ struct ast_vm_msg_snapshot *msg_snapshot;
+ struct ast_vm_msg_snapshot *msg_snapshot_tmp;
+ struct ast_config *msg_cfg;
+ struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+ char filename[PATH_MAX];
+ const char *value;
+
+ for (vms->curmsg = 0; vms->curmsg <= vms->lastmsg; vms->curmsg++) {
+ int inserted = 0;
+ /* Find the msg */
+ make_file(vms->fn, sizeof(vms->fn), vms->curdir, vms->curmsg);
+ snprintf(filename, sizeof(filename), "%s.txt", vms->fn);
+ RETRIEVE(vms->curdir, vms->curmsg, vmu->mailbox, vmu->context);
+ msg_cfg = ast_config_load(filename, config_flags);
+ if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) {
+ DISPOSE(vms->curdir, vms->curmsg);
+ continue;
+ }
+
+ /* Create the snapshot object */
+ if (!(msg_snapshot = vm_msg_snapshot_alloc())) {
+ ast_config_destroy(msg_cfg);
+ return -1;
+ }
+
+ /* Fill in the snapshot object */
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "msg_id"))) {
+ ast_string_field_set(msg_snapshot, msg_id, value);
+ } else {
+ /* Message snapshots *really* should have a
+ * message ID. Add one to the message config
+ * if it does not already exist
+ */
+ char id[33];
+ if (!(add_message_id(msg_cfg, vms->curdir, vms->curmsg,
+ filename, id, sizeof(id), vmu, mailbox_index))) {
+ ast_string_field_set(msg_snapshot, msg_id, id);
+ } else {
+ ast_log(LOG_WARNING, "Unable to create a message ID for message %s/%d\n", vms->curdir, vms->curmsg);
+ }
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "callerid"))) {
+ ast_string_field_set(msg_snapshot, callerid, value);
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "callerchan"))) {
+ ast_string_field_set(msg_snapshot, callerchan, value);
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "exten"))) {
+ ast_string_field_set(msg_snapshot, exten, value);
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "origdate"))) {
+ ast_string_field_set(msg_snapshot, origdate, value);
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "origtime"))) {
+ ast_string_field_set(msg_snapshot, origtime, value);
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "duration"))) {
+ ast_string_field_set(msg_snapshot, duration, value);
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "flag"))) {
+ ast_string_field_set(msg_snapshot, flag, value);
+ }
+ msg_snapshot->msg_number = vms->curmsg;
+ ast_string_field_set(msg_snapshot, folder_name, mailbox_folders[mailbox_index]);
+
+ /* store msg snapshot in mailbox snapshot */
+ switch (sort_val) {
+ default:
+ case AST_VM_SNAPSHOT_SORT_BY_ID:
+ if (descending) {
+ AST_LIST_INSERT_HEAD(&mailbox_snapshot->snapshots[snapshot_index], msg_snapshot, msg);
+ } else {
+ AST_LIST_INSERT_TAIL(&mailbox_snapshot->snapshots[snapshot_index], msg_snapshot, msg);
+ }
+ inserted = 1;
+ break;
+ case AST_VM_SNAPSHOT_SORT_BY_TIME:
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&mailbox_snapshot->snapshots[snapshot_index], msg_snapshot_tmp, msg) {
+ int val = strcmp(msg_snapshot->origtime, msg_snapshot_tmp->origtime);
+ if (descending && val >= 0) {
+ AST_LIST_INSERT_BEFORE_CURRENT(msg_snapshot, msg);
+ inserted = 1;
+ break;
+ } else if (!descending && val <= 0) {
+ AST_LIST_INSERT_BEFORE_CURRENT(msg_snapshot, msg);
+ inserted = 1;
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ break;
+ }
+
+ if (!inserted) {
+ AST_LIST_INSERT_TAIL(&mailbox_snapshot->snapshots[snapshot_index], msg_snapshot, msg);
+ }
+
+ mailbox_snapshot->total_msg_num++;
+
+ /* cleanup configs and msg */
+ ast_config_destroy(msg_cfg);
+ DISPOSE(vms->curdir, vms->curmsg);
+ }
+
+ return 0;
+}
+
+struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_create(const char *mailbox,
+ const char *context,
+ const char *folder,
+ int descending,
+ enum ast_vm_snapshot_sort_val sort_val,
+ int combine_INBOX_and_OLD)
+{
+ struct ast_vm_mailbox_snapshot *mailbox_snapshot;
+ struct vm_state vms;
+ struct ast_vm_user *vmu = NULL, vmus;
+ int res;
+ int i;
+ int this_index_only = -1;
+ int open = 0;
+ int inbox_index = 0;
+ int old_index = 1;
+
+ if (ast_strlen_zero(mailbox)) {
+ ast_log(LOG_WARNING, "Cannot create a mailbox snapshot since no mailbox was specified\n");
+ return NULL;
+ }
+
+ memset(&vmus, 0, sizeof(vmus));
+
+ if (!(ast_strlen_zero(folder))) {
+ /* find the folder index */
+ for (i = 0; i < AST_VM_FOLDER_NUMBER; i++) {
+ if (!strcasecmp(mailbox_folders[i], folder)) {
+ this_index_only = i;
+ break;
+ }
+ }
+ if (this_index_only == -1) {
+ /* Folder was specified and it did not match any folder in our list */
+ return NULL;
+ }
+ }
+
+ if (!(vmu = find_user(&vmus, context, mailbox))) {
+ ast_log(AST_LOG_WARNING, "Failed to create mailbox snapshot for unknown voicemail user %s@%s\n", mailbox, context);
+ return NULL;
+ }
+
+ if (!(mailbox_snapshot = ast_calloc(1, sizeof(*mailbox_snapshot)))) {
+ ast_log(AST_LOG_ERROR, "Failed to allocate memory for mailbox snapshot\n");
+ return NULL;
+ }
+
+ for (i = 0; i < AST_VM_FOLDER_NUMBER; i++) {
+ int combining_old = 0;
+ if ((i == old_index) && (combine_INBOX_and_OLD)) {
+ combining_old = 1;
+ }
+
+ /* This if statement is confusing looking. Here is what it means in english.
+ * - If a folder is given to the function and that folder's index is not the one we are iterating over, skip it...
+ * - Unless the folder provided is the INBOX folder and the current index is the OLD folder and we are combining OLD and INBOX msgs.
+ */
+ if ((this_index_only != -1) && (this_index_only != i) && !(combining_old && i == old_index && this_index_only == inbox_index)) {
+ continue;
+ }
+
+ memset(&vms, 0, sizeof(vms));
+ ast_copy_string(vms.username, mailbox, sizeof(vms.username));
+ vms.lastmsg = -1;
+ open = 0;
+
+ /* open the mailbox state */
+ if ((res = open_mailbox(&vms, vmu, i)) < 0) {
+ ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox);
+ goto snapshot_cleanup;
+ }
+ open = 1;
+
+ /* Iterate through each msg, storing off info */
+ if (vms.lastmsg != -1) {
+ if ((vm_msg_snapshot_create(vmu, &vms, mailbox_snapshot, combining_old ? inbox_index : i, i, descending, sort_val))) {
+ ast_log(LOG_WARNING, "Failed to create msg snapshots for %s@%s\n", mailbox, context);
+ goto snapshot_cleanup;
+ }
+ }
+
+ /* close mailbox */
+ if ((res = close_mailbox(&vms, vmu) == ERROR_LOCK_PATH)) {
+ goto snapshot_cleanup;
+ }
+ open = 0;
+ }
+
+snapshot_cleanup:
+ if (vmu && open) {
+ close_mailbox(&vms, vmu);
+ }
+
+#ifdef IMAP_STORAGE
+ if (vmu) {
+ vmstate_delete(&vms);
+ }
+#endif
+
+ return mailbox_snapshot;
+}
+
+struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_destroy(struct ast_vm_mailbox_snapshot *mailbox_snapshot)
+{
+ int i;
+ struct ast_vm_msg_snapshot *msg_snapshot;
+
+ for (i = 0; i < AST_VM_FOLDER_NUMBER; i++) {
+ while ((msg_snapshot = AST_LIST_REMOVE_HEAD(&mailbox_snapshot->snapshots[i], msg))) {
+ msg_snapshot = vm_msg_snapshot_destroy(msg_snapshot);
+ }
+ }
+ ast_free(mailbox_snapshot);
+ return NULL;
+}
+
+struct ast_str *vm_mailbox_snapshot_str(const char *mailbox, const char *context)
+{
+ struct ast_vm_mailbox_snapshot *mailbox_snapshot = ast_vm_mailbox_snapshot_create(mailbox, context, NULL, 0, AST_VM_SNAPSHOT_SORT_BY_ID, 0);
+ struct ast_vm_msg_snapshot *msg_snapshot;
+ int i;
+ struct ast_str *str;
+
+ if (!mailbox_snapshot) {
+ return NULL;
+ }
+
+ if (!(str = ast_str_create(512))) {
+ return NULL;
+ mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot);
+ }
+
+ for (i = 0; i < AST_VM_FOLDER_NUMBER; i++) {
+ ast_str_append(&str, 0, "FOLDER: %s\n", mailbox_folders[i]);
+ AST_LIST_TRAVERSE(&mailbox_snapshot->snapshots[i], msg_snapshot, msg) {
+ ast_str_append(&str, 0, "MSG Number: %d\n", msg_snapshot->msg_number);
+ ast_str_append(&str, 0, "MSG ID: %s\n", msg_snapshot->msg_id);
+ ast_str_append(&str, 0, "CALLER ID: %s\n", msg_snapshot->callerid);
+ ast_str_append(&str, 0, "CALLER CHAN: %s\n", msg_snapshot->callerchan);
+ ast_str_append(&str, 0, "CALLER EXTEN: %s\n", msg_snapshot->exten);
+ ast_str_append(&str, 0, "DATE: %s\n", msg_snapshot->origdate);
+ ast_str_append(&str, 0, "TIME: %s\n", msg_snapshot->origtime);
+ ast_str_append(&str, 0, "DURATION: %s\n", msg_snapshot->duration);
+ ast_str_append(&str, 0, "FOLDER NAME: %s\n", msg_snapshot->folder_name);
+ ast_str_append(&str, 0, "FLAG: %s\n", msg_snapshot->flag);
+ ast_str_append(&str, 0, "\n");
+ }
+ }
+
+ mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot);
+ return str;
+}
+
+/*!
+ * \brief common bounds checking and existence check for Voicemail API functions.
+ *
+ * \details
+ * This is called by ast_vm_msg_move, ast_vm_msg_remove, and ast_vm_msg_forward to
+ * ensure that data passed in are valid. This ensures that given the
+ * desired message IDs, they can be found.
+ *
+ * \param vms The voicemail state corresponding to an open mailbox
+ * \param msg_ids An array of message identifiers
+ * \param num_msgs The number of identifiers in msg_ids
+ * \param msg_nums [out] The message indexes corresponding to the given
+ * message IDs
+ * \pre vms must have open_mailbox() called on it prior to this function.
+ *
+ * \retval -1 Failure
+ * \retval 0 Success
+ */
+static int message_range_and_existence_check(struct vm_state *vms, const char *msg_ids [], size_t num_msgs, int *msg_nums, struct ast_vm_user *vmu)
+{
+ int i;
+ int res = 0;
+ for (i = 0; i < num_msgs; ++i) {
+ const char *msg_id = msg_ids[i];
+ int found = 0;
+ for (vms->curmsg = 0; vms->curmsg <= vms->lastmsg; vms->curmsg++) {
+ const char *other_msg_id;
+ char filename[PATH_MAX];
+ struct ast_config *msg_cfg;
+ struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+
+ make_file(vms->fn, sizeof(vms->fn), vms->curdir, vms->curmsg);
+ snprintf(filename, sizeof(filename), "%s.txt", vms->fn);
+ RETRIEVE(vms->curdir, vms->curmsg, vmu->mailbox, vmu->context);
+ msg_cfg = ast_config_load(filename, config_flags);
+ if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) {
+ DISPOSE(vms->curdir, vms->curmsg);
+ res = -1;
+ goto done;
+ }
+
+ other_msg_id = ast_variable_retrieve(msg_cfg, "message", "msg_id");
+
+ if (!ast_strlen_zero(other_msg_id) && !strcmp(other_msg_id, msg_id)) {
+ /* Message found. We can get out of this inner loop
+ * and move on to the next message to find
+ */
+ found = 1;
+ msg_nums[i] = vms->curmsg;
+ ast_config_destroy(msg_cfg);
+ DISPOSE(vms->curdir, vms->curmsg);
+ break;
+ }
+ DISPOSE(vms->curdir, vms->curmsg);
+ }
+ if (!found) {
+ /* If we can't find one of the message IDs requested, then OH NO! */
+ res = -1;
+ goto done;
+ }
+ }
+
+done:
+ return res;
+}
+
+static void notify_new_state(struct ast_vm_user *vmu)
+{
+ int new = 0, old = 0, urgent = 0;
+ char ext_context[1024];
+
+ snprintf(ext_context, sizeof(ext_context), "%s@%s", vmu->mailbox, vmu->context);
+ run_externnotify(vmu->context, vmu->mailbox, NULL);
+ ast_app_inboxcount2(ext_context, &urgent, &new, &old);
+ queue_mwi_event(ext_context, urgent, new, old);
+}
+
+int ast_vm_msg_forward(const char *from_mailbox,
+ const char *from_context,
+ const char *from_folder,
+ const char *to_mailbox,
+ const char *to_context,
+ const char *to_folder,
+ size_t num_msgs,
+ const char *msg_ids [],
+ int delete_old)
+{
+ struct vm_state from_vms;
+ struct ast_vm_user *vmu = NULL, vmus;
+ struct ast_vm_user *to_vmu = NULL, to_vmus;
+ struct ast_config *msg_cfg;
+ struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+ char filename[PATH_MAX];
+ int from_folder_index;
+ int open = 0;
+ int res = 0;
+ int i;
+ int *msg_nums;
+
+ if (ast_strlen_zero(from_mailbox) || ast_strlen_zero(to_mailbox)) {
+ ast_log(LOG_WARNING, "Cannot forward message because either the from or to mailbox was not specified\n");
+ return -1;
+ }
+
+ if (!num_msgs) {
+ ast_log(LOG_WARNING, "Invalid number of messages specified to forward: %zu\n", num_msgs);
+ return -1;
+ }
+
+ if (ast_strlen_zero(from_folder) || ast_strlen_zero(to_folder)) {
+ ast_log(LOG_WARNING, "Cannot forward message because the from_folder or to_folder was not specified\n");
+ return -1;
+ }
+
+ memset(&vmus, 0, sizeof(vmus));
+ memset(&to_vmus, 0, sizeof(to_vmus));
+ memset(&from_vms, 0, sizeof(from_vms));
+
+ from_folder_index = get_folder_by_name(from_folder);
+ if (from_folder_index == -1) {
+ return -1;
+ }
+
+ if (get_folder_by_name(to_folder) == -1) {
+ return -1;
+ }
+
+ if (!(vmu = find_user(&vmus, from_context, from_mailbox))) {
+ ast_log(LOG_WARNING, "Can't find voicemail user to forward from (%s@%s)\n", from_mailbox, from_context);
+ return -1;
+ }
+
+ if (!(to_vmu = find_user(&to_vmus, to_context, to_mailbox))) {
+ ast_log(LOG_WARNING, "Can't find voicemail user to forward to (%s@%s)\n", to_mailbox, to_context);
+ return -1;
+ }
+
+ ast_copy_string(from_vms.username, from_mailbox, sizeof(from_vms.username));
+ from_vms.lastmsg = -1;
+ open = 0;
+
+ /* open the mailbox state */
+ if ((res = open_mailbox(&from_vms, vmu, from_folder_index)) < 0) {
+ ast_log(LOG_WARNING, "Could not open mailbox %s\n", from_mailbox);
+ res = -1;
+ goto vm_forward_cleanup;
+ }
+
+ open = 1;
+
+ if ((from_vms.lastmsg + 1) < num_msgs) {
+ ast_log(LOG_WARNING, "Folder %s has less than %zu messages\n", from_folder, num_msgs);
+ res = -1;
+ goto vm_forward_cleanup;
+ }
+
+ if (!(msg_nums = alloca(sizeof(int) * num_msgs)))
+ {
+ ast_log(LOG_ERROR, "Unable to allocate stack space! Expect awful things!\n");
+ res = -1;
+ goto vm_forward_cleanup;
+ }
+
+ if ((res = message_range_and_existence_check(&from_vms, msg_ids, num_msgs, msg_nums, vmu) < 0)) {
+ goto vm_forward_cleanup;
+ }
+
+ /* Now we actually forward the messages */
+ for (i = 0; i < num_msgs; i++) {
+ int cur_msg = msg_nums[i];
+ int duration = 0;
+ const char *value;
+
+ make_file(from_vms.fn, sizeof(from_vms.fn), from_vms.curdir, cur_msg);
+ snprintf(filename, sizeof(filename), "%s.txt", from_vms.fn);
+ RETRIEVE(from_vms.curdir, cur_msg, vmu->mailbox, vmu->context);
+ msg_cfg = ast_config_load(filename, config_flags);
+ /* XXX This likely will not fail since we previously ensured that the
+ * message we are looking for exists. However, there still could be some
+ * circumstance where this fails, so atomicity is not guaranteed.
+ */
+ if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) {
+ DISPOSE(from_vms.curdir, cur_msg);
+ continue;
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "duration"))) {
+ duration = atoi(value);
+ }
+
+ copy_message(NULL, vmu, from_folder_index, cur_msg, duration, to_vmu, vmfmts, from_vms.curdir, "", to_folder);
+
+ if (delete_old) {
+ from_vms.deleted[cur_msg] = 1;
+ }
+ ast_config_destroy(msg_cfg);
+ DISPOSE(from_vms.curdir, cur_msg);
+ }
+
+ /* close mailbox */
+ if ((res = close_mailbox(&from_vms, vmu) == ERROR_LOCK_PATH)) {
+ res = -1;
+ goto vm_forward_cleanup;
+ }
+ open = 0;
+
+vm_forward_cleanup:
+ if (vmu && open) {
+ close_mailbox(&from_vms, vmu);
+ }
+#ifdef IMAP_STORAGE
+ if (vmu) {
+ vmstate_delete(&from_vms);
+ }
+#endif
+
+ if (!res) {
+ notify_new_state(to_vmu);
+ }
+
+ return res;
+}
+
+int ast_vm_msg_move(const char *mailbox,
+ const char *context,
+ size_t num_msgs,
+ const char *oldfolder,
+ const char *old_msg_ids [],
+ const char *newfolder)
+{
+ struct vm_state vms;
+ struct ast_vm_user *vmu = NULL, vmus;
+ int old_folder_index;
+ int new_folder_index;
+ int open = 0;
+ int res = 0;
+ int i;
+ int *old_msg_nums;
+
+ if (ast_strlen_zero(mailbox)) {
+ ast_log(LOG_WARNING, "Cannot move message because no mailbox was specified\n");
+ return -1;
+ }
+
+ if (!num_msgs) {
+ ast_log(LOG_WARNING, "Invalid number of messages specified to move: %zu\n", num_msgs);
+ return -1;
+ }
+
+ if (ast_strlen_zero(oldfolder) || ast_strlen_zero(newfolder)) {
+ ast_log(LOG_WARNING, "Cannot move message because either oldfolder or newfolder was not specified\n");
+ return -1;
+ }
+
+ old_folder_index = get_folder_by_name(oldfolder);
+ new_folder_index = get_folder_by_name(newfolder);
+
+ memset(&vmus, 0, sizeof(vmus));
+ memset(&vms, 0, sizeof(vms));
+
+ if (old_folder_index == -1 || new_folder_index == -1) {
+ return -1;
+ }
+
+ if (!(vmu = find_user(&vmus, context, mailbox))) {
+ return -1;
+ }
+
+ ast_copy_string(vms.username, mailbox, sizeof(vms.username));
+ vms.lastmsg = -1;
+ open = 0;
+
+ /* open the mailbox state */
+ if ((res = open_mailbox(&vms, vmu, old_folder_index)) < 0) {
+ ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox);
+ res = -1;
+ goto vm_move_cleanup;
+ }
+
+ open = 1;
+
+ if ((vms.lastmsg + 1) < num_msgs) {
+ ast_log(LOG_WARNING, "Folder %s has less than %zu messages\n", oldfolder, num_msgs);
+ res = -1;
+ goto vm_move_cleanup;
+ }
+
+ if (!(old_msg_nums = alloca(sizeof(int) * num_msgs))) {
+ ast_log(LOG_ERROR, "Unable to allocate stack space! Expect awful things!\n");
+ res = -1;
+ goto vm_move_cleanup;
+ }
+
+ if ((res = message_range_and_existence_check(&vms, old_msg_ids, num_msgs, old_msg_nums, vmu)) < 0) {
+ goto vm_move_cleanup;
+ }
+
+ /* Now actually move the message */
+ for (i = 0; i < num_msgs; ++i) {
+ if (save_to_folder(vmu, &vms, old_msg_nums[i], new_folder_index, NULL, 0)) {
+ res = -1;
+ goto vm_move_cleanup;
+ }
+ vms.deleted[old_msg_nums[i]] = 1;
+ }
+
+ /* close mailbox */
+ if ((res = close_mailbox(&vms, vmu) == ERROR_LOCK_PATH)) {
+ res = -1;
+ goto vm_move_cleanup;
+ }
+ open = 0;
+
+vm_move_cleanup:
+ if (vmu && open) {
+ close_mailbox(&vms, vmu);
+ }
+#ifdef IMAP_STORAGE
+ if (vmu) {
+ vmstate_delete(&vms);
+ }
+#endif
+
+ if (!res) {
+ notify_new_state(vmu);
+ }
+
+ return res;
+}
+
+int ast_vm_msg_remove(const char *mailbox,
+ const char *context,
+ size_t num_msgs,
+ const char *folder,
+ const char *msgs[])
+{
+ struct vm_state vms;
+ struct ast_vm_user *vmu = NULL, vmus;
+ int folder_index;
+ int open = 0;
+ int res = 0;
+ int i;
+ int *msg_nums;
+
+ if (ast_strlen_zero(mailbox)) {
+ ast_log(LOG_WARNING, "Cannot remove message because no mailbox was specified\n");
+ return -1;
+ }
+
+ if (!num_msgs) {
+ ast_log(LOG_WARNING, "Invalid number of messages specified to remove: %zu\n", num_msgs);
+ return -1;
+ }
+
+ if (ast_strlen_zero(folder)) {
+ ast_log(LOG_WARNING, "Cannot remove message because no folder was specified\n");
+ return -1;
+ }
+
+ memset(&vmus, 0, sizeof(vmus));
+ memset(&vms, 0, sizeof(vms));
+
+ folder_index = get_folder_by_name(folder);
+ if (folder_index == -1) {
+ ast_log(LOG_WARNING, "Could not remove msgs from unknown folder %s\n", folder);
+ return -1;
+ }
+
+ if (!(vmu = find_user(&vmus, context, mailbox))) {
+ ast_log(LOG_WARNING, "Can't find voicemail user to remove msg from (%s@%s)\n", mailbox, context);
+ return -1;
+ }
+
+ ast_copy_string(vms.username, mailbox, sizeof(vms.username));
+ vms.lastmsg = -1;
+ open = 0;
+
+ /* open the mailbox state */
+ if ((res = open_mailbox(&vms, vmu, folder_index)) < 0) {
+ ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox);
+ res = -1;
+ goto vm_remove_cleanup;
+ }
+
+ open = 1;
+
+ if ((vms.lastmsg + 1) < num_msgs) {
+ ast_log(LOG_WARNING, "Folder %s has less than %zu messages\n", folder, num_msgs);
+ res = -1;
+ goto vm_remove_cleanup;
+ }
+
+ if (!(msg_nums = alloca(sizeof(int) * num_msgs))) {
+ ast_log(LOG_ERROR, "Unable to allocate stack space! Expect awful things\n");
+ res = -1;
+ goto vm_remove_cleanup;
+ }
+
+ if ((res = message_range_and_existence_check(&vms, msgs, num_msgs, msg_nums, vmu)) < 0) {
+ goto vm_remove_cleanup;
+ }
+
+ for (i = 0; i < num_msgs; i++) {
+ vms.deleted[msg_nums[i]] = 1;
+ }
+
+ /* close mailbox */
+ if ((res = close_mailbox(&vms, vmu) == ERROR_LOCK_PATH)) {
+ res = -1;
+ ast_log(AST_LOG_ERROR, "Failed to close mailbox folder %s while removing msgs\n", folder);
+ goto vm_remove_cleanup;
+ }
+ open = 0;
+
+vm_remove_cleanup:
+ if (vmu && open) {
+ close_mailbox(&vms, vmu);
+ }
+#ifdef IMAP_STORAGE
+ if (vmu) {
+ vmstate_delete(&vms);
+ }
+#endif
+
+ if (!res) {
+ notify_new_state(vmu);
+ }
+
+ return res;
+}
+
+const char *ast_vm_index_to_foldername(unsigned int index)
+{
+ if (index >= AST_VM_FOLDER_NUMBER) {
+ return "";
+ }
+ return mailbox_folders[index];
+}
+
+int ast_vm_msg_play(struct ast_channel *chan,
+ const char *mailbox,
+ const char *context,
+ const char *folder,
+ const char *msg_id,
+ ast_vm_msg_play_cb cb)
+{
+ struct vm_state vms;
+ struct ast_vm_user *vmu = NULL, vmus;
+ int res = 0;
+ int open = 0;
+ int i;
+ char filename[PATH_MAX];
+ struct ast_config *msg_cfg;
+ struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+ int duration = 0;
+ const char *value;
+
+ if (ast_strlen_zero(mailbox)) {
+ ast_log(LOG_WARNING, "Cannot play message because no mailbox was specified\n");
+ return -1;
+ }
+
+ if (ast_strlen_zero(folder)) {
+ ast_log(LOG_WARNING, "Cannot play message because no folder was specified\n");
+ return -1;
+ }
+
+ if (ast_strlen_zero(msg_id)) {
+ ast_log(LOG_WARNING, "Cannot play message because no message number was specified\n");
+ return -1;
+ }
+
+ memset(&vmus, 0, sizeof(vmus));
+ memset(&vms, 0, sizeof(vms));
+
+ if (ast_strlen_zero(context)) {
+ context = "default";
+ }
+
+ if (!(vmu = find_user(&vmus, context, mailbox))) {
+ return -1;
+ }
+
+ i = get_folder_by_name(folder);
+ ast_copy_string(vms.username, mailbox, sizeof(vms.username));
+ vms.lastmsg = -1;
+ if ((res = open_mailbox(&vms, vmu, i)) < 0) {
+ ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox);
+ goto play2_msg_cleanup;
+ }
+ open = 1;
+
+ if (message_range_and_existence_check(&vms, &msg_id, 1, &vms.curmsg, vmu)) {
+ res = -1;
+ goto play2_msg_cleanup;
+ }
+
+ /* Find the msg */
+ make_file(vms.fn, sizeof(vms.fn), vms.curdir, vms.curmsg);
+ snprintf(filename, sizeof(filename), "%s.txt", vms.fn);
+ RETRIEVE(vms.curdir, vms.curmsg, vmu->mailbox, vmu->context);
+
+ msg_cfg = ast_config_load(filename, config_flags);
+ if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) {
+ DISPOSE(vms.curdir, vms.curmsg);
+ res = -1;
+ goto play2_msg_cleanup;
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "duration"))) {
+ duration = atoi(value);
+ }
+ ast_config_destroy(msg_cfg);
+
+#ifdef IMAP_STORAGE
+ /*IMAP storage stores any prepended message from a forward
+ * as a separate file from the rest of the message
+ */
+ if (!ast_strlen_zero(vms.introfn) && ast_fileexists(vms.introfn, NULL, NULL) > 0) {
+ wait_file(chan, &vms, vms.introfn);
+ }
+#endif
+ if (cb) {
+ cb(chan, vms.fn, duration);
+ } else if ((wait_file(chan, &vms, vms.fn)) < 0) {
+ ast_log(AST_LOG_WARNING, "Playback of message %s failed\n", vms.fn);
+ } else {
+ res = 0;
+ }
+
+ vms.heard[vms.curmsg] = 1;
+
+ /* cleanup configs and msg */
+ DISPOSE(vms.curdir, vms.curmsg);
+
+play2_msg_cleanup:
+ if (vmu && open) {
+ close_mailbox(&vms, vmu);
+ }
+
+#ifdef IMAP_STORAGE
+ if (vmu) {
+ vmstate_delete(&vms);
+ }
+#endif
+
+ if (!res) {
+ notify_new_state(vmu);
+ }
+
+ return res;
+}
+
/* This is a workaround so that menuselect displays a proper description
* AST_MODULE_INFO(, , "Comedian Mail (Voicemail System)"
*/
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, tdesc,
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, tdesc,
.load = load_module,
.unload = unload_module,
.reload = reload,
diff --git a/apps/app_voicemail.exports.in b/apps/app_voicemail.exports.in
index 3520d2216..e66bb359c 100644
--- a/apps/app_voicemail.exports.in
+++ b/apps/app_voicemail.exports.in
@@ -15,6 +15,15 @@
LINKER_SYMBOL_PREFIXmm_notify;
LINKER_SYMBOL_PREFIXmm_searched;
LINKER_SYMBOL_PREFIXmm_status;
+ LINKER_SYMBOL_PREFIXast_vm_mailbox_snapshot_create;
+ LINKER_SYMBOL_PREFIXast_vm_mailbox_snapshot_destroy;
+ LINKER_SYMBOL_PREFIXast_vm_msg_move;
+ LINKER_SYMBOL_PREFIXast_vm_msg_remove;
+ LINKER_SYMBOL_PREFIXast_vm_msg_forward;
+ LINKER_SYMBOL_PREFIXast_vm_index_to_foldername;
+ LINKER_SYMBOL_PREFIXast_vm_msg_play;
+ LINKER_SYMBOL_PREFIXast_vm_test_create_user;
+ LINKER_SYMBOL_PREFIXast_vm_test_destroy_user;
local:
*;
};
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 289a807cf..8456b0b99 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -230,6 +230,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
affect the speed of the program at all. They can be considered to be documentation.
*/
/* #define REF_DEBUG 1 */
+
#include "asterisk/lock.h"
#include "asterisk/config.h"
#include "asterisk/module.h"
@@ -277,7 +278,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "sip/include/dialog.h"
#include "sip/include/dialplan_functions.h"
#include "sip/include/security_events.h"
-
+#include "asterisk/sip_api.h"
/*** DOCUMENTATION
<application name="SIPDtmfMode" language="en_US">
@@ -339,6 +340,20 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<para>Always returns <literal>0</literal>.</para>
</description>
</application>
+ <application name="SIPSendCustomINFO" language="en_US">
+ <synopsis>
+ Send a custom INFO frame on specified channels.
+ </synopsis>
+ <syntax>
+ <parameter name="Data" required="true" />
+ <parameter name="UserAgent" required="false" />
+ </syntax>
+ <description>
+ <para>SIPSendCustomINFO() allows you to send a custom INFO message on all
+ active SIP channels or on channels with the specified User Agent. This
+ application is only available if TEST_FRAMEWORK is defined.</para>
+ </description>
+ </application>
<function name="SIP_HEADER" language="en_US">
<synopsis>
Gets the specified SIP header from an incoming INVITE message.
@@ -671,7 +686,8 @@ static const struct sip_reasons {
{ AST_REDIRECTING_REASON_FOLLOW_ME, "follow-me" },
{ AST_REDIRECTING_REASON_OUT_OF_ORDER, "out-of-service" },
{ AST_REDIRECTING_REASON_AWAY, "away" },
- { AST_REDIRECTING_REASON_CALL_FWD_DTE, "unknown"}
+ { AST_REDIRECTING_REASON_CALL_FWD_DTE, "unknown"},
+ { AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm"},
};
@@ -1090,6 +1106,13 @@ static void destroy_escs(void)
}
}
+struct state_notify_data {
+ int state;
+ int presence_state;
+ const char *presence_subtype;
+ const char *presence_message;
+};
+
/*!
* \details
* Here we implement the container for dialogs which are in the
@@ -1373,7 +1396,8 @@ static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target
static int do_magic_pickup(struct ast_channel *channel, const char *extension, const char *context);
/*--- Device monitoring and Device/extension state/event handling */
-static int cb_extensionstate(const char *context, const char *exten, enum ast_extension_states state, void *data);
+static int extensionstate_update(char *context, char *exten, struct state_notify_data *data, struct sip_pvt *p);
+static int cb_extensionstate(char *context, char *exten, struct ast_state_cb_info *info, void *data);
static int sip_poke_noanswer(const void *data);
static int sip_poke_peer(struct sip_peer *peer, int force);
static void sip_poke_all_peers(void);
@@ -1505,7 +1529,7 @@ static int get_rpid(struct sip_pvt *p, struct sip_request *oreq);
static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq, char **name, char **number, int *reason);
static enum sip_get_dest_result get_destination(struct sip_pvt *p, struct sip_request *oreq, int *cc_recall_core_id);
static int get_msg_text(char *buf, int len, struct sip_request *req);
-static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout);
+static int transmit_state_notify(struct sip_pvt *p, struct state_notify_data *data, int full, int timeout);
static void update_connectedline(struct sip_pvt *p, const void *data, size_t datalen);
static void update_redirecting(struct sip_pvt *p, const void *data, size_t datalen);
static int get_domain(const char *str, char *domain, int len);
@@ -3865,7 +3889,10 @@ static int __sip_autodestruct(const void *data)
/* If this is a subscription, tell the phone that we got a timeout */
if (p->subscribed && p->subscribed != MWI_NOTIFICATION && p->subscribed != CALL_COMPLETION) {
- transmit_state_notify(p, AST_EXTENSION_DEACTIVATED, 1, TRUE); /* Send last notification */
+ struct state_notify_data data = { 0, };
+ data.state = AST_EXTENSION_DEACTIVATED;
+
+ transmit_state_notify(p, &data, 1, TRUE); /* Send last notification */
p->subscribed = NONE;
append_history(p, "Subscribestatus", "timeout");
ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", p->callid ? p->callid : "<unknown>");
@@ -6928,6 +6955,52 @@ static int initialize_udptl(struct sip_pvt *p)
return 0;
}
+int ast_sipinfo_send(
+ struct ast_channel *chan,
+ struct ast_variable *headers,
+ const char *content_type,
+ const char *content,
+ const char *useragent_filter)
+{
+ struct sip_pvt *p;
+ struct ast_variable *var;
+ struct sip_request req;
+ int res = -1;
+
+ ast_channel_lock(chan);
+
+ if (ast_channel_tech(chan) != &sip_tech) {
+ ast_log(LOG_WARNING, "Attempted to send a custom INFO on a non-SIP channel %s\n", ast_channel_name(chan));
+ ast_channel_unlock(chan);
+ return res;
+ }
+
+ p = ast_channel_tech_pvt(chan);
+ sip_pvt_lock(p);
+
+ if (!(ast_strlen_zero(useragent_filter))) {
+ int match = (strstr(p->useragent, useragent_filter)) ? 1 : 0;
+ if (!match) {
+ goto cleanup;
+ }
+ }
+
+ reqprep(&req, p, SIP_INFO, 0, 1);
+ for (var = headers; var; var = var->next) {
+ add_header(&req, var->name, var->value);
+ }
+ if (!ast_strlen_zero(content) && !ast_strlen_zero(content_type)) {
+ add_header(&req, "Content-Type", content_type);
+ add_content(&req, content);
+ }
+
+ res = send_request(p, &req, XMIT_RELIABLE, p->ocseq);
+
+cleanup:
+ sip_pvt_unlock(p);
+ ast_channel_unlock(chan);
+ return res;
+}
/*! \brief Play indication to user
* With SIP a lot of indications is sent as messages, letting the device play
the indication - busy signal, congestion etc
@@ -13070,8 +13143,14 @@ static int find_calling_channel(void *obj, void *arg, void *data, int flags)
return res ? CMP_MATCH | CMP_STOP : 0;
}
+/* XXX Candidate for moving into its own file */
+static int allow_notify_user_presence(struct sip_pvt *p)
+{
+ return (strstr(p->useragent, "Digium")) ? 1 : 0;
+}
+
/*! \brief Builds XML portion of NOTIFY messages for presence or dialog updates */
-static void state_notify_build_xml(int state, int full, const char *exten, const char *context, struct ast_str **tmp, struct sip_pvt *p, int subscribed, const char *mfrom, const char *mto)
+static void state_notify_build_xml(struct state_notify_data *data, int full, const char *exten, const char *context, struct ast_str **tmp, struct sip_pvt *p, int subscribed, const char *mfrom, const char *mto)
{
enum state { NOTIFY_OPEN, NOTIFY_INUSE, NOTIFY_CLOSED } local_state = NOTIFY_OPEN;
const char *statestring = "terminated";
@@ -13079,7 +13158,7 @@ static void state_notify_build_xml(int state, int full, const char *exten, const
const char *pidfnote= "Ready";
char hint[AST_MAX_EXTENSION];
- switch (state) {
+ switch (data->state) {
case (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE):
statestring = (sip_cfg.notifyringing) ? "early" : "confirmed";
local_state = NOTIFY_INUSE;
@@ -13124,9 +13203,16 @@ static void state_notify_build_xml(int state, int full, const char *exten, const
/* Check which device/devices we are watching and if they are registered */
if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten)) {
- char *hint2 = hint, *individual_hint = NULL;
+ char *hint2;
+ char *individual_hint = NULL;
int hint_count = 0, unavailable_count = 0;
+ /* strip off any possible PRESENCE providers from hint */
+ if ((hint2 = strrchr(hint, ','))) {
+ *hint2 = '\0';
+ }
+ hint2 = hint;
+
while ((individual_hint = strsep(&hint2, "&"))) {
hint_count++;
@@ -13174,12 +13260,30 @@ static void state_notify_build_xml(int state, int full, const char *exten, const
ast_str_append(tmp, 0, "<status><basic>open</basic></status>\n");
else
ast_str_append(tmp, 0, "<status><basic>%s</basic></status>\n", (local_state != NOTIFY_CLOSED) ? "open" : "closed");
+
+ if (allow_notify_user_presence(p) && (data->presence_state > 0)) {
+ ast_str_append(tmp, 0, "</tuple>\n");
+ ast_str_append(tmp, 0, "<tuple id=\"digium-presence\">\n");
+ ast_str_append(tmp, 0, "<status>\n");
+ ast_str_append(tmp, 0, "<digium_presence type=\"%s\" subtype=\"%s\">%s</digium_presence>\n",
+ ast_presence_state2str(data->presence_state),
+ S_OR(data->presence_subtype, ""),
+ S_OR(data->presence_message, ""));
+ ast_str_append(tmp, 0, "</status>\n");
+ ast_test_suite_event_notify("DIGIUM_PRESENCE_SENT",
+ "PresenceState: %s\r\n"
+ "Subtype: %s\r\n"
+ "Message: %s",
+ ast_presence_state2str(data->presence_state),
+ S_OR(data->presence_subtype, ""),
+ S_OR(data->presence_message, ""));
+ }
ast_str_append(tmp, 0, "</tuple>\n</presence>\n");
break;
case DIALOG_INFO_XML: /* SNOM subscribes in this format */
ast_str_append(tmp, 0, "<?xml version=\"1.0\"?>\n");
ast_str_append(tmp, 0, "<dialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"%u\" state=\"%s\" entity=\"%s\">\n", p->dialogver, full ? "full" : "partial", mto);
- if ((state & AST_EXTENSION_RINGING) && sip_cfg.notifyringing) {
+ if ((data->state & AST_EXTENSION_RINGING) && sip_cfg.notifyringing) {
const char *local_display = exten;
char *local_target = ast_strdupa(mto);
const char *remote_display = exten;
@@ -13256,7 +13360,7 @@ static void state_notify_build_xml(int state, int full, const char *exten, const
ast_str_append(tmp, 0, "<dialog id=\"%s\">\n", exten);
}
ast_str_append(tmp, 0, "<state>%s</state>\n", statestring);
- if (state == AST_EXTENSION_ONHOLD) {
+ if (data->state == AST_EXTENSION_ONHOLD) {
ast_str_append(tmp, 0, "<local>\n<target uri=\"%s\">\n"
"<param pname=\"+sip.rendering\" pvalue=\"no\"/>\n"
"</target>\n</local>\n", mto);
@@ -13300,7 +13404,7 @@ static int transmit_cc_notify(struct ast_cc_agent *agent, struct sip_pvt *subscr
}
/*! \brief Used in the SUBSCRIBE notification subsystem (RFC3265) */
-static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout)
+static int transmit_state_notify(struct sip_pvt *p, struct state_notify_data *data, int full, int timeout)
{
struct ast_str *tmp = ast_str_alloca(4000);
char from[256], to[256];
@@ -13332,7 +13436,7 @@ static int transmit_state_notify(struct sip_pvt *p, int state, int full, int tim
reqprep(&req, p, SIP_NOTIFY, 0, 1);
- switch(state) {
+ switch(data->state) {
case AST_EXTENSION_DEACTIVATED:
if (timeout)
add_header(&req, "Subscription-State", "terminated;reason=timeout");
@@ -13355,19 +13459,19 @@ static int transmit_state_notify(struct sip_pvt *p, int state, int full, int tim
case XPIDF_XML:
case CPIM_PIDF_XML:
add_header(&req, "Event", subscriptiontype->event);
- state_notify_build_xml(state, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto);
+ state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto);
add_header(&req, "Content-Type", subscriptiontype->mediatype);
p->dialogver++;
break;
case PIDF_XML: /* Eyebeam supports this format */
add_header(&req, "Event", subscriptiontype->event);
- state_notify_build_xml(state, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto);
+ state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto);
add_header(&req, "Content-Type", subscriptiontype->mediatype);
p->dialogver++;
break;
case DIALOG_INFO_XML: /* SNOM subscribes in this format */
add_header(&req, "Event", subscriptiontype->event);
- state_notify_build_xml(state, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto);
+ state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto);
add_header(&req, "Content-Type", subscriptiontype->mediatype);
p->dialogver++;
break;
@@ -15152,34 +15256,35 @@ static void cb_extensionstate_destroy(int id, void *data)
/*! \brief Callback for the devicestate notification (SUBSCRIBE) support subsystem
\note If you add an "hint" priority to the extension in the dial plan,
you will get notifications on device state changes */
-static int cb_extensionstate(const char *context, const char *exten, enum ast_extension_states state, void *data)
+static int extensionstate_update(char *context, char *exten, struct state_notify_data *data, struct sip_pvt *p)
{
- struct sip_pvt *p = data;
-
sip_pvt_lock(p);
- switch(state) {
+ switch (data->state) {
case AST_EXTENSION_DEACTIVATED: /* Retry after a while */
case AST_EXTENSION_REMOVED: /* Extension is gone */
sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); /* Delete subscription in 32 secs */
- ast_verb(2, "Extension state: Watcher for hint %s %s. Notify User %s\n", exten, state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", p->username);
+ ast_verb(2, "Extension state: Watcher for hint %s %s. Notify User %s\n", exten, data->state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", p->username);
p->subscribed = NONE;
- append_history(p, "Subscribestatus", "%s", state == AST_EXTENSION_REMOVED ? "HintRemoved" : "Deactivated");
+ append_history(p, "Subscribestatus", "%s", data->state == AST_EXTENSION_REMOVED ? "HintRemoved" : "Deactivated");
break;
default: /* Tell user */
- p->laststate = state;
+ p->laststate = data->state;
+ p->last_presence_state = data->presence_state;
+ ast_string_field_set(p, last_presence_subtype, S_OR(data->presence_subtype, ""));
+ ast_string_field_set(p, last_presence_message, S_OR(data->presence_message, ""));
break;
}
if (p->subscribed != NONE) { /* Only send state NOTIFY if we know the format */
if (!p->pendinginvite) {
- transmit_state_notify(p, state, 1, FALSE);
+ transmit_state_notify(p, data, 1, FALSE);
} else {
/* We already have a NOTIFY sent that is not answered. Queue the state up.
if many state changes happen meanwhile, we will only send a notification of the last one */
ast_set_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE);
}
}
- ast_verb(2, "Extension Changed %s[%s] new state %s for Notify User %s %s\n", exten, context, ast_extension_state2str(state), p->username,
+ ast_verb(2, "Extension Changed %s[%s] new state %s for Notify User %s %s\n", exten, context, ast_extension_state2str(data->state), p->username,
ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE) ? "(queued)" : "");
sip_pvt_unlock(p);
@@ -15187,6 +15292,27 @@ static int cb_extensionstate(const char *context, const char *exten, enum ast_ex
return 0;
}
+/*! \brief Callback for the devicestate notification (SUBSCRIBE) support subsystem
+\note If you add an "hint" priority to the extension in the dial plan,
+ you will get notifications on device state changes */
+static int cb_extensionstate(char *context, char *exten, struct ast_state_cb_info *info, void *data)
+{
+ struct sip_pvt *p = data;
+ struct state_notify_data notify_data = {
+ .state = info->exten_state,
+ .presence_state = info->presence_state,
+ .presence_subtype = info->presence_subtype,
+ .presence_message = info->presence_message,
+ };
+
+ if ((info->reason == AST_HINT_UPDATE_PRESENCE) && !(allow_notify_user_presence(p))) {
+ /* ignore a presence triggered update if we know the useragent doesn't care */
+ return 0;
+ }
+
+ return extensionstate_update(context, exten, &notify_data, p);
+}
+
/*! \brief Send a fake 401 Unauthorized response when the administrator
wants to hide the names of local devices from fishers
*/
@@ -21342,9 +21468,15 @@ static void handle_response_notify(struct sip_pvt *p, int resp, const char *rest
pvt_set_needdestroy(p, "received 200 response");
}
if (ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE)) {
+ struct state_notify_data data = {
+ .state = p->laststate,
+ .presence_state = p->last_presence_state,
+ .presence_subtype = p->last_presence_subtype,
+ .presence_message = p->last_presence_message,
+ };
/* Ready to send the next state we have on queue */
ast_clear_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE);
- cb_extensionstate((char *)p->context, (char *)p->exten, p->laststate, (void *) p);
+ extensionstate_update((char *)p->context, (char *)p->exten, &data, (void *) p);
}
}
break;
@@ -24331,6 +24463,8 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, int
int localtransfer = 0;
int attendedtransfer = 0;
int res = 0;
+ struct ast_party_redirecting redirecting;
+ struct ast_set_party_redirecting update_redirecting;
if (req->debug) {
ast_verbose("Call %s got a SIP call transfer from %s: (REFER)!\n",
@@ -24635,6 +24769,16 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, int
}
ast_set_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Delay hangup */
+ /* When a call is transferred to voicemail from a Digium phone, there may be
+ * a Diversion header present in the REFER with an appropriate reason parameter
+ * set. We need to update the redirecting information appropriately.
+ */
+ ast_party_redirecting_init(&redirecting);
+ memset(&update_redirecting, 0, sizeof(update_redirecting));
+ change_redirecting_information(p, req, &redirecting, &update_redirecting, FALSE);
+ ast_channel_update_redirecting(current.chan2, &redirecting, &update_redirecting);
+ ast_party_redirecting_free(&redirecting);
+
/* Do not hold the pvt lock during the indicate and async_goto. Those functions
* lock channels which will invalidate locking order if the pvt lock is held.*/
/* For blind transfers, move the call to the new extensions. For attended transfers on multiple
@@ -25684,7 +25828,6 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req,
{
int gotdest = 0;
int res = 0;
- int firststate;
struct sip_peer *authpeer = NULL;
const char *eventheader = sip_get_header(req, "Event"); /* Get Event package name */
int resubscribe = (p->subscribed != NONE) && !req->ignore;
@@ -26037,9 +26180,10 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req,
sip_unref_peer(peer, "release a peer ref now that MWI is sent");
}
} else if (p->subscribed != CALL_COMPLETION) {
-
- if ((firststate = ast_extension_state(NULL, p->context, p->exten)) < 0) {
-
+ struct state_notify_data data = { 0, };
+ char *subtype = NULL;
+ char *message = NULL;
+ if ((data.state = ast_extension_state(NULL, p->context, p->exten)) < 0) {
ast_log(LOG_NOTICE, "Got SUBSCRIBE for extension %s@%s from %s, but there is no hint for that extension.\n", p->exten, p->context, ast_sockaddr_stringify(&p->sa));
transmit_response(p, "404 Not found", req);
pvt_set_needdestroy(p, "no extension for SUBSCRIBE");
@@ -26048,14 +26192,21 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req,
}
return 0;
}
+ if (allow_notify_user_presence(p)) {
+ data.presence_state = ast_hint_presence_state(NULL, p->context, p->exten, &subtype, &message);
+ data.presence_subtype = subtype;
+ data.presence_message = message;
+ }
ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
transmit_response(p, "200 OK", req);
- transmit_state_notify(p, firststate, 1, FALSE); /* Send first notification */
- append_history(p, "Subscribestatus", "%s", ast_extension_state2str(firststate));
+ transmit_state_notify(p, &data, 1, FALSE); /* Send first notification */
+ append_history(p, "Subscribestatus", "%s", ast_extension_state2str(data.state));
/* hide the 'complete' exten/context in the refer_to field for later display */
ast_string_field_build(p, subscribeuri, "%s@%s", p->exten, p->context);
/* Deleted the slow iteration of all sip dialogs to find old subscribes from this peer for exten@context */
+ ast_free(subtype);
+ ast_free(message);
}
if (!p->expiry) {
pvt_set_needdestroy(p, "forcing expiration");
@@ -30813,6 +30964,9 @@ static struct ast_rtp_glue sip_rtp_glue = {
static char *app_dtmfmode = "SIPDtmfMode";
static char *app_sipaddheader = "SIPAddHeader";
static char *app_sipremoveheader = "SIPRemoveHeader";
+#ifdef TEST_FRAMEWORK
+static char *app_sipsendcustominfo = "SIPSendCustomINFO";
+#endif
/*! \brief Set the DTMFmode for an outbound SIP call (application) */
static int sip_dtmfmode(struct ast_channel *chan, const char *data)
@@ -30941,6 +31095,28 @@ static int sip_removeheader(struct ast_channel *chan, const char *data)
return 0;
}
+#ifdef TEST_FRAMEWORK
+/*! \brief Send a custom INFO message via AST_CONTROL_CUSTOM indication */
+static int sip_sendcustominfo(struct ast_channel *chan, const char *data)
+{
+ char *info_data, *useragent;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "You must provide data to be sent\n");
+ return 0;
+ }
+
+ useragent = ast_strdupa(data);
+ info_data = strsep(&useragent, ",");
+
+ if (ast_sipinfo_send(chan, NULL, "text/plain", info_data, useragent)) {
+ ast_log(LOG_WARNING, "Failed to create payload for custom SIP INFO\n");
+ return 0;
+ }
+ return 0;
+}
+#endif
+
/*! \brief Transfer call before connect with a 302 redirect
\note Called by the transfer() dialplan application through the sip_transfer()
pbx interface function if the call is in ringing state
@@ -31922,6 +32098,9 @@ static int load_module(void)
ast_register_application_xml(app_dtmfmode, sip_dtmfmode);
ast_register_application_xml(app_sipaddheader, sip_addheader);
ast_register_application_xml(app_sipremoveheader, sip_removeheader);
+#ifdef TEST_FRAMEWORK
+ ast_register_application_xml(app_sipsendcustominfo, sip_sendcustominfo);
+#endif
/* Register dialplan functions */
ast_custom_function_register(&sip_header_function);
@@ -32015,8 +32194,9 @@ static int unload_module(void)
ast_unregister_application(app_dtmfmode);
ast_unregister_application(app_sipaddheader);
ast_unregister_application(app_sipremoveheader);
-
#ifdef TEST_FRAMEWORK
+ ast_unregister_application(app_sipsendcustominfo);
+
AST_TEST_UNREGISTER(test_sip_peers_get);
AST_TEST_UNREGISTER(test_sip_mwi_subscribe_parse);
#endif
@@ -32166,7 +32346,7 @@ static int unload_module(void)
return 0;
}
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Session Initiation Protocol (SIP)",
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Session Initiation Protocol (SIP)",
.load = load_module,
.unload = unload_module,
.reload = reload,
diff --git a/channels/chan_sip.exports.in b/channels/chan_sip.exports.in
new file mode 100644
index 000000000..1e9972a72
--- /dev/null
+++ b/channels/chan_sip.exports.in
@@ -0,0 +1,6 @@
+{
+ global:
+ LINKER_SYMBOL_PREFIX*ast_sipinfo_send;
+ local:
+ *;
+};
diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c
index 753d7aeba..6923f8fb9 100644
--- a/channels/chan_skinny.c
+++ b/channels/chan_skinny.c
@@ -1510,7 +1510,7 @@ static struct ast_channel_tech skinny_tech = {
.bridge = ast_rtp_instance_bridge,
};
-static int skinny_extensionstate_cb(const char *context, const char *exten, enum ast_extension_states state, void *data);
+static int skinny_extensionstate_cb(char *context, char *id, struct ast_state_cb_info *info, void *data);
static int skinny_transfer(struct skinny_subchannel *sub);
static struct skinny_line *skinny_line_alloc(void)
@@ -3029,11 +3029,17 @@ static void transmit_capabilitiesreq(struct skinny_device *d)
transmit_response(d, req);
}
-static int skinny_extensionstate_cb(const char *context, const char *exten, enum ast_extension_states state, void *data)
+static int skinny_extensionstate_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data)
{
struct skinny_container *container = data;
struct skinny_device *d = NULL;
char hint[AST_MAX_EXTENSION];
+ int state = info->exten_state;
+
+ /* only interested in device state here */
+ if (info->reason != AST_HINT_UPDATE_DEVICE) {
+ return 0;
+ }
if (container->type == SKINNY_SDCONTAINER) {
struct skinny_speeddial *sd = container->data;
@@ -5060,10 +5066,13 @@ static void setsubstate(struct skinny_subchannel *sub, int state)
AST_LIST_TRAVERSE(&tmpline->sublines, tmpsubline, list) {
if (!(subline == tmpsubline)) {
if (!strcasecmp(subline->lnname, tmpsubline->lnname)) {
+ struct ast_state_cb_info info = {
+ .exten_state = tmpsubline->extenstate,
+ };
tmpsubline->callid = callnums++;
transmit_callstate(tmpsubline->line->device, tmpsubline->line->instance, tmpsubline->callid, SKINNY_OFFHOOK);
push_callinfo(tmpsubline, sub);
- skinny_extensionstate_cb(NULL, NULL, tmpsubline->extenstate, tmpsubline->container);
+ skinny_extensionstate_cb(NULL, NULL, &info, tmpsubline->container);
}
}
}
diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h
index 958fe9af2..e8f079df6 100644
--- a/channels/sip/include/sip.h
+++ b/channels/sip/include/sip.h
@@ -1041,6 +1041,8 @@ struct sip_pvt {
AST_STRING_FIELD(parkinglot); /*!< Parkinglot */
AST_STRING_FIELD(engine); /*!< RTP engine to use */
AST_STRING_FIELD(dialstring); /*!< The dialstring used to call this SIP endpoint */
+ AST_STRING_FIELD(last_presence_subtype); /*!< The last presence subtype sent for a subscription. */
+ AST_STRING_FIELD(last_presence_message); /*!< The last presence message for a subscription */
AST_STRING_FIELD(msg_body); /*!< Text for a MESSAGE body */
);
char via[128]; /*!< Via: header */
@@ -1141,6 +1143,7 @@ struct sip_pvt {
enum subscriptiontype subscribed; /*!< SUBSCRIBE: Is this dialog a subscription? */
int stateid; /*!< SUBSCRIBE: ID for devicestate subscriptions */
int laststate; /*!< SUBSCRIBE: Last known extension state */
+ int last_presence_state; /*!< SUBSCRIBE: Last known presence state */
uint32_t dialogver; /*!< SUBSCRIBE: Version for subscription dialog-info */
struct ast_dsp *dsp; /*!< Inband DTMF or Fax CNG tone Detection dsp */
diff --git a/configs/manager.conf.sample b/configs/manager.conf.sample
index fb44e74d4..5e99cf80d 100644
--- a/configs/manager.conf.sample
+++ b/configs/manager.conf.sample
@@ -140,7 +140,8 @@ bindaddr = 0.0.0.0
; test - Ability to read TestEvent notifications sent to the Asterisk Test
; Suite. Note that this is only enabled when the TEST_FRAMEWORK
; compiler flag is defined.
+; message - Permissions to send out of call messages. Write-only
;
;read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan
-;write = system,call,agent,user,config,command,reporting,originate
+;write = system,call,agent,user,config,command,reporting,originate,message
diff --git a/contrib/realtime/mysql/voicemail_messages.sql b/contrib/realtime/mysql/voicemail_messages.sql
index 79dc0ead5..7290a9af0 100644
--- a/contrib/realtime/mysql/voicemail_messages.sql
+++ b/contrib/realtime/mysql/voicemail_messages.sql
@@ -25,5 +25,7 @@ CREATE TABLE voicemail_messages (
mailboxuser CHAR(30),
-- Context of the owner of the mailbox
mailboxcontext CHAR(30),
+ -- Unique ID of the message,
+ msg_id char(40),
PRIMARY KEY (dir, msgnum)
);
diff --git a/funcs/func_presencestate.c b/funcs/func_presencestate.c
new file mode 100644
index 000000000..fa7211288
--- /dev/null
+++ b/funcs/func_presencestate.c
@@ -0,0 +1,781 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2011, Digium, Inc.
+ *
+ * David Vossel <dvossel@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Custom presence provider
+ * \ingroup functions
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/utils.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/presencestate.h"
+#include "asterisk/cli.h"
+#include "asterisk/astdb.h"
+#include "asterisk/app.h"
+#ifdef TEST_FRAMEWORK
+#include "asterisk/test.h"
+#include "asterisk/event.h"
+#include <semaphore.h>
+#endif
+
+/*** DOCUMENTATION
+ <function name="PRESENCE_STATE" language="en_US">
+ <synopsis>
+ Get or Set a presence state.
+ </synopsis>
+ <syntax>
+ <parameter name="provider" required="true">
+ <para>The provider of the presence, such as <literal>CustomPresence</literal></para>
+ </parameter>
+ <parameter name="field" required="true">
+ <para>Which field of the presence state information is wanted.</para>
+ <optionlist>
+ <option name="value">
+ <para>The current presence, such as <literal>away</literal></para>
+ </option>
+ <option name="subtype">
+ <para>Further information about the current presence</para>
+ </option>
+ <option name="message">
+ <para>A custom message that may indicate further details about the presence</para>
+ </option>
+ </optionlist>
+ </parameter>
+ <parameter name="options" required="false">
+ <optionlist>
+ <option name="e">
+ <para>Base-64 encode the data.</para>
+ </option>
+ </optionlist>
+ </parameter>
+ </syntax>
+ <description>
+ <para>The PRESENCE_STATE function can be used to retrieve the presence from any
+ presence provider. For example:</para>
+ <para>NoOp(SIP/mypeer has presence ${PRESENCE_STATE(SIP/mypeer,value)})</para>
+ <para>NoOp(Conference number 1234 has presence message ${PRESENCE_STATE(MeetMe:1234,message)})</para>
+ <para>The PRESENCE_STATE function can also be used to set custom presence state from
+ the dialplan. The <literal>CustomPresence:</literal> prefix must be used. For example:</para>
+ <para>Set(PRESENCE_STATE(CustomPresence:lamp1)=away,temporary,Out to lunch)</para>
+ <para>Set(PRESENCE_STATE(CustomPresence:lamp2)=dnd,,Trying to get work done)</para>
+ <para>You can subscribe to the status of a custom presence state using a hint in
+ the dialplan:</para>
+ <para>exten => 1234,hint,CustomPresence:lamp1</para>
+ <para>The possible values for both uses of this function are:</para>
+ <para>not_set | unavailable | available | away | xa | chat | dnd</para>
+ </description>
+ </function>
+ ***/
+
+
+static const char astdb_family[] = "CustomPresence";
+
+static int presence_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ int state;
+ char *message = NULL;
+ char *subtype = NULL;
+ char *parse;
+ int base64encode = 0;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(provider);
+ AST_APP_ARG(field);
+ AST_APP_ARG(options);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "PRESENCE_STATE reading requires an argument \n");
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (ast_strlen_zero(args.provider) || ast_strlen_zero(args.field)) {
+ ast_log(LOG_WARNING, "PRESENCE_STATE reading requires both presence provider and presence field arguments. \n");
+ return -1;
+ }
+
+ state = ast_presence_state_nocache(args.provider, &subtype, &message);
+ if (state == AST_PRESENCE_INVALID) {
+ ast_log(LOG_WARNING, "PRESENCE_STATE unknown \n");
+ return -1;
+ }
+
+ if (!(ast_strlen_zero(args.options)) && (strchr(args.options, 'e'))) {
+ base64encode = 1;
+ }
+
+ if (!ast_strlen_zero(subtype) && !strcasecmp(args.field, "subtype")) {
+ if (base64encode) {
+ ast_base64encode(buf, (unsigned char *) subtype, strlen(subtype), len);
+ } else {
+ ast_copy_string(buf, subtype, len);
+ }
+ } else if (!ast_strlen_zero(message) && !strcasecmp(args.field, "message")) {
+ if (base64encode) {
+ ast_base64encode(buf, (unsigned char *) message, strlen(message), len);
+ } else {
+ ast_copy_string(buf, message, len);
+ }
+
+ } else if (!strcasecmp(args.field, "value")) {
+ ast_copy_string(buf, ast_presence_state2str(state), len);
+ }
+
+ ast_free(message);
+ ast_free(subtype);
+
+ return 0;
+}
+
+static int parse_data(char *data, enum ast_presence_state *state, char **subtype, char **message, char **options)
+{
+ char *state_str;
+
+ /* data syntax is state,subtype,message,options */
+ *subtype = "";
+ *message = "";
+ *options = "";
+
+ state_str = strsep(&data, ",");
+ if (ast_strlen_zero(state_str)) {
+ return -1; /* state is required */
+ }
+
+ *state = ast_presence_state_val(state_str);
+
+ /* not a valid state */
+ if (*state == AST_PRESENCE_INVALID) {
+ ast_log(LOG_WARNING, "Unknown presence state value %s\n", state_str);
+ return -1;
+ }
+
+ if (!(*subtype = strsep(&data,","))) {
+ *subtype = "";
+ return 0;
+ }
+
+ if (!(*message = strsep(&data, ","))) {
+ *message = "";
+ return 0;
+ }
+
+ if (!(*options = strsep(&data, ","))) {
+ *options = "";
+ return 0;
+ }
+
+ if (!ast_strlen_zero(*options) && !(strchr(*options, 'e'))) {
+ ast_log(LOG_NOTICE, "Invalid options '%s'\n", *options);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int presence_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
+{
+ size_t len = strlen("CustomPresence:");
+ char *tmp = data;
+ char *args = ast_strdupa(value);
+ enum ast_presence_state state;
+ char *options, *message, *subtype;
+
+ if (strncasecmp(data, "CustomPresence:", len)) {
+ ast_log(LOG_WARNING, "The PRESENCE_STATE function can only set CustomPresence: presence providers.\n");
+ return -1;
+ }
+ data += len;
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "PRESENCE_STATE function called with no custom device name!\n");
+ return -1;
+ }
+
+ if (parse_data(args, &state, &subtype, &message, &options)) {
+ ast_log(LOG_WARNING, "Invalid arguments to PRESENCE_STATE\n");
+ return -1;
+ }
+
+ ast_db_put(astdb_family, data, value);
+
+ ast_presence_state_changed_literal(state, subtype, message, tmp);
+
+ return 0;
+}
+
+static enum ast_presence_state custom_presence_callback(const char *data, char **subtype, char **message)
+{
+ char buf[1301] = "";
+ enum ast_presence_state state;
+ char *_options;
+ char *_message;
+ char *_subtype;
+
+ ast_log(LOG_NOTICE, "TITTY BOMBS!\n");
+
+ ast_db_get(astdb_family, data, buf, sizeof(buf));
+
+ if (parse_data(buf, &state, &_subtype, &_message, &_options)) {
+ return -1;
+ }
+
+ if ((strchr(_options, 'e'))) {
+ char tmp[1301];
+ if (ast_strlen_zero(_subtype)) {
+ *subtype = NULL;
+ } else {
+ memset(tmp, 0, sizeof(tmp));
+ ast_log(LOG_NOTICE, "Hey there, I'm doing some base64 decoding\n");
+ ast_base64decode((unsigned char *) tmp, _subtype, sizeof(tmp) - 1);
+ *subtype = ast_strdup(tmp);
+ }
+
+ if (ast_strlen_zero(_message)) {
+ *message = NULL;
+ } else {
+ memset(tmp, 0, sizeof(tmp));
+ ast_log(LOG_NOTICE, "Hey there, I'm doing some more base64 decoding\n");
+ ast_base64decode((unsigned char *) tmp, _message, sizeof(tmp) - 1);
+ *message = ast_strdup(tmp);
+ }
+ } else {
+ ast_log(LOG_NOTICE, "Not doing any base64 decoding\n");
+ *subtype = ast_strlen_zero(_subtype) ? NULL : ast_strdup(_subtype);
+ *message = ast_strlen_zero(_message) ? NULL : ast_strdup(_message);
+ }
+ return state;
+}
+
+static struct ast_custom_function presence_function = {
+ .name = "PRESENCE_STATE",
+ .read = presence_read,
+ .write = presence_write,
+};
+
+static char *handle_cli_presencestate_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_db_entry *db_entry, *db_tree;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "presencestate list";
+ e->usage =
+ "Usage: presencestate list\n"
+ " List all custom presence states that have been set by using\n"
+ " the PRESENCE_STATE dialplan function.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != e->args) {
+ return CLI_SHOWUSAGE;
+ }
+
+ ast_cli(a->fd, "\n"
+ "---------------------------------------------------------------------\n"
+ "--- Custom Presence States ------------------------------------------\n"
+ "---------------------------------------------------------------------\n"
+ "---\n");
+
+ db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
+ if (!db_entry) {
+ ast_cli(a->fd, "No custom presence states defined\n");
+ return CLI_SUCCESS;
+ }
+ for (; db_entry; db_entry = db_entry->next) {
+ const char *object_name = strrchr(db_entry->key, '/') + 1;
+ char state_info[1301];
+ enum ast_presence_state state;
+ char *subtype;
+ char *message;
+ char *options;
+
+ ast_copy_string(state_info, db_entry->data, sizeof(state_info));
+ if (parse_data(state_info, &state, &subtype, &message, &options)) {
+ ast_log(LOG_WARNING, "Invalid CustomPresence entry %s encountered\n", db_entry->data);
+ continue;
+ }
+
+ if (object_name <= (const char *) 1) {
+ continue;
+ }
+ ast_cli(a->fd, "--- Name: 'CustomPresence:%s'\n"
+ " --- State: '%s'\n"
+ " --- Subtype: '%s'\n"
+ " --- Message: '%s'\n"
+ " --- Base64 Encoded: '%s'\n"
+ "---\n",
+ object_name,
+ ast_presence_state2str(state),
+ subtype,
+ message,
+ AST_CLI_YESNO(strchr(options, 'e')));
+ }
+ ast_db_freetree(db_tree);
+ db_tree = NULL;
+
+ ast_cli(a->fd,
+ "---------------------------------------------------------------------\n"
+ "---------------------------------------------------------------------\n"
+ "\n");
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_presencestate_change(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ size_t len;
+ const char *dev, *state, *full_dev;
+ enum ast_presence_state state_val;
+ char *message;
+ char *subtype;
+ char *options;
+ char *args;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "presencestate change";
+ e->usage =
+ "Usage: presencestate change <entity> <state>[,<subtype>[,message[,options]]]\n"
+ " Change a custom presence to a new state.\n"
+ " The possible values for the state are:\n"
+ "NOT_SET | UNAVAILABLE | AVAILABLE | AWAY | XA | CHAT | DND\n"
+ "Optionally, a custom subtype and message may be provided, along with any options\n"
+ "accepted by func_presencestate. If the subtype or message provided contain spaces,\n"
+ "be sure to enclose the data in quotation marks (\"\")\n"
+ "\n"
+ "Examples:\n"
+ " presencestate change CustomPresence:mystate1 AWAY\n"
+ " presencestate change CustomPresence:mystate1 AVAILABLE\n"
+ " presencestate change CustomPresence:mystate1 \"Away,upstairs,eating lunch\"\n"
+ " \n";
+ return NULL;
+ case CLI_GENERATE:
+ {
+ static const char * const cmds[] = { "NOT_SET", "UNAVAILABLE", "AVAILABLE", "AWAY",
+ "XA", "CHAT", "DND", NULL };
+
+ if (a->pos == e->args + 1) {
+ return ast_cli_complete(a->word, cmds, a->n);
+ }
+
+ return NULL;
+ }
+ }
+
+ if (a->argc != e->args + 2) {
+ return CLI_SHOWUSAGE;
+ }
+
+ len = strlen("CustomPresence:");
+ full_dev = dev = a->argv[e->args];
+ state = a->argv[e->args + 1];
+
+ if (strncasecmp(dev, "CustomPresence:", len)) {
+ ast_cli(a->fd, "The presencestate command can only be used to set 'CustomPresence:' presence state!\n");
+ return CLI_FAILURE;
+ }
+
+ dev += len;
+ if (ast_strlen_zero(dev)) {
+ return CLI_SHOWUSAGE;
+ }
+
+ args = ast_strdupa(state);
+ if (parse_data(args, &state_val, &subtype, &message, &options)) {
+ return CLI_SHOWUSAGE;
+ }
+
+ if (state_val == AST_PRESENCE_NOT_SET) {
+ return CLI_SHOWUSAGE;
+ }
+
+ ast_cli(a->fd, "Changing %s to %s\n", dev, args);
+
+ ast_db_put(astdb_family, dev, state);
+
+ ast_presence_state_changed_literal(state_val, subtype, message, full_dev);
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_funcpresencestate[] = {
+ AST_CLI_DEFINE(handle_cli_presencestate_list, "List currently know custom presence states"),
+ AST_CLI_DEFINE(handle_cli_presencestate_change, "Change a custom presence state"),
+};
+
+#ifdef TEST_FRAMEWORK
+
+struct test_string {
+ char *parse_string;
+ struct {
+ int value;
+ const char *subtype;
+ const char *message;
+ const char *options;
+ } outputs;
+};
+
+AST_TEST_DEFINE(test_valid_parse_data)
+{
+ int i;
+ enum ast_presence_state state;
+ char *subtype;
+ char *message;
+ char *options;
+ enum ast_test_result_state res = AST_TEST_PASS;
+
+ struct test_string tests [] = {
+ { "away",
+ { AST_PRESENCE_AWAY,
+ "",
+ "",
+ ""
+ }
+ },
+ { "not_set",
+ { AST_PRESENCE_NOT_SET,
+ "",
+ "",
+ ""
+ }
+ },
+ { "unavailable",
+ { AST_PRESENCE_UNAVAILABLE,
+ "",
+ "",
+ ""
+ }
+ },
+ { "available",
+ { AST_PRESENCE_AVAILABLE,
+ "",
+ "",
+ ""
+ }
+ },
+ { "xa",
+ { AST_PRESENCE_XA,
+ "",
+ "",
+ ""
+ }
+ },
+ { "chat",
+ { AST_PRESENCE_CHAT,
+ "",
+ "",
+ ""
+ }
+ },
+ { "dnd",
+ { AST_PRESENCE_DND,
+ "",
+ "",
+ ""
+ }
+ },
+ { "away,down the hall",
+ { AST_PRESENCE_AWAY,
+ "down the hall",
+ "",
+ ""
+ }
+ },
+ { "away,down the hall,Quarterly financial meeting",
+ { AST_PRESENCE_AWAY,
+ "down the hall",
+ "Quarterly financial meeting",
+ ""
+ }
+ },
+ { "away,,Quarterly financial meeting",
+ { AST_PRESENCE_AWAY,
+ "",
+ "Quarterly financial meeting",
+ ""
+ }
+ },
+ { "away,,,e",
+ { AST_PRESENCE_AWAY,
+ "",
+ "",
+ "e",
+ }
+ },
+ { "away,down the hall,,e",
+ { AST_PRESENCE_AWAY,
+ "down the hall",
+ "",
+ "e"
+ }
+ },
+ { "away,down the hall,Quarterly financial meeting,e",
+ { AST_PRESENCE_AWAY,
+ "down the hall",
+ "Quarterly financial meeting",
+ "e"
+ }
+ },
+ { "away,,Quarterly financial meeting,e",
+ { AST_PRESENCE_AWAY,
+ "",
+ "Quarterly financial meeting",
+ "e"
+ }
+ }
+ };
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "parse_valid_presence_data";
+ info->category = "/funcs/func_presence";
+ info->summary = "PRESENCESTATE parsing test";
+ info->description =
+ "Ensure that parsing function accepts proper values, and gives proper outputs";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ for (i = 0; i < ARRAY_LEN(tests); ++i) {
+ int parse_result;
+ char *parse_string = ast_strdup(tests[i].parse_string);
+ if (!parse_string) {
+ res = AST_TEST_FAIL;
+ break;
+ }
+ parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
+ if (parse_result == -1) {
+ res = AST_TEST_FAIL;
+ ast_free(parse_string);
+ break;
+ }
+ if (tests[i].outputs.value != state ||
+ strcmp(tests[i].outputs.subtype, subtype) ||
+ strcmp(tests[i].outputs.message, message) ||
+ strcmp(tests[i].outputs.options, options)) {
+ res = AST_TEST_FAIL;
+ ast_free(parse_string);
+ break;
+ }
+ ast_free(parse_string);
+ }
+
+ return res;
+}
+
+AST_TEST_DEFINE(test_invalid_parse_data)
+{
+ int i;
+ enum ast_presence_state state;
+ char *subtype;
+ char *message;
+ char *options;
+ enum ast_test_result_state res = AST_TEST_PASS;
+
+ char *tests[] = {
+ "",
+ "bored",
+ "away,,,i",
+ /* XXX The following actually is parsed correctly. Should that
+ * be changed?
+ * "away,,,,e",
+ */
+ };
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "parse_invalid_presence_data";
+ info->category = "/funcs/func_presence";
+ info->summary = "PRESENCESTATE parsing test";
+ info->description =
+ "Ensure that parsing function rejects improper values";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ for (i = 0; i < ARRAY_LEN(tests); ++i) {
+ int parse_result;
+ char *parse_string = ast_strdup(tests[i]);
+ if (!parse_string) {
+ res = AST_TEST_FAIL;
+ break;
+ }
+ parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
+ if (parse_result == 0) {
+ ast_log(LOG_WARNING, "Invalid string parsing failed on %s\n", tests[i]);
+ res = AST_TEST_FAIL;
+ ast_free(parse_string);
+ break;
+ }
+ ast_free(parse_string);
+ }
+
+ return res;
+}
+
+struct test_cb_data {
+ enum ast_presence_state presence;
+ const char *provider;
+ const char *subtype;
+ const char *message;
+ /* That's right. I'm using a semaphore */
+ sem_t sem;
+};
+
+static void test_cb(const struct ast_event *event, void *userdata)
+{
+ struct test_cb_data *cb_data = userdata;
+ cb_data->presence = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
+ cb_data->provider = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER));
+ cb_data->subtype = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE));
+ cb_data->message = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE));
+ sem_post(&cb_data->sem);
+}
+
+/* XXX This test could probably stand to be moved since
+ * it does not test func_presencestate but rather code in
+ * presencestate.h and presencestate.c. However, the convenience
+ * of presence_write() makes this a nice location for this test.
+ */
+AST_TEST_DEFINE(test_presence_state_change)
+{
+ struct ast_event_sub *test_sub;
+ struct test_cb_data *cb_data;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "test_presence_state_change";
+ info->category = "/funcs/func_presence";
+ info->summary = "presence state change subscription";
+ info->description =
+ "Ensure that presence state changes are communicated to subscribers";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ cb_data = ast_calloc(1, sizeof(*cb_data));
+ if (!cb_data) {
+ return AST_TEST_FAIL;
+ }
+
+ if (!(test_sub = ast_event_subscribe(AST_EVENT_PRESENCE_STATE,
+ test_cb, "Test presence state callbacks", cb_data, AST_EVENT_IE_END))) {
+ return AST_TEST_FAIL;
+ }
+
+ if (sem_init(&cb_data->sem, 0, 0)) {
+ return AST_TEST_FAIL;
+ }
+
+ presence_write(NULL, "PRESENCESTATE", "CustomPresence:TestPresenceStateChange", "away,down the hall,Quarterly financial meeting");
+ sem_wait(&cb_data->sem);
+ if (cb_data->presence != AST_PRESENCE_AWAY ||
+ strcmp(cb_data->provider, "CustomPresence:TestPresenceStateChange") ||
+ strcmp(cb_data->subtype, "down the hall") ||
+ strcmp(cb_data->message, "Quarterly financial meeting")) {
+ return AST_TEST_FAIL;
+ }
+
+ ast_free((char *)cb_data->provider);
+ ast_free((char *)cb_data->subtype);
+ ast_free((char *)cb_data->message);
+ ast_free((char *)cb_data);
+
+ ast_db_del("CustomPresence", "TestPresenceStateChange");
+
+ return AST_TEST_PASS;
+}
+
+#endif
+
+static int unload_module(void)
+{
+ int res = 0;
+
+ res |= ast_custom_function_unregister(&presence_function);
+ res |= ast_presence_state_prov_del("CustomPresence");
+ res |= ast_cli_unregister_multiple(cli_funcpresencestate, ARRAY_LEN(cli_funcpresencestate));
+#ifdef TEST_FRAMEWORK
+ AST_TEST_UNREGISTER(test_valid_parse_data);
+ AST_TEST_UNREGISTER(test_invalid_parse_data);
+ AST_TEST_UNREGISTER(test_presence_state_change);
+#endif
+ return res;
+}
+
+static int load_module(void)
+{
+ int res = 0;
+ struct ast_db_entry *db_entry, *db_tree;
+
+ /* Populate the presence state cache on the system with all of the currently
+ * known custom presence states. */
+ db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
+ for (; db_entry; db_entry = db_entry->next) {
+ const char *dev_name = strrchr(db_entry->key, '/') + 1;
+ char state_info[1301];
+ enum ast_presence_state state;
+ char *message;
+ char *subtype;
+ char *options;
+ if (dev_name <= (const char *) 1) {
+ continue;
+ }
+ ast_copy_string(state_info, db_entry->data, sizeof(state_info));
+ if (parse_data(state_info, &state, &subtype, &message, &options)) {
+ ast_log(LOG_WARNING, "Invalid CustomPresence entry %s encountered\n", db_entry->data);
+ continue;
+ }
+ ast_presence_state_changed(state, subtype, message, "CustomPresence:%s", dev_name);
+ }
+ ast_db_freetree(db_tree);
+ db_tree = NULL;
+
+ res |= ast_custom_function_register(&presence_function);
+ res |= ast_presence_state_prov_add("CustomPresence", custom_presence_callback);
+ res |= ast_cli_register_multiple(cli_funcpresencestate, ARRAY_LEN(cli_funcpresencestate));
+#ifdef TEST_FRAMEWORK
+ AST_TEST_REGISTER(test_valid_parse_data);
+ AST_TEST_REGISTER(test_invalid_parse_data);
+ AST_TEST_REGISTER(test_presence_state_change);
+#endif
+
+ return res;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Gets or sets a presence state in the dialplan",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_DEVSTATE_PROVIDER,
+);
+
diff --git a/include/asterisk/app.h b/include/asterisk/app.h
index edf1c1c37..d438790e3 100644
--- a/include/asterisk/app.h
+++ b/include/asterisk/app.h
@@ -23,8 +23,10 @@
#ifndef _ASTERISK_APP_H
#define _ASTERISK_APP_H
+#include "asterisk/stringfields.h"
#include "asterisk/strings.h"
#include "asterisk/threadstorage.h"
+#include "asterisk/file.h"
struct ast_flags64;
@@ -77,6 +79,27 @@ struct ast_ivr_menu {
unsigned int flags; /*!< Flags */
struct ast_ivr_option *options; /*!< All options */
};
+
+/*!
+ * \brief Structure used for ast_copy_recording_to_vm in order to cleanly supply
+ * data needed for making the recording from the recorded file.
+ */
+struct ast_vm_recording_data {
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(context);
+ AST_STRING_FIELD(mailbox);
+ AST_STRING_FIELD(folder);
+ AST_STRING_FIELD(recording_file);
+ AST_STRING_FIELD(recording_ext);
+
+ AST_STRING_FIELD(call_context);
+ AST_STRING_FIELD(call_macrocontext);
+ AST_STRING_FIELD(call_extension);
+ AST_STRING_FIELD(call_callerchan);
+ AST_STRING_FIELD(call_callerid);
+ );
+ int call_priority;
+};
#define AST_IVR_FLAG_AUTORESTART (1 << 0)
@@ -219,11 +242,21 @@ void ast_install_vm_functions(int (*has_voicemail_func)(const char *mailbox, con
int (*inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsgs),
int (*inboxcount2_func)(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs),
int (*messagecount_func)(const char *context, const char *mailbox, const char *folder),
- int (*sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context));
+ int (*sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context),
+ int (*copy_recording_to_vm_func)(struct ast_vm_recording_data *vm_rec_data));
+
void ast_uninstall_vm_functions(void);
/*!
+ * \brief
+ * param[in] vm_rec_data Contains data needed to make the recording.
+ * retval 0 voicemail successfully created from recording.
+ * retval -1 Failure
+ */
+int ast_app_copy_recording_to_vm(struct ast_vm_recording_data *vm_rec_data);
+
+/*!
* \brief Determine if a given mailbox has any voicemail
* If folder is NULL, defaults to "INBOX". If folder is "INBOX", includes the
* number of messages in the "Urgent" folder.
@@ -339,6 +372,29 @@ int ast_linear_stream(struct ast_channel *chan, const char *filename, int fd, in
*/
int ast_control_streamfile(struct ast_channel *chan, const char *file, const char *fwd, const char *rev, const char *stop, const char *pause, const char *restart, int skipms, long *offsetms);
+/*!
+ * \brief Stream a file with fast forward, pause, reverse, restart.
+ * \param chan
+ * \param file filename
+ * \param fwd, rev, stop, pause, restart, skipms, offsetms
+ * \param waitstream callback to invoke when fastforward or rewind occurrs.
+ *
+ * Before calling this function, set this to be the number
+ * of ms to start from the beginning of the file. When the function
+ * returns, it will be the number of ms from the beginning where the
+ * playback stopped. Pass NULL if you don't care.
+ */
+int ast_control_streamfile_w_cb(struct ast_channel *chan,
+ const char *file,
+ const char *fwd,
+ const char *rev,
+ const char *stop,
+ const char *pause,
+ const char *restart,
+ int skipms,
+ long *offsetms,
+ ast_waitstream_fr_cb cb);
+
/*! \brief Play a stream and wait for a digit, returning the digit that was pressed */
int ast_play_and_wait(struct ast_channel *chan, const char *fn);
diff --git a/include/asterisk/app_voicemail.h b/include/asterisk/app_voicemail.h
new file mode 100644
index 000000000..8a42bd7bb
--- /dev/null
+++ b/include/asterisk/app_voicemail.h
@@ -0,0 +1,212 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2011, Digium, Inc.
+ *
+ * David Vossel <dvossel@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ * \brief Voice Mail API
+ * \author David Vossel <dvossel@digium.com>
+ */
+
+#ifndef _ASTERISK_VM_H
+#define _ASTERISK_VM_H
+
+#include "asterisk/stringfields.h"
+#include "asterisk/linkedlists.h"
+
+#define AST_VM_FOLDER_NUMBER 12
+
+enum ast_vm_snapshot_sort_val {
+ AST_VM_SNAPSHOT_SORT_BY_ID = 0,
+ AST_VM_SNAPSHOT_SORT_BY_TIME,
+};
+
+struct ast_vm_msg_snapshot {
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(msg_id);
+ AST_STRING_FIELD(callerid);
+ AST_STRING_FIELD(callerchan);
+ AST_STRING_FIELD(exten);
+ AST_STRING_FIELD(origdate);
+ AST_STRING_FIELD(origtime);
+ AST_STRING_FIELD(duration);
+ AST_STRING_FIELD(folder_name);
+ AST_STRING_FIELD(flag);
+ );
+ unsigned int msg_number;
+
+ AST_LIST_ENTRY(ast_vm_msg_snapshot) msg;
+};
+
+struct ast_vm_mailbox_snapshot {
+ int total_msg_num;
+ AST_LIST_HEAD_NOLOCK(, ast_vm_msg_snapshot) snapshots[AST_VM_FOLDER_NUMBER];
+};
+
+/*
+ * \brief Create a snapshot of a mailbox which contains information about every msg.
+ *
+ * \param mailbox, the mailbox to look for
+ * \param context, the context to look for the mailbox in
+ * \param folder, OPTIONAL. When not NULL only msgs from the specified folder will be included.
+ * \param desending, list the msgs in descending order rather than ascending order.
+ * \param combine_INBOX_and_OLD, When this argument is set, The OLD folder will be represented
+ * in the INBOX folder of the snapshot. This allows the snapshot to represent the
+ * OLD and INBOX messages in sorted order merged together.
+ *
+ * \retval snapshot on success
+ * \retval NULL on failure
+ */
+struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_create(const char *mailbox,
+ const char *context,
+ const char *folder,
+ int descending,
+ enum ast_vm_snapshot_sort_val sort_val,
+ int combine_INBOX_and_OLD);
+
+/*
+ * \brief destroy a snapshot
+ *
+ * \param mailbox_snapshot The snapshot to destroy.
+ * \retval NULL
+ */
+struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_destroy(struct ast_vm_mailbox_snapshot *mailbox_snapshot);
+
+/*!
+ * \brief Move messages from one folder to another
+ *
+ * \param mailbox The mailbox to which the folders belong
+ * \param context The voicemail context for the mailbox
+ * \param num_msgs The number of messages to move
+ * \param oldfolder The folder from where messages should be moved
+ * \param old_msg_nums The message IDs of the messages to move
+ * \param newfolder The folder to which messages should be moved
+ * \param new_msg_ids[out] An array of message IDs for the messages as they are in the
+ * new folder. This array must be num_msgs sized.
+ *
+ * \retval -1 Failure
+ * \retval 0 Success
+ */
+int ast_vm_msg_move(const char *mailbox,
+ const char *context,
+ size_t num_msgs,
+ const char *oldfolder,
+ const char *old_msg_ids [],
+ const char *newfolder);
+
+/*!
+ * \brief Remove/delete messages from a mailbox folder.
+ *
+ * \param mailbox The mailbox from which to delete messages
+ * \param context The voicemail context for the mailbox
+ * \param num_msgs The number of messages to delete
+ * \param folder The folder from which to remove messages
+ * \param msgs The message IDs of the messages to delete
+ *
+ * \retval -1 Failure
+ * \retval 0 Success
+ */
+int ast_vm_msg_remove(const char *mailbox,
+ const char *context,
+ size_t num_msgs,
+ const char *folder,
+ const char *msgs []);
+
+/*!
+ * \brief forward a message from one mailbox to another.
+ *
+ * \brief from_mailbox The original mailbox the message is being forwarded from
+ * \brief from_context The voicemail context of the from_mailbox
+ * \brief from_folder The folder from which the message is being forwarded
+ * \brief to_mailbox The mailbox to forward the message to
+ * \brief to_context The voicemail context of the to_mailbox
+ * \brief to_folder The voicemail folder to forward the message to
+ * \brief num_msgs The number of messages being forwarded
+ * \brief msg_ids The message IDs of the messages in from_mailbox to forward
+ * \brief delete_old If non-zero, the forwarded messages are also deleted from from_mailbox.
+ * Otherwise, the messages will remain in the from_mailbox.
+ *
+ * \retval -1 Failure
+ * \retval 0 Success
+ */
+int ast_vm_msg_forward(const char *from_mailbox,
+ const char *from_context,
+ const char *from_folder,
+ const char *to_mailbox,
+ const char *to_context,
+ const char *to_folder,
+ size_t num_msgs,
+ const char *msg_ids [],
+ int delete_old);
+
+/*!
+ * \brief Voicemail playback callback function definition
+ *
+ * \param channel to play the file back on.
+ * \param location of file on disk
+ * \param duration of file in seconds. This will be zero if msg is very short or
+ * has an unknown duration.
+ */
+typedef void (ast_vm_msg_play_cb)(struct ast_channel *chan, const char *playfile, int duration);
+
+/*!
+ * \brief Play a voicemail msg back on a channel.
+ *
+ * \param mailbox msg is in.
+ * \param context of mailbox.
+ * \param voicemail folder to look in.
+ * \param message number in the voicemailbox to playback to the channel.
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_vm_msg_play(struct ast_channel *chan,
+ const char *mailbox,
+ const char *context,
+ const char *folder,
+ const char *msg_id,
+ ast_vm_msg_play_cb cb);
+
+/*!
+ * \brief Get the name of a folder given its numeric index
+ *
+ * \param index The integer value of the mailbox.
+ * \retval "" Invalid index provided
+ * \retval other The name of the mailbox
+ */
+const char *ast_vm_index_to_foldername(unsigned int index);
+
+#ifdef TEST_FRAMEWORK
+/*!
+ * \brief Add a user to the voicemail system for test purposes
+ * \param context The context of the mailbox
+ * \param mailbox The mailbox for the user
+ * \retval 0 success
+ * \retval other failure
+ */
+int ast_vm_test_create_user(const char *context, const char *mailbox);
+
+/*!
+ * \brief Dispose of a user. This should be used to destroy a user that was
+ * previously created using ast_vm_test_create_user
+ * \param context The context of the mailbox
+ * \param mailbox The mailbox for the user to destroy
+ */
+int ast_vm_test_destroy_user(const char *context, const char *mailbox);
+
+#endif
+
+#endif
diff --git a/include/asterisk/callerid.h b/include/asterisk/callerid.h
index c047632b9..7c4905e13 100644
--- a/include/asterisk/callerid.h
+++ b/include/asterisk/callerid.h
@@ -400,6 +400,7 @@ enum AST_REDIRECTING_REASON {
AST_REDIRECTING_REASON_OUT_OF_ORDER,
AST_REDIRECTING_REASON_AWAY,
AST_REDIRECTING_REASON_CALL_FWD_DTE, /* This is something defined in Q.931, and no I don't know what it means */
+ AST_REDIRECTING_REASON_SEND_TO_VM,
};
/*!
diff --git a/include/asterisk/config.h b/include/asterisk/config.h
index 5e71d80b1..b2ffcf5c4 100644
--- a/include/asterisk/config.h
+++ b/include/asterisk/config.h
@@ -589,6 +589,63 @@ int ast_config_text_file_save(const char *filename, const struct ast_config *cfg
int config_text_file_save(const char *filename, const struct ast_config *cfg, const char *generator) __attribute__((deprecated));
struct ast_config *ast_config_internal_load(const char *configfile, struct ast_config *cfg, struct ast_flags flags, const char *suggested_incl_file, const char *who_asked);
+/*!
+ * \brief
+ * Copies the contents of one ast_config into another
+ *
+ * \note
+ * This creates a config on the heap. The caller of this must
+ * be prepared to free the memory returned.
+ *
+ * \param orig the config to copy
+ * \return The new config on success, NULL on failure.
+ */
+struct ast_config *ast_config_copy(const struct ast_config *orig);
+
+/*!
+ * \brief
+ * Flags that affect the behaviour of config hooks.
+ */
+enum config_hook_flags {
+ butt,
+};
+
+/*
+ * \brief Callback when configuration is updated
+ *
+ * \param cfg A copy of the configuration that is being changed.
+ * This MUST be freed by the callback before returning.
+ */
+typedef int (*config_hook_cb)(struct ast_config *cfg);
+
+/*!
+ * \brief
+ * Register a config hook for a particular file and module
+ *
+ * \param name The name of the hook you are registering.
+ * \param filename The file whose config you wish to hook into.
+ * \param module The module that is reloading the config. This
+ * can be useful if multiple modules may possibly
+ * reload the same file, but you are only interested
+ * when a specific module reloads the file
+ * \param flags Flags that affect the way hooks work.
+ * \param hook The callback to be called when config is loaded.
+ * return 0 Success
+ * return -1 Unsuccess, also known as UTTER AND COMPLETE FAILURE
+ */
+int ast_config_hook_register(const char *name,
+ const char *filename,
+ const char *module,
+ enum config_hook_flags flags,
+ config_hook_cb hook);
+
+/*!
+ * \brief
+ * Unregister a config hook
+ *
+ * \param name The name of the hook to unregister
+ */
+void ast_config_hook_unregister(const char *name);
/*!
* \brief Support code to parse config file arguments
diff --git a/include/asterisk/event_defs.h b/include/asterisk/event_defs.h
index 4d1892256..b2bf0e4ca 100644
--- a/include/asterisk/event_defs.h
+++ b/include/asterisk/event_defs.h
@@ -54,8 +54,10 @@ enum ast_event_type {
AST_EVENT_SECURITY = 0x08,
/*! Used by res_stun_monitor to alert listeners to an exernal network address change. */
AST_EVENT_NETWORK_CHANGE = 0x09,
+ /*! The presence state for a presence provider */
+ AST_EVENT_PRESENCE_STATE = 0x0a,
/*! Number of event types. This should be the last event type + 1 */
- AST_EVENT_TOTAL = 0x0a,
+ AST_EVENT_TOTAL = 0x0b,
};
/*! \brief Event Information Element types */
@@ -287,9 +289,13 @@ enum ast_event_ie_type {
AST_EVENT_IE_RECEIVED_HASH = 0x0036,
AST_EVENT_IE_USING_PASSWORD = 0x0037,
AST_EVENT_IE_ATTEMPTED_TRANSPORT = 0x0038,
+ AST_EVENT_IE_PRESENCE_PROVIDER = 0x0039,
+ AST_EVENT_IE_PRESENCE_STATE = 0x003a,
+ AST_EVENT_IE_PRESENCE_SUBTYPE = 0x003b,
+ AST_EVENT_IE_PRESENCE_MESSAGE = 0x003c,
/*! \brief Must be the last IE value +1 */
- AST_EVENT_IE_TOTAL = 0x0039,
+ AST_EVENT_IE_TOTAL = 0x003d,
};
/*!
diff --git a/include/asterisk/file.h b/include/asterisk/file.h
index e7817b377..ec2a38e1f 100644
--- a/include/asterisk/file.h
+++ b/include/asterisk/file.h
@@ -49,7 +49,21 @@ struct ast_format;
#define AST_DIGIT_ANYNUM "0123456789"
#define SEEK_FORCECUR 10
-
+
+/*! The type of event associated with a ast_waitstream_fr_cb invocation */
+enum ast_waitstream_fr_cb_values {
+ AST_WAITSTREAM_CB_REWIND = 1,
+ AST_WAITSTREAM_CB_FASTFORWARD,
+ AST_WAITSTREAM_CB_START
+};
+
+/*!
+ * \brief callback used during dtmf controlled file playback to indicate
+ * location of playback in a file after rewinding or fastfowarding
+ * a file.
+ */
+typedef void (ast_waitstream_fr_cb)(struct ast_channel *chan, long ms, enum ast_waitstream_fr_cb_values val);
+
/*!
* \brief Streams a file
* \param c channel to stream the file to
@@ -162,6 +176,28 @@ int ast_waitstream_exten(struct ast_channel *c, const char *context);
*/
int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *forward, const char *rewind, int ms);
+/*!
+ * \brief Same as waitstream_fr but allows a callback to be alerted when a user
+ * fastforwards or rewinds the file.
+ * \param c channel to waitstream on
+ * \param breakon string of DTMF digits to break upon
+ * \param forward DTMF digit to fast forward upon
+ * \param rewind DTMF digit to rewind upon
+ * \param ms How many milliseconds to skip forward/back
+ * \param cb to call when rewind or fastfoward occurs.
+ * Begins playback of a stream...
+ * Wait for a stream to stop or for any one of a given digit to arrive,
+ * \retval 0 if the stream finishes.
+ * \retval the character if it was interrupted.
+ * \retval -1 on error.
+ */
+int ast_waitstream_fr_w_cb(struct ast_channel *c,
+ const char *breakon,
+ const char *forward,
+ const char *rewind,
+ int ms,
+ ast_waitstream_fr_cb cb);
+
/*!
* Same as waitstream, but with audio output to fd and monitored fd checking.
*
diff --git a/include/asterisk/manager.h b/include/asterisk/manager.h
index fac8c2bdf..257e939cf 100644
--- a/include/asterisk/manager.h
+++ b/include/asterisk/manager.h
@@ -86,6 +86,8 @@
#define EVENT_FLAG_CC (1 << 15) /* Call Completion events */
#define EVENT_FLAG_AOC (1 << 16) /* Advice Of Charge events */
#define EVENT_FLAG_TEST (1 << 17) /* Test event used to signal the Asterisk Test Suite */
+/*XXX Why shifted by 30? XXX */
+#define EVENT_FLAG_MESSAGE (1 << 30) /* MESSAGE events. */
/*@} */
/*! \brief Export manager structures */
diff --git a/include/asterisk/message.h b/include/asterisk/message.h
index d989563e5..31ed0b28a 100644
--- a/include/asterisk/message.h
+++ b/include/asterisk/message.h
@@ -114,6 +114,11 @@ struct ast_msg *ast_msg_alloc(void);
struct ast_msg *ast_msg_destroy(struct ast_msg *msg);
/*!
+ * \brief Bump a msg's ref count
+ */
+struct ast_msg *ast_msg_ref(struct ast_msg *msg);
+
+/*!
* \brief Set the 'to' URI of a message
*
* \retval 0 success
@@ -159,7 +164,7 @@ int __attribute__((format(printf, 2, 3)))
ast_msg_set_exten(struct ast_msg *msg, const char *fmt, ...);
/*!
- * \brief Set a variable on the message
+ * \brief Set a variable on the message going to the dialplan.
* \note Setting a variable that already exists overwrites the existing variable value
*
* \param name Name of variable to set
@@ -171,6 +176,18 @@ int __attribute__((format(printf, 2, 3)))
int ast_msg_set_var(struct ast_msg *msg, const char *name, const char *value);
/*!
+ * \brief Set a variable on the message being sent to a message tech directly.
+ * \note Setting a variable that already exists overwrites the existing variable value
+ *
+ * \param name Name of variable to set
+ * \param value Value of variable to set
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_msg_set_var_outbound(struct ast_msg *msg, const char *name, const char *value);
+
+/*!
* \brief Get the specified variable on the message
* \note The return value is valid only as long as the ast_message is valid. Hold a reference
* to the message if you plan on storing the return value. Do re-set the same
@@ -201,6 +218,17 @@ const char *ast_msg_get_body(const struct ast_msg *msg);
int ast_msg_queue(struct ast_msg *msg);
/*!
+ * \brief Send a msg directly to an endpoint.
+ *
+ * Regardless of the return value of this function, this funciton will take
+ * care of ensuring that the message object is properly destroyed when needed.
+ *
+ * \retval 0 message successfully queued to be sent out
+ * \retval non-zero failure, message not get sent out.
+ */
+int ast_msg_send(struct ast_msg *msg, const char *to, const char *from);
+
+/*!
* \brief Opaque iterator for msg variables
*/
struct ast_msg_var_iterator;
diff --git a/include/asterisk/pbx.h b/include/asterisk/pbx.h
index f7dc7b919..2305f3910 100644
--- a/include/asterisk/pbx.h
+++ b/include/asterisk/pbx.h
@@ -26,6 +26,7 @@
#include "asterisk/channel.h"
#include "asterisk/sched.h"
#include "asterisk/devicestate.h"
+#include "asterisk/presencestate.h"
#include "asterisk/chanvars.h"
#include "asterisk/hashtab.h"
#include "asterisk/stringfields.h"
@@ -74,9 +75,24 @@ struct ast_exten;
struct ast_include;
struct ast_ignorepat;
struct ast_sw;
+
+enum ast_state_cb_update_reason {
+ /*! The extension state update is a result of a device state changing on the extension. */
+ AST_HINT_UPDATE_DEVICE = 1,
+ /*! The extension state update is a result of presence state changing on the extension. */
+ AST_HINT_UPDATE_PRESENCE = 2,
+};
+
+struct ast_state_cb_info {
+ enum ast_state_cb_update_reason reason;
+ enum ast_extension_states exten_state;
+ enum ast_presence_state presence_state;
+ const char *presence_subtype;
+ const char *presence_message;
+};
/*! \brief Typedef for devicestate and hint callbacks */
-typedef int (*ast_state_cb_type)(const char *context, const char *exten, enum ast_extension_states state, void *data);
+typedef int (*ast_state_cb_type)(char *context, char *id, struct ast_state_cb_info *info, void *data);
/*! \brief Typedef for devicestate and hint callback removal indication callback */
typedef void (*ast_state_cb_destroy_type)(int id, void *data);
@@ -402,6 +418,22 @@ enum ast_extension_states ast_devstate_to_extenstate(enum ast_device_state devst
int ast_extension_state(struct ast_channel *c, const char *context, const char *exten);
/*!
+ * \brief Uses hint and presence state callback to get the presence state of an extension
+ *
+ * \param c this is not important
+ * \param context which context to look in
+ * \param exten which extension to get state
+ * \param[out] subtype Further information regarding the presence returned
+ * \param[out] message Custom message further describing current presence
+ *
+ * \note The subtype and message are dynamically allocated and must be freed by
+ * the caller of this function.
+ *
+ * \return returns the presence state value.
+ */
+int ast_hint_presence_state(struct ast_channel *c, const char *context, const char *exten, char **subtype, char **message);
+
+/*!
* \brief Return string representation of the state of an extension
*
* \param extension_state is the numerical state delivered by ast_extension_state
diff --git a/include/asterisk/presencestate.h b/include/asterisk/presencestate.h
new file mode 100644
index 000000000..dbbe5dcab
--- /dev/null
+++ b/include/asterisk/presencestate.h
@@ -0,0 +1,154 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2011, Digium, Inc.
+ *
+ * David Vossel <dvossel@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ * \brief Presence state management
+ */
+
+#ifndef _ASTERISK_PRESSTATE_H
+#define _ASTERISK_PRESSTATE_H
+
+enum ast_presence_state {
+ AST_PRESENCE_NOT_SET = 0,
+ AST_PRESENCE_UNAVAILABLE,
+ AST_PRESENCE_AVAILABLE,
+ AST_PRESENCE_AWAY,
+ AST_PRESENCE_XA,
+ AST_PRESENCE_CHAT,
+ AST_PRESENCE_DND,
+ /* This is not something that a user can
+ * set his presence to. Rather, this is returned
+ * to indicate that presence is in some invalid
+ * state
+ */
+ AST_PRESENCE_INVALID,
+};
+
+/*! \brief Presence state provider call back */
+typedef enum ast_presence_state (*ast_presence_state_prov_cb_type)(const char *data, char **subtype, char **message);
+
+/*!
+ * \brief Convert presence state to text string for output
+ *
+ * \param state Current presence state
+ */
+const char *ast_presence_state2str(enum ast_presence_state state);
+
+/*!
+ * \brief Convert presence state from text to integer value
+ *
+ * \param val The text representing the presence state. Valid values are anything
+ * that comes after AST_PRESENCE_ in one of the defined values.
+ *
+ * \return The AST_PRESENCE_ integer value
+ */
+enum ast_presence_state ast_presence_state_val(const char *val);
+
+/*!
+ * \brief Asks a presence state provider for the current presence state.
+ *
+ * \param presence_provider, The presence provider to retrieve the state from.
+ * \param subtype, The output paramenter to store the subtype string in. Must be freed if returned
+ * \param message, The output paramenter to store the message string in. Must be freed if returned
+ *
+ * \retval presence state value on success,
+ * \retval -1 on failure.
+ */
+enum ast_presence_state ast_presence_state(const char *presence_provider, char **subtype, char **message);
+
+/*!
+ * \brief Asks a presence state provider for the current presence state, bypassing the event cache
+ *
+ * \details Some presence state providers may perform transformations on presence data when it is
+ * requested (such as a base64 decode). In such instances, use of the event cache is not suitable
+ * and should be bypassed.
+ *
+ * \param presence_provider, The presence provider to retrieve the state from.
+ * \param subtype, The output paramenter to store the subtype string in. Must be freed if returned
+ * \param message, The output paramenter to store the message string in. Must be freed if returned
+ *
+ * \retval presence state value on success,
+ * \retval -1 on failure.
+ */
+enum ast_presence_state ast_presence_state_nocache(const char *presence_provider, char **subtype, char **message);
+
+/*!
+ * \brief Notify the world that a presence provider state changed.
+ *
+ * \param state the new presence state
+ * \param subtype the new presence subtype
+ * \param message the new presence message
+ * \param fmt Presence entity whose state has changed
+ *
+ * The new state of the entity will be sent off to any subscribers
+ * of the presence state. It will also be stored in the internal event
+ * cache.
+ *
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+int ast_presence_state_changed(enum ast_presence_state state,
+ const char *subtype,
+ const char *message,
+ const char *fmt, ...)
+ __attribute__((format(printf, 4, 5)));
+
+/*!
+ * \brief Notify the world that a presence provider state changed.
+ *
+ * \param state the new presence state
+ * \param subtype the new presence subtype
+ * \param message the new presence message
+ * \param presence_provider Presence entity whose state has changed
+ *
+ * The new state of the entity will be sent off to any subscribers
+ * of the presence state. It will also be stored in the internal event
+ * cache.
+ *
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+int ast_presence_state_changed_literal(enum ast_presence_state state,
+ const char *subtype,
+ const char *message,
+ const char *presence_provider);
+
+/*!
+ * \brief Add presence state provider
+ *
+ * \param label to use in hint, like label:object
+ * \param callback Callback
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_presence_state_prov_add(const char *label, ast_presence_state_prov_cb_type callback);
+
+/*!
+ * \brief Remove presence state provider
+ *
+ * \param label to use in hint, like label:object
+ *
+ * \retval -1 on failure
+ * \retval 0 on success
+ */
+int ast_presence_state_prov_del(const char *label);
+
+int ast_presence_state_engine_init(void);
+#endif
+
diff --git a/include/asterisk/sip_api.h b/include/asterisk/sip_api.h
new file mode 100644
index 000000000..018785a8d
--- /dev/null
+++ b/include/asterisk/sip_api.h
@@ -0,0 +1,51 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef __ASTERISK_SIP_H
+#define __ASTERISK_SIP_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+#include "asterisk/optional_api.h"
+#include "asterisk/config.h"
+
+/*!
+ * \brief Send a customized SIP INFO request
+ *
+ * \param headers The headers to add to the INFO request
+ * \param content_type The content type header to add
+ * \param conten The body of the INFO request
+ * \param useragent_filter If non-NULL, only send the INFO if the
+ * recipient's User-Agent contains useragent_filter as a substring
+ *
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+int ast_sipinfo_send(struct ast_channel *chan,
+ struct ast_variable *headers,
+ const char *content_type,
+ const char *content,
+ const char *useragent_filter);
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* __ASTERISK_SIP_H */
diff --git a/main/app.c b/main/app.c
index 3d2fa52c3..31484a18d 100644
--- a/main/app.c
+++ b/main/app.c
@@ -423,18 +423,21 @@ static int (*ast_inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsg
static int (*ast_inboxcount2_func)(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs) = NULL;
static int (*ast_sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context) = NULL;
static int (*ast_messagecount_func)(const char *context, const char *mailbox, const char *folder) = NULL;
+static int (*ast_copy_recording_to_vm_func)(struct ast_vm_recording_data *vm_rec_data) = NULL;
void ast_install_vm_functions(int (*has_voicemail_func)(const char *mailbox, const char *folder),
int (*inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsgs),
int (*inboxcount2_func)(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs),
int (*messagecount_func)(const char *context, const char *mailbox, const char *folder),
- int (*sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context))
+ int (*sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context),
+ int (*copy_recording_to_vm_func)(struct ast_vm_recording_data *vm_rec_data))
{
ast_has_voicemail_func = has_voicemail_func;
ast_inboxcount_func = inboxcount_func;
ast_inboxcount2_func = inboxcount2_func;
ast_messagecount_func = messagecount_func;
ast_sayname_func = sayname_func;
+ ast_copy_recording_to_vm_func = copy_recording_to_vm_func;
}
void ast_uninstall_vm_functions(void)
@@ -444,6 +447,7 @@ void ast_uninstall_vm_functions(void)
ast_inboxcount2_func = NULL;
ast_messagecount_func = NULL;
ast_sayname_func = NULL;
+ ast_copy_recording_to_vm_func = NULL;
}
int ast_app_has_voicemail(const char *mailbox, const char *folder)
@@ -459,6 +463,28 @@ int ast_app_has_voicemail(const char *mailbox, const char *folder)
return 0;
}
+/*!
+ * \internal
+ * \brief Function used as a callback for ast_copy_recording_to_vm when a real one isn't installed.
+ * \param vm_rec_data Stores crucial information about the voicemail that will basically just be used
+ * to figure out what the name of the recipient was supposed to be
+ */
+int ast_app_copy_recording_to_vm(struct ast_vm_recording_data *vm_rec_data)
+{
+ static int warned = 0;
+
+ if (ast_copy_recording_to_vm_func) {
+ return ast_copy_recording_to_vm_func(vm_rec_data);
+ }
+
+ if (warned++ % 10 == 0) {
+ ast_verb(3, "copy recording to voicemail called to copy %s.%s to %s@%s, but voicemail not loaded.\n",
+ vm_rec_data->recording_file, vm_rec_data->recording_ext,
+ vm_rec_data->mailbox, vm_rec_data->context);
+ }
+
+ return -1;
+}
int ast_app_inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
{
@@ -709,10 +735,16 @@ int ast_linear_stream(struct ast_channel *chan, const char *filename, int fd, in
return res;
}
-int ast_control_streamfile(struct ast_channel *chan, const char *file,
- const char *fwd, const char *rev,
- const char *stop, const char *suspend,
- const char *restart, int skipms, long *offsetms)
+static int control_streamfile(struct ast_channel *chan,
+ const char *file,
+ const char *fwd,
+ const char *rev,
+ const char *stop,
+ const char *suspend,
+ const char *restart,
+ int skipms,
+ long *offsetms,
+ ast_waitstream_fr_cb cb)
{
char *breaks = NULL;
char *end = NULL;
@@ -784,7 +816,11 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file,
ast_seekstream(ast_channel_stream(chan), offset, SEEK_SET);
offset = 0;
}
- res = ast_waitstream_fr(chan, breaks, fwd, rev, skipms);
+ if (cb) {
+ res = ast_waitstream_fr_w_cb(chan, breaks, fwd, rev, skipms, cb);
+ } else {
+ res = ast_waitstream_fr(chan, breaks, fwd, rev, skipms);
+ }
}
if (res < 1) {
@@ -848,6 +884,28 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file,
return res;
}
+int ast_control_streamfile_w_cb(struct ast_channel *chan,
+ const char *file,
+ const char *fwd,
+ const char *rev,
+ const char *stop,
+ const char *suspend,
+ const char *restart,
+ int skipms,
+ long *offsetms,
+ ast_waitstream_fr_cb cb)
+{
+ return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, cb);
+}
+
+int ast_control_streamfile(struct ast_channel *chan, const char *file,
+ const char *fwd, const char *rev,
+ const char *stop, const char *suspend,
+ const char *restart, int skipms, long *offsetms)
+{
+ return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, NULL);
+}
+
int ast_play_and_wait(struct ast_channel *chan, const char *fn)
{
int d = 0;
diff --git a/main/asterisk.c b/main/asterisk.c
index 849cbdd80..6122028bf 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -136,6 +136,7 @@ int daemon(int, int); /* defined in libresolv of all places */
#include "asterisk/ast_version.h"
#include "asterisk/linkedlists.h"
#include "asterisk/devicestate.h"
+#include "asterisk/presencestate.h"
#include "asterisk/module.h"
#include "asterisk/dsp.h"
#include "asterisk/buildinfo.h"
@@ -4028,6 +4029,11 @@ int main(int argc, char *argv[])
exit(1);
}
+ if (ast_presence_state_engine_init()) {
+ printf("%s", term_quit());
+ exit(1);
+ }
+
ast_dsp_init();
ast_udptl_init();
diff --git a/main/callerid.c b/main/callerid.c
index dc3a91093..37edd992c 100644
--- a/main/callerid.c
+++ b/main/callerid.c
@@ -1203,6 +1203,7 @@ static const struct ast_value_translation redirecting_reason_types[] = {
{ AST_REDIRECTING_REASON_OUT_OF_ORDER, "out_of_order", "Called DTE Out-Of-Order" },
{ AST_REDIRECTING_REASON_AWAY, "away", "Callee is Away" },
{ AST_REDIRECTING_REASON_CALL_FWD_DTE, "cf_dte", "Call Forwarding By The Called DTE" },
+ { AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm", "Call is being redirected to user's voicemail"},
/* *INDENT-ON* */
};
diff --git a/main/channel.c b/main/channel.c
index 8bd973501..320947da7 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -2514,6 +2514,7 @@ void ast_channel_clear_softhangup(struct ast_channel *chan, int flag)
int ast_softhangup_nolock(struct ast_channel *chan, int cause)
{
ast_debug(1, "Soft-Hanging up channel '%s'\n", ast_channel_name(chan));
+ ast_backtrace();
/* Inform channel driver that we need to be hung up, if it cares */
ast_channel_softhangup_internal_flag_add(chan, cause);
ast_queue_frame(chan, &ast_null_frame);
diff --git a/main/config.c b/main/config.c
index 9d9eefc25..127d89bb1 100644
--- a/main/config.c
+++ b/main/config.c
@@ -64,6 +64,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
static char *extconfig_conf = "extconfig.conf";
+static struct ao2_container *cfg_hooks;
+static void config_hook_exec(const char *filename, const char *module, struct ast_config *cfg);
/*! \brief Structure to keep comments for rewriting configuration files */
struct ast_comment {
@@ -2278,6 +2280,39 @@ static struct ast_config_engine text_file_engine = {
.load_func = config_text_file_load,
};
+struct ast_config *ast_config_copy(const struct ast_config *old)
+{
+ struct ast_config *new_config = ast_config_new();
+ struct ast_category *cat_iter;
+
+ if (!new_config) {
+ return NULL;
+ }
+
+ for (cat_iter = old->root; cat_iter; cat_iter = cat_iter->next) {
+ struct ast_category *new_cat =
+ ast_category_new(cat_iter->name, cat_iter->file, cat_iter->lineno);
+ if (!new_cat) {
+ goto fail;
+ }
+ ast_category_append(new_config, new_cat);
+ if (cat_iter->root) {
+ new_cat->root = ast_variables_dup(cat_iter->root);
+ if (!new_cat->root) {
+ goto fail;
+ }
+ new_cat->last = cat_iter->last;
+ }
+ }
+
+ return new_config;
+
+fail:
+ ast_config_destroy(new_config);
+ return NULL;
+}
+
+
struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, struct ast_flags flags, const char *suggested_include_file, const char *who_asked)
{
char db[256];
@@ -2310,10 +2345,12 @@ struct ast_config *ast_config_internal_load(const char *filename, struct ast_con
result = loader->load_func(db, table, filename, cfg, flags, suggested_include_file, who_asked);
- if (result && result != CONFIG_STATUS_FILEINVALID && result != CONFIG_STATUS_FILEUNCHANGED)
+ if (result && result != CONFIG_STATUS_FILEINVALID && result != CONFIG_STATUS_FILEUNCHANGED) {
result->include_level--;
- else if (result != CONFIG_STATUS_FILEINVALID)
+ config_hook_exec(filename, who_asked, result);
+ } else if (result != CONFIG_STATUS_FILEINVALID) {
cfg->include_level--;
+ }
return result;
}
@@ -2968,3 +3005,85 @@ int register_config_cli(void)
ast_cli_register_multiple(cli_config, ARRAY_LEN(cli_config));
return 0;
}
+
+struct cfg_hook {
+ const char *name;
+ const char *filename;
+ const char *module;
+ config_hook_cb hook_cb;
+};
+
+static void hook_destroy(void *obj)
+{
+ struct cfg_hook *hook = obj;
+ ast_free((void *) hook->name);
+ ast_free((void *) hook->filename);
+ ast_free((void *) hook->module);
+}
+
+static int hook_cmp(void *obj, void *arg, int flags)
+{
+ struct cfg_hook *hook1 = obj;
+ struct cfg_hook *hook2 = arg;
+
+ return !(strcasecmp(hook1->name, hook2->name)) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static int hook_hash(const void *obj, const int flags)
+{
+ const struct cfg_hook *hook = obj;
+
+ return ast_str_hash(hook->name);
+}
+
+void ast_config_hook_unregister(const char *name)
+{
+ struct cfg_hook tmp;
+
+ tmp.name = ast_strdupa(name);
+
+ ao2_find(cfg_hooks, &tmp, OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
+}
+
+static void config_hook_exec(const char *filename, const char *module, struct ast_config *cfg)
+{
+ struct ao2_iterator it;
+ struct cfg_hook *hook;
+ if (!(cfg_hooks)) {
+ return;
+ }
+ it = ao2_iterator_init(cfg_hooks, 0);
+ while ((hook = ao2_iterator_next(&it))) {
+ if (!strcasecmp(hook->filename, filename) &&
+ !strcasecmp(hook->module, module)) {
+ struct ast_config *copy = ast_config_copy(cfg);
+ hook->hook_cb(copy);
+ }
+ ao2_ref(hook, -1);
+ }
+ ao2_iterator_destroy(&it);
+}
+
+int ast_config_hook_register(const char *name,
+ const char *filename,
+ const char *module,
+ enum config_hook_flags flags,
+ config_hook_cb hook_cb)
+{
+ struct cfg_hook *hook;
+ if (!cfg_hooks && !(cfg_hooks = ao2_container_alloc(17, hook_hash, hook_cmp))) {
+ return -1;
+ }
+
+ if (!(hook = ao2_alloc(sizeof(*hook), hook_destroy))) {
+ return -1;
+ }
+
+ hook->hook_cb = hook_cb;
+ hook->filename = ast_strdup(filename);
+ hook->name = ast_strdup(name);
+ hook->module = ast_strdup(module);
+
+ ao2_link(cfg_hooks, hook);
+ return 0;
+}
diff --git a/main/event.c b/main/event.c
index 9ecf71028..d79b07a89 100644
--- a/main/event.c
+++ b/main/event.c
@@ -137,6 +137,7 @@ static int ast_event_cmp(void *obj, void *arg, int flags);
static int ast_event_hash_mwi(const void *obj, const int flags);
static int ast_event_hash_devstate(const void *obj, const int flags);
static int ast_event_hash_devstate_change(const void *obj, const int flags);
+static int ast_event_hash_presence_state_change(const void *obj, const int flags);
#ifdef LOW_MEMORY
#define NUM_CACHE_BUCKETS 17
@@ -181,6 +182,11 @@ static struct {
.hash_fn = ast_event_hash_devstate_change,
.cache_args = { AST_EVENT_IE_DEVICE, AST_EVENT_IE_EID, },
},
+ [AST_EVENT_PRESENCE_STATE] = {
+ .hash_fn = ast_event_hash_presence_state_change,
+ .cache_args = { AST_EVENT_IE_PRESENCE_STATE, },
+ },
+
};
/*!
@@ -1587,6 +1593,22 @@ static int ast_event_hash_devstate_change(const void *obj, const int flags)
return ast_str_hash(ast_event_get_ie_str(event, AST_EVENT_IE_DEVICE));
}
+/*!
+ * \internal
+ * \brief Hash function for AST_EVENT_PRESENCE_STATE
+ *
+ * \param[in] obj an ast_event
+ * \param[in] flags unused
+ *
+ * \return hash value
+ */
+static int ast_event_hash_presence_state_change(const void *obj, const int flags)
+{
+ const struct ast_event *event = obj;
+
+ return ast_str_hash(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER));
+}
+
static int ast_event_hash(const void *obj, const int flags)
{
const struct ast_event_ref *event_ref;
diff --git a/main/features.c b/main/features.c
index 8b69d1385..04bc5326a 100644
--- a/main/features.c
+++ b/main/features.c
@@ -409,6 +409,17 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<para>Bridge together two channels already in the PBX.</para>
</description>
</manager>
+ <manager name="Parkinglots" language="en_US">
+ <synopsis>
+ Get a list of parking lots
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ </syntax>
+ <description>
+ <para>List all parking lots as a series of AMI events</para>
+ </description>
+ </manager>
<function name="FEATURE" language="en_US">
<synopsis>
Get or set a feature option on a channel.
@@ -7347,7 +7358,42 @@ static struct ast_cli_entry cli_features[] = {
AST_CLI_DEFINE(handle_parkedcalls, "List currently parked calls"),
};
-/*!
+static int manager_parkinglot_list(struct mansession *s, const struct message *m)
+{
+ const char *id = astman_get_header(m, "ActionID");
+ char idText[256] = "";
+ struct ao2_iterator iter;
+ struct ast_parkinglot *curlot;
+
+ if (!ast_strlen_zero(id))
+ snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
+
+ astman_send_ack(s, m, "Parking lots will follow");
+
+ iter = ao2_iterator_init(parkinglots, 0);
+ while ((curlot = ao2_iterator_next(&iter))) {
+ astman_append(s, "Event: Parkinglot\r\n"
+ "Name: %s\r\n"
+ "StartExten: %d\r\n"
+ "StopExten: %d\r\n"
+ "Timeout: %d\r\n"
+ "\r\n",
+ curlot->name,
+ curlot->cfg.parking_start,
+ curlot->cfg.parking_stop,
+ curlot->cfg.parkingtime ? curlot->cfg.parkingtime / 1000 : curlot->cfg.parkingtime);
+ ao2_ref(curlot, -1);
+ }
+
+ astman_append(s,
+ "Event: ParkinglotsComplete\r\n"
+ "%s"
+ "\r\n",idText);
+
+ return RESULT_SUCCESS;
+}
+
+/*!
* \brief Dump parking lot status
* \param s
* \param m
@@ -7363,6 +7409,7 @@ static int manager_parking_status(struct mansession *s, const struct message *m)
struct ao2_iterator iter;
struct ast_parkinglot *curlot;
int numparked = 0;
+ long now = time(NULL);
if (!ast_strlen_zero(id))
snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
@@ -7379,6 +7426,7 @@ static int manager_parking_status(struct mansession *s, const struct message *m)
"Channel: %s\r\n"
"From: %s\r\n"
"Timeout: %ld\r\n"
+ "Duration: %ld\r\n"
"CallerIDNum: %s\r\n"
"CallerIDName: %s\r\n"
"ConnectedLineNum: %s\r\n"
@@ -7387,7 +7435,8 @@ static int manager_parking_status(struct mansession *s, const struct message *m)
"\r\n",
curlot->name,
cur->parkingnum, ast_channel_name(cur->chan), cur->peername,
- (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - (long) time(NULL),
+ (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - now,
+ now - (long) cur->start.tv_sec,
S_COR(ast_channel_caller(cur->chan)->id.number.valid, ast_channel_caller(cur->chan)->id.number.str, ""), /* XXX in other places it is <unknown> */
S_COR(ast_channel_caller(cur->chan)->id.name.valid, ast_channel_caller(cur->chan)->id.name.str, ""),
S_COR(ast_channel_connected(cur->chan)->id.number.valid, ast_channel_connected(cur->chan)->id.number.str, ""), /* XXX in other places it is <unknown> */
@@ -8669,6 +8718,7 @@ int ast_features_init(void)
res = ast_register_application2(parkcall, park_call_exec, NULL, NULL, NULL);
if (!res) {
ast_manager_register_xml_core("ParkedCalls", 0, manager_parking_status);
+ ast_manager_register_xml_core("Parkinglots", 0, manager_parkinglot_list);
ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park);
ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge);
}
diff --git a/main/file.c b/main/file.c
index 52f5af58d..076b31dd7 100644
--- a/main/file.c
+++ b/main/file.c
@@ -1237,11 +1237,18 @@ struct ast_filestream *ast_writefile(const char *filename, const char *type, con
/*!
* \brief the core of all waitstream() functions
*/
-static int waitstream_core(struct ast_channel *c, const char *breakon,
- const char *forward, const char *reverse, int skip_ms,
- int audiofd, int cmdfd, const char *context)
+static int waitstream_core(struct ast_channel *c,
+ const char *breakon,
+ const char *forward,
+ const char *reverse,
+ int skip_ms,
+ int audiofd,
+ int cmdfd,
+ const char *context,
+ ast_waitstream_fr_cb cb)
{
const char *orig_chan_name = NULL;
+
int err = 0;
if (!breakon)
@@ -1257,6 +1264,11 @@ static int waitstream_core(struct ast_channel *c, const char *breakon,
if (ast_test_flag(ast_channel_flags(c), AST_FLAG_MASQ_NOSTREAM))
orig_chan_name = ast_strdupa(ast_channel_name(c));
+ if (ast_channel_stream(c) && cb) {
+ long ms_len = ast_tellstream(ast_channel_stream(c)) / (ast_format_rate(&ast_channel_stream(c)->fmt->format) / 1000);
+ cb(c, ms_len, AST_WAITSTREAM_CB_START);
+ }
+
while (ast_channel_stream(c)) {
int res;
int ms;
@@ -1318,6 +1330,7 @@ static int waitstream_core(struct ast_channel *c, const char *breakon,
return res;
}
} else {
+ enum ast_waitstream_fr_cb_values cb_val = 0;
res = fr->subclass.integer;
if (strchr(forward, res)) {
int eoftest;
@@ -1328,13 +1341,19 @@ static int waitstream_core(struct ast_channel *c, const char *breakon,
} else {
ungetc(eoftest, ast_channel_stream(c)->f);
}
+ cb_val = AST_WAITSTREAM_CB_FASTFORWARD;
} else if (strchr(reverse, res)) {
ast_stream_rewind(ast_channel_stream(c), skip_ms);
+ cb_val = AST_WAITSTREAM_CB_REWIND;
} else if (strchr(breakon, res)) {
ast_frfree(fr);
ast_clear_flag(ast_channel_flags(c), AST_FLAG_END_DTMF_ONLY);
return res;
}
+ if (cb_val && cb) {
+ long ms_len = ast_tellstream(ast_channel_stream(c)) / (ast_format_rate(&ast_channel_stream(c)->fmt->format) / 1000);
+ cb(c, ms_len, cb_val);
+ }
}
break;
case AST_FRAME_CONTROL:
@@ -1385,21 +1404,32 @@ static int waitstream_core(struct ast_channel *c, const char *breakon,
return (err || ast_channel_softhangup_internal_flag(c)) ? -1 : 0;
}
+int ast_waitstream_fr_w_cb(struct ast_channel *c,
+ const char *breakon,
+ const char *forward,
+ const char *reverse,
+ int ms,
+ ast_waitstream_fr_cb cb)
+{
+ return waitstream_core(c, breakon, forward, reverse, ms,
+ -1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */, cb);
+}
+
int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *forward, const char *reverse, int ms)
{
return waitstream_core(c, breakon, forward, reverse, ms,
- -1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */);
+ -1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */, NULL /* no callback */);
}
int ast_waitstream(struct ast_channel *c, const char *breakon)
{
- return waitstream_core(c, breakon, NULL, NULL, 0, -1, -1, NULL);
+ return waitstream_core(c, breakon, NULL, NULL, 0, -1, -1, NULL, NULL /* no callback */);
}
int ast_waitstream_full(struct ast_channel *c, const char *breakon, int audiofd, int cmdfd)
{
return waitstream_core(c, breakon, NULL, NULL, 0,
- audiofd, cmdfd, NULL /* no context */);
+ audiofd, cmdfd, NULL /* no context */, NULL /* no callback */);
}
int ast_waitstream_exten(struct ast_channel *c, const char *context)
@@ -1410,7 +1440,7 @@ int ast_waitstream_exten(struct ast_channel *c, const char *context)
if (!context)
context = ast_channel_context(c);
return waitstream_core(c, NULL, NULL, NULL, 0,
- -1, -1, context);
+ -1, -1, context, NULL /* no callback */);
}
/*
diff --git a/main/manager.c b/main/manager.c
index 2861c6d9b..4bf859e2e 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -82,6 +82,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/security_events.h"
#include "asterisk/aoc.h"
#include "asterisk/stringfields.h"
+#include "asterisk/presencestate.h"
/*** DOCUMENTATION
<manager name="Ping" language="en_US">
@@ -510,6 +511,22 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
the hint for the extension and the status.</para>
</description>
</manager>
+ <manager name="PresenceState" language="en_US">
+ <synopsis>
+ Check Presence State
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="Provider" required="true">
+ <para>Presence Provider to check the state of</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>Report the presence state for the given presence provider.</para>
+ <para>Will return a <literal>Presence State</literal> message. The response will include the
+ presence state and, if set, a presence subtype and custom message.</para>
+ </description>
+ </manager>
<manager name="AbsoluteTimeout" language="en_US">
<synopsis>
Set absolute timeout.
@@ -1218,6 +1235,7 @@ static const struct permalias {
{ EVENT_FLAG_CC, "cc" },
{ EVENT_FLAG_AOC, "aoc" },
{ EVENT_FLAG_TEST, "test" },
+ { EVENT_FLAG_MESSAGE, "message" },
{ INT_MAX, "all" },
{ 0, "none" },
};
@@ -3211,6 +3229,7 @@ static int action_hangup(struct mansession *s, const struct message *m)
if (name_or_regex[0] != '/') {
if (!(c = ast_channel_get_by_name(name_or_regex))) {
+ ast_log(LOG_NOTICE, "!!!!!!!!!! Can't find channel to hang up!\n");
astman_send_error(s, m, "No such channel");
return 0;
}
@@ -4384,6 +4403,43 @@ static int action_extensionstate(struct mansession *s, const struct message *m)
return 0;
}
+static int action_presencestate(struct mansession *s, const struct message *m)
+{
+ const char *provider = astman_get_header(m, "Provider");
+ enum ast_presence_state state;
+ char *subtype;
+ char *message;
+ char subtype_header[256] = "";
+ char message_header[256] = "";
+
+ if (ast_strlen_zero(provider)) {
+ astman_send_error(s, m, "No provider specified");
+ return 0;
+ }
+
+ state = ast_presence_state(provider, &subtype, &message);
+
+ if (!ast_strlen_zero(subtype)) {
+ snprintf(subtype_header, sizeof(subtype_header),
+ "Subtype: %s\r\n", subtype);
+ }
+
+ if (!ast_strlen_zero(message)) {
+ snprintf(message_header, sizeof(message_header),
+ "Message: %s\r\n", message);
+ }
+
+ astman_append(s, "Message: Presence State\r\n"
+ "State: %s\r\n"
+ "%s"
+ "%s"
+ "\r\n",
+ ast_presence_state2str(state),
+ subtype_header,
+ message_header);
+ return 0;
+}
+
static int action_timeout(struct mansession *s, const struct message *m)
{
struct ast_channel *c;
@@ -5485,10 +5541,17 @@ int ast_manager_unregister(const char *action)
return 0;
}
-static int manager_state_cb(const char *context, const char *exten, enum ast_extension_states state, void *data)
+static int manager_state_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data)
{
/* Notify managers of change */
char hint[512];
+ int state = info->exten_state;
+
+ /* only interested in device state for this right now */
+ if (info->reason != AST_HINT_UPDATE_DEVICE) {
+ return 0;
+ }
+
ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten);
manager_event(EVENT_FLAG_CALL, "ExtensionStatus", "Exten: %s\r\nContext: %s\r\nHint: %s\r\nStatus: %d\r\n", exten, context, hint, state);
@@ -6850,6 +6913,7 @@ static int __init_manager(int reload)
ast_manager_register_xml_core("Originate", EVENT_FLAG_ORIGINATE, action_originate);
ast_manager_register_xml_core("Command", EVENT_FLAG_COMMAND, action_command);
ast_manager_register_xml_core("ExtensionState", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_extensionstate);
+ ast_manager_register_xml_core("PresenceState", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_presencestate);
ast_manager_register_xml_core("AbsoluteTimeout", EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL, action_timeout);
ast_manager_register_xml_core("MailboxStatus", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_mailboxstatus);
ast_manager_register_xml_core("MailboxCount", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_mailboxcount);
diff --git a/main/message.c b/main/message.c
index 5722969d0..e26b8c375 100644
--- a/main/message.c
+++ b/main/message.c
@@ -32,6 +32,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/module.h"
#include "asterisk/datastore.h"
#include "asterisk/pbx.h"
+#include "asterisk/manager.h"
#include "asterisk/strings.h"
#include "asterisk/astobj2.h"
#include "asterisk/app.h"
@@ -56,6 +57,18 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<para>Read-only. The source of the message. When processing an
incoming message, this will be set to the source of the message.</para>
</enum>
+ <enum name="custom_data">
+ <para>Write-only. Mark or unmark all message headers for an outgoing
+ message. The following values can be set:</para>
+ <enumlist>
+ <enum name="mark_all_outbound">
+ <para>Mark all headers for an outgoing message.</para>
+ </enum>
+ <enum name="clear_all_outbound">
+ <para>Unmark all headers for an outgoing message.</para>
+ </enum>
+ </enumlist>
+ </enum>
<enum name="body">
<para>Read/Write. The message body. When processing an incoming
message, this includes the body of the message that Asterisk
@@ -139,6 +152,39 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
</variablelist>
</description>
</application>
+ <manager name="MessageSend" language="en_US">
+ <synopsis>
+ Send an out of call message to an endpoint.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="To" required="true">
+ <para>The URI the message is to be sent to.</para>
+ </parameter>
+ <parameter name="From">
+ <para>A From URI for the message if needed for the
+ message technology being used to send this message.</para>
+ <note>
+ <para>For SIP the from parameter can be a configured peer name
+ or in the form of "display-name" &lt;URI&gt;.</para>
+ </note>
+ </parameter>
+ <parameter name="Body">
+ <para>The message body text. This must not contain any newlines as that
+ conflicts with the AMI protocol.</para>
+ </parameter>
+ <parameter name="Base64Body">
+ <para>Text bodies requiring the use of newlines have to be base64 encoded
+ in this field. Base64Body will be decoded before being sent out.
+ Base64Body takes precedence over Body.</para>
+ </parameter>
+ <parameter name="Variable">
+ <para>Message variable to set, multiple Variable: headers are
+ allowed. The header value is a comma separated list of
+ name=value pairs.</para>
+ </parameter>
+ </syntax>
+ </manager>
***/
struct msg_data {
@@ -393,6 +439,12 @@ struct ast_msg *ast_msg_alloc(void)
return msg;
}
+struct ast_msg *ast_msg_ref(struct ast_msg *msg)
+{
+ ao2_ref(msg, 1);
+ return msg;
+}
+
struct ast_msg *ast_msg_destroy(struct ast_msg *msg)
{
ao2_ref(msg, -1);
@@ -519,7 +571,7 @@ static int msg_set_var_full(struct ast_msg *msg, const char *name, const char *v
return 0;
}
-static int msg_set_var_outbound(struct ast_msg *msg, const char *name, const char *value)
+int ast_msg_set_var_outbound(struct ast_msg *msg, const char *name, const char *value)
{
return msg_set_var_full(msg, name, value, 1);
}
@@ -850,6 +902,26 @@ static int msg_func_write(struct ast_channel *chan, const char *function,
ast_msg_set_from(msg, "%s", value);
} else if (!strcasecmp(data, "body")) {
ast_msg_set_body(msg, "%s", value);
+ } else if (!strcasecmp(data, "custom_data")) {
+ int outbound = -1;
+ if (!strcasecmp(value, "mark_all_outbound")) {
+ outbound = 1;
+ } else if (!strcasecmp(value, "clear_all_outbound")) {
+ outbound = 0;
+ } else {
+ ast_log(LOG_WARNING, "'%s' is not a valid value for custom_data\n", value);
+ }
+
+ if (outbound != -1) {
+ struct msg_data *hdr_data;
+ struct ao2_iterator iter = ao2_iterator_init(msg->vars, 0);
+
+ while ((hdr_data = ao2_iterator_next(&iter))) {
+ hdr_data->send = outbound;
+ ao2_ref(hdr_data, -1);
+ }
+ ao2_iterator_destroy(&iter);
+ }
} else {
ast_log(LOG_WARNING, "'%s' is not a valid write argument.\n", data);
}
@@ -910,7 +982,7 @@ static int msg_data_func_write(struct ast_channel *chan, const char *function,
ao2_lock(msg);
- msg_set_var_outbound(msg, data, value);
+ ast_msg_set_var_outbound(msg, data, value);
ao2_unlock(msg);
ao2_ref(msg, -1);
@@ -1041,6 +1113,120 @@ exit_cleanup:
return 0;
}
+static int action_messagesend(struct mansession *s, const struct message *m)
+{
+ const char *to = ast_strdupa(astman_get_header(m, "To"));
+ const char *from = astman_get_header(m, "From");
+ const char *body = astman_get_header(m, "Body");
+ const char *base64body = astman_get_header(m, "Base64Body");
+ char base64decoded[1301] = { 0, };
+ char *tech_name = NULL;
+ struct ast_variable *vars = NULL;
+ struct ast_variable *data = NULL;
+ struct ast_msg_tech_holder *tech_holder = NULL;
+ struct ast_msg *msg;
+ int res = -1;
+
+ if (ast_strlen_zero(to)) {
+ astman_send_error(s, m, "No 'To' address specified.");
+ return -1;
+ }
+
+ if (!ast_strlen_zero(base64body)) {
+ ast_base64decode((unsigned char *) base64decoded, base64body, sizeof(base64decoded) - 1);
+ body = base64decoded;
+ }
+
+ tech_name = ast_strdupa(to);
+ tech_name = strsep(&tech_name, ":");
+ {
+ struct ast_msg_tech tmp_msg_tech = {
+ .name = tech_name,
+ };
+ struct ast_msg_tech_holder tmp_tech_holder = {
+ .tech = &tmp_msg_tech,
+ };
+
+ tech_holder = ao2_find(msg_techs, &tmp_tech_holder, OBJ_POINTER);
+ }
+
+ if (!tech_holder) {
+ astman_send_error(s, m, "Message technology not found.");
+ return -1;
+ }
+
+ if (!(msg = ast_msg_alloc())) {
+ ao2_ref(tech_holder, -1);
+ astman_send_error(s, m, "Internal failure\n");
+ return -1;
+ }
+
+ data = astman_get_variables(m);
+ for (vars = data; vars; vars = vars->next) {
+ ast_msg_set_var_outbound(msg, vars->name, vars->value);
+ }
+
+ ast_msg_set_body(msg, "%s", body);
+
+ ast_rwlock_rdlock(&tech_holder->tech_lock);
+ if (tech_holder->tech) {
+ res = tech_holder->tech->msg_send(msg, S_OR(to, ""), S_OR(from, ""));
+ }
+ ast_rwlock_unlock(&tech_holder->tech_lock);
+
+ ast_variables_destroy(vars);
+ ao2_ref(tech_holder, -1);
+ ao2_ref(msg, -1);
+
+ if (res) {
+ astman_send_error(s, m, "Message failed to send.");
+ } else {
+ astman_send_ack(s, m, "Message successfully sent");
+ }
+ return res;
+}
+
+int ast_msg_send(struct ast_msg *msg, const char *to, const char *from)
+{
+ char *tech_name = NULL;
+ struct ast_msg_tech_holder *tech_holder = NULL;
+ int res = -1;
+
+ if (ast_strlen_zero(to)) {
+ ao2_ref(msg, -1);
+ return -1;
+ }
+
+ tech_name = ast_strdupa(to);
+ tech_name = strsep(&tech_name, ":");
+ {
+ struct ast_msg_tech tmp_msg_tech = {
+ .name = tech_name,
+ };
+ struct ast_msg_tech_holder tmp_tech_holder = {
+ .tech = &tmp_msg_tech,
+ };
+
+ tech_holder = ao2_find(msg_techs, &tmp_tech_holder, OBJ_POINTER);
+ }
+
+ if (!tech_holder) {
+ ao2_ref(msg, -1);
+ return -1;
+ }
+
+ ast_rwlock_rdlock(&tech_holder->tech_lock);
+ if (tech_holder->tech) {
+ res = tech_holder->tech->msg_send(msg, S_OR(to, ""), S_OR(from, ""));
+ }
+ ast_rwlock_unlock(&tech_holder->tech_lock);
+
+ ao2_ref(tech_holder, -1);
+ ao2_ref(msg, -1);
+
+ return res;
+}
+
int ast_msg_tech_register(const struct ast_msg_tech *tech)
{
struct ast_msg_tech_holder tmp_tech_holder = {
@@ -1125,6 +1311,7 @@ int ast_msg_init(void)
res = __ast_custom_function_register(&msg_function, NULL);
res |= __ast_custom_function_register(&msg_data_function, NULL);
res |= ast_register_application2(app_msg_send, msg_send_exec, NULL, NULL, NULL);
+ res |= ast_manager_register_xml_core("MessageSend", EVENT_FLAG_MESSAGE, action_messagesend);
return res;
}
diff --git a/main/pbx.c b/main/pbx.c
index bdaea7288..3ed7df4ed 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -59,6 +59,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/musiconhold.h"
#include "asterisk/app.h"
#include "asterisk/devicestate.h"
+#include "asterisk/presencestate.h"
#include "asterisk/event.h"
#include "asterisk/hashtab.h"
#include "asterisk/module.h"
@@ -801,7 +802,7 @@ AST_APP_OPTIONS(waitexten_opts, {
struct ast_context;
struct ast_app;
-static struct ast_taskprocessor *device_state_tps;
+static struct ast_taskprocessor *extension_state_tps;
AST_THREADSTORAGE(switch_data);
AST_THREADSTORAGE(extensionstate_buf);
@@ -946,8 +947,16 @@ struct ast_hint {
* Will never be NULL while the hint is in the hints container.
*/
struct ast_exten *exten;
- struct ao2_container *callbacks; /*!< Callback container for this extension */
- int laststate; /*!< Last known state */
+ struct ao2_container *callbacks; /*!< Device state callback container for this extension */
+
+ /*! Dev state variables */
+ int laststate; /*!< Last known device state */
+
+ /*! Presence state variables */
+ int last_presence_state; /*!< Last known presence state */
+ char *last_presence_subtype; /*!< Last known presence subtype string */
+ char *last_presence_message; /*!< Last known presence message string */
+
char context_name[AST_MAX_CONTEXT];/*!< Context of destroyed hint extension. */
char exten_name[AST_MAX_EXTENSION];/*!< Extension of destroyed hint extension. */
};
@@ -1024,6 +1033,7 @@ static int remove_hintdevice(struct ast_hint *hint)
return 0;
}
+static char *parse_hint_device(struct ast_str *hint_args);
/*!
* \internal
* \brief Destroy the given hintdevice object.
@@ -1060,7 +1070,7 @@ static int add_hintdevice(struct ast_hint *hint, const char *devicelist)
return -1;
}
ast_str_set(&str, 0, "%s", devicelist);
- parse = ast_str_buffer(str);
+ parse = parse_hint_device(str);
while ((cur = strsep(&parse, "&"))) {
devicelength = strlen(cur);
@@ -1094,6 +1104,13 @@ static const struct cfextension_states {
{ AST_EXTENSION_INUSE | AST_EXTENSION_ONHOLD, "InUse&Hold" }
};
+struct presencechange {
+ char *provider;
+ int state;
+ char *subtype;
+ char *message;
+};
+
struct statechange {
AST_LIST_ENTRY(statechange) entry;
char dev[0];
@@ -1264,6 +1281,8 @@ static char *overrideswitch = NULL;
/*! \brief Subscription for device state change events */
static struct ast_event_sub *device_state_sub;
+/*! \brief Subscription for presence state change events */
+static struct ast_event_sub *presence_state_sub;
AST_MUTEX_DEFINE_STATIC(maxcalllock);
static int countcalls;
@@ -4474,6 +4493,42 @@ enum ast_extension_states ast_devstate_to_extenstate(enum ast_device_state devst
return AST_EXTENSION_NOT_INUSE;
}
+/*!
+ * \internal
+ * \brief Parse out the presence portion of the hint string
+ */
+static char *parse_hint_presence(struct ast_str *hint_args)
+{
+ char *copy = ast_strdupa(ast_str_buffer(hint_args));
+ char *tmp = "";
+
+ if ((tmp = strrchr(copy, ','))) {
+ *tmp = '\0';
+ tmp++;
+ } else {
+ return NULL;
+ }
+ ast_str_set(&hint_args, 0, "%s", tmp);
+ return ast_str_buffer(hint_args);
+}
+
+/*!
+ * \internal
+ * \brief Parse out the device portion of the hint string
+ */
+static char *parse_hint_device(struct ast_str *hint_args)
+{
+ char *copy = ast_strdupa(ast_str_buffer(hint_args));
+ char *tmp;
+
+ if ((tmp = strrchr(copy, ','))) {
+ *tmp = '\0';
+ }
+
+ ast_str_set(&hint_args, 0, "%s", copy);
+ return ast_str_buffer(hint_args);
+}
+
static int ast_extension_state3(struct ast_str *hint_app)
{
char *cur;
@@ -4481,7 +4536,7 @@ static int ast_extension_state3(struct ast_str *hint_app)
struct ast_devstate_aggregate agg;
/* One or more devices separated with a & character */
- rest = ast_str_buffer(hint_app);
+ rest = parse_hint_device(hint_app);
ast_devstate_aggregate_init(&agg);
while ((cur = strsep(&rest, "&"))) {
@@ -4539,6 +4594,209 @@ int ast_extension_state(struct ast_channel *c, const char *context, const char *
return ast_extension_state2(e); /* Check all devices in the hint */
}
+static int extension_presence_state_helper(struct ast_exten *e, char **subtype, char **message)
+{
+ struct ast_str *hint_app = ast_str_thread_get(&extensionstate_buf, 32);
+ char *presence_provider;
+ const char *app;
+
+ if (!e || !hint_app) {
+ return -1;
+ }
+
+ app = ast_get_extension_app(e);
+ if (ast_strlen_zero(app)) {
+ return -1;
+ }
+
+ ast_str_set(&hint_app, 0, "%s", app);
+ presence_provider = parse_hint_presence(hint_app);
+
+ if (ast_strlen_zero(presence_provider)) {
+ /* No presence string in the hint */
+ return 0;
+ }
+
+ return ast_presence_state(presence_provider, subtype, message);
+}
+
+int ast_hint_presence_state(struct ast_channel *c, const char *context, const char *exten, char **subtype, char **message)
+{
+ struct ast_exten *e;
+
+ if (!(e = ast_hint_extension(c, context, exten))) { /* Do we have a hint for this extension ? */
+ return -1; /* No hint, return -1 */
+ }
+
+ if (e->exten[0] == '_') {
+ /* Create this hint on-the-fly */
+ ast_add_extension(e->parent->name, 0, exten, e->priority, e->label,
+ e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr,
+ e->registrar);
+ if (!(e = ast_hint_extension(c, context, exten))) {
+ /* Improbable, but not impossible */
+ return -1;
+ }
+ }
+
+ return extension_presence_state_helper(e, subtype, message);
+}
+
+static int execute_state_callback(ast_state_cb_type cb,
+ const char *context,
+ const char *exten,
+ void *data,
+ enum ast_state_cb_update_reason reason,
+ struct ast_hint *hint)
+{
+ int res = 0;
+ struct ast_state_cb_info info = { 0, };
+
+ info.reason = reason;
+
+ /* Copy over current hint data */
+ if (hint) {
+ ao2_lock(hint);
+ info.exten_state = hint->laststate;
+ info.presence_state = hint->last_presence_state;
+ if (!(ast_strlen_zero(hint->last_presence_subtype))) {
+ info.presence_subtype = ast_strdupa(hint->last_presence_subtype);
+ } else {
+ info.presence_subtype = "";
+ }
+ if (!(ast_strlen_zero(hint->last_presence_message))) {
+ info.presence_message = ast_strdupa(hint->last_presence_message);
+ } else {
+ info.presence_message = "";
+ }
+ ao2_unlock(hint);
+ } else {
+ info.exten_state = AST_EXTENSION_REMOVED;
+ }
+
+ /* NOTE: The casts will not be needed for v10 and later */
+ res = cb((char *) context, (char *) exten, &info, data);
+
+ return res;
+}
+
+static int handle_presencechange(void *datap)
+{
+ struct ast_hint *hint;
+ struct ast_str *hint_app = NULL;
+ struct presencechange *pc = datap;
+ struct ao2_iterator i;
+ struct ao2_iterator cb_iter;
+ char context_name[AST_MAX_CONTEXT];
+ char exten_name[AST_MAX_EXTENSION];
+ int res = -1;
+
+ hint_app = ast_str_create(1024);
+ if (!hint_app) {
+ goto presencechange_cleanup;
+ }
+
+ ast_mutex_lock(&context_merge_lock);/* Hold off ast_merge_contexts_and_delete */
+ i = ao2_iterator_init(hints, 0);
+ for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) {
+ struct ast_state_cb *state_cb;
+ const char *app;
+ char *parse;
+
+ ao2_lock(hint);
+
+ if (!hint->exten) {
+ /* The extension has already been destroyed */
+ ao2_unlock(hint);
+ continue;
+ }
+
+ /* Does this hint monitor the device that changed state? */
+ app = ast_get_extension_app(hint->exten);
+ if (ast_strlen_zero(app)) {
+ /* The hint does not monitor presence at all. */
+ ao2_unlock(hint);
+ continue;
+ }
+
+ ast_str_set(&hint_app, 0, "%s", app);
+ parse = parse_hint_presence(hint_app);
+ if (ast_strlen_zero(parse)) {
+ ao2_unlock(hint);
+ continue;
+ }
+ if (strcasecmp(parse, pc->provider)) {
+ /* The hint does not monitor the presence provider. */
+ ao2_unlock(hint);
+ continue;
+ }
+
+ /*
+ * Save off strings in case the hint extension gets destroyed
+ * while we are notifying the watchers.
+ */
+ ast_copy_string(context_name,
+ ast_get_context_name(ast_get_extension_context(hint->exten)),
+ sizeof(context_name));
+ ast_copy_string(exten_name, ast_get_extension_name(hint->exten),
+ sizeof(exten_name));
+ ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(hint->exten));
+
+ /* Check to see if update is necessary */
+ if ((hint->last_presence_state == pc->state) &&
+ ((hint->last_presence_subtype && pc->subtype && !strcmp(hint->last_presence_subtype, pc->subtype)) || (!hint->last_presence_subtype && !pc->subtype)) &&
+ ((hint->last_presence_message && pc->message && !strcmp(hint->last_presence_message, pc->message)) || (!hint->last_presence_message && !pc->message))) {
+
+ /* this update is the same as the last, do nothing */
+ ao2_unlock(hint);
+ continue;
+ }
+
+ /* update new values */
+ ast_free(hint->last_presence_subtype);
+ ast_free(hint->last_presence_message);
+ hint->last_presence_state = pc->state;
+ hint->last_presence_subtype = pc->subtype ? ast_strdup(pc->subtype) : NULL;
+ hint->last_presence_message = pc->message ? ast_strdup(pc->message) : NULL;
+
+ ao2_unlock(hint);
+
+ /* For general callbacks */
+ cb_iter = ao2_iterator_init(statecbs, 0);
+ for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+ execute_state_callback(state_cb->change_cb,
+ context_name,
+ exten_name,
+ state_cb->data,
+ AST_HINT_UPDATE_PRESENCE,
+ hint);
+ }
+ ao2_iterator_destroy(&cb_iter);
+
+ /* For extension callbacks */
+ cb_iter = ao2_iterator_init(hint->callbacks, 0);
+ for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+ execute_state_callback(state_cb->change_cb,
+ context_name,
+ exten_name,
+ state_cb->data,
+ AST_HINT_UPDATE_PRESENCE,
+ hint);
+ }
+ ao2_iterator_destroy(&cb_iter);
+ }
+ ao2_iterator_destroy(&i);
+ ast_mutex_unlock(&context_merge_lock);
+
+ res = 0;
+
+presencechange_cleanup:
+ ast_free(hint_app);
+ ao2_ref(pc, -1);
+
+ return res;
+}
+
static int handle_statechange(void *datap)
{
struct ast_hint *hint;
@@ -4626,14 +4884,24 @@ static int handle_statechange(void *datap)
/* For general callbacks */
cb_iter = ao2_iterator_init(statecbs, 0);
for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
- state_cb->change_cb(context_name, exten_name, state, state_cb->data);
+ execute_state_callback(state_cb->change_cb,
+ context_name,
+ exten_name,
+ state_cb->data,
+ AST_HINT_UPDATE_DEVICE,
+ hint);
}
ao2_iterator_destroy(&cb_iter);
/* For extension callbacks */
cb_iter = ao2_iterator_init(hint->callbacks, 0);
for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
- state_cb->change_cb(context_name, exten_name, state, state_cb->data);
+ execute_state_callback(state_cb->change_cb,
+ context_name,
+ exten_name,
+ state_cb->data,
+ AST_HINT_UPDATE_DEVICE,
+ hint);
}
ao2_iterator_destroy(&cb_iter);
}
@@ -4805,7 +5073,6 @@ int ast_extension_state_del(int id, ast_state_cb_type change_cb)
return ret;
}
-
static int hint_id_cmp(void *obj, void *arg, int flags)
{
const struct ast_state_cb *cb = obj;
@@ -4840,14 +5107,21 @@ static void destroy_hint(void *obj)
context_name = hint->context_name;
exten_name = hint->exten_name;
}
+ hint->laststate = AST_EXTENSION_DEACTIVATED;
while ((state_cb = ao2_callback(hint->callbacks, OBJ_UNLINK, NULL, NULL))) {
/* Notify with -1 and remove all callbacks */
- state_cb->change_cb(context_name, exten_name, AST_EXTENSION_DEACTIVATED,
- state_cb->data);
+ execute_state_callback(state_cb->change_cb,
+ context_name,
+ exten_name,
+ state_cb->data,
+ AST_HINT_UPDATE_DEVICE,
+ hint);
ao2_ref(state_cb, -1);
}
ao2_ref(hint->callbacks, -1);
}
+ ast_free(hint->last_presence_subtype);
+ ast_free(hint->last_presence_message);
}
/*! \brief Remove hint from extension */
@@ -4890,6 +5164,9 @@ static int ast_add_hint(struct ast_exten *e)
{
struct ast_hint *hint_new;
struct ast_hint *hint_found;
+ char *message = NULL;
+ char *subtype = NULL;
+ int presence_state;
if (!e) {
return -1;
@@ -4913,6 +5190,12 @@ static int ast_add_hint(struct ast_exten *e)
}
hint_new->exten = e;
hint_new->laststate = ast_extension_state2(e);
+ if ((presence_state = extension_presence_state_helper(e, &subtype, &message)) > 0) {
+ hint_new->last_presence_state = presence_state;
+ hint_new->last_presence_subtype = subtype;
+ hint_new->last_presence_message = message;
+ message = subtype = NULL;
+ }
/* Prevent multiple add hints from adding the same hint at the same time. */
ao2_lock(hints);
@@ -7432,6 +7715,10 @@ struct store_hint {
char *exten;
AST_LIST_HEAD_NOLOCK(, ast_state_cb) callbacks;
int laststate;
+ int last_presence_state;
+ char *last_presence_subtype;
+ char *last_presence_message;
+
AST_LIST_ENTRY(store_hint) list;
char data[1];
};
@@ -7633,6 +7920,13 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
strcpy(saved_hint->data, hint->exten->parent->name);
saved_hint->exten = saved_hint->data + strlen(saved_hint->context) + 1;
strcpy(saved_hint->exten, hint->exten->exten);
+ if (hint->last_presence_subtype) {
+ saved_hint->last_presence_subtype = ast_strdup(hint->last_presence_subtype);
+ }
+ if (hint->last_presence_message) {
+ saved_hint->last_presence_message = ast_strdup(hint->last_presence_message);
+ }
+ saved_hint->last_presence_state = hint->last_presence_state;
ao2_unlock(hint);
AST_LIST_INSERT_HEAD(&hints_stored, saved_hint, list);
}
@@ -7686,8 +7980,15 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
ao2_ref(thiscb, -1);
}
hint->laststate = saved_hint->laststate;
+ hint->last_presence_state = saved_hint->last_presence_state;
+ hint->last_presence_subtype = saved_hint->last_presence_subtype;
+ hint->last_presence_message = saved_hint->last_presence_message;
ao2_unlock(hint);
ao2_ref(hint, -1);
+ /*
+ * The free of saved_hint->last_presence_subtype and
+ * saved_hint->last_presence_message is not necessary here.
+ */
ast_free(saved_hint);
}
}
@@ -7702,11 +8003,17 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
while ((saved_hint = AST_LIST_REMOVE_HEAD(&hints_removed, list))) {
/* this hint has been removed, notify the watchers */
while ((thiscb = AST_LIST_REMOVE_HEAD(&saved_hint->callbacks, entry))) {
- thiscb->change_cb(saved_hint->context, saved_hint->exten,
- AST_EXTENSION_REMOVED, thiscb->data);
+ execute_state_callback(thiscb->change_cb,
+ saved_hint->context,
+ saved_hint->exten,
+ thiscb->data,
+ AST_HINT_UPDATE_DEVICE,
+ NULL);
/* Ref that we added when putting into saved_hint->callbacks */
ao2_ref(thiscb, -1);
}
+ ast_free(saved_hint->last_presence_subtype);
+ ast_free(saved_hint->last_presence_message);
ast_free(saved_hint);
}
@@ -10469,6 +10776,51 @@ static int pbx_builtin_sayphonetic(struct ast_channel *chan, const char *data)
return res;
}
+static void presencechange_destroy(void *data)
+{
+ struct presencechange *pc = data;
+ ast_free(pc->provider);
+ ast_free(pc->subtype);
+ ast_free(pc->message);
+}
+
+static void presence_state_cb(const struct ast_event *event, void *unused)
+{
+ struct presencechange *pc;
+ const char *tmp;
+
+ if (!(pc = ao2_alloc(sizeof(*pc), presencechange_destroy))) {
+ return;
+ }
+
+ tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER);
+ if (ast_strlen_zero(tmp)) {
+ ast_log(LOG_ERROR, "Received invalid event that had no presence provider IE\n");
+ ao2_ref(pc, -1);
+ return;
+ }
+ pc->provider = ast_strdup(tmp);
+
+ pc->state = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
+ if (pc->state < 0) {
+ ao2_ref(pc, -1);
+ return;
+ }
+
+ if ((tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE))) {
+ pc->subtype = ast_strdup(tmp);
+ }
+
+ if ((tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE))) {
+ pc->message = ast_strdup(tmp);
+ }
+
+ /* The task processor thread is taking our reference to the presencechange object. */
+ if (ast_taskprocessor_push(extension_state_tps, handle_presencechange, pc) < 0) {
+ ao2_ref(pc, -1);
+ }
+}
+
static void device_state_cb(const struct ast_event *event, void *unused)
{
const char *device;
@@ -10483,7 +10835,7 @@ static void device_state_cb(const struct ast_event *event, void *unused)
if (!(sc = ast_calloc(1, sizeof(*sc) + strlen(device) + 1)))
return;
strcpy(sc->dev, device);
- if (ast_taskprocessor_push(device_state_tps, handle_statechange, sc) < 0) {
+ if (ast_taskprocessor_push(extension_state_tps, handle_statechange, sc) < 0) {
ast_free(sc);
}
}
@@ -10515,6 +10867,9 @@ static int hints_data_provider_get(const struct ast_data_search *search,
ast_data_add_str(data_hint, "context", ast_get_context_name(ast_get_extension_context(hint->exten)));
ast_data_add_str(data_hint, "application", ast_get_extension_app(hint->exten));
ast_data_add_str(data_hint, "state", ast_extension_state2str(hint->laststate));
+ ast_data_add_str(data_hint, "presence_state", ast_presence_state2str(hint->last_presence_state));
+ ast_data_add_str(data_hint, "presence_subtype", S_OR(hint->last_presence_subtype, ""));
+ ast_data_add_str(data_hint, "presence_subtype", S_OR(hint->last_presence_message, ""));
ast_data_add_int(data_hint, "watchers", watchers);
if (!ast_data_search_match(search, data_hint)) {
@@ -10541,7 +10896,7 @@ int load_pbx(void)
/* Initialize the PBX */
ast_verb(1, "Asterisk PBX Core Initializing\n");
- if (!(device_state_tps = ast_taskprocessor_get("pbx-core", 0))) {
+ if (!(extension_state_tps = ast_taskprocessor_get("pbx-core", 0))) {
ast_log(LOG_WARNING, "failed to create pbx-core taskprocessor\n");
}
@@ -10568,6 +10923,11 @@ int load_pbx(void)
return -1;
}
+ if (!(presence_state_sub = ast_event_subscribe(AST_EVENT_PRESENCE_STATE, presence_state_cb, "pbx Presence State Change", NULL,
+ AST_EVENT_IE_END))) {
+ return -1;
+ }
+
return 0;
}
diff --git a/main/presencestate.c b/main/presencestate.c
new file mode 100644
index 000000000..df64dab24
--- /dev/null
+++ b/main/presencestate.c
@@ -0,0 +1,317 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2011-2012, Digium, Inc.
+ *
+ * David Vossel <dvossel@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Presence state management
+ */
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+#include "asterisk/utils.h"
+#include "asterisk/lock.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 AST_PRESENCE_INVALID;
+}
+
+static enum ast_presence_state presence_state_cached(const char *presence_provider, char **subtype, char **message)
+{
+ enum ast_presence_state res = AST_PRESENCE_INVALID;
+ 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 = AST_PRESENCE_INVALID;
+
+ if (check_cache) {
+ res = presence_state_cached(presence_provider, subtype, message);
+ if (res != AST_PRESENCE_INVALID) {
+ 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);
+}
+
+enum ast_presence_state ast_presence_state_nocache(const char *presence_provider, char **subtype, char **message)
+{
+ return ast_presence_state_helper(presence_provider, subtype, message, 0);
+}
+
+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 presence_state_event(const char *provider,
+ enum ast_presence_state state,
+ const char *subtype,
+ const char *message)
+{
+ struct ast_event *event;
+
+ if (!(event = ast_event_new(AST_EVENT_PRESENCE_STATE,
+ 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);
+}
+
+static void do_presence_state_change(const char *provider)
+{
+ char *subtype = NULL;
+ char *message = NULL;
+ enum ast_presence_state state;
+
+ state = ast_presence_state_helper(provider, &subtype, &message, 0);
+
+ if (state < 0) {
+ return;
+ }
+
+ presence_state_event(provider, state, subtype, message);
+ ast_free(subtype);
+ ast_free(message);
+}
+
+int ast_presence_state_changed_literal(enum ast_presence_state state,
+ const char *subtype,
+ const char *message,
+ const char *presence_provider)
+{
+ struct state_change *change;
+
+ if (state != AST_PRESENCE_NOT_SET) {
+ presence_state_event(presence_provider, state, subtype, message);
+ } else 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;
+}
+
+int ast_presence_state_changed(enum ast_presence_state state,
+ const char *subtype,
+ const char *message,
+ const char *fmt, ...)
+{
+ char buf[AST_MAX_EXTENSION];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ return ast_presence_state_changed_literal(state, subtype, message, buf);
+}
+
+/*! \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;
+}
+
diff --git a/tests/test_config.c b/tests/test_config.c
index c8f7a077b..57d2bbded 100644
--- a/tests/test_config.c
+++ b/tests/test_config.c
@@ -1,9 +1,9 @@
/*
* Asterisk -- An open source telephony toolkit.
*
- * Copyright (C) 2011, Digium, Inc.
+ * Copyright (C) 2010, Digium, Inc.
*
- * Terry Wilson <twilson@digium.com>
+ * Mark Michelson <mmichelson@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
@@ -18,9 +18,9 @@
/*!
* \file
- * \brief Config-related tests
+ * \brief Configuration unit tests
*
- * \author Terry Wilson <twilson@digium.com>
+ * \author Mark Michelson <mmichelson@digium.com>
*
*/
@@ -31,13 +31,14 @@
#include "asterisk.h"
-ASTERISK_FILE_VERSION(__FILE__, "")
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
#include <math.h> /* HUGE_VAL */
-#include "asterisk/test.h"
-#include "asterisk/module.h"
#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "asterisk/paths.h"
#include "asterisk/config_options.h"
#include "asterisk/netsock2.h"
#include "asterisk/acl.h"
@@ -45,6 +46,342 @@ ASTERISK_FILE_VERSION(__FILE__, "")
#include "asterisk/utils.h"
#include "asterisk/logger.h"
+#define CONFIG_FILE "test_config.conf"
+
+/*
+ * This builds the folowing config:
+ * [Capitals]
+ * Germany = Berlin
+ * China = Beijing
+ * Canada = Ottawa
+ *
+ * [Protagonists]
+ * 1984 = Winston Smith
+ * Green Eggs And Ham = Sam I Am
+ * The Kalevala = Vainamoinen
+ *
+ * This config is used for all tests below.
+ */
+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 },
+ }
+ },
+};
+
+/*!
+ * \brief Build ast_config struct from above definitions
+ *
+ * \retval NULL Failed to build the config
+ * \retval non-NULL An ast_config struct populated with data
+ */
+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;
+}
+
+/*!
+ * \brief Tests that the contents of an ast_config is what is expected
+ *
+ * \param cfg Config to test
+ * \retval -1 Failed to pass a test
+ * \retval 0 Config passes checks
+ */
+static int test_config_validity(struct ast_config *cfg)
+{
+ int i;
+ const char *cat_iter = NULL;
+ /* 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(cfg, 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);
+ return -1;
+ }
+ for (j = 0; j < ARRAY_LEN(categories[i].vars); ++j) {
+ var = var ? var->next : ast_variable_browse(cfg, 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);
+ return -1;
+ }
+ 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);
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+AST_TEST_DEFINE(copy_config)
+{
+ enum ast_test_result_state res = AST_TEST_FAIL;
+ struct ast_config *cfg = NULL;
+ struct ast_config *copy = NULL;
+
+ 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;
+ }
+
+ if (test_config_validity(copy) != 0) {
+ goto out;
+ }
+
+ res = AST_TEST_PASS;
+
+out:
+ ast_config_destroy(cfg);
+ ast_config_destroy(copy);
+ return res;
+}
+
+/*!
+ * \brief Write the config file to disk
+ *
+ * This is necessary for testing config hooks since
+ * they are only triggered when a config is read from
+ * its intended storage medium
+ */
+static int write_config_file(void)
+{
+ int i;
+ FILE *config_file;
+ char filename[PATH_MAX];
+
+ snprintf(filename, sizeof(filename), "%s/%s",
+ ast_config_AST_CONFIG_DIR, CONFIG_FILE);
+ config_file = fopen(filename, "w");
+
+ if (!config_file) {
+ return -1;
+ }
+
+ for (i = 0; i < ARRAY_LEN(categories); ++i) {
+ int j;
+ fprintf(config_file, "[%s]\n", categories[i].category);
+ for (j = 0; j < ARRAY_LEN(categories[i].vars); ++j) {
+ fprintf(config_file, "%s = %s\n",
+ categories[i].vars[j].name,
+ categories[i].vars[j].val);
+ }
+ }
+
+ fclose(config_file);
+ return 0;
+}
+
+/*!
+ * \brief Delete config file created by write_config_file
+ */
+static void delete_config_file(void)
+{
+ char filename[PATH_MAX];
+ snprintf(filename, sizeof(filename), "%s/%s",
+ ast_config_AST_CONFIG_DIR, CONFIG_FILE);
+ unlink(filename);
+}
+
+/*
+ * Boolean to indicate if the config hook has run
+ */
+static int hook_run;
+
+/*
+ * Boolean to indicate if, when the hook runs, the
+ * data passed to it is what is expected
+ */
+static int hook_config_sane;
+
+static int hook_cb(struct ast_config *cfg)
+{
+ hook_run = 1;
+ if (test_config_validity(cfg) == 0) {
+ hook_config_sane = 1;
+ }
+ ast_config_destroy(cfg);
+ return 0;
+}
+
+AST_TEST_DEFINE(config_hook)
+{
+ enum ast_test_result_state res = AST_TEST_FAIL;
+ enum config_hook_flags hook_flags = { 0, };
+ struct ast_flags config_flags = { CONFIG_FLAG_FILEUNCHANGED };
+ struct ast_config *cfg;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "config_hook";
+ info->category = "/main/config/";
+ info->summary = "Test config hooks";
+ info->description =
+ "Ensure that config hooks are called at approriate times,"
+ "not called at inappropriate times, and that all information"
+ "that should be present is present.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ write_config_file();
+
+ /*
+ * Register a config hook to run when CONFIG_FILE is loaded by this module
+ */
+ ast_config_hook_register("test_hook",
+ CONFIG_FILE,
+ AST_MODULE,
+ hook_flags,
+ hook_cb);
+
+ /*
+ * Try loading the config file. This should result in the hook
+ * being called
+ */
+ cfg = ast_config_load(CONFIG_FILE, config_flags);
+ ast_config_destroy(cfg);
+ if (!hook_run || !hook_config_sane) {
+ ast_test_status_update(test, "Config hook either did not run or was given bad data!\n");
+ goto out;
+ }
+
+ /*
+ * Now try loading the wrong config file but from the right module.
+ * Hook should not run
+ */
+ hook_run = 0;
+ cfg = ast_config_load("asterisk.conf", config_flags);
+ ast_config_destroy(cfg);
+ if (hook_run) {
+ ast_test_status_update(test, "Config hook ran even though an incorrect file was specified.\n");
+ goto out;
+ }
+
+ /*
+ * Now try loading the correct config file but from the wrong module.
+ * Hook should not run
+ */
+ hook_run = 0;
+ cfg = ast_config_load2(CONFIG_FILE, "fake_module.so", config_flags);
+ ast_config_destroy(cfg);
+ if (hook_run) {
+ ast_test_status_update(test, "Config hook ran even though an incorrect module was specified.\n");
+ goto out;
+ }
+
+ /*
+ * Now try loading the file correctly, but without any changes to the file.
+ * Hook should not run
+ */
+ hook_run = 0;
+ cfg = ast_config_load(CONFIG_FILE, config_flags);
+ /* Only destroy this cfg conditionally. Otherwise a crash happens. */
+ if (cfg != CONFIG_STATUS_FILEUNCHANGED) {
+ ast_config_destroy(cfg);
+ }
+ if (hook_run) {
+ ast_test_status_update(test, "Config hook ran even though file contents had not changed\n");
+ goto out;
+ }
+
+ res = AST_TEST_PASS;
+
+out:
+ delete_config_file();
+ return res;
+}
+
enum {
EXPECT_FAIL = 0,
EXPECT_SUCCEED,
@@ -278,7 +615,6 @@ AST_TEST_DEFINE(ast_parse_arg_test)
return ret;
}
-
struct test_item {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(name);
@@ -575,6 +911,8 @@ AST_TEST_DEFINE(config_options_test)
static int unload_module(void)
{
+ AST_TEST_UNREGISTER(copy_config);
+ AST_TEST_UNREGISTER(config_hook);
AST_TEST_UNREGISTER(ast_parse_arg_test);
AST_TEST_UNREGISTER(config_options_test);
return 0;
@@ -582,9 +920,12 @@ static int unload_module(void)
static int load_module(void)
{
+ AST_TEST_REGISTER(copy_config);
+ AST_TEST_REGISTER(config_hook);
AST_TEST_REGISTER(ast_parse_arg_test);
AST_TEST_REGISTER(config_options_test);
return AST_MODULE_LOAD_SUCCESS;
}
-AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "config test module");
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Config test module");
+
diff --git a/tests/test_voicemail_api.c b/tests/test_voicemail_api.c
new file mode 100644
index 000000000..cc83625bf
--- /dev/null
+++ b/tests/test_voicemail_api.c
@@ -0,0 +1,1443 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Matt Jordan
+ *
+ * Matt Jordan <mjordan@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 Skeleton Test
+ *
+ * \author\verbatim Matt Jordan <mjordan@digium.com> \endverbatim
+ *
+ * Tests for the publicly exposed Voicemail API
+ * \ingroup tests
+ */
+
+/*** MODULEINFO
+ <depend>TEST_FRAMEWORK</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/stat.h>
+
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "asterisk/paths.h"
+#include "asterisk/channel.h"
+#include "asterisk/app.h"
+#include "asterisk/app_voicemail.h"
+
+/*! \internal \brief Permissions to set on the voicemail directories we create
+ * - taken from app_voicemail */
+#define VOICEMAIL_DIR_MODE 0777
+
+/*! \internal \brief Permissions to set on the voicemail files we create
+ * - taken from app_voicemail */
+#define VOICEMAIL_FILE_MODE 0666
+
+/*! \internal \brief The number of mock snapshot objects we use for tests */
+#define TOTAL_SNAPSHOTS 4
+
+/*! \internal \brief Create and populate the mock message objects and create the
+ * envelope files on the file system */
+#define VM_API_TEST_SETUP do { \
+ if (test_vm_api_test_setup()) { \
+ VM_API_TEST_CLEANUP; \
+ ast_test_status_update(test, "Failed to set up necessary mock objects for voicemail API test\n"); \
+ return AST_TEST_FAIL; \
+ } else { \
+ int i = 0; \
+ for (; i < TOTAL_SNAPSHOTS; i++) { \
+ ast_test_status_update(test, "Created message in %s/%s with ID %s\n", \
+ test_snapshots[i]->exten, test_snapshots[i]->folder_name, test_snapshots[i]->msg_id); \
+ } \
+} } while (0)
+
+/*! \internal \brief Safely cleanup after a test run. This should be called both when a
+ * test fails and when it passes */
+#define VM_API_TEST_CLEANUP test_vm_api_test_teardown()
+
+/*! \internal \brief Safely cleanup a snapshot and a test run. Note that it assumes
+ * that the mailbox snapshot object is test_mbox_snapshot */
+#define VM_API_SNAPSHOT_TEST_CLEANUP \
+ if (test_mbox_snapshot) { \
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); \
+ } \
+ VM_API_TEST_CLEANUP; \
+
+/*! \internal \brief Verify the expected result from two string values obtained
+ * from a mailbox snapshot. Note that it assumes the mailbox snapshot
+ * object is test_mbox_snapshot
+ */
+#define VM_API_STRING_FIELD_VERIFY(expected, actual) do { \
+ if (strncmp((expected), (actual), sizeof((expected)))) { \
+ ast_test_status_update(test, "Test failed for parameter %s: Expected [%s], Actual [%s]\n", #actual, expected, actual); \
+ VM_API_SNAPSHOT_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Verify the expected result from two integer values. Note
+ * that it assumes the mailbox snapshot object is test_mbox_snapshot */
+#define VM_API_INT_VERIFY(expected, actual) do { \
+ if ((expected) != (actual)) { \
+ ast_test_status_update(test, "Test failed for parameter %s: Expected [%d], Actual [%d]\n", #actual, expected, actual); \
+ VM_API_SNAPSHOT_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Verify that a mailbox snapshot contains the expected message
+ * snapshot, in the correct position, with the expected values. Note
+ * that it assumes the mailbox snapshot object is test_mbox_snapshot
+ */
+#define VM_API_SNAPSHOT_MSG_VERIFY(expected, actual, expected_folder, expected_index) do { \
+ struct ast_vm_msg_snapshot *msg; \
+ int found = 0; \
+ int counter = 0; \
+ AST_LIST_TRAVERSE(&((actual)->snapshots[get_folder_by_name(expected_folder)]), msg, msg) { \
+ if (!(strcmp(msg->msg_id, (expected)->msg_id))) { \
+ ast_test_status_update(test, "Found message %s in snapshot\n", msg->msg_id); \
+ found = 1; \
+ if ((expected_index) != counter) { \
+ ast_test_status_update(test, "Expected message %s at index %d; Actual [%d]\n", \
+ (expected)->msg_id, (expected_index), counter); \
+ VM_API_SNAPSHOT_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } \
+ VM_API_STRING_FIELD_VERIFY((expected)->callerid, msg->callerid); \
+ VM_API_STRING_FIELD_VERIFY((expected)->callerchan, msg->callerchan); \
+ VM_API_STRING_FIELD_VERIFY((expected)->exten, msg->exten); \
+ VM_API_STRING_FIELD_VERIFY((expected)->origdate, msg->origdate); \
+ VM_API_STRING_FIELD_VERIFY((expected)->origtime, msg->origtime); \
+ VM_API_STRING_FIELD_VERIFY((expected)->duration, msg->duration); \
+ VM_API_STRING_FIELD_VERIFY((expected)->folder_name, msg->folder_name); \
+ VM_API_STRING_FIELD_VERIFY((expected)->flag, msg->flag); \
+ VM_API_INT_VERIFY((expected)->msg_number, msg->msg_number); \
+ break; \
+ } \
+ ++counter; \
+ } \
+ if (!found) { \
+ ast_test_status_update(test, "Test failed for message snapshot %s: not found in mailbox snapshot\n", (expected)->msg_id); \
+ VM_API_SNAPSHOT_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+} } while (0)
+
+
+/*! \internal \brief Create a message snapshot, failing the test if the snapshot could not be created.
+ * This requires having a snapshot named test_mbox_snapshot.
+ */
+#define VM_API_SNAPSHOT_CREATE(mailbox, context, folder, desc, sort, old_and_inbox) do { \
+ if (!(test_mbox_snapshot = ast_vm_mailbox_snapshot_create( \
+ (mailbox), (context), (folder), (desc), (sort), (old_and_inbox)))) { \
+ ast_test_status_update(test, "Failed to create voicemail mailbox snapshot\n"); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Create a message snapshot, failing the test if the snapshot could be created.
+ * This is used to test off nominal conditions.
+ * This requires having a snapshot named test_mbox_snapshot.
+ */
+#define VM_API_SNAPSHOT_OFF_NOMINAL_TEST(mailbox, context, folder, desc, sort, old_and_inbox) do { \
+ if ((test_mbox_snapshot = ast_vm_mailbox_snapshot_create( \
+ (mailbox), (context), (folder), (desc), (sort), (old_and_inbox)))) { \
+ ast_test_status_update(test, "Created mailbox snapshot when none was expected\n"); \
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Move a voicemail message, failing the test if the message could not be moved */
+#define VM_API_MOVE_MESSAGE(mailbox, context, number_of_messages, source, message_numbers_in, dest) do { \
+ if (ast_vm_msg_move((mailbox), (context), (number_of_messages), (source), (message_numbers_in), (dest))) { \
+ ast_test_status_update(test, "Failed to move message %s@%s from %s to %s\n", \
+ (mailbox) ? (mailbox): "(NULL)", (context) ? (context) : "(NULL)", (source) ? (source) : "(NULL)", (dest) ? (dest) : "(NULL)"); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+ /*! \internal \brief Attempt to move a voicemail message, failing the test if the message could be moved */
+#define VM_API_MOVE_MESSAGE_OFF_NOMINAL(mailbox, context, number_of_messages, source, message_numbers_in, dest) do { \
+ if (!ast_vm_msg_move((mailbox), (context), (number_of_messages), (source), (message_numbers_in), (dest))) { \
+ ast_test_status_update(test, "Succeeded to move message %s@%s from %s to %s when we really shouldn't\n", \
+ (mailbox) ? (mailbox): "(NULL)", (context) ? (context) : "(NULL)", (source) ? (source) : "(NULL)", (dest) ? (dest) : "(NULL)"); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Remove a message, failing the test if the method failed or if the message is still present. */
+#define VM_API_REMOVE_MESSAGE(mailbox, context, number_of_messages, folder, message_numbers_in) do { \
+ if (ast_vm_msg_remove((mailbox), (context), (number_of_messages), (folder), (message_numbers_in))) { \
+ ast_test_status_update(test, "Failed to remove message from mailbox %s@%s, folder %s", \
+ (mailbox) ? (mailbox): "(NULL)", (context) ? (context) : "(NULL)", (folder) ? (folder) : "(NULL)"); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } \
+ VM_API_SNAPSHOT_CREATE((mailbox), (context), (folder), 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); \
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 0); \
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); \
+} while (0)
+
+/*! \internal \brief Remove a message, failing the test if the method succeeds */
+#define VM_API_REMOVE_MESSAGE_OFF_NOMINAL(mailbox, context, number_of_messages, folder, message_numbers_in) do { \
+ if (!ast_vm_msg_remove((mailbox), (context), (number_of_messages), (folder), (message_numbers_in))) { \
+ ast_test_status_update(test, "Succeeded in removing message from mailbox %s@%s, folder %s, when expected result was failure\n", \
+ (mailbox) ? (mailbox): "(NULL)", (context) ? (context) : "(NULL)", (folder) ? (folder) : "(NULL)"); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Forward a message, failing the test if the message could not be forwarded */
+# define VM_API_FORWARD_MESSAGE(from_mailbox, from_context, from_folder, to_mailbox, to_context, to_folder, number_of_messages, message_numbers_in, delete_old) do { \
+ if (ast_vm_msg_forward((from_mailbox), (from_context), (from_folder), (to_mailbox), (to_context), (to_folder), (number_of_messages), (message_numbers_in), (delete_old))) { \
+ ast_test_status_update(test, "Failed to forward message from %s@%s [%s] to %s@%s [%s]\n", \
+ (from_mailbox) ? (from_mailbox) : "(NULL)", (from_context) ? (from_context) : "(NULL)", (from_folder) ? (from_folder) : "(NULL)", \
+ (to_mailbox) ? (to_mailbox) : "(NULL)", (to_context) ? (to_context) : "(NULL)", (to_folder) ? (to_folder) : "(NULL)"); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+ /*! \internal \brief Forward a message, failing the test if the message was successfully forwarded */
+#define VM_API_FORWARD_MESSAGE_OFF_NOMINAL(from_mailbox, from_context, from_folder, to_mailbox, to_context, to_folder, number_of_messages, message_numbers_in, delete_old) do { \
+ if (!ast_vm_msg_forward((from_mailbox), (from_context), (from_folder), (to_mailbox), (to_context), (to_folder), (number_of_messages), (message_numbers_in), (delete_old))) { \
+ ast_test_status_update(test, "Succeeded in forwarding message from %s@%s [%s] to %s@%s [%s] when expected result was fail\n", \
+ (from_mailbox) ? (from_mailbox) : "(NULL)", (from_context) ? (from_context) : "(NULL)", (from_folder) ? (from_folder) : "(NULL)", \
+ (to_mailbox) ? (to_mailbox) : "(NULL)", (to_context) ? (to_context) : "(NULL)", (to_folder) ? (to_folder) : "(NULL)"); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Playback a message on a channel or callback function. Note that the channel name must be test_channel.
+ * Fail the test if the message could not be played. */
+#define VM_API_PLAYBACK_MESSAGE(channel, mailbox, context, folder, message, callback_fn) do { \
+ if (ast_vm_msg_play((channel), (mailbox), (context), (folder), (message), (callback_fn))) { \
+ ast_test_status_update(test, "Failed nominal playback message test\n"); \
+ if (test_channel) { \
+ ast_hangup(test_channel); \
+ } \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Playback a message on a channel or callback function. Note that the channel name must be test_channel.
+ * Fail the test if the message is successfully played */
+#define VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(channel, mailbox, context, folder, message, callback_fn) do { \
+ if (!ast_vm_msg_play((channel), (mailbox), (context), (folder), (message), (callback_fn))) { \
+ ast_test_status_update(test, "Succeeded in playing back of message when expected result was to fail\n"); \
+ if (test_channel) { \
+ ast_hangup(test_channel); \
+ } \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+
+/*! \internal \brief Possible names of folders. Taken from app_voicemail */
+static const char * const mailbox_folders[] = {
+ "INBOX",
+ "Old",
+ "Work",
+ "Family",
+ "Friends",
+ "Cust1",
+ "Cust2",
+ "Cust3",
+ "Cust4",
+ "Cust5",
+ "Deleted",
+ "Urgent",
+};
+
+/*! \internal \brief Message snapshots representing the messages that are used by the various tests */
+static struct ast_vm_msg_snapshot *test_snapshots[TOTAL_SNAPSHOTS];
+
+/*! \internal \brief Tracks whether or not we entered into the message playback callback function */
+static int global_entered_playback_callback = 0;
+
+/*! \internal \brief Get a folder index by its name */
+static int get_folder_by_name(const char *folder)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_LEN(mailbox_folders); i++) {
+ if (strcasecmp(folder, mailbox_folders[i]) == 0) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/*! \internal \brief Get a mock snapshot object
+ * \param context The mailbox context
+ * \param exten The mailbox extension
+ * \param callerid The caller ID of the person leaving the message
+ * \returns an ast_vm_msg_snapshot object on success
+ * \returns NULL on error
+ */
+static struct ast_vm_msg_snapshot *test_vm_api_create_mock_snapshot(const char *context, const char *exten, const char *callerid)
+{
+ char msg_id_hash[AST_MAX_CONTEXT + AST_MAX_EXTENSION + sizeof(callerid) + 1];
+ char msg_id_buf[256];
+ struct ast_vm_msg_snapshot *snapshot;
+
+ snprintf(msg_id_hash, sizeof(msg_id_hash), "%s%s%s", exten, context, callerid);
+ snprintf(msg_id_buf, sizeof(msg_id_buf), "%ld-%d", (long)time(NULL), ast_str_hash(msg_id_hash));
+
+ if ((snapshot = ast_calloc(1, sizeof(*snapshot)))) {
+ ast_string_field_init(snapshot, 128);
+ ast_string_field_set(snapshot, msg_id, msg_id_buf);
+ ast_string_field_set(snapshot, exten, exten);
+ ast_string_field_set(snapshot, callerid, callerid);
+ }
+ return snapshot;
+}
+
+/*! \internal \brief Make a voicemail mailbox folder based on the values provided in a message snapshot
+ * \param snapshot The snapshot containing the information to create the folder from
+ * \returns 0 on success
+ * \returns 1 on failure
+ */
+static int test_vm_api_create_voicemail_folder(const char *folder_path)
+{
+ mode_t mode = VOICEMAIL_DIR_MODE;
+ int res;
+
+ if ((res = ast_mkdir(folder_path, mode))) {
+ ast_log(AST_LOG_ERROR, "ast_mkdir '%s' failed: %s\n", folder_path, strerror(res));
+ return 1;
+ }
+ return 0;
+}
+
+/*! \internal \brief Create the voicemail files specified by a snapshot
+ * \param context The context of the mailbox
+ * \param mailbox The actual mailbox
+ * \param snapshot The message snapshot object containing the relevant envelope data
+ * \note This will symbolic link the sound file 'beep.gsm' to act as the 'sound' portion of the voicemail.
+ * Certain actions in app_voicemail will fail if an actual sound file does not exist
+ * \returns 0 on success
+ * \returns 1 on any failure
+ */
+static int test_vm_api_create_voicemail_files(const char *context, const char *mailbox, struct ast_vm_msg_snapshot *snapshot)
+{
+ FILE *msg_file;
+ char folder_path[PATH_MAX];
+ char msg_path[PATH_MAX];
+ char snd_path[PATH_MAX];
+ char beep_path[PATH_MAX];
+
+ /* Note that we create both the text and a dummy sound file here. Without
+ * the sound file, a number of the voicemail operations 'silently' fail, as it
+ * does not believe that an actual voicemail exists
+ */
+ snprintf(folder_path, sizeof(folder_path), "%s/voicemail/%s/%s/%s",
+ ast_config_AST_SPOOL_DIR, context, mailbox, snapshot->folder_name);
+ snprintf(msg_path, sizeof(msg_path), "%s/msg%04d.txt",
+ folder_path, snapshot->msg_number);
+ snprintf(snd_path, sizeof(snd_path), "%s/msg%04d.gsm",
+ folder_path, snapshot->msg_number);
+ snprintf(beep_path, sizeof(beep_path), "%s/sounds/en/beep.gsm", ast_config_AST_VAR_DIR);
+
+ if (test_vm_api_create_voicemail_folder(folder_path)) {
+ return 1;
+ }
+
+ if (ast_lock_path(folder_path) == AST_LOCK_FAILURE) {
+ ast_log(AST_LOG_ERROR, "Unable to lock directory %s\n", folder_path);
+ return 1;
+ }
+
+ if (symlink(beep_path, snd_path)) {
+ ast_unlock_path(folder_path);
+ ast_log(AST_LOG_ERROR, "Failed to create a symbolic link from %s to %s: %s\n",
+ beep_path, snd_path, strerror(errno));
+ return 1;
+ }
+
+ if (!(msg_file = fopen(msg_path, "w"))) {
+ /* Attempt to remove the sound file */
+ unlink(snd_path);
+ ast_unlock_path(folder_path);
+ ast_log(AST_LOG_ERROR, "Failed to open %s for writing\n", msg_path);
+ return 1;
+ }
+
+ fprintf(msg_file, ";\n; Message Information file\n;\n"
+ "[message]\n"
+ "origmailbox=%s\n"
+ "context=%s\n"
+ "macrocontext=%s\n"
+ "exten=%s\n"
+ "rdnis=%s\n"
+ "priority=%d\n"
+ "callerchan=%s\n"
+ "callerid=%s\n"
+ "origdate=%s\n"
+ "origtime=%s\n"
+ "category=%s\n"
+ "msg_id=%s\n"
+ "flag=%s\n"
+ "duration=%s\n",
+ mailbox,
+ context,
+ "",
+ snapshot->exten,
+ "unknown",
+ 1,
+ snapshot->callerchan,
+ snapshot->callerid,
+ snapshot->origdate,
+ snapshot->origtime,
+ "",
+ snapshot->msg_id,
+ snapshot->flag,
+ snapshot->duration);
+ fclose(msg_file);
+
+ if (chmod(msg_path, VOICEMAIL_FILE_MODE) < 0) {
+ ast_unlock_path(folder_path);
+ ast_log(AST_LOG_ERROR, "Couldn't set permissions on voicemail text file %s: %s", msg_path, strerror(errno));
+ return 1;
+ }
+ ast_unlock_path(folder_path);
+
+ return 0;
+}
+
+/*! \internal \brief Destroy the voicemail on the file system associated with a snapshot
+ * \param snapshot The snapshot describing the voicemail
+ */
+static void test_vm_api_remove_voicemail(struct ast_vm_msg_snapshot *snapshot)
+{
+ char msg_path[PATH_MAX];
+ char snd_path[PATH_MAX];
+ char folder_path[PATH_MAX];
+
+ if (!snapshot) {
+ return;
+ }
+
+ snprintf(folder_path, sizeof(folder_path), "%s/voicemail/%s/%s/%s",
+ ast_config_AST_SPOOL_DIR, "default", snapshot->exten, snapshot->folder_name);
+
+ snprintf(msg_path, sizeof(msg_path), "%s/msg%04d.txt",
+ folder_path, snapshot->msg_number);
+ snprintf(snd_path, sizeof(snd_path), "%s/msg%04d.gsm",
+ folder_path, snapshot->msg_number);
+ unlink(msg_path);
+ unlink(snd_path);
+
+ return;
+}
+
+/*! \internal \brief Destroy the voicemails associated with a mailbox snapshot
+ * \param mailbox The actual mailbox name
+ * \param mailbox_snapshot The mailbox snapshot containing the voicemails to destroy
+ * \note Its necessary to specify not just the snapshot, but the mailbox itself. The
+ * message snapshots contained in the snapshot may have originated from a different mailbox
+ * then the one we're destroying, which means that we can't determine the files to delete
+ * without knowing the actual mailbox they exist in.
+ */
+static void test_vm_api_destroy_mailbox_voicemails(const char *mailbox, struct ast_vm_mailbox_snapshot *mailbox_snapshot)
+{
+ struct ast_vm_msg_snapshot *msg;
+ int i;
+
+ for (i = 0; i < 12; ++i) {
+ AST_LIST_TRAVERSE(&mailbox_snapshot->snapshots[i], msg, msg) {
+ ast_string_field_set(msg, exten, mailbox);
+ test_vm_api_remove_voicemail(msg);
+ }
+ }
+}
+
+/*! \internal \brief Use snapshots to remove all messages in the mailboxes */
+static void test_vm_api_remove_all_messages(void)
+{
+ struct ast_vm_mailbox_snapshot *mailbox_snapshot;
+
+ /* Take a snapshot of each mailbox and remove the contents. Note that we need to use
+ * snapshots of the mailboxes in addition to our tracked test snapshots, as there's a good chance
+ * we've created copies of the snapshots */
+ if ((mailbox_snapshot = ast_vm_mailbox_snapshot_create("test_vm_api_1234", "default", NULL, 0, AST_VM_SNAPSHOT_SORT_BY_ID, 0))) {
+ test_vm_api_destroy_mailbox_voicemails("test_vm_api_1234", mailbox_snapshot);
+ mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot);
+ } else {
+ ast_log(AST_LOG_WARNING, "Failed to create mailbox snapshot - could not remove test messages for test_vm_api_1234\n");
+ }
+ if ((mailbox_snapshot = ast_vm_mailbox_snapshot_create("test_vm_api_2345", "default", NULL, 0, AST_VM_SNAPSHOT_SORT_BY_ID, 0))) {
+ test_vm_api_destroy_mailbox_voicemails("test_vm_api_2345", mailbox_snapshot);
+ mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot);
+ } else {
+ ast_log(AST_LOG_WARNING, "Failed to create mailbox snapshot - could not remove test messages for test_vm_api_2345\n");
+ }
+}
+
+/*! \internal \brief Set up the necessary voicemails for a unit test run
+ * \note
+ * This creates 4 voicemails, stores them on the file system, and creates snapshot objects
+ * representing them for expected/actual value comparisons in the array test_snapshots.
+ *
+ * test_snapshots[0] => in test_vm_1234@default, folder INBOX, message 0
+ * test_snapshots[1] => in test_vm_1234@default, folder Old, message 0
+ * test_snapshots[2] => in test_vm_2345@default, folder INBOX, message 0
+ * test_snapshots[3] => in test_vm_2345@default, folder Old, message 1
+ *
+ * \returns 0 on success
+ * \returns 1 on failure
+ */
+static int test_vm_api_test_setup(void)
+{
+ int i, res = 0;
+ struct ast_vm_msg_snapshot *msg_one = NULL;
+ struct ast_vm_msg_snapshot *msg_two = NULL;
+ struct ast_vm_msg_snapshot *msg_three = NULL;
+ struct ast_vm_msg_snapshot *msg_four = NULL;
+
+ /* Make the four sample voicemails */
+ if ( !((msg_one = test_vm_api_create_mock_snapshot("default", "test_vm_api_1234", "\"Phil\" <2000>")))
+ || !((msg_two = test_vm_api_create_mock_snapshot("default", "test_vm_api_1234", "\"Noel\" <8000>")))
+ || !((msg_three = test_vm_api_create_mock_snapshot("default", "test_vm_api_2345", "\"Phil\" <2000>")))
+ || !((msg_four = test_vm_api_create_mock_snapshot("default", "test_vm_api_2345", "\"Bill\" <3000>")))) {
+ ast_log(AST_LOG_ERROR, "Failed to create mock snapshots for test\n");
+ ast_free(msg_one);
+ ast_free(msg_two);
+ ast_free(msg_three);
+ ast_free(msg_four);
+ return 1;
+ }
+
+ /* Create the voicemail users */
+ if (ast_vm_test_create_user("default", "test_vm_api_1234")
+ || ast_vm_test_create_user("default", "test_vm_api_2345")) {
+ ast_log(AST_LOG_ERROR, "Failed to create test voicemail users\n");
+ ast_free(msg_one);
+ ast_free(msg_two);
+ ast_free(msg_three);
+ ast_free(msg_four);
+ /* Note that the cleanup macro will ensure that any test user that
+ * was successfully created is removed
+ */
+ return 1;
+ }
+
+ /* Now that the users exist from the perspective of the voicemail
+ * application, attempt to remove any existing voicemails
+ */
+ test_vm_api_remove_all_messages();
+
+ /* Set the basic properties on each */
+ ast_string_field_set(msg_one, callerchan, "SIP/2000-00000000");
+ ast_string_field_set(msg_one, origdate, "Mon Mar 19 04:14:21 PM UTC 2012");
+ ast_string_field_set(msg_one, origtime, "1332173661");
+ ast_string_field_set(msg_one, duration, "8");
+ ast_string_field_set(msg_one, folder_name, "Old");
+ msg_one->msg_number = 0;
+ test_snapshots[0] = msg_one;
+
+ ast_string_field_set(msg_two, callerchan, "SIP/8000-00000001");
+ ast_string_field_set(msg_two, origdate, "Mon Mar 19 06:16:13 PM UTC 2012");
+ ast_string_field_set(msg_two, origtime, "1332180973");
+ ast_string_field_set(msg_two, duration, "24");
+ ast_string_field_set(msg_two, folder_name, "INBOX");
+ msg_two->msg_number = 0;
+ test_snapshots[1] = msg_two;
+
+ ast_string_field_set(msg_three, callerchan, "IAX/2000-000000a3");
+ ast_string_field_set(msg_three, origdate, "Thu Mar 22 23:13:03 PM UTC 2012");
+ ast_string_field_set(msg_three, origtime, "1332181251");
+ ast_string_field_set(msg_three, duration, "25");
+ ast_string_field_set(msg_three, folder_name, "INBOX");
+ msg_three->msg_number = 0;
+ test_snapshots[2] = msg_three;
+
+ ast_string_field_set(msg_four, callerchan, "DAHDI/3000-00000010");
+ ast_string_field_set(msg_four, origdate, "Fri Mar 23 03:01:03 AM UTC 2012");
+ ast_string_field_set(msg_four, origtime, "1332181362");
+ ast_string_field_set(msg_four, duration, "13");
+ ast_string_field_set(msg_four, folder_name, "INBOX");
+ msg_three->msg_number = 1;
+ test_snapshots[3] = msg_four;
+
+ /* Store the messages */
+ for (i = 0; i < TOTAL_SNAPSHOTS; ++i) {
+ if (test_vm_api_create_voicemail_files("default", test_snapshots[i]->exten, test_snapshots[i])) {
+ /* On a failure, the test_vm_api_test_teardown method will remove and
+ * unlink any created files. Since we failed to create the file, clean
+ * up the object here instead */
+ ast_log(AST_LOG_ERROR, "Failed to store voicemail %s/%s\n",
+ "default", test_snapshots[i]->exten);
+ ast_free(test_snapshots[i]);
+ test_snapshots[i] = NULL;
+ res = 1;
+ }
+ }
+
+ return res;
+}
+
+static void test_vm_api_test_teardown(void)
+{
+ int i;
+
+ /* Remove our test message snapshots */
+ for (i = 0; i < TOTAL_SNAPSHOTS; ++i) {
+ test_vm_api_remove_voicemail(test_snapshots[i]);
+ ast_free(test_snapshots[i]);
+ test_snapshots[i] = NULL;
+ }
+
+ test_vm_api_remove_all_messages();
+
+ /* Remove the test users */
+ ast_vm_test_destroy_user("default", "test_vm_api_1234");
+ ast_vm_test_destroy_user("default", "test_vm_api_2345");
+}
+
+/*! \internal \brief Update the test snapshots with a new mailbox snapshot
+ * \param mailbox_snapshot The new mailbox shapshot to update the test snapshots with
+ */
+static void test_vm_api_update_test_snapshots(struct ast_vm_mailbox_snapshot *mailbox_snapshot)
+{
+ int i, j;
+ struct ast_vm_msg_snapshot *msg;
+
+ for (i = 0; i < TOTAL_SNAPSHOTS; ++i) {
+ for (j = 0; j < 12; ++j) {
+ AST_LIST_TRAVERSE(&mailbox_snapshot->snapshots[j], msg, msg) {
+ if (!strcmp(msg->msg_id, test_snapshots[i]->msg_id)) {
+ ast_string_field_set(test_snapshots[i], callerid, msg->callerid);
+ ast_string_field_set(test_snapshots[i], callerchan, msg->callerchan);
+ ast_string_field_set(test_snapshots[i], exten, msg->exten);
+ ast_string_field_set(test_snapshots[i], origdate, msg->origdate);
+ ast_string_field_set(test_snapshots[i], origtime, msg->origtime);
+ ast_string_field_set(test_snapshots[i], duration, msg->duration);
+ ast_string_field_set(test_snapshots[i], folder_name, msg->folder_name);
+ ast_string_field_set(test_snapshots[i], flag, msg->flag);
+ test_snapshots[i]->msg_number = msg->msg_number;
+ }
+ }
+ }
+ }
+}
+
+/*! \internal \brief A callback function for message playback
+ * \param chan The channel the file would be played back on
+ * \param file The file to play back
+ * \param duration The duration of the file
+ * \note This sets global_entered_playback_callback to 1 if the parameters
+ * passed to the callback are minimally valid
+ */
+static void message_playback_callback_fn(struct ast_channel *chan, const char *file, int duration)
+{
+ if ((chan) && !ast_strlen_zero(file) && duration > 0) {
+ global_entered_playback_callback = 1;
+ } else {
+ ast_log(AST_LOG_WARNING, "Entered into message playback callback function with invalid parameters\n");
+ }
+}
+
+/*! \internal \brief Dummy channel write function for mock_channel_tech */
+static int test_vm_api_mock_channel_write(struct ast_channel *chan, struct ast_frame *frame)
+{
+ return 0;
+}
+
+/*! \internal \brief Dummy channel read function for mock_channel_tech */
+static struct ast_frame *test_vm_api_mock_channel_read(struct ast_channel *chan)
+{
+ return &ast_null_frame;
+}
+
+/*! \internal \brief A dummy channel technology */
+static const struct ast_channel_tech mock_channel_tech = {
+ .write = test_vm_api_mock_channel_write,
+ .read = test_vm_api_mock_channel_read,
+};
+
+/*! \internal \brief Create a dummy channel suitable for 'playing back' gsm sound files on
+ * \returns a channel on success
+ * \returns NULL on failure
+ */
+static struct ast_channel *test_vm_api_create_mock_channel(void)
+{
+ struct ast_channel *mock_channel;
+ struct ast_format_cap *native_formats;
+
+ if (!(mock_channel = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, NULL, NULL, 0, 0, "TestChannel"))) {
+ return NULL;
+ }
+
+ ast_format_set(ast_channel_writeformat(mock_channel), AST_FORMAT_GSM, 0);
+ native_formats = ast_channel_nativeformats(mock_channel);
+ ast_format_cap_add(native_formats, ast_channel_writeformat(mock_channel));
+ ast_format_set(ast_channel_rawwriteformat(mock_channel), AST_FORMAT_GSM, 0);
+ ast_format_set(ast_channel_readformat(mock_channel), AST_FORMAT_GSM, 0);
+ ast_format_set(ast_channel_rawreadformat(mock_channel), AST_FORMAT_GSM, 0);
+ ast_channel_tech_set(mock_channel, &mock_channel_tech);
+
+ return mock_channel;
+}
+
+AST_TEST_DEFINE(voicemail_api_nominal_snapshot)
+{
+ struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "nominal_snapshot";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Nominal mailbox snapshot tests";
+ info->description =
+ "Test retrieving mailbox snapshots";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ ast_test_status_update(test, "Test retrieving message 1 from INBOX of test_vm_1234\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(1, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test retrieving message 0 from Old of test_vm_1234\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "Old", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(1, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "Old", 0);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test retrieving message 0, 1 from Old and INBOX of test_vm_1234 ordered by time\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 1);
+ VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "INBOX", 0);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 1);
+
+ ast_test_status_update(test, "Test retrieving message 1, 0 from Old and INBOX of test_vm_1234 ordered by time desc\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 1, AST_VM_SNAPSHOT_SORT_BY_TIME, 1);
+ VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "INBOX", 1);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test retrieving message 0, 1 from Old and INBOX of test_vm_1234 ordered by id\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_ID, 1);
+ VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "INBOX", 1);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test retrieving message 1, 0 from Old and INBOX of test_vm_1234 ordered by id desc\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 1, AST_VM_SNAPSHOT_SORT_BY_ID, 1);
+ VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "INBOX", 0);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 1);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test retrieving message 0, 1 from all folders of test_vm_1234 ordered by id\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", NULL, 0, AST_VM_SNAPSHOT_SORT_BY_ID, 0);
+ VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "Old", 0);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test retrieving message 0, 1 from all folders of test_vm_1234 ordered by time\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", NULL, 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "Old", 0);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test retrieving message 0, 1 from all folders of test_vm_1234, default context ordered by time\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", NULL, NULL, 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "Old", 0);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_off_nominal_snapshot)
+{
+ struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "off_nominal_snapshot";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Off nominal mailbox snapshot tests";
+ info->description =
+ "Test off nominal requests for mailbox snapshots. This includes"
+ " testing the following:\n"
+ " * Access to non-exisstent mailbox\n"
+ " * Access to NULL mailbox\n"
+ " * Access to non-existent context\n"
+ " * Access to non-existent folder\n"
+ " * Access to NULL folder\n"
+ " * Invalid sort identifier\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ ast_test_status_update(test, "Test access to non-existent mailbox test_vm_api_3456\n");
+ VM_API_SNAPSHOT_OFF_NOMINAL_TEST("test_vm_api_3456", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+
+ ast_test_status_update(test, "Test access to null mailbox\n");
+ VM_API_SNAPSHOT_OFF_NOMINAL_TEST(NULL, "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+
+ ast_test_status_update(test, "Test access non-existent context test_vm_api_defunct\n");
+ VM_API_SNAPSHOT_OFF_NOMINAL_TEST("test_vm_api_1234", "test_vm_api_defunct", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+
+ ast_test_status_update(test, "Test non-existent folder test_vm_api_platypus\n");
+ VM_API_SNAPSHOT_OFF_NOMINAL_TEST("test_vm_api_1234", "default", "test_vm_api_platypus", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_nominal_move)
+{
+ struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL;
+ const char *inbox_msg_id;
+ const char *old_msg_id;
+ const char *multi_msg_ids[2];
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "nominal_move";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Nominal move voicemail tests";
+ info->description =
+ "Test nominal requests to move a voicemail to a different"
+ " folder. This includes moving messages given a context,"
+ " given a NULL context, and moving multiple messages";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+ old_msg_id = test_snapshots[0]->msg_id;
+ inbox_msg_id = test_snapshots[1]->msg_id;
+
+ multi_msg_ids[0] = test_snapshots[2]->msg_id;
+ multi_msg_ids[1] = test_snapshots[3]->msg_id;
+
+ ast_test_status_update(test, "Test move of test_vm_api_1234 message from INBOX to Family\n");
+ VM_API_MOVE_MESSAGE("test_vm_api_1234", "default", 1, "INBOX", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move of test_vm_api_1234 message from Old to Family\n");
+ VM_API_MOVE_MESSAGE("test_vm_api_1234", NULL, 1, "Old", &old_msg_id, "Family");
+
+ /* Take a snapshot and update the test snapshots for verification */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "Family", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ test_vm_api_update_test_snapshots(test_mbox_snapshot);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ VM_API_STRING_FIELD_VERIFY(test_snapshots[0]->folder_name, "Family");
+ VM_API_STRING_FIELD_VERIFY(test_snapshots[1]->folder_name, "Family");
+ VM_API_INT_VERIFY(test_snapshots[1]->msg_number, 0);
+ VM_API_INT_VERIFY(test_snapshots[0]->msg_number, 1);
+
+ /* Move both of the 2345 messages to Family */
+ ast_test_status_update(test, "Test move of test_vm_api_2345 messages from Inbox to Family\n");
+ VM_API_MOVE_MESSAGE("test_vm_api_2345", "default", 2, "INBOX", multi_msg_ids, "Family");
+
+ /* Take a snapshot and update the test snapshots for verification */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "Family", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ test_vm_api_update_test_snapshots(test_mbox_snapshot);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ VM_API_STRING_FIELD_VERIFY(test_snapshots[2]->folder_name, "Family");
+ VM_API_STRING_FIELD_VERIFY(test_snapshots[3]->folder_name, "Family");
+
+ ast_test_status_update(test, "Test move of test_vm_api_2345 message from Family to INBOX\n");
+ VM_API_MOVE_MESSAGE("test_vm_api_2345", "default", 2, "Family", multi_msg_ids, "INBOX");
+
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ test_vm_api_update_test_snapshots(test_mbox_snapshot);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ VM_API_STRING_FIELD_VERIFY(test_snapshots[2]->folder_name, "INBOX");
+ VM_API_STRING_FIELD_VERIFY(test_snapshots[3]->folder_name, "INBOX");
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_off_nominal_move)
+{
+ const char *inbox_msg_id;
+ const char *multi_msg_ids[4];
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "off_nominal_move";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Off nominal mailbox message move tests";
+ info->description =
+ "Test nominal requests to move a voicemail to a different"
+ " folder. This includes testing the following:\n"
+ " * Moving to a non-existent mailbox\n"
+ " * Moving to a NULL mailbox\n"
+ " * Moving to a non-existent context\n"
+ " * Moving to/from non-existent folder\n"
+ " * Moving to/from NULL folder\n"
+ " * Invalid message identifier(s)\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ inbox_msg_id = test_snapshots[1]->msg_id;
+
+ multi_msg_ids[0] = test_snapshots[0]->msg_id;
+ multi_msg_ids[1] = test_snapshots[1]->msg_id;
+ multi_msg_ids[2] = test_snapshots[2]->msg_id;
+ multi_msg_ids[3] = test_snapshots[3]->msg_id;
+
+ ast_test_status_update(test, "Test move attempt for invalid mailbox test_vm_3456\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_3456", "default", 1, "INBOX", &inbox_msg_id, "Family");
+
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL(NULL, "default", 1, "INBOX", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt for invalid context test_vm_api_defunct\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "test_vm_api_defunct", 1, "INBOX", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt to invalid folder\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", &inbox_msg_id, "SPAMALOT");
+
+ ast_test_status_update(test, "Test move attempt from invalid folder\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "MEATINACAN", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt to NULL folder\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", &inbox_msg_id, NULL);
+
+ ast_test_status_update(test, "Test move attempt from NULL folder\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, NULL, &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt with non-existent message number\n");
+ inbox_msg_id = "6";
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt with invalid message number\n");
+ inbox_msg_id = "";
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt with 0 number of messages\n");
+ inbox_msg_id = test_snapshots[1]->msg_id;
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 0, "INBOX", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt with invalid number of messages\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", -30, "INBOX", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt with non-existent multiple messages, where some messages exist\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 4, "INBOX", multi_msg_ids, "Family");
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_nominal_remove)
+{
+ struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL;
+ const char *inbox_msg_id;
+ const char *old_msg_id;
+ const char *multi_msg_ids[2];
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "nominal_remove";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Nominal mailbox remove message tests";
+ info->description =
+ "Tests removing messages from voicemail folders. Includes"
+ " both removing messages one at a time, and in a set";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ old_msg_id = test_snapshots[0]->msg_id;
+ inbox_msg_id = test_snapshots[1]->msg_id;
+
+ multi_msg_ids[0] = test_snapshots[2]->msg_id;
+ multi_msg_ids[1] = test_snapshots[3]->msg_id;
+
+ ast_test_status_update(test, "Test removing a single message from INBOX\n");
+ VM_API_REMOVE_MESSAGE("test_vm_api_1234", "default", 1, "INBOX", &inbox_msg_id);
+
+ ast_test_status_update(test, "Test removing a single message from Old\n");
+ VM_API_REMOVE_MESSAGE("test_vm_api_1234", "default", 1, "Old", &old_msg_id);
+
+ ast_test_status_update(test, "Test removing multiple messages from INBOX\n");
+ VM_API_REMOVE_MESSAGE("test_vm_api_2345", "default", 2, "INBOX", multi_msg_ids);
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_off_nominal_remove)
+{
+ const char *inbox_msg_id;
+ const char *multi_msg_ids[2];
+ const char *empty_msg_ids[] = { };
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "off_nominal_remove";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Off nominal mailbox message removal tests";
+ info->description =
+ "Test off nominal requests for removing messages from "
+ "a mailbox. This includes:\n"
+ " * Removing messages with an invalid mailbox\n"
+ " * Removing messages from a NULL mailbox\n"
+ " * Removing messages from an invalid context\n"
+ " * Removing messages from an invalid folder\n"
+ " * Removing messages from a NULL folder\n"
+ " * Removing messages with bad identifiers\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ inbox_msg_id = test_snapshots[1]->msg_id;
+ multi_msg_ids[0] = test_snapshots[2]->msg_id;
+ multi_msg_ids[1] = test_snapshots[3]->msg_id;
+
+ ast_test_status_update(test, "Test removing a single message with an invalid mailbox\n");
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_3456", "default", 1, "INBOX", &inbox_msg_id);
+
+ ast_test_status_update(test, "Test removing a single message with a NULL mailbox\n");
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL(NULL, "default", 1, "INBOX", &inbox_msg_id);
+
+ ast_test_status_update(test, "Test removing a single message with an invalid context\n");
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "defunct", 1, "INBOX", &inbox_msg_id);
+
+ ast_test_status_update(test, "Test removing a single message with an invalid folder\n");
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "SPAMINACAN", &inbox_msg_id);
+
+ ast_test_status_update(test, "Test removing a single message with a NULL folder\n");
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, NULL, &inbox_msg_id);
+
+ ast_test_status_update(test, "Test removing a single message with an invalid message number\n");
+ inbox_msg_id = "POOPOO";
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", &inbox_msg_id);
+
+ ast_test_status_update(test, "Test removing multiple messages with a single invalid message number\n");
+ multi_msg_ids[1] = "POOPOO";
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_2345", "default", 2, "INBOX", multi_msg_ids);
+
+ ast_test_status_update(test, "Test removing no messages with no message numbers\n");
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 0, "INBOX", empty_msg_ids);
+
+ ast_test_status_update(test, "Test removing multiple messages with an invalid size specifier\n");
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_2345", "default", -30, "INBOX", multi_msg_ids);
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_nominal_forward)
+{
+ struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL;
+ const char *inbox_msg_id;
+ const char *multi_msg_ids[2];
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "nominal_forward";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Nominal message forward tests";
+ info->description =
+ "Tests the nominal cases of forwarding messages"
+ " between mailboxes";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ inbox_msg_id = test_snapshots[1]->msg_id;
+
+ multi_msg_ids[0] = test_snapshots[2]->msg_id;
+ multi_msg_ids[1] = test_snapshots[3]->msg_id;
+
+ ast_test_status_update(test, "Test forwarding message 0 from test_vm_api_1234 INBOX to test_vm_api_2345 INBOX\n");
+ VM_API_FORWARD_MESSAGE("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ /* Make sure we didn't delete the message */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 1);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ /* We should now have a total of 3 messages in test_vm_api_2345 INBOX */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 3);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test forwarding message 0 from test_vm_api_1234 INBOX with default context to test_vm_api_2345 INBOX\n");
+ VM_API_FORWARD_MESSAGE("test_vm_api_1234", NULL, "INBOX", "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ /* Make sure we didn't delete the message */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 1);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ /* We should now have a total of 4 messages in test_vm_api_2345 INBOX */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 4);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test forwarding message 0 from test_vm_api_1234 INBOX to test_vm_api_2345 INBOX with default context\n");
+ VM_API_FORWARD_MESSAGE("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", NULL, "INBOX", 1, &inbox_msg_id, 0);
+
+ /* Make sure we didn't delete the message */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 1);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ /* We should now have a total of 5 messages in test_vm_api_2345 INBOX */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 5);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test forwarding message 0 from test_vm_api_1234 INBOX to test_vm_api_2345 INBOX, deleting original\n");
+ VM_API_FORWARD_MESSAGE("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", NULL, "INBOX", 1, &inbox_msg_id, 1);
+
+ /* Make sure we deleted the message */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 0);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ /* We should now have a total of 6 messages in test_vm_api_2345 INBOX */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 6);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test forwarding 2 messages from test_vm_api_2345 INBOX to test_vm_api_1234 INBOX");
+ VM_API_FORWARD_MESSAGE("test_vm_api_2345", "default", "INBOX", "test_vm_api_1234", "default", "INBOX", 2, multi_msg_ids, 0);
+
+ /* Make sure we didn't delete the messages */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 6);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ /* We should now have a total of 2 messages in test_vm_api_1234 INBOX */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 2);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test forwarding 2 messages from test_vm_api_2345 INBOX to test_vm_api_1234 Family, deleting original\n");
+ VM_API_FORWARD_MESSAGE("test_vm_api_2345", "default", "INBOX", "test_vm_api_1234", "default", "Family", 2, multi_msg_ids, 1);
+ /* Make sure we deleted the messages */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 4);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ /* We should now have a total of 2 messages in test_vm_api_1234 Family */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "Family", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 2);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_off_nominal_forward)
+{
+ const char *inbox_msg_id;
+ const char *multi_msg_ids[4];
+
+ const char *empty_msg_ids[] = { };
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "off_nominal_forward";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Off nominal message forwarding tests";
+ info->description =
+ "Test off nominal forwarding of messages. This includes:\n"
+ " * Invalid/NULL from mailbox\n"
+ " * Invalid from context\n"
+ " * Invalid/NULL from folder\n"
+ " * Invalid/NULL to mailbox\n"
+ " * Invalid to context\n"
+ " * Invalid/NULL to folder\n"
+ " * Invalid message numbers\n"
+ " * Invalid number of messages\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ inbox_msg_id = test_snapshots[1]->msg_id;
+
+ multi_msg_ids[0] = test_snapshots[0]->msg_id;
+ multi_msg_ids[1] = test_snapshots[1]->msg_id;
+ multi_msg_ids[2] = test_snapshots[2]->msg_id;
+ multi_msg_ids[3] = test_snapshots[3]->msg_id;
+
+ ast_test_status_update(test, "Test forwarding from an invalid mailbox\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_3456", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding from a NULL mailbox\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL(NULL, "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding from an invalid context\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "defunct", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding from an invalid folder\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "POTTEDMEAT", "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding from a NULL folder\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", NULL, "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding to an invalid mailbox\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_3456", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding to a NULL mailbox\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", NULL, "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding to an invalid context\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "defunct", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding to an invalid folder\n");
+
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "POTTEDMEAT", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding to a NULL folder\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", NULL, 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding when no messages are select\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 0, empty_msg_ids, 0);
+
+ ast_test_status_update(test, "Test forwarding a message that doesn't exist\n");
+ inbox_msg_id = "POOPOO";
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding multiple messages, where some messages don't exist\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_2345", "default", "INBOX", "test_vm_api_1234", "default", "INBOX", 4, multi_msg_ids, 0);
+
+ ast_test_status_update(test, "Test forwarding a message with an invalid size specifier\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", -30, &inbox_msg_id, 0);
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_nominal_msg_playback)
+{
+ struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL;
+ struct ast_channel *test_channel;
+ const char *message_id_1234;
+ const char *message_id_2345[2];
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "nominal_msg_playback";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Nominal message playback";
+ info->description =
+ "Tests playing back a message on a provided"
+ " channel or callback function\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ message_id_1234 = test_snapshots[1]->msg_id;
+ message_id_2345[0] = test_snapshots[2]->msg_id;
+ message_id_2345[1] = test_snapshots[3]->msg_id;
+
+ if (!(test_channel = test_vm_api_create_mock_channel())) {
+ ast_log(AST_LOG_ERROR, "Failed to create mock channel for testing\n");
+ VM_API_TEST_CLEANUP;
+ return AST_TEST_FAIL;
+ }
+
+ ast_test_status_update(test, "Playing back message from test_vm_api_1234 to mock channel\n");
+ VM_API_PLAYBACK_MESSAGE(test_channel, "test_vm_api_1234", "default", "INBOX", message_id_1234, NULL);
+
+ ast_test_status_update(test, "Playing back message from test_vm_api_2345 to callback function\n");
+ VM_API_PLAYBACK_MESSAGE(test_channel, "test_vm_api_2345", "default", "INBOX", message_id_2345[0], &message_playback_callback_fn);
+ VM_API_INT_VERIFY(global_entered_playback_callback, 1);
+ global_entered_playback_callback = 0;
+
+ ast_test_status_update(test, "Playing back message from test_vm_api_2345 to callback function with default context\n");
+ VM_API_PLAYBACK_MESSAGE(test_channel, "test_vm_api_2345", NULL, "INBOX", message_id_2345[1], &message_playback_callback_fn);
+ VM_API_INT_VERIFY(global_entered_playback_callback, 1);
+ global_entered_playback_callback = 0;
+
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "Old", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 2);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "Old", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 2);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ if (test_channel) {
+ ast_hangup(test_channel);
+ }
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_off_nominal_msg_playback)
+{
+ struct ast_channel *test_channel;
+ const char *msg_id;
+ const char *invalid_msg_id = "POOPOO";
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "off_nominal_msg_playback";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Off nominal message playback";
+ info->description =
+ "Tests off nominal conditions in playing back a "
+ "message. This includes:\n"
+ " * Invalid/NULL mailbox\n"
+ " * Invalid context\n"
+ " * Invalid/NULL folder\n"
+ " * Invalid message identifiers\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+ msg_id = test_snapshots[0]->msg_id;
+
+ if (!(test_channel = test_vm_api_create_mock_channel())) {
+ ast_log(AST_LOG_ERROR, "Failed to create mock channel for testing\n");
+ VM_API_TEST_CLEANUP;
+ return AST_TEST_FAIL;
+ }
+
+ ast_test_status_update(test, "Playing back message from invalid mailbox\n");
+ VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_3456", "default", "INBOX", msg_id, NULL);
+
+ ast_test_status_update(test, "Playing back message from NULL mailbox\n");
+ VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, NULL, "default", "INBOX", msg_id, NULL);
+
+ ast_test_status_update(test, "Playing back message from invalid context\n");
+ VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "defunct", "INBOX", msg_id, NULL);
+
+ ast_test_status_update(test, "Playing back message from invalid folder\n");
+ VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "default", "BACON", msg_id, NULL);
+
+ ast_test_status_update(test, "Playing back message from NULL folder\n");
+ VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "default", NULL, msg_id, NULL);
+
+ ast_test_status_update(test, "Playing back message with invalid message specifier\n");
+ VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "default", "INBOX", invalid_msg_id, NULL);
+
+ ast_test_status_update(test, "Playing back message with NULL message specifier\n");
+ VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "default", "INBOX", NULL, NULL);
+ if (test_channel) {
+ ast_hangup(test_channel);
+ }
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+static int unload_module(void)
+{
+ /* Snapshot tests */
+ AST_TEST_UNREGISTER(voicemail_api_nominal_snapshot);
+ AST_TEST_UNREGISTER(voicemail_api_off_nominal_snapshot);
+
+ /* Move Tests */
+ AST_TEST_UNREGISTER(voicemail_api_nominal_move);
+ AST_TEST_UNREGISTER(voicemail_api_off_nominal_move);
+
+ /* Remove Tests */
+ AST_TEST_UNREGISTER(voicemail_api_nominal_remove);
+ AST_TEST_UNREGISTER(voicemail_api_off_nominal_remove);
+
+ /* Forward Tests */
+ AST_TEST_UNREGISTER(voicemail_api_nominal_forward);
+ AST_TEST_UNREGISTER(voicemail_api_off_nominal_forward);
+
+ /* Message Playback Tests */
+ AST_TEST_UNREGISTER(voicemail_api_nominal_msg_playback);
+ AST_TEST_UNREGISTER(voicemail_api_off_nominal_msg_playback);
+ return 0;
+}
+
+static int load_module(void)
+{
+ /* Snapshot tests */
+ AST_TEST_REGISTER(voicemail_api_nominal_snapshot);
+ AST_TEST_REGISTER(voicemail_api_off_nominal_snapshot);
+
+ /* Move Tests */
+ AST_TEST_REGISTER(voicemail_api_nominal_move);
+ AST_TEST_REGISTER(voicemail_api_off_nominal_move);
+
+ /* Remove Tests */
+ AST_TEST_REGISTER(voicemail_api_nominal_remove);
+ AST_TEST_REGISTER(voicemail_api_off_nominal_remove);
+
+ /* Forward Tests */
+ AST_TEST_REGISTER(voicemail_api_nominal_forward);
+ AST_TEST_REGISTER(voicemail_api_off_nominal_forward);
+
+ /* Message Playback Tests */
+ AST_TEST_REGISTER(voicemail_api_nominal_msg_playback);
+ AST_TEST_REGISTER(voicemail_api_off_nominal_msg_playback);
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Core Voicemail API Tests");