/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.embedded.ssh.internal;
import java.io.IOException;
import java.security.PublicKey;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.PatternSyntaxException;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
import org.apache.sshd.server.session.ServerSession;
import org.mindrot.jbcrypt.BCrypt;
import de.rcenvironment.core.authentication.AuthenticationException;
import de.rcenvironment.core.embedded.ssh.api.SshAccount;
import de.rcenvironment.core.embedded.ssh.api.TemporarySshAccount;
import de.rcenvironment.core.embedded.ssh.api.TemporarySshAccountControl;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.common.TempFileServiceAccess;
/**
* A simple Password Authenticator to be replaced by a public/private key infrastructure.
*
* @author Sebastian Holtappels
* @author Robert Mischke
* @author Brigitte Boden (added public key authentication)
*/
public class SshAuthenticationManager implements PasswordAuthenticator, TemporarySshAccountControl, PublickeyAuthenticator {
private SshConfiguration configuration;
private List<TemporarySshAccount> temporaryAccounts;
private final Log log = LogFactory.getLog(getClass());
public SshAuthenticationManager(SshConfiguration configuration) {
this.configuration = configuration;
}
@Override
// implementation of MINA PasswordAuthenticator
public boolean authenticate(String usernameParam, String passwordParam, ServerSession session) {
boolean loginCorrect = false;
if (usernameParam != null && !usernameParam.isEmpty() && passwordParam != null && !passwordParam.isEmpty()) {
if (usernameParam.startsWith(SshConstants.TEMP_USER_PREFIX)) {
TemporarySshAccount tempUser = getTemporaryAccountByName(usernameParam);
if (tempUser != null && checkPassword(tempUser, passwordParam)) {
loginCorrect = true;
}
} else {
SshAccount user = configuration.getAccountByName(usernameParam, false);
if (user != null && checkPassword(user, passwordParam)) {
loginCorrect = true;
}
}
}
return loginCorrect;
}
/*
* For public key authentication. Does not perform the actual authentication, but just checks if the given public key is allowed to
* authenticate.
*
*/
@Override
public boolean authenticate(String userName, PublicKey key, ServerSession session) {
boolean loginCorrect = false;
//Check if account with this username exists
if (configuration.getAccountByName(userName, false) == null) {
return false;
}
PublicKey knownKey = configuration.getAccountByName(userName, false).getPublicKeyObj();
if (knownKey != null) {
loginCorrect = key.equals(knownKey);
}
return loginCorrect;
}
/**
*
* Used to determine if a user has the rights to execute a command.
*
* @param username the user who wants to execute the command
* @param command the command to be executed
* @return true (is allowed) false (not allowed)
*/
public boolean isAllowedToExecuteConsoleCommand(String username, String command) {
boolean isAllowed = false;
SshAccountRole userRole = getRoleForUser(username);
try {
if (userRole != null && command.matches(userRole.getAllowedCommandRegEx())) {
isAllowed = true;
}
} catch (PatternSyntaxException e) {
//Should never happen as the allowed command patterns are checked when the SSH server is started
log.error("Could not verify if user " + username + " is allowed to execute command " + command
+ ". Probable cause: The allowed commands pattern is invalid.");
}
return isAllowed;
}
/**
*
* Used to determine if a user has the rights to use scp to copy files to the given destinations.
*
* @param username - The name of the active user
* @param destination - the destinations of the scp command
* @return - true if user is allowed
*/
public boolean isAllowedToUseScpDestination(String username, String destination) {
boolean isAllowed = false;
TemporarySshAccount tempUser = getTemporaryAccountByName(username);
if (tempUser != null) {
isAllowed = destination != null && destination.startsWith(tempUser.getVirtualScpRootPath()) && !destination.contains("../")
&& !destination.contains("..\\");
}
return isAllowed;
}
/**
* @param username the account name to check
* @return if this account name matches an existing temporary account
*/
@Deprecated
public boolean isTemporaryAccountName(String username) {
return getTemporaryAccountByName(username) != null;
}
private SshAccountRole getRoleForUser(String userName) {
SshAccount user = configuration.getAccountByName(userName, true);
SshAccountRole role = null;
if (user != null) {
role = configuration.getRoleByName(user.getRole());
}
return role;
}
/**
* Returns the {@link SshUser} object matching the given login name, or throws an {@link AuthenticationException} if no such user
* exists.
*
* @param loginName the login name of the account to fetch
* @param allowDisabled true if disabled accounts should be returned as well; if false, null is returned for disabled accounts
* @return the {@link SshAccount} object matching the given login name, or null if no such account exists
*/
public SshAccount getAccountByLoginName(String loginName, boolean allowDisabled) {
// TODO nothing prevents "weird" SSH names to be defined via the configuration file at this time (e.g. starting with space)
return configuration.getAccountByName(loginName, allowDisabled);
}
/**
* @return all accounts in a sorted map, with their login names as map key
*/
public SortedMap<String, SshAccount> getAllAcountsByLoginName() {
SortedMap<String, SshAccount> result = new TreeMap<>();
for (SshAccountImpl account : configuration.getAccounts()) {
result.put(account.getLoginName(), account);
}
return result;
}
/**
*
* Return the user with the given name.
*
* @param name - The name of the user
* @return - the user
*/
@Deprecated
public TemporarySshAccount getTemporaryAccountByName(String name) {
TemporarySshAccount result = null;
if (temporaryAccounts != null) {
for (TemporarySshAccount tempUser : temporaryAccounts) {
if (tempUser.getLoginName().equals(name)) {
result = tempUser;
}
}
}
return result;
}
@Deprecated
public List<TemporarySshAccount> getTemporaryAccounts() {
return temporaryAccounts;
}
@Deprecated
public void setTemporaryAccounts(List<TemporarySshAccount> tempUsers) {
this.temporaryAccounts = tempUsers;
}
// TODO add synchronization - misc_ro
@Override
@Deprecated
public TemporarySshAccount createTemporarySshAccount() {
if (temporaryAccounts == null) {
temporaryAccounts = new CopyOnWriteArrayList<TemporarySshAccount>();
}
TemporarySshAccountImpl tempAccount = new TemporarySshAccountImpl();
// create name
String randomAccountNamePart = RandomStringUtils.randomAlphanumeric(SshConstants.TEMP_USER_NAME_RANDOM_LENGTH);
String username = SshConstants.TEMP_USER_PREFIX + randomAccountNamePart;
tempAccount.setLoginName(username);
// create password
tempAccount.setPassword(RandomStringUtils.randomAlphanumeric(SshConstants.TEMP_USER_PASSWORD_RANDOM_LENGTH));
// set Path
tempAccount.setVirtualScpRootPath("/temp/" + username);
try {
tempAccount.setLocalScpRootPath(TempFileServiceAccess.getInstance().createManagedTempDir("temp-scp-" + randomAccountNamePart));
} catch (IOException e) {
throw new RuntimeException("Failed to create temporary SCP directory", e);
}
log.debug(StringUtils.format("Created temporary SCP account '%s' with virtual SCP path '%s' mapped to local path '%s'",
tempAccount.getLoginName(), tempAccount.getVirtualScpRootPath(), tempAccount.getLocalScpRootPath().getAbsolutePath()));
temporaryAccounts.add(tempAccount);
return tempAccount;
}
@Override
@Deprecated
public void discardTemporarySshAccount(String name) {
for (int i = 0; i < temporaryAccounts.size(); i++) {
TemporarySshAccount tempUser = temporaryAccounts.get(i);
if (tempUser.getLoginName().equals(name)) {
temporaryAccounts.remove(i);
}
}
}
private boolean checkPassword(SshAccount account, String password) {
if (account.getPasswordHash() != null) {
// TODO move to common utility function
final boolean result = BCrypt.checkpw(password, account.getPasswordHash());
log.debug(StringUtils.format("Used password hash to check login attempt for user \"%s\" - accepted = %s",
account.getLoginName(), result));
return result;
}
if (account.getPassword() != null) {
final boolean result = account.getPassword().equals(password);
log.warn(StringUtils.format("Used clear-text password to check login attempt for user \"%s\" - accepted = %s",
account.getLoginName(), result));
return result;
}
log.error("Consistency error: SSH login attempt with a password for user \"" + account.getLoginName()
+ "\", but the local account has neither a clear-text nor a hashed password");
return false;
}
}