minor
[tinc] / src / fsck.c
1 /*
2     fsck.c -- Check the configuration files for problems
3     Copyright (C) 2014 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 "crypto.h"
23 #include "ecdsa.h"
24 #include "ecdsagen.h"
25 #include "fsck.h"
26 #include "names.h"
27 #ifndef DISABLE_LEGACY
28 #include "rsa.h"
29 #include "rsagen.h"
30 #endif
31 #include "tincctl.h"
32 #include "utils.h"
33
34 static bool ask_fix(void) {
35         if(force) {
36                 return true;
37         }
38
39         if(!tty) {
40                 return false;
41         }
42
43 again:
44         fprintf(stderr, "Fix y/n? ");
45         char buf[1024];
46
47         if(!fgets(buf, sizeof(buf), stdin)) {
48                 tty = false;
49                 return false;
50         }
51
52         if(buf[0] == 'y' || buf[0] == 'Y') {
53                 return true;
54         }
55
56         if(buf[0] == 'n' || buf[0] == 'N') {
57                 return false;
58         }
59
60         goto again;
61 }
62
63 static void print_tinc_cmd(const char *argv0, const char *format, ...) {
64         if(confbasegiven) {
65                 fprintf(stderr, "%s -c %s ", argv0, confbase);
66         } else if(netname) {
67                 fprintf(stderr, "%s -n %s ", argv0, netname);
68         } else {
69                 fprintf(stderr, "%s ", argv0);
70         }
71
72         va_list va;
73         va_start(va, format);
74         vfprintf(stderr, format, va);
75         va_end(va);
76         fputc('\n', stderr);
77 }
78
79 static int strtailcmp(const char *str, const char *tail) {
80         size_t slen = strlen(str);
81         size_t tlen = strlen(tail);
82
83         if(tlen > slen) {
84                 return -1;
85         }
86
87         return memcmp(str + slen - tlen, tail, tlen);
88 }
89
90 static void check_conffile(const char *fname, bool server) {
91         FILE *f = fopen(fname, "r");
92
93         if(!f) {
94                 fprintf(stderr, "ERROR: cannot read %s: %s\n", fname, strerror(errno));
95                 return;
96         }
97
98         char line[2048];
99         int lineno = 0;
100         bool skip = false;
101         const int maxvariables = 50;
102         int count[maxvariables];
103         memset(count, 0, sizeof(count));
104
105         while(fgets(line, sizeof(line), f)) {
106                 if(skip) {
107                         if(!strncmp(line, "-----END", 8)) {
108                                 skip = false;
109                         }
110
111                         continue;
112                 } else {
113                         if(!strncmp(line, "-----BEGIN", 10)) {
114                                 skip = true;
115                                 continue;
116                         }
117                 }
118
119                 int len;
120                 char *variable, *value, *eol;
121                 variable = value = line;
122
123                 lineno++;
124
125                 eol = line + strlen(line);
126
127                 while(strchr("\t \r\n", *--eol)) {
128                         *eol = '\0';
129                 }
130
131                 if(!line[0] || line[0] == '#') {
132                         continue;
133                 }
134
135                 len = strcspn(value, "\t =");
136                 value += len;
137                 value += strspn(value, "\t ");
138
139                 if(*value == '=') {
140                         value++;
141                         value += strspn(value, "\t ");
142                 }
143
144                 variable[len] = '\0';
145
146                 bool found = false;
147
148                 for(int i = 0; variables[i].name; i++) {
149                         if(strcasecmp(variables[i].name, variable)) {
150                                 continue;
151                         }
152
153                         found = true;
154
155                         if(variables[i].type & VAR_OBSOLETE) {
156                                 fprintf(stderr, "WARNING: obsolete variable %s in %s line %d\n", variable, fname, lineno);
157                         }
158
159                         if(i < maxvariables) {
160                                 count[i]++;
161                         }
162                 }
163
164                 if(!found) {
165                         fprintf(stderr, "WARNING: unknown variable %s in %s line %d\n", variable, fname, lineno);
166                 }
167
168                 if(!*value) {
169                         fprintf(stderr, "ERROR: no value for variable %s in %s line %d\n", variable, fname, lineno);
170                 }
171         }
172
173         for(int i = 0; variables[i].name && i < maxvariables; i++) {
174                 if(count[i] > 1 && !(variables[i].type & VAR_MULTIPLE)) {
175                         fprintf(stderr, "WARNING: multiple instances of variable %s in %s\n", variables[i].name, fname);
176                 }
177         }
178
179         if(ferror(f)) {
180                 fprintf(stderr, "ERROR: while reading %s: %s\n", fname, strerror(errno));
181         }
182
183         fclose(f);
184 }
185
186 int fsck(const char *argv0) {
187 #ifdef HAVE_MINGW
188         int uid = 0;
189 #else
190         uid_t uid = getuid();
191 #endif
192
193         // Check that tinc.conf is readable.
194
195         if(access(tinc_conf, R_OK)) {
196                 fprintf(stderr, "ERROR: cannot read %s: %s\n", tinc_conf, strerror(errno));
197
198                 if(errno == ENOENT) {
199                         fprintf(stderr, "No tinc configuration found. Create a new one with:\n\n");
200                         print_tinc_cmd(argv0, "init");
201                 } else if(errno == EACCES) {
202                         if(uid != 0) {
203                                 fprintf(stderr, "You are currently not running tinc as root. Use sudo?\n");
204                         } else {
205                                 fprintf(stderr, "Check the permissions of each component of the path %s.\n", tinc_conf);
206                         }
207                 }
208
209                 return 1;
210         }
211
212         char *name = get_my_name(true);
213
214         if(!name) {
215                 fprintf(stderr, "ERROR: tinc cannot run without a valid Name.\n");
216                 return 1;
217         }
218
219         // Check for private keys.
220         // TODO: use RSAPrivateKeyFile and Ed25519PrivateKeyFile variables if present.
221
222         struct stat st;
223         char fname[PATH_MAX];
224         char dname[PATH_MAX];
225
226 #ifndef DISABLE_LEGACY
227         rsa_t *rsa_priv = NULL;
228         snprintf(fname, sizeof(fname), "%s/rsa_key.priv", confbase);
229
230         if(stat(fname, &st)) {
231                 if(errno != ENOENT) {
232                         // Something is seriously wrong here. If we can access the directory with tinc.conf in it, we should certainly be able to stat() an existing file.
233                         fprintf(stderr, "ERROR: cannot read %s: %s\n", fname, strerror(errno));
234                         fprintf(stderr, "Please correct this error.\n");
235                         return 1;
236                 }
237         } else {
238                 FILE *f = fopen(fname, "r");
239
240                 if(!f) {
241                         fprintf(stderr, "ERROR: could not open %s: %s\n", fname, strerror(errno));
242                         return 1;
243                 }
244
245                 rsa_priv = rsa_read_pem_private_key(f);
246                 fclose(f);
247
248                 if(!rsa_priv) {
249                         fprintf(stderr, "ERROR: No key or unusable key found in %s.\n", fname);
250                         fprintf(stderr, "You can generate a new RSA key with:\n\n");
251                         print_tinc_cmd(argv0, "generate-rsa-keys");
252                         return 1;
253                 }
254
255 #if !defined(HAVE_MINGW) && !defined(HAVE_CYGWIN)
256
257                 if(st.st_mode & 077) {
258                         fprintf(stderr, "WARNING: unsafe file permissions on %s.\n", fname);
259
260                         if(st.st_uid != uid) {
261                                 fprintf(stderr, "You are not running %s as the same uid as %s.\n", argv0, fname);
262                         } else if(ask_fix()) {
263                                 if(chmod(fname, st.st_mode & ~077)) {
264                                         fprintf(stderr, "ERROR: could not change permissions of %s: %s\n", fname, strerror(errno));
265                                 } else {
266                                         fprintf(stderr, "Fixed permissions of %s.\n", fname);
267                                 }
268                         }
269                 }
270
271 #endif
272         }
273
274 #endif
275
276         ecdsa_t *ecdsa_priv = NULL;
277         snprintf(fname, sizeof(fname), "%s/ed25519_key.priv", confbase);
278
279         if(stat(fname, &st)) {
280                 if(errno != ENOENT) {
281                         // Something is seriously wrong here. If we can access the directory with tinc.conf in it, we should certainly be able to stat() an existing file.
282                         fprintf(stderr, "ERROR: cannot read %s: %s\n", fname, strerror(errno));
283                         fprintf(stderr, "Please correct this error.\n");
284                         return 1;
285                 }
286         } else {
287                 FILE *f = fopen(fname, "r");
288
289                 if(!f) {
290                         fprintf(stderr, "ERROR: could not open %s: %s\n", fname, strerror(errno));
291                         return 1;
292                 }
293
294                 ecdsa_priv = ecdsa_read_pem_private_key(f);
295                 fclose(f);
296
297                 if(!ecdsa_priv) {
298                         fprintf(stderr, "ERROR: No key or unusable key found in %s.\n", fname);
299                         fprintf(stderr, "You can generate a new Ed25519 key with:\n\n");
300                         print_tinc_cmd(argv0, "generate-ed25519-keys");
301                         return 1;
302                 }
303
304 #if !defined(HAVE_MINGW) && !defined(HAVE_CYGWIN)
305
306                 if(st.st_mode & 077) {
307                         fprintf(stderr, "WARNING: unsafe file permissions on %s.\n", fname);
308
309                         if(st.st_uid != uid) {
310                                 fprintf(stderr, "You are not running %s as the same uid as %s.\n", argv0, fname);
311                         } else if(ask_fix()) {
312                                 if(chmod(fname, st.st_mode & ~077)) {
313                                         fprintf(stderr, "ERROR: could not change permissions of %s: %s\n", fname, strerror(errno));
314                                 } else {
315                                         fprintf(stderr, "Fixed permissions of %s.\n", fname);
316                                 }
317                         }
318                 }
319
320 #endif
321         }
322
323 #ifdef DISABLE_LEGACY
324
325         if(!ecdsa_priv) {
326                 fprintf(stderr, "ERROR: No Ed25519 private key found.\n");
327 #else
328
329         if(!rsa_priv && !ecdsa_priv) {
330                 fprintf(stderr, "ERROR: Neither RSA or Ed25519 private key found.\n");
331 #endif
332                 fprintf(stderr, "You can generate new keys with:\n\n");
333                 print_tinc_cmd(argv0, "generate-keys");
334                 return 1;
335         }
336
337         // Check for public keys.
338         // TODO: use RSAPublicKeyFile variable if present.
339
340         snprintf(fname, sizeof(fname), "%s/hosts/%s", confbase, name);
341
342         if(access(fname, R_OK)) {
343                 fprintf(stderr, "WARNING: cannot read %s\n", fname);
344         }
345
346         FILE *f;
347
348 #ifndef DISABLE_LEGACY
349         rsa_t *rsa_pub = NULL;
350
351         f = fopen(fname, "r");
352
353         if(f) {
354                 rsa_pub = rsa_read_pem_public_key(f);
355                 fclose(f);
356         }
357
358         if(rsa_priv) {
359                 if(!rsa_pub) {
360                         fprintf(stderr, "WARNING: No (usable) public RSA key found.\n");
361
362                         if(ask_fix()) {
363                                 FILE *f = fopen(fname, "a");
364
365                                 if(f) {
366                                         if(rsa_write_pem_public_key(rsa_priv, f)) {
367                                                 fprintf(stderr, "Wrote RSA public key to %s.\n", fname);
368                                         } else {
369                                                 fprintf(stderr, "ERROR: could not write RSA public key to %s.\n", fname);
370                                         }
371
372                                         fclose(f);
373                                 } else {
374                                         fprintf(stderr, "ERROR: could not append to %s: %s\n", fname, strerror(errno));
375                                 }
376                         }
377                 } else {
378                         // TODO: suggest remedies
379                         size_t len = rsa_size(rsa_priv);
380
381                         if(len != rsa_size(rsa_pub)) {
382                                 fprintf(stderr, "ERROR: public and private RSA keys do not match.\n");
383                                 return 1;
384                         }
385
386                         char buf1[len], buf2[len], buf3[len];
387                         randomize(buf1, sizeof(buf1));
388                         buf1[0] &= 0x7f;
389                         memset(buf2, 0, sizeof(buf2));
390                         memset(buf3, 0, sizeof(buf2));
391
392                         if(!rsa_public_encrypt(rsa_pub, buf1, sizeof(buf1), buf2)) {
393                                 fprintf(stderr, "ERROR: public RSA key does not work.\n");
394                                 return 1;
395                         }
396
397                         if(!rsa_private_decrypt(rsa_priv, buf2, sizeof(buf2), buf3)) {
398                                 fprintf(stderr, "ERROR: private RSA key does not work.\n");
399                                 return 1;
400                         }
401
402                         if(memcmp(buf1, buf3, sizeof(buf1))) {
403                                 fprintf(stderr, "ERROR: public and private RSA keys do not match.\n");
404                                 return 1;
405                         }
406                 }
407         } else {
408                 if(rsa_pub) {
409                         fprintf(stderr, "WARNING: A public RSA key was found but no private key is known.\n");
410                 }
411         }
412
413 #endif
414
415         ecdsa_t *ecdsa_pub = NULL;
416
417         f = fopen(fname, "r");
418
419         if(f) {
420                 ecdsa_pub = get_pubkey(f);
421
422                 if(!ecdsa_pub) {
423                         rewind(f);
424                         ecdsa_pub = ecdsa_read_pem_public_key(f);
425                 }
426
427                 fclose(f);
428         }
429
430         if(ecdsa_priv) {
431                 if(!ecdsa_pub) {
432                         fprintf(stderr, "WARNING: No (usable) public Ed25519 key found.\n");
433
434                         if(ask_fix()) {
435                                 FILE *f = fopen(fname, "a");
436
437                                 if(f) {
438                                         if(ecdsa_write_pem_public_key(ecdsa_priv, f)) {
439                                                 fprintf(stderr, "Wrote Ed25519 public key to %s.\n", fname);
440                                         } else {
441                                                 fprintf(stderr, "ERROR: could not write Ed25519 public key to %s.\n", fname);
442                                         }
443
444                                         fclose(f);
445                                 } else {
446                                         fprintf(stderr, "ERROR: could not append to %s: %s\n", fname, strerror(errno));
447                                 }
448                         }
449                 } else {
450                         // TODO: suggest remedies
451                         char *key1 = ecdsa_get_base64_public_key(ecdsa_pub);
452
453                         if(!key1) {
454                                 fprintf(stderr, "ERROR: public Ed25519 key does not work.\n");
455                                 return 1;
456                         }
457
458                         char *key2 = ecdsa_get_base64_public_key(ecdsa_priv);
459
460                         if(!key2) {
461                                 free(key1);
462                                 fprintf(stderr, "ERROR: private Ed25519 key does not work.\n");
463                                 return 1;
464                         }
465
466                         int result = strcmp(key1, key2);
467                         free(key1);
468                         free(key2);
469
470                         if(result) {
471                                 fprintf(stderr, "ERROR: public and private Ed25519 keys do not match.\n");
472                                 return 1;
473                         }
474                 }
475         } else {
476                 if(ecdsa_pub) {
477                         fprintf(stderr, "WARNING: A public Ed25519 key was found but no private key is known.\n");
478                 }
479         }
480
481         // Check whether scripts are executable
482
483         struct dirent *ent;
484         DIR *dir = opendir(confbase);
485
486         if(!dir) {
487                 fprintf(stderr, "ERROR: cannot read directory %s: %s\n", confbase, strerror(errno));
488                 return 1;
489         }
490
491         while((ent = readdir(dir))) {
492                 if(strtailcmp(ent->d_name, "-up") && strtailcmp(ent->d_name, "-down")) {
493                         continue;
494                 }
495
496                 strncpy(fname, ent->d_name, sizeof(fname));
497                 char *dash = strrchr(fname, '-');
498
499                 if(!dash) {
500                         continue;
501                 }
502
503                 *dash = 0;
504
505                 if(strcmp(fname, "tinc") && strcmp(fname, "host") && strcmp(fname, "subnet")) {
506                         static bool explained = false;
507                         fprintf(stderr, "WARNING: Unknown script %s" SLASH "%s found.\n", confbase, ent->d_name);
508
509                         if(!explained) {
510                                 fprintf(stderr, "The only scripts in %s executed by tinc are:\n", confbase);
511                                 fprintf(stderr, "tinc-up, tinc-down, host-up, host-down, subnet-up and subnet-down.\n");
512                                 explained = true;
513                         }
514
515                         continue;
516                 }
517
518                 snprintf(fname, sizeof(fname), "%s" SLASH "%s", confbase, ent->d_name);
519
520                 if(access(fname, R_OK | X_OK)) {
521                         if(errno != EACCES) {
522                                 fprintf(stderr, "ERROR: cannot access %s: %s\n", fname, strerror(errno));
523                                 continue;
524                         }
525
526                         fprintf(stderr, "WARNING: cannot read and execute %s: %s\n", fname, strerror(errno));
527
528                         if(ask_fix()) {
529                                 if(chmod(fname, 0755)) {
530                                         fprintf(stderr, "ERROR: cannot change permissions on %s: %s\n", fname, strerror(errno));
531                                 }
532                         }
533                 }
534         }
535
536         closedir(dir);
537
538         snprintf(dname, sizeof(dname), "%s" SLASH "hosts", confbase);
539         dir = opendir(dname);
540
541         if(!dir) {
542                 fprintf(stderr, "ERROR: cannot read directory %s: %s\n", dname, strerror(errno));
543                 return 1;
544         }
545
546         while((ent = readdir(dir))) {
547                 if(strtailcmp(ent->d_name, "-up") && strtailcmp(ent->d_name, "-down")) {
548                         continue;
549                 }
550
551                 strncpy(fname, ent->d_name, sizeof(fname));
552                 char *dash = strrchr(fname, '-');
553
554                 if(!dash) {
555                         continue;
556                 }
557
558                 *dash = 0;
559
560                 snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, ent->d_name);
561
562                 if(access(fname, R_OK | X_OK)) {
563                         if(errno != EACCES) {
564                                 fprintf(stderr, "ERROR: cannot access %s: %s\n", fname, strerror(errno));
565                                 continue;
566                         }
567
568                         fprintf(stderr, "WARNING: cannot read and execute %s: %s\n", fname, strerror(errno));
569
570                         if(ask_fix()) {
571                                 if(chmod(fname, 0755)) {
572                                         fprintf(stderr, "ERROR: cannot change permissions on %s: %s\n", fname, strerror(errno));
573                                 }
574                         }
575                 }
576         }
577
578         closedir(dir);
579
580         // Check for obsolete / unsafe / unknown configuration variables.
581
582         check_conffile(tinc_conf, true);
583
584         dir = opendir(dname);
585
586         if(dir) {
587                 while((ent = readdir(dir))) {
588                         if(!check_id(ent->d_name)) {
589                                 continue;
590                         }
591
592                         snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, ent->d_name);
593                         check_conffile(fname, false);
594                 }
595
596                 closedir(dir);
597         }
598
599         return 0;
600 }
601