Only call ioctlsocket() on Windows.
[tinc] / src / tincctl.c
1 /*
2     tincctl.c -- Controlling a running tincd
3     Copyright (C) 2007-2009 Guus Sliepen <guus@tinc-vpn.org>
4
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.
9
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.
14
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.
18 */
19
20 #include "system.h"
21
22 #include <getopt.h>
23
24 #include "xalloc.h"
25 #include "protocol.h"
26 #include "control_common.h"
27 #include "rsagen.h"
28 #include "utils.h"
29
30 /* The name this program was run with. */
31 char *program_name = NULL;
32
33 /* If nonzero, display usage information and exit. */
34 bool show_help = false;
35
36 /* If nonzero, print the version on standard output and exit.  */
37 bool show_version = false;
38
39 /* If nonzero, it will attempt to kill a running tincd and exit. */
40 int kill_tincd = 0;
41
42 /* If nonzero, generate public/private keypair for this host/net. */
43 int generate_keys = 0;
44
45 static char *name = NULL;
46 static char *identname = NULL;                          /* program name for syslog */
47 static char *controlcookiename = NULL;                  /* cookie file location */
48 static char controlcookie[1024];
49 char *netname = NULL;
50 char *confbase = NULL;
51
52 #ifdef HAVE_MINGW
53 static struct WSAData wsa_state;
54 #endif
55
56 static struct option const long_options[] = {
57         {"config", required_argument, NULL, 'c'},
58         {"net", required_argument, NULL, 'n'},
59         {"help", no_argument, NULL, 1},
60         {"version", no_argument, NULL, 2},
61         {"controlsocket", required_argument, NULL, 5},
62         {NULL, 0, NULL, 0}
63 };
64
65 static void usage(bool status) {
66         if(status)
67                 fprintf(stderr, "Try `%s --help\' for more information.\n",
68                                 program_name);
69         else {
70                 printf("Usage: %s [options] command\n\n", program_name);
71                 printf("Valid options are:\n"
72                                 "  -c, --config=DIR              Read configuration options from DIR.\n"
73                                 "  -n, --net=NETNAME             Connect to net NETNAME.\n"
74                                 "      --controlcookie=FILENAME  Read control socket from FILENAME.\n"
75                                 "      --help                    Display this help and exit.\n"
76                                 "      --version                 Output version information and exit.\n"
77                                 "\n"
78                                 "Valid commands are:\n"
79                                 "  start                      Start tincd.\n"
80                                 "  stop                       Stop tincd.\n"
81                                 "  restart                    Restart tincd.\n"
82                                 "  reload                     Reload configuration of running tincd.\n"
83                                 "  pid                        Show PID of currently running tincd.\n"
84                                 "  generate-keys [bits]       Generate a new public/private keypair.\n"
85                                 "  dump                       Dump a list of one of the following things:\n"
86                                 "    nodes                    - all known nodes in the VPN\n"
87                                 "    edges                    - all known connections in the VPN\n"
88                                 "    subnets                  - all known subnets in the VPN\n"
89                                 "    connections              - all meta connections with ourself\n"
90                                 "    graph                    - graph of the VPN in dotty format\n"
91                                 "  purge                      Purge unreachable nodes\n"
92                                 "  debug N                    Set debug level\n"
93                                 "  retry                      Retry all outgoing connections\n"
94                                 "  reload                     Partial reload of configuration\n"
95                                 "\n");
96                 printf("Report bugs to tinc@tinc-vpn.org.\n");
97         }
98 }
99
100 static bool parse_options(int argc, char **argv) {
101         int r;
102         int option_index = 0;
103
104         while((r = getopt_long(argc, argv, "c:n:", long_options, &option_index)) != EOF) {
105                 switch (r) {
106                         case 0:                         /* long option */
107                                 break;
108
109                         case 'c':                               /* config file */
110                                 confbase = xstrdup(optarg);
111                                 break;
112
113                         case 'n':                               /* net name given */
114                                 netname = xstrdup(optarg);
115                                 break;
116
117                         case 1:                                 /* show help */
118                                 show_help = true;
119                                 break;
120
121                         case 2:                                 /* show version */
122                                 show_version = true;
123                                 break;
124
125                         case 5:                                 /* open control socket here */
126                                 controlcookiename = xstrdup(optarg);
127                                 break;
128
129                         case '?':
130                                 usage(true);
131                                 return false;
132
133                         default:
134                                 break;
135                 }
136         }
137
138         return true;
139 }
140
141 FILE *ask_and_open(const char *filename, const char *what, const char *mode) {
142         FILE *r;
143         char *directory;
144         char buf[PATH_MAX];
145         char buf2[PATH_MAX];
146         size_t len;
147
148         /* Check stdin and stdout */
149         if(isatty(0) && isatty(1)) {
150                 /* Ask for a file and/or directory name. */
151                 fprintf(stdout, "Please enter a file to save %s to [%s]: ",
152                                 what, filename);
153                 fflush(stdout);
154
155                 if(fgets(buf, sizeof buf, stdin) < 0) {
156                         fprintf(stderr, "Error while reading stdin: %s\n",
157                                         strerror(errno));
158                         return NULL;
159                 }
160
161                 len = strlen(buf);
162                 if(len)
163                         buf[--len] = 0;
164
165                 if(len)
166                         filename = buf;
167         }
168
169 #ifdef HAVE_MINGW
170         if(filename[0] != '\\' && filename[0] != '/' && !strchr(filename, ':')) {
171 #else
172         if(filename[0] != '/') {
173 #endif
174                 /* The directory is a relative path or a filename. */
175                 directory = get_current_dir_name();
176                 snprintf(buf2, sizeof buf2, "%s/%s", directory, filename);
177                 filename = buf2;
178         }
179
180         umask(0077);                            /* Disallow everything for group and other */
181
182         /* Open it first to keep the inode busy */
183
184         r = fopen(filename, mode);
185
186         if(!r) {
187                 fprintf(stderr, "Error opening file `%s': %s\n", filename, strerror(errno));
188                 return NULL;
189         }
190
191         return r;
192 }
193
194 /*
195   Generate a public/private RSA keypair, and ask for a file to store
196   them in.
197 */
198 static bool keygen(int bits) {
199         rsa_t key;
200         FILE *f;
201         char *filename;
202
203         fprintf(stderr, "Generating %d bits keys:\n", bits);
204
205         if(!rsa_generate(&key, bits, 0x10001)) {
206                 fprintf(stderr, "Error during key generation!\n");
207                 return false;
208         } else
209                 fprintf(stderr, "Done.\n");
210
211         xasprintf(&filename, "%s/rsa_key.priv", confbase);
212         f = ask_and_open(filename, "private RSA key", "a");
213
214         if(!f)
215                 return false;
216   
217 #ifdef HAVE_FCHMOD
218         /* Make it unreadable for others. */
219         fchmod(fileno(f), 0600);
220 #endif
221                 
222         if(ftell(f))
223                 fprintf(stderr, "Appending key to existing contents.\nMake sure only one key is stored in the file.\n");
224
225         rsa_write_pem_private_key(&key, f);
226
227         fclose(f);
228         free(filename);
229
230         if(name)
231                 xasprintf(&filename, "%s/hosts/%s", confbase, name);
232         else
233                 xasprintf(&filename, "%s/rsa_key.pub", confbase);
234
235         f = ask_and_open(filename, "public RSA key", "a");
236
237         if(!f)
238                 return false;
239
240         if(ftell(f))
241                 fprintf(stderr, "Appending key to existing contents.\nMake sure only one key is stored in the file.\n");
242
243         rsa_write_pem_public_key(&key, f);
244
245         fclose(f);
246         free(filename);
247
248         return true;
249 }
250
251 /*
252   Set all files and paths according to netname
253 */
254 static void make_names(void) {
255 #ifdef HAVE_MINGW
256         HKEY key;
257         char installdir[1024] = "";
258         long len = sizeof installdir;
259 #endif
260
261         if(netname)
262                 xasprintf(&identname, "tinc.%s", netname);
263         else
264                 identname = xstrdup("tinc");
265
266 #ifdef HAVE_MINGW
267         if(!RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\tinc", 0, KEY_READ, &key)) {
268                 if(!RegQueryValueEx(key, NULL, 0, 0, installdir, &len)) {
269                         if(!confbase) {
270                                 if(netname)
271                                         xasprintf(&confbase, "%s/%s", installdir, netname);
272                                 else
273                                         xasprintf(&confbase, "%s", installdir);
274                         }
275                 }
276                 if(!controlcookiename)
277                         xasprintf(&controlcookiename, "%s/cookie", confbase);
278                 RegCloseKey(key);
279                 if(*installdir)
280                         return;
281         }
282 #endif
283
284         if(!controlcookiename)
285                 xasprintf(&controlcookiename, "%s/run/%s.control/socket", LOCALSTATEDIR, identname);
286
287         if(netname) {
288                 if(!confbase)
289                         xasprintf(&confbase, CONFDIR "/tinc/%s", netname);
290                 else
291                         fprintf(stderr, "Both netname and configuration directory given, using the latter...\n");
292         } else {
293                 if(!confbase)
294                         xasprintf(&confbase, CONFDIR "/tinc");
295         }
296 }
297
298 static bool recvline(int fd, char *line, size_t len) {
299         static char buffer[4096];
300         static size_t blen = 0;
301         char *newline = NULL;
302
303         while(!(newline = memchr(buffer, '\n', blen))) {
304                 int result = recv(fd, buffer + blen, sizeof buffer - blen, 0);
305                 if(result == -1 && errno == EINTR)
306                         continue;
307                 else if(result <= 0)
308                         return false;
309                 blen += result;
310         }
311
312         if(newline - buffer >= len)
313                 return false;
314
315         len = newline - buffer;
316
317         memcpy(line, buffer, len);
318         line[len] = 0;
319         memmove(buffer, newline + 1, blen - len - 1);
320         blen -= len + 1;
321
322         return true;
323 }
324
325 static bool sendline(int fd, char *format, ...) {
326         static char buffer[4096];
327         char *p = buffer;
328         size_t blen = 0;
329         va_list ap;
330
331         va_start(ap, format);
332         blen = vsnprintf(buffer, sizeof buffer, format, ap);
333         va_end(ap);
334
335         if(blen < 0 || blen >= sizeof buffer)
336                 return false;
337
338         buffer[blen] = '\n';
339         blen++;
340
341         while(blen) {
342                 int result = send(fd, p, blen, 0);
343                 if(result == -1 && errno == EINTR)
344                         continue;
345                 else if(result <= 0);
346                         return false;
347                 p += result;
348                 blen -= result;
349         }
350
351         return true;    
352 }
353
354 int main(int argc, char *argv[], char *envp[]) {
355         int fd;
356         int result;
357         int port;
358         int pid;
359
360         program_name = argv[0];
361
362         if(!parse_options(argc, argv))
363                 return 1;
364         
365         make_names();
366
367         if(show_version) {
368                 printf("%s version %s (built %s %s, protocol %d)\n", PACKAGE,
369                            VERSION, __DATE__, __TIME__, PROT_CURRENT);
370                 printf("Copyright (C) 1998-2009 Ivo Timmermans, Guus Sliepen and others.\n"
371                                 "See the AUTHORS file for a complete list.\n\n"
372                                 "tinc comes with ABSOLUTELY NO WARRANTY.  This is free software,\n"
373                                 "and you are welcome to redistribute it under certain conditions;\n"
374                                 "see the file COPYING for details.\n");
375
376                 return 0;
377         }
378
379         if(show_help) {
380                 usage(false);
381                 return 0;
382         }
383
384         if(optind >= argc) {
385                 fprintf(stderr, "Not enough arguments.\n");
386                 usage(true);
387                 return 1;
388         }
389
390         // First handle commands that don't involve connecting to a running tinc daemon.
391
392         if(!strcasecmp(argv[optind], "generate-keys")) {
393                 return !keygen(optind > argc ? atoi(argv[optind + 1]) : 2048);
394         }
395
396         if(!strcasecmp(argv[optind], "start")) {
397                 argv[optind] = NULL;
398                 execve(SBINDIR "/tincd", argv, envp);
399                 fprintf(stderr, "Could not start tincd: %s", strerror(errno));
400                 return 1;
401         }
402
403         /*
404          * Now handle commands that do involve connecting to a running tinc daemon.
405          * Authenticate the server by ensuring the parent directory can be
406          * traversed only by root. Note this is not totally race-free unless all
407          * ancestors are writable only by trusted users, which we don't verify.
408          */
409
410         FILE *f = fopen(controlcookiename, "r");
411         if(!f) {
412                 fprintf(stderr, "Could not open control socket cookie file %s: %s\n", controlcookiename, strerror(errno));
413                 return 1;
414         }
415         if(fscanf(f, "%1024s %d %d", controlcookie, &port, &pid) != 3) {
416                 fprintf(stderr, "Could not parse control socket cookie file %s\n", controlcookiename);
417                 return 1;
418         }
419
420 #ifdef HAVE_MINGW
421         if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) {
422                 fprintf(stderr, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError()));
423                 return 1;
424         }
425 #endif
426
427         struct sockaddr_in addr;
428         memset(&addr, 0, sizeof addr);
429         addr.sin_family = AF_INET;
430         addr.sin_addr.s_addr = htonl(0x7f000001);
431         addr.sin_port = htons(port);
432
433         fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
434         if(fd < 0) {
435                 fprintf(stderr, "Cannot create TCP socket: %s\n", sockstrerror(sockerrno));
436                 return 1;
437         }
438
439 #ifdef HAVE_MINGW
440         unsigned long arg = 0;
441
442         if(ioctlsocket(fd, FIONBIO, &arg) != 0) {
443                 fprintf(stderr, "ioctlsocket failed: %s", sockstrerror(sockerrno));
444         }
445 #endif
446
447         if(connect(fd, (struct sockaddr *)&addr, sizeof addr) < 0) {
448                         
449                 fprintf(stderr, "Cannot connect to %s: %s\n", controlcookiename, sockstrerror(sockerrno));
450                 return 1;
451         }
452
453         char line[4096];
454         char data[4096];
455         int code, version, req;
456
457         if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %s %d", &code, data, &version) != 3 || code != 0) {
458                 fprintf(stderr, "Cannot read greeting from control socket: %s\n",
459                                 sockstrerror(sockerrno));
460                 return 1;
461         }
462
463         sendline(fd, "%d ^%s %d", ID, controlcookie, TINC_CTL_VERSION_CURRENT);
464         
465         if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &version, &pid) != 3 || code != 4 || version != TINC_CTL_VERSION_CURRENT) {
466                 fprintf(stderr, "Could not fully establish control socket connection\n");
467                 return 1;
468         }
469
470         if(!strcasecmp(argv[optind], "pid")) {
471                 printf("%d\n", pid);
472                 return 0;
473         }
474
475         if(!strcasecmp(argv[optind], "stop")) {
476                 sendline(fd, "%d %d", CONTROL, REQ_STOP);
477                 if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_STOP || result) {
478                         fprintf(stderr, "Could not stop tinc daemon\n");
479                         return 1;
480                 }
481                 return 0;
482         }
483
484         if(!strcasecmp(argv[optind], "reload")) {
485                 sendline(fd, "%d %d", CONTROL, REQ_RELOAD);
486                 if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RELOAD || result) {
487                         fprintf(stderr, "Could not reload tinc daemon\n");
488                         return 1;
489                 }
490                 return 0;
491         }
492
493         if(!strcasecmp(argv[optind], "restart")) {
494                 sendline(fd, "%d %d", CONTROL, REQ_RESTART);
495                 if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RESTART || result) {
496                         fprintf(stderr, "Could not restart tinc daemon\n");
497                         return 1;
498                 }
499                 return 0;
500         }
501
502         if(!strcasecmp(argv[optind], "retry")) {
503                 sendline(fd, "%d %d", CONTROL, REQ_RETRY);
504                 if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RETRY || result) {
505                         fprintf(stderr, "Could not retry outgoing connections\n");
506                         return 1;
507                 }
508                 return 0;
509         }
510
511         if(!strcasecmp(argv[optind], "dump")) {
512                 if(argc < optind + 2) {
513                         fprintf(stderr, "Not enough arguments.\n");
514                         usage(true);
515                         return 1;
516                 }
517
518                 bool do_graph = false;
519                 int dumps = 1;
520
521                 if(!strcasecmp(argv[optind+1], "nodes"))
522                         sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES);
523                 else if(!strcasecmp(argv[optind+1], "edges"))
524                         sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES);
525                 else if(!strcasecmp(argv[optind+1], "subnets"))
526                         sendline(fd, "%d %d", CONTROL, REQ_DUMP_SUBNETS);
527                 else if(!strcasecmp(argv[optind+1], "connections"))
528                         sendline(fd, "%d %d", CONTROL, REQ_DUMP_CONNECTIONS);
529                 else if(!strcasecmp(argv[optind+1], "graph")) {
530                         sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES);
531                         sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES);
532                         do_graph = true;
533                         dumps = 2;
534                         printf("digraph {\n");
535                 } else {
536                         fprintf(stderr, "Unknown dump type '%s'.\n", argv[optind+1]);
537                         usage(true);
538                         return 1;
539                 }
540
541                 while(recvline(fd, line, sizeof line)) {
542                         char node1[4096], node2[4096];
543                         int n = sscanf(line, "%d %d %s to %s", &code, &req, &node1, &node2);
544                         if(n == 2) {
545                                 if(do_graph && req == REQ_DUMP_NODES)
546                                         continue;
547                                 else {
548                                         if(do_graph)
549                                                 printf("}\n");
550                                         return 0;
551                                 }
552                         }
553                         if(n < 2)
554                                 break;
555
556                         if(!do_graph)
557                                 printf("%s\n", line + 5);
558                         else {
559                                 if(req == REQ_DUMP_NODES)
560                                         printf(" %s [label = \"%s\"];\n", node1, node1);
561                                 else
562                                         printf(" %s -> %s;\n", node1, node2);
563                         }
564                 }
565
566                 fprintf(stderr, "Error receiving dump\n");
567                 return 1;
568         }
569
570         if(!strcasecmp(argv[optind], "purge")) {
571                 sendline(fd, "%d %d", CONTROL, REQ_PURGE);
572                 if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_PURGE || result) {
573                         fprintf(stderr, "Could not purge tinc daemon\n");
574                         return 1;
575                 }
576                 return 0;
577         }
578
579         if(!strcasecmp(argv[optind], "debug")) {
580                 int debuglevel, origlevel;
581
582                 if(argc != optind + 2) {
583                         fprintf(stderr, "Invalid arguments.\n");
584                         return 1;
585                 }
586                 debuglevel = atoi(argv[optind+1]);
587
588                 sendline(fd, "%d %d %d", CONTROL, REQ_SET_DEBUG, debuglevel);
589                 if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_SET_DEBUG) {
590                         fprintf(stderr, "Could not purge tinc daemon\n");
591                         return 1;
592                 }
593
594                 fprintf(stderr, "Old level %d, new level %d\n", origlevel, debuglevel);
595                 return 0;
596         }
597
598         fprintf(stderr, "Unknown command `%s'.\n", argv[optind]);
599         usage(true);
600         
601         close(fd);
602
603         return 0;
604 }