tinc/src/conf.c

479 lines
10 KiB
C
Raw Normal View History

2000-03-26 00:33:07 +00:00
/*
conf.c -- configuration code
Copyright (C) 1998 Robert van der Meulen
1998-2005 Ivo Timmermans
2000-2009 Guus Sliepen <guus@tinc-vpn.org>
2000 Cris van Pelt
2000-03-26 00:33:07 +00:00
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.
2000-03-26 00:33:07 +00:00
*/
#include "system.h"
2000-03-26 00:33:07 +00:00
#include "avl_tree.h"
2000-03-26 00:33:07 +00:00
#include "conf.h"
#include "logger.h"
#include "netutl.h" /* for str2address */
#include "utils.h" /* for cp */
#include "xalloc.h"
2000-05-29 21:01:26 +00:00
avl_tree_t *config_tree;
int pinginterval = 0; /* seconds between pings */
int pingtimeout = 0; /* seconds to wait for response */
2002-09-09 21:25:28 +00:00
char *confbase = NULL; /* directory in which all config files are */
char *netname = NULL; /* name of the vpn network */
static int config_compare(const config_t *a, const config_t *b) {
2002-09-09 21:25:28 +00:00
int result;
2002-09-09 21:25:28 +00:00
result = strcasecmp(a->variable, b->variable);
2002-09-09 21:25:28 +00:00
if(result)
return result;
2000-03-26 00:33:07 +00:00
2002-09-09 21:25:28 +00:00
result = a->line - b->line;
2002-09-09 21:25:28 +00:00
if(result)
return result;
else
return strcmp(a->file, b->file);
}
void init_configuration(avl_tree_t ** config_tree) {
2002-09-09 21:25:28 +00:00
*config_tree = avl_alloc_tree((avl_compare_t) config_compare, (avl_action_t) free_config);
}
void exit_configuration(avl_tree_t ** config_tree) {
2002-09-09 21:25:28 +00:00
avl_delete_tree(*config_tree);
*config_tree = NULL;
}
config_t *new_config(void) {
2003-08-28 21:05:11 +00:00
return xmalloc_and_zero(sizeof(config_t));
}
2000-03-26 00:33:07 +00:00
void free_config(config_t *cfg) {
2002-09-09 21:25:28 +00:00
if(cfg->variable)
free(cfg->variable);
if(cfg->value)
free(cfg->value);
if(cfg->file)
free(cfg->file);
free(cfg);
}
void config_add(avl_tree_t *config_tree, config_t *cfg) {
2002-09-09 21:25:28 +00:00
avl_insert(config_tree, cfg);
}
config_t *lookup_config(avl_tree_t *config_tree, char *variable) {
2002-09-09 21:25:28 +00:00
config_t cfg, *found;
cfg.variable = variable;
cfg.file = "";
cfg.line = 0;
2002-09-09 21:25:28 +00:00
found = avl_search_closest_greater(config_tree, &cfg);
2002-09-09 21:25:28 +00:00
if(!found)
return NULL;
2002-09-09 21:25:28 +00:00
if(strcasecmp(found->variable, variable))
return NULL;
2002-09-09 21:25:28 +00:00
return found;
}
config_t *lookup_config_next(avl_tree_t *config_tree, const config_t *cfg) {
2002-09-09 21:25:28 +00:00
avl_node_t *node;
config_t *found;
node = avl_search_node(config_tree, cfg);
if(node) {
if(node->next) {
2003-08-28 21:05:11 +00:00
found = node->next->data;
2002-09-09 21:25:28 +00:00
if(!strcasecmp(found->variable, cfg->variable))
return found;
}
}
return NULL;
}
bool get_config_bool(const config_t *cfg, bool *result) {
2002-09-09 21:25:28 +00:00
if(!cfg)
2003-07-22 20:55:21 +00:00
return false;
2002-09-09 21:25:28 +00:00
if(!strcasecmp(cfg->value, "yes")) {
2003-07-22 20:55:21 +00:00
*result = true;
return true;
2002-09-09 21:25:28 +00:00
} else if(!strcasecmp(cfg->value, "no")) {
2003-07-22 20:55:21 +00:00
*result = false;
return true;
2002-09-09 21:25:28 +00:00
}
logger(LOG_ERR, "\"yes\" or \"no\" expected for configuration variable %s in %s line %d",
2002-09-09 21:25:28 +00:00
cfg->variable, cfg->file, cfg->line);
2003-07-22 20:55:21 +00:00
return false;
}
bool get_config_int(const config_t *cfg, int *result) {
2002-09-09 21:25:28 +00:00
if(!cfg)
2003-07-22 20:55:21 +00:00
return false;
2002-09-09 21:25:28 +00:00
if(sscanf(cfg->value, "%d", result) == 1)
2003-07-22 20:55:21 +00:00
return true;
logger(LOG_ERR, "Integer expected for configuration variable %s in %s line %d",
2002-09-09 21:25:28 +00:00
cfg->variable, cfg->file, cfg->line);
2003-07-22 20:55:21 +00:00
return false;
}
bool get_config_string(const config_t *cfg, char **result) {
2002-09-09 21:25:28 +00:00
if(!cfg)
2003-07-22 20:55:21 +00:00
return false;
2002-09-09 21:25:28 +00:00
*result = xstrdup(cfg->value);
2003-07-22 20:55:21 +00:00
return true;
}
bool get_config_address(const config_t *cfg, struct addrinfo **result) {
2002-09-09 21:25:28 +00:00
struct addrinfo *ai;
if(!cfg)
2003-07-22 20:55:21 +00:00
return false;
2002-09-09 21:25:28 +00:00
ai = str2addrinfo(cfg->value, NULL, 0);
if(ai) {
*result = ai;
2003-07-22 20:55:21 +00:00
return true;
2002-09-09 21:25:28 +00:00
}
logger(LOG_ERR, "Hostname or IP address expected for configuration variable %s in %s line %d",
2002-09-09 21:25:28 +00:00
cfg->variable, cfg->file, cfg->line);
2003-07-22 20:55:21 +00:00
return false;
2002-02-10 21:57:54 +00:00
}
bool get_config_subnet(const config_t *cfg, subnet_t ** result) {
subnet_t subnet = {0};
2002-09-09 21:25:28 +00:00
if(!cfg)
2003-07-22 20:55:21 +00:00
return false;
2002-09-09 21:25:28 +00:00
if(!str2net(&subnet, cfg->value)) {
logger(LOG_ERR, "Subnet expected for configuration variable %s in %s line %d",
2002-09-09 21:25:28 +00:00
cfg->variable, cfg->file, cfg->line);
2003-07-22 20:55:21 +00:00
return false;
2002-09-09 21:25:28 +00:00
}
/* Teach newbies what subnets are... */
if(((subnet.type == SUBNET_IPV4)
&& !maskcheck(&subnet.net.ipv4.address, subnet.net.ipv4.prefixlength, sizeof(ipv4_t)))
|| ((subnet.type == SUBNET_IPV6)
&& !maskcheck(&subnet.net.ipv6.address, subnet.net.ipv6.prefixlength, sizeof(ipv6_t)))) {
logger(LOG_ERR, "Network address and prefix length do not match for configuration variable %s in %s line %d",
2002-09-09 21:25:28 +00:00
cfg->variable, cfg->file, cfg->line);
2003-07-22 20:55:21 +00:00
return false;
2002-09-09 21:25:28 +00:00
}
*(*result = new_subnet()) = subnet;
2002-09-09 21:25:28 +00:00
2003-07-22 20:55:21 +00:00
return true;
}
/*
Read exactly one line and strip the trailing newline if any. If the
file was on EOF, return NULL. Otherwise, return all the data in a
dynamically allocated buffer.
If line is non-NULL, it will be used as an initial buffer, to avoid
unnecessary mallocing each time this function is called. If buf is
given, and buf needs to be expanded, the var pointed to by buflen
will be increased.
*/
static char *readline(FILE * fp, char **buf, size_t *buflen) {
2002-09-09 21:25:28 +00:00
char *newline = NULL;
char *p;
char *line; /* The array that contains everything that has been read so far */
char *idx; /* Read into this pointer, which points to an offset within line */
size_t size, newsize; /* The size of the current array pointed to by line */
size_t maxlen; /* Maximum number of characters that may be read with fgets. This is newsize - oldsize. */
if(feof(fp))
return NULL;
if(buf && buflen) {
size = *buflen;
line = *buf;
} else {
size = 100;
line = xmalloc(size);
}
2002-09-09 21:25:28 +00:00
maxlen = size;
idx = line;
*idx = 0;
for(;;) {
errno = 0;
p = fgets(idx, maxlen, fp);
if(!p) { /* EOF or error */
if(feof(fp))
break;
/* otherwise: error; let the calling function print an error message if applicable */
free(line);
return NULL;
}
newline = strchr(p, '\n');
if(!newline) { /* We haven't yet read everything to the end of the line */
newsize = size << 1;
line = xrealloc(line, newsize);
idx = &line[size - 1];
maxlen = newsize - size + 1;
size = newsize;
} else {
*newline = '\0'; /* kill newline */
if(newline > p && newline[-1] == '\r') /* and carriage return if necessary */
newline[-1] = '\0';
2002-09-09 21:25:28 +00:00
break; /* yay */
}
}
2002-09-09 21:25:28 +00:00
if(buf && buflen) {
*buflen = size;
*buf = line;
}
2002-09-09 21:25:28 +00:00
return line;
}
2000-03-26 00:33:07 +00:00
/*
Parse a configuration file and put the results in the configuration tree
starting at *base.
2000-03-26 00:33:07 +00:00
*/
int read_config_file(avl_tree_t *config_tree, const char *fname) {
2002-09-09 21:25:28 +00:00
int err = -2; /* Parse error */
FILE *fp;
char *buffer, *line;
char *variable, *value, *eol;
2003-07-22 20:55:21 +00:00
int lineno = 0;
2003-08-02 15:27:24 +00:00
int len;
2003-07-22 20:55:21 +00:00
bool ignore = false;
2002-09-09 21:25:28 +00:00
config_t *cfg;
size_t bufsize;
fp = fopen(fname, "r");
if(!fp) {
logger(LOG_ERR, "Cannot open config file %s: %s", fname,
2002-09-09 21:25:28 +00:00
strerror(errno));
return -3;
}
2002-09-09 21:25:28 +00:00
bufsize = 100;
buffer = xmalloc(bufsize);
for(;;) {
if(feof(fp)) {
err = 0;
break;
}
2002-09-09 21:25:28 +00:00
line = readline(fp, &buffer, &bufsize);
if(!line) {
err = -1;
break;
}
lineno++;
2003-08-08 14:59:27 +00:00
if(!*line || *line == '#')
2003-08-02 15:27:24 +00:00
continue;
2000-03-26 00:33:07 +00:00
2003-08-02 15:27:24 +00:00
if(ignore) {
2003-08-02 21:34:10 +00:00
if(!strncmp(line, "-----END", 8))
2003-08-02 15:27:24 +00:00
ignore = false;
continue;
}
if(!strncmp(line, "-----BEGIN", 10)) {
2003-07-22 20:55:21 +00:00
ignore = true;
2003-08-02 15:27:24 +00:00
continue;
}
2003-08-02 15:27:24 +00:00
variable = value = line;
eol = line + strlen(line);
while(strchr("\t ", *--eol))
*eol = '\0';
2003-08-02 15:27:24 +00:00
len = strcspn(value, "\t =");
value += len;
value += strspn(value, "\t ");
if(*value == '=') {
value++;
value += strspn(value, "\t ");
}
variable[len] = '\0';
2003-08-02 15:27:24 +00:00
if(!*value) {
logger(LOG_ERR, "No value for variable `%s' on line %d while reading config file %s",
2003-08-02 15:27:24 +00:00
variable, lineno, fname);
break;
2002-09-09 21:25:28 +00:00
}
2000-03-26 00:33:07 +00:00
2003-08-02 15:27:24 +00:00
cfg = new_config();
cfg->variable = xstrdup(variable);
cfg->value = xstrdup(value);
cfg->file = xstrdup(fname);
cfg->line = lineno;
config_add(config_tree, cfg);
2002-09-09 21:25:28 +00:00
}
free(buffer);
fclose(fp);
return err;
2000-03-26 00:33:07 +00:00
}
bool read_server_config() {
2002-09-09 21:25:28 +00:00
char *fname;
int x;
2009-09-08 16:18:36 +00:00
xasprintf(&fname, "%s/tinc.conf", confbase);
2002-09-09 21:25:28 +00:00
x = read_config_file(config_tree, fname);
if(x == -1) { /* System error: complain */
logger(LOG_ERR, "Failed to read `%s': %s", fname, strerror(errno));
2002-09-09 21:25:28 +00:00
}
free(fname);
2003-07-22 20:55:21 +00:00
return x == 0;
}
FILE *ask_and_open(const char *filename, const char *what) {
2002-09-09 21:25:28 +00:00
FILE *r;
char *directory;
char *fn;
/* Check stdin and stdout */
if(!isatty(0) || !isatty(1)) {
/* Argh, they are running us from a script or something. Write
the files to the current directory and let them burn in hell
for ever. */
fn = xstrdup(filename);
} else {
/* Ask for a file and/or directory name. */
fprintf(stdout, "Please enter a file to save %s to [%s]: ",
2002-09-09 21:25:28 +00:00
what, filename);
fflush(stdout);
fn = readline(stdin, NULL, NULL);
if(!fn) {
fprintf(stderr, "Error while reading stdin: %s\n",
2002-09-09 21:25:28 +00:00
strerror(errno));
return NULL;
}
if(!strlen(fn))
/* User just pressed enter. */
fn = xstrdup(filename);
}
2003-08-08 12:24:52 +00:00
#ifdef HAVE_MINGW
if(fn[0] != '\\' && fn[0] != '/' && !strchr(fn, ':')) {
#else
if(fn[0] != '/') {
#endif
2002-09-09 21:25:28 +00:00
/* The directory is a relative path or a filename. */
char *p;
directory = get_current_dir_name();
2009-09-08 16:18:36 +00:00
xasprintf(&p, "%s/%s", directory, fn);
2002-09-09 21:25:28 +00:00
free(fn);
free(directory);
fn = p;
}
umask(0077); /* Disallow everything for group and other */
/* Open it first to keep the inode busy */
r = fopen(fn, "r+") ?: fopen(fn, "w+");
2002-09-09 21:25:28 +00:00
if(!r) {
fprintf(stderr, "Error opening file `%s': %s\n",
2002-09-09 21:25:28 +00:00
fn, strerror(errno));
free(fn);
return NULL;
}
free(fn);
return r;
}
bool disable_old_keys(FILE *f) {
char buf[100];
long pos;
bool disabled = false;
rewind(f);
pos = ftell(f);
while(fgets(buf, sizeof buf, f)) {
if(!strncmp(buf, "-----BEGIN RSA", 14)) {
buf[11] = 'O';
buf[12] = 'L';
buf[13] = 'D';
fseek(f, pos, SEEK_SET);
fputs(buf, f);
disabled = true;
}
else if(!strncmp(buf, "-----END RSA", 12)) {
buf[ 9] = 'O';
buf[10] = 'L';
buf[11] = 'D';
fseek(f, pos, SEEK_SET);
fputs(buf, f);
disabled = true;
}
pos = ftell(f);
}
return disabled;
}