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