remote.c 10.8 KB
Newer Older
Daniel Salzman's avatar
Daniel Salzman committed
1
/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

    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/>.
 */

17
#include <assert.h>
Daniel Salzman's avatar
Daniel Salzman committed
18
#include <urcu.h>
19

20
#include "dnssec/random.h"
21
#include "knot/common/log.h"
22
#include "knot/conf/conf.h"
Daniel Salzman's avatar
Daniel Salzman committed
23
#include "knot/ctl/commands.h"
24
#include "knot/ctl/remote.h"
Daniel Salzman's avatar
Daniel Salzman committed
25 26
#include "knot/server/tcp-handler.h"
#include "libknot/libknot.h"
27 28
#include "contrib/net.h"
#include "contrib/sockaddr.h"
29
#include "contrib/string.h"
30
#include "contrib/openbsd/strlcpy.h"
31
#include "contrib/wire.h"
32 33 34

#define KNOT_CTL_REALM "knot."
#define KNOT_CTL_REALM_EXT ("." KNOT_CTL_REALM)
Daniel Salzman's avatar
Daniel Salzman committed
35
#define CMDARGS_BUFLEN_LOG 256
36

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
/*! \brief Initialize cmdargs_t structure. */
static int cmdargs_init(remote_cmdargs_t *args)
{
	assert(args);

	char *response = malloc(CMDARGS_ALLOC_BLOCK);
	if (!response) {
		return KNOT_ENOMEM;
	}

	memset(args, 0, sizeof(*args));
	args->response = response;
	args->response_max = CMDARGS_ALLOC_BLOCK;

	return KNOT_EOK;
}

/*! \brief Deinitialize cmdargs_t structure. */
static void cmdargs_deinit(remote_cmdargs_t *args)
{
	assert(args);

	free(args->response);
	memset(args, 0, sizeof(*args));
}

63
int remote_bind(const char *path)
64
{
65
	if (path == NULL) {
66
		return KNOT_EINVAL;
67
	}
Jan Včelák's avatar
Jan Včelák committed
68

69
	log_info("remote control, binding to '%s'", path);
Jan Včelák's avatar
Jan Včelák committed
70

71 72 73 74 75
	/* Prepare socket address. */
	struct sockaddr_storage addr;
	int ret = sockaddr_set(&addr, AF_UNIX, path, 0);
	if (ret != KNOT_EOK) {
		return ret;
76 77
	}

78 79
	/* Create new socket. */
	int sock = net_bound_socket(SOCK_STREAM, &addr, 0);
80
	if (sock < 0) {
81
		log_error("remote control, failed to bind to '%s' (%s)",
82
		          path, knot_strerror(sock));
83
		return sock;
84
	}
85 86

	/* Start listening. */
87
	if (listen(sock, TCP_BACKLOG_SIZE) != 0) {
88
		log_error("remote control, failed to listen on '%s'", path);
89
		close(sock);
Daniel Salzman's avatar
Daniel Salzman committed
90
		return knot_map_errno();
91
	}
Jan Včelák's avatar
Jan Včelák committed
92

93
	return sock;
94 95
}

96
void remote_unbind(int sock)
97
{
98 99
	if (sock < 0) {
		return;
100
	}
Jan Včelák's avatar
Jan Včelák committed
101

102 103 104 105
	/* Remove the control socket file.  */
	struct sockaddr_storage addr;
	socklen_t addr_len = sizeof(addr);
	if (getsockname(sock, (struct sockaddr *)&addr, &addr_len) == 0) {
Daniel Salzman's avatar
Daniel Salzman committed
106
		char addr_str[SOCKADDR_STRLEN] = { 0 };
107 108 109
		if (sockaddr_tostr(addr_str, sizeof(addr_str), &addr) > 0) {
			(void)unlink(addr_str);
		}
110 111
	}

112 113
	/* Close the socket.  */
	(void)close(sock);
114 115
}

