pjsip: race condition in registrar
authorKevin Harwell <kharwell@digium.com>
Thu, 26 Sep 2013 18:51:54 +0000 (18:51 +0000)
committerKevin Harwell <kharwell@digium.com>
Thu, 26 Sep 2013 18:51:54 +0000 (18:51 +0000)
While handling a registration request a race condition could occur if/when two+
clients registered at the same time.  This happened when one request obtained a
copy of the current contacts for an AOR and another request did the same before
the first request updated.  Thus the second would update and overwrite the first
(or vice-versa depending on which actually updated first).  In the case of it
being the same contact two "add" events would be raised.

pjsip registration handling is now serialized to alleviate this issue.

(closes issue AST-1213)
Reported by: John Bigelow
Review: https://reviewboard.asterisk.org/r/2860/
........

Merged revisions 399897 from http://svn.asterisk.org/svn/asterisk/branches/12

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@399898 65c4cc65-6c06-0410-ace0-fbb531ad65f3

include/asterisk/res_pjsip.h
res/res_pjsip.exports.in
res/res_pjsip/security_events.c
res/res_pjsip_registrar.c

index 354e8a7..9ba2e90 100644 (file)
@@ -1478,6 +1478,24 @@ void ast_sip_report_auth_success(struct ast_sip_endpoint *endpoint, pjsip_rx_dat
  */
 void ast_sip_report_auth_challenge_sent(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, pjsip_tx_data *tdata);
 
+/*!
+ * \brief Send a security event notification for when a request is not supported
+ *
+ * \param endpoint Pointer to the endpoint in use
+ * \param rdata Received message
+ * \param req_type the type of request
+ */
+void ast_sip_report_req_no_support(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata,
+                                  const char* req_type);
+
+/*!
+ * \brief Send a security event notification for when a memory limit is hit.
+ *
+ * \param endpoint Pointer to the endpoint in use
+ * \param rdata Received message
+ */
+void ast_sip_report_mem_limit(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata);
+
 void ast_sip_initialize_global_headers(void);
 void ast_sip_destroy_global_headers(void);
 
index a4b23bc..d896ee6 100644 (file)
@@ -61,6 +61,8 @@
                LINKER_SYMBOL_PREFIXast_sip_report_auth_failed_challenge_response;
                LINKER_SYMBOL_PREFIXast_sip_report_auth_success;
                LINKER_SYMBOL_PREFIXast_sip_report_auth_challenge_sent;
+               LINKER_SYMBOL_PREFIXast_sip_report_req_no_support;
+               LINKER_SYMBOL_PREFIXast_sip_report_mem_limit;
                LINKER_SYMBOL_PREFIXast_sip_initialize_global_headers;
                LINKER_SYMBOL_PREFIXast_sip_destroy_global_headers;
                LINKER_SYMBOL_PREFIXast_sip_add_global_request_header;
index 7b49137..6bdb6cb 100644 (file)
@@ -232,3 +232,59 @@ void ast_sip_report_auth_challenge_sent(struct ast_sip_endpoint *endpoint, pjsip
 
        ast_security_event_report(AST_SEC_EVT(&chal_sent));
 }
+
+void ast_sip_report_req_no_support(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata,
+                                  const char* req_type)
+{
+       enum ast_transport transport = security_event_get_transport(rdata);
+       char call_id[pj_strlen(&rdata->msg_info.cid->id) + 1];
+       struct ast_sockaddr local, remote;
+
+       struct ast_security_event_req_no_support req_no_support_event = {
+               .common.event_type  = AST_SECURITY_EVENT_REQ_NO_SUPPORT,
+               .common.version     = AST_SECURITY_EVENT_REQ_NO_SUPPORT_VERSION,
+               .common.service     = "PJSIP",
+               .common.account_id  = ast_sorcery_object_get_id(endpoint),
+               .common.local_addr  = {
+                       .addr       = &local,
+                       .transport  = transport,
+               },
+               .common.remote_addr = {
+                       .addr       = &remote,
+                       .transport  = transport,
+               },
+               .common.session_id  = call_id,
+               .request_type       = req_type
+       };
+
+       security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote);
+
+       ast_security_event_report(AST_SEC_EVT(&req_no_support_event));
+}
+
+void ast_sip_report_mem_limit(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
+{
+       enum ast_transport transport = security_event_get_transport(rdata);
+       char call_id[pj_strlen(&rdata->msg_info.cid->id) + 1];
+       struct ast_sockaddr local, remote;
+
+       struct ast_security_event_mem_limit mem_limit_event = {
+               .common.event_type  = AST_SECURITY_EVENT_MEM_LIMIT,
+               .common.version     = AST_SECURITY_EVENT_MEM_LIMIT_VERSION,
+               .common.service     = "PJSIP",
+               .common.account_id  = ast_sorcery_object_get_id(endpoint),
+               .common.local_addr  = {
+                       .addr       = &local,
+                       .transport  = transport,
+               },
+               .common.remote_addr = {
+                       .addr       = &remote,
+                       .transport  = transport,
+               },
+               .common.session_id  = call_id
+       };
+
+       security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote);
+
+       ast_security_event_report(AST_SEC_EVT(&mem_limit_event));
+}
index 0cee19e..143f96d 100644 (file)
@@ -30,6 +30,7 @@
 #include "asterisk/res_pjsip.h"
 #include "asterisk/module.h"
 #include "asterisk/test.h"
