/* * Copyright 1999-2006 University of Chicago * * Licensed 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.globus.ftp.extended; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.globus.ftp.GridFTPSession; import org.globus.ftp.exception.ServerException; import org.globus.ftp.exception.UnexpectedReplyCodeException; import org.globus.ftp.exception.FTPReplyParseException; import org.globus.ftp.vanilla.Reply; import org.globus.ftp.vanilla.FTPControlChannel; import org.globus.ftp.vanilla.Command; import org.globus.common.ChainedIOException; import org.globus.gsi.gssapi.auth.Authorization; import org.globus.gsi.gssapi.auth.GSSAuthorization; import org.globus.gsi.gssapi.auth.HostAuthorization; import org.globus.gsi.gssapi.auth.AuthorizationException; import org.globus.gsi.gssapi.GSSConstants; import org.gridforum.jgss.ExtendedGSSManager; import org.ietf.jgss.GSSManager; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** GridFTP control channel, unlike the vanilla control channel, uses GSI autentication. **/ public class GridFTPControlChannel extends FTPControlChannel { private static Log logger = LogFactory.getLog(GridFTPControlChannel.class.getName()); protected static final int TIMEOUT = 120000; //maybe this is useless protected GSSCredential credentials = null; protected Authorization authorization = HostAuthorization.getInstance(); protected int protection = GridFTPSession.PROTECTION_PRIVATE; public GridFTPControlChannel(String host, int port) { super(host,port); } public GridFTPControlChannel(InputStream in, OutputStream out) { super(in, out); } /** * Sets data channel protection level. * * @param protection should be * {@link GridFTPSession#PROTECTION_CLEAR CLEAR}, * {@link GridFTPSession#PROTECTION_SAFE SAFE}, or * {@link GridFTPSession#PROTECTION_PRIVATE PRIVATE}, or * {@link GridFTPSession#PROTECTION_CONFIDENTIAL CONFIDENTIAL}. **/ public void setProtection(int protection) { switch(protection) { case GridFTPSession.PROTECTION_CLEAR: throw new IllegalArgumentException("Unsupported protection: " + protection); case GridFTPSession.PROTECTION_SAFE: case GridFTPSession.PROTECTION_CONFIDENTIAL: case GridFTPSession.PROTECTION_PRIVATE: break; default: throw new IllegalArgumentException("Bad protection: " + protection); } this.protection = protection; } /** * Returns control channel protection level. * * @return control channel protection level: * {@link GridFTPSession#PROTECTION_CLEAR CLEAR}, * {@link GridFTPSession#PROTECTION_SAFE SAFE}, or * {@link GridFTPSession#PROTECTION_PRIVATE PRIVATE}, or * {@link GridFTPSession#PROTECTION_CONFIDENTIAL CONFIDENTIAL}. **/ public int getProtection() { return this.protection; } /** * Sets authorization method for the control channel. * * @param authorization authorization method. */ public void setAuthorization(Authorization authorization) { this.authorization = authorization; } /** * Returns authorization method for the control channel. * * @return authorization method performed on the control channel. */ public Authorization getAuthorization() { return this.authorization; } /** * Performs authentication with specified user credentials. * * @param credential user credentials to use. * @throws IOException on i/o error * @throws ServerException on server refusal or faulty server behavior */ public void authenticate(GSSCredential credential) throws IOException, ServerException { authenticate(credential, null); } /** * Performs authentication with specified user credentials and * a specific username (assuming the user dn maps to the passed username). * * @param credential user credentials to use. * @param username specific username to authenticate as. * @throws IOException on i/o error * @throws ServerException on server refusal or faulty server behavior */ public void authenticate(GSSCredential credential, String username) throws IOException, ServerException { setCredentials( credential ); write(new Command("AUTH", "GSSAPI")); Reply reply0 = null; try { reply0 = read(); } catch (FTPReplyParseException rpe) { throw ServerException.embedFTPReplyParseException( rpe, "Received faulty reply to AUTH GSSAPI"); } if (! Reply.isPositiveIntermediate(reply0)) { close(); throw ServerException.embedUnexpectedReplyCodeException( new UnexpectedReplyCodeException(reply0), "Server refused GSSAPI authentication."); } GSSManager manager = ExtendedGSSManager.getInstance(); GSSContext context = null; GridFTPOutputStream gssout = null; GridFTPInputStream gssin = null; try { String host = this.socket.getInetAddress().getHostAddress(); GSSName expectedName = null; if (this.authorization instanceof GSSAuthorization) { GSSAuthorization auth = (GSSAuthorization)this.authorization; expectedName = auth.getExpectedName(credential, host); } context = manager.createContext(expectedName, GSSConstants.MECH_OID, credential, GSSContext.DEFAULT_LIFETIME); context.requestCredDeleg(true); context.requestConf(this.protection == GridFTPSession.PROTECTION_PRIVATE); gssout = new GridFTPOutputStream(ftpOut, context); gssin = new GridFTPInputStream(rawFtpIn, context); byte [] inToken = new byte[0]; byte [] outToken = null; while( !context.isEstablished() ) { outToken = context.initSecContext(inToken, 0, inToken.length); if (outToken != null) { gssout.writeHandshakeToken(outToken); } if (!context.isEstablished()) { inToken = gssin.readHandshakeToken(); } } } catch (GSSException e) { throw new ChainedIOException("Authentication failed", e); } if (this.authorization != null) { try { this.authorization.authorize(context, host); } catch (AuthorizationException e) { throw new ChainedIOException("Authorization failed", e); } } // this should be authentication success msg (plain) // 234 (ok, no further data required) Reply reply1 = null; try { reply1 = read(); } catch (FTPReplyParseException rpe) { throw ServerException.embedFTPReplyParseException( rpe, "Received faulty reply to authentication"); } if ( ! Reply.isPositiveCompletion(reply1)) { close(); throw ServerException.embedUnexpectedReplyCodeException( new UnexpectedReplyCodeException(reply1), "GSSAPI authentication failed."); } // enter secure mode - send MIC commands setInputStream(gssin); setOutputStream(gssout); //from now on, the commands and replies //are protected and pass through gsi wrapped socket write(new Command("USER", (username == null) ? ":globus-mapping:" : username)); Reply reply2 = null; try { reply2 = read(); } catch (FTPReplyParseException rpe) { throw ServerException.embedFTPReplyParseException( rpe, "Received faulty reply to USER command"); } if (Reply.isPositiveCompletion(reply2) || Reply.isPositiveIntermediate(reply2)) { // wu-gsiftp sends intermediate code while // gssftp send completion reply code } else { close(); throw ServerException.embedUnexpectedReplyCodeException( new UnexpectedReplyCodeException(reply2), "User authorization failed."); } write(new Command("PASS", "dummy")); Reply reply3 = null; try { reply3=read(); } catch (FTPReplyParseException rpe) { throw ServerException.embedFTPReplyParseException( rpe, "Received faulty reply to PASS command"); } if (!Reply.isPositiveCompletion(reply3)) { close(); throw ServerException.embedUnexpectedReplyCodeException( new UnexpectedReplyCodeException(reply3), "Bad password."); } } protected void setCredentials( GSSCredential credentials ) { this.credentials = credentials; } protected GSSCredential getCredentials() { return credentials; } }