1 /*
2  * Copyright (c) 2014-2021 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 /* asprintf() does not appear on linux without this */
18 #define _GNU_SOURCE
19 
20 #include <sys/types.h>
21 #include <sys/wait.h>
22 #include <assert.h>
23 #include <ctype.h>
24 #include <errno.h>
25 
26 #include "asinfo.h"
27 #include "defs.h"
28 #include "netio.h"
29 #include "ns_ttl.h"
30 #include "pdns.h"
31 #include "time.h"
32 #include "globals.h"
33 
34 static void present_text_line(const char *, const char *, const char *);
35 static void present_csv_line(pdns_tuple_ct, const char *);
36 static void present_minimal_thing(const char *thing);
37 static json_t *annotate_json(pdns_tuple_ct, bool);
38 static json_t *annotate_one(json_t *, const char *, const char *, json_t *);
39 #ifndef CRIPPLED_LIBC
40 static json_t *annotate_asinfo(const char *, const char *);
41 #endif
42 static struct counted *countoff_r(const char *, int);
43 
44 /* present_text_lookup -- render one pdns tuple in "dig" style ascii text.
45  */
46 void
present_text_lookup(pdns_tuple_ct tup,mode_e mode,writer_t writer)47 present_text_lookup(pdns_tuple_ct tup,
48 		    mode_e mode __attribute__ ((unused)),
49 		    writer_t writer __attribute__ ((unused)))
50 {
51 	bool pflag, ppflag;
52 	const char *prefix;
53 
54 	ppflag = false;
55 
56 	/* Timestamps. */
57 	if (tup->obj.time_first != NULL && tup->obj.time_last != NULL) {
58 		char duration[50];
59 
60 		if (ns_format_ttl(tup->time_last - tup->time_first + 1, //non-0
61 				  duration, sizeof duration) < 0)
62 			strcpy(duration, "?");
63 		printf(";; record times: %s",
64 			time_str(tup->time_first, iso8601));
65 		printf(" .. %s (%s)\n",
66 			time_str(tup->time_last, iso8601),
67 			duration);
68 		ppflag = true;
69 	}
70 	if (tup->obj.zone_first != NULL && tup->obj.zone_last != NULL) {
71 		char duration[50];
72 
73 		if (ns_format_ttl(tup->zone_last - tup->zone_first, // no +1
74 				  duration, sizeof duration) < 0)
75 			strcpy(duration, "?");
76 		printf(";;   zone times: %s",
77 			time_str(tup->zone_first, iso8601));
78 		printf(" .. %s (%s)\n",
79 			time_str(tup->zone_last, iso8601),
80 			duration);
81 		ppflag = true;
82 	}
83 
84 	/* Count and Bailiwick. */
85 	prefix = ";;";
86 	pflag = false;
87 	if (tup->obj.count != NULL) {
88 		printf("%s count: %lld", prefix, (long long)tup->count);
89 		prefix = ";";
90 		pflag = true;
91 		ppflag = true;
92 	}
93 	if (tup->obj.bailiwick != NULL) {
94 		printf("%s bailiwick: %s", prefix, tup->bailiwick);
95 		prefix = NULL;
96 		pflag = true;
97 		ppflag = true;
98 	}
99 	if (pflag)
100 		putchar('\n');
101 
102 	/* Records. */
103 	if (json_is_array(tup->obj.rdata)) {
104 		size_t index;
105 		json_t *rr;
106 
107 		json_array_foreach(tup->obj.rdata, index, rr) {
108 			const char *rdata = NULL;
109 
110 			if (json_is_string(rr))
111 				rdata = json_string_value(rr);
112 			else
113 				rdata = "[bad value]";
114 			present_text_line(tup->rrname, tup->rrtype, rdata);
115 			ppflag = true;
116 		}
117 	} else {
118 		present_text_line(tup->rrname, tup->rrtype, tup->rdata);
119 		ppflag = true;
120 	}
121 
122 	/* Cleanup. */
123 	if (ppflag)
124 		putchar('\n');
125 }
126 
127 /* present_text_line -- render one RR in "dig" style ascii text.
128  */
129 static void
present_text_line(const char * rrname,const char * rrtype,const char * rdata)130 present_text_line(const char *rrname, const char *rrtype, const char *rdata) {
131 	char *asnum = NULL, *cidr = NULL, *comment = NULL;
132 	const char *result = NULL;
133 
134 #ifndef CRIPPLED_LIBC
135 	result = asinfo_from_rr(rrtype, rdata, &asnum, &cidr);
136 #endif
137 	if (result != NULL) {
138 		comment = strdup(result);
139 	} else if (asnum != NULL && cidr != NULL) {
140 		const char *src = asnum;
141 		bool wordbreak = true;
142 		char ch, *dst;
143 
144 		dst = comment = malloc(strlen(asnum) * 3 + strlen(cidr) + 1);
145 		while ((ch = *src++) != '\0') {
146 			if (wordbreak) {
147 				*dst++ = 'A';
148 				*dst++ = 'S';
149 			}
150 			*dst++ = ch;
151 			wordbreak = (ch == '\040');
152 		}
153 		*dst++ = '\040';
154 		dst = stpcpy(dst, cidr);
155 		free(asnum);
156 		free(cidr);
157 	}
158 	printf("%s  %s  %s", rrname, rrtype, rdata);
159 	if (comment != NULL) {
160 		printf("  ; %s", comment);
161 		free(comment);
162 	}
163 	putchar('\n');
164 }
165 
166 /* present_text_summ -- render summarize object in "dig" style ascii text.
167  */
168 void
present_text_summarize(pdns_tuple_ct tup,mode_e mode,writer_t writer)169 present_text_summarize(pdns_tuple_ct tup,
170 		       mode_e mode __attribute__ ((unused)),
171 		       writer_t writer __attribute__ ((unused)))
172 {
173 	const char *prefix;
174 
175 	/* Timestamps. */
176 	if (tup->obj.time_first != NULL && tup->obj.time_last != NULL) {
177 		printf(";; record times: %s",
178 		       time_str(tup->time_first, iso8601));
179 		printf(" .. %s\n",
180 		       time_str(tup->time_last, iso8601));
181 	}
182 	if (tup->obj.zone_first != NULL && tup->obj.zone_last != NULL) {
183 		printf(";;   zone times: %s",
184 		       time_str(tup->zone_first, iso8601));
185 		printf(" .. %s\n",
186 		       time_str(tup->zone_last, iso8601));
187 		putchar('\n');
188 	}
189 
190 	/* Count and Num_Results. */
191 	prefix = ";;";
192 	if (tup->obj.count != NULL) {
193 		printf("%s count: %lld",
194 		       prefix, (long long)tup->count);
195 		prefix = ";";
196 	}
197 	if (tup->obj.num_results != NULL) {
198 		printf("%s num_results: %lld",
199 		       prefix, (long long)tup->num_results);
200 		prefix = NULL;
201 	}
202 
203 	putchar('\n');
204 }
205 
206 /* pprint_json -- pretty-print a JSON buffer after validation.
207  *
208  * returns true if could parse the json ok, otherwise returns false.
209  */
210 bool
pprint_json(const char * buf,size_t len,FILE * outf)211 pprint_json(const char *buf, size_t len, FILE *outf) {
212 	json_error_t error;
213 
214 	json_t *js = json_loadb(buf, len, 0, &error);
215 	if (js == NULL) {
216 		fprintf(stderr, "JSON parsing error %d:%d: %s %s\n",
217 			error.line, error.column,
218 			error.text, error.source);
219 		return false;
220 	}
221 
222 	json_dumpf(js, outf, JSON_INDENT(2));
223 	fputc('\n', outf);
224 
225 	json_decref(js);
226 	return true;
227 }
228 
229 /* present_json_lookup -- render one DNSDB tuple as newline-separated JSON.
230  */
231 void
present_json_lookup(pdns_tuple_ct tup,mode_e mode,writer_t writer)232 present_json_lookup(pdns_tuple_ct tup,
233 		    mode_e mode __attribute__ ((unused)),
234 		    writer_t writer __attribute__ ((unused)))
235 {
236 	present_json(tup, true);
237 }
238 
239 /* present_json_summarize -- render one DNSDB tuple as newline-separated JSON.
240  */
241 void
present_json_summarize(pdns_tuple_ct tup,mode_e mode,writer_t writer)242 present_json_summarize(pdns_tuple_ct tup,
243 		       mode_e mode __attribute__ ((unused)),
244 		       writer_t writer __attribute__ ((unused)))
245 {
246 	present_json(tup, false);
247 }
248 
249 /* present_json -- shared renderer for DNSDB JSON tuples (lookup and summarize)
250  */
251 void
present_json(pdns_tuple_ct tup,bool rd)252 present_json(pdns_tuple_ct tup, bool rd) {
253 	json_t *copy = annotate_json(tup, rd);
254 
255 	if (copy != NULL) {
256 		json_dumpf(copy, stdout, JSON_INDENT(0) | JSON_COMPACT);
257 		json_decref(copy);
258 	} else {
259 		json_dumpf(tup->obj.cof_obj, stdout,
260 			   JSON_INDENT(0) | JSON_COMPACT);
261 	}
262 	putchar('\n');
263 }
264 
265 /* annotate_json -- create a temporary copy of a tuple; apply transforms.
266  */
267 static json_t *
annotate_json(pdns_tuple_ct tup,bool rd)268 annotate_json(pdns_tuple_ct tup, bool rd) {
269 	json_t *annoRD = NULL, *annoTF = NULL, *annoTL = NULL,
270 		*annoZF = NULL, *annoZL = NULL;
271 
272 	/* annotate zone first/last? */
273 	if ((transforms & TRANS_DATEFIX) != 0 &&
274 	    tup->obj.zone_first != NULL && tup->obj.zone_last != NULL)
275 	{
276 		annoZF = json_string_nocheck(time_str(tup->zone_first,
277 						      iso8601));
278 		annoZL = json_string_nocheck(time_str(tup->zone_last,
279 						      iso8601));
280 	}
281 
282 	/* annotate time first/last? */
283 	if ((transforms & TRANS_DATEFIX) != 0 &&
284 	    tup->obj.time_first != NULL && tup->obj.time_last != NULL)
285 	{
286 		annoTF = json_string_nocheck(time_str(tup->time_first,
287 						      iso8601));
288 		annoTL = json_string_nocheck(time_str(tup->time_last,
289 						      iso8601));
290 	}
291 
292 	/* annotate rdata? */
293 	if (rd) {
294 		if (json_is_array(tup->obj.rdata)) {
295 			size_t index;
296 			json_t *rr;
297 
298 			json_array_foreach(tup->obj.rdata, index, rr) {
299 				const char *rdata = json_string_value(rr);
300 				json_t *asinfo = NULL;
301 #ifndef CRIPPLED_LIBC
302 				asinfo = annotate_asinfo(tup->rrtype, rdata);
303 #endif
304 				if (asinfo != NULL)
305 					annoRD = annotate_one(annoRD, rdata,
306 							      "asinfo", asinfo);
307 			}
308 		} else {
309 			json_t *asinfo = NULL;
310 #ifndef CRIPPLED_LIBC
311 			asinfo = annotate_asinfo(tup->rrtype, tup->rdata);
312 #endif
313 			if (asinfo != NULL)
314 				annoRD = annotate_one(annoRD, tup->rdata,
315 						      "asinfo", asinfo);
316 		}
317 	} //rd?
318 
319 	/* anything annotated? */
320 	if ((annoZF != NULL && annoZL != NULL) ||
321 	    (annoTF != NULL && annoTL != NULL) ||
322 	    (transforms & (TRANS_REVERSE|TRANS_CHOMP)) != 0 ||
323 	    annoRD != NULL)
324 	{
325 		json_t *copy = json_deep_copy(tup->obj.cof_obj);
326 
327 		if (annoZF != NULL || annoZL != NULL) {
328 			json_object_set_new_nocheck(copy, "zone_time_first",
329 						    annoZF);
330 			json_object_set_new_nocheck(copy, "zone_time_last",
331 						    annoZL);
332 		}
333 		if (annoTF != NULL || annoTL != NULL) {
334 			json_object_set_new_nocheck(copy, "time_first",
335 						    annoTF);
336 			json_object_set_new_nocheck(copy, "time_last",
337 						    annoTL);
338 		}
339 		if ((transforms & (TRANS_REVERSE|TRANS_CHOMP)) != 0)
340 			json_object_set_new_nocheck(copy, "rrname",
341 						    json_string(tup->rrname));
342 		if (annoRD != NULL)
343 			json_object_set_new_nocheck(copy, "dnsdbq_rdata",
344 						    annoRD);
345 		return copy;
346 	}
347 	return NULL;
348 }
349 
350 static json_t *
annotate_one(json_t * anno,const char * rdata,const char * name,json_t * obj)351 annotate_one(json_t *anno, const char *rdata, const char *name, json_t *obj) {
352 	json_t *this = NULL;
353 	bool new = false;
354 
355 	if (anno == NULL)
356 		anno = json_object();
357 	if ((this = json_object_get(anno, rdata)) == NULL) {
358 		this = json_object();
359 		new = true;
360 	}
361 	json_object_set_new_nocheck(this, name, obj);
362 	if (new)
363 		json_object_set_new_nocheck(anno, rdata, this);
364 	else
365 		json_decref(this);
366 	return anno;
367 }
368 
369 #ifndef CRIPPLED_LIBC
370 static json_t *
annotate_asinfo(const char * rrtype,const char * rdata)371 annotate_asinfo(const char *rrtype, const char *rdata) {
372 	char *asnum = NULL, *cidr = NULL;
373 	json_t *asinfo = NULL;
374 	const char *result;
375 
376 	if ((result = asinfo_from_rr(rrtype, rdata, &asnum, &cidr)) != NULL) {
377 		asinfo = json_object();
378 		json_object_set_new_nocheck(asinfo, "comment",
379 					    json_string(result));
380 	} else if (asnum != NULL && cidr != NULL) {
381 		json_t *array = json_array();
382 		char *copy, *walker, *token;
383 
384 		copy = walker = strdup(asnum);
385 		while ((token = strsep(&walker, " ")) != NULL)
386 			json_array_append(array, json_integer(atol(token)));
387 		free(copy);
388 		asinfo = json_object();
389 		json_object_set_new_nocheck(asinfo, "as", array);
390 		json_object_set_new_nocheck(asinfo, "cidr", json_string(cidr));
391 		free(asnum);
392 		free(cidr);
393 	}
394 	return asinfo;
395 }
396 #endif
397 
398 /* present_csv_lookup -- render one DNSDB tuple as comma-separated values (CSV)
399  */
400 void
present_csv_lookup(pdns_tuple_ct tup,mode_e mode,writer_t writer)401 present_csv_lookup(pdns_tuple_ct tup,
402 		   mode_e mode __attribute__ ((unused)),
403 		   writer_t writer)
404 {
405 	if (!writer->csv_headerp) {
406 		printf("time_first,time_last,zone_first,zone_last,"
407 		       "count,bailiwick,"
408 		       "rrname,rrtype,rdata");
409 		if (asinfo_lookup)
410 			fputs(",asnum,cidr", stdout);
411 		putchar('\n');
412 		writer->csv_headerp = true;
413 	}
414 
415 	if (json_is_array(tup->obj.rdata)) {
416 		size_t index;
417 		json_t *rr;
418 
419 		json_array_foreach(tup->obj.rdata, index, rr) {
420 			const char *rdata = NULL;
421 
422 			if (json_is_string(rr))
423 				rdata = json_string_value(rr);
424 			else
425 				rdata = "[bad value]";
426 			present_csv_line(tup, rdata);
427 		}
428 	} else {
429 		present_csv_line(tup, tup->rdata);
430 	}
431 }
432 
433 /* present_csv_line -- display a CSV for one rdatum out of an rrset.
434  */
435 static void
present_csv_line(pdns_tuple_ct tup,const char * rdata)436 present_csv_line(pdns_tuple_ct tup, const char *rdata) {
437 	/* Timestamps. */
438 	if (tup->obj.time_first != NULL)
439 		printf("\"%s\"", time_str(tup->time_first, iso8601));
440 	putchar(',');
441 	if (tup->obj.time_last != NULL)
442 		printf("\"%s\"", time_str(tup->time_last, iso8601));
443 	putchar(',');
444 	if (tup->obj.zone_first != NULL)
445 		printf("\"%s\"", time_str(tup->zone_first, iso8601));
446 	putchar(',');
447 	if (tup->obj.zone_last != NULL)
448 		printf("\"%s\"", time_str(tup->zone_last, iso8601));
449 	putchar(',');
450 
451 	/* Count and bailiwick. */
452 	if (tup->obj.count != NULL)
453 		printf("%lld", (long long) tup->count);
454 	putchar(',');
455 	if (tup->obj.bailiwick != NULL)
456 		printf("\"%s\"", tup->bailiwick);
457 	putchar(',');
458 
459 	/* Records. */
460 	if (tup->obj.rrname != NULL)
461 		printf("\"%s\"", tup->rrname);
462 	putchar(',');
463 	if (tup->obj.rrtype != NULL)
464 		printf("\"%s\"", tup->rrtype);
465 	putchar(',');
466 	if (tup->obj.rdata != NULL)
467 		printf("\"%s\"", rdata);
468 	if (asinfo_lookup && tup->obj.rrtype != NULL &&
469 	    tup->obj.rdata != NULL) {
470 		char *asnum = NULL, *cidr = NULL;
471 		const char *result = NULL;
472 
473 #ifndef CRIPPLED_LIBC
474 		result = asinfo_from_rr(tup->rrtype, rdata, &asnum, &cidr);
475 #endif
476 		if (result != NULL) {
477 			asnum = strdup(result);
478 			cidr = strdup(result);
479 		}
480 		putchar(',');
481 		if (asnum != NULL) {
482 			printf("\"%s\"", asnum);
483 			free(asnum);
484 		}
485 		putchar(',');
486 		if (cidr != NULL) {
487 			printf("\"%s\"", cidr);
488 			free(cidr);
489 		}
490 	}
491 	putchar('\n');
492 }
493 
494 /* present_rdata_lookup -- render one DNSDB tuple as a "line" (MINIMAL)
495  */
496 void
present_minimal_lookup(pdns_tuple_ct tup,mode_e mode,writer_t writer)497 present_minimal_lookup(pdns_tuple_ct tup,
498 		       mode_e mode __attribute__ ((unused)),
499 		       writer_t writer __attribute__ ((unused)))
500 {
501 	/* did this tuple come from a left hand or right hand query? */
502 	bool left = true;
503 	switch (mode) {
504 	case no_mode:
505 		abort();
506 	case rrset_mode:
507 		/* FALLTHROUGH */
508 	case raw_rrset_mode:
509 		break;
510 	case name_mode:
511 		/* FALLTHROUGH */
512 	case ip_mode:
513 		/* FALLTHROUGH */
514 	case raw_name_mode:
515 		left = false;
516 	}
517 
518 	/* for RHS queries, output the LHS once, and exit. */
519 	if (!left) {
520 		present_minimal_thing(tup->rrname);
521 		return;
522 	}
523 
524 	/* for LHS queries, output each RHS found. */
525 	if (json_is_array(tup->obj.rdata)) {
526 		size_t index;
527 		json_t *rr;
528 
529 		json_array_foreach(tup->obj.rdata, index, rr) {
530 			const char *rdata = NULL;
531 
532 			if (json_is_string(rr))
533 				rdata = json_string_value(rr);
534 			else
535 				rdata = "[bad value]";
536 			present_minimal_thing(rdata);
537 		}
538 	} else {
539 		present_minimal_thing(tup->rdata);
540 	}
541 }
542 
543 static void
present_minimal_thing(const char * thing)544 present_minimal_thing(const char *thing) {
545 	if (!deduper_tas(minimal_deduper, thing))
546 		puts(thing);
547 }
548 
549 /* present_csv_summ -- render a summarize result as CSV.
550  */
551 void
present_csv_summarize(pdns_tuple_ct tup,mode_e mode,writer_t writer)552 present_csv_summarize(pdns_tuple_ct tup,
553 		      mode_e mode __attribute__ ((unused)),
554 		      writer_t writer __attribute__ ((unused)))
555 {
556 	printf("time_first,time_last,zone_first,zone_last,"
557 	       "count,num_results\n");
558 
559 	/* Timestamps. */
560 	if (tup->obj.time_first != NULL)
561 		printf("\"%s\"", time_str(tup->time_first, iso8601));
562 	putchar(',');
563 	if (tup->obj.time_last != NULL)
564 		printf("\"%s\"", time_str(tup->time_last, iso8601));
565 	putchar(',');
566 	if (tup->obj.zone_first != NULL)
567 		printf("\"%s\"", time_str(tup->zone_first, iso8601));
568 	putchar(',');
569 	if (tup->obj.zone_last != NULL)
570 		printf("\"%s\"", time_str(tup->zone_last, iso8601));
571 	putchar(',');
572 
573 	/* Count and num_results. */
574 	if (tup->obj.count != NULL)
575 		printf("%lld", (long long) tup->count);
576 	putchar(',');
577 	if (tup->obj.num_results != NULL)
578 		printf("%lld", tup->num_results);
579 	putchar('\n');
580 }
581 
582 /* tuple_make -- create one DNSDB tuple object out of a JSON object.
583  */
584 const char *
tuple_make(pdns_tuple_t tup,const char * buf,size_t len)585 tuple_make(pdns_tuple_t tup, const char *buf, size_t len) {
586 	const char *msg = NULL;
587 	json_error_t error;
588 
589 	memset(tup, 0, sizeof *tup);
590 	DEBUG(4, true, "[%d] '%-*.*s'\n", (int)len, (int)len, (int)len, buf);
591 	tup->obj.main = json_loadb(buf, len, 0, &error);
592 	if (tup->obj.main == NULL) {
593 		fprintf(stderr, "%s: warning: json_loadb: %d:%d: %s %s\n",
594 			program_name, error.line, error.column,
595 			error.text, error.source);
596 		abort();
597 	}
598 	if (debug_level >= 4) {
599 		char *pretty = json_dumps(tup->obj.main, JSON_INDENT(2));
600 		fprintf(stderr, "debug: %s\n", pretty);
601 		free(pretty);
602 	}
603 
604 	switch (psys->encap) {
605 	case encap_cof:
606 		/* the COF just is the JSON object. */
607 		tup->obj.cof_obj = tup->obj.main;
608 		break;
609 	case encap_saf:
610 		/* the COF is embedded in the JSONL object. */
611 		tup->obj.saf_cond = json_object_get(tup->obj.main, "cond");
612 		if (tup->obj.saf_cond != NULL) {
613 			if (!json_is_string(tup->obj.saf_cond)) {
614 				msg = "cond must be a string";
615 				goto ouch;
616 			}
617 			tup->cond = json_string_value(tup->obj.saf_cond);
618 		}
619 
620 		tup->obj.saf_msg = json_object_get(tup->obj.main, "msg");
621 		if (tup->obj.saf_msg != NULL) {
622 			if (!json_is_string(tup->obj.saf_msg)) {
623 				msg = "msg must be a string";
624 				goto ouch;
625 			}
626 			tup->msg = json_string_value(tup->obj.saf_msg);
627 		}
628 
629 		tup->obj.saf_obj = json_object_get(tup->obj.main, "obj");
630 		if (tup->obj.saf_obj != NULL) {
631 			if (!json_is_object(tup->obj.saf_obj)) {
632 				msg = "obj must be an object";
633 				goto ouch;
634 			}
635 			tup->obj.cof_obj = tup->obj.saf_obj;
636 		}
637 		break;
638 	default:
639 		/* we weren't prepared for this -- unknown program state. */
640 		abort();
641 	}
642 
643 	/* Timestamps. */
644 	tup->obj.zone_first = json_object_get(tup->obj.cof_obj,
645 					      "zone_time_first");
646 	if (tup->obj.zone_first != NULL) {
647 		if (!json_is_integer(tup->obj.zone_first)) {
648 			msg = "zone_time_first must be an integer";
649 			goto ouch;
650 		}
651 		tup->zone_first = (u_long)
652 			json_integer_value(tup->obj.zone_first);
653 	}
654 	tup->obj.zone_last =
655 		json_object_get(tup->obj.cof_obj, "zone_time_last");
656 	if (tup->obj.zone_last != NULL) {
657 		if (!json_is_integer(tup->obj.zone_last)) {
658 			msg = "zone_time_last must be an integer";
659 			goto ouch;
660 		}
661 		tup->zone_last = (u_long)
662 			json_integer_value(tup->obj.zone_last);
663 	}
664 	tup->obj.time_first = json_object_get(tup->obj.cof_obj, "time_first");
665 	if (tup->obj.time_first != NULL) {
666 		if (!json_is_integer(tup->obj.time_first)) {
667 			msg = "time_first must be an integer";
668 			goto ouch;
669 		}
670 		tup->time_first = (u_long)
671 			json_integer_value(tup->obj.time_first);
672 	}
673 	tup->obj.time_last = json_object_get(tup->obj.cof_obj, "time_last");
674 	if (tup->obj.time_last != NULL) {
675 		if (!json_is_integer(tup->obj.time_last)) {
676 			msg = "time_last must be an integer";
677 			goto ouch;
678 		}
679 		tup->time_last = (u_long)
680 			json_integer_value(tup->obj.time_last);
681 	}
682 
683 	/* Count. */
684 	tup->obj.count = json_object_get(tup->obj.cof_obj, "count");
685 	if (tup->obj.count != NULL) {
686 		if (!json_is_integer(tup->obj.count)) {
687 			msg = "count must be an integer";
688 			goto ouch;
689 		}
690 		tup->count = json_integer_value(tup->obj.count);
691 	}
692 	/* Bailiwick. */
693 	tup->obj.bailiwick = json_object_get(tup->obj.cof_obj, "bailiwick");
694 	if (tup->obj.bailiwick != NULL) {
695 		if (!json_is_string(tup->obj.bailiwick)) {
696 			msg = "bailiwick must be a string";
697 			goto ouch;
698 		}
699 		tup->bailiwick = json_string_value(tup->obj.bailiwick);
700 	}
701 	/* num_results -- just for a summarize. */
702 	tup->obj.num_results =
703 		json_object_get(tup->obj.cof_obj, "num_results");
704 	if (tup->obj.num_results != NULL) {
705 		if (!json_is_integer(tup->obj.num_results)) {
706 			msg = "num_results must be an integer";
707 			goto ouch;
708 		}
709 		tup->num_results = json_integer_value(tup->obj.num_results);
710 	}
711 
712 	/* Records. */
713 	tup->obj.rrname = json_object_get(tup->obj.cof_obj, "rrname");
714 	if (tup->obj.rrname != NULL) {
715 		if (!json_is_string(tup->obj.rrname)) {
716 			msg = "rrname must be a string";
717 			goto ouch;
718 		}
719 
720 		char *r = strdup(json_string_value(tup->obj.rrname));
721 		int dot = 0;
722 
723 		if ((transforms & TRANS_REVERSE) != 0) {
724 			char *t = reverse(r);
725 			DESTROY(r);
726 			r = t;
727 			t = NULL;
728 			/* leading dot comes from reverse() */
729 			if ((transforms & TRANS_CHOMP) != 0)
730 				dot = 1;
731 		} else if ((transforms & TRANS_CHOMP) != 0) {
732 			/* unescaped trailing dot? */
733 			size_t l = strlen(r);
734 			if (l > 0 && r[l-1] == '.' &&
735 			    (l == 1 || r[l-2] != '\\'))
736 				r[l-1] = '\0';
737 		}
738 
739 		if (dot) {
740 			/* in chomp+reverse, the dot to chomp is now leading. */
741 			tup->rrname = strdup(r + dot);
742 			DESTROY(r);
743 		} else {
744 			tup->rrname = r;
745 		}
746 	}
747 	tup->obj.rrtype = json_object_get(tup->obj.cof_obj, "rrtype");
748 	if (tup->obj.rrtype != NULL) {
749 		if (!json_is_string(tup->obj.rrtype)) {
750 			msg = "rrtype must be a string";
751 			goto ouch;
752 		}
753 		tup->rrtype = json_string_value(tup->obj.rrtype);
754 	}
755 	tup->obj.rdata = json_object_get(tup->obj.cof_obj, "rdata");
756 	if (tup->obj.rdata != NULL) {
757 		if (json_is_string(tup->obj.rdata)) {
758 			tup->rdata = json_string_value(tup->obj.rdata);
759 		} else if (!json_is_array(tup->obj.rdata)) {
760 			msg = "rdata must be a string or array";
761 			goto ouch;
762 		}
763 		/* N.b., the array case is for the consumer to iterate over. */
764 	}
765 
766 	assert(msg == NULL);
767 	return NULL;
768 
769  ouch:
770 	assert(msg != NULL);
771 	tuple_unmake(tup);
772 	return msg;
773 }
774 
775 /* tuple_unmake -- deallocate the heap storage associated with one tuple.
776  */
777 void
tuple_unmake(pdns_tuple_t tup)778 tuple_unmake(pdns_tuple_t tup) {
779 	DESTROY(tup->rrname);
780 	json_decref(tup->obj.main);
781 }
782 
783 /* countoff{_r,_debug} -- count and map the labels in a DNS name.
784  */
785 static struct counted *
countoff_r(const char * src,int nlabel)786 countoff_r(const char *src, int nlabel) {
787 	const char *sp = src;
788 	bool slash = false;
789 	struct counted *c;
790 	int ch;
791 
792 	/* count and map the alnums in the facing dns label. */
793 	size_t nalnum = 0;
794 	while ((ch = *sp++) != '\0') {
795 		if (isalnum(ch))
796 			nalnum++;
797 		if (!slash) {
798 			if (ch == '\\')
799 				slash = true;
800 			else if (ch == '.')
801 				break;
802 		} else {
803 			slash = false;
804 		}
805 	}
806 	size_t len = (size_t) (sp - src);
807 	if (ch == '.') {
808 		/* end of label, recurse to reach rest of name. */
809 		c = countoff_r(sp, nlabel+1);
810 		/* fill in output structure on the way back up. */
811 		c->nchar += len;
812 		c->nalnum += nalnum;
813 		c->lens[nlabel] = len;
814 	} else if (ch == '\0') {
815 		/* end of name, and perhaps of a unterminated label. */
816 		len--; /*'\0'*/
817 		if (len != 0)
818 			nlabel++;
819 		c = (struct counted *)malloc(COUNTED_SIZE(nlabel));
820 		memset(c, 0, COUNTED_SIZE(nlabel));
821 		c->nlabel = nlabel;
822 		c->nalnum = nalnum;
823 		if (len != 0) {
824 			c->nchar = len;
825 			c->lens[nlabel-1] = c->nchar;
826 		}
827 	} else {
828 		abort();
829 	}
830 	return c;
831 }
832 
833 struct counted *
countoff(const char * src)834 countoff(const char *src) {
835 	return countoff_r(src, 0);
836 }
837 
838 void
countoff_debug(const char * place,const char * thing,const struct counted * c)839 countoff_debug(const char *place, const char *thing, const struct counted *c) {
840 	printf("\"%s\" -> {nlabel %d, nchar %zd, nalnum %zd, lens [",
841 	       thing, c->nlabel, c->nchar, c->nalnum);
842 	const char *sep = "";
843 	for (int i = 0; i < c->nlabel; i++) {
844 		printf("%s%zd", sep, c->lens[i]);
845 		sep = ", ";
846 	}
847 	printf("]} (%s)\n", place);
848 }
849 
850 /* reverse -- put a domain name into TLD-first order.
851  *
852  * returns NULL if errno is set, else, a heap string.
853  */
854 char *
reverse(const char * src)855 reverse(const char *src) {
856 	struct counted *c = countoff(src);
857 	char *ret = malloc(c->nchar + 1/*'.'*/ + 1/*'\0'*/);
858 	char *p = ret;
859 	size_t nchar = 0;
860 
861 	for (ssize_t i = (ssize_t)c->nlabel-1; i >= 0; i--) {
862 		size_t dot = (src[c->nchar - nchar - 1] == '.');
863 		*p++ = '.';
864 		memcpy(p, src + c->nchar - nchar - c->lens[i],
865 		       c->lens[i] - dot);
866 		p += c->lens[i] - dot;
867 		nchar += c->lens[i];
868 	}
869 	*p = '\0';
870 	DESTROY(c);
871 	return ret;
872 }
873 
874 /* data_blob -- process one deblocked json blob as a counted string.
875  *
876  * presents, or outputs to POSIX sort(1), each blob and then frees it.
877  * returns number of tuples processed (for now, 1 or 0).
878  */
879 int
data_blob(fetch_t fetch,size_t len)880 data_blob(fetch_t fetch, size_t len) {
881 	query_t query = fetch->query;
882 	writer_t writer = query->writer;
883 	struct pdns_tuple tup;
884 	u_long first, last;
885 	const char *msg;
886 	int ret = 0;
887 
888 	msg = tuple_make(&tup, fetch->buf, len);
889 	if (msg != NULL) {
890 		fputs(msg, stderr);
891 		fputc('\n', stderr);
892 		goto more;
893 	}
894 
895 	if (psys->encap == encap_saf) {
896 		if (tup.msg != NULL) {
897 			DEBUG(5, true, "data_blob tup.msg = %s\n", tup.msg);
898 			fetch->saf_msg = strdup(tup.msg);
899 		}
900 
901 		if (tup.cond != NULL) {
902 			DEBUG(5, true, "data_blob tup.cond = %s\n", tup.cond);
903 			/* if we goto next now, this line will not be counted.
904 			 */
905 			if (strcmp(tup.cond, "begin") == 0) {
906 				fetch->saf_cond = sc_begin;
907 				goto next;
908 			} else if (strcmp(tup.cond, "ongoing") == 0) {
909 				/* "cond":"ongoing" key vals should
910 				 * be ignored but the rest of line used. */
911 				fetch->saf_cond = sc_ongoing;
912 			} else if (strcmp(tup.cond, "succeeded") == 0) {
913 				fetch->saf_cond = sc_succeeded;
914 				goto next;
915 			} else if (strcmp(tup.cond, "limited") == 0) {
916 				fetch->saf_cond = sc_limited;
917 				goto next;
918 			} else if (strcmp(tup.cond, "failed") == 0) {
919 				fetch->saf_cond = sc_failed;
920 				goto next;
921 			} else {
922 				/* use sc_missing for an invalid cond value */
923 				fetch->saf_cond = sc_missing;
924 				fprintf(stderr,
925 					"%s: Unknown value for \"cond\": %s\n",
926 					program_name, tup.cond);
927 			}
928 		}
929 
930 		/* A COF keepalive will have no "obj"
931 		 * but may have a "cond" or "msg".
932 		 */
933 		if (tup.obj.saf_obj == NULL) {
934 			DEBUG(4, true,
935 			      "COF object is empty, i.e. a keepalive\n");
936 			goto next;
937 		}
938 	}
939 
940 	/* there are two sets of timestamps in a tuple. we prefer
941 	 * the on-the-wire times to the zone times, when available.
942 	 */
943 	if (tup.time_first != 0 && tup.time_last != 0) {
944 		first = (u_long)tup.time_first;
945 		last = (u_long)tup.time_last;
946 	} else {
947 		first = (u_long)tup.zone_first;
948 		last = (u_long)tup.zone_last;
949 	}
950 
951 	if (sorting != no_sort) {
952 		/* POSIX sort(1) is given six extra fields at the front
953 		 * of each line (first,last,duration,count,name,data)
954 		 * which are accessed as -k1 .. -k7 on the
955 		 * sort command line. we strip them off later
956 		 * when reading the result back. the reason
957 		 * for all this PDP11-era logic is to avoid
958 		 * having to store the full result in memory.
959 		 */
960 		char *dyn_rrname = sortable_rrname(&tup),
961 			*dyn_rdata = sortable_rdata(&tup);
962 
963 		DEBUG(3, true, "dyn_rrname = '%s'\n", dyn_rrname);
964 		DEBUG(3, true, "dyn_rdata = '%s'\n", dyn_rdata);
965 		fprintf(writer->sort_stdin,
966 			"%lu %lu %lu %lu %s %s %s %d %*.*s\n",
967 			(unsigned long)first,
968 			(unsigned long)last,
969 			(unsigned long)(last - first),
970 			(unsigned long)tup.count,
971 			or_else(dyn_rrname, "n/a"),
972 			tup.rrtype,
973 			or_else(dyn_rdata, "n/a"),
974 			(int)query->mode,
975 			(int)len, (int)len, fetch->buf);
976 		DEBUG(2, true, "sort0: '%lu %lu %lu %lu %s %s %s %d %*.*s'\n",
977 		      (unsigned long)first,
978 		      (unsigned long)last,
979 		      (unsigned long)(last - first),
980 		      (unsigned long)tup.count,
981 		      or_else(dyn_rrname, "n/a"),
982 		      tup.rrtype,
983 		      or_else(dyn_rdata, "n/a"),
984 		      (int)query->mode,
985 		      (int)len, (int)len, fetch->buf);
986 		DESTROY(dyn_rrname);
987 		DESTROY(dyn_rdata);
988 	} else {
989 		(*presenter)(&tup, query->mode, writer);
990 	}
991 
992 	ret = 1;
993  next:
994 	tuple_unmake(&tup);
995  more:
996 	return ret;
997 }
998 
999 /* pick_system -- find a named system descriptor, return t/f as to "found?"
1000  *
1001  * returns if psys != NULL, or exits fatally otherwise.
1002  */
1003 void
pick_system(const char * name,const char * context)1004 pick_system(const char *name, const char *context) {
1005 	pdns_system_ct tsys = NULL;
1006 	char *msg = NULL;
1007 
1008 	DEBUG(1, true, "pick_system(%s)\n", name);
1009 #if WANT_PDNS_DNSDB
1010 	if (strcmp(name, "dnsdb1") == 0)
1011 		tsys = pdns_dnsdb1();
1012 	/* "dnsdb" is an alias for "dnsdb2". */
1013 	if (strcmp(name, "dnsdb2") == 0 || strcmp(name, "dnsdb") == 0)
1014 		tsys = pdns_dnsdb2();
1015 #endif
1016 #if WANT_PDNS_CIRCL
1017 	if (strcmp(name, "circl") == 0)
1018 		tsys = pdns_circl();
1019 #endif
1020 	if (tsys == NULL) {
1021 		if (asprintf(&msg,
1022 			     "unrecognized system name (%s)", name) < 0)
1023 			my_panic(true, "asprintf");
1024 	} else if (tsys == psys) {
1025 		/* likely recursion via read_config due to DNSDBQ_SYSTEM. */
1026 		return;
1027 	} else {
1028 		if (psys != NULL) {
1029 			psys->destroy();
1030 			psys = NULL;
1031 		}
1032 		psys = tsys;
1033 		tsys = NULL;
1034 		if (config_file != NULL)
1035 			read_config();
1036 		const char *tmsg = psys->ready();
1037 		if (tmsg != NULL) {
1038 			msg = strdup(tmsg);
1039 			tmsg = NULL;
1040 		}
1041 	}
1042 
1043 	if (msg != NULL) {
1044 		fprintf(stderr, "%s (in %s)\n", msg, context);
1045 		DESTROY(msg);
1046 		my_exit(1);
1047 	}
1048 }
1049 
1050 /* read_config -- parse a given config file.
1051  */
1052 void
read_config(void)1053 read_config(void) {
1054 	char *cmd, *line;
1055 	size_t n;
1056 	int x, l;
1057 	FILE *f;
1058 
1059 	/* in the "echo dnsdb server..." lines, the
1060 	 * first parameter is the pdns system to which to dispatch
1061 	 * the key and value (i.e. second the third parameters).
1062 	 */
1063 	x = asprintf(&cmd,
1064 		     "set -e; . '%s';"
1065 		     "echo dnsdbq system ${" DNSDBQ_SYSTEM
1066 			":-" DEFAULT_SYS "};"
1067 #if WANT_PDNS_DNSDB
1068 		     "echo dnsdb1 apikey ${DNSDB_API_KEY:-$APIKEY};"
1069 		     "echo dnsdb1 server $DNSDB_SERVER;"
1070 		     "echo dnsdb2 apikey ${DNSDB_API_KEY:-$APIKEY};"
1071 		     "echo dnsdb2 server $DNSDB_SERVER;"
1072 #endif
1073 #if WANT_PDNS_CIRCL
1074 		     "echo circl apikey $CIRCL_AUTH;"
1075 		     "echo circl server $CIRCL_SERVER;"
1076 #endif
1077 		     "exit", config_file);
1078 	if (x < 0)
1079 		my_panic(true, "asprintf");
1080 	// this variable can be set in the config file but not the environ.
1081 	unsetenv("APIKEY");
1082 	f = popen(cmd, "r");
1083 	if (f == NULL) {
1084 		fprintf(stderr, "%s: [%s]: %s",
1085 			program_name, cmd, strerror(errno));
1086 		DESTROY(cmd);
1087 		my_exit(1);
1088 	}
1089 	DEBUG(1, true, "conf cmd = '%s'\n", cmd);
1090 	DESTROY(cmd);
1091 	line = NULL;
1092 	n = 0;
1093 	l = 0;
1094 	while (getline(&line, &n, f) > 0) {
1095 		char *tok1, *tok2, *tok3;
1096 		char *saveptr = NULL;
1097 		const char *msg;
1098 
1099 		l++;
1100 		if (strchr(line, '\n') == NULL) {
1101 			fprintf(stderr, "%s: conf line #%d: too long\n",
1102 				program_name, l);
1103 			my_exit(1);
1104 		}
1105 		tok1 = strtok_r(line, "\040\012", &saveptr);
1106 		tok2 = strtok_r(NULL, "\040\012", &saveptr);
1107 		tok3 = strtok_r(NULL, "\040\012", &saveptr);
1108 		if (tok1 == NULL || tok2 == NULL) {
1109 			fprintf(stderr,
1110 				"%s: conf line #%d: malformed\n",
1111 				program_name, l);
1112 			my_exit(1);
1113 		}
1114 		if (tok3 == NULL || *tok3 == '\0') {
1115 			/* variable wasn't set, ignore the line. */
1116 			continue;
1117 		}
1118 
1119 		/* some env/conf variables are dnsdbq-specific. */
1120 		if (strcmp(tok1, "dnsdbq") == 0) {
1121 			/* env/config psys does not override -u. */
1122 			if (strcmp(tok2, "system") == 0 && !psys_specified) {
1123 				pick_system(tok3, config_file);
1124 				if (psys == NULL) {
1125 					fprintf(stderr, "%s: unknown %s %s\n",
1126 						program_name,
1127 						DNSDBQ_SYSTEM,
1128 						tok3);
1129 					my_exit(1);
1130 				}
1131 			}
1132 			continue;
1133 		}
1134 
1135 		/* if this variable is for this system, consume it. */
1136 		if (debug_level >= 1) {
1137 			char *t = NULL;
1138 
1139 			if (strcmp(tok2, "apikey") == 0) {
1140 				int ignored __attribute__((unused));
1141 				ignored = asprintf(&t, "[%zu]", strlen(tok3));
1142 			} else
1143 				t = strdup(tok3);
1144 			fprintf(stderr, "line #%d: sets %s|%s|%s\n",
1145 				l, tok1, tok2, t);
1146 			DESTROY(t);
1147 		}
1148 		if (strcmp(tok1, psys->name) == 0) {
1149 			msg = psys->setval(tok2, tok3);
1150 			if (msg != NULL) {
1151 				fprintf(stderr, "setval: %s\n", msg);
1152 				my_exit(1);
1153 			}
1154 		}
1155 	}
1156 	DESTROY(line);
1157 	x = pclose(f);
1158 	if (!WIFEXITED(x) || WEXITSTATUS(x) != 0)
1159 		my_exit(1);
1160 	assert(psys != NULL);
1161 }
1162