e40f9bff4934ff91014d2f3f50402b811020874b
[asterisk/asterisk.git] / res / res_pjsip_endpoint_identifier_ip.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Mark Michelson <mmichelson@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 /*** MODULEINFO
20         <depend>pjproject</depend>
21         <depend>res_pjsip</depend>
22         <support_level>core</support_level>
23  ***/
24
25 #include "asterisk.h"
26
27 #include <pjsip.h>
28
29 #include "asterisk/res_pjsip.h"
30 #include "asterisk/res_pjsip_cli.h"
31 #include "asterisk/module.h"
32 #include "asterisk/acl.h"
33 #include "asterisk/manager.h"
34 #include "res_pjsip/include/res_pjsip_private.h"
35
36 /*** DOCUMENTATION
37         <configInfo name="res_pjsip_endpoint_identifier_ip" language="en_US">
38                 <synopsis>Module that identifies endpoints</synopsis>
39                 <configFile name="pjsip.conf">
40                         <configObject name="identify">
41                                 <synopsis>Identifies endpoints via some criteria.</synopsis>
42                                 <description>
43                                         <para>This module provides alternatives to matching inbound requests to
44                                         a configured endpoint. At least one of the matching mechanisms
45                                         must be provided, or the object configuration will be invalid.</para>
46                                         <para>If multiple criteria are provided, an inbound request will
47                                         be matched if it matches <emphasis>any</emphasis> of the criteria.</para>
48                                         <para>The matching mechanisms are provided by the following
49                                         configuration options:</para>
50                                         <enumlist>
51                                                 <enum name="match"><para>Match by source IP address.</para></enum>
52                                                 <enum name="match_header"><para>Match by SIP header.</para></enum>
53                                         </enumlist>
54                                 </description>
55                                 <configOption name="endpoint">
56                                         <synopsis>Name of Endpoint</synopsis>
57                                 </configOption>
58                                 <configOption name="match">
59                                         <synopsis>IP addresses or networks to match against.</synopsis>
60                                         <description><para>
61                                                 The value is a comma-delimited list of IP addresses. IP addresses may
62                                                 have a subnet mask appended. The subnet mask may be written in either
63                                                 CIDR or dot-decimal notation. Separate the IP address and subnet
64                                                 mask with a slash ('/').
65                                         </para></description>
66                                 </configOption>
67                                 <configOption name="srv_lookups" default="yes">
68                                         <synopsis>Perform SRV lookups for provided hostnames.</synopsis>
69                                         <description><para>When enabled, <replaceable>srv_lookups</replaceable> will
70                                         perform SRV lookups for _sip._udp, _sip._tcp, and _sips._tcp of the given
71                                         hostnames to determine additional addresses that traffic may originate from.
72                                         </para></description>
73                                 </configOption>
74                                 <configOption name="match_header">
75                                         <synopsis>Header/value pair to match against.</synopsis>
76                                         <description><para>A SIP header who value is used to match against. SIP
77                                         requests containing the header, along with the specified value, will be
78                                         mapped to the specified endpoint. The header must be specified with a
79                                         <literal>:</literal>, as in <literal>match_header = SIPHeader: value</literal>.
80                                         </para></description>
81                                 </configOption>
82                                 <configOption name="type">
83                                         <synopsis>Must be of type 'identify'.</synopsis>
84                                 </configOption>
85                         </configObject>
86                 </configFile>
87         </configInfo>
88  ***/
89
90 /*! \brief The number of buckets for storing hosts for resolution */
91 #define HOSTS_BUCKETS 53
92
93 /*! \brief Structure for an IP identification matching object */
94 struct ip_identify_match {
95         /*! \brief Sorcery object details */
96         SORCERY_OBJECT(details);
97         /*! \brief Stringfields */
98         AST_DECLARE_STRING_FIELDS(
99                 /*! The name of the endpoint */
100                 AST_STRING_FIELD(endpoint_name);
101                 /*! If matching by header, the header/value to match against */
102                 AST_STRING_FIELD(match_header);
103         );
104         /*! \brief Networks or addresses that should match this */
105         struct ast_ha *matches;
106         /*! \brief Perform SRV resolution of hostnames */
107         unsigned int srv_lookups;
108         /*! \brief Hosts to be resolved when applying configuration */
109         struct ao2_container *hosts;
110 };
111
112 /*! \brief Destructor function for a matching object */
113 static void ip_identify_destroy(void *obj)
114 {
115         struct ip_identify_match *identify = obj;
116
117         ast_string_field_free_memory(identify);
118         ast_free_ha(identify->matches);
119         ao2_cleanup(identify->hosts);
120 }
121
122 /*! \brief Allocator function for a matching object */
123 static void *ip_identify_alloc(const char *name)
124 {
125         struct ip_identify_match *identify = ast_sorcery_generic_alloc(sizeof(*identify), ip_identify_destroy);
126
127         if (!identify || ast_string_field_init(identify, 256)) {
128                 ao2_cleanup(identify);
129                 return NULL;
130         }
131
132         return identify;
133 }
134
135 /*! \brief Comparator function for matching an object by header */
136 static int header_identify_match_check(void *obj, void *arg, int flags)
137 {
138         struct ip_identify_match *identify = obj;
139         struct pjsip_rx_data *rdata = arg;
140         pjsip_generic_string_hdr *header;
141         pj_str_t pj_header_name;
142         pj_str_t pj_header_value;
143         char *c_header;
144         char *c_value;
145
146         if (ast_strlen_zero(identify->match_header)) {
147                 return 0;
148         }
149
150         c_header = ast_strdupa(identify->match_header);
151         c_value = strchr(c_header, ':');
152         if (!c_value) {
153                 /* This should not be possible.  The object cannot be created if so. */
154                 ast_assert(0);
155                 return 0;
156         }
157         *c_value = '\0';
158         c_value++;
159         c_value = ast_strip(c_value);
160
161         pj_header_name = pj_str(c_header);
162         header = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &pj_header_name, NULL);
163         if (!header) {
164                 ast_debug(3, "Identify '%s': SIP message does not have header '%s'\n",
165                         ast_sorcery_object_get_id(identify),
166                         c_header);
167                 return 0;
168         }
169
170         pj_header_value = pj_str(c_value);
171         if (pj_strcmp(&pj_header_value, &header->hvalue)) {
172                 ast_debug(3, "Identify '%s': SIP message has header '%s' but value '%.*s' does not match '%s'\n",
173                         ast_sorcery_object_get_id(identify),
174                         c_header,
175                         (int) pj_strlen(&header->hvalue), pj_strbuf(&header->hvalue),
176                         c_value);
177                 return 0;
178         }
179
180         return CMP_MATCH | CMP_STOP;
181 }
182
183 /*! \brief Comparator function for matching an object by IP address */
184 static int ip_identify_match_check(void *obj, void *arg, int flags)
185 {
186         struct ip_identify_match *identify = obj;
187         struct ast_sockaddr *addr = arg;
188         int sense;
189
190         sense = ast_apply_ha(identify->matches, addr);
191         if (sense != AST_SENSE_ALLOW) {
192                 ast_debug(3, "Source address %s matches identify '%s'\n",
193                                 ast_sockaddr_stringify(addr),
194                                 ast_sorcery_object_get_id(identify));
195                 return CMP_MATCH | CMP_STOP;
196         } else {
197                 ast_debug(3, "Source address %s does not match identify '%s'\n",
198                                 ast_sockaddr_stringify(addr),
199                                 ast_sorcery_object_get_id(identify));
200                 return 0;
201         }
202 }
203
204 static struct ast_sip_endpoint *ip_identify(pjsip_rx_data *rdata)
205 {
206         struct ast_sockaddr addr = { { 0, } };
207         RAII_VAR(struct ao2_container *, candidates, NULL, ao2_cleanup);
208         RAII_VAR(struct ip_identify_match *, match, NULL, ao2_cleanup);
209         struct ast_sip_endpoint *endpoint;
210
211         /* If no possibilities exist return early to save some time */
212         if (!(candidates = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "identify", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL)) ||
213                 !ao2_container_count(candidates)) {
214                 ast_debug(3, "No identify sections to match against\n");
215                 return NULL;
216         }
217
218         ast_sockaddr_parse(&addr, rdata->pkt_info.src_name, PARSE_PORT_FORBID);
219         ast_sockaddr_set_port(&addr, rdata->pkt_info.src_port);
220
221         match = ao2_callback(candidates, 0, ip_identify_match_check, &addr);
222         if (!match) {
223                 ast_debug(3, "Identify checks by IP address failed to find match: '%s' did not match any identify section rules\n",
224                                 ast_sockaddr_stringify(&addr));
225                 match = ao2_callback(candidates, 0, header_identify_match_check, rdata);
226                 if (!match) {
227                         return NULL;
228                 }
229         }
230
231         endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", match->endpoint_name);
232
233         if (endpoint) {
234                 if (!(endpoint->ident_method & AST_SIP_ENDPOINT_IDENTIFY_BY_IP)) {
235                         ast_debug(3, "Endpoint '%s' found for '%s' but 'ip' method not supported'\n", match->endpoint_name,
236                                 ast_sockaddr_stringify(&addr));
237                         ao2_cleanup(endpoint);
238                         return NULL;
239                 }
240                 ast_debug(3, "Retrieved endpoint %s\n", ast_sorcery_object_get_id(endpoint));
241         } else {
242                 ast_log(LOG_WARNING, "Identify section '%s' points to endpoint '%s' but endpoint could not be looked up\n",
243                                 ast_sorcery_object_get_id(match), match->endpoint_name);
244         }
245
246         return endpoint;
247 }
248
249 static struct ast_sip_endpoint_identifier ip_identifier = {
250         .identify_endpoint = ip_identify,
251 };
252
253 /*! \brief Helper function which performs a host lookup and adds result to identify match */
254 static int ip_identify_match_host_lookup(struct ip_identify_match *identify, const char *host)
255 {
256         struct ast_sockaddr *addrs;
257         int num_addrs = 0, error = 0, i;
258         int results = 0;
259
260         num_addrs = ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC);
261         if (!num_addrs) {
262                 return -1;
263         }
264
265         for (i = 0; i < num_addrs; ++i) {
266                 /* Check if the address is already in the list, if so don't add it again */
267                 if (identify->matches && (ast_apply_ha(identify->matches, &addrs[i]) != AST_SENSE_ALLOW)) {
268                         continue;
269                 }
270
271                 /* We deny what we actually want to match because there is an implicit permit all rule for ACLs */
272                 identify->matches = ast_append_ha("d", ast_sockaddr_stringify_addr(&addrs[i]), identify->matches, &error);
273
274                 if (!identify->matches || error) {
275                         results = -1;
276                         break;
277                 }
278
279                 results += 1;
280         }
281
282         ast_free(addrs);
283
284         return results;
285 }
286
287 /*! \brief Helper function which performs an SRV lookup and then resolves the hostname */
288 static int ip_identify_match_srv_lookup(struct ip_identify_match *identify, const char *prefix, const char *host, int results)
289 {
290         char service[NI_MAXHOST];
291         struct srv_context *context = NULL;
292         int srv_ret;
293         const char *srvhost;
294         unsigned short srvport;
295
296         snprintf(service, sizeof(service), "%s.%s", prefix, host);
297
298         while (!(srv_ret = ast_srv_lookup(&context, service, &srvhost, &srvport))) {
299                 int hosts;
300
301                 /* In the case of the SRV lookup we don't care if it fails, we will output a log message
302                  * when we fallback to a normal lookup.
303                  */
304                 hosts = ip_identify_match_host_lookup(identify, srvhost);
305                 if (hosts == -1) {
306                         results = -1;
307                         break;
308                 } else {
309                         results += hosts;
310                 }
311         }
312
313         ast_srv_cleanup(&context);
314
315         return results;
316 }
317
318 /*! \brief Custom handler for match field */
319 static int ip_identify_match_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
320 {
321         struct ip_identify_match *identify = obj;
322         char *input_string = ast_strdupa(var->value);
323         char *current_string;
324
325         if (ast_strlen_zero(var->value)) {
326                 return 0;
327         }
328
329         while ((current_string = ast_strip(strsep(&input_string, ",")))) {
330                 char *mask = strrchr(current_string, '/');
331                 int error = 0;
332
333                 if (ast_strlen_zero(current_string)) {
334                         continue;
335                 }
336
337                 if (mask) {
338                         identify->matches = ast_append_ha("d", current_string, identify->matches, &error);
339
340                         if (!identify->matches || error) {
341                                 ast_log(LOG_ERROR, "Failed to add address '%s' to ip endpoint identifier '%s'\n",
342                                         current_string, ast_sorcery_object_get_id(obj));
343                                 return -1;
344                         }
345
346                         continue;
347                 }
348
349                 if (!identify->hosts) {
350                         identify->hosts = ast_str_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, HOSTS_BUCKETS);
351                         if (!identify->hosts) {
352                                 ast_log(LOG_ERROR, "Failed to create container to store hosts on ip endpoint identifier '%s'\n",
353                                         ast_sorcery_object_get_id(obj));
354                                 return -1;
355                         }
356                 }
357
358                 error = ast_str_container_add(identify->hosts, current_string);
359                 if (error) {
360                         ast_log(LOG_ERROR, "Failed to store host '%s' for resolution on ip endpoint identifier '%s'\n",
361                                 current_string, ast_sorcery_object_get_id(obj));
362                         return -1;
363                 }
364         }
365
366         return 0;
367 }
368
369 /*! \brief Apply handler for identify type */
370 static int ip_identify_apply(const struct ast_sorcery *sorcery, void *obj)
371 {
372         struct ip_identify_match *identify = obj;
373         char *current_string;
374         struct ao2_iterator i;
375
376         /* Validate the identify object configuration */
377         if (ast_strlen_zero(identify->endpoint_name)) {
378                 ast_log(LOG_ERROR, "Identify '%s' missing required endpoint name.\n",
379                         ast_sorcery_object_get_id(identify));
380                 return -1;
381         }
382         if (ast_strlen_zero(identify->match_header) /* No header to match */
383                 /* and no static IP addresses with a mask */
384                 && !identify->matches
385                 /* and no addresses to resolve */
386                 && (!identify->hosts || !ao2_container_count(identify->hosts))) {
387                 ast_log(LOG_ERROR, "Identify '%s' is not configured to match anything.\n",
388                         ast_sorcery_object_get_id(identify));
389                 return -1;
390         }
391         if (!ast_strlen_zero(identify->match_header)
392                 && !strchr(identify->match_header, ':')) {
393                 ast_log(LOG_ERROR, "Identify '%s' missing ':' separator in match_header '%s'.\n",
394                         ast_sorcery_object_get_id(identify), identify->match_header);
395                 return -1;
396         }
397
398         if (!identify->hosts) {
399                 return 0;
400         }
401
402         /* Resolve the match addresses now */
403         i = ao2_iterator_init(identify->hosts, 0);
404         while ((current_string = ao2_iterator_next(&i))) {
405                 struct ast_sockaddr address;
406                 int results = 0;
407
408                 /* If the provided string is not an IP address perform SRV resolution on it */
409                 if (identify->srv_lookups && !ast_sockaddr_parse(&address, current_string, 0)) {
410                         results = ip_identify_match_srv_lookup(identify, "_sip._udp", current_string,
411                                 results);
412                         if (results != -1) {
413                                 results = ip_identify_match_srv_lookup(identify, "_sip._tcp",
414                                         current_string, results);
415                         }
416                         if (results != -1) {
417                                 results = ip_identify_match_srv_lookup(identify, "_sips._tcp",
418                                         current_string, results);
419                         }
420                 }
421
422                 /* If SRV fails fall back to a normal lookup on the host itself */
423                 if (!results) {
424                         results = ip_identify_match_host_lookup(identify, current_string);
425                 }
426
427                 if (results == 0) {
428                         ast_log(LOG_WARNING, "Identify '%s' provided address '%s' did not resolve to any address\n",
429                                 ast_sorcery_object_get_id(identify), current_string);
430                 } else if (results == -1) {
431                         ast_log(LOG_ERROR, "Identify '%s' failed when adding resolution results of '%s'\n",
432                                 ast_sorcery_object_get_id(identify), current_string);
433                         ao2_ref(current_string, -1);
434                         ao2_iterator_destroy(&i);
435                         return -1;
436                 }
437
438                 ao2_ref(current_string, -1);
439         }
440         ao2_iterator_destroy(&i);
441
442         ao2_ref(identify->hosts, -1);
443         identify->hosts = NULL;
444
445         return 0;
446 }
447
448 static int match_to_str(const void *obj, const intptr_t *args, char **buf)
449 {
450         RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
451         const struct ip_identify_match *identify = obj;
452
453         ast_ha_join(identify->matches, &str);
454         *buf = ast_strdup(ast_str_buffer(str));
455         return 0;
456 }
457
458 static int match_to_var_list(const void *obj, struct ast_variable **fields)
459 {
460         char str[MAX_OBJECT_FIELD];
461         const struct ip_identify_match *identify = obj;
462         struct ast_variable *head = NULL;
463         struct ast_ha *ha = identify->matches;
464
465         for (; ha; ha = ha->next) {
466                 const char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
467                 snprintf(str, MAX_OBJECT_FIELD, "%s%s/%s", ha->sense == AST_SENSE_ALLOW ? "!" : "",
468                         addr, ast_sockaddr_stringify_addr(&ha->netmask));
469
470                 ast_variable_list_append(&head, ast_variable_new("match", str, ""));
471
472         }
473
474         if (head) {
475                 *fields = head;
476         }
477
478         return 0;
479 }
480
481 static int sip_identify_to_ami(const struct ip_identify_match *identify,
482                                struct ast_str **buf)
483 {
484         return ast_sip_sorcery_object_to_ami(identify, buf);
485 }
486
487 static int find_identify_by_endpoint(void *obj, void *arg, int flags)
488 {
489         struct ip_identify_match *identify = obj;
490         const char *endpoint_name = arg;
491
492         return strcmp(identify->endpoint_name, endpoint_name) ? 0 : CMP_MATCH;
493 }
494
495 static int format_ami_endpoint_identify(const struct ast_sip_endpoint *endpoint,
496                                         struct ast_sip_ami *ami)
497 {
498         RAII_VAR(struct ao2_container *, identifies, NULL, ao2_cleanup);
499         RAII_VAR(struct ip_identify_match *, identify, NULL, ao2_cleanup);
500         RAII_VAR(struct ast_str *, buf, NULL, ast_free);
501
502         identifies = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "identify",
503                 AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
504         if (!identifies) {
505                 return -1;
506         }
507
508         identify = ao2_callback(identifies, 0, find_identify_by_endpoint,
509                 (void *) ast_sorcery_object_get_id(endpoint));
510         if (!identify) {
511                 return 1;
512         }
513
514         if (!(buf = ast_sip_create_ami_event("IdentifyDetail", ami))) {
515                 return -1;
516         }
517
518         if (sip_identify_to_ami(identify, &buf)) {
519                 return -1;
520         }
521
522         ast_str_append(&buf, 0, "EndpointName: %s\r\n",
523                 ast_sorcery_object_get_id(endpoint));
524
525         astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
526         ami->count++;
527
528         return 0;
529 }
530
531 struct ast_sip_endpoint_formatter endpoint_identify_formatter = {
532         .format_ami = format_ami_endpoint_identify
533 };
534
535 static int cli_iterator(void *container, ao2_callback_fn callback, void *args)
536 {
537         const struct ast_sip_endpoint *endpoint = container;
538         struct ao2_container *identifies;
539
540         struct ast_variable fields = {
541                 .name = "endpoint",
542                 .value = ast_sorcery_object_get_id(endpoint),
543                 .next = NULL,
544         };
545
546         identifies = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "identify",
547                 AST_RETRIEVE_FLAG_MULTIPLE, &fields);
548         if (!identifies) {
549                 return -1;
550         }
551
552         ao2_callback(identifies, OBJ_NODATA, callback, args);
553         ao2_cleanup(identifies);
554
555         return 0;
556 }
557
558 static struct ao2_container *cli_get_container(const char *regex)
559 {
560         RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
561         struct ao2_container *s_container;
562
563         container =  ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "identify", regex);
564         if (!container) {
565                 return NULL;
566         }
567
568         s_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
569                 ast_sorcery_object_id_sort, ast_sorcery_object_id_compare);
570         if (!s_container) {
571                 return NULL;
572         }
573
574         if (ao2_container_dup(s_container, container, 0)) {
575                 ao2_ref(s_container, -1);
576                 return NULL;
577         }
578
579         return s_container;
580 }
581
582 static void *cli_retrieve_by_id(const char *id)
583 {
584         return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "identify", id);
585 }
586
587 static int cli_print_header(void *obj, void *arg, int flags)
588 {
589         struct ast_sip_cli_context *context = arg;
590         int indent = CLI_INDENT_TO_SPACES(context->indent_level);
591         int filler = CLI_MAX_WIDTH - indent - 22;
592
593         ast_assert(context->output_buffer != NULL);
594
595         ast_str_append(&context->output_buffer, 0,
596                 "%*s:  <Identify/Endpoint%*.*s>\n",
597                 indent, "Identify", filler, filler, CLI_HEADER_FILLER);
598
599         if (context->recurse) {
600                 context->indent_level++;
601                 indent = CLI_INDENT_TO_SPACES(context->indent_level);
602                 filler = CLI_LAST_TABSTOP - indent - 24;
603
604                 ast_str_append(&context->output_buffer, 0,
605                         "%*s:  <criteria%*.*s>\n",
606                         indent, "Match", filler, filler, CLI_HEADER_FILLER);
607
608                 context->indent_level--;
609         }
610
611         return 0;
612 }
613
614 static int cli_print_body(void *obj, void *arg, int flags)
615 {
616         RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
617         struct ip_identify_match *ident = obj;
618         struct ast_sip_cli_context *context = arg;
619         struct ast_ha *match;
620         int indent;
621
622         ast_assert(context->output_buffer != NULL);
623
624         ast_str_append(&context->output_buffer, 0, "%*s:  %s/%s\n",
625                 CLI_INDENT_TO_SPACES(context->indent_level), "Identify",
626                 ast_sorcery_object_get_id(ident), ident->endpoint_name);
627
628         if (context->recurse) {
629                 context->indent_level++;
630                 indent = CLI_INDENT_TO_SPACES(context->indent_level);
631
632                 for (match = ident->matches; match; match = match->next) {
633                         const char *addr = ast_sockaddr_stringify_addr(&match->addr);
634
635                         ast_str_append(&context->output_buffer, 0, "%*s: %s%s/%d\n",
636                                 indent,
637                                 "Match",
638                                 match->sense == AST_SENSE_ALLOW ? "!" : "",
639                                 addr, ast_sockaddr_cidr_bits(&match->netmask));
640                 }
641
642                 if (!ast_strlen_zero(ident->match_header)) {
643                         ast_str_append(&context->output_buffer, 0, "%*s: %s\n",
644                                 indent,
645                                 "Match",
646                                 ident->match_header);
647                 }
648
649                 context->indent_level--;
650
651                 if (context->indent_level == 0) {
652                         ast_str_append(&context->output_buffer, 0, "\n");
653                 }
654         }
655
656         if (context->show_details
657                 || (context->show_details_only_level_0 && context->indent_level == 0)) {
658                 ast_str_append(&context->output_buffer, 0, "\n");
659                 ast_sip_cli_print_sorcery_objectset(ident, context, 0);
660         }
661
662         return 0;
663 }
664
665 /*
666  * A function pointer to callback needs to be within the
667  * module in order to avoid problems with an undefined
668  * symbol when the module is loaded.
669  */
670 static char *my_cli_traverse_objects(struct ast_cli_entry *e, int cmd,
671         struct ast_cli_args *a)
672 {
673         return ast_sip_cli_traverse_objects(e, cmd, a);
674 }
675
676 static struct ast_cli_entry cli_identify[] = {
677 AST_CLI_DEFINE(my_cli_traverse_objects, "List PJSIP Identifies",
678         .command = "pjsip list identifies",
679         .usage = "Usage: pjsip list identifies [ like <pattern> ]\n"
680         "       List the configured PJSIP Identifies\n"
681         "       Optional regular expression pattern is used to filter the list.\n"),
682 AST_CLI_DEFINE(my_cli_traverse_objects, "Show PJSIP Identifies",
683         .command = "pjsip show identifies",
684         .usage = "Usage: pjsip show identifies [ like <pattern> ]\n"
685         "       Show the configured PJSIP Identifies\n"
686         "       Optional regular expression pattern is used to filter the list.\n"),
687 AST_CLI_DEFINE(my_cli_traverse_objects, "Show PJSIP Identify",
688         .command = "pjsip show identify",
689         .usage = "Usage: pjsip show identify <id>\n"
690         "       Show the configured PJSIP Identify\n"),
691 };
692
693 static struct ast_sip_cli_formatter_entry *cli_formatter;
694
695 static int load_module(void)
696 {
697         CHECK_PJSIP_MODULE_LOADED();
698
699         ast_sorcery_apply_config(ast_sip_get_sorcery(), "res_pjsip_endpoint_identifier_ip");
700         ast_sorcery_apply_default(ast_sip_get_sorcery(), "identify", "config", "pjsip.conf,criteria=type=identify");
701
702         if (ast_sorcery_object_register(ast_sip_get_sorcery(), "identify", ip_identify_alloc, NULL, ip_identify_apply)) {
703                 return AST_MODULE_LOAD_DECLINE;
704         }
705
706         ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "type", "", OPT_NOOP_T, 0, 0);
707         ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "endpoint", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, endpoint_name));
708         ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "identify", "match", "", ip_identify_match_handler, match_to_str, match_to_var_list, 0, 0);
709         ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "match_header", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, match_header));
710         ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "srv_lookups", "yes", OPT_BOOL_T, 1, FLDSET(struct ip_identify_match, srv_lookups));
711         ast_sorcery_load_object(ast_sip_get_sorcery(), "identify");
712
713         ast_sip_register_endpoint_identifier_with_name(&ip_identifier, "ip");
714         ast_sip_register_endpoint_formatter(&endpoint_identify_formatter);
715
716         cli_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
717         if (!cli_formatter) {
718                 ast_log(LOG_ERROR, "Unable to allocate memory for cli formatter\n");
719                 return AST_MODULE_LOAD_DECLINE;
720         }
721         cli_formatter->name = "identify";
722         cli_formatter->print_header = cli_print_header;
723         cli_formatter->print_body = cli_print_body;
724         cli_formatter->get_container = cli_get_container;
725         cli_formatter->iterate = cli_iterator;
726         cli_formatter->get_id = ast_sorcery_object_get_id;
727         cli_formatter->retrieve_by_id = cli_retrieve_by_id;
728
729         ast_sip_register_cli_formatter(cli_formatter);
730         ast_cli_register_multiple(cli_identify, ARRAY_LEN(cli_identify));
731
732         return AST_MODULE_LOAD_SUCCESS;
733 }
734
735 static int reload_module(void)
736 {
737         ast_sorcery_reload_object(ast_sip_get_sorcery(), "identify");
738
739         return 0;
740 }
741
742 static int unload_module(void)
743 {
744         ast_cli_unregister_multiple(cli_identify, ARRAY_LEN(cli_identify));
745         ast_sip_unregister_cli_formatter(cli_formatter);
746         ast_sip_unregister_endpoint_formatter(&endpoint_identify_formatter);
747         ast_sip_unregister_endpoint_identifier(&ip_identifier);
748
749         return 0;
750 }
751
752 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP IP endpoint identifier",
753         .support_level = AST_MODULE_SUPPORT_CORE,
754         .load = load_module,
755         .reload = reload_module,
756         .unload = unload_module,
757         .load_pri = AST_MODPRI_CHANNEL_DEPEND - 4,
758 );