Merge "res_calendar: Specialized calendars depend on symbols of general calendar."
[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         return acf->read_escalates;
305 }
306
307 /*!
308  * \brief Returns true if given custom function escalates privileges on write.
309  *
310  * \param acf Custom function to query.
311  * \return True (non-zero) if writes escalate privileges.
312  * \return False (zero) if writes just write.
313  */
314 static int write_escalates(const struct ast_custom_function *acf) {
315         return acf->write_escalates;
316 }
317
318 /*! \internal
319  *  \brief Retrieve the XML documentation of a specified ast_custom_function,
320  *         and populate ast_custom_function string fields.
321  *  \param acf ast_custom_function structure with empty 'desc' and 'synopsis'
322  *             but with a function 'name'.
323  *  \retval -1 On error.
324  *  \retval 0 On succes.
325  */
326 static int acf_retrieve_docs(struct ast_custom_function *acf)
327 {
328 #ifdef AST_XML_DOCS
329         char *tmpxml;
330
331         /* Let's try to find it in the Documentation XML */
332         if (!ast_strlen_zero(acf->desc) || !ast_strlen_zero(acf->synopsis)) {
333                 return 0;
334         }
335
336         if (ast_string_field_init(acf, 128)) {
337                 return -1;
338         }
339
340         /* load synopsis */
341         tmpxml = ast_xmldoc_build_synopsis("function", acf->name, ast_module_name(acf->mod));
342         ast_string_field_set(acf, synopsis, tmpxml);
343         ast_free(tmpxml);
344
345         /* load description */
346         tmpxml = ast_xmldoc_build_description("function", acf->name, ast_module_name(acf->mod));
347         ast_string_field_set(acf, desc, tmpxml);
348         ast_free(tmpxml);
349
350         /* load syntax */
351         tmpxml = ast_xmldoc_build_syntax("function", acf->name, ast_module_name(acf->mod));
352         ast_string_field_set(acf, syntax, tmpxml);
353         ast_free(tmpxml);
354
355         /* load arguments */
356         tmpxml = ast_xmldoc_build_arguments("function", acf->name, ast_module_name(acf->mod));
357         ast_string_field_set(acf, arguments, tmpxml);
358         ast_free(tmpxml);
359
360         /* load seealso */
361         tmpxml = ast_xmldoc_build_seealso("function", acf->name, ast_module_name(acf->mod));
362         ast_string_field_set(acf, seealso, tmpxml);
363         ast_free(tmpxml);
364
365         acf->docsrc = AST_XML_DOC;
366 #endif
367
368         return 0;
369 }
370
371 int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_module *mod)
372 {
373         struct ast_custom_function *cur;
374
375         if (!acf) {
376                 return -1;
377         }
378
379         acf->mod = mod;
380 #ifdef AST_XML_DOCS
381         acf->docsrc = AST_STATIC_DOC;
382 #endif
383
384         if (acf_retrieve_docs(acf)) {
385                 return -1;
386         }
387
388         AST_RWLIST_WRLOCK(&acf_root);
389
390         cur = ast_custom_function_find_nolock(acf->name);
391         if (cur) {
392                 ast_log(LOG_ERROR, "Function %s already registered.\n", acf->name);
393                 AST_RWLIST_UNLOCK(&acf_root);
394                 return -1;
395         }
396
397         /* Store in alphabetical order */
398         AST_RWLIST_TRAVERSE_SAFE_BEGIN(&acf_root, cur, acflist) {
399                 if (strcmp(acf->name, cur->name) < 0) {
400                         AST_RWLIST_INSERT_BEFORE_CURRENT(acf, acflist);
401                         break;
402                 }
403         }
404         AST_RWLIST_TRAVERSE_SAFE_END;
405         if (!cur) {
406                 AST_RWLIST_INSERT_TAIL(&acf_root, acf, acflist);
407         }
408
409         AST_RWLIST_UNLOCK(&acf_root);
410
411         ast_verb(2, "Registered custom function '" COLORIZE_FMT "'\n", COLORIZE(COLOR_BRCYAN, 0, acf->name));
412
413         return 0;
414 }
415
416 int __ast_custom_function_register_escalating(struct ast_custom_function *acf, enum ast_custom_function_escalation escalation, struct ast_module *mod)
417 {
418         int res;
419
420         res = __ast_custom_function_register(acf, mod);
421         if (res != 0) {
422                 return -1;
423         }
424
425         switch (escalation) {
426         case AST_CFE_NONE:
427                 break;
428         case AST_CFE_READ:
429                 acf->read_escalates = 1;
430                 break;
431         case AST_CFE_WRITE:
432                 acf->write_escalates = 1;
433                 break;
434         case AST_CFE_BOTH:
435                 acf->read_escalates = 1;
436                 acf->write_escalates = 1;
437                 break;
438         }
439
440         return 0;
441 }
442
443 /*! \brief return a pointer to the arguments of the function,
444  * and terminates the function name with '\\0'
445  */
446 static char *func_args(char *function)
447 {
448         char *args = strchr(function, '(');
449
450         if (!args) {
451                 ast_log(LOG_WARNING, "Function '%s' doesn't contain parentheses.  Assuming null argument.\n", function);
452         } else {
453                 char *p;
454                 *args++ = '\0';
455                 if ((p = strrchr(args, ')'))) {
456                         *p = '\0';
457                 } else {
458                         ast_log(LOG_WARNING, "Can't find trailing parenthesis for function '%s(%s'?\n", function, args);
459                 }
460         }
461         return args;
462 }
463
464 void pbx_live_dangerously(int new_live_dangerously)
465 {
466         if (new_live_dangerously && !live_dangerously) {
467                 ast_log(LOG_WARNING, "Privilege escalation protection disabled!\n"
468                         "See https://wiki.asterisk.org/wiki/x/1gKfAQ for more details.\n");
469         }
470
471         if (!new_live_dangerously && live_dangerously) {
472                 ast_log(LOG_NOTICE, "Privilege escalation protection enabled.\n");
473         }
474         live_dangerously = new_live_dangerously;
475 }
476
477 int ast_thread_inhibit_escalations(void)
478 {
479         int *thread_inhibit_escalations;
480
481         thread_inhibit_escalations = ast_threadstorage_get(
482                 &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
483         if (thread_inhibit_escalations == NULL) {
484                 ast_log(LOG_ERROR, "Error inhibiting privilege escalations for current thread\n");
485                 return -1;
486         }
487
488         *thread_inhibit_escalations = 1;
489         return 0;
490 }
491
492 int ast_thread_inhibit_escalations_swap(int inhibit)
493 {
494         int *thread_inhibit_escalations;
495         int orig;
496
497         thread_inhibit_escalations = ast_threadstorage_get(
498                 &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
499         if (thread_inhibit_escalations == NULL) {
500                 ast_log(LOG_ERROR, "Error swapping privilege escalations inhibit for current thread\n");
501                 return -1;
502         }
503
504         orig = *thread_inhibit_escalations;
505         *thread_inhibit_escalations = !!inhibit;
506         return orig;
507 }
508
509 /*!
510  * \brief Indicates whether the current thread inhibits the execution of
511  * dangerous functions.
512  *
513  * \return True (non-zero) if dangerous function execution is inhibited.
514  * \return False (zero) if dangerous function execution is allowed.
515  */
516 static int thread_inhibits_escalations(void)
517 {
518         int *thread_inhibit_escalations;
519
520         thread_inhibit_escalations = ast_threadstorage_get(
521                 &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
522         if (thread_inhibit_escalations == NULL) {
523                 ast_log(LOG_ERROR, "Error checking thread's ability to run dangerous functions\n");
524                 /* On error, assume that we are inhibiting */
525                 return 1;
526         }
527
528         return *thread_inhibit_escalations;
529 }
530
531 /*!
532  * \brief Determines whether execution of a custom function's read function
533  * is allowed.
534  *
535  * \param acfptr Custom function to check
536  * \return True (non-zero) if reading is allowed.
537  * \return False (zero) if reading is not allowed.
538  */
539 static int is_read_allowed(struct ast_custom_function *acfptr)
540 {
541         if (!acfptr) {
542                 return 1;
543         }
544
545         if (!read_escalates(acfptr)) {
546                 return 1;
547         }
548
549         if (!thread_inhibits_escalations()) {
550                 return 1;
551         }
552
553         if (live_dangerously) {
554                 /* Global setting overrides the thread's preference */
555                 ast_debug(2, "Reading %s from a dangerous context\n",
556                         acfptr->name);
557                 return 1;
558         }
559
560         /* We have no reason to allow this function to execute */
561         return 0;
562 }
563
564 /*!
565  * \brief Determines whether execution of a custom function's write function
566  * is allowed.
567  *
568  * \param acfptr Custom function to check
569  * \return True (non-zero) if writing is allowed.
570  * \return False (zero) if writing is not allowed.
571  */
572 static int is_write_allowed(struct ast_custom_function *acfptr)
573 {
574         if (!acfptr) {
575                 return 1;
576         }
577
578         if (!write_escalates(acfptr)) {
579                 return 1;
580         }
581
582         if (!thread_inhibits_escalations()) {
583                 return 1;
584         }
585
586         if (live_dangerously) {
587                 /* Global setting overrides the thread's preference */
588                 ast_debug(2, "Writing %s from a dangerous context\n",
589                         acfptr->name);
590                 return 1;
591         }
592
593         /* We have no reason to allow this function to execute */
594         return 0;
595 }
596
597 int ast_func_read(struct ast_channel *chan, const char *function, char *workspace, size_t len)
598 {
599         char *copy = ast_strdupa(function);
600         char *args = func_args(copy);
601         struct ast_custom_function *acfptr = ast_custom_function_find(copy);
602         int res;
603         struct ast_module_user *u = NULL;
604
605         if (acfptr == NULL) {
606                 ast_log(LOG_ERROR, "Function %s not registered\n", copy);
607         } else if (!acfptr->read && !acfptr->read2) {
608                 ast_log(LOG_ERROR, "Function %s cannot be read\n", copy);
609         } else if (!is_read_allowed(acfptr)) {
610                 ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy);
611         } else if (acfptr->read) {
612                 if (acfptr->mod) {
613                         u = __ast_module_user_add(acfptr->mod, chan);
614                 }
615                 res = acfptr->read(chan, copy, args, workspace, len);
616                 if (acfptr->mod && u) {
617                         __ast_module_user_remove(acfptr->mod, u);
618                 }
619
620                 return res;
621         } else {
622                 struct ast_str *str = ast_str_create(16);
623
624                 if (acfptr->mod) {
625                         u = __ast_module_user_add(acfptr->mod, chan);
626                 }
627                 res = acfptr->read2(chan, copy, args, &str, 0);
628                 if (acfptr->mod && u) {
629                         __ast_module_user_remove(acfptr->mod, u);
630                 }
631                 ast_copy_string(workspace, ast_str_buffer(str), len > ast_str_size(str) ? ast_str_size(str) : len);
632                 ast_free(str);
633
634                 return res;
635         }
636
637         return -1;
638 }
639
640 int ast_func_read2(struct ast_channel *chan, const char *function, struct ast_str **str, ssize_t maxlen)
641 {
642         char *copy = ast_strdupa(function);
643         char *args = func_args(copy);
644         struct ast_custom_function *acfptr = ast_custom_function_find(copy);
645         int res;
646         struct ast_module_user *u = NULL;
647
648         if (acfptr == NULL) {
649                 ast_log(LOG_ERROR, "Function %s not registered\n", copy);
650         } else if (!acfptr->read && !acfptr->read2) {
651                 ast_log(LOG_ERROR, "Function %s cannot be read\n", copy);
652         } else if (!is_read_allowed(acfptr)) {
653                 ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy);
654         } else {
655                 if (acfptr->mod) {
656                         u = __ast_module_user_add(acfptr->mod, chan);
657                 }
658                 ast_str_reset(*str);
659                 if (acfptr->read2) {
660                         /* ast_str enabled */
661                         res = acfptr->read2(chan, copy, args, str, maxlen);
662                 } else {
663                         /* Legacy function pointer, allocate buffer for result */
664                         int maxsize = ast_str_size(*str);
665
666                         if (maxlen > -1) {
667                                 if (maxlen == 0) {
668                                         if (acfptr->read_max) {
669                                                 maxsize = acfptr->read_max;
670                                         } else {
671                                                 maxsize = VAR_BUF_SIZE;
672                                         }
673                                 } else {
674                                         maxsize = maxlen;
675                                 }
676                                 ast_str_make_space(str, maxsize);
677                         }
678                         res = acfptr->read(chan, copy, args, ast_str_buffer(*str), maxsize);
679                 }
680                 if (acfptr->mod && u) {
681                         __ast_module_user_remove(acfptr->mod, u);
682                 }
683
684                 return res;
685         }
686
687         return -1;
688 }
689
690 int ast_func_write(struct ast_channel *chan, const char *function, const char *value)
691 {
692         char *copy = ast_strdupa(function);
693         char *args = func_args(copy);
694         struct ast_custom_function *acfptr = ast_custom_function_find(copy);
695
696         if (acfptr == NULL) {
697                 ast_log(LOG_ERROR, "Function %s not registered\n", copy);
698         } else if (!acfptr->write) {
699                 ast_log(LOG_ERROR, "Function %s cannot be written to\n", copy);
700         } else if (!is_write_allowed(acfptr)) {
701                 ast_log(LOG_ERROR, "Dangerous function %s write blocked\n", copy);
702         } else {
703                 int res;
704                 struct ast_module_user *u = NULL;
705
706                 if (acfptr->mod) {
707                         u = __ast_module_user_add(acfptr->mod, chan);
708                 }
709                 res = acfptr->write(chan, copy, args, value);
710                 if (acfptr->mod && u) {
711                         __ast_module_user_remove(acfptr->mod, u);
712                 }
713
714                 return res;
715         }
716
717         return -1;
718 }
719
720 static struct ast_cli_entry acf_cli[] = {
721         AST_CLI_DEFINE(handle_show_functions, "Shows registered dialplan functions"),
722         AST_CLI_DEFINE(handle_show_function, "Describe a specific dialplan function"),
723 };
724
725 static void unload_pbx_functions_cli(void)
726 {
727         ast_cli_unregister_multiple(acf_cli, ARRAY_LEN(acf_cli));
728 }
729
730 int load_pbx_functions_cli(void)
731 {
732         ast_cli_register_multiple(acf_cli, ARRAY_LEN(acf_cli));
733         ast_register_cleanup(unload_pbx_functions_cli);
734
735         return 0;
736 }