Newer
Older
/*
* Asterisk -- A telephony toolkit for Linux.
*
* Configuration File Parser
*
Mark Spencer
committed
* Copyright (C) 1999-2004, 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>
#include <asterisk/config_pvt.h>
#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 '-'
struct ast_config *(*global_load_func)(const char *dbname, const char *table, const char *, struct ast_config *,struct ast_category **,struct ast_variable **,int);
static struct ast_config_map {
struct ast_config_map *next;
char *name;
char *driver;
char *database;
char *table;
char stuff[0];
} *maps = NULL;
AST_MUTEX_DEFINE_STATIC(ast_cust_config_lock);
static struct ast_config_reg *ast_cust_config_list;
static char *config_conf_file = "extconfig.conf";
void ast_destroy_realtime(struct ast_variable *v)
struct ast_variable *vn;
while(v) {
vn = v;
v = v->next;
free(vn);
Mark Spencer
committed
void ast_category_destroy(struct ast_category *cat)
{
ast_destroy_realtime(cat->root);
free(cat);
}
void ast_destroy(struct ast_config *ast)
{
struct ast_category *cat, *catn;
ast_destroy_realtime(cat->root);
catn = cat;
cat = cat->next;
free(catn);
}
free(ast);
}
/* Determine if this is a true value */
if (!strcasecmp(s, "yes") ||
!strcasecmp(s, "true") ||
!strcasecmp(s, "y") ||
!strcasecmp(s, "t") ||
{
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 0;
}
struct ast_variable *ast_variable_browse(struct ast_config *config, char *category)
{
struct ast_category *cat;
cat = config->root;
while(cat) {
if (cat->name == category)
return cat->root;
cat = cat->next;
}
cat = config->root;
while(cat) {
if (!strcasecmp(cat->name, category))
return cat->root;
cat = cat->next;
}
return NULL;
}
char *ast_variable_retrieve(struct ast_config *config, char *category, char *value)
{
struct ast_variable *v;
v = ast_variable_browse(config, category);
while (v) {
if (value == v->name)
return v->value;
v=v->next;
}
v = ast_variable_browse(config, category);
while (v) {
if (!strcasecmp(value, v->name))
return v->value;
v=v->next;
}
} else {
struct ast_category *cat;
cat = config->root;
while(cat) {
v = cat->root;
while (v) {
if (!strcasecmp(value, v->name))
return v->value;
v=v->next;
}
cat = cat->next;
}
int ast_category_exist(struct ast_config *config, char *category_name)
struct ast_category *category = NULL;
category = config->root;
while(category) {
if (!strcasecmp(category->name,category_name))
return 1;
category = category->next;
}
return 0;
static struct ast_config_reg *get_ast_cust_config_keyword(const char *name, char *database, int dbsiz, char *table, int tabsiz)
struct ast_config_reg *reg,*ret=NULL;
struct ast_config_map *map;
ast_mutex_lock(&ast_cust_config_lock);
map = maps;
while(map) {
if (!strcasecmp(name, map->name)) {
strncpy(database, map->database, dbsiz - 1);
if (map->table)
strncpy(table, map->table, tabsiz - 1);
else
strncpy(table, name, tabsiz - 1);
map = map->next;
if (map) {
for (reg=ast_cust_config_list;reg && !ret;reg=reg->next) {
if (!strcmp(reg->name,map->driver))
ret=reg;
ast_mutex_unlock(&ast_cust_config_lock);
return ret;
void ast_config_destroy_all(void)
struct ast_config_reg *key;
ast_mutex_lock(&ast_cust_config_lock);
for (key=ast_cust_config_list;key;key=key->next) {
ast_config_deregister(key);
ast_cust_config_list = NULL;
ast_mutex_unlock(&ast_cust_config_lock);
static struct ast_config_reg *get_config_registrations(void)
return ast_cust_config_list;
Mark Spencer
committed
static struct ast_config *__ast_load(const char *configfile, struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, int includelevel);
static int cfg_process(struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, char *buf, int lineno, const char *configfile, int includelevel )
cur = ast_strip(buf);
/* Actually parse the entry */
if (cur[0] == '[') {
/* A category header */
c = strchr(cur, ']');
if (c) {
*c = 0;
*_tmpc = malloc(sizeof(struct ast_category));
if (!*_tmpc) {
ast_destroy(tmp);
ast_log(LOG_WARNING,
"Out of memory, line %d\n", lineno);
return -1;
}
strncpy((*_tmpc)->name, cur+1, sizeof((*_tmpc)->name) - 1);
(*_tmpc)->root = NULL;
if (!tmp->prev)
tmp->root = *_tmpc;
else
tmp->prev->next = *_tmpc;
tmp->prev = *_tmpc;
*_last = NULL;
} else {
ast_log(LOG_WARNING,
"parse error: no closing ']', line %d of %s\n", lineno, configfile);
}
} 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)
c = NULL;
} else
c = NULL;
if (!strcasecmp(cur, "include")) {
/* A #include */
if (c) {
while((*c == '<') || (*c == '>') || (*c == '\"')) c++;
/* Get rid of leading mess */
cur = c;
c = cur + strlen(cur) - 1;
if ((*c == '>') || (*c == '<') || (*c == '\"'))
*c = '\0';
else
break;
}
if((c = strchr(cur,':'))) {
*c = '\0';
ast_log(LOG_WARNING, "Including files with explicit config engine no longer permitted. Please use extconfig.conf to specify all mappings\n");
} else {
__ast_load(cur, tmp, _tmpc, _last, includelevel + 1);
ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", includelevel);
} else
ast_log(LOG_WARNING, "Directive '#include' needs an argument (filename) at line %d of %s\n", lineno, configfile);
/* Strip off leading and trailing "'s and <>'s */
ast_log(LOG_WARNING, "Unknown directive '%s' at line %d of %s\n", cur, lineno, configfile);
} else {
/* Just a line (variable = value) */
if (!*_tmpc) {
ast_log(LOG_WARNING,
"parse error: No category context for line %d of %s\n", lineno, configfile);
}
c = strchr(cur, '=');
if (c) {
*c = 0;
c++;
/* Ignore > in => */
v = ast_new_variable(ast_strip(cur), ast_strip(c));
if (v) {
v->next = NULL;
v->lineno = lineno;
v->object = object;
/* Put and reset comments */
Mark Spencer
committed
v->blanklines = 0;
(*_last)->next = v;
else
(*_tmpc)->root = v;
*_last = v;
} else {
ast_destroy(tmp);
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);
}
int ast_save(char *configfile, struct ast_config *cfg, 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);
} else {
snprintf(fn, sizeof(fn), "%s/%s", AST_CONFIG_DIR, configfile);
}
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);
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
else
fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
if (var->blanklines) {
blanklines = var->blanklines;
while (blanklines) {
fprintf(f, "\n");
blanklines--;
}
}
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;
}
Mark Spencer
committed
struct ast_variable *ast_load_realtime(const char *family, ...)
{
struct ast_config_reg *reg;
char db[256]="";
char table[256]="";
Mark Spencer
committed
struct ast_variable *res=NULL;
va_list ap;
va_start(ap, family);
reg = get_ast_cust_config_keyword(family, db, sizeof(db), table, sizeof(table));
if (reg && reg->realtime_func)
Mark Spencer
committed
res = reg->realtime_func(db, table, ap);
va_end(ap);
return res;
}
struct ast_config *ast_load_realtime_multientry(const char *family, ...)
{
struct ast_config_reg *reg;
char db[256]="";
char table[256]="";
struct ast_config *res=NULL;
va_list ap;
va_start(ap, family);
reg = get_ast_cust_config_keyword(family, db, sizeof(db), table, sizeof(table));
if (reg && reg->realtime_multi_func)
res = reg->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_reg *reg;
int res = -1;
char db[256]="";
char table[256]="";
va_list ap;
va_start(ap, lookup);
reg = get_ast_cust_config_keyword(family, db, sizeof(db), table, sizeof(table));
if (reg && reg->update_func)
res = reg->update_func(db, table, keyfield, lookup, ap);
va_end(ap);
return res;
}
static struct ast_config *__ast_load(const char *configfile, struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, int includelevel)
char buf[8192];
char db[256];
char table[256];
int comment = 0, nest[MAX_NESTED_COMMENTS];
struct ast_config *(*load_func)(const char *database, const char *table, const char *, struct ast_config *,struct ast_category **,struct ast_variable **,int);
char *comment_p, *process_buf, *new_buf=NULL;
if (strcmp(configfile,config_conf_file) && strcmp(configfile,"asterisk.conf") && ast_cust_config_list) {
if (global_load_func) {
load_func = global_load_func;
} else {
reg = get_ast_cust_config_keyword(configfile, db, sizeof(db), table, sizeof(table));
if (reg && reg->static_func) {
load_func = reg->static_func;
reg = get_ast_cust_config_keyword(configfile, db, sizeof(db), table, sizeof(table));
if (reg && reg->static_func)
global_load_func = load_func = reg->static_func;
}
}
if (load_func) {
ast_log(LOG_NOTICE,"Loading Config %s via %s engine\n",configfile,reg && reg->name ? reg->name : "global");
if((tmp = load_func(db, table, configfile,tmp, _tmpc, _last, includelevel)))
snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, configfile);
}
if ((option_verbose > 1) && !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)
if((tmp = malloc(sizeof(struct ast_config)))) {
master = 1;
}
if (tmp) {
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;
Mark Spencer
committed
} else if(comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) {
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
/* 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
process_buf = new_buf;
}
} 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 && cfg_process(tmp, _tmpc, _last, process_buf, lineno, configfile, includelevel)) {
tmp = NULL;
break;
}
} else
ast_log(LOG_WARNING, "Out of memory\n");
} 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]);
}
int ast_config_register(struct ast_config_reg *new)
{
struct ast_config_reg *ptr;
ast_mutex_lock(&ast_cust_config_lock);
for(ptr=ast_cust_config_list;ptr->next;ptr=ptr->next);
}
ast_mutex_unlock(&ast_cust_config_lock);
ast_log(LOG_NOTICE,"Registered Config Engine %s\n",new->name);
return 1;
}
int ast_config_deregister(struct ast_config_reg *del)
{
struct ast_config_reg *ptr=NULL,*last=NULL;
ast_mutex_lock(&ast_cust_config_lock);
for (ptr=ast_cust_config_list;ptr;ptr=ptr->next) {
if (ptr == del) {
if (last && ptr->next) {
last->next = ptr->next;
} else if (last && ! ptr->next) {
last->next = NULL;
} else if (!last && ptr->next) {
ast_cust_config_list = ptr->next;
} else if (!last && !ptr->next) {
ast_cust_config_list = NULL;
}
}
last = ptr;
}
ast_mutex_unlock(&ast_cust_config_lock);
return 0;
}
int ast_cust_config_active(void) {
return (ast_cust_config >0) ? 1 : 0;
}
struct ast_config *ast_load(char *configfile)
{
struct ast_category *tmpc=NULL;
struct ast_variable *last = NULL;
return __ast_load(configfile, NULL, &tmpc, &last, 0);
Mark Spencer
committed
void ast_category_append(struct ast_config *config, struct ast_category *cat)
{
struct ast_category *prev = NULL;
cat->next = NULL;
if (config->root) {
prev = config->root;
while(prev->next) prev = prev->next;
prev->next = cat;
} else
config->root = cat;
}
char *ast_category_browse(struct ast_config *config, char *prev)
{
struct ast_category *cat;
if (!prev) {
if (config->root)
return config->root->name;
else
return NULL;
}
cat = config->root;
while(cat) {
if (cat->name == prev) {
if (cat->next)
return cat->next->name;
else
return NULL;
}
cat = cat->next;
}
cat = config->root;
while(cat) {
if (!strcasecmp(cat->name, prev)) {
if (cat->next)
return cat->next->name;
else
return NULL;
}
cat = cat->next;
}
return NULL;
}
struct ast_config *ast_new_config(void)
{
struct ast_config *config;
config = malloc(sizeof(struct ast_config));
memset(config,0,sizeof(struct ast_config));
return config;
}
struct ast_category *ast_new_category(char *name)
{
struct ast_category *category;
category = malloc(sizeof(struct ast_category));
memset(category,0,sizeof(struct ast_category));
strncpy(category->name,name,sizeof(category->name) - 1);
struct ast_variable *ast_new_variable(char *name, char *value)
{
int length = strlen(name) + strlen(value) + 2 + sizeof(struct ast_variable);
variable = malloc(length);
memset(variable, 0, length);
variable->name = variable->stuff;
variable->value = variable->stuff + strlen(name) + 1;
strcpy(variable->name,name);
strcpy(variable->value,value);
int ast_cust_config_register(struct ast_config_reg *new)
{
ast_config_register(new);
read_ast_cust_config();
return 1;
}
int ast_cust_config_deregister(struct ast_config_reg *new)
{
ast_config_deregister(new);
read_ast_cust_config();
return 1;
}
static void clear_cust_keywords(void)
{
struct ast_config_map *map, *prev;
map = maps;
while(map) {
prev = map;
map = map->next;
free(prev);
maps = NULL;
ast_mutex_unlock(&ast_cust_config_lock);
}
static int config_command(int fd, int argc, char **argv)
{
struct ast_config_map *map;
ast_cli(fd,"\n\n");
ast_mutex_lock(&ast_cust_config_lock);
for (key=get_config_registrations();key;key=key->next) {
ast_cli(fd,"\nConfig Engine: %s\n",key->name);
map = maps;
while(map) {
if (!strcasecmp(map->driver, key->name))
ast_cli(fd,"===> %s (db=%s, table=%s)\n",map->name, map->database, map->table ? map->table : map->name);
map = map->next;
}
}
ast_mutex_unlock(&ast_cust_config_lock);
ast_cli(fd,"\n\n");
}
static struct ast_cli_entry config_command_struct = {
{ "show","config","handles", NULL }, config_command,
"Show Config Handles", NULL };
return ast_cli_register(&config_command_struct);
}
char *cfg = config_conf_file;
struct ast_config *config;
struct ast_variable *v;
struct ast_config_map *map;
int length;
char *driver, *table, *database, *stringp;
clear_cust_keywords();
config = ast_load(cfg);
if (config) {
for (v = ast_variable_browse(config,"settings");v;v=v->next) {
stringp = v->value;
driver = strsep(&stringp, ",");
database = strsep(&stringp, ",");
table = strsep(&stringp, ",");
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
if (!strcmp(v->name,config_conf_file) || !strcmp(v->name,"asterisk.conf")) {
ast_log(LOG_WARNING, "Cannot bind asterisk.conf or extconfig.conf!\n");
} else if (driver && database) {
length = sizeof(struct ast_config_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) {
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);
} else
map->table = NULL;
map->next = 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);
maps = map;
}
}
}
ast_destroy(config);
}