Java tutorial
/* * Copyright 2007 The Board of Trustees of the University of Illinois. * All rights reserved. * * Developed by: * * MyProxy Team * National Center for Supercomputing Applications * University of Illinois * http://myproxy.ncsa.uiuc.edu/ * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal with the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimers. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimers in the * documentation and/or other materials provided with the distribution. * * Neither the names of the National Center for Supercomputing * Applications, the University of Illinois, nor the names of its * contributors may be used to endorse or promote products derived from * this Software without specific prior written permission. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. */ package edu.uiuc.ncsa.myproxy; import edu.uiuc.ncsa.security.core.exceptions.GeneralException; import edu.uiuc.ncsa.security.core.util.HostUtil; import edu.uiuc.ncsa.security.core.util.MyLoggingFacade; import edu.uiuc.ncsa.security.util.pkcs.CertUtil; import edu.uiuc.ncsa.security.util.pkcs.KeyUtil; import edu.uiuc.ncsa.security.util.pkcs.MyPKCS10CertRequest; import edu.uiuc.ncsa.security.util.ssl.MyTrustManager; import org.apache.commons.codec.binary.Base64; import javax.net.ssl.*; import javax.security.auth.login.FailedLoginException; import java.io.*; import java.net.ProtocolException; import java.net.UnknownHostException; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.logging.Logger; /** * The MyProxyLogon class provides an interface for retrieving credentials from * a MyProxy server. * <p/> * First, use * <code> * <ul> * <li>setHost</li> * <li>setPort</li> * <li>setUsername</li> * <li>setPassphrase</li> * <li>setCredentialName</li> * <li>setLifetime</li> * <li>requestTrustRoots</li> * </ul> * </code> * to configure. Then call * <code> * <ul> * <li>logon</li> * <li>getCredentials</li> * <li>disconnect</li> * </ul> * </code> * Use <code>getCertificates</code> and * <code>getPrivateKey</code> to access the retrieved credentials, or * <code>writeProxyFile</code> or <code>saveCredentialsToFile</code> to * write them to a file. Use <code>writeTrustRoots</code>, * <code>getTrustedCAs</code>, <code>getCRLs</code>, * <code>getTrustRootData</code>, and <code>getTrustRootFilenames</code> * for trust root information. * * @version 1.6 * @see <a href="http://myproxy.ncsa.uiuc.edu/">MyProxy Project Home Page</a> */ public class MyProxyLogon { public MyLoggingFacade getMlf() { return mlf; } MyLoggingFacade mlf = null; // public final static String version = "1.8"; protected enum State { READY, CONNECTED, LOGGEDON, DONE } long socketTimeout = 0L; private final static int b64linelen = 64; private final static String X509_USER_PROXY_FILE = "x509up_u"; private final static String VERSION = "VERSION=MYPROXYv2"; private final static String GETCOMMAND = "COMMAND=0"; private final static String TRUSTROOTS = "TRUSTED_CERTS="; private final static String USERNAME = "USERNAME="; private final static String PASSPHRASE = "PASSPHRASE="; private final static String LIFETIME = "LIFETIME="; private final static String CREDNAME = "CRED_NAME="; private final static String RESPONSE = "RESPONSE="; private final static String ERROR = "ERROR="; private final static String DN = "CN=ignore"; private final static String TRUSTED_CERT_PATH = "/.globus/certificates"; public final int DEFAULT_KEY_SIZE = 2048; protected int keySize = DEFAULT_KEY_SIZE; protected final int MIN_PASS_PHRASE_LEN = 6; protected final static String keyAlg = "RSA"; protected final static String pkcs10SigAlgName = "SHA1withRSA"; protected final static String pkcs10Provider = "BC"; protected State state = State.READY; protected String host = "localhost"; protected String username; protected String credname; protected String passphrase; protected int port = 7512; protected int lifetime = 43200; protected boolean requestTrustRoots = false; protected SSLSocket socket; protected BufferedInputStream socketIn; protected BufferedOutputStream socketOut; protected KeyPair keypair; protected Collection<X509Certificate> certificateChain; protected String[] trustrootFilenames; protected String[] trustrootData; KeyManagerFactory keyManagerFactory; /** * Set the socket timeout (in milliseconds) for this connection. If this is not set, * then this is equivalent to setting it to 0, which in turn means that whatever the * system default is will be used. * * @return */ public long getSocketTimeout() { return socketTimeout; } public void setSocketTimeout(long socketTimeout) { this.socketTimeout = socketTimeout; } public MyProxyLogon(MyLoggingFacade myLoggingFacade) { super(); this.mlf = myLoggingFacade; } public MyProxyLogon(MyLoggingFacade myLoggingFacade, String serverDN) { super(); this.mlf = myLoggingFacade; this.serverDN = serverDN; } /** * Constructs a MyProxyLogon object. This turns off any logging, so only use this * constructor if you need to do that. Otherwise, inject your favorite logger wrapped * in an {@link MyLoggingFacade} object. */ public MyProxyLogon() { super(); Logger logger = Logger.getLogger(MyProxyLogon.class.getName()); logger.setUseParentHandlers(false); MyLoggingFacade facade = new MyLoggingFacade(logger); this.mlf = facade; host = System.getenv("MYPROXY_SERVER"); if (host == null) { host = "localhost"; } String portString = System.getenv("MYPROXY_SERVER_PORT"); if (portString != null) { port = Integer.parseInt(portString); } username = System.getProperty("user.name"); } /** * Gets the hostname of the MyProxy server. * * @return MyProxy server hostname */ public String getHost() { return this.host; } /** * This is the *real* host. The user may set the host property but this should be used internally * since it will do any reverse lookups needed. * * @return * @throws UnknownHostException */ protected String hostLookup() throws UnknownHostException { return HostUtil.canonicalName(getHost()); } /** * Sets the hostname of the MyProxy server. Defaults to localhost. * * @param host MyProxy server hostname */ public void setHost(String host) { this.host = host; } /** * Gets the port of the MyProxy server. * * @return MyProxy server port */ public int getPort() { return this.port; } /** * Sets the port of the MyProxy server. Defaults to 7512. * * @param port MyProxy server port */ public void setPort(int port) { this.port = port; } /** * Gets the key size. If this has not been set, it will be set to the default * * @return MyProxy key size */ public int getKeySize() { return keySize; } /** * Sets the key size. * * @param keySize */ public void setKeySize(int keySize) { this.keySize = keySize; } /** * Gets the MyProxy username. * * @return MyProxy server port */ public String getUsername() { return this.username; } /** * Sets the MyProxy username. Defaults to user.name. * * @param username MyProxy username */ public void setUsername(String username) { this.username = username; } /** * Gets the optional MyProxy credential name. * * @return credential name */ public String getCredentialName() { return this.credname; } /** * Sets the optional MyProxy credential name. * * @param credname credential name */ public void setCredentialName(String credname) { this.credname = credname; } /** * Sets the MyProxy passphrase. * * @param passphrase MyProxy passphrase */ public void setPassphrase(String passphrase) { this.passphrase = passphrase; } /** * Gets the requested credential lifetime. * * @return Credential lifetime */ public int getLifetime() { return this.lifetime; } /** * Sets the requested credential lifetime. Defaults to 43200 seconds (12 * hours). * * @param seconds Credential lifetime */ public void setLifetime(int seconds) { this.lifetime = seconds; if (this.lifetime < 0) { getMlf().warn( "Negative cert lifetime of " + this.lifetime + " encountered. Server should default to 0."); } } /** * Gets the certificates returned from the MyProxy server by * getCredentials(). * * @return Collection of java.security.cert.Certificate objects */ public Collection<X509Certificate> getCertificates() { return this.certificateChain; } /** * Gets the private key generated by getCredentials(). * * @return PrivateKey */ public PrivateKey getPrivateKey() { return this.keypair.getPrivate(); } /** * Sets whether to request trust roots (CA certificates, CRLs, signing * policy files) from the MyProxy server. Defaults to false (i.e., not * to request trust roots). * * @param flag If true, request trust roots. If false, don't request trust * roots. */ public void requestTrustRoots(boolean flag) { this.requestTrustRoots = flag; } /** * Gets trust root filenames. * * @return trust root filenames */ public String[] getTrustRootFilenames() { return this.trustrootFilenames; } /** * Gets trust root data corresponding to the trust root filenames. * * @return trust root data */ public String[] getTrustRootData() { return this.trustrootData; } /** * This will be used against the server's DN by the trust manager when checking the certificate. This * allows for setting a single DN across a set of servers (e.g. for load balancing). * * @return */ public String getServerDN() { return serverDN; } public void setServerDN(String serverDN) { this.serverDN = serverDN; } String serverDN = null; /** * Connects to the MyProxy server at the desired host and port. Requires * host authentication via SSL. The host's certificate subject must * match the requested hostname. If CA certificates are found in the * standard GSI locations, they will be used to verify the server's * certificate. If trust roots are requested and no CA certificates are * found, the server's certificate will still be accepted. */ public void connect() throws IOException, GeneralSecurityException { try { SSLContext sc = SSLContext.getInstance("SSL"); MyTrustManager mtm = new MyTrustManager(getMlf(), getExistingTrustRootPath(), getServerDN()); mtm.setHost(hostLookup()); TrustManager[] trustAllCerts = new TrustManager[] { mtm }; sc.init(getKeyManagers(), trustAllCerts, new java.security.SecureRandom()); SSLSocketFactory sf = sc.getSocketFactory(); this.socket = (SSLSocket) sf.createSocket(this.hostLookup(), this.port); if (0 < getSocketTimeout()) { // NOTE that this is an integer that is used for milliseconds. socket.setSoTimeout((int) getSocketTimeout()); } this.socket.startHandshake(); this.socketIn = new BufferedInputStream(this.socket.getInputStream()); this.socketOut = new BufferedOutputStream(this.socket.getOutputStream()); this.state = State.CONNECTED; } catch (Throwable t) { handleException(t, getClass().getSimpleName() + " could not connect to the server, socket " + (this.socket == null ? "" : "not") + " created."); } } protected void handleException(Throwable t, String msg) throws IOException, GeneralSecurityException { if (t instanceof SSLHandshakeException) { throw new GeneralException("Error connecting to server:" + t.getMessage(), t); } if (t instanceof IOException) { throw (IOException) t; } if (t instanceof GeneralSecurityException) { throw (GeneralSecurityException) t; } throw new GeneralSecurityException("Error: " + msg, t); } /** * Get the key manager factory set by setKeyManagerFactory(). * * @return KeyManagerFactory */ public KeyManagerFactory getKeyManagerFactory() { return keyManagerFactory; } /** * Set the key manager factory for use in client-side SSLSocket * certificate-based authentication to the MyProxy server. * Call this before connect(). * * @param keyManagerFactory Key manager factory to use */ public void setKeyManagerFactory(KeyManagerFactory keyManagerFactory) { this.keyManagerFactory = keyManagerFactory; } /** * Internal method that returns the KeyManagers for a KeyManagerFactory or a null if no KeyManagerFactory is set. * * @return */ KeyManager[] getKeyManagers() { if (getKeyManagerFactory() == null) { return null; } return getKeyManagerFactory().getKeyManagers(); } /** * Disconnects from the MyProxy server. */ public void disconnect() throws IOException, GeneralSecurityException { try { this.socket.close(); this.socket = null; this.socketIn = null; this.socketOut = null; this.state = State.READY; } catch (Throwable t) { handleException(t, getClass().getSimpleName() + " could not disconnect from the server, socket " + (this.socket == null ? "" : "not") + " created"); } } /** * Logs on to the MyProxy server by issuing the MyProxy GET command. */ public void logon() throws IOException, GeneralSecurityException { String line; char response; if (this.state != State.CONNECTED) { this.connect(); } try { this.socketOut.write('0'); this.socketOut.flush(); this.socketOut.write(VERSION.getBytes()); this.socketOut.write('\n'); this.socketOut.write(GETCOMMAND.getBytes()); this.socketOut.write('\n'); this.socketOut.write(USERNAME.getBytes()); this.socketOut.write(this.username.getBytes()); this.socketOut.write('\n'); this.socketOut.write(PASSPHRASE.getBytes()); this.socketOut.write(this.passphrase.getBytes()); this.socketOut.write('\n'); this.socketOut.write(LIFETIME.getBytes()); this.socketOut.write(Integer.toString(this.lifetime).getBytes()); this.socketOut.write('\n'); if (this.credname != null) { this.socketOut.write(CREDNAME.getBytes()); this.socketOut.write(this.credname.getBytes()); this.socketOut.write('\n'); } if (this.requestTrustRoots) { this.socketOut.write(TRUSTROOTS.getBytes()); this.socketOut.write("1\n".getBytes()); } this.socketOut.flush(); line = readLine(this.socketIn); if (line == null) { throw new EOFException(); } if (!line.equals(VERSION)) { throw new ProtocolException("bad MyProxy protocol VERSION string: " + line); } line = readLine(this.socketIn); if (line == null) { throw new EOFException(); } if (!line.startsWith(RESPONSE) || line.length() != RESPONSE.length() + 1) { throw new ProtocolException("bad MyProxy protocol RESPONSE string: " + line); } response = line.charAt(RESPONSE.length()); if (response == '1') { StringBuffer errString; errString = new StringBuffer("MyProxy logon failed"); while ((line = readLine(this.socketIn)) != null) { if (line.startsWith(ERROR)) { errString.append('\n'); errString.append(line.substring(ERROR.length())); } } throw new FailedLoginException(errString.toString()); } else if (response == '2') { throw new ProtocolException("MyProxy authorization RESPONSE not implemented"); } else if (response != '0') { throw new ProtocolException("unknown MyProxy protocol RESPONSE string: " + line); } while ((line = readLine(this.socketIn)) != null) { if (line.startsWith(TRUSTROOTS)) { String filenameList = line.substring(TRUSTROOTS.length()); this.trustrootFilenames = filenameList.split(","); this.trustrootData = new String[this.trustrootFilenames.length]; for (int i = 0; i < this.trustrootFilenames.length; i++) { String lineStart = "FILEDATA_" + this.trustrootFilenames[i] + "="; line = readLine(this.socketIn); if (line == null) { throw new EOFException(); } if (!line.startsWith(lineStart)) { throw new ProtocolException("bad MyProxy protocol RESPONSE: expecting " + lineStart + " but received " + line); } this.trustrootData[i] = new String(Base64.decodeBase64(line.substring(lineStart.length()))); } } } this.state = State.LOGGEDON; } catch (Throwable t) { handleException(t, getClass().getSimpleName() + " logon failed."); } } public void getCredentials(byte[] derEncodedCertRequest) throws IOException, GeneralSecurityException { try { if (this.state != State.LOGGEDON) { this.logon(); } this.socketOut.write(derEncodedCertRequest); this.socketOut.flush(); int numCertificates = this.socketIn.read(); if (numCertificates == -1) { System.err.println("connection aborted"); throw new IOException("Error: connection aborted"); } else if (numCertificates == 0 || numCertificates < 0) { System.err.print("bad number of certificates sent by server: "); System.err.println(Integer.toString(numCertificates)); throw new GeneralSecurityException("Error: bad number of certificates sent by server"); } CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); this.certificateChain = (Collection<X509Certificate>) certFactory.generateCertificates(this.socketIn); this.state = State.DONE; } catch (Throwable t) { handleException(t, getClass().getSimpleName() + " failure getting the credential."); } } /** * Retrieves credentials from the MyProxy server. */ public void getCredentials() throws IOException, GeneralSecurityException { KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance(keyAlg); keyGenerator.initialize(getKeySize()); this.keypair = keyGenerator.genKeyPair(); MyPKCS10CertRequest pkcs10 = CertUtil.createCertRequest(this.keypair, pkcs10SigAlgName, DN, pkcs10Provider); getCredentials(pkcs10.getEncoded()); } /** * Writes the retrieved credentials to the Globus proxy file location. */ public void writeProxyFile() throws IOException, GeneralSecurityException { saveCredentialsToFile(getProxyLocation()); } /** * Writes the retrieved credentials to the specified output stream. * * @param os OutputStream to write to * @throws IOException * @throws GeneralSecurityException */ public void saveCredentials(OutputStream os) throws IOException, GeneralSecurityException { CertUtil.toPEM(certificateChain, os); // since we are putting the private key in the same file as the certs, we have to // add a new line. os.write('\n'); KeyUtil.toPKCS1PEM(keypair.getPrivate(), os); } public X509Certificate getCertificate() { if (certificateChain == null) { return null; } Iterator<X509Certificate> iter = this.certificateChain.iterator(); return iter.next(); } /** * Writes the retrieved credentials to the specified filename. */ public void saveCredentialsToFile(String filename) throws IOException, GeneralSecurityException { File outFile = new File(filename); outFile.delete(); outFile.createNewFile(); setFilePermissions(filename, "0600"); FileOutputStream fos = new FileOutputStream(outFile); saveCredentials(fos); fos.flush(); fos.close(); } /** * Writes the retrieved trust roots to the Globus trusted certificates * directory. * * @return true if trust roots are written successfully, false if no * trust roots are available to be written */ public boolean writeTrustRoots() throws IOException { return writeTrustRoots(getTrustRootPath()); } /** * Writes the retrieved trust roots to a trusted certificates directory. * * @param directory path where the trust roots should be written * @return true if trust roots are written successfully, false if no * trust roots are available to be written */ public boolean writeTrustRoots(String directory) throws IOException { if (this.trustrootFilenames == null || this.trustrootData == null) { return false; } File rootDir = new File(directory); if (!rootDir.exists()) { rootDir.mkdirs(); } for (int i = 0; i < trustrootFilenames.length; i++) { FileOutputStream out = new FileOutputStream(directory + File.separator + this.trustrootFilenames[i]); out.write(this.trustrootData[i].getBytes()); out.close(); } return true; } /** * Gets the trusted CA certificates returned by the MyProxy server. * * @return trusted CA certificates, or null if none available */ public X509Certificate[] getTrustedCAs() throws CertificateException { if (trustrootData == null) return null; return CertUtil.getX509CertsFromStringList(trustrootData, trustrootFilenames); } /** * Gets the CRLs returned by the MyProxy server. * * @return CRLs or null if none available */ public X509CRL[] getCRLs() throws CertificateException { if (trustrootData == null) return null; CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); Collection<X509CRL> c = new ArrayList<X509CRL>(trustrootData.length); for (int i = 0; i < trustrootData.length; i++) { String crlData = trustrootData[i]; int index = crlData.indexOf("-----BEGIN X509 CRL-----"); if (index >= 0) { crlData = crlData.substring(index); ByteArrayInputStream inputStream = new ByteArrayInputStream(crlData.getBytes()); try { X509CRL crl = (X509CRL) certFactory.generateCRL(inputStream); c.add(crl); } catch (Exception e) { getMlf().warn(this.trustrootFilenames[i] + " can not be parsed as an X509CRL."); } } } if (c.isEmpty()) return null; return c.toArray(new X509CRL[0]); } /** * Returns the trusted certificates directory location where * writeTrustRoots() will store certificates. */ public static String getTrustRootPath() { String path; path = System.getenv("X509_CERT_DIR"); if (path == null) { path = System.getProperty("X509_CERT_DIR"); } if (path == null) { path = System.getProperty("user.home") + TRUSTED_CERT_PATH; } return path; } /** * Gets the existing trusted CA certificates directory. * * @return directory path string or null if none found */ public static String getExistingTrustRootPath() { String path, GL; GL = System.getenv("GLOBUS_LOCATION"); if (GL == null) { GL = System.getProperty("GLOBUS_LOCATION"); } path = System.getenv("X509_CERT_DIR"); if (path == null) { path = System.getProperty("X509_CERT_DIR"); } if (path == null) { path = getDir(System.getProperty("user.home") + TRUSTED_CERT_PATH); } if (path == null) { path = getDir("/etc/grid-security/certificates"); } if (path == null) { path = getDir(GL + File.separator + "share" + File.separator + "certificates"); } return path; } /** * Returns the default Globus proxy file location. */ public static String getProxyLocation() throws IOException { String loc, suffix = null; Process proc; BufferedReader bufferedReader; loc = System.getenv("X509_USER_PROXY"); if (loc == null) { loc = System.getProperty("X509_USER_PROXY"); } if (loc != null) { return loc; } try { proc = Runtime.getRuntime().exec("id -u"); bufferedReader = new BufferedReader(new InputStreamReader(proc.getInputStream())); suffix = bufferedReader.readLine(); } catch (IOException e) { // will fail on windows } if (suffix == null) { suffix = System.getProperty("user.name"); if (suffix != null) { suffix = suffix.toLowerCase(); } else { suffix = "nousername"; } } if (File.separator.equals("/")) { // Unix return "/tmp/" + X509_USER_PROXY_FILE + suffix; } else { return System.getProperty("java.io.tmpdir") + File.separator + X509_USER_PROXY_FILE + suffix; } } /** * Provides a simple command-line interface. */ public static void main(String[] args) { try { Logger logger = Logger.getLogger(MyProxyLogon.class.getName()); logger.setUseParentHandlers(false); MyLoggingFacade facade = new MyLoggingFacade(logger); MyProxyLogon m = new MyProxyLogon(facade); //MyLoggingFacade myLoggingFacade = new MyLoggingFacade(MyProxyLogon.class.getName()); //MyProxyLogon m = new MyProxyLogon(myLoggingFacade); // Console cons = System.console(); String passphrase = null; X509Certificate[] CAcerts; X509CRL[] CRLs; //MyProxyLogon.logger.setLevel(Level.ALL); // Turn on for debugging if needed. // if (cons != null) { // char[] pass = cons.readPassword("[%s]", "MyProxy Passphrase: // "); // if (pass != null) { // passphrase = new String(pass); // } // } else { System.out.println("Warning: terminal will echo passphrase as you type."); System.out.print("MyProxy Passphrase: "); passphrase = m.readLine(System.in); // } if (passphrase == null) { System.err.println("Error reading passphrase."); System.exit(1); } m.setPassphrase(passphrase); m.requestTrustRoots(true); m.getCredentials(); m.writeProxyFile(); System.out.println("Credential written successfully."); CAcerts = m.getTrustedCAs(); if (CAcerts != null) { System.out.println(Integer.toString(CAcerts.length) + " CA certificates received."); } CRLs = m.getCRLs(); if (CRLs != null) { System.out.println(Integer.toString(CRLs.length) + " CRLs received."); } if (m.writeTrustRoots()) { System.out.println("Wrote trust roots to " + MyProxyLogon.getTrustRootPath() + "."); } else { System.out.println("Received no trust roots from MyProxy server."); } } catch (Exception e) { e.printStackTrace(System.err); } } private void setFilePermissions(String file, String mode) { String command = "chmod " + mode + " " + file; try { Runtime.getRuntime().exec(command); } catch (IOException e) { getMlf().warn("Failed to run: " + command); // windows } } private String readLine(InputStream is) throws IOException { StringBuffer sb = new StringBuffer(); for (int c = is.read(); c > 0 && c != '\n'; c = is.read()) { sb.append((char) c); } if (sb.length() > 0) { return new String(sb); } return null; } private static String getDir(String path) { if (path == null) return null; File f = new File(path); if (f.isDirectory() && f.canRead()) { return f.getAbsolutePath(); } return null; } public boolean isReady() { return this.state == State.READY; } public boolean isConnected() { // Cannot be logged on unless connected, so this should check both. return (this.state == State.CONNECTED) || (this.state == State.LOGGEDON); } public boolean isLoggedOn() { return this.state == State.LOGGEDON; } public boolean isDone() { return this.state == State.DONE; } }