tinc/src/conf.c
Florian Klink 91355b9ac5 (read|append)_config_file: log open errors as LOG_DEBUG
In a "decentrally managed vpn" it is very likely that host config
files for some reachable nodes do not exist. Currently, tinc
fills the logs with "Cannot open config file" messages.

This commit changes the log level to LOG_DEBUG so
syslog doesn't get filled by default.
2015-07-02 21:22:47 +02:00

433 lines
9.8 KiB
C

/*
conf.c -- configuration code
Copyright (C) 1998 Robert van der Meulen
1998-2005 Ivo Timmermans
2000 Cris van Pelt
2010-2011 Julien Muchembled <jm@jmuchemb.eu>
2000-2015 Guus Sliepen <guus@tinc-vpn.org>
2013 Florent Clairambault <florent@clairambault.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "system.h"
#include "splay_tree.h"
#include "connection.h"
#include "conf.h"
#include "list.h"
#include "logger.h"
#include "names.h"
#include "netutl.h" /* for str2address */
#include "protocol.h"
#include "utils.h" /* for cp */
#include "xalloc.h"
splay_tree_t *config_tree;
int pinginterval = 0; /* seconds between pings */
int pingtimeout = 0; /* seconds to wait for response */
list_t *cmdline_conf = NULL; /* global/host configuration values given at the command line */
static int config_compare(const config_t *a, const config_t *b) {
int result;
result = strcasecmp(a->variable, b->variable);
if(result)
return result;
/* give priority to command line options */
result = !b->file - !a->file;
if (result)
return result;
result = a->line - b->line;
if(result)
return result;
else
return a->file ? strcmp(a->file, b->file) : 0;
}
void init_configuration(splay_tree_t ** config_tree) {
*config_tree = splay_alloc_tree((splay_compare_t) config_compare, (splay_action_t) free_config);
}
void exit_configuration(splay_tree_t ** config_tree) {
splay_delete_tree(*config_tree);
*config_tree = NULL;
}
config_t *new_config(void) {
return xzalloc(sizeof(config_t));
}
void free_config(config_t *cfg) {
if(cfg->variable)
free(cfg->variable);
if(cfg->value)
free(cfg->value);
if(cfg->file)
free(cfg->file);
free(cfg);
}
void config_add(splay_tree_t *config_tree, config_t *cfg) {
splay_insert(config_tree, cfg);
}
config_t *lookup_config(splay_tree_t *config_tree, char *variable) {
config_t cfg, *found;
cfg.variable = variable;
cfg.file = NULL;
cfg.line = 0;
found = splay_search_closest_greater(config_tree, &cfg);
if(!found)
return NULL;
if(strcasecmp(found->variable, variable))
return NULL;
return found;
}
config_t *lookup_config_next(splay_tree_t *config_tree, const config_t *cfg) {
splay_node_t *node;
config_t *found;
node = splay_search_node(config_tree, cfg);
if(node) {
if(node->next) {
found = node->next->data;
if(!strcasecmp(found->variable, cfg->variable))
return found;
}
}
return NULL;
}
bool get_config_bool(const config_t *cfg, bool *result) {
if(!cfg)
return false;
if(!strcasecmp(cfg->value, "yes")) {
*result = true;
return true;
} else if(!strcasecmp(cfg->value, "no")) {
*result = false;
return true;
}
logger(DEBUG_ALWAYS, LOG_ERR, "\"yes\" or \"no\" expected for configuration variable %s in %s line %d",
cfg->variable, cfg->file, cfg->line);
return false;
}
bool get_config_int(const config_t *cfg, int *result) {
if(!cfg)
return false;
if(sscanf(cfg->value, "%d", result) == 1)
return true;
logger(DEBUG_ALWAYS, LOG_ERR, "Integer expected for configuration variable %s in %s line %d",
cfg->variable, cfg->file, cfg->line);
return false;
}
bool get_config_string(const config_t *cfg, char **result) {
if(!cfg)
return false;
*result = xstrdup(cfg->value);
return true;
}
bool get_config_address(const config_t *cfg, struct addrinfo **result) {
struct addrinfo *ai;
if(!cfg)
return false;
ai = str2addrinfo(cfg->value, NULL, 0);
if(ai) {
*result = ai;
return true;
}
logger(DEBUG_ALWAYS, LOG_ERR, "Hostname or IP address expected for configuration variable %s in %s line %d",
cfg->variable, cfg->file, cfg->line);
return false;
}
bool get_config_subnet(const config_t *cfg, subnet_t ** result) {
subnet_t subnet = {NULL};
if(!cfg)
return false;
if(!str2net(&subnet, cfg->value)) {
logger(DEBUG_ALWAYS, LOG_ERR, "Subnet expected for configuration variable %s in %s line %d",
cfg->variable, cfg->file, cfg->line);
return false;
}
/* Teach newbies what subnets are... */
if(((subnet.type == SUBNET_IPV4)
&& !maskcheck(&subnet.net.ipv4.address, subnet.net.ipv4.prefixlength, sizeof subnet.net.ipv4.address))
|| ((subnet.type == SUBNET_IPV6)
&& !maskcheck(&subnet.net.ipv6.address, subnet.net.ipv6.prefixlength, sizeof subnet.net.ipv6.address))) {
logger(DEBUG_ALWAYS, LOG_ERR, "Network address and prefix length do not match for configuration variable %s in %s line %d",
cfg->variable, cfg->file, cfg->line);
return false;
}
*(*result = new_subnet()) = subnet;
return true;
}
/*
Read exactly one line and strip the trailing newline if any.
*/
static char *readline(FILE * fp, char *buf, size_t buflen) {
char *newline = NULL;
char *p;
if(feof(fp))
return NULL;
p = fgets(buf, buflen, fp);
if(!p)
return NULL;
newline = strchr(p, '\n');
if(!newline)
return buf;
/* kill newline and carriage return if necessary */
*newline = '\0';
if(newline > p && newline[-1] == '\r')
newline[-1] = '\0';
return buf;
}
config_t *parse_config_line(char *line, const char *fname, int lineno) {
config_t *cfg;
int len;
char *variable, *value, *eol;
variable = value = line;
eol = line + strlen(line);
while(strchr("\t ", *--eol))
*eol = '\0';
len = strcspn(value, "\t =");
value += len;
value += strspn(value, "\t ");
if(*value == '=') {
value++;
value += strspn(value, "\t ");
}
variable[len] = '\0';
if(!*value) {
const char err[] = "No value for variable";
if (fname)
logger(DEBUG_ALWAYS, LOG_ERR, "%s `%s' on line %d while reading config file %s",
err, variable, lineno, fname);
else
logger(DEBUG_ALWAYS, LOG_ERR, "%s `%s' in command line option %d",
err, variable, lineno);
return NULL;
}
cfg = new_config();
cfg->variable = xstrdup(variable);
cfg->value = xstrdup(value);
cfg->file = fname ? xstrdup(fname) : NULL;
cfg->line = lineno;
return cfg;
}
/*
Parse a configuration file and put the results in the configuration tree
starting at *base.
*/
bool read_config_file(splay_tree_t *config_tree, const char *fname) {
FILE *fp;
char buffer[MAX_STRING_SIZE];
char *line;
int lineno = 0;
bool ignore = false;
config_t *cfg;
bool result = false;
fp = fopen(fname, "r");
if(!fp) {
logger(DEBUG_ALWAYS, LOG_DEBUG, "Cannot open config file %s: %s", fname, strerror(errno));
return false;
}
for(;;) {
line = readline(fp, buffer, sizeof buffer);
if(!line) {
if(feof(fp))
result = true;
break;
}
lineno++;
if(!*line || *line == '#')
continue;
if(ignore) {
if(!strncmp(line, "-----END", 8))
ignore = false;
continue;
}
if(!strncmp(line, "-----BEGIN", 10)) {
ignore = true;
continue;
}
cfg = parse_config_line(line, fname, lineno);
if (!cfg)
break;
config_add(config_tree, cfg);
}
fclose(fp);
return result;
}
void read_config_options(splay_tree_t *config_tree, const char *prefix) {
size_t prefix_len = prefix ? strlen(prefix) : 0;
for(const list_node_t *node = cmdline_conf->tail; node; node = node->prev) {
const config_t *cfg = node->data;
config_t *new;
if(!prefix) {
if(strchr(cfg->variable, '.'))
continue;
} else {
if(strncmp(prefix, cfg->variable, prefix_len) ||
cfg->variable[prefix_len] != '.')
continue;
}
new = new_config();
if(prefix)
new->variable = xstrdup(cfg->variable + prefix_len + 1);
else
new->variable = xstrdup(cfg->variable);
new->value = xstrdup(cfg->value);
new->file = NULL;
new->line = cfg->line;
config_add(config_tree, new);
}
}
bool read_server_config(void) {
char fname[PATH_MAX];
bool x;
read_config_options(config_tree, NULL);
snprintf(fname, sizeof fname, "%s" SLASH "tinc.conf", confbase);
errno = 0;
x = read_config_file(config_tree, fname);
// We will try to read the conf files in the "conf.d" dir
if (x) {
char dname[PATH_MAX];
snprintf(dname, sizeof dname, "%s" SLASH "conf.d", confbase);
DIR *dir = opendir (dname);
// If we can find this dir
if (dir) {
struct dirent *ep;
// We list all the files in it
while (x && (ep = readdir (dir))) {
size_t l = strlen(ep->d_name);
// And we try to read the ones that end with ".conf"
if (l > 5 && !strcmp(".conf", & ep->d_name[ l - 5 ])) {
snprintf(fname, sizeof fname, "%s" SLASH "%s", dname, ep->d_name);
x = read_config_file(config_tree, fname);
}
}
closedir (dir);
}
}
if(!x && errno)
logger(DEBUG_ALWAYS, LOG_ERR, "Failed to read `%s': %s", fname, strerror(errno));
return x;
}
bool read_host_config(splay_tree_t *config_tree, const char *name) {
char fname[PATH_MAX];
bool x;
read_config_options(config_tree, name);
snprintf(fname, sizeof fname, "%s" SLASH "hosts" SLASH "%s", confbase, name);
x = read_config_file(config_tree, fname);
return x;
}
bool append_config_file(const char *name, const char *key, const char *value) {
char fname[PATH_MAX];
snprintf(fname, sizeof fname, "%s" SLASH "hosts" SLASH "%s", confbase, name);
FILE *fp = fopen(fname, "a");
if(!fp) {
logger(DEBUG_ALWAYS, LOG_DEBUG, "Cannot open config file %s: %s", fname, strerror(errno));
return false;
}
fprintf(fp, "\n# The following line was automatically added by tinc\n%s = %s\n", key, value);
fclose(fp);
return true;
}