From 2a37712b0d3d5c441424cf1fac6c95f7c76cc709 Mon Sep 17 00:00:00 2001
From: Kirill Isakov <is-kir@ya.ru>
Date: Sun, 25 Jul 2021 13:23:27 +0600
Subject: [PATCH] Cleanup and improve `tinc fsck`.

- implement TODOs
- fix an invalid warning:
	WARNING: public and private RSA keys do not match
- use the same configuration reading & parsing logic as in tincd
- read keys from all supported variables
- auto fix a few more broken key configurations
- fix a couple of rare memory leaks
- add warnings for host variables in server config and vice versa
- check duplicates for all configuration variables (not the first 50)
- check_conffile had a stack-buffer-underflow with going before the start of the line
---
 src/fsck.c        | 807 ++++++++++++++++++++++++----------------------
 src/openssl/rsa.c |   2 +-
 src/rsa.h         |   2 +-
 3 files changed, 419 insertions(+), 392 deletions(-)

diff --git a/src/fsck.c b/src/fsck.c
index f10fe3c4..7b18c19a 100644
--- a/src/fsck.c
+++ b/src/fsck.c
@@ -18,7 +18,6 @@
 */
 
 #include "system.h"
-
 #include "crypto.h"
 #include "ecdsa.h"
 #include "ecdsagen.h"
@@ -30,6 +29,11 @@
 #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) {
@@ -60,13 +64,13 @@ again:
 	goto again;
 }
 
