Merged revisions 56856 via svnmerge from
[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 static char userscontext[AST_MAX_EXTENSION] = "default";
48
49 static int static_config = 0;
50 static int write_protect_config = 1;
51 static int autofallthrough_config = 1;
52 static int clearglobalvars_config = 0;
53
54 AST_MUTEX_DEFINE_STATIC(save_dialplan_lock);
55
56 static struct ast_context *local_contexts = NULL;
57
58 /*
59  * Help for commands provided by this module ...
60  */
61 static char context_add_extension_help[] =
62 "Usage: dialplan add extension <exten>,<priority>,<app>,<app-data>\n"
63 "       into <context> [replace]\n\n"
64 "       This command will add new extension into <context>. If there is an\n"
65 "       existence of extension with the same priority and last 'replace'\n"
66 "       arguments is given here we simply replace this extension.\n"
67 "\n"
68 "Example: dialplan add extension 6123,1,Dial,IAX/216.207.245.56/6123 into local\n"
69 "         Now, you can dial 6123 and talk to Markster :)\n";
70
71 static char context_remove_extension_help[] =
72 "Usage: dialplan remove extension exten@context [priority]\n"
73 "       Remove an extension from a given context. If a priority\n"
74 "       is given, only that specific priority from the given extension\n"
75 "       will be removed.\n";
76
77 static char context_add_ignorepat_help[] =
78 "Usage: dialplan add ignorepat <pattern> into <context>\n"
79 "       This command adds a new ignore pattern into context <context>\n"
80 "\n"
81 "Example: dialplan add ignorepat _3XX into local\n";
82
83 static char context_remove_ignorepat_help[] =
84 "Usage: dialplan remove ignorepat <pattern> from <context>\n"
85 "       This command removes an ignore pattern from context <context>\n"
86 "\n"
87 "Example: dialplan remove ignorepat _3XX from local\n";
88
89 static char context_add_include_help[] =
90 "Usage: dialplan add include <context> into <context>\n"
91 "       Include a context in another context.\n";
92
93 static char context_remove_include_help[] =
94 "Usage: dialplan remove include <context> from <context>\n"
95 "       Remove an included context from another context.\n";
96
97 static char save_dialplan_help[] =
98 "Usage: dialplan save [/path/to/extension/file]\n"
99 "       Save dialplan created by pbx_config module.\n"
100 "\n"
101 "Example: dialplan save                 (/etc/asterisk/extensions.conf)\n"
102 "         dialplan save /home/markster  (/home/markster/extensions.conf)\n";
103
104 static char reload_extensions_help[] =
105 "Usage: dialplan reload\n"
106 "       reload extensions.conf without reloading any other modules\n"
107 "       This command does not delete global variables unless\n"
108 "       clearglobalvars is set to yes in extensions.conf\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_remove_include(int fd, int argc, char *argv[])
118 {
119         if (argc != 6)
120                 return RESULT_SHOWUSAGE;
121
122         if (strcmp(argv[4], "into"))
123                 return RESULT_SHOWUSAGE;
124
125         if (!ast_context_remove_include(argv[5], argv[3], registrar)) {
126                 ast_cli(fd, "We are not including '%s' into '%s' now\n",
127                         argv[3], argv[5]);
128                 return RESULT_SUCCESS;
129         }
130
131         ast_cli(fd, "Failed to remove '%s' include from '%s' context\n",
132                 argv[3], argv[5]);
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_remove_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 == 3) {         /* "dialplan remove 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 == 4) { /* "dialplan remove include CTX _X_" */
256                 /*
257                  * complete as 'from', but only if previous context is really
258                  * included somewhere
259                  */
260                 char *context, *dupline;
261                 const char *s = skip_words(line, 3); /* skip 'dialplan' 'remove' '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 "from" command */
281                                 res = strdup("from");
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 == 5) { /* "dialplan remove include CTX from _X_" */
288                 /*
289                  * Context from which we removing include ... 
290                  */
291                 char *context, *dupline, *from;
292                 const char *s = skip_words(line, 3); /* skip 'dialplan' 'remove' '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                 /* fourth word must be 'from' */
302                 from = strsep(&dupline, " ");
303                 if (!from || strcmp(from, "from")) {
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 != 5 && argc != 4) return RESULT_SHOWUSAGE;
342
343         /*
344          * Priority input checking ...
345          */
346         if (argc == 5) {
347                 char *c = argv[4];
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[4]);
360                                 return RESULT_FAILURE;
361                         }
362                         removing_priority = atoi(argv[4]);
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[3] */
373         /*
374          * Format exten@context checking ...
375          */
376         if (split_ec(argv[3], &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 third argument '%s'\n",
380                         argv[3]);
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 == 3) { /* 'dialplan 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 == 4) { /* 'dialplan 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, 3); /* skip 'dialplan' '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 != 6) /* dialplan add include CTX in CTX */
577                 return RESULT_SHOWUSAGE;
578
579         /* fifth arg must be 'into' ... */
580         if (strcmp(argv[4], "into"))
581                 return RESULT_SHOWUSAGE;
582
583         if (ast_context_add_include(argv[5], argv[3], 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[3], argv[5]);
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[5] : argv[3]);
602                         break;
603
604                 default:
605                         ast_cli(fd, "Failed to include '%s' in '%s' context\n",
606                                 argv[3], argv[5]);
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[3], argv[5]);
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 == 3) {         /* 'dialplan add include _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 == 4) { /* dialplan add include CTX _X_ */
638                 /* complete  as 'into' 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, 3); /* 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("into");
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 'into' ... */
658                         ret = strdup("into");
659                 } else {
660                         for (c = NULL; !ret && (c = ast_walk_contexts(c)); )
661                                 if (!strcmp(context, ast_get_context_name(c)))
662                                         ret = strdup("into"); /* found */
663                         ast_unlock_contexts();
664                 }
665                 free(context);
666                 return ret;
667         } else if (pos == 5) { /* 'dialplan add include CTX into _X_' (dst context) */
668                 char *context, *dupline, *into;
669                 const char *s = skip_words(line, 3); /* 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                 into = strsep(&dupline, " ");
677                 /* error if missing context or fifth word is not 'into' */
678                 if (!strlen(context) || strcmp(into, "into")) {
679                         ast_log(LOG_ERROR, "bad context %s or missing into %s\n",
680                                 context, into);
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\nautofallthrough=%s\nclearglobalvars=%s\npriorityjumping=%s\n\n",
786                 static_config ? "yes" : "no",
787                 write_protect_config ? "yes" : "no",
788                 autofallthrough_config ? "yes" : "no",
789                 clearglobalvars_config ? "yes" : "no",
790                 ast_true(ast_variable_retrieve(cfg, "general", "priorityjumping")) ? "yes" : "no");
791
792         if ((v = ast_variable_browse(cfg, "globals"))) {
793                 fprintf(output, "[globals]\n");
794                 while(v) {
795                         fprintf(output, "%s => %s\n", v->name, v->value);
796                         v = v->next;
797                 }
798                 fprintf(output, "\n");
799         }
800
801         ast_config_destroy(cfg);
802         
803 #define PUT_CTX_HDR     do { \
804         if (!context_header_written) {  \
805                 fprintf(output, "[%s]\n", ast_get_context_name(c));     \
806                 context_header_written = 1;     \
807         }       \
808         } while (0)
809
810         /* walk all contexts */
811         for (c = NULL; (c = ast_walk_contexts(c)); ) {
812                 int context_header_written = 0;
813                 struct ast_exten *e, *last_written_e = NULL;
814                 struct ast_include *i;
815                 struct ast_ignorepat *ip;
816                 struct ast_sw *sw;
817
818                 /* try to lock context and fireout all info */  
819                 if (ast_lock_context(c)) { /* lock failure */
820                         incomplete = 1;
821                         continue;
822                 }
823                 /* registered by this module? */
824                 /* XXX do we need this ? */
825                 if (!strcmp(ast_get_context_registrar(c), registrar)) {
826                         fprintf(output, "[%s]\n", ast_get_context_name(c));
827                         context_header_written = 1;
828                 }
829
830                 /* walk extensions ... */
831                 for (e = NULL; (e = ast_walk_context_extensions(c, e)); ) {
832                         struct ast_exten *p = NULL;
833
834                         /* fireout priorities */
835                         while ( (p = ast_walk_extension_priorities(e, p)) ) {
836                                 if (strcmp(ast_get_extension_registrar(p), registrar) != 0) /* not this source */
837                                         continue;
838                 
839                                 /* make empty line between different extensions */      
840                                 if (last_written_e != NULL &&
841                                             strcmp(ast_get_extension_name(last_written_e),
842                                                     ast_get_extension_name(p)))
843                                         fprintf(output, "\n");
844                                 last_written_e = p;
845                         
846                                 PUT_CTX_HDR;
847
848                                 if (ast_get_extension_priority(p)==PRIORITY_HINT) { /* easy */
849                                         fprintf(output, "exten => %s,hint,%s\n",
850                                                     ast_get_extension_name(p),
851                                                     ast_get_extension_app(p));
852                                 } else { /* copy and replace '|' with ',' */
853                                         const char *sep, *cid;
854                                         char *tempdata;
855                                         char *s;
856                                         const char *el = ast_get_extension_label(p);
857                                         char label[128];
858  
859                                         tempdata = ast_strdupa(ast_get_extension_app_data(p));
860
861                                         for (s = tempdata; *s; s++) {
862                                                 if (*s == '|')
863                                                         *s = ',';
864                                         }
865
866                                         if (ast_get_extension_matchcid(p)) {
867                                                 sep = "/";
868                                                 cid = ast_get_extension_cidmatch(p);
869                                         } else
870                                                 sep = cid = "";
871                                 
872                                         if (el && (snprintf(label, 127, "(%s)", el) != (strlen(el) + 2)))
873                                                 incomplete = 1; /* error encountered or label > 125 chars */
874                                         
875                                         fprintf(output, "exten => %s%s%s,%d%s,%s(%s)\n",
876                                             ast_get_extension_name(p), sep, cid,
877                                             ast_get_extension_priority(p), label,
878                                             ast_get_extension_app(p), tempdata);
879                                 }
880                         }
881                 }
882
883                 /* written any extensions? ok, write space between exten & inc */
884                 if (last_written_e)
885                         fprintf(output, "\n");
886
887                 /* walk through includes */
888                 for (i = NULL; (i = ast_walk_context_includes(c, i)) ; ) {
889                         if (strcmp(ast_get_include_registrar(i), registrar) != 0)
890                                 continue; /* not mine */
891                         PUT_CTX_HDR;
892                         fprintf(output, "include => %s\n", ast_get_include_name(i));
893                 }
894                 if (ast_walk_context_includes(c, NULL))
895                         fprintf(output, "\n");
896
897                 /* walk through switches */
898                 for (sw = NULL; (sw = ast_walk_context_switches(c, sw)) ; ) {
899                         if (strcmp(ast_get_switch_registrar(sw), registrar) != 0)
900                                 continue; /* not mine */
901                         PUT_CTX_HDR;
902                         fprintf(output, "switch => %s/%s\n",
903                                     ast_get_switch_name(sw), ast_get_switch_data(sw));
904                 }
905
906                 if (ast_walk_context_switches(c, NULL))
907                         fprintf(output, "\n");
908
909                 /* fireout ignorepats ... */
910                 for (ip = NULL; (ip = ast_walk_context_ignorepats(c, ip)); ) {
911                         if (strcmp(ast_get_ignorepat_registrar(ip), registrar) != 0)
912                                 continue; /* not mine */
913                         PUT_CTX_HDR;
914                         fprintf(output, "ignorepat => %s\n",
915                                                 ast_get_ignorepat_name(ip));
916                 }
917
918                 ast_unlock_context(c);
919         }       
920
921         ast_unlock_contexts();
922         ast_mutex_unlock(&save_dialplan_lock);
923         fclose(output);
924
925         if (incomplete) {
926                 ast_cli(fd, "Saved dialplan is incomplete\n");
927                 return RESULT_FAILURE;
928         }
929
930         ast_cli(fd, "Dialplan successfully saved into '%s'\n",
931                 filename);
932         return RESULT_SUCCESS;
933 }
934
935 /*!
936  * \brief ADD EXTENSION command stuff
937  */
938 static int handle_context_add_extension(int fd, int argc, char *argv[])
939 {
940         char *whole_exten;
941         char *exten, *prior;
942         int iprior = -2;
943         char *cidmatch, *app, *app_data;
944         char *start, *end;
945
946         /* check for arguments at first */
947         if (argc != 6 && argc != 7)
948                 return RESULT_SHOWUSAGE;
949         if (strcmp(argv[4], "into"))
950                 return RESULT_SHOWUSAGE;
951         if (argc == 7) if (strcmp(argv[6], "replace")) return RESULT_SHOWUSAGE;
952
953         /* XXX overwrite argv[3] */
954         whole_exten = argv[3];
955         exten   = strsep(&whole_exten,",");
956         if (strchr(exten, '/')) {
957                 cidmatch = exten;
958                 strsep(&cidmatch,"/");
959         } else {
960                 cidmatch = NULL;
961         }
962         prior = strsep(&whole_exten,",");
963         if (prior) {
964                 if (!strcmp(prior, "hint")) {
965                         iprior = PRIORITY_HINT;
966                 } else {
967                         if (sscanf(prior, "%d", &iprior) != 1) {
968                                 ast_cli(fd, "'%s' is not a valid priority\n", prior);
969                                 prior = NULL;
970                         }
971                 }
972         }
973         app = whole_exten;
974         if (app && (start = strchr(app, '(')) && (end = strrchr(app, ')'))) {
975                 *start = *end = '\0';
976                 app_data = start + 1;
977                 ast_process_quotes_and_slashes(app_data, ',', '|');
978         } else {
979                 if (app) {
980                         app_data = strchr(app, ',');
981                         if (app_data) {
982                                 *app_data = '\0';
983                                 app_data++;
984                         }
985                 } else  
986                         app_data = NULL;
987         }
988
989         if (!exten || !prior || !app || (!app_data && iprior != PRIORITY_HINT))
990                 return RESULT_SHOWUSAGE;
991
992         if (!app_data)
993                 app_data="";
994         if (ast_add_extension(argv[5], argc == 7 ? 1 : 0, exten, iprior, NULL, cidmatch, app,
995                 (void *)strdup(app_data), free, registrar)) {
996                 switch (errno) {
997                 case ENOMEM:
998                         ast_cli(fd, "Out of free memory\n");
999                         break;
1000
1001                 case EBUSY:
1002                         ast_cli(fd, "Failed to lock context(s) list, please try again later\n");
1003                         break;
1004
1005                 case ENOENT:
1006                         ast_cli(fd, "No existence of '%s' context\n", argv[5]);
1007                         break;
1008
1009                 case EEXIST:
1010                         ast_cli(fd, "Extension %s@%s with priority %s already exists\n",
1011                                 exten, argv[5], prior);
1012                         break;
1013
1014                 default:
1015                         ast_cli(fd, "Failed to add '%s,%s,%s,%s' extension into '%s' context\n",
1016                                         exten, prior, app, app_data, argv[5]);
1017                         break;
1018                 }
1019                 return RESULT_FAILURE;
1020         }
1021
1022         if (argc == 7)
1023                 ast_cli(fd, "Extension %s@%s (%s) replace by '%s,%s,%s,%s'\n",
1024                         exten, argv[5], prior, exten, prior, app, app_data);
1025         else
1026                 ast_cli(fd, "Extension '%s,%s,%s,%s' added into '%s' context\n",
1027                         exten, prior, app, app_data, argv[5]);
1028
1029         return RESULT_SUCCESS;
1030 }
1031
1032 /*! dialplan add extension 6123,1,Dial,IAX/212.71.138.13/6123 into local */
1033 static char *complete_context_add_extension(const char *line, const char *word, int pos, int state)
1034 {
1035         int which = 0;
1036
1037         if (pos == 4) {         /* complete 'into' word ... */
1038                 return (state == 0) ? strdup("into") : NULL;
1039         } else if (pos == 5) { /* complete context */
1040                 struct ast_context *c = NULL;
1041                 int len = strlen(word);
1042                 char *res = NULL;
1043
1044                 /* try to lock contexts list ... */
1045                 if (ast_lock_contexts()) {
1046                         ast_log(LOG_WARNING, "Failed to lock contexts list\n");
1047                         return NULL;
1048                 }
1049
1050                 /* walk through all contexts */
1051                 while ( !res && (c = ast_walk_contexts(c)) )
1052                         if (partial_match(ast_get_context_name(c), word, len) && ++which > state)
1053                                 res = strdup(ast_get_context_name(c));
1054                 ast_unlock_contexts();
1055                 return res;
1056         } else if (pos == 6) {
1057                 return state == 0 ? strdup("replace") : NULL;
1058         }
1059         return NULL;
1060 }
1061
1062 /*!
1063  * IGNOREPAT CLI stuff
1064  */
1065 static int handle_context_add_ignorepat(int fd, int argc, char *argv[])
1066 {
1067         if (argc != 6)
1068                 return RESULT_SHOWUSAGE;
1069         if (strcmp(argv[4], "into"))
1070                 return RESULT_SHOWUSAGE;
1071
1072         if (ast_context_add_ignorepat(argv[5], argv[3], registrar)) {
1073                 switch (errno) {
1074                 case ENOMEM:
1075                         ast_cli(fd, "Out of free memory\n");
1076                         break;
1077
1078                 case ENOENT:
1079                         ast_cli(fd, "There is no existence of '%s' context\n", argv[5]);
1080                         break;
1081
1082                 case EEXIST:
1083                         ast_cli(fd, "Ignore pattern '%s' already included in '%s' context\n",
1084                                 argv[3], argv[5]);
1085                         break;
1086
1087                 case EBUSY:
1088                         ast_cli(fd, "Failed to lock context(s) list, please, try again later\n");
1089                         break;
1090
1091                 default:
1092                         ast_cli(fd, "Failed to add ingore pattern '%s' into '%s' context\n",
1093                                 argv[3], argv[5]);
1094                         break;
1095                 }
1096                 return RESULT_FAILURE;
1097         }
1098
1099         ast_cli(fd, "Ignore pattern '%s' added into '%s' context\n",
1100                 argv[3], argv[5]);
1101         return RESULT_SUCCESS;
1102 }
1103
1104 static char *complete_context_add_ignorepat(const char *line, const char *word,
1105         int pos, int state)
1106 {
1107         if (pos == 4)
1108                 return state == 0 ? strdup("into") : NULL;
1109         else if (pos == 5) {
1110                 struct ast_context *c;
1111                 int which = 0;
1112                 char *dupline, *ignorepat = NULL;
1113                 const char *s;
1114                 char *ret = NULL;
1115                 int len = strlen(word);
1116
1117                 /* XXX skip first three words 'dialplan' 'add' 'ignorepat' */
1118                 s = skip_words(line, 3);
1119                 if (s == NULL)
1120                         return NULL;
1121                 dupline = strdup(s);
1122                 if (!dupline) {
1123                         ast_log(LOG_ERROR, "Malloc failure\n");
1124                         return NULL;
1125                 }
1126                 ignorepat = strsep(&dupline, " ");
1127
1128                 if (ast_lock_contexts()) {
1129                         ast_log(LOG_ERROR, "Failed to lock contexts list\n");
1130                         return NULL;
1131                 }
1132
1133                 for (c = NULL; !ret && (c = ast_walk_contexts(c));) {
1134                         int found = 0;
1135
1136                         if (!partial_match(ast_get_context_name(c), word, len))
1137                                 continue; /* not mine */
1138                         if (ignorepat) /* there must be one, right ? */
1139                                 found = lookup_c_ip(c, ignorepat);
1140                         if (!found && ++which > state)
1141                                 ret = strdup(ast_get_context_name(c));
1142                 }
1143
1144                 if (ignorepat)
1145                         free(ignorepat);
1146                 ast_unlock_contexts();
1147                 return ret;
1148         }
1149
1150         return NULL;
1151 }
1152
1153 static int handle_context_remove_ignorepat(int fd, int argc, char *argv[])
1154 {
1155         if (argc != 6)
1156                 return RESULT_SHOWUSAGE;
1157         if (strcmp(argv[4], "from"))
1158                 return RESULT_SHOWUSAGE;
1159
1160         if (ast_context_remove_ignorepat(argv[5], argv[3], registrar)) {
1161                 switch (errno) {
1162                 case EBUSY:
1163                         ast_cli(fd, "Failed to lock context(s) list, please try again later\n");
1164                         break;
1165
1166                 case ENOENT:
1167                         ast_cli(fd, "There is no existence of '%s' context\n", argv[5]);
1168                         break;
1169
1170                 case EINVAL:
1171                         ast_cli(fd, "There is no existence of '%s' ignore pattern in '%s' context\n",
1172                                         argv[3], argv[5]);
1173                         break;
1174
1175                 default:
1176                         ast_cli(fd, "Failed to remove ignore pattern '%s' from '%s' context\n", argv[3], argv[5]);
1177                         break;
1178                 }
1179                 return RESULT_FAILURE;
1180         }
1181
1182         ast_cli(fd, "Ignore pattern '%s' removed from '%s' context\n",
1183                 argv[3], argv[5]);
1184         return RESULT_SUCCESS;
1185 }
1186
1187 static char *complete_context_remove_ignorepat(const char *line, const char *word,
1188         int pos, int state)
1189 {
1190         struct ast_context *c;
1191         int which = 0;
1192         char *ret = NULL;
1193
1194         if (pos == 3) {
1195                 int len = strlen(word);
1196                 if (ast_lock_contexts()) {
1197                         ast_log(LOG_WARNING, "Failed to lock contexts list\n");
1198                         return NULL;
1199                 }
1200
1201                 for (c = NULL; !ret && (c = ast_walk_contexts(c));) {
1202                         struct ast_ignorepat *ip;
1203
1204                         if (ast_lock_context(c))        /* error, skip it */
1205                                 continue;
1206                         
1207                         for (ip = NULL; !ret && (ip = ast_walk_context_ignorepats(c, ip));) {
1208                                 if (partial_match(ast_get_ignorepat_name(ip), word, len) && ++which > state) {
1209                                         /* n-th match */
1210                                         struct ast_context *cw = NULL;
1211                                         int found = 0;
1212                                         while ( (cw = ast_walk_contexts(cw)) && cw != c && !found) {
1213                                                 /* XXX do i stop on c, or skip it ? */
1214                                                 found = lookup_c_ip(cw, ast_get_ignorepat_name(ip));
1215                                         }
1216                                         if (!found)
1217                                                 ret = strdup(ast_get_ignorepat_name(ip));
1218                                 }
1219                         }
1220                         ast_unlock_context(c);
1221                 }
1222                 ast_unlock_contexts();
1223                 return ret;
1224         } else if (pos == 4) {
1225                  return state == 0 ? strdup("from") : NULL;
1226         } else if (pos == 5) { /* XXX check this */
1227                 char *dupline, *duplinet, *ignorepat;
1228                 int len = strlen(word);
1229
1230                 dupline = strdup(line);
1231                 if (!dupline) {
1232                         ast_log(LOG_WARNING, "Out of free memory\n");
1233                         return NULL;
1234                 }
1235
1236                 duplinet = dupline;
1237                 strsep(&duplinet, " ");
1238                 strsep(&duplinet, " ");
1239                 ignorepat = strsep(&duplinet, " ");
1240
1241                 if (!ignorepat) {
1242                         free(dupline);
1243                         return NULL;
1244                 }
1245
1246                 if (ast_lock_contexts()) {
1247                         ast_log(LOG_WARNING, "Failed to lock contexts list\n");
1248                         free(dupline);
1249                         return NULL;
1250                 }
1251
1252                 for (c = NULL; !ret && (c = ast_walk_contexts(c)); ) {
1253                         if (ast_lock_context(c))        /* fail, skip it */
1254                                 continue;
1255                         if (!partial_match(ast_get_context_name(c), word, len))
1256                                 continue;
1257                         if (lookup_c_ip(c, ignorepat) && ++which > state)
1258                                 ret = strdup(ast_get_context_name(c));
1259                         ast_unlock_context(c);
1260                 }
1261                 ast_unlock_contexts();
1262                 free(dupline);
1263                 return NULL;
1264         }
1265
1266         return NULL;
1267 }
1268
1269 static int pbx_load_module(void);
1270
1271 static int handle_reload_extensions(int fd, int argc, char *argv[])
1272 {
1273         if (argc != 2)
1274                 return RESULT_SHOWUSAGE;
1275         if (clearglobalvars_config)
1276                 pbx_builtin_clear_globals();
1277         pbx_load_module();
1278         return RESULT_SUCCESS;
1279 }
1280
1281 /*!
1282  * CLI entries for commands provided by this module
1283  */
1284 static struct ast_cli_entry cli_pbx_config[] = {
1285         { { "dialplan", "add", "extension", NULL },
1286         handle_context_add_extension, "Add new extension into context",
1287         context_add_extension_help, complete_context_add_extension },
1288
1289         { { "dialplan", "remove", "extension", NULL },
1290         handle_context_remove_extension, "Remove a specified extension",
1291         context_remove_extension_help, complete_context_remove_extension },
1292
1293         { { "dialplan", "add", "ignorepat", NULL },
1294         handle_context_add_ignorepat, "Add new ignore pattern",
1295         context_add_ignorepat_help, complete_context_add_ignorepat },
1296
1297         { { "dialplan", "remove", "ignorepat", NULL },
1298         handle_context_remove_ignorepat, "Remove ignore pattern from context",
1299         context_remove_ignorepat_help, complete_context_remove_ignorepat },
1300
1301         { { "dialplan", "add", "include", NULL },
1302         handle_context_add_include, "Include context in other context",
1303         context_add_include_help, complete_context_add_include },
1304
1305         { { "dialplan", "remove", "include", NULL },
1306         handle_context_remove_include, "Remove a specified include from context",
1307         context_remove_include_help, complete_context_remove_include },
1308
1309         { { "dialplan", "reload", NULL },
1310         handle_reload_extensions, "Reload extensions and *only* extensions",
1311         reload_extensions_help },
1312 };
1313
1314
1315 static struct ast_cli_entry cli_dialplan_save = {
1316         { "dialplan", "save", NULL },
1317         handle_save_dialplan, "Save dialplan",
1318         save_dialplan_help };
1319
1320 /*!
1321  * Standard module functions ...
1322  */
1323 static int unload_module(void)
1324 {
1325         if (static_config && !write_protect_config)
1326                 ast_cli_unregister(&cli_dialplan_save);
1327         ast_cli_unregister_multiple(cli_pbx_config, sizeof(cli_pbx_config) / sizeof(struct ast_cli_entry));
1328         ast_context_destroy(NULL, registrar);
1329         return 0;
1330 }
1331
1332 static int 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         const char *cxt;
1342         const char *aft;
1343
1344         cfg = ast_config_load(config_file);
1345         if (!cfg)
1346                 return 0;
1347
1348         /* Use existing config to populate the PBX table */
1349         static_config = ast_true(ast_variable_retrieve(cfg, "general", "static"));
1350         write_protect_config = ast_true(ast_variable_retrieve(cfg, "general", "writeprotect"));
1351         if ((aft = ast_variable_retrieve(cfg, "general", "autofallthrough")))
1352                 autofallthrough_config = ast_true(aft);
1353         clearglobalvars_config = ast_true(ast_variable_retrieve(cfg, "general", "clearglobalvars"));
1354         ast_set2_flag(&ast_options, ast_true(ast_variable_retrieve(cfg, "general", "priorityjumping")), AST_OPT_FLAG_PRIORITY_JUMPING);
1355
1356         if ((cxt = ast_variable_retrieve(cfg, "general", "userscontext"))) 
1357                 ast_copy_string(userscontext, cxt, sizeof(userscontext));
1358         else
1359                 ast_copy_string(userscontext, "default", sizeof(userscontext));
1360                                                                     
1361         for (v = ast_variable_browse(cfg, "globals"); v; v = v->next) {
1362                 memset(realvalue, 0, sizeof(realvalue));
1363                 pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1);
1364                 pbx_builtin_setvar_helper(NULL, v->name, realvalue);
1365         }
1366         for (cxt = NULL; (cxt = ast_category_browse(cfg, cxt)); ) {
1367                 /* All categories but "general" or "globals" are considered contexts */
1368                 if (!strcasecmp(cxt, "general") || !strcasecmp(cxt, "globals"))
1369                         continue;
1370                 con=ast_context_find_or_create(&local_contexts,cxt, registrar);
1371                 if (con == NULL)
1372                         continue;
1373
1374                 for (v = ast_variable_browse(cfg, cxt); v; v = v->next) {
1375                         if (!strcasecmp(v->name, "exten")) {
1376                                 char *tc = ast_strdup(v->value);
1377                                 if (tc) {
1378                                         int ipri = -2;
1379                                         char realext[256]="";
1380                                         char *plus, *firstp, *firstc;
1381                                         char *pri, *appl, *data, *cidmatch;
1382                                         char *stringp = tc;
1383                                         char *ext = strsep(&stringp, ",");
1384                                         if (!ext)
1385                                                 ext="";
1386                                         pbx_substitute_variables_helper(NULL, ext, realext, sizeof(realext) - 1);
1387                                         cidmatch = strchr(realext, '/');
1388                                         if (cidmatch) {
1389                                                 *cidmatch++ = '\0';
1390                                                 ast_shrink_phone_number(cidmatch);
1391                                         }
1392                                         pri = strsep(&stringp, ",");
1393                                         if (!pri)
1394                                                 pri="";
1395                                         label = strchr(pri, '(');
1396                                         if (label) {
1397                                                 *label++ = '\0';
1398                                                 end = strchr(label, ')');
1399                                                 if (end)
1400                                                         *end = '\0';
1401                                                 else
1402                                                         ast_log(LOG_WARNING, "Label missing trailing ')' at line %d\n", v->lineno);
1403                                         }
1404                                         plus = strchr(pri, '+');
1405                                         if (plus)
1406                                                 *plus++ = '\0';
1407                                         if (!strcmp(pri,"hint"))
1408                                                 ipri=PRIORITY_HINT;
1409                                         else if (!strcmp(pri, "next") || !strcmp(pri, "n")) {
1410                                                 if (lastpri > -2)
1411                                                         ipri = lastpri + 1;
1412                                                 else
1413                                                         ast_log(LOG_WARNING, "Can't use 'next' priority on the first entry!\n");
1414                                         } else if (!strcmp(pri, "same") || !strcmp(pri, "s")) {
1415                                                 if (lastpri > -2)
1416                                                         ipri = lastpri;
1417                                                 else
1418                                                         ast_log(LOG_WARNING, "Can't use 'same' priority on the first entry!\n");
1419                                         } else if (sscanf(pri, "%d", &ipri) != 1 &&
1420                                             (ipri = ast_findlabel_extension2(NULL, con, realext, pri, cidmatch)) < 1) {
1421                                                 ast_log(LOG_WARNING, "Invalid priority/label '%s' at line %d\n", pri, v->lineno);
1422                                                 ipri = 0;
1423                                         }
1424                                         appl = S_OR(stringp, "");
1425                                         /* Find the first occurrence of either '(' or ',' */
1426                                         firstc = strchr(appl, ',');
1427                                         firstp = strchr(appl, '(');
1428                                         if (firstc && (!firstp || firstc < firstp)) {
1429                                                 /* comma found, no parenthesis */
1430                                                 /* or both found, but comma found first */
1431                                                 appl = strsep(&stringp, ",");
1432                                                 data = stringp;
1433                                         } else if (!firstc && !firstp) {
1434                                                 /* Neither found */
1435                                                 data = "";
1436                                         } else {
1437                                                 /* Final remaining case is parenthesis found first */
1438                                                 appl = strsep(&stringp, "(");
1439                                                 data = stringp;
1440                                                 end = strrchr(data, ')');
1441                                                 if ((end = strrchr(data, ')'))) {
1442                                                         *end = '\0';
1443                                                 } else {
1444                                                         ast_log(LOG_WARNING, "No closing parenthesis found? '%s(%s'\n", appl, data);
1445                                                 }
1446                                                 ast_process_quotes_and_slashes(data, ',', '|');
1447                                         }
1448
1449                                         if (!data)
1450                                                 data="";
1451                                         appl = ast_skip_blanks(appl);
1452                                         if (ipri) {
1453                                                 if (plus)
1454                                                         ipri += atoi(plus);
1455                                                 lastpri = ipri;
1456                                                 if (!ast_opt_dont_warn && !strcmp(realext, "_."))
1457                                                         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);
1458                                                 if (ast_add_extension2(con, 0, realext, ipri, label, cidmatch, appl, strdup(data), ast_free, registrar)) {
1459                                                         ast_log(LOG_WARNING, "Unable to register extension at line %d\n", v->lineno);
1460                                                 }
1461                                         }
1462                                         free(tc);
1463                                 }
1464                         } else if (!strcasecmp(v->name, "include")) {
1465                                 memset(realvalue, 0, sizeof(realvalue));
1466                                 pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1);
1467                                 if (ast_context_add_include2(con, realvalue, registrar))
1468                                         ast_log(LOG_WARNING, "Unable to include context '%s' in context '%s'\n", v->value, cxt);
1469                         } else if (!strcasecmp(v->name, "ignorepat")) {
1470                                 memset(realvalue, 0, sizeof(realvalue));
1471                                 pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1);
1472                                 if (ast_context_add_ignorepat2(con, realvalue, registrar))
1473                                         ast_log(LOG_WARNING, "Unable to include ignorepat '%s' in context '%s'\n", v->value, cxt);
1474                         } else if (!strcasecmp(v->name, "switch") || !strcasecmp(v->name, "lswitch") || !strcasecmp(v->name, "eswitch")) {
1475                                 char *stringp= realvalue;
1476                                 char *appl, *data;
1477
1478                                 memset(realvalue, 0, sizeof(realvalue));
1479                                 if (!strcasecmp(v->name, "switch"))
1480                                         pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1);
1481                                 else
1482                                         ast_copy_string(realvalue, v->value, sizeof(realvalue));
1483                                 appl = strsep(&stringp, "/");
1484                                 data = strsep(&stringp, ""); /* XXX what for ? */
1485                                 if (!data)
1486                                         data = "";
1487                                 if (ast_context_add_switch2(con, appl, data, !strcasecmp(v->name, "eswitch"), registrar))
1488                                         ast_log(LOG_WARNING, "Unable to include switch '%s' in context '%s'\n", v->value, cxt);
1489                         } else {
1490                                 ast_log(LOG_WARNING, "==!!== Unknown directive: %s at line %d -- IGNORING!!!\n", v->name, v->lineno);
1491                         }
1492                 }
1493         }
1494         ast_config_destroy(cfg);
1495         return 1;
1496 }
1497
1498 static void append_interface(char *iface, int maxlen, char *add)
1499 {
1500         int len = strlen(iface);
1501         if (strlen(add) + len < maxlen - 2) {
1502                 if (strlen(iface)) {
1503                         iface[len] = '&';
1504                         strcpy(iface + len + 1, add);
1505                 } else
1506                         strcpy(iface, add);
1507         }
1508 }
1509
1510 static void pbx_load_users(void)
1511 {
1512         struct ast_config *cfg;
1513         char *cat, *chan;
1514         const char *zapchan;
1515         const char *hasexten;
1516         char tmp[256];
1517         char iface[256];
1518         char zapcopy[256];
1519         char *c;
1520         int len;
1521         int hasvoicemail;
1522         int start, finish, x;
1523         struct ast_context *con;
1524         
1525         cfg = ast_config_load("users.conf");
1526         if (!cfg)
1527                 return;
1528         con = ast_context_find_or_create(&local_contexts, userscontext, registrar);
1529         if (!con)
1530                 return;
1531
1532         for (cat = ast_category_browse(cfg, NULL); cat ; cat = ast_category_browse(cfg, cat)) {
1533                 if (!strcasecmp(cat, "general"))
1534                         continue;
1535                 iface[0] = '\0';
1536                 len = sizeof(iface);
1537                 if (ast_true(ast_config_option(cfg, cat, "hassip"))) {
1538                         snprintf(tmp, sizeof(tmp), "SIP/%s", cat);
1539                         append_interface(iface, sizeof(iface), tmp);
1540                 }
1541                 if (ast_true(ast_config_option(cfg, cat, "hasiax"))) {
1542                         snprintf(tmp, sizeof(tmp), "IAX2/%s", cat);
1543                         append_interface(iface, sizeof(iface), tmp);
1544                 }
1545                 if (ast_true(ast_config_option(cfg, cat, "hash323"))) {
1546                         snprintf(tmp, sizeof(tmp), "H323/%s", cat);
1547                         append_interface(iface, sizeof(iface), tmp);
1548                 }
1549                 hasexten = ast_config_option(cfg, cat, "hasexten");
1550                 if (hasexten && !ast_true(hasexten))
1551                         continue;
1552                 hasvoicemail = ast_true(ast_config_option(cfg, cat, "hasvoicemail"));
1553                 zapchan = ast_variable_retrieve(cfg, cat, "zapchan");
1554                 if (!zapchan)
1555                         zapchan = ast_variable_retrieve(cfg, "general", "zapchan");
1556                 if (!ast_strlen_zero(zapchan)) {
1557                         ast_copy_string(zapcopy, zapchan, sizeof(zapcopy));
1558                         c = zapcopy;
1559                         chan = strsep(&c, ",");
1560                         while (chan) {
1561                                 if (sscanf(chan, "%d-%d", &start, &finish) == 2) {
1562                                         /* Range */
1563                                 } else if (sscanf(chan, "%d", &start)) {
1564                                         /* Just one */
1565                                         finish = start;
1566                                 } else {
1567                                         start = 0; finish = 0;
1568                                 }
1569                                 if (finish < start) {
1570                                         x = finish;
1571                                         finish = start;
1572                                         start = x;
1573                                 }
1574                                 for (x = start; x <= finish; x++) {
1575                                         snprintf(tmp, sizeof(tmp), "Zap/%d", x);
1576                                         append_interface(iface, sizeof(iface), tmp);
1577                                 }
1578                                 chan = strsep(&c, ",");
1579                         }
1580                 }
1581                 if (!ast_strlen_zero(iface)) {
1582                         /* Add hint */
1583                         ast_add_extension2(con, 0, cat, -1, NULL, NULL, iface, NULL, NULL, registrar);
1584                         /* If voicemail, use "stdexten" else use plain old dial */
1585                         if (hasvoicemail) {
1586                                 snprintf(tmp, sizeof(tmp), "stdexten|%s|${HINT}", cat);
1587                                 ast_add_extension2(con, 0, cat, 1, NULL, NULL, "Macro", strdup(tmp), ast_free, registrar);
1588                         } else {
1589                                 ast_add_extension2(con, 0, cat, 1, NULL, NULL, "Dial", strdup("${HINT}"), ast_free, registrar);
1590                         }
1591                 }
1592         }
1593         ast_config_destroy(cfg);
1594 }
1595
1596 static int pbx_load_module(void)
1597 {
1598         struct ast_context *con;
1599
1600         if(!pbx_load_config(config))
1601                 return AST_MODULE_LOAD_DECLINE;
1602         
1603         pbx_load_users();
1604
1605         ast_merge_contexts_and_delete(&local_contexts, registrar);
1606
1607         for (con = NULL; (con = ast_walk_contexts(con));)
1608                 ast_context_verify_includes(con);
1609
1610         pbx_set_autofallthrough(autofallthrough_config);
1611
1612         return 0;
1613 }
1614
1615 static int load_module(void)
1616 {
1617         if (pbx_load_module())
1618                 return AST_MODULE_LOAD_DECLINE;
1619  
1620         if (static_config && !write_protect_config)
1621                 ast_cli_register(&cli_dialplan_save);
1622         ast_cli_register_multiple(cli_pbx_config, sizeof(cli_pbx_config) / sizeof(struct ast_cli_entry));
1623
1624         return 0;
1625 }
1626
1627 static int reload(void)
1628 {
1629         if (clearglobalvars_config)
1630                 pbx_builtin_clear_globals();
1631         pbx_load_module();
1632         return 0;
1633 }
1634
1635 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Text Extension Configuration",
1636                 .load = load_module,
1637                 .unload = unload_module,
1638                 .reload = reload,
1639                );