MINOR: ssl: Add statement 'verifyhost' to "server" statements

verifyhost allows you to specify a hostname that the remote server's
SSL certificate must match. Connections that don't match will be
closed with an SSL error.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 4bb2572..37d16cb 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -8100,6 +8100,16 @@
 
   Supported in default-server: No
 
+verifyhost <hostname>
+  This setting is only available when support for OpenSSL was built in, and
+  only takes effect if 'verify required' is also specified. When set, the
+  hostnames in the subject and subjectAlternateNames of the certificate
+  provided by the server are checked. If none of the hostnames in the
+  certificate match the specified hostname, the handshake is aborted. The
+  hostnames in the server-provided certificate may include wildcards.
+
+  Supported in default-server: No
+
 weight <weight>
   The "weight" parameter is used to adjust the server's weight relative to
   other servers. All servers will receive a load proportional to their weight
diff --git a/include/types/server.h b/include/types/server.h
index e70ad8f..0d50575 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -187,6 +187,7 @@
 		char *ciphers;			/* cipher suite to use if non-null */
 		int options;			/* ssl options */
 		int verify;			/* verify method (set of SSL_VERIFY_* flags) */
+		char *verify_host;              /* hostname of certificate must match this host */
 		char *ca_file;			/* CAfile to use on verify */
 		char *crl_file;			/* CRLfile to use on verify */
 		char *client_crt;		/* client certificate to send */
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 8169d78..a55a5bf 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -104,7 +104,7 @@
 /* Callback is called for each certificate of the chain during a verify
    ok is set to 1 if preverify detect no error on current certificate.
    Returns 0 to break the handshake, 1 otherwise. */
