main.c 11.7 KB
Newer Older
Marek Vavruša's avatar
Marek Vavruša committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

Marek Vavruša's avatar
Marek Vavruša committed
17 18 19
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
20
#include <uv.h>
21
#include <assert.h>
22 23 24
#include <contrib/cleanup.h>
#include <contrib/ucw/mempool.h>
#include <contrib/ccan/asprintf/asprintf.h>
25
#include <libknot/error.h>
26 27 28
#ifdef HAS_SYSTEMD
#include <systemd/sd-daemon.h>
#endif
Marek Vavruša's avatar
Marek Vavruša committed
29

30
#include "lib/defines.h"
31
#include "lib/resolve.h"
32
#include "lib/dnssec.h"
33 34
#include "daemon/network.h"
#include "daemon/worker.h"
35
#include "daemon/engine.h"
36
#include "daemon/bindings.h"
37

38 39 40
/*
 * Globals
 */
41 42
static bool g_quiet = false;
static bool g_interactive = true;
43 44 45 46

/*
 * TTY control
 */
47 48
static void tty_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
{
49 50 51 52 53 54 55 56 57
	/* Set output streams */
	FILE *out = stdout, *outerr = stderr;
	uv_os_fd_t stream_fd = 0;
	uv_fileno((uv_handle_t *)stream, &stream_fd);
	if (stream_fd != STDIN_FILENO) {
		if (nread <= 0) { /* Close if disconnected */
			uv_close((uv_handle_t *)stream, (uv_close_cb) free);
			return;
		}
58 59
		uv_os_fd_t dup_fd = dup(stream_fd);
		if (dup_fd >= 0) {
60
			out = outerr = fdopen(dup_fd, "w");
61
		}
62 63
	}
	/* Execute */
64 65
	if (stream && buf && nread > 0) {
		char *cmd = buf->base;
66 67 68 69 70 71
		if (cmd[nread - 1] == '\n') {
			cmd[nread - 1] = '\0';
		}
		struct engine *engine = stream->data;
		lua_State *L = engine->L;
		int ret = engine_cmd(engine, cmd);
72
		const char *message = "";
73
		if (lua_gettop(L) > 0) {
74
			message = lua_tostring(L, -1);
75
		}
76 77
		/* Log to remote socket if connected */
		const char *delim = g_quiet ? "" : "> ";
78 79
		if (stream_fd != STDIN_FILENO) {
			fprintf(stdout, "%s\n", cmd); /* Duplicate command to logs */
80 81 82 83 84
			if (message)
				fprintf(out, "%s", message); /* Duplicate output to sender */
			if (message || !g_quiet)
				fprintf(out, "\n");
			fprintf(out, "%s", delim);
85
		}
86 87 88 89 90 91 92
		/* Log to standard streams */
		FILE *fp_out = ret ? stderr : stdout;
		if (message)
			fprintf(fp_out, "%s", message);
		if (message || !g_quiet)
			fprintf(fp_out, "\n");
		fprintf(fp_out, "%s", delim);
93
		lua_settop(L, 0);
94
		free(buf->base);
95
	}
96 97 98 99 100
	fflush(out);
	/* Close if redirected */
	if (stream_fd != STDIN_FILENO) {
		fclose(out); /* outerr is the same */
	}
101 102 103 104

}

static void tty_alloc(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) {
Marek Vavruša's avatar
Marek Vavruša committed
105 106
	buf->len = suggested;
	buf->base = malloc(suggested);
107
}
108

109 110 111 112 113 114
static void tty_accept(uv_stream_t *master, int status)
{
	uv_tcp_t *client = malloc(sizeof(*client));
	if (client) {
		 uv_tcp_init(master->loop, client);
		 if (uv_accept(master, (uv_stream_t *)client) != 0) {
115 116
			free(client);
			return;
117 118 119 120
		 }
		 client->data = master->data;
		 uv_read_start((uv_stream_t *)client, tty_alloc, tty_read);
		 /* Write command line */
121 122 123 124
		 if (!g_quiet) {
		 	uv_buf_t buf = { "> ", 2 };
		 	uv_try_write((uv_stream_t *)client, &buf, 1);
		 }
125 126 127 128
	}
}

static void signal_handler(uv_signal_t *handle, int signum)
129 130 131 132 133
{
	uv_stop(uv_default_loop());
	uv_signal_stop(handle);
}

