Files
asterisk/res/res_sip/sip_options.c

379 lines
10 KiB
C
Raw Normal View History

/*
* sip_options.c
*
* Created on: Jan 25, 2013
* Author: mjordan
*/
#include "asterisk.h"
#include <pjsip.h>
#include <pjsip_ua.h>
#include <pjlib.h>
#include "asterisk/res_sip.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/astobj2.h"
#include "asterisk/cli.h"
#include "include/res_sip_private.h"
#define DEFAULT_LANGUAGE "en"
#define DEFAULT_ENCODING "text/plain"
#define QUALIFIED_BUCKETS 211
/*! \brief Scheduling context for qualifies */
static struct ast_sched_context *sched; /* XXX move this to registrar */
struct ao2_container *scheduled_qualifies;
struct qualify_info {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(endpoint_id);
);
char *scheduler_data;
int scheduler_id;
};
static pj_bool_t options_module_start(void);
static pj_bool_t options_module_stop(void);
static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata);
static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata);
static pjsip_module options_module = {
.name = {"Options Module", 14},
.id = -1,
.priority = PJSIP_MOD_PRIORITY_APPLICATION,
.start = options_module_start,
.stop = options_module_stop,
.on_rx_request = options_module_on_rx_request,
.on_rx_response = options_module_on_rx_response,
};
static pj_bool_t options_module_start(void)
{
if (!(sched = ast_sched_context_create()) ||
ast_sched_start_thread(sched)) {
return -1;
}
return PJ_SUCCESS;
}
static pj_bool_t options_module_stop(void)
{
ao2_t_ref(scheduled_qualifies, -1, "Remove scheduled qualifies on module stop");
if (sched) {
ast_sched_context_destroy(sched);
}
return PJ_SUCCESS;
}
static pj_status_t send_options_response(pjsip_rx_data *rdata, pjsip_dialog *pj_dlg, int code)
{
pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint();
pjsip_transaction *pj_trans = pjsip_rdata_get_tsx(rdata);
pjsip_tx_data *tdata;
const pjsip_hdr *hdr;
pjsip_response_addr res_addr;
pj_status_t status;
/* Make the response object */
status = pjsip_endpt_create_response(endpt, rdata, code, NULL, &tdata);
if (status != PJ_SUCCESS) {
return status;
}
/* Add appropriate headers */
if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_ACCEPT, NULL))) {
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr));
}
if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_ALLOW, NULL))) {
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr));
}
if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED, NULL))) {
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr));
}
/*
* XXX TODO: pjsip doesn't care a lot about either of these headers -
* while it provides specific methods to create them, they are defined
* to be the standard string header creation. We never did add them
* in chan_sip, although RFC 3261 says they SHOULD. Hard coded here.
*/
ast_sip_add_header(tdata, "Accept-Encoding", DEFAULT_ENCODING);
ast_sip_add_header(tdata, "Accept-Language", DEFAULT_LANGUAGE);
if (pj_dlg && pj_trans) {
status = pjsip_dlg_send_response(pj_dlg, pj_trans, tdata);
} else {
/* Get where to send request. */
status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
if (status != PJ_SUCCESS) {
pjsip_tx_data_dec_ref(tdata);
return status;
}
status = pjsip_endpt_send_response(endpt, &res_addr, tdata, NULL, NULL);
}
return status;
}
static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata)
{
RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
pjsip_uri *ruri;
pjsip_sip_uri *sip_ruri;
char exten[AST_MAX_EXTENSION];
if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_options_method)) {
return PJ_FALSE;
}
endpoint = ast_pjsip_rdata_get_endpoint(rdata);
ast_assert(endpoint != NULL);
ruri = rdata->msg_info.msg->line.req.uri;
if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) {
send_options_response(rdata, dlg, 416);
return -1;
}
sip_ruri = pjsip_uri_get_uri(ruri);
ast_copy_pj_str(exten, &sip_ruri->user, sizeof(exten));
if (ast_shutting_down()) {
send_options_response(rdata, dlg, 503);
} else if (!ast_exists_extension(NULL, endpoint->context, exten, 1, NULL)) {
send_options_response(rdata, dlg, 404);
} else {
send_options_response(rdata, dlg, 200);
}
return PJ_TRUE;
}
static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata)
{
return PJ_SUCCESS;
}
static int qualify_info_hash_fn(const void *obj, int flags)
{
const struct qualify_info *info = obj;
const char *endpoint_id = flags & OBJ_KEY ? obj : info->endpoint_id;
return ast_str_hash(endpoint_id);
}
static int qualify_info_cmp_fn(void *obj, void *arg, int flags)
{
struct qualify_info *left = obj;
struct qualify_info *right = arg;
const char *right_endpoint_id = flags & OBJ_KEY ? arg : right->endpoint_id;
return strcmp(left->endpoint_id, right_endpoint_id) ? 0 : CMP_MATCH | CMP_STOP;
}
static void qualify_info_destructor(void *obj)
{
struct qualify_info *info = obj;
if (!info) {
return;
}
ast_string_field_free_memory(info);
/* Cancel the qualify */
if (!AST_SCHED_DEL(sched, info->scheduler_id)) {
/* If we successfully deleted the qualify, we got it before it
* fired. We can safely delete the data that was passed to it.
* Otherwise, we're getting deleted while this is firing - don't
* touch that memory!
*/
ast_free(info->scheduler_data);
}
}
static struct qualify_info *create_qualify_info(struct ast_sip_endpoint *endpoint)
{
struct qualify_info *info;
info = ao2_alloc(sizeof(*info), qualify_info_destructor);
if (!info) {
return NULL;
}
if (ast_string_field_init(info, 64)) {
ao2_ref(info, -1);
return NULL;
}
ast_string_field_set(info, endpoint_id, ast_sorcery_object_get_id(endpoint));
return info;
}
static int send_qualify_request(void *data)
{
struct ast_sip_endpoint *endpoint = data;
pjsip_tx_data *tdata;
/* YAY! Send an OPTIONS request. */
ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata);
ast_sip_send_request(tdata, NULL, endpoint);
ao2_cleanup(endpoint);
return 0;
}
static int qualify_endpoint_scheduler_cb(const void *data)
{
RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
struct ast_sorcery *sorcery;
char *endpoint_id = (char *)data;
sorcery = ast_sip_get_sorcery();
if (!sorcery) {
ast_free(endpoint_id);
return 0;
}
endpoint = ast_sorcery_retrieve_by_id(sorcery, "endpoint", endpoint_id);
if (!endpoint) {
/* Whoops, endpoint went away */
ast_free(endpoint_id);
return 0;
}
ast_sip_push_task(NULL, send_qualify_request, endpoint);
return 1;
}
static void schedule_qualifies(void)
{
RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup);
struct ao2_iterator it_endpoints;
struct ast_sip_endpoint *endpoint;
struct qualify_info *info;
char *endpoint_id;
endpoints = ast_res_sip_get_endpoints();
if (!endpoints) {
return;
}
it_endpoints = ao2_iterator_init(endpoints, 0);
while ((endpoint = ao2_iterator_next(&it_endpoints))) {
if (endpoint->qualify_frequency) {
/* XXX TODO: This really should only qualify registered peers,
* which means we need a registrar. We should check the
* registrar to see if this endpoint has registered and, if
* not, pass on it.
*
* Actually, all of this should just get moved into the registrar.
* Otherwise, the registar will have to kick this off when a
* new endpoint registers, so it just makes sense to have it
* all live there.
*/
info = create_qualify_info(endpoint);
if (!info) {
ao2_ref(endpoint, -1);
break;
}
endpoint_id = ast_strdup(info->endpoint_id);
if (!endpoint_id) {
ao2_t_ref(info, -1, "Dispose of info on off nominal");
ao2_ref(endpoint, -1);
break;
}
info->scheduler_data = endpoint_id;
info->scheduler_id = ast_sched_add_variable(sched, endpoint->qualify_frequency * 1000, qualify_endpoint_scheduler_cb, endpoint_id, 1);
ao2_t_link(scheduled_qualifies, info, "Link scheduled qualify information into container");
ao2_t_ref(info, -1, "Dispose of creation ref");
}
ao2_t_ref(endpoint, -1, "Dispose of iterator ref");
}
ao2_iterator_destroy(&it_endpoints);
}
static char *send_options(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
const char *endpoint_name;
pjsip_tx_data *tdata;
switch (cmd) {
case CLI_INIT:
e->command = "sip send options";
e->usage =
"Usage: sip send options <endpoint>\n"
" Send a SIP OPTIONS request to the specified endpoint.\n";
return NULL;
case CLI_GENERATE:
return NULL;
}
if (a->argc != 4) {
return CLI_SHOWUSAGE;
}
endpoint_name = a->argv[3];
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
if (!endpoint) {
ast_log(LOG_ERROR, "Unable to retrieve endpoint %s\n", endpoint_name);
return CLI_FAILURE;
}
if (ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata)) {
ast_log(LOG_ERROR, "Unable to create OPTIONS request to endpoint %s\n", endpoint_name);
return CLI_FAILURE;
}
if (ast_sip_send_request(tdata, NULL, endpoint)) {
ast_log(LOG_ERROR, "Unable to send OPTIONS request to endpoint %s\n", endpoint_name);
return CLI_FAILURE;
}
return CLI_SUCCESS;
}
static struct ast_cli_entry cli_options[] = {
AST_CLI_DEFINE(send_options, "Send an OPTIONS requst to an arbitrary SIP URI"),
};
int ast_res_sip_init_options_handling(int reload)
{
const pj_str_t STR_OPTIONS = { "OPTIONS", 7 };
if (scheduled_qualifies) {
ao2_t_ref(scheduled_qualifies, -1, "Remove old scheduled qualifies");
}
scheduled_qualifies = ao2_t_container_alloc(QUALIFIED_BUCKETS, qualify_info_hash_fn, qualify_info_cmp_fn, "Create container for scheduled qualifies");
if (!scheduled_qualifies) {
return -1;
}
if (reload) {
return 0;
}
if (pjsip_endpt_register_module(ast_sip_get_pjsip_endpoint(), &options_module) != PJ_SUCCESS) {
options_module_stop();
return -1;
}
if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW, NULL, 1, &STR_OPTIONS) != PJ_SUCCESS) {
pjsip_endpt_unregister_module(ast_sip_get_pjsip_endpoint(), &options_module);
return -1;
}
ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));
schedule_qualifies();
return 0;
}