1c9b6ed6d04535ad7d440b0e3aa6169f8d9f92d1
[tinc] / src / tincd.c
1 /*
2     tincd.c -- the main file for tincd
3     Copyright (C) 1998-2005 Ivo Timmermans
4                   2000-2022 Guus Sliepen <guus@tinc-vpn.org>
5                   2008      Max Rijevski <maksuf@gmail.com>
6                   2009      Michael Tokarev <mjt@tls.msk.ru>
7                   2010      Julien Muchembled <jm@jmuchemb.eu>
8                   2010      Timothy Redaelli <timothy@redaelli.eu>
9
10     This program is free software; you can redistribute it and/or modify
11     it under the terms of the GNU General Public License as published by
12     the Free Software Foundation; either version 2 of the License, or
13     (at your option) any later version.
14
15     This program is distributed in the hope that it will be useful,
16     but WITHOUT ANY WARRANTY; without even the implied warranty of
17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18     GNU General Public License for more details.
19
20     You should have received a copy of the GNU General Public License along
21     with this program; if not, write to the Free Software Foundation, Inc.,
22     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 */
24
25 #include "system.h"
26
27 /* Darwin (MacOS/X) needs the following definition... */
28 #ifndef _P1003_1B_VISIBLE
29 #define _P1003_1B_VISIBLE
30 #endif
31
32 #ifdef HAVE_LZO
33 #include LZO1X_H
34 #endif
35
36 #ifdef HAVE_LZ4
37 #include <lz4.h>
38 #endif
39
40 #ifndef HAVE_WINDOWS
41 #include <pwd.h>
42 #include <grp.h>
43 #include <time.h>
44 #endif
45
46 #include "conf.h"
47 #include "crypto.h"
48 #include "event.h"
49 #include "logger.h"
50 #include "names.h"
51 #include "net.h"
52 #include "process.h"
53 #include "protocol.h"
54 #include "utils.h"
55 #include "xalloc.h"
56 #include "version.h"
57 #include "random.h"
58 #include "sandbox.h"
59 #include "watchdog.h"
60 #include "fs.h"
61
62 /* If nonzero, display usage information and exit. */
63 static bool show_help = false;
64
65 /* If nonzero, print the version on standard output and exit.  */
66 static bool show_version = false;
67
68 #ifdef HAVE_MLOCKALL
69 /* If nonzero, disable swapping for this process. */
70 static bool do_mlock = false;
71 #endif
72
73 #ifndef HAVE_WINDOWS
74 /* If nonzero, chroot to netdir after startup. */
75 static bool do_chroot = false;
76
77 /* If !NULL, do setuid to given user after startup */
78 static const char *switchuser = NULL;
79 #endif
80
81 char **g_argv;                  /* a copy of the cmdline arguments */
82
83 static int status = 1;
84
85 typedef enum option_t {
86         OPT_BAD_OPTION  = '?',
87         OPT_LONG_OPTION =  0,
88
89         // Short options
90         OPT_CONFIG_FILE = 'c',
91         OPT_NETNAME     = 'n',
92         OPT_NO_DETACH   = 'D',
93         OPT_DEBUG       = 'd',
94         OPT_MLOCK       = 'L',
95         OPT_CHROOT      = 'R',
96         OPT_CHANGE_USER = 'U',
97         OPT_SYSLOG      = 's',
98         OPT_OPTION      = 'o',
99
100         // Long options
101         OPT_HELP        = 255,
102         OPT_VERSION,
103         OPT_NO_SECURITY,
104         OPT_LOGFILE,
105         OPT_PIDFILE,
106 } option_t;
107
108 static struct option const long_options[] = {
109         {"config",          required_argument, NULL, OPT_CONFIG_FILE},
110         {"net",             required_argument, NULL, OPT_NETNAME},
111         {"no-detach",       no_argument,       NULL, OPT_NO_DETACH},
112         {"debug",           optional_argument, NULL, OPT_DEBUG},
113         {"mlock",           no_argument,       NULL, OPT_MLOCK},
114         {"chroot",          no_argument,       NULL, OPT_CHROOT},
115         {"user",            required_argument, NULL, OPT_CHANGE_USER},
116         {"syslog",          no_argument,       NULL, OPT_SYSLOG},
117         {"option",          required_argument, NULL, OPT_OPTION},
118         {"help",            no_argument,       NULL, OPT_HELP},
119         {"version",         no_argument,       NULL, OPT_VERSION},
120         {"bypass-security", no_argument,       NULL, OPT_NO_SECURITY},
121         {"logfile",         optional_argument, NULL, OPT_LOGFILE},
122         {"pidfile",         required_argument, NULL, OPT_PIDFILE},
123         {NULL,              0,                 NULL, 0},
124 };
125
126 #ifdef HAVE_WINDOWS
127 static struct WSAData wsa_state;
128 int main2(int argc, char **argv);
129 #endif
130
131 static void usage(bool status) {
132         if(status)
133                 fprintf(stderr, "Try `%s --help\' for more information.\n",
134                         program_name);
135         else {
136                 fprintf(stdout,
137                         "Usage: %s [option]...\n"
138                         "\n"
139                         "  -c, --config=DIR              Read configuration options from DIR.\n"
140                         "  -D, --no-detach               Don't fork and detach.\n"
141                         "  -d, --debug[=LEVEL]           Increase debug level or set it to LEVEL.\n"
142                         "  -n, --net=NETNAME             Connect to net NETNAME.\n"
143 #ifdef HAVE_MLOCKALL
144                         "  -L, --mlock                   Lock tinc into main memory.\n"
145 #endif
146                         "      --logfile[=FILENAME]      Write log entries to a logfile.\n"
147                         "  -s  --syslog                  Use syslog instead of stderr with --no-detach.\n"
148                         "      --pidfile=FILENAME        Write PID and control socket cookie to FILENAME.\n"
149                         "      --bypass-security         Disables meta protocol security, for debugging.\n"
150                         "  -o, --option[HOST.]KEY=VALUE  Set global/host configuration value.\n"
151 #ifndef HAVE_WINDOWS
152                         "  -R, --chroot                  chroot to NET dir at startup.\n"
153                         "  -U, --user=USER               setuid to given USER at startup.\n"
154 #endif
155                         "      --help                    Display this help and exit.\n"
156                         "      --version                 Output version information and exit.\n"
157                         "\n"
158                         "Report bugs to tinc@tinc-vpn.org.\n",
159                         program_name);
160         }
161 }
162
163 // Try to resolve path to absolute, return a copy of the argument if this fails.
164 static char *get_path_arg(char *arg) {
165         char *result = absolute_path(arg);
166
167         if(!result) {
168                 result = xstrdup(arg);
169         }
170
171         return result;
172 }
173
174 static bool parse_options(int argc, char **argv) {
175         config_t *cfg;
176         int r;
177         int option_index = 0;
178         int lineno = 0;
179
180         while((r = getopt_long(argc, argv, "c:DLd::n:so:RU:", long_options, &option_index)) != EOF) {
181                 switch((option_t) r) {
182                 case OPT_LONG_OPTION:
183                         break;
184
185                 case OPT_BAD_OPTION:
186                         usage(true);
187                         goto exit_fail;
188
189                 case OPT_CONFIG_FILE:
190                         assert(optarg);
191                         free(confbase);
192                         confbase = get_path_arg(optarg);
193                         break;
194
195                 case OPT_NO_DETACH:
196                         do_detach = false;
197                         break;
198
199                 case OPT_MLOCK: /* lock tincd into RAM */
200 #ifndef HAVE_MLOCKALL
201                         logger(DEBUG_ALWAYS, LOG_ERR, "The %s option is not supported on this platform.", argv[optind - 1]);
202                         goto exit_fail;
203 #else
204                         do_mlock = true;
205                         break;
206 #endif
207
208                 case OPT_DEBUG: /* increase debug level */
209                         if(!optarg && optind < argc && *argv[optind] != '-') {
210                                 optarg = argv[optind++];
211                         }
212
213                         if(optarg) {
214                                 debug_level = atoi(optarg);
215                         } else {
216                                 debug_level++;
217                         }
218
219                         break;
220
221                 case OPT_NETNAME:
222                         assert(optarg);
223                         free(netname);
224                         netname = xstrdup(optarg);
225                         break;
226
227                 case OPT_SYSLOG:
228                         use_logfile = false;
229                         use_syslog = true;
230                         break;
231
232                 case OPT_OPTION:
233                         cfg = parse_config_line(optarg, NULL, ++lineno);
234
235                         if(!cfg) {
236                                 goto exit_fail;
237                         }
238
239                         list_insert_tail(&cmdline_conf, cfg);
240                         break;
241
242 #ifdef HAVE_WINDOWS
243
244                 case OPT_CHANGE_USER:
245                 case OPT_CHROOT:
246                         logger(DEBUG_ALWAYS, LOG_ERR, "The %s option is not supported on this platform.", argv[optind - 1]);
247                         goto exit_fail;
248 #else
249
250                 case OPT_CHROOT:
251                         do_chroot = true;
252                         break;
253
254                 case OPT_CHANGE_USER:
255                         switchuser = optarg;
256                         break;
257 #endif
258
259                 case OPT_HELP:
260                         show_help = true;
261                         break;
262
263                 case OPT_VERSION:
264                         show_version = true;
265                         break;
266
267                 case OPT_NO_SECURITY:
268                         bypass_security = true;
269                         break;
270
271                 case OPT_LOGFILE:
272                         use_syslog = false;
273                         use_logfile = true;
274
275                         if(!optarg && optind < argc && *argv[optind] != '-') {
276                                 optarg = argv[optind++];
277                         }
278
279                         if(optarg) {
280                                 free(logfilename);
281                                 logfilename = get_path_arg(optarg);
282                         }
283
284                         break;
285
286                 case OPT_PIDFILE:
287                         assert(optarg);
288                         free(pidfilename);
289                         pidfilename = get_path_arg(optarg);
290                         break;
291
292                 default:
293                         break;
294                 }
295         }
296
297         if(optind < argc) {
298                 fprintf(stderr, "%s: unrecognized argument '%s'\n", argv[0], argv[optind]);
299                 usage(true);
300                 goto exit_fail;
301         }
302
303         if(!netname && (netname = getenv("NETNAME"))) {
304                 netname = xstrdup(netname);
305         }
306
307         /* netname "." is special: a "top-level name" */
308
309         if(netname && (!*netname || !strcmp(netname, "."))) {
310                 free(netname);
311                 netname = NULL;
312         }
313
314         if(netname && !check_netname(netname, false)) {
315                 fprintf(stderr, "Invalid character in netname!\n");
316                 goto exit_fail;
317         }
318
319         if(netname && !check_netname(netname, true)) {
320                 fprintf(stderr, "Warning: unsafe character in netname!\n");
321         }
322
323         return true;
324
325 exit_fail:
326         free_names();
327         list_empty_list(&cmdline_conf);
328         return false;
329 }
330
331 static bool read_sandbox_level(void) {
332         sandbox_level_t level;
333         char *value = NULL;
334
335         if(get_config_string(lookup_config(&config_tree, "Sandbox"), &value)) {
336                 if(!strcasecmp("off", value)) {
337                         level = SANDBOX_NONE;
338                 } else if(!strcasecmp("normal", value)) {
339                         level = SANDBOX_NORMAL;
340                 } else if(!strcasecmp("high", value)) {
341                         level = SANDBOX_HIGH;
342                 } else {
343                         logger(DEBUG_ALWAYS, LOG_ERR, "Bad sandbox value %s!", value);
344                         free(value);
345                         return false;
346                 }
347
348                 free(value);
349         } else {
350 #ifdef HAVE_SANDBOX
351                 level = SANDBOX_NORMAL;
352 #else
353                 level = SANDBOX_NONE;
354 #endif
355         }
356
357 #ifndef HAVE_SANDBOX
358
359         if(level > SANDBOX_NONE) {
360                 logger(DEBUG_ALWAYS, LOG_ERR, "Sandbox is used but is not supported on this platform");
361                 return false;
362         }
363
364 #endif
365         sandbox_set_level(level);
366         return true;
367 }
368
369 static bool drop_privs(void) {
370 #ifndef HAVE_WINDOWS
371         uid_t uid = 0;
372
373         if(switchuser) {
374                 struct passwd *pw = getpwnam(switchuser);
375
376                 if(!pw) {
377                         logger(DEBUG_ALWAYS, LOG_ERR, "unknown user `%s'", switchuser);
378                         return false;
379                 }
380
381                 uid = pw->pw_uid;
382
383                 // The second parameter to initgroups on macOS requires int,
384                 // but __gid_t is unsigned int. There's not much we can do here.
385                 if(initgroups(switchuser, pw->pw_gid) != 0 || // NOLINT(bugprone-narrowing-conversions)
386                                 setgid(pw->pw_gid) != 0) {
387                         logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s",
388                                "initgroups", strerror(errno));
389                         return false;
390                 }
391
392 #ifndef __ANDROID__
393 // Not supported in android NDK
394                 endgrent();
395                 endpwent();
396 #endif
397         }
398
399         if(do_chroot) {
400                 tzset();        /* for proper timestamps in logs */
401
402                 if(chroot(confbase) != 0 || chdir("/") != 0) {
403                         logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s",
404                                "chroot", strerror(errno));
405                         return false;
406                 }
407
408                 free(confbase);
409                 confbase = xstrdup("");
410         }
411
412         if(switchuser)
413                 if(setuid(uid) != 0) {
414                         logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s",
415                                "setuid", strerror(errno));
416                         return false;
417                 }
418
419 #endif // HAVE_WINDOWS
420
421         makedirs(DIR_CACHE | DIR_HOSTS | DIR_INVITATIONS);
422
423         return sandbox_enter();
424 }
425
426 #ifdef HAVE_WINDOWS
427 # define setpriority(level) !SetPriorityClass(GetCurrentProcess(), (level))
428
429 static void stop_handler(void *data, int flags) {
430         (void)data;
431         (void)flags;
432
433         event_exit();
434 }
435
436 static BOOL WINAPI console_ctrl_handler(DWORD type) {
437         (void)type;
438
439         logger(DEBUG_ALWAYS, LOG_NOTICE, "Got console shutdown request");
440
441         if(WSASetEvent(stop_io.event) == FALSE) {
442                 abort();
443         }
444
445         return TRUE;
446 }
447 #else
448 # define NORMAL_PRIORITY_CLASS 0
449 # define BELOW_NORMAL_PRIORITY_CLASS 10
450 # define HIGH_PRIORITY_CLASS -10
451 # define setpriority(level) (setpriority(PRIO_PROCESS, 0, (level)))
452 #endif
453
454 static void cleanup(void) {
455         splay_empty_tree(&config_tree);
456         list_empty_list(&cmdline_conf);
457         free_names();
458 }
459
460 int main(int argc, char **argv) {
461         program_name = argv[0];
462
463         if(!parse_options(argc, argv)) {
464                 return 1;
465         }
466
467         if(show_version) {
468                 fprintf(stdout,
469                         "%s version %s (built %s %s, protocol %d.%d)\n"
470                         "Features:"
471 #ifdef HAVE_OPENSSL
472                         " openssl"
473 #endif
474 #ifdef HAVE_LIBGCRYPT
475                         " libgcrypt"
476 #endif
477 #ifdef HAVE_LZO
478                         " comp_lzo"
479 #endif
480 #ifdef HAVE_ZLIB
481                         " comp_zlib"
482 #endif
483 #ifdef HAVE_LZ4
484                         " comp_lz4"
485 #endif
486 #ifndef DISABLE_LEGACY
487                         " legacy_protocol"
488 #endif
489 #ifdef ENABLE_JUMBOGRAMS
490                         " jumbograms"
491 #endif
492 #ifdef ENABLE_TUNEMU
493                         " tunemu"
494 #endif
495 #ifdef HAVE_MINIUPNPC
496                         " miniupnpc"
497 #endif
498 #ifdef HAVE_SANDBOX
499                         " sandbox"
500 #endif
501 #ifdef ENABLE_UML
502                         " uml"
503 #endif
504 #ifdef ENABLE_VDE
505                         " vde"
506 #endif
507                         "\n\n"
508                         "Copyright (C) 1998-2021 Ivo Timmermans, Guus Sliepen and others.\n"
509                         "See the AUTHORS file for a complete list.\n"
510                         "\n"
511                         "tinc comes with ABSOLUTELY NO WARRANTY.  This is free software,\n"
512                         "and you are welcome to redistribute it under certain conditions;\n"
513                         "see the file COPYING for details.\n",
514                         PACKAGE, BUILD_VERSION, BUILD_DATE, BUILD_TIME, PROT_MAJOR, PROT_MINOR);
515                 return 0;
516         }
517
518         if(show_help) {
519                 usage(false);
520                 return 0;
521         }
522
523         make_names(true);
524         atexit(cleanup);
525
526         if(chdir(confbase) == -1) {
527                 logger(DEBUG_ALWAYS, LOG_ERR, "Could not change to configuration directory: %s", strerror(errno));
528                 return 1;
529         }
530
531 #ifdef HAVE_WINDOWS
532
533         if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) {
534                 logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError()));
535                 return 1;
536         }
537
538 #else
539         // Check if we got an umbilical fd from the process that started us
540         char *umbstr = getenv("TINC_UMBILICAL");
541
542         if(umbstr) {
543                 int colorize = 0;
544                 sscanf(umbstr, "%d %d", &umbilical, &colorize);
545                 umbilical_colorize = colorize;
546
547                 if(fcntl(umbilical, F_GETFL) < 0) {
548                         umbilical = 0;
549                 }
550
551 #ifdef FD_CLOEXEC
552
553                 if(umbilical) {
554                         fcntl(umbilical, F_SETFD, FD_CLOEXEC);
555                 }
556
557 #endif
558         }
559
560 #endif
561
562         openlogger("tinc", use_logfile ? LOGMODE_FILE : LOGMODE_STDERR);
563
564         g_argv = argv;
565
566         if(getenv("LISTEN_PID") && atoi(getenv("LISTEN_PID")) == getpid()) {
567                 do_detach = false;
568         }
569
570 #ifdef HAVE_UNSETENV
571         unsetenv("LISTEN_PID");
572 #endif
573
574         gettimeofday(&now, NULL);
575         random_init();
576         crypto_init();
577         prng_init();
578
579         if(!read_server_config(&config_tree)) {
580                 return 1;
581         }
582
583         if(!read_sandbox_level()) {
584                 return 1;
585         }
586
587         if(debug_level == DEBUG_NOTHING) {
588                 int level = 0;
589
590                 if(get_config_int(lookup_config(&config_tree, "LogLevel"), &level)) {
591                         debug_level = level;
592                 }
593         }
594
595 #ifdef HAVE_LZO
596
597         if(lzo_init() != LZO_E_OK) {
598                 logger(DEBUG_ALWAYS, LOG_ERR, "Error initializing LZO compressor!");
599                 return 1;
600         }
601
602 #endif
603
604 #ifdef HAVE_WINDOWS
605         io_add_event(&stop_io, stop_handler, NULL, WSACreateEvent());
606
607         if(stop_io.event == FALSE) {
608                 abort();
609         }
610
611         int result;
612
613         if(!do_detach || !init_service()) {
614                 SetConsoleCtrlHandler(console_ctrl_handler, TRUE);
615                 result = main2(argc, argv);
616         } else {
617                 result = 1;
618         }
619
620         if(WSACloseEvent(stop_io.event) == FALSE) {
621                 abort();
622         }
623
624         io_del(&stop_io);
625         return result;
626 }
627
628 int main2(int argc, char **argv) {
629         (void)argc;
630         (void)argv;
631 #endif
632         char *priority = NULL;
633
634         if(!detach()) {
635                 return 1;
636         }
637
638 #ifdef HAVE_MLOCKALL
639
640         /* Lock all pages into memory if requested.
641          * This has to be done after daemon()/fork() so it works for child.
642          * No need to do that in parent as it's very short-lived. */
643         if(do_mlock && mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
644                 logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "mlockall",
645                        strerror(errno));
646                 return 1;
647         }
648
649 #endif
650
651         /* Setup sockets and open device. */
652
653         if(!setup_network()) {
654                 goto end;
655         }
656
657         /* Change process priority */
658
659         if(get_config_string(lookup_config(&config_tree, "ProcessPriority"), &priority)) {
660                 if(!strcasecmp(priority, "Normal")) {
661                         if(setpriority(NORMAL_PRIORITY_CLASS) != 0) {
662                                 logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setpriority", strerror(errno));
663                                 goto end;
664                         }
665                 } else if(!strcasecmp(priority, "Low")) {
666                         if(setpriority(BELOW_NORMAL_PRIORITY_CLASS) != 0) {
667                                 logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setpriority", strerror(errno));
668                                 goto end;
669                         }
670                 } else if(!strcasecmp(priority, "High")) {
671                         if(setpriority(HIGH_PRIORITY_CLASS) != 0) {
672                                 logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setpriority", strerror(errno));
673                                 goto end;
674                         }
675                 } else {
676                         logger(DEBUG_ALWAYS, LOG_ERR, "Invalid priority `%s`!", priority);
677                         goto end;
678                 }
679         }
680
681         /* drop privileges */
682         if(!drop_privs()) {
683                 goto end;
684         }
685
686         /* Start main loop. It only exits when tinc is killed. */
687
688         logger(DEBUG_ALWAYS, LOG_NOTICE, "Ready");
689
690         if(umbilical) { // snip!
691                 if(write(umbilical, "", 1) != 1) {
692                         // Pipe full or broken, nothing we can do about it.
693                 }
694
695                 close(umbilical);
696                 umbilical = 0;
697         }
698
699         try_outgoing_connections();
700
701 #ifdef HAVE_WATCHDOG
702         watchdog_start();
703 #endif
704
705         status = main_loop();
706
707 #ifdef HAVE_WATCHDOG
708         watchdog_stop();
709 #endif
710
711         /* Shutdown properly. */
712
713 end:
714         close_network_connections();
715
716         logger(DEBUG_ALWAYS, LOG_NOTICE, "Terminating");
717
718         free(priority);
719
720         random_exit();
721
722         return status;
723 }