http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKHostnameVerifier.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKHostnameVerifier.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKHostnameVerifier.java
new file mode 100644
index 0000000..740fef0
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKHostnameVerifier.java
@@ -0,0 +1,349 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.common;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.naming.InvalidNameException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.security.auth.x500.X500Principal;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * Note: copied from Apache httpclient with some modifications. We want host verification,
but depending
+ * on the httpclient jar caused unexplained performance regressions (even when the code was
not used).
+ */
+final class ZKHostnameVerifier implements HostnameVerifier {
+
+ /**
+ * Note: copied from Apache httpclient with some minor modifications. We want host verification,
but depending
+ * on the httpclient jar caused unexplained performance regressions (even when the code
was not used).
+ */
+ private static final class SubjectName {
+ static final int DNS = 2;
+ static final int IP = 7;
+
+ private final String value;
+ private final int type;
+
+ static SubjectName IP(final String value) {
+ return new SubjectName(value, IP);
+ }
+
+ static SubjectName DNS(final String value) {
+ return new SubjectName(value, DNS);
+ }
+
+ SubjectName(final String value, final int type) {
+ if (type != DNS && type != IP) {
+ throw new IllegalArgumentException("Invalid type: " + type);
+ }
+ this.value = Objects.requireNonNull(value);
+ this.type = type;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+ }
+
+ /**
+ * Note: copied from Apache httpclient. We want host verification, but depending on the
+ * httpclient jar caused unexplained performance regressions (even when the code was
not used).
+ */
+ private static class InetAddressUtils {
+ private InetAddressUtils() {}
+
+ private static final Pattern IPV4_PATTERN = Pattern.compile(
+ "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$");
+
+ private static final Pattern IPV6_STD_PATTERN = Pattern.compile(
+ "^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$");
+
+ private static final Pattern IPV6_HEX_COMPRESSED_PATTERN = Pattern.compile(
+ "^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$");
+
+ static boolean isIPv4Address(final String input) {
+ return IPV4_PATTERN.matcher(input).matches();
+ }
+
+ static boolean isIPv6StdAddress(final String input) {
+ return IPV6_STD_PATTERN.matcher(input).matches();
+ }
+
+ static boolean isIPv6HexCompressedAddress(final String input) {
+ return IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches();
+ }
+
+ static boolean isIPv6Address(final String input) {
+ return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input);
+ }
+ }
+
+ enum HostNameType {
+
+ IPv4(7), IPv6(7), DNS(2);
+
+ final int subjectType;
+
+ HostNameType(final int subjectType) {
+ this.subjectType = subjectType;
+ }
+
+ }
+
+ private final Logger log = LoggerFactory.getLogger(ZKHostnameVerifier.class);
+
+ @Override
+ public boolean verify(final String host, final SSLSession session) {
+ try {
+ final Certificate[] certs = session.getPeerCertificates();
+ final X509Certificate x509 = (X509Certificate) certs[0];
+ verify(host, x509);
+ return true;
+ } catch (final SSLException ex) {
+ if (log.isDebugEnabled()) {
+ log.debug(ex.getMessage(), ex);
+ }
+ return false;
+ }
+ }
+
+ void verify(final String host, final X509Certificate cert) throws SSLException {
+ final HostNameType hostType = determineHostFormat(host);
+ final List<SubjectName> subjectAlts = getSubjectAltNames(cert);
+ if (subjectAlts != null && !subjectAlts.isEmpty()) {
+ switch (hostType) {
+ case IPv4:
+ matchIPAddress(host, subjectAlts);
+ break;
+ case IPv6:
+ matchIPv6Address(host, subjectAlts);
+ break;
+ default:
+ matchDNSName(host, subjectAlts);
+ }
+ } else {
+ // CN matching has been deprecated by rfc2818 and can be used
+ // as fallback only when no subjectAlts are available
+ final X500Principal subjectPrincipal = cert.getSubjectX500Principal();
+ final String cn = extractCN(subjectPrincipal.getName(X500Principal.RFC2253));
+ if (cn == null) {
+ throw new SSLException("Certificate subject for <" + host + "> doesn't
contain " +
+ "a common name and does not have alternative names");
+ }
+ matchCN(host, cn);
+ }
+ }
+
+ private static void matchIPAddress(final String host, final List<SubjectName> subjectAlts)
throws SSLException {
+ for (int i = 0; i < subjectAlts.size(); i++) {
+ final SubjectName subjectAlt = subjectAlts.get(i);
+ if (subjectAlt.getType() == SubjectName.IP) {
+ if (host.equals(subjectAlt.getValue())) {
+ return;
+ }
+ }
+ }
+ throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't
match any " +
+ "of the subject alternative names: " + subjectAlts);
+ }
+
+ private static void matchIPv6Address(final String host, final List<SubjectName>
subjectAlts) throws SSLException {
+ final String normalisedHost = normaliseAddress(host);
+ for (int i = 0; i < subjectAlts.size(); i++) {
+ final SubjectName subjectAlt = subjectAlts.get(i);
+ if (subjectAlt.getType() == SubjectName.IP) {
+ final String normalizedSubjectAlt = normaliseAddress(subjectAlt.getValue());
+ if (normalisedHost.equals(normalizedSubjectAlt)) {
+ return;
+ }
+ }
+ }
+ throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't
match any " +
+ "of the subject alternative names: " + subjectAlts);
+ }
+
+ private static void matchDNSName(final String host, final List<SubjectName> subjectAlts)
throws SSLException {
+ final String normalizedHost = host.toLowerCase(Locale.ROOT);
+ for (int i = 0; i < subjectAlts.size(); i++) {
+ final SubjectName subjectAlt = subjectAlts.get(i);
+ if (subjectAlt.getType() == SubjectName.DNS) {
+ final String normalizedSubjectAlt = subjectAlt.getValue().toLowerCase(Locale.ROOT);
+ if (matchIdentityStrict(normalizedHost, normalizedSubjectAlt)) {
+ return;
+ }
+ }
+ }
+ throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't
match any " +
+ "of the subject alternative names: " + subjectAlts);
+ }
+
+ private static void matchCN(final String host, final String cn) throws SSLException {
+ final String normalizedHost = host.toLowerCase(Locale.ROOT);
+ final String normalizedCn = cn.toLowerCase(Locale.ROOT);
+ if (!matchIdentityStrict(normalizedHost, normalizedCn)) {
+ throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't
match " +
+ "common name of the certificate subject: " + cn);
+ }
+ }
+
+ private static boolean matchIdentity(final String host, final String identity,
+ final boolean strict) {
+ // RFC 2818, 3.1. Server Identity
+ // "...Names may contain the wildcard
+ // character * which is considered to match any single domain name
+ // component or component fragment..."
+ // Based on this statement presuming only singular wildcard is legal
+ final int asteriskIdx = identity.indexOf('*');
+ if (asteriskIdx != -1) {
+ final String prefix = identity.substring(0, asteriskIdx);
+ final String suffix = identity.substring(asteriskIdx + 1);
+ if (!prefix.isEmpty() && !host.startsWith(prefix)) {
+ return false;
+ }
+ if (!suffix.isEmpty() && !host.endsWith(suffix)) {
+ return false;
+ }
+ // Additional sanity checks on content selected by wildcard can be done here
+ if (strict) {
+ final String remainder = host.substring(
+ prefix.length(), host.length() - suffix.length());
+ if (remainder.contains(".")) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return host.equalsIgnoreCase(identity);
+ }
+
+ private static boolean matchIdentityStrict(final String host, final String identity)
{
+ return matchIdentity(host, identity, true);
+ }
+
+ private static String extractCN(final String subjectPrincipal) throws SSLException {
+ if (subjectPrincipal == null) {
+ return null;
+ }
+ try {
+ final LdapName subjectDN = new LdapName(subjectPrincipal);
+ final List<Rdn> rdns = subjectDN.getRdns();
+ for (int i = rdns.size() - 1; i >= 0; i--) {
+ final Rdn rds = rdns.get(i);
+ final Attributes attributes = rds.toAttributes();
+ final Attribute cn = attributes.get("cn");
+ if (cn != null) {
+ try {
+ final Object value = cn.get();
+ if (value != null) {
+ return value.toString();
+ }
+ } catch (final NoSuchElementException ignore) {
+ // ignore exception
+ } catch (final NamingException ignore) {
+ // ignore exception
+ }
+ }
+ }
+ return null;
+ } catch (final InvalidNameException e) {
+ throw new SSLException(subjectPrincipal + " is not a valid X500 distinguished
name");
+ }
+ }
+
+ private static HostNameType determineHostFormat(final String host) {
+ if (InetAddressUtils.isIPv4Address(host)) {
+ return HostNameType.IPv4;
+ }
+ String s = host;
+ if (s.startsWith("[") && s.endsWith("]")) {
+ s = host.substring(1, host.length() - 1);
+ }
+ if (InetAddressUtils.isIPv6Address(s)) {
+ return HostNameType.IPv6;
+ }
+ return HostNameType.DNS;
+ }
+
+ private static List<SubjectName> getSubjectAltNames(final X509Certificate cert)
{
+ try {
+ final Collection<List<?>> entries = cert.getSubjectAlternativeNames();
+ if (entries == null) {
+ return Collections.emptyList();
+ }
+ final List<SubjectName> result = new ArrayList<SubjectName>();
+ for (List<?> entry: entries) {
+ final Integer type = entry.size() >= 2 ? (Integer) entry.get(0) : null;
+ if (type != null) {
+ final String s = (String) entry.get(1);
+ result.add(new SubjectName(s, type));
+ }
+ }
+ return result;
+ } catch (final CertificateParsingException ignore) {
+ return Collections.emptyList();
+ }
+ }
+
+ /*
+ * Normalize IPv6 or DNS name.
+ */
+ private static String normaliseAddress(final String hostname) {
+ if (hostname == null) {
+ return hostname;
+ }
+ try {
+ final InetAddress inetAddress = InetAddress.getByName(hostname);
+ return inetAddress.getHostAddress();
+ } catch (final UnknownHostException unexpected) { // Should not happen, because we
check for IPv6 address above
+ return hostname;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKTrustManager.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKTrustManager.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKTrustManager.java
new file mode 100644
index 0000000..73006d0
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKTrustManager.java
@@ -0,0 +1,150 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.zookeeper.common;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.X509ExtendedTrustManager;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+/**
+ * A custom TrustManager that supports hostname verification via org.apache.http.conn.ssl.DefaultHostnameVerifier.
+ *
+ * We attempt to perform verification using just the IP address first and if that fails will
attempt to perform a
+ * reverse DNS lookup and verify using the hostname.
+ */
+public class ZKTrustManager extends X509ExtendedTrustManager {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ZKTrustManager.class);
+
+ private X509ExtendedTrustManager x509ExtendedTrustManager;
+ private boolean serverHostnameVerificationEnabled;
+ private boolean clientHostnameVerificationEnabled;
+
+ private ZKHostnameVerifier hostnameVerifier;
+
+ /**
+ * Instantiate a new ZKTrustManager.
+ *
+ * @param x509ExtendedTrustManager The trustmanager to use for checkClientTrusted/checkServerTrusted
logic
+ * @param serverHostnameVerificationEnabled If true, this TrustManager should verify
hostnames of servers that this
+ * instance connects to.
+ * @param clientHostnameVerificationEnabled If true, the hostname of a client connecting
to this machine will be
+ * verified.
+ */
+ ZKTrustManager(X509ExtendedTrustManager x509ExtendedTrustManager, boolean serverHostnameVerificationEnabled,
+ boolean clientHostnameVerificationEnabled) {
+ this.x509ExtendedTrustManager = x509ExtendedTrustManager;
+ this.serverHostnameVerificationEnabled = serverHostnameVerificationEnabled;
+ this.clientHostnameVerificationEnabled = clientHostnameVerificationEnabled;
+ hostnameVerifier = new ZKHostnameVerifier();
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return x509ExtendedTrustManager.getAcceptedIssuers();
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
throws CertificateException {
+ x509ExtendedTrustManager.checkClientTrusted(chain, authType, socket);
+ if (clientHostnameVerificationEnabled) {
+ performHostVerification(socket.getInetAddress(), chain[0]);
+ }
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
throws CertificateException {
+ x509ExtendedTrustManager.checkServerTrusted(chain, authType, socket);
+ if (serverHostnameVerificationEnabled) {
+ performHostVerification(socket.getInetAddress(), chain[0]);
+ }
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
throws CertificateException {
+ x509ExtendedTrustManager.checkServerTrusted(chain, authType, engine);
+ if (clientHostnameVerificationEnabled) {
+ try {
+ performHostVerification(InetAddress.getByName(engine.getPeerHost()), chain[0]);
+ } catch (UnknownHostException e) {
+ throw new CertificateException("Failed to verify host", e);
+ }
+ }
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
+ throws CertificateException {
+ x509ExtendedTrustManager.checkServerTrusted(chain, authType, engine);
+ if (serverHostnameVerificationEnabled) {
+ try {
+ performHostVerification(InetAddress.getByName(engine.getPeerHost()), chain[0]);
+ } catch (UnknownHostException e) {
+ throw new CertificateException("Failed to verify host", e);
+ }
+ }
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
+ x509ExtendedTrustManager.checkClientTrusted(chain, authType);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
+ x509ExtendedTrustManager.checkServerTrusted(chain, authType);
+ }
+
+ /**
+ * Compares peer's hostname with the one stored in the provided client certificate. Performs
verification
+ * with the help of provided HostnameVerifier.
+ *
+ * @param inetAddress Peer's inet address.
+ * @param certificate Peer's certificate
+ * @throws CertificateException Thrown if the provided certificate doesn't match the
peer hostname.
+ */
+ private void performHostVerification(InetAddress inetAddress, X509Certificate certificate)
+ throws CertificateException {
+ String hostAddress = "";
+ String hostName = "";
+ try {
+ hostAddress = inetAddress.getHostAddress();
+ hostnameVerifier.verify(hostAddress, certificate);
+ } catch (SSLException addressVerificationException) {
+ try {
+ LOG.debug("Failed to verify host address: {} attempting to verify host name
with reverse dns lookup",
+ hostAddress, addressVerificationException);
+ hostName = inetAddress.getHostName();
+ hostnameVerifier.verify(hostName, certificate);
+ } catch (SSLException hostnameVerificationException) {
+ LOG.error("Failed to verify host address: {}", hostAddress, addressVerificationException);
+ LOG.error("Failed to verify hostname: {}", hostName, hostnameVerificationException);
+ throw new CertificateException("Failed to verify both host address and host
name",
+ hostnameVerificationException);
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/util/SecurityUtils.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/util/SecurityUtils.java b/zookeeper-server/src/main/java/org/apache/zookeeper/util/SecurityUtils.java
new file mode 100644
index 0000000..67484e4
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/util/SecurityUtils.java
@@ -0,0 +1,298 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.util;
+
+import java.security.Principal;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+
+import org.apache.zookeeper.SaslClientCallbackHandler;
+import org.apache.zookeeper.server.auth.KerberosName;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+import org.slf4j.Logger;
+
+public final class SecurityUtils {
+
+ public static final String QUORUM_HOSTNAME_PATTERN = "_HOST";
+
+ /**
+ * Create an instance of a SaslClient. It will return null if there is an exception.
+ *
+ * @param subject subject
+ * @param servicePrincipal principal
+ * @param protocol name of the protocol for which the authentication is being performed
+ * @param serverName name of the server to authenticate to
+ * @param LOG logger
+ * @param entity can be either zookeeper client or quorum learner
+ *
+ * @return saslclient object
+ * @throws SaslException
+ */
+ public static SaslClient createSaslClient(final Subject subject,
+ final String servicePrincipal, final String protocol,
+ final String serverName, final Logger LOG, final String entity) throws SaslException
{
+ SaslClient saslClient;
+ // Use subject.getPrincipals().isEmpty() as an indication of which SASL
+ // mechanism to use: if empty, use DIGEST-MD5; otherwise, use GSSAPI.
+ if (subject.getPrincipals().isEmpty()) {
+ // no principals: must not be GSSAPI: use DIGEST-MD5 mechanism
+ // instead.
+ LOG.info("{} will use DIGEST-MD5 as SASL mechanism.", entity);
+ String[] mechs = { "DIGEST-MD5" };
+ String username = (String) (subject.getPublicCredentials()
+ .toArray()[0]);
+ String password = (String) (subject.getPrivateCredentials()
+ .toArray()[0]);
+ // 'domain' parameter is hard-wired between the server and client
+ saslClient = Sasl.createSaslClient(mechs, username, protocol,
+ serverName, null, new SaslClientCallbackHandler(password, entity));
+ return saslClient;
+ } else { // GSSAPI.
+ final Object[] principals = subject.getPrincipals().toArray();
+ // determine client principal from subject.
+ final Principal clientPrincipal = (Principal) principals[0];
+ boolean usingNativeJgss = Boolean
+ .getBoolean("sun.security.jgss.native");
+ if (usingNativeJgss) {
+ // http://docs.oracle.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html
+ // """
+ // In addition, when performing operations as a particular
+ // Subject, e.g. Subject.doAs(...) or
+ // Subject.doAsPrivileged(...),
+ // the to-be-used GSSCredential should be added to Subject's
+ // private credential set. Otherwise, the GSS operations will
+ // fail since no credential is found.
+ // """
+ try {
+ GSSManager manager = GSSManager.getInstance();
+ Oid krb5Mechanism = new Oid("1.2.840.113554.1.2.2");
+ GSSCredential cred = manager.createCredential(null,
+ GSSContext.DEFAULT_LIFETIME, krb5Mechanism,
+ GSSCredential.INITIATE_ONLY);
+ subject.getPrivateCredentials().add(cred);
+ LOG.debug("Added private credential to {} principal name: '{}'",
+ entity, clientPrincipal);
+ } catch (GSSException ex) {
+ LOG.warn("Cannot add private credential to subject; "
+ + "authentication at the server may fail", ex);
+ }
+ }
+ final KerberosName clientKerberosName = new KerberosName(
+ clientPrincipal.getName());
+ // assume that server and client are in the same realm (by default;
+ // unless the system property
+ // "zookeeper.server.realm" is set).
+ String serverRealm = System.getProperty("zookeeper.server.realm",
+ clientKerberosName.getRealm());
+ KerberosName serviceKerberosName = new KerberosName(
+ servicePrincipal + "@" + serverRealm);
+ final String serviceName = serviceKerberosName.getServiceName();
+ final String serviceHostname = serviceKerberosName.getHostName();
+ final String clientPrincipalName = clientKerberosName.toString();
+ try {
+ saslClient = Subject.doAs(subject,
+ new PrivilegedExceptionAction<SaslClient>() {
+ public SaslClient run() throws SaslException {
+ LOG.info("{} will use GSSAPI as SASL mechanism.", entity);
+ String[] mechs = { "GSSAPI" };
+ LOG.debug("creating sasl client: {}={};service={};serviceHostname={}",
+ new Object[] { entity, clientPrincipalName, serviceName,
serviceHostname });
+ SaslClient saslClient = Sasl.createSaslClient(
+ mechs, clientPrincipalName, serviceName,
+ serviceHostname, null,
+ new SaslClientCallbackHandler(null, entity));
+ return saslClient;
+ }
+ });
+ return saslClient;
+ } catch (Exception e) {
+ LOG.error("Exception while trying to create SASL client", e);
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Create an instance of a SaslServer. It will return null if there is an exception.
+ *
+ * @param subject subject
+ * @param protocol protocol
+ * @param serverName server name
+ * @param callbackHandler login callback handler
+ * @param LOG logger
+ * @return sasl server object
+ */
+ public static SaslServer createSaslServer(final Subject subject,
+ final String protocol, final String serverName,
+ final CallbackHandler callbackHandler, final Logger LOG) {
+ if (subject != null) {
+ // server is using a JAAS-authenticated subject: determine service
+ // principal name and hostname from zk server's subject.
+ if (subject.getPrincipals().size() > 0) {
+ try {
+ final Object[] principals = subject.getPrincipals()
+ .toArray();
+ final Principal servicePrincipal = (Principal) principals[0];
+
+ // e.g. servicePrincipalNameAndHostname :=
+ // "zookeeper/myhost.foo.com@FOO.COM"
+ final String servicePrincipalNameAndHostname = servicePrincipal
+ .getName();
+
+ int indexOf = servicePrincipalNameAndHostname.indexOf("/");
+
+ // e.g. servicePrincipalName := "zookeeper"
+ final String servicePrincipalName = servicePrincipalNameAndHostname
+ .substring(0, indexOf);
+
+ // e.g. serviceHostnameAndKerbDomain :=
+ // "myhost.foo.com@FOO.COM"
+ final String serviceHostnameAndKerbDomain = servicePrincipalNameAndHostname
+ .substring(indexOf + 1,
+ servicePrincipalNameAndHostname.length());
+
+ indexOf = serviceHostnameAndKerbDomain.indexOf("@");
+ // e.g. serviceHostname := "myhost.foo.com"
+ final String serviceHostname = serviceHostnameAndKerbDomain
+ .substring(0, indexOf);
+
+ // TODO: should depend on zoo.cfg specified mechs, but if
+ // subject is non-null, it can be assumed to be GSSAPI.
+ final String mech = "GSSAPI";
+
+ LOG.debug("serviceHostname is '" + serviceHostname + "'");
+ LOG.debug("servicePrincipalName is '" + servicePrincipalName
+ + "'");
+ LOG.debug("SASL mechanism(mech) is '" + mech + "'");
+
+ boolean usingNativeJgss = Boolean
+ .getBoolean("sun.security.jgss.native");
+ if (usingNativeJgss) {
+ // http://docs.oracle.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html
+ // """
+ // In addition, when performing operations as a
+ // particular
+ // Subject, e.g. Subject.doAs(...) or
+ // Subject.doAsPrivileged(...), the to-be-used
+ // GSSCredential should be added to Subject's
+ // private credential set. Otherwise, the GSS operations
+ // will fail since no credential is found.
+ // """
+ try {
+ GSSManager manager = GSSManager.getInstance();
+ Oid krb5Mechanism = new Oid("1.2.840.113554.1.2.2");
+ GSSName gssName = manager.createName(
+ servicePrincipalName + "@"
+ + serviceHostname,
+ GSSName.NT_HOSTBASED_SERVICE);
+ GSSCredential cred = manager.createCredential(
+ gssName, GSSContext.DEFAULT_LIFETIME,
+ krb5Mechanism, GSSCredential.ACCEPT_ONLY);
+ subject.getPrivateCredentials().add(cred);
+ LOG.debug("Added private credential to service principal name:
'{}',"
+ + " GSSCredential name: {}", servicePrincipalName,
cred.getName());
+ } catch (GSSException ex) {
+ LOG.warn("Cannot add private credential to subject; "
+ + "clients authentication may fail", ex);
+ }
+ }
+ try {
+ return Subject.doAs(subject,
+ new PrivilegedExceptionAction<SaslServer>() {
+ public SaslServer run() {
+ try {
+ SaslServer saslServer;
+ saslServer = Sasl.createSaslServer(
+ mech, servicePrincipalName,
+ serviceHostname, null,
+ callbackHandler);
+ return saslServer;
+ } catch (SaslException e) {
+ LOG.error("Zookeeper Server failed to create
a SaslServer to interact with a client during session initiation: ", e);
+ return null;
+ }
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ // TODO: exit server at this point(?)
+ LOG.error("Zookeeper Quorum member experienced a PrivilegedActionException
exception while creating a SaslServer using a JAAS principal context:", e);
+ }
+ } catch (IndexOutOfBoundsException e) {
+ LOG.error("server principal name/hostname determination error: ", e);
+ }
+ } else {
+ // JAAS non-GSSAPI authentication: assuming and supporting only
+ // DIGEST-MD5 mechanism for now.
+ // TODO: use 'authMech=' value in zoo.cfg.
+ try {
+ SaslServer saslServer = Sasl.createSaslServer("DIGEST-MD5",
+ protocol, serverName, null, callbackHandler);
+ return saslServer;
+ } catch (SaslException e) {
+ LOG.error("Zookeeper Quorum member failed to create a SaslServer to interact
with a client during session initiation", e);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Convert Kerberos principal name pattern to valid Kerberos principal name.
+ * If the principal name contains hostname pattern "_HOST" then it replaces
+ * with the given hostname, which should be fully-qualified domain name.
+ *
+ * @param principalConfig
+ * the Kerberos principal name conf value to convert
+ * @param hostname
+ * the fully-qualified domain name used for substitution
+ * @return converted Kerberos principal name
+ */
+ public static String getServerPrincipal(String principalConfig,
+ String hostname) {
+ String[] components = getComponents(principalConfig);
+ if (components == null || components.length != 2
+ || !components[1].equals(QUORUM_HOSTNAME_PATTERN)) {
+ return principalConfig;
+ } else {
+ return replacePattern(components, hostname);
+ }
+ }
+
+ private static String[] getComponents(String principalConfig) {
+ if (principalConfig == null)
+ return null;
+ return principalConfig.split("[/]");
+ }
+
+ private static String replacePattern(String[] components, String hostname) {
+ return components[0] + "/" + hostname.toLowerCase();
+ }
+}
http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/version/util/VerGen.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/version/util/VerGen.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/version/util/VerGen.java
new file mode 100644
index 0000000..014f01d
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/version/util/VerGen.java
@@ -0,0 +1,167 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.version.util;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class VerGen {
+ private static final String PACKAGE_NAME = "org.apache.zookeeper.version";
+ private static final String TYPE_NAME = "Info";
+
+ static void printUsage() {
+ System.out.print("Usage:\tjava -cp <classpath> org.apache.zookeeper."
+ + "version.util.VerGen maj.min.micro[-qualifier] rev buildDate");
+ System.exit(1);
+ }
+
+ public static void generateFile(File outputDir, Version version, String rev, String buildDate)
+ {
+ String path = PACKAGE_NAME.replaceAll("\\.", "/");
+ File pkgdir = new File(outputDir, path);
+ if (!pkgdir.exists()) {
+ // create the pkg directory
+ boolean ret = pkgdir.mkdirs();
+ if (!ret) {
+ System.out.println("Cannnot create directory: " + path);
+ System.exit(1);
+ }
+ } else if (!pkgdir.isDirectory()) {
+ // not a directory
+ System.out.println(path + " is not a directory.");
+ System.exit(1);
+ }
+
+ try (FileWriter w = new FileWriter(new File(pkgdir, TYPE_NAME + ".java"))) {
+ w.write("// Do not edit!\n// File generated by org.apache.zookeeper"
+ + ".version.util.VerGen.\n");
+ w.write("/**\n");
+ w.write("* Licensed to the Apache Software Foundation (ASF) under one\n");
+ w.write("* or more contributor license agreements. See the NOTICE file\n");
+ w.write("* distributed with this work for additional information\n");
+ w.write("* regarding copyright ownership. The ASF licenses this file\n");
+ w.write("* to you under the Apache License, Version 2.0 (the\n");
+ w.write("* \"License\"); you may not use this file except in compliance\n");
+ w.write("* with the License. You may obtain a copy of the License at\n");
+ w.write("*\n");
+ w.write("* http://www.apache.org/licenses/LICENSE-2.0\n");
+ w.write("*\n");
+ w.write("* Unless required by applicable law or agreed to in writing, software\n");
+ w.write("* distributed under the License is distributed on an \"AS IS\" BASIS,\n");
+ w.write("* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n");
+ w.write("* See the License for the specific language governing permissions and\n");
+ w.write("* limitations under the License.\n");
+ w.write("*/\n");
+ w.write("\n");
+ w.write("package " + PACKAGE_NAME + ";\n\n");
+ w.write("public interface " + TYPE_NAME + " {\n");
+ w.write(" int MAJOR=" + version.maj + ";\n");
+ w.write(" int MINOR=" + version.min + ";\n");
+ w.write(" int MICRO=" + version.micro + ";\n");
+ w.write(" String QUALIFIER="
+ + (version.qualifier == null ? null :
+ "\"" + version.qualifier + "\"")
+ + ";\n");
+ if (rev.equals("-1")) {
+ System.out.println("Unknown REVISION number, using " + rev);
+ }
+ w.write(" int REVISION=-1; //TODO: remove as related to SVN VCS\n");
+ w.write(" String REVISION_HASH=\"" + rev + "\";\n");
+ w.write(" String BUILD_DATE=\"" + buildDate
+ + "\";\n");
+ w.write("}\n");
+ } catch (IOException e) {
+ System.out.println("Unable to generate version.Info file: "
+ + e.getMessage());
+ System.exit(1);
+ }
+ }
+
+ public static class Version {
+ public int maj;
+ public int min;
+ public int micro;
+ public String qualifier;
+ }
+
+ public static Version parseVersionString(String input) {
+ Version result = new Version();
+
+ Pattern p = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)((\\.\\d+)*)(-(.+))?$");
+ Matcher m = p.matcher(input);
+
+ if (!m.matches()) {
+ return null;
+ }
+ result.maj = Integer.parseInt(m.group(1));
+ result.min = Integer.parseInt(m.group(2));
+ result.micro = Integer.parseInt(m.group(3));
+ if (m.groupCount() == 7) {
+ result.qualifier = m.group(7);
+ } else {
+ result.qualifier = null;
+ }
+ return result;
+ }
+
+ /**
+ * Emits a org.apache.zookeeper.version.Info interface file with version and
+ * revision information constants set to the values passed in as command
+ * line parameters. The file is created in the current directory. <br>
+ * Usage: java org.apache.zookeeper.version.util.VerGen maj.min.micro[-qualifier]
+ * rev buildDate
+ *
+ * @param args
+ * <ul>
+ * <li>maj - major version number
+ * <li>min - minor version number
+ * <li>micro - minor minor version number
+ * <li>qualifier - optional qualifier (dash followed by qualifier text)
+ * <li>rev - current Git revision number
+ * <li>buildDate - date the build
+ * </ul>
+ */
+ public static void main(String[] args) {
+ if (args.length != 3)
+ printUsage();
+ try {
+ Version version = parseVersionString(args[0]);
+ if (version == null) {
+ System.err.println(
+ "Invalid version number format, must be \"x.y.z(-.*)?\"");
+ System.exit(1);
+ }
+ String rev = args[1];
+ if (rev == null || rev.trim().isEmpty()) {
+ rev = "-1";
+ } else {
+ rev = rev.trim();
+ }
+ generateFile(new File("."), version, rev, args[2]);
+ } catch (NumberFormatException e) {
+ System.err.println(
+ "All version-related parameters must be valid integers!");
+ throw e;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/resources/lib/cobertura/README.txt
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/resources/lib/cobertura/README.txt b/zookeeper-server/src/main/resources/lib/cobertura/README.txt
new file mode 100644
index 0000000..f5ba88f
--- /dev/null
+++ b/zookeeper-server/src/main/resources/lib/cobertura/README.txt
@@ -0,0 +1,3 @@
+Download the cobertura binary from the following location and unpack it into this directory.
Run "cobertura-report" target from build.xml to generate coverage report.
+
+http://cobertura.sourceforge.net/download.html
|