116
int remote_poll(int sock, const sigset_t *sigmask)
117 118 119 120
{
	/* Wait for events. */
	fd_set rfds;
	FD_ZERO(&rfds);
121 122
	if (sock > -1) {
		FD_SET(sock, &rfds);
123
	} else {
124
		sock = -1; /* Make sure n == r + 1 == 0 */
125 126
	}

127
	return pselect(sock + 1, &rfds, NULL, NULL, NULL, sigmask);
128
}
129

130
int remote_recv(int sock, uint8_t *buf, size_t *buflen)
131
{
132
	int c = tcp_accept(sock);
133 134 135 136 137
	if (c < 0) {
		return c;
	}

	/* Receive data. */
138
	int n = net_dns_tcp_recv(c, buf, *buflen, NULL);
139
	*buflen = n;
140
	if (n <= 0) {
141
		close(c);
142
		return KNOT_ECONNREFUSED;
143
	}
Jan Včelák's avatar
Jan Včelák committed
144

145 146
	return c;
}
147

Daniel Salzman's avatar
Daniel Salzman committed
148
int remote_parse(knot_pkt_t *pkt)
149
{
150
	return knot_pkt_parse(pkt, 0);
151 152
}

153 154
static int remote_send_chunk(int c, knot_pkt_t *query, const char *d, uint16_t len,
                             int index)
155
{
Marek Vavrusa's avatar
Marek Vavrusa committed
156
	knot_pkt_t *resp = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, &query->mm);
157
	if (!resp) {
Marek Vavrusa's avatar
Marek Vavrusa committed
158
		return KNOT_ENOMEM;
159
	}
Jan Včelák's avatar
Jan Včelák committed
160

Marek Vavrusa's avatar
Marek Vavrusa committed
161
	/* Initialize response. */
162 163 164 165
	int ret = knot_pkt_init_response(resp, query);
	if (ret != KNOT_EOK) {
		goto failed;
	}
166

Marek Vavrusa's avatar
Marek Vavrusa committed
167
	/* Write to NS section. */
168 169
	ret = knot_pkt_begin(resp, KNOT_AUTHORITY);
	assert(ret == KNOT_EOK);
170

Marek Vavrusa's avatar
Marek Vavrusa committed
171
	/* Create TXT RR with result. */
172 173 174
	knot_rrset_t rr;
	ret = remote_build_rr(&rr, "result.", KNOT_RRTYPE_TXT);
	if (ret != KNOT_EOK) {
175 176 177
		goto failed;
	}

178
	ret = remote_create_txt(&rr, d, len, index);
179 180
	assert(ret == KNOT_EOK);

181
	ret = knot_pkt_put(resp, 0, &rr, KNOT_PF_FREE);
182
	if (ret != KNOT_EOK) {
183
		knot_rrset_clear(&rr, NULL);
184
		goto failed;
185
	}
Jan Včelák's avatar
Jan Včelák committed
186

Daniel Salzman's avatar
Daniel Salzman committed
187
	rcu_read_lock();
188 189
	conf_val_t *val = &conf()->cache.srv_tcp_reply_timeout;
	struct timeval timeout = { conf_int(val), 0 };
Daniel Salzman's avatar
Daniel Salzman committed
190 191
	rcu_read_unlock();

192
	ret = net_dns_tcp_send(c, resp->wire, resp->size, &timeout);
193 194 195

failed:

Marek Vavrusa's avatar
Marek Vavrusa committed
196 197
	/* Free packet. */
	knot_pkt_free(&resp);
Jan Včelák's avatar
Jan Včelák committed
198

199
	return ret;
200 201
}

