X-Git-Url: https://www.tinc-vpn.org/git/browse?a=blobdiff_plain;f=src%2Ftincctl.c;h=1cdcf586a2556511e8e1b27558b29f4080b1b02e;hb=07980b056c5371f8b6fdd50172f501be07155bdf;hp=1fe4fffee956dce28e3ad5b34620fc2c77715eeb;hpb=dd102efd24d847c41890adfcc7ce6d9d2592dcdb;p=tinc diff --git a/src/tincctl.c b/src/tincctl.c index 1fe4fffe..1cdcf586 100644 --- a/src/tincctl.c +++ b/src/tincctl.c @@ -25,11 +25,16 @@ #include "protocol.h" #include "control_common.h" #include "ecdsagen.h" +#include "info.h" #include "rsagen.h" #include "utils.h" #include "tincctl.h" #include "top.h" +#ifdef HAVE_MINGW +#define mkdir(a, b) mkdir(a) +#endif + /* The name this program was run with. */ static char *program_name = NULL; @@ -42,9 +47,12 @@ static bool show_version = false; static char *name = NULL; static char *identname = NULL; /* program name for syslog */ static char *pidfilename = NULL; /* pid file location */ +static char *confdir = NULL; static char controlcookie[1024]; char *netname = NULL; char *confbase = NULL; +static char *tinc_conf = NULL; +static char *hosts_dir = NULL; // Horrible global variables... static int pid = 0; @@ -53,6 +61,7 @@ static char line[4096]; static int code; static int req; static int result; +static bool force = false; #ifdef HAVE_MINGW static struct WSAData wsa_state; @@ -60,10 +69,19 @@ static struct WSAData wsa_state; static struct option const long_options[] = { {"config", required_argument, NULL, 'c'}, + {"debug", optional_argument, NULL, 0}, + {"no-detach", no_argument, NULL, 0}, + {"mlock", no_argument, NULL, 0}, {"net", required_argument, NULL, 'n'}, {"help", no_argument, NULL, 1}, {"version", no_argument, NULL, 2}, {"pidfile", required_argument, NULL, 5}, + {"logfile", required_argument, NULL, 0}, + {"bypass-security", no_argument, NULL, 0}, + {"chroot", no_argument, NULL, 0}, + {"user", required_argument, NULL, 0}, + {"option", required_argument, NULL, 0}, + {"force", no_argument, NULL, 6}, {NULL, 0, NULL, 0} }; @@ -92,7 +110,11 @@ static void usage(bool status) { "\n" "Valid commands are:\n" " init [name] Create initial configuration files.\n" - " start Start tincd.\n" + " config Change configuration:\n" + " [set] VARIABLE VALUE - set VARIABLE to VALUE\n" + " add VARIABLE VALUE - add VARIABLE with the given VALUE\n" + " del VARIABLE [VALUE] - remove VARIABLE [only ones with watching VALUE]\n" + " start [tincd options] Start tincd.\n" " stop Stop tincd.\n" " restart Restart tincd.\n" " reload Partially reload configuration of running tincd.\n" @@ -106,6 +128,7 @@ static void usage(bool status) { " subnets - all known subnets in the VPN\n" " connections - all meta connections with ourself\n" " graph - graph of the VPN in dotty format\n" + " info NODE|SUBNET|ADDRESS Give information about a particular NODE, SUBNET or ADDRESS.\n" " purge Purge unreachable nodes\n" " debug N Set debug level\n" " retry Retry all outgoing connections\n" @@ -115,6 +138,9 @@ static void usage(bool status) { #endif " pcap [snaplen] Dump traffic in pcap format [up to snaplen bytes per packet]\n" " log [level] Dump log output [up to the specified level]\n" + " export Export host configuration of local node to standard output\n" + " export-all Export all host configuration files to standard output\n" + " import [--force] Import host configuration file(s) from standard input\n" "\n"); printf("Report bugs to tinc@tinc-vpn.org.\n"); } @@ -124,7 +150,7 @@ static bool parse_options(int argc, char **argv) { int r; int option_index = 0; - while((r = getopt_long(argc, argv, "c:n:", long_options, &option_index)) != EOF) { + while((r = getopt_long(argc, argv, "c:n:Dd::Lo:RU:", long_options, &option_index)) != EOF) { switch (r) { case 0: /* long option */ break; @@ -149,6 +175,10 @@ static bool parse_options(int argc, char **argv) { pidfilename = xstrdup(optarg); break; + case 6: + force = true; + break; + case '?': usage(true); return false; @@ -158,11 +188,15 @@ static bool parse_options(int argc, char **argv) { } } - if(!netname) { - netname = getenv("NETNAME"); - if(netname) - netname = xstrdup(netname); - } + if(!netname && (netname = getenv("NETNAME"))) + netname = xstrdup(netname); + + /* netname "." is special: a "top-level name" */ + + if(netname && !strcmp(netname, ".")) { + free(netname); + netname = NULL; + } return true; } @@ -201,7 +235,7 @@ static FILE *ask_and_open(const char *filename, const char *what, const char *mo #endif /* The directory is a relative path or a filename. */ directory = get_current_dir_name(); - snprintf(buf2, sizeof buf2, "%s/%s", directory, filename); + snprintf(buf2, sizeof buf2, "%s" SLASH "%s", directory, filename); filename = buf2; } @@ -236,7 +270,7 @@ static bool ecdsa_keygen() { } else fprintf(stderr, "Done.\n"); - xasprintf(&filename, "%s/ecdsa_key.priv", confbase); + xasprintf(&filename, "%s" SLASH "ecdsa_key.priv", confbase); f = ask_and_open(filename, "private ECDSA key", "a"); if(!f) @@ -256,9 +290,9 @@ static bool ecdsa_keygen() { free(filename); if(name) - xasprintf(&filename, "%s/hosts/%s", confbase, name); + xasprintf(&filename, "%s" SLASH "hosts" SLASH "%s", confbase, name); else - xasprintf(&filename, "%s/ecdsa_key.pub", confbase); + xasprintf(&filename, "%s" SLASH "ecdsa_key.pub", confbase); f = ask_and_open(filename, "public ECDSA key", "a"); @@ -295,7 +329,7 @@ static bool rsa_keygen(int bits) { } else fprintf(stderr, "Done.\n"); - xasprintf(&filename, "%s/rsa_key.priv", confbase); + xasprintf(&filename, "%s" SLASH "rsa_key.priv", confbase); f = ask_and_open(filename, "private RSA key", "a"); if(!f) @@ -315,9 +349,9 @@ static bool rsa_keygen(int bits) { free(filename); if(name) - xasprintf(&filename, "%s/hosts/%s", confbase, name); + xasprintf(&filename, "%s" SLASH "hosts" SLASH "%s", confbase, name); else - xasprintf(&filename, "%s/rsa_key.pub", confbase); + xasprintf(&filename, "%s" SLASH "rsa_key.pub", confbase); f = ask_and_open(filename, "public RSA key", "a"); @@ -355,31 +389,40 @@ static void make_names(void) { if(!RegQueryValueEx(key, NULL, 0, 0, installdir, &len)) { if(!confbase) { if(netname) - xasprintf(&confbase, "%s/%s", installdir, netname); + xasprintf(&confbase, "%s" SLASH "%s", installdir, netname); else xasprintf(&confbase, "%s", installdir); } } if(!pidfilename) - xasprintf(&pidfilename, "%s/pid", confbase); + xasprintf(&pidfilename, "%s" SLASH "pid", confbase); RegCloseKey(key); - if(*installdir) - return; } + + if(!*installdir) { #endif + confdir = xstrdup(CONFDIR); if(!pidfilename) - xasprintf(&pidfilename, "%s/run/%s.pid", LOCALSTATEDIR, identname); + xasprintf(&pidfilename, "%s" SLASH "run" SLASH "%s.pid", LOCALSTATEDIR, identname); if(netname) { if(!confbase) - xasprintf(&confbase, CONFDIR "/tinc/%s", netname); + xasprintf(&confbase, CONFDIR SLASH "tinc" SLASH "%s", netname); else fprintf(stderr, "Both netname and configuration directory given, using the latter...\n"); } else { if(!confbase) - xasprintf(&confbase, CONFDIR "/tinc"); + xasprintf(&confbase, CONFDIR SLASH "tinc"); } + +#ifdef HAVE_MINGW + } else + confdir = xstrdup(installdir); +#endif + + xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase); + xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase); } static char buffer[4096]; @@ -642,6 +685,9 @@ static bool connect_tincd() { static int cmd_start(int argc, char *argv[]) { int i, j; char *c; + + argc += optind; + argv -= optind; char *slash = strrchr(argv[0], '/'); #ifdef HAVE_MINGW @@ -649,10 +695,8 @@ static int cmd_start(int argc, char *argv[]) { slash = c; #endif - if (slash++) { - c = xmalloc((slash - argv[0]) + sizeof("tincd")); - sprintf(c, "%.*stincd", (int)(slash - argv[0]), argv[0]); - } + if (slash++) + xasprintf(&c, "%.*stincd", (int)(slash - argv[0]), argv[0]); else c = "tincd"; @@ -670,8 +714,16 @@ static int cmd_start(int argc, char *argv[]) { static int cmd_stop(int argc, char *argv[]) { #ifndef HAVE_MINGW - if(!connect_tincd()) + if(!connect_tincd()) { + if(pid) { + if(kill(pid, SIGTERM)) + return 1; + fprintf(stderr, "Sent TERM signal to process with PID %u.\n", pid); + return 0; + } + return 1; + } sendline(fd, "%d %d", CONTROL, REQ_STOP); if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_STOP || result) { @@ -710,6 +762,9 @@ static int cmd_dump(int argc, char *argv[]) { return 1; } + if(!connect_tincd()) + return 1; + bool do_graph = false; if(!strcasecmp(argv[1], "nodes")) @@ -730,9 +785,6 @@ static int cmd_dump(int argc, char *argv[]) { return 1; } - if(!connect_tincd()) - return 1; - if(do_graph) printf("digraph {\n"); @@ -819,6 +871,11 @@ static int cmd_connect(int argc, char *argv[]) { return 1; } + if(!check_id(argv[2])) { + fprintf(stderr, "Invalid name for node.\n"); + return 1; + } + if(!connect_tincd()) return 1; @@ -837,6 +894,11 @@ static int cmd_disconnect(int argc, char *argv[]) { return 1; } + if(!check_id(argv[2])) { + fprintf(stderr, "Invalid name for node.\n"); + return 1; + } + if(!connect_tincd()) return 1; @@ -886,15 +948,386 @@ static int cmd_pid(int argc, char *argv[]) { return 0; } +static int rstrip(char *value) { + int len = strlen(value); + while(len && strchr("\t\r\n ", value[len - 1])) + value[--len] = 0; + return len; +} + +static char *get_my_name() { + FILE *f = fopen(tinc_conf, "r"); + if(!f) { + fprintf(stderr, "Could not open %s: %s\n", tinc_conf, strerror(errno)); + return NULL; + } + + char buf[4096]; + char *value; + while(fgets(buf, sizeof buf, f)) { + int len = strcspn(buf, "\t ="); + value = buf + len; + value += strspn(value, "\t "); + if(*value == '=') { + value++; + value += strspn(value, "\t "); + } + if(!rstrip(value)) + continue; + buf[len] = 0; + if(strcasecmp(buf, "Name")) + continue; + if(*value) { + fclose(f); + return strdup(value); + } + } + + fclose(f); + fprintf(stderr, "Could not find Name in %s.\n", tinc_conf); + return NULL; +} + +#define VAR_SERVER 1 /* Should be in tinc.conf */ +#define VAR_HOST 2 /* Can be in host config file */ +#define VAR_MULTIPLE 4 /* Multiple statements allowed */ +#define VAR_OBSOLETE 8 /* Should not be used anymore */ + +static struct { + const char *name; + int type; +} const variables[] = { + /* Server configuration */ + {"AddressFamily", VAR_SERVER}, + {"BindToAddress", VAR_SERVER | VAR_MULTIPLE}, + {"BindToInterface", VAR_SERVER}, + {"Broadcast", VAR_SERVER}, + {"ConnectTo", VAR_SERVER | VAR_MULTIPLE}, + {"DecrementTTL", VAR_SERVER}, + {"Device", VAR_SERVER}, + {"DeviceType", VAR_SERVER}, + {"DirectOnly", VAR_SERVER}, + {"ECDSAPrivateKeyFile", VAR_SERVER}, + {"ExperimentalProtocol", VAR_SERVER}, + {"Forwarding", VAR_SERVER}, + {"GraphDumpFile", VAR_SERVER}, + {"Hostnames", VAR_SERVER}, + {"IffOneQueue", VAR_SERVER}, + {"Interface", VAR_SERVER}, + {"KeyExpire", VAR_SERVER}, + {"LocalDiscovery", VAR_SERVER}, + {"MACExpire", VAR_SERVER}, + {"MaxTimeout", VAR_SERVER}, + {"Mode", VAR_SERVER}, + {"Name", VAR_SERVER}, + {"PingInterval", VAR_SERVER}, + {"PingTimeout", VAR_SERVER}, + {"PriorityInheritance", VAR_SERVER}, + {"PrivateKey", VAR_SERVER | VAR_OBSOLETE}, + {"PrivateKeyFile", VAR_SERVER}, + {"ProcessPriority", VAR_SERVER}, + {"ReplayWindow", VAR_SERVER}, + {"StrictSubnets", VAR_SERVER}, + {"TunnelServer", VAR_SERVER}, + {"UDPRcvBuf", VAR_SERVER}, + {"UDPSndBuf", VAR_SERVER}, + /* Host configuration */ + {"Address", VAR_HOST | VAR_MULTIPLE}, + {"Cipher", VAR_SERVER | VAR_HOST}, + {"ClampMSS", VAR_SERVER | VAR_HOST}, + {"Compression", VAR_SERVER | VAR_HOST}, + {"Digest", VAR_SERVER | VAR_HOST}, + {"IndirectData", VAR_SERVER | VAR_HOST}, + {"MACLength", VAR_SERVER | VAR_HOST}, + {"PMTU", VAR_SERVER | VAR_HOST}, + {"PMTUDiscovery", VAR_SERVER | VAR_HOST}, + {"Port", VAR_HOST}, + {"PublicKey", VAR_SERVER | VAR_HOST | VAR_OBSOLETE}, + {"PublicKeyFile", VAR_SERVER | VAR_HOST | VAR_OBSOLETE}, + {"Subnet", VAR_HOST | VAR_MULTIPLE}, + {"TCPOnly", VAR_SERVER | VAR_HOST}, + {NULL, 0} +}; + +static int cmd_config(int argc, char *argv[]) { + if(argc < 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + int action = 0; + if(!strcasecmp(argv[1], "add")) { + argv++, argc--, action = 1; + } else if(!strcasecmp(argv[1], "del")) { + argv++, argc--, action = -1; + } else if(!strcasecmp(argv[1], "replace") || !strcasecmp(argv[1], "set") || !strcasecmp(argv[1], "change")) { + argv++, argc--, action = 0; + } + + if(argc < 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + // Concatenate the rest of the command line + strncpy(line, argv[1], sizeof line - 1); + for(int i = 2; i < argc; i++) { + strncat(line, " ", sizeof line - 1 - strlen(line)); + strncat(line, argv[i], sizeof line - 1 - strlen(line)); + } + + // Liberal parsing into node name, variable name and value. + char *node = NULL; + char *variable; + char *value; + int len; + + len = strcspn(line, "\t ="); + value = line + len; + value += strspn(value, "\t "); + if(*value == '=') { + value++; + value += strspn(value, "\t "); + } + line[len] = '\0'; + variable = strchr(line, '.'); + if(variable) { + node = line; + *variable++ = 0; + } else { + variable = line; + } + + if(!*variable) { + fprintf(stderr, "No variable given.\n"); + return 1; + } + + if(action >= 0 && !*value) { + fprintf(stderr, "No value for variable given.\n"); + return 1; + } + + /* Some simple checks. */ + bool found = false; + + for(int i = 0; variables[i].name; i++) { + if(strcasecmp(variables[i].name, variable)) + continue; + + found = true; + variable = (char *)variables[i].name; + + /* Discourage use of obsolete variables. */ + + if(variables[i].type & VAR_OBSOLETE && action >= 0) { + if(force) { + fprintf(stderr, "Warning: %s is an obsolete variable!\n", variable); + } else { + fprintf(stderr, "%s is an obsolete variable! Use --force to use it anyway.\n", variable); + return 1; + } + } + + /* Don't put server variables in host config files */ + + if(node && !(variables[i].type & VAR_HOST) && action >= 0) { + if(force) { + fprintf(stderr, "Warning: %s is not a host configuration variable!\n", variable); + } else { + fprintf(stderr, "%s is not a host configuration variable! Use --force to use it anyway.\n", variable); + return 1; + } + } + + /* Should this go into our own host config file? */ + + if(!node && !(variables[i].type & VAR_SERVER)) { + node = get_my_name(); + if(!node) + return 1; + } + + break; + } + + if(node && !check_id(node)) { + fprintf(stderr, "Invalid name for node.\n"); + return 1; + } + + if(!found && action >= 0) { + if(force) { + fprintf(stderr, "Warning: %s is not a known configuration variable!\n", variable); + } else { + fprintf(stderr, "%s: is not a known configuration variable! Use --force to use it anyway.\n", variable); + return 1; + } + } + + // Open the right configuration file. + char *filename; + if(node) + xasprintf(&filename, "%s" SLASH "%s", hosts_dir, node); + else + filename = tinc_conf; + + FILE *f = fopen(filename, "r"); + if(!f) { + if(action < 0 || errno != ENOENT) { + fprintf(stderr, "Could not open configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } + + // If it doesn't exist, create it. + f = fopen(filename, "a+"); + if(!f) { + fprintf(stderr, "Could not create configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } else { + fprintf(stderr, "Created configuration file %s.\n", filename); + } + } + + char *tmpfile; + xasprintf(&tmpfile, "%s.config.tmp", filename); + FILE *tf = fopen(tmpfile, "w"); + if(!tf) { + fprintf(stderr, "Could not open temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; + } + + // Copy the file, making modifications on the fly. + char buf1[4096]; + char buf2[4096]; + bool set = false; + bool removed = false; + + while(fgets(buf1, sizeof buf1, f)) { + buf1[sizeof buf1 - 1] = 0; + strncpy(buf2, buf1, sizeof buf2); + + // Parse line in a simple way + char *bvalue; + int len; + + len = strcspn(buf2, "\t ="); + bvalue = buf2 + len; + bvalue += strspn(bvalue, "\t "); + if(*bvalue == '=') { + bvalue++; + bvalue += strspn(bvalue, "\t "); + } + rstrip(bvalue); + buf2[len] = '\0'; + + // Did it match? + if(!strcasecmp(buf2, variable)) { + // Del + if(action < 0) { + if(!*value || !strcasecmp(bvalue, value)) { + removed = true; + continue; + } + // Set + } else if(action == 0) { + // Already set? Delete the rest... + if(set) + continue; + // Otherwise, replace. + if(fprintf(tf, "%s = %s\n", variable, value) < 0) { + fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; + } + set = true; + continue; + } + } + + // Copy original line... + if(fputs(buf1, tf) < 0) { + fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; + } + + // Add newline if it is missing... + if(*buf1 && buf1[strlen(buf1) - 1] != '\n') { + if(fputc('\n', tf) < 0) { + fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; + } + } + } + + // Make sure we read everything... + if(ferror(f) || !feof(f)) { + fprintf(stderr, "Error while reading from configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } + + if(fclose(f)) { + fprintf(stderr, "Error closing configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } + + // Add new variable if necessary. + if(action > 0 || (action == 0 && !set)) { + if(fprintf(tf, "%s = %s\n", variable, value) < 0) { + fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; + } + } + + // Make sure we wrote everything... + if(fclose(tf)) { + fprintf(stderr, "Error closing temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; + } + + // Could we find what we had to remove? + if(action < 0 && !removed) { + remove(tmpfile); + fprintf(stderr, "No configuration variables deleted.\n"); + return *value; + } + + // Replace the configuration file with the new one +#ifdef HAVE_MINGW + if(remove(filename)) { + fprintf(stderr, "Error replacing file %s: %s\n", filename, strerror(errno)); + return 1; + } +#endif + if(rename(tmpfile, filename)) { + fprintf(stderr, "Error renaming temporary file %s to configuration file %s: %s\n", tmpfile, filename, strerror(errno)); + return 1; + } + + // Silently try notifying a running tincd of changes. + fclose(stderr); + + if(connect_tincd()) + sendline(fd, "%d %d", CONTROL, REQ_RELOAD); + + return 0; +} + +bool check_id(const char *name) { + for(int i = 0; i < strlen(name); i++) { + if(!isalnum(name[i]) && name[i] != '_') + return false; + } + + return true; +} + static int cmd_init(int argc, char *argv[]) { - char *tinc_conf = NULL; - xasprintf(&tinc_conf, "%s/tinc.conf", confbase); - if(!access(confbase, F_OK)) { + if(!access(tinc_conf, F_OK)) { fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf); return 1; } - if(optind >= argc - 1) { + if(argc < 2) { if(isatty(0) && isatty(1)) { char buf[1024]; fprintf(stdout, "Enter the Name you want your tinc node to have: "); @@ -903,9 +1336,7 @@ static int cmd_init(int argc, char *argv[]) { fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno)); return 1; } - int len = strlen(buf); - if(len) - buf[--len] = 0; + int len = rstrip(buf); if(!len) { fprintf(stderr, "No name given!\n"); return 1; @@ -916,21 +1347,19 @@ static int cmd_init(int argc, char *argv[]) { return 1; } } else { - name = strdup(argv[optind + 1]); + name = strdup(argv[1]); if(!*name) { fprintf(stderr, "No Name given!\n"); return 1; } } - for(int i = 0; i < strlen(name); i++) { - if(!isalnum(name[i]) && name[i] != '_') { - fprintf(stderr, "Invalid Name! Only a-z, A-Z, 0-9 and _ are allowed characters.\n"); - return 1; - } + if(!check_id(name)) { + fprintf(stderr, "Invalid Name! Only a-z, A-Z, 0-9 and _ are allowed characters.\n"); + return 1; } - if(mkdir(CONFDIR, 0755) && errno != EEXIST) { + if(mkdir(confdir, 0755) && errno != EEXIST) { fprintf(stderr, "Could not create directory %s: %s\n", CONFDIR, strerror(errno)); return 1; } @@ -940,8 +1369,6 @@ static int cmd_init(int argc, char *argv[]) { return 1; } - char *hosts_dir = NULL; - xasprintf(&hosts_dir, "%s/hosts", confbase); if(mkdir(hosts_dir, 0755) && errno != EEXIST) { fprintf(stderr, "Could not create directory %s: %s\n", hosts_dir, strerror(errno)); return 1; @@ -958,9 +1385,24 @@ static int cmd_init(int argc, char *argv[]) { fclose(stdin); if(!rsa_keygen(2048) || !ecdsa_keygen()) - return false; + return 1; - return true; +#ifndef HAVE_MINGW + char *filename; + xasprintf(&filename, "%s" SLASH "tinc-up", confbase); + if(access(filename, F_OK)) { + FILE *f = fopen(filename, "w"); + if(!f) { + fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); + return 1; + } + fchmod(fileno(f), 0755); + fprintf(f, "#!/bin/sh\n\necho 'Unconfigured tinc-up script, please edit!'\n\n#ifconfig $INTERFACE netmask \n"); + fclose(f); + } +#endif + + return 0; } @@ -986,6 +1428,206 @@ static int cmd_version(int argc, char *argv[]) { 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[]); @@ -1004,12 +1646,18 @@ static const struct { {"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}, };