X-Git-Url: http://git.meshlink.io/?a=blobdiff_plain;f=src%2Ftincctl.c;h=3b0c614feec4dca1a90828c7d6ff4fde98b1e96f;hb=73348be58ecb9c40cf435122a00e72ac4d1a4c9b;hp=8a3309556c3ef32c6c137593b289b0f4170a1e2b;hpb=e72e6febfeddbd4354560388c8e0e125a8017909;p=meshlink diff --git a/src/tincctl.c b/src/tincctl.c index 8a330955..3b0c614f 100644 --- a/src/tincctl.c +++ b/src/tincctl.c @@ -21,15 +21,26 @@ #include +#ifdef HAVE_READLINE +#include "readline/readline.h" +#include "readline/history.h" +#endif + #include "xalloc.h" #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,6 +53,7 @@ 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; @@ -55,6 +67,8 @@ static char line[4096]; static int code; static int req; static int result; +static bool force = false; +static bool tty = true; #ifdef HAVE_MINGW static struct WSAData wsa_state; @@ -74,6 +88,7 @@ static struct option const long_options[] = { {"chroot", no_argument, NULL, 0}, {"user", required_argument, NULL, 0}, {"option", required_argument, NULL, 0}, + {"force", no_argument, NULL, 6}, {NULL, 0, NULL, 0} }; @@ -103,6 +118,7 @@ static void usage(bool status) { "Valid commands are:\n" " init [name] Create initial configuration files.\n" " config Change configuration:\n" + " [get] VARIABLE - print current value of VARIABLE\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" @@ -120,6 +136,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" @@ -129,6 +146,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"); } @@ -163,6 +183,10 @@ static bool parse_options(int argc, char **argv) { pidfilename = xstrdup(optarg); break; + case 6: + force = true; + break; + case '?': usage(true); return false; @@ -172,23 +196,32 @@ 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 && (!*netname || !strcmp(netname, "."))) { + free(netname); + netname = NULL; + } + + if(netname && (strpbrk(netname, "\\/") || *netname == '.')) { + fprintf(stderr, "Invalid character in netname!\n"); + return false; } return true; } -static FILE *ask_and_open(const char *filename, const char *what, const char *mode) { +static FILE *ask_and_open(const char *filename, const char *what, const char *mode, bool ask) { FILE *r; char *directory; char buf[PATH_MAX]; char buf2[PATH_MAX]; /* Check stdin and stdout */ - if(isatty(0) && isatty(1)) { + if(ask && tty) { /* Ask for a file and/or directory name. */ fprintf(stdout, "Please enter a file to save %s to [%s]: ", what, filename); @@ -215,7 +248,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; } @@ -237,7 +270,7 @@ static FILE *ask_and_open(const char *filename, const char *what, const char *mo Generate a public/private ECDSA keypair, and ask for a file to store them in. */ -static bool ecdsa_keygen() { +static bool ecdsa_keygen(bool ask) { ecdsa_t key; FILE *f; char *filename; @@ -250,8 +283,8 @@ static bool ecdsa_keygen() { } else fprintf(stderr, "Done.\n"); - xasprintf(&filename, "%s/ecdsa_key.priv", confbase); - f = ask_and_open(filename, "private ECDSA key", "a"); + xasprintf(&filename, "%s" SLASH "ecdsa_key.priv", confbase); + f = ask_and_open(filename, "private ECDSA key", "a", ask); if(!f) return false; @@ -270,11 +303,11 @@ 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"); + f = ask_and_open(filename, "public ECDSA key", "a", ask); if(!f) return false; @@ -296,7 +329,7 @@ static bool ecdsa_keygen() { Generate a public/private RSA keypair, and ask for a file to store them in. */ -static bool rsa_keygen(int bits) { +static bool rsa_keygen(int bits, bool ask) { rsa_t key; FILE *f; char *filename; @@ -309,8 +342,8 @@ static bool rsa_keygen(int bits) { } else fprintf(stderr, "Done.\n"); - xasprintf(&filename, "%s/rsa_key.priv", confbase); - f = ask_and_open(filename, "private RSA key", "a"); + xasprintf(&filename, "%s" SLASH "rsa_key.priv", confbase); + f = ask_and_open(filename, "private RSA key", "a", ask); if(!f) return false; @@ -329,11 +362,11 @@ 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"); + f = ask_and_open(filename, "public RSA key", "a", ask); if(!f) return false; @@ -369,38 +402,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) { #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/tinc.conf", confbase); - xasprintf(&hosts_dir, "%s/hosts", confbase); + xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase); + xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase); } static char buffer[4096]; @@ -581,13 +616,14 @@ static bool remove_service(void) { } #endif -static bool connect_tincd() { +static bool connect_tincd(bool verbose) { if(fd >= 0) return true; FILE *f = fopen(pidfilename, "r"); if(!f) { - fprintf(stderr, "Could not open pid file %s: %s\n", pidfilename, strerror(errno)); + if(verbose) + fprintf(stderr, "Could not open pid file %s: %s\n", pidfilename, strerror(errno)); return false; } @@ -595,13 +631,18 @@ static bool connect_tincd() { char port[128]; if(fscanf(f, "%20d %1024s %128s port %128s", &pid, controlcookie, host, port) != 4) { - fprintf(stderr, "Could not parse pid file %s\n", pidfilename); + if(verbose) + fprintf(stderr, "Could not parse pid file %s\n", pidfilename); + fclose(f); return false; } + fclose(f); + #ifdef HAVE_MINGW if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) { - fprintf(stderr, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError())); + if(verbose) + fprintf(stderr, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError())); return false; } #endif @@ -616,13 +657,15 @@ static bool connect_tincd() { struct addrinfo *res = NULL; if(getaddrinfo(host, port, &hints, &res) || !res) { - fprintf(stderr, "Cannot resolve %s port %s: %s", host, port, strerror(errno)); + if(verbose) + fprintf(stderr, "Cannot resolve %s port %s: %s", host, port, strerror(errno)); return false; } fd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP); if(fd < 0) { - fprintf(stderr, "Cannot create TCP socket: %s\n", sockstrerror(sockerrno)); + if(verbose) + fprintf(stderr, "Cannot create TCP socket: %s\n", sockstrerror(sockerrno)); return false; } @@ -630,12 +673,14 @@ static bool connect_tincd() { unsigned long arg = 0; if(ioctlsocket(fd, FIONBIO, &arg) != 0) { - fprintf(stderr, "ioctlsocket failed: %s", sockstrerror(sockerrno)); + if(verbose) + fprintf(stderr, "ioctlsocket failed: %s", sockstrerror(sockerrno)); } #endif if(connect(fd, res->ai_addr, res->ai_addrlen) < 0) { - fprintf(stderr, "Cannot connect to %s port %s: %s\n", host, port, sockstrerror(sockerrno)); + if(verbose) + fprintf(stderr, "Cannot connect to %s port %s: %s\n", host, port, sockstrerror(sockerrno)); return false; } @@ -645,14 +690,16 @@ static bool connect_tincd() { int version; if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %s %d", &code, data, &version) != 3 || code != 0) { - fprintf(stderr, "Cannot read greeting from control socket: %s\n", sockstrerror(sockerrno)); + if(verbose) + fprintf(stderr, "Cannot read greeting from control socket: %s\n", sockstrerror(sockerrno)); return false; } sendline(fd, "%d ^%s %d", ID, controlcookie, TINC_CTL_VERSION_CURRENT); if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &version, &pid) != 3 || code != 4 || version != TINC_CTL_VERSION_CURRENT) { - fprintf(stderr, "Could not fully establish control socket connection\n"); + if(verbose) + fprintf(stderr, "Could not fully establish control socket connection\n"); return false; } @@ -673,10 +720,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"; @@ -694,8 +739,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(true)) { + 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,11 +763,12 @@ static int cmd_stop(int argc, char *argv[]) { } static int cmd_restart(int argc, char *argv[]) { - return cmd_stop(argc, argv) ?: cmd_start(argc, argv); + cmd_stop(argc, argv); + return cmd_start(argc, argv); } static int cmd_reload(int argc, char *argv[]) { - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; sendline(fd, "%d %d", CONTROL, REQ_RELOAD); @@ -734,7 +788,7 @@ static int cmd_dump(int argc, char *argv[]) { return 1; } - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; bool do_graph = false; @@ -790,7 +844,7 @@ static int cmd_dump(int argc, char *argv[]) { } static int cmd_purge(int argc, char *argv[]) { - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; sendline(fd, "%d %d", CONTROL, REQ_PURGE); @@ -808,7 +862,7 @@ static int cmd_debug(int argc, char *argv[]) { return 1; } - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; int debuglevel = atoi(argv[1]); @@ -825,7 +879,7 @@ static int cmd_debug(int argc, char *argv[]) { } static int cmd_retry(int argc, char *argv[]) { - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; sendline(fd, "%d %d", CONTROL, REQ_RETRY); @@ -848,7 +902,7 @@ static int cmd_connect(int argc, char *argv[]) { return 1; } - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; sendline(fd, "%d %d %s", CONTROL, REQ_CONNECT, argv[1]); @@ -871,7 +925,7 @@ static int cmd_disconnect(int argc, char *argv[]) { return 1; } - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; sendline(fd, "%d %d %s", CONTROL, REQ_DISCONNECT, argv[1]); @@ -885,7 +939,7 @@ static int cmd_disconnect(int argc, char *argv[]) { static int cmd_top(int argc, char *argv[]) { #ifdef HAVE_CURSES - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; top(fd); @@ -897,7 +951,7 @@ static int cmd_top(int argc, char *argv[]) { } static int cmd_pcap(int argc, char *argv[]) { - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; pcap(fd, stdout, argc > 1 ? atoi(argv[1]) : 0); @@ -905,7 +959,7 @@ static int cmd_pcap(int argc, char *argv[]) { } static int cmd_log(int argc, char *argv[]) { - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; logcontrol(fd, stdout, argc > 1 ? atoi(argv[1]) : -1); @@ -913,7 +967,7 @@ static int cmd_log(int argc, char *argv[]) { } static int cmd_pid(int argc, char *argv[]) { - if(!connect_tincd()) + if(!connect_tincd(true) && !pid) return 1; printf("%d\n", pid); @@ -960,12 +1014,72 @@ static char *get_my_name() { return NULL; } -static char *hostvariables[] = { - "Address", - "Port", - "PublicKey", - "Subnet", - 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}, + {"MaxOutputBufferSize", 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}, + {"Proxy", VAR_SERVER}, + {"ReplayWindow", VAR_SERVER}, + {"StrictSubnets", VAR_SERVER}, + {"TunnelServer", VAR_SERVER}, + {"UDPRcvBuf", VAR_SERVER}, + {"UDPSndBuf", VAR_SERVER}, + {"VDEGroup", VAR_SERVER}, + {"VDEPort", 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}, + {"ECDSAPublicKey", VAR_HOST}, + {"ECDSAPublicKeyFile", 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_HOST | VAR_OBSOLETE}, + {"PublicKeyFile", VAR_SERVER | VAR_HOST | VAR_OBSOLETE}, + {"Subnet", VAR_HOST | VAR_MULTIPLE}, + {"TCPOnly", VAR_SERVER | VAR_HOST}, + {"Weight", VAR_HOST}, + {NULL, 0} }; static int cmd_config(int argc, char *argv[]) { @@ -974,8 +1088,10 @@ static int cmd_config(int argc, char *argv[]) { return 1; } - int action = 0; - if(!strcasecmp(argv[1], "add")) { + int action = -2; + if(!strcasecmp(argv[1], "get")) { + argv++, argc--; + } else if(!strcasecmp(argv[1], "add")) { argv++, argc--, action = 1; } else if(!strcasecmp(argv[1], "del")) { argv++, argc--, action = -1; @@ -1027,16 +1143,50 @@ static int cmd_config(int argc, char *argv[]) { return 1; } - // Should this go into our own host config file? - if(!node) { - for(int i = 0; hostvariables[i]; i++) { - if(!strcasecmp(hostvariables[i], variable)) { - node = get_my_name(); - if(!node) - return 1; - break; + if(action < -1 && *value) + action = 0; + + /* 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)) { @@ -1044,10 +1194,19 @@ static int cmd_config(int argc, char *argv[]) { return 1; } + if(!found) { + if(force || action < 0) { + 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/%s", hosts_dir, node); + xasprintf(&filename, "%s" SLASH "%s", hosts_dir, node); else filename = tinc_conf; @@ -1068,23 +1227,29 @@ static int cmd_config(int argc, char *argv[]) { } } - 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; + char *tmpfile = NULL; + FILE *tf = NULL; + + if(action >= -1) { + xasprintf(&tmpfile, "%s.config.tmp", filename); + tf = fopen(tmpfile, "w"); + if(!tf) { + fprintf(stderr, "Could not open temporary file %s: %s\n", tmpfile, strerror(errno)); + fclose(f); + return 1; + } } - // Copy the file, making modifications on the fly. + // Copy the file, making modifications on the fly, unless we are just getting a value. char buf1[4096]; char buf2[4096]; bool set = false; bool removed = false; + found = false; while(fgets(buf1, sizeof buf1, f)) { buf1[sizeof buf1 - 1] = 0; - strcpy(buf2, buf1); + strncpy(buf2, buf1, sizeof buf2); // Parse line in a simple way char *bvalue; @@ -1102,8 +1267,12 @@ static int cmd_config(int argc, char *argv[]) { // Did it match? if(!strcasecmp(buf2, variable)) { + // Get + if(action < -1) { + found = true; + printf("%s\n", bvalue); // Del - if(action < 0) { + } else if(action == -1) { if(!*value || !strcasecmp(bvalue, value)) { removed = true; continue; @@ -1123,10 +1292,20 @@ static int cmd_config(int argc, char *argv[]) { } } - // Copy original line... - if(fputs(buf1, tf) < 0) { - fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno)); - return 1; + if(action >= -1) { + // 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; + } + } } } @@ -1149,6 +1328,12 @@ static int cmd_config(int argc, char *argv[]) { } } + if(action < -1) { + if(!found) + fprintf(stderr, "No matching configuration variables found.\n"); + return 0; + } + // Make sure we wrote everything... if(fclose(tf)) { fprintf(stderr, "Error closing temporary file %s: %s\n", tmpfile, strerror(errno)); @@ -1175,15 +1360,16 @@ static int cmd_config(int argc, char *argv[]) { } // Silently try notifying a running tincd of changes. - fclose(stderr); - - if(connect_tincd()) + if(connect_tincd(false)) sendline(fd, "%d %d", CONTROL, REQ_RELOAD); return 0; } bool check_id(const char *name) { + if(!name || !*name) + return false; + for(int i = 0; i < strlen(name); i++) { if(!isalnum(name[i]) && name[i] != '_') return false; @@ -1199,7 +1385,7 @@ static int cmd_init(int argc, char *argv[]) { } if(argc < 2) { - if(isatty(0) && isatty(1)) { + if(tty) { char buf[1024]; fprintf(stdout, "Enter the Name you want your tinc node to have: "); fflush(stdout); @@ -1230,7 +1416,7 @@ static int cmd_init(int argc, char *argv[]) { 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; } @@ -1240,8 +1426,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; @@ -1256,24 +1440,38 @@ static int cmd_init(int argc, char *argv[]) { fprintf(f, "Name = %s\n", name); fclose(f); - fclose(stdin); - if(!rsa_keygen(2048) || !ecdsa_keygen()) - return false; + if(!rsa_keygen(2048, false) || !ecdsa_keygen(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; } static int cmd_generate_keys(int argc, char *argv[]) { - return !(rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048) && ecdsa_keygen()); + return !(rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true) && ecdsa_keygen(true)); } static int cmd_generate_rsa_keys(int argc, char *argv[]) { - return !rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048); + return !rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true); } static int cmd_generate_ecdsa_keys(int argc, char *argv[]) { - return !ecdsa_keygen(); + return !ecdsa_keygen(true); } static int cmd_help(int argc, char *argv[]) { @@ -1286,6 +1484,18 @@ 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(true)) + return 1; + + return info(fd, argv[1]); +} + static const char *conffiles[] = { "tinc.conf", "tinc-up", @@ -1305,10 +1515,10 @@ static int cmd_edit(int argc, char *argv[]) { char *filename = NULL; - if(strncmp(argv[1], "hosts/", 6)) { + if(strncmp(argv[1], "hosts" SLASH, 6)) { for(int i = 0; conffiles[i]; i++) { if(!strcmp(argv[1], conffiles[i])) { - xasprintf(&filename, "%s/%s", confbase, argv[1]); + xasprintf(&filename, "%s" SLASH "%s", confbase, argv[1]); break; } } @@ -1317,7 +1527,7 @@ static int cmd_edit(int argc, char *argv[]) { } if(!filename) { - xasprintf(&filename, "%s/%s", hosts_dir, argv[1]); + xasprintf(&filename, "%s" SLASH "%s", hosts_dir, argv[1]); char *dash = strchr(argv[1], '-'); if(dash) { *dash++ = 0; @@ -1328,27 +1538,151 @@ static int cmd_edit(int argc, char *argv[]) { } } + char *command; #ifndef HAVE_MINGW - char *editor = getenv("VISUAL") ?: getenv("EDITOR") ?: "vi"; + xasprintf(&command, "\"%s\" \"%s\"", getenv("VISUAL") ?: getenv("EDITOR") ?: "vi", filename); #else - char *editor = "edit" + xasprintf(&command, "edit \"%s\"", filename); #endif - - char *command; - xasprintf(&command, "\"%s\" \"%s\"", editor, filename); int result = system(command); if(result) return result; // Silently try notifying a running tincd of changes. - fclose(stderr); - - if(connect_tincd()) + if(connect_tincd(false)) 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)); + fclose(in); + 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[]); @@ -1374,10 +1708,157 @@ static const struct { {"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}, }; +#ifdef HAVE_READLINE +static char *complete_command(const char *text, int state) { + static int i; + + if(!state) + i = 0; + else + i++; + + while(commands[i].command) { + if(!strncasecmp(commands[i].command, text, strlen(text))) + return xstrdup(commands[i].command); + i++; + } + + return NULL; +} + +static char *complete_dump(const char *text, int state) { + const char *matches[] = {"nodes", "edges", "subnets", "connections", "graph", NULL}; + static int i; + + if(!state) + i = 0; + else + i++; + + while(matches[i]) { + if(!strncasecmp(matches[i], text, strlen(text))) + return xstrdup(matches[i]); + i++; + } + + return NULL; +} + +static char **completion (const char *text, int start, int end) { + char **matches = NULL; + + if(!start) + matches = rl_completion_matches(text, complete_command); + else if(!strncasecmp(rl_line_buffer, "dump ", 5)) + matches = rl_completion_matches(text, complete_dump); + + return matches; +} +#endif + +static int cmd_shell(int argc, char *argv[]) { + char *prompt; + xasprintf(&prompt, "%s> ", identname); + int result = 0; + char buf[4096]; + char *line = NULL; + int maxargs = argc + 16; + char **nargv = xmalloc(maxargs * sizeof *nargv); + optind = argc; + + for(int i = 0; i < argc; i++) + nargv[i] = argv[i]; + +#ifdef HAVE_READLINE + rl_readline_name = "tinc"; + rl_attempted_completion_function = completion; + rl_filename_completion_desired = 0; + char *copy = NULL; +#endif + + while(true) { +#ifdef HAVE_READLINE + if(tty) { + free(copy); + free(line); + line = readline(prompt); + if(line) + copy = xstrdup(line); + } else { + line = fgets(buf, sizeof buf, stdin); + } +#else + if(tty) + fputs(stdout, prompt); + + line = fgets(buf, sizeof buf, stdin); +#endif + + if(!line) + break; + + /* Ignore comments */ + + if(*line == '#') + continue; + + /* Split */ + + int nargc = argc; + char *p = line + strspn(line, " \t\n"); + char *next = strtok(p, " \t\n"); + + while(p && *p) { + if(nargc >= maxargs) { + fprintf(stderr, "next %p '%s', p %p '%s'\n", next, next, p, p); + abort(); + maxargs *= 2; + nargv = xrealloc(nargv, maxargs * sizeof *nargv); + } + + nargv[nargc++] = p; + p = next; + next = strtok(NULL, " \t\n"); + } + + if(nargc == argc) + continue; + + bool found = false; + + for(int i = 0; commands[i].command; i++) { + if(!strcasecmp(nargv[argc], commands[i].command)) { + result |= commands[i].function(nargc - argc - 1, nargv + argc + 1); + found = true; + break; + } + } + +#ifdef HAVE_READLINE + if(found) + add_history(copy); +#endif + + if(!found) { + fprintf(stderr, "Unknown command `%s'.\n", nargv[argc]); + result |= 1; + } + } + + if(tty) + printf("\n"); + return result; +} + + int main(int argc, char *argv[]) { program_name = argv[0]; @@ -1396,11 +1877,10 @@ int main(int argc, char *argv[]) { return 0; } - if(optind >= argc) { - fprintf(stderr, "No command given.\n"); - usage(true); - return 1; - } + tty = isatty(0) && isatty(1); + + if(optind >= argc) + return cmd_shell(argc, argv); for(int i = 0; commands[i].command; i++) { if(!strcasecmp(argv[optind], commands[i].command))