diff --git a/channels/pjsip/dialplan_functions_doc.xml b/channels/pjsip/dialplan_functions_doc.xml
index 3d19d929ba..ed077bc447 100644
--- a/channels/pjsip/dialplan_functions_doc.xml
+++ b/channels/pjsip/dialplan_functions_doc.xml
@@ -55,6 +55,46 @@
+
+
+ Send a NOTIFY to either an arbitrary URI, or inside a SIP dialog.
+
+
+
+ Abritrary URI to which to send the NOTIFY. If none is specified, send inside
+ the SIP dialog for the current channel.
+
+
+ Either an option pre-configured in pjsip_notify.conf or a list of headers and body content to send in the NOTIFY.
+
+
+
+
+ Sends a NOTIFY to a specified URI, or if none provided, within the current SIP dialog for the
+ current channel. The content can either be set to either an entry configured in pjsip_notify.conf
+ or specified as a list of key value pairs.
+
+
+ To send a NOTIFY to a specified URI, a default_outbound_endpoint must be configured. This
+ endpoint determines the message contact.
+
+
+
+
+ same = n,PJSIPNotify(,&Event=Test&X-Data=Fun)
+
+
+ same = n,PJSIPNotify(,force-answer)
+
+
+ same = n,PJSIPNotify(<sip:bob@127.0.0.1:5260>,&Event=Test&X-Data=Fun)
+
+
+ same = n,PJSIPNotify(<sip:bob@127.0.0.1:5260>,&Event=Custom&Content-type=application/voicemail&Content=check-messages&Content=)
+
+
+
+
Hangup an incoming PJSIP channel with a SIP response code
diff --git a/configs/samples/extensions.conf.sample b/configs/samples/extensions.conf.sample
index 494738ebf6..d3f5eba899 100644
--- a/configs/samples/extensions.conf.sample
+++ b/configs/samples/extensions.conf.sample
@@ -828,3 +828,53 @@ exten => _X.,40000(ani),NoOp(ANI: ${EXTEN})
; "core show functions" will list all dialplan functions
; "core show function " will show you more information about
; one function. Remember that function names are UPPER CASE.
+
+; Examples using PJSIPNotify application.
+;[generate-notify]
+;
+; Send a NOTIFY with the following headers inside the SIP dialog for the current channel:
+;
+; Event: Test
+; X-Data: Fun
+;
+;exten => 6880,1,noOp()
+; same => n,Answer()
+; same => n,PJSIPNotify(,&Event=Test&X-Data=Fun)
+; same => n,Wait(1)
+; same => n,Hangup()
+;
+; Send a NOTIFY with the following headers to bob's custom uri. This requries a
+; default outbound endpoint to be configured in pjsip.conf.
+;
+; Event: Test
+; X-Data: Over
+;
+;exten => 6881,1,noOp()
+; same => n,Answer()
+; same => n,PJSIPNotify(,&Event=Test&X-Data=Over)
+; same => n,Wait(1)
+; same => n,Hangup()
+;
+; Send a NOTIFY with the the custom headers defined in pjsip_notify.conf under
+; 'custom-notify-1' inside the SIP dialog for the current channel.
+;
+;exten => 6882,1,noOp()
+; same => n,Answer()
+; same => n,PJSIPNotify(,custom-notify-1)
+; same => n,Wait(1)
+; same => n,Hangup()
+; Send a NOTIFY with the following headers and body to bob's custom uri. This
+; requries a default outbound endpoint to be configured in pjsip.conf.
+;
+; Event: Custom
+;
+; Content-Type: application/voicemail
+; Content-Length: 14
+;
+; check-messages
+;
+;exten => 6882,1,noOp()
+; same => n,Answer()
+; same => n,PJSIPNotify(,&Event=Custom&Content-type=application/voicemail&Content=check-messages&Content=)
+; same => n,Wait(1)
+; same => n,Hangup()
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index 61c8846b8f..a808694045 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -432,6 +432,16 @@
;type=aor
;max_contacts=2
+;===============DEFAULT ENDPOINT FOR OUTBOUND REQUESTS TO URI===================
+;
+; This is an example default outbound endpoint. The global setting:
+; default_outbound_endpoint needs to be set to such an endpoint in order to be
+; able to send an outbound request to a URI without a specified endpoint.
+;
+;[default_outbound_endpoint]
+;type=endpoint
+;context=none-invalid
+
;============EXAMPLE ACL CONFIGURATION==========================================
;
diff --git a/configs/samples/pjsip_notify.conf.sample b/configs/samples/pjsip_notify.conf.sample
index 8224ee1ff4..46bb5a11a0 100644
--- a/configs/samples/pjsip_notify.conf.sample
+++ b/configs/samples/pjsip_notify.conf.sample
@@ -55,3 +55,17 @@ Event=>check-sync\;reboot=true
[cisco-check-cfg]
Event=>check-sync
+
+; custom examples to use for PJSIPNotify application
+
+; tell an endpoint to check messages
+[custom-notify-1]
+Event=>custom
+Content-type=>application/voicemail
+Content=>check-messages
+Content=>
+
+; tell an endpoint to force a remote hangup via custom header
+[custom-notify-2]
+Event=>custom
+X-Data=>force-hangup
\ No newline at end of file
diff --git a/res/res_pjsip_notify.c b/res/res_pjsip_notify.c
index 3d88c18a88..1acd3144d4 100644
--- a/res/res_pjsip_notify.c
+++ b/res/res_pjsip_notify.c
@@ -27,6 +27,7 @@
#include
#include
+#include "asterisk/app.h"
#include "asterisk/cli.h"
#include "asterisk/config.h"
#include "asterisk/manager.h"
@@ -317,6 +318,14 @@ static void notify_cli_uri_data_destroy(void *obj)
ao2_cleanup(data->info);
}
+static void notify_cli_channel_data_destroy(void *obj)
+{
+ struct notify_channel_data *data = obj;
+
+ ao2_cleanup(data->info);
+ ao2_cleanup(data->session);
+}
+
/*!
* \internal
* \brief Destroy the notify CLI data releasing any resources (URI variant)
@@ -375,6 +384,29 @@ static struct notify_uri_data* notify_cli_uri_data_create(
return data;
}
+/*!
+ * \internal
+ * \brief Construct a notify URI data object for CLI.
+ */
+static struct notify_channel_data* notify_cli_channel_data_create(
+ struct ast_sip_session *session, void *info)
+{
+ struct notify_channel_data *data = ao2_alloc_options(sizeof(*data),
+ notify_cli_channel_data_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+
+ if (!data) {
+ return NULL;
+ }
+
+ data->session = session;
+ data->info = info;
+ ao2_ref(data->info, +1);
+
+ data->build_notify = build_cli_notify;
+
+ return data;
+}
+
/*!
* \internal
* \brief Destroy the notify AMI data releasing any resources.
@@ -945,14 +977,13 @@ static char *cli_complete_endpoint(const char *word)
* \internal
* \brief Do completion on the notify CLI command.
*/
-static char *cli_complete_notify(const char *line, const char *word,
- int pos, int state, int using_uri)
+static char *cli_complete_notify(struct ast_cli_args *a)
{
char *c = NULL;
- if (pos == 3) {
+ if (a->pos == 3) {
int which = 0;
- int wordlen = strlen(word);
+ int wordlen = strlen(a->word);
RAII_VAR(struct notify_cfg *, cfg,
ao2_global_obj_ref(globals), ao2_cleanup);
@@ -961,7 +992,7 @@ static char *cli_complete_notify(const char *line, const char *word,
/* do completion for notify type */
struct ao2_iterator i = ao2_iterator_init(cfg->notify_options, 0);
while ((option = ao2_iterator_next(&i))) {
- if (!strncasecmp(word, option->name, wordlen) && ++which > state) {
+ if (!strncasecmp(a->word, option->name, wordlen) && ++which > a->n) {
c = ast_strdup(option->name);
}
@@ -974,27 +1005,38 @@ static char *cli_complete_notify(const char *line, const char *word,
return c;
}
- if (pos == 4) {
- int wordlen = strlen(word);
+ if (a->pos == 4) {
+ int wordlen = strlen(a->word);
- if (ast_strlen_zero(word)) {
- if (state == 0) {
+ if (ast_strlen_zero(a->word)) {
+ if (a->n == 0) {
c = ast_strdup("endpoint");
- } else if (state == 1) {
+ } else if (a->n == 1) {
c = ast_strdup("uri");
+ } else if (a->n == 2) {
+ c = ast_strdup("channel");
}
- } else if (state == 0) {
- if (!strncasecmp(word, "endpoint", wordlen)) {
+ } else if (a->n == 0) {
+ if (!strncasecmp(a->word, "endpoint", wordlen)) {
c = ast_strdup("endpoint");
- } else if (!strncasecmp(word, "uri", wordlen)) {
+ } else if (!strncasecmp(a->word, "uri", wordlen)) {
c = ast_strdup("uri");
+ } else if (!strncasecmp(a->word, "channel", wordlen)) {
+ c = ast_strdup("channel");
}
}
return c;
}
- return pos > 4 && !using_uri ? cli_complete_endpoint(word) : NULL;
+ if (a->pos > 4) {
+ if (!strcasecmp(a->argv[4], "endpoint")) {
+ return cli_complete_endpoint(a->word);
+ } else if (!strcasecmp(a->argv[4], "channel")) {
+ return ast_complete_channels(a->line, a->word, a->pos, a->n, 5);
+ }
+ }
+ return NULL;
}
/*!
@@ -1012,21 +1054,18 @@ static char *cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a
int i;
int using_uri = 0;
+ int using_channel = 0;
switch (cmd) {
case CLI_INIT:
e->command = "pjsip send notify";
e->usage =
- "Usage: pjsip send notify {endpoint|uri} [...]\n"
+ "Usage: pjsip send notify {endpoint|uri|channel} [...]\n"
" Send a NOTIFY request to an endpoint\n"
" Message types are defined in pjsip_notify.conf\n";
return NULL;
case CLI_GENERATE:
- if (a->argc > 4 && (!strcasecmp(a->argv[4], "uri"))) {
- using_uri = 1;
- }
-
- return cli_complete_notify(a->line, a->word, a->pos, a->n, using_uri);
+ return cli_complete_notify(a);
}
if (a->argc < 6) {
@@ -1035,6 +1074,8 @@ static char *cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a
if (!strcasecmp(a->argv[4], "uri")) {
using_uri = 1;
+ } else if (!strcasecmp(a->argv[4], "channel")) {
+ using_channel = 1;
} else if (strcasecmp(a->argv[4], "endpoint")) {
return CLI_SHOWUSAGE;
}
@@ -1049,14 +1090,23 @@ static char *cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a
}
for (i = 5; i < a->argc; ++i) {
+ enum notify_result result;
ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n",
a->argv[3], a->argv[i]);
- switch (using_uri ? push_notify_uri(a->argv[i], option, notify_cli_uri_data_create) :
- push_notify(a->argv[i], option, notify_cli_data_create)) {
+ if (using_uri) {
+ result = push_notify_uri(a->argv[i], option, notify_cli_uri_data_create);
+ } else if (using_channel) {
+ result = push_notify_channel(a->argv[i], option, notify_cli_channel_data_create);
+ } else {
+ result = push_notify(a->argv[i], option, notify_cli_data_create);
+ }
+ switch(result) {
case INVALID_ENDPOINT:
- ast_cli(a->fd, "Unable to retrieve endpoint %s\n",
- a->argv[i]);
+ ast_cli(a->fd, "Unable to retrieve endpoint %s\n", a->argv[i]);
+ break;
+ case INVALID_CHANNEL:
+ ast_cli(a->fd, "Unable to find channel %s\n", a->argv[i]);
break;
case ALLOC_ERROR:
ast_cli(a->fd, "Unable to allocate NOTIFY task data\n");
@@ -1246,6 +1296,97 @@ static int manager_notify(struct mansession *s, const struct message *m)
return 0;
}
+/*! \brief Convert headers string such as "Event=hold&Event=answer&..." into ast variable list*/
+/* Caller has to call ast_variables_destroy() to free the list*/
+static struct ast_variable *headers_to_variables(const char *headers)
+{
+ struct ast_variable *varlist = NULL;
+ struct ast_variable *var;
+ char *cur;
+ char *header;
+
+ cur = (char *)headers;
+
+ while( (header = strsep(&cur, "&")) ) {
+ char *name;
+ char *value;
+
+ name = value = header;
+ strsep(&value, "=");
+
+ if (!value || ast_strlen_zero(name)) {
+ continue;
+ }
+
+ var = ast_variable_new(name, value, "");
+ var->next = varlist;
+ varlist = var;
+ }
+ return varlist;
+}
+
+/*! \brief Application entry point to send a SIP notify to an endpoint. */
+static int app_notify(struct ast_channel *chan, const char *data)
+{
+ RAII_VAR(struct notify_cfg *, cfg, NULL, ao2_cleanup);
+ RAII_VAR(struct notify_option *, option, NULL, ao2_cleanup);
+
+ struct ast_variable *varlist = NULL;
+ char *tmp;
+ int res;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(to);
+ AST_APP_ARG(headers);
+ );
+
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "PJSIPNotify requires arguments (to, &header=...)\n");
+ return -1;
+ }
+
+ tmp = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, tmp);
+ cfg = ao2_global_obj_ref(globals);
+
+ if (!(option = notify_option_find(cfg->notify_options, args.headers))) {
+ /* If the app is passed a list of headers, use the notify_ami_*_data_create
+ functions as the option data is handled the same way as the ami command. */
+ varlist = headers_to_variables(args.headers);
+ if (ast_strlen_zero(args.to)) {
+ res = push_notify_channel(ast_channel_name(chan), varlist, notify_ami_channel_data_create);
+ } else {
+ res = push_notify_uri(args.to, varlist, notify_ami_uri_data_create);
+ }
+ } else {
+ /* If the app is passed a configured notify option, use the notify_cli_*_data_create
+ functions as the option data is handled the same way as the cli command. */
+ if (ast_strlen_zero(args.to)) {
+ res = push_notify_channel(ast_channel_name(chan), option, notify_cli_channel_data_create);
+ } else {
+ res = push_notify_uri(args.to, option, notify_cli_uri_data_create);
+ }
+ }
+
+ switch (res) {
+ case INVALID_CHANNEL:
+ case INVALID_ENDPOINT:
+ case ALLOC_ERROR:
+ res = -1;
+ ast_variables_destroy(varlist);
+ break;
+ case TASK_PUSH_ERROR:
+ /* Don't need to destroy vars since it is handled by cleanup in push_notify_channel */
+ res = -1;
+ break;
+ case SUCCESS:
+ res = 0;
+ break;
+ }
+
+ return res;
+}
+
static int load_module(void)
{
if (aco_info_init(¬ify_cfg)) {
@@ -1262,6 +1403,7 @@ static int load_module(void)
ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));
ast_manager_register_xml("PJSIPNotify", EVENT_FLAG_SYSTEM, manager_notify);
+ ast_register_application_xml("PJSIPNotify", app_notify);
return AST_MODULE_LOAD_SUCCESS;
}
@@ -1278,6 +1420,7 @@ static int reload_module(void)
static int unload_module(void)
{
ast_manager_unregister("PJSIPNotify");
+ ast_unregister_application("PJSIPNotify");
ast_cli_unregister_multiple(cli_options, ARRAY_LEN(cli_options));
aco_info_destroy(¬ify_cfg);
ao2_global_obj_release(globals);