Newer
Older
/*
* Asterisk -- A telephony toolkit for Linux.
*
* Configuration File Parser
*
* Copyright (C) 1999 - 2005, Digium, Inc.
Mark Spencer
committed
* Mark Spencer <markster@digium.com>
*
* This program is free software, distributed under the terms of
* the GNU General Public License
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define AST_INCLUDE_GLOB 1
#ifdef AST_INCLUDE_GLOB
#ifdef __OSX__
#define GLOB_ABORTED GLOB_ABEND
#endif
#include <asterisk/cli.h>
#include <asterisk/lock.h>
#include <asterisk/options.h>
#include <asterisk/logger.h>
#define MAX_NESTED_COMMENTS 128
#define COMMENT_START ";--"
#define COMMENT_END "--;"
#define COMMENT_META ';'
#define COMMENT_TAG '-'
static char *extconfig_conf = "extconfig.conf";
static struct ast_config_map {
struct ast_config_map *next;
char *name;
char *driver;
char *database;
char *table;
char stuff[0];
} *config_maps = NULL;
AST_MUTEX_DEFINE_STATIC(config_lock);
static struct ast_config_engine *config_engine_list;
#define MAX_INCLUDE_LEVEL 10
struct ast_comment {
struct ast_comment *next;
char cmt[0];
};
struct ast_category {
char name[80];
struct ast_variable *root;
struct ast_variable *last;
struct ast_category *next;
};
struct ast_config {
struct ast_category *root;
struct ast_category *last;
struct ast_category *current;
struct ast_category *last_browse; /* used to cache the last category supplied via category_browse */
int include_level;
int max_include_level;
};
/* Determine if this is a true value */
if (!strcasecmp(s, "yes") ||
!strcasecmp(s, "true") ||
!strcasecmp(s, "y") ||
!strcasecmp(s, "t") ||
!strcasecmp(s, "1") ||
!strcasecmp(s, "on"))
return -1;
{
if (!s)
return 0;
/* Determine if this is a false value */
if (!strcasecmp(s, "no") ||
!strcasecmp(s, "false") ||
!strcasecmp(s, "n") ||
!strcasecmp(s, "f") ||
!strcasecmp(s, "0") ||
!strcasecmp(s, "off"))
return -1;
struct ast_variable *ast_variable_new(const char *name, const char *value)
struct ast_variable *variable;
int length = strlen(name) + strlen(value) + 2 + sizeof(struct ast_variable);
variable = malloc(length);
if (variable) {
memset(variable, 0, length);
variable->name = variable->stuff;
variable->value = variable->stuff + strlen(name) + 1;
strcpy(variable->name,name);
strcpy(variable->value,value);
return variable;
}
void ast_variable_append(struct ast_category *category, struct ast_variable *variable)
{
if (category->last)
category->last->next = variable;
else
category->root = variable;
category->last = variable;
}
void ast_variables_destroy(struct ast_variable *v)
{
struct ast_variable *vn;
while(v) {
vn = v;
v = v->next;
free(vn);
struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category)
{
struct ast_category *cat = NULL;
if (category && config->last_browse && (config->last_browse->name == category))
cat = config->last_browse;
else
cat = ast_category_get(config, category);
if (cat)
return cat->root;
else
return NULL;
}
char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
for (v = ast_variable_browse(config, category); v; v = v->next)
if (variable == v->name)
for (v = ast_variable_browse(config, category); v; v = v->next)
if (!strcasecmp(variable, v->name))
return v->value;
} else {
struct ast_category *cat;
for (cat = config->root; cat; cat = cat->next)
for (v = cat->root; v; v = v->next)
if (!strcasecmp(variable, v->name))
struct ast_category *ast_category_new(const char *name)
{
struct ast_category *category;
category = malloc(sizeof(struct ast_category));
if (category) {
memset(category, 0, sizeof(struct ast_category));
strncpy(category->name, name, sizeof(category->name) - 1);
}
return category;
}
struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name)
for (cat = config->root; cat; cat = cat->next) {
if (cat->name == category_name)
return cat;
}
for (cat = config->root; cat; cat = cat->next) {
if (!strcasecmp(cat->name, category_name))
return cat;
int ast_category_exist(const struct ast_config *config, const char *category_name)
{
return !!ast_category_get(config, category_name);
void ast_category_append(struct ast_config *config, struct ast_category *category)
{
if (config->last)
config->last->next = category;
else
config->root = category;
config->last = category;
config->current = category;
}
void ast_category_destroy(struct ast_category *cat)
ast_variables_destroy(cat->root);
free(cat);
}
char *ast_category_browse(struct ast_config *config, const char *prev)
{
struct ast_category *cat = NULL;
if (prev && config->last_browse && (config->last_browse->name == prev))
cat = config->last_browse->next;
else if (!prev && config->root)
cat = config->root;
else if (prev) {
for (cat = config->root; cat; cat = cat->next) {
if (cat->name == prev) {
cat = cat->next;
break;
}
if (!cat) {
for (cat = config->root; cat; cat = cat->next) {
if (!strcasecmp(cat->name, prev)) {
cat = cat->next;
break;
}
}
config->last_browse = cat;
if (cat)
return cat->name;
else
return NULL;
struct ast_variable *ast_category_detach_variables(struct ast_category *cat)
struct ast_variable *v;
v = cat->root;
cat->root = NULL;
return v;
}
void ast_category_rename(struct ast_category *cat, const char *name)
{
strncpy(cat->name, name, sizeof(cat->name) - 1);
}
struct ast_config *ast_config_new(void)
{
struct ast_config *config;
config = malloc(sizeof(*config));
if (config) {
memset(config, 0, sizeof(*config));
config->max_include_level = MAX_INCLUDE_LEVEL;
void ast_config_destroy(struct ast_config *cfg)
struct ast_category *cat, *catn;
if (!cfg)
return;
cat = cfg->root;
while(cat) {
ast_variables_destroy(cat->root);
catn = cat;
cat = cat->next;
free(catn);
}
free(cfg);
struct ast_category *ast_config_get_current_category(const struct ast_config *cfg)
Mark Spencer
committed
void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat)
/* cast below is just to silence compiler warning about dropping "const" */
cfg->current = (struct ast_category *) cat;
static int process_text_line(struct ast_config *cfg, struct ast_category **cat, char *buf, int lineno, const char *configfile)
{
char *c;
char *cur;
struct ast_variable *v;
cur = ast_strip(buf);
if (ast_strlen_zero(cur))
return 0;
/* Actually parse the entry */
if (cur[0] == '[') {
/* A category header */
c = strchr(cur, ']');
if (!c) {
ast_log(LOG_WARNING, "parse error: no closing ']', line %d of %s\n", lineno, configfile);
return -1;
}
*c = '\0';
cur++;
if (!*cat) {
ast_log(LOG_WARNING, "Out of memory, line %d of %s\n", lineno, configfile);
return -1;
}
} else if (cur[0] == '#') {
/* A directive */
cur++;
c = cur;
while(*c && (*c > 32)) c++;
if (*c) {
*c = '\0';
c++;
/* Find real argument */
while(*c && (*c < 33)) c++;
if (!*c)
} else
c = NULL;
if (!strcasecmp(cur, "include")) {
/* A #include */
if (c) {
/* Strip off leading and trailing "'s and <>'s */
while((*c == '<') || (*c == '>') || (*c == '\"')) c++;
/* Get rid of leading mess */
cur = c;
while (!ast_strlen_zero(cur)) {
c = cur + strlen(cur) - 1;
if ((*c == '>') || (*c == '<') || (*c == '\"'))
if (!ast_config_internal_load(cur, cfg))
return -1;
} else
ast_log(LOG_WARNING, "Directive '#include' needs an argument (filename) at line %d of %s\n", lineno, configfile);
}
else
ast_log(LOG_WARNING, "Unknown directive '%s' at line %d of %s\n", cur, lineno, configfile);
} else {
/* Just a line (variable = value) */
if (!*cat) {
ast_log(LOG_WARNING,
"parse error: No category context for line %d of %s\n", lineno, configfile);
return -1;
}
c = strchr(cur, '=');
if (c) {
*c = 0;
c++;
/* Ignore > in => */
if (*c== '>') {
object = 1;
v = ast_variable_new(ast_strip(cur), ast_strip(c));
if (v) {
v->lineno = lineno;
v->object = object;
/* Put and reset comments */
v->blanklines = 0;
ast_log(LOG_WARNING, "Out of memory, line %d\n", lineno);
return -1;
} else {
ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
static struct ast_config *config_text_file_load(const char *database, const char *table, const char *filename, struct ast_config *cfg)
char buf[8192];
char *new_buf, *comment_p, *process_buf;
int comment = 0, nest[MAX_NESTED_COMMENTS];
cat = ast_config_get_current_category(cfg);
if (filename[0] == '/') {
strncpy(fn, filename, sizeof(fn)-1);
snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, filename);
#ifdef AST_INCLUDE_GLOB
{
int glob_ret;
glob_t globbuf;
globbuf.gl_offs = 0; /* initialize it to silence gcc */
#else
glob_ret = glob(fn, GLOB_NOMAGIC|GLOB_BRACE, NULL, &globbuf);
#endif
if (glob_ret == GLOB_NOSPACE)
ast_log(LOG_WARNING,
"Glob Expansion of pattern '%s' failed: Not enough memory\n", fn);
else if (glob_ret == GLOB_ABORTED)
ast_log(LOG_WARNING,
"Glob Expansion of pattern '%s' failed: Read error\n", fn);
else {
/* loop over expanded files */
int i;
for (i=0; i<globbuf.gl_pathc; i++) {
strncpy(fn, globbuf.gl_pathv[i], sizeof(fn)-1);
#endif
if ((option_verbose > 2) && !option_debug) {
ast_verbose( VERBOSE_PREFIX_2 "Parsing '%s': ", fn);
fflush(stdout);
}
if ((f = fopen(fn, "r"))) {
if (option_debug)
ast_log(LOG_DEBUG, "Parsing %s\n", fn);
else if (option_verbose > 2)
ast_verbose("Found\n");
while(!feof(f)) {
lineno++;
if (fgets(buf, sizeof(buf), f)) {
new_buf = buf;
if (comment)
process_buf = NULL;
else
process_buf = buf;
while ((comment_p = strchr(new_buf, COMMENT_META))) {
if ((comment_p > new_buf) && (*(comment_p-1) == '\\')) {
/* Yuck, gotta memmove */
memmove(comment_p - 1, comment_p, strlen(comment_p) + 1);
new_buf = comment_p;
} else if(comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) {
/* Meta-Comment start detected ";--" */
if (comment < MAX_NESTED_COMMENTS) {
*comment_p = '\0';
new_buf = comment_p + 3;
comment++;
nest[comment-1] = lineno;
} else {
ast_log(LOG_ERROR, "Maximum nest limit of %d reached.\n", MAX_NESTED_COMMENTS);
}
} else if ((comment_p >= new_buf + 2) &&
(*(comment_p - 1) == COMMENT_TAG) &&
(*(comment_p - 2) == COMMENT_TAG)) {
/* Meta-Comment end detected */
comment--;
new_buf = comment_p + 1;
if (!comment) {
/* Back to non-comment now */
if (process_buf) {
/* Actually have to move what's left over the top, then continue */
char *oldptr;
oldptr = process_buf + strlen(process_buf);
memmove(oldptr, new_buf, strlen(new_buf) + 1);
new_buf = oldptr;
} else
} else {
if (!comment) {
/* If ; is found, and we are not nested in a comment,
we immediately stop all comment processing */
*comment_p = '\0';
new_buf = comment_p;
} else
new_buf = comment_p + 1;
if (process_buf && process_text_line(cfg, &cat, process_buf, lineno, filename)) {
cfg = NULL;
} else { /* can't open file */
if (option_debug)
ast_log(LOG_DEBUG, "No file to parse: %s\n", fn);
else if (option_verbose > 2)
ast_verbose( "Not found (%s)\n", strerror(errno));
if (comment) {
ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment]);
}
break;
}
globfree(&globbuf);
}
}
#endif
int config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
FILE *f;
char fn[256];
char date[256]="";
time_t t;
struct ast_variable *var;
struct ast_category *cat;
int blanklines = 0;
if (configfile[0] == '/') {
strncpy(fn, configfile, sizeof(fn)-1);
snprintf(fn, sizeof(fn), "%s/%s", AST_CONFIG_DIR, configfile);
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
time(&t);
strncpy(date, ctime(&t), sizeof(date) - 1);
if ((f = fopen(fn, "w"))) {
if ((option_verbose > 1) && !option_debug)
ast_verbose( VERBOSE_PREFIX_2 "Saving '%s': ", fn);
fprintf(f, ";!\n");
fprintf(f, ";! Automatically generated configuration file\n");
fprintf(f, ";! Filename: %s (%s)\n", configfile, fn);
fprintf(f, ";! Generator: %s\n", generator);
fprintf(f, ";! Creation Date: %s", date);
fprintf(f, ";!\n");
cat = cfg->root;
while(cat) {
/* Dump section with any appropriate comment */
fprintf(f, "[%s]\n", cat->name);
var = cat->root;
while(var) {
if (var->sameline)
fprintf(f, "%s %s %s ; %s\n", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
else
fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
if (var->blanklines) {
blanklines = var->blanklines;
while (blanklines--)
fprintf(f, "\n");
}
var = var->next;
}
#if 0
/* Put an empty line */
fprintf(f, "\n");
#endif
cat = cat->next;
}
} else {
if (option_debug)
printf("Unable to open for writing: %s\n", fn);
else if (option_verbose > 1)
printf( "Unable to write (%s)", strerror(errno));
return -1;
}
fclose(f);
return 0;
}
static void clear_config_maps(void)
{
struct ast_config_map *map;
ast_mutex_lock(&config_lock);
while (config_maps) {
map = config_maps;
config_maps = config_maps->next;
free(map);
}
ast_mutex_unlock(&config_lock);
}
void read_config_maps(void)
{
struct ast_config *config;
struct ast_variable *v;
struct ast_config_map *map;
int length;
char *driver, *table, *database, *stringp;
clear_config_maps();
config = ast_config_new();
config->max_include_level = 1;
config = ast_config_internal_load(extconfig_conf, config);
if (!config)
return;
for (v = ast_variable_browse(config, "settings"); v; v = v->next) {
stringp = v->value;
driver = strsep(&stringp, ",");
database = strsep(&stringp, ",");
table = strsep(&stringp, ",");
if (!strcmp(v->name, extconfig_conf) || !strcmp(v->name, "asterisk.conf")) {
ast_log(LOG_WARNING, "Cannot bind asterisk.conf or extconfig.conf!\n");
continue;
}
if (!driver || !database)
continue;
length = sizeof(*map);
length += strlen(v->name) + 1;
length += strlen(driver) + 1;
length += strlen(database) + 1;
if (table)
length += strlen(table) + 1;
map = malloc(length);
if (!map)
continue;
memset(map, 0, length);
map->name = map->stuff;
strcpy(map->name, v->name);
map->driver = map->name + strlen(map->name) + 1;
strcpy(map->driver, driver);
map->database = map->driver + strlen(map->driver) + 1;
strcpy(map->database, database);
if (table) {
map->table = map->database + strlen(map->database) + 1;
strcpy(map->table, table);
}
map->next = config_maps;
if (option_verbose > 1)
ast_verbose(VERBOSE_PREFIX_2 "Binding %s to %s/%s/%s\n",
map->name, map->driver, map->database, map->table ? map->table : map->name);
config_maps = map;
}
ast_config_destroy(config);
}
int ast_config_engine_register(struct ast_config_engine *new)
{
struct ast_config_engine *ptr;
ast_mutex_lock(&config_lock);
if (!config_engine_list) {
config_engine_list = new;
} else {
for (ptr = config_engine_list; ptr->next; ptr=ptr->next);
ptr->next = new;
}
ast_mutex_unlock(&config_lock);
ast_log(LOG_NOTICE,"Registered Config Engine %s\n", new->name);
int ast_config_engine_deregister(struct ast_config_engine *del)
struct ast_config_engine *ptr, *last=NULL;
ast_mutex_lock(&config_lock);
for (ptr = config_engine_list; ptr; ptr=ptr->next) {
else
config_engine_list = ptr->next;
break;
ast_mutex_unlock(&config_lock);
return 0;
static struct ast_config_engine *find_engine(const char *filename, char *database, int dbsiz, char *table, int tabsiz)
struct ast_config_engine *eng, *ret=NULL;
struct ast_config_map *map;
Mark Spencer
committed
map = config_maps;
while (map) {
if (!strcasecmp(filename, map->name)) {
strncpy(database, map->database, dbsiz-1);
if (map->table)
strncpy(table, map->table, tabsiz-1);
strncpy(table, filename, tabsiz-1);
break;
if (map) {
for (eng = config_engine_list; eng; eng = eng->next) {
if (!strcmp(eng->name, map->driver)) {
ret = eng;
break;
}
ast_mutex_unlock(&config_lock);
return ret;
static struct ast_config_engine text_file_engine = {
.name = "text",
.load_func = config_text_file_load,
};
struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg)
char db[256];
char table[256];
struct ast_config_engine *loader = &text_file_engine;
struct ast_config *result;
if (cfg->include_level == cfg->max_include_level) {
ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", cfg->max_include_level);
return NULL;
cfg->include_level++;
if (strcmp(filename, extconfig_conf) && strcmp(filename, "asterisk.conf") && config_engine_list) {
struct ast_config_engine *eng;
eng = find_engine(filename, db, sizeof(db), table, sizeof(table));
if (eng && eng->load_func) {
loader = eng;
} else {
eng = find_engine("global", db, sizeof(db), table, sizeof(table));
if (eng && eng->load_func)
loader = eng;
}
}
result = loader->load_func(db, table, filename, cfg);
if (result)
result->include_level--;
return result;
struct ast_config *ast_config_load(const char *filename)
struct ast_config *cfg;
struct ast_config *result;
cfg = ast_config_new();
if (!cfg)
return NULL;
result = ast_config_internal_load(filename, cfg);
if (!result)
ast_config_destroy(cfg);
return result;
struct ast_variable *ast_load_realtime(const char *family, ...)
struct ast_config_engine *eng;
char db[256]="";
char table[256]="";
struct ast_variable *res=NULL;
va_list ap;
va_start(ap, family);
eng = find_engine(family, db, sizeof(db), table, sizeof(table));
if (eng && eng->realtime_func)
res = eng->realtime_func(db, table, ap);
va_end(ap);
return res;
struct ast_config *ast_load_realtime_multientry(const char *family, ...)
struct ast_config_engine *eng;
char db[256]="";
char table[256]="";
struct ast_config *res=NULL;
va_list ap;
va_start(ap, family);
eng = find_engine(family, db, sizeof(db), table, sizeof(table));
if (eng && eng->realtime_multi_func)
res = eng->realtime_multi_func(db, table, ap);
va_end(ap);
return res;
int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...)
struct ast_config_engine *eng;
int res = -1;
char db[256]="";
char table[256]="";
va_list ap;
va_start(ap, lookup);
eng = find_engine(family, db, sizeof(db), table, sizeof(table));
if (eng && eng->update_func)
res = eng->update_func(db, table, keyfield, lookup, ap);
va_end(ap);
return res;
static int config_command(int fd, int argc, char **argv)
{
struct ast_config_map *map;
ast_mutex_lock(&config_lock);
ast_cli(fd, "\n\n");
for (eng = config_engine_list; eng; eng = eng->next) {
ast_cli(fd, "\nConfig Engine: %s\n", eng->name);
for (map = config_maps; map; map = map->next)
if (!strcasecmp(map->driver, eng->name)) {
ast_cli(fd, "===> %s (db=%s, table=%s)\n", map->name, map->database,
map->table ? map->table : map->name);
break;
}
}
static struct ast_cli_entry config_command_struct = {
{ "show", "config", "mappings" }, config_command, "Show Config mappings (file names to config engines)"
};
return ast_cli_register(&config_command_struct);
}