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