+
+static int cmd_version(int argc, char *argv[]) {
+ 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())
+ 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 = NULL;
+
+ if(strncmp(argv[1], "hosts" SLASH, 6)) {
+ for(int i = 0; conffiles[i]; i++) {
+ if(!strcmp(argv[1], conffiles[i])) {
+ xasprintf(&filename, "%s" SLASH "%s", confbase, argv[1]);
+ break;
+ }
+ }
+ } else {
+ argv[1] += 6;
+ }
+
+ if(!filename) {
+ xasprintf(&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
+ xasprintf(&command, "\"%s\" \"%s\"", getenv("VISUAL") ?: getenv("EDITOR") ?: "vi", filename);
+#else
+ xasprintf(&command, "edit \"%s\"", filename);
+#endif
+ int result = system(command);
+ if(result)
+ return result;
+
+ // Silently try notifying a running tincd of changes.
+ fclose(stderr);
+
+ if(connect_tincd())
+ sendline(fd, "%d %d", CONTROL, REQ_RELOAD);
+
+ return 0;
+}
+
+static int export(const char *name, FILE *out) {
+ char *filename;
+ xasprintf(&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));
+ return 1;
+ }
+
+ fclose(in);
+ return 0;
+}
+
+static int cmd_export(int argc, char *argv[]) {
+ char *name = get_my_name();
+ if(!name)
+ return 1;
+
+ return export(name, stdout);
+}
+
+static int cmd_export_all(int argc, char *argv[]) {
+ 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);
+ return result;
+}
+
+static int cmd_import(int argc, char *argv[]) {
+ FILE *in = stdin;
+ FILE *out = NULL;
+
+ char buf[4096];
+ char name[4096];
+ char *filename;
+ int count = 0;
+ bool firstline = true;
+
+ while(fgets(buf, sizeof buf, in)) {
+ if(sscanf(buf, "Name = %s", name) == 1) {
+ if(!check_id(name)) {
+ fprintf(stderr, "Invalid Name in input!\n");
+ return 1;
+ }
+
+ if(out)
+ fclose(out);
+
+ free(filename);
+ xasprintf(&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++;
+ firstline = false;
+ 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 const struct {
+ const char *command;
+ int (*function)(int argc, char *argv[]);
+} commands[] = {
+ {"start", cmd_start},
+ {"stop", cmd_stop},
+ {"restart", cmd_restart},
+ {"reload", cmd_reload},
+ {"dump", cmd_dump},
+ {"purge", cmd_purge},
+ {"debug", cmd_debug},
+ {"retry", cmd_retry},
+ {"connect", cmd_connect},
+ {"disconnect", cmd_disconnect},
+ {"top", cmd_top},
+ {"pcap", cmd_pcap},
+ {"log", cmd_log},
+ {"pid", cmd_pid},
+ {"config", cmd_config},
+ {"init", cmd_init},
+ {"generate-keys", cmd_generate_keys},
+ {"generate-rsa-keys", cmd_generate_rsa_keys},
+ {"generate-ecdsa-keys", cmd_generate_ecdsa_keys},
+ {"help", cmd_help},
+ {"version", cmd_version},
+ {"info", cmd_info},
+ {"edit", cmd_edit},
+ {"export", cmd_export},
+ {"export-all", cmd_export_all},
+ {"import", cmd_import},
+ {NULL, NULL},
+};
+
+int main(int argc, char *argv[]) {
+ program_name = argv[0];
+
+ if(!parse_options(argc, argv))
+ return 1;
+
+ make_names();
+
+ if(show_version) {
+ version();
+ return 0;
+ }
+
+ if(show_help) {
+ usage(false);
+ return 0;
+ }
+
+ if(optind >= argc) {
+ fprintf(stderr, "No command given.\n");
+ usage(true);
+ return 1;
+ }
+
+ for(int i = 0; commands[i].command; i++) {
+ if(!strcasecmp(argv[optind], commands[i].command))
+ return commands[i].function(argc - optind, argv + optind);
+ }
+
+ fprintf(stderr, "Unknown command `%s'.\n", argv[optind]);
+ usage(true);
+ return 1;
+}