+#include "asterisk/taskprocessor.h"
 
 /*! \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)
@@ -188,122 +189,195 @@ static void registrar_add_date_header(pjsip_tx_data *tdata)
        ast_sip_add_header(tdata, "Date", date);
 }
 
-static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
+#define SERIALIZER_BUCKETS 59
+
+static struct ao2_container *serializers;
+
+/*! \brief Serializer with associated aor key */
+struct serializer {
+       /* Serializer to distribute tasks to */
+       struct ast_taskprocessor *serializer;
+       /* The name of the aor to associate with the serializer */
+       char aor_name[0];
+};
+
+static void serializer_destroy(void *obj)
 {
-       struct ast_sip_endpoint *endpoint = ast_pjsip_rdata_get_endpoint(rdata);
-       pjsip_sip_uri *uri;
-       char user_name[64], domain_name[64];
-       char *configured_aors, *aor_name;
-       RAII_VAR(struct ast_sip_aor *, aor, NULL, ao2_cleanup);
-       RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
-       int added = 0, updated = 0, deleted = 0;
-       pjsip_contact_hdr *contact_hdr = NULL;
-       struct registrar_contact_details details = { 0, };
-       pjsip_tx_data *tdata;
-       pjsip_response_addr addr;
+       struct serializer *ser = obj;
 
-       if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_register_method) || !endpoint) {
-               return PJ_FALSE;
-       }
+       ast_taskprocessor_unreference(ser->serializer);
+}
 
-       if (ast_strlen_zero(endpoint->aors)) {
-               /* Short circuit early if the endpoint has no AORs configured on it, which means no registration possible */
-               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
-               ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_without_configured_aors");
-               ast_log(LOG_WARNING, "Endpoint '%s' has no configured AORs\n", ast_sorcery_object_get_id(endpoint));
-               return PJ_TRUE;
+static struct serializer *serializer_create(const char *aor_name)
+{
+       size_t size = strlen(aor_name) + 1;
+       struct serializer *ser = ao2_alloc(
+               sizeof(*ser) + size, serializer_destroy);
+
+       if (!ser) {
+               return NULL;
        }
 
-       if (!PJSIP_URI_SCHEME_IS_SIP(rdata->msg_info.to->uri) && !PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.to->uri)) {
-               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 416, NULL, NULL, NULL);
-               ast_sip_report_failed_acl(endpoint, rdata, "registrar_invalid_uri_in_to_received");
-               ast_log(LOG_WARNING, "Endpoint '%s' attempted to register to an AOR with a non-SIP URI\n", ast_sorcery_object_get_id(endpoint));
-               return PJ_TRUE;
+       if (!(ser->serializer = ast_sip_create_serializer())) {
+               ao2_ref(ser, -1);
+               return NULL;
        }
 
-       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));
+       strcpy(ser->aor_name, aor_name);
+       return ser;
+}
 
