Fix a few memory leaks at exit time.
[tinc] / src / tincctl.c
index 12e5ead..664b2ea 100644 (file)
@@ -1,6 +1,6 @@
 /*
     tincctl.c -- Controlling a running tincd
-    Copyright (C) 2007-2018 Guus Sliepen <guus@tinc-vpn.org>
+    Copyright (C) 2007-2021 Guus Sliepen <guus@tinc-vpn.org>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -40,6 +40,7 @@
 #include "tincctl.h"
 #include "top.h"
 #include "version.h"
+#include "subnet.h"
 
 #ifndef MSG_NOSIGNAL
 #define MSG_NOSIGNAL 0
@@ -92,6 +93,17 @@ static struct option const long_options[] = {
 static void version(void) {
        printf("%s version %s (built %s %s, protocol %d.%d)\n", PACKAGE,
               BUILD_VERSION, BUILD_DATE, BUILD_TIME, PROT_MAJOR, PROT_MINOR);
+       printf("Features:"
+#ifdef HAVE_READLINE
+              " readline"
+#endif
+#ifdef HAVE_CURSES
+              " curses"
+#endif
+#ifndef DISABLE_LEGACY
+              " legacy_protocol"
+#endif
+              "\n\n");
        printf("Copyright (C) 1998-2018 Ivo Timmermans, Guus Sliepen and others.\n"
               "See the AUTHORS file for a complete list.\n\n"
               "tinc comes with ABSOLUTELY NO WARRANTY.  This is free software,\n"
@@ -125,12 +137,12 @@ static void usage(bool status) {
                       "  reload                     Partially reload configuration of running tincd.\n"
                       "  pid                        Show PID of currently running tincd.\n"
 #ifdef DISABLE_LEGACY
-                      "  generate-keys              Generate a new Ed25519 public/private keypair.\n"
+                      "  generate-keys              Generate a new Ed25519 public/private key pair.\n"
 #else
-                      "  generate-keys [bits]       Generate new RSA and Ed25519 public/private keypairs.\n"
-                      "  generate-rsa-keys [bits]   Generate a new RSA public/private keypair.\n"
+                      "  generate-keys [bits]       Generate new RSA and Ed25519 public/private key pairs.\n"
+                      "  generate-rsa-keys [bits]   Generate a new RSA public/private key pair.\n"
 #endif
-                      "  generate-ed25519-keys      Generate a new Ed25519 public/private keypair.\n"
+                      "  generate-ed25519-keys      Generate a new Ed25519 public/private key pair.\n"
                       "  dump                       Dump a list of one of the following things:\n"
                       "    [reachable] nodes        - all known nodes in the VPN\n"
                       "    edges                    - all known connections in the VPN\n"
@@ -237,7 +249,7 @@ static bool parse_options(int argc, char **argv) {
 FILE *fopenmask(const char *filename, const char *mode, mode_t perms) {
        mode_t mask = umask(0);
        perms &= ~mask;
-       umask(~perms);
+       umask(~perms & 0777);
        FILE *f = fopen(filename, mode);
 
        if(!f) {
@@ -262,19 +274,21 @@ static void disable_old_keys(const char *filename, const char *what) {
        bool disabled = false;
        bool block = false;
        bool error = false;
-       FILE *r, *w;
 
-       r = fopen(filename, "r");
+       FILE *r = fopen(filename, "r");
+       FILE *w = NULL;
 
        if(!r) {
                return;
        }
 
-       snprintf(tmpfile, sizeof(tmpfile), "%s.tmp", filename);
+       int result = snprintf(tmpfile, sizeof(tmpfile), "%s.tmp", filename);
 
-       struct stat st = {.st_mode = 0600};
-       fstat(fileno(r), &st);
-       w = fopenmask(tmpfile, "w", st.st_mode);
+       if(result < sizeof(tmpfile)) {
+               struct stat st = {.st_mode = 0600};
+               fstat(fileno(r), &st);
+               w = fopenmask(tmpfile, "w", st.st_mode);
+       }
 
        while(fgets(buf, sizeof(buf), r)) {
                if(!block && !strncmp(buf, "-----BEGIN ", 11)) {
@@ -355,6 +369,8 @@ static FILE *ask_and_open(const char *filename, const char *what, const char *mo
        char buf[PATH_MAX];
        char buf2[PATH_MAX];
 
+ask_filename:
+
        /* Check stdin and stdout */
        if(ask && tty) {
                /* Ask for a file and/or directory name. */
@@ -385,7 +401,17 @@ static FILE *ask_and_open(const char *filename, const char *what, const char *mo
 #endif
                /* The directory is a relative path or a filename. */
                getcwd(directory, sizeof(directory));
-               snprintf(buf2, sizeof(buf2), "%s" SLASH "%s", directory, filename);
+
+               if((size_t)snprintf(buf2, sizeof(buf2), "%s" SLASH "%s", directory, filename) >= sizeof(buf2)) {
+                       fprintf(stderr, "Filename too long: %s" SLASH "%s\n", directory, filename);
+
+                       if(ask && tty) {
+                               goto ask_filename;
+                       } else {
+                               return NULL;
+                       }
+               }
+
                filename = buf2;
        }
 
@@ -404,7 +430,7 @@ static FILE *ask_and_open(const char *filename, const char *what, const char *mo
 }
 
 /*
-  Generate a public/private Ed25519 keypair, and ask for a file to store
+  Generate a public/private Ed25519 key pair, and ask for a file to store
   them in.
 */
 static bool ed25519_keygen(bool ask) {
@@ -412,7 +438,7 @@ static bool ed25519_keygen(bool ask) {
        FILE *f;
        char fname[PATH_MAX];
 
-       fprintf(stderr, "Generating Ed25519 keypair:\n");
+       fprintf(stderr, "Generating Ed25519 key pair:\n");
 
        if(!(key = ecdsa_generate())) {
                fprintf(stderr, "Error during key generation!\n");
@@ -468,7 +494,7 @@ error:
 
 #ifndef DISABLE_LEGACY
 /*
-  Generate a public/private RSA keypair, and ask for a file to store
+  Generate a public/private RSA key pair, and ask for a file to store
   them in.
 */
 static bool rsa_keygen(int bits, bool ask) {
@@ -565,7 +591,7 @@ bool recvline(int fd, char *line, size_t len) {
                blen += result;
        }
 
-       if(newline - buffer >= len) {
+       if((size_t)(newline - buffer) >= len) {
                return false;
        }
 
@@ -579,11 +605,7 @@ bool recvline(int fd, char *line, size_t len) {
        return true;
 }
 
-bool recvdata(int fd, char *data, size_t len) {
-       if(len == -1) {
-               len = blen;
-       }
-
+static bool recvdata(int fd, char *data, size_t len) {
        while(blen < len) {
                int result = recv(fd, buffer + blen, sizeof(buffer) - blen, 0);
 
@@ -606,7 +628,7 @@ bool recvdata(int fd, char *data, size_t len) {
 bool sendline(int fd, char *format, ...) {
        static char buffer[4096];
        char *p = buffer;
-       int blen = 0;
+       int blen;
        va_list ap;
 
        va_start(ap, format);
@@ -614,7 +636,7 @@ bool sendline(int fd, char *format, ...) {
        buffer[sizeof(buffer) - 1] = 0;
        va_end(ap);
 
-       if(blen < 1 || blen >= sizeof(buffer)) {
+       if(blen < 1 || (size_t)blen >= sizeof(buffer)) {
                return false;
        }
 
@@ -637,7 +659,7 @@ bool sendline(int fd, char *format, ...) {
        return true;
 }
 
-static void pcap(int fd, FILE *out, int snaplen) {
+static void pcap(int fd, FILE *out, uint32_t snaplen) {
        sendline(fd, "%d %d %d", CONTROL, REQ_PCAP, snaplen);
        char data[9018];
 
@@ -653,7 +675,7 @@ static void pcap(int fd, FILE *out, int snaplen) {
                0xa1b2c3d4,
                2, 4,
                0, 0,
-               snaplen ? : sizeof(data),
+               snaplen ? snaplen : sizeof(data),
                1,
        };
 
@@ -676,7 +698,7 @@ static void pcap(int fd, FILE *out, int snaplen) {
                int n = sscanf(line, "%d %d %d", &code, &req, &len);
                gettimeofday(&tv, NULL);
 
-               if(n != 3 || code != CONTROL || req != REQ_PCAP || len < 0 || len > sizeof(data)) {
+               if(n != 3 || code != CONTROL || req != REQ_PCAP || len < 0 || (size_t)len > sizeof(data)) {
                        break;
                }
 
@@ -703,7 +725,7 @@ static void logcontrol(int fd, FILE *out, int level) {
                int code, req, len;
                int n = sscanf(line, "%d %d %d", &code, &req, &len);
 
-               if(n != 3 || code != CONTROL || req != REQ_LOG || len < 0 || len > sizeof(data)) {
+               if(n != 3 || code != CONTROL || req != REQ_LOG || len < 0 || (size_t)len > sizeof(data)) {
                        break;
                }
 
@@ -717,6 +739,24 @@ static void logcontrol(int fd, FILE *out, int level) {
        }
 }
 
+static bool stop_tincd(void) {
+       if(!connect_tincd(true)) {
+               return false;
+       }
+
+       sendline(fd, "%d %d", CONTROL, REQ_STOP);
+
+       while(recvline(fd, line, sizeof(line))) {
+               // wait for tincd to close the connection...
+       }
+
+       close(fd);
+       pid = 0;
+       fd = -1;
+
+       return true;
+}
+
 #ifdef HAVE_MINGW
 static bool remove_service(void) {
        SC_HANDLE manager = NULL;
@@ -734,7 +774,12 @@ static bool remove_service(void) {
        service = OpenService(manager, identname, SERVICE_ALL_ACCESS);
 
        if(!service) {
-               fprintf(stderr, "Could not open %s service: %s\n", identname, winerror(GetLastError()));
+               if(GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) {
+                       success = stop_tincd();
+               } else {
+                       fprintf(stderr, "Could not open %s service: %s\n", identname, winerror(GetLastError()));
+               }
+
                goto exit;
        }
 
@@ -819,9 +864,14 @@ bool connect_tincd(bool verbose) {
                return false;
        }
 
-       struct sockaddr_un sa;
+       struct sockaddr_un sa = {
+               .sun_family = AF_UNIX,
+       };
 
-       sa.sun_family = AF_UNIX;
+       if(strlen(unixsocketname) >= sizeof(sa.sun_path)) {
+               fprintf(stderr, "UNIX socket filename %s is too long!", unixsocketname);
+               return false;
+       }
 
        strncpy(sa.sun_path, unixsocketname, sizeof(sa.sun_path));
 
@@ -873,7 +923,6 @@ bool connect_tincd(bool verbose) {
                return false;
        }
 
-#ifdef HAVE_MINGW
        unsigned long arg = 0;
 
        if(ioctlsocket(fd, FIONBIO, &arg) != 0) {
@@ -882,8 +931,6 @@ bool connect_tincd(bool verbose) {
                }
        }
 
-#endif
-
        if(connect(fd, res->ai_addr, res->ai_addrlen) < 0) {
                if(verbose) {
                        fprintf(stderr, "Cannot connect to %s port %s: %s\n", host, port, sockstrerror(sockerrno));
@@ -902,6 +949,8 @@ bool connect_tincd(bool verbose) {
        setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&one, sizeof(one));
 #endif
 
+       sendline(fd, "%d ^%s %d", ID, controlcookie, TINC_CTL_VERSION_CURRENT);
+
        char data[4096];
        int version;
 
@@ -915,8 +964,6 @@ bool connect_tincd(bool verbose) {
                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) {
                if(verbose) {
                        fprintf(stderr, "Could not fully establish control socket connection\n");
@@ -1066,14 +1113,18 @@ static int cmd_start(int argc, char *argv[]) {
 }
 
 static int cmd_stop(int argc, char *argv[]) {
+       (void)argv;
+
        if(argc > 1) {
                fprintf(stderr, "Too many arguments!\n");
                return 1;
        }
 
-#ifndef HAVE_MINGW
+#ifdef HAVE_MINGW
+       return remove_service() ? EXIT_SUCCESS : EXIT_FAILURE;
+#else
 
-       if(!connect_tincd(true)) {
+       if(!stop_tincd()) {
                if(pid) {
                        if(kill(pid, SIGTERM)) {
                                fprintf(stderr, "Could not send TERM signal to process with PID %d: %s\n", pid, strerror(errno));
@@ -1088,24 +1139,8 @@ static int cmd_stop(int argc, char *argv[]) {
                return 1;
        }
 
-       sendline(fd, "%d %d", CONTROL, REQ_STOP);
-
-       while(recvline(fd, line, sizeof(line))) {
-               // Wait for tincd to close the connection...
-       }
-
-#else
-
-       if(!remove_service()) {
-               return 1;
-       }
-
-#endif
-       close(fd);
-       pid = 0;
-       fd = -1;
-
        return 0;
+#endif
 }
 
 static int cmd_restart(int argc, char *argv[]) {
@@ -1114,6 +1149,8 @@ static int cmd_restart(int argc, char *argv[]) {
 }
 
 static int cmd_reload(int argc, char *argv[]) {
+       (void)argv;
+
        if(argc > 1) {
                fprintf(stderr, "Too many arguments!\n");
                return 1;
@@ -1161,7 +1198,12 @@ static int dump_invitations(void) {
                }
 
                char fname[PATH_MAX];
-               snprintf(fname, sizeof(fname), "%s" SLASH "%s", dname, ent->d_name);
+
+               if((size_t)snprintf(fname, sizeof(fname), "%s" SLASH "%s", dname, ent->d_name) >= sizeof(fname)) {
+                       fprintf(stderr, "Filename too long: %s" SLASH "%s\n", dname, ent->d_name);
+                       continue;
+               }
+
                FILE *f = fopen(fname, "r");
 
                if(!f) {
@@ -1327,7 +1369,7 @@ static int cmd_dump(int argc, char *argv[]) {
                                        color = "green";
                                }
 
-                               printf(" %s [label = \"%s\", color = \"%s\"%s];\n", node, node, color, strcmp(host, "MYSELF") ? "" : ", style = \"filled\"");
+                               printf(" \"%s\" [label = \"%s\", color = \"%s\"%s];\n", node, node, color, strcmp(host, "MYSELF") ? "" : ", style = \"filled\"");
                        } else {
                                if(only_reachable && !status.reachable) {
                                        continue;
@@ -1357,9 +1399,9 @@ static int cmd_dump(int argc, char *argv[]) {
                                float w = 1 + 65536.0 / weight;
 
                                if(do_graph == 1 && strcmp(node1, node2) > 0) {
-                                       printf(" %s -- %s [w = %f, weight = %f];\n", node1, node2, w, w);
+                                       printf(" \"%s\" -- \"%s\" [w = %f, weight = %f];\n", node1, node2, w, w);
                                } else if(do_graph == 2) {
-                                       printf(" %s -> %s [w = %f, weight = %f];\n", node1, node2, w, w);
+                                       printf(" \"%s\" -> \"%s\" [w = %f, weight = %f];\n", node1, node2, w, w);
                                }
                        } else {
                                printf("%s to %s at %s port %s local %s port %s options %x weight %d\n", from, to, host, port, local_host, local_port, options, weight);
@@ -1402,6 +1444,8 @@ static int cmd_dump(int argc, char *argv[]) {
 }
 
 static int cmd_purge(int argc, char *argv[]) {
+       (void)argv;
+
        if(argc > 1) {
                fprintf(stderr, "Too many arguments!\n");
                return 1;
@@ -1446,6 +1490,8 @@ static int cmd_debug(int argc, char *argv[]) {
 }
 
 static int cmd_retry(int argc, char *argv[]) {
+       (void)argv;
+
        if(argc > 1) {
                fprintf(stderr, "Too many arguments!\n");
                return 1;
@@ -1516,6 +1562,8 @@ static int cmd_disconnect(int argc, char *argv[]) {
 }
 
 static int cmd_top(int argc, char *argv[]) {
+       (void)argv;
+
        if(argc > 1) {
                fprintf(stderr, "Too many arguments!\n");
                return 1;
@@ -1551,6 +1599,8 @@ static int cmd_pcap(int argc, char *argv[]) {
 
 #ifdef SIGINT
 static void sigint_handler(int sig) {
+       (void)sig;
+
        fprintf(stderr, "\n");
        shutdown(fd, SHUT_RDWR);
 }
@@ -1582,6 +1632,8 @@ static int cmd_log(int argc, char *argv[]) {
 }
 
 static int cmd_pid(int argc, char *argv[]) {
+       (void)argv;
+
        if(argc > 1) {
                fprintf(stderr, "Too many arguments!\n");
                return 1;
@@ -1688,18 +1740,18 @@ ecdsa_t *get_pubkey(FILE *f) {
 
 const var_t variables[] = {
        /* Server configuration */
-       {"AddressFamily", VAR_SERVER},
+       {"AddressFamily", VAR_SERVER | VAR_SAFE},
        {"AutoConnect", VAR_SERVER | VAR_SAFE},
        {"BindToAddress", VAR_SERVER | VAR_MULTIPLE},
        {"BindToInterface", VAR_SERVER},
        {"Broadcast", VAR_SERVER | VAR_SAFE},
        {"BroadcastSubnet", VAR_SERVER | VAR_MULTIPLE | VAR_SAFE},
        {"ConnectTo", VAR_SERVER | VAR_MULTIPLE | VAR_SAFE},
-       {"DecrementTTL", VAR_SERVER},
+       {"DecrementTTL", VAR_SERVER | VAR_SAFE},
        {"Device", VAR_SERVER},
        {"DeviceStandby", VAR_SERVER},
        {"DeviceType", VAR_SERVER},
-       {"DirectOnly", VAR_SERVER},
+       {"DirectOnly", VAR_SERVER | VAR_SAFE},
        {"Ed25519PrivateKeyFile", VAR_SERVER},
        {"ExperimentalProtocol", VAR_SERVER},
        {"Forwarding", VAR_SERVER},
@@ -1709,34 +1761,34 @@ const var_t variables[] = {
        {"IffOneQueue", VAR_SERVER},
        {"Interface", VAR_SERVER},
        {"InvitationExpire", VAR_SERVER},
-       {"KeyExpire", VAR_SERVER},
+       {"KeyExpire", VAR_SERVER | VAR_SAFE},
        {"ListenAddress", VAR_SERVER | VAR_MULTIPLE},
-       {"LocalDiscovery", VAR_SERVER},
+       {"LocalDiscovery", VAR_SERVER | VAR_SAFE},
        {"LogLevel", VAR_SERVER},
-       {"MACExpire", VAR_SERVER},
-       {"MaxConnectionBurst", VAR_SERVER},
-       {"MaxOutputBufferSize", VAR_SERVER},
-       {"MaxTimeout", VAR_SERVER},
+       {"MACExpire", VAR_SERVER | VAR_SAFE},
+       {"MaxConnectionBurst", VAR_SERVER | VAR_SAFE},
+       {"MaxOutputBufferSize", VAR_SERVER | VAR_SAFE},
+       {"MaxTimeout", VAR_SERVER | VAR_SAFE},
        {"Mode", VAR_SERVER | VAR_SAFE},
        {"Name", VAR_SERVER},
-       {"PingInterval", VAR_SERVER},
-       {"PingTimeout", VAR_SERVER},
+       {"PingInterval", VAR_SERVER | VAR_SAFE},
+       {"PingTimeout", VAR_SERVER | VAR_SAFE},
        {"PriorityInheritance", VAR_SERVER},
        {"PrivateKey", VAR_SERVER | VAR_OBSOLETE},
        {"PrivateKeyFile", VAR_SERVER},
        {"ProcessPriority", VAR_SERVER},
        {"Proxy", VAR_SERVER},
-       {"ReplayWindow", VAR_SERVER},
+       {"ReplayWindow", VAR_SERVER | VAR_SAFE},
        {"ScriptsExtension", VAR_SERVER},
        {"ScriptsInterpreter", VAR_SERVER},
-       {"StrictSubnets", VAR_SERVER},
-       {"TunnelServer", VAR_SERVER},
-       {"UDPDiscovery", VAR_SERVER},
-       {"UDPDiscoveryKeepaliveInterval", VAR_SERVER},
-       {"UDPDiscoveryInterval", VAR_SERVER},
-       {"UDPDiscoveryTimeout", VAR_SERVER},
-       {"MTUInfoInterval", VAR_SERVER},
-       {"UDPInfoInterval", VAR_SERVER},
+       {"StrictSubnets", VAR_SERVER | VAR_SAFE},
+       {"TunnelServer", VAR_SERVER | VAR_SAFE},
+       {"UDPDiscovery", VAR_SERVER | VAR_SAFE},
+       {"UDPDiscoveryKeepaliveInterval", VAR_SERVER | VAR_SAFE},
+       {"UDPDiscoveryInterval", VAR_SERVER | VAR_SAFE},
+       {"UDPDiscoveryTimeout", VAR_SERVER | VAR_SAFE},
+       {"MTUInfoInterval", VAR_SERVER | VAR_SAFE},
+       {"UDPInfoInterval", VAR_SERVER | VAR_SAFE},
        {"UDPRcvBuf", VAR_SERVER},
        {"UDPSndBuf", VAR_SERVER},
        {"UPnP", VAR_SERVER},
@@ -1747,12 +1799,12 @@ const var_t variables[] = {
        /* Host configuration */
        {"Address", VAR_HOST | VAR_MULTIPLE},
        {"Cipher", VAR_SERVER | VAR_HOST},
-       {"ClampMSS", VAR_SERVER | VAR_HOST},
-       {"Compression", VAR_SERVER | VAR_HOST},
+       {"ClampMSS", VAR_SERVER | VAR_HOST | VAR_SAFE},
+       {"Compression", VAR_SERVER | VAR_HOST | VAR_SAFE},
        {"Digest", VAR_SERVER | VAR_HOST},
        {"Ed25519PublicKey", VAR_HOST},
        {"Ed25519PublicKeyFile", VAR_SERVER | VAR_HOST},
-       {"IndirectData", VAR_SERVER | VAR_HOST},
+       {"IndirectData", VAR_SERVER | VAR_HOST | VAR_SAFE},
        {"MACLength", VAR_SERVER | VAR_HOST},
        {"PMTU", VAR_SERVER | VAR_HOST},
        {"PMTUDiscovery", VAR_SERVER | VAR_HOST},
@@ -1760,7 +1812,7 @@ const var_t variables[] = {
        {"PublicKey", VAR_HOST | VAR_OBSOLETE},
        {"PublicKeyFile", VAR_SERVER | VAR_HOST | VAR_OBSOLETE},
        {"Subnet", VAR_HOST | VAR_MULTIPLE | VAR_SAFE},
-       {"TCPOnly", VAR_SERVER | VAR_HOST},
+       {"TCPOnly", VAR_SERVER | VAR_HOST | VAR_SAFE},
        {"Weight", VAR_HOST | VAR_SAFE},
        {NULL, 0}
 };
@@ -1851,6 +1903,19 @@ static int cmd_config(int argc, char *argv[]) {
                found = true;
                variable = (char *)variables[i].name;
 
+               if(!strcasecmp(variable, "Subnet")) {
+                       subnet_t s = {0};
+
+                       if(!str2net(&s, value)) {
+                               fprintf(stderr, "Malformed subnet definition %s\n", value);
+                       }
+
+                       if(!subnetcheck(s)) {
+                               fprintf(stderr, "Network address and prefix length do not match: %s\n", value);
+                               return 1;
+                       }
+               }
+
                /* Discourage use of obsolete variables. */
 
                if(variables[i].type & VAR_OBSOLETE && action >= 0) {
@@ -1930,7 +1995,11 @@ static int cmd_config(int argc, char *argv[]) {
        FILE *tf = NULL;
 
        if(action >= -1) {
-               snprintf(tmpfile, sizeof(tmpfile), "%s.config.tmp", filename);
+               if((size_t)snprintf(tmpfile, sizeof(tmpfile), "%s.config.tmp", filename) >= sizeof(tmpfile)) {
+                       fprintf(stderr, "Filename too long: %s.config.tmp\n", filename);
+                       return 1;
+               }
+
                tf = fopen(tmpfile, "w");
 
                if(!tf) {
@@ -2128,7 +2197,7 @@ static bool try_bind(int port) {
        return success;
 }
 
-int check_port(char *name) {
+int check_port(const char *name) {
        if(try_bind(655)) {
                return 655;
        }
@@ -2268,6 +2337,7 @@ static int cmd_init(int argc, char *argv[]) {
 
 static int cmd_generate_keys(int argc, char *argv[]) {
 #ifdef DISABLE_LEGACY
+       (void)argv;
 
        if(argc > 1) {
 #else
@@ -2313,6 +2383,8 @@ static int cmd_generate_rsa_keys(int argc, char *argv[]) {
 #endif
 
 static int cmd_generate_ed25519_keys(int argc, char *argv[]) {
+       (void)argv;
+
        if(argc > 1) {
                fprintf(stderr, "Too many arguments!\n");
                return 1;
@@ -2326,11 +2398,16 @@ static int cmd_generate_ed25519_keys(int argc, char *argv[]) {
 }
 
 static int cmd_help(int argc, char *argv[]) {
+       (void)argc;
+       (void)argv;
+
        usage(false);
        return 0;
 }
 
 static int cmd_version(int argc, char *argv[]) {
+       (void)argv;
+
        if(argc > 1) {
                fprintf(stderr, "Too many arguments!\n");
                return 1;
@@ -2399,7 +2476,17 @@ static int cmd_edit(int argc, char *argv[]) {
 
        char *command;
 #ifndef HAVE_MINGW
-       xasprintf(&command, "\"%s\" \"%s\"", getenv("VISUAL") ? : getenv("EDITOR") ? : "vi", filename);
+       const char *editor = getenv("VISUAL");
+
+       if(!editor) {
+               editor = getenv("EDITOR");
+       }
+
+       if(!editor) {
+               editor = "vi";
+       }
+
+       xasprintf(&command, "\"%s\" \"%s\"", editor, filename);
 #else
        xasprintf(&command, "edit \"%s\"", filename);
 #endif
@@ -2448,6 +2535,8 @@ static int export(const char *name, FILE *out) {
 }
 
 static int cmd_export(int argc, char *argv[]) {
+       (void)argv;
+
        if(argc > 1) {
                fprintf(stderr, "Too many arguments!\n");
                return 1;
@@ -2470,6 +2559,8 @@ static int cmd_export(int argc, char *argv[]) {
 }
 
 static int cmd_export_all(int argc, char *argv[]) {
+       (void)argv;
+
        if(argc > 1) {
                fprintf(stderr, "Too many arguments!\n");
                return 1;
@@ -2510,6 +2601,8 @@ static int cmd_export_all(int argc, char *argv[]) {
 }
 
 static int cmd_import(int argc, char *argv[]) {
+       (void)argv;
+
        if(argc > 1) {
                fprintf(stderr, "Too many arguments!\n");
                return 1;
@@ -2537,7 +2630,10 @@ static int cmd_import(int argc, char *argv[]) {
                                fclose(out);
                        }
 
-                       snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, name);
+                       if((size_t)snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, name) >= sizeof(filename)) {
+                               fprintf(stderr, "Filename too long: %s" SLASH "%s\n", hosts_dir, name);
+                               return 1;
+                       }
 
                        if(!force && !access(filename, F_OK)) {
                                fprintf(stderr, "Host configuration file %s already exists, skipping.\n", filename);
@@ -2586,11 +2682,11 @@ static int cmd_import(int argc, char *argv[]) {
 }
 
 static int cmd_exchange(int argc, char *argv[]) {
-       return cmd_export(argc, argv) ? : cmd_import(argc, argv);
+       return cmd_export(argc, argv) ? : cmd_import(argc, argv);
 }
 
 static int cmd_exchange_all(int argc, char *argv[]) {
-       return cmd_export_all(argc, argv) ? : cmd_import(argc, argv);
+       return cmd_export_all(argc, argv) ? : cmd_import(argc, argv);
 }
 
 static int switch_network(char *name) {
@@ -2668,6 +2764,8 @@ static int cmd_network(int argc, char *argv[]) {
 }
 
 static int cmd_fsck(int argc, char *argv[]) {
+       (void)argv;
+
        if(argc > 1) {
                fprintf(stderr, "Too many arguments!\n");
                return 1;
@@ -2678,11 +2776,11 @@ static int cmd_fsck(int argc, char *argv[]) {
 
 static void *readfile(FILE *in, size_t *len) {
        size_t count = 0;
-       size_t alloced = 4096;
-       char *buf = xmalloc(alloced);
+       size_t bufsize = 4096;
+       char *buf = xmalloc(bufsize);
 
        while(!feof(in)) {
-               size_t read = fread(buf + count, 1, alloced - count, in);
+               size_t read = fread(buf + count, 1, bufsize - count, in);
 
                if(!read) {
                        break;
@@ -2690,9 +2788,9 @@ static void *readfile(FILE *in, size_t *len) {
 
                count += read;
 
-               if(count >= alloced) {
-                       alloced *= 2;
-                       buf = xrealloc(buf, alloced);
+               if(count >= bufsize) {
+                       bufsize *= 2;
+                       buf = xrealloc(buf, bufsize);
                }
        }
 
@@ -2936,48 +3034,48 @@ static const struct {
        int (*function)(int argc, char *argv[]);
        bool hidden;
 } commands[] = {
-       {"start", cmd_start},
-       {"stop", cmd_stop},
-       {"restart", cmd_restart},
-       {"reload", cmd_reload},
-       {"dump", cmd_dump},
-       {"list", cmd_dump},
-       {"purge", cmd_purge},
-       {"debug", cmd_debug},
-       {"retry", cmd_retry},
-       {"connect", cmd_connect},
-       {"disconnect", cmd_disconnect},
-       {"top", cmd_top},
-       {"pcap", cmd_pcap},
-       {"log", cmd_log},
-       {"pid", cmd_pid},
+       {"start", cmd_start, false},
+       {"stop", cmd_stop, false},
+       {"restart", cmd_restart, false},
+       {"reload", cmd_reload, false},
+       {"dump", cmd_dump, false},
+       {"list", cmd_dump, false},
+       {"purge", cmd_purge, false},
+       {"debug", cmd_debug, false},
+       {"retry", cmd_retry, false},
+       {"connect", cmd_connect, false},
+       {"disconnect", cmd_disconnect, false},
+       {"top", cmd_top, false},
+       {"pcap", cmd_pcap, false},
+       {"log", cmd_log, false},
+       {"pid", cmd_pid, false},
        {"config", cmd_config, true},
-       {"add", cmd_config},
-       {"del", cmd_config},
-       {"get", cmd_config},
-       {"set", cmd_config},
-       {"init", cmd_init},
-       {"generate-keys", cmd_generate_keys},
+       {"add", cmd_config, false},
+       {"del", cmd_config, false},
+       {"get", cmd_config, false},
+       {"set", cmd_config, false},
+       {"init", cmd_init, false},
+       {"generate-keys", cmd_generate_keys, false},
 #ifndef DISABLE_LEGACY
-       {"generate-rsa-keys", cmd_generate_rsa_keys},
+       {"generate-rsa-keys", cmd_generate_rsa_keys, false},
 #endif
-       {"generate-ed25519-keys", cmd_generate_ed25519_keys},
-       {"help", cmd_help},
-       {"version", cmd_version},
-       {"info", cmd_info},
-       {"edit", cmd_edit},
-       {"export", cmd_export},
-       {"export-all", cmd_export_all},
-       {"import", cmd_import},
-       {"exchange", cmd_exchange},
-       {"exchange-all", cmd_exchange_all},
-       {"invite", cmd_invite},
-       {"join", cmd_join},
-       {"network", cmd_network},
-       {"fsck", cmd_fsck},
-       {"sign", cmd_sign},
-       {"verify", cmd_verify},
-       {NULL, NULL},
+       {"generate-ed25519-keys", cmd_generate_ed25519_keys, false},
+       {"help", cmd_help, false},
+       {"version", cmd_version, false},
+       {"info", cmd_info, false},
+       {"edit", cmd_edit, false},
+       {"export", cmd_export, false},
+       {"export-all", cmd_export_all, false},
+       {"import", cmd_import, false},
+       {"exchange", cmd_exchange, false},
+       {"exchange-all", cmd_exchange_all, false},
+       {"invite", cmd_invite, false},
+       {"join", cmd_join, false},
+       {"network", cmd_network, false},
+       {"fsck", cmd_fsck, false},
+       {"sign", cmd_sign, false},
+       {"verify", cmd_verify, false},
+       {NULL, NULL, false},
 };
 
 #ifdef HAVE_READLINE
@@ -3095,10 +3193,13 @@ static char *complete_info(const char *text, int state) {
 }
 
 static char *complete_nothing(const char *text, int state) {
+       (void)text;
+       (void)state;
        return NULL;
 }
 
 static char **completion(const char *text, int start, int end) {
+       (void)end;
        char **matches = NULL;
 
        if(!start) {
@@ -3149,10 +3250,7 @@ static int cmd_shell(int argc, char *argv[]) {
                        free(line);
                        rl_basic_word_break_characters = "\t\n ";
                        line = readline(prompt);
-
-                       if(line) {
-                               copy = xstrdup(line);
-                       }
+                       copy = line ? xstrdup(line) : NULL;
                } else {
                        line = fgets(buf, sizeof(buf), stdin);
                }
@@ -3198,6 +3296,9 @@ static int cmd_shell(int argc, char *argv[]) {
                }
 
                if(!strcasecmp(nargv[argc], "exit") || !strcasecmp(nargv[argc], "quit")) {
+#ifdef HAVE_READLINE
+                       free(copy);
+#endif
                        free(nargv);
                        return result;
                }
@@ -3226,6 +3327,9 @@ static int cmd_shell(int argc, char *argv[]) {
                }
        }
 
+#ifdef HAVE_READLINE
+       free(copy);
+#endif
        free(nargv);
 
        if(tty) {
@@ -3235,6 +3339,11 @@ static int cmd_shell(int argc, char *argv[]) {
        return result;
 }
 
+static void cleanup() {
+       free(tinc_conf);
+       free(hosts_dir);
+       free_names();
+}
 
 int main(int argc, char *argv[]) {
        program_name = argv[0];
@@ -3249,6 +3358,7 @@ int main(int argc, char *argv[]) {
        make_names(false);
        xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase);
        xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase);
+       atexit(cleanup);
 
        if(show_version) {
                version();
@@ -3264,13 +3374,14 @@ int main(int argc, char *argv[]) {
        static struct WSAData wsa_state;
 
        if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) {
-               fprintf(stderr, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError()));
+               fprintf(stderr, "System call `%s' failed: %s\n", "WSAStartup", winerror(GetLastError()));
                return false;
        }
 
 #endif
 
-       srand(time(NULL));
+       gettimeofday(&now, NULL);
+       srand(now.tv_sec + now.tv_usec);
        crypto_init();
 
        if(optind >= argc) {