res_pjsip_notify: add dialplan application

Add dialplan application PJSIPNOTIFY to send either pre-configured
NOTIFY messages from pjsip_notify.conf or with headers defined in
dialplan.

Also adds the ability to send pre-configured NOTIFY commands to a
channel via the CLI.

Resolves: #799

UserNote: A new dialplan application PJSIPNotify is now available
which can send SIP NOTIFY requests from the dialplan.

The pjsip send notify CLI command has also been enhanced to allow
sending NOTIFY messages to a specific channel. Syntax:

pjsip send notify <option> channel <channel>
pull/838/head
Mike Bradeen 10 months ago committed by asterisk-org-access-app[bot]
parent 2328a0ab3a
commit b2455b2732

@ -55,6 +55,46 @@
</description>
</application>
<application name="PJSIPNotify" language="en_US">
<synopsis>
Send a NOTIFY to either an arbitrary URI, or inside a SIP dialog.
</synopsis>
<syntax>
<parameter name="to" required="false">
<para>Abritrary URI to which to send the NOTIFY. If none is specified, send inside
the SIP dialog for the current channel.</para>
</parameter>
<parameter name="content" required="true">
<para>Either an option pre-configured in pjsip_notify.conf or a list of headers and body content to send in the NOTIFY.</para>
</parameter>
</syntax>
<description>
<para>
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.
</para>
<warning><para>
To send a NOTIFY to a specified URI, a default_outbound_endpoint must be configured. This
endpoint determines the message contact.
</para></warning>
<para>
</para>
<example title="Send a NOTIFY with Event and X-Data headers in current dialog">
same = n,PJSIPNotify(,&amp;Event=Test&amp;X-Data=Fun)
</example>
<example title="Send a preconfigured NOTIFY force-answer defined in pjsip_notify.conf in current dialog">
same = n,PJSIPNotify(,force-answer)
</example>
<example title="Send a NOTIFY to &lt;sip:bob@127.0.0.1:5260&gt; with Test Event and X-Data headers">
same = n,PJSIPNotify(&lt;sip:bob@127.0.0.1:5260&gt;,&amp;Event=Test&amp;X-Data=Fun)
</example>
<example title="Send a NOTIFY to &lt;sip:bob@127.0.0.1:5260&gt; with Custom Event and message body">
same = n,PJSIPNotify(&lt;sip:bob@127.0.0.1:5260&gt;,&amp;Event=Custom&amp;Content-type=application&#47;voicemail&amp;Content=check-messages&amp;Content=)
</example>
</description>
</application>
<manager name="PJSIPHangup" language="en_US">
<synopsis>
Hangup an incoming PJSIP channel with a SIP response code

@ -828,3 +828,53 @@ exten => _X.,40000(ani),NoOp(ANI: ${EXTEN})
; "core show functions" will list all dialplan functions
; "core show function <COMMAND>" 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(<sip:bob@127.0.0.1:5260>,&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()

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

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

@ -27,6 +27,7 @@
#include <pjsip.h>
#include <pjsip_ua.h>
#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 <type> {endpoint|uri} <peer> [<peer>...]\n"
"Usage: pjsip send notify <type> {endpoint|uri|channel} <peer> [<peer>...]\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(&notify_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(&notify_cfg);
ao2_global_obj_release(globals);

Loading…
Cancel
Save