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