#include "asterisk/module.h"
#include "asterisk/test.h"
#include "asterisk/taskprocessor.h"
+#include "asterisk/manager.h"
+#include "res_pjsip/include/res_pjsip_private.h"
+
+/*** DOCUMENTATION
+ <manager name="PJSIPShowRegistrationsInbound" language="en_US">
+ <synopsis>
+ Lists PJSIP inbound registrations.
+ </synopsis>
+ <syntax />
+ <description>
+ <para>
+ In response <literal>InboundRegistrationDetail</literal> events showing configuration and status
+ information are raised for each inbound registration object. As well as <literal>AuthDetail</literal>
+ events for each associated auth object. Once all events are completed an
+ <literal>InboundRegistrationDetailComplete</literal> is issued.
+ </para>
+ </description>
+ </manager>
+ ***/
/*! \brief Internal function which returns the expiration time for a contact */
static int registrar_get_expiration(const struct ast_sip_aor *aor, const pjsip_contact_hdr *contact, const pjsip_rx_data *rdata)
return ast_tvzero(contact->expiration_time) ? CMP_MATCH : 0;
}
-/*! \brief Internal function used to delete all contacts from an AOR */
+/*! \brief Internal function used to delete a contact from an AOR */
static int registrar_delete_contact(void *obj, void *arg, int flags)
{
struct ast_sip_contact *contact = obj;
ast_verb(3, "Removed contact '%s' from AOR '%s' due to request\n", contact->uri, aor_name);
ast_test_suite_event_notify("AOR_CONTACT_REMOVED",
"Contact: %s\r\n"
- "AOR: %s",
+ "AOR: %s\r\n"
+ "UserAgent: %s",
contact->uri,
- aor_name);
+ aor_name,
+ contact->user_agent);
}
return 0;
return task_data;
}
+static const pj_str_t path_hdr_name = { "Path", 4 };
+
+static int build_path_data(struct rx_task_data *task_data, struct ast_str **path_str)
+{
+ pjsip_generic_string_hdr *path_hdr = pjsip_msg_find_hdr_by_name(task_data->rdata->msg_info.msg, &path_hdr_name, NULL);
+
+ if (!path_hdr) {
+ return 0;
+ }
+
+ *path_str = ast_str_create(64);
+ if (!path_str) {
+ return -1;
+ }
+
+ ast_str_set(path_str, 0, "%.*s", (int)path_hdr->hvalue.slen, path_hdr->hvalue.ptr);
+
+ while ((path_hdr = (pjsip_generic_string_hdr *) pjsip_msg_find_hdr_by_name(task_data->rdata->msg_info.msg, &path_hdr_name, path_hdr->next))) {
+ ast_str_append(path_str, 0, ",%.*s", (int)path_hdr->hvalue.slen, path_hdr->hvalue.ptr);
+ }
+
+ return 0;
+}
+
+static int registrar_validate_path(struct rx_task_data *task_data, struct ast_str **path_str)
+{
+ const pj_str_t path_supported_name = { "path", 4 };
+ pjsip_supported_hdr *supported_hdr;
+ int i;
+
+ if (!task_data->aor->support_path) {
+ return 0;
+ }
+
+ if (build_path_data(task_data, path_str)) {
+ return -1;
+ }
+
+ if (!*path_str) {
+ return 0;
+ }
+
+ supported_hdr = pjsip_msg_find_hdr(task_data->rdata->msg_info.msg, PJSIP_H_SUPPORTED, NULL);
+ if (!supported_hdr) {
+ return -1;
+ }
+
+ /* Find advertised path support */
+ for (i = 0; i < supported_hdr->count; i++) {
+ if (!pj_stricmp(&supported_hdr->values[i], &path_supported_name)) {
+ return 0;
+ }
+ }
+
+ /* Path header present, but support not advertised */
+ return -1;
+}
+
static int rx_task(void *data)
{
+ static const pj_str_t USER_AGENT = { "User-Agent", 10 };
+
RAII_VAR(struct rx_task_data *, task_data, data, ao2_cleanup);
RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
pjsip_tx_data *tdata;
pjsip_response_addr addr;
const char *aor_name = ast_sorcery_object_get_id(task_data->aor);
+ RAII_VAR(struct ast_str *, path_str, NULL, ast_free);
+ struct ast_sip_contact *response_contact;
+ char *user_agent = NULL;
+ pjsip_user_agent_hdr *user_agent_hdr;
/* Retrieve the current contacts, we'll need to know whether to update or not */
contacts = ast_sip_location_retrieve_aor_contacts(task_data->aor);
return PJ_TRUE;
}
+ if (registrar_validate_path(task_data, &path_str)) {
+ /* Ensure that intervening proxies did not make invalid modifications to the request */
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), task_data->rdata, 420, NULL, NULL, NULL);
+ ast_log(LOG_WARNING, "Invalid modifications made to REGISTER request from '%s' by intervening proxy\n",
+ ast_sorcery_object_get_id(task_data->endpoint));
+ return PJ_TRUE;
+ }
+
if ((MAX(added - deleted, 0) + (!task_data->aor->remove_existing ? ao2_container_count(contacts) : 0)) > task_data->aor->max_contacts) {
/* Enforce the maximum number of contacts */
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), task_data->rdata, 403, NULL, NULL, NULL);
return PJ_TRUE;
}
+ user_agent_hdr = pjsip_msg_find_hdr_by_name(task_data->rdata->msg_info.msg, &USER_AGENT, NULL);
+ if (user_agent_hdr) {
+ size_t alloc_size = pj_strlen(&user_agent_hdr->hvalue) + 1;
+ user_agent = ast_alloca(alloc_size);
+ ast_copy_pj_str(user_agent, &user_agent_hdr->hvalue, alloc_size);
+ }
+
/* Iterate each provided Contact header and add, update, or delete */
while ((contact_hdr = pjsip_msg_find_hdr(task_data->rdata->msg_info.msg, PJSIP_H_CONTACT, contact_hdr ? contact_hdr->next : NULL))) {
int expiration;
continue;
}
- ast_sip_location_add_contact(task_data->aor, contact_uri, ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1)));
+ if (ast_sip_location_add_contact(task_data->aor, contact_uri, ast_tvadd(ast_tvnow(),
+ ast_samp2tv(expiration, 1)), path_str ? ast_str_buffer(path_str) : NULL,
+ user_agent)) {
+ ast_log(LOG_ERROR, "Unable to bind contact '%s' to AOR '%s'\n",
+ contact_uri, aor_name);
+ continue;
+ }
+
ast_verb(3, "Added contact '%s' to AOR '%s' with expiration of %d seconds\n",
contact_uri, aor_name, expiration);
ast_test_suite_event_notify("AOR_CONTACT_ADDED",
"Contact: %s\r\n"
"AOR: %s\r\n"
- "Expiration: %d",
+ "Expiration: %d\r\n"
+ "UserAgent: %s",
contact_uri,
aor_name,
- expiration);
+ expiration,
+ user_agent);
} else if (expiration) {
- RAII_VAR(struct ast_sip_contact *, updated, ast_sorcery_copy(ast_sip_get_sorcery(), contact), ao2_cleanup);
- updated->expiration_time = ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1));
- updated->qualify_frequency = task_data->aor->qualify_frequency;
- updated->authenticate_qualify = task_data->aor->authenticate_qualify;
+ struct ast_sip_contact *contact_update;
- ast_sip_location_update_contact(updated);
+ contact_update = ast_sorcery_copy(ast_sip_get_sorcery(), contact);
+ if (!contact_update) {
+ ast_log(LOG_ERROR, "Failed to update contact '%s' expiration time to %d seconds.\n",
+ contact->uri, expiration);
+ continue;
+ }
+
+ contact_update->expiration_time = ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1));
+ contact_update->qualify_frequency = task_data->aor->qualify_frequency;
+ contact_update->authenticate_qualify = task_data->aor->authenticate_qualify;
+ if (path_str) {
+ ast_string_field_set(contact_update, path, ast_str_buffer(path_str));
+ }
+ if (user_agent) {
+ ast_string_field_set(contact_update, user_agent, user_agent);
+ }
+
+ if (ast_sip_location_update_contact(contact_update)) {
+ ast_log(LOG_ERROR, "Failed to update contact '%s' expiration time to %d seconds.\n",
+ contact->uri, expiration);
+ ast_sorcery_delete(ast_sip_get_sorcery(), contact);
+ continue;
+ }
ast_debug(3, "Refreshed contact '%s' on AOR '%s' with new expiration of %d seconds\n",
contact_uri, aor_name, expiration);
ast_test_suite_event_notify("AOR_CONTACT_REFRESHED",
"Contact: %s\r\n"
"AOR: %s\r\n"
- "Expiration: %d",
+ "Expiration: %d\r\n"
+ "UserAgent: %s",
contact_uri,
aor_name,
- expiration);
+ expiration,
+ contact_update->user_agent);
+ ao2_cleanup(contact_update);
} else {
+ /* We want to report the user agent that was actually in the removed contact */
+ user_agent = ast_strdupa(contact->user_agent);
ast_sip_location_delete_contact(contact);
ast_verb(3, "Removed contact '%s' from AOR '%s' due to request\n", contact_uri, aor_name);
ast_test_suite_event_notify("AOR_CONTACT_REMOVED",
"Contact: %s\r\n"
- "AOR: %s",
+ "AOR: %s\r\n"
+ "UserAgent: %s",
contact_uri,
- aor_name);
+ aor_name,
+ user_agent);
}
}
/* Update the contacts as things will probably have changed */
ao2_cleanup(contacts);
+
contacts = ast_sip_location_retrieve_aor_contacts(task_data->aor);
+ response_contact = ao2_callback(contacts, 0, NULL, NULL);
/* Send a response containing all of the contacts (including static) that are present on this AOR */
- if (pjsip_endpt_create_response(ast_sip_get_pjsip_endpoint(), task_data->rdata, 200, NULL, &tdata) != PJ_SUCCESS) {
+ if (ast_sip_create_response(task_data->rdata, 200, response_contact, &tdata) != PJ_SUCCESS) {
+ ao2_cleanup(response_contact);
return PJ_TRUE;
}
+ ao2_cleanup(response_contact);
/* Add the date header to the response, some UAs use this to set their date and time */
registrar_add_date_header(tdata);
ao2_callback(contacts, 0, registrar_add_contact, tdata);
if (pjsip_get_response_addr(tdata->pool, task_data->rdata, &addr) == PJ_SUCCESS) {
- pjsip_endpt_send_response(ast_sip_get_pjsip_endpoint(), &addr, tdata, NULL, NULL);
+ ast_sip_send_response(&addr, tdata, task_data->endpoint);
} else {
pjsip_tx_data_dec_ref(tdata);
}
ast_pjsip_rdata_get_endpoint(rdata), ao2_cleanup);
RAII_VAR(struct ast_sip_aor *, aor, NULL, ao2_cleanup);
pjsip_sip_uri *uri;
- char user_name[64], domain_name[64];
+ char *domain_name;
char *configured_aors, *aor_name;
+ RAII_VAR(struct ast_str *, id, NULL, ast_free);
if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_register_method) || !endpoint) {
return PJ_FALSE;
}
uri = pjsip_uri_get_uri(rdata->msg_info.to->uri);
- ast_copy_pj_str(user_name, &uri->user, sizeof(user_name));
- ast_copy_pj_str(domain_name, &uri->host, sizeof(domain_name));
+ domain_name = ast_alloca(uri->host.slen + 1);
+ ast_copy_pj_str(domain_name, &uri->host, uri->host.slen + 1);
configured_aors = ast_strdupa(endpoint->aors);
/* Iterate the configured AORs to see if the user or the user+domain match */
while ((aor_name = strsep(&configured_aors, ","))) {
- char id[AST_UUID_STR_LEN];
- RAII_VAR(struct ast_sip_domain_alias *, alias, NULL, ao2_cleanup);
+ struct ast_sip_domain_alias *alias = NULL;
- snprintf(id, sizeof(id), "%s@%s", user_name, domain_name);
- if (!strcmp(aor_name, id)) {
+ if (!pj_strcmp2(&uri->user, aor_name)) {
break;
}
+ if (!id && !(id = ast_str_create(uri->user.slen + uri->host.slen + 2))) {
+ return PJ_TRUE;
+ }
+
+ ast_str_set(&id, 0, "%.*s@", (int)uri->user.slen, uri->user.ptr);
if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) {
- snprintf(id, sizeof(id), "%s@%s", user_name, alias->domain);
- if (!strcmp(aor_name, id)) {
- break;
- }
+ ast_str_append(&id, 0, "%s", alias->domain);
+ ao2_cleanup(alias);
+ } else {
+ ast_str_append(&id, 0, "%s", domain_name);
}
- if (!strcmp(aor_name, user_name)) {
+ if (!strcmp(aor_name, ast_str_buffer(id))) {
+ ast_free(id);
break;
}
}
/* The provided AOR name was not found (be it within the configuration or sorcery itself) */
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL);
ast_sip_report_req_no_support(endpoint, rdata, "registrar_requested_aor_not_found");
- ast_log(LOG_WARNING, "AOR '%s' not found for endpoint '%s'\n", user_name, ast_sorcery_object_get_id(endpoint));
+ ast_log(LOG_WARNING, "AOR '%.*s' not found for endpoint '%s'\n", (int)uri->user.slen, uri->user.ptr, ast_sorcery_object_get_id(endpoint));
return PJ_TRUE;
}
return PJ_TRUE;
}
+/* function pointer to callback needs to be within the module
+ in order to avoid problems with an undefined symbol */
+static int sip_contact_to_str(void *acp, void *arg, int flags)
+{
+ return ast_sip_contact_to_str(acp, arg, flags);
+}
+
+static int ami_registrations_aor(void *obj, void *arg, int flags)
+{
+ struct ast_sip_aor *aor = obj;
+ struct ast_sip_ami *ami = arg;
+ int *count = ami->arg;
+ RAII_VAR(struct ast_str *, buf,
+ ast_sip_create_ami_event("InboundRegistrationDetail", ami), ast_free);
+
+ if (!buf) {
+ return -1;
+ }
+
+ ast_sip_sorcery_object_to_ami(aor, &buf);
+ ast_str_append(&buf, 0, "Contacts: ");
+ ast_sip_for_each_contact(aor, sip_contact_to_str, &buf);
+ ast_str_append(&buf, 0, "\r\n");
+
+ astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
+ (*count)++;
+ return 0;
+}
+
+static int ami_registrations_endpoint(void *obj, void *arg, int flags)
+{
+ struct ast_sip_endpoint *endpoint = obj;
+ return ast_sip_for_each_aor(
+ endpoint->aors, ami_registrations_aor, arg);
+}
+
+static int ami_registrations_endpoints(void *arg)
+{
+ RAII_VAR(struct ao2_container *, endpoints,
+ ast_sip_get_endpoints(), ao2_cleanup);
+
+ if (!endpoints) {
+ return 0;
+ }
+
+ ao2_callback(endpoints, OBJ_NODATA, ami_registrations_endpoint, arg);
+ return 0;
+}
+
+static int ami_show_registrations(struct mansession *s, const struct message *m)
+{
+ int count = 0;
+ struct ast_sip_ami ami = { .s = s, .m = m, .arg = &count };
+ astman_send_listack(s, m, "Following are Events for each Inbound "
+ "registration", "start");
+
+ ami_registrations_endpoints(&ami);
+
+ astman_append(s,
+ "Event: InboundRegistrationDetailComplete\r\n"
+ "EventList: Complete\r\n"
+ "ListItems: %d\r\n\r\n", count);
+ return 0;
+}
+
+#define AMI_SHOW_REGISTRATIONS "PJSIPShowRegistrationsInbound"
+
static pjsip_module registrar_module = {
.name = { "Registrar", 9 },
.id = -1,
return AST_MODULE_LOAD_DECLINE;
}
+ ast_manager_register_xml(AMI_SHOW_REGISTRATIONS, EVENT_FLAG_SYSTEM,
+ ami_show_registrations);
+
return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
{
+ ast_manager_unregister(AMI_SHOW_REGISTRATIONS);
ast_sip_unregister_service(®istrar_module);
ao2_cleanup(serializers);