| /** |
| * 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.client; |
| |
| import java.io.IOException; |
| import java.security.Principal; |
| import java.security.PrivilegedActionException; |
| import java.security.PrivilegedExceptionAction; |
| |
| import javax.security.auth.Subject; |
| import javax.security.auth.callback.Callback; |
| import javax.security.auth.callback.CallbackHandler; |
| import javax.security.auth.callback.NameCallback; |
| import javax.security.auth.callback.PasswordCallback; |
| import javax.security.auth.callback.UnsupportedCallbackException; |
| import javax.security.auth.login.AppConfigurationEntry; |
| import javax.security.auth.login.Configuration; |
| import javax.security.auth.login.LoginException; |
| import javax.security.sasl.AuthorizeCallback; |
| import javax.security.sasl.RealmCallback; |
| import javax.security.sasl.Sasl; |
| import javax.security.sasl.SaslClient; |
| import javax.security.sasl.SaslException; |
| |
| import org.apache.zookeeper.AsyncCallback; |
| import org.apache.zookeeper.ClientCnxn; |
| import org.apache.zookeeper.Login; |
| import org.apache.zookeeper.Watcher.Event.KeeperState; |
| import org.apache.zookeeper.ZooDefs; |
| import org.apache.zookeeper.common.ZKConfig; |
| import org.apache.zookeeper.data.Stat; |
| import org.apache.zookeeper.proto.GetSASLRequest; |
| import org.apache.zookeeper.proto.SetSASLResponse; |
| 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.Oid; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * This class manages SASL authentication for the client. It |
| * allows ClientCnxn to authenticate using SASL with a ZooKeeper server. |
| */ |
| public class ZooKeeperSaslClient { |
| /** |
| * @deprecated Use {@link ZKClientConfig#LOGIN_CONTEXT_NAME_KEY} |
| * instead. |
| */ |
| @Deprecated |
| public static final String LOGIN_CONTEXT_NAME_KEY = "zookeeper.sasl.clientconfig"; |
| /** |
| * @deprecated Use {@link ZKClientConfig#ENABLE_CLIENT_SASL_KEY} |
| * instead. |
| */ |
| @Deprecated |
| public static final String ENABLE_CLIENT_SASL_KEY = "zookeeper.sasl.client"; |
| /** |
| * @deprecated Use {@link ZKClientConfig#ENABLE_CLIENT_SASL_DEFAULT} |
| * instead. |
| */ |
| @Deprecated |
| public static final String ENABLE_CLIENT_SASL_DEFAULT = "true"; |
| private volatile boolean initializedLogin = false; |
| |
| /** |
| * Returns true if the SASL client is enabled. By default, the client |
| * is enabled but can be disabled by setting the system property |
| * <code>zookeeper.sasl.client</code> to <code>false</code>. See |
| * ZOOKEEPER-1657 for more information. |
| * |
| * @return true if the SASL client is enabled. |
| * @deprecated Use {@link ZKClientConfig#isSaslClientEnabled} instead |
| */ |
| @Deprecated |
| public static boolean isEnabled() { |
| return Boolean.valueOf(System.getProperty( |
| ZKClientConfig.ENABLE_CLIENT_SASL_KEY, |
| ZKClientConfig.ENABLE_CLIENT_SASL_DEFAULT)); |
| } |
| |
| private static final Logger LOG = LoggerFactory.getLogger(ZooKeeperSaslClient.class); |
| private Login login = null; |
| private SaslClient saslClient; |
| private boolean isSASLConfigured = true; |
| private final ZKClientConfig clientConfig; |
| |
| private byte[] saslToken = new byte[0]; |
| |
| public enum SaslState { |
| INITIAL,INTERMEDIATE,COMPLETE,FAILED |
| } |
| |
| private SaslState saslState = SaslState.INITIAL; |
| |
| private boolean gotLastPacket = false; |
| /** informational message indicating the current configuration status */ |
| private final String configStatus; |
| |
| public SaslState getSaslState() { |
| return saslState; |
| } |
| |
| public String getLoginContext() { |
| if (login != null) |
| return login.getLoginContextName(); |
| return null; |
| } |
| |
| public ZooKeeperSaslClient(final String serverPrincipal, ZKClientConfig clientConfig) throws LoginException { |
| /** |
| * ZOOKEEPER-1373: allow system property to specify the JAAS |
| * configuration section that the zookeeper client should use. |
| * Default to "Client". |
| */ |
| String clientSection = clientConfig.getProperty( |
| ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, |
| ZKClientConfig.LOGIN_CONTEXT_NAME_KEY_DEFAULT); |
| this.clientConfig = clientConfig; |
| // Note that 'Configuration' here refers to javax.security.auth.login.Configuration. |
| AppConfigurationEntry entries[] = null; |
| RuntimeException runtimeException = null; |
| try { |
| entries = Configuration.getConfiguration() |
| .getAppConfigurationEntry(clientSection); |
| } catch (SecurityException e) { |
| // handle below: might be harmless if the user doesn't intend to use JAAS authentication. |
| runtimeException = e; |
| } catch (IllegalArgumentException e) { |
| // third party customized getAppConfigurationEntry could throw IllegalArgumentException when JAAS |
| // configuration isn't set. We can reevaluate whether to catch RuntimeException instead when more |
| // different types of RuntimeException found |
| runtimeException = e; |
| } |
| if (entries != null) { |
| this.configStatus = "Will attempt to SASL-authenticate using Login Context section '" + clientSection + "'"; |
| this.saslClient = createSaslClient(serverPrincipal, clientSection); |
| } else { |
| // Handle situation of clientSection's being null: it might simply because the client does not intend to |
| // use SASL, so not necessarily an error. |
| saslState = SaslState.FAILED; |
| String explicitClientSection = clientConfig |
| .getProperty(ZKClientConfig.LOGIN_CONTEXT_NAME_KEY); |
| if (explicitClientSection != null) { |
| // If the user explicitly overrides the default Login Context, they probably expected SASL to |
| // succeed. But if we got here, SASL failed. |
| if (runtimeException != null) { |
| throw new LoginException( |
| "Zookeeper client cannot authenticate using the " |
| + explicitClientSection |
| + " section of the supplied JAAS configuration: '" |
| + clientConfig.getJaasConfKey() + "' because of a " |
| + "RuntimeException: " + runtimeException); |
| } else { |
| throw new LoginException("Client cannot SASL-authenticate because the specified JAAS configuration " + |
| "section '" + explicitClientSection + "' could not be found."); |
| } |
| } else { |
| // The user did not override the default context. It might be that they just don't intend to use SASL, |
| // so log at INFO, not WARN, since they don't expect any SASL-related information. |
| String msg = "Will not attempt to authenticate using SASL "; |
| if (runtimeException != null) { |
| msg += "(" + runtimeException + ")"; |
| } else { |
| msg += "(unknown error)"; |
| } |
| this.configStatus = msg; |
| this.isSASLConfigured = false; |
| } |
| if (clientConfig.getJaasConfKey() != null) { |
| // Again, the user explicitly set something SASL-related, so |
| // they probably expected SASL to succeed. |
| if (runtimeException != null) { |
| throw new LoginException( |
| "Zookeeper client cannot authenticate using the '" |
| + clientConfig.getProperty( |
| ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, |
| ZKClientConfig.LOGIN_CONTEXT_NAME_KEY_DEFAULT) |
| + "' section of the supplied JAAS configuration: '" |
| + clientConfig.getJaasConfKey() + "' because of a " |
| + "RuntimeException: " + runtimeException); |
| } else { |
| throw new LoginException( |
| "No JAAS configuration section named '" |
| + clientConfig.getProperty( |
| ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, |
| ZKClientConfig.LOGIN_CONTEXT_NAME_KEY_DEFAULT) |
| + "' was found in specified JAAS configuration file: '" |
| + clientConfig.getJaasConfKey() + "'."); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @return informational message indicating the current configuration status. |
| */ |
| public String getConfigStatus() { |
| return configStatus; |
| } |
| |
| public boolean isComplete() { |
| return (saslState == SaslState.COMPLETE); |
| } |
| |
| public boolean isFailed() { |
| return (saslState == SaslState.FAILED); |
| } |
| |
| public static class ServerSaslResponseCallback implements AsyncCallback.DataCallback { |
| public void processResult(int rc, String path, Object ctx, byte data[], Stat stat) { |
| // processResult() is used by ClientCnxn's sendThread to respond to |
| // data[] contains the Zookeeper Server's SASL token. |
| // ctx is the ZooKeeperSaslClient object. We use this object's respondToServer() method |
| // to reply to the Zookeeper Server's SASL token |
| ZooKeeperSaslClient client = ((ClientCnxn)ctx).zooKeeperSaslClient; |
| if (client == null) { |
| LOG.warn("sasl client was unexpectedly null: cannot respond to Zookeeper server."); |
| return; |
| } |
| byte[] usedata = data; |
| if (data != null) { |
| LOG.debug("ServerSaslResponseCallback(): saslToken server response: (length="+usedata.length+")"); |
| } |
| else { |
| usedata = new byte[0]; |
| LOG.debug("ServerSaslResponseCallback(): using empty data[] as server response (length="+usedata.length+")"); |
| } |
| client.respondToServer(usedata, (ClientCnxn)ctx); |
| } |
| } |
| |
| private SaslClient createSaslClient(final String servicePrincipal, final String loginContext) |
| throws LoginException { |
| try { |
| if (!initializedLogin) { |
| synchronized (this) { |
| if (login == null) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("JAAS loginContext is: " + loginContext); |
| } |
| // note that the login object is static: it's shared amongst all zookeeper-related connections. |
| // in order to ensure the login is initialized only once, it must be synchronized the code snippet. |
| login = new Login(loginContext, new ClientCallbackHandler(null), clientConfig); |
| login.startThreadIfNeeded(); |
| initializedLogin = true; |
| } |
| } |
| } |
| Subject subject = login.getSubject(); |
| 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("Client will use DIGEST-MD5 as SASL mechanism."); |
| String[] mechs = {"DIGEST-MD5"}; |
| String username = (String)(subject.getPublicCredentials().toArray()[0]); |
| String password = (String)(subject.getPrivateCredentials().toArray()[0]); |
| // "zk-sasl-md5" is a hard-wired 'domain' parameter shared with zookeeper server code (see ServerCnxnFactory.java) |
| saslClient = Sasl.createSaslClient(mechs, username, "zookeeper", "zk-sasl-md5", null, new ClientCallbackHandler(password)); |
| return saslClient; |
| } |
| else { // GSSAPI. |
| boolean usingNativeJgss = clientConfig.getBoolean(ZKConfig.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); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Added private credential to subject: " + cred); |
| } |
| } catch (GSSException ex) { |
| LOG.warn("Cannot add private credential to subject; " + |
| "authentication at the server may fail", ex); |
| } |
| } |
| final Object[] principals = subject.getPrincipals().toArray(); |
| // determine client principal from subject. |
| final Principal clientPrincipal = (Principal)principals[0]; |
| 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 = clientConfig.getProperty( |
| ZKClientConfig.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("Client will use GSSAPI as SASL mechanism."); |
| String[] mechs = {"GSSAPI"}; |
| LOG.debug("creating sasl client: client="+clientPrincipalName+";service="+serviceName+";serviceHostname="+serviceHostname); |
| SaslClient saslClient = Sasl.createSaslClient(mechs,clientPrincipalName,serviceName,serviceHostname,null,new ClientCallbackHandler(null)); |
| return saslClient; |
| } |
| }); |
| return saslClient; |
| } |
| catch (Exception e) { |
| LOG.error("Exception while trying to create SASL client", e); |
| e.printStackTrace(); |
| return null; |
| } |
| } |
| } catch (LoginException e) { |
| // We throw LoginExceptions... |
| throw e; |
| } catch (Exception e) { |
| // ..but consume (with a log message) all other types of exceptions. |
| LOG.error("Exception while trying to create SASL client: " + e); |
| return null; |
| } |
| } |
| |
| public void respondToServer(byte[] serverToken, ClientCnxn cnxn) { |
| if (saslClient == null) { |
| LOG.error("saslClient is unexpectedly null. Cannot respond to server's SASL message; ignoring."); |
| return; |
| } |
| |
| if (!(saslClient.isComplete())) { |
| try { |
| saslToken = createSaslToken(serverToken); |
| if (saslToken != null) { |
| sendSaslPacket(saslToken, cnxn); |
| } |
| } catch (SaslException e) { |
| LOG.error("SASL authentication failed using login context '" + |
| this.getLoginContext() + "'."); |
| saslState = SaslState.FAILED; |
| gotLastPacket = true; |
| } |
| } |
| |
| if (saslClient.isComplete()) { |
| // GSSAPI: server sends a final packet after authentication succeeds |
| // or fails. |
| if ((serverToken == null) && (saslClient.getMechanismName().equals("GSSAPI"))) |
| gotLastPacket = true; |
| // non-GSSAPI: no final packet from server. |
| if (!saslClient.getMechanismName().equals("GSSAPI")) { |
| gotLastPacket = true; |
| } |
| // SASL authentication is completed, successfully or not: |
| // enable the socket's writable flag so that any packets waiting for authentication to complete in |
| // the outgoing queue will be sent to the Zookeeper server. |
| cnxn.saslCompleted(); |
| } |
| } |
| |
| private byte[] createSaslToken() throws SaslException { |
| saslState = SaslState.INTERMEDIATE; |
| return createSaslToken(saslToken); |
| } |
| |
| private byte[] createSaslToken(final byte[] saslToken) throws SaslException { |
| if (saslToken == null) { |
| // TODO: introspect about runtime environment (such as jaas.conf) |
| saslState = SaslState.FAILED; |
| throw new SaslException("Error in authenticating with a Zookeeper Quorum member: the quorum member's saslToken is null."); |
| } |
| |
| Subject subject = login.getSubject(); |
| if (subject != null) { |
| synchronized(login) { |
| try { |
| final byte[] retval = |
| Subject.doAs(subject, new PrivilegedExceptionAction<byte[]>() { |
| public byte[] run() throws SaslException { |
| LOG.debug("saslClient.evaluateChallenge(len="+saslToken.length+")"); |
| return saslClient.evaluateChallenge(saslToken); |
| } |
| }); |
| return retval; |
| } |
| catch (PrivilegedActionException e) { |
| String error = "An error: (" + e + ") occurred when evaluating Zookeeper Quorum Member's " + |
| " received SASL token."; |
| // Try to provide hints to use about what went wrong so they can fix their configuration. |
| // TODO: introspect about e: look for GSS information. |
| final String UNKNOWN_SERVER_ERROR_TEXT = |
| "(Mechanism level: Server not found in Kerberos database (7) - UNKNOWN_SERVER)"; |
| if (e.toString().indexOf(UNKNOWN_SERVER_ERROR_TEXT) > -1) { |
| error += " This may be caused by Java's being unable to resolve the Zookeeper Quorum Member's" + |
| " hostname correctly. You may want to try to adding" + |
| " '-Dsun.net.spi.nameservice.provider.1=dns,sun' to your client's JVMFLAGS environment."; |
| } |
| error += " Zookeeper Client will go to AUTH_FAILED state."; |
| LOG.error(error); |
| saslState = SaslState.FAILED; |
| throw new SaslException(error); |
| } |
| } |
| } |
| else { |
| throw new SaslException("Cannot make SASL token without subject defined. " + |
| "For diagnosis, please look for WARNs and ERRORs in your log related to the Login class."); |
| } |
| } |
| |
| private void sendSaslPacket(byte[] saslToken, ClientCnxn cnxn) |
| throws SaslException{ |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("ClientCnxn:sendSaslPacket:length="+saslToken.length); |
| } |
| |
| GetSASLRequest request = new GetSASLRequest(); |
| request.setToken(saslToken); |
| SetSASLResponse response = new SetSASLResponse(); |
| ServerSaslResponseCallback cb = new ServerSaslResponseCallback(); |
| |
| try { |
| cnxn.sendPacket(request,response,cb, ZooDefs.OpCode.sasl); |
| } catch (IOException e) { |
| throw new SaslException("Failed to send SASL packet to server.", |
| e); |
| } |
| } |
| |
| private void sendSaslPacket(ClientCnxn cnxn) throws SaslException { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("ClientCnxn:sendSaslPacket:length="+saslToken.length); |
| } |
| GetSASLRequest request = new GetSASLRequest(); |
| request.setToken(createSaslToken()); |
| SetSASLResponse response = new SetSASLResponse(); |
| ServerSaslResponseCallback cb = new ServerSaslResponseCallback(); |
| try { |
| cnxn.sendPacket(request,response,cb, ZooDefs.OpCode.sasl); |
| } catch (IOException e) { |
| throw new SaslException("Failed to send SASL packet to server due " + |
| "to IOException:", e); |
| } |
| } |
| |
| // used by ClientCnxn to know whether to emit a SASL-related event: either AuthFailed or SaslAuthenticated, |
| // or none, if not ready yet. Sets saslState to COMPLETE as a side-effect. |
| public KeeperState getKeeperState() { |
| if (saslClient != null) { |
| if (saslState == SaslState.FAILED) { |
| return KeeperState.AuthFailed; |
| } |
| if (saslClient.isComplete()) { |
| if (saslState == SaslState.INTERMEDIATE) { |
| saslState = SaslState.COMPLETE; |
| return KeeperState.SaslAuthenticated; |
| } |
| } |
| } |
| // No event ready to emit yet. |
| return null; |
| } |
| |
| // Initialize the client's communications with the Zookeeper server by sending the server the first |
| // authentication packet. |
| public void initialize(ClientCnxn cnxn) throws SaslException { |
| if (saslClient == null) { |
| saslState = SaslState.FAILED; |
| throw new SaslException("saslClient failed to initialize properly: it's null."); |
| } |
| if (saslState == SaslState.INITIAL) { |
| if (saslClient.hasInitialResponse()) { |
| sendSaslPacket(cnxn); |
| } |
| else { |
| byte[] emptyToken = new byte[0]; |
| sendSaslPacket(emptyToken, cnxn); |
| } |
| saslState = SaslState.INTERMEDIATE; |
| } |
| } |
| |
| // The CallbackHandler interface here refers to |
| // javax.security.auth.callback.CallbackHandler. |
| // It should not be confused with Zookeeper packet callbacks like |
| // org.apache.zookeeper.server.auth.SaslServerCallbackHandler. |
| public static class ClientCallbackHandler implements CallbackHandler { |
| private String password = null; |
| |
| public ClientCallbackHandler(String password) { |
| this.password = password; |
| } |
| |
| public void handle(Callback[] callbacks) throws |
| UnsupportedCallbackException { |
| for (Callback callback : callbacks) { |
| if (callback instanceof NameCallback) { |
| NameCallback nc = (NameCallback) callback; |
| nc.setName(nc.getDefaultName()); |
| } |
| else { |
| if (callback instanceof PasswordCallback) { |
| PasswordCallback pc = (PasswordCallback)callback; |
| if (password != null) { |
| pc.setPassword(this.password.toCharArray()); |
| } else { |
| LOG.warn("Could not login: the client is being asked for a password, but the Zookeeper" + |
| " client code does not currently support obtaining a password from the user." + |
| " Make sure that the client is configured to use a ticket cache (using" + |
| " the JAAS configuration setting 'useTicketCache=true)' and restart the client. If" + |
| " you still get this message after that, the TGT in the ticket cache has expired and must" + |
| " be manually refreshed. To do so, first determine if you are using a password or a" + |
| " keytab. If the former, run kinit in a Unix shell in the environment of the user who" + |
| " is running this Zookeeper client using the command" + |
| " 'kinit <princ>' (where <princ> is the name of the client's Kerberos principal)." + |
| " If the latter, do" + |
| " 'kinit -k -t <keytab> <princ>' (where <princ> is the name of the Kerberos principal, and" + |
| " <keytab> is the location of the keytab file). After manually refreshing your cache," + |
| " restart this client. If you continue to see this message after manually refreshing" + |
| " your cache, ensure that your KDC host's clock is in sync with this host's clock."); |
| } |
| } |
| else { |
| if (callback instanceof RealmCallback) { |
| RealmCallback rc = (RealmCallback) callback; |
| rc.setText(rc.getDefaultText()); |
| } |
| else { |
| if (callback instanceof AuthorizeCallback) { |
| AuthorizeCallback ac = (AuthorizeCallback) callback; |
| String authid = ac.getAuthenticationID(); |
| String authzid = ac.getAuthorizationID(); |
| if (authid.equals(authzid)) { |
| ac.setAuthorized(true); |
| } else { |
| ac.setAuthorized(false); |
| } |
| if (ac.isAuthorized()) { |
| ac.setAuthorizedID(authzid); |
| } |
| } |
| else { |
| throw new UnsupportedCallbackException(callback,"Unrecognized SASL ClientCallback"); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| public boolean clientTunneledAuthenticationInProgress() { |
| if (!isSASLConfigured) { |
| return false; |
| } |
| // TODO: Rather than checking a disjunction here, should be a single member |
| // variable or method in this class to determine whether the client is |
| // configured to use SASL. (see also ZOOKEEPER-1455). |
| try { |
| if ((clientConfig.getJaasConfKey() != null) |
| || ((Configuration.getConfiguration() != null) && (Configuration.getConfiguration() |
| .getAppConfigurationEntry(clientConfig.getProperty(ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, |
| ZKClientConfig.LOGIN_CONTEXT_NAME_KEY_DEFAULT)) != null))) { |
| // Client is configured to use a valid login Configuration, so |
| // authentication is either in progress, successful, or failed. |
| |
| // 1. Authentication hasn't finished yet: we must wait for it to do so. |
| if ((isComplete() == false) && |
| (isFailed() == false)) { |
| return true; |
| } |
| |
| // 2. SASL authentication has succeeded or failed.. |
| if (isComplete() || isFailed()) { |
| if (gotLastPacket == false) { |
| // ..but still in progress, because there is a final SASL |
| // message from server which must be received. |
| return true; |
| } |
| } |
| } |
| // Either client is not configured to use a tunnelled authentication |
| // scheme, or tunnelled authentication has completed (successfully or |
| // not), and all server SASL messages have been received. |
| return false; |
| } catch (SecurityException e) { |
| // Thrown if the caller does not have permission to retrieve the Configuration. |
| // In this case, simply returning false is correct. |
| if (LOG.isDebugEnabled() == true) { |
| LOG.debug("Could not retrieve login configuration: " + e); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * close login thread if running |
| */ |
| public void shutdown() { |
| if (null != login) { |
| login.shutdown(); |
| } |
| } |
| } |