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