package org.infoglue.common.util.cvsclient;
/*****************************************************************************
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
* The Original Software is the CVS Client Library.
* The Initial Developer of the Original Software is Robert Greig.
* Portions created by Robert Greig are Copyright (C) 2000.
* All Rights Reserved.
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
* Contributor(s): Robert Greig.
*****************************************************************************/
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.net.Socket;
import java.text.MessageFormat;
import javax.net.SocketFactory;
import org.netbeans.lib.cvsclient.CVSRoot;
import org.netbeans.lib.cvsclient.command.CommandAbortedException;
import org.netbeans.lib.cvsclient.command.CommandException;
import org.netbeans.lib.cvsclient.connection.AbstractConnection;
import org.netbeans.lib.cvsclient.connection.AuthenticationException;
import org.netbeans.lib.cvsclient.connection.ConnectionModifier;
import org.netbeans.lib.cvsclient.connection.StandardScrambler;
import org.netbeans.lib.cvsclient.util.LoggedDataInputStream;
import org.netbeans.lib.cvsclient.util.LoggedDataOutputStream;
/**
* Implements a connection to a pserver. See the cvs documents for more
* information about different connection methods. PServer is popular where
* security is not an issue. For secure connections, consider using a
* kserver (Kerberos) or the GSSAPI.
*
* @author Robert Greig
*/
public class MyConnection extends AbstractConnection {
/**
* The string that is sent at the beginning of the request to open
* a connection.
*/
protected static final String OPEN_PREAMBLE = "BEGIN AUTH REQUEST\n"; //NOI18N
/**
* The string that is sent at the end of the request to open a connection.
*/
protected static final String OPEN_POSTAMBLE = "END AUTH REQUEST\n"; //NOI18N
/**
* The string that is sent at the beginning of the request to
* verify a connection.
* Note the difference between opening a connection and simply verifying.
*/
protected static final String VERIFY_PREAMBLE =
"BEGIN VERIFICATION REQUEST\n"; //NOI18N
/**
* The string that is sent at the end of a verify request.
*/
protected static final String VERIFY_POSTAMBLE =
"END VERIFICATION REQUEST\n"; //NOI18N
/**
* A response indicating that authorisation has succeeded.
*/
protected static final String AUTHENTICATION_SUCCEEDED_RESPONSE =
"I LOVE YOU"; //NOI18N
private static final String AUTHENTICATION_SUCCEEDED_RESPONSE_RAW =
"I LOVE YOU\n"; //NOI18N
/**
* A response indicating that the authorisation has failed.
*/
protected static final String AUTHENTICATION_FAILED_RESPONSE =
"I HATE YOU"; //NOI18N
private static final String AUTHENTICATION_FAILED_RESPONSE_RAW =
"I HATE YOU\n"; //NOI18N
/**
* The user name to use.
*/
protected String userName;
/**
* The password, encoded appropriately.
*/
protected String encodedPassword;
/**
* The default port number to use.
*/
public static final int DEFAULT_PORT = 2401;
/**
* The port number to use.
*/
protected int port = DEFAULT_PORT;
/**
* The host to use.
*/
protected String hostName;
/**
* The socket used for the connection.
*/
protected Socket socket;
/**
* The socket factory that will be used to create sockets.
*/
protected SocketFactory socketFactory;
/**
* Create an uninitialized PServerConnection. All properties needs to be set
* explicitly by appropriate setters before this connection can be opened.
*/
public MyConnection (){
}
/**
* Create PServerConnection and setup it's properties from the supplied
* CVSRoot object.
* @throws IllegalArgumentException if the cvsRoot does not represent pserver
* connection type.
*/
public MyConnection(CVSRoot cvsRoot) {
this(cvsRoot, null);
}
/**
* Create PServerConnection and setup it's properties from the supplied
* CVSRoot object.
* @throws IllegalArgumentException if the cvsRoot does not represent pserver
* connection type.
*/
public MyConnection(CVSRoot cvsRoot, SocketFactory factory) {
if (!CVSRoot.METHOD_PSERVER.equals(cvsRoot.getMethod())) {
throw new IllegalArgumentException("CVS Root '"+cvsRoot+"' does not represent :pserver: connection type.");
}
socketFactory = factory;
String userName = cvsRoot.getUserName();
if (userName == null) {
userName = System.getProperty("user.name");
}
setUserName(userName);
String password = cvsRoot.getPassword();
if (password != null) {
setEncodedPassword(StandardScrambler.getInstance().scramble(password));
}
setHostName(cvsRoot.getHostName());
setRepository(cvsRoot.getRepository());
int port = cvsRoot.getPort();
if (port == 0) {
port = 2401; // The default pserver port
}
setPort(port);
}
/**
* Authenticate a connection with the server, using the specified
* postamble and preamble.
*
* @param preamble the preamble to use
* @param postamble the postamble to use
*
* @throws AuthenticationException if an error occurred
* @return the socket used to make the connection. The socket is
* guaranteed to be open if an exception has not been thrown
*/
private void openConnection(String preamble, String postamble)
throws AuthenticationException, CommandAbortedException {
if (hostName == null) {
String locMessage = "The hostname was null, can't continue."; //NOI18N
throw new AuthenticationException("HostIsNull", locMessage); //NOI18N
}
try {
SocketFactory sf = (socketFactory != null) ? socketFactory : SocketFactory.getDefault();
socket = sf.createSocket(hostName, port);
BufferedOutputStream bos =
new BufferedOutputStream(socket.getOutputStream(), 32768);
LoggedDataOutputStream outputStream = new LoggedDataOutputStream(bos);
setOutputStream(outputStream);
BufferedInputStream bis =
new BufferedInputStream(socket.getInputStream(), 32768);
LoggedDataInputStream inputStream = new LoggedDataInputStream(bis);
setInputStream(inputStream);
outputStream.writeBytes(preamble, "US-ASCII");
outputStream.writeBytes(getRepository() + "\n"); //NOI18N
outputStream.writeBytes(userName + "\n"); //NOI18N
outputStream.writeBytes(getEncodedPasswordNotNull() + "\n", "US-ASCII"); //NOI18N
outputStream.writeBytes(postamble, "US-ASCII");
outputStream.flush();
if (Thread.interrupted()) {
reset();
String localMsg = CommandException.getLocalMessage("Client.connectionAborted", null); //NOI18N
throw new CommandAbortedException("Aborted during connecting to the server.", localMsg); // NOI18N
}
// read first 11 bytes only (AUTHENTICATION_SUCCEEDED_RESPONSE\n)
// I observed lock caused by missing '\n' in reponse
// this method then blocks forever
byte rawResponse[] = inputStream.readBytes(AUTHENTICATION_SUCCEEDED_RESPONSE_RAW.length());
String response = new String(rawResponse, "utf8"); // NOI18N
if (Thread.interrupted()) {
reset();
String localMsg = CommandException.getLocalMessage("Client.connectionAborted", null); //NOI18N
throw new CommandAbortedException("Aborted during connecting to the server.", localMsg); // NOI18N
}
if (AUTHENTICATION_SUCCEEDED_RESPONSE_RAW.equals(response)) {
return;
}
if (AUTHENTICATION_FAILED_RESPONSE_RAW.equals(response)) {
String localizedMsg = getLocalMessage("AuthenticationException.badPassword",
null);
throw new AuthenticationException("AuthenticationFailed", //NOI18N
localizedMsg);
}
if (response == null) response = ""; // NOI18N
String locMessage = getLocalMessage("AuthenticationException.AuthenticationFailed", //NOI18N
new Object[]{ response });
throw new AuthenticationException("AuthenticationFailed", //NOI18N
locMessage);
}
catch (AuthenticationException ex) {
reset();
throw ex;
}
catch (ConnectException ex) {
reset();
String locMessage =
getLocalMessage("AuthenticationException.ConnectException", //NOI18N
new Object[]{hostName, Integer.toString(port)});
throw new AuthenticationException("ConnectException", ex, //NOI18N
locMessage);
}
catch (NoRouteToHostException ex) {
reset();
String locMessage =
getLocalMessage("AuthenticationException.NoRouteToHostException", //NOI18N
new Object[]{hostName});
throw new AuthenticationException("NoRouteToHostException", ex, //NOI18N
locMessage);
}
catch (IOException ex) {
reset();
String locMessage =
getLocalMessage("AuthenticationException.IOException", //NOI18N
new Object[]{hostName});
throw new AuthenticationException("IOException", ex, locMessage); //NOI18N
}
/* catch (Throwable t) {
reset();
String locMessage = AuthenticationException.getBundleString(
"AuthenticationException.Throwable"); //NOI18N
throw new AuthenticationException("General error", t, locMessage); //NOI18N
}
*/
}
private void reset() {
socket = null;
setInputStream(null);
setOutputStream(null);
}
/**
* Authenticate with the server.
* Closes the connection immediately. Clients can use this method to ensure
* that they are capable of authenticating with the server. If no exception
* is thrown, you can assume that authentication was successful.
*
* @throws AuthenticationException if the connection with the server
* cannot be established
*/
public void verify() throws AuthenticationException {
try {
openConnection(VERIFY_PREAMBLE, VERIFY_POSTAMBLE);
} catch (CommandAbortedException caex) {
// Ignore, follow the next steps
}
if (socket == null) {
return;
}
try {
socket.close();
}
catch (IOException exc) {
String locMessage = "An IO Exception occured when verifying: " + exc.getMessage(); //NOI18N
throw new AuthenticationException("General error", exc, locMessage); //NOI18N
}
finally {
reset();
}
}
/**
* Authenticate with the server and open a channel of communication with
* the server.
* This Client will call this method before interacting with the server. It
* is up to implementing classes to ensure that they are configured to talk
* to the server (e.g. port number etc.).
*
* @throws AutenticationException if the connection with the server
* cannot be established
*/
public void open() throws AuthenticationException, CommandAbortedException {
openConnection(OPEN_PREAMBLE, OPEN_POSTAMBLE);
}
/**
* Get the username.
*/
public String getUserName() {
return userName;
}
/**
* Set the userName.
* @param name the userName
*/
public void setUserName(String userName) {
this.userName = userName;
}
/**
* Get the encoded password.
* @return the encoded password
*/
public String getEncodedPassword() {
return encodedPassword;
}
private String getEncodedPasswordNotNull() {
if (encodedPassword == null) {
return StandardScrambler.getInstance().scramble("");
}
return encodedPassword;
}
/**
* Set the encoded password.
* @param password the encoded password to use for authentication
*/
public void setEncodedPassword(String encodedPassword) {
this.encodedPassword = encodedPassword;
}
/**
* Get the port number to use.
* @return the port number
*/
public int getPort() {
return port;
}
/**
* Set the port number to use.
* @param thePort the port number to use. If you do not set this, 2401
* is used by default for pserver.
*/
public void setPort(int port) {
this.port = port;
}
/**
* Get the host name to use.
* @return the host name of the server to connect to. If you do not set
* this, localhost is used by default for pserver.
*/
public String getHostName() {
return hostName;
}
/**
* Get the host name to use.
* @param theHostName the host name of the server to connect to. If you
* do not set this, localhost is used by default for pserver.
*/
public void setHostName(String hostName) {
this.hostName = hostName;
}
/**
* Close the connection with the server.
*/
public void close() throws IOException {
if (!isOpen()) {
return;
}
try {
socket.close();
}
finally {
reset();
}
}
/**
* Modify the underlying inputstream.
* @param modifier the connection modifier that performs the modifications
* @throws IOException if an error occurs modifying the streams
*/
public void modifyInputStream(ConnectionModifier modifier)
throws IOException {
modifier.modifyInputStream(getInputStream());
}
/**
* Modify the underlying outputstream.
* @param modifier the connection modifier that performs the modifications
* @throws IOException if an error occurs modifying the streams
*/
public void modifyOutputStream(ConnectionModifier modifier)
throws IOException {
modifier.modifyOutputStream(getOutputStream());
}
private String getLocalMessage(String key, Object[] arguments) {
String locMessage = "An error occured: " + key;
if (locMessage == null) {
return null;
}
locMessage = MessageFormat.format(locMessage, arguments);
return locMessage;
}
/**
* Returns true to indicate that the connection was successfully established.
*/
public boolean isOpen() {
return socket != null;
}
}