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