Merged revisions 68595 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_rdlock_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_rdlock_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_wrlock_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_rdlock_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_rdlock_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_rdlock_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_rdlock_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_rdlock_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_rdlock_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_rdlock_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_rdlock_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_rdlock_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_rdlock_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                                         s = ast_get_extension_app_data(p);
860                                         if (s) {
861                                                 char *t;
862                                                 tempdata = alloca(strlen(tempdata) * 2 + 1);
863
864                                                 for (t = tempdata; *s; s++, t++) {
865                                                         if (*s == '|')
866                                                                 *t = ',';
867                                                         else {
868                                                                 if (*s == ',')
869                                                                         *t++ = '\\';
870                                                                 *t = *s;
871                                                         }
872                                                 }
873                                                 /* Terminating NULL */
874                                                 *t = *s;
875                                         }
876
877                                         if (ast_get_extension_matchcid(p)) {
878                                                 sep = "/";
879                                                 cid = ast_get_extension_cidmatch(p);
880                                         } else
881                                                 sep = cid = "";
882                                 
883                                         if (el && (snprintf(label, sizeof(label), "(%s)", el) != (strlen(el) + 2)))
884                                                 incomplete = 1; /* error encountered or label > 125 chars */
885                                         
886                                         fprintf(output, "exten => %s%s%s,%d%s,%s(%s)\n",
887                                             ast_get_extension_name(p), sep, cid,
888                                             ast_get_extension_priority(p), label,
889                                             ast_get_extension_app(p), tempdata);
890                                 }
891                         }
892                 }
893
894                 /* written any extensions? ok, write space between exten & inc */
895                 if (last_written_e)
896                         fprintf(output, "\n");
897
898                 /* walk through includes */
899                 for (i = NULL; (i = ast_walk_context_includes(c, i)) ; ) {
900                         if (strcmp(ast_get_include_registrar(i), registrar) != 0)
901                                 continue; /* not mine */
902                         PUT_CTX_HDR;
903                         fprintf(output, "include => %s\n", ast_get_include_name(i));
904                 }
905                 if (ast_walk_context_includes(c, NULL))
906                         fprintf(output, "\n");
907
908                 /* walk through switches */
909                 for (sw = NULL; (sw = ast_walk_context_switches(c, sw)) ; ) {
910                         if (strcmp(ast_get_switch_registrar(sw), registrar) != 0)
911                                 continue; /* not mine */
912                         PUT_CTX_HDR;
913                         fprintf(output, "switch => %s/%s\n",
914                                     ast_get_switch_name(sw), ast_get_switch_data(sw));
915                 }
916
917                 if (ast_walk_context_switches(c, NULL))
918                         fprintf(output, "\n");
919
920                 /* fireout ignorepats ... */
921                 for (ip = NULL; (ip = ast_walk_context_ignorepats(c, ip)); ) {
922                         if (strcmp(ast_get_ignorepat_registrar(ip), registrar) != 0)
923                                 continue; /* not mine */
924                         PUT_CTX_HDR;
925                         fprintf(output, "ignorepat => %s\n",
926                                                 ast_get_ignorepat_name(ip));
927                 }
928
929                 ast_unlock_context(c);
930         }       
931
932         ast_unlock_contexts();
933         ast_mutex_unlock(&save_dialplan_lock);
934         fclose(output);
935
936         if (incomplete) {
937                 ast_cli(fd, "Saved dialplan is incomplete\n");
938                 return RESULT_FAILURE;
939         }
940
941         ast_cli(fd, "Dialplan successfully saved into '%s'\n",
942                 filename);
943         return RESULT_SUCCESS;
944 }
945
946 /*!
947  * \brief ADD EXTENSION command stuff
948  */
949 static int handle_context_add_extension(int fd, int argc, char *argv[])
950 {
951         char *whole_exten;
952         char *exten, *prior;
953         int iprior = -2;
954         char *cidmatch, *app, *app_data;
955         char *start, *end;
956
957         /* check for arguments at first */
958         if (argc != 6 && argc != 7)
959                 return RESULT_SHOWUSAGE;
960         if (strcmp(argv[4], "into"))
961                 return RESULT_SHOWUSAGE;
962         if (argc == 7) if (strcmp(argv[6], "replace")) return RESULT_SHOWUSAGE;
963
964         /* XXX overwrite argv[3] */
965         whole_exten = argv[3];
966         exten   = strsep(&whole_exten,",");
967         if (strchr(exten, '/')) {
968                 cidmatch = exten;
969                 strsep(&cidmatch,"/");
970         } else {
971                 cidmatch = NULL;
972         }
973         prior = strsep(&whole_exten,",");
974         if (prior) {
975                 if (!strcmp(prior, "hint")) {
976                         iprior = PRIORITY_HINT;
977                 } else {
978                         if (sscanf(prior, "%d", &iprior) != 1) {
979                                 ast_cli(fd, "'%s' is not a valid priority\n", prior);
980                                 prior = NULL;
981                         }
982                 }
983         }
984         app = whole_exten;
985         if (app && (start = strchr(app, '(')) && (end = strrchr(app, ')'))) {
986                 *start = *end = '\0';
987                 app_data = start + 1;
988                 ast_process_quotes_and_slashes(app_data, ',', '|');
989         } else {
990                 if (app) {
991                         app_data = strchr(app, ',');
992                         if (app_data) {
993                                 *app_data = '\0';
994                                 app_data++;
995                         }
996                 } else  
997                         app_data = NULL;
998         }
999
1000         if (!exten || !prior || !app || (!app_data && iprior != PRIORITY_HINT))
1001                 return RESULT_SHOWUSAGE;
1002
1003         if (!app_data)
1004                 app_data="";
1005         if (ast_add_extension(argv[5], argc == 7 ? 1 : 0, exten, iprior, NULL, cidmatch, app,
1006                 (void *)strdup(app_data), free, registrar)) {
1007                 switch (errno) {
1008                 case ENOMEM:
1009                         ast_cli(fd, "Out of free memory\n");
1010                         break;
1011
1012                 case EBUSY:
1013                         ast_cli(fd, "Failed to lock context(s) list, please try again later\n");
1014                         break;
1015
1016                 case ENOENT:
1017                         ast_cli(fd, "No existence of '%s' context\n", argv[5]);
1018                         break;
1019
1020                 case EEXIST:
1021                         ast_cli(fd, "Extension %s@%s with priority %s already exists\n",
1022                                 exten, argv[5], prior);
1023                         break;
1024
1025                 default:
1026                         ast_cli(fd, "Failed to add '%s,%s,%s,%s' extension into '%s' context\n",
1027                                         exten, prior, app, app_data, argv[5]);
1028                         break;
1029                 }
1030                 return RESULT_FAILURE;
1031         }
1032
1033         if (argc == 7)
1034                 ast_cli(fd, "Extension %s@%s (%s) replace by '%s,%s,%s,%s'\n",
1035                         exten, argv[5], prior, exten, prior, app, app_data);
1036         else
1037                 ast_cli(fd, "Extension '%s,%s,%s,%s' added into '%s' context\n",
1038                         exten, prior, app, app_data, argv[5]);
1039
1040         return RESULT_SUCCESS;
1041 }
1042
1043 /*! dialplan add extension 6123,1,Dial,IAX/212.71.138.13/6123 into local */
1044 static char *complete_context_add_extension(const char *line, const char *word, int pos, int state)
1045 {
1046         int which = 0;
1047
1048         if (pos == 4) {         /* complete 'into' word ... */
1049                 return (state == 0) ? strdup("into") : NULL;
1050         } else if (pos == 5) { /* complete context */
1051                 struct ast_context *c = NULL;
1052                 int len = strlen(word);
1053                 char *res = NULL;
1054
1055                 /* try to lock contexts list ... */
1056                 if (ast_rdlock_contexts()) {
1057                         ast_log(LOG_WARNING, "Failed to lock contexts list\n");
1058                         return NULL;
1059                 }
1060
1061                 /* walk through all contexts */
1062                 while ( !res && (c = ast_walk_contexts(c)) )
1063                         if (partial_match(ast_get_context_name(c), word, len) && ++which > state)
1064                                 res = strdup(ast_get_context_name(c));
1065                 ast_unlock_contexts();
1066                 return res;
1067         } else if (pos == 6) {
1068                 return state == 0 ? strdup("replace") : NULL;
1069         }
1070         return NULL;
1071 }
1072
1073 /*!
1074  * IGNOREPAT CLI stuff
1075  */
1076 static int handle_context_add_ignorepat(int fd, int argc, char *argv[])
1077 {
1078         if (argc != 6)
1079                 return RESULT_SHOWUSAGE;
1080         if (strcmp(argv[4], "into"))
1081                 return RESULT_SHOWUSAGE;
1082
1083         if (ast_context_add_ignorepat(argv[5], argv[3], registrar)) {
1084                 switch (errno) {
1085                 case ENOMEM:
1086                         ast_cli(fd, "Out of free memory\n");
1087                         break;
1088
1089                 case ENOENT:
1090                         ast_cli(fd, "There is no existence of '%s' context\n", argv[5]);
1091                         break;
1092
1093                 case EEXIST:
1094                         ast_cli(fd, "Ignore pattern '%s' already included in '%s' context\n",
1095                                 argv[3], argv[5]);
1096                         break;
1097
1098                 case EBUSY:
1099                         ast_cli(fd, "Failed to lock context(s) list, please, try again later\n");
1100                         break;
1101
1102                 default:
1103                         ast_cli(fd, "Failed to add ingore pattern '%s' into '%s' context\n",
1104                                 argv[3], argv[5]);
1105                         break;
1106                 }
1107                 return RESULT_FAILURE;
1108         }
1109
1110         ast_cli(fd, "Ignore pattern '%s' added into '%s' context\n",
1111                 argv[3], argv[5]);
1112         return RESULT_SUCCESS;
1113 }
1114
1115 static char *complete_context_add_ignorepat(const char *line, const char *word,
1116         int pos, int state)
1117 {
1118         if (pos == 4)
1119                 return state == 0 ? strdup("into") : NULL;
1120         else if (pos == 5) {
1121                 struct ast_context *c;
1122                 int which = 0;
1123                 char *dupline, *ignorepat = NULL;
1124                 const char *s;
1125                 char *ret = NULL;
1126                 int len = strlen(word);
1127
1128                 /* XXX skip first three words 'dialplan' 'add' 'ignorepat' */
1129                 s = skip_words(line, 3);
1130                 if (s == NULL)
1131                         return NULL;
1132                 dupline = strdup(s);
1133                 if (!dupline) {
1134                         ast_log(LOG_ERROR, "Malloc failure\n");
1135                         return NULL;
1136                 }
1137                 ignorepat = strsep(&dupline, " ");
1138
1139                 if (ast_rdlock_contexts()) {
1140                         ast_log(LOG_ERROR, "Failed to lock contexts list\n");
1141                         return NULL;
1142                 }
1143
1144                 for (c = NULL; !ret && (c = ast_walk_contexts(c));) {
1145                         int found = 0;
1146
1147                         if (!partial_match(ast_get_context_name(c), word, len))
1148                                 continue; /* not mine */
1149                         if (ignorepat) /* there must be one, right ? */
1150                                 found = lookup_c_ip(c, ignorepat);
1151                         if (!found && ++which > state)
1152                                 ret = strdup(ast_get_context_name(c));
1153                 }
1154
1155                 if (ignorepat)
1156                         free(ignorepat);
1157                 ast_unlock_contexts();
1158                 return ret;
1159         }
1160
1161         return NULL;
1162 }
1163
1164 static int handle_context_remove_ignorepat(int fd, int argc, char *argv[])
1165 {
1166         if (argc != 6)
1167                 return RESULT_SHOWUSAGE;
1168         if (strcmp(argv[4], "from"))
1169                 return RESULT_SHOWUSAGE;
1170
1171         if (ast_context_remove_ignorepat(argv[5], argv[3], registrar)) {
1172                 switch (errno) {
1173                 case EBUSY:
1174                         ast_cli(fd, "Failed to lock context(s) list, please try again later\n");
1175                         break;
1176
1177                 case ENOENT:
1178                         ast_cli(fd, "There is no existence of '%s' context\n", argv[5]);
1179                         break;
1180
1181                 case EINVAL:
1182                         ast_cli(fd, "There is no existence of '%s' ignore pattern in '%s' context\n",
1183                                         argv[3], argv[5]);
1184                         break;
1185
1186                 default:
1187                         ast_cli(fd, "Failed to remove ignore pattern '%s' from '%s' context\n", argv[3], argv[5]);
1188                         break;
1189                 }
1190                 return RESULT_FAILURE;
1191         }
1192
1193         ast_cli(fd, "Ignore pattern '%s' removed from '%s' context\n",
1194                 argv[3], argv[5]);
1195         return RESULT_SUCCESS;
1196 }
1197
1198 static char *complete_context_remove_ignorepat(const char *line, const char *word,
1199         int pos, int state)
1200 {
1201         struct ast_context *c;
1202         int which = 0;
1203         char *ret = NULL;
1204
1205         if (pos == 3) {
1206                 int len = strlen(word);
1207                 if (ast_rdlock_contexts()) {
1208                         ast_log(LOG_WARNING, "Failed to lock contexts list\n");
1209                         return NULL;
1210                 }
1211
1212                 for (c = NULL; !ret && (c = ast_walk_contexts(c));) {
1213                         struct ast_ignorepat *ip;
1214
1215                         if (ast_rdlock_context(c))      /* error, skip it */
1216                                 continue;
1217                         
1218                         for (ip = NULL; !ret && (ip = ast_walk_context_ignorepats(c, ip));) {
1219                                 if (partial_match(ast_get_ignorepat_name(ip), word, len) && ++which > state) {
1220                                         /* n-th match */
1221                                         struct ast_context *cw = NULL;
1222                                         int found = 0;
1223                                         while ( (cw = ast_walk_contexts(cw)) && cw != c && !found) {
1224                                                 /* XXX do i stop on c, or skip it ? */
1225                                                 found = lookup_c_ip(cw, ast_get_ignorepat_name(ip));
1226                                         }
1227                                         if (!found)
1228                                                 ret = strdup(ast_get_ignorepat_name(ip));
1229                                 }
1230                         }
1231                         ast_unlock_context(c);
1232                 }
1233                 ast_unlock_contexts();
1234                 return ret;
1235         } else if (pos == 4) {
1236                  return state == 0 ? strdup("from") : NULL;
1237         } else if (pos == 5) { /* XXX check this */
1238                 char *dupline, *duplinet, *ignorepat;
1239                 int len = strlen(word);
1240
1241                 dupline = strdup(line);
1242                 if (!dupline) {
1243                         ast_log(LOG_WARNING, "Out of free memory\n");
1244                         return NULL;
1245                 }
1246
1247                 duplinet = dupline;
1248                 strsep(&duplinet, " ");
1249                 strsep(&duplinet, " ");
1250                 ignorepat = strsep(&duplinet, " ");
1251
1252                 if (!ignorepat) {
1253                         free(dupline);
1254                         return NULL;
1255                 }
1256
1257                 if (ast_rdlock_contexts()) {
1258                         ast_log(LOG_WARNING, "Failed to lock contexts list\n");
1259                         free(dupline);
1260                         return NULL;
1261                 }
1262
1263                 for (c = NULL; !ret && (c = ast_walk_contexts(c)); ) {
1264                         if (ast_rdlock_context(c))      /* fail, skip it */
1265                                 continue;
1266                         if (!partial_match(ast_get_context_name(c), word, len))
1267                                 continue;
1268                         if (lookup_c_ip(c, ignorepat) && ++which > state)
1269                                 ret = strdup(ast_get_context_name(c));
1270                         ast_unlock_context(c);
1271                 }
1272                 ast_unlock_contexts();
1273                 free(dupline);
1274                 return NULL;
1275         }
1276
1277         return NULL;
1278 }
1279
1280 static int pbx_load_module(void);
1281
1282 static int handle_reload_extensions(int fd, int argc, char *argv[])
1283 {
1284         if (argc != 2)
1285                 return RESULT_SHOWUSAGE;
1286         if (clearglobalvars_config)
1287                 pbx_builtin_clear_globals();
1288         pbx_load_module();
1289         return RESULT_SUCCESS;
1290 }
1291
1292 /*!
1293  * CLI entries for commands provided by this module
1294  */
1295 static struct ast_cli_entry cli_pbx_config[] = {
1296         { { "dialplan", "add", "extension", NULL },
1297         handle_context_add_extension, "Add new extension into context",
1298         context_add_extension_help, complete_context_add_extension },
1299
1300         { { "dialplan", "remove", "extension", NULL },
1301         handle_context_remove_extension, "Remove a specified extension",
1302         context_remove_extension_help, complete_context_remove_extension },
1303
1304         { { "dialplan", "add", "ignorepat", NULL },
1305         handle_context_add_ignorepat, "Add new ignore pattern",
1306         context_add_ignorepat_help, complete_context_add_ignorepat },
1307
1308         { { "dialplan", "remove", "ignorepat", NULL },
1309         handle_context_remove_ignorepat, "Remove ignore pattern from context",
1310         context_remove_ignorepat_help, complete_context_remove_ignorepat },
1311
1312         { { "dialplan", "add", "include", NULL },
1313         handle_context_add_include, "Include context in other context",
1314         context_add_include_help, complete_context_add_include },
1315
1316         { { "dialplan", "remove", "include", NULL },
1317         handle_context_remove_include, "Remove a specified include from context",
1318         context_remove_include_help, complete_context_remove_include },
1319
1320         { { "dialplan", "reload", NULL },
1321         handle_reload_extensions, "Reload extensions and *only* extensions",
1322         reload_extensions_help },
1323 };
1324
1325
1326 static struct ast_cli_entry cli_dialplan_save = {
1327         { "dialplan", "save", NULL },
1328         handle_save_dialplan, "Save dialplan",
1329         save_dialplan_help };
1330
1331 /*!
1332  * Standard module functions ...
1333  */
1334 static int unload_module(void)
1335 {
1336         if (static_config && !write_protect_config)
1337                 ast_cli_unregister(&cli_dialplan_save);
1338         ast_cli_unregister_multiple(cli_pbx_config, sizeof(cli_pbx_config) / sizeof(struct ast_cli_entry));
1339         ast_context_destroy(NULL, registrar);
1340         return 0;
1341 }
1342
1343 static int pbx_load_config(const char *config_file)
1344 {
1345         struct ast_config *cfg;
1346         char *end;
1347         char *label;
1348         char realvalue[256];
1349         int lastpri = -2;
1350         struct ast_context *con;
1351         struct ast_variable *v;
1352         const char *cxt;
1353         const char *aft;
1354
1355         cfg = ast_config_load(config_file);
1356         if (!cfg)
1357                 return 0;
1358
1359         /* Use existing config to populate the PBX table */
1360         static_config = ast_true(ast_variable_retrieve(cfg, "general", "static"));
1361         write_protect_config = ast_true(ast_variable_retrieve(cfg, "general", "writeprotect"));
1362         if ((aft = ast_variable_retrieve(cfg, "general", "autofallthrough")))
1363                 autofallthrough_config = ast_true(aft);
1364         clearglobalvars_config = ast_true(ast_variable_retrieve(cfg, "general", "clearglobalvars"));
1365         ast_set2_flag(&ast_options, ast_true(ast_variable_retrieve(cfg, "general", "priorityjumping")), AST_OPT_FLAG_PRIORITY_JUMPING);
1366
1367         if ((cxt = ast_variable_retrieve(cfg, "general", "userscontext"))) 
1368                 ast_copy_string(userscontext, cxt, sizeof(userscontext));
1369         else
1370                 ast_copy_string(userscontext, "default", sizeof(userscontext));
1371                                                                     
1372         for (v = ast_variable_browse(cfg, "globals"); v; v = v->next) {
1373                 memset(realvalue, 0, sizeof(realvalue));
1374                 pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1);
1375                 pbx_builtin_setvar_helper(NULL, v->name, realvalue);
1376         }
1377         for (cxt = NULL; (cxt = ast_category_browse(cfg, cxt)); ) {
1378                 /* All categories but "general" or "globals" are considered contexts */
1379                 if (!strcasecmp(cxt, "general") || !strcasecmp(cxt, "globals"))
1380                         continue;
1381                 con=ast_context_find_or_create(&local_contexts,cxt, registrar);
1382                 if (con == NULL)
1383                         continue;
1384
1385                 for (v = ast_variable_browse(cfg, cxt); v; v = v->next) {
1386                         if (!strcasecmp(v->name, "exten")) {
1387                                 char *tc = ast_strdup(v->value);
1388                                 if (tc) {
1389                                         int ipri = -2;
1390                                         char realext[256]="";
1391                                         char *plus, *firstp, *firstc;
1392                                         char *pri, *appl, *data, *cidmatch;
1393                                         char *stringp = tc;
1394                                         char *ext = strsep(&stringp, ",");
1395                                         if (!ext)
1396                                                 ext="";
1397                                         pbx_substitute_variables_helper(NULL, ext, realext, sizeof(realext) - 1);
1398                                         cidmatch = strchr(realext, '/');
1399                                         if (cidmatch) {
1400                                                 *cidmatch++ = '\0';
1401                                                 ast_shrink_phone_number(cidmatch);
1402                                         }
1403                                         pri = strsep(&stringp, ",");
1404                                         if (!pri)
1405                                                 pri="";
1406                                         label = strchr(pri, '(');
1407                                         if (label) {
1408                                                 *label++ = '\0';
1409                                                 end = strchr(label, ')');
1410                                                 if (end)
1411                                                         *end = '\0';
1412                                                 else
1413                                                         ast_log(LOG_WARNING, "Label missing trailing ')' at line %d\n", v->lineno);
1414                                         }
1415                                         plus = strchr(pri, '+');
1416                                         if (plus)
1417                                                 *plus++ = '\0';
1418                                         if (!strcmp(pri,"hint"))
1419                                                 ipri=PRIORITY_HINT;
1420                                         else if (!strcmp(pri, "next") || !strcmp(pri, "n")) {
1421                                                 if (lastpri > -2)
1422                                                         ipri = lastpri + 1;
1423                                                 else
1424                                                         ast_log(LOG_WARNING, "Can't use 'next' priority on the first entry!\n");
1425                                         } else if (!strcmp(pri, "same") || !strcmp(pri, "s")) {
1426                                                 if (lastpri > -2)
1427                                                         ipri = lastpri;
1428                                                 else
1429                                                         ast_log(LOG_WARNING, "Can't use 'same' priority on the first entry!\n");
1430                                         } else if (sscanf(pri, "%d", &ipri) != 1 &&
1431                                             (ipri = ast_findlabel_extension2(NULL, con, realext, pri, cidmatch)) < 1) {
1432                                                 ast_log(LOG_WARNING, "Invalid priority/label '%s' at line %d\n", pri, v->lineno);
1433                                                 ipri = 0;
1434                                         }
1435                                         appl = S_OR(stringp, "");
1436                                         /* Find the first occurrence of either '(' or ',' */
1437                                         firstc = strchr(appl, ',');
1438                                         firstp = strchr(appl, '(');
1439                                         if (firstc && (!firstp || firstc < firstp)) {
1440                                                 /* comma found, no parenthesis */
1441                                                 /* or both found, but comma found first */
1442                                                 appl = strsep(&stringp, ",");
1443                                                 data = stringp;
1444                                         } else if (!firstc && !firstp) {
1445                                                 /* Neither found */
1446                                                 data = "";
1447                                         } else {
1448                                                 /* Final remaining case is parenthesis found first */
1449                                                 appl = strsep(&stringp, "(");
1450                                                 data = stringp;
1451                                                 end = strrchr(data, ')');
1452                                                 if ((end = strrchr(data, ')'))) {
1453                                                         *end = '\0';
1454                                                 } else {
1455                                                         ast_log(LOG_WARNING, "No closing parenthesis found? '%s(%s'\n", appl, data);
1456                                                 }
1457                                                 ast_process_quotes_and_slashes(data, ',', '|');
1458                                         }
1459
1460                                         if (!data)
1461                                                 data="";
1462                                         appl = ast_skip_blanks(appl);
1463                                         if (ipri) {
1464                                                 if (plus)
1465                                                         ipri += atoi(plus);
1466                                                 lastpri = ipri;
1467                                                 if (!ast_opt_dont_warn && !strcmp(realext, "_."))
1468                                                         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);
1469                                                 if (ast_add_extension2(con, 0, realext, ipri, label, cidmatch, appl, strdup(data), ast_free, registrar)) {
1470                                                         ast_log(LOG_WARNING, "Unable to register extension at line %d\n", v->lineno);
1471                                                 }
1472                                         }
1473                                         free(tc);
1474                                 }
1475                         } else if (!strcasecmp(v->name, "include")) {
1476                                 memset(realvalue, 0, sizeof(realvalue));
1477                                 pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1);
1478                                 if (ast_context_add_include2(con, realvalue, registrar))
1479                                         ast_log(LOG_WARNING, "Unable to include context '%s' in context '%s'\n", v->value, cxt);
1480                         } else if (!strcasecmp(v->name, "ignorepat")) {
1481                                 memset(realvalue, 0, sizeof(realvalue));
1482                                 pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1);
1483                                 if (ast_context_add_ignorepat2(con, realvalue, registrar))
1484                                         ast_log(LOG_WARNING, "Unable to include ignorepat '%s' in context '%s'\n", v->value, cxt);
1485                         } else if (!strcasecmp(v->name, "switch") || !strcasecmp(v->name, "lswitch") || !strcasecmp(v->name, "eswitch")) {
1486                                 char *stringp= realvalue;
1487                                 char *appl, *data;
1488
1489                                 memset(realvalue, 0, sizeof(realvalue));
1490                                 if (!strcasecmp(v->name, "switch"))
1491                                         pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1);
1492                                 else
1493                                         ast_copy_string(realvalue, v->value, sizeof(realvalue));
1494                                 appl = strsep(&stringp, "/");
1495                                 data = strsep(&stringp, ""); /* XXX what for ? */
1496                                 if (!data)
1497                                         data = "";
1498                                 if (ast_context_add_switch2(con, appl, data, !strcasecmp(v->name, "eswitch"), registrar))
1499                                         ast_log(LOG_WARNING, "Unable to include switch '%s' in context '%s'\n", v->value, cxt);
1500                         } else {
1501                                 ast_log(LOG_WARNING, "==!!== Unknown directive: %s at line %d -- IGNORING!!!\n", v->name, v->lineno);
1502                         }
1503                 }
1504         }
1505         ast_config_destroy(cfg);
1506         return 1;
1507 }
1508
1509 static void append_interface(char *iface, int maxlen, char *add)
1510 {
1511         int len = strlen(iface);
1512         if (strlen(add) + len < maxlen - 2) {
1513                 if (strlen(iface)) {
1514                         iface[len] = '&';
1515                         strcpy(iface + len + 1, add);
1516                 } else
1517                         strcpy(iface, add);
1518         }
1519 }
1520
1521 static void pbx_load_users(void)
1522 {
1523         struct ast_config *cfg;
1524         char *cat, *chan;
1525         const char *zapchan;
1526         const char *hasexten;
1527         char tmp[256];
1528         char iface[256];
1529         char zapcopy[256];
1530         char *c;
1531         int len;
1532         int hasvoicemail;
1533         int start, finish, x;
1534         struct ast_context *con;
1535         
1536         cfg = ast_config_load("users.conf");
1537         if (!cfg)
1538                 return;
1539         con = ast_context_find_or_create(&local_contexts, userscontext, registrar);
1540         if (!con)
1541                 return;
1542
1543         for (cat = ast_category_browse(cfg, NULL); cat ; cat = ast_category_browse(cfg, cat)) {
1544                 if (!strcasecmp(cat, "general"))
1545                         continue;
1546                 iface[0] = '\0';
1547                 len = sizeof(iface);
1548                 if (ast_true(ast_config_option(cfg, cat, "hassip"))) {
1549                         snprintf(tmp, sizeof(tmp), "SIP/%s", cat);
1550                         append_interface(iface, sizeof(iface), tmp);
1551                 }
1552                 if (ast_true(ast_config_option(cfg, cat, "hasiax"))) {
1553                         snprintf(tmp, sizeof(tmp), "IAX2/%s", cat);
1554                         append_interface(iface, sizeof(iface), tmp);
1555                 }
1556                 if (ast_true(ast_config_option(cfg, cat, "hash323"))) {
1557                         snprintf(tmp, sizeof(tmp), "H323/%s", cat);
1558                         append_interface(iface, sizeof(iface), tmp);
1559                 }
1560                 hasexten = ast_config_option(cfg, cat, "hasexten");
1561                 if (hasexten && !ast_true(hasexten))
1562                         continue;
1563                 hasvoicemail = ast_true(ast_config_option(cfg, cat, "hasvoicemail"));
1564                 zapchan = ast_variable_retrieve(cfg, cat, "zapchan");
1565                 if (!zapchan)
1566                         zapchan = ast_variable_retrieve(cfg, "general", "zapchan");
1567                 if (!ast_strlen_zero(zapchan)) {
1568                         ast_copy_string(zapcopy, zapchan, sizeof(zapcopy));
1569                         c = zapcopy;
1570                         chan = strsep(&c, ",");
1571                         while (chan) {
1572                                 if (sscanf(chan, "%d-%d", &start, &finish) == 2) {
1573                                         /* Range */
1574                                 } else if (sscanf(chan, "%d", &start)) {
1575                                         /* Just one */
1576                                         finish = start;
1577                                 } else {
1578                                         start = 0; finish = 0;
1579                                 }
1580                                 if (finish < start) {
1581                                         x = finish;
1582                                         finish = start;
1583                                         start = x;
1584                                 }
1585                                 for (x = start; x <= finish; x++) {
1586                                         snprintf(tmp, sizeof(tmp), "Zap/%d", x);
1587                                         append_interface(iface, sizeof(iface), tmp);
1588                                 }
1589                                 chan = strsep(&c, ",");
1590                         }
1591                 }
1592                 if (!ast_strlen_zero(iface)) {
1593                         /* Add hint */
1594                         ast_add_extension2(con, 0, cat, -1, NULL, NULL, iface, NULL, NULL, registrar);
1595                         /* If voicemail, use "stdexten" else use plain old dial */
1596                         if (hasvoicemail) {
1597                                 snprintf(tmp, sizeof(tmp), "stdexten|%s|${HINT}", cat);
1598                                 ast_add_extension2(con, 0, cat, 1, NULL, NULL, "Macro", strdup(tmp), ast_free, registrar);
1599                         } else {
1600                                 ast_add_extension2(con, 0, cat, 1, NULL, NULL, "Dial", strdup("${HINT}"), ast_free, registrar);
1601                         }
1602                 }
1603         }
1604         ast_config_destroy(cfg);
1605 }
1606
1607 static int pbx_load_module(void)
1608 {
1609         struct ast_context *con;
1610
1611         if(!pbx_load_config(config))
1612                 return AST_MODULE_LOAD_DECLINE;
1613         
1614         pbx_load_users();
1615
1616         ast_merge_contexts_and_delete(&local_contexts, registrar);
1617
1618         for (con = NULL; (con = ast_walk_contexts(con));)
1619                 ast_context_verify_includes(con);
1620
1621         pbx_set_autofallthrough(autofallthrough_config);
1622
1623         return 0;
1624 }
1625
1626 static int load_module(void)
1627 {
1628         if (pbx_load_module())
1629                 return AST_MODULE_LOAD_DECLINE;
1630  
1631         if (static_config && !write_protect_config)
1632                 ast_cli_register(&cli_dialplan_save);
1633         ast_cli_register_multiple(cli_pbx_config, sizeof(cli_pbx_config) / sizeof(struct ast_cli_entry));
1634
1635         return 0;
1636 }
1637
1638 static int reload(void)
1639 {
1640         if (clearglobalvars_config)
1641                 pbx_builtin_clear_globals();
1642         pbx_load_module();
1643         return 0;
1644 }
1645
1646 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Text Extension Configuration",
1647                 .load = load_module,
1648                 .unload = unload_module,
1649                 .reload = reload,
1650                );