134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
static const char *set_addr(char *addr, int *port)
{
	char *p = strchr(addr, '#');
	if (p) {
		*port = atoi(p + 1);
		*p = '\0';
	}

	return addr;
}

/*
 * Server operation.
 */

Marek Vavruša's avatar
Marek Vavruša committed
149
static void help(int argc, char *argv[])
Marek Vavruša's avatar
Marek Vavruša committed
150
{
151
	printf("Usage: %s [parameters] [rundir]\n", argv[0]);
Marek Vavruša's avatar
Marek Vavruša committed
152
	printf("\nParameters:\n"
153
	       " -a, --addr=[addr]    Server address (default: localhost#53).\n"
154
	       " -S, --fd=[fd]        Listen on given fd (handed out by supervisor).\n"
155
	       " -c, --config=[path]  Config file path (relative to [rundir]) (default: config).\n"
156 157
	       " -k, --keyfile=[path] File containing trust anchors (DS or DNSKEY).\n"
	       " -f, --forks=N        Start N forks sharing the configuration.\n"
158
	       " -q, --quiet          Quiet output, no prompt in interactive mode.\n"
159 160 161
	       " -v, --verbose        Run in verbose mode.\n"
	       " -V, --version        Print version of the server.\n"
	       " -h, --help           Print help and usage.\n"
162
	       "Options:\n"
163
	       " [rundir]             Path to the working directory (default: .)\n");
Marek Vavruša's avatar
Marek Vavruša committed
164 165
}

166
static struct worker_ctx *init_worker(uv_loop_t *loop, struct engine *engine, knot_mm_t *pool, int worker_id, int worker_count)
167
{
168 169 170 171 172
	/* Load bindings */
	engine_lualib(engine, "modules", lib_modules);
	engine_lualib(engine, "net",     lib_net);
	engine_lualib(engine, "cache",   lib_cache);
	engine_lualib(engine, "event",   lib_event);
173
	engine_lualib(engine, "worker",  lib_worker);
174 175 176 177 178

	/* Create main worker. */
	struct worker_ctx *worker = mm_alloc(pool, sizeof(*worker));
	if(!worker) {
		return NULL;
Marek Vavruša's avatar
Marek Vavruša committed
179
	}
180
	memset(worker, 0, sizeof(*worker));
181 182
	worker->id = worker_id;
	worker->count = worker_count;
183 184 185 186
	worker->engine = engine,
	worker->loop = loop;
	loop->data = worker;
	worker_reserve(worker, MP_FREELIST_SIZE);
187 188 189
	/* Register worker in Lua thread */
	lua_pushlightuserdata(engine->L, worker);
	lua_setglobal(engine->L, "__worker");
190 191 192
	lua_getglobal(engine->L, "worker");
	lua_pushnumber(engine->L, worker_id);
	lua_setfield(engine->L, -2, "id");
193 194
	lua_pushnumber(engine->L, worker_count);
	lua_setfield(engine->L, -2, "count");
195
	lua_pop(engine->L, 1);
196 197
	return worker;
}
198

199
static int run_worker(uv_loop_t *loop, struct engine *engine)
200 201 202 203 204 205 206
{
	/* Control sockets or TTY */
	auto_free char *sock_file = NULL;
	uv_pipe_t pipe;
	uv_pipe_init(loop, &pipe, 0);
	pipe.data = engine;
	if (g_interactive) {
207 208
		if (!g_quiet)
			printf("[system] interactive mode\n> ");
209 210 211 212 213 214 215 216 217 218 219
		fflush(stdout);
		uv_pipe_open(&pipe, 0);
		uv_read_start((uv_stream_t*) &pipe, tty_alloc, tty_read);
	} else {
		(void) mkdir("tty", S_IRWXU|S_IRWXG);
		sock_file = afmt("tty/%ld", getpid());
		if (sock_file) {
			uv_pipe_bind(&pipe, sock_file);
			uv_listen((uv_stream_t *) &pipe, 16, tty_accept);
		}
	}
220 221 222 223
	/* Notify supervisor. */
#ifdef HAS_SYSTEMD
	sd_notify(0, "READY=1");
#endif
224
	/* Run event loop */
225
	uv_run(loop, UV_RUN_DEFAULT);
226 227 228
	if (sock_file) {
		unlink(sock_file);
	}
229
	return kr_ok();
Marek Vavruša's avatar
Marek Vavruša committed
230 231 232 233
}

