]> git.meshlink.io Git - meshlink/blobdiff - src/tincctl.c
Have tincctl act as a shell when no command is given.
[meshlink] / src / tincctl.c
index a3688ecaebc420bd4dde901d1df4f21984cc03ef..3b0c614feec4dca1a90828c7d6ff4fde98b1e96f 100644 (file)
 
 #include <getopt.h>
 
+#ifdef HAVE_READLINE
+#include "readline/readline.h"
+#include "readline/history.h"
+#endif
+
 #include "xalloc.h"
 #include "protocol.h"
 #include "control_common.h"
@@ -35,6 +40,7 @@
 #define mkdir(a, b) mkdir(a)
 #endif
 
+
 /* The name this program was run with. */
 static char *program_name = NULL;
 
@@ -47,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;
@@ -61,6 +68,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;
@@ -110,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"
@@ -187,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);
@@ -230,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;
        }
 
@@ -252,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;
@@ -265,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;
@@ -285,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;
@@ -311,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;
@@ -324,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;
@@ -344,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;
@@ -384,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];
@@ -596,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;
        }
 
@@ -610,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
@@ -631,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;
        }
 
@@ -645,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;
        }
 
@@ -660,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;
        }
 
@@ -707,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) {
@@ -723,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);
@@ -747,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;
@@ -803,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);
@@ -821,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]);
@@ -838,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);
@@ -861,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]);
@@ -884,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]);
@@ -898,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);
@@ -910,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);
@@ -918,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);
@@ -926,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);
@@ -1002,6 +1043,7 @@ static struct {
        {"KeyExpire", VAR_SERVER},
        {"LocalDiscovery", VAR_SERVER},
        {"MACExpire", VAR_SERVER},
+       {"MaxOutputBufferSize", VAR_SERVER},
        {"MaxTimeout", VAR_SERVER},
        {"Mode", VAR_SERVER},
        {"Name", VAR_SERVER},
@@ -1011,26 +1053,32 @@ static struct {
        {"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_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}
 };
 
@@ -1040,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;
@@ -1093,6 +1143,9 @@ static int cmd_config(int argc, char *argv[]) {
                return 1;
        }
 
+       if(action < -1 && *value)
+               action = 0;
+
        /* Some simple checks. */
        bool found = false;
 
@@ -1141,8 +1194,8 @@ static int cmd_config(int argc, char *argv[]) {
                return 1;
        }
 
-       if(!found && action >= 0) {
-               if(force) {
+       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);
@@ -1153,7 +1206,7 @@ static int cmd_config(int argc, char *argv[]) {
        // 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;
 
@@ -1174,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;
@@ -1208,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;
@@ -1229,18 +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;
-               }
-
-               // Add newline if it is missing...
-               if(*buf1 && buf1[strlen(buf1) - 1] != '\n') {
-                       if(fputc('\n', tf) < 0) {
+               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;
+                               }
+                       }
                }
        }
 
@@ -1263,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));
@@ -1289,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;
@@ -1313,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);
@@ -1344,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;
        }
@@ -1354,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;
@@ -1370,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 <your vpn IP address> netmask <netmask of whole VPN>\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[]) {
@@ -1406,7 +1490,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]);
@@ -1431,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;
                        }
                }
@@ -1443,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;
@@ -1454,22 +1538,18 @@ 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;
@@ -1477,7 +1557,7 @@ static int cmd_edit(int argc, char *argv[]) {
 
 static int export(const char *name, FILE *out) {
        char *filename;
-       xasprintf(&filename, "%s/%s", hosts_dir, name);
+       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));
@@ -1493,6 +1573,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;
        }
 
@@ -1556,7 +1637,7 @@ static int cmd_import(int argc, char *argv[]) {
                                fclose(out);
 
                        free(filename);
-                       xasprintf(&filename, "%s/%s", hosts_dir, name);
+                       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);
@@ -1635,6 +1716,149 @@ 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 **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];
 
@@ -1653,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))