X-Git-Url: https://tinc-vpn.org/git/browse?a=blobdiff_plain;f=src%2Ftincctl.c;h=99c04852d61e8049048a107d3b570998224ea3b3;hb=d917c8cb6b69475d568ccbe82389b9f2b3eb5e80;hp=64340c7f3f36f200907d9196554589453d6c7c56;hpb=b0f3a76e9bf8ceeab75c1e6f4dce6763aecddc5e;p=tinc diff --git a/src/tincctl.c b/src/tincctl.c index 64340c7f..99c04852 100644 --- a/src/tincctl.c +++ b/src/tincctl.c @@ -21,6 +21,11 @@ #include +#ifdef HAVE_READLINE +#include "readline/readline.h" +#include "readline/history.h" +#endif + #include "xalloc.h" #include "protocol.h" #include "control_common.h" @@ -35,9 +40,13 @@ #define mkdir(a, b) mkdir(a) #endif + /* The name this program was run with. */ static char *program_name = NULL; +static char **orig_argv; +static int orig_argc; + /* If nonzero, display usage information and exit. */ static bool show_help = false; @@ -45,8 +54,8 @@ static bool show_help = false; 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 *identname = NULL; /* program name for syslog */ +static char *pidfilename = NULL; /* pid file location */ static char *confdir = NULL; static char controlcookie[1024]; char *netname = NULL; @@ -62,6 +71,7 @@ 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; @@ -128,7 +138,7 @@ static void usage(bool status) { " edges - all known connections in the VPN\n" " subnets - all known subnets in the VPN\n" " connections - all meta connections with ourself\n" - " graph - graph of the VPN in dotty format\n" + " [di]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" @@ -153,34 +163,34 @@ static bool parse_options(int argc, char **argv) { while((r = getopt_long(argc, argv, "c:n:Dd::Lo:RU:", long_options, &option_index)) != EOF) { switch (r) { - case 0: /* long option */ + case 0: /* long option */ break; - case 'c': /* config file */ + case 'c': /* config file */ confbase = xstrdup(optarg); break; - case 'n': /* net name given */ + case 'n': /* net name given */ netname = xstrdup(optarg); break; - case 1: /* show help */ + case 1: /* show help */ show_help = true; break; - case 2: /* show version */ + case 2: /* show version */ show_version = true; break; - case 5: /* open control socket here */ + case 5: /* open control socket here */ pidfilename = xstrdup(optarg); break; - case 6: + case 6: /* force */ force = true; break; - case '?': + case '?': /* wrong options */ usage(true); return false; @@ -189,27 +199,109 @@ static bool parse_options(int argc, char **argv) { } } - if(!netname && (netname = getenv("NETNAME"))) - netname = xstrdup(netname); + if(!netname && (netname = getenv("NETNAME"))) + netname = xstrdup(netname); + + /* netname "." is special: a "top-level name" */ - /* netname "." is special: a "top-level name" */ + if(netname && (!*netname || !strcmp(netname, "."))) { + free(netname); + netname = NULL; + } - if(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 void disable_old_keys(const char *filename, const char *what) { + char tmpfile[PATH_MAX] = ""; + char buf[1024]; + bool disabled = false; + bool block = false; + bool error = false; + FILE *r, *w; + + r = fopen(filename, "r"); + if(!r) + return; + + snprintf(tmpfile, sizeof tmpfile, "%s.tmp", filename); + + w = fopen(tmpfile, "w"); + + while(fgets(buf, sizeof buf, r)) { + if(!block && !strncmp(buf, "-----BEGIN ", 11)) { + if((strstr(buf, " EC ") && strstr(what, "ECDSA")) || (strstr(buf, " RSA ") && strstr(what, "RSA"))) { + disabled = true; + block = true; + } + } + + bool ecdsapubkey = !strncasecmp(buf, "ECDSAPublicKey", 14) && strchr(" \t=", buf[14]) && strstr(what, "ECDSA"); + + if(ecdsapubkey) + disabled = true; + + if(w) { + if(block || ecdsapubkey) + fputc('#', w); + if(fputs(buf, w) < 0) { + error = true; + break; + } + } + + if(block && !strncmp(buf, "-----END ", 9)) + block = false; + } + + if(w) + if(fclose(w) < 0) + error = true; + if(ferror(r) || fclose(r) < 0) + error = true; + + if(disabled) { + if(!w || error) { + fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n"); + if(w) + unlink(tmpfile); + return; + } + +#ifdef HAVE_MINGW + // We cannot atomically replace files on Windows. + char bakfile[PATH_MAX] = ""; + snprintf(bakfile, sizeof bakfile, "%s.bak", filename); + if(rename(filename, bakfile) || rename(tmpfile, filename)) { + rename(bakfile, filename); +#else + if(rename(tmpfile, filename)) { +#endif + fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n"); + } else { +#ifdef HAVE_MINGW + unlink(bakfile); +#endif + fprintf(stderr, "Warning: old key(s) found and disabled.\n"); + } + } + + unlink(tmpfile); +} + +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); @@ -240,7 +332,9 @@ static FILE *ask_and_open(const char *filename, const char *what, const char *mo filename = buf2; } - umask(0077); /* Disallow everything for group and other */ + umask(0077); /* Disallow everything for group and other */ + + disable_old_keys(filename, what); /* Open it first to keep the inode busy */ @@ -258,10 +352,10 @@ 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; + char *pubname, *privname; fprintf(stderr, "Generating ECDSA keypair:\n"); @@ -271,44 +365,38 @@ static bool ecdsa_keygen() { } else fprintf(stderr, "Done.\n"); - xasprintf(&filename, "%s" SLASH "ecdsa_key.priv", confbase); - f = ask_and_open(filename, "private ECDSA key", "a"); + xasprintf(&privname, "%s" SLASH "ecdsa_key.priv", confbase); + f = ask_and_open(privname, "private ECDSA key", "a", ask); + free(privname); if(!f) return false; - + #ifdef HAVE_FCHMOD /* Make it unreadable for others. */ fchmod(fileno(f), 0600); #endif - - if(ftell(f)) - fprintf(stderr, "Appending key to existing contents.\nMake sure only one key is stored in the file.\n"); ecdsa_write_pem_private_key(&key, f); fclose(f); - free(filename); if(name) - xasprintf(&filename, "%s" SLASH "hosts" SLASH "%s", confbase, name); + xasprintf(&pubname, "%s" SLASH "hosts" SLASH "%s", confbase, name); else - xasprintf(&filename, "%s" SLASH "ecdsa_key.pub", confbase); + xasprintf(&pubname, "%s" SLASH "ecdsa_key.pub", confbase); - f = ask_and_open(filename, "public ECDSA key", "a"); + f = ask_and_open(pubname, "public ECDSA key", "a", ask); + free(pubname); if(!f) return false; - if(ftell(f)) - fprintf(stderr, "Appending key to existing contents.\nMake sure only one key is stored in the file.\n"); - char *pubkey = ecdsa_get_base64_public_key(&key); fprintf(f, "ECDSAPublicKey = %s\n", pubkey); free(pubkey); fclose(f); - free(filename); return true; } @@ -317,10 +405,10 @@ 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; + char *pubname, *privname; fprintf(stderr, "Generating %d bits keys:\n", bits); @@ -330,42 +418,36 @@ static bool rsa_keygen(int bits) { } else fprintf(stderr, "Done.\n"); - xasprintf(&filename, "%s" SLASH "rsa_key.priv", confbase); - f = ask_and_open(filename, "private RSA key", "a"); + xasprintf(&privname, "%s" SLASH "rsa_key.priv", confbase); + f = ask_and_open(privname, "private RSA key", "a", ask); + free(privname); if(!f) return false; - + #ifdef HAVE_FCHMOD /* Make it unreadable for others. */ fchmod(fileno(f), 0600); #endif - - if(ftell(f)) - fprintf(stderr, "Appending key to existing contents.\nMake sure only one key is stored in the file.\n"); rsa_write_pem_private_key(&key, f); fclose(f); - free(filename); if(name) - xasprintf(&filename, "%s" SLASH "hosts" SLASH "%s", confbase, name); + xasprintf(&pubname, "%s" SLASH "hosts" SLASH "%s", confbase, name); else - xasprintf(&filename, "%s" SLASH "rsa_key.pub", confbase); + xasprintf(&pubname, "%s" SLASH "rsa_key.pub", confbase); - f = ask_and_open(filename, "public RSA key", "a"); + f = ask_and_open(pubname, "public RSA key", "a", ask); + free(pubname); if(!f) return false; - if(ftell(f)) - fprintf(stderr, "Appending key to existing contents.\nMake sure only one key is stored in the file.\n"); - rsa_write_pem_public_key(&key, f); fclose(f); - free(filename); return true; } @@ -497,7 +579,7 @@ bool sendline(int fd, char *format, ...) { blen -= result; } - return true; + return true; } static void pcap(int fd, FILE *out, int snaplen) { @@ -604,13 +686,25 @@ static bool remove_service(void) { } #endif -static bool connect_tincd() { - if(fd >= 0) - return true; +static bool connect_tincd(bool verbose) { + if(fd >= 0) { + fd_set r; + FD_ZERO(&r); + FD_SET(fd, &r); + struct timeval tv = {0, 0}; + if(select(fd + 1, &r, NULL, NULL, &tv)) { + fprintf(stderr, "Previous connection to tincd lost, reconnecting.\n"); + close(fd); + fd = -1; + } else { + 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; } @@ -618,13 +712,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 @@ -639,13 +738,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; } @@ -653,12 +754,16 @@ 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)); + close(fd); + fd = -1; return false; } @@ -668,14 +773,20 @@ 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)); + close(fd); + fd = -1; 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"); + close(fd); + fd = -1; return false; } @@ -684,42 +795,74 @@ static bool connect_tincd() { static int cmd_start(int argc, char *argv[]) { - int i, j; - char *c; + if(connect_tincd(false)) { + if(netname) + fprintf(stderr, "A tincd is already running for net `%s' with pid %d.\n", netname, pid); + else + fprintf(stderr, "A tincd is already running with pid %d.\n", pid); + return 0; + } - argc += optind; - argv -= optind; - char *slash = strrchr(argv[0], '/'); + char *c; + char *slash = strrchr(program_name, '/'); #ifdef HAVE_MINGW - if ((c = strrchr(argv[0], '\\')) > slash) + if ((c = strrchr(program_name, '\\')) > slash) slash = c; #endif if (slash++) - xasprintf(&c, "%.*stincd", (int)(slash - argv[0]), argv[0]); + xasprintf(&c, "%.*stincd", (int)(slash - program_name), program_name); else c = "tincd"; - argv[0] = c; + int nargc = 0; + char **nargv = xmalloc_and_zero((optind + argc) * sizeof *nargv); - for(i = j = 1; argv[i]; ++i) - if (i != optind && strcmp(argv[i], "--") != 0) - argv[j++] = argv[i]; + nargv[nargc++] = c; + for(int i = 1; i < optind; i++) + nargv[nargc++] = orig_argv[i]; + for(int i = 1; i < argc; i++) + nargv[nargc++] = argv[i]; - argv[j] = NULL; - execvp(c, argv); - fprintf(stderr, "Could not start %s: %s\n", c, strerror(errno)); +#ifdef HAVE_MINGW + execvp(c, nargv); + fprintf(stderr, "Error starting %s: %s\n", c, strerror(errno)); return 1; +#else + pid_t pid = fork(); + if(pid == -1) { + fprintf(stderr, "Could not fork: %s\n", strerror(errno)); + free(nargv); + return 1; + } + + if(!pid) + exit(execvp(c, nargv)); + + free(nargv); + + int status = -1; + if(waitpid(pid, &status, 0) != pid || !WIFEXITED(status) || WEXITSTATUS(status)) { + fprintf(stderr, "Error starting %s\n", c); + return 1; + } + + return 0; +#endif } static int cmd_stop(int argc, char *argv[]) { #ifndef HAVE_MINGW - if(!connect_tincd()) { + if(!connect_tincd(true)) { if(pid) { - if(kill(pid, SIGTERM)) + if(kill(pid, SIGTERM)) { + fprintf(stderr, "Could not send TERM signal to process with PID %u: %s\n", pid, strerror(errno)); return 1; + } + fprintf(stderr, "Sent TERM signal to process with PID %u.\n", pid); + waitpid(pid, NULL, 0); return 0; } @@ -731,10 +874,20 @@ static int cmd_stop(int argc, char *argv[]) { fprintf(stderr, "Could not stop tinc daemon.\n"); return 1; } + + // Wait for tincd to close the connection... + fd_set r; + FD_ZERO(&r); + FD_SET(fd, &r); + select(fd + 1, &r, NULL, NULL, NULL); #else if(!remove_service()) return 1; #endif + close(fd); + pid = 0; + fd = -1; + return 0; } @@ -744,7 +897,7 @@ static int cmd_restart(int argc, char *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); @@ -764,10 +917,10 @@ static int cmd_dump(int argc, char *argv[]) { return 1; } - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; - bool do_graph = false; + int do_graph = 0; if(!strcasecmp(argv[1], "nodes")) sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); @@ -780,19 +933,25 @@ static int cmd_dump(int argc, char *argv[]) { else if(!strcasecmp(argv[1], "graph")) { sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES); - do_graph = true; + do_graph = 1; + } else if(!strcasecmp(argv[1], "digraph")) { + sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); + sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES); + do_graph = 2; } else { fprintf(stderr, "Unknown dump type '%s'.\n", argv[1]); usage(true); return 1; } - if(do_graph) + if(do_graph == 1) + printf("graph {\n"); + else if(do_graph == 2) printf("digraph {\n"); while(recvline(fd, line, sizeof line)) { char node1[4096], node2[4096]; - int n = sscanf(line, "%d %d %s to %s", &code, &req, node1, node2); + int n = sscanf(line, "%d %d %s %s", &code, &req, node1, node2); if(n == 2) { if(do_graph && req == REQ_DUMP_NODES) continue; @@ -805,13 +964,71 @@ static int cmd_dump(int argc, char *argv[]) { if(n < 2) break; - if(!do_graph) - printf("%s\n", line + 5); - else { + if(!do_graph) { + char node[4096]; + char from[4096]; + char to[4096]; + char subnet[4096]; + char host[4096]; + char port[4096]; + char via[4096]; + char nexthop[4096]; + int cipher, digest, maclength, compression, distance, socket, weight; + short int pmtu, minmtu, maxmtu; + unsigned int options, status; + long int last_state_change; + + switch(req) { + case REQ_DUMP_NODES: { + int n = sscanf(line, "%*d %*d %s %s port %s %d %d %d %d %x %x %s %s %d %hd %hd %hd %ld", node, host, port, &cipher, &digest, &maclength, &compression, &options, &status, nexthop, via, &distance, &pmtu, &minmtu, &maxmtu, &last_state_change); + if(n != 16) { + fprintf(stderr, "Unable to parse node dump from tincd: %s\n", line); + return 1; + } + printf("%s at %s port %s cipher %d digest %d maclength %d compression %d options %x status %04x nexthop %s via %s distance %d pmtu %hd (min %hd max %hd)\n", + node, host, port, cipher, digest, maclength, compression, options, status, nexthop, via, distance, pmtu, minmtu, maxmtu); + } break; + + case REQ_DUMP_EDGES: { + int n = sscanf(line, "%*d %*d %s %s %s port %s %x %d", from, to, host, port, &options, &weight); + if(n != 6) { + fprintf(stderr, "Unable to parse edge dump from tincd.\n"); + return 1; + } + printf("%s to %s at %s port %s options %x weight %d\n", from, to, host, port, options, weight); + } break; + + case REQ_DUMP_SUBNETS: { + int n = sscanf(line, "%*d %*d %s %s", subnet, node); + if(n != 2) { + fprintf(stderr, "Unable to parse subnet dump from tincd.\n"); + return 1; + } + printf("%s owner %s\n", strip_weight(subnet), node); + } break; + + case REQ_DUMP_CONNECTIONS: { + int n = sscanf(line, "%*d %*d %s %s port %s %x %d %x", node, host, port, &options, &socket, &status); + if(n != 6) { + fprintf(stderr, "Unable to parse connection dump from tincd.\n"); + return 1; + } + printf("%s at %s port %s options %x socket %d status %x\n", node, host, port, options, socket, status); + } break; + + default: + fprintf(stderr, "Unable to parse dump from tincd.\n"); + return 1; + } + } else { if(req == REQ_DUMP_NODES) printf(" %s [label = \"%s\"];\n", node1, node1); - else - printf(" %s -> %s;\n", node1, node2); + else { + if(do_graph == 1 && strcmp(node1, node2) > 0) + printf(" %s -- %s;\n", node1, node2); + else if(do_graph == 2) + printf(" %s -> %s;\n", node1, node2); + } } } @@ -820,7 +1037,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); @@ -838,7 +1055,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]); @@ -855,7 +1072,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); @@ -873,12 +1090,12 @@ static int cmd_connect(int argc, char *argv[]) { return 1; } - if(!check_id(argv[2])) { + if(!check_id(argv[1])) { fprintf(stderr, "Invalid name for node.\n"); return 1; } - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; sendline(fd, "%d %d %s", CONTROL, REQ_CONNECT, argv[1]); @@ -896,12 +1113,12 @@ static int cmd_disconnect(int argc, char *argv[]) { return 1; } - if(!check_id(argv[2])) { + if(!check_id(argv[1])) { fprintf(stderr, "Invalid name for node.\n"); return 1; } - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; sendline(fd, "%d %d %s", CONTROL, REQ_DISCONNECT, argv[1]); @@ -915,7 +1132,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); @@ -927,7 +1144,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); @@ -935,7 +1152,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); @@ -943,7 +1160,7 @@ static int cmd_log(int argc, char *argv[]) { } static int cmd_pid(int argc, char *argv[]) { - if(!connect_tincd() && !pid) + if(!connect_tincd(true) && !pid) return 1; printf("%d\n", pid); @@ -1012,13 +1229,14 @@ static struct { {"ECDSAPrivateKeyFile", VAR_SERVER}, {"ExperimentalProtocol", VAR_SERVER}, {"Forwarding", VAR_SERVER}, - {"GraphDumpFile", VAR_SERVER}, + {"GraphDumpFile", VAR_SERVER | VAR_OBSOLETE}, {"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}, @@ -1028,26 +1246,34 @@ static struct { {"PrivateKey", VAR_SERVER | VAR_OBSOLETE}, {"PrivateKeyFile", VAR_SERVER}, {"ProcessPriority", VAR_SERVER}, + {"Proxy", VAR_SERVER}, {"ReplayWindow", VAR_SERVER}, + {"ScriptsExtension", VAR_SERVER}, + {"ScriptsInterpreter", 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_SERVER | VAR_HOST | VAR_OBSOLETE}, + {"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} }; @@ -1086,14 +1312,14 @@ static int cmd_config(int argc, char *argv[]) { 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'; + 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; @@ -1204,6 +1430,7 @@ static int cmd_config(int argc, char *argv[]) { tf = fopen(tmpfile, "w"); if(!tf) { fprintf(stderr, "Could not open temporary file %s: %s\n", tmpfile, strerror(errno)); + fclose(f); return 1; } } @@ -1328,15 +1555,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; @@ -1352,7 +1580,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); @@ -1407,8 +1635,7 @@ static int cmd_init(int argc, char *argv[]) { fprintf(f, "Name = %s\n", name); fclose(f); - fclose(stdin); - if(!rsa_keygen(2048) || !ecdsa_keygen()) + if(!rsa_keygen(2048, false) || !ecdsa_keygen(false)) return 1; #ifndef HAVE_MINGW @@ -1431,15 +1658,15 @@ static int cmd_init(int argc, char *argv[]) { } 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[]) { @@ -1458,7 +1685,7 @@ static int cmd_info(int argc, char *argv[]) { return 1; } - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; return info(fd, argv[1]); @@ -1517,9 +1744,7 @@ static int cmd_edit(int argc, char *argv[]) { 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; @@ -1543,6 +1768,7 @@ static int export(const char *name, FILE *out) { if(ferror(in)) { fprintf(stderr, "Error while reading configuration file %s: %s\n", filename, strerror(errno)); + fclose(in); return 1; } @@ -1685,12 +1911,249 @@ static const struct { {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 *complete_config(const char *text, int state) { + const char *sub[] = {"get", "set", "add", "del"}; + static int i; + if(!state) { + i = 0; + if(!strchr(rl_line_buffer + 7, ' ')) + i = -4; + else { + bool found = false; + for(int i = 0; i < 4; i++) { + if(!strncasecmp(rl_line_buffer + 7, sub[i], strlen(sub[i])) && rl_line_buffer[7 + strlen(sub[i])] == ' ') { + found = true; + break; + } + } + if(!found) + return NULL; + } + } else { + i++; + } + + while(i < 0 || variables[i].name) { + if(i < 0 && !strncasecmp(sub[i + 4], text, strlen(text))) + return xstrdup(sub[i + 4]); + if(i >= 0) { + char *dot = strchr(text, '.'); + if(dot) { + if((variables[i].type & VAR_HOST) && !strncasecmp(variables[i].name, dot + 1, strlen(dot + 1))) { + char *match; + xasprintf(&match, "%.*s.%s", dot - text, text, variables[i].name); + return match; + } + } else { + if(!strncasecmp(variables[i].name, text, strlen(text))) + return xstrdup(variables[i].name); + } + } + i++; + } + + return NULL; +} + +static char *complete_info(const char *text, int state) { + static int i; + if(!state) { + i = 0; + if(!connect_tincd(false)) + return NULL; + // Check the list of nodes + sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); + sendline(fd, "%d %d", CONTROL, REQ_DUMP_SUBNETS); + } + + while(recvline(fd, line, sizeof line)) { + char item[4096]; + int n = sscanf(line, "%d %d %s", &code, &req, item); + if(n == 2) { + i++; + if(i >= 2) + break; + else + continue; + } + + if(n != 3) { + fprintf(stderr, "Unable to parse dump from tincd, n = %d, i = %d.\n", n, i); + break; + } + + if(!strncmp(item, text, strlen(text))) + return xstrdup(strip_weight(item)); + } + + return NULL; +} + +static char *complete_nothing(const char *text, int state) { + 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); + else if(!strncasecmp(rl_line_buffer, "config ", 7)) + matches = rl_completion_matches(text, complete_config); + else if(!strncasecmp(rl_line_buffer, "info ", 5)) + matches = rl_completion_matches(text, complete_info); + + 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); + + for(int i = 0; i < argc; i++) + nargv[i] = argv[i]; + +#ifdef HAVE_READLINE + rl_readline_name = "tinc"; + rl_completion_entry_function = complete_nothing; + 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); + rl_basic_word_break_characters = "\t\n "; + line = readline(prompt); + if(line) + copy = xstrdup(line); + } else { + line = fgets(buf, sizeof buf, stdin); + } +#else + if(tty) + fputs(prompt, stdout); + + 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; + + if(!strcasecmp(nargv[argc], "exit") || !strcasecmp(nargv[argc], "quit")) + return result; + + 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(tty && found) + add_history(copy); +#endif + + if(!found) { + fprintf(stderr, "Unknown command `%s'.\n", nargv[argc]); + result |= 1; + } + } + + free(nargv); + + if(tty) + printf("\n"); + return result; +} + + int main(int argc, char *argv[]) { program_name = argv[0]; + orig_argv = argv; + orig_argc = argc; if(!parse_options(argc, argv)) return 1; - + make_names(); if(show_version) { @@ -1703,11 +2166,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))