patch to fetch sshfp using getdns

Philip Homburg pch-openssh at u-1.phicoh.com
Fri Jul 24 17:17:47 AEST 2015


Hi,

Here is a patch to fetch sshfp DNS records using getdns instead of ldns.
Enable using --with-getdns. The original ldns code is still there.

Getdns solves two problem with ldns: it know where the root trust anchors
lives and it can handle recursive resolvers that are not dnssec-aware.


diff --git a/configure.ac b/configure.ac
index 9b05c30..7c0fd88 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1459,6 +1459,38 @@ int main() { ldns_status status = ldns_verify_trusted(NULL, NULL, NULL, NULL); s
     ]
 )
 
+# Check whether user wants to use getdns
+GETDNS_MSG="no"
+AC_ARG_WITH(getdns,
+	[  --with-getdns[[=PATH]]      Use getdns for DNSSEC support (optionally in PATH)],
+    [
+        if test "x$withval" != "xno" ; then
+
+			if test "x$withval" != "xyes" ; then
+				CPPFLAGS="$CPPFLAGS -I${withval}/include"
+				LDFLAGS="$LDFLAGS -L${withval}/lib"
+			fi
+
+            AC_DEFINE(HAVE_GETDNS, 1, [Define if you want getdns support])
+            LIBS="-lgetdns $LIBS"
+            GETDNS_MSG="yes"
+
+            AC_MSG_CHECKING([for getdns support])
+            AC_LINK_IFELSE(
+                [AC_LANG_SOURCE([[
+#include <getdns/getdns.h>
+int main() { getdns_context *this_context; getdns_return_t status = getdns_context_create(&this_context, 1); return (status == GETDNS_RETURN_GOOD ? 0 : 1); }
+                                ]])
+                ],
+				[AC_MSG_RESULT(yes)],
+				[
+					AC_MSG_RESULT(no)
+					AC_MSG_ERROR([** Incomplete or missing getdns libraries.])
+				])
+        fi
+    ]
+)
+
 # Check whether user wants libedit support
 LIBEDIT_MSG="no"
 AC_ARG_WITH([libedit],
diff --git a/openbsd-compat/Makefile.in b/openbsd-compat/Makefile.in
index 3c5e3b7..24b52b3 100644
--- a/openbsd-compat/Makefile.in
+++ b/openbsd-compat/Makefile.in
@@ -18,7 +18,7 @@ LDFLAGS=-L. @LDFLAGS@
 
 OPENBSD=base64.o basename.o bcrypt_pbkdf.o bindresvport.o blowfish.o daemon.o dirname.o fmt_scaled.o getcwd.o getgrouplist.o getopt_long.o getrrsetbyname.o glob.o inet_aton.o inet_ntoa.o inet_ntop.o mktemp.o pwcache.o readpassphrase.o reallocarray.o realpath.o rresvport.o setenv.o setproctitle.o sha1.o sha2.o rmd160.o md5.o sigact.o strlcat.o strlcpy.o strmode.o strnlen.o strptime.o strsep.o strtonum.o strtoll.o strtoul.o strtoull.o timingsafe_bcmp.o vis.o blowfish.o bcrypt_pbkdf.o explicit_bzero.o
 
-COMPAT=arc4random.o bsd-asprintf.o bsd-closefrom.o bsd-cray.o bsd-cygwin_util.o bsd-getpeereid.o getrrsetbyname-ldns.o bsd-misc.o bsd-nextstep.o bsd-openpty.o bsd-poll.o bsd-setres_id.o bsd-snprintf.o bsd-statvfs.o bsd-waitpid.o fake-rfc2553.o openssl-compat.o xmmap.o xcrypt.o kludge-fd_set.o
+COMPAT=arc4random.o bsd-asprintf.o bsd-closefrom.o bsd-cray.o bsd-cygwin_util.o bsd-getpeereid.o getrrsetbyname-getdns.o getrrsetbyname-ldns.o bsd-misc.o bsd-nextstep.o bsd-openpty.o bsd-poll.o bsd-setres_id.o bsd-snprintf.o bsd-statvfs.o bsd-waitpid.o fake-rfc2553.o openssl-compat.o xmmap.o xcrypt.o kludge-fd_set.o
 
 PORTS=port-aix.o port-irix.o port-linux.o port-solaris.o port-tun.o port-uw.o
 
diff --git a/openbsd-compat/getrrsetbyname-getdns.c b/openbsd-compat/getrrsetbyname-getdns.c
new file mode 100644
index 0000000..a2dc147
--- /dev/null
+++ b/openbsd-compat/getrrsetbyname-getdns.c
@@ -0,0 +1,322 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2015 Philip Homburg <philip at f-src.phicoh.com>
+ * Copyright (c) 2007 Simon Vallet / Genoscope <svallet at genoscope.cns.fr>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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 "includes.h"
+
+#if !defined (HAVE_GETRRSETBYNAME) && defined (HAVE_GETDNS)
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <getdns/getdns.h>
+
+#include "getrrsetbyname.h"
+#include "log.h"
+#include "xmalloc.h"
+
+#define malloc(x)	(xmalloc(x))
+#define calloc(x, y)	(xcalloc((x),(y)))
+
+
+int
+getrrsetbyname(const char *hostname, unsigned int rdclass,
+	       unsigned int rdtype, unsigned int flags,
+	       struct rrsetinfo **res)
+{
+	int result, dnssec_status;
+	getdns_return_t this_ret;  /* Holder for all function returns */
+	uint32_t this_error;
+	getdns_context *this_context = NULL;
+	getdns_dict * this_extensions = NULL;
+	getdns_dict * this_response = NULL;
+	getdns_list *replies_tree_list;
+	getdns_dict *reply_dict;
+	getdns_list *answer_list;
+	size_t num_answers, rec_count;
+	struct rrsetinfo *rrset = NULL;
+	struct rdatainfo *rdata;
+
+	/* don't allow flags yet, unimplemented */
+	if (flags) {
+		result = ERRSET_INVAL;
+		goto done;
+	}
+
+	if (rdclass != ns_c_in)
+	{
+		/* We only support class IN */
+		debug2("getdns: we only support class IN\n");
+		result = ERRSET_FAIL;
+		goto done;
+	}
+
+	/* Create the DNS context for this call */
+	this_ret = getdns_context_create(&this_context, 1);
+	if (this_ret != GETDNS_RETURN_GOOD)
+	{
+		debug2("getdns: trying to create the context failed: %d\n",
+			this_ret);
+		result = ERRSET_FAIL;
+		goto done;
+	}
+
+	this_extensions = getdns_dict_create();
+	this_ret = getdns_dict_set_int(this_extensions,
+		"dnssec_return_status", GETDNS_EXTENSION_TRUE);
+	if (this_ret != GETDNS_RETURN_GOOD)
+	{
+		debug2("getdns: trying to set an extension for DNSSEC failed: %d", this_ret);
+		result = ERRSET_FAIL;
+		goto done;
+	}
+
+	/* Set up the getdns_sync_request call */
+	this_ret = getdns_general_sync(this_context, hostname, rdtype,
+		this_extensions, &this_response);
+	if (this_ret == GETDNS_RETURN_BAD_DOMAIN_NAME)
+	{
+		debug2("getdns: bad domain name was used: %s\n", hostname);
+		result = ERRSET_FAIL;
+		goto done;
+	}
+
+	/* Be sure the search returned something */
+	this_ret = getdns_dict_get_int(this_response, "status", &this_error);
+	if (this_ret != GETDNS_RETURN_GOOD)
+	{
+		debug2("getdns: getdns_dict_get_int failed for 'status': %d",
+			this_ret);
+		result = ERRSET_FAIL;
+		goto done;
+	}
+
+	if (this_error != GETDNS_RESPSTATUS_GOOD)  // If the search didn't return "good"
+	{
+		debug2("getdns: the search had no results, and status %d",
+			this_error);
+		result = ERRSET_FAIL;
+		goto done;
+	}
+
+	this_ret = getdns_dict_get_list(this_response, "replies_tree",
+		&replies_tree_list);
+	if (this_ret != GETDNS_RETURN_GOOD)
+	{
+		debug2(
+		"getdns: getdns_dict_get_list failed for 'replies_tree': %d",
+			this_ret);
+		result = ERRSET_FAIL;
+		goto done;
+	}
+
+	/* Assume one reply */
+	this_ret = getdns_list_get_dict(replies_tree_list, 0, &reply_dict);
+	if (this_ret != GETDNS_RETURN_GOOD)
+	{
+		debug2("getdns: getdns_list_get_dict failed for '[0]': %d",
+			this_ret);
+		result = ERRSET_FAIL;
+		goto done;
+	}
+
+	this_ret = getdns_dict_get_int(reply_dict, "dnssec_status", &dnssec_status);
+	if (this_ret != GETDNS_RETURN_GOOD)
+	{
+		debug2(
+		"getdns: getdns_dict_get_int failed for 'dnssec_status': %d",
+			this_ret);
+		result = ERRSET_FAIL;
+		goto done;
+	}
+
+	this_ret = getdns_dict_get_list(reply_dict, "answer",
+		&answer_list);
+	if (this_ret != GETDNS_RETURN_GOOD)
+	{
+		debug2(
+		"getdns: getdns_dict_get_list failed for 'answer': %d",
+			this_ret);
+		result = ERRSET_FAIL;
+		goto done;
+	}
+
+	this_ret = getdns_list_get_length(answer_list, &num_answers);
+	if (this_ret != GETDNS_RETURN_GOOD)
+	{
+		debug2("getdns: getdns_list_get_length failed: %d",
+			this_ret);
+		result = ERRSET_FAIL;
+		goto done;
+	}
+
+	/* initialize rrset */
+	rrset = calloc(1, sizeof(struct rrsetinfo));
+	if (rrset == NULL) {
+		result = ERRSET_NOMEMORY;
+		goto done;
+	}
+	rrset->rri_nrdatas = num_answers;
+	if (!rrset->rri_nrdatas) {
+		result = ERRSET_NODATA;
+		goto done;
+	}
+
+	if (dnssec_status == GETDNS_DNSSEC_SECURE)
+		rrset->rri_flags |= RRSET_VALIDATED;
+
+	/* allocate memory for answers */
+	rrset->rri_rdatas = calloc(rrset->rri_nrdatas,
+	   sizeof(struct rdatainfo));
+
+	if (rrset->rri_rdatas == NULL) {
+		result = ERRSET_NOMEMORY;
+		goto done;
+	}
+
+
+	/* Go through each record */
+	rec_count= 0;
+	for ( size_t ans_count = 0; ans_count < num_answers; ++ans_count )
+	{
+		getdns_dict * this_answer;
+		getdns_dict *rdata_dict;
+		getdns_bindata *this_rdata_data;
+		int answer_type;
+
+		this_ret = getdns_list_get_dict(answer_list, ans_count,
+			&this_answer);
+		if (this_ret != GETDNS_RETURN_GOOD)
+		{
+			debug2(
+			"getdns: getdns_list_get_dict failed for '[%d]': %d",
+				ans_count, this_ret);
+			result = ERRSET_FAIL;
+			goto done;
+		}
+
+		this_ret= getdns_dict_get_int(this_answer, "type",
+			&answer_type);		
+		if (this_ret != GETDNS_RETURN_GOOD)
+		{
+			debug2(
+			"getdns: getdns_dict_get_int failed for 'type': %d",
+				this_ret);
+			result = ERRSET_FAIL;
+			goto done;
+		}
+
+		if ((unsigned)answer_type != rdtype)
+			continue;
+
+		this_ret = getdns_dict_get_dict(this_answer, "rdata",
+			&rdata_dict);
+		if (this_ret != GETDNS_RETURN_GOOD)
+		{
+			debug2(
+			"getdns: getdns_dict_get_dict failed for 'rdata': %d",
+				this_ret);
+			result = ERRSET_FAIL;
+			goto done;
+		}
+
+		this_ret = getdns_dict_get_bindata(rdata_dict, "rdata_raw",
+			&this_rdata_data); // Ignore any error
+		if (this_ret != GETDNS_RETURN_GOOD)
+		{
+			debug2(
+		"getdns: getdns_dict_get_bindata failed for 'rdata_raw': %d",
+				this_ret);
+			result = ERRSET_FAIL;
+			goto done;
+		}
+
+		rdata = &rrset->rri_rdatas[rec_count];
+		rdata->rdi_length = this_rdata_data->size;
+
+		rdata->rdi_data = malloc(rdata->rdi_length);
+		if (rdata->rdi_data == NULL) {
+			result = ERRSET_NOMEMORY;
+			goto done;
+		}
+
+		memcpy(rdata->rdi_data, this_rdata_data->data,
+			rdata->rdi_length);
+
+		rec_count++;
+	}
+
+	rrset->rri_nrdatas = rec_count;
+
+	*res = rrset;
+	rrset= NULL;
+	result = ERRSET_SUCCESS;
+
+done:
+	getdns_dict_destroy(this_response); 
+	getdns_dict_destroy(this_extensions);
+	getdns_context_destroy(this_context);
+	freerrset(rrset);
+
+	return result;
+}
+
+
+void
+freerrset(struct rrsetinfo *rrset)
+{
+	u_int16_t i;
+
+	if (rrset == NULL)
+		return;
+
+	if (rrset->rri_rdatas) {
+		for (i = 0; i < rrset->rri_nrdatas; i++) {
+			if (rrset->rri_rdatas[i].rdi_data == NULL)
+				break;
+			free(rrset->rri_rdatas[i].rdi_data);
+		}
+		free(rrset->rri_rdatas);
+	}
+
+	if (rrset->rri_sigs) {
+		for (i = 0; i < rrset->rri_nsigs; i++) {
+			if (rrset->rri_sigs[i].rdi_data == NULL)
+				break;
+			free(rrset->rri_sigs[i].rdi_data);
+		}
+		free(rrset->rri_sigs);
+	}
+
+	if (rrset->rri_name)
+		free(rrset->rri_name);
+	free(rrset);
+}
+
+
+#endif /* !defined (HAVE_GETRRSETBYNAME) && defined (HAVE_LDNS) */
diff --git a/openbsd-compat/getrrsetbyname.c b/openbsd-compat/getrrsetbyname.c
index dc6fe05..f9ca5df 100644
--- a/openbsd-compat/getrrsetbyname.c
+++ b/openbsd-compat/getrrsetbyname.c
@@ -47,7 +47,7 @@
 
 #include "includes.h"
 
-#if !defined (HAVE_GETRRSETBYNAME) && !defined (HAVE_LDNS)
+#if !defined (HAVE_GETRRSETBYNAME) && !defined (HAVE_LDNS) && !defined(HAVE_GETDNS)
 
 #include <stdlib.h>
 #include <string.h>
diff --git a/sshconnect.c b/sshconnect.c
index f41960c..9f1eafa 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -71,6 +71,7 @@ char *server_version_string = NULL;
 Key *previous_host_key = NULL;
 
 static int matching_host_key_dns = 0;
+static int dns_secure = 0;
 
 static pid_t proxy_command_pid = 0;
 
@@ -972,13 +973,18 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
 				fatal("%s: sshkey_fingerprint fail", __func__);
 			msg2[0] = '\0';
 			if (options.verify_host_key_dns) {
-				if (matching_host_key_dns)
+				if (!matching_host_key_dns)
 					snprintf(msg2, sizeof(msg2),
-					    "Matching host key fingerprint"
+					    "No matching host key fingerprint"
 					    " found in DNS.\n");
+				else if (!dns_secure)
+					snprintf(msg2, sizeof(msg2),
+					    "The DNS lookup was not secure,"
+					    " however a matching host key"
+					    " fingerprint was found in DNS.\n");
 				else
 					snprintf(msg2, sizeof(msg2),
-					    "No matching host key fingerprint"
+					    "Matching host key fingerprint"
 					    " found in DNS.\n");
 			}
 			snprintf(msg, sizeof(msg),
@@ -1295,6 +1301,9 @@ verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key)
 					r = 0;
 					goto out;
 				}
+				if (flags & DNS_VERIFY_SECURE) {
+					dns_secure = 1;
+				}
 				if (flags & DNS_VERIFY_MATCH) {
 					matching_host_key_dns = 1;
 				} else {


More information about the openssh-unix-dev mailing list