/**
* 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.server.auth;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.sasl.AuthorizeCallback;
import javax.security.sasl.RealmCallback;
public class SaslServerCallbackHandler implements CallbackHandler {
private String userName = null;
private Map<String,String> credentials = new HashMap<String,String>();
Logger LOG = LoggerFactory.getLogger(SaslServerCallbackHandler.class);
public SaslServerCallbackHandler(Configuration configuration) throws IOException {
AppConfigurationEntry configurationEntries[] = configuration.getAppConfigurationEntry("Server");
if (configurationEntries == null) {
String errorMessage = "could not find a 'Server' entry in this configuration: server cannot start.";
LOG.error(errorMessage);
throw(new IOException(errorMessage));
}
credentials.clear();
for(AppConfigurationEntry entry: configurationEntries) {
Map<String,?> options = entry.getOptions();
// Populate DIGEST-MD5 user -> password map with JAAS configuration entries from the "Server" section.
// Usernames are distinguished from other options by prefixing the username with a "user_" prefix.
Iterator it = options.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry)it.next();
String key = (String)pair.getKey();
if (key.substring(0,5).equals("user_")) {
String userName = key.substring(5);
credentials.put(userName,(String)pair.getValue());
}
}
}
return;
}
public void handle(Callback[] callbacks) throws
UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
NameCallback nc = (NameCallback) callback;
// check to see if this user is in the user password database.
if (credentials.get(nc.getDefaultName()) != null) {
nc.setName(nc.getDefaultName());
this.userName = nc.getDefaultName();
}
else { // no such user.
LOG.warn("User '" + nc.getDefaultName() + "' not found in list of DIGEST-MD5 authenticateable users.");
}
}
else {
if (callback instanceof PasswordCallback) {
PasswordCallback pc = (PasswordCallback) callback;
if ((this.userName.equals("super")
&&
(System.getProperty("zookeeper.SASLAuthenticationProvider.superPassword") != null))) {
// superuser: use Java system property for password, if available.
pc.setPassword(System.getProperty("zookeeper.SASLAuthenticationProvider.superPassword").toCharArray());
}
else {
if (this.credentials.get(this.userName) != null) {
pc.setPassword(this.credentials.get(this.userName).toCharArray());
}
else {
LOG.warn("No password found for user: " + this.userName);
}
}
}
else {
if (callback instanceof RealmCallback) {
RealmCallback rc = (RealmCallback) callback;
LOG.debug("client supplied realm: " + rc.getDefaultText());
rc.setText(rc.getDefaultText());
}
else {
if (callback instanceof AuthorizeCallback) {
AuthorizeCallback ac = (AuthorizeCallback) callback;
String authenticationID = ac.getAuthenticationID();
String authorizationID = ac.getAuthorizationID();
LOG.info("Successfully authenticated client: authenticationID=" + authenticationID + "; authorizationID=" + authorizationID + ".");
if (authenticationID.equals(authorizationID)) {
LOG.debug("setAuthorized(true) since " + authenticationID + "==" + authorizationID);
ac.setAuthorized(true);
} else {
LOG.debug("setAuthorized(true), even though " + authenticationID + "!=" + authorizationID + ".");
ac.setAuthorized(true);
}
if (ac.isAuthorized()) {
LOG.debug("isAuthorized() since ac.isAuthorized() == true");
// canonicalize authorization id according to system properties:
// kerberos.removeRealmFromPrincipal(={true,false})
// kerberos.removeHostFromPrincipal(={true,false})
KerberosName kerberosName = new KerberosName(authenticationID);
try {
String userName = kerberosName.getShortName();
if (!removeHost() && (kerberosName.getHostName() != null)) {
userName += "/" + kerberosName.getServiceName();
}
if (!removeRealm() && (kerberosName.getRealm() != null)) {
userName += "@" + kerberosName.getRealm();
}
LOG.info("Setting authorizedID: " + userName);
ac.setAuthorizedID(userName);
}
catch (IOException e) {
LOG.error("Failed to set name based on Kerberos authentication rules.");
}
}
}
}
}
}
}
}
private boolean removeRealm() {
return ((System.getProperty("zookeeper.kerberos.removeRealmFromPrincipal") != null) &&
(System.getProperty("zookeeper.kerberos.removeRealmFromPrincipal").equals("true")));
}
private boolean removeHost() {
return ((System.getProperty("zookeeper.kerberos.removeHostFromPrincipal") != null) &&
(System.getProperty("zookeeper.kerberos.removeHostFromPrincipal").equals("true")));
}
}