/*
fsck.c -- Check the configuration files for problems
- Copyright (C) 2014 Guus Sliepen <guus@tinc-vpn.org>
+ Copyright (C) 2014-2022 Guus Sliepen <guus@tinc-vpn.org>
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
*/
#include "system.h"
-
#include "crypto.h"
#include "ecdsa.h"
#include "ecdsagen.h"
#endif
#include "tincctl.h"
#include "utils.h"
+#include "xalloc.h"
+#include "keys.h"
+#include "conf.h"
+
+static const char *exe_name = NULL;
static bool ask_fix(void) {
- if(force)
+ if(force) {
return true;
- if(!tty)
+ }
+
+ if(!tty) {
return false;
+ }
+
again:
fprintf(stderr, "Fix y/n? ");
char buf[1024];
- if(!fgets(buf, sizeof buf, stdin)) {
+
+ if(!fgets(buf, sizeof(buf), stdin)) {
tty = false;
return false;
}
- if(buf[0] == 'y' || buf[0] == 'Y')
+
+ if(buf[0] == 'y' || buf[0] == 'Y') {
return true;
- if(buf[0] == 'n' || buf[0] == 'N')
+ }
+
+ if(buf[0] == 'n' || buf[0] == 'N') {
return false;
+ }
+
goto again;
}
-static void print_tinc_cmd(const char *argv0, const char *format, ...) {
- if(confbasegiven)
- fprintf(stderr, "%s -c %s ", argv0, confbase);
- else if(netname)
- fprintf(stderr, "%s -n %s ", argv0, netname);
- else
- fprintf(stderr, "%s ", argv0);
+static void print_tinc_cmd(const char *format, ...) {
+ if(confbasegiven) {
+ fprintf(stderr, "%s -c %s ", exe_name, confbase);
+ } else if(netname) {
+ fprintf(stderr, "%s -n %s ", exe_name, netname);
+ } else {
+ fprintf(stderr, "%s ", exe_name);
+ }
+
va_list va;
va_start(va, format);
vfprintf(stderr, format, va);
fputc('\n', stderr);
}
+typedef enum {
+ KEY_RSA,
+ KEY_ED25519,
+ KEY_BOTH,
+} key_type_t;
+
+static void print_new_keys_cmd(key_type_t key_type, const char *message) {
+ fprintf(stderr, "%s\n\n", message);
+
+ switch(key_type) {
+ case KEY_RSA:
+ fprintf(stderr, "You can generate a new RSA keypair with:\n\n");
+ print_tinc_cmd("generate-rsa-keys");
+ break;
+
+ case KEY_ED25519:
+ fprintf(stderr, "You can generate a new Ed25519 keypair with:\n\n");
+ print_tinc_cmd("generate-ed25519-keys");
+ break;
+
+ case KEY_BOTH:
+ fprintf(stderr, "You can generate new keys with:\n\n");
+ print_tinc_cmd("generate-keys");
+ break;
+ }
+}
+
static int strtailcmp(const char *str, const char *tail) {
size_t slen = strlen(str);
size_t tlen = strlen(tail);
- if(tlen > slen)
+
+ if(tlen > slen) {
return -1;
+ }
+
return memcmp(str + slen - tlen, tail, tlen);
}
-static void check_conffile(const char *fname, bool server) {
- FILE *f = fopen(fname, "r");
- if(!f) {
- fprintf(stderr, "ERROR: cannot read %s: %s\n", fname, strerror(errno));
+static void check_conffile(const char *nodename, bool server) {
+ splay_tree_t config;
+ init_configuration(&config);
+
+ bool read;
+
+ if(server) {
+ read = read_server_config(&config);
+ } else {
+ read = read_host_config(&config, nodename, true);
+ }
+
+ if(!read) {
+ splay_empty_tree(&config);
return;
}
- char line[2048];
- int lineno = 0;
- bool skip = false;
- const int maxvariables = 50;
- int count[maxvariables];
- memset(count, 0, sizeof count);
+ size_t total_vars = 0;
- while(fgets(line, sizeof line, f)) {
- if(skip) {
- if(!strncmp(line, "-----END", 8))
- skip = false;
- continue;
- } else {
- if(!strncmp(line, "-----BEGIN", 10)) {
- skip = true;
- continue;
+ while(variables[total_vars].name) {
+ ++total_vars;
+ }
+
+ int count[total_vars];
+ memset(count, 0, sizeof(count));
+
+ for splay_each(config_t, conf, &config) {
+ int var_type = 0;
+
+ for(size_t i = 0; variables[i].name; ++i) {
+ if(strcasecmp(variables[i].name, conf->variable) == 0) {
+ count[i]++;
+ var_type = variables[i].type;
}
}
- int len;
- char *variable, *value, *eol;
- variable = value = line;
+ if(var_type == 0) {
+ continue;
+ }
- lineno++;
+ if(var_type & VAR_OBSOLETE) {
+ fprintf(stderr, "WARNING: obsolete variable %s in %s line %d\n",
+ conf->variable, conf->file, conf->line);
+ }
- eol = line + strlen(line);
- while(strchr("\t \r\n", *--eol))
- *eol = '\0';
+ if(server && !(var_type & VAR_SERVER)) {
+ fprintf(stderr, "WARNING: host variable %s found in server config %s line %d \n",
+ conf->variable, conf->file, conf->line);
+ }
- if(!line[0] || line[0] == '#')
- continue;
+ if(!server && !(var_type & VAR_HOST)) {
+ fprintf(stderr, "WARNING: server variable %s found in host config %s line %d \n",
+ conf->variable, conf->file, conf->line);
+ }
+ }
- len = strcspn(value, "\t =");
- value += len;
- value += strspn(value, "\t ");
- if(*value == '=') {
- value++;
- value += strspn(value, "\t ");
+ for(size_t i = 0; i < total_vars; ++i) {
+ if(count[i] > 1 && !(variables[i].type & VAR_MULTIPLE)) {
+ fprintf(stderr, "WARNING: multiple instances of variable %s in %s\n",
+ variables[i].name, nodename ? nodename : "tinc.conf");
}
- variable[len] = '\0';
+ }
- bool found = false;
+ splay_empty_tree(&config);
+}
- for(int i = 0; variables[i].name; i++) {
- if(strcasecmp(variables[i].name, variable))
- continue;
+#ifdef HAVE_MINGW
+typedef int uid_t;
- found = true;
+static uid_t getuid(void) {
+ return 0;
+}
- if(variables[i].type & VAR_OBSOLETE) {
- fprintf(stderr, "WARNING: obsolete variable %s in %s line %d\n", variable, fname, lineno);
- }
+static void check_key_file_mode(const char *fname) {
+ (void)fname;
+}
+#else
+static void check_key_file_mode(const char *fname) {
+ const uid_t uid = getuid();
+ struct stat st;
- if(i < maxvariables)
- count[i]++;
- }
+ if(stat(fname, &st)) {
+ fprintf(stderr, "ERROR: could not stat private key file %s\n", fname);
+ return;
+ }
- if(!found)
- fprintf(stderr, "WARNING: unknown variable %s in %s line %d\n", variable, fname, lineno);
+ if(st.st_mode & 077) {
+ fprintf(stderr, "WARNING: unsafe file permissions on %s.\n", fname);
- if(!*value)
- fprintf(stderr, "ERROR: no value for variable %s in %s line %d\n", variable, fname, lineno);
+ if(st.st_uid != uid) {
+ fprintf(stderr, "You are not running %s as the same uid as %s.\n", exe_name, fname);
+ } else if(ask_fix()) {
+ if(chmod(fname, st.st_mode & ~077u)) {
+ fprintf(stderr, "ERROR: could not change permissions of %s: %s\n", fname, strerror(errno));
+ } else {
+ fprintf(stderr, "Fixed permissions of %s.\n", fname);
+ }
+ }
}
+}
+#endif // HAVE_MINGW
- for(int i = 0; variables[i].name && i < maxvariables; i++) {
- if(count[i] > 1 && !(variables[i].type & VAR_MULTIPLE))
- fprintf(stderr, "WARNING: multiple instances of variable %s in %s\n", variables[i].name, fname);
+static char *read_node_name(void) {
+ if(access(tinc_conf, R_OK) == 0) {
+ return get_my_name(true);
}
- if(ferror(f))
- fprintf(stderr, "ERROR: while reading %s: %s\n", fname, strerror(errno));
+ fprintf(stderr, "ERROR: cannot read %s: %s\n", tinc_conf, strerror(errno));
- fclose(f);
-}
+ if(errno == ENOENT) {
+ fprintf(stderr, "No tinc configuration found. Create a new one with:\n\n");
+ print_tinc_cmd("init");
+ return NULL;
+ }
-int fsck(const char *argv0) {
-#ifdef HAVE_MINGW
- int uid = 0;
-#else
- uid_t uid = getuid();
-#endif
+ if(errno == EACCES) {
+ uid_t uid = getuid();
- // Check that tinc.conf is readable.
-
- if(access(tinc_conf, R_OK)) {
- fprintf(stderr, "ERROR: cannot read %s: %s\n", tinc_conf, strerror(errno));
- if(errno == ENOENT) {
- fprintf(stderr, "No tinc configuration found. Create a new one with:\n\n");
- print_tinc_cmd(argv0, "init");
- } else if(errno == EACCES) {
- if(uid != 0)
- fprintf(stderr, "You are currently not running tinc as root. Use sudo?\n");
- else
- fprintf(stderr, "Check the permissions of each component of the path %s.\n", tinc_conf);
+ if(uid != 0) {
+ fprintf(stderr, "You are currently not running tinc as root. Use sudo?\n");
+ } else {
+ fprintf(stderr, "Check the permissions of each component of the path %s.\n", tinc_conf);
}
- return 1;
}
+ return NULL;
+}
+
+static bool build_host_conf_path(char *fname, const size_t len) {
char *name = get_my_name(true);
+
if(!name) {
fprintf(stderr, "ERROR: tinc cannot run without a valid Name.\n");
- return 1;
+ return false;
}
- // Check for private keys.
- // TODO: use RSAPrivateKeyFile and Ed25519PrivateKeyFile variables if present.
+ snprintf(fname, len, "%s/hosts/%s", confbase, name);
+ free(name);
+ return true;
+}
- struct stat st;
- char fname[PATH_MAX];
- char dname[PATH_MAX];
+static bool ask_fix_ec_public_key(const char *fname, ecdsa_t *ec_priv) {
+ if(!ask_fix()) {
+ return true;
+ }
-#ifndef DISABLE_LEGACY
- rsa_t *rsa_priv = NULL;
- snprintf(fname, sizeof fname, "%s/rsa_key.priv", confbase);
+ if(!disable_old_keys(fname, "public Ed25519 key")) {
+ return false;
+ }
- if(stat(fname, &st)) {
- if(errno != ENOENT) {
- // Something is seriously wrong here. If we can access the directory with tinc.conf in it, we should certainly be able to stat() an existing file.
- fprintf(stderr, "ERROR: cannot read %s: %s\n", fname, strerror(errno));
- fprintf(stderr, "Please correct this error.\n");
- return 1;
- }
- } else {
- FILE *f = fopen(fname, "r");
- if(!f) {
- fprintf(stderr, "ERROR: could not open %s: %s\n", fname, strerror(errno));
- return 1;
- }
- rsa_priv = rsa_read_pem_private_key(f);
- fclose(f);
- if(!rsa_priv) {
- fprintf(stderr, "ERROR: No key or unusable key found in %s.\n", fname);
- fprintf(stderr, "You can generate a new RSA key with:\n\n");
- print_tinc_cmd(argv0, "generate-rsa-keys");
- return 1;
- }
+ FILE *f = fopen(fname, "a");
- if(st.st_mode & 077) {
- fprintf(stderr, "WARNING: unsafe file permissions on %s.\n", fname);
- if(st.st_uid != uid) {
- fprintf(stderr, "You are not running %s as the same uid as %s.\n", argv0, fname);
- } else if(ask_fix()) {
- if(chmod(fname, st.st_mode & ~077))
- fprintf(stderr, "ERROR: could not change permissions of %s: %s\n", fname, strerror(errno));
- else
- fprintf(stderr, "Fixed permissions of %s.\n", fname);
- }
- }
+ if(!f) {
+ fprintf(stderr, "ERROR: could not append to %s: %s\n", fname, strerror(errno));
+ return false;
}
-#endif
- ecdsa_t *ecdsa_priv = NULL;
- snprintf(fname, sizeof fname, "%s/ed25519_key.priv", confbase);
+ bool success = ecdsa_write_pem_public_key(ec_priv, f);
+ fclose(f);
- if(stat(fname, &st)) {
- if(errno != ENOENT) {
- // Something is seriously wrong here. If we can access the directory with tinc.conf in it, we should certainly be able to stat() an existing file.
- fprintf(stderr, "ERROR: cannot read %s: %s\n", fname, strerror(errno));
- fprintf(stderr, "Please correct this error.\n");
- return 1;
- }
+ if(success) {
+ fprintf(stderr, "Wrote Ed25519 public key to %s.\n", fname);
} else {
- FILE *f = fopen(fname, "r");
- if(!f) {
- fprintf(stderr, "ERROR: could not open %s: %s\n", fname, strerror(errno));
- return 1;
- }
- ecdsa_priv = ecdsa_read_pem_private_key(f);
- fclose(f);
- if(!ecdsa_priv) {
- fprintf(stderr, "ERROR: No key or unusable key found in %s.\n", fname);
- fprintf(stderr, "You can generate a new Ed25519 key with:\n\n");
- print_tinc_cmd(argv0, "generate-ed25519-keys");
- return 1;
- }
+ fprintf(stderr, "ERROR: could not write Ed25519 public key to %s.\n", fname);
+ }
- if(st.st_mode & 077) {
- fprintf(stderr, "WARNING: unsafe file permissions on %s.\n", fname);
- if(st.st_uid != uid) {
- fprintf(stderr, "You are not running %s as the same uid as %s.\n", argv0, fname);
- } else if(ask_fix()) {
- if(chmod(fname, st.st_mode & ~077))
- fprintf(stderr, "ERROR: could not change permissions of %s: %s\n", fname, strerror(errno));
- else
- fprintf(stderr, "Fixed permissions of %s.\n", fname);
- }
- }
+ return success;
+}
+
+#ifndef DISABLE_LEGACY
+static bool ask_fix_rsa_public_key(const char *fname, rsa_t *rsa_priv) {
+ if(!ask_fix()) {
+ return true;
}
-#ifdef DISABLE_LEGACY
- if(!ecdsa_priv) {
- fprintf(stderr, "ERROR: No Ed25519 private key found.\n");
-#else
- if(!rsa_priv && !ecdsa_priv) {
- fprintf(stderr, "ERROR: Neither RSA or Ed25519 private key found.\n");
-#endif
- fprintf(stderr, "You can generate new keys with:\n\n");
- print_tinc_cmd(argv0, "generate-keys");
- return 1;
+ if(!disable_old_keys(fname, "public RSA key")) {
+ return false;
}
- // Check for public keys.
- // TODO: use RSAPublicKeyFile and Ed25519PublicKeyFile variables if present.
+ FILE *f = fopen(fname, "a");
- snprintf(fname, sizeof fname, "%s/hosts/%s", confbase, name);
- if(access(fname, R_OK))
- fprintf(stderr, "WARNING: cannot read %s\n", fname);
+ if(!f) {
+ fprintf(stderr, "ERROR: could not append to %s: %s\n", fname, strerror(errno));
+ return false;
+ }
- FILE *f;
+ bool success = rsa_write_pem_public_key(rsa_priv, f);
+ fclose(f);
-#ifndef DISABLE_LEGACY
- rsa_t *rsa_pub = NULL;
+ if(success) {
+ fprintf(stderr, "Wrote RSA public key to %s.\n", fname);
+ } else {
+ fprintf(stderr, "ERROR: could not write RSA public key to %s.\n", fname);
+ }
- f = fopen(fname, "r");
- if(f)
- rsa_pub = rsa_read_pem_public_key(f);
- fclose(f);
+ return success;
+}
- if(rsa_priv) {
- if(!rsa_pub) {
- fprintf(stderr, "WARNING: No (usable) public RSA key found.\n");
- if(ask_fix()) {
- FILE *f = fopen(fname, "a");
- if(f) {
- if(rsa_write_pem_public_key(rsa_priv, f))
- fprintf(stderr, "Wrote RSA public key to %s.\n", fname);
- else
- fprintf(stderr, "ERROR: could not write RSA public key to %s.\n", fname);
- fclose(f);
- } else {
- fprintf(stderr, "ERROR: could not append to %s: %s\n", fname, strerror(errno));
- }
- }
- } else {
- // TODO: suggest remedies
- size_t len = rsa_size(rsa_priv);
- if(len != rsa_size(rsa_pub)) {
- fprintf(stderr, "ERROR: public and private RSA keys do not match.\n");
- return 1;
- }
- char buf1[len], buf2[len], buf3[len];
- randomize(buf1, sizeof buf1);
- buf1[0] &= 0x7f;
- memset(buf2, 0, sizeof buf2);
- memset(buf3, 0, sizeof buf2);
- if(!rsa_public_encrypt(rsa_pub, buf1, sizeof buf1, buf2)) {
- fprintf(stderr, "ERROR: public RSA key does not work.\n");
- return 1;
- }
- if(!rsa_private_decrypt(rsa_priv, buf2, sizeof buf2, buf3)) {
- fprintf(stderr, "ERROR: private RSA key does not work.\n");
- return 1;
- }
- if(memcmp(buf1, buf3, sizeof buf1)) {
+static bool test_rsa_keypair(rsa_t *rsa_priv, rsa_t *rsa_pub, const char *host_file) {
+ size_t len = rsa_size(rsa_priv);
+
+ if(len != rsa_size(rsa_pub)) {
+ fprintf(stderr, "ERROR: public and private RSA key lengths do not match.\n");
+ return false;
+ }
+
+ bool success = false;
+ uint8_t *plaintext = xmalloc(len);
+ uint8_t *encrypted = xzalloc(len);
+ uint8_t *decrypted = xzalloc(len);
+
+ prng_randomize(plaintext, len);
+ plaintext[0] &= 0x7f;
+
+ if(rsa_public_encrypt(rsa_pub, plaintext, len, encrypted)) {
+ if(rsa_private_decrypt(rsa_priv, encrypted, len, decrypted)) {
+ if(memcmp(plaintext, decrypted, len) == 0) {
+ success = true;
+ } else {
fprintf(stderr, "ERROR: public and private RSA keys do not match.\n");
- return 1;
+ success = ask_fix_rsa_public_key(host_file, rsa_priv);
}
+ } else {
+ print_new_keys_cmd(KEY_RSA, "ERROR: private RSA key does not work.");
}
} else {
- if(rsa_pub)
- fprintf(stderr, "WARNING: A public RSA key was found but no private key is known.\n");
+ fprintf(stderr, "ERROR: public RSA key does not work.\n");
+ success = ask_fix_rsa_public_key(host_file, rsa_priv);
}
-#endif
- //
- // TODO: this should read the Ed25519PublicKey config variable instead.
- ecdsa_t *ecdsa_pub = NULL;
- f = fopen(fname, "r");
- if(f)
- ecdsa_pub = ecdsa_read_pem_public_key(f);
- fclose(f);
+ free(decrypted);
+ free(encrypted);
+ free(plaintext);
- if(ecdsa_priv) {
- if(!ecdsa_pub) {
- fprintf(stderr, "WARNING: No (usable) public Ed25519 key found.\n");
- if(ask_fix()) {
- FILE *f = fopen(fname, "a");
- if(f) {
- if(ecdsa_write_pem_public_key(ecdsa_priv, f))
- fprintf(stderr, "Wrote Ed25519 public key to %s.\n", fname);
- else
- fprintf(stderr, "ERROR: could not write Ed25519 public key to %s.\n", fname);
- fclose(f);
- } else {
- fprintf(stderr, "ERROR: could not append to %s: %s\n", fname, strerror(errno));
- }
- }
- } else {
- // TODO: suggest remedies
- char *key1 = ecdsa_get_base64_public_key(ecdsa_pub);
- if(!key1) {
- fprintf(stderr, "ERROR: public Ed25519 key does not work.\n");
- return 1;
- }
- char *key2 = ecdsa_get_base64_public_key(ecdsa_priv);
- if(!key2) {
- free(key1);
- fprintf(stderr, "ERROR: private Ed25519 key does not work.\n");
- return 1;
- }
- int result = strcmp(key1, key2);
- free(key1);
- free(key2);
- if(result) {
- fprintf(stderr, "ERROR: public and private Ed25519 keys do not match.\n");
- return 1;
- }
+ return success;
+}
+
+static bool check_rsa_pubkey(rsa_t *rsa_priv, rsa_t *rsa_pub, const char *host_file) {
+ if(!rsa_pub) {
+ fprintf(stderr, "WARNING: No (usable) public RSA key found.\n");
+ return ask_fix_rsa_public_key(host_file, rsa_priv);
+ }
+
+ if(!rsa_priv) {
+ fprintf(stderr, "WARNING: A public RSA key was found but no private key is known.\n");
+ return true;
+ }
+
+ return test_rsa_keypair(rsa_priv, rsa_pub, host_file);
+}
+#endif // DISABLE_LEGACY
+
+static bool test_ec_keypair(ecdsa_t *ec_priv, ecdsa_t *ec_pub, const char *host_file) {
+ // base64-encoded public key obtained from the PRIVATE key.
+ char *b64_priv_pub = ecdsa_get_base64_public_key(ec_priv);
+
+ if(!b64_priv_pub) {
+ print_new_keys_cmd(KEY_ED25519, "ERROR: private Ed25519 key does not work.");
+ return false;
+ }
+
+ // base64-encoded public key obtained from the PUBLIC key.
+ char *b64_pub_pub = ecdsa_get_base64_public_key(ec_pub);
+
+ if(!b64_pub_pub) {
+ fprintf(stderr, "ERROR: public Ed25519 key does not work.\n");
+ free(b64_priv_pub);
+ return ask_fix_ec_public_key(host_file, ec_priv);
+ }
+
+ bool match = strcmp(b64_pub_pub, b64_priv_pub) == 0;
+ free(b64_pub_pub);
+ free(b64_priv_pub);
+
+ if(match) {
+ return true;
+ }
+
+ fprintf(stderr, "ERROR: public and private Ed25519 keys do not match.\n");
+ return ask_fix_ec_public_key(host_file, ec_priv);
+}
+
+static bool check_ec_pubkey(ecdsa_t *ec_priv, ecdsa_t *ec_pub, const char *host_file) {
+ if(!ec_priv) {
+ if(ec_pub) {
+ print_new_keys_cmd(KEY_ED25519, "WARNING: A public Ed25519 key was found but no private key is known.");
}
- } else {
- if(ecdsa_pub)
- fprintf(stderr, "WARNING: A public Ed25519 key was found but no private key is known.\n");
+
+ return true;
}
- // Check whether scripts are executable
+ if(ec_pub) {
+ return test_ec_keypair(ec_priv, ec_pub, host_file);
+ }
- struct dirent *ent;
+ fprintf(stderr, "WARNING: No (usable) public Ed25519 key found.\n");
+ return ask_fix_ec_public_key(host_file, ec_priv);
+}
+
+static bool check_config_mode(const char *fname) {
+ if(access(fname, R_OK | X_OK) == 0) {
+ return true;
+ }
+
+ if(errno != EACCES) {
+ fprintf(stderr, "ERROR: cannot access %s: %s\n", fname, strerror(errno));
+ return false;
+ }
+
+ fprintf(stderr, "WARNING: cannot read and execute %s: %s\n", fname, strerror(errno));
+
+ if(ask_fix()) {
+ if(chmod(fname, 0755)) {
+ fprintf(stderr, "ERROR: cannot change permissions on %s: %s\n", fname, strerror(errno));
+ }
+ }
+
+ return true;
+}
+
+static bool check_script_confdir(void) {
+ char fname[PATH_MAX];
DIR *dir = opendir(confbase);
+
if(!dir) {
fprintf(stderr, "ERROR: cannot read directory %s: %s\n", confbase, strerror(errno));
- return 1;
+ return false;
}
+ struct dirent *ent;
+
while((ent = readdir(dir))) {
- if(strtailcmp(ent->d_name, "-up") && strtailcmp(ent->d_name, "-down"))
+ if(strtailcmp(ent->d_name, "-up") && strtailcmp(ent->d_name, "-down")) {
continue;
+ }
- strncpy(fname, ent->d_name, sizeof fname);
+ strncpy(fname, ent->d_name, sizeof(fname));
char *dash = strrchr(fname, '-');
- if(!dash)
+
+ if(!dash) {
continue;
+ }
+
*dash = 0;
if(strcmp(fname, "tinc") && strcmp(fname, "host") && strcmp(fname, "subnet")) {
static bool explained = false;
fprintf(stderr, "WARNING: Unknown script %s" SLASH "%s found.\n", confbase, ent->d_name);
+
if(!explained) {
fprintf(stderr, "The only scripts in %s executed by tinc are:\n", confbase);
fprintf(stderr, "tinc-up, tinc-down, host-up, host-down, subnet-up and subnet-down.\n");
explained = true;
}
+
continue;
}
- snprintf(fname, sizeof fname, "%s" SLASH "%s", confbase, ent->d_name);
- if(access(fname, R_OK | X_OK)) {
- if(errno != EACCES) {
- fprintf(stderr, "ERROR: cannot access %s: %s\n", fname, strerror(errno));
- continue;
- }
- fprintf(stderr, "WARNING: cannot read and execute %s: %s\n", fname, strerror(errno));
- if(ask_fix()) {
- if(chmod(fname, 0755))
- fprintf(stderr, "ERROR: cannot change permissions on %s: %s\n", fname, strerror(errno));
- }
- }
+ snprintf(fname, sizeof(fname), "%s" SLASH "%s", confbase, ent->d_name);
+ check_config_mode(fname);
}
+
closedir(dir);
- snprintf(dname, sizeof dname, "%s" SLASH "hosts", confbase);
- dir = opendir(dname);
+ return true;
+}
+
+static bool check_script_hostdir(const char *host_dir) {
+ char fname[PATH_MAX];
+ DIR *dir = opendir(host_dir);
+
if(!dir) {
- fprintf(stderr, "ERROR: cannot read directory %s: %s\n", dname, strerror(errno));
- return 1;
+ fprintf(stderr, "ERROR: cannot read directory %s: %s\n", host_dir, strerror(errno));
+ return false;
}
+ struct dirent *ent;
+
while((ent = readdir(dir))) {
- if(strtailcmp(ent->d_name, "-up") && strtailcmp(ent->d_name, "-down"))
+ if(strtailcmp(ent->d_name, "-up") && strtailcmp(ent->d_name, "-down")) {
continue;
+ }
- strncpy(fname, ent->d_name, sizeof fname);
+ strncpy(fname, ent->d_name, sizeof(fname));
char *dash = strrchr(fname, '-');
- if(!dash)
+
+ if(!dash) {
continue;
+ }
+
*dash = 0;
- snprintf(fname, sizeof fname, "%s" SLASH "hosts" SLASH "%s", confbase, ent->d_name);
- if(access(fname, R_OK | X_OK)) {
- if(errno != EACCES) {
- fprintf(stderr, "ERROR: cannot access %s: %s\n", fname, strerror(errno));
- continue;
- }
- fprintf(stderr, "WARNING: cannot read and execute %s: %s\n", fname, strerror(errno));
- if(ask_fix()) {
- if(chmod(fname, 0755))
- fprintf(stderr, "ERROR: cannot change permissions on %s: %s\n", fname, strerror(errno));
- }
- }
+ snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, ent->d_name);
+ check_config_mode(fname);
}
+
closedir(dir);
-
- // Check for obsolete / unsafe / unknown configuration variables.
- check_conffile(tinc_conf, true);
+ return true;
+}
- dir = opendir(dname);
- if(dir) {
- while((ent = readdir(dir))) {
- if(!check_id(ent->d_name))
- continue;
+#ifdef DISABLE_LEGACY
+static bool check_public_keys(splay_tree_t *config, const char *name, ecdsa_t *ec_priv) {
+#else
+static bool check_public_keys(splay_tree_t *config, const char *name, rsa_t *rsa_priv, ecdsa_t *ec_priv) {
+#endif
+ // Check public keys.
+ char host_file[PATH_MAX];
+
+ if(!build_host_conf_path(host_file, sizeof(host_file))) {
+ return false;
+ }
+
+ if(access(host_file, R_OK)) {
+ fprintf(stderr, "WARNING: cannot read %s\n", host_file);
+ }
+
+ ecdsa_t *ec_pub = NULL;
+ read_ecdsa_public_key(&ec_pub, &config, name);
+
+ bool success = true;
+#ifndef DISABLE_LEGACY
+ rsa_t *rsa_pub = NULL;
+ read_rsa_public_key(&rsa_pub, config, name);
+
+ success = check_rsa_pubkey(rsa_priv, rsa_pub, host_file);
+ rsa_free(rsa_pub);
+#endif
+
+ if(!check_ec_pubkey(ec_priv, ec_pub, host_file)) {
+ success = false;
+ }
+
+ ecdsa_free(ec_pub);
+
+ return success;
+}
+
+static bool check_keypairs(splay_tree_t *config, const char *name) {
+ // Check private keys.
+ char *priv_keyfile = NULL;
+ ecdsa_t *ec_priv = read_ecdsa_private_key(config, &priv_keyfile);
+
+ if(priv_keyfile) {
+ check_key_file_mode(priv_keyfile);
+ free(priv_keyfile);
+ priv_keyfile = NULL;
+ }
+
+#ifdef DISABLE_LEGACY
+
+ if(!ec_priv) {
+ print_new_keys_cmd(KEY_ED25519, "ERROR: No Ed25519 private key found.");
+ return false;
+ }
+
+#else
+ rsa_t *rsa_priv = read_rsa_private_key(config, &priv_keyfile);
+
+ if(priv_keyfile) {
+ check_key_file_mode(priv_keyfile);
+ free(priv_keyfile);
+ }
+
+ if(!rsa_priv && !ec_priv) {
+ print_new_keys_cmd(KEY_BOTH, "ERROR: Neither RSA or Ed25519 private key found.");
+ return false;
+ }
+
+#endif
- snprintf(fname, sizeof fname, "%s" SLASH "hosts" SLASH "%s", confbase, ent->d_name);
- check_conffile(fname, false);
+#ifdef DISABLE_LEGACY
+ bool success = check_public_keys(config, name, ec_priv);
+#else
+ bool success = check_public_keys(config, name, rsa_priv, ec_priv);
+ rsa_free(rsa_priv);
+#endif
+ ecdsa_free(ec_priv);
+
+ return success;
+}
+
+static void check_config_variables(const char *host_dir) {
+ check_conffile(NULL, true);
+
+ DIR *dir = opendir(host_dir);
+
+ if(dir) {
+ for(struct dirent * ent; (ent = readdir(dir));) {
+ if(check_id(ent->d_name)) {
+ check_conffile(ent->d_name, false);
+ }
}
+
closedir(dir);
}
+}
- return 0;
+static bool check_scripts_and_configs(void) {
+ // Check whether scripts are executable.
+ if(!check_script_confdir()) {
+ return false;
+ }
+
+ char host_dir[PATH_MAX];
+ snprintf(host_dir, sizeof(host_dir), "%s" SLASH "hosts", confbase);
+
+ if(!check_script_hostdir(host_dir)) {
+ return false;
+ }
+
+ // Check for obsolete / unsafe / unknown configuration variables (and print warnings).
+ check_config_variables(host_dir);
+
+ return true;
}
+int fsck(const char *argv0) {
+ exe_name = argv0;
+
+ // Check that tinc.conf is readable and read our name if it is.
+ char *name = read_node_name();
+
+ if(!name) {
+ fprintf(stderr, "ERROR: tinc cannot run without a valid Name.\n");
+ exe_name = NULL;
+ return EXIT_FAILURE;
+ }
+
+ // Avoid touching global configuration here. Read the config files into
+ // a temporary configuration tree, then throw it away after fsck is done.
+ splay_tree_t config;
+ init_configuration(&config);
+
+ // Read the server configuration file and append host configuration for our node.
+ bool success = read_server_config(&config) &&
+ read_host_config(&config, name, true);
+
+ // Check both RSA and EC key pairs.
+ // We need working configuration to run this check.
+ if(success) {
+ success = check_keypairs(&config, name);
+ }
+
+ // Check that scripts are executable and check the config for invalid variables.
+ // This check does not require working configuration, so run it always.
+ // This way, we can diagnose more issues on the first run.
+ success = success & check_scripts_and_configs();
+
+ splay_empty_tree(&config);
+ free(name);
+ exe_name = NULL;
+
+ return success ? EXIT_SUCCESS : EXIT_FAILURE;
+}