-       configured_aors = ast_strdupa(endpoint->aors);
+static struct serializer *serializer_find_or_create(const char *aor_name)
+{
+       struct serializer *ser = ao2_find(serializers, aor_name, OBJ_SEARCH_KEY);
 
-       /* 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);
+       if (ser) {
+               return ser;
+       }
 
-               snprintf(id, sizeof(id), "%s@%s", user_name, domain_name);
-               if (!strcmp(aor_name, id)) {
-                       break;
-               }
+       if (!(ser = serializer_create(aor_name))) {
+               return NULL;
+       }
 
-               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;
-                       }
-               }
+       ao2_link(serializers, ser);
+       return ser;
+}
 
-               if (!strcmp(aor_name, user_name)) {
-                       break;
-               }
+static int serializer_hash(const void *obj, const int flags)
+{
+       const struct serializer *object;
+       const char *key;
+
+       switch (flags & OBJ_SEARCH_MASK) {
+       case OBJ_SEARCH_KEY:
+               key = obj;
+               return ast_str_hash(key);
+       case OBJ_SEARCH_OBJECT:
+               object = obj;
+               return ast_str_hash(object->aor_name);
+       default:
+               /* Hash can only work on something with a full key. */
+               ast_assert(0);
+               return 0;
        }
+}
 
-       if (ast_strlen_zero(aor_name) || !(aor = ast_sip_location_retrieve_aor(aor_name))) {
-               /* 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_failed_acl(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));
-               return PJ_TRUE;
+static int serializer_cmp(void *obj_left, void *obj_right, int flags)
+{
+       const struct serializer *object_left = obj_left;
+       const struct serializer *object_right = obj_right;
+       const char *right_key = obj_right;
+       int cmp;
+
+       switch (flags & OBJ_SEARCH_MASK) {
+       case OBJ_SEARCH_OBJECT:
+               right_key = object_right->aor_name;
+               /* Fall through */
+       case OBJ_SEARCH_KEY:
+               cmp = strcmp(object_left->aor_name, right_key);
+               break;
+       case OBJ_SEARCH_PARTIAL_KEY:
+               /*
+                * We could also use a partial key struct containing a length
+                * so strlen() does not get called for every comparison instead.
+                */
+               cmp = strncmp(object_left->aor_name, right_key, strlen(right_key));
+               break;
+       default:
+               cmp = 0;
+               break;
        }
 
-       if (!aor->max_contacts) {
-               /* Registration is not permitted for this AOR */
-               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
-               ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_without_registration_permitted");
-               ast_log(LOG_WARNING, "AOR '%s' has no configured max_contacts. Endpoint '%s' unable to register\n",
-                               ast_sorcery_object_get_id(aor), ast_sorcery_object_get_id(endpoint));
-               return PJ_TRUE;
+       return cmp ? 0 : CMP_MATCH;
+}
+
+struct rx_task_data {
+       pjsip_rx_data *rdata;
+       struct ast_sip_endpoint *endpoint;
+       struct ast_sip_aor *aor;
+};
+
+static void rx_task_data_destroy(void *obj)
+{
+       struct rx_task_data *task_data = obj;
+
+       pjsip_rx_data_free_cloned(task_data->rdata);
+       ao2_cleanup(task_data->endpoint);
+       ao2_cleanup(task_data->aor);
+}
+
+static struct rx_task_data *rx_task_data_create(pjsip_rx_data *rdata,
+                                               struct ast_sip_endpoint *endpoint,
+                                               struct ast_sip_aor *aor)
+{
+       struct rx_task_data *task_data = ao2_alloc(
+               sizeof(*task_data), rx_task_data_destroy);
+
+       if (!task_data) {
+               return NULL;
        }
 
+       pjsip_rx_data_clone(rdata, 0, &task_data->rdata);
+
+       task_data->endpoint = endpoint;
+       ao2_ref(task_data->endpoint, +1);
+
+       task_data->aor = aor;
+       ao2_ref(task_data->aor, +1);
+
+       return task_data;
+}
+
+static int rx_task(void *data)
+{
+       RAII_VAR(struct rx_task_data *, task_data, data, ao2_cleanup);
+       RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
+
+       int added = 0, updated = 0, deleted = 0;
+       pjsip_contact_hdr *contact_hdr = NULL;
+       struct registrar_contact_details details = { 0, };
+       pjsip_tx_data *tdata;
+       pjsip_response_addr addr;
+       const char *aor_name = ast_sorcery_object_get_id(task_data->aor);
+
        /* Retrieve the current contacts, we'll need to know whether to update or not */
-       contacts = ast_sip_location_retrieve_aor_contacts(aor);
+       contacts = ast_sip_location_retrieve_aor_contacts(task_data->aor);
 
        /* 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);
 
-       if (registrar_validate_contacts(rdata, contacts, aor, &added, &updated, &deleted)) {
+       if (registrar_validate_contacts(task_data->rdata, contacts, task_data->aor, &added, &updated, &deleted)) {
                /* The provided Contact headers do not conform to the specification */
-               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 400, NULL, NULL, NULL);
-               ast_sip_report_failed_acl(endpoint, rdata, "registrar_invalid_contacts_provided");
+               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), task_data->rdata, 400, NULL, NULL, NULL);
+               ast_sip_report_failed_acl(task_data->endpoint, task_data->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));
+                               ast_sorcery_object_get_id(task_data->endpoint));
                return PJ_TRUE;
        }
 