-static void print_tinc_cmd(const char *argv0, const char *format, ...) {
+static void print_tinc_cmd(const char *format, ...) {
 	if(confbasegiven) {
-		fprintf(stderr, "%s -c %s ", argv0, confbase);
+		fprintf(stderr, "%s -c %s ", exe_name, confbase);
 	} else if(netname) {
-		fprintf(stderr, "%s -n %s ", argv0, netname);
+		fprintf(stderr, "%s -n %s ", exe_name, netname);
 	} else {
-		fprintf(stderr, "%s ", argv0);
+		fprintf(stderr, "%s ", exe_name);
 	}
 
 	va_list va;
@@ -76,6 +80,33 @@ static void print_tinc_cmd(const char *argv0, const char *format, ...) {
 	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);
@@ -87,454 +118,337 @@ static int strtailcmp(const char *str, const char *tail) {
 	return memcmp(str + slen - tlen, tail, tlen);
 }
 
-static void check_conffile(const char *fname, bool server) {
-	(void)server;
+static void check_conffile(const char *nodename, bool server) {
+	splay_tree_t *config = NULL;
+	init_configuration(&config);
 
-	FILE *f = fopen(fname, "r");
+	bool read;
 
-	if(!f) {
-		fprintf(stderr, "ERROR: cannot read %s: %s\n", fname, strerror(errno));
-		return;
+	if(server) {
+		read = read_server_config(config);
+	} else {
+		read = read_host_config(config, nodename, true);
 	}
 
-	char line[2048];
-	int lineno = 0;
-	bool skip = false;
-	const int maxvariables = 50;
-	int count[maxvariables];
-	memset(count, 0, sizeof(count));
-
-	while(fgets(line, sizeof(line), f)) {
-		if(skip) {
-			if(!strncmp(line, "-----END", 8)) {
-				skip = false;
-			}
+	if(!read) {
+		exit_configuration(&config);
+		return;
+	}
 
-			continue;
-		} else {
-			if(!strncmp(line, "-----BEGIN", 10)) {
-				skip = true;
-				continue;
-			}
-		}
+	size_t total_vars = 0;
 
-		int len;
-		char *variable, *value, *eol;
-		variable = value = line;
+	while(variables[total_vars].name) {
+		++total_vars;
+	}
 
-		lineno++;
+	int count[total_vars];
+	memset(count, 0, sizeof(count));
 
-		eol = line + strlen(line);
+	for splay_each(config_t, conf, config) {
+		int var_type = 0;
 
-		while(strchr("\t \r\n", *--eol)) {
-			*eol = '\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;
+			}
 		}
 
-		if(!line[0] || line[0] == '#') {
+		if(var_type == 0) {
 			continue;
 		}
 
-		len = strcspn(value, "\t =");
-		value += len;
-		value += strspn(value, "\t ");
-
-		if(*value == '=') {
-			value++;
-			value += strspn(value, "\t ");
+		if(var_type & VAR_OBSOLETE) {
+			fprintf(stderr, "WARNING: obsolete variable %s in %s line %d\n",
+			        conf->variable, conf->file, conf->line);
 		}
 
-		variable[len] = '\0';
-
-		bool found = false;
-
-		for(int i = 0; variables[i].name; i++) {
-			if(strcasecmp(variables[i].name, variable)) {
-				continue;
-			}
-
-			found = true;
-
-			if(variables[i].type & VAR_OBSOLETE) {
-				fprintf(stderr, "WARNING: obsolete variable %s in %s line %d\n", variable, fname, lineno);
-			}
-
-			if(i < maxvariables) {
-				count[i]++;
-			}
-		}
-
-		if(!found) {
-			fprintf(stderr, "WARNING: unknown variable %s in %s line %d\n", variable, fname, lineno);
+		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(!*value) {
-			fprintf(stderr, "ERROR: no value for variable %s in %s line %d\n", variable, fname, lineno);
+		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);
 		}
 	}
 
-	for(int i = 0; variables[i].name && i < maxvariables; i++) {
+	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, fname);
+			fprintf(stderr, "WARNING: multiple instances of variable %s in %s\n",
+			        variables[i].name, nodename ? nodename : "tinc.conf");
 		}
 	}
 
-	if(ferror(f)) {
-		fprintf(stderr, "ERROR: while reading %s: %s\n", fname, strerror(errno));
-	}
-
-	fclose(f);
+	exit_configuration(&config);
 }
 
-int fsck(const char *argv0) {
 #ifdef HAVE_MINGW
-	int uid = 0;
+typedef int uid_t;
+
+static uid_t getuid() {
+	return 0;
+}
+
+static void check_key_file_mode(const char *fname) {
+	(void)fname;
+}
 #else
-	uid_t uid = getuid();
-#endif
+static void check_key_file_mode(const char *fname) {
+	const uid_t uid = getuid();
+	struct stat st;
 
-	// Check that tinc.conf is readable.
+	if(stat(fname, &st)) {
+		fprintf(stderr, "ERROR: could not stat private key file %s\n", fname);
+		return;
+	}
 
-	if(access(tinc_conf, R_OK)) {
-		fprintf(stderr, "ERROR: cannot read %s: %s\n", tinc_conf, strerror(errno));
+	if(st.st_mode & 077) {
+		fprintf(stderr, "WARNING: unsafe file permissions on %s.\n", fname);
 
-		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");
+		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, "Check the permissions of each component of the path %s.\n", tinc_conf);
+				fprintf(stderr, "Fixed permissions of %s.\n", fname);
 			}
 		}
-
-		return 1;
 	}
+}
+#endif // HAVE_MINGW
 
-	char *name = get_my_name(true);
-
-	if(!name) {
-		fprintf(stderr, "ERROR: tinc cannot run without a valid Name.\n");
-		return 1;
+static char *read_node_name() {
+	if(access(tinc_conf, R_OK) == 0) {
+		return get_my_name(true);
 	}
 
-	// Check for private keys.
-	// TODO: use RSAPrivateKeyFile and Ed25519PrivateKeyFile variables if present.
-
-	struct stat st;
-	char fname[PATH_MAX];
-	char dname[PATH_MAX];
-
-#ifndef DISABLE_LEGACY
-	rsa_t *rsa_priv = NULL;
-	snprintf(fname, sizeof(fname), "%s/rsa_key.priv", confbase);
+	fprintf(stderr, "ERROR: cannot read %s: %s\n", tinc_conf, strerror(errno));
 
-	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");
-			free(name);
-			return 1;
-		}
-	} else {
-		FILE *f = fopen(fname, "r");
-
-		if(!f) {
-			fprintf(stderr, "ERROR: could not open %s: %s\n", fname, strerror(errno));
-			free(name);
-			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");
-			free(name);
-			return 1;
-		}
-
-#ifndef HAVE_MINGW
+	if(errno == ENOENT) {
+		fprintf(stderr, "No tinc configuration found. Create a new one with:\n\n");
+		print_tinc_cmd("init");
+		return NULL;
+	}
 
-		if(st.st_mode & 077) {
-			fprintf(stderr, "WARNING: unsafe file permissions on %s.\n", fname);
+	if(errno == EACCES) {
+		uid_t uid = getuid();
 
-			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(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);
 		}
-
-#endif
 	}
 
-#endif
-
-	ecdsa_t *ecdsa_priv = NULL;
-	snprintf(fname, sizeof(fname), "%s/ed25519_key.priv", confbase);
-
-	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");
-			free(name);
-			return 1;
-		}
-	} else {
-		FILE *f = fopen(fname, "r");
+	return NULL;
+}
 
-		if(!f) {
-			fprintf(stderr, "ERROR: could not open %s: %s\n", fname, strerror(errno));
-			free(name);
-			return 1;
-		}
+static bool build_host_conf_path(char *fname, const size_t len) {
+	char *name = get_my_name(true);
 
-		ecdsa_priv = ecdsa_read_pem_private_key(f);
-		fclose(f);
+	if(!name) {
+		fprintf(stderr, "ERROR: tinc cannot run without a valid Name.\n");
+		return false;
+	}
 
-		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");
-			free(name);
-			return 1;
-		}
+	snprintf(fname, len, "%s/hosts/%s", confbase, name);
+	free(name);
+	return true;
+}
 
-#ifndef HAVE_MINGW
+static bool ask_fix_ec_public_key(const char *fname, ecdsa_t *ec_priv) {
+	if(!ask_fix()) {
+		return true;
+	}
 
-		if(st.st_mode & 077) {
-			fprintf(stderr, "WARNING: unsafe file permissions on %s.\n", fname);
+	if(!disable_old_keys(fname, "public Ed25519 key")) {
+		return false;
+	}
 
-			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);
-				}
-			}
-		}
+	FILE *f = fopen(fname, "a");
 
