merge new_loader_completion branch, including (at least):
[asterisk/asterisk.git] / pbx / pbx_config.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2006, Digium, Inc.
5  *
6  * Mark Spencer <markster@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 /*! \file
20  *
21  * \brief Populate and remember extensions from static config file
22  *
23  * 
24  */
25
26 #include "asterisk.h"
27
28 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
29
30 #include <sys/types.h>
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <ctype.h>
35 #include <errno.h>
36
37 #include "asterisk/pbx.h"
38 #include "asterisk/config.h"
39 #include "asterisk/options.h"
40 #include "asterisk/module.h"
41 #include "asterisk/logger.h"
42 #include "asterisk/cli.h"
43 #include "asterisk/callerid.h"
44
45 static char *config = "extensions.conf";
46 static char *registrar = "pbx_config";
47
48 static int static_config = 0;
49 static int write_protect_config = 1;
50 static int autofallthrough_config = 0;
51 static int clearglobalvars_config = 0;
52
53 AST_MUTEX_DEFINE_STATIC(save_dialplan_lock);
54
55 static struct ast_context *local_contexts = NULL;
56
57 /*
58  * Help for commands provided by this module ...
59  */
60 static char context_dont_include_help[] =
61 "Usage: dont include <context> in <context>\n"
62 "       Remove an included context from another context.\n";
63
64 static char context_remove_extension_help[] =
65 "Usage: remove extension exten@context [priority]\n"
66 "       Remove an extension from a given context. If a priority\n"
67 "       is given, only that specific priority from the given extension\n"
68 "       will be removed.\n";
69
70 static char context_add_include_help[] =
71 "Usage: include <context> in <context>\n"
72 "       Include a context in another context.\n";
73
74 static char save_dialplan_help[] =
75 "Usage: save dialplan [/path/to/extension/file]\n"
76 "       Save dialplan created by pbx_config module.\n"
77 "\n"
78 "Example: save dialplan                 (/etc/asterisk/extensions.conf)\n"
79 "         save dialplan /home/markster  (/home/markster/extensions.conf)\n";
80
81 static char context_add_extension_help[] =
82 "Usage: add extension <exten>,<priority>,<app>,<app-data> into <context>\n"
83 "       [replace]\n\n"
84 "       This command will add new extension into <context>. If there is an\n"
85 "       existence of extension with the same priority and last 'replace'\n"
86 "       arguments is given here we simply replace this extension.\n"
87 "\n"
88 "Example: add extension 6123,1,Dial,IAX/216.207.245.56/6123 into local\n"
89 "         Now, you can dial 6123 and talk to Markster :)\n";
90
91 static char context_add_ignorepat_help[] =
92 "Usage: add ignorepat <pattern> into <context>\n"
93 "       This command adds a new ignore pattern into context <context>\n"
94 "\n"
95 "Example: add ignorepat _3XX into local\n";
96
97 static char context_remove_ignorepat_help[] =
98 "Usage: remove ignorepat <pattern> from <context>\n"
99 "       This command removes an ignore pattern from context <context>\n"
100 "\n"
101 "Example: remove ignorepat _3XX from local\n";
102
103 static char reload_extensions_help[] =
104 "Usage: reload extensions.conf without reloading any other modules\n"
105 "       This command does not delete global variables unless\n"
106 "       clearglobalvars is set to yes in extensions.conf\n"
107 "\n"
108 "Example: extensions reload\n";
109
110 /*
111  * Implementation of functions provided by this module
112  */
113
114 /*!
115  * REMOVE INCLUDE command stuff
116  */
117 static int handle_context_dont_include(int fd, int argc, char *argv[])
118 {
119         if (argc != 5)
120                 return RESULT_SHOWUSAGE;
121
122         if (strcmp(argv[3], "in"))
123                 return RESULT_SHOWUSAGE;
124
125         if (!ast_context_remove_include(argv[4], argv[2], registrar)) {
126                 ast_cli(fd, "We are not including '%s' in '%s' now\n",
127                         argv[2], argv[4]);
128                 return RESULT_SUCCESS;
129         }
130
131         ast_cli(fd, "Failed to remove '%s' include from '%s' context\n",
132                 argv[2], argv[4]);
133         return RESULT_FAILURE;
134 }
135
136 /*! \brief return true if 'name' is included by context c */
137 static int lookup_ci(struct ast_context *c, const char *name)
138 {
139         struct ast_include *i = NULL;
140
141         if (ast_lock_context(c))        /* error, skip */
142                 return 0;
143         while ( (i = ast_walk_context_includes(c, i)) )
144                 if (!strcmp(name, ast_get_include_name(i)))
145                         break;
146         ast_unlock_context(c);
147         return i ? -1 /* success */ : 0;
148 }
149
150 /*! \brief return true if 'name' is in the ignorepats for context c */
151 static int lookup_c_ip(struct ast_context *c, const char *name)
152 {
153         struct ast_ignorepat *ip = NULL;
154
155         if (ast_lock_context(c))        /* error, skip */
156                 return 0;
157         while ( (ip = ast_walk_context_ignorepats(c, ip)) )
158                 if (!strcmp(name, ast_get_ignorepat_name(ip)))
159                         break;
160         ast_unlock_context(c);
161         return ip ? -1 /* success */ : 0;
162 }
163
164 /*! \brief moves to the n-th word in the string, or empty string if none */
165 static const char *skip_words(const char *p, int n)
166 {
167         int in_blank = 0;
168         for (;n && *p; p++) {
169                 if (isblank(*p) /* XXX order is important */ && !in_blank) {
170                         n--;    /* one word is gone */
171                         in_blank = 1;
172                 } else if (/* !is_blank(*p), we know already, && */ in_blank) {
173                         in_blank = 0;
174                 }
175         }
176         return p;
177 }
178
179 /*! \brief match the first 'len' chars of word. len==0 always succeeds */
180 static int partial_match(const char *s, const char *word, int len)
181 {
182         return (len == 0 || !strncmp(s, word, len));
183 }
184
185 /*! \brief split extension\@context in two parts, return -1 on error.
186  * The return string is malloc'ed and pointed by *ext
187  */
188 static int split_ec(const char *src, char **ext, char ** const ctx)
189 {
190         char *c, *e = ast_strdup(src); /* now src is not used anymore */
191
192         if (e == NULL)
193                 return -1;      /* malloc error */
194         /* now, parse values from 'exten@context' */
195         *ext = e;
196         c = strchr(e, '@');
197         if (c == NULL)  /* no context part */
198                 *ctx = "";      /* it is not overwritten, anyways */
199         else {  /* found context, check for duplicity ... */
200                 *c++ = '\0';
201                 *ctx = c;
202                 if (strchr(c, '@')) { /* two @, not allowed */
203                         free(e);
204                         return -1;
205                 }
206         } 
207         return 0;
208 }
209
210 /* _X_ is the string we need to complete */
211 static char *complete_context_dont_include(const char *line, const char *word,
212         int pos, int state)
213 {
214         int which = 0;
215         char *res = NULL;
216         int len = strlen(word); /* how many bytes to match */
217         struct ast_context *c = NULL;
218
219         if (pos == 2) {         /* "dont include _X_" */
220                 if (ast_lock_contexts()) {
221                         ast_log(LOG_ERROR, "Failed to lock context list\n");
222                         return NULL;
223                 }
224                 /* walk contexts and their includes, return the n-th match */
225                 while (!res && (c = ast_walk_contexts(c))) {
226                         struct ast_include *i = NULL;
227
228                         if (ast_lock_context(c))        /* error ? skip this one */
229                                 continue;
230
231                         while ( !res && (i = ast_walk_context_includes(c, i)) ) {
232                                 const char *i_name = ast_get_include_name(i);
233                                 struct ast_context *nc = NULL;
234                                 int already_served = 0;
235
236                                 if (!partial_match(i_name, word, len))
237                                         continue;       /* not matched */
238
239                                 /* check if this include is already served or not */
240
241                                 /* go through all contexts again till we reach actual
242                                  * context or already_served = 1
243                                  */
244                                 while ( (nc = ast_walk_contexts(nc)) && nc != c && !already_served)
245                                         already_served = lookup_ci(nc, i_name);
246
247                                 if (!already_served && ++which > state)
248                                         res = strdup(i_name);
249                         }
250                         ast_unlock_context(c);
251                 }
252
253                 ast_unlock_contexts();
254                 return res;
255         } else if (pos == 3) { /* "dont include CTX _X_" */
256                 /*
257                  * complete as 'in', but only if previous context is really
258                  * included somewhere
259                  */
260                 char *context, *dupline;
261                 const char *s = skip_words(line, 2); /* skip 'dont' 'include' */
262
263                 if (state > 0)
264                         return NULL;
265                 context = dupline = strdup(s);
266                 if (!dupline) {
267                         ast_log(LOG_ERROR, "Out of free memory\n");
268                         return NULL;
269                 }
270                 strsep(&dupline, " ");
271
272                 if (ast_lock_contexts()) {
273                         ast_log(LOG_ERROR, "Failed to lock contexts list\n");
274                         free(context);
275                         return NULL;
276                 }
277
278                 /* go through all contexts and check if is included ... */
279                 while (!res && (c = ast_walk_contexts(c)))
280                         if (lookup_ci(c, context)) /* context is really included, complete "in" command */
281                                 res = strdup("in");
282                 ast_unlock_contexts();
283                 if (!res)
284                         ast_log(LOG_WARNING, "%s not included anywhere\n", context);
285                 free(context);
286                 return res;
287         } else if (pos == 4) { /* "dont include CTX in _X_" */
288                 /*
289                  * Context from which we removing include ... 
290                  */
291                 char *context, *dupline, *in;
292                 const char *s = skip_words(line, 2); /* skip 'dont' 'include' */
293                 context = dupline = strdup(s);
294                 if (!dupline) {
295                         ast_log(LOG_ERROR, "Out of free memory\n");
296                         return NULL;
297                 }
298
299                 strsep(&dupline, " "); /* skip context */
300
301                 /* third word must be 'in' */
302                 in = strsep(&dupline, " ");
303                 if (!in || strcmp(in, "in")) {
304                         free(context);
305                         return NULL;
306                 }
307
308                 if (ast_lock_contexts()) {
309                         ast_log(LOG_ERROR, "Failed to lock context list\n");
310                         free(context);
311                         return NULL;
312                 }
313
314                 /* walk through all contexts ... */
315                 c = NULL;
316                 while ( !res && (c = ast_walk_contexts(c))) {
317                         const char *c_name = ast_get_context_name(c);
318                         if (!partial_match(c_name, word, len))  /* not a good target */
319                                 continue;
320                         /* walk through all includes and check if it is our context */  
321                         if (lookup_ci(c, context) && ++which > state)
322                                 res = strdup(c_name);
323                 }
324                 ast_unlock_contexts();
325                 free(context);
326                 return res;
327         }
328
329         return NULL;
330 }
331
332 /*!
333  * REMOVE EXTENSION command stuff
334  */
335 static int handle_context_remove_extension(int fd, int argc, char *argv[])
336 {
337         int removing_priority = 0;
338         char *exten, *context;
339         int ret = RESULT_FAILURE;
340
341         if (argc != 4 && argc != 3) return RESULT_SHOWUSAGE;
342
343         /*
344          * Priority input checking ...
345          */
346         if (argc == 4) {
347                 char *c = argv[3];
348
349                 /* check for digits in whole parameter for right priority ...
350                  * why? because atoi (strtol) returns 0 if any characters in
351                  * string and whole extension will be removed, it's not good
352                  */
353                 if (!strcmp("hint", c))
354                         removing_priority = PRIORITY_HINT;
355                 else {
356                         while (*c && isdigit(*c))
357                                 c++;
358                         if (*c) { /* non-digit in string */
359                                 ast_cli(fd, "Invalid priority '%s'\n", argv[3]);
360                                 return RESULT_FAILURE;
361                         }
362                         removing_priority = atoi(argv[3]);
363                 }
364
365                 if (removing_priority == 0) {
366                         ast_cli(fd, "If you want to remove whole extension, please " \
367                                 "omit priority argument\n");
368                         return RESULT_FAILURE;
369                 }
370         }
371
372         /* XXX original overwrote argv[2] */
373         /*
374          * Format exten@context checking ...
375          */
376         if (split_ec(argv[2], &exten, &context))
377                 return RESULT_FAILURE; /* XXX malloc failure */
378         if ((!strlen(exten)) || (!(strlen(context)))) {
379                 ast_cli(fd, "Missing extension or context name in second argument '%s'\n",
380                         argv[2]);
381                 free(exten);
382                 return RESULT_FAILURE;
383         }
384
385         if (!ast_context_remove_extension(context, exten, removing_priority, registrar)) {
386                 if (!removing_priority)
387                         ast_cli(fd, "Whole extension %s@%s removed\n",
388                                 exten, context);
389                 else
390                         ast_cli(fd, "Extension %s@%s with priority %d removed\n",
391                                 exten, context, removing_priority);
392                         
393                 ret = RESULT_SUCCESS;
394         } else {
395                 ast_cli(fd, "Failed to remove extension %s@%s\n", exten, context);
396                 ret = RESULT_FAILURE;
397         }
398         free(exten);
399         return ret;
400 }
401
402 #define BROKEN_READLINE 1
403
404 #ifdef BROKEN_READLINE
405 /*
406  * There is one funny thing, when you have word like 300@ and you hit
407  * <tab>, you arguments will like as your word is '300 ', so it '@'
408  * characters acts sometimes as word delimiter and sometimes as a part
409  * of word
410  *
411  * This fix function, allocates new word variable and store here every
412  * time xxx@yyy always as one word and correct pos is set too
413  *
414  * It's ugly, I know, but I'm waiting for Mark suggestion if upper is
415  * bug or feature ...
416  */
417 static int fix_complete_args(const char *line, char **word, int *pos)
418 {
419         char *_line, *_strsep_line, *_previous_word = NULL, *_word = NULL;
420         int words = 0;
421
422         _line = strdup(line);
423
424         _strsep_line = _line;
425         while (_strsep_line) {
426                 _previous_word = _word;
427                 _word = strsep(&_strsep_line, " ");
428
429                 if (_word && strlen(_word)) words++;
430         }
431
432
433         if (_word || _previous_word) {
434                 if (_word) {
435                         if (!strlen(_word)) words++;
436                         *word = strdup(_word);
437                 } else
438                         *word = strdup(_previous_word);
439                 *pos = words - 1;
440                 free(_line);
441                 return 0;
442         }
443
444         free(_line);
445         return -1;
446 }
447 #endif /* BROKEN_READLINE */
448
449 static char *complete_context_remove_extension(const char *line, const char *word, int pos,
450         int state)
451 {
452         char *ret = NULL;
453         int which = 0;
454
455 #ifdef BROKEN_READLINE
456         char *word2;
457         /*
458          * Fix arguments, *word is a new allocated structure, REMEMBER to
459          * free *word when you want to return from this function ...
460          */
461         if (fix_complete_args(line, &word2, &pos)) {
462                 ast_log(LOG_ERROR, "Out of free memory\n");
463                 return NULL;
464         }
465         word = word2;
466 #endif
467
468         if (pos == 2) { /* 'remove extension _X_' (exten@context ... */
469                 struct ast_context *c = NULL;
470                 char *context = NULL, *exten = NULL;
471                 int le = 0;     /* length of extension */
472                 int lc = 0;     /* length of context */
473
474                 lc = split_ec(word, &exten, &context);
475 #ifdef BROKEN_READLINE
476                 free(word2);
477 #endif
478                 if (lc) /* error */
479                         return NULL;
480                 le = strlen(exten);
481                 lc = strlen(context);
482
483                 if (ast_lock_contexts()) {
484                         ast_log(LOG_ERROR, "Failed to lock context list\n");
485                         goto error2;
486                 }
487
488                 /* find our context ... */
489                 while ( (c = ast_walk_contexts(c)) ) {  /* match our context if any */
490                         struct ast_exten *e = NULL;
491                         /* XXX locking ? */
492                         if (!partial_match(ast_get_context_name(c), context, lc))
493                                 continue;       /* context not matched */
494                         while ( (e = ast_walk_context_extensions(c, e)) ) { /* try to complete extensions ... */
495                                 if ( partial_match(ast_get_extension_name(e), exten, le) && ++which > state) { /* n-th match */
496                                         /* If there is an extension then return exten@context. XXX otherwise ? */
497                                         if (exten)
498                                                 asprintf(&ret, "%s@%s", ast_get_extension_name(e), ast_get_context_name(c));
499                                         break;
500                                 }
501                         }
502                         if (e)  /* got a match */
503                                 break;
504                 }
505
506                 ast_unlock_contexts();
507         error2:
508                 if (exten)
509                         free(exten);
510         } else if (pos == 3) { /* 'remove extension EXT _X_' (priority) */
511                 char *exten = NULL, *context, *p;
512                 struct ast_context *c;
513                 int le, lc, len;
514                 const char *s = skip_words(line, 2); /* skip 'remove' 'extension' */
515                 int i = split_ec(s, &exten, &context);  /* parse ext@context */
516
517                 if (i)  /* error */
518                         goto error3;
519                 if ( (p = strchr(exten, ' ')) ) /* remove space after extension */
520                         *p = '\0';
521                 if ( (p = strchr(context, ' ')) ) /* remove space after context */
522                         *p = '\0';
523                 le = strlen(exten);
524                 lc = strlen(context);
525                 len = strlen(word);
526                 if (le == 0 || lc == 0)
527                         goto error3;
528
529                 if (ast_lock_contexts()) {
530                         ast_log(LOG_ERROR, "Failed to lock context list\n");
531                         goto error3;
532                 }
533
534                 /* walk contexts */
535                 c = NULL;
536                 while ( (c = ast_walk_contexts(c)) ) {
537                         /* XXX locking on c ? */
538                         struct ast_exten *e;
539                         if (strcmp(ast_get_context_name(c), context) != 0)
540                                 continue;
541                         /* got it, we must match here */
542                         e = NULL;
543                         while ( (e = ast_walk_context_extensions(c, e)) ) {
544                                 struct ast_exten *priority;
545                                 char buffer[10];
546
547                                 if (strcmp(ast_get_extension_name(e), exten) != 0)
548                                         continue;
549                                 /* XXX lock e ? */
550                                 priority = NULL;
551                                 while ( !ret && (priority = ast_walk_extension_priorities(e, priority)) ) {
552                                         snprintf(buffer, sizeof(buffer), "%u", ast_get_extension_priority(priority));
553                                         if (partial_match(buffer, word, len) && ++which > state) /* n-th match */
554                                                 ret = strdup(buffer);
555                                 }
556                                 break;
557                         }
558                         break;
559                 }
560                 ast_unlock_contexts();
561         error3:
562                 if (exten)
563                         free(exten);
564 #ifdef BROKEN_READLINE
565                 free(word2);
566 #endif
567         }
568         return ret; 
569 }
570
571 /*!
572  * Include context ...
573  */
574 static int handle_context_add_include(int fd, int argc, char *argv[])
575 {
576         if (argc != 5) /* include context CTX in CTX */
577                 return RESULT_SHOWUSAGE;
578
579         /* third arg must be 'in' ... */
580         if (strcmp(argv[3], "in") && strcmp(argv[3], "into")) /* XXX why both ? */
581                 return RESULT_SHOWUSAGE;
582
583         if (ast_context_add_include(argv[4], argv[2], registrar)) {
584                 switch (errno) {
585                 case ENOMEM:
586                         ast_cli(fd, "Out of memory for context addition\n");
587                         break;
588
589                 case EBUSY:
590                         ast_cli(fd, "Failed to lock context(s) list, please try again later\n");
591                         break;
592
593                 case EEXIST:
594                         ast_cli(fd, "Context '%s' already included in '%s' context\n",
595                                 argv[2], argv[4]);
596                         break;
597
598                 case ENOENT:
599                 case EINVAL:
600                         ast_cli(fd, "There is no existence of context '%s'\n",
601                                 errno == ENOENT ? argv[4] : argv[2]);
602                         break;
603
604                 default:
605                         ast_cli(fd, "Failed to include '%s' in '%s' context\n",
606                                 argv[2], argv[4]);
607                         break;
608                 }
609                 return RESULT_FAILURE;
610         }
611
612         /* show some info ... */
613         ast_cli(fd, "Context '%s' included in '%s' context\n",
614                 argv[2], argv[4]);
615
616         return RESULT_SUCCESS;
617 }
618
619 static char *complete_context_add_include(const char *line, const char *word, int pos,
620     int state)
621 {
622         struct ast_context *c;
623         int which = 0;
624         char *ret = NULL;
625         int len = strlen(word);
626
627         if (pos == 2) {         /* 'include context _X_' (context) ... */
628                 if (ast_lock_contexts()) {
629                         ast_log(LOG_ERROR, "Failed to lock context list\n");
630                         return NULL;
631                 }
632                 for (c = NULL; !ret && (c = ast_walk_contexts(c)); )
633                         if (partial_match(ast_get_context_name(c), word, len) && ++which > state)
634                                 ret = strdup(ast_get_context_name(c));
635                 ast_unlock_contexts();
636                 return ret;
637         } else if (pos == 3) { /* include context CTX _X_ */
638                 /* complete  as 'in' if context exists or we are unable to check */
639                 char *context, *dupline;
640                 struct ast_context *c;
641                 const char *s = skip_words(line, 2);    /* should not fail */
642
643                 if (state != 0) /* only once */
644                         return NULL;
645
646                 /* parse context from line ... */
647                 context = dupline = strdup(s);
648                 if (!context) {
649                         ast_log(LOG_ERROR, "Out of free memory\n");
650                         return strdup("in");
651                 }
652                 strsep(&dupline, " ");
653
654                 /* check for context existence ... */
655                 if (ast_lock_contexts()) {
656                         ast_log(LOG_ERROR, "Failed to lock context list\n");
657                         /* our fault, we can't check, so complete 'in' ... */
658                         ret = strdup("in");
659                 } else {
660                         for (c = NULL; !ret && (c = ast_walk_contexts(c)); )
661                                 if (!strcmp(context, ast_get_context_name(c)))
662                                         ret = strdup("in"); /* found */
663                         ast_unlock_contexts();
664                 }
665                 free(context);
666                 return ret;
667         } else if (pos == 4) { /* 'include context CTX in _X_' (dst context) */
668                 char *context, *dupline, *in;
669                 const char *s = skip_words(line, 2); /* should not fail */
670                 context = dupline = strdup(s);
671                 if (!dupline) {
672                         ast_log(LOG_ERROR, "Out of free memory\n");
673                         return NULL;
674                 }
675                 strsep(&dupline, " "); /* skip context */
676                 in = strsep(&dupline, " ");
677                 /* error if missing context or third word is not 'in' */
678                 if (!strlen(context) || strcmp(in, "in")) {
679                         ast_log(LOG_ERROR, "bad context %s or missing in %s\n",
680                                 context, in);
681                         goto error3;
682                 }
683
684                 if (ast_lock_contexts()) {
685                         ast_log(LOG_ERROR, "Failed to lock context list\n");
686                         goto error3;
687                 }
688
689                 for (c = NULL; (c = ast_walk_contexts(c)); )
690                         if (!strcmp(context, ast_get_context_name(c)))
691                                 break;
692                 if (c) { /* first context exists, go on... */
693                         /* go through all contexts ... */
694                         for (c = NULL; !ret && (c = ast_walk_contexts(c)); ) {
695                                 if (!strcmp(context, ast_get_context_name(c)))
696                                         continue; /* skip ourselves */
697                                 if (partial_match(ast_get_context_name(c), word, len) &&
698                                                 !lookup_ci(c, context) /* not included yet */ &&
699                                                 ++which > state)
700                                         ret = strdup(ast_get_context_name(c));
701                         }
702                 } else {
703                         ast_log(LOG_ERROR, "context %s not found\n", context);
704                 }
705                 ast_unlock_contexts();
706         error3:
707                 free(context);
708                 return ret;
709         }
710
711         return NULL;
712 }
713
714 /*!
715  * \brief 'save dialplan' CLI command implementation functions ...
716  */
717 static int handle_save_dialplan(int fd, int argc, char *argv[])
718 {
719         char filename[256];
720         struct ast_context *c;
721         struct ast_config *cfg;
722         struct ast_variable *v;
723         int incomplete = 0; /* incomplete config write? */
724         FILE *output;
725
726         const char *base, *slash, *file;
727
728         if (! (static_config && !write_protect_config)) {
729                 ast_cli(fd,
730                         "I can't save dialplan now, see '%s' example file.\n",
731                         config);
732                 return RESULT_FAILURE;
733         }
734
735         if (argc != 2 && argc != 3)
736                 return RESULT_SHOWUSAGE;
737
738         if (ast_mutex_lock(&save_dialplan_lock)) {
739                 ast_cli(fd,
740                         "Failed to lock dialplan saving (another proccess saving?)\n");
741                 return RESULT_FAILURE;
742         }
743         /* XXX the code here is quite loose, a pathname with .conf in it
744          * is assumed to be a complete pathname
745          */
746         if (argc == 3) {        /* have config path. Look for *.conf */
747                 base = argv[2];
748                 if (!strstr(argv[2], ".conf")) { /*no, this is assumed to be a pathname */
749                         /* if filename ends with '/', do not add one */
750                         slash = (*(argv[2] + strlen(argv[2]) -1) == '/') ? "/" : "";
751                         file = config;  /* default: 'extensions.conf' */
752                 } else {        /* yes, complete file name */
753                         slash = "";
754                         file = "";
755                 }
756         } else {
757                 /* no config file, default one */
758                 base = ast_config_AST_CONFIG_DIR;
759                 slash = "/";
760                 file = config;
761         }
762         snprintf(filename, sizeof(filename), "%s%s%s", base, slash, config);
763
764         cfg = ast_config_load("extensions.conf");
765
766         /* try to lock contexts list */
767         if (ast_lock_contexts()) {
768                 ast_cli(fd, "Failed to lock contexts list\n");
769                 ast_mutex_unlock(&save_dialplan_lock);
770                 ast_config_destroy(cfg);
771                 return RESULT_FAILURE;
772         }
773
774         /* create new file ... */
775         if (!(output = fopen(filename, "wt"))) {
776                 ast_cli(fd, "Failed to create file '%s'\n",
777                         filename);
778                 ast_unlock_contexts();
779                 ast_mutex_unlock(&save_dialplan_lock);
780                 ast_config_destroy(cfg);
781                 return RESULT_FAILURE;
782         }
783
784         /* fireout general info */
785         fprintf(output, "[general]\nstatic=%s\nwriteprotect=%s\n\n",
786                 static_config ? "yes" : "no",
787                 write_protect_config ? "yes" : "no");
788
789         if ((v = ast_variable_browse(cfg, "globals"))) {
790                 fprintf(output, "[globals]\n");
791                 while(v) {
792                         fprintf(output, "%s => %s\n", v->name, v->value);
793                         v = v->next;
794                 }
795                 fprintf(output, "\n");
796         }
797
798         ast_config_destroy(cfg);
799         
800 #define PUT_CTX_HDR     do { \
801         if (!context_header_written) {  \
802                 fprintf(output, "[%s]\n", ast_get_context_name(c));     \
803                 context_header_written = 1;     \
804         }       \
805         } while (0)
806
807         /* walk all contexts */
808         for (c = NULL; (c = ast_walk_contexts(c)); ) {
809                 int context_header_written = 0;
810                 struct ast_exten *e, *last_written_e = NULL;
811                 struct ast_include *i;
812                 struct ast_ignorepat *ip;
813                 struct ast_sw *sw;
814
815                 /* try to lock context and fireout all info */  
816                 if (ast_lock_context(c)) { /* lock failure */
817                         incomplete = 1;
818                         continue;
819                 }
820                 /* registered by this module? */
821                 /* XXX do we need this ? */
822                 if (!strcmp(ast_get_context_registrar(c), registrar)) {
823                         fprintf(output, "[%s]\n", ast_get_context_name(c));
824                         context_header_written = 1;
825                 }
826
827                 /* walk extensions ... */
828                 for (e = NULL; (e = ast_walk_context_extensions(c, e)); ) {
829                         struct ast_exten *p = NULL;
830
831                         /* fireout priorities */
832                         while ( (p = ast_walk_extension_priorities(e, p)) ) {
833                                 if (strcmp(ast_get_extension_registrar(p), registrar) != 0) /* not this source */
834                                         continue;
835                 
836                                 /* make empty line between different extensions */      
837                                 if (last_written_e != NULL &&
838                                             strcmp(ast_get_extension_name(last_written_e),
839                                                     ast_get_extension_name(p)))
840                                         fprintf(output, "\n");
841                                 last_written_e = p;
842                         
843                                 PUT_CTX_HDR;
844
845                                 if (ast_get_extension_priority(p)==PRIORITY_HINT) { /* easy */
846                                         fprintf(output, "exten => %s,hint,%s\n",
847                                                     ast_get_extension_name(p),
848                                                     ast_get_extension_app(p));
849                                 } else { /* copy and replace '|' with ',' */
850                                         const char *sep, *cid;
851                                         char *tempdata = strdup(ast_get_extension_app_data(p));
852                                         char *s;
853
854                                         if (!tempdata) { /* XXX error duplicating string ? */
855                                                 incomplete = 1;
856                                                 continue;
857                                         }
858                                         for (s = tempdata; *s; s++)
859                                                 if (*s == '|')
860                                                         *s = ',';
861                                         if (ast_get_extension_matchcid(p)) {
862                                                 sep = "/";
863                                                 cid = ast_get_extension_cidmatch(p);
864                                         } else {
865                                                 sep = cid = "";
866                                         }
867                                         fprintf(output, "exten => %s%s%s,%d,%s(%s)\n",
868                                             ast_get_extension_name(p), sep, cid,
869                                             ast_get_extension_priority(p),
870                                             ast_get_extension_app(p), tempdata);
871                                         free(tempdata);
872                                 }
873                         }
874                 }
875
876                 /* written any extensions? ok, write space between exten & inc */
877                 if (last_written_e)
878                         fprintf(output, "\n");
879
880                 /* walk through includes */
881                 for (i = NULL; (i = ast_walk_context_includes(c, i)) ; ) {
882                         if (strcmp(ast_get_include_registrar(i), registrar) != 0)
883                                 continue; /* not mine */
884                         PUT_CTX_HDR;
885                         fprintf(output, "include => %s\n", ast_get_include_name(i));
886                 }
887                 if (ast_walk_context_includes(c, NULL))
888                         fprintf(output, "\n");
889
890                 /* walk through switches */
891                 for (sw = NULL; (sw = ast_walk_context_switches(c, sw)) ; ) {
892                         if (strcmp(ast_get_switch_registrar(sw), registrar) != 0)
893                                 continue; /* not mine */
894                         PUT_CTX_HDR;
895                         fprintf(output, "switch => %s/%s\n",
896                                     ast_get_switch_name(sw), ast_get_switch_data(sw));
897                 }
898
899                 if (ast_walk_context_switches(c, NULL))
900                         fprintf(output, "\n");
901
902                 /* fireout ignorepats ... */
903                 for (ip = NULL; (ip = ast_walk_context_ignorepats(c, ip)); ) {
904                         if (strcmp(ast_get_ignorepat_registrar(ip), registrar) != 0)
905                                 continue; /* not mine */
906                         PUT_CTX_HDR;
907                         fprintf(output, "ignorepat => %s\n",
908                                                 ast_get_ignorepat_name(ip));
909                 }
910
911                 ast_unlock_context(c);
912         }       
913
914         ast_unlock_contexts();
915         ast_mutex_unlock(&save_dialplan_lock);
916         fclose(output);
917
918         if (incomplete) {
919                 ast_cli(fd, "Saved dialplan is incomplete\n");
920                 return RESULT_FAILURE;
921         }
922
923         ast_cli(fd, "Dialplan successfully saved into '%s'\n",
924                 filename);
925         return RESULT_SUCCESS;
926 }
927
928 /*!
929  * \brief ADD EXTENSION command stuff
930  */
931 static int handle_context_add_extension(int fd, int argc, char *argv[])
932 {
933         char *whole_exten;
934         char *exten, *prior;
935         int iprior = -2;
936         char *cidmatch, *app, *app_data;
937         char *start, *end;
938
939         /* check for arguments at first */
940         if (argc != 5 && argc != 6)
941                 return RESULT_SHOWUSAGE;
942         if (strcmp(argv[3], "into"))
943                 return RESULT_SHOWUSAGE;
944         if (argc == 6) if (strcmp(argv[5], "replace")) return RESULT_SHOWUSAGE;
945
946         /* XXX overwrite argv[2] */
947         whole_exten = argv[2];
948         exten   = strsep(&whole_exten,",");
949         if (strchr(exten, '/')) {
950                 cidmatch = exten;
951                 strsep(&cidmatch,"/");
952         } else {
953                 cidmatch = NULL;
954         }
955         prior       = strsep(&whole_exten,",");
956         if (prior) {
957                 if (!strcmp(prior, "hint")) {
958                         iprior = PRIORITY_HINT;
959                 } else {
960                         if (sscanf(prior, "%d", &iprior) != 1) {
961                                 ast_cli(fd, "'%s' is not a valid priority\n", prior);
962                                 prior = NULL;
963                         }
964                 }
965         }
966         app = whole_exten;
967         if (app && (start = strchr(app, '(')) && (end = strrchr(app, ')'))) {
968                 *start = *end = '\0';
969                 app_data = start + 1;
970                 ast_process_quotes_and_slashes(app_data, ',', '|');
971         } else {
972                 if (app) {
973                         app_data = strchr(app, ',');
974                         if (app_data) {
975                                 *app_data = '\0';
976                                 app_data++;
977                         }
978                 } else  
979                         app_data = NULL;
980         }
981
982         if (!exten || !prior || !app || (!app_data && iprior != PRIORITY_HINT))
983                 return RESULT_SHOWUSAGE;
984
985         if (!app_data)
986                 app_data="";
987         if (ast_add_extension(argv[4], argc == 6 ? 1 : 0, exten, iprior, NULL, cidmatch, app,
988                 (void *)strdup(app_data), free, registrar)) {
989                 switch (errno) {
990                 case ENOMEM:
991                         ast_cli(fd, "Out of free memory\n");
992                         break;
993
994                 case EBUSY:
995                         ast_cli(fd, "Failed to lock context(s) list, please try again later\n");
996                         break;
997
998                 case ENOENT:
999                         ast_cli(fd, "No existence of '%s' context\n", argv[4]);
1000                         break;
1001
1002                 case EEXIST:
1003                         ast_cli(fd, "Extension %s@%s with priority %s already exists\n",
1004                                 exten, argv[4], prior);
1005                         break;
1006
1007                 default:
1008                         ast_cli(fd, "Failed to add '%s,%s,%s,%s' extension into '%s' context\n",
1009                                         exten, prior, app, app_data, argv[4]);
1010                         break;
1011                 }
1012                 return RESULT_FAILURE;
1013         }
1014
1015         if (argc == 6) 
1016                 ast_cli(fd, "Extension %s@%s (%s) replace by '%s,%s,%s,%s'\n",
1017                         exten, argv[4], prior, exten, prior, app, app_data);
1018         else
1019                 ast_cli(fd, "Extension '%s,%s,%s,%s' added into '%s' context\n",
1020                         exten, prior, app, app_data, argv[4]);
1021
1022         return RESULT_SUCCESS;
1023 }
1024
1025 /*! add extension 6123,1,Dial,IAX/212.71.138.13/6123 into local */
1026 static char *complete_context_add_extension(const char *line, const char *word,
1027         int pos, int state)
1028 {
1029         int which = 0;
1030
1031         if (pos == 3) {         /* complete 'into' word ... */
1032                 return (state == 0) ? strdup("into") : NULL;
1033         } else if (pos == 4) { /* complete context */
1034                 struct ast_context *c = NULL;
1035                 int len = strlen(word);
1036                 char *res = NULL;
1037
1038                 /* try to lock contexts list ... */
1039                 if (ast_lock_contexts()) {
1040                         ast_log(LOG_WARNING, "Failed to lock contexts list\n");
1041                         return NULL;
1042                 }
1043
1044                 /* walk through all contexts */
1045                 while ( !res && (c = ast_walk_contexts(c)) )
1046                         if (partial_match(ast_get_context_name(c), word, len) && ++which > state)
1047                                 res = strdup(ast_get_context_name(c));
1048                 ast_unlock_contexts();
1049                 return res;
1050         } else if (pos == 5) {
1051                 return state == 0 ? strdup("replace") : NULL;
1052         }
1053         return NULL;
1054 }
1055
1056 /*!
1057  * IGNOREPAT CLI stuff
1058  */
1059 static int handle_context_add_ignorepat(int fd, int argc, char *argv[])
1060 {
1061         if (argc != 5)
1062                 return RESULT_SHOWUSAGE;
1063         if (strcmp(argv[3], "into"))
1064                 return RESULT_SHOWUSAGE;
1065
1066         if (ast_context_add_ignorepat(argv[4], argv[2], registrar)) {
1067                 switch (errno) {
1068                 case ENOMEM:
1069                         ast_cli(fd, "Out of free memory\n");
1070                         break;
1071
1072                 case ENOENT:
1073                         ast_cli(fd, "There is no existence of '%s' context\n", argv[4]);
1074                         break;
1075
1076                 case EEXIST:
1077                         ast_cli(fd, "Ignore pattern '%s' already included in '%s' context\n",
1078                                 argv[2], argv[4]);
1079                         break;
1080
1081                 case EBUSY:
1082                         ast_cli(fd, "Failed to lock context(s) list, please, try again later\n");
1083                         break;
1084
1085                 default:
1086                         ast_cli(fd, "Failed to add ingore pattern '%s' into '%s' context\n",
1087                                 argv[2], argv[4]);
1088                         break;
1089                 }
1090                 return RESULT_FAILURE;
1091         }
1092
1093         ast_cli(fd, "Ignore pattern '%s' added into '%s' context\n",
1094                 argv[2], argv[4]);
1095         return RESULT_SUCCESS;
1096 }
1097
1098 static char *complete_context_add_ignorepat(const char *line, const char *word,
1099         int pos, int state)
1100 {
1101         if (pos == 3)
1102                 return state == 0 ? strdup("into") : NULL;
1103         else if (pos == 4) {
1104                 struct ast_context *c;
1105                 int which = 0;
1106                 char *dupline, *ignorepat = NULL;
1107                 const char *s;
1108                 char *ret = NULL;
1109                 int len = strlen(word);
1110
1111                 /* XXX skip first two words 'add' 'ignorepat' */
1112                 s = skip_words(line, 2);
1113                 if (s == NULL)
1114                         return NULL;
1115                 dupline = strdup(s);
1116                 if (!dupline) {
1117                         ast_log(LOG_ERROR, "Malloc failure\n");
1118                         return NULL;
1119                 }
1120                 ignorepat = strsep(&dupline, " ");
1121
1122                 if (ast_lock_contexts()) {
1123                         ast_log(LOG_ERROR, "Failed to lock contexts list\n");
1124                         return NULL;
1125                 }
1126
1127                 for (c = NULL; !ret && (c = ast_walk_contexts(c));) {
1128                         int found = 0;
1129
1130                         if (!partial_match(ast_get_context_name(c), word, len))
1131                                 continue; /* not mine */
1132                         if (ignorepat) /* there must be one, right ? */
1133                                 found = lookup_c_ip(c, ignorepat);
1134                         if (!found && ++which > state)
1135                                 ret = strdup(ast_get_context_name(c));
1136                 }
1137
1138                 if (ignorepat)
1139                         free(ignorepat);
1140                 ast_unlock_contexts();
1141                 return ret;
1142         }
1143
1144         return NULL;
1145 }
1146
1147 static int handle_context_remove_ignorepat(int fd, int argc, char *argv[])
1148 {
1149         if (argc != 5)
1150                 return RESULT_SHOWUSAGE;
1151         if (strcmp(argv[3], "from"))
1152                 return RESULT_SHOWUSAGE;
1153
1154         if (ast_context_remove_ignorepat(argv[4], argv[2], registrar)) {
1155                 switch (errno) {
1156                 case EBUSY:
1157                         ast_cli(fd, "Failed to lock context(s) list, please try again later\n");
1158                         break;
1159
1160                 case ENOENT:
1161                         ast_cli(fd, "There is no existence of '%s' context\n", argv[4]);
1162                         break;
1163
1164                 case EINVAL:
1165                         ast_cli(fd, "There is no existence of '%s' ignore pattern in '%s' context\n",
1166                                         argv[2], argv[4]);
1167                         break;
1168
1169                 default:
1170                         ast_cli(fd, "Failed to remove ignore pattern '%s' from '%s' context\n", argv[2], argv[4]);
1171                         break;
1172                 }
1173                 return RESULT_FAILURE;
1174         }
1175
1176         ast_cli(fd, "Ignore pattern '%s' removed from '%s' context\n",
1177                 argv[2], argv[4]);
1178         return RESULT_SUCCESS;
1179 }
1180
1181 static int pbx_load_module(void);
1182
1183 static int handle_reload_extensions(int fd, int argc, char *argv[])
1184 {
1185         if (argc!=2)
1186                 return RESULT_SHOWUSAGE;
1187         pbx_load_module();
1188         return RESULT_SUCCESS;
1189 }
1190
1191 static char *complete_context_remove_ignorepat(const char *line, const char *word,
1192         int pos, int state)
1193 {
1194         struct ast_context *c;
1195         int which = 0;
1196         char *ret = NULL;
1197
1198         if (pos == 2) {
1199                 int len = strlen(word);
1200                 if (ast_lock_contexts()) {
1201                         ast_log(LOG_WARNING, "Failed to lock contexts list\n");
1202                         return NULL;
1203                 }
1204
1205                 for (c = NULL; !ret && (c = ast_walk_contexts(c));) {
1206                         struct ast_ignorepat *ip;
1207
1208                         if (ast_lock_context(c))        /* error, skip it */
1209                                 continue;
1210                         
1211                         for (ip = NULL; !ret && (ip = ast_walk_context_ignorepats(c, ip));) {
1212                                 if (partial_match(ast_get_ignorepat_name(ip), word, len) && ++which > state) {
1213                                         /* n-th match */
1214                                         struct ast_context *cw = NULL;
1215                                         int found = 0;
1216                                         while ( (cw = ast_walk_contexts(cw)) && cw != c && !found) {
1217                                                 /* XXX do i stop on c, or skip it ? */
1218                                                 found = lookup_c_ip(cw, ast_get_ignorepat_name(ip));
1219                                         }
1220                                         if (!found)
1221                                                 ret = strdup(ast_get_ignorepat_name(ip));
1222                                 }
1223                         }
1224                         ast_unlock_context(c);
1225                 }
1226                 ast_unlock_contexts();
1227                 return ret;
1228         } else if (pos == 3) {
1229                  return state == 0 ? strdup("from") : NULL;
1230         } else if (pos == 4) { /* XXX check this */
1231                 char *dupline, *duplinet, *ignorepat;
1232                 int len = strlen(word);
1233
1234                 dupline = strdup(line);
1235                 if (!dupline) {
1236                         ast_log(LOG_WARNING, "Out of free memory\n");
1237                         return NULL;
1238                 }
1239
1240                 duplinet = dupline;
1241                 strsep(&duplinet, " ");
1242                 strsep(&duplinet, " ");
1243                 ignorepat = strsep(&duplinet, " ");
1244
1245                 if (!ignorepat) {
1246                         free(dupline);
1247                         return NULL;
1248                 }
1249
1250                 if (ast_lock_contexts()) {
1251                         ast_log(LOG_WARNING, "Failed to lock contexts list\n");
1252                         free(dupline);
1253                         return NULL;
1254                 }
1255
1256                 for (c = NULL; !ret && (c = ast_walk_contexts(c)); ) {
1257                         if (ast_lock_context(c))        /* fail, skip it */
1258                                 continue;
1259                         if (!partial_match(ast_get_context_name(c), word, len))
1260                                 continue;
1261                         if (lookup_c_ip(c, ignorepat) && ++which > state)
1262                                 ret = strdup(ast_get_context_name(c));
1263                         ast_unlock_context(c);
1264                 }
1265                 ast_unlock_contexts();
1266                 free(dupline);
1267                 return NULL;
1268         }
1269
1270         return NULL;
1271 }
1272
1273 /*!
1274  * CLI entries for commands provided by this module
1275  */
1276 static struct ast_cli_entry context_dont_include_cli =
1277         { { "dont", "include", NULL }, handle_context_dont_include,
1278                 "Remove a specified include from context", context_dont_include_help,
1279                 complete_context_dont_include };
1280
1281 static struct ast_cli_entry context_remove_extension_cli =
1282         { { "remove", "extension", NULL }, handle_context_remove_extension,
1283                 "Remove a specified extension", context_remove_extension_help,
1284                 complete_context_remove_extension };
1285
1286 static struct ast_cli_entry context_add_include_cli =
1287         { { "include", "context", NULL }, handle_context_add_include,
1288                 "Include context in other context", context_add_include_help,
1289                 complete_context_add_include };
1290
1291 static struct ast_cli_entry save_dialplan_cli =
1292         { { "save", "dialplan", NULL }, handle_save_dialplan,
1293                 "Save dialplan", save_dialplan_help };
1294
1295 static struct ast_cli_entry context_add_extension_cli =
1296         { { "add", "extension", NULL }, handle_context_add_extension,
1297                 "Add new extension into context", context_add_extension_help,
1298                 complete_context_add_extension };
1299
1300 static struct ast_cli_entry context_add_ignorepat_cli =
1301         { { "add", "ignorepat", NULL }, handle_context_add_ignorepat,
1302                 "Add new ignore pattern", context_add_ignorepat_help,
1303                 complete_context_add_ignorepat };
1304
1305 static struct ast_cli_entry context_remove_ignorepat_cli =
1306         { { "remove", "ignorepat", NULL }, handle_context_remove_ignorepat,
1307                 "Remove ignore pattern from context", context_remove_ignorepat_help,
1308                 complete_context_remove_ignorepat };
1309
1310 static struct ast_cli_entry reload_extensions_cli = 
1311         { { "extensions", "reload", NULL}, handle_reload_extensions,
1312                 "Reload extensions and *only* extensions", reload_extensions_help };
1313
1314 /*!
1315  * Standard module functions ...
1316  */
1317 static int unload_module(void)
1318 {
1319         ast_cli_unregister(&context_add_extension_cli);
1320         if (static_config && !write_protect_config)
1321                 ast_cli_unregister(&save_dialplan_cli);
1322         ast_cli_unregister(&context_add_include_cli);
1323         ast_cli_unregister(&context_dont_include_cli);
1324         ast_cli_unregister(&context_remove_extension_cli);
1325         ast_cli_unregister(&context_remove_ignorepat_cli);
1326         ast_cli_unregister(&context_add_ignorepat_cli);
1327         ast_cli_unregister(&reload_extensions_cli);
1328         ast_context_destroy(NULL, registrar);
1329         return 0;
1330 }
1331
1332 static void pbx_load_config(const char *config_file)
1333 {
1334         struct ast_config *cfg;
1335         char *end;
1336         char *label;
1337         char realvalue[256];
1338         int lastpri = -2;
1339         struct ast_context *con;
1340         struct ast_variable *v;
1341         char *cxt;
1342
1343         cfg = ast_config_load(config_file);
1344         if (!cfg)
1345                 return;
1346
1347         /* Use existing config to populate the PBX table */
1348         static_config = ast_true(ast_variable_retrieve(cfg, "general", "static"));
1349         write_protect_config = ast_true(ast_variable_retrieve(cfg, "general", "writeprotect"));
1350         autofallthrough_config = ast_true(ast_variable_retrieve(cfg, "general", "autofallthrough"));
1351         clearglobalvars_config = ast_true(ast_variable_retrieve(cfg, "general", "clearglobalvars"));
1352         ast_set2_flag(&ast_options, ast_true(ast_variable_retrieve(cfg, "general", "priorityjumping")), AST_OPT_FLAG_PRIORITY_JUMPING);
1353                                                                     
1354         for (v = ast_variable_browse(cfg, "globals"); v; v = v->next) {
1355                 memset(realvalue, 0, sizeof(realvalue));
1356                 pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1);
1357                 pbx_builtin_setvar_helper(NULL, v->name, realvalue);
1358         }
1359         for (cxt = NULL; (cxt = ast_category_browse(cfg, cxt)); ) {
1360
1361                 /* All categories but "general" or "globals" are considered contexts */
1362                 if (!strcasecmp(cxt, "general") || !strcasecmp(cxt, "globals"))
1363                         continue;
1364                 con=ast_context_create(&local_contexts,cxt, registrar);
1365                 if (con == NULL)
1366                         continue;
1367
1368                 for (v = ast_variable_browse(cfg, cxt); v; v = v->next) {
1369                         if (!strcasecmp(v->name, "exten")) {
1370                                 char *tc = ast_strdup(v->value);
1371                                 if (tc) {
1372                                         int ipri = -2;
1373                                         char realext[256]="";
1374                                         char *plus, *firstp, *firstc;
1375                                         char *pri, *appl, *data, *cidmatch;
1376                                         char *stringp = tc;
1377                                         char *ext = strsep(&stringp, ",");
1378                                         if (!ext)
1379                                                 ext="";
1380                                         pbx_substitute_variables_helper(NULL, ext, realext, sizeof(realext) - 1);
1381                                         cidmatch = strchr(realext, '/');
1382                                         if (cidmatch) {
1383                                                 *cidmatch++ = '\0';
1384                                                 ast_shrink_phone_number(cidmatch);
1385                                         }
1386                                         pri = strsep(&stringp, ",");
1387                                         if (!pri)
1388                                                 pri="";
1389                                         label = strchr(pri, '(');
1390                                         if (label) {
1391                                                 *label++ = '\0';
1392                                                 end = strchr(label, ')');
1393                                                 if (end)
1394                                                         *end = '\0';
1395                                                 else
1396                                                         ast_log(LOG_WARNING, "Label missing trailing ')' at line %d\n", v->lineno);
1397                                         }
1398                                         plus = strchr(pri, '+');
1399                                         if (plus)
1400                                                 *plus++ = '\0';
1401                                         if (!strcmp(pri,"hint"))
1402                                                 ipri=PRIORITY_HINT;
1403                                         else if (!strcmp(pri, "next") || !strcmp(pri, "n")) {
1404                                                 if (lastpri > -2)
1405                                                         ipri = lastpri + 1;
1406                                                 else
1407                                                         ast_log(LOG_WARNING, "Can't use 'next' priority on the first entry!\n");
1408                                         } else if (!strcmp(pri, "same") || !strcmp(pri, "s")) {
1409                                                 if (lastpri > -2)
1410                                                         ipri = lastpri;
1411                                                 else
1412                                                         ast_log(LOG_WARNING, "Can't use 'same' priority on the first entry!\n");
1413                                         } else if (sscanf(pri, "%d", &ipri) != 1 &&
1414                                             (ipri = ast_findlabel_extension2(NULL, con, realext, pri, cidmatch)) < 1) {
1415                                                 ast_log(LOG_WARNING, "Invalid priority/label '%s' at line %d\n", pri, v->lineno);
1416                                                 ipri = 0;
1417                                         }
1418                                         appl = S_OR(stringp, "");
1419                                         /* Find the first occurrence of either '(' or ',' */
1420                                         firstc = strchr(appl, ',');
1421                                         firstp = strchr(appl, '(');
1422                                         if (firstc && (!firstp || firstc < firstp)) {
1423                                                 /* comma found, no parenthesis */
1424                                                 /* or both found, but comma found first */
1425                                                 appl = strsep(&stringp, ",");
1426                                                 data = stringp;
1427                                         } else if (!firstc && !firstp) {
1428                                                 /* Neither found */
1429                                                 data = "";
1430                                         } else {
1431                                                 /* Final remaining case is parenthesis found first */
1432                                                 appl = strsep(&stringp, "(");
1433                                                 data = stringp;
1434                                                 end = strrchr(data, ')');
1435                                                 if ((end = strrchr(data, ')'))) {
1436                                                         *end = '\0';
1437                                                 } else {
1438                                                         ast_log(LOG_WARNING, "No closing parenthesis found? '%s(%s'\n", appl, data);
1439                                                 }
1440                                                 ast_process_quotes_and_slashes(data, ',', '|');
1441                                         }
1442
1443                                         if (!data)
1444                                                 data="";
1445                                         appl = ast_skip_blanks(appl);
1446                                         if (ipri) {
1447                                                 if (plus)
1448                                                         ipri += atoi(plus);
1449                                                 lastpri = ipri;
1450                                                 if (!ast_opt_dont_warn && !strcmp(realext, "_."))
1451                                                         ast_log(LOG_WARNING, "The use of '_.' for an extension is strongly discouraged and can have unexpected behavior.  Please use '_X.' instead at line %d\n", v->lineno);
1452                                                 if (ast_add_extension2(con, 0, realext, ipri, label, cidmatch, appl, strdup(data), ast_free, registrar)) {
1453                                                         ast_log(LOG_WARNING, "Unable to register extension at line %d\n", v->lineno);
1454                                                 }
1455                                         }
1456                                         free(tc);
1457                                 }
1458                         } else if (!strcasecmp(v->name, "include")) {
1459                                 memset(realvalue, 0, sizeof(realvalue));
1460                                 pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1);
1461                                 if (ast_context_add_include2(con, realvalue, registrar))
1462                                         ast_log(LOG_WARNING, "Unable to include context '%s' in context '%s'\n", v->value, cxt);
1463                         } else if (!strcasecmp(v->name, "ignorepat")) {
1464                                 memset(realvalue, 0, sizeof(realvalue));
1465                                 pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1);
1466                                 if (ast_context_add_ignorepat2(con, realvalue, registrar))
1467                                         ast_log(LOG_WARNING, "Unable to include ignorepat '%s' in context '%s'\n", v->value, cxt);
1468                         } else if (!strcasecmp(v->name, "switch") || !strcasecmp(v->name, "lswitch") || !strcasecmp(v->name, "eswitch")) {
1469                                 char *stringp= realvalue;
1470                                 char *appl, *data;
1471
1472                                 memset(realvalue, 0, sizeof(realvalue));
1473                                 if (!strcasecmp(v->name, "switch"))
1474                                         pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1);
1475                                 else
1476                                         ast_copy_string(realvalue, v->value, sizeof(realvalue));
1477                                 appl = strsep(&stringp, "/");
1478                                 data = strsep(&stringp, ""); /* XXX what for ? */
1479                                 if (!data)
1480                                         data = "";
1481                                 if (ast_context_add_switch2(con, appl, data, !strcasecmp(v->name, "eswitch"), registrar))
1482                                         ast_log(LOG_WARNING, "Unable to include switch '%s' in context '%s'\n", v->value, cxt);
1483                         }
1484                 }
1485         }
1486         ast_config_destroy(cfg);
1487 }
1488
1489 static int pbx_load_module(void)
1490 {
1491         struct ast_context *con;
1492
1493         pbx_load_config(config);
1494         ast_merge_contexts_and_delete(&local_contexts, registrar);
1495
1496         for (con = NULL; (con = ast_walk_contexts(con));)
1497                 ast_context_verify_includes(con);
1498
1499         pbx_set_autofallthrough(autofallthrough_config);
1500
1501         return 0;
1502 }
1503
1504 static int load_module(void)
1505 {
1506         if (pbx_load_module())
1507                 return -1;
1508  
1509         ast_cli_register(&context_remove_extension_cli);
1510         ast_cli_register(&context_dont_include_cli);
1511         ast_cli_register(&context_add_include_cli);
1512         if (static_config && !write_protect_config)
1513                 ast_cli_register(&save_dialplan_cli);
1514         ast_cli_register(&context_add_extension_cli);
1515         ast_cli_register(&context_add_ignorepat_cli);
1516         ast_cli_register(&context_remove_ignorepat_cli);
1517         ast_cli_register(&reload_extensions_cli);
1518
1519         return 0;
1520 }
1521
1522 static int reload(void)
1523 {
1524         if (clearglobalvars_config)
1525                 pbx_builtin_clear_globals();
1526         pbx_load_module();
1527         return 0;
1528 }
1529
1530 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Text Extension Configuration",
1531                 .load = load_module,
1532                 .unload = unload_module,
1533                 .reload = reload,
1534                );