-       if ((MAX(added - deleted, 0) + (!aor->remove_existing ? ao2_container_count(contacts) : 0)) > aor->max_contacts) {
+       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(), rdata, 403, NULL, NULL, NULL);
-               ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_exceeds_maximum_configured_contacts");
+               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), task_data->rdata, 403, NULL, NULL, NULL);
+               ast_sip_report_failed_acl(task_data->endpoint, task_data->rdata, "registrar_attempt_exceeds_maximum_configured_contacts");
                ast_log(LOG_WARNING, "Registration attempt from endpoint '%s' to AOR '%s' will exceed max contacts of %d\n",
-                               ast_sorcery_object_get_id(endpoint), ast_sorcery_object_get_id(aor), aor->max_contacts);
+                               ast_sorcery_object_get_id(task_data->endpoint), ast_sorcery_object_get_id(task_data->aor), task_data->aor->max_contacts);
                return PJ_TRUE;
        }
 
        if (!(details.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Contact Comparison", 256, 256))) {
-               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
+               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), task_data->rdata, 500, NULL, NULL, NULL);
                return PJ_TRUE;
        }
 
        /* 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))) {
+       while ((contact_hdr = pjsip_msg_find_hdr(task_data->rdata->msg_info.msg, PJSIP_H_CONTACT, contact_hdr ? contact_hdr->next : NULL))) {
                int expiration;
                char contact_uri[PJSIP_MAX_URL_SIZE];
                RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
 
                if (contact_hdr->star) {
                        /* A star means to unregister everything, so do so for the possible contacts */
-                       ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, registrar_delete_contact, aor_name);
+                       ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, registrar_delete_contact, (void *)aor_name);
                        break;
                }
 
@@ -312,7 +386,7 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
                        continue;
                }
 
-               expiration = registrar_get_expiration(aor, contact_hdr, rdata);
+               expiration = registrar_get_expiration(task_data->aor, contact_hdr, task_data->rdata);
                details.uri = pjsip_uri_get_uri(contact_hdr->uri);
                pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, details.uri, contact_uri, sizeof(contact_uri));
 
@@ -324,7 +398,7 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
                                continue;
                        }
 
-                       ast_sip_location_add_contact(aor, contact_uri, ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1)));
+                       ast_sip_location_add_contact(task_data->aor, contact_uri, ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1)));
                        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",
@@ -337,8 +411,8 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
                } 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 = aor->qualify_frequency;
