package org.jscsi.target.connection.stage.login;
import java.io.IOException;
import java.security.DigestException;
import java.util.List;
import java.util.Vector;
import org.jscsi.exception.InternetSCSIException;
import org.jscsi.parser.BasicHeaderSegment;
import org.jscsi.parser.ProtocolDataUnit;
import org.jscsi.parser.login.LoginStage;
import org.jscsi.parser.login.LoginStatus;
import org.jscsi.target.connection.Connection.TargetConnection;
import org.jscsi.target.connection.phase.TargetLoginPhase;
import org.jscsi.target.settings.SettingsException;
import org.jscsi.target.settings.TextKeyword;
import org.jscsi.target.settings.TextParameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link TargetLoginStage} sub-class representing Security Negotiation
* Stages.
*
* @author Andreas Ergenzinger
*/
public final class SecurityNegotiationStage extends TargetLoginStage {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityNegotiationStage.class);
/**
* The constructor.
*
* @param targetLoginPhase
* the login phase this stage is a part of
*/
public SecurityNegotiationStage(TargetLoginPhase targetLoginPhase) {
super(targetLoginPhase, LoginStage.SECURITY_NEGOTIATION);
}
@Override
public void execute(ProtocolDataUnit initialPdu) throws IOException, InterruptedException,
InternetSCSIException, DigestException, SettingsException {
// "receive" initial PDU
BasicHeaderSegment bhs = initialPdu.getBasicHeaderSegment();
initiatorTaskTag = bhs.getInitiatorTaskTag();
boolean authenticated = false;
do {// while initiator is not willing and not authorized to transit to
// next stage
// build text parameter string from current login PDU sequence
final String requestTextParameters = receivePduSequence(initialPdu);
// split key-value pairs
final List<String> requestKeyValuePairs =
TextParameter.tokenizeKeyValuePairs(requestTextParameters);
// Vector for AuthMethod keys
final List<String> authMethodKeyValuePairs = new Vector<String>();
// log initiator's key-value pairs
if (LOGGER.isDebugEnabled()) {
final StringBuilder sb = new StringBuilder();
sb.append("request key value pairs:\n");
for (String s : requestKeyValuePairs)
sb.append(" " + s + "\n");
LOGGER.debug(sb.toString());
}
// extract available AuthMethod key-value pair, so that settings can
// finish
// processing the other parameters before authorization begins
String authMethodValues = null;
if (!authenticated) {// authentication part one
for (int i = 0; i < requestKeyValuePairs.size(); ++i) {
final String[] split = TextParameter.splitKeyValuePair(requestKeyValuePairs.get(i));
if (split == null) {
sendRejectPdu(LoginStatus.INITIATOR_ERROR);
throw new InternetSCSIException("key=value format error: "
+ requestKeyValuePairs.get(i));
}
if (TextKeyword.AUTH_METHOD.equals(split[0])) {
authMethodValues = split[1];
// remove key-value pair from Vector
requestKeyValuePairs.remove(i--);// correct for shifted
// indices
// no break here to catch all authMethodKeyValuePairs in
// else block
} else if (isAuthenticationKey(split[0])) {
// move key-value pair to authMethodKeyValuePairs
authMethodKeyValuePairs.add(requestKeyValuePairs.remove(i--));// correct for shifted
// indices
}
}
if (authMethodValues == null) {// missing AuthMethod key
sendRejectPdu(LoginStatus.MISSING_PARAMETER);// require
// AuthMethod
// to be
// specified in
// first PDU
// sequence
// close connection
throw new InternetSCSIException("Missing AuthMethod key-value pair");
}
}
// negotiate remaining parameters
final Vector<String> responseKeyValuePairs = new Vector<String>();// these
// will
// be
// sent
// back
if (!negotiator.negotiate(session, stageNumber, (TargetConnection) connection
, ((TargetLoginPhase)targetPhase).getFirstPduAndSetToFalse(),
requestKeyValuePairs, responseKeyValuePairs)) {
// negotiation error
sendRejectPdu(LoginStatus.INITIATOR_ERROR);
throw new InternetSCSIException("negotiation failure");
}
// ** authentication ** (part two)
if (!authenticated) {
if (authMethodValues.contains(TextKeyword.NONE)) {
authenticated = true;
responseKeyValuePairs.add(TextParameter.toKeyValuePair(TextKeyword.AUTH_METHOD,// key
TextKeyword.NONE));// value
// concatenate key value pairs to single string
final String responseString =
TextParameter.concatenateKeyValuePairs(responseKeyValuePairs);
if (LOGGER.isDebugEnabled())
LOGGER.debug("response: " + responseString);
// send reply (sequence), set transit bit of last PDU
sendPduSequence(responseString, requestedNextStageNumber);
// leave this (and proceed to next) stage
if (requestedNextStageNumber == LoginStage.LOGIN_OPERATIONAL_NEGOTIATION
|| requestedNextStageNumber == LoginStage.FULL_FEATURE_PHASE) {
nextStageNumber = requestedNextStageNumber;
return;
}
} else {
// TODO support CHAP (and use String
// authMethodKeyValuePairs)
LOGGER.error("initiator attempted CHAP authentication");
// nextStageNumber = null;//no change
return;
}
}
} while (!bhs.isFinalFlag() && !authenticated);
}
/**
* Checks if a the parameter is the key of an AuthMethod Key, which means
* one of the following (where <key> depends on the AuthMethod
* prefix):
* <ul>
* <li>CHAP_<key>/il>
* <li>KRB_<key>/il>
* <li>SPKM_<key>/il>
* <li>SRP_<key>/il>
* </ul>
*
* @param <i>key</i> part of a <i>key-value</i> pair
* @return <code>true</code> if the String is an AuthMethod key, <code>false</code> if it is not.
*/
private final boolean isAuthenticationKey(final String key) {
if (key == null || key.length() < 5)
return false;
final String fourChars = key.substring(0, 4);
final String fiveChars = key.substring(0, 5);
if ("CHAP_".matches(fiveChars) || "KRB_".matches(fourChars) || "SPKM_".matches(fiveChars)
|| "SRP_".matches(fourChars)
|| (key.length() >= 10 && "TargetAuth".matches(key.substring(0, 10))))
return true;
return false;
}
}