1 /*
2  * Copyright (c) 2018, NLNet Labs, Sinodun
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * * Redistributions of source code must retain the above copyright
8  *   notice, this list of conditions and the following disclaimer.
9  * * Redistributions in binary form must reproduce the above copyright
10  *   notice, this list of conditions and the following disclaimer in the
11  *   documentation and/or other materials provided with the distribution.
12  * * Neither the names of the copyright holders nor the
13  *   names of its contributors may be used to endorse or promote products
14  *   derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL Verisign, Inc. BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "config.h"
29 #include <ctype.h>
30 #include <stdarg.h>
31 #include <stdbool.h>
32 #include <stddef.h>
33 #include <stdio.h>
34 #include <stdint.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <time.h>
38 
39 #ifdef USE_GNUTLS
40 #include <gnutls/x509.h>
41 #else
42 #include <openssl/x509.h>
43 #include <openssl/x509v3.h>
44 #include <openssl/bio.h>
45 #endif
46 
47 #include <getdns/getdns.h>
48 #include <getdns/getdns_extra.h>
49 
50 #define APP_NAME "getdns_server_mon"
51 
52 #define RTT_CRITICAL_MS                 500
53 #define RTT_WARNING_MS                  250
54 
55 #define CERT_EXPIRY_CRITICAL_DAYS       7
56 #define CERT_EXPIRY_WARNING_DAYS        14
57 
58 #define DEFAULT_LOOKUP_NAME             "getdnsapi.net"
59 #define DEFAULT_LOOKUP_TYPE             GETDNS_RRTYPE_AAAA
60 
61 #define EDNS0_PADDING_CODE              12
62 
63 static const char TLS13_CIPHER_SUITE[] =
64         "TLS13-AES-256-GCM-SHA384:"
65         "TLS13-CHACHA20-POLY1305-SHA256:"
66         "TLS13-AES-128-GCM-SHA256:"
67         "TLS13-AES-128-CCM-8-SHA256:"
68         "TLS13-AES-128-CCM-SHA256";
69 
70 #define EXAMPLE_PIN "pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\""
71 
72 /* Plugin exit values */
73 typedef enum {
74         EXIT_OK = 0,
75         EXIT_WARNING,
76         EXIT_CRITICAL,
77         EXIT_UNKNOWN,
78         EXIT_USAGE              /* Special case - internal only. */
79 } exit_value;
80 
81 /* Plugin verbosity values */
82 typedef enum {
83         VERBOSITY_MINIMAL = 0,
84         VERBOSITY_ADDITIONAL,
85         VERBOSITY_CONFIG,
86         VERBOSITY_DEBUG
87 } plugin_verbosity;
88 
89 #define MAX_BASE_OUTPUT_LEN             256
90 #define MAX_PERF_OUTPUT_LEN             256
91 
92 static struct test_info_s
93 {
94         getdns_context *context;
95 
96         /* Output */
97         bool monitoring;
98         FILE *errout;
99         plugin_verbosity verbosity;
100         bool debug_output;
101         char base_output[MAX_BASE_OUTPUT_LEN + 1];
102         char perf_output[MAX_PERF_OUTPUT_LEN + 1];
103 
104         /* Test config info */
105         bool fail_on_dns_errors;
106 } test_info;
107 
snprintcat(char * buf,size_t buflen,const char * format,...)108 static void snprintcat(char *buf, size_t buflen, const char *format, ...)
109 {
110         va_list ap;
111         int l = strlen(buf);
112 
113         buf += l;
114         buflen -= l;
115 
116         va_start(ap, format);
117         vsnprintf(buf, buflen, format, ap);
118         va_end(ap);
119         buf[buflen] = '\0';
120 }
121 
get_rrtype(const char * t)122 static int get_rrtype(const char *t)
123 {
124         char buf[128] = "GETDNS_RRTYPE_";
125         uint32_t rrtype;
126         long int l;
127         size_t i;
128         char *endptr;
129 
130         if (strlen(t) > sizeof(buf) - 15)
131                 return -1;
132         for (i = 14; *t && i < sizeof(buf) - 1; i++, t++)
133                 buf[i] = *t == '-' ? '_' : toupper((unsigned char)*t);
134         buf[i] = '\0';
135 
136         if (!getdns_str2int(buf, &rrtype))
137                 return (int)rrtype;
138 
139         if (strncasecmp(buf + 14, "TYPE", 4) == 0) {
140                 l = strtol(buf + 18, &endptr, 10);
141                 if (!*endptr && l >= 0 && l < 65536)
142                         return l;
143         }
144         return -1;
145 }
146 
getdns_intval_text(int val,const char * name,const char * prefix)147 static const char *getdns_intval_text(int val, const char *name, const char *prefix)
148 {
149         getdns_dict *d = getdns_dict_create();
150         char buf[128];
151         static char res[20];
152 
153         if (getdns_dict_set_int(d, name, val) != GETDNS_RETURN_GOOD)
154                 goto err;
155 
156         getdns_pretty_snprint_dict(buf, sizeof(buf), d);
157 
158         const char *p = strstr(buf, prefix);
159 
160         if (!p)
161                 goto err;
162         p += strlen(prefix);
163 
164         char *q = res;
165 
166         while (*p && *p != '\n')
167                 *q++ = *p++;
168 
169         getdns_dict_destroy(d);
170         return res;
171 
172 err:
173         getdns_dict_destroy(d);
174         snprintf(res, sizeof(res), "%d", val);
175         return res;
176 }
177 
rrtype_text(int rrtype)178 static const char *rrtype_text(int rrtype)
179 {
180         return getdns_intval_text(rrtype, "type", "GETDNS_RRTYPE_");
181 }
182 
rcode_text(int rcode)183 static const char *rcode_text(int rcode)
184 {
185         return getdns_intval_text(rcode, "rcode", "GETDNS_RCODE_");
186 }
187 
188 #if !defined(USE_GNUTLS) && (OPENSSL_VERSION_NUMBER < 0x10002000 || defined(LIBRESSL_VERSION_NUMBER))
189 /*
190  * Convert date to Julian day.
191  * See https://en.wikipedia.org/wiki/Julian_day
192  */
julian_day(const struct tm * tm)193 static long julian_day(const struct tm *tm)
194 {
195         long dd, mm, yyyy;
196 
197         dd = tm->tm_mday;
198         mm = tm->tm_mon + 1;
199         yyyy = tm->tm_year + 1900;
200 
201         return (1461 * (yyyy + 4800 + (mm - 14) / 12)) / 4 +
202                 (367 * (mm - 2 - 12 * ((mm - 14) / 12))) / 12 -
203                 (3 * ((yyyy + 4900 + (mm - 14) / 12) / 100)) / 4 +
204                 dd - 32075;
205 }
206 
secs_in_day(const struct tm * tm)207 static long secs_in_day(const struct tm *tm)
208 {
209         return ((tm->tm_hour * 60) + tm->tm_min) * 60 + tm->tm_sec;
210 }
211 #endif
212 
213 /*
214  * Thanks to:
215  * https://zakird.com/2013/10/13/certificate-parsing-with-openssl
216  */
extract_cert_expiry(const unsigned char * data,size_t len,time_t * t)217 static bool extract_cert_expiry(const unsigned char *data, size_t len, time_t *t)
218 {
219 #ifdef USE_GNUTLS
220         gnutls_x509_crt_t cert;
221         gnutls_datum_t datum;
222         bool res = false;
223 
224         datum.data = (unsigned char*) data;
225         datum.size = len;
226 
227         if (gnutls_x509_crt_init(&cert) != GNUTLS_E_SUCCESS)
228                 return false;
229 
230         if (gnutls_x509_crt_import(cert, &datum, GNUTLS_X509_FMT_DER) == GNUTLS_E_SUCCESS) {
231                 time_t expiry = gnutls_x509_crt_get_expiration_time(cert);
232                 if (expiry != GNUTLS_X509_NO_WELL_DEFINED_EXPIRATION) {
233                         res = true;
234                         *t = expiry;
235                 }
236         }
237         gnutls_x509_crt_deinit(cert);
238         return res;
239 #else
240         X509 *cert = d2i_X509(NULL, &data, len);
241         if (!cert)
242                 return false;
243 
244         int day_diff, sec_diff;
245         const long SECS_IN_DAY = 60 * 60 * 24;
246 #if defined(X509_get_notAfter) || defined(HAVE_X509_GET_NOTAFTER)
247         const ASN1_TIME *not_after = X509_get_notAfter(cert);
248 #elif defined(X509_get0_notAfter) || defined(HAVE_X509_GET0_NOTAFTER)
249         const ASN1_TIME *not_after = X509_get0_notAfter(cert);
250 #endif
251         *t = time(NULL);
252 
253 #if OPENSSL_VERSION_NUMBER < 0x10002000 || defined(LIBRESSL_VERSION_NUMBER)
254         /*
255          * OpenSSL before 1.0.2 does not support ASN1_TIME_diff().
256          * So work around by using ASN1_TIME_print() to print to a buffer
257          * and parsing that. This does not do any kind of sane format,
258          * but 'Mar 15 11:58:50 2018 GMT'. Note the month name is not
259          * locale-dependent but always English, so strptime() to parse
260          * isn't going to work. It also *appears* to always end 'GMT'.
261          * Ideally one could then convert this UTC time to a time_t, but
262          * there's no way to do that in standard C/POSIX. So follow the
263          * lead of OpenSSL, convert to Julian days and use the difference.
264          */
265 
266         char buf[40];
267         BIO *b = BIO_new(BIO_s_mem());
268         if (ASN1_TIME_print(b, not_after) <= 0) {
269                 BIO_free(b);
270                 X509_free(cert);
271                 return false;
272         }
273         if (BIO_gets(b, buf, sizeof(buf)) <= 0) {
274                 BIO_free(b);
275                 X509_free(cert);
276                 return false;
277         }
278         BIO_free(b);
279         X509_free(cert);
280 
281         struct tm tm;
282         char month[4];
283         char tz[4];
284         memset(&tm, 0, sizeof(tm));
285         if (sscanf(buf,
286                    "%3s %d %d:%d:%d %d %3s",
287                    month,
288                    &tm.tm_mday,
289                    &tm.tm_hour,
290                    &tm.tm_min,
291                    &tm.tm_sec,
292                    &tm.tm_year,
293                    tz) != 7)
294                 return false;
295         tm.tm_year -= 1900;
296         if (strcmp(tz, "GMT") != 0)
297                 return false;
298 
299         const char *mon[] = {
300                 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
301                 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
302         };
303 
304         while(tm.tm_mon < 12 && strcmp(mon[tm.tm_mon], month) != 0)
305                 ++tm.tm_mon;
306         if (tm.tm_mon > 11)
307                 return false;
308 
309         struct tm tm_now;
310         gmtime_r(t, &tm_now);
311 
312         day_diff = julian_day(&tm) - julian_day(&tm_now);
313         sec_diff = secs_in_day(&tm) - secs_in_day(&tm_now);
314         if (sec_diff < 0) {
315                 sec_diff += SECS_IN_DAY;
316                 --day_diff;
317         }
318 #else
319         /*
320          * Use ASN1_TIME_diff to get a time delta between now and expiry.
321          * This is much easier than trying to parse the time.
322          */
323         ASN1_TIME_diff(&day_diff, &sec_diff, NULL, not_after);
324         X509_free(cert);
325 #endif
326         *t += day_diff * SECS_IN_DAY + sec_diff;
327 #endif /* USE_GNUTLS */
328         return true;
329 }
330 
exit_tidy()331 static void exit_tidy()
332 {
333         if (test_info.context)
334                 getdns_context_destroy(test_info.context);
335 }
336 
usage()337 static void usage()
338 {
339         fputs(
340 "Usage: " APP_NAME " [-M] [-E] [(-u|-t|-T)] [-S] [-K <spki-pin>]\n"
341 "        [-v [-v [-v]]] [-V] @upstream testname [<test args>]\n"
342 "  -M|--monitoring               Make output suitable for monitoring tools\n"
343 "  -E|--fail-on-dns-errors       Fail on DNS error (NXDOMAIN, SERVFAIL)\n"
344 "  -u|--udp                      Use UDP transport\n"
345 "  -t|--tcp                      Use TCP transport\n"
346 "  -T|--tls                      Use TLS transport\n"
347 "  -S|--strict-usage-profile     Use strict profile (require authentication)\n"
348 "  -K|--spki-pin <spki-pin>      SPKI pin for TLS connections (can repeat)\n"
349 "  -v|--verbose                  Increase output verbosity\n"
350 "  -D|--debug                    Enable debugging output\n"
351 "  -V|--version                  Report GetDNS version\n"
352 "\n"
353 "spki-pin: Should look like '" EXAMPLE_PIN "'\n"
354 "\n"
355 "upstream: @<ip>[%<scope_id][@<port>][#<tls_port>][~tls name>][^<tsig spec>]\n"
356 "          <ip>@<port> may be given as <IPv4>:<port> or\n"
357 "                      '['<IPv6>[%<scope_id>]']':<port>\n"
358 "\n"
359 "tsig spec: [<algorithm>:]<name>:<secret in Base64>\n"
360 "\n"
361 "Tests:\n"
362 "  lookup [<name> [<type>]]      Check lookup on server\n"
363 "  keepalive [<name> [<type>]]   Check server support for EDNS0 keepalive in\n"
364 "                                TCP or TLS connections\n"
365 "                                Timeout of 0 is off.\n"
366 "  OOOR                          Check whether server delivers responses out of\n"
367 "                                query order on a TCP or TLS connection\n"
368 "  qname-min                     Check whether server supports QNAME minimisation\n"
369 "  rtt [warn-ms,crit-ms] [<name> [<type>]]\n"
370 "                                Check if server round trip time exceeds\n"
371 "                                thresholds (default 250,500)\n"
372 "\n"
373 "  dnssec-validate               Check whether server does DNSSEC validation\n"
374 "\n"
375 "  tls-auth [<name> [<type>]]    Check authentication of TLS server\n"
376 "                                If both a SPKI pin and authentication name are\n"
377 "                                provided, both must authenticate for this test\n"
378 "                                to pass.\n"
379 "  tls-cert-valid [warn-days,crit-days] [<name> [type]]\n"
380 "                                Check server certificate validity, report\n"
381 "                                warning or critical if days to expiry at\n"
382 "                                or below thresholds (default 14,7).\n"
383 "  tls-padding <blocksize> [<name> [<type>]]\n"
384 "                                Check server support for EDNS0 padding in TLS\n"
385 "                                Special blocksize values are 0 = off,\n"
386 "                                1 = sensible default.\n"
387 "  tls-1.3                       Check whether server supports TLS 1.3\n"
388 "\n"
389 "Enabling monitoring mode ensures output messages and exit statuses conform\n"
390 "to the requirements of monitoring plugins (www.monitoring-plugins.org).\n",
391                 test_info.errout);
392         exit(EXIT_UNKNOWN);
393 }
394 
version()395 static void version()
396 {
397         fprintf(test_info.errout,
398                 APP_NAME ": getdns version %s, API version '%s'.\n",
399                 getdns_get_version(),
400                 getdns_get_api_version());
401         exit(EXIT_UNKNOWN);
402 }
403 
404 /**
405  ** Functions used by tests.
406  **/
407 
get_thresholds(char *** av,int * critical,int * warning)408 static void get_thresholds(char ***av,
409                            int *critical,
410                            int *warning)
411 {
412         if (**av) {
413                 char *comma = strchr(**av, ',');
414                 if (!comma)
415                         return;
416 
417                 char *end;
418                 long w,c;
419 
420                 w = strtol(**av, &end, 10);
421                 /*
422                  * If the number doesn't end at a comma, this isn't a
423                  * properly formatted thresholds arg. Pass over it.
424                  */
425                 if (end != comma)
426                         return;
427 
428                 /*
429                  * Similarly, if the number doesn't end at the end of the
430                  * argument, this isn't a properly formatted arg.
431                  */
432                 c = strtol(comma + 1, &end, 10);
433                 if (*end != '\0')
434                         return;
435 
436                 /* Got two numbers, so consume the argument. */
437                 *critical = (int) c;
438                 *warning = (int) w;
439                 ++*av;
440                 return;
441         }
442 
443         return;
444 }
445 
get_name_type_args(struct test_info_s * test_info,char *** av,const char ** lookup_name,uint32_t * lookup_type)446 static exit_value get_name_type_args(struct test_info_s *test_info,
447                                      char ***av,
448                                      const char **lookup_name,
449                                      uint32_t *lookup_type)
450 {
451         if (**av) {
452                 if (strlen(**av) > 0) {
453                         *lookup_name = **av;
454                 } else {
455                         strcpy(test_info->base_output, "Empty name not valid");
456                         return EXIT_UNKNOWN;
457                 }
458                 ++*av;
459 
460                 if (**av) {
461                         int rrtype = get_rrtype(**av);
462                         if (rrtype >= 0) {
463                                 *lookup_type = (uint32_t) rrtype;
464                                 ++*av;
465                         }
466                 }
467         }
468 
469         return EXIT_OK;
470 }
471 
search(struct test_info_s * test_info,const char * name,uint16_t type,getdns_dict * extensions,getdns_dict ** response,getdns_return_t * getdns_return)472 static exit_value search(struct test_info_s *test_info,
473                          const char *name,
474                          uint16_t type,
475                          getdns_dict *extensions,
476                          getdns_dict **response,
477                          getdns_return_t *getdns_return)
478 {
479         getdns_return_t ret;
480         getdns_dict *search_extensions =
481                 (extensions) ? extensions : getdns_dict_create();
482 
483         /* We always turn on the return_call_reporting extension. */
484         if ((ret = getdns_dict_set_int(search_extensions, "return_call_reporting", GETDNS_EXTENSION_TRUE)) != GETDNS_RETURN_GOOD) {
485                 if (getdns_return)
486                         *getdns_return = ret;
487                 snprintf(test_info->base_output,
488                          MAX_BASE_OUTPUT_LEN,
489                          "Cannot set return call reporting: %s (%d)",
490                          getdns_get_errorstr_by_id(ret),
491                          ret);
492                 if (!extensions)
493                         getdns_dict_destroy(search_extensions);
494                 return EXIT_UNKNOWN;
495         }
496 
497         if (test_info->verbosity >= VERBOSITY_ADDITIONAL &&
498             !test_info->monitoring) {
499                 printf("DNS Lookup name:\t%s\n", name);
500                 printf("DNS Lookup RR type:\t%s\n", rrtype_text(type));
501         }
502 
503         if (test_info->debug_output) {
504                 printf("Context: %s\n",
505                        getdns_pretty_print_dict(getdns_context_get_api_information(test_info->context)));
506         }
507 
508         ret = getdns_general_sync(test_info->context,
509                                   name,
510                                   type,
511                                   search_extensions,
512                                   response);
513         if (!extensions)
514                 getdns_dict_destroy(search_extensions);
515         if (ret != GETDNS_RETURN_GOOD) {
516                 if (getdns_return) {
517                         *getdns_return = ret;
518                 } else {
519                         snprintf(test_info->base_output,
520                                  MAX_BASE_OUTPUT_LEN,
521                                  "Error resolving '%s': %s (%d)",
522                                  name,
523                                  getdns_get_errorstr_by_id(ret),
524                                  ret);
525                 }
526                 return EXIT_CRITICAL;
527         }
528 
529         if (test_info->debug_output) {
530                 printf("Response: %s\n",
531                        getdns_pretty_print_dict(*response));
532         }
533 
534         if (getdns_return)
535                 *getdns_return = ret;
536         return EXIT_OK;
537 }
538 
get_result(struct test_info_s * test_info,const getdns_dict * response,uint32_t * error_id,uint32_t * rcode)539 static exit_value get_result(struct test_info_s *test_info,
540                              const getdns_dict *response,
541                              uint32_t *error_id,
542                              uint32_t *rcode)
543 {
544         getdns_return_t ret;
545 
546         if ((ret = getdns_dict_get_int(response, "status", error_id)) != GETDNS_RETURN_GOOD) {
547                 snprintf(test_info->base_output,
548                          MAX_BASE_OUTPUT_LEN,
549                          "Cannot get result status: %s (%d)",
550                          getdns_get_errorstr_by_id(ret),
551                          ret);
552                 return EXIT_UNKNOWN;
553         }
554 
555         if (*error_id != GETDNS_RESPSTATUS_GOOD && *error_id != GETDNS_RESPSTATUS_NO_NAME) {
556                 *rcode = 0;
557                 return EXIT_OK;
558         }
559 
560         if ((ret = getdns_dict_get_int(response, "/replies_tree/0/header/rcode", rcode)) !=  GETDNS_RETURN_GOOD) {
561                 snprintf(test_info->base_output,
562                          MAX_BASE_OUTPUT_LEN,
563                          "Cannot get DNS return code: %s (%d)",
564                          getdns_get_errorstr_by_id(ret),
565                          ret);
566                 return EXIT_UNKNOWN;
567         }
568 
569         return EXIT_OK;
570 }
571 
check_result(struct test_info_s * test_info,const getdns_dict * response)572 static exit_value check_result(struct test_info_s *test_info,
573                                const getdns_dict *response)
574 {
575         exit_value xit;
576         uint32_t error_id, rcode;
577 
578         if ((xit = get_result(test_info, response, &error_id, &rcode)) != EXIT_OK)
579                 return xit;
580 
581         switch(error_id) {
582         case GETDNS_RESPSTATUS_ALL_TIMEOUT:
583                 strcpy(test_info->base_output, "Search timed out");
584                 return EXIT_CRITICAL;
585 
586         case GETDNS_RESPSTATUS_NO_SECURE_ANSWERS:
587                 strcpy(test_info->base_output, "No secure answers");
588                 return EXIT_CRITICAL;
589 
590         case GETDNS_RESPSTATUS_ALL_BOGUS_ANSWERS:
591                 strcpy(test_info->base_output, "All answers are bogus");
592                 return EXIT_CRITICAL;
593 
594         default:
595                 break;
596         }
597 
598         if (test_info->verbosity >= VERBOSITY_ADDITIONAL){
599                 if (test_info->monitoring) {
600                         snprintcat(test_info->perf_output,
601                                    MAX_PERF_OUTPUT_LEN,
602                                    "getdns=%d;",
603                                    error_id);
604                 } else {
605                         printf("getdns result:\t\t%s (%d)\n",
606                                    getdns_get_errorstr_by_id(error_id),
607                                    error_id);
608                 }
609         }
610 
611         if (test_info->fail_on_dns_errors && rcode > 0) {
612                 snprintf(test_info->base_output,
613                          MAX_BASE_OUTPUT_LEN,
614                          "DNS error %s (%d)",
615                          rcode_text(rcode),
616                          rcode);
617                 return EXIT_CRITICAL;
618         }
619 
620         return EXIT_OK;
621 }
622 
get_report_info(struct test_info_s * test_info,const getdns_dict * response,uint32_t * rtt,getdns_bindata ** auth_status,time_t * cert_expire_time)623 static exit_value get_report_info(struct test_info_s *test_info,
624                                   const getdns_dict *response,
625                                   uint32_t *rtt,
626                                   getdns_bindata **auth_status,
627                                   time_t *cert_expire_time)
628 {
629         getdns_return_t ret;
630         getdns_list *l;
631         uint32_t rtt_val;
632         getdns_bindata *auth_status_val = NULL;
633         time_t cert_expire_time_val = 0;
634 
635         if ((ret = getdns_dict_get_list(response, "call_reporting", &l)) != GETDNS_RETURN_GOOD) {
636                 snprintf(test_info->base_output,
637                          MAX_BASE_OUTPUT_LEN,
638                          "Cannot get call report: %s (%d)",
639                          getdns_get_errorstr_by_id(ret),
640                          ret);
641                 return EXIT_UNKNOWN;
642         }
643 
644         getdns_dict *d;
645 
646         if ((ret = getdns_list_get_dict(l, 0, &d)) != GETDNS_RETURN_GOOD) {
647                 snprintf(test_info->base_output,
648                          MAX_BASE_OUTPUT_LEN,
649                          "Cannot get call report first item: %s (%d)",
650                          getdns_get_errorstr_by_id(ret),
651                          ret);
652                 return EXIT_UNKNOWN;
653         }
654         if (test_info->verbosity >= VERBOSITY_ADDITIONAL) {
655                 uint32_t transport;
656                 const char *transport_text = "???";
657 
658                 if ((ret = getdns_dict_get_int(d, "transport", &transport)) != GETDNS_RETURN_GOOD) {
659                         snprintf(test_info->base_output,
660                                  MAX_BASE_OUTPUT_LEN,
661                                  "Cannot get transport: %s (%d)",
662                                  getdns_get_errorstr_by_id(ret),
663                                  ret);
664                         return EXIT_UNKNOWN;
665                 }
666 
667                 switch(transport) {
668                 case GETDNS_TRANSPORT_UDP:
669                         transport_text = "UDP";
670                         break;
671 
672                 case GETDNS_TRANSPORT_TCP:
673                         transport_text = "TCP";
674                         break;
675 
676                 case GETDNS_TRANSPORT_TLS:
677                         transport_text = "TLS";
678                         break;
679                 }
680 
681                 if (test_info->monitoring) {
682                         snprintcat(test_info->perf_output,
683                                    MAX_PERF_OUTPUT_LEN,
684                                    "transport=%s;",
685                                    transport_text);
686                 } else {
687                         printf("Transport:\t\t%s\n", transport_text);
688                 }
689         }
690 
691         if ((ret = getdns_dict_get_int(d, "run_time/ms", &rtt_val)) != GETDNS_RETURN_GOOD) {
692                 snprintf(test_info->base_output,
693                          MAX_BASE_OUTPUT_LEN,
694                          "Cannot get RTT: %s (%d)",
695                          getdns_get_errorstr_by_id(ret),
696                          ret);
697                 return EXIT_UNKNOWN;
698         }
699         if (rtt)
700                 *rtt = rtt_val;
701         if (test_info->verbosity >= VERBOSITY_ADDITIONAL) {
702                 if (test_info->monitoring) {
703                         snprintcat(test_info->perf_output,
704                                    MAX_PERF_OUTPUT_LEN,
705                                    "rtt=%dms;",
706                                    rtt_val);
707                 } else {
708                         printf("RTT:\t\t\t%dms\n", rtt_val);
709                 }
710         }
711 
712         if (getdns_dict_get_bindata(d, "tls_auth_status", &auth_status_val) == GETDNS_RETURN_GOOD) {
713                 const char *auth_status_text = (char *) auth_status_val->data;
714 
715                 /* Just in case - not sure this is necessary */
716                 auth_status_val->data[auth_status_val->size] = '\0';
717                 if (test_info->verbosity >= VERBOSITY_ADDITIONAL) {
718                         if (test_info->monitoring) {
719                         snprintcat(test_info->perf_output,
720                                    MAX_PERF_OUTPUT_LEN,
721                                    "auth=%s;",
722                                    auth_status_text);
723                         } else {
724                                 printf("Authentication:\t\t%s\n", auth_status_text);
725                         }
726                 }
727         }
728         if (auth_status)
729                 *auth_status = auth_status_val;
730 
731         getdns_bindata *cert;
732 
733         if (getdns_dict_get_bindata(d, "tls_peer_cert", &cert) == GETDNS_RETURN_GOOD) {
734                 if (!extract_cert_expiry(cert->data, cert->size, &cert_expire_time_val)) {
735                         strcpy(test_info->base_output, "Cannot parse PKIX certificate");
736                         return EXIT_UNKNOWN;
737                 }
738                 if (test_info->verbosity >= VERBOSITY_ADDITIONAL) {
739                         struct tm *tm = gmtime(&cert_expire_time_val);
740                         char buf[25];
741                         strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm);
742                         if (test_info->monitoring) {
743                                 snprintcat(test_info->perf_output,
744                                            MAX_PERF_OUTPUT_LEN,
745                                            "expire=%s;",
746                                            buf);
747                         } else {
748                                 printf("Certificate expires:\t%s UTC\n", buf);
749                         }
750                 }
751         }
752         if (cert_expire_time)
753                 *cert_expire_time = cert_expire_time_val;
754 
755         getdns_bindata *tls_version;
756 
757         if (getdns_dict_get_bindata(d, "tls_version", &tls_version) == GETDNS_RETURN_GOOD) {
758                 const char *version_text = (char *) tls_version->data;
759 
760                 if (test_info->verbosity >= VERBOSITY_ADDITIONAL) {
761                         if (test_info->monitoring) {
762                         snprintcat(test_info->perf_output,
763                                    MAX_PERF_OUTPUT_LEN,
764                                    "tls=%s;",
765                                    version_text);
766                         } else {
767                                 printf("TLS version:\t\t%s\n", version_text);
768                         }
769                 }
770 
771                 /*
772                  * This is always in the context, so show only if we got
773                  * TLS info in the call reporting.
774                  */
775                 uint32_t tls_auth;
776                 getdns_dict *context_dict = getdns_context_get_api_information(test_info->context);
777 
778                 if (getdns_dict_get_int(context_dict, "/all_context/tls_authentication", &tls_auth) == GETDNS_RETURN_GOOD) {
779                         const char *auth_text = "???";
780 
781                         switch(tls_auth) {
782                         case GETDNS_AUTHENTICATION_NONE:
783                                 auth_text = "Opportunistic";
784                                 break;
785 
786                         case GETDNS_AUTHENTICATION_REQUIRED:
787                                 auth_text = "Strict";
788                                 break;
789                         }
790 
791                         if (test_info->verbosity >= VERBOSITY_ADDITIONAL) {
792                                 if (test_info->monitoring) {
793                                         snprintcat(test_info->perf_output,
794                                                    MAX_PERF_OUTPUT_LEN,
795                                                    "%s;",
796                                                    auth_text);
797                                 } else {
798                                         printf("TLS authentication:\t%s\n", auth_text);
799                                 }
800                         }
801                 }
802         }
803 
804         return EXIT_OK;
805 }
806 
get_answers(struct test_info_s * test_info,const getdns_dict * response,const char * section,getdns_list ** answers,size_t * no_answers)807 static exit_value get_answers(struct test_info_s *test_info,
808                               const getdns_dict *response,
809                               const char *section,
810                               getdns_list **answers,
811                               size_t *no_answers)
812 {
813         getdns_return_t ret;
814         char buf[40];
815 
816         snprintf(buf, sizeof(buf), "/replies_tree/0/%s", section);
817 
818         if ((ret = getdns_dict_get_list(response, buf, answers)) != GETDNS_RETURN_GOOD) {
819                 snprintf(test_info->base_output,
820                          MAX_BASE_OUTPUT_LEN,
821                          "Cannot get section '%s': %s (%d)",
822                          section,
823                          getdns_get_errorstr_by_id(ret),
824                          ret);
825                 return EXIT_UNKNOWN;
826         }
827 
828         if ((ret = getdns_list_get_length(*answers, no_answers)) != GETDNS_RETURN_GOOD) {
829                 snprintf(test_info->base_output,
830                          MAX_BASE_OUTPUT_LEN,
831                          "Cannot get number of items in '%s': %s (%d)",
832                          section,
833                          getdns_get_errorstr_by_id(ret),
834                          ret);
835                 return EXIT_UNKNOWN;
836         }
837         if (*no_answers <= 0) {
838                 snprintf(test_info->base_output,
839                          MAX_BASE_OUTPUT_LEN,
840                          "Zero entries in '%s'",
841                          section);
842                 return EXIT_WARNING;
843         }
844 
845         return EXIT_OK;
846 }
847 
check_answer_type(struct test_info_s * test_info,const getdns_dict * response,uint32_t rrtype)848 static exit_value check_answer_type(struct test_info_s *test_info,
849                                     const getdns_dict *response,
850                                     uint32_t rrtype)
851 {
852         getdns_list *answers;
853         size_t no_answers;
854         exit_value xit;
855 
856         if ((xit = get_answers(test_info, response, "answer", &answers, &no_answers)) != EXIT_OK)
857                 return xit;
858 
859         for (size_t i = 0; i < no_answers; ++i) {
860                 getdns_dict *answer;
861                 getdns_return_t ret;
862 
863                 if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) {
864                         snprintf(test_info->base_output,
865                                  MAX_BASE_OUTPUT_LEN,
866                                  "Cannot get answer number %zu: %s (%d)",
867                                  i,
868                                  getdns_get_errorstr_by_id(ret),
869                                  ret);
870                         return EXIT_UNKNOWN;
871                 }
872 
873                 uint32_t rtype;
874 
875                 if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) {
876                         snprintf(test_info->base_output,
877                                  MAX_BASE_OUTPUT_LEN,
878                                  "Cannot get answer type: %s (%d)",
879                                  getdns_get_errorstr_by_id(ret),
880                                  ret);
881                         return EXIT_UNKNOWN;
882                 }
883                 if (rtype == rrtype)
884                         return EXIT_OK;
885         }
886 
887         strcpy(test_info->base_output, "Answer does not contain expected type");
888         return EXIT_UNKNOWN;
889 }
890 
search_check(struct test_info_s * test_info,const char * lookup_name,uint16_t lookup_type,getdns_dict ** response,uint32_t * rtt,getdns_bindata ** auth_status,time_t * cert_expire_time)891 static exit_value search_check(struct test_info_s *test_info,
892                                const char *lookup_name,
893                                uint16_t lookup_type,
894                                getdns_dict **response,
895                                uint32_t *rtt,
896                                getdns_bindata **auth_status,
897                                time_t *cert_expire_time)
898 {
899         exit_value xit;
900         getdns_dict *resp;
901 
902         if ((xit = search(test_info, lookup_name, lookup_type, NULL, &resp, NULL)) != EXIT_OK)
903                 return xit;
904 
905         if ((xit = check_result(test_info, resp)) != EXIT_OK)
906                 return xit;
907 
908         if ((xit = get_report_info(test_info, resp, rtt, auth_status, cert_expire_time)) != EXIT_OK)
909                 return xit;
910 
911         if ((xit = check_answer_type(test_info, resp, lookup_type)) != EXIT_OK)
912                 return xit;
913 
914         if (response)
915                 *response = resp;
916         return xit;
917 }
918 
parse_search_check(struct test_info_s * test_info,char ** av,const char * usage,getdns_dict ** response,uint32_t * rtt,getdns_bindata ** auth_status,time_t * cert_expire_time)919 static exit_value parse_search_check(struct test_info_s *test_info,
920                                      char **av,
921                                      const char *usage,
922                                      getdns_dict **response,
923                                      uint32_t *rtt,
924                                      getdns_bindata **auth_status,
925                                      time_t *cert_expire_time)
926 {
927         const char *lookup_name = DEFAULT_LOOKUP_NAME;
928         uint32_t lookup_type = DEFAULT_LOOKUP_TYPE;
929         exit_value xit;
930 
931         if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK)
932                 return xit;
933 
934         if (*av) {
935                 strcpy(test_info->base_output, usage);
936                 return EXIT_USAGE;
937         }
938 
939         return search_check(test_info,
940                             lookup_name, lookup_type,
941                             response,
942                             rtt, auth_status, cert_expire_time);
943 }
944 
945 /**
946  ** Test routines.
947  **/
948 
test_lookup(struct test_info_s * test_info,char ** av)949 static exit_value test_lookup(struct test_info_s *test_info,
950                               char ** av)
951 {
952         exit_value xit;
953 
954         if ((xit = parse_search_check(test_info,
955                                       av,
956                                       "lookup takes arguments [<name> [<type>]]",
957                                       NULL,
958                                       NULL,
959                                       NULL,
960                                       NULL)) != EXIT_OK)
961                 return xit;
962 
963         strcpy(test_info->base_output, "Lookup succeeded");
964         return EXIT_OK;
965 }
966 
test_rtt(struct test_info_s * test_info,char ** av)967 static exit_value test_rtt(struct test_info_s *test_info,
968                            char ** av)
969 {
970         exit_value xit;
971         int critical_ms = RTT_CRITICAL_MS;
972         int warning_ms = RTT_WARNING_MS;
973         uint32_t rtt_val;
974 
975         get_thresholds(&av, &critical_ms, &warning_ms);
976 
977         if ((xit = parse_search_check(test_info,
978                                       av,
979                                       "rtt takes arguments [warn-ms,crit-ms] [<name> [<type>]]",
980                                       NULL,
981                                       &rtt_val,
982                                       NULL,
983                                       NULL)) != EXIT_OK)
984                 return xit;
985 
986         snprintf(test_info->base_output,
987                  MAX_BASE_OUTPUT_LEN,
988                  "RTT lookup succeeded in %dms",
989                  rtt_val);
990 
991         if ((int) rtt_val > critical_ms)
992                 return EXIT_CRITICAL;
993         else if ((int) rtt_val > warning_ms)
994                 return EXIT_WARNING;
995         return EXIT_OK;
996 }
997 
test_authenticate(struct test_info_s * test_info,char ** av)998 static exit_value test_authenticate(struct test_info_s *test_info,
999                                     char ** av)
1000 {
1001         exit_value xit;
1002         getdns_bindata *auth_status;
1003 
1004         if ((xit = parse_search_check(test_info,
1005                                       av,
1006                                       "auth takes arguments [<name> [<type>]]",
1007                                       NULL,
1008                                       NULL,
1009                                       &auth_status,
1010                                       NULL)) != EXIT_OK)
1011                 return xit;
1012 
1013         if (!auth_status || strcmp((char *) auth_status->data, "Success") != 0) {
1014                 strcpy(test_info->base_output, "Authentication failed");
1015                 return EXIT_CRITICAL;
1016         } else {
1017                 strcpy(test_info->base_output, "Authentication succeeded");
1018                 return EXIT_OK;
1019         }
1020 }
1021 
test_certificate_valid(struct test_info_s * test_info,char ** av)1022 static exit_value test_certificate_valid(struct test_info_s *test_info,
1023                                          char **av)
1024 {
1025         exit_value xit;
1026         int warning_days = CERT_EXPIRY_WARNING_DAYS;
1027         int critical_days = CERT_EXPIRY_CRITICAL_DAYS;
1028         time_t expire_time;
1029 
1030         get_thresholds(&av, &critical_days, &warning_days);
1031 
1032         if ((xit = parse_search_check(test_info,
1033                                       av,
1034                                       "cert-valid takes arguments [warn-days,crit-days] [<name> [<type>]]",
1035                                       NULL,
1036                                       NULL,
1037                                       NULL,
1038                                       &expire_time)) != EXIT_OK)
1039                 return xit;
1040 
1041 
1042         if (expire_time == 0) {
1043                 strcpy(test_info->base_output, "No PKIX certificate");
1044                 return EXIT_CRITICAL;
1045         }
1046 
1047         time_t now = time(NULL);
1048         int days_to_expiry = (expire_time - now) / 86400;
1049 
1050         if (days_to_expiry < 0) {
1051                 snprintf(test_info->base_output,
1052                          MAX_BASE_OUTPUT_LEN,
1053                          "Certificate expired %d day%s ago",
1054                          -days_to_expiry,
1055                          (days_to_expiry < -1) ? "s" : "");
1056                 return EXIT_CRITICAL;
1057         }
1058         if (days_to_expiry == 0) {
1059                 strcpy(test_info->base_output, "Certificate expires today");
1060         } else {
1061                 snprintf(test_info->base_output,
1062                          MAX_BASE_OUTPUT_LEN,
1063                          "Certificate will expire in %d day%s",
1064                          days_to_expiry,
1065                          (days_to_expiry > 1) ? "s" : "");
1066         }
1067         if (days_to_expiry <= critical_days) {
1068                 return EXIT_CRITICAL;
1069         }
1070         if (days_to_expiry <= warning_days) {
1071                 return EXIT_WARNING;
1072         }
1073         return EXIT_OK;
1074 }
1075 
test_qname_minimisation(struct test_info_s * test_info,char ** av)1076 static exit_value test_qname_minimisation(struct test_info_s *test_info,
1077                                           char ** av)
1078 {
1079         if (*av) {
1080                 strcpy(test_info->base_output, "qname-min takes no arguments");
1081                 return EXIT_USAGE;
1082         }
1083 
1084         getdns_dict *response;
1085         exit_value xit;
1086 
1087         if ((xit = search_check(test_info,
1088                                 "qnamemintest.internet.nl",
1089                                 GETDNS_RRTYPE_TXT,
1090                                 &response,
1091                                 NULL,
1092                                 NULL,
1093                                 NULL)) != EXIT_OK)
1094                 return xit;
1095 
1096         getdns_list *answers;
1097         size_t no_answers;
1098 
1099         if ((xit = get_answers(test_info, response, "answer", &answers, &no_answers)) != EXIT_OK)
1100                 return xit;
1101 
1102         for (size_t i = 0; i < no_answers; ++i) {
1103                 getdns_dict *answer;
1104                 getdns_return_t ret;
1105 
1106                 if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) {
1107                         snprintf(test_info->base_output,
1108                                  MAX_BASE_OUTPUT_LEN,
1109                                  "Cannot get answer number %zu: %s (%d)",
1110                                  i,
1111                                  getdns_get_errorstr_by_id(ret),
1112                                  ret);
1113                         return EXIT_UNKNOWN;
1114                 }
1115 
1116                 uint32_t rtype;
1117 
1118                 if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) {
1119                         snprintf(test_info->base_output,
1120                                  MAX_BASE_OUTPUT_LEN,
1121                                  "Cannot get answer type: %s (%d)",
1122                                  getdns_get_errorstr_by_id(ret),
1123                                  ret);
1124                         return EXIT_UNKNOWN;
1125                 }
1126                 if (rtype != GETDNS_RRTYPE_TXT)
1127                         continue;
1128 
1129                 getdns_bindata *rtxt;
1130 
1131                 if ((ret = getdns_dict_get_bindata(answer, "/rdata/txt_strings/0", &rtxt)) != GETDNS_RETURN_GOOD) {
1132                         strcpy(test_info->base_output, "No answer text");
1133                         return EXIT_UNKNOWN;
1134                 }
1135 
1136                 if (rtxt->size > 0 ) {
1137                         switch(rtxt->data[0]) {
1138                         case 'H':
1139                                 strcpy(test_info->base_output, "QNAME minimisation ON");
1140                                 return EXIT_OK;
1141 
1142                         case 'N':
1143                                 strcpy(test_info->base_output, "QNAME minimisation OFF");
1144                                 return EXIT_CRITICAL;
1145 
1146                         default:
1147                                 /* Unrecognised message. */
1148                                 break;
1149                         }
1150                 }
1151         }
1152 
1153         strcpy(test_info->base_output, "No valid QNAME minimisation data");
1154         return EXIT_UNKNOWN;
1155 }
1156 
test_padding(struct test_info_s * test_info,char ** av)1157 static exit_value test_padding(struct test_info_s *test_info,
1158                                char ** av)
1159 {
1160         getdns_dict *response;
1161         exit_value xit;
1162         long blocksize;
1163         char *endptr;
1164         const char USAGE[] = "padding takes arguments <blocksize> [<name> [<type>]]";
1165 
1166         if (!*av || (blocksize = strtol(*av, &endptr, 10), *endptr != '\0' || blocksize < 0)) {
1167                 strcpy(test_info->base_output, USAGE);
1168                 return EXIT_USAGE;
1169         }
1170         ++av;
1171 
1172         getdns_return_t ret;
1173         if ((ret = getdns_context_set_tls_query_padding_blocksize(test_info->context, (uint16_t) blocksize)) != GETDNS_RETURN_GOOD) {
1174                 snprintf(test_info->base_output,
1175                          MAX_BASE_OUTPUT_LEN,
1176                          "Cannot set padding blocksize: %s (%d)",
1177                          getdns_get_errorstr_by_id(ret),
1178                          ret);
1179                 return EXIT_UNKNOWN;
1180         }
1181 
1182         if ((xit = parse_search_check(test_info,
1183                                       av,
1184                                       USAGE,
1185                                       &response,
1186                                       NULL,
1187                                       NULL,
1188                                       NULL)) != EXIT_OK)
1189                 return xit;
1190 
1191         getdns_list *answers;
1192         size_t no_answers;
1193 
1194         if ((xit = get_answers(test_info, response, "additional", &answers, &no_answers)) != EXIT_OK)
1195                 return xit;
1196 
1197         for (size_t i = 0; i < no_answers; ++i) {
1198                 getdns_dict *answer;
1199                 getdns_return_t ret;
1200 
1201                 if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) {
1202                         snprintf(test_info->base_output,
1203                                  MAX_BASE_OUTPUT_LEN,
1204                                  "Cannot get answer number %zu: %s (%d)",
1205                                  i,
1206                                  getdns_get_errorstr_by_id(ret),
1207                                  ret);
1208                         return EXIT_UNKNOWN;
1209                 }
1210 
1211                 uint32_t rtype;
1212 
1213                 if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) {
1214                         snprintf(test_info->base_output,
1215                                  MAX_BASE_OUTPUT_LEN,
1216                                  "Cannot get answer type: %s (%d)",
1217                                  getdns_get_errorstr_by_id(ret),
1218                                  ret);
1219                         return EXIT_UNKNOWN;
1220                 }
1221                 if (rtype != GETDNS_RRTYPE_OPT)
1222                         continue;
1223 
1224                 getdns_list *options;
1225                 size_t no_options;
1226 
1227                 if ((ret = getdns_dict_get_list(answer, "/rdata/options", &options)) != GETDNS_RETURN_GOOD) {
1228                         goto no_padding;
1229                 }
1230                 if ((ret = getdns_list_get_length(options, &no_options)) != GETDNS_RETURN_GOOD) {
1231                         snprintf(test_info->base_output,
1232                                  MAX_BASE_OUTPUT_LEN,
1233                                  "Cannot get number of options: %s (%d)",
1234                                  getdns_get_errorstr_by_id(ret),
1235                                  ret);
1236                         return EXIT_UNKNOWN;
1237                 }
1238 
1239                 for (size_t j = 0; j < no_options; ++j) {
1240                         getdns_dict *option;
1241                         uint32_t code;
1242 
1243                         if ((ret = getdns_list_get_dict(options, j, &option)) != GETDNS_RETURN_GOOD) {
1244                                 snprintf(test_info->base_output,
1245                                          MAX_BASE_OUTPUT_LEN,
1246                                          "Cannot get option number %zu: %s (%d)",
1247                                          j,
1248                                          getdns_get_errorstr_by_id(ret),
1249                                          ret);
1250                                 return EXIT_UNKNOWN;
1251                         }
1252                         if ((ret = getdns_dict_get_int(option, "option_code", &code)) != GETDNS_RETURN_GOOD) {
1253                                 snprintf(test_info->base_output,
1254                                          MAX_BASE_OUTPUT_LEN,
1255                                          "Cannot get option code: %s (%d)",
1256                                          getdns_get_errorstr_by_id(ret),
1257                                          ret);
1258                                 return EXIT_UNKNOWN;
1259                         }
1260 
1261                         if (code != EDNS0_PADDING_CODE)
1262                                 continue;
1263 
1264                         /* Yes, we have padding! */
1265                         getdns_bindata *data;
1266 
1267                         if ((ret = getdns_dict_get_bindata(option, "option_data", &data)) != GETDNS_RETURN_GOOD) {
1268                                 snprintf(test_info->base_output,
1269                                          MAX_BASE_OUTPUT_LEN,
1270                                          "Cannot get option code: %s (%d)",
1271                                          getdns_get_errorstr_by_id(ret),
1272                                          ret);
1273                                 return EXIT_UNKNOWN;
1274                         }
1275 
1276                         snprintf(test_info->base_output,
1277                                  MAX_BASE_OUTPUT_LEN,
1278                                  "Padding found, length %zu",
1279                                  data->size);
1280                         return EXIT_OK;
1281                 }
1282         }
1283 
1284 no_padding:
1285         strcpy(test_info->base_output, "No padding found");
1286         return EXIT_CRITICAL;
1287 }
1288 
test_keepalive(struct test_info_s * test_info,char ** av)1289 static exit_value test_keepalive(struct test_info_s *test_info,
1290                                  char ** av)
1291 {
1292         getdns_dict *response;
1293         exit_value xit;
1294         const uint64_t MAX_TIMEOUT_VALUE = 0xffff * 10000;
1295 
1296         getdns_return_t ret;
1297         if ((ret = getdns_context_set_idle_timeout(test_info->context, MAX_TIMEOUT_VALUE)) != GETDNS_RETURN_GOOD) {
1298                 snprintf(test_info->base_output,
1299                          MAX_BASE_OUTPUT_LEN,
1300                          "Cannot set keepalive timeout: %s (%d)",
1301                          getdns_get_errorstr_by_id(ret),
1302                          ret);
1303                 return EXIT_UNKNOWN;
1304         }
1305 
1306         if ((xit = parse_search_check(test_info,
1307                                       av,
1308                                       "keepalive takes arguments [<name> [<type>]]",
1309                                       &response,
1310                                       NULL,
1311                                       NULL,
1312                                       NULL)) != EXIT_OK)
1313                 return xit;
1314 
1315         getdns_list *l;
1316 
1317         if ((ret = getdns_dict_get_list(response, "call_reporting", &l)) != GETDNS_RETURN_GOOD) {
1318                 snprintf(test_info->base_output,
1319                          MAX_BASE_OUTPUT_LEN,
1320                          "Cannot get call report: %s (%d)",
1321                          getdns_get_errorstr_by_id(ret),
1322                          ret);
1323                 return EXIT_UNKNOWN;
1324         }
1325 
1326         getdns_dict *d;
1327         if ((ret = getdns_list_get_dict(l, 0, &d)) != GETDNS_RETURN_GOOD) {
1328                 snprintf(test_info->base_output,
1329                          MAX_BASE_OUTPUT_LEN,
1330                          "Cannot get call report first item: %s (%d)",
1331                          getdns_get_errorstr_by_id(ret),
1332                          ret);
1333                 return EXIT_UNKNOWN;
1334         }
1335 
1336         /* Search is forced to be TCP or TLS, so server keepalive flag must exist. */
1337         uint32_t server_keepalive_received;
1338         if ((ret = getdns_dict_get_int(d, "server_keepalive_received", &server_keepalive_received)) != GETDNS_RETURN_GOOD) {
1339                 snprintf(test_info->base_output,
1340                          MAX_BASE_OUTPUT_LEN,
1341                          "Cannot get server keepalive flag: %s (%d)",
1342                          getdns_get_errorstr_by_id(ret),
1343                          ret);
1344                 return EXIT_UNKNOWN;
1345         }
1346 
1347         if (server_keepalive_received) {
1348                 uint32_t t;
1349                 bool overflow = false;
1350 
1351                 if (!((ret = getdns_dict_get_int(d, "idle timeout in ms", &t)) == GETDNS_RETURN_GOOD ||
1352                       (overflow = true, ret = getdns_dict_get_int(d, "idle timeout in ms (overflow)", &t)) == GETDNS_RETURN_GOOD)) {
1353                         snprintf(test_info->base_output,
1354                                  MAX_BASE_OUTPUT_LEN,
1355                                  "Cannot get idle timeout: %s (%d)",
1356                                  getdns_get_errorstr_by_id(ret),
1357                                  ret);
1358                         return EXIT_UNKNOWN;
1359                 }
1360 
1361                 if (overflow) {
1362                         strcpy(test_info->base_output, "Server sent keepalive, idle timeout now (overflow)");
1363                 } else {
1364                         snprintf(test_info->base_output,
1365                                  MAX_BASE_OUTPUT_LEN,
1366                                  "Server sent keepalive, idle timeout now %ums",
1367                                  t);
1368                 }
1369                 return EXIT_OK;
1370         } else {
1371                 strcpy(test_info->base_output, "Server did not send keepalive");
1372                 return EXIT_CRITICAL;
1373         }
1374 }
1375 
test_dnssec_validate(struct test_info_s * test_info,char ** av)1376 static exit_value test_dnssec_validate(struct test_info_s *test_info,
1377                                        char ** av)
1378 {
1379         if (*av) {
1380                 strcpy(test_info->base_output, "dnssec-validate takes no arguments");
1381                 return EXIT_USAGE;
1382         }
1383 
1384         getdns_dict *response;
1385         exit_value xit;
1386 
1387         if ((xit = search(test_info,
1388                           "dnssec-failed.org",
1389                           GETDNS_RRTYPE_A,
1390                           NULL,
1391                           &response,
1392                           NULL)) != EXIT_OK)
1393                 return xit;
1394 
1395         uint32_t error_id, rcode;
1396 
1397         if ((xit = get_result(test_info, response, &error_id, &rcode)) != EXIT_OK)
1398                 return xit;
1399 
1400         if (error_id == GETDNS_RESPSTATUS_ALL_TIMEOUT) {
1401                 strcpy(test_info->base_output, "Search timed out");
1402                 return EXIT_CRITICAL;
1403         }
1404 
1405         if (rcode != GETDNS_RCODE_SERVFAIL) {
1406                 strcpy(test_info->base_output, "Server does NOT validate DNSSEC");
1407                 return EXIT_CRITICAL;
1408         }
1409 
1410         /*
1411          * Rerun the query, but this time set the CD bit. The lookup should
1412          * succeed.
1413          */
1414         getdns_return_t ret;
1415         getdns_dict *response2;
1416         getdns_dict *extensions = getdns_dict_create();
1417 
1418         if ((ret = getdns_dict_set_int(extensions, "/header/cd", 1)) != GETDNS_RETURN_GOOD) {
1419                 snprintf(test_info->base_output,
1420                          MAX_BASE_OUTPUT_LEN,
1421                          "Cannot set CD bit: %s (%d)",
1422                          getdns_get_errorstr_by_id(ret),
1423                          ret);
1424                 getdns_dict_destroy(extensions);
1425                 return EXIT_UNKNOWN;
1426         }
1427 
1428         if ((xit = search(test_info,
1429                           "dnssec-failed.org",
1430                           GETDNS_RRTYPE_A,
1431                           extensions,
1432                           &response2,
1433                           NULL)) != EXIT_OK)
1434                 return xit;
1435 
1436         getdns_dict_destroy(extensions);
1437 
1438         /*
1439          * Only now get report info from the first search, so that any
1440          * verbose output appears after the context/response dumps.
1441          */
1442         if ((xit = get_report_info(test_info, response, NULL, NULL, NULL)) != EXIT_OK)
1443                 return xit;
1444 
1445         if ((xit = get_result(test_info, response2, &error_id, &rcode)) != EXIT_OK)
1446                 return xit;
1447 
1448         if (error_id == GETDNS_RESPSTATUS_ALL_TIMEOUT) {
1449                 strcpy(test_info->base_output, "Search timed out");
1450                 return EXIT_CRITICAL;
1451         }
1452 
1453         if (error_id != GETDNS_RESPSTATUS_GOOD || rcode != GETDNS_RCODE_NOERROR) {
1454                 strcpy(test_info->base_output, "Server error - cannot determine DNSSEC status");
1455                 return EXIT_UNKNOWN;
1456         }
1457 
1458         strcpy(test_info->base_output, "Server validates DNSSEC");
1459         return EXIT_OK;
1460 }
1461 
test_tls13(struct test_info_s * test_info,char ** av)1462 static exit_value test_tls13(struct test_info_s *test_info,
1463                              char ** av)
1464 {
1465         exit_value xit;
1466         getdns_return_t ret;
1467 
1468         if (*av) {
1469                 strcpy(test_info->base_output, "tls-1.3 takes no arguments");
1470                 return EXIT_USAGE;
1471         }
1472 
1473         /*
1474          * Set cipher list to TLS 1.3-only ciphers. If we are using
1475          * an OpenSSL version that doesn't support TLS 1.3 this will cause
1476          * a Bad Context error on the lookup.
1477          */
1478         if ((ret = getdns_context_set_tls_cipher_list(test_info->context, TLS13_CIPHER_SUITE)) != GETDNS_RETURN_GOOD) {
1479                 snprintf(test_info->base_output,
1480                          MAX_BASE_OUTPUT_LEN,
1481                          "Cannot set TLS 1.3 cipher list: %s (%d)",
1482                          getdns_get_errorstr_by_id(ret),
1483                          ret);
1484                 return EXIT_UNKNOWN;
1485         }
1486 
1487         getdns_dict *response;
1488 
1489         if ((xit = search(test_info,
1490                           DEFAULT_LOOKUP_NAME,
1491                           DEFAULT_LOOKUP_TYPE,
1492                           NULL,
1493                           &response,
1494                           &ret)) != EXIT_OK) {
1495                 if (xit == EXIT_CRITICAL) {
1496                         if (ret == GETDNS_RETURN_BAD_CONTEXT) {
1497                                 strcpy(test_info->base_output, "Your version of OpenSSL does not support TLS 1.3 ciphers. You need at least OpenSSL 1.1.1.");
1498                                 return EXIT_UNKNOWN;
1499                         } else {
1500                                 strcpy(test_info->base_output, "Cannot establish TLS 1.3 connection.");
1501                                 return EXIT_CRITICAL;
1502                         }
1503                 } else {
1504                         return xit;
1505                 }
1506         }
1507 
1508         if ((xit = get_report_info(test_info, response, NULL, NULL, NULL)) != EXIT_OK)
1509                 return xit;
1510 
1511         getdns_list *l;
1512 
1513         if ((ret = getdns_dict_get_list(response, "call_reporting", &l)) != GETDNS_RETURN_GOOD) {
1514                 snprintf(test_info->base_output,
1515                          MAX_BASE_OUTPUT_LEN,
1516                          "Cannot get call report: %s (%d)",
1517                          getdns_get_errorstr_by_id(ret),
1518                          ret);
1519                 return EXIT_UNKNOWN;
1520         }
1521 
1522         getdns_dict *d;
1523 
1524         if ((ret = getdns_list_get_dict(l, 0, &d)) != GETDNS_RETURN_GOOD) {
1525                 snprintf(test_info->base_output,
1526                          MAX_BASE_OUTPUT_LEN,
1527                          "Cannot get call report first item: %s (%d)",
1528                          getdns_get_errorstr_by_id(ret),
1529                          ret);
1530                 return EXIT_UNKNOWN;
1531         }
1532 
1533         /* Search is forced TLS, so tls_version flag must exist. */
1534         getdns_bindata *version;
1535 
1536         if ((ret = getdns_dict_get_bindata(d, "tls_version", &version)) != GETDNS_RETURN_GOOD) {
1537                 snprintf(test_info->base_output,
1538                          MAX_BASE_OUTPUT_LEN,
1539                          "Cannot get TLS version: %s (%d)",
1540                          getdns_get_errorstr_by_id(ret),
1541                          ret);
1542                 return EXIT_UNKNOWN;
1543         }
1544 
1545         const char TLS_1_3_SIG[] = "TLSv1.3";
1546 
1547         if (strncmp((const char *)version->data, TLS_1_3_SIG, sizeof(TLS_1_3_SIG) - 1) != 0) {
1548                 strcpy(test_info->base_output, "Server does not support TLS 1.3");
1549                 return EXIT_CRITICAL;
1550         }
1551 
1552         if ((xit = check_result(test_info, response)) != EXIT_OK)
1553                 return xit;
1554 
1555         strcpy(test_info->base_output, "Server supports TLS 1.3");
1556         return EXIT_OK;
1557 }
1558 
1559 struct async_query
1560 {
1561         const char *name;
1562         unsigned done_order;
1563         bool error;
1564 };
1565 
out_of_order_callback(getdns_context * context,getdns_callback_type_t callback_type,getdns_dict * response,void * userarg,getdns_transaction_t transaction_id)1566 static void out_of_order_callback(getdns_context          *context,
1567                                   getdns_callback_type_t  callback_type,
1568                                   getdns_dict             *response,
1569                                   void                    *userarg,
1570                                   getdns_transaction_t    transaction_id)
1571 {
1572         (void) context;
1573         (void) callback_type;
1574         (void) response;
1575         (void) transaction_id;
1576 
1577         struct async_query *query = (struct async_query *) userarg;
1578         static unsigned callback_no;
1579 
1580         query->done_order = ++callback_no;
1581         query->error = (callback_type != GETDNS_CALLBACK_COMPLETE);
1582 
1583         if (test_info.verbosity >= VERBOSITY_ADDITIONAL) {
1584                 if (test_info.monitoring) {
1585                         snprintcat(test_info.perf_output,
1586                                    MAX_PERF_OUTPUT_LEN,
1587                                    "->%s;",
1588                                    query->name);
1589                 } else {
1590                         printf("Result:\t\t\t%s", query->name);
1591                         if (query->error)
1592                                 printf(" (callback %d)\n", callback_type);
1593                         else
1594                                 putchar('\n');
1595                 }
1596         }
1597 
1598 }
1599 
test_out_of_order(struct test_info_s * test_info,char ** av)1600 static exit_value test_out_of_order(struct test_info_s *test_info,
1601                                     char ** av)
1602 {
1603         if (*av) {
1604                 strcpy(test_info->base_output, "OOOR takes no arguments");
1605                 return EXIT_USAGE;
1606         }
1607 
1608         /*
1609          * A set of asynchronous queries to send. One exists.
1610          *
1611          * Replies from delay.getdns.api come back with a 1s TTL. It turns out
1612          * that delay.getdns.api will ignore all leftmost labels bar the first,
1613          * so to further mitigate any cache effects insert a random string
1614          * into the name.
1615          */
1616         const char GOOD_NAME[] = "getdnsapi.net";
1617         char delay_name[50];
1618         srand(time(NULL));
1619         snprintf(delay_name, sizeof(delay_name) - 1, "400.n%04d.delay.getdnsapi.net", rand() % 10000);
1620         struct async_query async_queries[] = {
1621                 { delay_name, 0, false },
1622                 { GOOD_NAME, 0, false }
1623         };
1624         unsigned NQUERIES = sizeof(async_queries) / sizeof(async_queries[0]);
1625 
1626         /* Pre-load the cache with result of the good query. */
1627         getdns_dict *response;
1628         exit_value xit;
1629 
1630         if ((xit = search_check(test_info,
1631                                 GOOD_NAME,
1632                                 GETDNS_RRTYPE_AAAA,
1633                                 &response,
1634                                 NULL,
1635                                 NULL,
1636                                 NULL)) != EXIT_OK)
1637                 return xit;
1638 
1639         /* Now launch async searches for all the above. */
1640         for (unsigned i = 0; i < NQUERIES; ++i) {
1641                 getdns_return_t ret;
1642 
1643                 if (test_info->verbosity >= VERBOSITY_ADDITIONAL) {
1644                         if (test_info->monitoring) {
1645                                 snprintcat(test_info->perf_output,
1646                                            MAX_PERF_OUTPUT_LEN,
1647                                            "%s->;",
1648                                            async_queries[i].name);
1649                         } else {
1650                                 printf("Search:\t\t\t%s\n", async_queries[i].name);
1651                         }
1652                 }
1653 
1654                 if ((ret = getdns_general(test_info->context,
1655                                           async_queries[i].name,
1656                                           GETDNS_RRTYPE_AAAA,
1657                                           NULL,
1658                                           &async_queries[i],
1659                                           NULL,
1660                                           out_of_order_callback)) != GETDNS_RETURN_GOOD) {
1661                         snprintf(test_info->base_output,
1662                                  MAX_BASE_OUTPUT_LEN,
1663                                  "Async search failed: %s (%d)",
1664                                  getdns_get_errorstr_by_id(ret),
1665                                  ret);
1666                         return EXIT_UNKNOWN;
1667                 }
1668         }
1669 
1670         /* And wait for them to complete. */
1671         getdns_context_run(test_info->context);
1672 
1673         for (unsigned i = 0; i < NQUERIES; ++i) {
1674                 if (async_queries[i].error) {
1675                         strcpy(test_info->base_output, "Query did not complete");
1676                         return EXIT_UNKNOWN;
1677                 }
1678         }
1679 
1680         if (async_queries[NQUERIES - 1].done_order == NQUERIES) {
1681                 strcpy(test_info->base_output, "Responses are in query order");
1682                 return EXIT_CRITICAL;
1683         } else {
1684                 strcpy(test_info->base_output, "Responses are not in query order");
1685                 return EXIT_OK;
1686         }
1687 }
1688 
1689 static struct test_funcs_s
1690 {
1691         const char *name;
1692         bool implies_tls;
1693         bool implies_tcp;
1694         exit_value (*func)(struct test_info_s *test_info, char **av);
1695 } TESTS[] =
1696 {
1697         { "lookup", false, false, test_lookup },
1698         { "rtt", false, false, test_rtt },
1699         { "qname-min", false, false, test_qname_minimisation },
1700         { "tls-auth", true, false, test_authenticate },
1701         { "tls-cert-valid", true, false, test_certificate_valid },
1702         { "tls-padding", true, false, test_padding },
1703         { "keepalive", false, true, test_keepalive },
1704         { "dnssec-validate", false, false, test_dnssec_validate },
1705         { "tls-1.3", true, false, test_tls13 },
1706         { "OOOR", false, true, test_out_of_order },
1707         { NULL, false, false, NULL }
1708 };
1709 
main(int ac,char * av[])1710 int main(int ac, char *av[])
1711 {
1712         getdns_return_t ret;
1713         getdns_list *pinset = NULL;
1714         size_t pinset_size = 0;
1715         bool strict_usage_profile = false;
1716         bool use_udp = false;
1717         bool use_tcp = false;
1718         bool use_tls = false;
1719 
1720         (void) ac;
1721 
1722         test_info.errout = stderr;
1723         atexit(exit_tidy);
1724         if ((ret = getdns_context_create(&test_info.context, 1)) != GETDNS_RETURN_GOOD) {
1725                 fprintf(test_info.errout,
1726                         "Create context failed: %s (%d)\n",
1727                         getdns_get_errorstr_by_id(ret),
1728                         ret);
1729                 exit(EXIT_UNKNOWN);
1730         }
1731 
1732         for (++av; *av && *av[0] == '-'; ++av) {
1733                 if (strcmp(*av, "-M") == 0 ||
1734                     strcmp(*av, "--monitoring") == 0) {
1735                         test_info.monitoring = true;
1736                         test_info.errout = stdout;
1737                 } else if (strcmp(*av, "-D") == 0 ||
1738                            strcmp(*av, "--debug") == 0) {
1739                         test_info.debug_output = true;
1740                 } else if (strcmp(*av, "-E") == 0 ||
1741                            strcmp(*av, "--fail-on-dns-errors") == 0) {
1742                         test_info.fail_on_dns_errors = true;
1743                 } else if (strcmp(*av, "-u") == 0 ||
1744                            strcmp(*av, "--udp") == 0 ) {
1745                         use_udp = true;
1746                 } else if (strcmp(*av, "-t") == 0 ||
1747                            strcmp(*av, "--tcp") == 0 ) {
1748                         use_tcp = true;
1749                 } else if (strcmp(*av, "-T") == 0 ||
1750                            strcmp(*av, "--tls") == 0 ) {
1751                         use_tls = true;
1752                 } else if (strcmp(*av, "-S") == 0 ||
1753                            strcmp(*av, "--strict-usage-profile") == 0 ) {
1754                         strict_usage_profile = true;
1755                         use_tls = true;
1756                 } else if (strcmp(*av, "-K") == 0 ||
1757                            strcmp(*av, "--spki-pin") == 0 ) {
1758                         ++av;
1759                         if (!*av) {
1760                                 fputs("pin string of the form " EXAMPLE_PIN "expected after -K|--pin\n", test_info.errout);
1761                                 exit(EXIT_UNKNOWN);
1762                         }
1763 
1764                         getdns_dict *pin;
1765 
1766                         pin = getdns_pubkey_pin_create_from_string(test_info.context, *av);
1767                         if (!pin) {
1768                                 fprintf(test_info.errout,
1769                                         "Could not convert '%s' into a public key pin.\n"
1770                                         "Good pins look like: " EXAMPLE_PIN "\n"
1771                                         "Please see RFC 7469 for details about the format.\n", *av);
1772                                 exit(EXIT_UNKNOWN);
1773                         }
1774                         if (!pinset)
1775                                 pinset = getdns_list_create_with_context(test_info.context);
1776                         ret = getdns_list_set_dict(pinset,
1777                                                    pinset_size++,
1778                                                    pin);
1779                         getdns_dict_destroy(pin);
1780                         if (ret != GETDNS_RETURN_GOOD) {
1781                                 fprintf(test_info.errout,
1782                                         "Could not add pin '%s' to pin set.\n",
1783                                         *av);
1784                                 getdns_list_destroy(pinset);
1785                                 exit(EXIT_UNKNOWN);
1786 
1787                         }
1788                         use_tls = true;
1789                 } else if (strcmp(*av, "-v") == 0 ||
1790                            strcmp(*av, "--verbose") == 0) {
1791                         ++test_info.verbosity;
1792                 } else if (strcmp(*av, "-V") == 0 ||
1793                            strcmp(*av, "--version") == 0) {
1794                         version();
1795                 } else {
1796                         usage();
1797                 }
1798         }
1799 
1800         if (*av == NULL || *av[0] != '@')
1801                 usage();
1802 
1803         /* Non-monitoring output is chatty by default. */
1804         if (!test_info.monitoring)
1805                 ++test_info.verbosity;
1806 
1807         const char *upstream = *av++;
1808         getdns_dict *resolver;
1809         getdns_bindata *address;
1810 
1811         if ((ret = getdns_str2dict(&upstream[1], &resolver)) != GETDNS_RETURN_GOOD) {
1812                 fprintf(test_info.errout,
1813                         "Could not convert \"%s\" to an IP dict: %s (%d)\n",
1814                         &upstream[1],
1815                         getdns_get_errorstr_by_id(ret),
1816                         ret);
1817                 exit(EXIT_UNKNOWN);
1818         }
1819 
1820         /* If the resolver info include TLS auth name, use TLS. */
1821         getdns_bindata *tls_auth_name;
1822         if (getdns_dict_get_bindata(resolver, "tls_auth_name", &tls_auth_name) == GETDNS_RETURN_GOOD)
1823                 use_tls = true;
1824 
1825         if ((ret = getdns_dict_get_bindata(resolver, "address_data", &address)) != GETDNS_RETURN_GOOD) {
1826                 fprintf(test_info.errout,
1827                         "\"%s\" did not translate to an IP dict: %s (%d)\n",
1828                         &upstream[1],
1829                         getdns_get_errorstr_by_id(ret),
1830                         ret);
1831                 getdns_dict_destroy(resolver);
1832                 exit(EXIT_UNKNOWN);
1833         }
1834 
1835         /* Set parameters on the resolver. */
1836         if (pinset) {
1837                 ret = getdns_dict_set_list(resolver,
1838                                            "tls_pubkey_pinset",
1839                                            pinset);
1840                 getdns_list_destroy(pinset);
1841                 if (ret != GETDNS_RETURN_GOOD) {
1842                         fprintf(test_info.errout,
1843                                 "Cannot set keys for \"%s\": %s (%d)\n",
1844                                 &upstream[1],
1845                                 getdns_get_errorstr_by_id(ret),
1846                                 ret);
1847                         exit(EXIT_UNKNOWN);
1848                 }
1849         }
1850 
1851         /* Set getdns context to use the indicated resolver. */
1852         getdns_list *l = getdns_list_create();
1853         ret = getdns_list_set_dict(l, 0, resolver);
1854         getdns_dict_destroy(resolver);
1855         if (ret != GETDNS_RETURN_GOOD) {
1856                 fprintf(test_info.errout,
1857                         "Unable to add upstream '%s' to list: %s (%d)\n",
1858                         upstream,
1859                         getdns_get_errorstr_by_id(ret),
1860                         ret);
1861                 getdns_list_destroy(l);
1862                 exit(EXIT_UNKNOWN);
1863         }
1864         ret = getdns_context_set_upstream_recursive_servers(test_info.context, l);
1865         getdns_list_destroy(l);
1866         if (ret != GETDNS_RETURN_GOOD) {
1867                 fprintf(test_info.errout,
1868                         "Unable to set upstream resolver to '%s': %s (%d)\n",
1869                         upstream,
1870                         getdns_get_errorstr_by_id(ret),
1871                         ret);
1872                 exit(EXIT_UNKNOWN);
1873         }
1874 
1875         /* Set context to stub mode. */
1876         if ((ret = getdns_context_set_resolution_type(test_info.context, GETDNS_RESOLUTION_STUB)) != GETDNS_RETURN_GOOD) {
1877                 fprintf(test_info.errout,
1878                         "Unable to set stub mode: %s (%d)\n",
1879                         getdns_get_errorstr_by_id(ret),
1880                         ret);
1881                 exit(EXIT_UNKNOWN);
1882         }
1883 
1884         /* Set other context parameters. */
1885         if (strict_usage_profile) {
1886                 ret = getdns_context_set_tls_authentication(test_info.context, GETDNS_AUTHENTICATION_REQUIRED);
1887                 if (ret != GETDNS_RETURN_GOOD) {
1888                         fprintf(test_info.errout,
1889                                 "Unable to set strict profile: %s (%d)\n",
1890                                 getdns_get_errorstr_by_id(ret),
1891                                 ret);
1892                         exit(EXIT_UNKNOWN);
1893                 }
1894         }
1895 
1896         /* Choose and run test */
1897         const char *testname = *av;
1898 
1899         if (!testname)
1900                 usage();
1901         ++av;
1902 
1903         const struct test_funcs_s *f;
1904         for (f = TESTS; f->name != NULL; ++f) {
1905                 if (strcmp(testname, f->name) == 0)
1906                         break;
1907         }
1908 
1909         if (f->name == NULL) {
1910                 fprintf(test_info.errout, "Unknown test %s\n", testname);
1911                 exit(EXIT_UNKNOWN);
1912         }
1913 
1914         if (f->implies_tcp) {
1915                 if (use_udp) {
1916                         fputs("Test requires TCP or TLS\n", test_info.errout);
1917                         exit(EXIT_UNKNOWN);
1918                 }
1919                 if (!use_tls)
1920                         use_tcp = true;
1921         }
1922 
1923         if (f->implies_tls) {
1924                 if (use_udp | use_tcp) {
1925                         fputs("Test requires TLS, or TLS authentication specified\n", test_info.errout);
1926                         exit(EXIT_UNKNOWN);
1927                 }
1928                 use_tls = true;
1929         }
1930 
1931         if ((use_tls + use_udp + use_tcp) > 1) {
1932                 fputs("Specify one only of -u, -t, -T\n", test_info.errout);
1933                 exit(EXIT_UNKNOWN);
1934         }
1935 
1936         if (use_tls || use_udp || use_tcp) {
1937                 getdns_transport_list_t udp[] = { GETDNS_TRANSPORT_UDP };
1938                 getdns_transport_list_t tcp[] = { GETDNS_TRANSPORT_TCP };
1939                 getdns_transport_list_t tls[] = { GETDNS_TRANSPORT_TLS };
1940                 getdns_transport_list_t *transport =
1941                         (use_tls) ? tls : (use_tcp) ? tcp : udp;
1942                 if ((ret = getdns_context_set_dns_transport_list(test_info.context, 1, transport)) != GETDNS_RETURN_GOOD) {
1943                         fprintf(test_info.errout,
1944                                 "Unable to set %s transport: %s (%d)\n",
1945                                 (use_tls) ? "TLS" : (use_tcp) ? "TCP" : "UDP",
1946                                 getdns_get_errorstr_by_id(ret),
1947                                 ret);
1948                         exit(EXIT_UNKNOWN);
1949                 }
1950         }
1951 
1952         exit_value xit = f->func(&test_info, av);
1953         const char *xit_text = "(\?\?\?)";
1954         FILE *out = stdout;
1955 
1956         switch(xit) {
1957         case EXIT_OK:
1958                 xit_text = "OK";
1959                 break;
1960 
1961         case EXIT_WARNING:
1962                 xit_text = "WARNING";
1963                 break;
1964 
1965         case EXIT_CRITICAL:
1966                 xit_text = "CRITICAL";
1967                 break;
1968 
1969         case EXIT_UNKNOWN:
1970         case EXIT_USAGE:
1971                 xit = EXIT_UNKNOWN;
1972                 xit_text = "UNKNOWN";
1973                 out = test_info.errout;
1974                 break;
1975         }
1976 
1977         if (test_info.monitoring)
1978                 fprintf(out, "DNS SERVER %s - ", xit_text);
1979         fputs(test_info.base_output, out);
1980 
1981         if (test_info.verbosity >= VERBOSITY_ADDITIONAL &&
1982             test_info.monitoring &&
1983             strlen(test_info.perf_output) > 0) {
1984                 fprintf(out, "|%s", test_info.perf_output);
1985         }
1986 
1987         fputc('\n', out);
1988         exit(xit);
1989 }
1990