res_pjsip_session: properly handle SDP from a forked call with early media
[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;
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;
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 *common_identify(ao2_callback_fn *identify_match_cb, void *arg)
214 {
215         RAII_VAR(struct ao2_container *, candidates, NULL, ao2_cleanup);
216         struct ip_identify_match *match;
217         struct ast_sip_endpoint *endpoint;
218
219         /* If no possibilities exist return early to save some time */
220         candidates = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "identify",
221                 AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
222         if (!candidates || !ao2_container_count(candidates)) {
223                 ast_debug(3, "No identify sections to match against\n");
224                 return NULL;
225         }
226
227         match = ao2_callback(candidates, 0, identify_match_cb, arg);
228         if (!match) {
229                 return NULL;
230         }
231
232         endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint",
233                 match->endpoint_name);
234         if (endpoint) {
235                 ast_debug(3, "Identify '%s' SIP message matched to endpoint %s\n",
236                         ast_sorcery_object_get_id(match), match->endpoint_name);
237         } else {
238                 ast_log(LOG_WARNING, "Identify '%s' points to endpoint '%s' but endpoint could not be found\n",
239                         ast_sorcery_object_get_id(match), match->endpoint_name);
240         }
241
242         ao2_ref(match, -1);
243         return endpoint;
244 }
245
246 static struct ast_sip_endpoint *ip_identify(pjsip_rx_data *rdata)
247 {
248         struct ast_sockaddr addr = { { 0, } };
249
250         ast_sockaddr_parse(&addr, rdata->pkt_info.src_name, PARSE_PORT_FORBID);
251         ast_sockaddr_set_port(&addr, rdata->pkt_info.src_port);
252
253         return common_identify(ip_identify_match_check, &addr);
254 }
255
256 static struct ast_sip_endpoint_identifier ip_identifier = {
257         .identify_endpoint = ip_identify,
258 };
259
260 static struct ast_sip_endpoint *header_identify(pjsip_rx_data *rdata)
261 {
262         return common_identify(header_identify_match_check, rdata);
263 }
264
265 static struct ast_sip_endpoint_identifier header_identifier = {
266         .identify_endpoint = header_identify,
267 };
268
269 /*! \brief Helper function which performs a host lookup and adds result to identify match */
270 static int ip_identify_match_host_lookup(struct ip_identify_match *identify, const char *host)
271 {
272         struct ast_sockaddr *addrs;
273         int num_addrs = 0, error = 0, i;
274         int results = 0;
275
276         num_addrs = ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC);
277         if (!num_addrs) {
278                 return -1;
279         }
280
281         for (i = 0; i < num_addrs; ++i) {
282                 /* Check if the address is already in the list, if so don't add it again */
283                 if (identify->matches && (ast_apply_ha(identify->matches, &addrs[i]) != AST_SENSE_ALLOW)) {
284                         continue;
285                 }
286
287                 /* We deny what we actually want to match because there is an implicit permit all rule for ACLs */
288                 identify->matches = ast_append_ha("d", ast_sockaddr_stringify_addr(&addrs[i]), identify->matches, &error);
289
290                 if (!identify->matches || error) {
291                         results = -1;
292                         break;
293                 }
294
295                 results += 1;
296         }
297
298         ast_free(addrs);
299
300         return results;
301 }
302
303 /*! \brief Helper function which performs an SRV lookup and then resolves the hostname */
304 static int ip_identify_match_srv_lookup(struct ip_identify_match *identify, const char *prefix, const char *host, int results)
305 {
306         char service[NI_MAXHOST];
307         struct srv_context *context = NULL;
308         int srv_ret;
309         const char *srvhost;
310         unsigned short srvport;
311
312         snprintf(service, sizeof(service), "%s.%s", prefix, host);
313
314         while (!(srv_ret = ast_srv_lookup(&context, service, &srvhost, &srvport))) {
315                 int hosts;
316
317                 /* In the case of the SRV lookup we don't care if it fails, we will output a log message
318                  * when we fallback to a normal lookup.
319                  */
320                 hosts = ip_identify_match_host_lookup(identify, srvhost);
321                 if (hosts == -1) {
322                         results = -1;
323                         break;
324                 } else {
325                         results += hosts;
326                 }
327         }
328
329         ast_srv_cleanup(&context);
330
331         return results;
332 }
333
334 /*! \brief Custom handler for match field */
335 static int ip_identify_match_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
336 {
337         struct ip_identify_match *identify = obj;
338         char *input_string = ast_strdupa(var->value);
339         char *current_string;
340
341         if (ast_strlen_zero(var->value)) {
342                 return 0;
343         }
344
345         while ((current_string = ast_strip(strsep(&input_string, ",")))) {
346                 char *mask = strrchr(current_string, '/');
347                 int error = 0;
348
349                 if (ast_strlen_zero(current_string)) {
350                         continue;
351                 }
352
353                 if (mask) {
354                         identify->matches = ast_append_ha("d", current_string, identify->matches, &error);
355
356                         if (!identify->matches || error) {
357                                 ast_log(LOG_ERROR, "Failed to add address '%s' to ip endpoint identifier '%s'\n",
358                                         current_string, ast_sorcery_object_get_id(obj));
359                                 return -1;
360                         }
361
362                         continue;
363                 }
364
365                 if (!identify->hosts) {
366                         identify->hosts = ast_str_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, HOSTS_BUCKETS);
367                         if (!identify->hosts) {
368                                 ast_log(LOG_ERROR, "Failed to create container to store hosts on ip endpoint identifier '%s'\n",
369                                         ast_sorcery_object_get_id(obj));
370                                 return -1;
371                         }
372                 }
373
374                 error = ast_str_container_add(identify->hosts, current_string);
375                 if (error) {
376                         ast_log(LOG_ERROR, "Failed to store host '%s' for resolution on ip endpoint identifier '%s'\n",
377                                 current_string, ast_sorcery_object_get_id(obj));
378                         return -1;
379                 }
380         }
381
382         return 0;
383 }
384
385 /*! \brief Apply handler for identify type */
386 static int ip_identify_apply(const struct ast_sorcery *sorcery, void *obj)
387 {
388         struct ip_identify_match *identify = obj;
389         char *current_string;
390         struct ao2_iterator i;
391
392         /* Validate the identify object configuration */
393         if (ast_strlen_zero(identify->endpoint_name)) {
394                 ast_log(LOG_ERROR, "Identify '%s' missing required endpoint name.\n",
395                         ast_sorcery_object_get_id(identify));
396                 return -1;
397         }
398         if (ast_strlen_zero(identify->match_header) /* No header to match */
399                 /* and no static IP addresses with a mask */
400                 && !identify->matches
401                 /* and no addresses to resolve */
402                 && (!identify->hosts || !ao2_container_count(identify->hosts))) {
403                 ast_log(LOG_ERROR, "Identify '%s' is not configured to match anything.\n",
404                         ast_sorcery_object_get_id(identify));
405                 return -1;
406         }
407         if (!ast_strlen_zero(identify->match_header)
408                 && !strchr(identify->match_header, ':')) {
409                 ast_log(LOG_ERROR, "Identify '%s' missing ':' separator in match_header '%s'.\n",
410                         ast_sorcery_object_get_id(identify), identify->match_header);
411                 return -1;
412         }
413
414         if (!identify->hosts) {
415                 return 0;
416         }
417
418         /* Resolve the match addresses now */
419         i = ao2_iterator_init(identify->hosts, 0);
420         while ((current_string = ao2_iterator_next(&i))) {
421                 struct ast_sockaddr address;
422                 int results = 0;
423
424                 /* If the provided string is not an IP address perform SRV resolution on it */
425                 if (identify->srv_lookups && !ast_sockaddr_parse(&address, current_string, 0)) {
426                         results = ip_identify_match_srv_lookup(identify, "_sip._udp", current_string,
427                                 results);
428                         if (results != -1) {
429                                 results = ip_identify_match_srv_lookup(identify, "_sip._tcp",
430                                         current_string, results);
431                         }
432                         if (results != -1) {
433                                 results = ip_identify_match_srv_lookup(identify, "_sips._tcp",
434                                         current_string, results);
435                         }
436                 }
437
438                 /* If SRV fails fall back to a normal lookup on the host itself */
439                 if (!results) {
440                         results = ip_identify_match_host_lookup(identify, current_string);
441                 }
442
443                 if (results == 0) {
444                         ast_log(LOG_WARNING, "Identify '%s' provided address '%s' did not resolve to any address\n",
445                                 ast_sorcery_object_get_id(identify), current_string);
446                 } else if (results == -1) {
447                         ast_log(LOG_ERROR, "Identify '%s' failed when adding resolution results of '%s'\n",
448                                 ast_sorcery_object_get_id(identify), current_string);
449                         ao2_ref(current_string, -1);
450                         ao2_iterator_destroy(&i);
451                         return -1;
452                 }
453
454                 ao2_ref(current_string, -1);
455         }
456         ao2_iterator_destroy(&i);
457
458         ao2_ref(identify->hosts, -1);
459         identify->hosts = NULL;
460
461         return 0;
462 }
463
464 static int match_to_str(const void *obj, const intptr_t *args, char **buf)
465 {
466         RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
467         const struct ip_identify_match *identify = obj;
468
469         ast_ha_join(identify->matches, &str);
470         *buf = ast_strdup(ast_str_buffer(str));
471         return 0;
472 }
473
474 static int match_to_var_list(const void *obj, struct ast_variable **fields)
475 {
476         char str[MAX_OBJECT_FIELD];
477         const struct ip_identify_match *identify = obj;
478         struct ast_variable *head = NULL;
479         struct ast_ha *ha = identify->matches;
480
481         for (; ha; ha = ha->next) {
482                 const char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
483                 snprintf(str, MAX_OBJECT_FIELD, "%s%s/%s", ha->sense == AST_SENSE_ALLOW ? "!" : "",
484                         addr, ast_sockaddr_stringify_addr(&ha->netmask));
485
486                 ast_variable_list_append(&head, ast_variable_new("match", str, ""));
487
488         }
489
490         if (head) {
491                 *fields = head;
492         }
493
494         return 0;
495 }
496
497 static int sip_identify_to_ami(const struct ip_identify_match *identify,
498                                struct ast_str **buf)
499 {
500         return ast_sip_sorcery_object_to_ami(identify, buf);
501 }
502
503 static int send_identify_ami_event(void *obj, void *arg, void *data, int flags)
504 {
505         struct ip_identify_match *identify = obj;
506         const char *endpoint_name = arg;
507         struct ast_sip_ami *ami = data;
508         struct ast_str *buf;
509
510         /* Build AMI event */
511         buf = ast_sip_create_ami_event("IdentifyDetail", ami);
512         if (!buf) {
513                 return CMP_STOP;
514         }
515         if (sip_identify_to_ami(identify, &buf)) {
516                 ast_free(buf);
517                 return CMP_STOP;
518         }
519         ast_str_append(&buf, 0, "EndpointName: %s\r\n", endpoint_name);
520
521         /* Send AMI event */
522         astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
523         ++ami->count;
524
525         ast_free(buf);
526         return 0;
527 }
528
529 static int format_ami_endpoint_identify(const struct ast_sip_endpoint *endpoint,
530                                         struct ast_sip_ami *ami)
531 {
532         struct ao2_container *identifies;
533         struct ast_variable fields = {
534                 .name = "endpoint",
535                 .value = ast_sorcery_object_get_id(endpoint),
536         };
537
538         identifies = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "identify",
539                 AST_RETRIEVE_FLAG_MULTIPLE, &fields);
540         if (!identifies) {
541                 return -1;
542         }
543
544         /* Build and send any found identify object's AMI IdentifyDetail event. */
545         ao2_callback_data(identifies, OBJ_MULTIPLE | OBJ_NODATA,
546                 send_identify_ami_event,
547                 (void *) ast_sorcery_object_get_id(endpoint),
548                 ami);
549
550         ao2_ref(identifies, -1);
551         return 0;
552 }
553
554 struct ast_sip_endpoint_formatter endpoint_identify_formatter = {
555         .format_ami = format_ami_endpoint_identify
556 };
557
558 static int cli_iterator(void *container, ao2_callback_fn callback, void *args)
559 {
560         const struct ast_sip_endpoint *endpoint = container;
561         struct ao2_container *identifies;
562
563         struct ast_variable fields = {
564                 .name = "endpoint",
565                 .value = ast_sorcery_object_get_id(endpoint),
566                 .next = NULL,
567         };
568
569         identifies = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "identify",
570                 AST_RETRIEVE_FLAG_MULTIPLE, &fields);
571         if (!identifies) {
572                 return -1;
573         }
574
575         ao2_callback(identifies, OBJ_NODATA, callback, args);
576         ao2_cleanup(identifies);
577
578         return 0;
579 }
580
581 static struct ao2_container *cli_get_container(const char *regex)
582 {
583         RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
584         struct ao2_container *s_container;
585
586         container =  ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "identify", regex);
587         if (!container) {
588                 return NULL;
589         }
590
591         s_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
592                 ast_sorcery_object_id_sort, ast_sorcery_object_id_compare);
593         if (!s_container) {
594                 return NULL;
595         }
596
597         if (ao2_container_dup(s_container, container, 0)) {
598                 ao2_ref(s_container, -1);
599                 return NULL;
600         }
601
602         return s_container;
603 }
604
605 static void *cli_retrieve_by_id(const char *id)
606 {
607         return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "identify", id);
608 }
609
610 static int cli_print_header(void *obj, void *arg, int flags)
611 {
612         struct ast_sip_cli_context *context = arg;
613         int indent = CLI_INDENT_TO_SPACES(context->indent_level);
614         int filler = CLI_MAX_WIDTH - indent - 22;
615
616         ast_assert(context->output_buffer != NULL);
617
618         ast_str_append(&context->output_buffer, 0,
619                 "%*s:  <Identify/Endpoint%*.*s>\n",
620                 indent, "Identify", filler, filler, CLI_HEADER_FILLER);
621
622         if (context->recurse) {
623                 context->indent_level++;
624                 indent = CLI_INDENT_TO_SPACES(context->indent_level);
625                 filler = CLI_LAST_TABSTOP - indent - 24;
626
627                 ast_str_append(&context->output_buffer, 0,
628                         "%*s:  <criteria%*.*s>\n",
629                         indent, "Match", filler, filler, CLI_HEADER_FILLER);
630
631                 context->indent_level--;
632         }
633
634         return 0;
635 }
636
637 static int cli_print_body(void *obj, void *arg, int flags)
638 {
639         RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
640         struct ip_identify_match *ident = obj;
641         struct ast_sip_cli_context *context = arg;
642         struct ast_ha *match;
643         int indent;
644
645         ast_assert(context->output_buffer != NULL);
646
647         ast_str_append(&context->output_buffer, 0, "%*s:  %s/%s\n",
648                 CLI_INDENT_TO_SPACES(context->indent_level), "Identify",
649                 ast_sorcery_object_get_id(ident), ident->endpoint_name);
650
651         if (context->recurse) {
652                 context->indent_level++;
653                 indent = CLI_INDENT_TO_SPACES(context->indent_level);
654
655                 for (match = ident->matches; match; match = match->next) {
656                         const char *addr = ast_sockaddr_stringify_addr(&match->addr);
657
658                         ast_str_append(&context->output_buffer, 0, "%*s: %s%s/%d\n",
659                                 indent,
660                                 "Match",
661                                 match->sense == AST_SENSE_ALLOW ? "!" : "",
662                                 addr, ast_sockaddr_cidr_bits(&match->netmask));
663                 }
664
665                 if (!ast_strlen_zero(ident->match_header)) {
666                         ast_str_append(&context->output_buffer, 0, "%*s: %s\n",
667                                 indent,
668                                 "Match",
669                                 ident->match_header);
670                 }
671
672                 context->indent_level--;
673
674                 if (context->indent_level == 0) {
675                         ast_str_append(&context->output_buffer, 0, "\n");
676                 }
677         }
678
679         if (context->show_details
680                 || (context->show_details_only_level_0 && context->indent_level == 0)) {
681                 ast_str_append(&context->output_buffer, 0, "\n");
682                 ast_sip_cli_print_sorcery_objectset(ident, context, 0);
683         }
684
685         return 0;
686 }
687
688 /*
689  * A function pointer to callback needs to be within the
690  * module in order to avoid problems with an undefined
691  * symbol when the module is loaded.
692  */
693 static char *my_cli_traverse_objects(struct ast_cli_entry *e, int cmd,
694         struct ast_cli_args *a)
695 {
696         return ast_sip_cli_traverse_objects(e, cmd, a);
697 }
698
699 static struct ast_cli_entry cli_identify[] = {
700 AST_CLI_DEFINE(my_cli_traverse_objects, "List PJSIP Identifies",
701         .command = "pjsip list identifies",
702         .usage = "Usage: pjsip list identifies [ like <pattern> ]\n"
703         "       List the configured PJSIP Identifies\n"
704         "       Optional regular expression pattern is used to filter the list.\n"),
705 AST_CLI_DEFINE(my_cli_traverse_objects, "Show PJSIP Identifies",
706         .command = "pjsip show identifies",
707         .usage = "Usage: pjsip show identifies [ like <pattern> ]\n"
708         "       Show the configured PJSIP Identifies\n"
709         "       Optional regular expression pattern is used to filter the list.\n"),
710 AST_CLI_DEFINE(my_cli_traverse_objects, "Show PJSIP Identify",
711         .command = "pjsip show identify",
712         .usage = "Usage: pjsip show identify <id>\n"
713         "       Show the configured PJSIP Identify\n"),
714 };
715
716 static struct ast_sip_cli_formatter_entry *cli_formatter;
717
718 static int load_module(void)
719 {
720         ast_sorcery_apply_config(ast_sip_get_sorcery(), "res_pjsip_endpoint_identifier_ip");
721         ast_sorcery_apply_default(ast_sip_get_sorcery(), "identify", "config", "pjsip.conf,criteria=type=identify");
722
723         if (ast_sorcery_object_register(ast_sip_get_sorcery(), "identify", ip_identify_alloc, NULL, ip_identify_apply)) {
724                 return AST_MODULE_LOAD_DECLINE;
725         }
726
727         ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "type", "", OPT_NOOP_T, 0, 0);
728         ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "endpoint", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, endpoint_name));
729         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);
730         ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "match_header", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, match_header));
731         ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "srv_lookups", "yes", OPT_BOOL_T, 1, FLDSET(struct ip_identify_match, srv_lookups));
732         ast_sorcery_load_object(ast_sip_get_sorcery(), "identify");
733
734         ast_sip_register_endpoint_identifier_with_name(&ip_identifier, "ip");
735         ast_sip_register_endpoint_identifier_with_name(&header_identifier, "header");
736         ast_sip_register_endpoint_formatter(&endpoint_identify_formatter);
737
738         cli_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
739         if (!cli_formatter) {
740                 ast_log(LOG_ERROR, "Unable to allocate memory for cli formatter\n");
741                 return AST_MODULE_LOAD_DECLINE;
742         }
743         cli_formatter->name = "identify";
744         cli_formatter->print_header = cli_print_header;
745         cli_formatter->print_body = cli_print_body;
746         cli_formatter->get_container = cli_get_container;
747         cli_formatter->iterate = cli_iterator;
748         cli_formatter->get_id = ast_sorcery_object_get_id;
749         cli_formatter->retrieve_by_id = cli_retrieve_by_id;
750
751         ast_sip_register_cli_formatter(cli_formatter);
752         ast_cli_register_multiple(cli_identify, ARRAY_LEN(cli_identify));
753
754         return AST_MODULE_LOAD_SUCCESS;
755 }
756
757 static int reload_module(void)
758 {
759         ast_sorcery_reload_object(ast_sip_get_sorcery(), "identify");
760
761         return 0;
762 }
763
764 static int unload_module(void)
765 {
766         ast_cli_unregister_multiple(cli_identify, ARRAY_LEN(cli_identify));
767         ast_sip_unregister_cli_formatter(cli_formatter);
768         ast_sip_unregister_endpoint_formatter(&endpoint_identify_formatter);
769         ast_sip_unregister_endpoint_identifier(&ip_identifier);
770
771         return 0;
772 }
773
774 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP IP endpoint identifier",
775         .support_level = AST_MODULE_SUPPORT_CORE,
776         .load = load_module,
777         .reload = reload_module,
778         .unload = unload_module,
779         .load_pri = AST_MODPRI_CHANNEL_DEPEND - 4,
780         .requires = "res_pjsip",
781 );