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