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