AST-2018-005: res_pjsip_transport_management: Move to core
[asterisk/asterisk.git] / res / res_pjsip / pjsip_cli.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Fairview 5 Engineering, LLC
5  *
6  * George Joseph <george.joseph@fairview5.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 #include "asterisk.h"
20
21 #include <pjsip.h>
22 #include <pjsip_ua.h>
23
24 #include "asterisk/res_pjsip.h"
25 #include "include/res_pjsip_private.h"
26 #include "asterisk/res_pjsip_cli.h"
27 #include "asterisk/acl.h"
28 #include "asterisk/cli.h"
29 #include "asterisk/astobj2.h"
30 #include "asterisk/hashtab.h"
31 #include "asterisk/utils.h"
32 #include "asterisk/sorcery.h"
33
34 static struct ao2_container *formatter_registry;
35
36 int ast_sip_cli_print_sorcery_objectset(void *obj, void *arg, int flags)
37 {
38         struct ast_sip_cli_context *context = arg;
39         struct ast_variable *i;
40         int max_name_width = 13;
41         int max_value_width = 14;
42         int width;
43         char *separator;
44         struct ast_variable *objset;
45
46         if (!context->output_buffer) {
47                 return -1;
48         }
49
50         objset = ast_sorcery_objectset_create(ast_sip_get_sorcery(),obj);
51         if (!objset) {
52                 return -1;
53         }
54
55         for (i = objset; i; i = i->next) {
56                 if (i->name) {
57                         width = strlen(i->name);
58                         max_name_width = width > max_name_width ? width : max_name_width;
59                 }
60                 if (i->value) {
61                         width = strlen(i->value);
62                         max_value_width = width > max_value_width ? width : max_value_width;
63                 }
64         }
65
66         separator = ast_alloca(max_name_width + max_value_width + 8);
67
68         memset(separator, '=', max_name_width + max_value_width + 3);
69         separator[max_name_width + max_value_width + 3] = 0;
70
71         ast_str_append(&context->output_buffer, 0, " %-*s : %s\n", max_name_width, "ParameterName", "ParameterValue");
72         ast_str_append(&context->output_buffer, 0, " %s\n", separator);
73
74         objset = ast_variable_list_sort(objset);
75
76         for (i = objset; i; i = i->next) {
77                 ast_str_append(&context->output_buffer, 0, " %-*s : %s\n", max_name_width, i->name, i->value);
78         }
79
80         ast_variables_destroy(objset);
81
82         return 0;
83 }
84
85 static void complete_show_sorcery_object(struct ao2_container *container,
86         struct ast_sip_cli_formatter_entry *formatter_entry,
87         const char *word)
88 {
89         size_t wordlen = strlen(word);
90         void *object;
91         struct ao2_iterator i = ao2_iterator_init(container, 0);
92
93         while ((object = ao2_t_iterator_next(&i, "iterate thru endpoints table"))) {
94                 const char *id = formatter_entry->get_id(object);
95                 if (!strncasecmp(word, id, wordlen)) {
96                         ast_cli_completion_add(ast_strdup(id));
97                 }
98                 ao2_t_ref(object, -1, "toss iterator endpoint ptr before break");
99         }
100         ao2_iterator_destroy(&i);
101 }
102
103 static void dump_str_and_free(int fd, struct ast_str *buf)
104 {
105         ast_cli(fd, "%s", ast_str_buffer(buf));
106         ast_free(buf);
107 }
108
109 char *ast_sip_cli_traverse_objects(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
110 {
111         RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
112         RAII_VAR(struct ast_sip_cli_formatter_entry *, formatter_entry, NULL, ao2_cleanup);
113         RAII_VAR(void *, object, NULL, ao2_cleanup);
114         int is_container = 0;
115         const char *cmd1;
116         const char *cmd2;
117         const char *object_id;
118         char formatter_type[64];
119         const char *regex;
120
121         struct ast_sip_cli_context context = {
122                 .indent_level = 0,
123                 .show_details = 0,
124                 .show_details_only_level_0 = 0,
125                 .recurse = 0,
126         };
127
128         if (cmd == CLI_INIT) {
129                 return NULL;
130         }
131
132         cmd1 = e->cmda[1];
133         cmd2 = e->cmda[2];
134         object_id = a->argv[3];
135
136         if (!ast_ends_with(cmd2, "s")) {
137                 ast_copy_string(formatter_type, cmd2, sizeof(formatter_type));
138                 is_container = 0;
139         } else if (ast_ends_with(cmd2, "ies")) {
140                 /* Take the plural "ies" off of the object name and re[place with "y". */
141                 int l = strlen(cmd2);
142                 snprintf(formatter_type, 64, "%*.*sy", l - 3, l - 3, cmd2);
143                 is_container = 1;
144         } else {
145                 /* Take the plural "s" off of the object name. */
146                 ast_copy_string(formatter_type, cmd2, strlen(cmd2));
147                 is_container = 1;
148         }
149
150         if (!strcmp(cmd1, "show")) {
151                 context.show_details_only_level_0 = !is_container;
152                 context.recurse = 1;
153         } else {
154                 is_container = 1;
155         }
156
157         if (cmd != CLI_GENERATE
158                 && is_container
159                 && a->argc >= 4
160                 && strcmp(object_id, "like") == 0) {
161                 if (ast_strlen_zero(a->argv[4])) {
162                         return CLI_SHOWUSAGE;
163                 }
164                 regex = a->argv[4];
165         } else {
166                 regex = "";
167         }
168
169         if (cmd == CLI_GENERATE
170                 && (is_container
171                         || a->argc > 4
172                         || (a->argc == 4 && ast_strlen_zero(a->word)))) {
173                 return CLI_SUCCESS;
174         }
175
176         context.output_buffer = ast_str_create(256);
177         if (!context.output_buffer) {
178                 return CLI_FAILURE;
179         }
180
181         formatter_entry = ast_sip_lookup_cli_formatter(formatter_type);
182         if (!formatter_entry) {
183                 ast_log(LOG_ERROR, "No formatter registered for object type %s.\n",
184                         formatter_type);
185                 ast_free(context.output_buffer);
186                 return CLI_FAILURE;
187         }
188         ast_str_append(&context.output_buffer, 0, "\n");
189         formatter_entry->print_header(NULL, &context, 0);
190         ast_str_append(&context.output_buffer, 0,
191                 "==========================================================================================\n\n");
192
193         if (is_container || cmd == CLI_GENERATE) {
194                 container = formatter_entry->get_container(regex);
195                 if (!container) {
196                         ast_cli(a->fd, "No container returned for object type %s.\n",
197                                 formatter_type);
198                         ast_free(context.output_buffer);
199                         return CLI_FAILURE;
200                 }
201         }
202
203         if (cmd == CLI_GENERATE) {
204                 ast_free(context.output_buffer);
205                 complete_show_sorcery_object(container, formatter_entry, a->word);
206                 return NULL;
207         }
208
209         if (is_container) {
210                 if (!ao2_container_count(container)) {
211                         ast_free(context.output_buffer);
212                         ast_cli(a->fd, "No objects found.\n\n");
213                         return CLI_SUCCESS;
214                 }
215                 ao2_callback(container, OBJ_NODATA, formatter_entry->print_body, &context);
216                 ast_str_append(&context.output_buffer, 0, "\nObjects found: %d\n", ao2_container_count(container));
217
218         } else {
219                 if (ast_strlen_zero(object_id)) {
220                         ast_free(context.output_buffer);
221                         ast_cli(a->fd, "No object specified.\n");
222                         return CLI_FAILURE;
223                 }
224
225                 object = formatter_entry->retrieve_by_id(object_id);
226                 if (!object) {
227                         ast_free(context.output_buffer);
228                         ast_cli(a->fd, "Unable to find object %s.\n\n", object_id);
229                         return CLI_SUCCESS;
230                 }
231                 formatter_entry->print_body(object, &context, 0);
232         }
233
234         ast_str_append(&context.output_buffer, 0, "\n");
235         dump_str_and_free(a->fd, context.output_buffer);
236         return CLI_SUCCESS;
237 }
238
239 static int formatter_sort(const void *obj, const void *arg, int flags)
240 {
241         const struct ast_sip_cli_formatter_entry *left_obj = obj;
242         const struct ast_sip_cli_formatter_entry *right_obj = arg;
243         const char *right_key = arg;
244         int cmp = 0;
245
246         switch (flags & OBJ_SEARCH_MASK) {
247         case OBJ_SEARCH_OBJECT:
248                 right_key = right_obj->name;
249                 /* Fall through */
250         case OBJ_SEARCH_KEY:
251                 cmp = strcmp(left_obj->name, right_key);
252                 break;
253         case OBJ_SEARCH_PARTIAL_KEY:
254                 cmp = strncmp(left_obj->name, right_key, strlen(right_key));
255                 break;
256         default:
257                 cmp = 0;
258                 break;
259         }
260
261         return cmp;
262 }
263
264 static int formatter_compare(void *obj, void *arg, int flags)
265 {
266         const struct ast_sip_cli_formatter_entry *left_obj = obj;
267         const struct ast_sip_cli_formatter_entry *right_obj = arg;
268         const char *right_key = arg;
269         int cmp = 0;
270
271         switch (flags & OBJ_SEARCH_MASK) {
272         case OBJ_SEARCH_OBJECT:
273                 right_key = right_obj->name;
274                 /* Fall through */
275         case OBJ_SEARCH_KEY:
276                 if (strcmp(left_obj->name, right_key) == 0) {;
277                         cmp = CMP_MATCH | CMP_STOP;
278                 }
279                 break;
280         case OBJ_SEARCH_PARTIAL_KEY:
281                 if (strncmp(left_obj->name, right_key, strlen(right_key)) == 0) {
282                         cmp = CMP_MATCH;
283                 }
284                 break;
285         default:
286                 cmp = 0;
287                 break;
288         }
289
290         return cmp;
291 }
292
293 static int formatter_hash(const void *obj, int flags)
294 {
295         const struct ast_sip_cli_formatter_entry *left_obj = obj;
296         if (flags & OBJ_SEARCH_OBJECT) {
297                 return ast_str_hash(left_obj->name);
298         } else if (flags & OBJ_SEARCH_KEY) {
299                 return ast_str_hash(obj);
300         }
301
302         return -1;
303 }
304
305 struct ast_sip_cli_formatter_entry *ast_sip_lookup_cli_formatter(const char *name)
306 {
307         return ao2_find(formatter_registry, name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
308 }
309
310 int ast_sip_register_cli_formatter(struct ast_sip_cli_formatter_entry *formatter)
311 {
312         ast_assert(formatter != NULL);
313         ast_assert(formatter->name != NULL);
314         ast_assert(formatter->print_body != NULL);
315         ast_assert(formatter->print_header != NULL);
316         ast_assert(formatter->get_container != NULL);
317         ast_assert(formatter->iterate != NULL);
318         ast_assert(formatter->get_id != NULL);
319         ast_assert(formatter->retrieve_by_id != NULL);
320
321         ao2_link(formatter_registry, formatter);
322
323         return 0;
324 }
325
326 int ast_sip_unregister_cli_formatter(struct ast_sip_cli_formatter_entry *formatter)
327 {
328         if (formatter) {
329                 ao2_wrlock(formatter_registry);
330                 if (ao2_ref(formatter, -1) == 2) {
331                         ao2_unlink_flags(formatter_registry, formatter, OBJ_NOLOCK);
332                 }
333                 ao2_unlock(formatter_registry);
334         }
335         return 0;
336 }
337
338 static char *handle_pjsip_show_version(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
339 {
340         switch(cmd) {
341         case CLI_INIT:
342                 e->command = "pjsip show version";
343                 e->usage =
344                         "Usage: pjsip show version\n"
345                         "       Show the version of pjproject that res_pjsip is running against\n";
346                 return NULL;
347         case CLI_GENERATE:
348                 return NULL;
349         }
350
351         ast_cli(a->fd, "PJPROJECT version currently running against: %s\n", pj_get_version());
352
353         return CLI_SUCCESS;
354 }
355
356 static struct ast_cli_entry pjsip_cli[] = {
357         AST_CLI_DEFINE(handle_pjsip_show_version, "Show the version of pjproject in use"),
358 };
359
360 int ast_sip_initialize_cli(void)
361 {
362         formatter_registry = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, 17,
363                 formatter_hash, formatter_sort, formatter_compare);
364
365         if (!formatter_registry) {
366                 ast_log(LOG_ERROR, "Unable to create formatter_registry.\n");
367                 return -1;
368         }
369
370         ast_cli_register_multiple(pjsip_cli, ARRAY_LEN(pjsip_cli));
371
372         return 0;
373 }
374
375 void ast_sip_destroy_cli(void)
376 {
377         ast_cli_unregister_multiple(pjsip_cli, ARRAY_LEN(pjsip_cli));
378         ao2_ref(formatter_registry, -1);
379 }