2 tincctl.c -- Controlling a running tincd
3 Copyright (C) 2007-2017 Guus Sliepen <guus@tinc-vpn.org>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "readline/readline.h"
26 #include "readline/history.h"
31 #include "control_common.h"
36 #include "invitation.h"
45 #define MSG_NOSIGNAL 0
48 static char **orig_argv;
51 /* If nonzero, display usage information and exit. */
52 static bool show_help = false;
54 /* If nonzero, print the version on standard output and exit. */
55 static bool show_version = false;
57 static char *name = NULL;
58 static char controlcookie[1025];
59 char *tinc_conf = NULL;
60 char *hosts_dir = NULL;
63 // Horrible global variables...
72 bool confbasegiven = false;
73 bool netnamegiven = false;
74 char *scriptinterpreter = NULL;
75 char *scriptextension = "";
81 static struct option const long_options[] = {
82 {"batch", no_argument, NULL, 'b'},
83 {"config", required_argument, NULL, 'c'},
84 {"net", required_argument, NULL, 'n'},
85 {"help", no_argument, NULL, 1},
86 {"version", no_argument, NULL, 2},
87 {"pidfile", required_argument, NULL, 3},
88 {"force", no_argument, NULL, 4},
92 static void version(void) {
93 printf("%s version %s (built %s %s, protocol %d.%d)\n", PACKAGE,
94 BUILD_VERSION, BUILD_DATE, BUILD_TIME, PROT_MAJOR, PROT_MINOR);
95 printf("Copyright (C) 1998-2017 Ivo Timmermans, Guus Sliepen and others.\n"
96 "See the AUTHORS file for a complete list.\n\n"
97 "tinc comes with ABSOLUTELY NO WARRANTY. This is free software,\n"
98 "and you are welcome to redistribute it under certain conditions;\n"
99 "see the file COPYING for details.\n");
102 static void usage(bool status) {
104 fprintf(stderr, "Try `%s --help\' for more information.\n", program_name);
106 printf("Usage: %s [options] command\n\n", program_name);
107 printf("Valid options are:\n"
108 " -b, --batch Don't ask for anything (non-interactive mode).\n"
109 " -c, --config=DIR Read configuration options from DIR.\n"
110 " -n, --net=NETNAME Connect to net NETNAME.\n"
111 " --pidfile=FILENAME Read control cookie from FILENAME.\n"
112 " --force Force some commands to work despite warnings.\n"
113 " --help Display this help and exit.\n"
114 " --version Output version information and exit.\n"
116 "Valid commands are:\n"
117 " init [name] Create initial configuration files.\n"
118 " get VARIABLE Print current value of VARIABLE\n"
119 " set VARIABLE VALUE Set VARIABLE to VALUE\n"
120 " add VARIABLE VALUE Add VARIABLE with the given VALUE\n"
121 " del VARIABLE [VALUE] Remove VARIABLE [only ones with watching VALUE]\n"
122 " start [tincd options] Start tincd.\n"
123 " stop Stop tincd.\n"
124 " restart [tincd options] Restart tincd.\n"
125 " reload Partially reload configuration of running tincd.\n"
126 " pid Show PID of currently running tincd.\n"
127 #ifdef DISABLE_LEGACY
128 " generate-keys Generate a new Ed25519 public/private keypair.\n"
130 " generate-keys [bits] Generate new RSA and Ed25519 public/private keypairs.\n"
131 " generate-rsa-keys [bits] Generate a new RSA public/private keypair.\n"
133 " generate-ed25519-keys Generate a new Ed25519 public/private keypair.\n"
134 " dump Dump a list of one of the following things:\n"
135 " [reachable] nodes - all known nodes in the VPN\n"
136 " edges - all known connections in the VPN\n"
137 " subnets - all known subnets in the VPN\n"
138 " connections - all meta connections with ourself\n"
139 " [di]graph - graph of the VPN in dotty format\n"
140 " invitations - outstanding invitations\n"
141 " info NODE|SUBNET|ADDRESS Give information about a particular NODE, SUBNET or ADDRESS.\n"
142 " purge Purge unreachable nodes\n"
143 " debug N Set debug level\n"
144 " retry Retry all outgoing connections\n"
145 " disconnect NODE Close meta connection with NODE\n"
147 " top Show real-time statistics\n"
149 " pcap [snaplen] Dump traffic in pcap format [up to snaplen bytes per packet]\n"
150 " log [level] Dump log output [up to the specified level]\n"
151 " export Export host configuration of local node to standard output\n"
152 " export-all Export all host configuration files to standard output\n"
153 " import Import host configuration file(s) from standard input\n"
154 " exchange Same as export followed by import\n"
155 " exchange-all Same as export-all followed by import\n"
156 " invite NODE [...] Generate an invitation for NODE\n"
157 " join INVITATION Join a VPN using an INVITATION\n"
158 " network [NETNAME] List all known networks, or switch to the one named NETNAME.\n"
159 " fsck Check the configuration files for problems.\n"
160 " sign [FILE] Generate a signed version of a file.\n"
161 " verify NODE [FILE] Verify that a file was signed by the given NODE.\n"
163 printf("Report bugs to tinc@tinc-vpn.org.\n");
167 static bool parse_options(int argc, char **argv) {
169 int option_index = 0;
171 while((r = getopt_long(argc, argv, "+bc:n:", long_options, &option_index)) != EOF) {
173 case 0: /* long option */
180 case 'c': /* config file */
181 confbase = xstrdup(optarg);
182 confbasegiven = true;
185 case 'n': /* net name given */
186 netname = xstrdup(optarg);
189 case 1: /* show help */
193 case 2: /* show version */
197 case 3: /* open control socket here */
198 pidfilename = xstrdup(optarg);
205 case '?': /* wrong options */
214 if(!netname && (netname = getenv("NETNAME"))) {
215 netname = xstrdup(netname);
218 /* netname "." is special: a "top-level name" */
220 if(netname && (!*netname || !strcmp(netname, "."))) {
225 if(netname && (strpbrk(netname, "\\/") || *netname == '.')) {
226 fprintf(stderr, "Invalid character in netname!\n");
233 /* Open a file with the desired permissions, minus the umask.
234 Also, if we want to create an executable file, we call fchmod()
235 to set the executable bits. */
237 FILE *fopenmask(const char *filename, const char *mode, mode_t perms) {
238 mode_t mask = umask(0);
241 FILE *f = fopen(filename, mode);
244 fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno));
250 if((perms & 0444) && f) {
251 fchmod(fileno(f), perms);
259 static void disable_old_keys(const char *filename, const char *what) {
260 char tmpfile[PATH_MAX] = "";
262 bool disabled = false;
267 r = fopen(filename, "r");
273 snprintf(tmpfile, sizeof(tmpfile), "%s.tmp", filename);
275 struct stat st = {.st_mode = 0600};
276 fstat(fileno(r), &st);
277 w = fopenmask(tmpfile, "w", st.st_mode);
279 while(fgets(buf, sizeof(buf), r)) {
280 if(!block && !strncmp(buf, "-----BEGIN ", 11)) {
281 if((strstr(buf, " ED25519 ") && strstr(what, "Ed25519")) || (strstr(buf, " RSA ") && strstr(what, "RSA"))) {
287 bool ed25519pubkey = !strncasecmp(buf, "Ed25519PublicKey", 16) && strchr(" \t=", buf[16]) && strstr(what, "Ed25519");
294 if(block || ed25519pubkey) {
298 if(fputs(buf, w) < 0) {
304 if(block && !strncmp(buf, "-----END ", 9)) {
314 if(ferror(r) || fclose(r) < 0) {
320 fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n");
330 // We cannot atomically replace files on Windows.
331 char bakfile[PATH_MAX] = "";
332 snprintf(bakfile, sizeof(bakfile), "%s.bak", filename);
334 if(rename(filename, bakfile) || rename(tmpfile, filename)) {
335 rename(bakfile, filename);
338 if(rename(tmpfile, filename)) {
340 fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n");
345 fprintf(stderr, "Warning: old key(s) found and disabled.\n");
352 static FILE *ask_and_open(const char *filename, const char *what, const char *mode, bool ask, mode_t perms) {
354 char directory[PATH_MAX] = ".";
358 /* Check stdin and stdout */
360 /* Ask for a file and/or directory name. */
361 fprintf(stderr, "Please enter a file to save %s to [%s]: ", what, filename);
363 if(fgets(buf, sizeof(buf), stdin) == NULL) {
364 fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
368 size_t len = strlen(buf);
381 if(filename[0] != '\\' && filename[0] != '/' && !strchr(filename, ':')) {
384 if(filename[0] != '/') {
386 /* The directory is a relative path or a filename. */
387 getcwd(directory, sizeof(directory));
388 snprintf(buf2, sizeof(buf2), "%s" SLASH "%s", directory, filename);
392 disable_old_keys(filename, what);
394 /* Open it first to keep the inode busy */
396 r = fopenmask(filename, mode, perms);
399 fprintf(stderr, "Error opening file `%s': %s\n", filename, strerror(errno));
407 Generate a public/private Ed25519 keypair, and ask for a file to store
410 static bool ed25519_keygen(bool ask) {
413 char fname[PATH_MAX];
415 fprintf(stderr, "Generating Ed25519 keypair:\n");
417 if(!(key = ecdsa_generate())) {
418 fprintf(stderr, "Error during key generation!\n");
421 fprintf(stderr, "Done.\n");
424 snprintf(fname, sizeof(fname), "%s" SLASH "ed25519_key.priv", confbase);
425 f = ask_and_open(fname, "private Ed25519 key", "a", ask, 0600);
431 if(!ecdsa_write_pem_private_key(key, f)) {
432 fprintf(stderr, "Error writing private key!\n");
439 snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, name);
441 snprintf(fname, sizeof(fname), "%s" SLASH "ed25519_key.pub", confbase);
444 f = ask_and_open(fname, "public Ed25519 key", "a", ask, 0666);
450 char *pubkey = ecdsa_get_base64_public_key(key);
451 fprintf(f, "Ed25519PublicKey = %s\n", pubkey);
469 #ifndef DISABLE_LEGACY
471 Generate a public/private RSA keypair, and ask for a file to store
474 static bool rsa_keygen(int bits, bool ask) {
477 char fname[PATH_MAX];
479 // Make sure the key size is a multiple of 8 bits.
482 // Make sure that a valid key size is used.
483 if(bits < 1024 || bits > 8192) {
484 fprintf(stderr, "Invalid key size %d specified! It should be between 1024 and 8192 bits.\n", bits);
486 } else if(bits < 2048) {
487 fprintf(stderr, "WARNING: generating a weak %d bits RSA key! 2048 or more bits are recommended.\n", bits);
490 fprintf(stderr, "Generating %d bits keys:\n", bits);
492 if(!(key = rsa_generate(bits, 0x10001))) {
493 fprintf(stderr, "Error during key generation!\n");
496 fprintf(stderr, "Done.\n");
499 snprintf(fname, sizeof(fname), "%s" SLASH "rsa_key.priv", confbase);
500 f = ask_and_open(fname, "private RSA key", "a", ask, 0600);
506 if(!rsa_write_pem_private_key(key, f)) {
507 fprintf(stderr, "Error writing private key!\n");
514 snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, name);
516 snprintf(fname, sizeof(fname), "%s" SLASH "rsa_key.pub", confbase);
519 f = ask_and_open(fname, "public RSA key", "a", ask, 0666);
525 if(!rsa_write_pem_public_key(key, f)) {
526 fprintf(stderr, "Error writing public key!\n");
549 bool recvline(int fd, char *line, size_t len) {
550 char *newline = NULL;
556 while(!(newline = memchr(buffer, '\n', blen))) {
557 int result = recv(fd, buffer + blen, sizeof(buffer) - blen, 0);
559 if(result == -1 && sockerrno == EINTR) {
561 } else if(result <= 0) {
568 if(newline - buffer >= len) {
572 len = newline - buffer;
574 memcpy(line, buffer, len);
576 memmove(buffer, newline + 1, blen - len - 1);
582 bool recvdata(int fd, char *data, size_t len) {
588 int result = recv(fd, buffer + blen, sizeof(buffer) - blen, 0);
590 if(result == -1 && sockerrno == EINTR) {
592 } else if(result <= 0) {
599 memcpy(data, buffer, len);
600 memmove(buffer, buffer + len, blen - len);
606 bool sendline(int fd, char *format, ...) {
607 static char buffer[4096];
612 va_start(ap, format);
613 blen = vsnprintf(buffer, sizeof(buffer), format, ap);
614 buffer[sizeof(buffer) - 1] = 0;
617 if(blen < 1 || blen >= sizeof(buffer)) {
625 int result = send(fd, p, blen, MSG_NOSIGNAL);
627 if(result == -1 && sockerrno == EINTR) {
629 } else if(result <= 0) {
640 static void pcap(int fd, FILE *out, int snaplen) {
641 sendline(fd, "%d %d %d", CONTROL, REQ_PCAP, snaplen);
649 uint32_t tz_accuracy;
656 snaplen ? : sizeof(data),
669 fwrite(&header, sizeof(header), 1, out);
674 while(recvline(fd, line, sizeof(line))) {
676 int n = sscanf(line, "%d %d %d", &code, &req, &len);
677 gettimeofday(&tv, NULL);
679 if(n != 3 || code != CONTROL || req != REQ_PCAP || len < 0 || len > sizeof(data)) {
683 if(!recvdata(fd, data, len)) {
687 packet.tv_sec = tv.tv_sec;
688 packet.tv_usec = tv.tv_usec;
690 packet.origlen = len;
691 fwrite(&packet, sizeof(packet), 1, out);
692 fwrite(data, len, 1, out);
697 static void logcontrol(int fd, FILE *out, int level) {
698 sendline(fd, "%d %d %d", CONTROL, REQ_LOG, level);
702 while(recvline(fd, line, sizeof(line))) {
704 int n = sscanf(line, "%d %d %d", &code, &req, &len);
706 if(n != 3 || code != CONTROL || req != REQ_LOG || len < 0 || len > sizeof(data)) {
710 if(!recvdata(fd, data, len)) {
714 fwrite(data, len, 1, out);
721 static bool remove_service(void) {
722 SC_HANDLE manager = NULL;
723 SC_HANDLE service = NULL;
724 SERVICE_STATUS status = {0};
726 manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
729 fprintf(stderr, "Could not open service manager: %s\n", winerror(GetLastError()));
733 service = OpenService(manager, identname, SERVICE_ALL_ACCESS);
736 fprintf(stderr, "Could not open %s service: %s\n", identname, winerror(GetLastError()));
740 if(!ControlService(service, SERVICE_CONTROL_STOP, &status)) {
741 fprintf(stderr, "Could not stop %s service: %s\n", identname, winerror(GetLastError()));
743 fprintf(stderr, "%s service stopped\n", identname);
746 if(!DeleteService(service)) {
747 fprintf(stderr, "Could not remove %s service: %s\n", identname, winerror(GetLastError()));
752 CloseServiceHandle(service);
756 CloseServiceHandle(manager);
759 fprintf(stderr, "%s service removed\n", identname);
765 bool connect_tincd(bool verbose) {
770 struct timeval tv = {0, 0};
772 if(select(fd + 1, &r, NULL, NULL, &tv)) {
773 fprintf(stderr, "Previous connection to tincd lost, reconnecting.\n");
781 FILE *f = fopen(pidfilename, "r");
785 fprintf(stderr, "Could not open pid file %s: %s\n", pidfilename, strerror(errno));
794 if(fscanf(f, "%20d %1024s %128s port %128s", &pid, controlcookie, host, port) != 4) {
796 fprintf(stderr, "Could not parse pid file %s\n", pidfilename);
807 if((pid == 0) || (kill(pid, 0) && (errno == ESRCH))) {
808 fprintf(stderr, "Could not find tincd running at pid %d\n", pid);
809 /* clean up the stale socket and pid file */
811 unlink(unixsocketname);
815 struct sockaddr_un sa;
817 sa.sun_family = AF_UNIX;
819 strncpy(sa.sun_path, unixsocketname, sizeof(sa.sun_path));
821 fd = socket(AF_UNIX, SOCK_STREAM, 0);
825 fprintf(stderr, "Cannot create UNIX socket: %s\n", sockstrerror(sockerrno));
831 if(connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
833 fprintf(stderr, "Cannot connect to UNIX socket %s: %s\n", unixsocketname, sockstrerror(sockerrno));
842 struct addrinfo hints = {
843 .ai_family = AF_UNSPEC,
844 .ai_socktype = SOCK_STREAM,
845 .ai_protocol = IPPROTO_TCP,
849 struct addrinfo *res = NULL;
851 if(getaddrinfo(host, port, &hints, &res) || !res) {
853 fprintf(stderr, "Cannot resolve %s port %s: %s\n", host, port, sockstrerror(sockerrno));
859 fd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP);
863 fprintf(stderr, "Cannot create TCP socket: %s\n", sockstrerror(sockerrno));
870 unsigned long arg = 0;
872 if(ioctlsocket(fd, FIONBIO, &arg) != 0) {
874 fprintf(stderr, "System call `%s' failed: %s\n", "ioctlsocket", sockstrerror(sockerrno));
880 if(connect(fd, res->ai_addr, res->ai_addrlen) < 0) {
882 fprintf(stderr, "Cannot connect to %s port %s: %s\n", host, port, sockstrerror(sockerrno));
894 static const int one = 1;
895 setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&one, sizeof(one));
901 if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %4095s %d", &code, data, &version) != 3 || code != 0) {
903 fprintf(stderr, "Cannot read greeting from control socket: %s\n", sockstrerror(sockerrno));
911 sendline(fd, "%d ^%s %d", ID, controlcookie, TINC_CTL_VERSION_CURRENT);
913 if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &version, &pid) != 3 || code != 4 || version != TINC_CTL_VERSION_CURRENT) {
915 fprintf(stderr, "Could not fully establish control socket connection\n");
927 static int cmd_start(int argc, char *argv[]) {
928 if(connect_tincd(false)) {
930 fprintf(stderr, "A tincd is already running for net `%s' with pid %d.\n", netname, pid);
932 fprintf(stderr, "A tincd is already running with pid %d.\n", pid);
939 char *slash = strrchr(program_name, '/');
943 if((c = strrchr(program_name, '\\')) > slash) {
950 xasprintf(&c, "%.*stincd", (int)(slash - program_name), program_name);
956 char **nargv = xzalloc((optind + argc) * sizeof(*nargv));
961 Windows has no real concept of an "argv array". A command line is just one string.
962 The CRT of the new process will decode the command line string to generate argv before calling main(), and (by convention)
963 it uses quotes to handle spaces in arguments.
964 Therefore we need to quote all arguments that might contain spaces. No, execvp() won't do that for us (see MSDN).
965 If we don't do that, then execvp() will run fine but any spaces in the filename contained in arg0 will bleed
966 into the next arguments when the spawned process' CRT parses its command line, resulting in chaos.
968 xasprintf(&arg0, "\"%s\"", arg0);
970 nargv[nargc++] = arg0;
972 for(int i = 1; i < optind; i++) {
973 nargv[nargc++] = orig_argv[i];
976 for(int i = 1; i < argc; i++) {
977 nargv[nargc++] = argv[i];
981 int status = spawnvp(_P_WAIT, c, nargv);
984 fprintf(stderr, "Error starting %s: %s\n", c, strerror(errno));
990 int pfd[2] = {-1, -1};
992 if(socketpair(AF_UNIX, SOCK_STREAM, 0, pfd)) {
993 fprintf(stderr, "Could not create umbilical socket: %s\n", strerror(errno));
1001 fprintf(stderr, "Could not fork: %s\n", strerror(errno));
1009 snprintf(buf, sizeof(buf), "%d", pfd[1]);
1010 setenv("TINC_UMBILICAL", buf, true);
1011 exit(execvp(c, nargv));
1018 int status = -1, result;
1020 signal(SIGINT, SIG_IGN);
1023 // Pass all log messages from the umbilical to stderr.
1024 // A nul-byte right before closure means tincd started successfully.
1025 bool failure = true;
1029 while((len = read(pfd[0], buf, sizeof(buf))) > 0) {
1030 failure = buf[len - 1];
1045 // Make sure the child process is really gone.
1046 result = waitpid(pid, &status, 0);
1049 signal(SIGINT, SIG_DFL);
1052 if(failure || result != pid || !WIFEXITED(status) || WEXITSTATUS(status)) {
1053 fprintf(stderr, "Error starting %s\n", c);
1061 static int cmd_stop(int argc, char *argv[]) {
1063 fprintf(stderr, "Too many arguments!\n");
1069 if(!connect_tincd(true)) {
1071 if(kill(pid, SIGTERM)) {
1072 fprintf(stderr, "Could not send TERM signal to process with PID %d: %s\n", pid, strerror(errno));
1076 fprintf(stderr, "Sent TERM signal to process with PID %d.\n", pid);
1077 waitpid(pid, NULL, 0);
1084 sendline(fd, "%d %d", CONTROL, REQ_STOP);
1086 while(recvline(fd, line, sizeof(line))) {
1087 // Wait for tincd to close the connection...
1092 if(!remove_service()) {
1104 static int cmd_restart(int argc, char *argv[]) {
1106 return cmd_start(argc, argv);
1109 static int cmd_reload(int argc, char *argv[]) {
1111 fprintf(stderr, "Too many arguments!\n");
1115 if(!connect_tincd(true)) {
1119 sendline(fd, "%d %d", CONTROL, REQ_RELOAD);
1121 if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RELOAD || result) {
1122 fprintf(stderr, "Could not reload configuration.\n");
1130 static int dump_invitations(void) {
1131 char dname[PATH_MAX];
1132 snprintf(dname, sizeof(dname), "%s" SLASH "invitations", confbase);
1133 DIR *dir = opendir(dname);
1136 if(errno == ENOENT) {
1137 fprintf(stderr, "No outstanding invitations.\n");
1141 fprintf(stderr, "Cannot not read directory %s: %s\n", dname, strerror(errno));
1149 while((ent = readdir(dir))) {
1150 char buf[MAX_STRING_SIZE];
1152 if(b64decode(ent->d_name, buf, 24) != 18) {
1156 char fname[PATH_MAX];
1157 snprintf(fname, sizeof(fname), "%s" SLASH "%s", dname, ent->d_name);
1158 FILE *f = fopen(fname, "r");
1161 fprintf(stderr, "Cannot open %s: %s\n", fname, strerror(errno));
1167 if(!fgets(buf, sizeof(buf), f)) {
1168 fprintf(stderr, "Invalid invitation file %s\n", fname);
1175 char *eol = buf + strlen(buf);
1177 while(strchr("\t \r\n", *--eol)) {
1181 if(strncmp(buf, "Name = ", 7) || !check_id(buf + 7)) {
1182 fprintf(stderr, "Invalid invitation file %s\n", fname);
1187 printf("%s %s\n", ent->d_name, buf + 7);
1193 fprintf(stderr, "No outstanding invitations.\n");
1199 static int cmd_dump(int argc, char *argv[]) {
1200 bool only_reachable = false;
1202 if(argc > 2 && !strcasecmp(argv[1], "reachable")) {
1203 if(strcasecmp(argv[2], "nodes")) {
1204 fprintf(stderr, "`reachable' only supported for nodes.\n");
1209 only_reachable = true;
1215 fprintf(stderr, "Invalid number of arguments.\n");
1220 if(!strcasecmp(argv[1], "invitations")) {
1221 return dump_invitations();
1224 if(!connect_tincd(true)) {
1230 if(!strcasecmp(argv[1], "nodes")) {
1231 sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES);
1232 } else if(!strcasecmp(argv[1], "edges")) {
1233 sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES);
1234 } else if(!strcasecmp(argv[1], "subnets")) {
1235 sendline(fd, "%d %d", CONTROL, REQ_DUMP_SUBNETS);
1236 } else if(!strcasecmp(argv[1], "connections")) {
1237 sendline(fd, "%d %d", CONTROL, REQ_DUMP_CONNECTIONS);
1238 } else if(!strcasecmp(argv[1], "graph")) {
1239 sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES);
1240 sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES);
1242 } else if(!strcasecmp(argv[1], "digraph")) {
1243 sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES);
1244 sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES);
1247 fprintf(stderr, "Unknown dump type '%s'.\n", argv[1]);
1253 printf("graph {\n");
1254 } else if(do_graph == 2) {
1255 printf("digraph {\n");
1258 while(recvline(fd, line, sizeof(line))) {
1259 char node1[4096], node2[4096];
1260 int n = sscanf(line, "%d %d %4095s %4095s", &code, &req, node1, node2);
1263 if(do_graph && req == REQ_DUMP_NODES) {
1285 char local_host[4096];
1286 char local_port[4096];
1289 int cipher, digest, maclength, compression, distance, socket, weight;
1290 short int pmtu, minmtu, maxmtu;
1291 unsigned int options, status_int;
1292 node_status_t status;
1293 long int last_state_change;
1296 case REQ_DUMP_NODES: {
1297 int n = sscanf(line, "%*d %*d %4095s %4095s %4095s port %4095s %d %d %d %d %x %x %4095s %4095s %d %hd %hd %hd %ld", node, id, host, port, &cipher, &digest, &maclength, &compression, &options, &status_int, nexthop, via, &distance, &pmtu, &minmtu, &maxmtu, &last_state_change);
1300 fprintf(stderr, "Unable to parse node dump from tincd: %s\n", line);
1304 memcpy(&status, &status_int, sizeof(status));
1307 const char *color = "black";
1309 if(!strcmp(host, "MYSELF")) {
1311 } else if(!status.reachable) {
1313 } else if(strcmp(via, node)) {
1315 } else if(!status.validkey) {
1317 } else if(minmtu > 0) {
1321 printf(" %s [label = \"%s\", color = \"%s\"%s];\n", node, node, color, strcmp(host, "MYSELF") ? "" : ", style = \"filled\"");
1323 if(only_reachable && !status.reachable) {
1327 printf("%s id %s at %s port %s cipher %d digest %d maclength %d compression %d options %x status %04x nexthop %s via %s distance %d pmtu %d (min %d max %d)\n",
1328 node, id, host, port, cipher, digest, maclength, compression, options, status_int, nexthop, via, distance, pmtu, minmtu, maxmtu);
1333 case REQ_DUMP_EDGES: {
1334 int n = sscanf(line, "%*d %*d %4095s %4095s %4095s port %4095s %4095s port %4095s %x %d", from, to, host, port, local_host, local_port, &options, &weight);
1337 fprintf(stderr, "Unable to parse edge dump from tincd.\n");
1342 float w = 1 + 65536.0 / weight;
1344 if(do_graph == 1 && strcmp(node1, node2) > 0) {
1345 printf(" %s -- %s [w = %f, weight = %f];\n", node1, node2, w, w);
1346 } else if(do_graph == 2) {
1347 printf(" %s -> %s [w = %f, weight = %f];\n", node1, node2, w, w);
1350 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);
1355 case REQ_DUMP_SUBNETS: {
1356 int n = sscanf(line, "%*d %*d %4095s %4095s", subnet, node);
1359 fprintf(stderr, "Unable to parse subnet dump from tincd.\n");
1363 printf("%s owner %s\n", strip_weight(subnet), node);
1367 case REQ_DUMP_CONNECTIONS: {
1368 int n = sscanf(line, "%*d %*d %4095s %4095s port %4095s %x %d %x", node, host, port, &options, &socket, &status_int);
1371 fprintf(stderr, "Unable to parse connection dump from tincd.\n");
1375 printf("%s at %s port %s options %x socket %d status %x\n", node, host, port, options, socket, status_int);
1380 fprintf(stderr, "Unable to parse dump from tincd.\n");
1385 fprintf(stderr, "Error receiving dump.\n");
1389 static int cmd_purge(int argc, char *argv[]) {
1391 fprintf(stderr, "Too many arguments!\n");
1395 if(!connect_tincd(true)) {
1399 sendline(fd, "%d %d", CONTROL, REQ_PURGE);
1401 if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_PURGE || result) {
1402 fprintf(stderr, "Could not purge old information.\n");
1409 static int cmd_debug(int argc, char *argv[]) {
1411 fprintf(stderr, "Invalid number of arguments.\n");
1415 if(!connect_tincd(true)) {
1419 int debuglevel = atoi(argv[1]);
1422 sendline(fd, "%d %d %d", CONTROL, REQ_SET_DEBUG, debuglevel);
1424 if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &req, &origlevel) != 3 || code != CONTROL || req != REQ_SET_DEBUG) {
1425 fprintf(stderr, "Could not set debug level.\n");
1429 fprintf(stderr, "Old level %d, new level %d.\n", origlevel, debuglevel);
1433 static int cmd_retry(int argc, char *argv[]) {
1435 fprintf(stderr, "Too many arguments!\n");
1439 if(!connect_tincd(true)) {
1443 sendline(fd, "%d %d", CONTROL, REQ_RETRY);
1445 if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RETRY || result) {
1446 fprintf(stderr, "Could not retry outgoing connections.\n");
1453 static int cmd_connect(int argc, char *argv[]) {
1455 fprintf(stderr, "Invalid number of arguments.\n");
1459 if(!check_id(argv[1])) {
1460 fprintf(stderr, "Invalid name for node.\n");
1464 if(!connect_tincd(true)) {
1468 sendline(fd, "%d %d %s", CONTROL, REQ_CONNECT, argv[1]);
1470 if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_CONNECT || result) {
1471 fprintf(stderr, "Could not connect to %s.\n", argv[1]);
1478 static int cmd_disconnect(int argc, char *argv[]) {
1480 fprintf(stderr, "Invalid number of arguments.\n");
1484 if(!check_id(argv[1])) {
1485 fprintf(stderr, "Invalid name for node.\n");
1489 if(!connect_tincd(true)) {
1493 sendline(fd, "%d %d %s", CONTROL, REQ_DISCONNECT, argv[1]);
1495 if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_DISCONNECT || result) {
1496 fprintf(stderr, "Could not disconnect %s.\n", argv[1]);
1503 static int cmd_top(int argc, char *argv[]) {
1505 fprintf(stderr, "Too many arguments!\n");
1511 if(!connect_tincd(true)) {
1518 fprintf(stderr, "This version of tinc was compiled without support for the curses library.\n");
1523 static int cmd_pcap(int argc, char *argv[]) {
1525 fprintf(stderr, "Too many arguments!\n");
1529 if(!connect_tincd(true)) {
1533 pcap(fd, stdout, argc > 1 ? atoi(argv[1]) : 0);
1538 static void sigint_handler(int sig) {
1539 fprintf(stderr, "\n");
1540 shutdown(fd, SHUT_RDWR);
1544 static int cmd_log(int argc, char *argv[]) {
1546 fprintf(stderr, "Too many arguments!\n");
1550 if(!connect_tincd(true)) {
1555 signal(SIGINT, sigint_handler);
1558 logcontrol(fd, stdout, argc > 1 ? atoi(argv[1]) : -1);
1561 signal(SIGINT, SIG_DFL);
1569 static int cmd_pid(int argc, char *argv[]) {
1571 fprintf(stderr, "Too many arguments!\n");
1575 if(!connect_tincd(true) || !pid) {
1579 printf("%d\n", pid);
1583 int rstrip(char *value) {
1584 int len = strlen(value);
1586 while(len && strchr("\t\r\n ", value[len - 1])) {
1593 char *get_my_name(bool verbose) {
1594 FILE *f = fopen(tinc_conf, "r");
1598 fprintf(stderr, "Could not open %s: %s\n", tinc_conf, strerror(errno));
1607 while(fgets(buf, sizeof(buf), f)) {
1608 int len = strcspn(buf, "\t =");
1610 value += strspn(value, "\t ");
1614 value += strspn(value, "\t ");
1617 if(!rstrip(value)) {
1623 if(strcasecmp(buf, "Name")) {
1629 return replace_name(value);
1636 fprintf(stderr, "Could not find Name in %s.\n", tinc_conf);
1642 ecdsa_t *get_pubkey(FILE *f) {
1646 while(fgets(buf, sizeof(buf), f)) {
1647 int len = strcspn(buf, "\t =");
1649 value += strspn(value, "\t ");
1653 value += strspn(value, "\t ");
1656 if(!rstrip(value)) {
1662 if(strcasecmp(buf, "Ed25519PublicKey")) {
1667 return ecdsa_set_base64_public_key(value);
1674 const var_t variables[] = {
1675 /* Server configuration */
1676 {"AddressFamily", VAR_SERVER},
1677 {"AutoConnect", VAR_SERVER | VAR_SAFE},
1678 {"BindToAddress", VAR_SERVER | VAR_MULTIPLE},
1679 {"BindToInterface", VAR_SERVER},
1680 {"Broadcast", VAR_SERVER | VAR_SAFE},
1681 {"BroadcastSubnet", VAR_SERVER | VAR_MULTIPLE | VAR_SAFE},
1682 {"ConnectTo", VAR_SERVER | VAR_MULTIPLE | VAR_SAFE},
1683 {"DecrementTTL", VAR_SERVER},
1684 {"Device", VAR_SERVER},
1685 {"DeviceStandby", VAR_SERVER},
1686 {"DeviceType", VAR_SERVER},
1687 {"DirectOnly", VAR_SERVER},
1688 {"Ed25519PrivateKeyFile", VAR_SERVER},
1689 {"ExperimentalProtocol", VAR_SERVER},
1690 {"Forwarding", VAR_SERVER},
1691 {"GraphDumpFile", VAR_SERVER | VAR_OBSOLETE},
1692 {"Hostnames", VAR_SERVER},
1693 {"IffOneQueue", VAR_SERVER},
1694 {"Interface", VAR_SERVER},
1695 {"InvitationExpire", VAR_SERVER},
1696 {"KeyExpire", VAR_SERVER},
1697 {"ListenAddress", VAR_SERVER | VAR_MULTIPLE},
1698 {"LocalDiscovery", VAR_SERVER},
1699 {"LogLevel", VAR_SERVER},
1700 {"MACExpire", VAR_SERVER},
1701 {"MaxConnectionBurst", VAR_SERVER},
1702 {"MaxOutputBufferSize", VAR_SERVER},
1703 {"MaxTimeout", VAR_SERVER},
1704 {"Mode", VAR_SERVER | VAR_SAFE},
1705 {"Name", VAR_SERVER},
1706 {"PingInterval", VAR_SERVER},
1707 {"PingTimeout", VAR_SERVER},
1708 {"PriorityInheritance", VAR_SERVER},
1709 {"PrivateKey", VAR_SERVER | VAR_OBSOLETE},
1710 {"PrivateKeyFile", VAR_SERVER},
1711 {"ProcessPriority", VAR_SERVER},
1712 {"Proxy", VAR_SERVER},
1713 {"ReplayWindow", VAR_SERVER},
1714 {"ScriptsExtension", VAR_SERVER},
1715 {"ScriptsInterpreter", VAR_SERVER},
1716 {"StrictSubnets", VAR_SERVER},
1717 {"TunnelServer", VAR_SERVER},
1718 {"UDPDiscovery", VAR_SERVER},
1719 {"UDPDiscoveryKeepaliveInterval", VAR_SERVER},
1720 {"UDPDiscoveryInterval", VAR_SERVER},
1721 {"UDPDiscoveryTimeout", VAR_SERVER},
1722 {"MTUInfoInterval", VAR_SERVER},
1723 {"UDPInfoInterval", VAR_SERVER},
1724 {"UDPRcvBuf", VAR_SERVER},
1725 {"UDPSndBuf", VAR_SERVER},
1726 {"UPnP", VAR_SERVER},
1727 {"UPnPDiscoverWait", VAR_SERVER},
1728 {"UPnPRefreshPeriod", VAR_SERVER},
1729 {"VDEGroup", VAR_SERVER},
1730 {"VDEPort", VAR_SERVER},
1731 /* Host configuration */
1732 {"Address", VAR_HOST | VAR_MULTIPLE},
1733 {"Cipher", VAR_SERVER | VAR_HOST},
1734 {"ClampMSS", VAR_SERVER | VAR_HOST},
1735 {"Compression", VAR_SERVER | VAR_HOST},
1736 {"Digest", VAR_SERVER | VAR_HOST},
1737 {"Ed25519PublicKey", VAR_HOST},
1738 {"Ed25519PublicKeyFile", VAR_SERVER | VAR_HOST},
1739 {"IndirectData", VAR_SERVER | VAR_HOST},
1740 {"MACLength", VAR_SERVER | VAR_HOST},
1741 {"PMTU", VAR_SERVER | VAR_HOST},
1742 {"PMTUDiscovery", VAR_SERVER | VAR_HOST},
1744 {"PublicKey", VAR_HOST | VAR_OBSOLETE},
1745 {"PublicKeyFile", VAR_SERVER | VAR_HOST | VAR_OBSOLETE},
1746 {"Subnet", VAR_HOST | VAR_MULTIPLE | VAR_SAFE},
1747 {"TCPOnly", VAR_SERVER | VAR_HOST},
1748 {"Weight", VAR_HOST | VAR_SAFE},
1752 static int cmd_config(int argc, char *argv[]) {
1754 fprintf(stderr, "Invalid number of arguments.\n");
1758 if(strcasecmp(argv[0], "config")) {
1764 if(!strcasecmp(argv[1], "get")) {
1766 } else if(!strcasecmp(argv[1], "add")) {
1767 argv++, argc--, action = 1;
1768 } else if(!strcasecmp(argv[1], "del")) {
1769 argv++, argc--, action = -1;
1770 } else if(!strcasecmp(argv[1], "replace") || !strcasecmp(argv[1], "set") || !strcasecmp(argv[1], "change")) {
1771 argv++, argc--, action = 0;
1775 fprintf(stderr, "Invalid number of arguments.\n");
1779 // Concatenate the rest of the command line
1780 strncpy(line, argv[1], sizeof(line) - 1);
1782 for(int i = 2; i < argc; i++) {
1783 strncat(line, " ", sizeof(line) - 1 - strlen(line));
1784 strncat(line, argv[i], sizeof(line) - 1 - strlen(line));
1787 // Liberal parsing into node name, variable name and value.
1793 len = strcspn(line, "\t =");
1795 value += strspn(value, "\t ");
1799 value += strspn(value, "\t ");
1803 variable = strchr(line, '.');
1813 fprintf(stderr, "No variable given.\n");
1817 if(action >= 0 && !*value) {
1818 fprintf(stderr, "No value for variable given.\n");
1822 if(action < -1 && *value) {
1826 /* Some simple checks. */
1828 bool warnonremove = false;
1830 for(int i = 0; variables[i].name; i++) {
1831 if(strcasecmp(variables[i].name, variable)) {
1836 variable = (char *)variables[i].name;
1838 /* Discourage use of obsolete variables. */
1840 if(variables[i].type & VAR_OBSOLETE && action >= 0) {
1842 fprintf(stderr, "Warning: %s is an obsolete variable!\n", variable);
1844 fprintf(stderr, "%s is an obsolete variable! Use --force to use it anyway.\n", variable);
1849 /* Don't put server variables in host config files */
1851 if(node && !(variables[i].type & VAR_HOST) && action >= 0) {
1853 fprintf(stderr, "Warning: %s is not a host configuration variable!\n", variable);
1855 fprintf(stderr, "%s is not a host configuration variable! Use --force to use it anyway.\n", variable);
1860 /* Should this go into our own host config file? */
1862 if(!node && !(variables[i].type & VAR_SERVER)) {
1863 node = get_my_name(true);
1870 /* Change "add" into "set" for variables that do not allow multiple occurrences.
1871 Turn on warnings when it seems variables might be removed unintentionally. */
1873 if(action == 1 && !(variables[i].type & VAR_MULTIPLE)) {
1874 warnonremove = true;
1876 } else if(action == 0 && (variables[i].type & VAR_MULTIPLE)) {
1877 warnonremove = true;
1883 if(node && !check_id(node)) {
1884 fprintf(stderr, "Invalid name for node.\n");
1889 if(force || action < 0) {
1890 fprintf(stderr, "Warning: %s is not a known configuration variable!\n", variable);
1892 fprintf(stderr, "%s: is not a known configuration variable! Use --force to use it anyway.\n", variable);
1897 // Open the right configuration file.
1898 char filename[PATH_MAX];
1901 snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, node);
1903 snprintf(filename, sizeof(filename), "%s", tinc_conf);
1906 FILE *f = fopen(filename, "r");
1909 fprintf(stderr, "Could not open configuration file %s: %s\n", filename, strerror(errno));
1913 char tmpfile[PATH_MAX];
1917 snprintf(tmpfile, sizeof(tmpfile), "%s.config.tmp", filename);
1918 tf = fopen(tmpfile, "w");
1921 fprintf(stderr, "Could not open temporary file %s: %s\n", tmpfile, strerror(errno));
1927 // Copy the file, making modifications on the fly, unless we are just getting a value.
1931 bool removed = false;
1934 while(fgets(buf1, sizeof(buf1), f)) {
1935 buf1[sizeof(buf1) - 1] = 0;
1936 strncpy(buf2, buf1, sizeof(buf2));
1938 // Parse line in a simple way
1942 len = strcspn(buf2, "\t =");
1943 bvalue = buf2 + len;
1944 bvalue += strspn(bvalue, "\t ");
1946 if(*bvalue == '=') {
1948 bvalue += strspn(bvalue, "\t ");
1955 if(!strcasecmp(buf2, variable)) {
1959 printf("%s\n", bvalue);
1961 } else if(action == -1) {
1962 if(!*value || !strcasecmp(bvalue, value)) {
1968 } else if(action == 0) {
1969 // Warn if "set" was used for variables that can occur multiple times
1970 if(warnonremove && strcasecmp(bvalue, value)) {
1971 fprintf(stderr, "Warning: removing %s = %s\n", variable, bvalue);
1974 // Already set? Delete the rest...
1979 // Otherwise, replace.
1980 if(fprintf(tf, "%s = %s\n", variable, value) < 0) {
1981 fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno));
1988 } else if(action > 0) {
1989 // Check if we've already seen this variable with the same value
1990 if(!strcasecmp(bvalue, value)) {
1997 // Copy original line...
1998 if(fputs(buf1, tf) < 0) {
1999 fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno));
2003 // Add newline if it is missing...
2004 if(*buf1 && buf1[strlen(buf1) - 1] != '\n') {
2005 if(fputc('\n', tf) < 0) {
2006 fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno));
2013 // Make sure we read everything...
2014 if(ferror(f) || !feof(f)) {
2015 fprintf(stderr, "Error while reading from configuration file %s: %s\n", filename, strerror(errno));
2020 fprintf(stderr, "Error closing configuration file %s: %s\n", filename, strerror(errno));
2024 // Add new variable if necessary.
2025 if((action > 0 && !found) || (action == 0 && !set)) {
2026 if(fprintf(tf, "%s = %s\n", variable, value) < 0) {
2027 fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno));
2036 fprintf(stderr, "No matching configuration variables found.\n");
2041 // Make sure we wrote everything...
2043 fprintf(stderr, "Error closing temporary file %s: %s\n", tmpfile, strerror(errno));
2047 // Could we find what we had to remove?
2048 if(action < 0 && !removed) {
2050 fprintf(stderr, "No configuration variables deleted.\n");
2054 // Replace the configuration file with the new one
2057 if(remove(filename)) {
2058 fprintf(stderr, "Error replacing file %s: %s\n", filename, strerror(errno));
2064 if(rename(tmpfile, filename)) {
2065 fprintf(stderr, "Error renaming temporary file %s to configuration file %s: %s\n", tmpfile, filename, strerror(errno));
2069 // Silently try notifying a running tincd of changes.
2070 if(connect_tincd(false)) {
2071 sendline(fd, "%d %d", CONTROL, REQ_RELOAD);
2077 static bool try_bind(int port) {
2078 struct addrinfo *ai = NULL, *aip;
2079 struct addrinfo hint = {
2080 .ai_flags = AI_PASSIVE,
2081 .ai_family = AF_UNSPEC,
2082 .ai_socktype = SOCK_STREAM,
2083 .ai_protocol = IPPROTO_TCP,
2086 bool success = true;
2088 snprintf(portstr, sizeof(portstr), "%d", port);
2090 if(getaddrinfo(NULL, portstr, &hint, &ai) || !ai) {
2094 for(aip = ai; aip; aip = aip->ai_next) {
2095 int fd = socket(ai->ai_family, SOCK_STREAM, IPPROTO_TCP);
2102 int result = bind(fd, ai->ai_addr, ai->ai_addrlen);
2115 int check_port(char *name) {
2120 fprintf(stderr, "Warning: could not bind to port 655. ");
2122 for(int i = 0; i < 100; i++) {
2123 int port = 0x1000 + (rand() & 0x7fff);
2125 if(try_bind(port)) {
2126 char filename[PATH_MAX];
2127 snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", confbase, name);
2128 FILE *f = fopen(filename, "a");
2131 fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno));
2132 fprintf(stderr, "Please change tinc's Port manually.\n");
2136 fprintf(f, "Port = %d\n", port);
2138 fprintf(stderr, "Tinc will instead listen on port %d.\n", port);
2143 fprintf(stderr, "Please change tinc's Port manually.\n");
2147 static int cmd_init(int argc, char *argv[]) {
2148 if(!access(tinc_conf, F_OK)) {
2149 fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf);
2154 fprintf(stderr, "Too many arguments!\n");
2156 } else if(argc < 2) {
2159 fprintf(stderr, "Enter the Name you want your tinc node to have: ");
2161 if(!fgets(buf, sizeof(buf), stdin)) {
2162 fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
2166 int len = rstrip(buf);
2169 fprintf(stderr, "No name given!\n");
2175 fprintf(stderr, "No Name given!\n");
2179 name = strdup(argv[1]);
2182 fprintf(stderr, "No Name given!\n");
2187 if(!check_id(name)) {
2188 fprintf(stderr, "Invalid Name! Only a-z, A-Z, 0-9 and _ are allowed characters.\n");
2192 if(!confbase_given && mkdir(confdir, 0755) && errno != EEXIST) {
2193 fprintf(stderr, "Could not create directory %s: %s\n", confdir, strerror(errno));
2197 if(mkdir(confbase, 0777) && errno != EEXIST) {
2198 fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno));
2202 if(mkdir(hosts_dir, 0777) && errno != EEXIST) {
2203 fprintf(stderr, "Could not create directory %s: %s\n", hosts_dir, strerror(errno));
2207 FILE *f = fopen(tinc_conf, "w");
2210 fprintf(stderr, "Could not create file %s: %s\n", tinc_conf, strerror(errno));
2214 fprintf(f, "Name = %s\n", name);
2217 #ifndef DISABLE_LEGACY
2219 if(!rsa_keygen(2048, false)) {
2225 if(!ed25519_keygen(false)) {
2232 char filename[PATH_MAX];
2233 snprintf(filename, sizeof(filename), "%s" SLASH "tinc-up", confbase);
2235 if(access(filename, F_OK)) {
2236 FILE *f = fopenmask(filename, "w", 0777);
2239 fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno));
2243 fprintf(f, "#!/bin/sh\n\necho 'Unconfigured tinc-up script, please edit '$0'!'\n\n#ifconfig $INTERFACE <your vpn IP address> netmask <netmask of whole VPN>\n");
2253 static int cmd_generate_keys(int argc, char *argv[]) {
2254 #ifdef DISABLE_LEGACY
2261 fprintf(stderr, "Too many arguments!\n");
2266 name = get_my_name(false);
2269 #ifndef DISABLE_LEGACY
2271 if(!rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true)) {
2277 if(!ed25519_keygen(true)) {
2284 #ifndef DISABLE_LEGACY
2285 static int cmd_generate_rsa_keys(int argc, char *argv[]) {
2287 fprintf(stderr, "Too many arguments!\n");
2292 name = get_my_name(false);
2295 return !rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true);
2299 static int cmd_generate_ed25519_keys(int argc, char *argv[]) {
2301 fprintf(stderr, "Too many arguments!\n");
2306 name = get_my_name(false);
2309 return !ed25519_keygen(true);
2312 static int cmd_help(int argc, char *argv[]) {
2317 static int cmd_version(int argc, char *argv[]) {
2319 fprintf(stderr, "Too many arguments!\n");
2327 static int cmd_info(int argc, char *argv[]) {
2329 fprintf(stderr, "Invalid number of arguments.\n");
2333 if(!connect_tincd(true)) {
2337 return info(fd, argv[1]);
2340 static const char *conffiles[] = {
2351 static int cmd_edit(int argc, char *argv[]) {
2353 fprintf(stderr, "Invalid number of arguments.\n");
2357 char filename[PATH_MAX] = "";
2359 if(strncmp(argv[1], "hosts" SLASH, 6)) {
2360 for(int i = 0; conffiles[i]; i++) {
2361 if(!strcmp(argv[1], conffiles[i])) {
2362 snprintf(filename, sizeof(filename), "%s" SLASH "%s", confbase, argv[1]);
2371 snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, argv[1]);
2372 char *dash = strchr(argv[1], '-');
2377 if((strcmp(dash, "up") && strcmp(dash, "down")) || !check_id(argv[1])) {
2378 fprintf(stderr, "Invalid configuration filename.\n");
2386 xasprintf(&command, "\"%s\" \"%s\"", getenv("VISUAL") ? : getenv("EDITOR") ? : "vi", filename);
2388 xasprintf(&command, "edit \"%s\"", filename);
2390 int result = system(command);
2397 // Silently try notifying a running tincd of changes.
2398 if(connect_tincd(false)) {
2399 sendline(fd, "%d %d", CONTROL, REQ_RELOAD);
2405 static int export(const char *name, FILE *out) {
2406 char filename[PATH_MAX];
2407 snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, name);
2408 FILE *in = fopen(filename, "r");
2411 fprintf(stderr, "Could not open configuration file %s: %s\n", filename, strerror(errno));
2415 fprintf(out, "Name = %s\n", name);
2418 while(fgets(buf, sizeof(buf), in)) {
2419 if(strcspn(buf, "\t =") != 4 || strncasecmp(buf, "Name", 4)) {
2425 fprintf(stderr, "Error while reading configuration file %s: %s\n", filename, strerror(errno));
2434 static int cmd_export(int argc, char *argv[]) {
2436 fprintf(stderr, "Too many arguments!\n");
2440 char *name = get_my_name(true);
2446 int result = export(name, stdout);
2456 static int cmd_export_all(int argc, char *argv[]) {
2458 fprintf(stderr, "Too many arguments!\n");
2462 DIR *dir = opendir(hosts_dir);
2465 fprintf(stderr, "Could not open host configuration directory %s: %s\n", hosts_dir, strerror(errno));
2473 while((ent = readdir(dir))) {
2474 if(!check_id(ent->d_name)) {
2481 printf("#---------------------------------------------------------------#\n");
2484 result |= export(ent->d_name, stdout);
2496 static int cmd_import(int argc, char *argv[]) {
2498 fprintf(stderr, "Too many arguments!\n");
2507 char filename[PATH_MAX] = "";
2509 bool firstline = true;
2511 while(fgets(buf, sizeof(buf), in)) {
2512 if(sscanf(buf, "Name = %4095s", name) == 1) {
2515 if(!check_id(name)) {
2516 fprintf(stderr, "Invalid Name in input!\n");
2524 snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, name);
2526 if(!force && !access(filename, F_OK)) {
2527 fprintf(stderr, "Host configuration file %s already exists, skipping.\n", filename);
2532 out = fopen(filename, "w");
2535 fprintf(stderr, "Error creating configuration file %s: %s\n", filename, strerror(errno));
2541 } else if(firstline) {
2542 fprintf(stderr, "Junk at the beginning of the input, ignoring.\n");
2547 if(!strcmp(buf, "#---------------------------------------------------------------#\n")) {
2552 if(fputs(buf, out) < 0) {
2553 fprintf(stderr, "Error writing to host configuration file %s: %s\n", filename, strerror(errno));
2564 fprintf(stderr, "Imported %d host configuration files.\n", count);
2567 fprintf(stderr, "No host configuration files imported.\n");
2572 static int cmd_exchange(int argc, char *argv[]) {
2573 return cmd_export(argc, argv) ? : cmd_import(argc, argv);
2576 static int cmd_exchange_all(int argc, char *argv[]) {
2577 return cmd_export_all(argc, argv) ? : cmd_import(argc, argv);
2580 static int switch_network(char *name) {
2581 if(strcmp(name, ".")) {
2582 if(!check_netname(name, false)) {
2583 fprintf(stderr, "Invalid character in netname!\n");
2587 if(!check_netname(name, true)) {
2588 fprintf(stderr, "Warning: unsafe character in netname!\n");
2598 netname = strcmp(name, ".") ? xstrdup(name) : NULL;
2605 xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase);
2606 xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase);
2607 xasprintf(&prompt, "%s> ", identname);
2612 static int cmd_network(int argc, char *argv[]) {
2614 fprintf(stderr, "Too many arguments!\n");
2619 return switch_network(argv[1]);
2622 DIR *dir = opendir(confdir);
2625 fprintf(stderr, "Could not read directory %s: %s\n", confdir, strerror(errno));
2631 while((ent = readdir(dir))) {
2632 if(*ent->d_name == '.') {
2636 if(!strcmp(ent->d_name, "tinc.conf")) {
2641 char fname[PATH_MAX];
2642 snprintf(fname, sizeof(fname), "%s/%s/tinc.conf", confdir, ent->d_name);
2644 if(!access(fname, R_OK)) {
2645 printf("%s\n", ent->d_name);
2654 static int cmd_fsck(int argc, char *argv[]) {
2656 fprintf(stderr, "Too many arguments!\n");
2660 return fsck(orig_argv[0]);
2663 static void *readfile(FILE *in, size_t *len) {
2665 size_t alloced = 4096;
2666 char *buf = xmalloc(alloced);
2669 size_t read = fread(buf + count, 1, alloced - count, in);
2677 if(count >= alloced) {
2679 buf = xrealloc(buf, alloced);
2690 static int cmd_sign(int argc, char *argv[]) {
2692 fprintf(stderr, "Too many arguments!\n");
2697 name = get_my_name(true);
2704 char fname[PATH_MAX];
2705 snprintf(fname, sizeof(fname), "%s" SLASH "ed25519_key.priv", confbase);
2706 FILE *fp = fopen(fname, "r");
2709 fprintf(stderr, "Could not open %s: %s\n", fname, strerror(errno));
2713 ecdsa_t *key = ecdsa_read_pem_private_key(fp);
2716 fprintf(stderr, "Could not read private key from %s\n", fname);
2726 in = fopen(argv[1], "rb");
2729 fprintf(stderr, "Could not open %s: %s\n", argv[1], strerror(errno));
2738 char *data = readfile(in, &len);
2745 fprintf(stderr, "Error reading %s: %s\n", argv[1], strerror(errno));
2750 // Ensure we sign our name and current time as well
2751 long t = time(NULL);
2753 xasprintf(&trailer, " %s %ld", name, t);
2754 int trailer_len = strlen(trailer);
2756 data = xrealloc(data, len + trailer_len);
2757 memcpy(data + len, trailer, trailer_len);
2762 if(!ecdsa_sign(key, data, len + trailer_len, sig)) {
2763 fprintf(stderr, "Error generating signature\n");
2769 b64encode(sig, sig, 64);
2772 fprintf(stdout, "Signature = %s %ld %s\n", name, t, sig);
2773 fwrite(data, len, 1, stdout);
2779 static int cmd_verify(int argc, char *argv[]) {
2781 fprintf(stderr, "Not enough arguments!\n");
2786 fprintf(stderr, "Too many arguments!\n");
2790 char *node = argv[1];
2792 if(!strcmp(node, ".")) {
2794 name = get_my_name(true);
2802 } else if(!strcmp(node, "*")) {
2805 if(!check_id(node)) {
2806 fprintf(stderr, "Invalid node name\n");
2814 in = fopen(argv[2], "rb");
2817 fprintf(stderr, "Could not open %s: %s\n", argv[2], strerror(errno));
2825 char *data = readfile(in, &len);
2832 fprintf(stderr, "Error reading %s: %s\n", argv[1], strerror(errno));
2836 char *newline = memchr(data, '\n', len);
2838 if(!newline || (newline - data > MAX_STRING_SIZE - 1)) {
2839 fprintf(stderr, "Invalid input\n");
2845 size_t skip = newline - data;
2847 char signer[MAX_STRING_SIZE] = "";
2848 char sig[MAX_STRING_SIZE] = "";
2851 if(sscanf(data, "Signature = %s %ld %s", signer, &t, sig) != 3 || strlen(sig) != 86 || !t || !check_id(signer)) {
2852 fprintf(stderr, "Invalid input\n");
2857 if(node && strcmp(node, signer)) {
2858 fprintf(stderr, "Signature is not made by %s\n", node);
2868 xasprintf(&trailer, " %s %ld", signer, t);
2869 int trailer_len = strlen(trailer);
2871 data = xrealloc(data, len + trailer_len);
2872 memcpy(data + len, trailer, trailer_len);
2875 newline = data + skip;
2877 char fname[PATH_MAX];
2878 snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, node);
2879 FILE *fp = fopen(fname, "r");
2882 fprintf(stderr, "Could not open %s: %s\n", fname, strerror(errno));
2887 ecdsa_t *key = get_pubkey(fp);
2891 key = ecdsa_read_pem_public_key(fp);
2895 fprintf(stderr, "Could not read public key from %s\n", fname);
2903 if(b64decode(sig, sig, 86) != 64 || !ecdsa_verify(key, newline, len + trailer_len - (newline - data), sig)) {
2904 fprintf(stderr, "Invalid signature\n");
2912 fwrite(newline, len - (newline - data), 1, stdout);
2918 static const struct {
2919 const char *command;
2920 int (*function)(int argc, char *argv[]);
2923 {"start", cmd_start},
2925 {"restart", cmd_restart},
2926 {"reload", cmd_reload},
2929 {"purge", cmd_purge},
2930 {"debug", cmd_debug},
2931 {"retry", cmd_retry},
2932 {"connect", cmd_connect},
2933 {"disconnect", cmd_disconnect},
2938 {"config", cmd_config, true},
2939 {"add", cmd_config},
2940 {"del", cmd_config},
2941 {"get", cmd_config},
2942 {"set", cmd_config},
2944 {"generate-keys", cmd_generate_keys},
2945 #ifndef DISABLE_LEGACY
2946 {"generate-rsa-keys", cmd_generate_rsa_keys},
2948 {"generate-ed25519-keys", cmd_generate_ed25519_keys},
2950 {"version", cmd_version},
2953 {"export", cmd_export},
2954 {"export-all", cmd_export_all},
2955 {"import", cmd_import},
2956 {"exchange", cmd_exchange},
2957 {"exchange-all", cmd_exchange_all},
2958 {"invite", cmd_invite},
2960 {"network", cmd_network},
2963 {"verify", cmd_verify},
2967 #ifdef HAVE_READLINE
2968 static char *complete_command(const char *text, int state) {
2977 while(commands[i].command) {
2978 if(!commands[i].hidden && !strncasecmp(commands[i].command, text, strlen(text))) {
2979 return xstrdup(commands[i].command);
2988 static char *complete_dump(const char *text, int state) {
2989 const char *matches[] = {"reachable", "nodes", "edges", "subnets", "connections", "graph", NULL};
2999 if(!strncasecmp(matches[i], text, strlen(text))) {
3000 return xstrdup(matches[i]);
3009 static char *complete_config(const char *text, int state) {
3018 while(variables[i].name) {
3019 char *dot = strchr(text, '.');
3022 if((variables[i].type & VAR_HOST) && !strncasecmp(variables[i].name, dot + 1, strlen(dot + 1))) {
3024 xasprintf(&match, "%.*s.%s", (int)(dot - text), text, variables[i].name);
3028 if(!strncasecmp(variables[i].name, text, strlen(text))) {
3029 return xstrdup(variables[i].name);
3039 static char *complete_info(const char *text, int state) {
3045 if(!connect_tincd(false)) {
3049 // Check the list of nodes
3050 sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES);
3051 sendline(fd, "%d %d", CONTROL, REQ_DUMP_SUBNETS);
3054 while(recvline(fd, line, sizeof(line))) {
3056 int n = sscanf(line, "%d %d %4095s", &code, &req, item);
3069 fprintf(stderr, "Unable to parse dump from tincd, n = %d, i = %d.\n", n, i);
3073 if(!strncmp(item, text, strlen(text))) {
3074 return xstrdup(strip_weight(item));
3081 static char *complete_nothing(const char *text, int state) {
3085 static char **completion(const char *text, int start, int end) {
3086 char **matches = NULL;
3089 matches = rl_completion_matches(text, complete_command);
3090 } else if(!strncasecmp(rl_line_buffer, "dump ", 5)) {
3091 matches = rl_completion_matches(text, complete_dump);
3092 } else if(!strncasecmp(rl_line_buffer, "add ", 4)) {
3093 matches = rl_completion_matches(text, complete_config);
3094 } else if(!strncasecmp(rl_line_buffer, "del ", 4)) {
3095 matches = rl_completion_matches(text, complete_config);
3096 } else if(!strncasecmp(rl_line_buffer, "get ", 4)) {
3097 matches = rl_completion_matches(text, complete_config);
3098 } else if(!strncasecmp(rl_line_buffer, "set ", 4)) {
3099 matches = rl_completion_matches(text, complete_config);
3100 } else if(!strncasecmp(rl_line_buffer, "info ", 5)) {
3101 matches = rl_completion_matches(text, complete_info);
3108 static int cmd_shell(int argc, char *argv[]) {
3109 xasprintf(&prompt, "%s> ", identname);
3113 int maxargs = argc + 16;
3114 char **nargv = xmalloc(maxargs * sizeof(*nargv));
3116 for(int i = 0; i < argc; i++) {
3120 #ifdef HAVE_READLINE
3121 rl_readline_name = "tinc";
3122 rl_completion_entry_function = complete_nothing;
3123 rl_attempted_completion_function = completion;
3124 rl_filename_completion_desired = 0;
3129 #ifdef HAVE_READLINE
3134 rl_basic_word_break_characters = "\t\n ";
3135 line = readline(prompt);
3138 copy = xstrdup(line);
3141 line = fgets(buf, sizeof(buf), stdin);
3147 fputs(prompt, stdout);
3150 line = fgets(buf, sizeof(buf), stdin);
3157 /* Ignore comments */
3166 char *p = line + strspn(line, " \t\n");
3167 char *next = strtok(p, " \t\n");
3170 if(nargc >= maxargs) {
3172 nargv = xrealloc(nargv, maxargs * sizeof(*nargv));
3177 next = strtok(NULL, " \t\n");
3184 if(!strcasecmp(nargv[argc], "exit") || !strcasecmp(nargv[argc], "quit")) {
3191 for(int i = 0; commands[i].command; i++) {
3192 if(!strcasecmp(nargv[argc], commands[i].command)) {
3193 result |= commands[i].function(nargc - argc - 1, nargv + argc + 1);
3199 #ifdef HAVE_READLINE
3208 fprintf(stderr, "Unknown command `%s'.\n", nargv[argc]);
3223 int main(int argc, char *argv[]) {
3224 program_name = argv[0];
3227 tty = isatty(0) && isatty(1);
3229 if(!parse_options(argc, argv)) {
3234 xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase);
3235 xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase);
3248 static struct WSAData wsa_state;
3250 if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) {
3251 fprintf(stderr, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError()));
3260 if(optind >= argc) {
3261 return cmd_shell(argc, argv);
3264 for(int i = 0; commands[i].command; i++) {
3265 if(!strcasecmp(argv[optind], commands[i].command)) {
3266 return commands[i].function(argc - optind, argv + optind);
3270 fprintf(stderr, "Unknown command `%s'.\n", argv[optind]);