Daniel Salzman's avatar
Daniel Salzman committed
202
static void log_command(const char *cmd, const remote_cmdargs_t *args)
203
{
Daniel Salzman's avatar
Daniel Salzman committed
204 205
	char params[CMDARGS_BUFLEN_LOG] = { 0 };
	size_t rest = CMDARGS_BUFLEN_LOG;
206
	size_t pos = 0;
207 208

	for (unsigned i = 0; i < args->argc; i++) {
209
		const knot_rrset_t *rr = &args->arg[i];
210
		if (rr->type != KNOT_RRTYPE_NS) {
211 212 213
			continue;
		}

214
		uint16_t rr_count = rr->rrs.rr_count;
215
		for (uint16_t j = 0; j < rr_count; j++) {
216
			const knot_dname_t *dn = knot_ns_name(&rr->rrs, j);
217
			char *name = knot_dname_to_str_alloc(dn);
218

219
			int ret = snprintf(params + pos, rest, " %s", name);
220
			free(name);
221

222 223 224
			if (ret <= 0 || ret >= rest) {
				break;
			}
225
			pos += ret;
226 227 228 229
			rest -= ret;
		}
	}

Jan Včelák's avatar
Jan Včelák committed
230
	log_info("remote control, received command '%s%s'", cmd, params);
231 232
}

233
int remote_answer(int sock, server_t *s, knot_pkt_t *pkt)
234
{
235
	if (sock < 0 || s == NULL || pkt == NULL) {
236 237
		return KNOT_EINVAL;
	}
238

239 240
	/* Prerequisites:
	 * QCLASS: CH
241
	 * QNAME: <CMD>.KNOT_CTL_REALM.
242
	 */
Marek Vavrusa's avatar
Marek Vavrusa committed
243 244
	const knot_dname_t *qname = knot_pkt_qname(pkt);
	if (knot_pkt_qclass(pkt) != KNOT_CLASS_CH) {
245
		return KNOT_EMALF;
246
	}
247

248
	knot_dname_t *realm = knot_dname_from_str_alloc(KNOT_CTL_REALM);
249
	if (!knot_dname_is_sub(qname, realm) != 0) {
250
		knot_dname_free(&realm, NULL);
251
		return KNOT_EMALF;
252
	}
253
	knot_dname_free(&realm, NULL);
Jan Včelák's avatar
Jan Včelák committed
254

255 256 257
	/* Command:
	 * QNAME: leftmost label of QNAME
	 */
258 259
	size_t cmd_len = *qname;
	char *cmd = strndup((char*)qname + 1, cmd_len);
260

261
	/* Data:
262 263
	 * NS: TSIG
	 * AR: data
264
	 */
265 266 267
	remote_cmdargs_t args = { 0 };
	int ret = cmdargs_init(&args);
	if (ret != KNOT_EOK) {
Daniel Salzman's avatar
Daniel Salzman committed
268
		free(cmd);
269
		return ret;
270
	}
Marek Vavrusa's avatar
Marek Vavrusa committed
271 272

	const knot_pktsection_t *authority = knot_pkt_section(pkt, KNOT_AUTHORITY);
273
	args.arg = knot_pkt_rr(authority, 0);
274 275
	args.argc = authority->count;
	args.rc = KNOT_RCODE_NOERROR;
276

277
	log_command(cmd, &args);
278

Daniel Salzman's avatar
Daniel Salzman committed
279
	const remote_cmd_t *c = remote_cmd_tbl;
280
	while (c->name != NULL) {
281
		if (strcmp(cmd, c->name) == 0) {
282
			ret = c->f(s, &args);
283
			break;
284
		}
285
		++c;
286
	}
Jan Včelák's avatar
Jan Včelák committed
287

288
	/* Prepare response. */
289
	if (args.response_size == 0) {
290 291
		args.response_size = strlen(knot_strerror(ret));
		strlcpy(args.response, knot_strerror(ret), args.response_max);
292
	}
Jan Včelák's avatar
Jan Včelák committed
293

294
	int index = 0;
295 296
	unsigned p = 0;
	size_t chunk = 16384;
297
	for (; p + chunk < args.response_size; p += chunk) {
298 299
		remote_send_chunk(sock, pkt, args.response + p, chunk, index);
		index++;
300
	}
301

302
	unsigned r = args.response_size - p;
303
	if (r > 0) {
304
		remote_send_chunk(sock, pkt, args.response + p, r, index);
305
	}
Jan Včelák's avatar
Jan Včelák committed
306

307
	cmdargs_deinit(&args);
308 309
	free(cmd);
	return ret;
310 311
}

