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