-int ssl_sock_verifycbk(int ok, X509_STORE_CTX *x_store)
+int ssl_sock_bind_verifycbk(int ok, X509_STORE_CTX *x_store)
 {
 	SSL *ssl;
 	struct connection *conn;
@@ -693,7 +693,7 @@
 
 	SSL_CTX_set_options(ctx, ssloptions);
 	SSL_CTX_set_mode(ctx, sslmode);
-	SSL_CTX_set_verify(ctx, bind_conf->verify ? bind_conf->verify : SSL_VERIFY_NONE, ssl_sock_verifycbk);
+	SSL_CTX_set_verify(ctx, bind_conf->verify ? bind_conf->verify : SSL_VERIFY_NONE, ssl_sock_bind_verifycbk);
 	if (bind_conf->verify & SSL_VERIFY_PEER) {
 		if (bind_conf->ca_file) {
 			/* load CAfile to verify */
@@ -769,6 +769,113 @@
 	return cfgerr;
 }
 
+static int ssl_sock_srv_hostcheck(const char *pattern, const char *hostname)
+{
+	const char *pattern_wildcard, *pattern_left_label_end, *hostname_left_label_end;
+	size_t prefixlen, suffixlen;
+
+	/* Trivial case */
+	if (strcmp(pattern, hostname) == 0)
+		return 1;
+
+	/* If it's not trivial and there are no wildcards, it can't
+	 * match */
+	if (!(pattern_wildcard = strchr(pattern, '*')))
+		return 0;
+
+	/* The rest of this logic is based on RFC 6125, section 6.4.3
+	 * (http://tools.ietf.org/html/rfc6125#section-6.4.3) */
+
+	/* Make sure the wildcard occurs in the leftmost label */
+	pattern_left_label_end = strchr(pattern, '.');
+	if (!pattern_left_label_end
+	    || pattern_left_label_end < pattern_wildcard)
+		return 0;
+
+	/* Make sure all labels match except the leftmost */
+	hostname_left_label_end = strchr(hostname, '.');
+	if (!hostname_left_label_end
+	    || strcmp(pattern_left_label_end, hostname_left_label_end) != 0)
+		return 0;
+
+	/* Make sure the leftmost label of the hostname is long enough
+	 * that the wildcard can match */
+	if (hostname_left_label_end - hostname < pattern_left_label_end - pattern)
+		return 0;
+
+	/* Finally compare the string on either side of the
+	 * wildcard */
+	prefixlen = pattern_wildcard - pattern;
+	suffixlen = pattern_left_label_end - (pattern_wildcard + 1);
+	if (strncmp(pattern, hostname, prefixlen) != 0
+	    || strncmp(pattern_wildcard + 1, hostname_left_label_end - suffixlen, suffixlen) != 0)
+		return 0;
+
+	return 1;
+}
+
+static int ssl_sock_srv_verifycbk(int ok, X509_STORE_CTX *ctx)
+{
+	SSL *ssl;
+	struct connection *conn;
+	char *servername;
+
+	int depth;
+	X509 *cert;
+	STACK_OF(GENERAL_NAME) *alt_names;
+	int i;
+	X509_NAME *cert_subject;
+	char *str;
+
+	if (ok == 0)
+		return ok;
+
+	ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+	conn = (struct connection *)SSL_get_app_data(ssl);
+
+	servername = objt_server(conn->target)->ssl_ctx.verify_host;
+
+	/* We only need to verify the CN on the actual server cert,
+	 * not the indirect CAs */
+	depth = X509_STORE_CTX_get_error_depth(ctx);
+	if (depth != 0)
+		return ok;
+
+	/* At this point, the cert is *not* OK unless we can find a
+	 * hostname match */
+	ok = 0;
+
+	cert = X509_STORE_CTX_get_current_cert(ctx);
+	/* It seems like this might happen if verify peer isn't set */
+	if (!cert)
+		return ok;
+
+	alt_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
+	if (alt_names) {
+		for (i = 0; !ok && i < sk_GENERAL_NAME_num(alt_names); i++) {
+			GENERAL_NAME *name = sk_GENERAL_NAME_value(alt_names, i);
+			if (name->type == GEN_DNS) {
+				if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
+					ok = ssl_sock_srv_hostcheck(str, servername);
+					OPENSSL_free(str);
+				}
+			}
+		}
+	}
+
+	cert_subject = X509_get_subject_name(cert);
+	i = -1;
+	while (!ok && (i = X509_NAME_get_index_by_NID(cert_subject, NID_commonName, i)) != -1) {
+		X509_NAME_ENTRY *entry = X509_NAME_get_entry(cert_subject, i);
+		if (ASN1_STRING_to_UTF8((unsigned char **)&str, entry->value) >= 0) {
+			ok = ssl_sock_srv_hostcheck(str, servername);
+			OPENSSL_free(str);
+		}
+	}
+
+	return ok;
+}
+
 /* prepare ssl context from servers options. Returns an error count */
 int ssl_sock_prepare_srv_ctx(struct server *srv, struct proxy *curproxy)
 {
@@ -849,7 +956,9 @@
 
 	SSL_CTX_set_options(srv->ssl_ctx.ctx, options);
 	SSL_CTX_set_mode(srv->ssl_ctx.ctx, mode);
-	SSL_CTX_set_verify(srv->ssl_ctx.ctx, srv->ssl_ctx.verify ? srv->ssl_ctx.verify : SSL_VERIFY_NONE, NULL);
+	SSL_CTX_set_verify(srv->ssl_ctx.ctx,
+	                   srv->ssl_ctx.verify ? srv->ssl_ctx.verify : SSL_VERIFY_NONE,
+	                   srv->ssl_ctx.verify_host ? ssl_sock_srv_verifycbk : NULL);
 	if (srv->ssl_ctx.verify & SSL_VERIFY_PEER) {
 		if (srv->ssl_ctx.ca_file) {
 			/* load CAfile to verify */
@@ -993,6 +1102,9 @@
 		/* set fd on SSL session context */
 		SSL_set_fd(conn->xprt_ctx, conn->t.sock.fd);
 
+		/* set connection pointer */
+		SSL_set_app_data(conn->xprt_ctx, conn);
+
 		/* leave init state and start handshake */
 		conn->flags |= CO_FL_SSL_WAIT_HS | CO_FL_WAIT_L6_CONN;
 
@@ -3074,6 +3186,20 @@
 		return ERR_ALERT | ERR_FATAL;
 	}
 
+	return 0;
+}
+
+/* parse the "verifyhost" server keyword */
+static int srv_parse_verifyhost(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
+{
+	if (!*args[*cur_arg + 1]) {
+		if (err)
+			memprintf(err, "'%s' : missing hostname to verify against", args[*cur_arg]);
+		return ERR_ALERT | ERR_FATAL;
+	}
+
+	newsrv->ssl_ctx.verify_host = strdup(args[*cur_arg + 1]);
+
 	return 0;
 }
 
@@ -3210,6 +3336,7 @@
 	{ "no-tls-tickets",        srv_parse_no_tls_tickets, 0, 0 }, /* disable session resumption tickets */
 	{ "ssl",                   srv_parse_ssl,            0, 0 }, /* enable SSL processing */
 	{ "verify",                srv_parse_verify,         1, 0 }, /* set SSL verify method */
+	{ "verifyhost",            srv_parse_verifyhost,     1, 0 }, /* require that SSL cert verifies for hostname */
 	{ NULL, NULL, 0, 0 },
 }};