312
int remote_process(server_t *server, int sock, uint8_t *buf, size_t buflen)
313
{
Marek Vavrusa's avatar
Marek Vavrusa committed
314 315
	knot_pkt_t *pkt =  knot_pkt_new(buf, buflen, NULL);
	if (pkt == NULL) {
316 317
		return KNOT_ENOMEM;
	}
Jan Včelák's avatar
Jan Včelák committed
318

319
	/* Accept incoming connection and read packet. */
320
	int client = remote_recv(sock, pkt->wire, &buflen);
Marek Vavrusa's avatar
Marek Vavrusa committed
321
	if (client < 0) {
Marek Vavrusa's avatar
Marek Vavrusa committed
322
		knot_pkt_free(&pkt);
Marek Vavrusa's avatar
Marek Vavrusa committed
323
		return client;
Marek Vavrusa's avatar
Marek Vavrusa committed
324
	} else {
Marek Vavrusa's avatar
Marek Vavrusa committed
325
		pkt->size = buflen;
326
	}
Jan Včelák's avatar
Jan Včelák committed
327

328
	/* Parse packet and answer if OK. */
Marek Vavrusa's avatar
Marek Vavrusa committed
329 330
	int ret = remote_parse(pkt);
	if (ret == KNOT_EOK) {
331
		ret = remote_answer(client, server, pkt);
Marek Vavrusa's avatar
Marek Vavrusa committed
332
	}
Jan Včelák's avatar
Jan Včelák committed
333

Marek Vavrusa's avatar
Marek Vavrusa committed
334
	knot_pkt_free(&pkt);
335
	close(client);
336 337 338
	return ret;
}

339
knot_pkt_t* remote_query(const char *query)
340
{
341 342
	if (!query) {
		return NULL;
343
	}
Jan Včelák's avatar
Jan Včelák committed
344

Marek Vavrusa's avatar
Marek Vavrusa committed
345
	knot_pkt_t *pkt = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, NULL);
Marek Vavrusa's avatar
Marek Vavrusa committed
346
	if (!pkt) {
347
		return NULL;
348
	}
Jan Včelák's avatar
Jan Včelák committed
349

350
	knot_wire_set_id(pkt->wire, dnssec_random_uint16_t());
Jan Včelák's avatar
Jan Včelák committed
351

352
	/* Question section. */
353
	char *qname = strcdup(query, KNOT_CTL_REALM_EXT);
354
	knot_dname_t *dname = knot_dname_from_str_alloc(qname);
Marek Vavrusa's avatar
Marek Vavrusa committed
355
	free(qname);
356
	if (!dname) {
Marek Vavrusa's avatar
Marek Vavrusa committed
357
		knot_pkt_free(&pkt);
358 359
		return NULL;
	}
360 361

	/* Cannot return != KNOT_EOK, but still. */
Marek Vavrusa's avatar
Marek Vavrusa committed
362 363
	if (knot_pkt_put_question(pkt, dname, KNOT_CLASS_CH, KNOT_RRTYPE_ANY) != KNOT_EOK) {
		knot_pkt_free(&pkt);
364
		knot_dname_free(&dname, NULL);
Jan Včelák's avatar
Jan Včelák committed
365
		return NULL;
366 367
	}

368
	knot_dname_free(&dname, NULL);
Marek Vavrusa's avatar
Marek Vavrusa committed
369
	return pkt;
370 371
}

372
int remote_build_rr(knot_rrset_t *rr, const char *owner, uint16_t type)
373
{
374
	if (!rr || !owner) {
375
		return KNOT_EINVAL;
376
	}
Jan Včelák's avatar
Jan Včelák committed
377

378
	/* Assert K is FQDN. */
379 380
	knot_dname_t *name = knot_dname_from_str_alloc(owner);
	if (name == NULL) {
381
		return KNOT_ENOMEM;
382
	}
Jan Včelák's avatar
Jan Včelák committed
383

384
	/* Init RRSet. */
385
	knot_rrset_init(rr, name, type, KNOT_CLASS_CH);
386

387
	return KNOT_EOK;
388 389
}

