1 /*
2  * Copyright (c) 2014-2020 by Farsight Security, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *    http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #if WANT_PDNS_DNSDB
18 
19 /* asprintf() does not appear on linux without this */
20 #define _GNU_SOURCE
21 
22 #include <assert.h>
23 #include <stdio.h>
24 
25 #include "defs.h"
26 #include "pdns.h"
27 #include "pdns_dnsdb.h"
28 #include "time.h"
29 #include "globals.h"
30 
31 /* types. */
32 
33 struct rate_json {
34 	json_t	*main,
35 		*reset, *expires, *limit, *remaining,
36 		*burst_size, *burst_window, *results_max,
37 		*offset_max;
38 };
39 
40 struct rateval {
41 	enum {
42 		rk_naught = 0,		/* not present. */
43 		rk_na,			/* "n/a". */
44 		rk_unlimited,		/* "unlimited". */
45 		rk_int			/* some integer in as_int. */
46 	} rk;
47 	u_long	as_int;		/* only for rk == rk_int. */
48 };
49 typedef struct rateval *rateval_t;
50 typedef const struct rateval *rateval_ct;
51 
52 struct rate_tuple {
53 	struct rate_json  obj;
54 	struct rateval	reset, expires, limit, remaining,
55 			burst_size, burst_window, results_max,
56 			offset_max;
57 };
58 typedef struct rate_tuple *rate_tuple_t;
59 
60 /* forwards. */
61 
62 static const char *dnsdb_setval(const char *, const char *);
63 static const char *dnsdb_ready(void);
64 static void dnsdb_destroy(void);
65 static char *dnsdb_url(const char *, char *, qparam_ct, pdns_fence_ct, bool);
66 static void dnsdb_info(void);
67 static void dnsdb_auth(fetch_t);
68 static const char *dnsdb_status(fetch_t);
69 static const char *dnsdb_verb_ok(const char *, qparam_ct);
70 
71 static void print_rateval(const char *, rateval_ct, FILE *);
72 static void print_burstrate(const char *, rateval_ct, rateval_ct, FILE *);
73 static const char *rateval_make(rateval_t, const json_t *, const char *);
74 static const char *rate_tuple_make(rate_tuple_t, const char *, size_t);
75 static void rate_tuple_unmake(rate_tuple_t);
76 
77 /* variables. */
78 
79 static const char env_api_key[] = "DNSDB_API_KEY";
80 static const char env_dnsdb_base_url[] = "DNSDB_SERVER";
81 
82 static char *api_key = NULL;
83 static char *dnsdb_base_url = NULL;
84 
85 static const char dnsdb2_url_prefix[] = "/dnsdb/v2";
86 
87 static const struct pdns_system dnsdb1 = {
88 	"dnsdb1", "https://api.dnsdb.info", encap_cof,
89 	dnsdb_url, dnsdb_info, dnsdb_auth, dnsdb_status, dnsdb_verb_ok,
90 	dnsdb_setval, dnsdb_ready, dnsdb_destroy
91 };
92 
93 static const struct pdns_system dnsdb2 = {
94 	"dnsdb2", "https://api.dnsdb.info/dnsdb/v2", encap_saf,
95 	dnsdb_url, dnsdb_info, dnsdb_auth, dnsdb_status, dnsdb_verb_ok,
96 	dnsdb_setval, dnsdb_ready, dnsdb_destroy
97 };
98 
99 /*---------------------------------------------------------------- public
100  */
101 
102 pdns_system_ct
pdns_dnsdb1(void)103 pdns_dnsdb1(void) {
104 	return &dnsdb1;
105 }
106 
107 pdns_system_ct
pdns_dnsdb2(void)108 pdns_dnsdb2(void) {
109 	return &dnsdb2;
110 }
111 
112 /*---------------------------------------------------------------- private
113  */
114 
115 /* dnsdb_setval() -- install configuration element
116  */
117 static const char *
dnsdb_setval(const char * key,const char * value)118 dnsdb_setval(const char *key, const char *value) {
119 	if (strcmp(key, "apikey") == 0) {
120 		DESTROY(api_key);
121 		api_key = strdup(value);
122 	} else if (strcmp(key, "server") == 0) {
123 		DESTROY(dnsdb_base_url);
124 		dnsdb_base_url = strdup(value);
125 	} else {
126 		return "dnsdb_setval() unrecognized key";
127 	}
128 	return NULL;
129 }
130 
131 /* dnsdb_ready() -- override the config file from environment variables?
132  */
133 static const char *
dnsdb_ready(void)134 dnsdb_ready(void) {
135 	const char *value;
136 
137 	if ((value = getenv(env_api_key)) != NULL) {
138 		dnsdb_setval("apikey", value);
139 		DEBUG(1, true, "conf env api_key was set\n");
140 	}
141 	if ((value = getenv(env_dnsdb_base_url)) != NULL) {
142 		dnsdb_setval("server", value);
143 		DEBUG(1, true, "conf env dnsdb_server = '%s'\n",
144 		      dnsdb_base_url);
145 	}
146 	if (dnsdb_base_url == NULL)
147 		dnsdb_base_url = strdup(psys->base_url);
148 
149 	/* If SAF (aka APIv2) ensure URL contains special /dnsdb/v2 prefix. */
150 	if (psys->encap == encap_saf &&
151 	    strstr(dnsdb_base_url, dnsdb2_url_prefix) == NULL) {
152 		char *temp;
153 		int x;
154 
155 		x = asprintf(&temp, "%s%s", dnsdb_base_url, dnsdb2_url_prefix);
156 		if (x < 0) {
157 			perror("asprintf");
158 			abort();
159 		}
160 		DESTROY(dnsdb_base_url);
161 		dnsdb_base_url = temp;
162 	}
163 
164 	if (api_key == NULL)
165 		return "no API key given";
166 	return NULL;
167 }
168 
169 /* dnsdb_destroy() -- drop heap storage
170  */
171 static void
dnsdb_destroy(void)172 dnsdb_destroy(void) {
173 	DESTROY(api_key);
174 	DESTROY(dnsdb_base_url);
175 }
176 
177 /* dnsdb_url -- create a URL corresponding to a command-path string.
178  *
179  * the batch file and command line syntax are in native DNSDB API format.
180  * this function has the opportunity to crack this into pieces, and re-form
181  * those pieces into the URL format needed by some other DNSDB-like system
182  * which might have the same JSON output format but a different REST syntax.
183  * returns a string that must be freed.
184  */
185 static char *
dnsdb_url(const char * path,char * sep,qparam_ct qpp,pdns_fence_ct fp,bool meta_query)186 dnsdb_url(const char *path, char *sep, qparam_ct qpp,
187 	  pdns_fence_ct fp, bool meta_query)
188 {
189 	const char *verb_path, *p, *scheme_if_needed, *aggr_if_needed;
190 	char *ret = NULL, *max_count_str = NULL, *offset_str = NULL,
191 		*first_after_str = NULL, *first_before_str = NULL,
192 		*last_after_str = NULL, *last_before_str = NULL,
193 		*query_limit_str = NULL;
194 	int x, num_slash;
195 
196 	/* count the number of slashes in the base url, after the ://
197 	 * if present.  1 or more means there's a /path after the host.
198 	 * In that case, don't add /[verb] here, and also don't allow
199 	 * selecting a verb that's not "lookup" since the /path could
200 	 * include its own verb. (this is from an old python-era rule.)
201 	 */
202 	if ((p = strstr(dnsdb_base_url, "://")) != NULL)
203 		p += sizeof "://" - sizeof "";
204 	else
205 		p = dnsdb_base_url;
206 	num_slash = 0;
207 	if (strstr(dnsdb_base_url, dnsdb2_url_prefix) == NULL)
208 		for (; *p != '\0'; p++)
209 			num_slash += (*p == '/');
210 	verb_path = "";
211 	if (num_slash == 0) {
212 		if (psys->encap == encap_saf && meta_query)
213 			verb_path = "";
214 		else if (pverb->url_fragment != NULL)
215 			verb_path = pverb->url_fragment;
216 		else
217 			verb_path = "/lookup";
218 	}
219 
220 	/* supply a scheme if the server string did not. */
221 	scheme_if_needed = "";
222 	if (strstr(dnsdb_base_url, "://") == NULL)
223 		scheme_if_needed = "https://";
224 
225 	/* handle gravel vs. rocks. */
226 	aggr_if_needed = "";
227 	if (qpp->gravel)
228 		aggr_if_needed = "&aggr=f";
229 
230 	if (qpp->offset > 0) {
231 		x = asprintf(&offset_str, "&offset=%ld", qpp->offset);
232 		if (x < 0) {
233 			perror("asprintf");
234 			goto done;
235 		}
236 	}
237 
238 	if (max_count > 0) {
239 		x = asprintf(&max_count_str, "&max_count=%ld", max_count);
240 		if (x < 0) {
241 			perror("asprintf");
242 			goto done;
243 		}
244 	}
245 
246 	if (qpp->query_limit != -1) {
247 		x = asprintf(&query_limit_str, "&limit=%ld", qpp->query_limit);
248 		if (x < 0) {
249 			perror("asprintf");
250 			goto done;
251 		}
252 	}
253 
254 	if (fp->first_after != 0) {
255 		x = asprintf(&first_after_str, "&time_first_after=%lu",
256 			     fp->first_after);
257 		if (x < 0) {
258 			perror("asprintf");
259 			goto done;
260 		}
261 	}
262 	if (fp->first_before != 0) {
263 		x = asprintf(&first_before_str, "&time_first_before=%lu",
264 			     fp->first_before);
265 		if (x < 0) {
266 			perror("asprintf");
267 			goto done;
268 		}
269 	}
270 	if (fp->last_after != 0) {
271 		x = asprintf(&last_after_str, "&time_last_after=%lu",
272 			     fp->last_after);
273 		if (x < 0) {
274 			perror("asprintf");
275 			goto done;
276 		}
277 	}
278 	if (fp->last_before != 0) {
279 		x = asprintf(&last_before_str, "&time_last_before=%lu",
280 			     fp->last_before);
281 		if (x < 0) {
282 			perror("asprintf");
283 			goto done;
284 		}
285 	}
286 
287 	x = asprintf(&ret, "%s%s%s/%s?swclient=%s&version=%s%s%s%s%s%s%s%s%s",
288 		     scheme_if_needed, dnsdb_base_url, verb_path, path,
289 		     id_swclient, id_version, aggr_if_needed,
290 		     or_else(offset_str, ""),
291 		     or_else(max_count_str, ""),
292 		     or_else(query_limit_str, ""),
293 		     or_else(first_after_str, ""),
294 		     or_else(first_before_str, ""),
295 		     or_else(last_after_str, ""),
296 		     or_else(last_before_str, ""));
297 	if (x < 0) {
298 		perror("asprintf");
299 		goto done;
300 	}
301 
302 	/* because we append query parameters, tell the caller to use & for
303 	 * any further query parameters.
304 	 */
305 	if (sep != NULL)
306 		*sep = '&';
307 
308  done:
309 	DESTROY(offset_str);
310 	DESTROY(max_count_str);
311 	DESTROY(query_limit_str);
312 	DESTROY(first_after_str);
313 	DESTROY(first_before_str);
314 	DESTROY(last_after_str);
315 	DESTROY(last_before_str);
316 	return ret;
317 }
318 
319 static void
dnsdb_infoback(writer_t writer)320 dnsdb_infoback(writer_t writer) {
321 	switch (presentation) {
322 	case pres_text: {
323 		struct rate_tuple tup;
324 		const char *msg;
325 
326 		msg = rate_tuple_make(&tup, writer->ps_buf, writer->ps_len);
327 		if (msg != NULL) { /* there was an error */
328 			puts(msg);
329 		} else {
330 			puts("rate:");
331 			print_rateval("reset", &tup.reset, stdout);
332 			print_rateval("expires", &tup.expires, stdout);
333 			print_rateval("limit", &tup.limit, stdout);
334 			print_rateval("remaining", &tup.remaining, stdout);
335 			print_rateval("results_max", &tup.results_max, stdout);
336 			print_rateval("offset_max", &tup.offset_max, stdout);
337 			print_burstrate("burst rate",
338 					&tup.burst_size, &tup.burst_window,
339 					stdout);
340 			rate_tuple_unmake(&tup);
341 		}
342 		break;
343 	    }
344 	case pres_json:
345 		/* Ignore any failure in pprint_json. */
346 		(void) pprint_json(writer->ps_buf, writer->ps_len, stdout);
347 		break;
348 	case pres_csv:
349 		/* FALLTHROUGH */
350 	case pres_none:
351 		/* FALLTHROUGH */
352 	case pres_minimal:
353 		abort();
354 	}
355 }
356 
357 static void
dnsdb_info(void)358 dnsdb_info(void) {
359 	query_t query = NULL;
360 	writer_t writer;
361 
362 	DEBUG(1, true, "dnsdb_info()\n");
363 
364 	/* start a meta_query writer. */
365 	writer = writer_init(qparam_empty.output_limit, dnsdb_infoback, true);
366 
367 	/* create a rump query. */
368 	CREATE(query, sizeof(struct query));
369 	query->writer = writer;
370 	query->descrip = strdup("rate_limit");
371 	writer->queries = query;
372 
373 	/* start a status fetch. */
374 	create_fetch(query, dnsdb_url("rate_limit", NULL, &qparam_empty,
375 				      &(struct pdns_fence){}, true));
376 
377 	/* run all jobs to completion. */
378 	io_engine(0);
379 
380 	/* stop the writer. */
381 	writer_fini(writer);
382 }
383 
384 static void
dnsdb_auth(fetch_t fetch)385 dnsdb_auth(fetch_t fetch) {
386 	if (api_key != NULL) {
387 		char *key_header;
388 
389 		if (asprintf(&key_header, "X-Api-Key: %s", api_key) < 0)
390 			my_panic(true, "asprintf");
391 		fetch->hdrs = curl_slist_append(fetch->hdrs, key_header);
392 		DESTROY(key_header);
393 	}
394 }
395 
396 static const char *
dnsdb_status(fetch_t fetch)397 dnsdb_status(fetch_t fetch) {
398 	/* APIv1 DNSDB returns 404 for "no rrs found".
399 	 * APIv2 DNSDB returns 200 with no SAF lines for "no rrs found".
400 	 */
401 	if (psys->encap == encap_saf && fetch->rcode == HTTP_NOT_FOUND)
402 		return status_error;
403 	return status_noerror;
404 }
405 
406 static const char *
dnsdb_verb_ok(const char * verb_name,qparam_ct qpp)407 dnsdb_verb_ok(const char *verb_name, qparam_ct qpp __attribute__((unused))) {
408 	if (strcasecmp(verb_name, "lookup") != 0) {
409 		/* -O (offset) cannot be used except for verb "lookup". */
410 		if (qpp->offset != 0)
411 			return "only 'lookup' understands offsets";
412 		/* -L (output_limit) cannot be used except for verb "lookup". */
413 		if (qpp->explicit_output_limit != -1)
414 			return "only 'lookup' understands output limits";
415 	}
416 	return NULL;
417 }
418 
419 /*---------------------------------------------------------------- private
420  */
421 
422 /* print_rateval -- output formatter for rateval.
423  */
424 static void
print_rateval(const char * key,rateval_ct tp,FILE * outf)425 print_rateval(const char *key, rateval_ct tp, FILE *outf) {
426 	/* if unspecified, output nothing, not even the key name. */
427 	if (tp->rk == rk_naught)
428 		return;
429 
430 	fprintf(outf, "\t%s: ", key);
431 	switch (tp->rk) {
432 	case rk_na:
433 		fprintf(outf, "n/a");
434 		break;
435 	case rk_unlimited:
436 		fprintf(outf, "unlimited");
437 		break;
438 	case rk_int:
439 		if (strcmp(key, "reset") == 0 || strcmp(key, "expires") == 0)
440 			fputs(time_str(tp->as_int, iso8601), outf);
441 		else
442 			fprintf(outf, "%lu", tp->as_int);
443 		break;
444 	case rk_naught: /*FALLTHROUGH*/
445 	default:
446 		abort();
447 	}
448 	fputc('\n', outf);
449 }
450 
451 /* print_burstrate -- output formatter for burst_size, burst_window ratevals.
452  */
453 static void
print_burstrate(const char * key,rateval_ct tp_size,rateval_ct tp_window,FILE * outf)454 print_burstrate(const char *key,
455 		rateval_ct tp_size,
456 		rateval_ct tp_window,
457 		FILE *outf)
458 {
459 	/* if unspecified, output nothing, not even the key name. */
460 	if (tp_size->rk == rk_naught || tp_window->rk == rk_naught)
461 		return;
462 
463 	assert(tp_size->rk == rk_int);
464 	assert(tp_window->rk == rk_int);
465 
466 	u_long b_w = tp_window->as_int;
467 	u_long b_s = tp_size->as_int;
468 
469 	fprintf(outf, "\t%s: ", key);
470 
471 	if (b_w == 3600)
472 		fprintf(outf, "%lu per hour", b_s);
473 	else if (b_w == 60)
474 		fprintf(outf, "%lu per minute", b_s);
475 	else if ((b_w % 3600) == 0)
476 		fprintf(outf, "%lu per %lu hours", b_s, b_w / 3600);
477 	else if ((b_w % 60) == 0)
478 		fprintf(outf, "%lu per %lu minutes", b_s, b_w / 60);
479 	else
480 		fprintf(outf, "%lu per %lu seconds", b_s, b_w);
481 
482 	fputc('\n', outf);
483 }
484 
485 /* rateval_make: make an optional key value from the json object.
486  *
487  * note: a missing key means the corresponding key's value is a "no value".
488  */
489 static const char *
rateval_make(rateval_t tp,const json_t * obj,const char * key)490 rateval_make(rateval_t tp, const json_t *obj, const char *key) {
491 	struct rateval rvalue = {.rk = rk_naught};
492 	const json_t *jvalue = json_object_get(obj, key);
493 
494 	if (jvalue != NULL) {
495 		if (json_is_integer(jvalue)) {
496 			rvalue.rk = rk_int;
497 			rvalue.as_int = (u_long)json_integer_value(jvalue);
498 		} else {
499 			const char *strvalue = json_string_value(jvalue);
500 			bool ok = false;
501 
502 			if (strvalue != NULL) {
503 				if (strcasecmp(strvalue, "n/a") == 0) {
504 					rvalue.rk = rk_na;
505 					ok = true;
506 				} else if (strcasecmp(strvalue,
507 						      "unlimited") == 0)
508 				{
509 					rvalue.rk = rk_unlimited;
510 					ok = true;
511 				}
512 			}
513 			if (!ok)
514 				return "value must be an integer "
515 					"or \"n/a\" or \"unlimited\"";
516 		}
517 	}
518 	*tp = rvalue;
519 	return NULL;
520 }
521 
522 /* rate_tuple_make -- create one rate tuple object out of a JSON object.
523  */
524 static const char *
rate_tuple_make(rate_tuple_t tup,const char * buf,size_t len)525 rate_tuple_make(rate_tuple_t tup, const char *buf, size_t len) {
526 	const char *msg = NULL;
527 	json_error_t error;
528 	json_t *rate;
529 
530 	memset(tup, 0, sizeof *tup);
531 	DEBUG(3, true, "[%d] '%-*.*s'\n", (int)len, (int)len, (int)len, buf);
532 	tup->obj.main = json_loadb(buf, len, 0, &error);
533 	if (tup->obj.main == NULL) {
534 		fprintf(stderr, "%s: warning: json_loadb: %d:%d: %s %s\n",
535 			program_name, error.line, error.column,
536 			error.text, error.source);
537 		abort();
538 	}
539 	if (debug_level >= 4) {
540 		char *pretty = json_dumps(tup->obj.main, JSON_INDENT(2));
541 		fprintf(stderr, "debug: %s\n", pretty);
542 		free(pretty);
543 	}
544 
545 	rate = json_object_get(tup->obj.main, "rate");
546 	if (rate == NULL) {
547 		msg = "Missing \"rate\" object";
548 		goto ouch;
549 	}
550 
551 	msg = rateval_make(&tup->reset, rate, "reset");
552 	if (msg != NULL)
553 		goto ouch;
554 
555 	msg = rateval_make(&tup->expires, rate, "expires");
556 	if (msg != NULL)
557 		goto ouch;
558 
559 	msg = rateval_make(&tup->limit, rate, "limit");
560 	if (msg != NULL)
561 		goto ouch;
562 
563 	msg = rateval_make(&tup->remaining, rate, "remaining");
564 	if (msg != NULL)
565 		goto ouch;
566 
567 	msg = rateval_make(&tup->results_max, rate, "results_max");
568 	if (msg != NULL)
569 		goto ouch;
570 
571 	msg = rateval_make(&tup->offset_max, rate, "offset_max");
572 	if (msg != NULL)
573 		goto ouch;
574 
575 	msg = rateval_make(&tup->burst_size, rate, "burst_size");
576 	if (msg != NULL)
577 		goto ouch;
578 
579 	msg = rateval_make(&tup->burst_window, rate, "burst_window");
580 	if (msg != NULL)
581 		goto ouch;
582 
583 	assert(msg == NULL);
584 	return NULL;
585 
586  ouch:
587 	assert(msg != NULL);
588 	rate_tuple_unmake(tup);
589 	return msg;
590 }
591 
592 /* rate_tuple_unmake -- deallocate heap storage associated with a rate tuple.
593  */
594 static void
rate_tuple_unmake(rate_tuple_t tup)595 rate_tuple_unmake(rate_tuple_t tup) {
596 	json_decref(tup->obj.main);
597 }
598 
599 #endif /*WANT_PDNS_DNSDB*/
600