+static int cmd_exchange(int argc, char *argv[]) {
+ return cmd_export(argc, argv) ? 1 : cmd_import(argc, argv);
+}
+
+static int cmd_exchange_all(int argc, char *argv[]) {
+ return cmd_export_all(argc, argv) ? 1 : 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) {
+ closesocket(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[]) {
+ (void)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 bufsize = 4096;
+ char *buf = xmalloc(bufsize);
+
+ while(!feof(in)) {
+ size_t read = fread(buf + count, 1, bufsize - count, in);
+
+ if(!read) {
+ break;
+ }
+
+ count += read;
+
+ if(count >= bufsize) {
+ bufsize *= 2;
+ buf = xrealloc(buf, bufsize);
+ }
+ }
+
+ 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);
+ size_t 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_tinc(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");
+ free(data);
+ 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");
+ free(data);
+ return 1;
+ }
+
+ if(node && strcmp(node, signer)) {
+ fprintf(stderr, "Signature is not made by %s\n", node);
+ free(data);
+ return 1;
+ }
+
+ if(!node) {
+ node = signer;
+ }
+
+ char *trailer;
+ xasprintf(&trailer, " %s %ld", signer, t);
+ size_t 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_tinc(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;
+}
+
+static const struct {
+ const char *command;
+ int (*function)(int argc, char *argv[]);
+ bool hidden;