-#endif
+	if(!f) {
+		fprintf(stderr, "ERROR: could not append to %s: %s\n", fname, strerror(errno));
+		return false;
 	}
 
-#ifdef DISABLE_LEGACY
-
-	if(!ecdsa_priv) {
-		fprintf(stderr, "ERROR: No Ed25519 private key found.\n");
-#else
+	bool success = ecdsa_write_pem_public_key(ec_priv, f);
+	fclose(f);
 
-	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");
-		free(name);
-		return 1;
+	if(success) {
+		fprintf(stderr, "Wrote Ed25519 public key to %s.\n", fname);
+	} else {
+		fprintf(stderr, "ERROR: could not write Ed25519 public key to %s.\n", fname);
 	}
 
-	// Check for public keys.
-	// TODO: use RSAPublicKeyFile variable if present.
-
-	snprintf(fname, sizeof(fname), "%s/hosts/%s", confbase, name);
+	return success;
+}
 
-	free(name);
+#ifndef DISABLE_LEGACY
+static bool ask_fix_rsa_public_key(const char *fname, rsa_t *rsa_priv) {
+	if(!ask_fix()) {
+		return true;
+	}
 
-	if(access(fname, R_OK)) {
-		fprintf(stderr, "WARNING: cannot read %s\n", fname);
+	if(!disable_old_keys(fname, "public RSA key")) {
+		return false;
 	}
 
-	FILE *f;
+	FILE *f = fopen(fname, "a");
 
-#ifndef DISABLE_LEGACY
-	rsa_t *rsa_pub = NULL;
+	if(!f) {
+		fprintf(stderr, "ERROR: could not append to %s: %s\n", fname, strerror(errno));
+		return false;
+	}
 
-	f = fopen(fname, "r");
+	bool success = rsa_write_pem_public_key(rsa_priv, f);
+	fclose(f);
 
-	if(f) {
-		rsa_pub = rsa_read_pem_public_key(f);
-		fclose(f);
+	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);
 	}
 
-	if(rsa_priv) {
-		if(!rsa_pub) {
-			fprintf(stderr, "WARNING: No (usable) public RSA key found.\n");
+	return success;
+}
 
-			if(ask_fix()) {
-				FILE *f = fopen(fname, "a");
+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(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);
-					}
+	if(len != rsa_size(rsa_pub)) {
+		fprintf(stderr, "ERROR: public and private RSA key lengths do not match.\n");
+		return false;
+	}
 
-					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);
+	bool success = false;
+	uint8_t *plaintext = xmalloc(len);
+	uint8_t *encrypted = xzalloc(len);
+	uint8_t *decrypted = xzalloc(len);
 
-			if(len != rsa_size(rsa_pub)) {
-				fprintf(stderr, "ERROR: public and private RSA keys do not match.\n");
-				rsa_free(rsa_pub);
-				rsa_free(rsa_priv);
-				free(ecdsa_priv);
-				return 1;
-			}
+	randomize(plaintext, len);
+	plaintext[0] &= 0x7f;
 
-			char *buf1 = malloc(len);
-			char *buf2 = malloc(len);
-			char *buf3 = malloc(len);
-
-			randomize(buf1, len);
-			buf1[0] &= 0x7f;
-			memset(buf2, 0, len);
-			memset(buf3, 0, len);
-			bool result = false;
-
-			if(rsa_public_encrypt(rsa_pub, buf1, len, buf2)) {
-				if(rsa_private_decrypt(rsa_priv, buf2, len, buf3)) {
-					if(memcmp(buf1, buf3, len)) {
-						result = true;
-					} else {
-						fprintf(stderr, "ERROR: public and private RSA keys do not match.\n");
-					}
-				} else {
-					fprintf(stderr, "ERROR: private RSA key does not work.\n");
-				}
+	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 RSA key does not work.\n");
+				fprintf(stderr, "ERROR: public and private RSA keys do not match.\n");
+				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 {
+		fprintf(stderr, "ERROR: public RSA key does not work.\n");
+		success = ask_fix_rsa_public_key(host_file, rsa_priv);
+	}
 
+	free(decrypted);
+	free(encrypted);
+	free(plaintext);
 
-			rsa_free(rsa_pub);
-			rsa_pub = NULL;
-
-			free(buf3);
-			free(buf2);
-			free(buf1);
-
-			if(!result) {
-				rsa_free(rsa_priv);
-				free(ecdsa_priv);
-				return 1;
-			}
-		}
+	return success;
+}
 
