+static int export(const char *name, FILE *out) {
+ char filename[PATH_MAX];
+ snprintf(filename, sizeof filename, "%s" SLASH "%s", hosts_dir, name);
+ FILE *in = fopen(filename, "r");
+ if(!in) {
+ fprintf(stderr, "Could not open configuration file %s: %s\n", filename, strerror(errno));
+ return 1;
+ }
+
+ fprintf(out, "Name = %s\n", name);
+ char buf[4096];
+ while(fgets(buf, sizeof buf, in)) {
+ if(strcspn(buf, "\t =") != 4 || strncasecmp(buf, "Name", 4))
+ fputs(buf, out);
+ }
+
+ if(ferror(in)) {
+ fprintf(stderr, "Error while reading configuration file %s: %s\n", filename, strerror(errno));
+ fclose(in);
+ return 1;
+ }
+
+ fclose(in);
+ return 0;
+}
+
+static int cmd_export(int argc, char *argv[]) {
+ if(argc > 1) {
+ fprintf(stderr, "Too many arguments!\n");
+ return 1;
+ }
+
+ char *name = get_my_name(true);
+ if(!name)
+ return 1;
+
+ int result = export(name, stdout);
+ if(!tty)
+ fclose(stdout);
+
+ free(name);
+ return result;
+}
+
+static int cmd_export_all(int argc, char *argv[]) {
+ if(argc > 1) {
+ fprintf(stderr, "Too many arguments!\n");
+ return 1;
+ }
+
+ DIR *dir = opendir(hosts_dir);
+ if(!dir) {
+ fprintf(stderr, "Could not open host configuration directory %s: %s\n", hosts_dir, strerror(errno));
+ return 1;
+ }
+
+ bool first = true;
+ int result = 0;
+ struct dirent *ent;
+
+ while((ent = readdir(dir))) {
+ if(!check_id(ent->d_name))
+ continue;
+
+ if(first)
+ first = false;
+ else
+ printf("#---------------------------------------------------------------#\n");
+
+ result |= export(ent->d_name, stdout);
+ }
+
+ closedir(dir);
+ if(!tty)
+ fclose(stdout);
+ return result;
+}
+
+static int cmd_import(int argc, char *argv[]) {
+ if(argc > 1) {
+ fprintf(stderr, "Too many arguments!\n");
+ return 1;
+ }
+
+ FILE *in = stdin;
+ FILE *out = NULL;
+
+ char buf[4096];
+ char name[4096];
+ char filename[PATH_MAX] = "";
+ int count = 0;
+ bool firstline = true;
+
+ while(fgets(buf, sizeof buf, in)) {
+ if(sscanf(buf, "Name = %s", name) == 1) {
+ firstline = false;
+
+ if(!check_id(name)) {
+ fprintf(stderr, "Invalid Name in input!\n");
+ return 1;
+ }
+
+ if(out)
+ fclose(out);
+
+ snprintf(filename, sizeof filename, "%s" SLASH "%s", hosts_dir, name);
+
+ if(!force && !access(filename, F_OK)) {
+ fprintf(stderr, "Host configuration file %s already exists, skipping.\n", filename);
+ out = NULL;
+ continue;
+ }
+
+ out = fopen(filename, "w");
+ if(!out) {
+ fprintf(stderr, "Error creating configuration file %s: %s\n", filename, strerror(errno));
+ return 1;
+ }
+
+ count++;
+ continue;
+ } else if(firstline) {
+ fprintf(stderr, "Junk at the beginning of the input, ignoring.\n");
+ firstline = false;
+ }
+
+
+ if(!strcmp(buf, "#---------------------------------------------------------------#\n"))
+ continue;
+
+ if(out) {
+ if(fputs(buf, out) < 0) {
+ fprintf(stderr, "Error writing to host configuration file %s: %s\n", filename, strerror(errno));
+ return 1;
+ }
+ }
+ }
+
+ if(out)
+ fclose(out);
+
+ if(count) {
+ fprintf(stderr, "Imported %d host configuration files.\n", count);
+ return 0;
+ } else {
+ fprintf(stderr, "No host configuration files imported.\n");
+ return 1;
+ }
+}
+
+static int cmd_exchange(int argc, char *argv[]) {
+ return cmd_export(argc, argv) ?: cmd_import(argc, argv);
+}
+
+static int cmd_exchange_all(int argc, char *argv[]) {
+ return cmd_export_all(argc, argv) ?: cmd_import(argc, argv);
+}
+
+static int switch_network(char *name) {
+ if(strcmp(name, ".")) {
+ if(!check_netname(name, false)) {
+ fprintf(stderr, "Invalid character in netname!\n");
+ return 1;
+ }
+
+ if(!check_netname(name, true))
+ fprintf(stderr, "Warning: unsafe character in netname!\n");
+ }
+
+ if(fd >= 0) {
+ close(fd);
+ fd = -1;
+ }
+
+ free_names();
+ netname = strcmp(name, ".") ? xstrdup(name) : NULL;
+ make_names(false);
+
+ free(tinc_conf);
+ free(hosts_dir);
+ free(prompt);
+
+ xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase);
+ xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase);
+ xasprintf(&prompt, "%s> ", identname);
+
+ return 0;
+}
+
+static int cmd_network(int argc, char *argv[]) {
+ if(argc > 2) {
+ fprintf(stderr, "Too many arguments!\n");
+ return 1;
+ }
+
+ if(argc == 2)
+ return switch_network(argv[1]);
+
+ DIR *dir = opendir(confdir);
+ if(!dir) {
+ fprintf(stderr, "Could not read directory %s: %s\n", confdir, strerror(errno));
+ return 1;
+ }
+
+ struct dirent *ent;
+ while((ent = readdir(dir))) {
+ if(*ent->d_name == '.')
+ continue;
+
+ if(!strcmp(ent->d_name, "tinc.conf")) {
+ printf(".\n");
+ continue;
+ }
+
+ char fname[PATH_MAX];
+ snprintf(fname, sizeof fname, "%s/%s/tinc.conf", confdir, ent->d_name);
+ if(!access(fname, R_OK))
+ printf("%s\n", ent->d_name);
+ }
+
+ closedir(dir);
+
+ return 0;
+}
+
+static int cmd_fsck(int argc, char *argv[]) {
+ if(argc > 1) {
+ fprintf(stderr, "Too many arguments!\n");
+ return 1;
+ }
+
+ return fsck(orig_argv[0]);
+}
+
+static void *readfile(FILE *in, size_t *len) {
+ size_t count = 0;
+ size_t alloced = 4096;
+ char *buf = xmalloc(alloced);
+
+ while(!feof(in)) {
+ size_t read = fread(buf + count, 1, alloced - count, in);
+ if(!read)
+ break;
+ count += read;
+ if(count >= alloced) {
+ alloced *= 2;
+ buf = xrealloc(buf, alloced);
+ }
+ }
+
+ if(len)
+ *len = count;
+
+ return buf;
+}
+
+static int cmd_sign(int argc, char *argv[]) {
+ if(argc > 2) {
+ fprintf(stderr, "Too many arguments!\n");
+ return 1;
+ }
+
+ if(!name) {
+ name = get_my_name(true);
+ if(!name)
+ return 1;
+ }
+
+ char fname[PATH_MAX];
+ snprintf(fname, sizeof fname, "%s" SLASH "ed25519_key.priv", confbase);
+ FILE *fp = fopen(fname, "r");
+ if(!fp) {
+ fprintf(stderr, "Could not open %s: %s\n", fname, strerror(errno));
+ return 1;
+ }
+
+ ecdsa_t *key = ecdsa_read_pem_private_key(fp);
+
+ if(!key) {
+ fprintf(stderr, "Could not read private key from %s\n", fname);
+ fclose(fp);
+ return 1;
+ }
+
+ fclose(fp);
+
+ FILE *in;
+
+ if(argc == 2) {
+ in = fopen(argv[1], "rb");
+ if(!in) {
+ fprintf(stderr, "Could not open %s: %s\n", argv[1], strerror(errno));
+ ecdsa_free(key);
+ return 1;
+ }
+ } else {
+ in = stdin;
+ }
+
+ size_t len;
+ char *data = readfile(in, &len);
+ if(in != stdin)
+ fclose(in);
+ if(!data) {
+ fprintf(stderr, "Error reading %s: %s\n", argv[1], strerror(errno));
+ ecdsa_free(key);
+ return 1;
+ }
+
+ // Ensure we sign our name and current time as well
+ long t = time(NULL);
+ char *trailer;
+ xasprintf(&trailer, " %s %ld", name, t);
+ int trailer_len = strlen(trailer);
+
+ data = xrealloc(data, len + trailer_len);
+ memcpy(data + len, trailer, trailer_len);
+ free(trailer);
+
+ char sig[87];
+ if(!ecdsa_sign(key, data, len + trailer_len, sig)) {
+ fprintf(stderr, "Error generating signature\n");
+ free(data);
+ ecdsa_free(key);
+ return 1;
+ }
+ b64encode(sig, sig, 64);
+ ecdsa_free(key);
+
+ fprintf(stdout, "Signature = %s %ld %s\n", name, t, sig);
+ fwrite(data, len, 1, stdout);
+
+ free(data);
+ return 0;
+}
+
+static int cmd_verify(int argc, char *argv[]) {
+ if(argc < 2) {
+ fprintf(stderr, "Not enough arguments!\n");
+ return 1;
+ }
+
+ if(argc > 3) {
+ fprintf(stderr, "Too many arguments!\n");
+ return 1;
+ }
+
+ char *node = argv[1];
+ if(!strcmp(node, ".")) {
+ if(!name) {
+ name = get_my_name(true);
+ if(!name)
+ return 1;
+ }
+ node = name;
+ } else if(!strcmp(node, "*")) {
+ node = NULL;
+ } else {
+ if(!check_id(node)) {
+ fprintf(stderr, "Invalid node name\n");
+ return 1;
+ }
+ }
+
+ FILE *in;
+
+ if(argc == 3) {
+ in = fopen(argv[2], "rb");
+ if(!in) {
+ fprintf(stderr, "Could not open %s: %s\n", argv[2], strerror(errno));
+ return 1;
+ }
+ } else {
+ in = stdin;
+ }
+
+ size_t len;
+ char *data = readfile(in, &len);
+ if(in != stdin)
+ fclose(in);
+ if(!data) {
+ fprintf(stderr, "Error reading %s: %s\n", argv[1], strerror(errno));
+ return 1;
+ }
+
+ char *newline = memchr(data, '\n', len);
+ if(!newline || (newline - data > MAX_STRING_SIZE - 1)) {
+ fprintf(stderr, "Invalid input\n");
+ return 1;
+ }
+
+ *newline++ = '\0';
+ size_t skip = newline - data;
+
+ char signer[MAX_STRING_SIZE] = "";
+ char sig[MAX_STRING_SIZE] = "";
+ long t = 0;
+
+ if(sscanf(data, "Signature = %s %ld %s", signer, &t, sig) != 3 || strlen(sig) != 86 || !t || !check_id(signer)) {
+ fprintf(stderr, "Invalid input\n");
+ return 1;
+ }
+
+ if(node && strcmp(node, signer)) {
+ fprintf(stderr, "Signature is not made by %s\n", node);
+ return 1;
+ }
+
+ if(!node)
+ node = signer;
+
+ char *trailer;
+ xasprintf(&trailer, " %s %ld", signer, t);
+ int trailer_len = strlen(trailer);
+
+ data = xrealloc(data, len + trailer_len);
+ memcpy(data + len, trailer, trailer_len);
+ free(trailer);
+
+ newline = data + skip;
+
+ char fname[PATH_MAX];
+ snprintf(fname, sizeof fname, "%s" SLASH "hosts" SLASH "%s", confbase, node);
+ FILE *fp = fopen(fname, "r");
+ if(!fp) {
+ fprintf(stderr, "Could not open %s: %s\n", fname, strerror(errno));
+ free(data);
+ return 1;
+ }
+
+ ecdsa_t *key = get_pubkey(fp);
+ if(!key) {
+ rewind(fp);
+ key = ecdsa_read_pem_public_key(fp);
+ }
+ if(!key) {
+ fprintf(stderr, "Could not read public key from %s\n", fname);
+ fclose(fp);
+ free(data);
+ return 1;
+ }
+
+ fclose(fp);
+
+ if(b64decode(sig, sig, 86) != 64 || !ecdsa_verify(key, newline, len + trailer_len - (newline - data), sig)) {
+ fprintf(stderr, "Invalid signature\n");
+ free(data);
+ ecdsa_free(key);
+ return 1;
+ }
+
+ ecdsa_free(key);
+
+ fwrite(newline, len - (newline - data), 1, stdout);
+
+ free(data);
+ return 0;
+}
+