int main(int argc, char **argv)
{
Marek Vavruša's avatar
Marek Vavruša committed
234
	int forks = 1;
235
	array_t(char*) addr_set;
236
	array_t(int) fd_set;
237
	array_init(addr_set);
238
	array_init(fd_set);
239
	char *keyfile = NULL;
240
	const char *config = NULL;
241
	char *keyfile_buf = NULL;
Marek Vavruša's avatar
Marek Vavruša committed
242 243 244 245

	/* Long options. */
	int c = 0, li = 0, ret = 0;
	struct option opts[] = {
246
		{"addr", required_argument,   0, 'a'},
247
		{"fd",   required_argument,   0, 'S'},
248
		{"config", required_argument, 0, 'c'},
249 250 251
		{"keyfile",required_argument, 0, 'k'},
		{"forks",required_argument,   0, 'f'},
		{"verbose",    no_argument,   0, 'v'},
252
		{"quiet",      no_argument,   0, 'q'},
253 254
		{"version",   no_argument,    0, 'V'},
		{"help",      no_argument,    0, 'h'},
Marek Vavruša's avatar
Marek Vavruša committed
255 256
		{0, 0, 0, 0}
	};
257
	while ((c = getopt_long(argc, argv, "a:S:c:f:k:vqVh", opts, &li)) != -1) {
Marek Vavruša's avatar
Marek Vavruša committed
258 259 260
		switch (c)
		{
		case 'a':
261
			array_push(addr_set, optarg);
Marek Vavruša's avatar
Marek Vavruša committed
262
			break;
263 264 265
		case 'S':
			array_push(fd_set,  atoi(optarg));
			break;
266 267 268
		case 'c':
			config = optarg;
			break;
Marek Vavruša's avatar
Marek Vavruša committed
269
		case 'f':
270
			g_interactive = false;
Marek Vavruša's avatar
Marek Vavruša committed
271 272
			forks = atoi(optarg);
			if (forks == 0) {
273
				kr_log_error("[system] error '-f' requires number, not '%s'\n", optarg);
Marek Vavruša's avatar
Marek Vavruša committed
274 275
				return EXIT_FAILURE;
			}
276 277
#if (!defined(UV_VERSION_HEX)) || (!defined(SO_REUSEPORT))
			if (forks > 1) {
278
				kr_log_error("[system] libuv 1.7+ is required for SO_REUSEPORT support, multiple forks not supported\n");
279 280 281
				return EXIT_FAILURE;
			}
#endif
Marek Vavruša's avatar
Marek Vavruša committed
282
			break;
283
		case 'k':
284
			keyfile_buf = malloc(PATH_MAX);
285
			assert(keyfile_buf);
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
			/* Check if the path is absolute */
			if (optarg[0] == '/') {
				keyfile = strdup(optarg);
			} else {
				/* Construct absolute path, the file may not exist */
				keyfile = realpath(".", keyfile_buf);
				if (keyfile) {
					int len = strlen(keyfile);
					int namelen = strlen(optarg);
					if (len + namelen < PATH_MAX - 1) {
						keyfile[len] = '/';
						memcpy(keyfile + len + 1, optarg, namelen + 1);
						keyfile = strdup(keyfile); /* Duplicate */
					} else {
						keyfile = NULL; /* Invalidate */
					}
				}
			}
304
			free(keyfile_buf);
305
			if (!keyfile) {
306
				kr_log_error("[system] keyfile '%s': not writeable\n", optarg);
307 308 309
				return EXIT_FAILURE;
			}
			break;
310
		case 'v':
311
			kr_debug_set(true);
312
			break;
313 314 315
		case 'q':
			g_quiet = true;
			break;
316
		case 'V':
317
			kr_log_info("%s, version %s\n", "Knot DNS Resolver", PACKAGE_VERSION);
Marek Vavruša's avatar
Marek Vavruša committed
318 319 320
			return EXIT_SUCCESS;
		case 'h':
		case '?':
Marek Vavruša's avatar
Marek Vavruša committed
321
			help(argc, argv);
Marek Vavruša's avatar
Marek Vavruša committed
322 323
			return EXIT_SUCCESS;
		default:
Marek Vavruša's avatar
Marek Vavruša committed
324
			help(argc, argv);
Marek Vavruša's avatar
Marek Vavruša committed
325 326 327 328
			return EXIT_FAILURE;
		}
	}

329 330 331 332 333 334 335 336 337
#ifdef HAS_SYSTEMD
	/* Accept passed sockets from systemd supervisor. */
	int sd_nsocks = sd_listen_fds(0);
	for (int i = 0; i < sd_nsocks; ++i) {
		int fd = SD_LISTEN_FDS_START + i;
		array_push(fd_set, fd);
	}
#endif

338 339
	/* Switch to rundir. */
	if (optind < argc) {
340 341
		const char *rundir = argv[optind];
		if (access(rundir, W_OK) != 0) {
342
			kr_log_error("[system] rundir '%s': %s\n", rundir, strerror(errno));
343 344 345
			return EXIT_FAILURE;
		}
		ret = chdir(rundir);
346
		if (ret != 0) {
347
			kr_log_error("[system] rundir '%s': %s\n", rundir, strerror(errno));
348 349
			return EXIT_FAILURE;
		}
350
		if(config && strcmp(config, "-") != 0 && access(config, R_OK) != 0) {
351 352
			kr_log_error("[system] rundir '%s'\n", rundir);
			kr_log_error("[system] config '%s': %s\n", config, strerror(errno));
353 354
			return EXIT_FAILURE;
		}
355
	}
356

357 358
	kr_crypto_init();

359
	/* Fork subprocesses if requested */
360
	int fork_count = forks;
361 362 363 364 365 366 367 368
	while (--forks > 0) {
		int pid = fork();
		if (pid < 0) {
			perror("[system] fork");
			return EXIT_FAILURE;
		}
		/* Forked process */
		if (pid == 0) {
369
			kr_crypto_reinit();
370 371 372 373
			break;
		}
	}

374
	/* Block signals. */
375
	uv_loop_t *loop = uv_default_loop();
376
	uv_signal_t sigint, sigterm;
377
	uv_signal_init(loop, &sigint);
378
	uv_signal_init(loop, &sigterm);
379
	uv_signal_start(&sigint, signal_handler, SIGINT);
380
	uv_signal_start(&sigterm, signal_handler, SIGTERM);
381
	/* Create a server engine. */
382
	knot_mm_t pool = {
383
		.ctx = mp_new (4096),
384
		.alloc = (knot_mm_alloc_t) mp_alloc
385
	};
386 387 388
	struct engine engine;
	ret = engine_init(&engine, &pool);
	if (ret != 0) {
389
		kr_log_error("[system] failed to initialize engine: %s\n", kr_strerror(ret));
390 391
		return EXIT_FAILURE;
	}
392
	/* Create worker */
393
	struct worker_ctx *worker = init_worker(loop, &engine, &pool, forks, fork_count);
394
	if (!worker) {
395
		kr_log_error("[system] not enough memory\n");
396 397
		return EXIT_FAILURE;
	}
398 399 400 401 402 403 404 405
	/* Bind to passed fds and run */
	for (size_t i = 0; i < fd_set.len; ++i) {
		ret = network_listen_fd(&engine.net, fd_set.at[i]);
		if (ret != 0) {
			kr_log_error("[system] listen on fd=%d %s\n", fd_set.at[i], kr_strerror(ret));
			ret = EXIT_FAILURE;
		}
	}
406
	/* Bind to sockets and run */
407 408 409
	for (size_t i = 0; i < addr_set.len; ++i) {
		int port = 53;
		const char *addr = set_addr(addr_set.at[i], &port);
410 411
		ret = network_listen(&engine.net, addr, (uint16_t)port, NET_UDP|NET_TCP);
		if (ret != 0) {
412
			kr_log_error("[system] bind to '%s#%d' %s\n", addr, port, kr_strerror(ret));
413 414 415
			ret = EXIT_FAILURE;
		}
	}
416
	/* Start the scripting engine */
417
	if (ret == 0) {
418
		ret = engine_start(&engine, config ? config : "config");
419 420 421 422
		if (ret == 0) {
			if (keyfile) {
				auto_free char *cmd = afmt("trust_anchors.file = '%s'", keyfile);
				if (!cmd) {
423
					kr_log_error("[system] not enough memory\n");
424 425 426 427 428 429 430 431
					return EXIT_FAILURE;
				}
				engine_cmd(&engine, cmd);
				lua_settop(engine.L, 0);
			}
			/* Run the event loop */
			ret = run_worker(loop, &engine);
		}
432
	}
433
	/* Cleanup. */
434
	array_clear(addr_set);
435
	engine_deinit(&engine);
436 437
	worker_reclaim(worker);
	mp_delete(pool.ctx);
438 439 440
	if (ret != 0) {
		ret = EXIT_FAILURE;
	}
441
	kr_crypto_cleanup();
442 443
	return ret;
}