2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2013, Digium, Inc.
6 * Joshua Colp <jcolp@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
20 <depend>pjproject</depend>
21 <depend>res_pjsip</depend>
22 <support_level>core</support_level>
30 #include "asterisk/res_pjsip.h"
31 #include "asterisk/module.h"
32 #include "asterisk/test.h"
33 #include "asterisk/taskprocessor.h"
34 #include "asterisk/manager.h"
35 #include "res_pjsip/include/res_pjsip_private.h"
38 <manager name="PJSIPShowRegistrationsInbound" language="en_US">
40 Lists PJSIP inbound registrations.
45 In response <literal>InboundRegistrationDetail</literal> events showing configuration and status
46 information are raised for each inbound registration object. As well as <literal>AuthDetail</literal>
47 events for each associated auth object. Once all events are completed an
48 <literal>InboundRegistrationDetailComplete</literal> is issued.
54 /*! \brief Internal function which returns the expiration time for a contact */
55 static int registrar_get_expiration(const struct ast_sip_aor *aor, const pjsip_contact_hdr *contact, const pjsip_rx_data *rdata)
57 pjsip_expires_hdr *expires;
58 int expiration = aor->default_expiration;
60 if (contact->expires != -1) {
61 /* Expiration was provided with the contact itself */
62 expiration = contact->expires;
63 } else if ((expires = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL))) {
64 /* Expiration was provided using the Expires header */
65 expiration = expires->ivalue;
68 /* If the value has explicitly been set to 0, do not enforce */
73 /* Enforce the range that we will allow for expiration */
74 if (expiration < aor->minimum_expiration) {
75 expiration = aor->minimum_expiration;
76 } else if (expiration > aor->maximum_expiration) {
77 expiration = aor->maximum_expiration;
83 /*! \brief Structure used for finding contact */
84 struct registrar_contact_details {
85 /*! \brief Pool used for parsing URI */
87 /*! \brief URI being looked for */
91 /*! \brief Callback function for finding a contact */
92 static int registrar_find_contact(void *obj, void *arg, int flags)
94 struct ast_sip_contact *contact = obj;
95 const struct registrar_contact_details *details = arg;
96 pjsip_uri *contact_uri = pjsip_parse_uri(details->pool, (char*)contact->uri, strlen(contact->uri), 0);
98 return (pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, details->uri, contact_uri) == PJ_SUCCESS) ? CMP_MATCH | CMP_STOP : 0;
101 /*! \brief Internal function which validates provided Contact headers to confirm that they are acceptable, and returns number of contacts */
102 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)
104 pjsip_contact_hdr *previous = NULL, *contact = (pjsip_contact_hdr *)&rdata->msg_info.msg->hdr;
105 struct registrar_contact_details details = {
106 .pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Contact Comparison", 256, 256),
113 while ((contact = (pjsip_contact_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact->next))) {
114 int expiration = registrar_get_expiration(aor, contact, rdata);
115 RAII_VAR(struct ast_sip_contact *, existing, NULL, ao2_cleanup);
118 /* The expiration MUST be 0 when a '*' contact is used and there must be no other contact */
119 if ((expiration != 0) || previous) {
120 pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
124 } else if (previous && previous->star) {
125 /* If there is a previous contact and it is a '*' this is a deal breaker */
126 pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
131 if (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri)) {
135 details.uri = pjsip_uri_get_uri(contact->uri);
137 /* Determine if this is an add, update, or delete for policy enforcement purposes */
138 if (!(existing = ao2_callback(contacts, 0, registrar_find_contact, &details))) {
142 } else if (expiration) {
149 /* The provided contacts are acceptable, huzzah! */
150 pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
154 /*! \brief Callback function which prunes static contacts */
155 static int registrar_prune_static(void *obj, void *arg, int flags)
157 struct ast_sip_contact *contact = obj;
159 return ast_tvzero(contact->expiration_time) ? CMP_MATCH : 0;
162 /*! \brief Internal function used to delete all contacts from an AOR */
163 static int registrar_delete_contact(void *obj, void *arg, int flags)
165 struct ast_sip_contact *contact = obj;
166 const char *aor_name = arg;
168 ast_sip_location_delete_contact(contact);
169 if (!ast_strlen_zero(aor_name)) {
170 ast_verb(3, "Removed contact '%s' from AOR '%s' due to request\n", contact->uri, aor_name);
171 ast_test_suite_event_notify("AOR_CONTACT_REMOVED",
181 /*! \brief Internal function which adds a contact to a response */
182 static int registrar_add_contact(void *obj, void *arg, int flags)
184 struct ast_sip_contact *contact = obj;
185 pjsip_tx_data *tdata = arg;
186 pjsip_contact_hdr *hdr = pjsip_contact_hdr_create(tdata->pool);
189 pj_strdup2_with_null(tdata->pool, &uri, contact->uri);
190 hdr->uri = pjsip_parse_uri(tdata->pool, uri.ptr, uri.slen, PJSIP_PARSE_URI_AS_NAMEADDR);
191 hdr->expires = ast_tvdiff_ms(contact->expiration_time, ast_tvnow()) / 1000;
193 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
198 /*! \brief Helper function which adds a Date header to a response */
199 static void registrar_add_date_header(pjsip_tx_data *tdata)
203 time_t t = time(NULL);
206 strftime(date, sizeof(date), "%a, %d %b %Y %T GMT", &tm);
208 ast_sip_add_header(tdata, "Date", date);
211 #define SERIALIZER_BUCKETS 59
213 static struct ao2_container *serializers;
215 /*! \brief Serializer with associated aor key */
217 /* Serializer to distribute tasks to */
218 struct ast_taskprocessor *serializer;
219 /* The name of the aor to associate with the serializer */
223 static void serializer_destroy(void *obj)
225 struct serializer *ser = obj;
227 ast_taskprocessor_unreference(ser->serializer);
230 static struct serializer *serializer_create(const char *aor_name)
232 size_t size = strlen(aor_name) + 1;
233 struct serializer *ser = ao2_alloc(
234 sizeof(*ser) + size, serializer_destroy);
240 if (!(ser->serializer = ast_sip_create_serializer())) {
245 strcpy(ser->aor_name, aor_name);
249 static struct serializer *serializer_find_or_create(const char *aor_name)
251 struct serializer *ser = ao2_find(serializers, aor_name, OBJ_SEARCH_KEY);
257 if (!(ser = serializer_create(aor_name))) {
261 ao2_link(serializers, ser);
265 static int serializer_hash(const void *obj, const int flags)
267 const struct serializer *object;
270 switch (flags & OBJ_SEARCH_MASK) {
273 return ast_str_hash(key);
274 case OBJ_SEARCH_OBJECT:
276 return ast_str_hash(object->aor_name);
278 /* Hash can only work on something with a full key. */
284 static int serializer_cmp(void *obj_left, void *obj_right, int flags)
286 const struct serializer *object_left = obj_left;
287 const struct serializer *object_right = obj_right;
288 const char *right_key = obj_right;
291 switch (flags & OBJ_SEARCH_MASK) {
292 case OBJ_SEARCH_OBJECT:
293 right_key = object_right->aor_name;
296 cmp = strcmp(object_left->aor_name, right_key);
298 case OBJ_SEARCH_PARTIAL_KEY:
300 * We could also use a partial key struct containing a length
301 * so strlen() does not get called for every comparison instead.
303 cmp = strncmp(object_left->aor_name, right_key, strlen(right_key));
310 return cmp ? 0 : CMP_MATCH;
313 struct rx_task_data {
314 pjsip_rx_data *rdata;
315 struct ast_sip_endpoint *endpoint;
316 struct ast_sip_aor *aor;
319 static void rx_task_data_destroy(void *obj)
321 struct rx_task_data *task_data = obj;
323 pjsip_rx_data_free_cloned(task_data->rdata);
324 ao2_cleanup(task_data->endpoint);
325 ao2_cleanup(task_data->aor);
328 static struct rx_task_data *rx_task_data_create(pjsip_rx_data *rdata,
329 struct ast_sip_endpoint *endpoint,
330 struct ast_sip_aor *aor)
332 struct rx_task_data *task_data = ao2_alloc(
333 sizeof(*task_data), rx_task_data_destroy);
339 pjsip_rx_data_clone(rdata, 0, &task_data->rdata);
341 task_data->endpoint = endpoint;
342 ao2_ref(task_data->endpoint, +1);
344 task_data->aor = aor;
345 ao2_ref(task_data->aor, +1);
350 static int rx_task(void *data)
352 RAII_VAR(struct rx_task_data *, task_data, data, ao2_cleanup);
353 RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
355 int added = 0, updated = 0, deleted = 0;
356 pjsip_contact_hdr *contact_hdr = NULL;
357 struct registrar_contact_details details = { 0, };
358 pjsip_tx_data *tdata;
359 pjsip_response_addr addr;
360 const char *aor_name = ast_sorcery_object_get_id(task_data->aor);
362 /* Retrieve the current contacts, we'll need to know whether to update or not */
363 contacts = ast_sip_location_retrieve_aor_contacts(task_data->aor);
365 /* So we don't count static contacts against max_contacts we prune them out from the container */
366 ao2_callback(contacts, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, registrar_prune_static, NULL);
368 if (registrar_validate_contacts(task_data->rdata, contacts, task_data->aor, &added, &updated, &deleted)) {
369 /* The provided Contact headers do not conform to the specification */
370 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), task_data->rdata, 400, NULL, NULL, NULL);
371 ast_sip_report_failed_acl(task_data->endpoint, task_data->rdata, "registrar_invalid_contacts_provided");
372 ast_log(LOG_WARNING, "Failed to validate contacts in REGISTER request from '%s'\n",
373 ast_sorcery_object_get_id(task_data->endpoint));
377 if ((MAX(added - deleted, 0) + (!task_data->aor->remove_existing ? ao2_container_count(contacts) : 0)) > task_data->aor->max_contacts) {
378 /* Enforce the maximum number of contacts */
379 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), task_data->rdata, 403, NULL, NULL, NULL);
380 ast_sip_report_failed_acl(task_data->endpoint, task_data->rdata, "registrar_attempt_exceeds_maximum_configured_contacts");
381 ast_log(LOG_WARNING, "Registration attempt from endpoint '%s' to AOR '%s' will exceed max contacts of %d\n",
382 ast_sorcery_object_get_id(task_data->endpoint), ast_sorcery_object_get_id(task_data->aor), task_data->aor->max_contacts);
386 if (!(details.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Contact Comparison", 256, 256))) {
387 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), task_data->rdata, 500, NULL, NULL, NULL);
391 /* Iterate each provided Contact header and add, update, or delete */
392 while ((contact_hdr = pjsip_msg_find_hdr(task_data->rdata->msg_info.msg, PJSIP_H_CONTACT, contact_hdr ? contact_hdr->next : NULL))) {
394 char contact_uri[PJSIP_MAX_URL_SIZE];
395 RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
397 if (contact_hdr->star) {
398 /* A star means to unregister everything, so do so for the possible contacts */
399 ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, registrar_delete_contact, (void *)aor_name);
403 if (!PJSIP_URI_SCHEME_IS_SIP(contact_hdr->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact_hdr->uri)) {
404 /* This registrar only currently supports sip: and sips: URI schemes */
408 expiration = registrar_get_expiration(task_data->aor, contact_hdr, task_data->rdata);
409 details.uri = pjsip_uri_get_uri(contact_hdr->uri);
410 pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, details.uri, contact_uri, sizeof(contact_uri));
412 if (!(contact = ao2_callback(contacts, OBJ_UNLINK, registrar_find_contact, &details))) {
413 /* If they are actually trying to delete a contact that does not exist... be forgiving */
415 ast_verb(3, "Attempted to remove non-existent contact '%s' from AOR '%s' by request\n",
416 contact_uri, aor_name);
420 ast_sip_location_add_contact(task_data->aor, contact_uri, ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1)));
421 ast_verb(3, "Added contact '%s' to AOR '%s' with expiration of %d seconds\n",
422 contact_uri, aor_name, expiration);
423 ast_test_suite_event_notify("AOR_CONTACT_ADDED",
430 } else if (expiration) {
431 RAII_VAR(struct ast_sip_contact *, updated, ast_sorcery_copy(ast_sip_get_sorcery(), contact), ao2_cleanup);
432 updated->expiration_time = ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1));
433 updated->qualify_frequency = task_data->aor->qualify_frequency;
434 updated->authenticate_qualify = task_data->aor->authenticate_qualify;
436 ast_sip_location_update_contact(updated);
437 ast_debug(3, "Refreshed contact '%s' on AOR '%s' with new expiration of %d seconds\n",
438 contact_uri, aor_name, expiration);
439 ast_test_suite_event_notify("AOR_CONTACT_REFRESHED",
447 ast_sip_location_delete_contact(contact);
448 ast_verb(3, "Removed contact '%s' from AOR '%s' due to request\n", contact_uri, aor_name);
449 ast_test_suite_event_notify("AOR_CONTACT_REMOVED",
457 pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
459 /* If the AOR is configured to remove any existing contacts that have not been updated/added as a result of this REGISTER
462 if (task_data->aor->remove_existing) {
463 ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, registrar_delete_contact, NULL);
466 /* Update the contacts as things will probably have changed */
467 ao2_cleanup(contacts);
468 contacts = ast_sip_location_retrieve_aor_contacts(task_data->aor);
470 /* Send a response containing all of the contacts (including static) that are present on this AOR */
471 if (pjsip_endpt_create_response(ast_sip_get_pjsip_endpoint(), task_data->rdata, 200, NULL, &tdata) != PJ_SUCCESS) {
475 /* Add the date header to the response, some UAs use this to set their date and time */
476 registrar_add_date_header(tdata);
478 ao2_callback(contacts, 0, registrar_add_contact, tdata);
480 if (pjsip_get_response_addr(tdata->pool, task_data->rdata, &addr) == PJ_SUCCESS) {
481 pjsip_endpt_send_response(ast_sip_get_pjsip_endpoint(), &addr, tdata, NULL, NULL);
483 pjsip_tx_data_dec_ref(tdata);
489 static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
491 RAII_VAR(struct serializer *, ser, NULL, ao2_cleanup);
492 struct rx_task_data *task_data;
494 RAII_VAR(struct ast_sip_endpoint *, endpoint,
495 ast_pjsip_rdata_get_endpoint(rdata), ao2_cleanup);
496 RAII_VAR(struct ast_sip_aor *, aor, NULL, ao2_cleanup);
498 char user_name[64], domain_name[64];
499 char *configured_aors, *aor_name;
501 if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_register_method) || !endpoint) {
505 if (ast_strlen_zero(endpoint->aors)) {
506 /* Short circuit early if the endpoint has no AORs configured on it, which means no registration possible */
507 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
508 ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_without_configured_aors");
509 ast_log(LOG_WARNING, "Endpoint '%s' has no configured AORs\n", ast_sorcery_object_get_id(endpoint));
513 if (!PJSIP_URI_SCHEME_IS_SIP(rdata->msg_info.to->uri) && !PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.to->uri)) {
514 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 416, NULL, NULL, NULL);
515 ast_sip_report_failed_acl(endpoint, rdata, "registrar_invalid_uri_in_to_received");
516 ast_log(LOG_WARNING, "Endpoint '%s' attempted to register to an AOR with a non-SIP URI\n", ast_sorcery_object_get_id(endpoint));
520 uri = pjsip_uri_get_uri(rdata->msg_info.to->uri);
521 ast_copy_pj_str(user_name, &uri->user, sizeof(user_name));
522 ast_copy_pj_str(domain_name, &uri->host, sizeof(domain_name));
524 configured_aors = ast_strdupa(endpoint->aors);
526 /* Iterate the configured AORs to see if the user or the user+domain match */
527 while ((aor_name = strsep(&configured_aors, ","))) {
528 char id[AST_UUID_STR_LEN];
529 RAII_VAR(struct ast_sip_domain_alias *, alias, NULL, ao2_cleanup);
531 snprintf(id, sizeof(id), "%s@%s", user_name, domain_name);
532 if (!strcmp(aor_name, id)) {
536 if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) {
537 snprintf(id, sizeof(id), "%s@%s", user_name, alias->domain);
538 if (!strcmp(aor_name, id)) {
543 if (!strcmp(aor_name, user_name)) {
548 if (ast_strlen_zero(aor_name) || !(aor = ast_sip_location_retrieve_aor(aor_name))) {
549 /* The provided AOR name was not found (be it within the configuration or sorcery itself) */
550 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL);
551 ast_sip_report_req_no_support(endpoint, rdata, "registrar_requested_aor_not_found");
552 ast_log(LOG_WARNING, "AOR '%s' not found for endpoint '%s'\n", user_name, ast_sorcery_object_get_id(endpoint));
556 if (!aor->max_contacts) {
557 /* Registration is not permitted for this AOR */
558 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
559 ast_sip_report_req_no_support(endpoint, rdata, "registrar_attempt_without_registration_permitted");
560 ast_log(LOG_WARNING, "AOR '%s' has no configured max_contacts. Endpoint '%s' unable to register\n",
561 ast_sorcery_object_get_id(aor), ast_sorcery_object_get_id(endpoint));
565 if (!(ser = serializer_find_or_create(aor_name))) {
566 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
567 ast_sip_report_mem_limit(endpoint, rdata);
568 ast_log(LOG_WARNING, "Endpoint '%s' unable to register on AOR '%s' - could not get serializer\n",
569 ast_sorcery_object_get_id(endpoint), ast_sorcery_object_get_id(aor));
573 if (!(task_data = rx_task_data_create(rdata, endpoint, aor))) {
574 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
575 ast_sip_report_mem_limit(endpoint, rdata);
576 ast_log(LOG_WARNING, "Endpoint '%s' unable to register on AOR '%s' - could not create rx_task_data\n",
577 ast_sorcery_object_get_id(endpoint), ast_sorcery_object_get_id(aor));
581 if (ast_sip_push_task(ser->serializer, rx_task, task_data)) {
582 pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
583 ast_sip_report_mem_limit(endpoint, rdata);
584 ast_log(LOG_WARNING, "Endpoint '%s' unable to register on AOR '%s' - could not serialize task\n",
585 ast_sorcery_object_get_id(endpoint), ast_sorcery_object_get_id(aor));
586 ao2_ref(task_data, -1);
591 static int ami_registrations_aor(void *obj, void *arg, int flags)
593 struct ast_sip_aor *aor = obj;
594 struct ast_sip_ami *ami = arg;
595 int *count = ami->arg;
596 RAII_VAR(struct ast_str *, buf,
597 ast_sip_create_ami_event("InboundRegistrationDetail", ami), ast_free);
603 ast_sip_sorcery_object_to_ami(aor, &buf);
604 ast_str_append(&buf, 0, "Contacts: ");
605 ast_sip_for_each_contact(aor, ast_sip_contact_to_str, &buf);
606 ast_str_append(&buf, 0, "\r\n");
608 astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
613 static int ami_registrations_endpoint(void *obj, void *arg, int flags)
615 struct ast_sip_endpoint *endpoint = obj;
616 return ast_sip_for_each_aor(
617 endpoint->aors, ami_registrations_aor, arg);
620 static int ami_registrations_endpoints(void *arg)
622 RAII_VAR(struct ao2_container *, endpoints,
623 ast_sip_get_endpoints(), ao2_cleanup);
629 ao2_callback(endpoints, OBJ_NODATA, ami_registrations_endpoint, arg);
633 static int ami_show_registrations(struct mansession *s, const struct message *m)
636 struct ast_sip_ami ami = { .s = s, .m = m, .arg = &count };
637 astman_send_listack(s, m, "Following are Events for each Inbound "
638 "registration", "start");
640 ami_registrations_endpoints(&ami);
643 "Event: InboundRegistrationDetailComplete\r\n"
644 "EventList: Complete\r\n"
645 "ListItems: %d\r\n\r\n", count);
649 #define AMI_SHOW_REGISTRATIONS "PJSIPShowRegistrationsInbound"
651 static pjsip_module registrar_module = {
652 .name = { "Registrar", 9 },
654 .priority = PJSIP_MOD_PRIORITY_APPLICATION,
655 .on_rx_request = registrar_on_rx_request,
658 static int load_module(void)
660 const pj_str_t STR_REGISTER = { "REGISTER", 8 };
662 if (!(serializers = ao2_container_alloc(
663 SERIALIZER_BUCKETS, serializer_hash, serializer_cmp))) {
664 return AST_MODULE_LOAD_DECLINE;
667 if (ast_sip_register_service(®istrar_module)) {
668 return AST_MODULE_LOAD_DECLINE;
671 if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW, NULL, 1, &STR_REGISTER) != PJ_SUCCESS) {
672 ast_sip_unregister_service(®istrar_module);
673 return AST_MODULE_LOAD_DECLINE;
676 ast_manager_register_xml(AMI_SHOW_REGISTRATIONS, EVENT_FLAG_SYSTEM,
677 ami_show_registrations);
679 return AST_MODULE_LOAD_SUCCESS;
682 static int unload_module(void)
684 ast_manager_unregister(AMI_SHOW_REGISTRATIONS);
685 ast_sip_unregister_service(®istrar_module);
687 ao2_cleanup(serializers);
691 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Registrar Support",
693 .unload = unload_module,
694 .load_pri = AST_MODPRI_APP_DEPEND,