/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.netbeans.lib.cvsclient.connection; 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.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 PServerConnection 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 PServerConnection() { } /** * Create PServerConnection and setup it's properties from the supplied CVSRoot object. * * @throws IllegalArgumentException * if the cvsRoot does not represent pserver connection type. */ public PServerConnection(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 PServerConnection(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 = AuthenticationException.getBundleString("AuthenticationException.HostIsNull"); // 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"); // System.out.println("userName='"+userName+"'"); // System.out.println (preamble+getRepository()+ "\n"+userName+"\n"+getEncodedPasswordNotNull() + "\n"+postamble); 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 // System.out.println("Response:"+response); 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 */ @Override 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 = AuthenticationException.getBundleString("AuthenticationException.Throwable"); // 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 */ @Override 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 */ @Override 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. */ @Override 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 */ @Override 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 */ @Override public void modifyOutputStream(ConnectionModifier modifier) throws IOException { modifier.modifyOutputStream(getOutputStream()); } private String getLocalMessage(String key, Object[] arguments) { String locMessage = AuthenticationException.getBundleString(key); if (locMessage == null) { return null; } locMessage = MessageFormat.format(locMessage, arguments); return locMessage; } /** * Returns true to indicate that the connection was successfully established. */ @Override public boolean isOpen() { return socket != null; } }