-		rsa_free(rsa_priv);
-		rsa_priv = NULL;
-	} else {
-		if(rsa_pub) {
-			fprintf(stderr, "WARNING: A public RSA key was found but no private key is known.\n");
-			rsa_free(rsa_pub);
-			rsa_pub = NULL;
-		}
+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);
 	}
 
-#endif
+	if(!rsa_priv) {
+		fprintf(stderr, "WARNING: A public RSA key was found but no private key is known.\n");
+		return true;
+	}
 
-	ecdsa_t *ecdsa_pub = NULL;
+	return test_rsa_keypair(rsa_priv, rsa_pub, host_file);
+}
+#endif // DISABLE_LEGACY
 
-	f = fopen(fname, "r");
+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(f) {
-		ecdsa_pub = get_pubkey(f);
+	if(!b64_priv_pub) {
+		print_new_keys_cmd(KEY_ED25519, "ERROR: private Ed25519 key does not work.");
+		return false;
+	}
 
-		if(!ecdsa_pub) {
-			rewind(f);
-			ecdsa_pub = ecdsa_read_pem_public_key(f);
-		}
+	// base64-encoded public key obtained from the PUBLIC key.
+	char *b64_pub_pub = ecdsa_get_base64_public_key(ec_pub);
 
-		fclose(f);
+	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);
 	}
 
-	if(ecdsa_priv) {
-		if(!ecdsa_pub) {
-			fprintf(stderr, "WARNING: No (usable) public Ed25519 key found.\n");
+	bool match = strcmp(b64_pub_pub, b64_priv_pub) == 0;
+	free(b64_pub_pub);
+	free(b64_priv_pub);
 
-			if(ask_fix()) {
-				FILE *f = fopen(fname, "a");
+	if(match) {
+		return true;
+	}
 
-				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);
-					}
+	fprintf(stderr, "ERROR: public and private Ed25519 keys do not match.\n");
+	return ask_fix_ec_public_key(host_file, ec_priv);
+}
 
-					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);
+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.");
+		}
 
-			free(ecdsa_pub);
-			ecdsa_pub = NULL;
+		return true;
+	}
 
-			if(!key1) {
-				fprintf(stderr, "ERROR: public Ed25519 key does not work.\n");
-				free(ecdsa_priv);
-				return 1;
-			}
+	if(ec_pub) {
+		return test_ec_keypair(ec_priv, ec_pub, host_file);
+	}
 
-			char *key2 = ecdsa_get_base64_public_key(ecdsa_priv);
+	fprintf(stderr, "WARNING: No (usable) public Ed25519 key found.\n");
+	return ask_fix_ec_public_key(host_file, ec_priv);
+}
 
-			if(!key2) {
-				fprintf(stderr, "ERROR: private Ed25519 key does not work.\n");
-				free(ecdsa_priv);
-				free(key1);
-				return 1;
-			}
+static bool check_config_mode(const char *fname) {
+	if(access(fname, R_OK | X_OK) == 0) {
+		return true;
+	}
 
-			int result = strcmp(key1, key2);
-			free(key1);
-			free(key2);
+	if(errno != EACCES) {
+		fprintf(stderr, "ERROR: cannot access %s: %s\n", fname, strerror(errno));
+		return false;
+	}
 
-			if(result) {
-				fprintf(stderr, "ERROR: public and private Ed25519 keys do not match.\n");
-				free(ecdsa_priv);
-				return 1;
-			}
-		}
+	fprintf(stderr, "WARNING: cannot read and execute %s: %s\n", fname, strerror(errno));
 
