diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c index 985933e2dff..efd2bd9397c 100644 --- a/res/res_pjsip_registrar.c +++ b/res/res_pjsip_registrar.c @@ -122,25 +122,28 @@ static int registrar_find_contact(void *obj, void *arg, int flags) { struct ast_sip_contact *contact = obj; const struct registrar_contact_details *details = arg; - pjsip_uri *contact_uri = pjsip_parse_uri(details->pool, (char*)contact->uri, strlen(contact->uri), 0); + pjsip_uri *contact_uri; + + if (ast_tvzero(contact->expiration_time)) { + return 0; + } + + contact_uri = pjsip_parse_uri(details->pool, (char*)contact->uri, strlen(contact->uri), 0); return (pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, details->uri, contact_uri) == PJ_SUCCESS) ? CMP_MATCH : 0; } /*! \brief Internal function which validates provided Contact headers to confirm that they are acceptable, and returns number of contacts */ -static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_container *contacts, struct ast_sip_aor *aor, int *added, int *updated, int *deleted) +static int registrar_validate_contacts(const pjsip_rx_data *rdata, pj_pool_t *pool, struct ao2_container *contacts, + struct ast_sip_aor *aor, int permanent, int *added, int *updated, int *deleted) { pjsip_contact_hdr *previous = NULL; pjsip_contact_hdr *contact = (pjsip_contact_hdr *)&rdata->msg_info.msg->hdr; struct registrar_contact_details details = { - .pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Contact Comparison", 256, 256), + .pool = pool, }; - if (!details.pool) { - return -1; - } - - while ((contact = (pjsip_contact_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact->next))) { + for (; (contact = (pjsip_contact_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact->next)); pj_pool_reset(pool)) { int expiration = registrar_get_expiration(aor, contact, rdata); struct ast_sip_contact *existing; char contact_uri[pjsip_max_url_size]; @@ -148,16 +151,14 @@ static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_co if (contact->star) { /* The expiration MUST be 0 when a '*' contact is used and there must be no other contact */ if (expiration != 0 || previous) { - pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool); return -1; } /* Count all contacts to delete */ - *deleted = ao2_container_count(contacts); + *deleted = ao2_container_count(contacts) - permanent; previous = contact; continue; } else if (previous && previous->star) { /* If there is a previous contact and it is a '*' this is a deal breaker */ - pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool); return -1; } previous = contact; @@ -171,13 +172,11 @@ static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_co /* pjsip_uri_print returns -1 if there's not enough room in the buffer */ if (pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, details.uri, contact_uri, sizeof(contact_uri)) < 0) { /* If the total length of the uri is greater than pjproject can handle, go no further */ - pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool); return -1; } if (details.uri->host.slen >= pj_max_hostname) { /* If the length of the hostname is greater than pjproject can handle, go no further */ - pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool); return -1; } @@ -195,25 +194,20 @@ static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_co } } - /* The provided contacts are acceptable, huzzah! */ - pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool); return 0; } -/*! \brief Callback function which prunes static contacts */ -static int registrar_prune_static(void *obj, void *arg, int flags) -{ - struct ast_sip_contact *contact = obj; - - return ast_tvzero(contact->expiration_time) ? CMP_MATCH : 0; -} - /*! \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; const char *aor_name = arg; + /* Permanent contacts can't be deleted */ + if (ast_tvzero(contact->expiration_time)) { + return 0; + } + ast_sip_location_delete_contact(contact); if (!ast_strlen_zero(aor_name)) { ast_verb(3, "Removed contact '%s' from AOR '%s' due to request\n", contact->uri, aor_name); @@ -270,7 +264,7 @@ static int build_path_data(pjsip_rx_data *rdata, struct ast_str **path_str) } *path_str = ast_str_create(64); - if (!path_str) { + if (!*path_str) { return -1; } @@ -442,7 +436,8 @@ static int vec_contact_add(void *obj, void *arg, int flags) * * \return Nothing */ -static void remove_excess_contacts(struct ao2_container *contacts, unsigned int to_remove) +static void remove_excess_contacts(struct ao2_container *contacts, struct ao2_container *response_contacts, + unsigned int to_remove) { struct excess_contact_vector contact_vec; @@ -482,11 +477,28 @@ static void remove_excess_contacts(struct ao2_container *contacts, unsigned int contact->uri, contact->aor, contact->user_agent); + + ao2_unlink(response_contacts, contact); } AST_VECTOR_FREE(&contact_vec); } +/*! \brief Callback function which adds non-permanent contacts to a container */ +static int registrar_add_non_permanent(void *obj, void *arg, int flags) +{ + struct ast_sip_contact *contact = obj; + struct ao2_container *container = arg; + + if (ast_tvzero(contact->expiration_time)) { + return 0; + } + + ao2_link(container, contact); + + return 0; +} + struct aor_core_response { /*! Tx data to use for statefull response. NULL for stateless response. */ pjsip_tx_data *tdata; @@ -506,8 +518,10 @@ static void register_aor_core(pjsip_rx_data *rdata, int added = 0; int updated = 0; int deleted = 0; + int permanent = 0; int contact_count; - pjsip_contact_hdr *contact_hdr = NULL; + struct ao2_container *existing_contacts = NULL; + pjsip_contact_hdr *contact_hdr = (pjsip_contact_hdr *)&rdata->msg_info.msg->hdr; struct registrar_contact_details details = { 0, }; pjsip_tx_data *tdata; RAII_VAR(struct ast_str *, path_str, NULL, ast_free); @@ -523,15 +537,28 @@ static void register_aor_core(pjsip_rx_data *rdata, char *call_id = NULL; size_t alloc_size; - /* So we don't count static contacts against max_contacts we prune them out from the container */ - ao2_callback(contacts, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, registrar_prune_static, NULL); + /* We create a single pool and use it throughout this function where we need one */ + details.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), + "Contact Comparison", 1024, 256); + if (!details.pool) { + response->code = 500; + return; + } - if (registrar_validate_contacts(rdata, contacts, aor, &added, &updated, &deleted)) { + /* If there are any permanent contacts configured on the AOR we need to take them + * into account when counting contacts. + */ + if (aor->permanent_contacts) { + permanent = ao2_container_count(aor->permanent_contacts); + } + + if (registrar_validate_contacts(rdata, details.pool, contacts, aor, permanent, &added, &updated, &deleted)) { /* The provided Contact headers do not conform to the specification */ ast_sip_report_failed_acl(endpoint, rdata, "registrar_invalid_contacts_provided"); ast_log(LOG_WARNING, "Failed to validate contacts in REGISTER request from '%s'\n", ast_sorcery_object_get_id(endpoint)); response->code = 400; + pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool); return; } @@ -540,15 +567,29 @@ static void register_aor_core(pjsip_rx_data *rdata, ast_log(LOG_WARNING, "Invalid modifications made to REGISTER request from '%s' by intervening proxy\n", ast_sorcery_object_get_id(endpoint)); response->code = 420; + pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool); return; } if (aor->remove_existing) { /* Cumulative number of contacts affected by this registration */ contact_count = MAX(updated + added - deleted, 0); + + /* We need to keep track of only existing contacts so we can later + * remove them if need be. + */ + existing_contacts = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, + NULL, ast_sorcery_object_id_compare); + if (!existing_contacts) { + response->code = 500; + pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool); + return; + } + + ao2_callback(contacts, OBJ_NODATA, registrar_add_non_permanent, existing_contacts); } else { /* Total contacts after this registration */ - contact_count = ao2_container_count(contacts) + added - deleted; + contact_count = ao2_container_count(contacts) - permanent + added - deleted; } if (contact_count > aor->max_contacts) { /* Enforce the maximum number of contacts */ @@ -556,13 +597,8 @@ static void register_aor_core(pjsip_rx_data *rdata, ast_log(LOG_WARNING, "Registration attempt from endpoint '%s' to AOR '%s' will exceed max contacts of %u\n", ast_sorcery_object_get_id(endpoint), aor_name, aor->max_contacts); response->code = 403; - return; - } - - details.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), - "Contact Comparison", 256, 256); - if (!details.pool) { - response->code = 500; + pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool); + ao2_cleanup(existing_contacts); return; } @@ -595,7 +631,7 @@ static void register_aor_core(pjsip_rx_data *rdata, } /* Iterate each provided Contact header and add, update, or delete */ - while ((contact_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact_hdr ? contact_hdr->next : NULL))) { + for (; (contact_hdr = (pjsip_contact_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact_hdr->next)); pj_pool_reset(details.pool)) { int expiration; char contact_uri[pjsip_max_url_size]; RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup); @@ -604,6 +640,13 @@ static void register_aor_core(pjsip_rx_data *rdata, /* A star means to unregister everything, so do so for the possible contacts */ ao2_callback(contacts, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, registrar_delete_contact, (void *)aor_name); + /* If we are keeping track of existing contacts for removal then, well, there is + * absolutely nothing left so no need to try to remove any. + */ + if (existing_contacts) { + ao2_ref(existing_contacts, -1); + existing_contacts = NULL; + } break; } @@ -617,6 +660,14 @@ static void register_aor_core(pjsip_rx_data *rdata, pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, details.uri, contact_uri, sizeof(contact_uri)); contact = ao2_callback(contacts, OBJ_UNLINK, registrar_find_contact, &details); + + /* If a contact was returned and we need to keep track of existing contacts then it + * should be removed. + */ + if (contact && existing_contacts) { + ao2_unlink(existing_contacts, contact); + } + if (!contact) { int prune_on_boot; @@ -672,6 +723,8 @@ static void register_aor_core(pjsip_rx_data *rdata, aor_name, expiration, user_agent); + + ao2_link(contacts, contact); } else if (expiration) { struct ast_sip_contact *contact_update; @@ -712,6 +765,7 @@ static void register_aor_core(pjsip_rx_data *rdata, aor_name, expiration, contact_update->user_agent); + ao2_link(contacts, contact_update); ao2_cleanup(contact_update); } else { if (contact->prune_on_boot) { @@ -749,24 +803,20 @@ static void register_aor_core(pjsip_rx_data *rdata, * that have not been updated/added/deleted as a result of this * REGISTER do so. * - * The contacts container currently holds the existing contacts that - * were not affected by this REGISTER. + * The existing contacts container holds all contacts that were not + * involved in this REGISTER. + * The contacts container holds the current contacts of the AOR. */ - if (aor->remove_existing) { + if (aor->remove_existing && existing_contacts) { /* Total contacts after this registration */ - contact_count = ao2_container_count(contacts) + updated + added; + contact_count = ao2_container_count(existing_contacts) + updated + added; if (contact_count > aor->max_contacts) { /* Remove excess existing contacts that expire the soonest */ - remove_excess_contacts(contacts, contact_count - aor->max_contacts); + remove_excess_contacts(existing_contacts, contacts, contact_count - aor->max_contacts); } + ao2_ref(existing_contacts, -1); } - /* Re-retrieve contacts. Caller will clean up the original container. */ - contacts = ast_sip_location_retrieve_aor_contacts_nolock(aor); - if (!contacts) { - response->code = 500; - return; - } response_contact = ao2_callback(contacts, 0, NULL, NULL); /* Send a response containing all of the contacts (including static) that are present on this AOR */ @@ -782,7 +832,6 @@ static void register_aor_core(pjsip_rx_data *rdata, registrar_add_date_header(tdata); ao2_callback(contacts, 0, registrar_add_contact, tdata); - ao2_cleanup(contacts); if ((expires_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL))) { expires_hdr = pjsip_expires_hdr_create(tdata->pool, registrar_get_expiration(aor, NULL, rdata));