390 391
int remote_create_txt(knot_rrset_t *rr, const char *str, size_t str_len,
                      uint16_t index)
392
{
393
	if (!rr || !str) {
394
		return KNOT_EINVAL;
395
	}
Jan Včelák's avatar
Jan Včelák committed
396

397
	/* Maximal chunk size. */
398
	const size_t K = 255;
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
	/* Number of chunks (ceiling operation). */
	const size_t chunks = (str_len + K - 1)/ K;
	/* Total raw chunk length. */
	const size_t raw_len = sizeof(uint8_t) + sizeof(index) + str_len + chunks;

	uint8_t raw[raw_len];
	memset(raw, 0, raw_len);

	uint8_t *out = raw;
	const char *in = str;

	/* Write index chunk. */
	*out++ = sizeof(index);
	wire_write_u16(out, index);
	out += sizeof(index);

	if (chunks > 0) {
		/* Write leading full chunks. */
		for (size_t i = 0; i < chunks - 1; i++) {
			/* Maximal chunk length. */
			*out++ = (uint8_t)K;
			/* Data chunk. */
			memcpy(out, in, K);
			out += K;
			in += K;
424
		}
425 426 427 428 429 430 431 432

		/* Write last chunk. */
		const size_t rest = str + str_len - in;
		assert(rest <= K);
		/* Last chunk length. */
		*out++ = (uint8_t)rest;
		/* Last data chunk. */
		memcpy(out, in, rest);
433 434
	}

435
	return knot_rrset_add_rdata(rr, raw, raw_len, 0, NULL);
436 437
}

438
int remote_create_ns(knot_rrset_t *rr, const char *name)
439
{
440
	if (!rr || !name) {
441
		return KNOT_EINVAL;
442 443
	}

444
	/* Create dname. */
445
	knot_dname_t *dn = knot_dname_from_str_alloc(name);
Jan Včelák's avatar
Jan Včelák committed
446 447 448
	if (!dn) {
		return KNOT_ERROR;
	}
Jan Včelák's avatar
Jan Včelák committed
449

450
	/* Build RDATA. */
Lubos Slovak's avatar
Lubos Slovak committed
451
	int dn_size = knot_dname_size(dn);
452
	int result = knot_rrset_add_rdata(rr, dn, dn_size, 0, NULL);
453
	knot_dname_free(&dn, NULL);
Jan Včelák's avatar
Jan Včelák committed
454

455
	return result;
456 457
}

458
int remote_print_txt(const knot_rrset_t *rr, uint16_t pos)
459
{
460 461 462 463 464 465 466 467
	if (!rr) {
		return KNOT_EINVAL;
	}

	size_t count = knot_txt_count(&rr->rrs, pos);
	for (size_t i = 0; i < count; i++) {
		const uint8_t *rdata = knot_txt_data(&rr->rrs, pos, i);
		printf("%.*s", (int)rdata[0], rdata + 1);
468
	}
469

470
	return KNOT_EOK;
471
}
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503

uint8_t *remote_get_txt(const knot_rrset_t *rr, uint16_t pos, size_t *out_len)
{
	if (!rr) {
		return NULL;
	}

	// The buffer will be slightly bigger (including string lengths).
	size_t buf_len = knot_rdata_rdlen(knot_rdataset_at(&rr->rrs, pos));
	uint8_t *buf = malloc(buf_len);
	if (buf == NULL) {
		return NULL;
	}

	size_t len = 0;

	size_t count = knot_txt_count(&rr->rrs, pos);
	for (size_t i = 1; i < count; i++) {
		const uint8_t *rdata = knot_txt_data(&rr->rrs, pos, i);
		memcpy(buf + len, rdata + 1, rdata[0]);
		len += rdata[0];
	}

	// There is always at least one free byte.
	buf[len] = '\0';

	if (out_len != NULL) {
		*out_len = len;
	}

	return buf;
}