+ FILE *f = fopen(tinc_conf, "w");
+
+ if(!f) {
+ fprintf(stderr, "Could not create file %s: %s\n", tinc_conf, strerror(errno));
+ return 1;
+ }
+
+ fprintf(f, "Name = %s\n", name);
+ fclose(f);
+
+#ifndef DISABLE_LEGACY
+
+ if(!rsa_keygen(2048, false)) {
+ return 1;
+ }
+
+#endif
+
+ if(!ed25519_keygen(false)) {
+ return 1;
+ }
+
+ check_port(name);
+
+#ifndef HAVE_MINGW
+ char filename[PATH_MAX];
+ snprintf(filename, sizeof(filename), "%s" SLASH "tinc-up", confbase);
+
+ if(access(filename, F_OK)) {
+ FILE *f = fopenmask(filename, "w", 0777);
+
+ if(!f) {
+ fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno));
+ return 1;
+ }
+
+ fprintf(f, "#!/bin/sh\n\necho 'Unconfigured tinc-up script, please edit '$0'!'\n\n#ifconfig $INTERFACE <your vpn IP address> netmask <netmask of whole VPN>\n");
+ fclose(f);
+ }
+
+#endif
+
+ return 0;
+
+}
+
+static int cmd_generate_keys(int argc, char *argv[]) {
+#ifdef DISABLE_LEGACY
+
+ if(argc > 1) {
+#else
+
+ if(argc > 2) {
+#endif
+ fprintf(stderr, "Too many arguments!\n");
+ return 1;
+ }
+
+ if(!name) {
+ name = get_my_name(false);
+ }
+
+#ifndef DISABLE_LEGACY
+
+ if(!rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true)) {
+ return 1;
+ }
+
+#endif
+
+ if(!ed25519_keygen(true)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+#ifndef DISABLE_LEGACY
+static int cmd_generate_rsa_keys(int argc, char *argv[]) {
+ if(argc > 2) {
+ fprintf(stderr, "Too many arguments!\n");
+ return 1;
+ }
+
+ if(!name) {
+ name = get_my_name(false);
+ }
+
+ return !rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true);
+}
+#endif
+
+static int cmd_generate_ed25519_keys(int argc, char *argv[]) {
+ (void)argv;
+
+ if(argc > 1) {
+ fprintf(stderr, "Too many arguments!\n");
+ return 1;
+ }
+
+ if(!name) {
+ name = get_my_name(false);
+ }
+
+ return !ed25519_keygen(true);
+}
+
+static int cmd_help(int argc, char *argv[]) {
+ (void)argc;
+ (void)argv;
+
+ usage(false);
+ return 0;
+}
+
+static int cmd_version(int argc, char *argv[]) {
+ (void)argv;
+
+ if(argc > 1) {
+ fprintf(stderr, "Too many arguments!\n");
+ return 1;
+ }
+
+ version();
+ return 0;
+}
+
+static int cmd_info(int argc, char *argv[]) {
+ if(argc != 2) {
+ fprintf(stderr, "Invalid number of arguments.\n");
+ return 1;
+ }
+
+ if(!connect_tincd(true)) {
+ return 1;
+ }
+
+ return info(fd, argv[1]);
+}
+
+static const char *conffiles[] = {
+ "tinc.conf",
+ "tinc-up",
+ "tinc-down",
+ "subnet-up",
+ "subnet-down",
+ "host-up",
+ "host-down",
+ NULL,
+};
+
+static int cmd_edit(int argc, char *argv[]) {
+ if(argc != 2) {
+ fprintf(stderr, "Invalid number of arguments.\n");
+ return 1;
+ }
+
+ char filename[PATH_MAX] = "";
+
+ if(strncmp(argv[1], "hosts" SLASH, 6)) {
+ for(int i = 0; conffiles[i]; i++) {
+ if(!strcmp(argv[1], conffiles[i])) {
+ snprintf(filename, sizeof(filename), "%s" SLASH "%s", confbase, argv[1]);
+ break;
+ }
+ }
+ } else {
+ argv[1] += 6;
+ }
+
+ if(!*filename) {
+ snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, argv[1]);
+ char *dash = strchr(argv[1], '-');
+
+ if(dash) {
+ *dash++ = 0;
+
+ if((strcmp(dash, "up") && strcmp(dash, "down")) || !check_id(argv[1])) {
+ fprintf(stderr, "Invalid configuration filename.\n");
+ return 1;
+ }
+ }
+ }
+
+ char *command;
+#ifndef HAVE_MINGW
+ const char *editor = getenv("VISUAL");
+ if (!editor)
+ editor = getenv("EDITOR");
+ if (!editor)
+ editor = "vi";
+
+ xasprintf(&command, "\"%s\" \"%s\"", editor, filename);
+#else
+ xasprintf(&command, "edit \"%s\"", filename);
+#endif
+ int result = system(command);
+ free(command);
+
+ if(result) {
+ return result;
+ }
+
+ // Silently try notifying a running tincd of changes.
+ if(connect_tincd(false)) {
+ sendline(fd, "%d %d", CONTROL, REQ_RELOAD);
+ }
+
+ return 0;
+}
+
+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[]) {
+ (void)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[]) {
+ (void)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[]) {
+ (void)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 = %4095s", name) == 1) {
+ firstline = false;
+
+ if(!check_id(name)) {
+ fprintf(stderr, "Invalid Name in input!\n");
+ return 1;
+ }
+
+ if(out) {
+ fclose(out);
+ }
+
+ if((size_t)snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, name) >= sizeof(filename)) {
+ fprintf(stderr, "Filename too long: %s" SLASH "%s\n", hosts_dir, name);
+ return 1;
+ }
+
+ 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) ? 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) {
+ 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[]) {
+ (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);