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