/*
* 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.sshd.server.auth.password;
import org.apache.sshd.common.RuntimeSshException;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.server.auth.AbstractUserAuth;
import org.apache.sshd.server.session.ServerSession;
/**
* TODO Add javadoc
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class UserAuthPassword extends AbstractUserAuth {
public static final String NAME = UserAuthPasswordFactory.NAME;
public UserAuthPassword() {
super(NAME);
}
@Override
public Boolean doAuth(Buffer buffer, boolean init) throws Exception {
ValidateUtils.checkTrue(init, "Instance not initialized");
boolean newPassword = buffer.getBoolean();
String password = buffer.getString();
if (newPassword) {
return handleClientPasswordChangeRequest(buffer, getServerSession(), getUsername(), password, buffer.getString());
} else {
return checkPassword(buffer, getServerSession(), getUsername(), password);
}
}
/**
* Invokes the configured {@link PasswordAuthenticator} and returns the result.
* If {@link PasswordChangeRequiredException} thrown by the authenticator then
* {@link #handleServerPasswordChangeRequest(Buffer, ServerSession, String, String, PasswordChangeRequiredException)}
* is invoked
*
* @param buffer The received {@link Buffer} to be re-used if need to send
* a password change request
* @param session The {@link ServerSession} through which the request was received
* @param username The username
* @param password The password
* @return The authentication result - if {@code null} then exception was handled
* internally and authentication is still in progress
* @throws Exception If internal error during authentication (exception for
* {@link PasswordChangeRequiredException} which is handled internally)
* @see #handleServerPasswordChangeRequest(Buffer, ServerSession, String, String, PasswordChangeRequiredException)
*/
protected Boolean checkPassword(Buffer buffer, ServerSession session, String username, String password) throws Exception {
PasswordAuthenticator auth = session.getPasswordAuthenticator();
if (auth == null) {
if (log.isDebugEnabled()) {
log.debug("checkPassword({}) no password authenticator", session);
}
return false;
}
try {
boolean authed;
try {
authed = auth.authenticate(username, password, session);
} catch (Error e) {
log.warn("checkPassword({}) failed ({}) to consult authenticator: {}",
session, e.getClass().getSimpleName(), e.getMessage());
if (log.isDebugEnabled()) {
log.debug("checkPassword(" + session + ") authenticator failure details", e);
}
throw new RuntimeSshException(e);
}
if (log.isDebugEnabled()) {
log.debug("checkPassword({}) authentication result: {}", session, authed);
}
return authed;
} catch (PasswordChangeRequiredException e) {
if (log.isDebugEnabled()) {
log.debug("checkPassword({}) password change required: {}", session, e.getMessage());
}
return handleServerPasswordChangeRequest(buffer, session, username, password, e);
}
}
/**
* Invoked when the client sends a {@code SSH_MSG_USERAUTH_REQUEST} indicating
* a password change. Throws {@link UnsupportedOperationException} by default
*
* @param buffer The {@link Buffer} to re-use in order to respond
* @param session The associated {@link ServerSession}
* @param username The username
* @param oldPassword The old password
* @param newPassword The new password
* @return Password change and authentication result - {@code null} means
* authentication incomplete - i.e., handler has sent some extra query.
* @throws Exception If failed to handle the request.
*/
protected Boolean handleClientPasswordChangeRequest(
Buffer buffer, ServerSession session, String username, String oldPassword, String newPassword)
throws Exception {
throw new UnsupportedOperationException("Password change not supported");
}
/**
* Invoked by {@link #checkPassword(Buffer, ServerSession, String, String)}
* when a {@link PasswordChangeRequiredException} was thrown by the authenticator.
* By default it re-throws the original exception.
*
* @param buffer The received {@link Buffer} to be re-used if need to send
* a password change request
* @param session The {@link ServerSession} through which the request was received
* @param username The username
* @param password The (rejected) password
* @param e The original thrown exception
* @return {@code null} by default to indicate incomplete authentication
* @throws Exception If failed to dispatch the message
*/
protected Boolean handleServerPasswordChangeRequest(
Buffer buffer, ServerSession session, String username, String password, PasswordChangeRequiredException e)
throws Exception {
String prompt = e.getPrompt();
String lang = e.getLanguage();
if (log.isDebugEnabled()) {
log.debug("handlePasswordChangeRequest({}) password change required - prompt={}, lang={}",
session, prompt, lang);
}
buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_PASSWD_CHANGEREQ,
GenericUtils.length(prompt) + GenericUtils.length(lang) + Integer.SIZE);
buffer.putString(prompt);
buffer.putString(lang);
session.writePacket(buffer);
return null; // authentication incomplete
}
}