-		free(ecdsa_priv);
-		ecdsa_priv = NULL;
-	} else {
-		if(ecdsa_pub) {
-			fprintf(stderr, "WARNING: A public Ed25519 key was found but no private key is known.\n");
-			free(ecdsa_pub);
-			ecdsa_pub = NULL;
+	if(ask_fix()) {
+		if(chmod(fname, 0755)) {
+			fprintf(stderr, "ERROR: cannot change permissions on %s: %s\n", fname, strerror(errno));
 		}
 	}
 
-	// Check whether scripts are executable
+	return true;
+}
 
-	struct dirent *ent;
+static bool check_script_confdir() {
+	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")) {
 			continue;
@@ -563,33 +477,25 @@ int fsck(const char *argv0) {
 		}
 
 		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));
-				}
-			}
-		}
+		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")) {
 			continue;
@@ -605,44 +511,165 @@ int fsck(const char *argv0) {
 		*dash = 0;
 
 		snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, ent->d_name);
+		check_config_mode(fname);
+	}
 
-		if(access(fname, R_OK | X_OK)) {
-			if(errno != EACCES) {
-				fprintf(stderr, "ERROR: cannot access %s: %s\n", fname, strerror(errno));
-				continue;
-			}
+	closedir(dir);
 
-			fprintf(stderr, "WARNING: cannot read and execute %s: %s\n", fname, strerror(errno));
+	return true;
+}
 
-			if(ask_fix()) {
-				if(chmod(fname, 0755)) {
-					fprintf(stderr, "ERROR: cannot change permissions on %s: %s\n", fname, strerror(errno));
-				}
-			}
-		}
+#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;
 	}
 
-	closedir(dir);
+	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
+
+#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);
 
-	// Check for obsolete / unsafe / unknown configuration variables.
+	return success;
+}
 
-	check_conffile(tinc_conf, true);
+static void check_config_variables(const char *host_dir) {
+	check_conffile(NULL, true);
 
-	dir = opendir(dname);
+	DIR *dir = opendir(host_dir);
 
 	if(dir) {
-		while((ent = readdir(dir))) {
-			if(!check_id(ent->d_name)) {
-				continue;
+		for(struct dirent * ent; (ent = readdir(dir));) {
+			if(check_id(ent->d_name)) {
+				check_conffile(ent->d_name, false);
 			}
-
-			snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, ent->d_name);
-			check_conffile(fname, false);
 		}
 
 		closedir(dir);
 	}
+}
+
+static bool check_scripts_and_configs() {
+	// Check whether scripts are executable.
+	if(!check_script_confdir()) {
+		return false;
+	}
 
-	return 0;
+	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 = NULL;
+	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();
+
+	exit_configuration(&config);
+	free(name);
+	exe_name = NULL;
+
+	return success ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/openssl/rsa.c b/src/openssl/rsa.c
index 104b9719..543262e4 100644
--- a/src/openssl/rsa.c
+++ b/src/openssl/rsa.c
@@ -102,7 +102,7 @@ rsa_t *rsa_read_pem_private_key(FILE *fp) {
 	return rsa;
 }
 
-size_t rsa_size(rsa_t *rsa) {
+size_t rsa_size(const rsa_t *rsa) {
 	return RSA_size(rsa);
 }
 
diff --git a/src/rsa.h b/src/rsa.h
index a31ec249..9db9a8f6 100644
--- a/src/rsa.h
+++ b/src/rsa.h
@@ -29,7 +29,7 @@ extern rsa_t *rsa_set_hex_public_key(char *n, char *e) __attribute__((__malloc__
 extern rsa_t *rsa_set_hex_private_key(char *n, char *e, char *d) __attribute__((__malloc__));
 extern rsa_t *rsa_read_pem_public_key(FILE *fp) __attribute__((__malloc__));
 extern rsa_t *rsa_read_pem_private_key(FILE *fp) __attribute__((__malloc__));
-extern size_t rsa_size(rsa_t *rsa);
+extern size_t rsa_size(const rsa_t *rsa);
 extern bool rsa_public_encrypt(rsa_t *rsa, void *in, size_t len, void *out) __attribute__((__warn_unused_result__));
 extern bool rsa_private_decrypt(rsa_t *rsa, void *in, size_t len, void *out) __attribute__((__warn_unused_result__));
 
-- 
2.39.5