res_pjsip: add "via_addr", "via_port", "call_id" to contact
[asterisk/asterisk.git] / res / res_pjsip / location.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@digium.com>
7  *
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.
13  *
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.
17  */
18
19 #include "asterisk.h"
20 #include "pjsip.h"
21 #include "pjlib.h"
22
23 #include "asterisk/res_pjsip.h"
24 #include "asterisk/logger.h"
25 #include "asterisk/astobj2.h"
26 #include "asterisk/paths.h"
27 #include "asterisk/sorcery.h"
28 #include "include/res_pjsip_private.h"
29 #include "asterisk/res_pjsip_cli.h"
30 #include "asterisk/statsd.h"
31 #include "asterisk/named_locks.h"
32
33 #include "asterisk/res_pjproject.h"
34
35 static int pj_max_hostname = PJ_MAX_HOSTNAME;
36 static int pjsip_max_url_size = PJSIP_MAX_URL_SIZE;
37
38 /*! \brief Destructor for AOR */
39 static void aor_destroy(void *obj)
40 {
41         struct ast_sip_aor *aor = obj;
42
43         ao2_cleanup(aor->permanent_contacts);
44         ast_string_field_free_memory(aor);
45         ast_free(aor->voicemail_extension);
46 }
47
48 /*! \brief Allocator for AOR */
49 static void *aor_alloc(const char *name)
50 {
51         struct ast_sip_aor *aor = ast_sorcery_generic_alloc(sizeof(struct ast_sip_aor), aor_destroy);
52         if (!aor) {
53                 return NULL;
54         }
55         ast_string_field_init(aor, 128);
56         return aor;
57 }
58
59 /*! \brief Internal callback function which destroys the specified contact */
60 static int destroy_contact(void *obj, void *arg, int flags)
61 {
62         struct ast_sip_contact *contact = obj;
63
64         ast_sip_location_delete_contact(contact);
65
66         return CMP_MATCH;
67 }
68
69 static void aor_deleted_observer(const void *object)
70 {
71         const struct ast_sip_aor *aor = object;
72         const char *aor_id = ast_sorcery_object_get_id(object);
73         /* Give enough space for ^ at the beginning and ;@ at the end, since that is our object naming scheme */
74         char regex[strlen(aor_id) + 4];
75         struct ao2_container *contacts;
76
77         if (aor->permanent_contacts) {
78                 ao2_callback(aor->permanent_contacts, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, destroy_contact, NULL);
79         }
80
81         snprintf(regex, sizeof(regex), "^%s;@", aor_id);
82         if (!(contacts = ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "contact", regex))) {
83                 return;
84         }
85         /* Destroy any contacts that may still exist that were made for this AoR */
86         ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, destroy_contact, NULL);
87
88         ao2_ref(contacts, -1);
89 }
90
91 /*! \brief Observer for contacts so state can be updated on respective endpoints */
92 static const struct ast_sorcery_observer aor_observer = {
93         .deleted = aor_deleted_observer,
94 };
95
96
97 /*! \brief Destructor for contact */
98 static void contact_destroy(void *obj)
99 {
100         struct ast_sip_contact *contact = obj;
101
102         ast_string_field_free_memory(contact);
103         ao2_cleanup(contact->endpoint);
104 }
105
106 /*! \brief Allocator for contact */
107 static void *contact_alloc(const char *name)
108 {
109         struct ast_sip_contact *contact = ast_sorcery_generic_alloc(sizeof(*contact), contact_destroy);
110         char *id = ast_strdupa(name);
111         char *aor = id;
112         char *aor_separator = NULL;
113
114         if (!contact) {
115                 return NULL;
116         }
117
118         if (ast_string_field_init(contact, 256)) {
119                 ao2_cleanup(contact);
120                 return NULL;
121         }
122
123         ast_string_field_init_extended(contact, reg_server);
124         ast_string_field_init_extended(contact, via_addr);
125         ast_string_field_init_extended(contact, call_id);
126
127         /* Dynamic contacts are delimited with ";@" and static ones with "@@" */
128         if ((aor_separator = strstr(id, ";@")) || (aor_separator = strstr(id, "@@"))) {
129                 *aor_separator = '\0';
130         }
131         ast_assert(aor_separator != NULL);
132
133         ast_string_field_set(contact, aor, aor);
134
135         return contact;
136 }
137
138 struct ast_sip_aor *ast_sip_location_retrieve_aor(const char *aor_name)
139 {
140         return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "aor", aor_name);
141 }
142
143 /*! \brief Internal callback function which deletes and unlinks any expired contacts */
144 static int contact_expire(void *obj, void *arg, int flags)
145 {
146         struct ast_sip_contact *contact = obj;
147
148         /* If the contact has not yet expired it is valid */
149         if (ast_tvdiff_ms(contact->expiration_time, ast_tvnow()) > 0) {
150                 return 0;
151         }
152
153         ast_sip_location_delete_contact(contact);
154
155         return CMP_MATCH;
156 }
157
158 /*! \brief Internal callback function which links static contacts into another container */
159 static int contact_link_static(void *obj, void *arg, int flags)
160 {
161         struct ao2_container *dest = arg;
162
163         ao2_link(dest, obj);
164         return 0;
165 }
166
167 struct ast_sip_contact *ast_sip_location_retrieve_first_aor_contact(const struct ast_sip_aor *aor)
168 {
169         RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
170         struct ast_sip_contact *contact;
171
172         contacts = ast_sip_location_retrieve_aor_contacts(aor);
173         if (!contacts || (ao2_container_count(contacts) == 0)) {
174                 return NULL;
175         }
176
177         /* Get the first AOR contact in the container. */
178         contact = ao2_callback(contacts, 0, NULL, NULL);
179         return contact;
180 }
181
182 struct ao2_container *ast_sip_location_retrieve_aor_contacts_nolock(const struct ast_sip_aor *aor)
183 {
184         /* Give enough space for ^ at the beginning and ;@ at the end, since that is our object naming scheme */
185         char regex[strlen(ast_sorcery_object_get_id(aor)) + 4];
186         struct ao2_container *contacts;
187
188         snprintf(regex, sizeof(regex), "^%s;@", ast_sorcery_object_get_id(aor));
189
190         if (!(contacts = ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "contact", regex))) {
191                 return NULL;
192         }
193
194         /* Prune any expired contacts and delete them, we do this first because static contacts can never expire */
195         ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, contact_expire, NULL);
196
197         /* Add any permanent contacts from the AOR */
198         if (aor->permanent_contacts) {
199                 ao2_callback(aor->permanent_contacts, OBJ_NODATA, contact_link_static, contacts);
200         }
201
202         return contacts;
203 }
204
205 struct ao2_container *ast_sip_location_retrieve_aor_contacts(const struct ast_sip_aor *aor)
206 {
207         struct ao2_container *contacts;
208         struct ast_named_lock *lock;
209
210         lock = ast_named_lock_get(AST_NAMED_LOCK_TYPE_RWLOCK, "aor", ast_sorcery_object_get_id(aor));
211         if (!lock) {
212                 return NULL;
213         }
214
215         ao2_wrlock(lock);
216         contacts = ast_sip_location_retrieve_aor_contacts_nolock(aor);
217         ao2_unlock(lock);
218         ast_named_lock_put(lock);
219
220         return contacts;
221 }
222
223 void ast_sip_location_retrieve_contact_and_aor_from_list(const char *aor_list, struct ast_sip_aor **aor,
224         struct ast_sip_contact **contact)
225 {
226         char *aor_name;
227         char *rest;
228
229         /* If the location is still empty we have nowhere to go */
230         if (ast_strlen_zero(aor_list) || !(rest = ast_strdupa(aor_list))) {
231                 ast_log(LOG_WARNING, "Unable to determine contacts from empty aor list\n");
232                 return;
233         }
234
235         *aor = NULL;
236         *contact = NULL;
237
238         while ((aor_name = ast_strip(strsep(&rest, ",")))) {
239                 *aor = ast_sip_location_retrieve_aor(aor_name);
240
241                 if (!(*aor)) {
242                         continue;
243                 }
244                 *contact = ast_sip_location_retrieve_first_aor_contact(*aor);
245                 /* If a valid contact is available use its URI for dialing */
246                 if (*contact) {
247                         break;
248                 }
249
250                 ao2_ref(*aor, -1);
251                 *aor = NULL;
252         }
253 }
254
255 struct ast_sip_contact *ast_sip_location_retrieve_contact_from_aor_list(const char *aor_list)
256 {
257         struct ast_sip_aor *aor;
258         struct ast_sip_contact *contact;
259
260         ast_sip_location_retrieve_contact_and_aor_from_list(aor_list, &aor, &contact);
261
262         ao2_cleanup(aor);
263
264         return contact;
265 }
266
267 static int permanent_uri_sort_fn(const void *obj_left, const void *obj_right, int flags);
268 static int cli_contact_populate_container(void *obj, void *arg, int flags);
269
270 static int gather_contacts_for_aor(void *obj, void *arg, int flags)
271 {
272         struct ao2_container *aor_contacts;
273         struct ast_sip_aor *aor = obj;
274         struct ao2_container *container = arg;
275
276         aor_contacts = ast_sip_location_retrieve_aor_contacts(aor);
277         if (!aor_contacts) {
278                 return 0;
279         }
280         ao2_callback(aor_contacts, OBJ_MULTIPLE | OBJ_NODATA, cli_contact_populate_container,
281                 container);
282         ao2_ref(aor_contacts, -1);
283         return CMP_MATCH;
284 }
285
286 struct ao2_container *ast_sip_location_retrieve_contacts_from_aor_list(const char *aor_list)
287 {
288         struct ao2_container *contacts;
289
290         contacts = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK,
291                 AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, permanent_uri_sort_fn, NULL);
292         if (!contacts) {
293                 return NULL;
294         }
295
296         ast_sip_for_each_aor(aor_list, gather_contacts_for_aor, contacts);
297
298         return contacts;
299 }
300
301 struct ast_sip_contact *ast_sip_location_retrieve_contact(const char *contact_name)
302 {
303         return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "contact", contact_name);
304 }
305
306 int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri,
307                 struct timeval expiration_time, const char *path_info, const char *user_agent,
308                 const char *via_addr, int via_port, const char *call_id,
309                 struct ast_sip_endpoint *endpoint)
310 {
311         char name[MAX_OBJECT_FIELD * 2 + 3];
312         RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
313         char hash[33];
314
315         ast_md5_hash(hash, uri);
316         snprintf(name, sizeof(name), "%s;@%s", ast_sorcery_object_get_id(aor), hash);
317
318         if (!(contact = ast_sorcery_alloc(ast_sip_get_sorcery(), "contact", name))) {
319                 return -1;
320         }
321
322         ast_string_field_set(contact, uri, uri);
323         contact->expiration_time = expiration_time;
324         contact->qualify_frequency = aor->qualify_frequency;
325         contact->qualify_timeout = aor->qualify_timeout;
326         contact->authenticate_qualify = aor->authenticate_qualify;
327         if (path_info && aor->support_path) {
328                 ast_string_field_set(contact, path, path_info);
329         }
330
331         if (!ast_strlen_zero(aor->outbound_proxy)) {
332                 ast_string_field_set(contact, outbound_proxy, aor->outbound_proxy);
333         }
334
335         if (!ast_strlen_zero(user_agent)) {
336                 ast_string_field_set(contact, user_agent, user_agent);
337         }
338
339         if (!ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) {
340                 ast_string_field_set(contact, reg_server, ast_config_AST_SYSTEM_NAME);
341         }
342
343         if (!ast_strlen_zero(via_addr)) {
344                 ast_string_field_set(contact, via_addr, via_addr);
345         }
346         contact->via_port = via_port;
347
348         if (!ast_strlen_zero(call_id)) {
349                 ast_string_field_set(contact, call_id, call_id);
350         }
351
352         contact->endpoint = ao2_bump(endpoint);
353
354         return ast_sorcery_create(ast_sip_get_sorcery(), contact);
355 }
356
357 int ast_sip_location_add_contact(struct ast_sip_aor *aor, const char *uri,
358                 struct timeval expiration_time, const char *path_info, const char *user_agent,
359                 const char *via_addr, int via_port, const char *call_id,
360                 struct ast_sip_endpoint *endpoint)
361 {
362         int res;
363         struct ast_named_lock *lock;
364
365         lock = ast_named_lock_get(AST_NAMED_LOCK_TYPE_RWLOCK, "aor", ast_sorcery_object_get_id(aor));
366         if (!lock) {
367                 return -1;
368         }
369
370         ao2_wrlock(lock);
371         res = ast_sip_location_add_contact_nolock(aor, uri, expiration_time, path_info, user_agent,
372                 via_addr, via_port, call_id,
373                 endpoint);
374         ao2_unlock(lock);
375         ast_named_lock_put(lock);
376
377         return res;
378 }
379
380 int ast_sip_location_update_contact(struct ast_sip_contact *contact)
381 {
382         return ast_sorcery_update(ast_sip_get_sorcery(), contact);
383 }
384
385 int ast_sip_location_delete_contact(struct ast_sip_contact *contact)
386 {
387         return ast_sorcery_delete(ast_sip_get_sorcery(), contact);
388 }
389
390 /*! \brief Custom handler for translating from a string timeval to actual structure */
391 static int expiration_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
392 {
393         struct ast_sip_contact *contact = obj;
394         return ast_get_timeval(var->value, &contact->expiration_time, ast_tv(0, 0), NULL);
395 }
396
397 /*! \brief Custom handler for translating from an actual structure timeval to string */
398 static int expiration_struct2str(const void *obj, const intptr_t *args, char **buf)
399 {
400         const struct ast_sip_contact *contact = obj;
401         return (ast_asprintf(buf, "%ld", contact->expiration_time.tv_sec) < 0) ? -1 : 0;
402 }
403
404 static int permanent_uri_sort_fn(const void *obj_left, const void *obj_right, int flags)
405 {
406         const struct ast_sip_contact *object_left = obj_left;
407         const struct ast_sip_contact *object_right = obj_right;
408         const char *right_key = obj_right;
409         int cmp;
410
411         switch (flags & OBJ_SEARCH_MASK) {
412         case OBJ_SEARCH_OBJECT:
413                 right_key = ast_sorcery_object_get_id(object_right);
414                 /* Fall through */
415         case OBJ_SEARCH_KEY:
416                 cmp = strcmp(ast_sorcery_object_get_id(object_left), right_key);
417                 break;
418         case OBJ_SEARCH_PARTIAL_KEY:
419                 /*
420                  * We could also use a partial key struct containing a length
421                  * so strlen() does not get called for every comparison instead.
422                  */
423                 cmp = strncmp(ast_sorcery_object_get_id(object_left), right_key, strlen(right_key));
424                 break;
425         default:
426                 /* Sort can only work on something with a full or partial key. */
427                 ast_assert(0);
428                 cmp = 0;
429                 break;
430         }
431         return cmp;
432 }
433
434 int ast_sip_validate_uri_length(const char *contact_uri)
435 {
436         int max_length = pj_max_hostname - 1;
437         char *contact = ast_strdupa(contact_uri);
438         char *host;
439         char *at;
440         int theres_a_port = 0;
441
442         if (strlen(contact_uri) > pjsip_max_url_size - 1) {
443                 return -1;
444         }
445
446         contact = ast_strip_quoted(contact, "<", ">");
447
448         if (!strncasecmp(contact, "sip:", 4)) {
449                 host = contact + 4;
450         } else if (!strncasecmp(contact, "sips:", 5)) {
451                 host = contact + 5;
452         } else {
453                 /* Not a SIP URI */
454                 return -1;
455         }
456
457         at = strchr(contact, '@');
458         if (at) {
459                 /* sip[s]:user@host */
460                 host = at + 1;
461         }
462
463         if (host[0] == '[') {
464                 /* Host is an IPv6 address. Just get up to the matching bracket */
465                 char *close_bracket;
466
467                 close_bracket = strchr(host, ']');
468                 if (!close_bracket) {
469                         return -1;
470                 }
471                 close_bracket++;
472                 if (*close_bracket == ':') {
473                         theres_a_port = 1;
474                 }
475                 *close_bracket = '\0';
476         } else {
477                 /* uri parameters could contain ';' so trim them off first */
478                 host = strsep(&host, ";?");
479                 /* Host is FQDN or IPv4 address. Need to find closing delimiter */
480                 if (strchr(host, ':')) {
481                         theres_a_port = 1;
482                         host = strsep(&host, ":");
483                 }
484         }
485
486         if (!theres_a_port) {
487                 max_length -= strlen("_sips.tcp.");
488         }
489
490         if (strlen(host) > max_length) {
491                 return -1;
492         }
493
494         return 0;
495 }
496
497 /*! \brief Custom handler for permanent URIs */
498 static int permanent_uri_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
499 {
500         struct ast_sip_aor *aor = obj;
501         const char *aor_id = ast_sorcery_object_get_id(aor);
502         char *contacts;
503         char *contact_uri;
504
505         if (ast_strlen_zero(var->value)) {
506                 return 0;
507         }
508
509         contacts = ast_strdupa(var->value);
510         while ((contact_uri = ast_strip(strsep(&contacts, ",")))) {
511                 struct ast_sip_contact *contact;
512                 struct ast_sip_contact_status *status;
513                 char hash[33];
514                 char contact_id[strlen(aor_id) + sizeof(hash) + 2];
515
516                 if (ast_strlen_zero(contact_uri)) {
517                         continue;
518                 }
519
520                 if (ast_sip_validate_uri_length(contact_uri)) {
521                         ast_log(LOG_ERROR, "Contact uri or hostname length exceeds pjproject limit: %s\n", contact_uri);
522                         return -1;
523                 }
524
525                 if (!aor->permanent_contacts) {
526                         aor->permanent_contacts = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK,
527                                 AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, permanent_uri_sort_fn, NULL);
528                         if (!aor->permanent_contacts) {
529                                 return -1;
530                         }
531                 }
532
533                 ast_md5_hash(hash, contact_uri);
534                 snprintf(contact_id, sizeof(contact_id), "%s@@%s", aor_id, hash);
535                 contact = ast_sorcery_alloc(ast_sip_get_sorcery(), "contact", contact_id);
536                 if (!contact) {
537                         return -1;
538                 }
539
540                 ast_string_field_set(contact, uri, contact_uri);
541
542                 status = ast_res_pjsip_find_or_create_contact_status(contact);
543                 if (!status) {
544                         ao2_ref(contact, -1);
545                         return -1;
546                 }
547                 ao2_ref(status, -1);
548
549                 ao2_link(aor->permanent_contacts, contact);
550                 ao2_ref(contact, -1);
551         }
552
553         return 0;
554 }
555
556 static int contact_to_var_list(void *object, void *arg, int flags)
557 {
558         struct ast_sip_contact_wrapper *wrapper = object;
559         struct ast_variable **var = arg;
560
561         ast_variable_list_append(&*var, ast_variable_new("contact", wrapper->contact->uri, ""));
562
563         return 0;
564 }
565
566 static int contacts_to_var_list(const void *obj, struct ast_variable **fields)
567 {
568         const struct ast_sip_aor *aor = obj;
569
570         ast_sip_for_each_contact(aor, contact_to_var_list, fields);
571
572         return 0;
573 }
574
575 static int voicemail_extension_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
576 {
577         struct ast_sip_aor *aor = obj;
578
579         aor->voicemail_extension = ast_strdup(var->value);
580
581         return aor->voicemail_extension ? 0 : -1;
582 }
583
584 static int voicemail_extension_to_str(const void *obj, const intptr_t *args, char **buf)
585 {
586         const struct ast_sip_aor *aor = obj;
587
588         *buf = ast_strdup(aor->voicemail_extension);
589
590         return 0;
591 }
592
593 int ast_sip_for_each_aor(const char *aors, ao2_callback_fn on_aor, void *arg)
594 {
595         char *copy, *name;
596
597         if (!on_aor || ast_strlen_zero(aors)) {
598                 return 0;
599         }
600
601         copy = ast_strdupa(aors);
602         while ((name = ast_strip(strsep(&copy, ",")))) {
603                 RAII_VAR(struct ast_sip_aor *, aor,
604                          ast_sip_location_retrieve_aor(name), ao2_cleanup);
605
606                 if (!aor) {
607                         continue;
608                 }
609
610                 if (on_aor(aor, arg, 0)) {
611                         return -1;
612                 }
613         }
614         return 0;
615 }
616
617 static void contact_wrapper_destroy(void *obj)
618 {
619         struct ast_sip_contact_wrapper *wrapper = obj;
620         ast_free(wrapper->aor_id);
621         ast_free(wrapper->contact_id);
622         ao2_ref(wrapper->contact, -1);
623 }
624
625 int ast_sip_for_each_contact(const struct ast_sip_aor *aor,
626                 ao2_callback_fn on_contact, void *arg)
627 {
628         RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
629         struct ao2_iterator i;
630         int res = 0;
631         void *object = NULL;
632
633         if (!on_contact ||
634             !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) {
635                 return 0;
636         }
637
638         i = ao2_iterator_init(contacts, 0);
639         while ((object = ao2_iterator_next(&i))) {
640                 RAII_VAR(struct ast_sip_contact *, contact, object, ao2_cleanup);
641                 RAII_VAR(struct ast_sip_contact_wrapper *, wrapper, NULL, ao2_cleanup);
642                 const char *aor_id = ast_sorcery_object_get_id(aor);
643
644                 wrapper = ao2_alloc(sizeof(struct ast_sip_contact_wrapper), contact_wrapper_destroy);
645                 if (!wrapper) {
646                         res = -1;
647                         break;
648                 }
649                 wrapper->contact_id = ast_malloc(strlen(aor_id) + strlen(contact->uri) + 2);
650                 if (!wrapper->contact_id) {
651                         res = -1;
652                         break;
653                 }
654                 sprintf(wrapper->contact_id, "%s/%s", aor_id, contact->uri);
655                 wrapper->aor_id = ast_strdup(aor_id);
656                 if (!wrapper->aor_id) {
657                         res = -1;
658                         break;
659                 }
660                 wrapper->contact = contact;
661                 ao2_bump(wrapper->contact);
662
663                 if ((res = on_contact(wrapper, arg, 0))) {
664                         break;
665                 }
666         }
667         ao2_iterator_destroy(&i);
668         return res;
669 }
670
671 int ast_sip_contact_to_str(void *object, void *arg, int flags)
672 {
673         struct ast_sip_contact_wrapper *wrapper = object;
674         struct ast_str **buf = arg;
675
676         ast_str_append(buf, 0, "%s,", wrapper->contact_id);
677
678         return 0;
679 }
680
681 static int sip_aor_to_ami(const struct ast_sip_aor *aor, struct ast_str **buf)
682 {
683         RAII_VAR(struct ast_variable *, objset, ast_sorcery_objectset_create2(
684                          ast_sip_get_sorcery(), aor, AST_HANDLER_ONLY_STRING), ast_variables_destroy);
685         struct ast_variable *i;
686
687         if (!objset) {
688                 return -1;
689         }
690
691         ast_str_append(buf, 0, "ObjectType: %s\r\n",
692                        ast_sorcery_object_get_type(aor));
693         ast_str_append(buf, 0, "ObjectName: %s\r\n",
694                        ast_sorcery_object_get_id(aor));
695
696         for (i = objset; i; i = i->next) {
697                 char *camel = ast_to_camel_case(i->name);
698                 if (strcmp(camel, "Contact") == 0) {
699                         ast_free(camel);
700                         camel = NULL;
701                 }
702                 ast_str_append(buf, 0, "%s: %s\r\n", S_OR(camel, "Contacts"), i->value);
703                 ast_free(camel);
704         }
705
706         return 0;
707 }
708
709 static int contacts_to_str(const void *obj, const intptr_t *args, char **buf)
710 {
711         const struct ast_sip_aor *aor = obj;
712         RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
713
714         ast_sip_for_each_contact(aor, ast_sip_contact_to_str, &str);
715         ast_str_truncate(str, -1);
716
717         *buf = ast_strdup(ast_str_buffer(str));
718         if (!*buf) {
719                 return -1;
720         }
721
722         return 0;
723 }
724
725 static int format_ami_aor_handler(void *obj, void *arg, int flags)
726 {
727         struct ast_sip_aor *aor = obj;
728         struct ast_sip_ami *ami = arg;
729         const struct ast_sip_endpoint *endpoint = ami->arg;
730         RAII_VAR(struct ast_str *, buf,
731                  ast_sip_create_ami_event("AorDetail", ami), ast_free);
732
733         int total_contacts;
734         int num_permanent;
735         RAII_VAR(struct ao2_container *, contacts,
736                  ast_sip_location_retrieve_aor_contacts(aor), ao2_cleanup);
737
738         if (!buf) {
739                 return -1;
740         }
741
742         sip_aor_to_ami(aor, &buf);
743         total_contacts = ao2_container_count(contacts);
744         num_permanent = aor->permanent_contacts ?
745                 ao2_container_count(aor->permanent_contacts) : 0;
746
747         ast_str_append(&buf, 0, "TotalContacts: %d\r\n", total_contacts);
748         ast_str_append(&buf, 0, "ContactsRegistered: %d\r\n",
749                        total_contacts - num_permanent);
750         ast_str_append(&buf, 0, "EndpointName: %s\r\n",
751                        ast_sorcery_object_get_id(endpoint));
752
753         astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
754         ami->count++;
755
756         return 0;
757 }
758
759 static int format_ami_endpoint_aor(const struct ast_sip_endpoint *endpoint,
760                                    struct ast_sip_ami *ami)
761 {
762         ami->arg = (void *)endpoint;
763         return ast_sip_for_each_aor(endpoint->aors,
764                                     format_ami_aor_handler, ami);
765 }
766
767 struct ast_sip_endpoint_formatter endpoint_aor_formatter = {
768         .format_ami = format_ami_endpoint_aor
769 };
770
771 static struct ao2_container *cli_aor_get_container(const char *regex)
772 {
773         RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
774         struct ao2_container *s_container;
775
776         container = ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "aor", regex);
777         if (!container) {
778                 return NULL;
779         }
780
781         s_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
782                 ast_sorcery_object_id_sort, ast_sorcery_object_id_compare);
783         if (!s_container) {
784                 return NULL;
785         }
786
787         if (ao2_container_dup(s_container, container, 0)) {
788                 ao2_ref(s_container, -1);
789                 return NULL;
790         }
791
792         return s_container;
793 }
794
795 static int cli_contact_populate_container(void *obj, void *arg, int flags)
796 {
797         ao2_link(arg, obj);
798
799         return 0;
800 }
801
802 static int cli_aor_gather_contacts(void *obj, void *arg, int flags)
803 {
804         struct ast_sip_aor *aor = obj;
805
806         return ast_sip_for_each_contact(aor, cli_contact_populate_container, arg);
807 }
808
809 static const char *cli_contact_get_id(const void *obj)
810 {
811         const struct ast_sip_contact_wrapper *wrapper = obj;
812         return wrapper->contact_id;
813 }
814
815 static int cli_contact_sort(const void *obj, const void *arg, int flags)
816 {
817         const struct ast_sip_contact_wrapper *left_wrapper = obj;
818         const struct ast_sip_contact_wrapper *right_wrapper = arg;
819         const char *right_key = arg;
820         int cmp = 0;
821
822         switch (flags & OBJ_SEARCH_MASK) {
823         case OBJ_SEARCH_OBJECT:
824                 right_key = right_wrapper->contact_id;
825                 /* Fall through */
826         case OBJ_SEARCH_KEY:
827                 cmp = strcmp(left_wrapper->contact_id, right_key);
828                 break;
829         case OBJ_SEARCH_PARTIAL_KEY:
830                 cmp = strncmp(left_wrapper->contact_id, right_key, strlen(right_key));
831                 break;
832         default:
833                 cmp = 0;
834                 break;
835         }
836
837         return cmp;
838 }
839
840 static int cli_contact_compare(void *obj, void *arg, int flags)
841 {
842         const struct ast_sip_contact_wrapper *left_wrapper = obj;
843         const struct ast_sip_contact_wrapper *right_wrapper = arg;
844         const char *right_key = arg;
845         int cmp = 0;
846
847         switch (flags & OBJ_SEARCH_MASK) {
848         case OBJ_SEARCH_OBJECT:
849                 right_key = right_wrapper->contact_id;
850                 /* Fall through */
851         case OBJ_SEARCH_KEY:
852                 if (strcmp(left_wrapper->contact_id, right_key) == 0) {;
853                         cmp = CMP_MATCH | CMP_STOP;
854                 }
855                 break;
856         case OBJ_SEARCH_PARTIAL_KEY:
857                 if (strncmp(left_wrapper->contact_id, right_key, strlen(right_key)) == 0) {
858                         cmp = CMP_MATCH;
859                 }
860                 break;
861         default:
862                 cmp = 0;
863                 break;
864         }
865
866         return cmp;
867 }
868
869 static int cli_contact_iterate(void *container, ao2_callback_fn callback, void *args)
870 {
871         return ast_sip_for_each_contact(container, callback, args);
872 }
873
874 static int cli_filter_contacts(void *obj, void *arg, int flags)
875 {
876         struct ast_sip_contact_wrapper *wrapper = obj;
877         regex_t *regexbuf = arg;
878
879         if (!regexec(regexbuf, wrapper->contact_id, 0, NULL, 0)) {
880                 return 0;
881         }
882
883         return CMP_MATCH;
884 }
885
886 static struct ao2_container *cli_contact_get_container(const char *regex)
887 {
888         RAII_VAR(struct ao2_container *, parent_container, NULL, ao2_cleanup);
889         struct ao2_container *child_container;
890         regex_t regexbuf;
891
892         parent_container =  cli_aor_get_container("");
893         if (!parent_container) {
894                 return NULL;
895         }
896
897         child_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
898                 cli_contact_sort, cli_contact_compare);
899         if (!child_container) {
900                 return NULL;
901         }
902
903         ao2_callback(parent_container, OBJ_NODATA, cli_aor_gather_contacts, child_container);
904
905         if (!ast_strlen_zero(regex)) {
906                 if (regcomp(&regexbuf, regex, REG_EXTENDED | REG_NOSUB)) {
907                         ao2_ref(child_container, -1);
908                         return NULL;
909                 }
910                 ao2_callback(child_container, OBJ_UNLINK | OBJ_MULTIPLE | OBJ_NODATA, cli_filter_contacts, &regexbuf);
911                 regfree(&regexbuf);
912         }
913
914         return child_container;
915 }
916
917 static void *cli_contact_retrieve_by_id(const char *id)
918 {
919         RAII_VAR(struct ao2_container *, container, cli_contact_get_container(""), ao2_cleanup);
920
921         return ao2_find(container, id, OBJ_KEY | OBJ_NOLOCK);
922 }
923
924 static int cli_contact_print_header(void *obj, void *arg, int flags)
925 {
926         struct ast_sip_cli_context *context = arg;
927         int indent = CLI_INDENT_TO_SPACES(context->indent_level);
928         int filler = CLI_LAST_TABSTOP - indent - 23;
929
930         ast_assert(context->output_buffer != NULL);
931
932         ast_str_append(&context->output_buffer, 0,
933                 "%*s:  <Aor/ContactUri%*.*s> <Hash....> <Status> <RTT(ms)..>\n",
934                 indent, "Contact", filler, filler, CLI_HEADER_FILLER);
935
936         return 0;
937 }
938
939 static int cli_contact_print_body(void *obj, void *arg, int flags)
940 {
941         struct ast_sip_contact_wrapper *wrapper = obj;
942         struct ast_sip_contact *contact = wrapper->contact;
943         struct ast_sip_cli_context *context = arg;
944         int indent;
945         int flexwidth;
946         const char *contact_id = ast_sorcery_object_get_id(contact);
947         const char *hash_start = contact_id + strlen(contact->aor) + 2;
948
949         RAII_VAR(struct ast_sip_contact_status *, status,
950                 ast_sorcery_retrieve_by_id( ast_sip_get_sorcery(), CONTACT_STATUS, contact_id),
951                 ao2_cleanup);
952
953         ast_assert(contact->uri != NULL);
954         ast_assert(context->output_buffer != NULL);
955
956         indent = CLI_INDENT_TO_SPACES(context->indent_level);
957         flexwidth = CLI_LAST_TABSTOP - indent - 9 - strlen(contact->aor) + 1;
958
959         ast_str_append(&context->output_buffer, 0, "%*s:  %s/%-*.*s %-10.10s %-7.7s %11.3f\n",
960                 indent,
961                 "Contact",
962                 contact->aor,
963                 flexwidth, flexwidth,
964                 contact->uri,
965                 hash_start,
966                 ast_sip_get_contact_short_status_label(status ? status->status : UNKNOWN),
967                 (status && (status->status != UNKNOWN) ? ((long long) status->rtt) / 1000.0 : NAN));
968
969         return 0;
970 }
971
972 static int cli_aor_iterate(void *container, ao2_callback_fn callback, void *args)
973 {
974         const char *aor_list = container;
975
976         return ast_sip_for_each_aor(aor_list, callback, args);
977 }
978
979 static void *cli_aor_retrieve_by_id(const char *id)
980 {
981         return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "aor", id);
982 }
983
984 static const char *cli_aor_get_id(const void *obj)
985 {
986         return ast_sorcery_object_get_id(obj);
987 }
988
989 static int cli_aor_print_header(void *obj, void *arg, int flags)
990 {
991         struct ast_sip_cli_context *context = arg;
992         RAII_VAR(struct ast_sip_cli_formatter_entry *, formatter_entry, NULL, ao2_cleanup);
993
994         int indent = CLI_INDENT_TO_SPACES(context->indent_level);
995         int filler = CLI_LAST_TABSTOP - indent - 7;
996
997         ast_assert(context->output_buffer != NULL);
998
999         ast_str_append(&context->output_buffer, 0,
1000                 "%*s:  <Aor%*.*s>  <MaxContact>\n",
1001                 indent, "Aor", filler, filler, CLI_HEADER_FILLER);
1002
1003         if (context->recurse) {
1004                 context->indent_level++;
1005                 formatter_entry = ast_sip_lookup_cli_formatter("contact");
1006                 if (formatter_entry) {
1007                         formatter_entry->print_header(NULL, context, 0);
1008                 }
1009                 context->indent_level--;
1010         }
1011
1012         return 0;
1013 }
1014
1015 static int cli_aor_print_body(void *obj, void *arg, int flags)
1016 {
1017         struct ast_sip_aor *aor = obj;
1018         struct ast_sip_cli_context *context = arg;
1019         RAII_VAR(struct ast_sip_cli_formatter_entry *, formatter_entry, NULL, ao2_cleanup);
1020         int indent;
1021         int flexwidth;
1022
1023         ast_assert(context->output_buffer != NULL);
1024
1025 //      context->current_aor = aor;
1026
1027         indent = CLI_INDENT_TO_SPACES(context->indent_level);
1028         flexwidth = CLI_LAST_TABSTOP - indent - 12;
1029
1030         ast_str_append(&context->output_buffer, 0, "%*s:  %-*.*s %12u\n",
1031                 indent,
1032                 "Aor",
1033                 flexwidth, flexwidth,
1034                 ast_sorcery_object_get_id(aor), aor->max_contacts);
1035
1036         if (context->recurse) {
1037                 context->indent_level++;
1038
1039                 formatter_entry = ast_sip_lookup_cli_formatter("contact");
1040                 if (formatter_entry) {
1041                         formatter_entry->iterate(aor, formatter_entry->print_body, context);
1042                 }
1043
1044                 context->indent_level--;
1045
1046                 if (context->indent_level == 0) {
1047                         ast_str_append(&context->output_buffer, 0, "\n");
1048                 }
1049         }
1050
1051         if (context->show_details || (context->show_details_only_level_0 && context->indent_level == 0)) {
1052                 ast_str_append(&context->output_buffer, 0, "\n");
1053                 ast_sip_cli_print_sorcery_objectset(aor, context, 0);
1054         }
1055
1056         return 0;
1057 }
1058
1059 static struct ast_cli_entry cli_commands[] = {
1060         AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Aors",
1061                 .command = "pjsip list aors",
1062                 .usage = "Usage: pjsip list aors [ like <pattern> ]\n"
1063                                 "       List the configured PJSIP Aors\n"
1064                                 "       Optional regular expression pattern is used to filter the list.\n"),
1065         AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Aors",
1066                 .command = "pjsip show aors",
1067                 .usage = "Usage: pjsip show aors [ like <pattern> ]\n"
1068                                 "       Show the configured PJSIP Aors\n"
1069                                 "       Optional regular expression pattern is used to filter the list.\n"),
1070         AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Aor",
1071                 .command = "pjsip show aor",
1072                 .usage = "Usage: pjsip show aor <id>\n"
1073                                  "       Show the configured PJSIP Aor\n"),
1074
1075         AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Contacts",
1076                 .command = "pjsip list contacts",
1077                 .usage = "Usage: pjsip list contacts [ like <pattern> ]\n"
1078                                 "       List the configured PJSIP contacts\n"
1079                                 "       Optional regular expression pattern is used to filter the list.\n"),
1080         AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Contacts",
1081                 .command = "pjsip show contacts",
1082                 .usage = "Usage: pjsip show contacts [ like <pattern> ]\n"
1083                                 "       Show the configured PJSIP contacts\n"
1084                                 "       Optional regular expression pattern is used to filter the list.\n"),
1085         AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Contact",
1086                 .command = "pjsip show contact",
1087                 .usage = "Usage: pjsip show contact\n"
1088                                  "       Show the configured PJSIP contact\n"),
1089 };
1090
1091 struct ast_sip_cli_formatter_entry *contact_formatter;
1092 struct ast_sip_cli_formatter_entry *aor_formatter;
1093
1094 /*! \brief Always create a contact_status for each contact */
1095 static int contact_apply_handler(const struct ast_sorcery *sorcery, void *object)
1096 {
1097         struct ast_sip_contact_status *status;
1098         struct ast_sip_contact *contact = object;
1099
1100         status = ast_res_pjsip_find_or_create_contact_status(contact);
1101         ao2_cleanup(status);
1102
1103         return status ? 0 : -1;
1104 }
1105
1106 /*! \brief Initialize sorcery with location support */
1107 int ast_sip_initialize_sorcery_location(void)
1108 {
1109         struct ast_sorcery *sorcery = ast_sip_get_sorcery();
1110         int i;
1111
1112         ast_pjproject_get_buildopt("PJ_MAX_HOSTNAME", "%d", &pj_max_hostname);
1113         /* As of pjproject 2.4.5, PJSIP_MAX_URL_SIZE isn't exposed yet but we try anyway. */
1114         ast_pjproject_get_buildopt("PJSIP_MAX_URL_SIZE", "%d", &pjsip_max_url_size);
1115
1116         ast_sorcery_apply_default(sorcery, "contact", "astdb", "registrar");
1117         ast_sorcery_apply_default(sorcery, "aor", "config", "pjsip.conf,criteria=type=aor");
1118
1119         if (ast_sorcery_object_register(sorcery, "contact", contact_alloc, NULL, contact_apply_handler) ||
1120                 ast_sorcery_object_register(sorcery, "aor", aor_alloc, NULL, NULL)) {
1121                 return -1;
1122         }
1123
1124         ast_sorcery_observer_add(sorcery, "aor", &aor_observer);
1125
1126         ast_sorcery_object_field_register(sorcery, "contact", "type", "", OPT_NOOP_T, 0, 0);
1127         ast_sorcery_object_field_register(sorcery, "contact", "uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, uri));
1128         ast_sorcery_object_field_register(sorcery, "contact", "path", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, path));
1129         ast_sorcery_object_field_register_custom(sorcery, "contact", "expiration_time", "", expiration_str2struct, expiration_struct2str, NULL, 0, 0);
1130         ast_sorcery_object_field_register(sorcery, "contact", "qualify_frequency", 0, OPT_UINT_T,
1131                 PARSE_IN_RANGE, FLDSET(struct ast_sip_contact, qualify_frequency), 0, 86400);
1132         ast_sorcery_object_field_register(sorcery, "contact", "qualify_timeout", "3.0", OPT_DOUBLE_T, 0, FLDSET(struct ast_sip_contact, qualify_timeout));
1133         ast_sorcery_object_field_register(sorcery, "contact", "authenticate_qualify", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_contact, authenticate_qualify));
1134         ast_sorcery_object_field_register(sorcery, "contact", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, outbound_proxy));
1135         ast_sorcery_object_field_register(sorcery, "contact", "user_agent", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, user_agent));
1136         ast_sorcery_object_field_register(sorcery, "contact", "reg_server", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, reg_server));
1137         ast_sorcery_object_field_register(sorcery, "contact", "via_addr", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, via_addr));
1138         ast_sorcery_object_field_register(sorcery, "contact", "via_port", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_contact, via_port));
1139         ast_sorcery_object_field_register(sorcery, "contact", "call_id", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, call_id));
1140
1141         ast_sorcery_object_field_register(sorcery, "aor", "type", "", OPT_NOOP_T, 0, 0);
1142         ast_sorcery_object_field_register(sorcery, "aor", "minimum_expiration", "60", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, minimum_expiration));
1143         ast_sorcery_object_field_register(sorcery, "aor", "maximum_expiration", "7200", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, maximum_expiration));
1144         ast_sorcery_object_field_register(sorcery, "aor", "default_expiration", "3600", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, default_expiration));
1145         ast_sorcery_object_field_register(sorcery, "aor", "qualify_frequency", 0, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_aor, qualify_frequency), 0, 86400);
1146         ast_sorcery_object_field_register(sorcery, "aor", "qualify_timeout", "3.0", OPT_DOUBLE_T, 0, FLDSET(struct ast_sip_aor, qualify_timeout));
1147         ast_sorcery_object_field_register(sorcery, "aor", "authenticate_qualify", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, authenticate_qualify));
1148         ast_sorcery_object_field_register(sorcery, "aor", "max_contacts", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, max_contacts));
1149         ast_sorcery_object_field_register(sorcery, "aor", "remove_existing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, remove_existing));
1150         ast_sorcery_object_field_register_custom(sorcery, "aor", "contact", "", permanent_uri_handler, contacts_to_str, contacts_to_var_list, 0, 0);
1151         ast_sorcery_object_field_register(sorcery, "aor", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_aor, mailboxes));
1152         ast_sorcery_object_field_register_custom(sorcery, "aor", "voicemail_extension", "", voicemail_extension_handler, voicemail_extension_to_str, NULL, 0, 0);
1153         ast_sorcery_object_field_register(sorcery, "aor", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_aor, outbound_proxy));
1154         ast_sorcery_object_field_register(sorcery, "aor", "support_path", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, support_path));
1155
1156         internal_sip_register_endpoint_formatter(&endpoint_aor_formatter);
1157
1158         contact_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
1159         if (!contact_formatter) {
1160                 ast_log(LOG_ERROR, "Unable to allocate memory for contact_formatter\n");
1161                 return -1;
1162         }
1163         contact_formatter->name = "contact";
1164         contact_formatter->print_header = cli_contact_print_header;
1165         contact_formatter->print_body = cli_contact_print_body;
1166         contact_formatter->get_container = cli_contact_get_container;
1167         contact_formatter->iterate = cli_contact_iterate;
1168         contact_formatter->get_id = cli_contact_get_id;
1169         contact_formatter->retrieve_by_id = cli_contact_retrieve_by_id;
1170
1171         aor_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
1172         if (!aor_formatter) {
1173                 ast_log(LOG_ERROR, "Unable to allocate memory for aor_formatter\n");
1174                 return -1;
1175         }
1176         aor_formatter->name = "aor";
1177         aor_formatter->print_header = cli_aor_print_header;
1178         aor_formatter->print_body = cli_aor_print_body;
1179         aor_formatter->get_container = cli_aor_get_container;
1180         aor_formatter->iterate = cli_aor_iterate;
1181         aor_formatter->get_id = cli_aor_get_id;
1182         aor_formatter->retrieve_by_id = cli_aor_retrieve_by_id;
1183
1184         ast_sip_register_cli_formatter(contact_formatter);
1185         ast_sip_register_cli_formatter(aor_formatter);
1186         ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
1187
1188         /*
1189          * Reset StatsD gauges in case we didn't shut down cleanly.
1190          * Note that this must done here, as contacts will create the contact_status
1191          * object before PJSIP options handling is initialized.
1192          */
1193         for (i = 0; i <= REMOVED; i++) {
1194                 ast_statsd_log_full_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE, 0, 1.0, ast_sip_get_contact_status_label(i));
1195         }
1196
1197         return 0;
1198 }
1199
1200 int ast_sip_destroy_sorcery_location(void)
1201 {
1202         ast_sorcery_observer_remove(ast_sip_get_sorcery(), "aor", &aor_observer);
1203         ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
1204         ast_sip_unregister_cli_formatter(contact_formatter);
1205         ast_sip_unregister_cli_formatter(aor_formatter);
1206
1207         internal_sip_unregister_endpoint_formatter(&endpoint_aor_formatter);
1208
1209         return 0;
1210 }
1211