Wipe (some) secrets from memory after use
[tinc] / src / invitation.c
1 /*
2     invitation.c -- Create and accept invitations
3     Copyright (C) 2013-2022 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 "control_common.h"
23 #include "crypto.h"
24 #include "ecdsa.h"
25 #include "ecdsagen.h"
26 #include "ifconfig.h"
27 #include "invitation.h"
28 #include "names.h"
29 #include "netutl.h"
30 #include "rsagen.h"
31 #include "script.h"
32 #include "sptps.h"
33 #include "subnet.h"
34 #include "tincctl.h"
35 #include "utils.h"
36 #include "xalloc.h"
37 #include "random.h"
38 #include "pidfile.h"
39
40 #include "ed25519/sha512.h"
41
42 int addressfamily = AF_UNSPEC;
43
44 static void scan_for_hostname(const char *filename, char **hostname, char **port) {
45         if(!filename || (*hostname && *port)) {
46                 return;
47         }
48
49         FILE *f = fopen(filename, "r");
50
51         if(!f) {
52                 return;
53         }
54
55         while(fgets(line, sizeof(line), f)) {
56                 if(!rstrip(line)) {
57                         continue;
58                 }
59
60                 char *p = line, *q;
61                 p += strcspn(p, "\t =");
62
63                 if(!*p) {
64                         continue;
65                 }
66
67                 q = p + strspn(p, "\t ");
68
69                 if(*q == '=') {
70                         q += 1 + strspn(q + 1, "\t ");
71                 }
72
73                 *p = 0;
74                 p = q + strcspn(q, "\t ");
75
76                 if(*p) {
77                         *p++ = 0;
78                 }
79
80                 p += strspn(p, "\t ");
81                 p[strcspn(p, "\t ")] = 0;
82
83                 if(!*port && !strcasecmp(line, "Port")) {
84                         free(*port);
85                         *port = xstrdup(q);
86                 } else if(!*hostname && !strcasecmp(line, "Address")) {
87                         free(*hostname);
88                         *hostname = xstrdup(q);
89
90                         if(*p) {
91                                 free(*port);
92                                 *port = xstrdup(p);
93                         }
94                 }
95
96                 if(*hostname && *port) {
97                         break;
98                 }
99         }
100
101         fclose(f);
102 }
103
104 static bool get_my_hostname(char **out_address, char **out_port) {
105         char *hostname = NULL;
106         char *port = NULL;
107         char *hostport = NULL;
108         char *name = get_my_name(false);
109         char filename[PATH_MAX] = {0};
110
111         // Use first Address statement in own host config file
112         if(check_id(name)) {
113                 snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", confbase, name);
114                 scan_for_hostname(filename, &hostname, &port);
115                 scan_for_hostname(tinc_conf, &hostname, &port);
116         }
117
118         free(name);
119         name = NULL;
120
121         if(!port || (is_decimal(port) && atoi(port) == 0)) {
122                 pidfile_t *pidfile = read_pidfile();
123
124                 if(pidfile) {
125                         free(port);
126                         port = xstrdup(pidfile->port);
127                         free(pidfile);
128                 } else {
129                         fprintf(stderr, "tincd is using a dynamic port and is not running. Please start tincd or set the Port option to a non-zero value.\n");
130                         goto exit;
131                 }
132         }
133
134         if(hostname) {
135                 goto done;
136         }
137
138         // If that doesn't work, guess externally visible hostname
139         fprintf(stderr, "Trying to discover externally visible hostname...\n");
140         struct addrinfo *ai = str2addrinfo("tinc-vpn.org", "80", SOCK_STREAM);
141         struct addrinfo *aip = ai;
142         static const char request[] = "GET http://tinc-vpn.org/host.cgi HTTP/1.0\r\n\r\n";
143
144         while(aip) {
145                 int s = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol);
146
147                 if(s >= 0) {
148                         if(connect(s, aip->ai_addr, aip->ai_addrlen)) {
149                                 closesocket(s);
150                                 s = -1;
151                         }
152                 }
153
154                 if(s >= 0) {
155                         send(s, request, sizeof(request) - 1, 0);
156                         ssize_t len = recv(s, line, sizeof(line) - 1, MSG_WAITALL);
157
158                         if(len > 0) {
159                                 line[len] = 0;
160
161                                 if(line[len - 1] == '\n') {
162                                         line[--len] = 0;
163                                 }
164
165                                 char *p = strrchr(line, '\n');
166
167                                 if(p && p[1]) {
168                                         hostname = xstrdup(p + 1);
169                                 }
170                         }
171
172                         closesocket(s);
173
174                         if(hostname) {
175                                 break;
176                         }
177                 }
178
179                 aip = aip->ai_next;
180                 continue;
181         }
182
183         if(ai) {
184                 freeaddrinfo(ai);
185         }
186
187         // Check that the hostname is reasonable
188         if(hostname) {
189                 for(char *p = hostname; *p; p++) {
190                         if(isalnum((uint8_t) *p) || *p == '-' || *p == '.' || *p == ':') {
191                                 continue;
192                         }
193
194                         // If not, forget it.
195                         free(hostname);
196                         hostname = NULL;
197                         break;
198                 }
199         }
200
201         if(!tty) {
202                 if(!hostname) {
203                         fprintf(stderr, "Could not determine the external address or hostname. Please set Address manually.\n");
204                         free(port);
205                         return false;
206                 }
207
208                 goto save;
209         }
210
211 again:
212         fprintf(stderr, "Please enter your host's external address or hostname");
213
214         if(hostname) {
215                 fprintf(stderr, " [%s]", hostname);
216         }
217
218         fprintf(stderr, ": ");
219
220         if(!fgets(line, sizeof(line), stdin)) {
221                 fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
222                 free(hostname);
223                 free(port);
224                 return false;
225         }
226
227         if(!rstrip(line)) {
228                 if(hostname) {
229                         goto save;
230                 } else {
231                         goto again;
232                 }
233         }
234
235         for(char *p = line; *p; p++) {
236                 if(isalnum((uint8_t) *p) || *p == '-' || *p == '.') {
237                         continue;
238                 }
239
240                 fprintf(stderr, "Invalid address or hostname.\n");
241                 goto again;
242         }
243
244         free(hostname);
245         hostname = xstrdup(line);
246
247 save:
248
249         if(*filename) {
250                 FILE *f = fopen(filename, "a");
251
252                 if(f) {
253                         fprintf(f, "\nAddress = %s\n", hostname);
254                         fclose(f);
255                 } else {
256                         fprintf(stderr, "Could not append Address to %s: %s\n", filename, strerror(errno));
257                 }
258         }
259
260 done:
261
262         if(port) {
263                 if(strchr(hostname, ':')) {
264                         xasprintf(&hostport, "[%s]:%s", hostname, port);
265                 } else {
266                         xasprintf(&hostport, "%s:%s", hostname, port);
267                 }
268         } else {
269                 if(strchr(hostname, ':')) {
270                         xasprintf(&hostport, "[%s]", hostname);
271                 } else {
272                         hostport = xstrdup(hostname);
273                 }
274         }
275
276 exit:
277         free(hostname);
278
279         if(hostport && port) {
280                 *out_address = hostport;
281                 *out_port = port;
282                 return true;
283         }
284
285         free(hostport);
286         free(port);
287         return false;
288 }
289
290 // Copy host configuration file, replacing Port with the value passed here. Host
291 // configs may contain this clause: `Port = 0`, which means 'ask the operating
292 // system to allocate any available port'. This obviously won't do for invitation
293 // files, so replace it with an actual port we've obtained previously.
294 static bool copy_config_replacing_port(FILE *out, const char *filename, const char *port) {
295         FILE *in = fopen(filename, "r");
296
297         if(!in) {
298                 fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno));
299                 return false;
300         }
301
302         char line[1024];
303
304         while(fgets(line, sizeof(line), in)) {
305                 const char *var_beg = line + strspn(line, "\t ");
306                 const char *var_end = var_beg + strcspn(var_beg, "\t ");
307
308                 // Check the name of the variable we've read. If it's Port, replace it with
309                 // a port we'll use in invitation URL. Otherwise, just copy the line.
310                 if(var_end > var_beg && !strncasecmp(var_beg, "Port", var_end - var_beg)) {
311                         fprintf(out, "Port = %s\n", port);
312                 } else {
313                         fprintf(out, "%s", line);
314                 }
315         }
316
317         memzero(line, sizeof(line));
318         fclose(in);
319         return true;
320 }
321
322 static bool append_host_config(FILE *f, const char *nodename, const char *port) {
323         char path[PATH_MAX];
324         snprintf(path, sizeof(path), "%s" SLASH "hosts" SLASH "%s", confbase, nodename);
325         bool success = copy_config_replacing_port(f, path, port);
326         fclose(f);
327         return success;
328 }
329
330 int cmd_invite(int argc, char *argv[]) {
331         if(argc < 2) {
332                 fprintf(stderr, "Not enough arguments!\n");
333                 return 1;
334         }
335
336         // Check validity of the new node's name
337         if(!check_id(argv[1])) {
338                 fprintf(stderr, "Invalid name for node.\n");
339                 return 1;
340         }
341
342         free(myname);
343         myname = get_my_name(true);
344
345         if(!myname) {
346                 return 1;
347         }
348
349         // Ensure no host configuration file with that name exists
350         char filename[PATH_MAX];
351         snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", confbase, argv[1]);
352
353         if(!access(filename, F_OK)) {
354                 fprintf(stderr, "A host config file for %s already exists!\n", argv[1]);
355                 return 1;
356         }
357
358         // If a daemon is running, ensure no other nodes know about this name
359         if(connect_tincd(false)) {
360                 bool found = false;
361                 sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES);
362
363                 while(recvline(fd, line, sizeof(line))) {
364                         char node[4096];
365                         int code, req;
366
367                         if(sscanf(line, "%d %d %4095s", &code, &req, node) != 3) {
368                                 break;
369                         }
370
371                         if(!strcmp(node, argv[1])) {
372                                 found = true;
373                         }
374                 }
375
376                 if(found) {
377                         fprintf(stderr, "A node with name %s is already known!\n", argv[1]);
378                         return 1;
379                 }
380         }
381
382         snprintf(filename, sizeof(filename), "%s" SLASH "invitations", confbase);
383
384         if(mkdir(filename, 0700) && errno != EEXIST) {
385                 fprintf(stderr, "Could not create directory %s: %s\n", filename, strerror(errno));
386                 return 1;
387         }
388
389         // Count the number of valid invitations, clean up old ones
390         DIR *dir = opendir(filename);
391
392         if(!dir) {
393                 fprintf(stderr, "Could not read directory %s: %s\n", filename, strerror(errno));
394                 return 1;
395         }
396
397         errno = 0;
398         int count = 0;
399         struct dirent *ent;
400         time_t deadline = time(NULL) - 604800; // 1 week in the past
401
402         while((ent = readdir(dir))) {
403                 if(strlen(ent->d_name) != 24) {
404                         continue;
405                 }
406
407                 char invname[PATH_MAX];
408                 struct stat st;
409
410                 if((size_t)snprintf(invname, sizeof(invname), "%s" SLASH "%s", filename, ent->d_name) >= sizeof(invname)) {
411                         fprintf(stderr, "Filename too long: %s" SLASH "%s\n", filename, ent->d_name);
412                         continue;
413                 }
414
415                 if(!stat(invname, &st)) {
416                         if(deadline < st.st_mtime) {
417                                 count++;
418                         } else {
419                                 unlink(invname);
420                         }
421                 } else {
422                         fprintf(stderr, "Could not stat %s: %s\n", invname, strerror(errno));
423                         errno = 0;
424                 }
425         }
426
427         closedir(dir);
428
429         if(errno) {
430                 fprintf(stderr, "Error while reading directory %s: %s\n", filename, strerror(errno));
431                 return 1;
432         }
433
434         ecdsa_t *key;
435         snprintf(filename, sizeof(filename), "%s" SLASH "invitations" SLASH "ed25519_key.priv", confbase);
436
437         // Remove the key if there are no outstanding invitations.
438         if(!count) {
439                 unlink(filename);
440         }
441
442         // Create a new key if necessary.
443         FILE *f = fopen(filename, "r");
444
445         if(!f) {
446                 if(errno != ENOENT) {
447                         fprintf(stderr, "Could not read %s: %s\n", filename, strerror(errno));
448                         return 1;
449                 }
450
451                 key = ecdsa_generate();
452
453                 if(!key) {
454                         return 1;
455                 }
456
457                 f = fopen(filename, "w");
458
459                 if(!f) {
460                         fprintf(stderr, "Could not write %s: %s\n", filename, strerror(errno));
461                         ecdsa_free(key);
462                         return 1;
463                 }
464
465                 chmod(filename, 0600);
466
467                 if(!ecdsa_write_pem_private_key(key, f)) {
468                         fprintf(stderr, "Could not write ECDSA private key\n");
469                         fclose(f);
470                         ecdsa_free(key);
471                         return 1;
472                 }
473
474                 fclose(f);
475
476                 if(connect_tincd(true)) {
477                         sendline(fd, "%d %d", CONTROL, REQ_RELOAD);
478                 } else {
479                         fprintf(stderr, "Could not signal the tinc daemon. Please restart or reload it manually.\n");
480                 }
481         } else {
482                 key = ecdsa_read_pem_private_key(f);
483                 fclose(f);
484
485                 if(!key) {
486                         fprintf(stderr, "Could not read private key from %s\n", filename);
487                 }
488         }
489
490         if(!key) {
491                 return 1;
492         }
493
494         // Create a hash of the key.
495         char hash[64];
496         char *fingerprint = ecdsa_get_base64_public_key(key);
497         sha512(fingerprint, strlen(fingerprint), hash);
498         b64encode_tinc_urlsafe(hash, hash, 18);
499
500         ecdsa_free(key);
501
502         // Create a random cookie for this invitation.
503         char cookie[25];
504         randomize(cookie, 18);
505
506         // Create a filename that doesn't reveal the cookie itself
507         const size_t buflen = 18 + strlen(fingerprint);
508         uint8_t *buf = alloca(buflen);
509
510         char cookiehash[64];
511         memcpy(buf, cookie, 18);
512         memcpy(buf + 18, fingerprint, buflen - 18);
513         sha512(buf, buflen, cookiehash);
514         b64encode_tinc_urlsafe(cookiehash, cookiehash, 18);
515
516         free(fingerprint);
517
518         b64encode_tinc_urlsafe(cookie, cookie, 18);
519
520         // Create a file containing the details of the invitation.
521         snprintf(filename, sizeof(filename), "%s" SLASH "invitations" SLASH "%s", confbase, cookiehash);
522         int ifd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
523
524         if(!ifd) {
525                 memzero(cookie, sizeof(cookie));
526                 fprintf(stderr, "Could not create invitation file %s: %s\n", filename, strerror(errno));
527                 return 1;
528         }
529
530         f = fdopen(ifd, "w");
531
532         if(!f) {
533                 abort();
534         }
535
536         // Get the local address
537         char *address = NULL;
538         char *port = NULL;
539
540         if(!get_my_hostname(&address, &port)) {
541                 memzero(cookie, sizeof(cookie));
542                 return 1;
543         }
544
545         // Create an URL from the local address, key hash and cookie
546         char *url;
547         xasprintf(&url, "%s/%s%s", address, hash, cookie);
548
549         memzero(cookie, sizeof(cookie));
550         free(address);
551
552         // Fill in the details.
553         fprintf(f, "Name = %s\n", argv[1]);
554
555         if(check_netname(netname, true)) {
556                 fprintf(f, "NetName = %s\n", netname);
557         }
558
559         fprintf(f, "ConnectTo = %s\n", myname);
560
561         // Copy Broadcast and Mode
562         FILE *tc = fopen(tinc_conf, "r");
563
564         if(tc) {
565                 char buf[1024];
566
567                 while(fgets(buf, sizeof(buf), tc)) {
568                         if((!strncasecmp(buf, "Mode", 4) && strchr(" \t=", buf[4]))
569                                         || (!strncasecmp(buf, "Broadcast", 9) && strchr(" \t=", buf[9]))) {
570                                 fputs(buf, f);
571
572                                 // Make sure there is a newline character.
573                                 if(!strchr(buf, '\n')) {
574                                         fputc('\n', f);
575                                 }
576                         }
577                 }
578
579                 fclose(tc);
580         }
581
582         fprintf(f, "#---------------------------------------------------------------#\n");
583         fprintf(f, "Name = %s\n", myname);
584
585         bool appended = append_host_config(f, myname, port);
586         free(port);
587
588         if(!appended) {
589                 fprintf(stderr, "Could not append my config to invitation file: %s.\n", strerror(errno));
590                 free_string(url);
591                 return 1;
592         }
593
594         // Call the inviation-created script
595         environment_t env;
596         environment_init(&env);
597         environment_add(&env, "NODE=%s", argv[1]);
598         environment_add(&env, "INVITATION_FILE=%s", filename);
599         environment_add(&env, "INVITATION_URL=%s", url);
600         execute_script("invitation-created", &env);
601         environment_exit(&env);
602
603         puts(url);
604         free_string(url);
605
606         return 0;
607 }
608
609 static int sock;
610 static char cookie[18], hash[18];
611 static sptps_t sptps;
612 static char *data;
613 static size_t datalen;
614 static bool success = false;
615
616 static char *get_line(char *line, size_t linelen, const char **data) {
617         if(!data || !*data) {
618                 return NULL;
619         }
620
621         if(! **data) {
622                 *data = NULL;
623                 return NULL;
624         }
625
626         const char *end = strchr(*data, '\n');
627         size_t len = end ? (size_t)(end - *data) : strlen(*data);
628
629         if(len >= linelen) {
630                 fprintf(stderr, "Maximum line length exceeded!\n");
631                 return NULL;
632         }
633
634         if(len && !isprint((uint8_t) **data)) {
635                 abort();
636         }
637
638         memcpy(line, *data, len);
639         line[len] = 0;
640
641         if(end) {
642                 *data = end + 1;
643         } else {
644                 *data = NULL;
645         }
646
647         return line;
648 }
649
650 static char *get_value(const char *data, const char *var) {
651         static char buf[1024];
652
653         char *line = get_line(buf, sizeof(buf), &data);
654
655         if(!line) {
656                 return NULL;
657         }
658
659         char *sep = line + strcspn(line, " \t=");
660         char *val = sep + strspn(sep, " \t");
661
662         if(*val == '=') {
663                 val += 1 + strspn(val + 1, " \t");
664         }
665
666         *sep = 0;
667
668         if(strcasecmp(line, var)) {
669                 return NULL;
670         }
671
672         return val;
673 }
674
675 static char *grep(const char *data, const char *var) {
676         static char value[1024];
677
678         const char *p = data;
679         size_t varlen = strlen(var);
680
681         // Skip all lines not starting with var
682         while(strncasecmp(p, var, varlen) || !strchr(" \t=", p[varlen])) {
683                 p = strchr(p, '\n');
684
685                 if(!p) {
686                         break;
687                 } else {
688                         p++;
689                 }
690         }
691
692         if(!p) {
693                 return NULL;
694         }
695
696         p += varlen;
697         p += strspn(p, " \t");
698
699         if(*p == '=') {
700                 p += 1 + strspn(p + 1, " \t");
701         }
702
703         const char *e = strchr(p, '\n');
704
705         if(!e) {
706                 return xstrdup(p);
707         }
708
709         if((size_t)(e - p) >= sizeof(value)) {
710                 fprintf(stderr, "Maximum line length exceeded!\n");
711                 return NULL;
712         }
713
714         memcpy(value, p, e - p);
715         value[e - p] = 0;
716         return value;
717 }
718
719 static bool finalize_join(void) {
720         const char *name = get_value(data, "Name");
721
722         if(!name) {
723                 fprintf(stderr, "No Name found in invitation!\n");
724                 return false;
725         }
726
727         if(!check_id(name)) {
728                 fprintf(stderr, "Invalid Name found in invitation!\n");
729                 return false;
730         }
731
732         if(!netname) {
733                 const char *net = grep(data, "NetName");
734
735                 if(net) {
736                         netname = xstrdup(net);
737
738                         if(!check_netname(netname, true)) {
739                                 fprintf(stderr, "Unsafe NetName found in invitation!\n");
740                                 return false;
741                         }
742                 }
743         }
744
745         bool ask_netname = false;
746         char temp_netname[32];
747
748 make_names:
749
750         if(!confbasegiven) {
751                 free(confbase);
752                 confbase = NULL;
753         }
754
755         make_names(false);
756
757         free(tinc_conf);
758         free(hosts_dir);
759
760         xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase);
761         xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase);
762
763         if(!access(tinc_conf, F_OK)) {
764                 fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf);
765
766                 if(confbasegiven) {
767                         return false;
768                 }
769
770                 // Generate a random netname, ask for a better one later.
771                 ask_netname = true;
772                 snprintf(temp_netname, sizeof(temp_netname), "join_%x", prng(UINT32_MAX));
773                 netname = temp_netname;
774                 goto make_names;
775         }
776
777         if(mkdir(confbase, 0777) && errno != EEXIST) {
778                 fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno));
779                 return false;
780         }
781
782         if(mkdir(hosts_dir, 0777) && errno != EEXIST) {
783                 fprintf(stderr, "Could not create directory %s: %s\n", hosts_dir, strerror(errno));
784                 return false;
785         }
786
787         FILE *f = fopen(tinc_conf, "w");
788
789         if(!f) {
790                 fprintf(stderr, "Could not create file %s: %s\n", tinc_conf, strerror(errno));
791                 return false;
792         }
793
794         fprintf(f, "Name = %s\n", name);
795
796         char filename[PATH_MAX];
797         snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, name);
798         FILE *fh = fopen(filename, "w");
799
800         if(!fh) {
801                 fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno));
802                 fclose(f);
803                 return false;
804         }
805
806         snprintf(filename, sizeof(filename), "%s" SLASH "invitation-data", confbase);
807         FILE *finv = fopen(filename, "w");
808
809         if(!finv || fwrite(data, datalen, 1, finv) != 1) {
810                 fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno));
811                 fclose(fh);
812                 fclose(f);
813                 fclose(finv);
814                 return false;
815         }
816
817         fclose(finv);
818
819         snprintf(filename, sizeof(filename), "%s" SLASH "tinc-up.invitation", confbase);
820         FILE *fup = fopen(filename, "w");
821
822         if(!fup) {
823                 fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno));
824                 fclose(f);
825                 fclose(fh);
826                 return false;
827         }
828
829         ifconfig_header(fup);
830
831         // Filter first chunk on approved keywords, split between tinc.conf and hosts/Name
832         // Generate a tinc-up script from Ifconfig and Route keywords.
833         // Other chunks go unfiltered to their respective host config files
834         const char *p = data;
835         char *l, *value;
836
837         static char line[1024];
838
839         while((l = get_line(line, sizeof(line), &p))) {
840                 // Ignore comments
841                 if(*l == '#') {
842                         continue;
843                 }
844
845                 // Split line into variable and value
846                 size_t len = strcspn(l, "\t =");
847                 value = l + len;
848                 value += strspn(value, "\t ");
849
850                 if(*value == '=') {
851                         value++;
852                         value += strspn(value, "\t ");
853                 }
854
855                 l[len] = 0;
856
857                 // Ignore lines with empty variable names
858                 if(!*l) {
859                         continue;
860                 }
861
862                 // Is it a Name?
863                 if(!strcasecmp(l, "Name")) {
864                         if(strcmp(value, name)) {
865                                 break;
866                         } else {
867                                 continue;
868                         }
869                 } else if(!strcasecmp(l, "NetName")) {
870                         continue;
871                 }
872
873                 // Check the list of known variables
874                 bool found = false;
875                 int i;
876
877                 for(i = 0; variables[i].name; i++) {
878                         if(strcasecmp(l, variables[i].name)) {
879                                 continue;
880                         }
881
882                         found = true;
883                         break;
884                 }
885
886                 // Handle Ifconfig and Route statements
887                 if(!found) {
888                         if(!strcasecmp(l, "Ifconfig")) {
889                                 if(!strcasecmp(value, "dhcp")) {
890                                         ifconfig_dhcp(fup);
891                                 } else if(!strcasecmp(value, "dhcp6")) {
892                                         ifconfig_dhcp6(fup);
893                                 } else if(!strcasecmp(value, "slaac")) {
894                                         ifconfig_slaac(fup);
895                                 } else {
896                                         ifconfig_address(fup, value);
897                                 }
898
899                                 continue;
900                         } else if(!strcasecmp(l, "Route")) {
901                                 ifconfig_route(fup, value);
902                                 continue;
903                         }
904                 }
905
906                 // Ignore unknown and unsafe variables
907                 if(!found) {
908                         fprintf(stderr, "Ignoring unknown variable '%s' in invitation.\n", l);
909                         continue;
910                 } else if(!(variables[i].type & VAR_SAFE)) {
911                         if(force) {
912                                 fprintf(stderr, "Warning: unsafe variable '%s' in invitation.\n", l);
913                         } else {
914                                 fprintf(stderr, "Ignoring unsafe variable '%s' in invitation.\n", l);
915                                 continue;
916                         }
917                 }
918
919                 // Copy the safe variable to the right config file
920                 fprintf((variables[i].type & VAR_HOST) ? fh : f, "%s = %s\n", l, value);
921         }
922
923         fclose(f);
924         bool valid_tinc_up = ifconfig_footer(fup);
925         fclose(fup);
926
927         while(l && !strcasecmp(l, "Name")) {
928                 if(!check_id(value)) {
929                         fprintf(stderr, "Invalid Name found in invitation.\n");
930                         return false;
931                 }
932
933                 if(!strcmp(value, name)) {
934                         fprintf(stderr, "Secondary chunk would overwrite our own host config file.\n");
935                         return false;
936                 }
937
938                 snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, value);
939                 f = fopen(filename, "w");
940
941                 if(!f) {
942                         fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno));
943                         return false;
944                 }
945
946                 while((l = get_line(line, sizeof(line), &p))) {
947                         if(!strcmp(l, "#---------------------------------------------------------------#")) {
948                                 continue;
949                         }
950
951                         size_t len = strcspn(l, "\t =");
952
953                         if(len == 4 && !strncasecmp(l, "Name", 4)) {
954                                 value = l + len;
955                                 value += strspn(value, "\t ");
956
957                                 if(*value == '=') {
958                                         value++;
959                                         value += strspn(value, "\t ");
960                                 }
961
962                                 l[len] = 0;
963                                 break;
964                         }
965
966                         fputs(l, f);
967                         fputc('\n', f);
968                 }
969
970                 fclose(f);
971         }
972
973         // Generate our key and send a copy to the server
974         ecdsa_t *key = ecdsa_generate();
975
976         if(!key) {
977                 return false;
978         }
979
980         char *b64_pubkey = ecdsa_get_base64_public_key(key);
981
982         if(!b64_pubkey) {
983                 return false;
984         }
985
986         snprintf(filename, sizeof(filename), "%s" SLASH "ed25519_key.priv", confbase);
987         f = fopenmask(filename, "w", 0600);
988
989         if(!f) {
990                 return false;
991         }
992
993         if(!ecdsa_write_pem_private_key(key, f)) {
994                 fprintf(stderr, "Error writing private key!\n");
995                 ecdsa_free(key);
996                 fclose(f);
997                 return false;
998         }
999
1000         fclose(f);
1001
1002         fprintf(fh, "Ed25519PublicKey = %s\n", b64_pubkey);
1003
1004         sptps_send_record(&sptps, 1, b64_pubkey, strlen(b64_pubkey));
1005         free(b64_pubkey);
1006         ecdsa_free(key);
1007
1008 #ifndef DISABLE_LEGACY
1009         rsa_t *rsa = rsa_generate(2048, 0x1001);
1010         snprintf(filename, sizeof(filename), "%s" SLASH "rsa_key.priv", confbase);
1011         f = fopenmask(filename, "w", 0600);
1012
1013         if(!f || !rsa_write_pem_private_key(rsa, f)) {
1014                 fprintf(stderr, "Could not write private RSA key\n");
1015         } else if(!rsa_write_pem_public_key(rsa, fh)) {
1016                 fprintf(stderr, "Could not write public RSA key\n");
1017         }
1018
1019         fclose(f);
1020
1021         fclose(fh);
1022
1023         rsa_free(rsa);
1024 #endif
1025
1026         check_port(name);
1027
1028 ask_netname:
1029
1030         if(ask_netname && tty) {
1031                 fprintf(stderr, "Enter a new netname: ");
1032
1033                 if(!fgets(line, sizeof(line), stdin)) {
1034                         fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
1035                         return false;
1036                 }
1037
1038                 if(!*line || *line == '\n') {
1039                         goto ask_netname;
1040                 }
1041
1042                 line[strlen(line) - 1] = 0;
1043
1044                 char newbase[PATH_MAX];
1045
1046                 if((size_t)snprintf(newbase, sizeof(newbase), CONFDIR SLASH "tinc" SLASH "%s", line) >= sizeof(newbase)) {
1047                         fprintf(stderr, "Filename too long: " CONFDIR SLASH "tinc" SLASH "%s\n", line);
1048                         goto ask_netname;
1049                 }
1050
1051                 if(rename(confbase, newbase)) {
1052                         fprintf(stderr, "Error trying to rename %s to %s: %s\n", confbase, newbase, strerror(errno));
1053                         goto ask_netname;
1054                 }
1055
1056                 netname = line;
1057                 make_names(false);
1058         }
1059
1060         char filename2[PATH_MAX];
1061         snprintf(filename, sizeof(filename), "%s" SLASH "tinc-up.invitation", confbase);
1062
1063 #ifdef HAVE_WINDOWS
1064         snprintf(filename2, sizeof(filename2), "%s" SLASH "tinc-up.bat", confbase);
1065 #else
1066         snprintf(filename2, sizeof(filename2), "%s" SLASH "tinc-up", confbase);
1067 #endif
1068
1069         if(valid_tinc_up) {
1070                 if(tty) {
1071                         FILE *fup = fopen(filename, "r");
1072
1073                         if(fup) {
1074                                 fprintf(stderr, "\nPlease review the following tinc-up script:\n\n");
1075
1076                                 char buf[MAXSIZE];
1077
1078                                 while(fgets(buf, sizeof(buf), fup)) {
1079                                         fputs(buf, stderr);
1080                                 }
1081
1082                                 fclose(fup);
1083
1084                                 int response = 0;
1085
1086                                 do {
1087                                         fprintf(stderr, "\nDo you want to use this script [y]es/[n]o/[e]dit? ");
1088                                         response = tolower(getchar());
1089                                 } while(!strchr("yne", response));
1090
1091                                 fprintf(stderr, "\n");
1092
1093                                 if(response == 'e') {
1094                                         char *command;
1095 #ifndef HAVE_WINDOWS
1096                                         const char *editor = getenv("VISUAL");
1097
1098                                         if(!editor) {
1099                                                 editor = getenv("EDITOR");
1100                                         }
1101
1102                                         if(!editor) {
1103                                                 editor = "vi";
1104                                         }
1105
1106                                         xasprintf(&command, "\"%s\" \"%s\"", editor, filename);
1107 #else
1108                                         xasprintf(&command, "edit \"%s\"", filename);
1109 #endif
1110
1111                                         if(system(command)) {
1112                                                 response = 'n';
1113                                         } else {
1114                                                 response = 'y';
1115                                         }
1116
1117                                         free(command);
1118                                 }
1119
1120                                 if(response == 'y') {
1121                                         rename(filename, filename2);
1122                                         chmod(filename2, 0755);
1123                                         fprintf(stderr, "tinc-up enabled.\n");
1124                                 } else {
1125                                         fprintf(stderr, "tinc-up has been left disabled.\n");
1126                                 }
1127                         }
1128                 } else {
1129                         if(force) {
1130                                 rename(filename, filename2);
1131                                 chmod(filename2, 0755);
1132                                 fprintf(stderr, "tinc-up enabled.\n");
1133                         } else {
1134                                 fprintf(stderr, "A tinc-up script was generated, but has been left disabled.\n");
1135                         }
1136                 }
1137         } else {
1138                 // A placeholder was generated.
1139                 rename(filename, filename2);
1140                 chmod(filename2, 0755);
1141         }
1142
1143         fprintf(stderr, "Configuration stored in: %s\n", confbase);
1144
1145         return true;
1146 }
1147
1148
1149 static bool invitation_send(void *handle, uint8_t type, const void *vdata, size_t len) {
1150         (void)handle;
1151         (void)type;
1152         const char *data = vdata;
1153
1154         while(len) {
1155                 ssize_t result = send(sock, data, len, 0);
1156
1157                 if(result == -1 && sockwouldblock(sockerrno)) {
1158                         continue;
1159                 } else if(result <= 0) {
1160                         return false;
1161                 }
1162
1163                 data += result;
1164                 len -= result;
1165         }
1166
1167         return true;
1168 }
1169
1170 static bool invitation_receive(void *handle, uint8_t type, const void *msg, uint16_t len) {
1171         (void)handle;
1172
1173         switch(type) {
1174         case SPTPS_HANDSHAKE:
1175                 return sptps_send_record(&sptps, 0, cookie, sizeof(cookie));
1176
1177         case 0:
1178                 data = xrealloc(data, datalen + len + 1);
1179                 memcpy(data + datalen, msg, len);
1180                 datalen += len;
1181                 data[datalen] = 0;
1182                 break;
1183
1184         case 1:
1185                 return finalize_join();
1186
1187         case 2:
1188                 fprintf(stderr, "Invitation successfully accepted.\n");
1189                 shutdown(sock, SHUT_RDWR);
1190                 success = true;
1191                 break;
1192
1193         default:
1194                 return false;
1195         }
1196
1197         return true;
1198 }
1199
1200 int cmd_join(int argc, char *argv[]) {
1201         free(data);
1202         data = NULL;
1203         datalen = 0;
1204
1205         if(argc > 2) {
1206                 fprintf(stderr, "Too many arguments!\n");
1207                 return 1;
1208         }
1209
1210         // Make sure confbase exists and is accessible.
1211         if(!confbase_given && mkdir(confdir, 0755) && errno != EEXIST) {
1212                 fprintf(stderr, "Could not create directory %s: %s\n", confdir, strerror(errno));
1213                 return 1;
1214         }
1215
1216         if(mkdir(confbase, 0777) && errno != EEXIST) {
1217                 fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno));
1218                 return 1;
1219         }
1220
1221         if(access(confbase, R_OK | W_OK | X_OK)) {
1222                 fprintf(stderr, "No permission to write in directory %s: %s\n", confbase, strerror(errno));
1223                 return 1;
1224         }
1225
1226         // If a netname or explicit configuration directory is specified, check for an existing tinc.conf.
1227         if((netname || confbasegiven) && !access(tinc_conf, F_OK)) {
1228                 fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf);
1229                 return 1;
1230         }
1231
1232         // Either read the invitation from the command line or from stdin.
1233         char *invitation;
1234
1235         if(argc > 1) {
1236                 invitation = argv[1];
1237         } else {
1238                 if(tty) {
1239                         fprintf(stderr, "Enter invitation URL: ");
1240                 }
1241
1242                 errno = EPIPE;
1243
1244                 if(!fgets(line, sizeof(line), stdin)) {
1245                         fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
1246                         return false;
1247                 }
1248
1249                 invitation = line;
1250         }
1251
1252         // Parse the invitation URL.
1253         rstrip(line);
1254
1255         char *slash = strchr(invitation, '/');
1256
1257         if(!slash) {
1258                 goto invalid;
1259         }
1260
1261         *slash++ = 0;
1262
1263         if(strlen(slash) != 48) {
1264                 goto invalid;
1265         }
1266
1267         char *address = invitation;
1268         char *port = NULL;
1269
1270         if(*address == '[') {
1271                 address++;
1272                 char *bracket = strchr(address, ']');
1273
1274                 if(!bracket) {
1275                         goto invalid;
1276                 }
1277
1278                 *bracket = 0;
1279
1280                 if(bracket[1] == ':') {
1281                         port = bracket + 2;
1282                 }
1283         } else {
1284                 port = strchr(address, ':');
1285
1286                 if(port) {
1287                         *port++ = 0;
1288                 }
1289         }
1290
1291         if(!port || !*port) {
1292                 static char default_port[] = "655";
1293                 port = default_port;
1294         }
1295
1296         if(!b64decode_tinc(slash, hash, 24) || !b64decode_tinc(slash + 24, cookie, 24)) {
1297                 goto invalid;
1298         }
1299
1300         // Generate a throw-away key for the invitation.
1301         ecdsa_t *key = ecdsa_generate();
1302
1303         if(!key) {
1304                 return 1;
1305         }
1306
1307         char *b64_pubkey = ecdsa_get_base64_public_key(key);
1308
1309         // Connect to the tinc daemon mentioned in the URL.
1310         struct addrinfo *ai = str2addrinfo(address, port, SOCK_STREAM);
1311
1312         if(!ai) {
1313                 free(b64_pubkey);
1314                 ecdsa_free(key);
1315                 return 1;
1316         }
1317
1318         struct addrinfo *aip = NULL;
1319
1320 next:
1321         if(!aip) {
1322                 aip = ai;
1323         } else {
1324                 aip = aip->ai_next;
1325
1326                 if(!aip) {
1327                         freeaddrinfo(ai);
1328                         free(b64_pubkey);
1329                         ecdsa_free(key);
1330                         return 1;
1331                 }
1332         }
1333
1334         sock = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol);
1335
1336         if(sock <= 0) {
1337                 fprintf(stderr, "Could not open socket: %s\n", strerror(errno));
1338                 goto next;
1339         }
1340
1341         if(connect(sock, aip->ai_addr, aip->ai_addrlen)) {
1342                 char *addrstr, *portstr;
1343                 sockaddr2str((sockaddr_t *)aip->ai_addr, &addrstr, &portstr);
1344                 fprintf(stderr, "Could not connect to %s port %s: %s\n", addrstr, portstr, strerror(errno));
1345                 free(addrstr);
1346                 free(portstr);
1347                 closesocket(sock);
1348                 goto next;
1349         }
1350
1351         fprintf(stderr, "Connected to %s port %s...\n", address, port);
1352
1353         // Tell him we have an invitation, and give him our throw-away key.
1354         ssize_t len = snprintf(line, sizeof(line), "0 ?%s %d.%d\n", b64_pubkey, PROT_MAJOR, PROT_MINOR);
1355
1356         if(len <= 0 || (size_t)len >= sizeof(line)) {
1357                 abort();
1358         }
1359
1360         if(!sendline(sock, "0 ?%s %d.%d", b64_pubkey, PROT_MAJOR, 1)) {
1361                 fprintf(stderr, "Error sending request to %s port %s: %s\n", address, port, strerror(errno));
1362                 closesocket(sock);
1363                 goto next;
1364         }
1365
1366         char hisname[4096] = "";
1367         int code, hismajor, hisminor = 0;
1368
1369         if(!recvline(sock, line, sizeof(line)) || sscanf(line, "%d %4095s %d.%d", &code, hisname, &hismajor, &hisminor) < 3 || code != 0 || hismajor != PROT_MAJOR || !check_id(hisname) || !recvline(sock, line, sizeof(line)) || !rstrip(line) || sscanf(line, "%d ", &code) != 1 || code != ACK || strlen(line) < 3) {
1370                 fprintf(stderr, "Cannot read greeting from peer\n");
1371                 closesocket(sock);
1372                 goto next;
1373         }
1374
1375         freeaddrinfo(ai);
1376         ai = NULL;
1377         aip = NULL;
1378
1379         free(b64_pubkey);
1380         b64_pubkey = NULL;
1381
1382         // Check if the hash of the key he gave us matches the hash in the URL.
1383         char *fingerprint = line + 2;
1384         char hishash[64];
1385
1386         if(sha512(fingerprint, strlen(fingerprint), hishash)) {
1387                 fprintf(stderr, "Could not create digest\n%s\n", line + 2);
1388                 ecdsa_free(key);
1389                 return 1;
1390         }
1391
1392         if(memcmp(hishash, hash, 18)) {
1393                 fprintf(stderr, "Peer has an invalid key!\n%s\n", line + 2);
1394                 ecdsa_free(key);
1395                 return 1;
1396
1397         }
1398
1399         ecdsa_t *hiskey = ecdsa_set_base64_public_key(fingerprint);
1400
1401         if(!hiskey) {
1402                 ecdsa_free(key);
1403                 return 1;
1404         }
1405
1406         // Start an SPTPS session
1407         if(!sptps_start(&sptps, NULL, true, false, key, hiskey, "tinc invitation", 15, invitation_send, invitation_receive)) {
1408                 ecdsa_free(hiskey);
1409                 ecdsa_free(key);
1410                 return 1;
1411         }
1412
1413         // Feed rest of input buffer to SPTPS
1414         if(!sptps_receive_data(&sptps, buffer, blen)) {
1415                 success = false;
1416                 goto exit;
1417         }
1418
1419         while((len = recv(sock, line, sizeof(line), 0))) {
1420                 if(len < 0) {
1421                         if(sockwouldblock(sockerrno)) {
1422                                 continue;
1423                         }
1424
1425 #if HAVE_WINDOWS
1426
1427                         // If socket has been shut down, recv() on Windows returns -1 and sets sockerrno
1428                         // to WSAESHUTDOWN, while on UNIX-like operating systems recv() returns 0, so we
1429                         // have to do an explicit check here.
1430                         if(sockshutdown(sockerrno)) {
1431                                 break;
1432                         }
1433
1434 #endif
1435                         fprintf(stderr, "Error reading data from %s port %s: %s\n", address, port, sockstrerror(sockerrno));
1436                         success = false;
1437                         goto exit;
1438                 }
1439
1440                 char *p = line;
1441
1442                 while(len) {
1443                         size_t done = sptps_receive_data(&sptps, p, len);
1444
1445                         if(!done) {
1446                                 success = false;
1447                                 goto exit;
1448                         }
1449
1450                         len -= (ssize_t) done;
1451                         p += done;
1452                 }
1453         }
1454
1455 exit:
1456         sptps_stop(&sptps);
1457         ecdsa_free(hiskey);
1458         ecdsa_free(key);
1459         closesocket(sock);
1460
1461         if(!success) {
1462                 fprintf(stderr, "Invitation cancelled.\n");
1463                 return 1;
1464         }
1465
1466         return 0;
1467
1468 invalid:
1469         fprintf(stderr, "Invalid invitation URL.\n");
1470         return 1;
1471 }