res_pjsip.c: Update the endpoint identification documentation.
[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
242         if (endpoint) {
243                 if (!(endpoint->ident_method & AST_SIP_ENDPOINT_IDENTIFY_BY_IP)) {
244                         ast_debug(3, "Endpoint '%s' found for '%s' but 'ip' method not supported'\n", match->endpoint_name,
245                                 ast_sockaddr_stringify(&addr));
246                         ao2_cleanup(endpoint);
247                         return NULL;
248                 }
249                 ast_debug(3, "Retrieved endpoint %s\n", ast_sorcery_object_get_id(endpoint));
250         } else {
251                 ast_log(LOG_WARNING, "Identify section '%s' points to endpoint '%s' but endpoint could not be looked up\n",
252                                 ast_sorcery_object_get_id(match), match->endpoint_name);
253         }
254
255         return endpoint;
256 }
257
258 static struct ast_sip_endpoint_identifier ip_identifier = {
259         .identify_endpoint = ip_identify,
260 };
261
262 /*! \brief Helper function which performs a host lookup and adds result to identify match */
263 static int ip_identify_match_host_lookup(struct ip_identify_match *identify, const char *host)
264 {
265         struct ast_sockaddr *addrs;
266         int num_addrs = 0, error = 0, i;
267         int results = 0;
268
269         num_addrs = ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC);
270         if (!num_addrs) {
271                 return -1;
272         }
273
274         for (i = 0; i < num_addrs; ++i) {
275                 /* Check if the address is already in the list, if so don't add it again */
276                 if (identify->matches && (ast_apply_ha(identify->matches, &addrs[i]) != AST_SENSE_ALLOW)) {
277                         continue;
278                 }
279
280                 /* We deny what we actually want to match because there is an implicit permit all rule for ACLs */
281                 identify->matches = ast_append_ha("d", ast_sockaddr_stringify_addr(&addrs[i]), identify->matches, &error);
282
283                 if (!identify->matches || error) {
284                         results = -1;
285                         break;
286                 }
287
288                 results += 1;
289         }
290
291         ast_free(addrs);
292
293         return results;
294 }
295
296 /*! \brief Helper function which performs an SRV lookup and then resolves the hostname */
297 static int ip_identify_match_srv_lookup(struct ip_identify_match *identify, const char *prefix, const char *host, int results)
298 {
299         char service[NI_MAXHOST];
300         struct srv_context *context = NULL;
301         int srv_ret;
302         const char *srvhost;
303         unsigned short srvport;
304
305         snprintf(service, sizeof(service), "%s.%s", prefix, host);
306
307         while (!(srv_ret = ast_srv_lookup(&context, service, &srvhost, &srvport))) {
308                 int hosts;
309
310                 /* In the case of the SRV lookup we don't care if it fails, we will output a log message
311                  * when we fallback to a normal lookup.
312                  */
313                 hosts = ip_identify_match_host_lookup(identify, srvhost);
314                 if (hosts == -1) {
315                         results = -1;
316                         break;
317                 } else {
318                         results += hosts;
319                 }
320         }
321
322         ast_srv_cleanup(&context);
323
324         return results;
325 }
326
327 /*! \brief Custom handler for match field */
328 static int ip_identify_match_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
329 {
330         struct ip_identify_match *identify = obj;
331         char *input_string = ast_strdupa(var->value);
332         char *current_string;
333
334         if (ast_strlen_zero(var->value)) {
335                 return 0;
336         }
337
338         while ((current_string = ast_strip(strsep(&input_string, ",")))) {
339                 char *mask = strrchr(current_string, '/');
340                 int error = 0;
341
342                 if (ast_strlen_zero(current_string)) {
343                         continue;
344                 }
345
346                 if (mask) {
347                         identify->matches = ast_append_ha("d", current_string, identify->matches, &error);
348
349                         if (!identify->matches || error) {
350                                 ast_log(LOG_ERROR, "Failed to add address '%s' to ip endpoint identifier '%s'\n",
351                                         current_string, ast_sorcery_object_get_id(obj));
352                                 return -1;
353                         }
354
355                         continue;
356                 }
357
358                 if (!identify->hosts) {
359                         identify->hosts = ast_str_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, HOSTS_BUCKETS);
360                         if (!identify->hosts) {
361                                 ast_log(LOG_ERROR, "Failed to create container to store hosts on ip endpoint identifier '%s'\n",
362                                         ast_sorcery_object_get_id(obj));
363                                 return -1;
364                         }
365                 }
366
367                 error = ast_str_container_add(identify->hosts, current_string);
368                 if (error) {
369                         ast_log(LOG_ERROR, "Failed to store host '%s' for resolution on ip endpoint identifier '%s'\n",
370                                 current_string, ast_sorcery_object_get_id(obj));
371                         return -1;
372                 }
373         }
374
375         return 0;
376 }
377
378 /*! \brief Apply handler for identify type */
379 static int ip_identify_apply(const struct ast_sorcery *sorcery, void *obj)
380 {
381         struct ip_identify_match *identify = obj;
382         char *current_string;
383         struct ao2_iterator i;
384
385         /* Validate the identify object configuration */
386         if (ast_strlen_zero(identify->endpoint_name)) {
387                 ast_log(LOG_ERROR, "Identify '%s' missing required endpoint name.\n",
388                         ast_sorcery_object_get_id(identify));
389                 return -1;
390         }
391         if (ast_strlen_zero(identify->match_header) /* No header to match */
392                 /* and no static IP addresses with a mask */
393                 && !identify->matches
394                 /* and no addresses to resolve */
395                 && (!identify->hosts || !ao2_container_count(identify->hosts))) {
396                 ast_log(LOG_ERROR, "Identify '%s' is not configured to match anything.\n",
397                         ast_sorcery_object_get_id(identify));
398                 return -1;
399         }
400         if (!ast_strlen_zero(identify->match_header)
401                 && !strchr(identify->match_header, ':')) {
402                 ast_log(LOG_ERROR, "Identify '%s' missing ':' separator in match_header '%s'.\n",
403                         ast_sorcery_object_get_id(identify), identify->match_header);
404                 return -1;
405         }
406
407         if (!identify->hosts) {
408                 return 0;
409         }
410
411         /* Resolve the match addresses now */
412         i = ao2_iterator_init(identify->hosts, 0);
413         while ((current_string = ao2_iterator_next(&i))) {
414                 struct ast_sockaddr address;
415                 int results = 0;
416
417                 /* If the provided string is not an IP address perform SRV resolution on it */
418                 if (identify->srv_lookups && !ast_sockaddr_parse(&address, current_string, 0)) {
419                         results = ip_identify_match_srv_lookup(identify, "_sip._udp", current_string,
420                                 results);
421                         if (results != -1) {
422                                 results = ip_identify_match_srv_lookup(identify, "_sip._tcp",
423                                         current_string, results);
424                         }
425                         if (results != -1) {
426                                 results = ip_identify_match_srv_lookup(identify, "_sips._tcp",
427                                         current_string, results);
428                         }
429                 }
430
431                 /* If SRV fails fall back to a normal lookup on the host itself */
432                 if (!results) {
433                         results = ip_identify_match_host_lookup(identify, current_string);
434                 }
435
436                 if (results == 0) {
437                         ast_log(LOG_WARNING, "Identify '%s' provided address '%s' did not resolve to any address\n",
438                                 ast_sorcery_object_get_id(identify), current_string);
439                 } else if (results == -1) {
440                         ast_log(LOG_ERROR, "Identify '%s' failed when adding resolution results of '%s'\n",
441                                 ast_sorcery_object_get_id(identify), current_string);
442                         ao2_ref(current_string, -1);
443                         ao2_iterator_destroy(&i);
444                         return -1;
445                 }
446
447                 ao2_ref(current_string, -1);
448         }
449         ao2_iterator_destroy(&i);
450
451         ao2_ref(identify->hosts, -1);
452         identify->hosts = NULL;
453
454         return 0;
455 }
456
457 static int match_to_str(const void *obj, const intptr_t *args, char **buf)
458 {
459         RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
460         const struct ip_identify_match *identify = obj;
461
462         ast_ha_join(identify->matches, &str);
463         *buf = ast_strdup(ast_str_buffer(str));
464         return 0;
465 }
466
467 static int match_to_var_list(const void *obj, struct ast_variable **fields)
468 {
469         char str[MAX_OBJECT_FIELD];
470         const struct ip_identify_match *identify = obj;
471         struct ast_variable *head = NULL;
472         struct ast_ha *ha = identify->matches;
473
474         for (; ha; ha = ha->next) {
475                 const char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
476                 snprintf(str, MAX_OBJECT_FIELD, "%s%s/%s", ha->sense == AST_SENSE_ALLOW ? "!" : "",
477                         addr, ast_sockaddr_stringify_addr(&ha->netmask));
478
479                 ast_variable_list_append(&head, ast_variable_new("match", str, ""));
480
481         }
482
483         if (head) {
484                 *fields = head;
485         }
486
487         return 0;
488 }
489
490 static int sip_identify_to_ami(const struct ip_identify_match *identify,
491                                struct ast_str **buf)
492 {
493         return ast_sip_sorcery_object_to_ami(identify, buf);
494 }
495
496 static int find_identify_by_endpoint(void *obj, void *arg, int flags)
497 {
498         struct ip_identify_match *identify = obj;
499         const char *endpoint_name = arg;
500
501         return strcmp(identify->endpoint_name, endpoint_name) ? 0 : CMP_MATCH;
502 }
503
504 static int format_ami_endpoint_identify(const struct ast_sip_endpoint *endpoint,
505                                         struct ast_sip_ami *ami)
506 {
507         RAII_VAR(struct ao2_container *, identifies, NULL, ao2_cleanup);
508         RAII_VAR(struct ip_identify_match *, identify, NULL, ao2_cleanup);
509         RAII_VAR(struct ast_str *, buf, NULL, ast_free);
510
511         identifies = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "identify",
512                 AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
513         if (!identifies) {
514                 return -1;
515         }
516
517         identify = ao2_callback(identifies, 0, find_identify_by_endpoint,
518                 (void *) ast_sorcery_object_get_id(endpoint));
519         if (!identify) {
520                 return 1;
521         }
522
523         if (!(buf = ast_sip_create_ami_event("IdentifyDetail", ami))) {
524                 return -1;
525         }
526
527         if (sip_identify_to_ami(identify, &buf)) {
528                 return -1;
529         }
530
531         ast_str_append(&buf, 0, "EndpointName: %s\r\n",
532                 ast_sorcery_object_get_id(endpoint));
533
534         astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
535         ami->count++;
536
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 );