upstream commit

don't record duplicate LocalForward and RemoteForward
 entries; fixes failure with ExitOnForwardFailure+hostname canonicalisation
 where the same forwards are added on the second pass through the
 configuration file. bz#2562; ok dtucker@

Upstream-ID: 40a51d68b6300f1cc61deecdb7d4847b8b7b0de1
diff --git a/misc.c b/misc.c
index db5ff56..9d59ca6 100644
--- a/misc.c
+++ b/misc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.c,v 1.103 2016/04/02 14:37:42 krw Exp $ */
+/* $OpenBSD: misc.c,v 1.104 2016/04/06 06:42:17 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  * Copyright (c) 2005,2006 Damien Miller.  All rights reserved.
@@ -1144,3 +1144,41 @@
 		error("setsockopt IPV6_V6ONLY: %s", strerror(errno));
 #endif
 }
+
+/*
+ * Compares two strings that maybe be NULL. Returns non-zero if strings
+ * are both NULL or are identical, returns zero otherwise.
+ */
+static int
+strcmp_maybe_null(const char *a, const char *b)
+{
+	if ((a == NULL && b != NULL) || (a != NULL && b == NULL))
+		return 0;
+	if (a != NULL && strcmp(a, b) != 0)
+		return 0;
+	return 1;
+}
+
+/*
+ * Compare two forwards, returning non-zero if they are identical or
+ * zero otherwise.
+ */
+int
+forward_equals(const struct Forward *a, const struct Forward *b)
+{
+	if (strcmp_maybe_null(a->listen_host, b->listen_host) == 0)
+		return 0;
+	if (a->listen_port != b->listen_port)
+		return 0;
+	if (strcmp_maybe_null(a->listen_path, b->listen_path) == 0)
+		return 0;
+	if (strcmp_maybe_null(a->connect_host, b->connect_host) == 0)
+		return 0;
+	if (a->connect_port != b->connect_port)
+		return 0;
+	if (strcmp_maybe_null(a->connect_path, b->connect_path) == 0)
+		return 0;
+	/* allocated_port and handle are not checked */
+	return 1;
+}
+
diff --git a/misc.h b/misc.h
index 434e06c..01432ba 100644
--- a/misc.h
+++ b/misc.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.h,v 1.55 2016/03/02 22:42:40 dtucker Exp $ */
+/* $OpenBSD: misc.h,v 1.56 2016/04/06 06:42:17 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -27,6 +27,8 @@
 	int	  handle;		/* Handle for dynamic listen ports */
 };
 
+int forward_equals(const struct Forward *, const struct Forward *);
+
 /* Common server and client forwarding options. */
 struct ForwardOptions {
 	int	 gateway_ports; /* Allow remote connects to forwarded ports. */
diff --git a/readconf.c b/readconf.c
index 69d4553..c692f7d 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.250 2016/02/08 23:40:12 djm Exp $ */
+/* $OpenBSD: readconf.c,v 1.251 2016/04/06 06:42:17 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -294,12 +294,19 @@
 add_local_forward(Options *options, const struct Forward *newfwd)
 {
 	struct Forward *fwd;
+	int i;
 #ifndef NO_IPPORT_RESERVED_CONCEPT
 	extern uid_t original_real_uid;
+
 	if (newfwd->listen_port < IPPORT_RESERVED && original_real_uid != 0 &&
 	    newfwd->listen_path == NULL)
 		fatal("Privileged ports can only be forwarded by root.");
 #endif
+	/* Don't add duplicates */
+	for (i = 0; i < options->num_local_forwards; i++) {
+		if (forward_equals(newfwd, options->local_forwards + i))
+			return;
+	}
 	options->local_forwards = xreallocarray(options->local_forwards,
 	    options->num_local_forwards + 1,
 	    sizeof(*options->local_forwards));
@@ -322,7 +329,13 @@
 add_remote_forward(Options *options, const struct Forward *newfwd)
 {
 	struct Forward *fwd;
+	int i;
 
+	/* Don't add duplicates */
+	for (i = 0; i < options->num_remote_forwards; i++) {
+		if (forward_equals(newfwd, options->remote_forwards + i))
+			return;
+	}
 	options->remote_forwards = xreallocarray(options->remote_forwards,
 	    options->num_remote_forwards + 1,
 	    sizeof(*options->remote_forwards));