mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-03 11:25:35 +00:00
core/ari/pjsip: Add refer mechanism
This change adds support for refers that are not session based. It includes a refer implementation for the PJSIP technology which results in out-of-dialog REFERs being sent to a PJSIP endpoint. These can be triggered using the new ARI endpoint `/endpoints/refer`. Resolves: #71 UserNote: There is a new ARI endpoint `/endpoints/refer` for referring an endpoint to some URI or endpoint.
This commit is contained in:
@@ -33,6 +33,7 @@
|
||||
#include "asterisk/stasis_endpoints.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/message.h"
|
||||
#include "asterisk/refer.h"
|
||||
|
||||
void ast_ari_endpoints_list(struct ast_variable *headers,
|
||||
struct ast_ari_endpoints_list_args *args,
|
||||
@@ -307,3 +308,135 @@ void ast_ari_endpoints_send_message_to_endpoint(struct ast_variable *headers,
|
||||
send_message(msg_to, args->from, args->body, variables, response);
|
||||
ast_variables_destroy(variables);
|
||||
}
|
||||
|
||||
static void send_refer(const char *to, const char *from, const char *refer_to, int to_self, struct ast_variable *variables, struct ast_ari_response *response)
|
||||
{
|
||||
struct ast_variable *current;
|
||||
struct ast_refer *refer;
|
||||
int res = 0;
|
||||
|
||||
if (ast_strlen_zero(to)) {
|
||||
ast_ari_response_error(response, 400, "Bad Request",
|
||||
"To must be specified");
|
||||
return;
|
||||
}
|
||||
|
||||
refer = ast_refer_alloc();
|
||||
if (!refer) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
return;
|
||||
}
|
||||
|
||||
ast_refer_set_to(refer, "%s", to);
|
||||
ast_refer_set_to_self(refer, to_self);
|
||||
|
||||
if (!ast_strlen_zero(from)) {
|
||||
ast_refer_set_from(refer, "%s", from);
|
||||
}
|
||||
if (!ast_strlen_zero(refer_to)) {
|
||||
ast_refer_set_refer_to(refer, "%s", refer_to);
|
||||
}
|
||||
|
||||
for (current = variables; current; current = current->next) {
|
||||
res |= ast_refer_set_var_outbound(refer, current->name, current->value);
|
||||
}
|
||||
|
||||
if (res) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
ast_refer_destroy(refer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ast_refer_send(refer)) {
|
||||
ast_ari_response_error(response, 404, "Not Found",
|
||||
"Endpoint not found");
|
||||
return;
|
||||
}
|
||||
|
||||
response->message = ast_json_null();
|
||||
response->response_code = 202;
|
||||
response->response_text = "Accepted";
|
||||
}
|
||||
|
||||
static int parse_refer_json(struct ast_json *body,
|
||||
struct ast_ari_response *response,
|
||||
struct ast_variable **variables)
|
||||
{
|
||||
const char *known_variables[] = { "display_name" };
|
||||
const char *value;
|
||||
struct ast_variable *new_var;
|
||||
struct ast_json *json_variable;
|
||||
int err = 0;
|
||||
int i;
|
||||
|
||||
if (!body) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
json_variable = ast_json_object_get(body, "variables");
|
||||
if (json_variable) {
|
||||
err = json_to_ast_variables(response, json_variable, variables);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < sizeof(known_variables) / sizeof(*known_variables); ++i) {
|
||||
json_variable = ast_json_object_get(body, known_variables[i]);
|
||||
if (json_variable && ast_json_typeof(json_variable) == AST_JSON_STRING) {
|
||||
value = ast_json_string_get(json_variable);
|
||||
new_var = ast_variable_new(known_variables[i], value, "");
|
||||
if (new_var) {
|
||||
ast_variable_list_append(variables, new_var);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void ast_ari_endpoints_refer(struct ast_variable *headers,
|
||||
struct ast_ari_endpoints_refer_args *args,
|
||||
struct ast_ari_response *response)
|
||||
{
|
||||
struct ast_variable *variables = NULL;
|
||||
|
||||
ast_ari_endpoints_refer_parse_body(args->variables, args);
|
||||
|
||||
if (parse_refer_json(args->variables, response, &variables)) {
|
||||
return;
|
||||
}
|
||||
|
||||
send_refer(args->to, args->from, args->refer_to, args->to_self, variables, response);
|
||||
ast_variables_destroy(variables);
|
||||
}
|
||||
|
||||
void ast_ari_endpoints_refer_to_endpoint(struct ast_variable *headers,
|
||||
struct ast_ari_endpoints_refer_to_endpoint_args *args,
|
||||
struct ast_ari_response *response)
|
||||
{
|
||||
struct ast_variable *variables = NULL;
|
||||
struct ast_endpoint_snapshot *snapshot;
|
||||
char to[128];
|
||||
char *tech = ast_strdupa(args->tech);
|
||||
|
||||
/* Really, we just want to know if this thing exists */
|
||||
snapshot = ast_endpoint_latest_snapshot(args->tech, args->resource);
|
||||
if (!snapshot) {
|
||||
ast_ari_response_error(response, 404, "Not Found",
|
||||
"Endpoint not found");
|
||||
return;
|
||||
}
|
||||
ao2_ref(snapshot, -1);
|
||||
|
||||
ast_ari_endpoints_refer_to_endpoint_parse_body(args->variables, args);
|
||||
|
||||
if (parse_refer_json(args->variables, response, &variables)) {
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(to, sizeof(to), "%s:%s", ast_str_to_lower(tech), args->resource);
|
||||
|
||||
send_refer(to, args->from, args->refer_to, args->to_self, variables, response);
|
||||
ast_variables_destroy(variables);
|
||||
}
|
||||
|
@@ -58,6 +58,7 @@ struct ast_ari_endpoints_send_message_args {
|
||||
const char *from;
|
||||
/*! The body of the message */
|
||||
const char *body;
|
||||
/*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip and sip resource types will add the key/value pairs as SIP headers, */
|
||||
struct ast_json *variables;
|
||||
};
|
||||
/*!
|
||||
@@ -79,6 +80,38 @@ int ast_ari_endpoints_send_message_parse_body(
|
||||
* \param[out] response HTTP response
|
||||
*/
|
||||
void ast_ari_endpoints_send_message(struct ast_variable *headers, struct ast_ari_endpoints_send_message_args *args, struct ast_ari_response *response);
|
||||
/*! Argument struct for ast_ari_endpoints_refer() */
|
||||
struct ast_ari_endpoints_refer_args {
|
||||
/*! The endpoint resource or technology specific URI that should be referred to somewhere. Valid resource is pjsip. */
|
||||
const char *to;
|
||||
/*! The endpoint resource or technology specific identity to refer from. */
|
||||
const char *from;
|
||||
/*! The endpoint resource or technology specific URI to refer to. */
|
||||
const char *refer_to;
|
||||
/*! If true and "refer_to" refers to an Asterisk endpoint, the "refer_to" value is set to point to this Asterisk endpoint - so the referee is referred to Asterisk. Otherwise, use the contact URI associated with the endpoint. */
|
||||
int to_self;
|
||||
/*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, the pjsip resource type will add the key/value pairs as SIP headers. The "display_name" key is used by the PJSIP technology. Its value will be prepended as a display name to the Refer-To URI. */
|
||||
struct ast_json *variables;
|
||||
};
|
||||
/*!
|
||||
* \brief Body parsing function for /endpoints/refer.
|
||||
* \param body The JSON body from which to parse parameters.
|
||||
* \param[out] args The args structure to parse into.
|
||||
* \retval zero on success
|
||||
* \retval non-zero on failure
|
||||
*/
|
||||
int ast_ari_endpoints_refer_parse_body(
|
||||
struct ast_json *body,
|
||||
struct ast_ari_endpoints_refer_args *args);
|
||||
|
||||
/*!
|
||||
* \brief Refer an endpoint or technology URI to some technology URI or endpoint.
|
||||
*
|
||||
* \param headers HTTP headers
|
||||
* \param args Swagger parameters
|
||||
* \param[out] response HTTP response
|
||||
*/
|
||||
void ast_ari_endpoints_refer(struct ast_variable *headers, struct ast_ari_endpoints_refer_args *args, struct ast_ari_response *response);
|
||||
/*! Argument struct for ast_ari_endpoints_list_by_tech() */
|
||||
struct ast_ari_endpoints_list_by_tech_args {
|
||||
/*! Technology of the endpoints (sip,iax2,...) */
|
||||
@@ -117,6 +150,7 @@ struct ast_ari_endpoints_send_message_to_endpoint_args {
|
||||
const char *from;
|
||||
/*! The body of the message */
|
||||
const char *body;
|
||||
/*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip and sip resource types will add the key/value pairs as SIP headers, */
|
||||
struct ast_json *variables;
|
||||
};
|
||||
/*!
|
||||
@@ -138,5 +172,39 @@ int ast_ari_endpoints_send_message_to_endpoint_parse_body(
|
||||
* \param[out] response HTTP response
|
||||
*/
|
||||
void ast_ari_endpoints_send_message_to_endpoint(struct ast_variable *headers, struct ast_ari_endpoints_send_message_to_endpoint_args *args, struct ast_ari_response *response);
|
||||
/*! Argument struct for ast_ari_endpoints_refer_to_endpoint() */
|
||||
struct ast_ari_endpoints_refer_to_endpoint_args {
|
||||
/*! Technology of the endpoint */
|
||||
const char *tech;
|
||||
/*! ID of the endpoint */
|
||||
const char *resource;
|
||||
/*! The endpoint resource or technology specific identity to refer from. */
|
||||
const char *from;
|
||||
/*! The endpoint resource or technology specific URI to refer to. */
|
||||
const char *refer_to;
|
||||
/*! If true and "refer_to" refers to an Asterisk endpoint, the "refer_to" value is set to point to this Asterisk endpoint - so the referee is referred to Asterisk. Otherwise, use the contact URI associated with the endpoint. */
|
||||
int to_self;
|
||||
/*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, the pjsip resource type will add the key/value pairs as SIP headers, */
|
||||
struct ast_json *variables;
|
||||
};
|
||||
/*!
|
||||
* \brief Body parsing function for /endpoints/{tech}/{resource}/refer.
|
||||
* \param body The JSON body from which to parse parameters.
|
||||
* \param[out] args The args structure to parse into.
|
||||
* \retval zero on success
|
||||
* \retval non-zero on failure
|
||||
*/
|
||||
int ast_ari_endpoints_refer_to_endpoint_parse_body(
|
||||
struct ast_json *body,
|
||||
struct ast_ari_endpoints_refer_to_endpoint_args *args);
|
||||
|
||||
/*!
|
||||
* \brief Refer an endpoint or technology URI to some technology URI or endpoint.
|
||||
*
|
||||
* \param headers HTTP headers
|
||||
* \param args Swagger parameters
|
||||
* \param[out] response HTTP response
|
||||
*/
|
||||
void ast_ari_endpoints_refer_to_endpoint(struct ast_variable *headers, struct ast_ari_endpoints_refer_to_endpoint_args *args, struct ast_ari_response *response);
|
||||
|
||||
#endif /* _ASTERISK_RESOURCE_ENDPOINTS_H */
|
||||
|
@@ -188,6 +188,102 @@ static void ast_ari_endpoints_send_message_cb(
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
return;
|
||||
}
|
||||
int ast_ari_endpoints_refer_parse_body(
|
||||
struct ast_json *body,
|
||||
struct ast_ari_endpoints_refer_args *args)
|
||||
{
|
||||
struct ast_json *field;
|
||||
/* Parse query parameters out of it */
|
||||
field = ast_json_object_get(body, "to");
|
||||
if (field) {
|
||||
args->to = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "from");
|
||||
if (field) {
|
||||
args->from = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "refer_to");
|
||||
if (field) {
|
||||
args->refer_to = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "to_self");
|
||||
if (field) {
|
||||
args->to_self = ast_json_is_true(field);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /endpoints/refer.
|
||||
* \param ser TCP/TLS session object
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
* \param body
|
||||
* \param[out] response Response to the HTTP request.
|
||||
*/
|
||||
static void ast_ari_endpoints_refer_cb(
|
||||
struct ast_tcptls_session_instance *ser,
|
||||
struct ast_variable *get_params, struct ast_variable *path_vars,
|
||||
struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
|
||||
{
|
||||
struct ast_ari_endpoints_refer_args args = {};
|
||||
struct ast_variable *i;
|
||||
#if defined(AST_DEVMODE)
|
||||
int is_valid;
|
||||
int code;
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
for (i = get_params; i; i = i->next) {
|
||||
if (strcmp(i->name, "to") == 0) {
|
||||
args.to = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "from") == 0) {
|
||||
args.from = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "refer_to") == 0) {
|
||||
args.refer_to = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "to_self") == 0) {
|
||||
args.to_self = ast_true(i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
args.variables = body;
|
||||
ast_ari_endpoints_refer(headers, &args, response);
|
||||
#if defined(AST_DEVMODE)
|
||||
code = response->response_code;
|
||||
|
||||
switch (code) {
|
||||
case 0: /* Implementation is still a stub, or the code wasn't set */
|
||||
is_valid = response->message == NULL;
|
||||
break;
|
||||
case 500: /* Internal Server Error */
|
||||
case 501: /* Not Implemented */
|
||||
case 400: /* Invalid parameters for referring. */
|
||||
case 404: /* Endpoint not found */
|
||||
is_valid = 1;
|
||||
break;
|
||||
default:
|
||||
if (200 <= code && code <= 299) {
|
||||
is_valid = ast_ari_validate_void(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /endpoints/refer\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /endpoints/refer\n");
|
||||
ast_ari_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
return;
|
||||
}
|
||||
@@ -403,6 +499,104 @@ static void ast_ari_endpoints_send_message_to_endpoint_cb(
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
return;
|
||||
}
|
||||
int ast_ari_endpoints_refer_to_endpoint_parse_body(
|
||||
struct ast_json *body,
|
||||
struct ast_ari_endpoints_refer_to_endpoint_args *args)
|
||||
{
|
||||
struct ast_json *field;
|
||||
/* Parse query parameters out of it */
|
||||
field = ast_json_object_get(body, "from");
|
||||
if (field) {
|
||||
args->from = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "refer_to");
|
||||
if (field) {
|
||||
args->refer_to = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "to_self");
|
||||
if (field) {
|
||||
args->to_self = ast_json_is_true(field);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /endpoints/{tech}/{resource}/refer.
|
||||
* \param ser TCP/TLS session object
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
* \param body
|
||||
* \param[out] response Response to the HTTP request.
|
||||
*/
|
||||
static void ast_ari_endpoints_refer_to_endpoint_cb(
|
||||
struct ast_tcptls_session_instance *ser,
|
||||
struct ast_variable *get_params, struct ast_variable *path_vars,
|
||||
struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
|
||||
{
|
||||
struct ast_ari_endpoints_refer_to_endpoint_args args = {};
|
||||
struct ast_variable *i;
|
||||
#if defined(AST_DEVMODE)
|
||||
int is_valid;
|
||||
int code;
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
for (i = get_params; i; i = i->next) {
|
||||
if (strcmp(i->name, "from") == 0) {
|
||||
args.from = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "refer_to") == 0) {
|
||||
args.refer_to = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "to_self") == 0) {
|
||||
args.to_self = ast_true(i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "tech") == 0) {
|
||||
args.tech = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "resource") == 0) {
|
||||
args.resource = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
args.variables = body;
|
||||
ast_ari_endpoints_refer_to_endpoint(headers, &args, response);
|
||||
#if defined(AST_DEVMODE)
|
||||
code = response->response_code;
|
||||
|
||||
switch (code) {
|
||||
case 0: /* Implementation is still a stub, or the code wasn't set */
|
||||
is_valid = response->message == NULL;
|
||||
break;
|
||||
case 500: /* Internal Server Error */
|
||||
case 501: /* Not Implemented */
|
||||
case 400: /* Invalid parameters for referring. */
|
||||
case 404: /* Endpoint not found */
|
||||
is_valid = 1;
|
||||
break;
|
||||
default:
|
||||
if (200 <= code && code <= 299) {
|
||||
is_valid = ast_ari_validate_void(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /endpoints/{tech}/{resource}/refer\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /endpoints/{tech}/{resource}/refer\n");
|
||||
ast_ari_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
return;
|
||||
}
|
||||
@@ -417,6 +611,15 @@ static struct stasis_rest_handlers endpoints_sendMessage = {
|
||||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/endpoints.json */
|
||||
static struct stasis_rest_handlers endpoints_refer = {
|
||||
.path_segment = "refer",
|
||||
.callbacks = {
|
||||
[AST_HTTP_POST] = ast_ari_endpoints_refer_cb,
|
||||
},
|
||||
.num_children = 0,
|
||||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/endpoints.json */
|
||||
static struct stasis_rest_handlers endpoints_tech_resource_sendMessage = {
|
||||
.path_segment = "sendMessage",
|
||||
.callbacks = {
|
||||
@@ -426,14 +629,23 @@ static struct stasis_rest_handlers endpoints_tech_resource_sendMessage = {
|
||||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/endpoints.json */
|
||||
static struct stasis_rest_handlers endpoints_tech_resource_refer = {
|
||||
.path_segment = "refer",
|
||||
.callbacks = {
|
||||
[AST_HTTP_POST] = ast_ari_endpoints_refer_to_endpoint_cb,
|
||||
},
|
||||
.num_children = 0,
|
||||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/endpoints.json */
|
||||
static struct stasis_rest_handlers endpoints_tech_resource = {
|
||||
.path_segment = "resource",
|
||||
.is_wildcard = 1,
|
||||
.callbacks = {
|
||||
[AST_HTTP_GET] = ast_ari_endpoints_get_cb,
|
||||
},
|
||||
.num_children = 1,
|
||||
.children = { &endpoints_tech_resource_sendMessage, }
|
||||
.num_children = 2,
|
||||
.children = { &endpoints_tech_resource_sendMessage,&endpoints_tech_resource_refer, }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/endpoints.json */
|
||||
static struct stasis_rest_handlers endpoints_tech = {
|
||||
@@ -451,8 +663,8 @@ static struct stasis_rest_handlers endpoints = {
|
||||
.callbacks = {
|
||||
[AST_HTTP_GET] = ast_ari_endpoints_list_cb,
|
||||
},
|
||||
.num_children = 2,
|
||||
.children = { &endpoints_sendMessage,&endpoints_tech, }
|
||||
.num_children = 3,
|
||||
.children = { &endpoints_sendMessage,&endpoints_refer,&endpoints_tech, }
|
||||
};
|
||||
|
||||
static int unload_module(void)
|
||||
|
714
res/res_pjsip.c
714
res/res_pjsip.c
@@ -27,6 +27,8 @@
|
||||
#include <pjmedia/errno.h>
|
||||
|
||||
#include "asterisk/res_pjsip.h"
|
||||
#include "asterisk/strings.h"
|
||||
#include "pjsip/sip_parser.h"
|
||||
#include "res_pjsip/include/res_pjsip_private.h"
|
||||
#include "asterisk/linkedlists.h"
|
||||
#include "asterisk/logger.h"
|
||||
@@ -48,6 +50,7 @@
|
||||
#include "asterisk/res_pjsip_presence_xml.h"
|
||||
#include "asterisk/res_pjproject.h"
|
||||
#include "asterisk/utf8.h"
|
||||
#include "asterisk/acl.h"
|
||||
|
||||
/*** MODULEINFO
|
||||
<depend>pjproject</depend>
|
||||
@@ -558,6 +561,140 @@ int ast_sip_will_uri_survive_restart(pjsip_sip_uri *uri, struct ast_sip_endpoint
|
||||
return result;
|
||||
}
|
||||
|
||||
pjsip_sip_uri *ast_sip_get_contact_sip_uri(pjsip_tx_data *tdata)
|
||||
{
|
||||
pjsip_contact_hdr *contact = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
|
||||
|
||||
if (!contact || (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri))) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return pjsip_uri_get_uri(contact->uri);
|
||||
}
|
||||
|
||||
/*! \brief Callback function for finding the transport the request is going out on */
|
||||
static int find_transport_state_in_use(void *obj, void *arg, int flags)
|
||||
{
|
||||
struct ast_sip_transport_state *transport_state = obj;
|
||||
struct ast_sip_request_transport_details *details = arg;
|
||||
|
||||
/* If an explicit transport or factory matches then this is what is in use, if we are unavailable
|
||||
* to compare based on that we make sure that the type is the same and the source IP address/port are the same
|
||||
*/
|
||||
if (transport_state && ((details->transport && details->transport == transport_state->transport) ||
|
||||
(details->factory && details->factory == transport_state->factory) ||
|
||||
((details->type == transport_state->type) && (transport_state->factory) &&
|
||||
!pj_strcmp(&transport_state->factory->addr_name.host, &details->local_address) &&
|
||||
transport_state->factory->addr_name.port == details->local_port))) {
|
||||
return CMP_MATCH | CMP_STOP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ast_sip_transport_state *ast_sip_find_transport_state_in_use(struct ast_sip_request_transport_details *details) {
|
||||
RAII_VAR(struct ao2_container *, transport_states, NULL, ao2_cleanup);
|
||||
|
||||
if (!(transport_states = ast_sip_get_transport_states())) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ao2_callback(transport_states, 0, find_transport_state_in_use, details);
|
||||
}
|
||||
|
||||
int ast_sip_rewrite_uri_to_local(pjsip_sip_uri *uri, pjsip_tx_data *tdata) {
|
||||
RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
|
||||
struct ast_sip_request_transport_details details;
|
||||
pjsip_sip_uri *tmp_uri;
|
||||
pjsip_dialog *dlg;
|
||||
struct ast_sockaddr addr = { { 0, } };
|
||||
|
||||
if ((tmp_uri = ast_sip_get_contact_sip_uri(tdata))) {
|
||||
pj_strdup(tdata->pool, &uri->host, &tmp_uri->host);
|
||||
uri->port = tmp_uri->port;
|
||||
} else if ((dlg = pjsip_tdata_get_dlg(tdata))
|
||||
&& (tmp_uri = pjsip_uri_get_uri(dlg->local.info->uri))
|
||||
&& (PJSIP_URI_SCHEME_IS_SIP(tmp_uri) || PJSIP_URI_SCHEME_IS_SIPS(tmp_uri))) {
|
||||
pj_strdup(tdata->pool, &uri->host, &tmp_uri->host);
|
||||
uri->port = tmp_uri->port;
|
||||
}
|
||||
|
||||
if (ast_sip_set_request_transport_details(&details, tdata, 1)
|
||||
|| !(transport_state = ast_sip_find_transport_state_in_use(&details))
|
||||
|| !(transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (transport_state->localnet) {
|
||||
ast_sockaddr_parse(&addr, tdata->tp_info.dst_name, PARSE_PORT_FORBID);
|
||||
ast_sockaddr_set_port(&addr, tdata->tp_info.dst_port);
|
||||
if (ast_sip_transport_is_local(transport_state, &addr)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ast_sockaddr_isnull(&transport_state->external_signaling_address)) {
|
||||
pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
|
||||
}
|
||||
|
||||
if (transport->external_signaling_port) {
|
||||
uri->port = transport->external_signaling_port;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ast_sip_set_request_transport_details(struct ast_sip_request_transport_details *details, pjsip_tx_data *tdata,
|
||||
int use_ipv6) {
|
||||
pjsip_sip_uri *uri;
|
||||
pjsip_via_hdr *via;
|
||||
long transport_type;
|
||||
|
||||
if (!details || !tdata) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* If IPv6 should be considered, un-set Bit 7 to make TCP6 equal to TCP and TLS6 equal to TLS */
|
||||
transport_type = use_ipv6 ? tdata->tp_info.transport->key.type & ~(PJSIP_TRANSPORT_IPV6)
|
||||
: tdata->tp_info.transport->key.type;
|
||||
|
||||
if (tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
|
||||
details->transport = tdata->tp_sel.u.transport;
|
||||
} else if (tdata->tp_sel.type == PJSIP_TPSELECTOR_LISTENER) {
|
||||
details->factory = tdata->tp_sel.u.listener;
|
||||
} else if (transport_type == PJSIP_TRANSPORT_UDP || transport_type == PJSIP_TRANSPORT_UDP6) {
|
||||
/* Connectionless uses the same transport for all requests */
|
||||
details->type = AST_TRANSPORT_UDP;
|
||||
details->transport = tdata->tp_info.transport;
|
||||
} else {
|
||||
if (transport_type == PJSIP_TRANSPORT_TCP) {
|
||||
details->type = AST_TRANSPORT_TCP;
|
||||
} else if (transport_type == PJSIP_TRANSPORT_TLS) {
|
||||
details->type = AST_TRANSPORT_TLS;
|
||||
} else {
|
||||
/* Unknown transport type, we can't map. */
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((uri = ast_sip_get_contact_sip_uri(tdata))) {
|
||||
details->local_address = uri->host;
|
||||
details->local_port = uri->port;
|
||||
} else if ((tdata->msg->type == PJSIP_REQUEST_MSG) &&
|
||||
(via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL))) {
|
||||
details->local_address = via->sent_by.host;
|
||||
details->local_port = via->sent_by.port;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!details->local_port) {
|
||||
details->local_port = (details->type == AST_TRANSPORT_TLS) ? 5061 : 5060;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ast_sip_get_transport_name(const struct ast_sip_endpoint *endpoint,
|
||||
pjsip_sip_uri *sip_uri, char *buf, size_t buf_len)
|
||||
{
|
||||
@@ -835,7 +972,11 @@ pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint,
|
||||
pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
|
||||
static const pj_str_t HCONTACT = { "Contact", 7 };
|
||||
|
||||
snprintf(enclosed_uri, sizeof(enclosed_uri), "<%s>", uri);
|
||||
if (!ast_begins_with(uri, "<")) {
|
||||
snprintf(enclosed_uri, sizeof(enclosed_uri), "<%s>", uri);
|
||||
} else {
|
||||
snprintf(enclosed_uri, sizeof(enclosed_uri), "%s", uri);
|
||||
}
|
||||
pj_cstr(&remote_uri, enclosed_uri);
|
||||
|
||||
pj_cstr(&target_uri, uri);
|
||||
@@ -1130,6 +1271,7 @@ int ast_sip_create_rdata(pjsip_rx_data *rdata, char *packet, const char *src_nam
|
||||
/* PJSIP doesn't know about the INFO method, so we have to define it ourselves */
|
||||
static const pjsip_method info_method = {PJSIP_OTHER_METHOD, {"INFO", 4} };
|
||||
static const pjsip_method message_method = {PJSIP_OTHER_METHOD, {"MESSAGE", 7} };
|
||||
static const pjsip_method refer_method = {PJSIP_OTHER_METHOD, {"REFER", 5} };
|
||||
|
||||
static struct {
|
||||
const char *method;
|
||||
@@ -1146,6 +1288,7 @@ static struct {
|
||||
{ "PUBLISH", &pjsip_publish_method },
|
||||
{ "INFO", &info_method },
|
||||
{ "MESSAGE", &message_method },
|
||||
{ "REFER", &refer_method },
|
||||
};
|
||||
|
||||
static const pjsip_method *get_pjsip_method(const char *method)
|
||||
@@ -2727,6 +2870,575 @@ void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr, const s
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Find a contact and insert a "user@" into its URI.
|
||||
*
|
||||
* \param to Original destination (for error messages only)
|
||||
* \param endpoint_name Endpoint name (for error messages only)
|
||||
* \param aors Command separated list of AORs
|
||||
* \param user The user to insert in the contact URI
|
||||
* \param uri Pointer to buffer in which to return the URI. Must be freed by caller.
|
||||
*
|
||||
* \return 0 Success
|
||||
* \return -1 Fail
|
||||
*
|
||||
* \note If the contact URI found for the endpoint already has a user in
|
||||
* its URI, it will be replaced by the user passed as an argument to this function.
|
||||
*/
|
||||
static int insert_user_in_contact_uri(const char *to, const char *endpoint_name, const char *aors,
|
||||
const char *user, char **uri)
|
||||
{
|
||||
RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
|
||||
pj_pool_t *pool;
|
||||
pjsip_name_addr *name_addr;
|
||||
pjsip_sip_uri *sip_uri;
|
||||
int err = 0;
|
||||
|
||||
contact = ast_sip_location_retrieve_contact_from_aor_list(aors);
|
||||
if (!contact) {
|
||||
ast_log(LOG_WARNING, "Dest: '%s'. Couldn't find contact for endpoint '%s'\n",
|
||||
to, endpoint_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "uri-user-insert", 128, 128);
|
||||
if (!pool) {
|
||||
ast_log(LOG_WARNING, "Failed to allocate ParseUri endpoint pool.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
name_addr = (pjsip_name_addr *) pjsip_parse_uri(pool, (char*)contact->uri, strlen(contact->uri), PJSIP_PARSE_URI_AS_NAMEADDR);
|
||||
if (!name_addr || (!PJSIP_URI_SCHEME_IS_SIP(name_addr->uri) && !PJSIP_URI_SCHEME_IS_SIPS(name_addr->uri))) {
|
||||
ast_log(LOG_WARNING, "Failed to parse URI '%s'\n", contact->uri);
|
||||
err = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s' ContactURI: '%s'\n", to, user, endpoint_name, contact->uri);
|
||||
|
||||
sip_uri = pjsip_uri_get_uri(name_addr->uri);
|
||||
pj_strset2(&sip_uri->user, (char*)user);
|
||||
|
||||
*uri = ast_malloc(PJSIP_MAX_URL_SIZE);
|
||||
if (!(*uri)) {
|
||||
err = -1;
|
||||
goto out;
|
||||
}
|
||||
pjsip_uri_print(PJSIP_URI_IN_REQ_URI, name_addr, *uri, PJSIP_MAX_URL_SIZE);
|
||||
|
||||
out:
|
||||
pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Get endpoint and URI when the destination is only a single token
|
||||
*
|
||||
* "destination" could be one of the following:
|
||||
* \verbatim
|
||||
endpoint_name
|
||||
hostname
|
||||
* \endverbatim
|
||||
*
|
||||
* \param to
|
||||
* \param destination
|
||||
* \param get_default_outbound If nonzero, try to retrieve the default
|
||||
* outbound endpoint if no endpoint was found.
|
||||
* Otherwise, return NULL if no endpoint was found.
|
||||
* \param uri Pointer to URI variable. Must be freed by caller - even if the return value is NULL!
|
||||
* \return endpoint
|
||||
*/
|
||||
static struct ast_sip_endpoint *handle_single_token(const char *to, char *destination, int get_default_outbound, char **uri) {
|
||||
RAII_VAR(struct ast_sip_contact*, contact, NULL, ao2_cleanup);
|
||||
char *endpoint_name = NULL;
|
||||
struct ast_sip_endpoint *endpoint = NULL;
|
||||
|
||||
/*
|
||||
* If "destination" is just one token, it could be an endpoint name
|
||||
* or a hostname without a scheme.
|
||||
*/
|
||||
|
||||
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", destination);
|
||||
if (!endpoint) {
|
||||
/*
|
||||
* We can only assume it's a hostname.
|
||||
*/
|
||||
char *temp_uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
|
||||
if (!temp_uri) {
|
||||
goto failure;
|
||||
}
|
||||
sprintf(temp_uri, "sip:%s", destination);
|
||||
*uri = temp_uri;
|
||||
if (get_default_outbound) {
|
||||
endpoint = ast_sip_default_outbound_endpoint();
|
||||
}
|
||||
ast_debug(3, "Dest: '%s' Didn't find endpoint so adding scheme and using URI '%s'%s\n",
|
||||
to, *uri, get_default_outbound ? " with default endpoint" : "");
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/*
|
||||
* It's an endpoint
|
||||
*/
|
||||
|
||||
endpoint_name = destination;
|
||||
contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
|
||||
if (!contact) {
|
||||
ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find an aor/contact for it\n",
|
||||
to, endpoint_name);
|
||||
ao2_cleanup(endpoint);
|
||||
goto failure;
|
||||
}
|
||||
|
||||
*uri = ast_strdup(contact->uri);
|
||||
if (!(*uri)) {
|
||||
ao2_cleanup(endpoint);
|
||||
goto failure;
|
||||
}
|
||||
|
||||
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s'\n",
|
||||
to, endpoint_name, *uri);
|
||||
return endpoint;
|
||||
|
||||
failure:
|
||||
*uri = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Get endpoint and URI when the destination contained a '/'
|
||||
*
|
||||
* "to" could be one of the following:
|
||||
* \verbatim
|
||||
endpoint/aor
|
||||
endpoint/<sip[s]:host>
|
||||
endpoint/<sip[s]:user@host>
|
||||
endpoint/"Bob" <sip[s]:host>
|
||||
endpoint/"Bob" <sip[s]:user@host>
|
||||
endpoint/sip[s]:host
|
||||
endpoint/sip[s]:user@host
|
||||
endpoint/host
|
||||
endpoint/user@host
|
||||
* \endverbatim
|
||||
*
|
||||
* \param to Destination
|
||||
* \param uri Pointer to URI variable. Must be freed by caller - even if the return value is NULL!
|
||||
* \param destination, slash, atsign, scheme
|
||||
* \return endpoint
|
||||
*/
|
||||
static struct ast_sip_endpoint *handle_slash(const char *to, char *destination, char **uri,
|
||||
char *slash, char *atsign, char *scheme)
|
||||
{
|
||||
char *endpoint_name = NULL;
|
||||
struct ast_sip_endpoint *endpoint = NULL;
|
||||
struct ast_sip_contact *contact = NULL;
|
||||
char *user = NULL;
|
||||
char *afterslash = slash + 1;
|
||||
struct ast_sip_aor *aor;
|
||||
|
||||
if (ast_begins_with(destination, "PJSIP/")) {
|
||||
ast_debug(3, "Dest: '%s' Dialplan format'\n", to);
|
||||
/*
|
||||
* This has to be the form PJSIP/user@endpoint
|
||||
*/
|
||||
if (!atsign || strchr(afterslash, '/')) {
|
||||
/*
|
||||
* If there's no "user@" or there's a slash somewhere after
|
||||
* "PJSIP/" then we go no further.
|
||||
*/
|
||||
ast_log(LOG_WARNING,
|
||||
"Dest: '%s'. Destinations beginning with 'PJSIP/' must be in the form of 'PJSIP/user@endpoint'\n",
|
||||
to);
|
||||
goto failure;
|
||||
}
|
||||
*atsign = '\0';
|
||||
user = afterslash;
|
||||
endpoint_name = atsign + 1;
|
||||
ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s'\n", to, user, endpoint_name);
|
||||
} else {
|
||||
/*
|
||||
* Either...
|
||||
* endpoint/aor
|
||||
* endpoint/uri
|
||||
*/
|
||||
*slash = '\0';
|
||||
endpoint_name = destination;
|
||||
ast_debug(3, "Dest: '%s' Endpoint: '%s'\n", to, endpoint_name);
|
||||
}
|
||||
|
||||
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
|
||||
if (!endpoint) {
|
||||
ast_log(LOG_WARNING, "Dest: '%s'. Didn't find endpoint with name '%s'\n",
|
||||
to, endpoint_name);
|
||||
goto failure;
|
||||
}
|
||||
|
||||
if (scheme) {
|
||||
/*
|
||||
* If we found a scheme, then everything after the slash MUST be a URI.
|
||||
* We don't need to do any further modification.
|
||||
*/
|
||||
*uri = ast_strdup(afterslash);
|
||||
if (!(*uri)) {
|
||||
goto failure;
|
||||
}
|
||||
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found URI '%s' after '/'\n",
|
||||
to, endpoint_name, *uri);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
if (user) {
|
||||
/*
|
||||
* This has to be the form PJSIP/user@endpoint
|
||||
*/
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* Set the return URI to be the endpoint's contact URI with the user
|
||||
* portion set to the user that was specified before the endpoint name.
|
||||
*/
|
||||
rc = insert_user_in_contact_uri(to, endpoint_name, endpoint->aors, user, uri);
|
||||
if (rc != 0) {
|
||||
/*
|
||||
* insert_user_in_contact_uri prints the warning message.
|
||||
*/
|
||||
goto failure;
|
||||
}
|
||||
ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s' URI: '%s'\n", to, user,
|
||||
endpoint_name, *uri);
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/*
|
||||
* We're now left with two possibilities...
|
||||
* endpoint/aor
|
||||
* endpoint/uri-without-scheme
|
||||
*/
|
||||
aor = ast_sip_location_retrieve_aor(afterslash);
|
||||
if (!aor) {
|
||||
/*
|
||||
* It's probably a URI without a scheme but we don't have a way to tell
|
||||
* for sure. We're going to assume it is and prepend it with a scheme.
|
||||
*/
|
||||
*uri = ast_malloc(strlen(afterslash) + strlen("sip:") + 1);
|
||||
if (!(*uri)) {
|
||||
goto failure;
|
||||
}
|
||||
sprintf(*uri, "sip:%s", afterslash);
|
||||
ast_debug(3, "Dest: '%s' Found endpoint '%s' but didn't find aor after '/' so using URI '%s'\n",
|
||||
to, endpoint_name, *uri);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only one possibility left... There was an aor name after the slash.
|
||||
*/
|
||||
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found aor '%s' after '/'\n",
|
||||
to, endpoint_name, ast_sorcery_object_get_id(aor));
|
||||
|
||||
contact = ast_sip_location_retrieve_first_aor_contact(aor);
|
||||
if (!contact) {
|
||||
ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find contact for aor '%s'\n",
|
||||
to, endpoint_name, ast_sorcery_object_get_id(aor));
|
||||
ao2_cleanup(aor);
|
||||
goto failure;
|
||||
}
|
||||
|
||||
*uri = ast_strdup(contact->uri);
|
||||
ao2_cleanup(contact);
|
||||
ao2_cleanup(aor);
|
||||
if (!(*uri)) {
|
||||
goto failure;
|
||||
}
|
||||
|
||||
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' for aor '%s'\n",
|
||||
to, endpoint_name, *uri, ast_sorcery_object_get_id(aor));
|
||||
|
||||
return endpoint;
|
||||
|
||||
failure:
|
||||
ao2_cleanup(endpoint);
|
||||
*uri = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Get endpoint and URI when the destination contained a '@' but no '/' or scheme
|
||||
*
|
||||
* "to" could be one of the following:
|
||||
* \verbatim
|
||||
<sip[s]:user@host>
|
||||
"Bob" <sip[s]:user@host>
|
||||
sip[s]:user@host
|
||||
user@host
|
||||
* \endverbatim
|
||||
*
|
||||
* \param to Destination
|
||||
* \param uri Pointer to URI variable. Must be freed by caller - even if the return value is NULL!
|
||||
* \param destination, slash, atsign, scheme
|
||||
* \param get_default_outbound If nonzero, try to retrieve the default
|
||||
* outbound endpoint if no endpoint was found.
|
||||
* Otherwise, return NULL if no endpoint was found.
|
||||
* \return endpoint
|
||||
*/
|
||||
static struct ast_sip_endpoint *handle_atsign(const char *to, char *destination, char **uri,
|
||||
char *slash, char *atsign, char *scheme, int get_default_outbound)
|
||||
{
|
||||
char *endpoint_name = NULL;
|
||||
struct ast_sip_endpoint *endpoint = NULL;
|
||||
struct ast_sip_contact *contact = NULL;
|
||||
char *afterat = atsign + 1;
|
||||
|
||||
*atsign = '\0';
|
||||
endpoint_name = destination;
|
||||
|
||||
/* Apparently there may be ';<user_options>' after the endpoint name ??? */
|
||||
AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(endpoint_name);
|
||||
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
|
||||
if (!endpoint) {
|
||||
/*
|
||||
* It's probably a uri with a user but without a scheme but we don't have a way to tell.
|
||||
* We're going to assume it is and prepend it with a scheme.
|
||||
*/
|
||||
*uri = ast_malloc(strlen(to) + strlen("sip:") + 1);
|
||||
if (!(*uri)) {
|
||||
goto failure;
|
||||
}
|
||||
sprintf(*uri, "sip:%s", to);
|
||||
if (get_default_outbound) {
|
||||
endpoint = ast_sip_default_outbound_endpoint();
|
||||
}
|
||||
ast_debug(3, "Dest: '%s' Didn't find endpoint before the '@' so using URI '%s'%s\n",
|
||||
to, *uri, get_default_outbound ? " with default endpoint" : "");
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/*
|
||||
* OK, it's an endpoint and a domain (which we ignore)
|
||||
*/
|
||||
contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
|
||||
if (!contact) {
|
||||
ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find contact\n",
|
||||
to, endpoint_name);
|
||||
goto failure;
|
||||
}
|
||||
|
||||
*uri = ast_strdup(contact->uri);
|
||||
ao2_cleanup(contact);
|
||||
if (!(*uri)) {
|
||||
goto failure;
|
||||
}
|
||||
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' (discarding domain %s)\n",
|
||||
to, endpoint_name, *uri, afterat);
|
||||
|
||||
return endpoint;
|
||||
|
||||
failure:
|
||||
ao2_cleanup(endpoint);
|
||||
*uri = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct ast_sip_endpoint *ast_sip_get_endpoint(const char *to, int get_default_outbound, char **uri)
|
||||
{
|
||||
char *destination;
|
||||
char *slash = NULL;
|
||||
char *atsign = NULL;
|
||||
char *scheme = NULL;
|
||||
struct ast_sip_endpoint *endpoint = NULL;
|
||||
|
||||
destination = ast_strdupa(to);
|
||||
|
||||
slash = strchr(destination, '/');
|
||||
atsign = strchr(destination, '@');
|
||||
scheme = S_OR(strstr(destination, "sip:"), strstr(destination, "sips:"));
|
||||
|
||||
if (!slash && !atsign && !scheme) {
|
||||
/*
|
||||
* If there's only a single token, it can be either...
|
||||
* endpoint
|
||||
* host
|
||||
*/
|
||||
return handle_single_token(to, destination, get_default_outbound, uri);
|
||||
}
|
||||
|
||||
if (slash) {
|
||||
/*
|
||||
* If there's a '/', then the form must be one of the following...
|
||||
* PJSIP/user@endpoint
|
||||
* endpoint/aor
|
||||
* endpoint/uri
|
||||
*/
|
||||
return handle_slash(to, destination, uri, slash, atsign, scheme);
|
||||
}
|
||||
|
||||
if (atsign && !scheme) {
|
||||
/*
|
||||
* If there's an '@' but no scheme then it's either following an endpoint name
|
||||
* and being followed by a domain name (which we discard).
|
||||
* OR is's a user@host uri without a scheme. It's probably the latter but because
|
||||
* endpoint@domain looks just like user@host, we'll test for endpoint first.
|
||||
*/
|
||||
return handle_atsign(to, destination, uri, slash, atsign, scheme, get_default_outbound);
|
||||
}
|
||||
|
||||
/*
|
||||
* If all else fails, we assume it's a URI or just a hostname.
|
||||
*/
|
||||
if (scheme) {
|
||||
*uri = ast_strdup(destination);
|
||||
if (!(*uri)) {
|
||||
goto failure;
|
||||
}
|
||||
ast_debug(3, "Dest: '%s' Didn't find an endpoint but did find a scheme so using URI '%s'%s\n",
|
||||
to, *uri, get_default_outbound ? " with default endpoint" : "");
|
||||
} else {
|
||||
*uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
|
||||
if (!(*uri)) {
|
||||
goto failure;
|
||||
}
|
||||
sprintf(*uri, "sip:%s", destination);
|
||||
ast_debug(3, "Dest: '%s' Didn't find an endpoint and didn't find scheme so adding scheme and using URI '%s'%s\n",
|
||||
to, *uri, get_default_outbound ? " with default endpoint" : "");
|
||||
}
|
||||
if (get_default_outbound) {
|
||||
endpoint = ast_sip_default_outbound_endpoint();
|
||||
}
|
||||
|
||||
return endpoint;
|
||||
|
||||
failure:
|
||||
ao2_cleanup(endpoint);
|
||||
*uri = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int ast_sip_update_to_uri(pjsip_tx_data *tdata, const char *to)
|
||||
{
|
||||
pjsip_name_addr *parsed_name_addr;
|
||||
pjsip_sip_uri *sip_uri;
|
||||
pjsip_name_addr *tdata_name_addr;
|
||||
pjsip_sip_uri *tdata_sip_uri;
|
||||
pjsip_to_hdr *to_hdr;
|
||||
char *buf = NULL;
|
||||
#define DEBUG_BUF_SIZE 256
|
||||
|
||||
parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, (char*)to, strlen(to),
|
||||
PJSIP_PARSE_URI_AS_NAMEADDR);
|
||||
|
||||
if (!parsed_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
|
||||
&& !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri))) {
|
||||
ast_log(LOG_WARNING, "To address '%s' is not a valid SIP/SIPS URI\n", to);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sip_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
|
||||
if (DEBUG_ATLEAST(3)) {
|
||||
buf = ast_alloca(DEBUG_BUF_SIZE);
|
||||
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, sip_uri, buf, DEBUG_BUF_SIZE);
|
||||
ast_debug(3, "Parsed To: %.*s %s\n", (int)parsed_name_addr->display.slen,
|
||||
parsed_name_addr->display.ptr, buf);
|
||||
}
|
||||
|
||||
to_hdr = PJSIP_MSG_TO_HDR(tdata->msg);
|
||||
tdata_name_addr = to_hdr ? (pjsip_name_addr *) to_hdr->uri : NULL;
|
||||
if (!tdata_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(tdata_name_addr->uri)
|
||||
&& !PJSIP_URI_SCHEME_IS_SIPS(tdata_name_addr->uri))) {
|
||||
/* Highly unlikely but we have to check */
|
||||
ast_log(LOG_WARNING, "tdata To address '%s' is not a valid SIP/SIPS URI\n", to);
|
||||
return -1;
|
||||
}
|
||||
|
||||
tdata_sip_uri = pjsip_uri_get_uri(tdata_name_addr->uri);
|
||||
if (DEBUG_ATLEAST(3)) {
|
||||
buf[0] = '\0';
|
||||
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, DEBUG_BUF_SIZE);
|
||||
ast_debug(3, "Original tdata To: %.*s %s\n", (int)tdata_name_addr->display.slen,
|
||||
tdata_name_addr->display.ptr, buf);
|
||||
}
|
||||
|
||||
/* Replace the uri */
|
||||
pjsip_sip_uri_assign(tdata->pool, tdata_sip_uri, sip_uri);
|
||||
/* The display name isn't part of the URI so we need to replace it separately */
|
||||
pj_strdup(tdata->pool, &tdata_name_addr->display, &parsed_name_addr->display);
|
||||
|
||||
if (DEBUG_ATLEAST(3)) {
|
||||
buf[0] = '\0';
|
||||
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, 256);
|
||||
ast_debug(3, "New tdata To: %.*s %s\n", (int)tdata_name_addr->display.slen,
|
||||
tdata_name_addr->display.ptr, buf);
|
||||
}
|
||||
|
||||
return 0;
|
||||
#undef DEBUG_BUF_SIZE
|
||||
}
|
||||
|
||||
int ast_sip_update_from(pjsip_tx_data *tdata, char *from)
|
||||
{
|
||||
pjsip_name_addr *name_addr;
|
||||
pjsip_sip_uri *uri;
|
||||
pjsip_name_addr *parsed_name_addr;
|
||||
pjsip_from_hdr *from_hdr;
|
||||
|
||||
if (ast_strlen_zero(from)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
from_hdr = PJSIP_MSG_FROM_HDR(tdata->msg);
|
||||
if (!from_hdr) {
|
||||
return -1;
|
||||
}
|
||||
name_addr = (pjsip_name_addr *) from_hdr->uri;
|
||||
uri = pjsip_uri_get_uri(name_addr);
|
||||
|
||||
parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, from,
|
||||
strlen(from), PJSIP_PARSE_URI_AS_NAMEADDR);
|
||||
if (parsed_name_addr) {
|
||||
pjsip_sip_uri *parsed_uri;
|
||||
|
||||
if (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
|
||||
&& !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri)) {
|
||||
ast_log(LOG_WARNING, "From address '%s' is not a valid SIP/SIPS URI\n", from);
|
||||
return -1;
|
||||
}
|
||||
|
||||
parsed_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
|
||||
|
||||
if (pj_strlen(&parsed_name_addr->display)) {
|
||||
pj_strdup(tdata->pool, &name_addr->display, &parsed_name_addr->display);
|
||||
}
|
||||
|
||||
/* Unlike the To header, we only want to replace the user, host and port */
|
||||
pj_strdup(tdata->pool, &uri->user, &parsed_uri->user);
|
||||
pj_strdup(tdata->pool, &uri->host, &parsed_uri->host);
|
||||
uri->port = parsed_uri->port;
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
/* assume it is 'user[@domain]' format */
|
||||
char *domain = strchr(from, '@');
|
||||
|
||||
if (domain) {
|
||||
pj_str_t pj_from;
|
||||
|
||||
pj_strset3(&pj_from, from, domain);
|
||||
pj_strdup(tdata->pool, &uri->user, &pj_from);
|
||||
|
||||
pj_strdup2(tdata->pool, &uri->host, domain + 1);
|
||||
} else {
|
||||
pj_strdup2(tdata->pool, &uri->user, from);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void remove_request_headers(pjsip_endpoint *endpt)
|
||||
{
|
||||
|
@@ -195,571 +195,6 @@ static enum pjsip_status_code check_content_type_in_dialog(const pjsip_rx_data *
|
||||
return res;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Find a contact and insert a "user@" into its URI.
|
||||
*
|
||||
* \param to Original destination (for error messages only)
|
||||
* \param endpoint_name Endpoint name (for error messages only)
|
||||
* \param aors Command separated list of AORs
|
||||
* \param user The user to insert in the contact URI
|
||||
* \param uri Pointer to buffer in which to return the URI
|
||||
*
|
||||
* \return 0 Success
|
||||
* \return -1 Fail
|
||||
*
|
||||
* \note If the contact URI found for the endpoint already has a user in
|
||||
* its URI, it will be replaced.
|
||||
*/
|
||||
static int insert_user_in_contact_uri(const char *to, const char *endpoint_name, const char *aors,
|
||||
const char *user, char **uri)
|
||||
{
|
||||
char *scheme = NULL;
|
||||
char *contact_uri = NULL;
|
||||
char *after_scheme = NULL;
|
||||
char *host;
|
||||
struct ast_sip_contact *contact = NULL;
|
||||
|
||||
|
||||
contact = ast_sip_location_retrieve_contact_from_aor_list(aors);
|
||||
if (!contact) {
|
||||
/*
|
||||
* We're getting the contact using the same method as
|
||||
* ast_sip_create_request() so if there's no contact
|
||||
* we can never send this message.
|
||||
*/
|
||||
ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Couldn't find contact for endpoint '%s'\n",
|
||||
to, endpoint_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
contact_uri = ast_strdupa(contact->uri);
|
||||
ao2_cleanup(contact);
|
||||
|
||||
ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s' ContactURI: '%s'\n", to, user, endpoint_name, contact_uri);
|
||||
|
||||
/*
|
||||
* Contact URIs must have a scheme so we must insert the user between it and the host.
|
||||
*/
|
||||
scheme = contact_uri;
|
||||
after_scheme = strchr(contact_uri, ':');
|
||||
if (!after_scheme) {
|
||||
/* A contact URI without a scheme? Something's wrong. Bail */
|
||||
ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: There was no scheme in the contact URI '%s'\n",
|
||||
to, contact_uri);
|
||||
return -1;
|
||||
}
|
||||
/*
|
||||
* Terminate the scheme.
|
||||
*/
|
||||
*after_scheme = '\0';
|
||||
after_scheme++;
|
||||
|
||||
/*
|
||||
* If the contact_uri already has a user, the host starts after the '@', otherwise
|
||||
* the host is at after_scheme.
|
||||
*
|
||||
* We're going to ignore the existing user.
|
||||
*/
|
||||
host = strchr(after_scheme, '@');
|
||||
if (host) {
|
||||
host++;
|
||||
} else {
|
||||
host = after_scheme;
|
||||
}
|
||||
|
||||
*uri = ast_malloc(strlen(scheme) + strlen(user) + strlen(host) + 3 /* One for the ':', '@' and terminating NULL */);
|
||||
sprintf(*uri, "%s:%s@%s", scheme, user, host); /* Safe */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Get endpoint and URI when the destination is only a single token
|
||||
*
|
||||
* "to" could be one of the following:
|
||||
* \verbatim
|
||||
endpoint_name
|
||||
hostname
|
||||
* \endverbatim
|
||||
*
|
||||
* \param to Destination specified in MessageSend
|
||||
* \param destination
|
||||
* \param uri Pointer to URI variable. Must be freed by caller
|
||||
* \return endpoint
|
||||
*/
|
||||
static struct ast_sip_endpoint *handle_single_token(const char *to, char *destination, char **uri) {
|
||||
char *endpoint_name = NULL;
|
||||
struct ast_sip_endpoint *endpoint = NULL;
|
||||
struct ast_sip_contact *contact = NULL;
|
||||
|
||||
/*
|
||||
* If "to" is just one token, it could be an endpoint name
|
||||
* or a hostname without a scheme.
|
||||
*/
|
||||
|
||||
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", destination);
|
||||
if (!endpoint) {
|
||||
/*
|
||||
* We can only assume it's a hostname.
|
||||
*/
|
||||
char *temp_uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
|
||||
sprintf(temp_uri, "sip:%s", destination);
|
||||
*uri = temp_uri;
|
||||
endpoint = ast_sip_default_outbound_endpoint();
|
||||
ast_debug(3, "Dest: '%s' Didn't find endpoint so adding scheme and using URI '%s' with default endpoint\n",
|
||||
to, *uri);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/*
|
||||
* It's an endpoint
|
||||
*/
|
||||
|
||||
endpoint_name = destination;
|
||||
contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
|
||||
if (!contact) {
|
||||
/*
|
||||
* We're getting the contact using the same method as
|
||||
* ast_sip_create_request() so if there's no contact
|
||||
* we can never send this message.
|
||||
*/
|
||||
ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find an aor/contact for it\n",
|
||||
to, endpoint_name);
|
||||
ao2_cleanup(endpoint);
|
||||
*uri = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*uri = ast_strdup(contact->uri);
|
||||
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s'\n",
|
||||
to, endpoint_name, *uri);
|
||||
ao2_cleanup(contact);
|
||||
return endpoint;
|
||||
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Get endpoint and URI when the destination contained a '/'
|
||||
*
|
||||
* "to" could be one of the following:
|
||||
* \verbatim
|
||||
endpoint/aor
|
||||
endpoint/<sip[s]:host>
|
||||
endpoint/<sip[s]:user@host>
|
||||
endpoint/"Bob" <sip[s]:host>
|
||||
endpoint/"Bob" <sip[s]:user@host>
|
||||
endpoint/sip[s]:host
|
||||
endpoint/sip[s]:user@host
|
||||
endpoint/host
|
||||
endpoint/user@host
|
||||
* \endverbatim
|
||||
*
|
||||
* \param to Destination specified in MessageSend
|
||||
* \param uri Pointer to URI variable. Must be freed by caller
|
||||
* \param destination, slash, atsign, scheme
|
||||
* \return endpoint
|
||||
*/
|
||||
static struct ast_sip_endpoint *handle_slash(const char *to, char *destination, char **uri,
|
||||
char *slash, char *atsign, char *scheme)
|
||||
{
|
||||
char *endpoint_name = NULL;
|
||||
struct ast_sip_endpoint *endpoint = NULL;
|
||||
struct ast_sip_contact *contact = NULL;
|
||||
char *user = NULL;
|
||||
char *afterslash = slash + 1;
|
||||
struct ast_sip_aor *aor;
|
||||
|
||||
if (ast_begins_with(destination, "PJSIP/")) {
|
||||
ast_debug(3, "Dest: '%s' Dialplan format'\n", to);
|
||||
/*
|
||||
* This has to be the form PJSIP/user@endpoint
|
||||
*/
|
||||
if (!atsign || strchr(afterslash, '/')) {
|
||||
/*
|
||||
* If there's no "user@" or there's a slash somewhere after
|
||||
* "PJSIP/" then we go no further.
|
||||
*/
|
||||
*uri = NULL;
|
||||
ast_log(LOG_WARNING,
|
||||
"Dest: '%s' MSG SEND FAIL: Destinations beginning with 'PJSIP/' must be in the form of 'PJSIP/user@endpoint'\n",
|
||||
to);
|
||||
return NULL;
|
||||
}
|
||||
*atsign = '\0';
|
||||
user = afterslash;
|
||||
endpoint_name = atsign + 1;
|
||||
ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s'\n", to, user, endpoint_name);
|
||||
} else {
|
||||
/*
|
||||
* Either...
|
||||
* endpoint/aor
|
||||
* endpoint/uri
|
||||
*/
|
||||
*slash = '\0';
|
||||
endpoint_name = destination;
|
||||
ast_debug(3, "Dest: '%s' Endpoint: '%s'\n", to, endpoint_name);
|
||||
}
|
||||
|
||||
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
|
||||
if (!endpoint) {
|
||||
*uri = NULL;
|
||||
ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Didn't find endpoint with name '%s'\n",
|
||||
to, endpoint_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (scheme) {
|
||||
/*
|
||||
* If we found a scheme, then everything after the slash MUST be a URI.
|
||||
* We don't need to do any further modification.
|
||||
*/
|
||||
*uri = ast_strdup(afterslash);
|
||||
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found URI '%s' after '/'\n",
|
||||
to, endpoint_name, *uri);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
if (user) {
|
||||
/*
|
||||
* This has to be the form PJSIP/user@endpoint
|
||||
*/
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* Set the return URI to be the endpoint's contact URI with the user
|
||||
* portion set to the user that was specified before the endpoint name.
|
||||
*/
|
||||
rc = insert_user_in_contact_uri(to, endpoint_name, endpoint->aors, user, uri);
|
||||
if (rc != 0) {
|
||||
/*
|
||||
* insert_user_in_contact_uri prints the warning message.
|
||||
*/
|
||||
ao2_cleanup(endpoint);
|
||||
endpoint = NULL;
|
||||
*uri = NULL;
|
||||
}
|
||||
ast_debug(3, "Dest: '%s' User: '%s' Endpoint: '%s' URI: '%s'\n", to, user,
|
||||
endpoint_name, *uri);
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/*
|
||||
* We're now left with two possibilities...
|
||||
* endpoint/aor
|
||||
* endpoint/uri-without-scheme
|
||||
*/
|
||||
aor = ast_sip_location_retrieve_aor(afterslash);
|
||||
if (!aor) {
|
||||
/*
|
||||
* It's probably a URI without a scheme but we don't have a way to tell
|
||||
* for sure. We're going to assume it is and prepend it with a scheme.
|
||||
*/
|
||||
*uri = ast_malloc(strlen(afterslash) + strlen("sip:") + 1);
|
||||
sprintf(*uri, "sip:%s", afterslash);
|
||||
ast_debug(3, "Dest: '%s' Found endpoint '%s' but didn't find aor after '/' so using URI '%s'\n",
|
||||
to, endpoint_name, *uri);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only one possibility left... There was an aor name after the slash.
|
||||
*/
|
||||
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found aor '%s' after '/'\n",
|
||||
to, endpoint_name, ast_sorcery_object_get_id(aor));
|
||||
|
||||
contact = ast_sip_location_retrieve_first_aor_contact(aor);
|
||||
if (!contact) {
|
||||
/*
|
||||
* An aor without a contact is useless and since
|
||||
* ast_sip_create_message() won't be able to find one
|
||||
* either, we just need to bail.
|
||||
*/
|
||||
ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find contact for aor '%s'\n",
|
||||
to, endpoint_name, ast_sorcery_object_get_id(aor));
|
||||
ao2_cleanup(aor);
|
||||
ao2_cleanup(endpoint);
|
||||
*uri = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*uri = ast_strdup(contact->uri);
|
||||
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' for aor '%s'\n",
|
||||
to, endpoint_name, *uri, ast_sorcery_object_get_id(aor));
|
||||
ao2_cleanup(contact);
|
||||
ao2_cleanup(aor);
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Get endpoint and URI when the destination contained a '\@' but no '/' or scheme
|
||||
*
|
||||
* "to" could be one of the following:
|
||||
* \verbatim
|
||||
<sip[s]:user@host>
|
||||
"Bob" <sip[s]:user@host>
|
||||
sip[s]:user@host
|
||||
user@host
|
||||
* \endverbatim
|
||||
*
|
||||
* \param to Destination specified in MessageSend
|
||||
* \param uri Pointer to URI variable. Must be freed by caller
|
||||
* \param destination, slash, atsign, scheme
|
||||
* \return endpoint
|
||||
*/
|
||||
static struct ast_sip_endpoint *handle_atsign(const char *to, char *destination, char **uri,
|
||||
char *slash, char *atsign, char *scheme)
|
||||
{
|
||||
char *endpoint_name = NULL;
|
||||
struct ast_sip_endpoint *endpoint = NULL;
|
||||
struct ast_sip_contact *contact = NULL;
|
||||
char *afterat = atsign + 1;
|
||||
|
||||
*atsign = '\0';
|
||||
endpoint_name = destination;
|
||||
|
||||
/* Apparently there may be ';<user_options>' after the endpoint name ??? */
|
||||
AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(endpoint_name);
|
||||
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
|
||||
if (!endpoint) {
|
||||
/*
|
||||
* It's probably a uri with a user but without a scheme but we don't have a way to tell.
|
||||
* We're going to assume it is and prepend it with a scheme.
|
||||
*/
|
||||
*uri = ast_malloc(strlen(to) + strlen("sip:") + 1);
|
||||
sprintf(*uri, "sip:%s", to);
|
||||
endpoint = ast_sip_default_outbound_endpoint();
|
||||
ast_debug(3, "Dest: '%s' Didn't find endpoint before the '@' so using URI '%s' with default endpoint\n",
|
||||
to, *uri);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/*
|
||||
* OK, it's an endpoint and a domain (which we ignore)
|
||||
*/
|
||||
contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
|
||||
if (!contact) {
|
||||
/*
|
||||
* We're getting the contact using the same method as
|
||||
* ast_sip_create_request() so if there's no contact
|
||||
* we can never send this message.
|
||||
*/
|
||||
ao2_cleanup(endpoint);
|
||||
endpoint = NULL;
|
||||
*uri = NULL;
|
||||
ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find contact\n",
|
||||
to, endpoint_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*uri = ast_strdup(contact->uri);
|
||||
ao2_cleanup(contact);
|
||||
ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' (discarding domain %s)\n",
|
||||
to, endpoint_name, *uri, afterat);
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Retrieves an endpoint and URI from the "to" string.
|
||||
*
|
||||
* This URI is used as the Request URI.
|
||||
*
|
||||
* Expects the given 'to' to be in one of the following formats:
|
||||
* Why we allow so many is a mystery.
|
||||
*
|
||||
* Basic:
|
||||
*
|
||||
* endpoint : We'll get URI from the default aor/contact
|
||||
* endpoint/aor : We'll get the URI from the specific aor/contact
|
||||
* endpoint@domain : We toss the domain part and just use the endpoint
|
||||
*
|
||||
* These all use the endpoint and specified URI:
|
||||
* \verbatim
|
||||
endpoint/<sip[s]:host>
|
||||
endpoint/<sip[s]:user@host>
|
||||
endpoint/"Bob" <sip[s]:host>
|
||||
endpoint/"Bob" <sip[s]:user@host>
|
||||
endpoint/sip[s]:host
|
||||
endpoint/sip[s]:user@host
|
||||
endpoint/host
|
||||
endpoint/user@host
|
||||
\endverbatim
|
||||
*
|
||||
* These all use the default endpoint and specified URI:
|
||||
* \verbatim
|
||||
<sip[s]:host>
|
||||
<sip[s]:user@host>
|
||||
"Bob" <sip[s]:host>
|
||||
"Bob" <sip[s]:user@host>
|
||||
sip[s]:host
|
||||
sip[s]:user@host
|
||||
\endverbatim
|
||||
*
|
||||
* These use the default endpoint and specified host:
|
||||
* \verbatim
|
||||
host
|
||||
user@host
|
||||
\endverbatim
|
||||
*
|
||||
* This form is similar to a dialstring:
|
||||
* \verbatim
|
||||
PJSIP/user@endpoint
|
||||
\endverbatim
|
||||
*
|
||||
* In this case, the user will be added to the endpoint contact's URI.
|
||||
* If the contact URI already has a user, it will be replaced.
|
||||
*
|
||||
* The ones that have the sip[s] scheme are the easiest to parse.
|
||||
* The rest all have some issue.
|
||||
*
|
||||
* endpoint vs host : We have to test for endpoint first
|
||||
* endpoint/aor vs endpoint/host : We have to test for aor first
|
||||
* What if there's an aor with the same
|
||||
* name as the host?
|
||||
* endpoint@domain vs user@host : We have to test for endpoint first.
|
||||
* What if there's an endpoint with the
|
||||
* same name as the user?
|
||||
*
|
||||
* \param to 'To' field with possible endpoint
|
||||
* \param uri Pointer to a char* which will be set to the URI.
|
||||
* Must be ast_free'd by the caller.
|
||||
*
|
||||
* \note The logic below could probably be condensed but then it wouldn't be
|
||||
* as clear.
|
||||
*/
|
||||
static struct ast_sip_endpoint *get_outbound_endpoint(const char *to, char **uri)
|
||||
{
|
||||
char *destination;
|
||||
char *slash = NULL;
|
||||
char *atsign = NULL;
|
||||
char *scheme = NULL;
|
||||
struct ast_sip_endpoint *endpoint = NULL;
|
||||
|
||||
destination = ast_strdupa(to);
|
||||
slash = strchr(destination, '/');
|
||||
atsign = strchr(destination, '@');
|
||||
scheme = S_OR(strstr(destination, "sip:"), strstr(destination, "sips:"));
|
||||
|
||||
if (!slash && !atsign && !scheme) {
|
||||
/*
|
||||
* If there's only a single token, it can be either...
|
||||
* endpoint
|
||||
* host
|
||||
*/
|
||||
return handle_single_token(to, destination, uri);
|
||||
}
|
||||
|
||||
if (slash) {
|
||||
/*
|
||||
* If there's a '/', then the form must be one of the following...
|
||||
* PJSIP/user@endpoint
|
||||
* endpoint/aor
|
||||
* endpoint/uri
|
||||
*/
|
||||
return handle_slash(to, destination, uri, slash, atsign, scheme);
|
||||
}
|
||||
|
||||
if (!endpoint && atsign && !scheme) {
|
||||
/*
|
||||
* If there's an '@' but no scheme then it's either following an endpoint name
|
||||
* and being followed by a domain name (which we discard).
|
||||
* OR is's a user@host uri without a scheme. It's probably the latter but because
|
||||
* endpoint@domain looks just like user@host, we'll test for endpoint first.
|
||||
*/
|
||||
return handle_atsign(to, destination, uri, slash, atsign, scheme);
|
||||
}
|
||||
|
||||
/*
|
||||
* If all else fails, we assume it's a URI or just a hostname.
|
||||
*/
|
||||
if (scheme) {
|
||||
*uri = ast_strdup(destination);
|
||||
ast_debug(3, "Dest: '%s' Didn't find an endpoint but did find a scheme so using URI '%s' with default endpoint\n",
|
||||
to, *uri);
|
||||
} else {
|
||||
*uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
|
||||
sprintf(*uri, "sip:%s", destination);
|
||||
ast_debug(3, "Dest: '%s' Didn't find an endpoint and didn't find scheme so adding scheme and using URI '%s' with default endpoint\n",
|
||||
to, *uri);
|
||||
}
|
||||
endpoint = ast_sip_default_outbound_endpoint();
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Replace the To URI in the tdata with the supplied one
|
||||
*
|
||||
* \param tdata the outbound message data structure
|
||||
* \param to URI to replace the To URI with
|
||||
*
|
||||
* \return 0: success, -1: failure
|
||||
*/
|
||||
static int update_to_uri(pjsip_tx_data *tdata, char *to)
|
||||
{
|
||||
pjsip_name_addr *parsed_name_addr;
|
||||
pjsip_sip_uri *sip_uri;
|
||||
pjsip_name_addr *tdata_name_addr;
|
||||
pjsip_sip_uri *tdata_sip_uri;
|
||||
char *buf = NULL;
|
||||
#define DEBUG_BUF_SIZE 256
|
||||
|
||||
parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, to, strlen(to),
|
||||
PJSIP_PARSE_URI_AS_NAMEADDR);
|
||||
|
||||
if (!parsed_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
|
||||
&& !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri))) {
|
||||
ast_log(LOG_WARNING, "To address '%s' is not a valid SIP/SIPS URI\n", to);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sip_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
|
||||
if (DEBUG_ATLEAST(3)) {
|
||||
buf = ast_alloca(DEBUG_BUF_SIZE);
|
||||
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, sip_uri, buf, DEBUG_BUF_SIZE);
|
||||
ast_debug(3, "Parsed To: %.*s %s\n", (int)parsed_name_addr->display.slen,
|
||||
parsed_name_addr->display.ptr, buf);
|
||||
}
|
||||
|
||||
tdata_name_addr = (pjsip_name_addr *) PJSIP_MSG_TO_HDR(tdata->msg)->uri;
|
||||
if (!tdata_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(tdata_name_addr->uri)
|
||||
&& !PJSIP_URI_SCHEME_IS_SIPS(tdata_name_addr->uri))) {
|
||||
/* Highly unlikely but we have to check */
|
||||
ast_log(LOG_WARNING, "tdata To address '%s' is not a valid SIP/SIPS URI\n", to);
|
||||
return -1;
|
||||
}
|
||||
|
||||
tdata_sip_uri = pjsip_uri_get_uri(tdata_name_addr->uri);
|
||||
if (DEBUG_ATLEAST(3)) {
|
||||
buf[0] = '\0';
|
||||
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, DEBUG_BUF_SIZE);
|
||||
ast_debug(3, "Original tdata To: %.*s %s\n", (int)tdata_name_addr->display.slen,
|
||||
tdata_name_addr->display.ptr, buf);
|
||||
}
|
||||
|
||||
/* Replace the uri */
|
||||
pjsip_sip_uri_assign(tdata->pool, tdata_sip_uri, sip_uri);
|
||||
/* The display name isn't part of the URI so we need to replace it separately */
|
||||
pj_strdup(tdata->pool, &tdata_name_addr->display, &parsed_name_addr->display);
|
||||
|
||||
if (DEBUG_ATLEAST(3)) {
|
||||
buf[0] = '\0';
|
||||
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, 256);
|
||||
ast_debug(3, "New tdata To: %.*s %s\n", (int)tdata_name_addr->display.slen,
|
||||
tdata_name_addr->display.ptr, buf);
|
||||
}
|
||||
|
||||
return 0;
|
||||
#undef DEBUG_BUF_SIZE
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Update the display name in the To uri in the tdata with the one from the supplied uri
|
||||
@@ -790,77 +225,6 @@ static int update_to_display_name(pjsip_tx_data *tdata, char *to)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Overwrite fields in the outbound 'From' header
|
||||
*
|
||||
* The outbound 'From' header is created/added in ast_sip_create_request with
|
||||
* default data. If available that data may be info specified in the 'from_user'
|
||||
* and 'from_domain' options found on the endpoint. That information will be
|
||||
* overwritten with data in the given 'from' parameter.
|
||||
*
|
||||
* \param tdata the outbound message data structure
|
||||
* \param from info to copy into the header
|
||||
*
|
||||
* \return 0: success, -1: failure
|
||||
*/
|
||||
static int update_from(pjsip_tx_data *tdata, char *from)
|
||||
{
|
||||
pjsip_name_addr *name_addr;
|
||||
pjsip_sip_uri *uri;
|
||||
pjsip_name_addr *parsed_name_addr;
|
||||
|
||||
if (ast_strlen_zero(from)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
name_addr = (pjsip_name_addr *) PJSIP_MSG_FROM_HDR(tdata->msg)->uri;
|
||||
uri = pjsip_uri_get_uri(name_addr);
|
||||
|
||||
parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, from,
|
||||
strlen(from), PJSIP_PARSE_URI_AS_NAMEADDR);
|
||||
if (parsed_name_addr) {
|
||||
pjsip_sip_uri *parsed_uri;
|
||||
|
||||
if (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
|
||||
&& !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri)) {
|
||||
ast_log(LOG_WARNING, "From address '%s' is not a valid SIP/SIPS URI\n", from);
|
||||
return -1;
|
||||
}
|
||||
|
||||
parsed_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
|
||||
|
||||
if (pj_strlen(&parsed_name_addr->display)) {
|
||||
pj_strdup(tdata->pool, &name_addr->display, &parsed_name_addr->display);
|
||||
}
|
||||
|
||||
/* Unlike the To header, we only want to replace the user, host and port */
|
||||
pj_strdup(tdata->pool, &uri->user, &parsed_uri->user);
|
||||
pj_strdup(tdata->pool, &uri->host, &parsed_uri->host);
|
||||
uri->port = parsed_uri->port;
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
/* assume it is 'user[@domain]' format */
|
||||
char *domain = strchr(from, '@');
|
||||
|
||||
if (domain) {
|
||||
pj_str_t pj_from;
|
||||
|
||||
pj_strset3(&pj_from, from, domain);
|
||||
pj_strdup(tdata->pool, &uri->user, &pj_from);
|
||||
|
||||
pj_strdup2(tdata->pool, &uri->host, domain + 1);
|
||||
} else {
|
||||
pj_strdup2(tdata->pool, &uri->user, from);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Checks if the given msg var name should be blocked.
|
||||
@@ -1252,7 +616,7 @@ static int msg_send(void *data)
|
||||
ast_debug(3, "mdata From: %s msg From: %s mdata Destination: %s msg To: %s\n",
|
||||
mdata->from, ast_msg_get_from(mdata->msg), mdata->destination, ast_msg_get_to(mdata->msg));
|
||||
|
||||
endpoint = get_outbound_endpoint(mdata->destination, &uri);
|
||||
endpoint = ast_sip_get_endpoint(mdata->destination, 1, &uri);
|
||||
if (!endpoint) {
|
||||
ast_log(LOG_ERROR,
|
||||
"PJSIP MESSAGE - Could not find endpoint '%s' and no default outbound endpoint configured\n",
|
||||
@@ -1290,7 +654,7 @@ static int msg_send(void *data)
|
||||
if (ast_begins_with(msg_to, "pjsip:")) {
|
||||
msg_to += 6;
|
||||
}
|
||||
update_to_uri(tdata, msg_to);
|
||||
ast_sip_update_to_uri(tdata, msg_to);
|
||||
} else {
|
||||
/*
|
||||
* If there was no To in the message, it's still possible
|
||||
@@ -1301,9 +665,9 @@ static int msg_send(void *data)
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(mdata->from)) {
|
||||
update_from(tdata, mdata->from);
|
||||
ast_sip_update_from(tdata, mdata->from);
|
||||
} else if (!ast_strlen_zero(ast_msg_get_from(mdata->msg))) {
|
||||
update_from(tdata, (char *)ast_msg_get_from(mdata->msg));
|
||||
ast_sip_update_from(tdata, (char *)ast_msg_get_from(mdata->msg));
|
||||
}
|
||||
|
||||
#ifdef TEST_FRAMEWORK
|
||||
|
@@ -235,52 +235,6 @@ static pj_bool_t nat_on_rx_message(pjsip_rx_data *rdata)
|
||||
return res;
|
||||
}
|
||||
|
||||
/*! \brief Structure which contains information about a transport */
|
||||
struct request_transport_details {
|
||||
/*! \brief Type of transport */
|
||||
enum ast_transport type;
|
||||
/*! \brief Potential pointer to the transport itself, if UDP */
|
||||
pjsip_transport *transport;
|
||||
/*! \brief Potential pointer to the transport factory itself, if TCP/TLS */
|
||||
pjsip_tpfactory *factory;
|
||||
/*! \brief Local address for transport */
|
||||
pj_str_t local_address;
|
||||
/*! \brief Local port for transport */
|
||||
int local_port;
|
||||
};
|
||||
|
||||
/*! \brief Callback function for finding the transport the request is going out on */
|
||||
static int find_transport_state_in_use(void *obj, void *arg, int flags)
|
||||
{
|
||||
struct ast_sip_transport_state *transport_state = obj;
|
||||
struct request_transport_details *details = arg;
|
||||
|
||||
/* If an explicit transport or factory matches then this is what is in use, if we are unavailable
|
||||
* to compare based on that we make sure that the type is the same and the source IP address/port are the same
|
||||
*/
|
||||
if (transport_state && ((details->transport && details->transport == transport_state->transport) ||
|
||||
(details->factory && details->factory == transport_state->factory) ||
|
||||
((details->type == transport_state->type) && (transport_state->factory) &&
|
||||
!pj_strcmp(&transport_state->factory->addr_name.host, &details->local_address) &&
|
||||
transport_state->factory->addr_name.port == details->local_port))) {
|
||||
return CMP_MATCH;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! \brief Helper function which returns the SIP URI of a Contact header */
|
||||
static pjsip_sip_uri *nat_get_contact_sip_uri(pjsip_tx_data *tdata)
|
||||
{
|
||||
pjsip_contact_hdr *contact = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
|
||||
|
||||
if (!contact || (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri))) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return pjsip_uri_get_uri(contact->uri);
|
||||
}
|
||||
|
||||
/*! \brief Structure which contains hook details */
|
||||
struct nat_hook_details {
|
||||
/*! \brief Outgoing message itself */
|
||||
@@ -363,55 +317,22 @@ static void restore_orig_contact_host(pjsip_tx_data *tdata)
|
||||
|
||||
static pj_status_t process_nat(pjsip_tx_data *tdata)
|
||||
{
|
||||
RAII_VAR(struct ao2_container *, transport_states, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
|
||||
struct request_transport_details details = { 0, };
|
||||
pjsip_via_hdr *via = NULL;
|
||||
struct ast_sip_request_transport_details details;
|
||||
struct ast_sockaddr addr = { { 0, } };
|
||||
pjsip_sip_uri *uri = NULL;
|
||||
RAII_VAR(struct ao2_container *, hooks, NULL, ao2_cleanup);
|
||||
|
||||
/* If a transport selector is in use we know the transport or factory, so explicitly find it */
|
||||
if (tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
|
||||
details.transport = tdata->tp_sel.u.transport;
|
||||
} else if (tdata->tp_sel.type == PJSIP_TPSELECTOR_LISTENER) {
|
||||
details.factory = tdata->tp_sel.u.listener;
|
||||
} else if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_UDP || tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_UDP6) {
|
||||
/* Connectionless uses the same transport for all requests */
|
||||
details.type = AST_TRANSPORT_UDP;
|
||||
details.transport = tdata->tp_info.transport;
|
||||
} else {
|
||||
if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_TCP) {
|
||||
details.type = AST_TRANSPORT_TCP;
|
||||
} else if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_TLS) {
|
||||
details.type = AST_TRANSPORT_TLS;
|
||||
} else {
|
||||
/* Unknown transport type, we can't map and thus can't apply NAT changes */
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
if ((uri = nat_get_contact_sip_uri(tdata))) {
|
||||
details.local_address = uri->host;
|
||||
details.local_port = uri->port;
|
||||
} else if ((tdata->msg->type == PJSIP_REQUEST_MSG) &&
|
||||
(via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL))) {
|
||||
details.local_address = via->sent_by.host;
|
||||
details.local_port = via->sent_by.port;
|
||||
} else {
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
if (!details.local_port) {
|
||||
details.local_port = (details.type == AST_TRANSPORT_TLS) ? 5061 : 5060;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(transport_states = ast_sip_get_transport_states())) {
|
||||
if (ast_sip_set_request_transport_details(&details, tdata, 0)) {
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
if (!(transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, &details))) {
|
||||
uri = ast_sip_get_contact_sip_uri(tdata);
|
||||
via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
|
||||
|
||||
if (!(transport_state = ast_sip_find_transport_state_in_use(&details))) {
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -443,7 +364,7 @@ static pj_status_t process_nat(pjsip_tx_data *tdata)
|
||||
if (!cseq || tdata->msg->type == PJSIP_REQUEST_MSG ||
|
||||
pjsip_method_cmp(&cseq->method, &pjsip_register_method)) {
|
||||
/* We can only rewrite the URI when one is present */
|
||||
if (uri || (uri = nat_get_contact_sip_uri(tdata))) {
|
||||
if (uri || (uri = ast_sip_get_contact_sip_uri(tdata))) {
|
||||
pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
|
||||
if (transport->external_signaling_port) {
|
||||
uri->port = transport->external_signaling_port;
|
||||
|
@@ -39,6 +39,11 @@
|
||||
#include "asterisk/stasis_bridges.h"
|
||||
#include "asterisk/stasis_channels.h"
|
||||
#include "asterisk/causes.h"
|
||||
#include "asterisk/refer.h"
|
||||
|
||||
static struct ast_taskprocessor *refer_serializer;
|
||||
|
||||
static pj_status_t refer_on_tx_request(pjsip_tx_data *tdata);
|
||||
|
||||
/*! \brief REFER Progress structure */
|
||||
struct refer_progress {
|
||||
@@ -786,6 +791,499 @@ static void refer_blind_callback(struct ast_channel *chan, struct transfer_chann
|
||||
ast_channel_unlock((session)->channel); \
|
||||
} while (0) \
|
||||
|
||||
struct refer_data {
|
||||
struct ast_refer *refer;
|
||||
char *destination;
|
||||
char *from;
|
||||
char *refer_to;
|
||||
int to_self;
|
||||
};
|
||||
|
||||
static void refer_data_destroy(void *obj)
|
||||
{
|
||||
struct refer_data *rdata = obj;
|
||||
|
||||
ast_free(rdata->destination);
|
||||
ast_free(rdata->from);
|
||||
ast_free(rdata->refer_to);
|
||||
|
||||
ast_refer_destroy(rdata->refer);
|
||||
}
|
||||
|
||||
static struct refer_data *refer_data_create(const struct ast_refer *refer)
|
||||
{
|
||||
char *uri_params;
|
||||
const char *destination;
|
||||
struct refer_data *rdata = ao2_alloc_options(sizeof(*rdata), refer_data_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
|
||||
|
||||
if (!rdata) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* typecast to suppress const warning */
|
||||
rdata->refer = ast_refer_ref((struct ast_refer *) refer);
|
||||
destination = ast_refer_get_to(refer);
|
||||
|
||||
/* To starts with 'pjsip:' which needs to be removed. */
|
||||
if (!(destination = strchr(destination, ':'))) {
|
||||
goto failure;
|
||||
}
|
||||
++destination;/* Now skip the ':' */
|
||||
|
||||
rdata->destination = ast_strdup(destination);
|
||||
if (!rdata->destination) {
|
||||
goto failure;
|
||||
}
|
||||
|
||||
rdata->from = ast_strdup(ast_refer_get_from(refer));
|
||||
if (!rdata->from) {
|
||||
goto failure;
|
||||
}
|
||||
|
||||
rdata->refer_to = ast_strdup(ast_refer_get_refer_to(refer));
|
||||
if (!rdata->refer_to) {
|
||||
goto failure;
|
||||
}
|
||||
rdata->to_self = ast_refer_get_to_self(refer);
|
||||
|
||||
/*
|
||||
* Sometimes from URI can contain URI parameters, so remove them.
|
||||
*
|
||||
* sip:user;user-options@domain;uri-parameters
|
||||
*/
|
||||
uri_params = strchr(rdata->from, '@');
|
||||
if (uri_params && (uri_params = strchr(uri_params, ';'))) {
|
||||
*uri_params = '\0';
|
||||
}
|
||||
return rdata;
|
||||
|
||||
failure:
|
||||
ao2_cleanup(rdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Checks if the given refer var name should be blocked.
|
||||
*
|
||||
* \details Some headers are not allowed to be overridden by the user.
|
||||
* Determine if the given var header name from the user is blocked for
|
||||
* an outgoing REFER.
|
||||
*
|
||||
* \param name name of header to see if it is blocked.
|
||||
*
|
||||
* \retval TRUE if the given header is blocked.
|
||||
*/
|
||||
static int is_refer_var_blocked(const char *name)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Don't block the Max-Forwards header because the user can override it */
|
||||
static const char *hdr[] = {
|
||||
"To",
|
||||
"From",
|
||||
"Via",
|
||||
"Route",
|
||||
"Contact",
|
||||
"Call-ID",
|
||||
"CSeq",
|
||||
"Allow",
|
||||
"Content-Length",
|
||||
"Content-Type",
|
||||
"Request-URI",
|
||||
};
|
||||
|
||||
for (i = 0; i < ARRAY_LEN(hdr); ++i) {
|
||||
if (!strcasecmp(name, hdr[i])) {
|
||||
/* Block addition of this header. */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Copies any other refer vars over to the request headers.
|
||||
*
|
||||
* \param refer The refer structure to copy headers from
|
||||
* \param tdata The SIP transmission data
|
||||
*/
|
||||
static enum pjsip_status_code vars_to_headers(const struct ast_refer *refer, pjsip_tx_data *tdata)
|
||||
{
|
||||
const char *name;
|
||||
const char *value;
|
||||
struct ast_refer_var_iterator *iter;
|
||||
|
||||
for (iter = ast_refer_var_iterator_init(refer);
|
||||
ast_refer_var_iterator_next(iter, &name, &value);
|
||||
ast_refer_var_unref_current(iter)) {
|
||||
if (!is_refer_var_blocked(name)) {
|
||||
ast_sip_add_header(tdata, name, value);
|
||||
}
|
||||
}
|
||||
ast_refer_var_iterator_destroy(iter);
|
||||
|
||||
return PJSIP_SC_OK;
|
||||
}
|
||||
|
||||
struct refer_out_of_dialog {
|
||||
pjsip_dialog *dlg;
|
||||
int authentication_challenge_count;
|
||||
};
|
||||
|
||||
/*! \brief REFER Out-of-dialog module, used to attach session data structure to subscription */
|
||||
static pjsip_module refer_out_of_dialog_module = {
|
||||
.name = { "REFER Out-of-dialog Module", 26 },
|
||||
.id = -1,
|
||||
.on_tx_request = refer_on_tx_request,
|
||||
/* Ensure that we are called after res_pjsp_nat module and before transport priority */
|
||||
.priority = PJSIP_MOD_PRIORITY_TSX_LAYER - 4,
|
||||
};
|
||||
|
||||
/*! \brief Helper function which returns the name-addr of the Refer-To header or NULL */
|
||||
static pjsip_uri *get_refer_to_uri(pjsip_tx_data *tdata)
|
||||
{
|
||||
const pj_str_t REFER_TO = { "Refer-To", 8 };
|
||||
pjsip_generic_string_hdr *refer_to;
|
||||
pjsip_uri *parsed_uri;
|
||||
|
||||
if (!(refer_to = pjsip_msg_find_hdr_by_name(tdata->msg, &REFER_TO, NULL))
|
||||
|| !(parsed_uri = pjsip_parse_uri(tdata->pool, refer_to->hvalue.ptr, refer_to->hvalue.slen, 0))
|
||||
|| (!PJSIP_URI_SCHEME_IS_SIP(parsed_uri) && !PJSIP_URI_SCHEME_IS_SIPS(parsed_uri))) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return parsed_uri;
|
||||
}
|
||||
|
||||
static pj_status_t refer_on_tx_request(pjsip_tx_data *tdata) {
|
||||
RAII_VAR(struct ast_str *, refer_to_str, ast_str_create(PJSIP_MAX_URL_SIZE), ast_free_ptr);
|
||||
const pj_str_t REFER_TO = { "Refer-To", 8 };
|
||||
pjsip_generic_string_hdr *refer_to_hdr;
|
||||
pjsip_dialog *dlg;
|
||||
struct refer_data *refer_data;
|
||||
pjsip_uri *parsed_uri;
|
||||
pjsip_sip_uri *refer_to_uri;
|
||||
|
||||
/*
|
||||
* If this is a request in response to a 401/407 Unauthorized challenge, the
|
||||
* Refer-To URI has been rewritten already, so don't attempt to re-write it again.
|
||||
* Checking for presence of the Authorization header is not an ideal solution. We do this because
|
||||
* there exists some race condition where this dialog is not the same as the one used
|
||||
* to send the original request in which case we don't have the correct refer_data.
|
||||
*/
|
||||
if (!refer_to_str
|
||||
|| pjsip_msg_find_hdr(tdata->msg, PJSIP_H_AUTHORIZATION, NULL)
|
||||
|| !(dlg = pjsip_tdata_get_dlg(tdata))
|
||||
|| !(refer_data = pjsip_dlg_get_mod_data(dlg, refer_out_of_dialog_module.id))
|
||||
|| !refer_data->to_self
|
||||
|| !(parsed_uri = get_refer_to_uri(tdata))) {
|
||||
goto out;
|
||||
}
|
||||
refer_to_uri = pjsip_uri_get_uri(parsed_uri);
|
||||
ast_sip_rewrite_uri_to_local(refer_to_uri, tdata);
|
||||
|
||||
pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, parsed_uri, ast_str_buffer(refer_to_str), ast_str_size(refer_to_str));
|
||||
refer_to_hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &REFER_TO, NULL);
|
||||
pj_strdup2(tdata->pool, &refer_to_hdr->hvalue, ast_str_buffer(refer_to_str));
|
||||
|
||||
out:
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
static int refer_unreference_dialog(void *obj)
|
||||
{
|
||||
struct refer_out_of_dialog *data = obj;
|
||||
|
||||
/* This is why we keep the dialog on the subscription. When the subscription
|
||||
* is destroyed, there is no guarantee that the underlying dialog is ready
|
||||
* to be destroyed. Furthermore, there's no guarantee in the opposite direction
|
||||
* either. The dialog could be destroyed before our subscription is. We fix
|
||||
* this problem by keeping a reference to the dialog until it is time to
|
||||
* destroy the subscription.
|
||||
*/
|
||||
pjsip_dlg_dec_session(data->dlg, &refer_out_of_dialog_module);
|
||||
data->dlg = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
/*! \brief Destructor for REFER out of dialog structure */
|
||||
static void refer_out_of_dialog_destroy(void *obj) {
|
||||
struct refer_out_of_dialog *data = obj;
|
||||
|
||||
if (data->dlg) {
|
||||
/* ast_sip_push_task_wait_servant should not be called in a destructor,
|
||||
* however in this case it seems to be fine.
|
||||
*/
|
||||
ast_sip_push_task_wait_servant(refer_serializer, refer_unreference_dialog, data);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Callback function to report status of implicit REFER-NOTIFY subscription.
|
||||
*
|
||||
* This function will be called on any state change in the REFER-NOTIFY subscription.
|
||||
* Its primary purpose is to report SUCCESS/FAILURE of a refer initiated via
|
||||
* \ref refer_send as well as to terminate the subscription, if necessary.
|
||||
*/
|
||||
static void refer_client_on_evsub_state(pjsip_evsub *sub, pjsip_event *event)
|
||||
{
|
||||
pjsip_tx_data *tdata;
|
||||
RAII_VAR(struct ast_sip_endpoint *, endpt, NULL, ao2_cleanup);
|
||||
struct refer_out_of_dialog *refer_data;
|
||||
int refer_success;
|
||||
int res = 0;
|
||||
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
refer_data = pjsip_evsub_get_mod_data(sub, refer_out_of_dialog_module.id);
|
||||
if (!refer_data || !refer_data->dlg) {
|
||||
return;
|
||||
}
|
||||
|
||||
endpt = ast_sip_dialog_get_endpoint(refer_data->dlg);
|
||||
|
||||
if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACCEPTED) {
|
||||
/* Check if subscription is suppressed and terminate and send completion code, if so. */
|
||||
pjsip_rx_data *rdata;
|
||||
pjsip_generic_string_hdr *refer_sub;
|
||||
const pj_str_t REFER_SUB = { "Refer-Sub", 9 };
|
||||
|
||||
ast_debug(3, "Refer accepted by %s\n", endpt ? ast_sorcery_object_get_id(endpt) : "(unknown endpoint)");
|
||||
|
||||
/* Check if response message */
|
||||
if (event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
|
||||
rdata = event->body.tsx_state.src.rdata;
|
||||
|
||||
/* Find Refer-Sub header */
|
||||
refer_sub = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &REFER_SUB, NULL);
|
||||
|
||||
/* Check if subscription is suppressed. If it is, the far end will not terminate it,
|
||||
* and the subscription will remain active until it times out. Terminating it here
|
||||
* eliminates the unnecessary timeout.
|
||||
*/
|
||||
if (refer_sub && !pj_stricmp2(&refer_sub->hvalue, "false")) {
|
||||
/* Since no subscription is desired, assume that call has been referred successfully
|
||||
* and terminate subscription.
|
||||
*/
|
||||
pjsip_evsub_set_mod_data(sub, refer_out_of_dialog_module.id, NULL);
|
||||
pjsip_evsub_terminate(sub, PJ_TRUE);
|
||||
res = -1;
|
||||
}
|
||||
}
|
||||
} else if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACTIVE ||
|
||||
pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
|
||||
/* Check for NOTIFY complete or error. */
|
||||
pjsip_msg *msg;
|
||||
pjsip_msg_body *body;
|
||||
pjsip_status_line status_line = { .code = 0 };
|
||||
pj_bool_t is_last;
|
||||
pj_status_t status;
|
||||
|
||||
if (event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
|
||||
pjsip_rx_data *rdata;
|
||||
pj_str_t refer_str;
|
||||
pj_cstr(&refer_str, "REFER");
|
||||
|
||||
rdata = event->body.tsx_state.src.rdata;
|
||||
msg = rdata->msg_info.msg;
|
||||
|
||||
if (msg->type == PJSIP_RESPONSE_MSG
|
||||
&& (event->body.tsx_state.tsx->status_code == 401
|
||||
|| event->body.tsx_state.tsx->status_code == 407)
|
||||
&& pj_stristr(&refer_str, &event->body.tsx_state.tsx->method.name)
|
||||
&& ++refer_data->authentication_challenge_count < MAX_RX_CHALLENGES
|
||||
&& endpt) {
|
||||
|
||||
if (!ast_sip_create_request_with_auth(&endpt->outbound_auths,
|
||||
event->body.tsx_state.src.rdata, event->body.tsx_state.tsx->last_tx, &tdata)) {
|
||||
/* Send authed REFER */
|
||||
ast_sip_send_request(tdata, refer_data->dlg, NULL, NULL, NULL);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg->type == PJSIP_REQUEST_MSG) {
|
||||
if (!pjsip_method_cmp(&msg->line.req.method, pjsip_get_notify_method())) {
|
||||
body = msg->body;
|
||||
if (body && !pj_stricmp2(&body->content_type.type, "message")
|
||||
&& !pj_stricmp2(&body->content_type.subtype, "sipfrag")) {
|
||||
pjsip_parse_status_line((char *)body->data, body->len, &status_line);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
status_line.code = msg->line.status.code;
|
||||
status_line.reason = msg->line.status.reason;
|
||||
}
|
||||
} else {
|
||||
status_line.code = 500;
|
||||
status_line.reason = *pjsip_get_status_text(500);
|
||||
}
|
||||
|
||||
is_last = (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED);
|
||||
/* If the status code is >= 200, the subscription is finished. */
|
||||
if (status_line.code >= 200 || is_last) {
|
||||
res = -1;
|
||||
|
||||
refer_success = status_line.code >= 200 && status_line.code < 300;
|
||||
|
||||
/* If subscription not terminated and subscription is finished (status code >= 200)
|
||||
* terminate it */
|
||||
if (!is_last) {
|
||||
pjsip_tx_data *tdata;
|
||||
|
||||
status = pjsip_evsub_initiate(sub, pjsip_get_subscribe_method(), 0, &tdata);
|
||||
if (status == PJ_SUCCESS) {
|
||||
pjsip_evsub_send_request(sub, tdata);
|
||||
}
|
||||
}
|
||||
ast_debug(3, "Refer completed: %d %.*s (%s)\n",
|
||||
status_line.code,
|
||||
(int)status_line.reason.slen, status_line.reason.ptr,
|
||||
refer_success ? "Success" : "Failure");
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
if (res) {
|
||||
ao2_cleanup(refer_data);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Send a REFER
|
||||
*
|
||||
* \param data The outbound refer data structure
|
||||
*
|
||||
* \return 0: success, -1: failure
|
||||
*/
|
||||
static int refer_send(void *data)
|
||||
{
|
||||
struct refer_data *rdata = data; /* The caller holds a reference */
|
||||
pjsip_tx_data *tdata;
|
||||
pjsip_evsub *sub;
|
||||
pj_str_t tmp;
|
||||
char refer_to_str[PJSIP_MAX_URL_SIZE];
|
||||
char disp_name_escaped[128];
|
||||
struct refer_out_of_dialog *refer;
|
||||
struct pjsip_evsub_user xfer_cb;
|
||||
RAII_VAR(char *, uri, NULL, ast_free);
|
||||
RAII_VAR(char *, tmp_str, NULL, ast_free);
|
||||
RAII_VAR(char *, display_name, NULL, ast_free);
|
||||
RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
|
||||
RAII_VAR(struct ast_sip_endpoint *, refer_to_endpoint, NULL, ao2_cleanup);
|
||||
|
||||
endpoint = ast_sip_get_endpoint(rdata->destination, 1, &uri);
|
||||
if (!endpoint) {
|
||||
ast_log(LOG_ERROR,
|
||||
"PJSIP REFER - Could not find endpoint '%s' and no default outbound endpoint configured\n",
|
||||
rdata->destination);
|
||||
return -1;
|
||||
}
|
||||
ast_debug(3, "Request URI: %s\n", uri);
|
||||
|
||||
refer_to_endpoint = ast_sip_get_endpoint(rdata->refer_to, 0, &tmp_str);
|
||||
if (!tmp_str) {
|
||||
ast_log(LOG_WARNING, "PJSIP REFER - Refer to not a valid resource identifier or SIP URI\n");
|
||||
return -1;
|
||||
}
|
||||
if (!(refer = ao2_alloc(sizeof(struct refer_out_of_dialog), refer_out_of_dialog_destroy))) {
|
||||
ast_log(LOG_ERROR, "PJSIP REFER - Could not allocate resources.\n");
|
||||
return -1;
|
||||
}
|
||||
/* The dialog will be terminated in the subscription event callback
|
||||
* when the subscription has terminated. */
|
||||
refer->authentication_challenge_count = 0;
|
||||
refer->dlg = ast_sip_create_dialog_uac(endpoint, uri, NULL);
|
||||
if (!refer->dlg) {
|
||||
ast_log(LOG_WARNING, "PJSIP REFER - Could not create dialog\n");
|
||||
ao2_cleanup(refer);
|
||||
return -1;
|
||||
}
|
||||
ast_sip_dialog_set_endpoint(refer->dlg, endpoint);
|
||||
|
||||
pj_bzero(&xfer_cb, sizeof(xfer_cb));
|
||||
xfer_cb.on_evsub_state = &refer_client_on_evsub_state;
|
||||
if (pjsip_xfer_create_uac(refer->dlg, &xfer_cb, &sub) != PJ_SUCCESS) {
|
||||
ast_log(LOG_WARNING, "PJSIP REFER - Could not create uac\n");
|
||||
ao2_cleanup(refer);
|
||||
return -1;
|
||||
}
|
||||
|
||||
display_name = ast_refer_get_var_and_unlink(rdata->refer, "display_name");
|
||||
if (display_name) {
|
||||
ast_escape_quoted(display_name, disp_name_escaped, sizeof(disp_name_escaped));
|
||||
snprintf(refer_to_str, sizeof(refer_to_str), "\"%s\" <%s>", disp_name_escaped, tmp_str);
|
||||
} else {
|
||||
snprintf(refer_to_str, sizeof(refer_to_str), "%s", tmp_str);
|
||||
}
|
||||
|
||||
/* refer_out_of_dialog_module requires a reference to dlg
|
||||
* which will be released in refer_client_on_evsub_state()
|
||||
* when the implicit REFER subscription terminates */
|
||||
pjsip_evsub_set_mod_data(sub, refer_out_of_dialog_module.id, refer);
|
||||
if (pjsip_xfer_initiate(sub, pj_cstr(&tmp, refer_to_str), &tdata) != PJ_SUCCESS) {
|
||||
ast_log(LOG_WARNING, "PJSIP REFER - Could not create request\n");
|
||||
goto failure;
|
||||
}
|
||||
|
||||
if (refer_to_endpoint && rdata->to_self) {
|
||||
pjsip_dlg_add_usage(refer->dlg, &refer_out_of_dialog_module, rdata);
|
||||
}
|
||||
|
||||
ast_sip_update_to_uri(tdata, uri);
|
||||
ast_sip_update_from(tdata, rdata->from);
|
||||
|
||||
/*
|
||||
* This copies any headers found in the refer's variables to
|
||||
* tdata.
|
||||
*/
|
||||
vars_to_headers(rdata->refer, tdata);
|
||||
ast_debug(1, "Sending REFER to '%s' (via endpoint %s) from '%s'\n",
|
||||
rdata->destination, ast_sorcery_object_get_id(endpoint), rdata->from);
|
||||
|
||||
if (pjsip_xfer_send_request(sub, tdata) == PJ_SUCCESS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
failure:
|
||||
ao2_cleanup(refer);
|
||||
pjsip_evsub_set_mod_data(sub, refer_out_of_dialog_module.id, NULL);
|
||||
pjsip_evsub_terminate(sub, PJ_FALSE);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int sip_refer_send(const struct ast_refer *refer)
|
||||
{
|
||||
struct refer_data *rdata;
|
||||
int res;
|
||||
|
||||
if (ast_strlen_zero(ast_refer_get_to(refer))) {
|
||||
ast_log(LOG_ERROR, "SIP REFER - a 'To' URI must be specified\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
rdata = refer_data_create(refer);
|
||||
if (!rdata) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = ast_sip_push_task_wait_serializer(refer_serializer, refer_send, rdata);
|
||||
ao2_ref(rdata, -1);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static const struct ast_refer_tech refer_tech = {
|
||||
.name = "pjsip",
|
||||
.refer_send = sip_refer_send,
|
||||
};
|
||||
|
||||
static int refer_incoming_attended_request(struct ast_sip_session *session, pjsip_rx_data *rdata, pjsip_sip_uri *target_uri,
|
||||
pjsip_param *replaces_param, struct refer_progress *progress)
|
||||
{
|
||||
@@ -1274,6 +1772,17 @@ static int load_module(void)
|
||||
pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_SUPPORTED, NULL, 1, &str_norefersub);
|
||||
}
|
||||
|
||||
if (ast_refer_tech_register(&refer_tech)) {
|
||||
return AST_MODULE_LOAD_DECLINE;
|
||||
}
|
||||
|
||||
refer_serializer = ast_sip_create_serializer("pjsip/refer");
|
||||
if (!refer_serializer) {
|
||||
ast_refer_tech_unregister(&refer_tech);
|
||||
return AST_MODULE_LOAD_DECLINE;
|
||||
}
|
||||
|
||||
ast_sip_register_service(&refer_out_of_dialog_module);
|
||||
ast_sip_register_service(&refer_progress_module);
|
||||
ast_sip_session_register_supplement(&refer_supplement);
|
||||
|
||||
@@ -1285,7 +1794,9 @@ static int load_module(void)
|
||||
static int unload_module(void)
|
||||
{
|
||||
ast_sip_session_unregister_supplement(&refer_supplement);
|
||||
ast_sip_unregister_service(&refer_out_of_dialog_module);
|
||||
ast_sip_unregister_service(&refer_progress_module);
|
||||
ast_taskprocessor_unreference(refer_serializer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Reference in New Issue
Block a user