-                       updated->authenticate_qualify = aor->authenticate_qualify;
+                       updated->qualify_frequency = task_data->aor->qualify_frequency;
+                       updated->authenticate_qualify = task_data->aor->authenticate_qualify;
 
                        ast_sip_location_update_contact(updated);
                        ast_debug(3, "Refreshed contact '%s' on AOR '%s' with new expiration of %d seconds\n",
@@ -366,16 +440,16 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
        /* If the AOR is configured to remove any existing contacts that have not been updated/added as a result of this REGISTER
         * do so
         */
-       if (aor->remove_existing) {
+       if (task_data->aor->remove_existing) {
                ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, registrar_delete_contact, NULL);
        }
 
        /* Update the contacts as things will probably have changed */
        ao2_cleanup(contacts);
-       contacts = ast_sip_location_retrieve_aor_contacts(aor);
+       contacts = ast_sip_location_retrieve_aor_contacts(task_data->aor);
 
        /* 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(), rdata, 200, NULL, &tdata) != PJ_SUCCESS) {
+       if (pjsip_endpt_create_response(ast_sip_get_pjsip_endpoint(), task_data->rdata, 200, NULL, &tdata) != PJ_SUCCESS) {
                return PJ_TRUE;
        }
 
@@ -384,7 +458,7 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
 
        ao2_callback(contacts, 0, registrar_add_contact, tdata);
 
-       if (pjsip_get_response_addr(tdata->pool, rdata, &addr) == PJ_SUCCESS) {
+       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);
        } else {
                pjsip_tx_data_dec_ref(tdata);
@@ -393,6 +467,108 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
        return PJ_TRUE;
 }
 
+static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
+{
+       RAII_VAR(struct serializer *, ser, NULL, ao2_cleanup);
+       struct rx_task_data *task_data;
+
+       RAII_VAR(struct ast_sip_endpoint *, endpoint,
+                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 *configured_aors, *aor_name;
+
+       if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_register_method) || !endpoint) {
+               return PJ_FALSE;
+       }
+
+       if (ast_strlen_zero(endpoint->aors)) {
+               /* Short circuit early if the endpoint has no AORs configured on it, which means no registration possible */
+               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+               ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_without_configured_aors");
+               ast_log(LOG_WARNING, "Endpoint '%s' has no configured AORs\n", ast_sorcery_object_get_id(endpoint));
+               return PJ_TRUE;
+       }
+
+       if (!PJSIP_URI_SCHEME_IS_SIP(rdata->msg_info.to->uri) && !PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.to->uri)) {
+               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 416, NULL, NULL, NULL);
+               ast_sip_report_failed_acl(endpoint, rdata, "registrar_invalid_uri_in_to_received");
+               ast_log(LOG_WARNING, "Endpoint '%s' attempted to register to an AOR with a non-SIP URI\n", ast_sorcery_object_get_id(endpoint));
+               return PJ_TRUE;
+       }
+
+       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));
+
+       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);
+
+               snprintf(id, sizeof(id), "%s@%s", user_name, domain_name);
+               if (!strcmp(aor_name, id)) {
+                       break;
+               }
+
+               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;
+                       }
+               }
+
+               if (!strcmp(aor_name, user_name)) {
+                       break;
+               }
+       }
+
+       if (ast_strlen_zero(aor_name) || !(aor = ast_sip_location_retrieve_aor(aor_name))) {
+               /* 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));
+               return PJ_TRUE;
+       }
+
+       if (!aor->max_contacts) {
+               /* Registration is not permitted for this AOR */
+               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+               ast_sip_report_req_no_support(endpoint, rdata, "registrar_attempt_without_registration_permitted");
+               ast_log(LOG_WARNING, "AOR '%s' has no configured max_contacts. Endpoint '%s' unable to register\n",
+                               ast_sorcery_object_get_id(aor), ast_sorcery_object_get_id(endpoint));
+               return PJ_TRUE;
+       }
+
+       if (!(ser = serializer_find_or_create(aor_name))) {
+               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+               ast_sip_report_mem_limit(endpoint, rdata);
+               ast_log(LOG_WARNING, "Endpoint '%s' unable to register on AOR '%s' - could not get serializer\n",
+                       ast_sorcery_object_get_id(endpoint), ast_sorcery_object_get_id(aor));
+               return PJ_TRUE;
+       }
+
+       if (!(task_data = rx_task_data_create(rdata, endpoint, aor))) {
+               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+               ast_sip_report_mem_limit(endpoint, rdata);
+               ast_log(LOG_WARNING, "Endpoint '%s' unable to register on AOR '%s' - could not create rx_task_data\n",
+                       ast_sorcery_object_get_id(endpoint), ast_sorcery_object_get_id(aor));
+               return PJ_TRUE;
+       }
+
+       if (ast_sip_push_task(ser->serializer, rx_task, task_data)) {
+               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+               ast_sip_report_mem_limit(endpoint, rdata);
+               ast_log(LOG_WARNING, "Endpoint '%s' unable to register on AOR '%s' - could not serialize task\n",
+                       ast_sorcery_object_get_id(endpoint), ast_sorcery_object_get_id(aor));
+               ao2_ref(task_data, -1);
+       }
+       return PJ_TRUE;
+}
+
 static pjsip_module registrar_module = {
        .name = { "Registrar", 9 },
        .id = -1,
@@ -404,6 +580,11 @@ static int load_module(void)
 {
        const pj_str_t STR_REGISTER = { "REGISTER", 8 };
 
+       if (!(serializers = ao2_container_alloc(
+                     SERIALIZER_BUCKETS, serializer_hash, serializer_cmp))) {
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
        if (ast_sip_register_service(&registrar_module)) {
                return AST_MODULE_LOAD_DECLINE;
        }
@@ -419,6 +600,8 @@ static int load_module(void)
 static int unload_module(void)
 {
        ast_sip_unregister_service(&registrar_module);
+
+       ao2_cleanup(serializers);
        return 0;
 }