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