getdns/src/tools/getdns_server_mon.c

916 lines
30 KiB
C

/*
* Copyright (c) 2018, NLNet Labs, Sinodun
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the names of the copyright holders nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Verisign, Inc. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "debug.h"
#include <ctype.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/bio.h>
#include <getdns/getdns.h>
#include <getdns/getdns_extra.h>
#define APP_NAME "getdns_server_mon"
#define CERT_EXPIRY_CRITICAL_DAYS 7
#define CERT_EXPIRY_WARNING_DAYS 14
#define DEFAULT_LOOKUP_NAME "getdnsapi.net"
#define DEFAULT_LOOKUP_TYPE GETDNS_RRTYPE_AAAA
#define EXAMPLE_PIN "pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\""
/* Plugin exit values */
typedef enum {
EXIT_OK = 0,
EXIT_WARNING,
EXIT_CRITICAL,
EXIT_UNKNOWN
} exit_value_t;
/* Plugin verbosity values */
typedef enum {
VERBOSITY_MINIMAL = 0,
VERBOSITY_ADDITIONAL,
VERBOSITY_CONFIG,
VERBOSITY_DEBUG
} verbosity_t;
static struct test_info_s
{
getdns_context *context;
/* Output control */
bool monitoring;
FILE *errout;
verbosity_t verbosity;
/* Test config info */
bool fail_on_dns_errors;
} test_info;
static int get_rrtype(const char *t)
{
char buf[1024] = "GETDNS_RRTYPE_";
uint32_t rrtype;
long int l;
size_t i;
char *endptr;
if (strlen(t) > sizeof(buf) - 15)
return -1;
for (i = 14; *t && i < sizeof(buf) - 1; i++, t++)
buf[i] = *t == '-' ? '_' : toupper(*t);
buf[i] = '\0';
if (!getdns_str2int(buf, &rrtype))
return (int)rrtype;
if (strncasecmp(buf + 14, "TYPE", 4) == 0) {
l = strtol(buf + 18, &endptr, 10);
if (!*endptr && l >= 0 && l < 65536)
return l;
}
return -1;
}
static const char *rcode_text(int rcode)
{
const char *text[] = {
"OK",
"FORMERR",
"SERVFAIL",
"NXDOMAIN",
"NOTIMP",
"REFUSED"
};
if ((size_t) rcode >= sizeof(text) / sizeof(text[0]))
return "(?)";
else
return text[rcode];
}
/* Thanks to:
* https://zakird.com/2013/10/13/certificate-parsing-with-openssl
*/
static bool extract_cert_expiry(const unsigned char *data, size_t len, time_t *t)
{
X509 *cert = d2i_X509(NULL, &data, len);
if (!cert)
return false;
ASN1_TIME *not_after = X509_get_notAfter(cert);
/*
* Use ASN1_TIME_diff to get a time delta between now and expiry.
* This is much easier than trying to parse the time.
*/
int day, sec;
*t = time(NULL);
ASN1_TIME_diff(&day, &sec, NULL, not_after);
*t += day * 86400 + sec;
X509_free(cert);
return true;
}
static void exit_tidy()
{
if (test_info.context)
getdns_context_destroy(test_info.context);
}
static void usage()
{
fputs(
"Usage: " APP_NAME " [-MEr] @upstream testname [<name>] [<type>]\n"
" -M|--monitoring Make output suitable for monitoring tools\n"
" -E|--fail-on-dns-errors Fail on DNS error (NXDOMAIN, SERVFAIL)\n"
" -T|--tls Use TLS transport\n"
" -S|--strict-usage-profile Use strict profile (require authentication)\n"
" -K|--spki-pin <spki-pin> SPKI pin for TLS connections (can repeat)\n"
" -v|--verbose Increase output verbosity\n"
" -V|--version Report GetDNS version\n"
"\n"
"spki-pin: Should look like '" EXAMPLE_PIN "'\n"
"\n"
"upstream: @<ip>[%<scope_id][@<port>][#<tls_port>][~tls name>][^<tsig spec>]\n"
" <ip>@<port> may be given as <IPv4>:<port> or\n"
" '['<IPv6>[%<scope_id>]']':<port>\n"
"\n"
"tsig spec: [<algorithm>:]<name>:<secret in Base64>\n"
"\n"
"Tests:\n"
" auth [<name> [<type>]] Check authentication of TLS server\n"
" If both a SPKI pin and authentication name are\n"
" provided, both must authenticate for this test\n"
" to pass.\n"
" qname-min Check whether server supports QNAME minimisation\n"
" cert-valid [<name> [type]] [warn-days,crit-days]\n"
" Check server certificate validity, report\n"
" warning or critical if days to expiry at\n"
" or below thresholds (default 14,7).\n"
"\n"
"Enabling monitoring mode ensures output messages and exit statuses conform\n"
"to the requirements of monitoring plugins (www.monitoring-plugins.org).\n",
test_info.errout);
exit(EXIT_UNKNOWN);
}
static void version()
{
fputs(APP_NAME ": getdns " GETDNS_VERSION " , API " GETDNS_API_VERSION ".\n",
test_info.errout);
exit(EXIT_UNKNOWN);
}
/**
** Functions used by tests.
**/
static exit_value_t get_cert_valid_thresholds(char ***av,
int *critical_days,
int *warning_days)
{
*critical_days = CERT_EXPIRY_CRITICAL_DAYS;
*warning_days = CERT_EXPIRY_WARNING_DAYS;
if (**av) {
char *comma = strchr(**av, ',');
if (!comma)
return EXIT_UNKNOWN;
char *end;
long w,c;
c = strtol(**av, &end, 10);
/*
* If the number doesn't end at a comma, this isn't a
* properly formatted thresholds arg. Pass over it.
*/
if (end != comma)
return EXIT_UNKNOWN;
/*
* Similarly, if the number doesn't end at the end of the
* argument, this isn't a properly formatted arg.
*/
w = strtol(comma + 1, &end, 10);
if (*end != '\0')
return EXIT_UNKNOWN;
/* Got two numbers, so consume the argument. */
*critical_days = (int) c;
*warning_days = (int) w;
++*av;
return EXIT_OK;
}
return EXIT_UNKNOWN;
}
static exit_value_t get_name_type_args(const struct test_info_s *test_info,
char ***av,
const char **lookup_name,
uint32_t *lookup_type)
{
if (**av) {
if (strlen(**av) > 0) {
*lookup_name = **av;
} else {
fputs("Empty name not valid", test_info->errout);
return EXIT_UNKNOWN;
}
++*av;
if (**av) {
int rrtype = get_rrtype(**av);
if (rrtype >= 0) {
*lookup_type = (uint32_t) rrtype;
++*av;
}
}
}
return EXIT_OK;
}
static exit_value_t search(const struct test_info_s *test_info,
const char *name,
uint16_t type,
getdns_dict **response)
{
getdns_return_t ret;
getdns_dict *extensions = getdns_dict_create();
if ((ret = getdns_dict_set_int(extensions, "return_call_reporting", GETDNS_EXTENSION_TRUE)) != GETDNS_RETURN_GOOD) {
fprintf(test_info->errout,
"Cannot set return call reporting: %s (%d)",
getdns_get_errorstr_by_id(ret),
ret);
getdns_dict_destroy(extensions);
return EXIT_UNKNOWN;
}
if ((ret = getdns_dict_set_int(extensions, "return_both_v4_and_v6", GETDNS_EXTENSION_TRUE)) != GETDNS_RETURN_GOOD) {
fprintf(test_info->errout,
"Cannot set return both IPv4 and IPv6: %s (%d)",
getdns_get_errorstr_by_id(ret),
ret);
getdns_dict_destroy(extensions);
return EXIT_UNKNOWN;
}
if (test_info->verbosity >= VERBOSITY_DEBUG) {
fprintf(test_info->errout,
"Context: %s\n",
getdns_pretty_print_dict(getdns_context_get_api_information(test_info->context)));
}
ret = getdns_general_sync(test_info->context,
name,
type,
extensions,
response);
getdns_dict_destroy(extensions);
if (ret != GETDNS_RETURN_GOOD) {
fprintf(test_info->errout,
"Error resolving '%s': %s (%d)",
name,
getdns_get_errorstr_by_id(ret),
ret);
return EXIT_CRITICAL;
}
if (test_info->verbosity >= VERBOSITY_DEBUG) {
fprintf(test_info->errout,
"Response: %s\n",
getdns_pretty_print_dict(*response));
}
return EXIT_OK;
}
static exit_value_t check_result(const struct test_info_s *test_info,
const getdns_dict *response)
{
getdns_return_t ret;
uint32_t error_id;
if ((ret = getdns_dict_get_int(response, "status", &error_id)) != GETDNS_RETURN_GOOD) {
fprintf(test_info->errout,
"Cannot get result status: %s (%d)",
getdns_get_errorstr_by_id(ret),
ret);
return EXIT_UNKNOWN;
}
if (test_info->verbosity >= VERBOSITY_ADDITIONAL){
fprintf(test_info->errout,
"result: %s (%d), ",
getdns_get_errorstr_by_id(error_id),
error_id);
}
if (error_id == GETDNS_RESPSTATUS_GOOD)
return EXIT_OK;
uint32_t rcode;
ret = getdns_dict_get_int(response, "/replies_tree/0/header/rcode", &rcode);
if (ret == GETDNS_RETURN_NO_SUCH_DICT_NAME ||
ret == GETDNS_RETURN_NO_SUCH_LIST_ITEM) {
fputs("Search had no results, timeout?", test_info->errout);
return EXIT_CRITICAL;
} else if (ret != GETDNS_RETURN_GOOD) {
fprintf(test_info->errout,
"Cannot get DNS return code: %s (%d)",
getdns_get_errorstr_by_id(ret),
ret);
return EXIT_UNKNOWN;
}
if (test_info->fail_on_dns_errors && rcode > 0) {
fprintf(test_info->errout,
"DNS error %s (%d)",
rcode_text(rcode),
rcode);
return EXIT_CRITICAL;
}
return EXIT_OK;
}
static exit_value_t get_report_info(const struct test_info_s *test_info,
const getdns_dict *response,
uint32_t *rtt,
getdns_bindata **auth_status,
time_t *cert_expire_time)
{
getdns_return_t ret;
getdns_list *l;
uint32_t rtt_val;
getdns_bindata *auth_status_val = NULL;
time_t cert_expire_time_val = 0;
if ((ret = getdns_dict_get_list(response, "call_reporting", &l)) != GETDNS_RETURN_GOOD) {
fprintf(test_info->errout,
"Cannot get call report: %s (%d)",
getdns_get_errorstr_by_id(ret),
ret);
return EXIT_UNKNOWN;
}
getdns_dict *d;
if ((ret = getdns_list_get_dict(l, 0, &d)) != GETDNS_RETURN_GOOD) {
fprintf(test_info->errout,
"Cannot get call report first item: %s (%d)",
getdns_get_errorstr_by_id(ret),
ret);
return EXIT_UNKNOWN;
}
if ((ret = getdns_dict_get_int(d, "run_time/ms", &rtt_val)) != GETDNS_RETURN_GOOD) {
fprintf(test_info->errout,
"Cannot get RTT: %s (%d)",
getdns_get_errorstr_by_id(ret),
ret);
return EXIT_UNKNOWN;
}
if (rtt)
*rtt = rtt_val;
if (test_info->verbosity >= VERBOSITY_ADDITIONAL)
fprintf(test_info->errout, "RTT %dms, ", rtt_val);
if (getdns_dict_get_bindata(d, "tls_auth_status", &auth_status_val) == GETDNS_RETURN_GOOD) {
/* Just in case - not sure this is necessary */
auth_status_val->data[auth_status_val->size] = '\0';
if (test_info->verbosity >= VERBOSITY_ADDITIONAL)
fprintf(test_info->errout, "auth. %s, ", (char *) auth_status_val->data);
}
if (auth_status)
*auth_status = auth_status_val;
getdns_bindata *cert;
if (getdns_dict_get_bindata(d, "tls_peer_cert", &cert) == GETDNS_RETURN_GOOD) {
if (!extract_cert_expiry(cert->data, cert->size, &cert_expire_time_val)) {
fputs("Cannot parse PKIX certificate", test_info->errout);
return EXIT_UNKNOWN;
}
if (test_info->verbosity >= VERBOSITY_ADDITIONAL) {
struct tm *tm = gmtime(&cert_expire_time_val);
char buf[25];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm);
fprintf(test_info->errout, "cert expiry %s, ", buf);
}
}
if (cert_expire_time)
*cert_expire_time = cert_expire_time_val;
return EXIT_OK;
}
static exit_value_t get_answers(const struct test_info_s *test_info,
const getdns_dict *response,
getdns_list **answers,
size_t *no_answers)
{
getdns_return_t ret;
if ((ret = getdns_dict_get_list(response, "/replies_tree/0/answer", answers)) != GETDNS_RETURN_GOOD) {
fprintf(test_info->errout,
"Cannot get answers: %s (%d)",
getdns_get_errorstr_by_id(ret),
ret);
return EXIT_UNKNOWN;
}
if ((ret = getdns_list_get_length(*answers, no_answers)) != GETDNS_RETURN_GOOD) {
fprintf(test_info->errout,
"Cannot get number of answers: %s (%d)",
getdns_get_errorstr_by_id(ret),
ret);
return EXIT_UNKNOWN;
}
if (*no_answers <= 0) {
fputs("Got zero answers", test_info->errout);
return EXIT_WARNING;
}
return EXIT_OK;
}
static exit_value_t check_answer_type(const struct test_info_s *test_info,
const getdns_dict *response,
uint32_t rrtype)
{
getdns_list *answers;
size_t no_answers;
exit_value_t xit;
if ((xit = get_answers(test_info, response, &answers, &no_answers)) != EXIT_OK)
return xit;
for (size_t i = 0; i < no_answers; ++i) {
getdns_dict *answer;
getdns_return_t ret;
if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) {
fprintf(test_info->errout,
"Cannot get answer number %zu: %s (%d)",
i,
getdns_get_errorstr_by_id(ret),
ret);
return EXIT_UNKNOWN;
}
uint32_t rtype;
if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) {
fprintf(test_info->errout,
"Cannot get answer type: %s (%d)",
getdns_get_errorstr_by_id(ret),
ret);
return EXIT_UNKNOWN;
}
if (rtype == rrtype)
return EXIT_OK;
}
fputs("Answer does not contain expected type", test_info->errout);
return EXIT_UNKNOWN;
}
/**
** Test routines.
**/
static exit_value_t test_authenticate(const struct test_info_s *test_info,
char ** av)
{
const char *lookup_name = DEFAULT_LOOKUP_NAME;
uint32_t lookup_type = DEFAULT_LOOKUP_TYPE;
exit_value_t xit;
if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK)
return xit;
if (*av) {
fputs("auth takes arguments [<name> [<type>]]",
test_info->errout);
return EXIT_UNKNOWN;
}
getdns_dict *response;
if ((xit = search(test_info, lookup_name, lookup_type, &response)) != EXIT_OK)
return xit;
if ((xit = check_result(test_info, response)) != EXIT_OK)
return xit;
getdns_bindata *auth_status;
if ((xit = get_report_info(test_info, response, NULL, &auth_status, NULL)) != EXIT_OK)
return xit;
if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK)
return xit;
if (!auth_status || strcmp((char *) auth_status->data, "Success") != 0) {
fputs("Authentication failed", test_info->errout);
return EXIT_CRITICAL;
} else {
fputs("Authentication succeeded", test_info->errout);
return EXIT_OK;
}
}
static exit_value_t test_certificate_valid(const struct test_info_s *test_info,
char **av)
{
const char *lookup_name = DEFAULT_LOOKUP_NAME;
uint32_t lookup_type = DEFAULT_LOOKUP_TYPE;
exit_value_t xit;
int warning_days;
int critical_days;
/* Is first arg the threshold? */
if (get_cert_valid_thresholds(&av, &critical_days, &warning_days) != EXIT_OK) {
if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK)
return xit;
if (*av)
get_cert_valid_thresholds(&av, &critical_days, &warning_days);
}
if (*av) {
fputs("cert-valid takes arguments [<name> [<type>]] [warn-days,crit-days]",
test_info->errout);
return EXIT_UNKNOWN;
}
getdns_dict *response;
if ((xit = search(test_info, lookup_name, lookup_type, &response)) != EXIT_OK)
return xit;
if ((xit = check_result(test_info, response)) != EXIT_OK)
return xit;
time_t expire_time;
if ((xit = get_report_info(test_info, response, NULL, NULL, &expire_time)) != EXIT_OK)
return xit;
if (expire_time == 0) {
fputs("No PKIX certificate", test_info->errout);
return EXIT_CRITICAL;
}
if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK)
return xit;
time_t now = time(NULL);
int days_to_expiry = (expire_time - now) / 86400;
if (days_to_expiry < 0) {
fprintf(test_info->errout,
"Certificate expired %d day%s ago",
-days_to_expiry,
(days_to_expiry < -1) ? "s" : "");
return EXIT_CRITICAL;
}
if (days_to_expiry == 0) {
fputs("Certificate expires today", test_info->errout);
return EXIT_CRITICAL;
}
fprintf(test_info->errout,
"Certificate will expire in %d day%s",
days_to_expiry,
(days_to_expiry > 1) ? "s" : "");
if (days_to_expiry <= critical_days) {
return EXIT_CRITICAL;
}
if (days_to_expiry <= warning_days) {
return EXIT_WARNING;
}
return EXIT_OK;
}
static exit_value_t test_qname_minimisation(const struct test_info_s *test_info,
char ** av)
{
if (*av) {
fputs("qname-min takes no arguments",
test_info->errout);
return EXIT_UNKNOWN;
}
getdns_dict *response;
exit_value_t xit = search(test_info, "qnamemintest.internet.nl", GETDNS_RRTYPE_TXT, &response);
if (xit != EXIT_OK)
return xit;
xit = check_result(test_info, response);
if (xit != EXIT_OK)
return xit;
/* Don't need any of this, but do want check and verbosity reporting. */
xit = get_report_info(test_info, response, NULL, NULL, NULL);
if (xit != EXIT_OK)
return xit;
getdns_list *answers;
size_t no_answers;
if ((xit = get_answers(test_info, response, &answers, &no_answers)) != EXIT_OK)
return xit;
for (size_t i = 0; i < no_answers; ++i) {
getdns_dict *answer;
getdns_return_t ret;
if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) {
fprintf(test_info->errout,
"Cannot get answer number %zu: %s (%d)",
i,
getdns_get_errorstr_by_id(ret),
ret);
return EXIT_UNKNOWN;
}
uint32_t rtype;
if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) {
fprintf(test_info->errout,
"Cannot get answer type: %s (%d)",
getdns_get_errorstr_by_id(ret),
ret);
return EXIT_UNKNOWN;
}
if (rtype != GETDNS_RRTYPE_TXT)
continue;
getdns_bindata *rtxt;
if ((ret = getdns_dict_get_bindata(answer, "/rdata/txt_strings/0", &rtxt)) != GETDNS_RETURN_GOOD) {
fputs("No answer text", test_info->errout);
return EXIT_WARNING;
}
if (rtxt->size > 0 ) {
switch(rtxt->data[0]) {
case 'H':
fputs("QNAME minimisation ON", test_info->errout);
return EXIT_OK;
case 'N':
fputs("QNAME minimisation OFF", test_info->errout);
return EXIT_WARNING;
default:
/* Unrecognised message. */
break;
}
}
}
fputs("No valid QNAME minimisation data", test_info->errout);
return EXIT_UNKNOWN;
}
static struct test_funcs_s
{
const char *name;
exit_value_t (*func)(const struct test_info_s *test_info, char **av);
} TESTS[] =
{
{ "auth", test_authenticate },
{ "cert-valid", test_certificate_valid },
{ "qname-min", test_qname_minimisation },
{ NULL, NULL }
};
int main(int ATTR_UNUSED(ac), char *av[])
{
getdns_return_t ret;
getdns_list *pinset = NULL;
size_t pinset_size = 0;
bool strict_usage_profile = false;
bool use_tls = false;
test_info.errout = stderr;
atexit(exit_tidy);
if ((ret = getdns_context_create(&test_info.context, 1)) != GETDNS_RETURN_GOOD) {
fprintf(test_info.errout,
"Create context failed: %s (%d)\n",
getdns_get_errorstr_by_id(ret),
ret);
exit(EXIT_UNKNOWN);
}
for (++av; *av && *av[0] == '-'; ++av) {
if (strcmp(*av, "-M") == 0 ||
strcmp(*av, "--monitoring") == 0) {
test_info.monitoring = true;
test_info.errout = stdout;
} else if (strcmp(*av, "-E") == 0 ||
strcmp(*av, "--fail-on-dns-errors") == 0) {
test_info.fail_on_dns_errors = true;
} else if (strcmp(*av, "-T") == 0 ||
strcmp(*av, "--tls") == 0 ) {
use_tls = true;
} else if (strcmp(*av, "-S") == 0 ||
strcmp(*av, "--strict-usage-profile") == 0 ) {
strict_usage_profile = true;
use_tls = true;
} else if (strcmp(*av, "-K") == 0 ||
strcmp(*av, "--spki-pin") == 0 ) {
++av;
if (!*av) {
fputs("pin string of the form " EXAMPLE_PIN "expected after -K|--pin\n", test_info.errout);
exit(EXIT_UNKNOWN);
}
getdns_dict *pin;
pin = getdns_pubkey_pin_create_from_string(test_info.context, *av);
if (!pin) {
fprintf(test_info.errout,
"Could not convert '%s' into a public key pin.\n"
"Good pins look like: " EXAMPLE_PIN "\n"
"Please see RFC 7469 for details about the format.\n", *av);
exit(EXIT_UNKNOWN);
}
if (!pinset)
pinset = getdns_list_create_with_context(test_info.context);
ret = getdns_list_set_dict(pinset,
pinset_size++,
pin);
getdns_dict_destroy(pin);
if (ret != GETDNS_RETURN_GOOD) {
fprintf(test_info.errout,
"Could not add pin '%s' to pin set.\n",
*av);
getdns_list_destroy(pinset);
exit(EXIT_UNKNOWN);
}
use_tls = true;
} else if (strcmp(*av, "-v") == 0 ||
strcmp(*av, "--verbose") == 0) {
++test_info.verbosity;
} else if (strcmp(*av, "-V") == 0 ||
strcmp(*av, "--version") == 0) {
version();
} else {
usage();
}
}
if (*av == NULL || *av[0] != '@')
usage();
const char *upstream = *av++;
getdns_dict *resolver;
getdns_bindata *address;
if ((ret = getdns_str2dict(&upstream[1], &resolver)) != GETDNS_RETURN_GOOD) {
fprintf(test_info.errout,
"Could not convert \"%s\" to an IP dict: %s (%d)\n",
&upstream[1],
getdns_get_errorstr_by_id(ret),
ret);
exit(EXIT_UNKNOWN);
}
if ((ret = getdns_dict_get_bindata(resolver, "address_data", &address)) != GETDNS_RETURN_GOOD) {
fprintf(test_info.errout,
"\"%s\" did not translate to an IP dict: %s (%d)\n",
&upstream[1],
getdns_get_errorstr_by_id(ret),
ret);
getdns_dict_destroy(resolver);
exit(EXIT_UNKNOWN);
}
/* Set parameters on the resolver. */
if (pinset) {
ret = getdns_dict_set_list(resolver,
"tls_pubkey_pinset",
pinset);
getdns_list_destroy(pinset);
if (ret != GETDNS_RETURN_GOOD) {
fprintf(test_info.errout,
"Cannot set keys for \"%s\": %s (%d)\n",
&upstream[1],
getdns_get_errorstr_by_id(ret),
ret);
exit(EXIT_UNKNOWN);
}
}
/* Set getdns context to use the indicated resolver. */
getdns_list *l = getdns_list_create();
ret = getdns_list_set_dict(l, 0, resolver);
getdns_dict_destroy(resolver);
if (ret != GETDNS_RETURN_GOOD) {
fprintf(test_info.errout,
"Unable to add upstream '%s' to list: %s (%d)\n",
upstream,
getdns_get_errorstr_by_id(ret),
ret);
getdns_list_destroy(l);
exit(EXIT_UNKNOWN);
}
ret = getdns_context_set_upstream_recursive_servers(test_info.context, l);
getdns_list_destroy(l);
if (ret != GETDNS_RETURN_GOOD) {
fprintf(test_info.errout,
"Unable to set upstream resolver to '%s': %s (%d)\n",
upstream,
getdns_get_errorstr_by_id(ret),
ret);
exit(EXIT_UNKNOWN);
}
/* Set context to stub mode. */
if ((ret = getdns_context_set_resolution_type(test_info.context, GETDNS_RESOLUTION_STUB)) != GETDNS_RETURN_GOOD) {
fprintf(test_info.errout,
"Unable to set stub mode: %s (%d)\n",
getdns_get_errorstr_by_id(ret),
ret);
exit(EXIT_UNKNOWN);
}
/* Set other context parameters. */
if (use_tls) {
getdns_transport_list_t t[] = { GETDNS_TRANSPORT_TLS };
if ((ret = getdns_context_set_dns_transport_list(test_info.context, 1, t)) != GETDNS_RETURN_GOOD) {
fprintf(test_info.errout,
"Unable to set TLS transport: %s (%d)\n",
getdns_get_errorstr_by_id(ret),
ret);
exit(EXIT_UNKNOWN);
}
}
if (strict_usage_profile) {
ret = getdns_context_set_tls_authentication(test_info.context, GETDNS_AUTHENTICATION_REQUIRED);
if (ret != GETDNS_RETURN_GOOD) {
fprintf(test_info.errout,
"Unable to set strict profile: %s (%d)\n",
getdns_get_errorstr_by_id(ret),
ret);
exit(EXIT_UNKNOWN);
}
}
/* Choose and run test */
const char *testname = *av;
if (!testname)
usage();
++av;
for (const struct test_funcs_s *f = TESTS;
f->name != NULL;
++f) {
if (strcmp(testname, f->name) == 0) {
exit_value_t xit = f->func(&test_info, av);
fputc('\n', test_info.errout);
exit(xit);
}
}
fprintf(test_info.errout, "Unknown test %s\n", testname);
exit(EXIT_UNKNOWN);
}