This solves an unreported solaris compile problem (missing -lnsl -lsocket).
[asterisk/asterisk.git] / main / config.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, 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 Configuration File Parser
22  *
23  * \author Mark Spencer <markster@digium.com>
24  *
25  * Includes the Asterisk Realtime API - ARA
26  * See doc/realtime.txt and doc/extconfig.txt
27  */
28
29 #include "asterisk.h"
30
31 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
32
33 #include <stdio.h>
34 #include <unistd.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <errno.h>
38 #include <time.h>
39 #include <sys/stat.h>
40 #include <sys/socket.h>         /* for AF_INET */
41 #define AST_INCLUDE_GLOB 1
42 #ifdef AST_INCLUDE_GLOB
43 #if defined(__Darwin__) || defined(__CYGWIN__)
44 #define GLOB_ABORTED GLOB_ABEND
45 #endif
46 # include <glob.h>
47 #endif
48
49 #include "asterisk/config.h"
50 #include "asterisk/cli.h"
51 #include "asterisk/lock.h"
52 #include "asterisk/options.h"
53 #include "asterisk/logger.h"
54 #include "asterisk/utils.h"
55 #include "asterisk/channel.h"
56 #include "asterisk/app.h"
57
58 #define MAX_NESTED_COMMENTS 128
59 #define COMMENT_START ";--"
60 #define COMMENT_END "--;"
61 #define COMMENT_META ';'
62 #define COMMENT_TAG '-'
63
64 static char *extconfig_conf = "extconfig.conf";
65
66
67 /*! \brief Structure to keep comments for rewriting configuration files */
68 struct ast_comment {
69         struct ast_comment *next;
70         char cmt[0];
71 };
72
73 /*! \brief Hold the mtime for config files, so if we don't need to reread our config, don't. */
74 struct cache_file_include {
75         AST_LIST_ENTRY(cache_file_include) list;
76         char include[0];
77 };
78
79 struct cache_file_mtime {
80         AST_LIST_ENTRY(cache_file_mtime) list;
81         AST_LIST_HEAD(includes, cache_file_include) includes;
82         unsigned int has_exec:1;
83         time_t mtime;
84         char filename[0];
85 };
86
87 static AST_LIST_HEAD_STATIC(cfmtime_head, cache_file_mtime);
88
89 #define CB_INCR 250
90
91 static void CB_INIT(char **comment_buffer, int *comment_buffer_size, char **lline_buffer, int *lline_buffer_size)
92 {
93         if (!(*comment_buffer)) {
94                 *comment_buffer = ast_malloc(CB_INCR);
95                 if (!(*comment_buffer))
96                         return;
97                 (*comment_buffer)[0] = 0;
98                 *comment_buffer_size = CB_INCR;
99                 *lline_buffer = ast_malloc(CB_INCR);
100                 if (!(*lline_buffer))
101                         return;
102                 (*lline_buffer)[0] = 0;
103                 *lline_buffer_size = CB_INCR;
104         } else {
105                 (*comment_buffer)[0] = 0;
106                 (*lline_buffer)[0] = 0;
107         }
108 }
109
110 static void  CB_ADD(char **comment_buffer, int *comment_buffer_size, char *str)
111 {
112         int rem = *comment_buffer_size - strlen(*comment_buffer) - 1;
113         int siz = strlen(str);
114         if (rem < siz+1) {
115                 *comment_buffer = ast_realloc(*comment_buffer, *comment_buffer_size + CB_INCR + siz + 1);
116                 if (!(*comment_buffer))
117                         return;
118                 *comment_buffer_size += CB_INCR+siz+1;
119         }
120         strcat(*comment_buffer,str);
121 }
122
123 static void  CB_ADD_LEN(char **comment_buffer, int *comment_buffer_size, char *str, int len)
124 {
125         int cbl = strlen(*comment_buffer) + 1;
126         int rem = *comment_buffer_size - cbl;
127         if (rem < len+1) {
128                 *comment_buffer = ast_realloc(*comment_buffer, *comment_buffer_size + CB_INCR + len + 1);
129                 if (!(*comment_buffer))
130                         return;
131                 *comment_buffer_size += CB_INCR+len+1;
132         }
133         strncat(*comment_buffer,str,len);
134         (*comment_buffer)[cbl+len-1] = 0;
135 }
136
137 static void  LLB_ADD(char **lline_buffer, int *lline_buffer_size, char *str)
138 {
139         int rem = *lline_buffer_size - strlen(*lline_buffer) - 1;
140         int siz = strlen(str);
141         if (rem < siz+1) {
142                 *lline_buffer = ast_realloc(*lline_buffer, *lline_buffer_size + CB_INCR + siz + 1);
143                 if (!(*lline_buffer)) 
144                         return;
145                 *lline_buffer_size += CB_INCR + siz + 1;
146         }
147         strcat(*lline_buffer,str);
148 }
149
150 static void CB_RESET(char **comment_buffer, char **lline_buffer)  
151
152         (*comment_buffer)[0] = 0; 
153         (*lline_buffer)[0] = 0;
154 }
155
156
157 static struct ast_comment *ALLOC_COMMENT(const char *buffer)
158
159         struct ast_comment *x;
160         x = ast_calloc(1, sizeof(*x)+strlen(buffer)+1);
161         strcpy(x->cmt, buffer);
162         return x;
163 }
164
165
166 static struct ast_config_map {
167         struct ast_config_map *next;
168         char *name;
169         char *driver;
170         char *database;
171         char *table;
172         char stuff[0];
173 } *config_maps = NULL;
174
175 AST_MUTEX_DEFINE_STATIC(config_lock);
176 static struct ast_config_engine *config_engine_list;
177
178 #define MAX_INCLUDE_LEVEL 10
179
180 struct ast_category {
181         char name[80];
182         int ignored;                    /*!< do not let user of the config see this category */
183         int include_level;
184         char *file;                /*!< the file name from whence this declaration was read */
185         int lineno;
186         struct ast_comment *precomments;
187         struct ast_comment *sameline;
188         struct ast_comment *trailing; /*!< the last object in the list will get assigned any trailing comments when EOF is hit */
189         struct ast_variable *root;
190         struct ast_variable *last;
191         struct ast_category *next;
192 };
193
194 struct ast_config {
195         struct ast_category *root;
196         struct ast_category *last;
197         struct ast_category *current;
198         struct ast_category *last_browse;     /*!< used to cache the last category supplied via category_browse */
199         int include_level;
200         int max_include_level;
201         struct ast_config_include *includes;  /*!< a list of inclusions, which should describe the entire tree */
202 };
203
204 struct ast_config_include {
205         char *include_location_file;     /*!< file name in which the include occurs */
206         int  include_location_lineno;    /*!< lineno where include occurred */
207         int  exec;                       /*!< set to non-zero if itsa #exec statement */
208         char *exec_file;                 /*!< if it's an exec, you'll have both the /var/tmp to read, and the original script */
209         char *included_file;             /*!< file name included */
210         int inclusion_count;             /*!< if the file is included more than once, a running count thereof -- but, worry not,
211                                               we explode the instances and will include those-- so all entries will be unique */
212         int output;                      /*!< a flag to indicate if the inclusion has been output */
213         struct ast_config_include *next; /*!< ptr to next inclusion in the list */
214 };
215
216 struct ast_variable *ast_variable_new(const char *name, const char *value, const char *filename) 
217 {
218         struct ast_variable *variable;
219         int name_len = strlen(name) + 1;        
220
221         if ((variable = ast_calloc(1, name_len + strlen(value) + 1 + strlen(filename) + 1 + sizeof(*variable)))) {
222                 variable->name = variable->stuff;
223                 variable->value = variable->stuff + name_len;           
224                 variable->file = variable->stuff + name_len + strlen(value) + 1;
225                 strcpy(variable->name,name);
226                 strcpy(variable->value,value);
227                 strcpy(variable->file,filename);
228         }
229         return variable;
230 }
231
232 struct ast_config_include *ast_include_new(struct ast_config *conf, const char *from_file, const char *included_file, int is_exec, const char *exec_file, int from_lineno, char *real_included_file_name, int real_included_file_name_size)
233 {
234         /* a file should be included ONCE. Otherwise, if one of the instances is changed,
235        then all be changed. -- how do we know to include it? -- Handling modified 
236        instances is possible, I'd have
237        to create a new master for each instance. */
238         struct ast_config_include *inc;
239         struct stat statbuf;
240         
241         inc = ast_include_find(conf, included_file);
242         if (inc) {
243                 do {
244                         inc->inclusion_count++;
245                         snprintf(real_included_file_name, real_included_file_name_size, "%s~~%d", included_file, inc->inclusion_count);
246                 } while (stat(real_included_file_name, &statbuf) == 0);
247                 ast_log(LOG_WARNING,"'%s', line %d:  Same File included more than once! This data will be saved in %s if saved back to disk.\n", from_file, from_lineno, real_included_file_name);
248         } else
249                 *real_included_file_name = 0;
250         
251         inc = ast_calloc(1,sizeof(struct ast_config_include));
252         inc->include_location_file = ast_strdup(from_file);
253         inc->include_location_lineno = from_lineno;
254         if (!ast_strlen_zero(real_included_file_name))
255                 inc->included_file = ast_strdup(real_included_file_name);
256         else
257                 inc->included_file = ast_strdup(included_file);
258         
259         inc->exec = is_exec;
260         if (is_exec)
261                 inc->exec_file = ast_strdup(exec_file);
262         
263         /* attach this new struct to the conf struct */
264         inc->next = conf->includes;
265         conf->includes = inc;
266         
267         return inc;
268 }
269
270 void ast_include_rename(struct ast_config *conf, const char *from_file, const char *to_file)
271 {
272         struct ast_config_include *incl;
273         struct ast_category *cat;
274         struct ast_variable *v;
275         
276         int from_len = strlen(from_file);
277         int to_len = strlen(to_file);
278         
279         if (strcmp(from_file, to_file) == 0) /* no use wasting time if the name is the same */
280                 return;
281         
282         /* the manager code allows you to read in one config file, then
283        write it back out under a different name. But, the new arrangement
284            ties output lines to the file name. So, before you try to write
285        the config file to disk, better riffle thru the data and make sure
286        the file names are changed.
287         */
288         /* file names are on categories, includes (of course), and on variables. So,
289            traverse all this and swap names */
290
291         for (incl = conf->includes; incl; incl=incl->next) {
292                 if (strcmp(incl->include_location_file,from_file) == 0) {
293                         if (from_len >= to_len)
294                                 strcpy(incl->include_location_file, to_file);
295                         else {
296                                 free(incl->include_location_file);
297                                 incl->include_location_file = strdup(to_file);
298                         }
299                 }
300         }
301         for (cat = conf->root; cat; cat = cat->next) {
302                 if (strcmp(cat->file,from_file) == 0) {
303                         if (from_len >= to_len)
304                                 strcpy(cat->file, to_file);
305                         else {
306                                 free(cat->file);
307                                 cat->file = strdup(to_file);
308                         }
309                 }
310                 for (v = cat->root; v; v = v->next) {
311                         if (strcmp(v->file,from_file) == 0) {
312                                 if (from_len >= to_len)
313                                         strcpy(v->file, to_file);
314                                 else {
315                                         free(v->file);
316                                         v->file = strdup(to_file);
317                                 }
318                         }
319                 }
320         }
321 }
322
323 struct ast_config_include *ast_include_find(struct ast_config *conf, const char *included_file)
324 {
325         struct ast_config_include *x;
326         for (x=conf->includes;x;x=x->next)
327         {
328                 if (strcmp(x->included_file,included_file) == 0)
329                         return x;
330         }
331         return 0;
332 }
333
334
335 void ast_variable_append(struct ast_category *category, struct ast_variable *variable)
336 {
337         if (!variable)
338                 return;
339         if (category->last)
340                 category->last->next = variable;
341         else
342                 category->root = variable;
343         category->last = variable;
344         while (category->last->next)
345                 category->last = category->last->next;
346 }
347
348 void ast_variables_destroy(struct ast_variable *v)
349 {
350         struct ast_variable *vn;
351
352         while (v) {
353                 vn = v;
354                 v = v->next;
355                 ast_free(vn);
356         }
357 }
358
359 struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category)
360 {
361         struct ast_category *cat = NULL;
362
363         if (category && config->last_browse && (config->last_browse->name == category))
364                 cat = config->last_browse;
365         else
366                 cat = ast_category_get(config, category);
367
368         return (cat) ? cat->root : NULL;
369 }
370
371 const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
372 {
373         const char *tmp;
374         tmp = ast_variable_retrieve(cfg, cat, var);
375         if (!tmp)
376                 tmp = ast_variable_retrieve(cfg, "general", var);
377         return tmp;
378 }
379
380
381 const char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
382 {
383         struct ast_variable *v;
384
385         if (category) {
386                 for (v = ast_variable_browse(config, category); v; v = v->next) {
387                         if (!strcasecmp(variable, v->name))
388                                 return v->value;
389                 }
390         } else {
391                 struct ast_category *cat;
392
393                 for (cat = config->root; cat; cat = cat->next)
394                         for (v = cat->root; v; v = v->next)
395                                 if (!strcasecmp(variable, v->name))
396                                         return v->value;
397         }
398
399         return NULL;
400 }
401
402 static struct ast_variable *variable_clone(const struct ast_variable *old)
403 {
404         struct ast_variable *new = ast_variable_new(old->name, old->value, old->file);
405
406         if (new) {
407                 new->lineno = old->lineno;
408                 new->object = old->object;
409                 new->blanklines = old->blanklines;
410                 /* TODO: clone comments? */
411         }
412
413         return new;
414 }
415  
416 static void move_variables(struct ast_category *old, struct ast_category *new)
417 {
418         struct ast_variable *var = old->root;
419         old->root = NULL;
420 #if 1
421         /* we can just move the entire list in a single op */
422         ast_variable_append(new, var);
423 #else
424         while (var) {
425                 struct ast_variable *next = var->next;
426                 var->next = NULL;
427                 ast_variable_append(new, var);
428                 var = next;
429         }
430 #endif
431 }
432
433 struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno) 
434 {
435         struct ast_category *category;
436
437         if ((category = ast_calloc(1, sizeof(*category))))
438                 ast_copy_string(category->name, name, sizeof(category->name));
439         category->file = strdup(in_file);
440         category->lineno = lineno; /* if you don't know the lineno, set it to 999999 or something real big */
441         return category;
442 }
443
444 static struct ast_category *category_get(const struct ast_config *config, const char *category_name, int ignored)
445 {
446         struct ast_category *cat;
447
448         /* try exact match first, then case-insensitive match */
449         for (cat = config->root; cat; cat = cat->next) {
450                 if (cat->name == category_name && (ignored || !cat->ignored))
451                         return cat;
452         }
453
454         for (cat = config->root; cat; cat = cat->next) {
455                 if (!strcasecmp(cat->name, category_name) && (ignored || !cat->ignored))
456                         return cat;
457         }
458
459         return NULL;
460 }
461
462 struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name)
463 {
464         return category_get(config, category_name, 0);
465 }
466
467 int ast_category_exist(const struct ast_config *config, const char *category_name)
468 {
469         return !!ast_category_get(config, category_name);
470 }
471
472 void ast_category_append(struct ast_config *config, struct ast_category *category)
473 {
474         if (config->last)
475                 config->last->next = category;
476         else
477                 config->root = category;
478         category->include_level = config->include_level;
479         config->last = category;
480         config->current = category;
481 }
482
483 void ast_category_destroy(struct ast_category *cat)
484 {
485         ast_variables_destroy(cat->root);
486         if (cat->file)
487                 free(cat->file);
488         
489         ast_free(cat);
490 }
491
492 static void ast_includes_destroy(struct ast_config_include *incls)
493 {
494         struct ast_config_include *incl,*inclnext;
495         
496         for (incl=incls; incl; incl = inclnext) {
497                 inclnext = incl->next;
498                 if (incl->include_location_file)
499                         free(incl->include_location_file);
500                 if (incl->exec_file)
501                         free(incl->exec_file);
502                 if (incl->included_file)
503                         free(incl->included_file);
504                 free(incl);
505         }
506 }
507
508 static struct ast_category *next_available_category(struct ast_category *cat)
509 {
510         for (; cat && cat->ignored; cat = cat->next);
511
512         return cat;
513 }
514
515 struct ast_variable *ast_category_root(struct ast_config *config, char *cat)
516 {
517         struct ast_category *category = ast_category_get(config, cat);
518         if (category)
519                 return category->root;
520         return NULL;
521 }
522
523 char *ast_category_browse(struct ast_config *config, const char *prev)
524 {       
525         struct ast_category *cat = NULL;
526
527         if (prev && config->last_browse && (config->last_browse->name == prev))
528                 cat = config->last_browse->next;
529         else if (!prev && config->root)
530                 cat = config->root;
531         else if (prev) {
532                 for (cat = config->root; cat; cat = cat->next) {
533                         if (cat->name == prev) {
534                                 cat = cat->next;
535                                 break;
536                         }
537                 }
538                 if (!cat) {
539                         for (cat = config->root; cat; cat = cat->next) {
540                                 if (!strcasecmp(cat->name, prev)) {
541                                         cat = cat->next;
542                                         break;
543                                 }
544                         }
545                 }
546         }
547         
548         if (cat)
549                 cat = next_available_category(cat);
550
551         config->last_browse = cat;
552         return (cat) ? cat->name : NULL;
553 }
554
555 struct ast_variable *ast_category_detach_variables(struct ast_category *cat)
556 {
557         struct ast_variable *v;
558
559         v = cat->root;
560         cat->root = NULL;
561         cat->last = NULL;
562
563         return v;
564 }
565
566 void ast_category_rename(struct ast_category *cat, const char *name)
567 {
568         ast_copy_string(cat->name, name, sizeof(cat->name));
569 }
570
571 static void inherit_category(struct ast_category *new, const struct ast_category *base)
572 {
573         struct ast_variable *var;
574
575         for (var = base->root; var; var = var->next)
576                 ast_variable_append(new, variable_clone(var));
577 }
578
579 struct ast_config *ast_config_new(void) 
580 {
581         struct ast_config *config;
582
583         if ((config = ast_calloc(1, sizeof(*config))))
584                 config->max_include_level = MAX_INCLUDE_LEVEL;
585         return config;
586 }
587
588 int ast_variable_delete(struct ast_category *category, const char *variable, const char *match)
589 {
590         struct ast_variable *cur, *prev=NULL, *curn;
591         int res = -1;
592         cur = category->root;
593         while (cur) {
594                 if (cur->name == variable) {
595                         if (prev) {
596                                 prev->next = cur->next;
597                                 if (cur == category->last)
598                                         category->last = prev;
599                         } else {
600                                 category->root = cur->next;
601                                 if (cur == category->last)
602                                         category->last = NULL;
603                         }
604                         cur->next = NULL;
605                         ast_variables_destroy(cur);
606                         return 0;
607                 }
608                 prev = cur;
609                 cur = cur->next;
610         }
611
612         prev = NULL;
613         cur = category->root;
614         while (cur) {
615                 curn = cur->next;
616                 if (!strcasecmp(cur->name, variable) && (ast_strlen_zero(match) || !strcasecmp(cur->value, match))) {
617                         if (prev) {
618                                 prev->next = cur->next;
619                                 if (cur == category->last)
620                                         category->last = prev;
621                         } else {
622                                 category->root = cur->next;
623                                 if (cur == category->last)
624                                         category->last = NULL;
625                         }
626                         cur->next = NULL;
627                         ast_variables_destroy(cur);
628                         res = 0;
629                 } else
630                         prev = cur;
631
632                 cur = curn;
633         }
634         return res;
635 }
636
637 int ast_variable_update(struct ast_category *category, const char *variable, 
638                                                 const char *value, const char *match, unsigned int object)
639 {
640         struct ast_variable *cur, *prev=NULL, *newer=NULL;
641
642         for (cur = category->root; cur; prev = cur, cur = cur->next) {
643                 if (strcasecmp(cur->name, variable) ||
644                         (!ast_strlen_zero(match) && strcasecmp(cur->value, match)))
645                         continue;
646
647                 if (!(newer = ast_variable_new(variable, value, cur->file)))
648                         return -1;
649         
650                 newer->next = cur->next;
651                 newer->object = cur->object || object;
652                 if (prev)
653                         prev->next = newer;
654                 else
655                         category->root = newer;
656                 if (category->last == cur)
657                         category->last = newer;
658
659                 cur->next = NULL;
660                 ast_variables_destroy(cur);
661
662                 return 0;
663         }
664
665         if (prev)
666                 prev->next = newer;
667         else
668                 category->root = newer;
669
670         return 0;
671 }
672
673 int ast_category_delete(struct ast_config *cfg, const char *category)
674 {
675         struct ast_category *prev=NULL, *cat;
676         cat = cfg->root;
677         while (cat) {
678                 if (cat->name == category) {
679                         ast_variables_destroy(cat->root);
680                         if (prev) {
681                                 prev->next = cat->next;
682                                 if (cat == cfg->last)
683                                         cfg->last = prev;
684                         } else {
685                                 cfg->root = cat->next;
686                                 if (cat == cfg->last)
687                                         cfg->last = NULL;
688                         }
689                         ast_free(cat);
690                         return 0;
691                 }
692                 prev = cat;
693                 cat = cat->next;
694         }
695
696         prev = NULL;
697         cat = cfg->root;
698         while (cat) {
699                 if (!strcasecmp(cat->name, category)) {
700                         ast_variables_destroy(cat->root);
701                         if (prev) {
702                                 prev->next = cat->next;
703                                 if (cat == cfg->last)
704                                         cfg->last = prev;
705                         } else {
706                                 cfg->root = cat->next;
707                                 if (cat == cfg->last)
708                                         cfg->last = NULL;
709                         }
710                         ast_free(cat);
711                         return 0;
712                 }
713                 prev = cat;
714                 cat = cat->next;
715         }
716         return -1;
717 }
718
719 void ast_config_destroy(struct ast_config *cfg)
720 {
721         struct ast_category *cat, *catn;
722
723         if (!cfg)
724                 return;
725
726         ast_includes_destroy(cfg->includes);
727
728         cat = cfg->root;
729         while (cat) {
730                 ast_variables_destroy(cat->root);
731                 catn = cat;
732                 cat = cat->next;
733                 ast_free(catn);
734         }
735         ast_free(cfg);
736 }
737
738 struct ast_category *ast_config_get_current_category(const struct ast_config *cfg)
739 {
740         return cfg->current;
741 }
742
743 void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat)
744 {
745         /* cast below is just to silence compiler warning about dropping "const" */
746         cfg->current = (struct ast_category *) cat;
747 }
748
749 enum config_cache_attribute_enum {
750         ATTRIBUTE_INCLUDE = 0,
751         ATTRIBUTE_EXEC = 1,
752 };
753
754 static void config_cache_attribute(const char *configfile, enum config_cache_attribute_enum attrtype, const char *filename)
755 {
756         struct cache_file_mtime *cfmtime;
757         struct cache_file_include *cfinclude;
758         struct stat statbuf = { 0, };
759
760         /* Find our cached entry for this configuration file */
761         AST_LIST_LOCK(&cfmtime_head);
762         AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
763                 if (!strcmp(cfmtime->filename, configfile))
764                         break;
765         }
766         if (!cfmtime) {
767                 cfmtime = ast_calloc(1, sizeof(*cfmtime) + strlen(configfile) + 1);
768                 if (!cfmtime) {
769                         AST_LIST_UNLOCK(&cfmtime_head);
770                         return;
771                 }
772                 AST_LIST_HEAD_INIT(&cfmtime->includes);
773                 strcpy(cfmtime->filename, configfile);
774                 /* Note that the file mtime is initialized to 0, i.e. 1970 */
775                 AST_LIST_INSERT_TAIL(&cfmtime_head, cfmtime, list);
776         }
777
778         if (!stat(configfile, &statbuf))
779                 cfmtime->mtime = 0;
780         else
781                 cfmtime->mtime = statbuf.st_mtime;
782
783         switch (attrtype) {
784         case ATTRIBUTE_INCLUDE:
785                 cfinclude = ast_calloc(1, sizeof(*cfinclude) + strlen(filename) + 1);
786                 if (!cfinclude) {
787                         AST_LIST_UNLOCK(&cfmtime_head);
788                         return;
789                 }
790                 strcpy(cfinclude->include, filename);
791                 AST_LIST_INSERT_TAIL(&cfmtime->includes, cfinclude, list);
792                 break;
793         case ATTRIBUTE_EXEC:
794                 cfmtime->has_exec = 1;
795                 break;
796         }
797         AST_LIST_UNLOCK(&cfmtime_head);
798 }
799
800 static int process_text_line(struct ast_config *cfg, struct ast_category **cat, char *buf, int lineno, const char *configfile, struct ast_flags flags,
801                                                          char **comment_buffer, int *comment_buffer_size, char **lline_buffer, int *lline_buffer_size, const char *suggested_include_file, struct ast_category **last_cat, struct ast_variable **last_var)
802 {
803         char *c;
804         char *cur = buf;
805         struct ast_variable *v;
806         char cmd[512], exec_file[512];
807         int object, do_exec, do_include;
808
809         /* Actually parse the entry */
810         if (cur[0] == '[') {
811                 struct ast_category *newcat = NULL;
812                 char *catname;
813
814                 /* A category header */
815                 c = strchr(cur, ']');
816                 if (!c) {
817                         ast_log(LOG_WARNING, "parse error: no closing ']', line %d of %s\n", lineno, configfile);
818                         return -1;
819                 }
820                 *c++ = '\0';
821                 cur++;
822                 if (*c++ != '(')
823                         c = NULL;
824                 catname = cur;
825                 if (!(*cat = newcat = ast_category_new(catname, ast_strlen_zero(suggested_include_file)?configfile:suggested_include_file, lineno))) {
826                         return -1;
827                 }
828                 (*cat)->lineno = lineno;
829                 *last_var = 0;
830                 *last_cat = newcat;
831                 
832                 /* add comments */
833                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && *comment_buffer && (*comment_buffer)[0] ) {
834                         newcat->precomments = ALLOC_COMMENT(*comment_buffer);
835                 }
836                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && *lline_buffer && (*lline_buffer)[0] ) {
837                         newcat->sameline = ALLOC_COMMENT(*lline_buffer);
838                 }
839                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
840                         CB_RESET(comment_buffer, lline_buffer);
841                 
842                 /* If there are options or categories to inherit from, process them now */
843                 if (c) {
844                         if (!(cur = strchr(c, ')'))) {
845                                 ast_log(LOG_WARNING, "parse error: no closing ')', line %d of %s\n", lineno, configfile);
846                                 return -1;
847                         }
848                         *cur = '\0';
849                         while ((cur = strsep(&c, ","))) {
850                                 if (!strcasecmp(cur, "!")) {
851                                         (*cat)->ignored = 1;
852                                 } else if (!strcasecmp(cur, "+")) {
853                                         *cat = category_get(cfg, catname, 1);
854                                         if (!(*cat)) {
855                                                 if (newcat)
856                                                         ast_category_destroy(newcat);
857                                                 ast_log(LOG_WARNING, "Category addition requested, but category '%s' does not exist, line %d of %s\n", catname, lineno, configfile);
858                                                 return -1;
859                                         }
860                                         if (newcat) {
861                                                 move_variables(newcat, *cat);
862                                                 ast_category_destroy(newcat);
863                                                 newcat = NULL;
864                                         }
865                                 } else {
866                                         struct ast_category *base;
867                                 
868                                         base = category_get(cfg, cur, 1);
869                                         if (!base) {
870                                                 ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile);
871                                                 return -1;
872                                         }
873                                         inherit_category(*cat, base);
874                                 }
875                         }
876                 }
877                 if (newcat)
878                         ast_category_append(cfg, *cat);
879         } else if (cur[0] == '#') {
880                 /* A directive */
881                 cur++;
882                 c = cur;
883                 while (*c && (*c > 32)) c++;
884                 if (*c) {
885                         *c = '\0';
886                         /* Find real argument */
887                         c = ast_skip_blanks(c + 1);
888                         if (!(*c))
889                                 c = NULL;
890                 } else 
891                         c = NULL;
892                 do_include = !strcasecmp(cur, "include");
893                 if (!do_include)
894                         do_exec = !strcasecmp(cur, "exec");
895                 else
896                         do_exec = 0;
897                 if (do_exec && !ast_opt_exec_includes) {
898                         ast_log(LOG_WARNING, "Cannot perform #exec unless execincludes option is enabled in asterisk.conf (options section)!\n");
899                         do_exec = 0;
900                 }
901                 if (do_include || do_exec) {
902                         if (c) {
903                                 char *cur2;
904                                 char real_inclusion_name[256];
905                                 struct ast_config_include *inclu;
906                                 
907                                 /* Strip off leading and trailing "'s and <>'s */
908                                 while ((*c == '<') || (*c == '>') || (*c == '\"')) c++;
909                                 /* Get rid of leading mess */
910                                 cur = c;
911                                 cur2 = cur;
912                                 while (!ast_strlen_zero(cur)) {
913                                         c = cur + strlen(cur) - 1;
914                                         if ((*c == '>') || (*c == '<') || (*c == '\"'))
915                                                 *c = '\0';
916                                         else
917                                                 break;
918                                 }
919                                 /* #exec </path/to/executable>
920                                    We create a tmp file, then we #include it, then we delete it. */
921                                 if (do_exec) {
922                                         if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
923                                                 config_cache_attribute(configfile, ATTRIBUTE_EXEC, NULL);
924                                         snprintf(exec_file, sizeof(exec_file), "/var/tmp/exec.%d.%ld", (int)time(NULL), (long)pthread_self());
925                                         snprintf(cmd, sizeof(cmd), "%s > %s 2>&1", cur, exec_file);
926                                         ast_safe_system(cmd);
927                                         cur = exec_file;
928                                 } else {
929                                         if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
930                                                 config_cache_attribute(configfile, ATTRIBUTE_INCLUDE, cur);
931                                         exec_file[0] = '\0';
932                                 }
933                                 /* A #include */
934                                 /* record this inclusion */
935                                 inclu = ast_include_new(cfg, configfile, cur, do_exec, cur2, lineno, real_inclusion_name, sizeof(real_inclusion_name));
936
937                                 do_include = ast_config_internal_load(cur, cfg, flags, real_inclusion_name) ? 1 : 0;
938                                 if (!ast_strlen_zero(exec_file))
939                                         unlink(exec_file);
940                                 if (!do_include)
941                                         return 0;
942
943                         } else {
944                                 ast_log(LOG_WARNING, "Directive '#%s' needs an argument (%s) at line %d of %s\n", 
945                                                 do_exec ? "exec" : "include",
946                                                 do_exec ? "/path/to/executable" : "filename",
947                                                 lineno,
948                                                 configfile);
949                         }
950                 }
951                 else 
952                         ast_log(LOG_WARNING, "Unknown directive '%s' at line %d of %s\n", cur, lineno, configfile);
953         } else {
954                 /* Just a line (variable = value) */
955                 if (!(*cat)) {
956                         ast_log(LOG_WARNING,
957                                 "parse error: No category context for line %d of %s\n", lineno, configfile);
958                         return -1;
959                 }
960                 c = strchr(cur, '=');
961                 if (c) {
962                         *c = 0;
963                         c++;
964                         /* Ignore > in => */
965                         if (*c== '>') {
966                                 object = 1;
967                                 c++;
968                         } else
969                                 object = 0;
970                         if ((v = ast_variable_new(ast_strip(cur), ast_strip(c), *suggested_include_file ? suggested_include_file : configfile))) {
971                                 v->lineno = lineno;
972                                 v->object = object;
973                                 *last_cat = 0;
974                                 *last_var = v;
975                                 /* Put and reset comments */
976                                 v->blanklines = 0;
977                                 ast_variable_append(*cat, v);
978                                 /* add comments */
979                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && *comment_buffer && (*comment_buffer)[0] ) {
980                                         v->precomments = ALLOC_COMMENT(*comment_buffer);
981                                 }
982                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && *lline_buffer && (*lline_buffer)[0] ) {
983                                         v->sameline = ALLOC_COMMENT(*lline_buffer);
984                                 }
985                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
986                                         CB_RESET(comment_buffer, lline_buffer);
987                                 
988                         } else {
989                                 return -1;
990                         }
991                 } else {
992                         ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
993                 }
994         }
995         return 0;
996 }
997
998 static struct ast_config *config_text_file_load(const char *database, const char *table, const char *filename, struct ast_config *cfg, struct ast_flags flags, const char *suggested_include_file)
999 {
1000         char fn[256];
1001         char buf[8192];
1002         char *new_buf, *comment_p, *process_buf;
1003         FILE *f;
1004         int lineno=0;
1005         int comment = 0, nest[MAX_NESTED_COMMENTS];
1006         struct ast_category *cat = NULL;
1007         int count = 0;
1008         struct stat statbuf;
1009         struct cache_file_mtime *cfmtime = NULL;
1010         struct cache_file_include *cfinclude;
1011         struct ast_variable *last_var = 0;
1012         struct ast_category *last_cat = 0;
1013         /*! Growable string buffer */
1014         char *comment_buffer=0;   /*!< this will be a comment collector.*/
1015         int   comment_buffer_size=0;  /*!< the amount of storage so far alloc'd for the comment_buffer */
1016
1017         char *lline_buffer=0;    /*!< A buffer for stuff behind the ; */
1018         int  lline_buffer_size=0;
1019
1020         if (cfg)
1021                 cat = ast_config_get_current_category(cfg);
1022
1023         if (filename[0] == '/') {
1024                 ast_copy_string(fn, filename, sizeof(fn));
1025         } else {
1026                 snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, filename);
1027         }
1028
1029         if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1030                 CB_INIT(&comment_buffer, &comment_buffer_size, &lline_buffer, &lline_buffer_size);
1031                 if (!lline_buffer || !comment_buffer) {
1032                         ast_log(LOG_ERROR, "Failed to initialize the comment buffer!\n");
1033                         return NULL;
1034                 }
1035         }
1036 #ifdef AST_INCLUDE_GLOB
1037         {
1038                 int glob_ret;
1039                 glob_t globbuf;
1040                 globbuf.gl_offs = 0;    /* initialize it to silence gcc */
1041 #ifdef SOLARIS
1042                 glob_ret = glob(fn, GLOB_NOCHECK, NULL, &globbuf);
1043 #else
1044                 glob_ret = glob(fn, GLOB_NOMAGIC|GLOB_BRACE, NULL, &globbuf);
1045 #endif
1046                 if (glob_ret == GLOB_NOSPACE)
1047                         ast_log(LOG_WARNING,
1048                                 "Glob Expansion of pattern '%s' failed: Not enough memory\n", fn);
1049                 else if (glob_ret  == GLOB_ABORTED)
1050                         ast_log(LOG_WARNING,
1051                                 "Glob Expansion of pattern '%s' failed: Read error\n", fn);
1052                 else  {
1053                         /* loop over expanded files */
1054                         int i;
1055                         for (i=0; i<globbuf.gl_pathc; i++) {
1056                                 ast_copy_string(fn, globbuf.gl_pathv[i], sizeof(fn));
1057 #endif
1058         do {
1059                 if (stat(fn, &statbuf))
1060                         continue;
1061
1062                 if (!S_ISREG(statbuf.st_mode)) {
1063                         ast_log(LOG_WARNING, "'%s' is not a regular file, ignoring\n", fn);
1064                         continue;
1065                 }
1066
1067                 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE)) {
1068                         /* Find our cached entry for this configuration file */
1069                         AST_LIST_LOCK(&cfmtime_head);
1070                         AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
1071                                 if (!strcmp(cfmtime->filename, fn))
1072                                         break;
1073                         }
1074                         if (!cfmtime) {
1075                                 cfmtime = ast_calloc(1, sizeof(*cfmtime) + strlen(fn) + 1);
1076                                 if (!cfmtime)
1077                                         continue;
1078                                 AST_LIST_HEAD_INIT(&cfmtime->includes);
1079                                 strcpy(cfmtime->filename, fn);
1080                                 /* Note that the file mtime is initialized to 0, i.e. 1970 */
1081                                 AST_LIST_INSERT_TAIL(&cfmtime_head, cfmtime, list);
1082                         }
1083                 }
1084
1085                 if (cfmtime && (!cfmtime->has_exec) && (cfmtime->mtime == statbuf.st_mtime) && ast_test_flag(&flags, CONFIG_FLAG_FILEUNCHANGED)) {
1086                         /* File is unchanged, what about the (cached) includes (if any)? */
1087                         int unchanged = 1;
1088                         AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
1089                                 /* We must glob here, because if we did not, then adding a file to globbed directory would
1090                                  * incorrectly cause no reload to be necessary. */
1091                                 char fn2[256];
1092 #ifdef AST_INCLUDE_GLOB
1093                                 int glob_ret;
1094                                 glob_t globbuf = { .gl_offs = 0 };
1095 #ifdef SOLARIS
1096                                 glob_ret = glob(cfinclude->include, GLOB_NOCHECK, NULL, &globbuf);
1097 #else
1098                                 glob_ret = glob(cfinclude->include, GLOB_NOMAGIC|GLOB_BRACE, NULL, &globbuf);
1099 #endif
1100                                 /* On error, we reparse */
1101                                 if (glob_ret == GLOB_NOSPACE || glob_ret  == GLOB_ABORTED)
1102                                         unchanged = 0;
1103                                 else  {
1104                                         /* loop over expanded files */
1105                                         int j;
1106                                         for (j = 0; j < globbuf.gl_pathc; j++) {
1107                                                 ast_copy_string(fn2, globbuf.gl_pathv[j], sizeof(fn2));
1108 #else
1109                                                 ast_copy_string(fn2, cfinclude->include);
1110 #endif
1111                                                 if (config_text_file_load(NULL, NULL, fn2, NULL, flags, "") == NULL) { /* that last field needs to be looked at in this case... TODO */
1112                                                         unchanged = 0;
1113                                                         /* One change is enough to short-circuit and reload the whole shebang */
1114                                                         break;
1115                                                 }
1116 #ifdef AST_INCLUDE_GLOB
1117                                         }
1118                                 }
1119 #endif
1120                         }
1121
1122                         if (unchanged) {
1123                                 AST_LIST_UNLOCK(&cfmtime_head);
1124                                 return CONFIG_STATUS_FILEUNCHANGED;
1125                         }
1126                 }
1127                 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
1128                         AST_LIST_UNLOCK(&cfmtime_head);
1129
1130                 /* If cfg is NULL, then we just want an answer */
1131                 if (cfg == NULL)
1132                         return NULL;
1133
1134                 if (cfmtime)
1135                         cfmtime->mtime = statbuf.st_mtime;
1136
1137                 ast_verb(2, "Parsing '%s': ", fn);
1138                         fflush(stdout);
1139                 if (!(f = fopen(fn, "r"))) {
1140                         ast_debug(1, "No file to parse: %s\n", fn);
1141                         ast_verb(2, "Not found (%s)\n", strerror(errno));
1142                         continue;
1143                 }
1144                 count++;
1145                 /* If we get to this point, then we're loading regardless */
1146                 ast_clear_flag(&flags, CONFIG_FLAG_FILEUNCHANGED);
1147                 ast_debug(1, "Parsing %s\n", fn);
1148                 ast_verb(2, "Found\n");
1149                 while (!feof(f)) {
1150                         lineno++;
1151                         if (fgets(buf, sizeof(buf), f)) {
1152                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1153                                         CB_ADD(&comment_buffer, &comment_buffer_size, lline_buffer);       /* add the current lline buffer to the comment buffer */
1154                                         lline_buffer[0] = 0;        /* erase the lline buffer */
1155                                 }
1156                                 
1157                                 new_buf = buf;
1158                                 if (comment) 
1159                                         process_buf = NULL;
1160                                 else
1161                                         process_buf = buf;
1162                                 
1163                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && comment_buffer[0] && (ast_strlen_zero(buf) || strlen(buf) == strspn(buf," \t\n\r"))) {
1164                                         /* blank line? really? Can we add it to an existing comment and maybe preserve inter- and post- comment spacing? */
1165                                         CB_ADD(&comment_buffer, &comment_buffer_size, "\n");       /* add a newline to the comment buffer */
1166                                         continue; /* go get a new line, then */
1167                                 }
1168                                 
1169                                 while ((comment_p = strchr(new_buf, COMMENT_META))) {
1170                                         if ((comment_p > new_buf) && (*(comment_p-1) == '\\')) {
1171                                                 /* Escaped semicolons aren't comments. */
1172                                                 new_buf = comment_p + 1;
1173                                         } else if (comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) {
1174                                                 /* Meta-Comment start detected ";--" */
1175                                                 if (comment < MAX_NESTED_COMMENTS) {
1176                                                         *comment_p = '\0';
1177                                                         new_buf = comment_p + 3;
1178                                                         comment++;
1179                                                         nest[comment-1] = lineno;
1180                                                 } else {
1181                                                         ast_log(LOG_ERROR, "Maximum nest limit of %d reached.\n", MAX_NESTED_COMMENTS);
1182                                                 }
1183                                         } else if ((comment_p >= new_buf + 2) &&
1184                                                    (*(comment_p - 1) == COMMENT_TAG) &&
1185                                                    (*(comment_p - 2) == COMMENT_TAG)) {
1186                                                 /* Meta-Comment end detected */
1187                                                 comment--;
1188                                                 new_buf = comment_p + 1;
1189                                                 if (!comment) {
1190                                                         /* Back to non-comment now */
1191                                                         if (process_buf) {
1192                                                                 /* Actually have to move what's left over the top, then continue */
1193                                                                 char *oldptr;
1194                                                                 oldptr = process_buf + strlen(process_buf);
1195                                                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1196                                                                         CB_ADD(&comment_buffer, &comment_buffer_size, ";");
1197                                                                         CB_ADD_LEN(&comment_buffer, &comment_buffer_size, oldptr+1, new_buf-oldptr-1);
1198                                                                 }
1199                                                                 
1200                                                                 memmove(oldptr, new_buf, strlen(new_buf) + 1);
1201                                                                 new_buf = oldptr;
1202                                                         } else
1203                                                                 process_buf = new_buf;
1204                                                 }
1205                                         } else {
1206                                                 if (!comment) {
1207                                                         /* If ; is found, and we are not nested in a comment, 
1208                                                            we immediately stop all comment processing */
1209                                                         if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1210                                                                 LLB_ADD(&lline_buffer, &lline_buffer_size, comment_p);
1211                                                         }
1212                                                         *comment_p = '\0'; 
1213                                                         new_buf = comment_p;
1214                                                 } else
1215                                                         new_buf = comment_p + 1;
1216                                         }
1217                                 }
1218                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment && !process_buf )
1219                                 {
1220                                         CB_ADD(&comment_buffer, &comment_buffer_size, buf);  /* the whole line is a comment, store it */
1221                                 }
1222                                 
1223                                 if (process_buf) {
1224                                         char *buf = ast_strip(process_buf);
1225                                         if (!ast_strlen_zero(buf)) {
1226                                                 if (process_text_line(cfg, &cat, buf, lineno, fn, flags, &comment_buffer, &comment_buffer_size, &lline_buffer, &lline_buffer_size, suggested_include_file, &last_cat, &last_var)) {
1227                                                         cfg = NULL;
1228                                                         break;
1229                                                 }
1230                                         }
1231                                 }
1232                         }
1233                 }
1234                 /* end of file-- anything in a comment buffer? */
1235                 if (last_cat) {
1236                         if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && comment_buffer[0] ) {
1237                                 CB_ADD(&comment_buffer, &comment_buffer_size, lline_buffer);       /* add the current lline buffer to the comment buffer */
1238                                 lline_buffer[0] = 0;        /* erase the lline buffer */
1239                                 last_cat->trailing = ALLOC_COMMENT(comment_buffer);
1240                         }
1241                 } else if (last_var) {
1242                         if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && comment_buffer[0] ) {
1243                                 CB_ADD(&comment_buffer, &comment_buffer_size, lline_buffer);       /* add the current lline buffer to the comment buffer */
1244                                 lline_buffer[0] = 0;        /* erase the lline buffer */
1245                                 last_var->trailing = ALLOC_COMMENT(comment_buffer);
1246                         }
1247                 } else {
1248                         if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && (comment_buffer)[0] ) {
1249                                 ast_debug(1, "Nothing to attach comments to, discarded: %s\n", comment_buffer);
1250                         }
1251                 }
1252                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
1253                         CB_RESET(&comment_buffer, &lline_buffer);
1254
1255                 fclose(f);              
1256         } while (0);
1257         if (comment) {
1258                 ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment - 1]);
1259         }
1260 #ifdef AST_INCLUDE_GLOB
1261                                         if (cfg == NULL || cfg == CONFIG_STATUS_FILEUNCHANGED)
1262                                                 break;
1263                                 }
1264                                 globfree(&globbuf);
1265                         }
1266                 }
1267 #endif
1268
1269         if (cfg && cfg != CONFIG_STATUS_FILEUNCHANGED && cfg->include_level == 1 && ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer) {
1270                 ast_free(comment_buffer);
1271                 ast_free(lline_buffer);
1272                 comment_buffer = NULL;
1273                 lline_buffer = NULL;
1274                 comment_buffer_size = 0;
1275                 lline_buffer_size = 0;
1276         }
1277         
1278         if (count == 0)
1279                 return NULL;
1280
1281         return cfg;
1282 }
1283
1284
1285 /* NOTE: categories and variables each have a file and lineno attribute. On a save operation, these are used to determine
1286    which file and line number to write out to. Thus, an entire hierarchy of config files (via #include statements) can be
1287    recreated. BUT, care must be taken to make sure that every cat and var has the proper file name stored, or you may
1288    be shocked and mystified as to why things are not showing up in the files! 
1289
1290    Also, All #include/#exec statements are recorded in the "includes" LL in the ast_config structure. The file name
1291    and line number are stored for each include, plus the name of the file included, so that these statements may be
1292    included in the output files on a file_save operation. 
1293
1294    The lineno's are really just for relative placement in the file. There is no attempt to make sure that blank lines
1295    are included to keep the lineno's the same between input and output. The lineno fields are used mainly to determine
1296    the position of the #include and #exec directives. So, blank lines tend to disappear from a read/rewrite operation,
1297    and a header gets added.
1298
1299    vars and category heads are output in the order they are stored in the config file. So, if the software
1300    shuffles these at all, then the placement of #include directives might get a little mixed up, because the
1301    file/lineno data probably won't get changed.
1302
1303 */
1304
1305 static void gen_header(FILE *f1, const char *configfile, const char *fn, const char *generator)
1306 {
1307         char date[256]="";
1308         time_t t;
1309         time(&t);
1310         ast_copy_string(date, ctime(&t), sizeof(date));
1311
1312         fprintf(f1, ";!\n");
1313         fprintf(f1, ";! Automatically generated configuration file\n");
1314         if (strcmp(configfile, fn))
1315                 fprintf(f1, ";! Filename: %s (%s)\n", configfile, fn);
1316         else
1317                 fprintf(f1, ";! Filename: %s\n", configfile);
1318         fprintf(f1, ";! Generator: %s\n", generator);
1319         fprintf(f1, ";! Creation Date: %s", date);
1320         fprintf(f1, ";!\n");
1321 }
1322
1323 static void set_fn(char *fn, int fn_size, const char *file, const char *configfile)
1324 {
1325         if (!file || file[0] == 0) {
1326                 if (configfile[0] == '/')
1327                         ast_copy_string(fn, configfile, fn_size);
1328                 else
1329                         snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, configfile);
1330         } else if (file[0] == '/') 
1331                 ast_copy_string(fn, file, fn_size);
1332         else
1333                 snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, file);
1334 }
1335
1336 static int count_linefeeds(char *str)
1337 {
1338         int count = 0;
1339         while (*str) {
1340                 if (*str =='\n')
1341                         count++;
1342         }
1343         return count;
1344 }
1345
1346 static int count_linefeeds_in_comments(struct ast_comment *x)
1347 {
1348         int count = 0;
1349         while (x)
1350         {
1351                 count += count_linefeeds(x->cmt);
1352                 x = x->next;
1353         }
1354         return count;
1355 }
1356
1357 int config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
1358 {
1359         FILE *f;
1360         char fn[256];
1361         struct ast_variable *var;
1362         struct ast_category *cat;
1363         struct ast_comment *cmt;
1364         struct ast_config_include *incl;
1365         int blanklines = 0;
1366
1367         /* reset all the output flags, in case this isn't our first time saving this data */
1368
1369         for (incl=cfg->includes; incl; incl = incl->next)
1370                 incl->output = 0;
1371
1372         /* go thru all the inclusions and make sure all the files involved (configfile plus all its inclusions)
1373            are all truncated to zero bytes and have that nice header*/
1374
1375         for (incl=cfg->includes; incl; incl = incl->next)
1376         {
1377                 if (!incl->exec) { /* leave the execs alone -- we'll write out the #exec directives, but won't zero out the include files or exec files*/
1378                         FILE *f1;
1379
1380                         set_fn(fn, sizeof(fn), incl->included_file, configfile); /* normally, fn is just set to incl->included_file, prepended with config dir if relative */
1381                         f1 = fopen(fn,"w");
1382                         if (f1) {
1383                                 gen_header(f1, configfile, fn, generator);
1384                                 fclose(f1); /* this should zero out the file */
1385                         } else {
1386                                 ast_debug(1, "Unable to open for writing: %s\n", fn);
1387                                 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1388                         }
1389                 }
1390         }
1391
1392         set_fn(fn, sizeof(fn), 0, configfile); /* just set fn to absolute ver of configfile */
1393 #ifdef __CYGWIN__       
1394         if ((f = fopen(fn, "w+"))) {
1395 #else
1396         if ((f = fopen(fn, "w"))) {
1397 #endif      
1398                 ast_verb(2, "Saving '%s': ", fn);
1399                 gen_header(f, configfile, fn, generator);
1400                 cat = cfg->root;
1401                 fclose(f);
1402                 
1403                 /* from here out, we open each involved file and concat the stuff we need to add to the end and immediately close... */
1404                 /* since each var, cat, and associated comments can come from any file, we have to be 
1405                    mobile, and open each file, print, and close it on an entry-by-entry basis */
1406
1407                 while (cat) {
1408                         set_fn(fn, sizeof(fn), cat->file, configfile);
1409                         f = fopen(fn, "a");
1410                         if (!f)
1411                         {
1412                                 ast_debug(1, "Unable to open for writing: %s\n", fn);
1413                                 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1414                                 return -1;
1415                         }
1416
1417                         /* dump any includes that happen before this category header */
1418                         for (incl=cfg->includes; incl; incl = incl->next) {
1419                                 if (strcmp(incl->include_location_file, cat->file) == 0){
1420                                         if (cat->lineno > incl->include_location_lineno && !incl->output) {
1421                                                 if (incl->exec)
1422                                                         fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1423                                                 else
1424                                                         fprintf(f,"#include \"%s\"\n", incl->included_file);
1425                                                 incl->output = 1;
1426                                         }
1427                                 }
1428                         }
1429                         
1430                         /* Dump section with any appropriate comment */
1431                         for (cmt = cat->precomments; cmt; cmt=cmt->next) {
1432                                 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1433                                         fprintf(f,"%s", cmt->cmt);
1434                         }
1435                         if (!cat->precomments)
1436                                 fprintf(f,"\n");
1437                         fprintf(f, "[%s]", cat->name);
1438                         for (cmt = cat->sameline; cmt; cmt=cmt->next) {
1439                                 fprintf(f,"%s", cmt->cmt);
1440                         }
1441                         if (!cat->sameline)
1442                                 fprintf(f,"\n");
1443                         for (cmt = cat->trailing; cmt; cmt=cmt->next) {
1444                                 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1445                                         fprintf(f,"%s", cmt->cmt);
1446                         }
1447                         fclose(f);
1448                         
1449                         var = cat->root;
1450                         while (var) {
1451                                 set_fn(fn, sizeof(fn), var->file, configfile);
1452                                 f = fopen(fn, "a");
1453                                 if (!f)
1454                                 {
1455                                         ast_debug(1, "Unable to open for writing: %s\n", fn);
1456                                         ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1457                                         return -1;
1458                                 }
1459                                 
1460                                 /* dump any includes that happen before this category header */
1461                                 for (incl=cfg->includes; incl; incl = incl->next) {
1462                                         if (strcmp(incl->include_location_file, var->file) == 0){
1463                                                 if (var->lineno > incl->include_location_lineno && !incl->output) {
1464                                                         if (incl->exec)
1465                                                                 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1466                                                         else
1467                                                                 fprintf(f,"#include \"%s\"\n", incl->included_file);
1468                                                         incl->output = 1;
1469                                                 }
1470                                         }
1471                                 }
1472                                 
1473                                 for (cmt = var->precomments; cmt; cmt=cmt->next) {
1474                                         if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1475                                                 fprintf(f,"%s", cmt->cmt);
1476                                 }
1477                                 if (var->sameline) 
1478                                         fprintf(f, "%s %s %s  %s", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
1479                                 else    
1480                                         fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
1481                                 for (cmt = var->trailing; cmt; cmt=cmt->next) {
1482                                         if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1483                                                 fprintf(f,"%s", cmt->cmt);
1484                                 }
1485                                 if (var->blanklines) {
1486                                         blanklines = var->blanklines;
1487                                         while (blanklines--)
1488                                                 fprintf(f, "\n");
1489                                 }
1490                                 
1491                                 fclose(f);
1492                                 
1493                                 var = var->next;
1494                         }
1495                         cat = cat->next;
1496                 }
1497                 if (!option_debug)
1498                         ast_verb(2, "Saved\n");
1499         } else {
1500                 ast_debug(1, "Unable to open for writing: %s\n", fn);
1501                 ast_verb(2, "Unable to write (%s)", strerror(errno));
1502                 return -1;
1503         }
1504
1505         /* Now, for files with trailing #include/#exec statements,
1506            we have to make sure every entry is output */
1507
1508         for (incl=cfg->includes; incl; incl = incl->next) {
1509                 if (!incl->output) {
1510                         /* open the respective file */
1511                         set_fn(fn, sizeof(fn), incl->include_location_file, configfile);
1512                         f = fopen(fn, "a");
1513                         if (!f)
1514                         {
1515                                 ast_debug(1, "Unable to open for writing: %s\n", fn);
1516                                 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1517                                 return -1;
1518                         }
1519                         
1520                         /* output the respective include */
1521                         if (incl->exec)
1522                                 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1523                         else
1524                                 fprintf(f,"#include \"%s\"\n", incl->included_file);
1525                         fclose(f);
1526                         incl->output = 1;
1527                 }
1528         }
1529                                 
1530         return 0;
1531 }
1532
1533 static void clear_config_maps(void) 
1534 {
1535         struct ast_config_map *map;
1536
1537         ast_mutex_lock(&config_lock);
1538
1539         while (config_maps) {
1540                 map = config_maps;
1541                 config_maps = config_maps->next;
1542                 ast_free(map);
1543         }
1544                 
1545         ast_mutex_unlock(&config_lock);
1546 }
1547
1548 static int append_mapping(char *name, char *driver, char *database, char *table)
1549 {
1550         struct ast_config_map *map;
1551         int length;
1552
1553         length = sizeof(*map);
1554         length += strlen(name) + 1;
1555         length += strlen(driver) + 1;
1556         length += strlen(database) + 1;
1557         if (table)
1558                 length += strlen(table) + 1;
1559
1560         if (!(map = ast_calloc(1, length)))
1561                 return -1;
1562
1563         map->name = map->stuff;
1564         strcpy(map->name, name);
1565         map->driver = map->name + strlen(map->name) + 1;
1566         strcpy(map->driver, driver);
1567         map->database = map->driver + strlen(map->driver) + 1;
1568         strcpy(map->database, database);
1569         if (table) {
1570                 map->table = map->database + strlen(map->database) + 1;
1571                 strcpy(map->table, table);
1572         }
1573         map->next = config_maps;
1574
1575         ast_verb(2, "Binding %s to %s/%s/%s\n", map->name, map->driver, map->database, map->table ? map->table : map->name);
1576
1577         config_maps = map;
1578         return 0;
1579 }
1580
1581 int read_config_maps(void) 
1582 {
1583         struct ast_config *config, *configtmp;
1584         struct ast_variable *v;
1585         char *driver, *table, *database, *stringp, *tmp;
1586         struct ast_flags flags = { 0 };
1587
1588         clear_config_maps();
1589
1590         configtmp = ast_config_new();
1591         configtmp->max_include_level = 1;
1592         config = ast_config_internal_load(extconfig_conf, configtmp, flags, "");
1593         if (!config) {
1594                 ast_config_destroy(configtmp);
1595                 return 0;
1596         }
1597
1598         for (v = ast_variable_browse(config, "settings"); v; v = v->next) {
1599                 stringp = v->value;
1600                 driver = strsep(&stringp, ",");
1601
1602                 if ((tmp = strchr(stringp, '\"')))
1603                         stringp = tmp;
1604
1605                 /* check if the database text starts with a double quote */
1606                 if (*stringp == '"') {
1607                         stringp++;
1608                         database = strsep(&stringp, "\"");
1609                         strsep(&stringp, ",");
1610                 } else {
1611                         /* apparently this text has no quotes */
1612                         database = strsep(&stringp, ",");
1613                 }
1614
1615                 table = strsep(&stringp, ",");
1616
1617                 if (!strcmp(v->name, extconfig_conf)) {
1618                         ast_log(LOG_WARNING, "Cannot bind '%s'!\n", extconfig_conf);
1619                         continue;
1620                 }
1621
1622                 if (!strcmp(v->name, "asterisk.conf")) {
1623                         ast_log(LOG_WARNING, "Cannot bind 'asterisk.conf'!\n");
1624                         continue;
1625                 }
1626
1627                 if (!strcmp(v->name, "logger.conf")) {
1628                         ast_log(LOG_WARNING, "Cannot bind 'logger.conf'!\n");
1629                         continue;
1630                 }
1631
1632                 if (!driver || !database)
1633                         continue;
1634                 if (!strcasecmp(v->name, "sipfriends")) {
1635                         ast_log(LOG_WARNING, "The 'sipfriends' table is obsolete, update your config to use sipusers and sippeers, though they can point to the same table.\n");
1636                         append_mapping("sipusers", driver, database, table ? table : "sipfriends");
1637                         append_mapping("sippeers", driver, database, table ? table : "sipfriends");
1638                 } else if (!strcasecmp(v->name, "iaxfriends")) {
1639                         ast_log(LOG_WARNING, "The 'iaxfriends' table is obsolete, update your config to use iaxusers and iaxpeers, though they can point to the same table.\n");
1640                         append_mapping("iaxusers", driver, database, table ? table : "iaxfriends");
1641                         append_mapping("iaxpeers", driver, database, table ? table : "iaxfriends");
1642                 } else 
1643                         append_mapping(v->name, driver, database, table);
1644         }
1645                 
1646         ast_config_destroy(config);
1647         return 0;
1648 }
1649
1650 int ast_config_engine_register(struct ast_config_engine *new) 
1651 {
1652         struct ast_config_engine *ptr;
1653
1654         ast_mutex_lock(&config_lock);
1655
1656         if (!config_engine_list) {
1657                 config_engine_list = new;
1658         } else {
1659                 for (ptr = config_engine_list; ptr->next; ptr=ptr->next);
1660                 ptr->next = new;
1661         }
1662
1663         ast_mutex_unlock(&config_lock);
1664         ast_log(LOG_NOTICE,"Registered Config Engine %s\n", new->name);
1665
1666         return 1;
1667 }
1668
1669 int ast_config_engine_deregister(struct ast_config_engine *del) 
1670 {
1671         struct ast_config_engine *ptr, *last=NULL;
1672
1673         ast_mutex_lock(&config_lock);
1674
1675         for (ptr = config_engine_list; ptr; ptr=ptr->next) {
1676                 if (ptr == del) {
1677                         if (last)
1678                                 last->next = ptr->next;
1679                         else
1680                                 config_engine_list = ptr->next;
1681                         break;
1682                 }
1683                 last = ptr;
1684         }
1685
1686         ast_mutex_unlock(&config_lock);
1687
1688         return 0;
1689 }
1690
1691 /*! \brief Find realtime engine for realtime family */
1692 static struct ast_config_engine *find_engine(const char *family, char *database, int dbsiz, char *table, int tabsiz) 
1693 {
1694         struct ast_config_engine *eng, *ret = NULL;
1695         struct ast_config_map *map;
1696
1697         ast_mutex_lock(&config_lock);
1698
1699         for (map = config_maps; map; map = map->next) {
1700                 if (!strcasecmp(family, map->name)) {
1701                         if (database)
1702                                 ast_copy_string(database, map->database, dbsiz);
1703                         if (table)
1704                                 ast_copy_string(table, map->table ? map->table : family, tabsiz);
1705                         break;
1706                 }
1707         }
1708
1709         /* Check if the required driver (engine) exist */
1710         if (map) {
1711                 for (eng = config_engine_list; !ret && eng; eng = eng->next) {
1712                         if (!strcasecmp(eng->name, map->driver))
1713                                 ret = eng;
1714                 }
1715         }
1716
1717         ast_mutex_unlock(&config_lock);
1718         
1719         /* if we found a mapping, but the engine is not available, then issue a warning */
1720         if (map && !ret)
1721                 ast_log(LOG_WARNING, "Realtime mapping for '%s' found to engine '%s', but the engine is not available\n", map->name, map->driver);
1722
1723         return ret;
1724 }
1725
1726 static struct ast_config_engine text_file_engine = {
1727         .name = "text",
1728         .load_func = config_text_file_load,
1729 };
1730
1731 struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, struct ast_flags flags, const char *suggested_include_file)
1732 {
1733         char db[256];
1734         char table[256];
1735         struct ast_config_engine *loader = &text_file_engine;
1736         struct ast_config *result; 
1737
1738         if (cfg->include_level == cfg->max_include_level) {
1739                 ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", cfg->max_include_level);
1740                 return NULL;
1741         }
1742
1743         cfg->include_level++;
1744
1745         if (strcmp(filename, extconfig_conf) && strcmp(filename, "asterisk.conf") && config_engine_list) {
1746                 struct ast_config_engine *eng;
1747
1748                 eng = find_engine(filename, db, sizeof(db), table, sizeof(table));
1749
1750
1751                 if (eng && eng->load_func) {
1752                         loader = eng;
1753                 } else {
1754                         eng = find_engine("global", db, sizeof(db), table, sizeof(table));
1755                         if (eng && eng->load_func)
1756                                 loader = eng;
1757                 }
1758         }
1759
1760         result = loader->load_func(db, table, filename, cfg, flags, suggested_include_file);
1761
1762         if (result && result != CONFIG_STATUS_FILEUNCHANGED)
1763                 result->include_level--;
1764         else
1765                 cfg->include_level--;
1766
1767         return result;
1768 }
1769
1770 struct ast_config *ast_config_load(const char *filename, struct ast_flags flags)
1771 {
1772         struct ast_config *cfg;
1773         struct ast_config *result;
1774
1775         cfg = ast_config_new();
1776         if (!cfg)
1777                 return NULL;
1778
1779         result = ast_config_internal_load(filename, cfg, flags, "");
1780         if (!result || result == CONFIG_STATUS_FILEUNCHANGED)
1781                 ast_config_destroy(cfg);
1782
1783         return result;
1784 }
1785
1786 static struct ast_variable *ast_load_realtime_helper(const char *family, va_list ap)
1787 {
1788         struct ast_config_engine *eng;
1789         char db[256]="";
1790         char table[256]="";
1791         struct ast_variable *res=NULL;
1792
1793         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1794         if (eng && eng->realtime_func) 
1795                 res = eng->realtime_func(db, table, ap);
1796
1797         return res;
1798 }
1799
1800 struct ast_variable *ast_load_realtime_all(const char *family, ...)
1801 {
1802         struct ast_variable *res;
1803         va_list ap;
1804
1805         va_start(ap, family);
1806         res = ast_load_realtime_helper(family, ap);
1807         va_end(ap);
1808
1809         return res;
1810 }
1811
1812 struct ast_variable *ast_load_realtime(const char *family, ...)
1813 {
1814         struct ast_variable *res, *cur, *prev = NULL, *freeme = NULL;
1815         va_list ap;
1816
1817         va_start(ap, family);
1818         res = ast_load_realtime_helper(family, ap);
1819         va_end(ap);
1820
1821         /* Eliminate blank entries */
1822         for (cur = res; cur; cur = cur->next) {
1823                 if (freeme) {
1824                         ast_free(freeme);
1825                         freeme = NULL;
1826                 }
1827
1828                 if (ast_strlen_zero(cur->value)) {
1829                         if (prev)
1830                                 prev->next = cur->next;
1831                         else
1832                                 res = cur->next;
1833                         freeme = cur;
1834                 } else {
1835                         prev = cur;
1836                 }
1837         }
1838         return res;
1839 }
1840
1841 /*! \brief Check if realtime engine is configured for family */
1842 int ast_check_realtime(const char *family)
1843 {
1844         struct ast_config_engine *eng;
1845
1846         eng = find_engine(family, NULL, 0, NULL, 0);
1847         if (eng)
1848                 return 1;
1849         return 0;
1850
1851 }
1852
1853 /*! \brief Check if there's any realtime engines loaded */
1854 int ast_realtime_enabled()
1855 {
1856         return config_maps ? 1 : 0;
1857 }
1858
1859 struct ast_config *ast_load_realtime_multientry(const char *family, ...)
1860 {
1861         struct ast_config_engine *eng;
1862         char db[256]="";
1863         char table[256]="";
1864         struct ast_config *res=NULL;
1865         va_list ap;
1866
1867         va_start(ap, family);
1868         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1869         if (eng && eng->realtime_multi_func) 
1870                 res = eng->realtime_multi_func(db, table, ap);
1871         va_end(ap);
1872
1873         return res;
1874 }
1875
1876 int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...)
1877 {
1878         struct ast_config_engine *eng;
1879         int res = -1;
1880         char db[256]="";
1881         char table[256]="";
1882         va_list ap;
1883
1884         va_start(ap, lookup);
1885         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1886         if (eng && eng->update_func) 
1887                 res = eng->update_func(db, table, keyfield, lookup, ap);
1888         va_end(ap);
1889
1890         return res;
1891 }
1892
1893 int ast_store_realtime(const char *family, ...) {
1894         struct ast_config_engine *eng;
1895         int res = -1;
1896         char db[256]="";
1897         char table[256]="";
1898         va_list ap;
1899
1900         va_start(ap, family);
1901         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1902         if (eng && eng->store_func) 
1903                 res = eng->store_func(db, table, ap);
1904         va_end(ap);
1905
1906         return res;
1907 }
1908
1909 int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...) {
1910         struct ast_config_engine *eng;
1911         int res = -1;
1912         char db[256]="";
1913         char table[256]="";
1914         va_list ap;
1915
1916         va_start(ap, lookup);
1917         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1918         if (eng && eng->destroy_func) 
1919                 res = eng->destroy_func(db, table, keyfield, lookup, ap);
1920         va_end(ap);
1921
1922         return res;
1923 }
1924
1925 /*! \brief Helper function to parse arguments
1926  * See documentation in config.h
1927  */
1928 int ast_parse_arg(const char *arg, enum ast_parse_flags flags,
1929         void *p_result, ...)
1930 {
1931         va_list ap;
1932         int error = 0;
1933
1934         va_start(ap, p_result);
1935         switch (flags & PARSE_TYPE) {
1936         case PARSE_INT32:
1937             {
1938                 int32_t *result = p_result;
1939                 int32_t x, def = result ? *result : 0,
1940                         high = (int32_t)0x7fffffff,
1941                         low  = (int32_t)0x80000000;
1942                 /* optional argument: first default value, then range */
1943                 if (flags & PARSE_DEFAULT)
1944                         def = va_arg(ap, int32_t);
1945                 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
1946                         /* range requested, update bounds */
1947                         low = va_arg(ap, int32_t);
1948                         high = va_arg(ap, int32_t);
1949                 }
1950                 x = strtol(arg, NULL, 0);
1951                 error = (x < low) || (x > high);
1952                 if (flags & PARSE_OUT_RANGE)
1953                         error = !error;
1954                 if (result)
1955                         *result  = error ? def : x;
1956                 ast_debug(3,
1957                         "extract int from [%s] in [%d, %d] gives [%d](%d)\n",
1958                         arg, low, high,
1959                         result ? *result : x, error);
1960                 break;
1961             }
1962
1963         case PARSE_UINT32:
1964             {
1965                 uint32_t *result = p_result;
1966                 uint32_t x, def = result ? *result : 0,
1967                         low = 0, high = (uint32_t)~0;
1968                 /* optional argument: first default value, then range */
1969                 if (flags & PARSE_DEFAULT)
1970                         def = va_arg(ap, uint32_t);
1971                 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
1972                         /* range requested, update bounds */
1973                         low = va_arg(ap, uint32_t);
1974                         high = va_arg(ap, uint32_t);
1975                 }
1976                 x = strtoul(arg, NULL, 0);
1977                 error = (x < low) || (x > high);
1978                 if (flags & PARSE_OUT_RANGE)
1979                         error = !error;
1980                 if (result)
1981                         *result  = error ? def : x;
1982                 ast_debug(3,
1983                         "extract uint from [%s] in [%u, %u] gives [%u](%d)\n",
1984                         arg, low, high,
1985                         result ? *result : x, error);
1986                 break;
1987             }
1988
1989         case PARSE_INADDR:
1990             {
1991                 char *port, *buf;
1992                 struct sockaddr_in _sa_buf;     /* buffer for the result */
1993                 struct sockaddr_in *sa = p_result ?
1994                         (struct sockaddr_in *)p_result : &_sa_buf;
1995                 /* default is either the supplied value or the result itself */
1996                 struct sockaddr_in *def = (flags & PARSE_DEFAULT) ?
1997                         va_arg(ap, struct sockaddr_in *) : sa;
1998                 struct hostent *hp;
1999                 struct ast_hostent ahp;
2000
2001                 bzero(&_sa_buf, sizeof(_sa_buf)); /* clear buffer */
2002                 /* duplicate the string to strip away the :port */
2003                 port = ast_strdupa(arg);
2004                 buf = strsep(&port, ":");
2005                 sa->sin_family = AF_INET;       /* assign family */
2006                 /*
2007                  * honor the ports flag setting, assign default value
2008                  * in case of errors or field unset.
2009                  */
2010                 flags &= PARSE_PORT_MASK; /* the only flags left to process */
2011                 if (port) {
2012                         if (flags == PARSE_PORT_FORBID) {
2013                                 error = 1;      /* port was forbidden */
2014                                 sa->sin_port = def->sin_port;
2015                         } else if (flags == PARSE_PORT_IGNORE)
2016                                 sa->sin_port = def->sin_port;
2017                         else /* accept or require */
2018                                 sa->sin_port = htons(strtol(port, NULL, 0));
2019                 } else {
2020                         sa->sin_port = def->sin_port;
2021                         if (flags == PARSE_PORT_REQUIRE)
2022                                 error = 1;
2023                 }
2024                 /* Now deal with host part, even if we have errors before. */
2025                 hp = ast_gethostbyname(buf, &ahp);
2026                 if (hp) /* resolved successfully */
2027                         memcpy(&sa->sin_addr, hp->h_addr, sizeof(sa->sin_addr));
2028                 else {
2029                         error = 1;
2030                         sa->sin_addr = def->sin_addr;
2031                 }
2032                 ast_debug(3,
2033                         "extract inaddr from [%s] gives [%s:%d](%d)\n",
2034                         arg, ast_inet_ntoa(sa->sin_addr),
2035                         ntohs(sa->sin_port), error);
2036                 break;
2037             }
2038         }
2039         va_end(ap);
2040         return error;
2041 }
2042
2043 static int config_command(int fd, int argc, char **argv) 
2044 {
2045         struct ast_config_engine *eng;
2046         struct ast_config_map *map;
2047         
2048         ast_mutex_lock(&config_lock);
2049
2050         ast_cli(fd, "\n\n");
2051         for (eng = config_engine_list; eng; eng = eng->next) {
2052                 ast_cli(fd, "\nConfig Engine: %s\n", eng->name);
2053                 for (map = config_maps; map; map = map->next)
2054                         if (!strcasecmp(map->driver, eng->name)) {
2055                                 ast_cli(fd, "===> %s (db=%s, table=%s)\n", map->name, map->database,
2056                                         map->table ? map->table : map->name);
2057                         }
2058         }
2059         ast_cli(fd,"\n\n");
2060         
2061         ast_mutex_unlock(&config_lock);
2062
2063         return 0;
2064 }
2065
2066 static char show_config_help[] =
2067         "Usage: core show config mappings\n"
2068         "       Shows the filenames to config engines.\n";
2069
2070 static struct ast_cli_entry cli_config[] = {
2071         { { "core", "show", "config", "mappings", NULL },
2072         config_command, "Display config mappings (file names to config engines)",
2073         show_config_help },
2074 };
2075
2076 int register_config_cli() 
2077 {
2078         ast_cli_register_multiple(cli_config, sizeof(cli_config) / sizeof(struct ast_cli_entry));
2079         return 0;
2080 }