CI: Various updates to buildAsterisk.sh
[asterisk/asterisk.git] / main / pbx_functions.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2015, CFWare, LLC
5  *
6  * Corey Farrell <git@cfware.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 /*! \file
20  *
21  * \brief Custom function management routines.
22  *
23  * \author Corey Farrell <git@cfware.com>
24  */
25
26 /*** MODULEINFO
27         <support_level>core</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 #include "asterisk/_private.h"
33 #include "asterisk/cli.h"
34 #include "asterisk/linkedlists.h"
35 #include "asterisk/module.h"
36 #include "asterisk/pbx.h"
37 #include "asterisk/term.h"
38 #include "asterisk/threadstorage.h"
39 #include "asterisk/xmldoc.h"
40 #include "pbx_private.h"
41
42 /*!
43  * \brief A thread local indicating whether the current thread can run
44  * 'dangerous' dialplan functions.
45  */
46 AST_THREADSTORAGE(thread_inhibit_escalations_tl);
47
48 /*!
49  * \brief Set to true (non-zero) to globally allow all dangerous dialplan
50  * functions to run.
51  */
52 static int live_dangerously;
53
54 /*!
55  * \brief Registered functions container.
56  *
57  * It is sorted by function name.
58  */
59 static AST_RWLIST_HEAD_STATIC(acf_root, ast_custom_function);
60
61 static char *handle_show_functions(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
62 {
63         struct ast_custom_function *acf;
64         int count_acf = 0;
65         int like = 0;
66
67         switch (cmd) {
68         case CLI_INIT:
69                 e->command = "core show functions [like]";
70                 e->usage =
71                         "Usage: core show functions [like <text>]\n"
72                         "       List builtin functions, optionally only those matching a given string\n";
73                 return NULL;
74         case CLI_GENERATE:
75                 return NULL;
76         }
77
78         if (a->argc == 5 && (!strcmp(a->argv[3], "like")) ) {
79                 like = 1;
80         } else if (a->argc != 3) {
81                 return CLI_SHOWUSAGE;
82         }
83
84         ast_cli(a->fd, "%s Custom Functions:\n"
85                 "--------------------------------------------------------------------------------\n",
86                 like ? "Matching" : "Installed");
87
88         AST_RWLIST_RDLOCK(&acf_root);
89         AST_RWLIST_TRAVERSE(&acf_root, acf, acflist) {
90                 if (!like || strstr(acf->name, a->argv[4])) {
91                         count_acf++;
92                         ast_cli(a->fd, "%-20.20s  %-35.35s  %s\n",
93                                 S_OR(acf->name, ""),
94                                 S_OR(acf->syntax, ""),
95                                 S_OR(acf->synopsis, ""));
96                 }
97         }
98         AST_RWLIST_UNLOCK(&acf_root);
99
100         ast_cli(a->fd, "%d %scustom functions installed.\n", count_acf, like ? "matching " : "");
101
102         return CLI_SUCCESS;
103 }
104
105 static char *complete_functions(const char *word, int pos, int state)
106 {
107         struct ast_custom_function *cur;
108         char *ret = NULL;
109         int which = 0;
110         int wordlen;
111         int cmp;
112
113         if (pos != 3) {
114                 return NULL;
115         }
116
117         wordlen = strlen(word);
118         AST_RWLIST_RDLOCK(&acf_root);
119         AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) {
120                 /*
121                  * Do a case-insensitive search for convenience in this
122                  * 'complete' function.
123                  *
124                  * We must search the entire container because the functions are
125                  * sorted and normally found case sensitively.
126                  */
127                 cmp = strncasecmp(word, cur->name, wordlen);
128                 if (!cmp) {
129                         /* Found match. */
130                         if (++which <= state) {
131                                 /* Not enough matches. */
132                                 continue;
133                         }
134                         ret = ast_strdup(cur->name);
135                         break;
136                 }
137         }
138         AST_RWLIST_UNLOCK(&acf_root);
139
140         return ret;
141 }
142
143 static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
144 {
145         struct ast_custom_function *acf;
146         /* Maximum number of characters added by terminal coloring is 22 */
147         char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40], argtitle[40], seealsotitle[40];
148         char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL, *seealso = NULL;
149         char stxtitle[40], *syntax = NULL, *arguments = NULL;
150         int syntax_size, description_size, synopsis_size, arguments_size, seealso_size;
151
152         switch (cmd) {
153         case CLI_INIT:
154                 e->command = "core show function";
155                 e->usage =
156                         "Usage: core show function <function>\n"
157                         "       Describe a particular dialplan function.\n";
158                 return NULL;
159         case CLI_GENERATE:
160                 return complete_functions(a->word, a->pos, a->n);
161         }
162
163         if (a->argc != 4) {
164                 return CLI_SHOWUSAGE;
165         }
166
167         if (!(acf = ast_custom_function_find(a->argv[3]))) {
168                 ast_cli(a->fd, "No function by that name registered.\n");
169
170                 return CLI_FAILURE;
171         }
172
173         syntax_size = strlen(S_OR(acf->syntax, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
174         syntax = ast_malloc(syntax_size);
175         if (!syntax) {
176                 ast_cli(a->fd, "Memory allocation failure!\n");
177
178                 return CLI_FAILURE;
179         }
180
181         snprintf(info, sizeof(info), "\n  -= Info about function '%s' =- \n\n", acf->name);
182         term_color(infotitle, info, COLOR_MAGENTA, 0, sizeof(infotitle));
183         term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40);
184         term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40);
185         term_color(stxtitle, "[Syntax]\n", COLOR_MAGENTA, 0, 40);
186         term_color(argtitle, "[Arguments]\n", COLOR_MAGENTA, 0, 40);
187         term_color(seealsotitle, "[See Also]\n", COLOR_MAGENTA, 0, 40);
188         term_color(syntax, S_OR(acf->syntax, "Not available"), COLOR_CYAN, 0, syntax_size);
189 #ifdef AST_XML_DOCS
190         if (acf->docsrc == AST_XML_DOC) {
191                 arguments = ast_xmldoc_printable(S_OR(acf->arguments, "Not available"), 1);
192                 synopsis = ast_xmldoc_printable(S_OR(acf->synopsis, "Not available"), 1);
193                 description = ast_xmldoc_printable(S_OR(acf->desc, "Not available"), 1);
194                 seealso = ast_xmldoc_printable(S_OR(acf->seealso, "Not available"), 1);
195         } else
196 #endif
197         {
198                 synopsis_size = strlen(S_OR(acf->synopsis, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
199                 synopsis = ast_malloc(synopsis_size);
200
201                 description_size = strlen(S_OR(acf->desc, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
202                 description = ast_malloc(description_size);
203
204                 arguments_size = strlen(S_OR(acf->arguments, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
205                 arguments = ast_malloc(arguments_size);
206
207                 seealso_size = strlen(S_OR(acf->seealso, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
208                 seealso = ast_malloc(seealso_size);
209
210                 /* check allocated memory. */
211                 if (!synopsis || !description || !arguments || !seealso) {
212                         ast_free(synopsis);
213                         ast_free(description);
214                         ast_free(arguments);
215                         ast_free(seealso);
216                         ast_free(syntax);
217
218                         return CLI_FAILURE;
219                 }
220
221                 term_color(arguments, S_OR(acf->arguments, "Not available"), COLOR_CYAN, 0, arguments_size);
222                 term_color(synopsis, S_OR(acf->synopsis, "Not available"), COLOR_CYAN, 0, synopsis_size);
223                 term_color(description, S_OR(acf->desc, "Not available"), COLOR_CYAN, 0, description_size);
224                 term_color(seealso, S_OR(acf->seealso, "Not available"), COLOR_CYAN, 0, seealso_size);
225         }
226
227         ast_cli(a->fd, "%s%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n",
228                         infotitle, syntitle, synopsis, destitle, description,
229                         stxtitle, syntax, argtitle, arguments, seealsotitle, seealso);
230
231         ast_free(arguments);
232         ast_free(synopsis);
233         ast_free(description);
234         ast_free(seealso);
235         ast_free(syntax);
236
237         return CLI_SUCCESS;
238 }
239
240 static struct ast_custom_function *ast_custom_function_find_nolock(const char *name)
241 {
242         struct ast_custom_function *cur;
243         int cmp;
244
245         AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) {
246                 cmp = strcmp(name, cur->name);
247                 if (cmp > 0) {
248                         continue;
249                 }
250                 if (!cmp) {
251                         /* Found it. */
252                         break;
253                 }
254                 /* Not in container. */
255                 cur = NULL;
256                 break;
257         }
258
259         return cur;
260 }
261
262 struct ast_custom_function *ast_custom_function_find(const char *name)
263 {
264         struct ast_custom_function *acf;
265
266         AST_RWLIST_RDLOCK(&acf_root);
267         acf = ast_custom_function_find_nolock(name);
268         AST_RWLIST_UNLOCK(&acf_root);
269
270         return acf;
271 }
272
273 int ast_custom_function_unregister(struct ast_custom_function *acf)
274 {
275         struct ast_custom_function *cur;
276
277         if (!acf) {
278                 return -1;
279         }
280
281         AST_RWLIST_WRLOCK(&acf_root);
282         cur = AST_RWLIST_REMOVE(&acf_root, acf, acflist);
283         if (cur) {
284 #ifdef AST_XML_DOCS
285                 if (cur->docsrc == AST_XML_DOC) {
286                         ast_string_field_free_memory(acf);
287                 }
288 #endif
289                 ast_verb(2, "Unregistered custom function %s\n", cur->name);
290         }
291         AST_RWLIST_UNLOCK(&acf_root);
292
293         return cur ? 0 : -1;
294 }
295
296 /*!
297  * \brief Returns true if given custom function escalates privileges on read.
298  *
299  * \param acf Custom function to query.
300  * \return True (non-zero) if reads escalate privileges.
301  * \return False (zero) if reads just read.
302  */
303 static int read_escalates(const struct ast_custom_function *acf)
304 {
305         return acf->read_escalates;
306 }
307
308 /*!
309  * \brief Returns true if given custom function escalates privileges on write.
310  *
311  * \param acf Custom function to query.
312  * \return True (non-zero) if writes escalate privileges.
313  * \return False (zero) if writes just write.
314  */
315 static int write_escalates(const struct ast_custom_function *acf)
316 {
317         return acf->write_escalates;
318 }
319
320 /*! \internal
321  *  \brief Retrieve the XML documentation of a specified ast_custom_function,
322  *         and populate ast_custom_function string fields.
323  *  \param acf ast_custom_function structure with empty 'desc' and 'synopsis'
324  *             but with a function 'name'.
325  *  \retval -1 On error.
326  *  \retval 0 On succes.
327  */
328 static int acf_retrieve_docs(struct ast_custom_function *acf)
329 {
330 #ifdef AST_XML_DOCS
331         char *tmpxml;
332
333         /* Let's try to find it in the Documentation XML */
334         if (!ast_strlen_zero(acf->desc) || !ast_strlen_zero(acf->synopsis)) {
335                 return 0;
336         }
337
338         if (ast_string_field_init(acf, 128)) {
339                 return -1;
340         }
341
342         /* load synopsis */
343         tmpxml = ast_xmldoc_build_synopsis("function", acf->name, ast_module_name(acf->mod));
344         ast_string_field_set(acf, synopsis, tmpxml);
345         ast_free(tmpxml);
346
347         /* load description */
348         tmpxml = ast_xmldoc_build_description("function", acf->name, ast_module_name(acf->mod));
349         ast_string_field_set(acf, desc, tmpxml);
350         ast_free(tmpxml);
351
352         /* load syntax */
353         tmpxml = ast_xmldoc_build_syntax("function", acf->name, ast_module_name(acf->mod));
354         ast_string_field_set(acf, syntax, tmpxml);
355         ast_free(tmpxml);
356
357         /* load arguments */
358         tmpxml = ast_xmldoc_build_arguments("function", acf->name, ast_module_name(acf->mod));
359         ast_string_field_set(acf, arguments, tmpxml);
360         ast_free(tmpxml);
361
362         /* load seealso */
363         tmpxml = ast_xmldoc_build_seealso("function", acf->name, ast_module_name(acf->mod));
364         ast_string_field_set(acf, seealso, tmpxml);
365         ast_free(tmpxml);
366
367         acf->docsrc = AST_XML_DOC;
368 #endif
369
370         return 0;
371 }
372
373 int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_module *mod)
374 {
375         struct ast_custom_function *cur;
376
377         if (!acf) {
378                 return -1;
379         }
380
381         acf->mod = mod;
382 #ifdef AST_XML_DOCS
383         acf->docsrc = AST_STATIC_DOC;
384 #endif
385
386         if (acf_retrieve_docs(acf)) {
387                 return -1;
388         }
389
390         AST_RWLIST_WRLOCK(&acf_root);
391
392         cur = ast_custom_function_find_nolock(acf->name);
393         if (cur) {
394                 ast_log(LOG_ERROR, "Function %s already registered.\n", acf->name);
395                 AST_RWLIST_UNLOCK(&acf_root);
396                 return -1;
397         }
398
399         /* Store in alphabetical order */
400         AST_RWLIST_TRAVERSE_SAFE_BEGIN(&acf_root, cur, acflist) {
401                 if (strcmp(acf->name, cur->name) < 0) {
402                         AST_RWLIST_INSERT_BEFORE_CURRENT(acf, acflist);
403                         break;
404                 }
405         }
406         AST_RWLIST_TRAVERSE_SAFE_END;
407         if (!cur) {
408                 AST_RWLIST_INSERT_TAIL(&acf_root, acf, acflist);
409         }
410
411         AST_RWLIST_UNLOCK(&acf_root);
412
413         ast_verb(2, "Registered custom function '" COLORIZE_FMT "'\n", COLORIZE(COLOR_BRCYAN, 0, acf->name));
414
415         return 0;
416 }
417
418 int __ast_custom_function_register_escalating(struct ast_custom_function *acf, enum ast_custom_function_escalation escalation, struct ast_module *mod)
419 {
420         int res;
421
422         res = __ast_custom_function_register(acf, mod);
423         if (res != 0) {
424                 return -1;
425         }
426
427         switch (escalation) {
428         case AST_CFE_NONE:
429                 break;
430         case AST_CFE_READ:
431                 acf->read_escalates = 1;
432                 break;
433         case AST_CFE_WRITE:
434                 acf->write_escalates = 1;
435                 break;
436         case AST_CFE_BOTH:
437                 acf->read_escalates = 1;
438                 acf->write_escalates = 1;
439                 break;
440         }
441
442         return 0;
443 }
444
445 /*! \brief return a pointer to the arguments of the function,
446  * and terminates the function name with '\\0'
447  */
448 static char *func_args(char *function)
449 {
450         char *args = strchr(function, '(');
451
452         if (!args) {
453                 ast_log(LOG_WARNING, "Function '%s' doesn't contain parentheses.  Assuming null argument.\n", function);
454         } else {
455                 char *p;
456                 *args++ = '\0';
457                 if ((p = strrchr(args, ')'))) {
458                         *p = '\0';
459                 } else {
460                         ast_log(LOG_WARNING, "Can't find trailing parenthesis for function '%s(%s'?\n", function, args);
461                 }
462         }
463         return args;
464 }
465
466 void pbx_live_dangerously(int new_live_dangerously)
467 {
468         if (new_live_dangerously && !live_dangerously) {
469                 ast_log(LOG_WARNING, "Privilege escalation protection disabled!\n"
470                         "See https://wiki.asterisk.org/wiki/x/1gKfAQ for more details.\n");
471         }
472
473         if (!new_live_dangerously && live_dangerously) {
474                 ast_log(LOG_NOTICE, "Privilege escalation protection enabled.\n");
475         }
476         live_dangerously = new_live_dangerously;
477 }
478
479 int ast_thread_inhibit_escalations(void)
480 {
481         int *thread_inhibit_escalations;
482
483         thread_inhibit_escalations = ast_threadstorage_get(
484                 &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
485         if (thread_inhibit_escalations == NULL) {
486                 ast_log(LOG_ERROR, "Error inhibiting privilege escalations for current thread\n");
487                 return -1;
488         }
489
490         *thread_inhibit_escalations = 1;
491         return 0;
492 }
493
494 int ast_thread_inhibit_escalations_swap(int inhibit)
495 {
496         int *thread_inhibit_escalations;
497         int orig;
498
499         thread_inhibit_escalations = ast_threadstorage_get(
500                 &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
501         if (thread_inhibit_escalations == NULL) {
502                 ast_log(LOG_ERROR, "Error swapping privilege escalations inhibit for current thread\n");
503                 return -1;
504         }
505
506         orig = *thread_inhibit_escalations;
507         *thread_inhibit_escalations = !!inhibit;
508         return orig;
509 }
510
511 /*!
512  * \brief Indicates whether the current thread inhibits the execution of
513  * dangerous functions.
514  *
515  * \return True (non-zero) if dangerous function execution is inhibited.
516  * \return False (zero) if dangerous function execution is allowed.
517  */
518 static int thread_inhibits_escalations(void)
519 {
520         int *thread_inhibit_escalations;
521
522         thread_inhibit_escalations = ast_threadstorage_get(
523                 &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
524         if (thread_inhibit_escalations == NULL) {
525                 ast_log(LOG_ERROR, "Error checking thread's ability to run dangerous functions\n");
526                 /* On error, assume that we are inhibiting */
527                 return 1;
528         }
529
530         return *thread_inhibit_escalations;
531 }
532
533 /*!
534  * \brief Determines whether execution of a custom function's read function
535  * is allowed.
536  *
537  * \param acfptr Custom function to check
538  * \return True (non-zero) if reading is allowed.
539  * \return False (zero) if reading is not allowed.
540  */
541 static int is_read_allowed(struct ast_custom_function *acfptr)
542 {
543         if (!acfptr) {
544                 return 1;
545         }
546
547         if (!read_escalates(acfptr)) {
548                 return 1;
549         }
550
551         if (!thread_inhibits_escalations()) {
552                 return 1;
553         }
554
555         if (live_dangerously) {
556                 /* Global setting overrides the thread's preference */
557                 ast_debug(2, "Reading %s from a dangerous context\n",
558                         acfptr->name);
559                 return 1;
560         }
561
562         /* We have no reason to allow this function to execute */
563         return 0;
564 }
565
566 /*!
567  * \brief Determines whether execution of a custom function's write function
568  * is allowed.
569  *
570  * \param acfptr Custom function to check
571  * \return True (non-zero) if writing is allowed.
572  * \return False (zero) if writing is not allowed.
573  */
574 static int is_write_allowed(struct ast_custom_function *acfptr)
575 {
576         if (!acfptr) {
577                 return 1;
578         }
579
580         if (!write_escalates(acfptr)) {
581                 return 1;
582         }
583
584         if (!thread_inhibits_escalations()) {
585                 return 1;
586         }
587
588         if (live_dangerously) {
589                 /* Global setting overrides the thread's preference */
590                 ast_debug(2, "Writing %s from a dangerous context\n",
591                         acfptr->name);
592                 return 1;
593         }
594
595         /* We have no reason to allow this function to execute */
596         return 0;
597 }
598
599 int ast_func_read(struct ast_channel *chan, const char *function, char *workspace, size_t len)
600 {
601         char *copy = ast_strdupa(function);
602         char *args = func_args(copy);
603         struct ast_custom_function *acfptr = ast_custom_function_find(copy);
604         int res;
605         struct ast_module_user *u = NULL;
606
607         if (acfptr == NULL) {
608                 ast_log(LOG_ERROR, "Function %s not registered\n", copy);
609         } else if (!acfptr->read && !acfptr->read2) {
610                 ast_log(LOG_ERROR, "Function %s cannot be read\n", copy);
611         } else if (!is_read_allowed(acfptr)) {
612                 ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy);
613         } else if (acfptr->read) {
614                 if (acfptr->mod) {
615                         u = __ast_module_user_add(acfptr->mod, chan);
616                 }
617                 res = acfptr->read(chan, copy, args, workspace, len);
618                 if (acfptr->mod && u) {
619                         __ast_module_user_remove(acfptr->mod, u);
620                 }
621
622                 return res;
623         } else {
624                 struct ast_str *str = ast_str_create(16);
625
626                 if (acfptr->mod) {
627                         u = __ast_module_user_add(acfptr->mod, chan);
628                 }
629                 res = acfptr->read2(chan, copy, args, &str, 0);
630                 if (acfptr->mod && u) {
631                         __ast_module_user_remove(acfptr->mod, u);
632                 }
633                 ast_copy_string(workspace, ast_str_buffer(str), len > ast_str_size(str) ? ast_str_size(str) : len);
634                 ast_free(str);
635
636                 return res;
637         }
638
639         return -1;
640 }
641
642 int ast_func_read2(struct ast_channel *chan, const char *function, struct ast_str **str, ssize_t maxlen)
643 {
644         char *copy = ast_strdupa(function);
645         char *args = func_args(copy);
646         struct ast_custom_function *acfptr = ast_custom_function_find(copy);
647         int res;
648         struct ast_module_user *u = NULL;
649
650         if (acfptr == NULL) {
651                 ast_log(LOG_ERROR, "Function %s not registered\n", copy);
652         } else if (!acfptr->read && !acfptr->read2) {
653                 ast_log(LOG_ERROR, "Function %s cannot be read\n", copy);
654         } else if (!is_read_allowed(acfptr)) {
655                 ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy);
656         } else {
657                 if (acfptr->mod) {
658                         u = __ast_module_user_add(acfptr->mod, chan);
659                 }
660                 ast_str_reset(*str);
661                 if (acfptr->read2) {
662                         /* ast_str enabled */
663                         res = acfptr->read2(chan, copy, args, str, maxlen);
664                 } else {
665                         /* Legacy function pointer, allocate buffer for result */
666                         int maxsize = ast_str_size(*str);
667
668                         if (maxlen > -1) {
669                                 if (maxlen == 0) {
670                                         if (acfptr->read_max) {
671                                                 maxsize = acfptr->read_max;
672                                         } else {
673                                                 maxsize = VAR_BUF_SIZE;
674                                         }
675                                 } else {
676                                         maxsize = maxlen;
677                                 }
678                                 ast_str_make_space(str, maxsize);
679                         }
680                         res = acfptr->read(chan, copy, args, ast_str_buffer(*str), maxsize);
681                 }
682                 if (acfptr->mod && u) {
683                         __ast_module_user_remove(acfptr->mod, u);
684                 }
685
686                 return res;
687         }
688
689         return -1;
690 }
691
692 int ast_func_write(struct ast_channel *chan, const char *function, const char *value)
693 {
694         char *copy = ast_strdupa(function);
695         char *args = func_args(copy);
696         struct ast_custom_function *acfptr = ast_custom_function_find(copy);
697
698         if (acfptr == NULL) {
699                 ast_log(LOG_ERROR, "Function %s not registered\n", copy);
700         } else if (!acfptr->write) {
701                 ast_log(LOG_ERROR, "Function %s cannot be written to\n", copy);
702         } else if (!is_write_allowed(acfptr)) {
703                 ast_log(LOG_ERROR, "Dangerous function %s write blocked\n", copy);
704         } else {
705                 int res;
706                 struct ast_module_user *u = NULL;
707
708                 if (acfptr->mod) {
709                         u = __ast_module_user_add(acfptr->mod, chan);
710                 }
711                 res = acfptr->write(chan, copy, args, value);
712                 if (acfptr->mod && u) {
713                         __ast_module_user_remove(acfptr->mod, u);
714                 }
715
716                 return res;
717         }
718
719         return -1;
720 }
721
722 static struct ast_cli_entry acf_cli[] = {
723         AST_CLI_DEFINE(handle_show_functions, "Shows registered dialplan functions"),
724         AST_CLI_DEFINE(handle_show_function, "Describe a specific dialplan function"),
725 };
726
727 static void unload_pbx_functions_cli(void)
728 {
729         ast_cli_unregister_multiple(acf_cli, ARRAY_LEN(acf_cli));
730 }
731
732 int load_pbx_functions_cli(void)
733 {
734         ast_cli_register_multiple(acf_cli, ARRAY_LEN(acf_cli));
735         ast_register_cleanup(unload_pbx_functions_cli);
736
737         return 0;
738 }