Merge changes dealing with support for Digium phones.

Presence support has been added. This is accomplished by
allowing for presence hints in addition to device state
hints. A dialplan function called PRESENCE_STATE has been
added to allow for setting and reading presence. Presence
can be transmitted to Digium phones using custom XML
elements in a PIDF presence document.

Voicemail has new APIs that allow for moving, removing,
forwarding, and playing messages. Messages have had a new
unique message ID added to them so that the APIs will work
reliably. The state of a voicemail mailbox can be obtained
using an API that allows one to get a snapshot of the mailbox.
A voicemail Dialplan App called VoiceMailPlayMsg has been
added to be able to play back a specific message.

Configuration hooks have been added. Configuration hooks
allow for a piece of code to be executed when a specific
configuration file is loaded by a specific module. This is
useful for modules that are dependent on the configuration
of other modules.

chan_sip now has a public method that allows for a custom
SIP INFO request to be sent mid-dialog. Digium phones use
this in order to display progress bars when files are played.

Messaging support has been expanded a bit. The main
visible difference is the addition of an AMI action
MessageSend.

Finally, a ParkingLots manager action has been added in order
to get a list of parking lots.



git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@368435 65c4cc65-6c06-0410-ace0-fbb531ad65f3
certified/11.2
Mark Michelson 13 years ago
parent c1bbe79748
commit 14a985560e

@ -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;
}

@ -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);

File diff suppressed because it is too large Load Diff

@ -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:
*;
};

@ -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,

@ -0,0 +1,6 @@
{
global:
LINKER_SYMBOL_PREFIX*ast_sipinfo_send;
local:
*;
};

@ -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);
}
}
}

@ -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 */

@ -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

@ -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)
);

@ -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,
);

@ -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,10 +242,20 @@ 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
@ -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);

@ -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

@ -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,
};
/*!

@ -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

@ -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,
};
/*!

@ -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.
*

@ -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 */

@ -113,6 +113,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
*
@ -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
@ -170,6 +175,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
@ -200,6 +217,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
*/

@ -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);
@ -401,6 +417,22 @@ enum ast_extension_states ast_devstate_to_extenstate(enum ast_device_state devst
*/
int ast_extension_state(struct ast_channel *c, const char *context, const char *exten);
/*!
* \brief Uses hint and presence state callback to get the presence state of an extension
*
* \param c this is not important
* \param context which context to look in
* \param exten which extension to get state
* \param[out] subtype Further information regarding the presence returned
* \param[out] message Custom message further describing current presence
*
* \note The subtype and message are dynamically allocated and must be freed by
* the caller of this function.
*
* \return returns the presence state value.
*/
int ast_hint_presence_state(struct ast_channel *c, const char *context, const char *exten, char **subtype, char **message);
/*!
* \brief Return string representation of the state of an extension
*

@ -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

@ -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 */

@ -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;

@ -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();

@ -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* */
};

@ -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);

@ -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;
}

@ -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;

@ -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);
}

@ -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 */);
}
/*

@ -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);

@ -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;
}

@ -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;
}

@ -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;
}

@ -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");

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save