Skip to main content.

Web Based Programming Tutorials

Homepage | Forum - Join the forum to discuss anything related to programming! | Programming Resources

Java Expert Solutions

Chapter 28 -- Accessing Remote Systems Securely

Chapter 28

Accessing Remote Systems Securely

by Mark Wutka


CONTENTS



The huge explosion of Internet access is both a blessing and a curse to businesses on the Net. Because Internet data passes over insecure networks, a company must be extremely careful when passing sensitive data over the Internet. In the past, it was difficult for companies to communicate with their employees out in the field, because networking technology was fairly primitive, and laptop computers were not very portable, or too powerful. This was especially a problem for sales force automation. A salesman may be on the road for weeks or months at a time, unable to communicate with the home base. Figure 28.1 illustrates the old design of a sales force automation network.

Figure 28.1 : In the past, companies had to create their own sales force automation networks.

Now, laptop computers are extremely powerful with a wide range of networking options, and Internet access is available all over the world. Figure 28.2 shows how the Internet has changed the environment for sales force automation.

Figure 28.2 : The widespread availability of the Internet eliminates the need for custom networks.

It seems like things should be easier for people on the road, and it is for people at some companies. For others, things haven't gotten any better.

The irony here is that it is still just as difficult to access secure data over the Net. The widespread infiltration of the Internet, which makes it easier for employees to communicate back to their home system, also increases the likelihood that the data can be intercepted. A devious spy from another company could snoop on private data transmissions over the Internet.

In addition, many companies have custom software that must be adapted to work over the Net. Whenever the software is updated, it is very difficult to distribute the new versions to the people in the field. The company must either ship new diskettes, ship out a new laptop, wait until the salesperson is back in the office, or download software over the network. The latter is a very difficult endeavor, most often meeting with failure.

Java can help solve the distribution problem. With Java, you can either write the custom software as applets, which can be cached on the local hard drive (as shown in Figure 28.3), or create a software distribution applet that downloads new versions of the local applications, as shown in Figure 28.4. A digitally signed applet can download updates to the local software, as well as update any information stored on the local hard disk.

Figure 28.3 : Because applets are downloaded at runtime, you can eliminate some installation headaches.

Figure 28.4 : A custom software installation applet can also assist in software installation.

You still have the problem of downloading important information without someone intercepting the information. The Secure Socket Layer (SSL) protocol enables you to access Web pages securely. SSL uses a combination of a digitally signed certificate and an encrypted network session.

Getting a Secure Web Server

If you want to use SSL to download your applets, you must get a Web server that supports the SSL protocol. Netscape Communications (http://home.netscape.com) has a whole line of Web servers, all of which support the SSL protocol. The secure Web server from Open Market Systems (http://www.openmarket.com) also supports SSL.

There is also a version of the Apache Web server that supports SSL. If you are within the United States, you can download the Apache-SSL Web server from Community Connexion, Inc. (http://www.c2.org). Apache-SSL is free for non-commercial use. If you want to use it for commercial purposes, you must buy a license from C2.

Caution
Don't forget that it is illegal to export many cryptography programs outside the U.S. If you are outside the U.S., you may not be able to download these programs legally. If you are inside the U.S., you should take extra care to ensure that no one from outside the U.S. can download any secure Web server you might have.

Once you get a secure Web server, you must also get a digitally signed certificate in order to use SSL. There are several certificate authorities around, one of which is Verisign (http://www.verisign.com). One of the nice things about the Apache-SSL server is that it will generate a local certificate that you can use for testing. You must still obtain a signed certificate if you want someone else to access your Web server securely.

Preventing Impersonations

The Secure Socket Layer, or another secure form of HTTP, is vital in downloading applets securely. When you download one of your company's applets, you must to be sure that it really came from your company. The digital signature mechanism warns you if you download an unsigned applet, which prevents all but the most devious impersonation attacks. If someone is able to digitally sign an applet with a signature you think is valid, you will not know anything is wrong. Of course, if this happens, it means that you have trusted someone you shouldn't have. Chapter 26, "Securing Applets with Digital Signatures," contains more information about digital signatures and their relationship to applets.

If you use SSL to download your applet, it cannot be impersonated, because SSL ensures that you are talking to the correct Web server.

Accessing Remote Data

You can use SSL to send and receive encrypted data without using any encryption software yourself. The URL class enables you to open up URLs with an https protocol type, which uses SSL for communications. Because of the way the applet security manager works, you cannot intermix http and https URL accesses in a single applet. If your applet was loaded using the http protocol type, you can open URLs only with a protocol type of http. If your applet was downloaded via https, you can open URLs only with a protocol type of https.

What the restriction means is that if you need to download information securely, you must also download the applet securely. It is one of the simple ways that Java protects you from yourself. If you were allowed to download applets insecurely and then download information securely, you would be vulnerable to an impersonation attack.

Since the SSL support is built into the URL class (actually, into the browser itself), you can use the methods discussed in Chapter 6 "Communicating with a Web Server," to store and retrieve files using the URL class. You cannot use any of the socket mechanisms to do this, however.

Passing Keys to Clients

One of the difficulties in performing encrypted communications is agreeing on an encryption key. You have to have a secure way to even agree on a key; otherwise, someone could just eavesdrop on your key exchange and see what key you're using.

Don't Reuse Symmetric Keys

You might be tempted to generate a nice key for symmetric key encryption and let all your applets use that key. Unless you have a foolproof method of ensuring that only trustworthy people can get your applet (which is easier said than done), you run the risk of a malicious person downloading your applet and examining it to find the key you're using.

It should be obvious that just using SSL doesn't prevent someone from stealing your key. SSL only prevents someone from impersonating your server, and from watching your applet being downloaded (since the download is encrypted). The amount of risk involved in reusing a symmetric key isn't worth the trouble.

Using Public Key Encryption to Get a Private Key

Public key encryption is generally very costly. You would not want to carry on an interactive session using public key encryption. Public keys are very handy for exchanging a private key, however.

Suppose you had a client and a server connected together using an insecure TCP/IP socket, as shown in Figure 28.5.

Figure 28.5 : A client connects to a server using an insecure socket.

Next, the client generates a random private encryption key. The client then encrypts the private key using the server's public encryption key and passes the encrypted key to the server, as shown in Figure 28.6.

Figure 28.6 : The client encrypts a private key using the server's public key.

Now, the server can decrypt the private session key and the two can use the private key to exchange data. It is safe for you to embed the server's public key in the client applet, because even if someone looked at it, they wouldn't learn anything that isn't already public knowledge. Because each session key is randomly generated, no one else can figure out your session key just by downloading the applet. Anyone who did so would just generate a different key.

Passing a Private Key as an Applet Parameter

Because you are using secure sockets to download your applet, you can take advantage of the fact that the applet and its parent Web page are transmitted in encrypted form. Instead of the client generating the session key and using public key encryption to pass the session key to the server, the server can pass the key to the client applet using the <PARAM> tag.

This mechanism has some peculiar drawbacks to it, stemming from the fact that you must use CGI (or its equivalent) to pass the key information back. If you are lucky enough to have a Java Web server that also does SSL, you don't have to worry about this. Unfortunately, since many, if not all, of the current Java Web servers do not support SSL, you have to come up with some unique ways of passing keys around.

Note
Because Java 1.1 includes the SSL protocol, all Java Web servers should soon support SSL.

The problem here is that a CGI program is supposed to generate content in response to a GET or a POST and then exit. Figure 28.7 illustrates the typical life of a CGI program.

Figure 28.7 : In response to an http GET or POST, a CGI program starts up, generates a response, and exits.

Unfortunately, since the response isn't sent back to the client until the CGI program terminates, by the time the client receives the random private session key, the program that generated the key is gone.

There are a number of ways you can solve this problem. One way is to have your CGI program start up another program that also knows the session key. Once the client knows the session key, it connects to the program started by the CGI program. Figure 28.8 illustrates this sequence.

Figure 28.8 : A CGI program can spawn another program that communicates securely with the client.

This solution may be fine for some situations, but it has a number of drawbacks:

Another solution is a bit less taxing on the system resources, but is harder to implement. In this solution, the server is a program that is already running, in the same way that the Web server is always running. When a CGI program starts up, it requests a new session key from the server and then passes the key to the client. The server then listens for an incoming connection. Figure 28.9 illustrates this relationship.

Figure 28.9 : The CGI program gets a key from the server, which was already running.

This architecture has some advantages over the previous one:

Implementing a Single-Client Secure Server

If you need something more than a simple http GET or POST interface and you need encryption, you'll probably have to settle on a socket connection. If you can find a CORBA or RMI implementation that supports encryption, that would be much better.

For a single-client secure server, you need a CGI program that invokes your server program. Since most server programs have a number of similar features, it makes sense to create an abstract class that handles many of the common features. The SingleSecureServer class, shown in Listing 28.1, implements a number of useful methods for creating a single-client secure server. The server expects to be started by a CGI program, and passes the port number it is listening on and the secure session key back to the CGI program as output.


Listing 28.1  Source Code for SingleSecureServer.java
import java.io.*;
import java.net.*;

// This class implements a single-client secure server. It is implemented
// as an abstract class, leaving your specific server to fill in
// the handleNewClient and makeSessionKey methods.

public abstract class SingleSecureServer extends Object
     implements TimerCallback, Runnable
{
     protected int clientTimeoutPeriod = 300000;     // 5 minute timeout
     protected byte[] sessionKey;

     protected int tickCount;

     protected PrintStream responseStream; 

     protected ServerSocket serverSock;

     protected Thread thread;

     public SingleSecureServer(OutputStream responseStream)
     {
          try {
               this.responseStream = new PrintStream(
                    responseStream);
          } catch (Exception e) {
          }
     }

// Called to create the listen socket. Override this if you want a
// specific port number

     public ServerSocket createSocket()
     throws IOException
     {
          return new ServerSocket(0);
     }

// waitForClient waits for a client to connect to the server. It also
// sets up a timer that goes off after a certain amount of time, this
// allows us to quit if the client never connects.

     public void waitForClient()
     {
// Start the timer
          Timer timer = new Timer(this, clientTimeoutPeriod);
          tickCount = 0;
          timer.start();

          while (true) {
               try {
// Accept a new client
                    Socket sock = serverSock.accept();

                    serverSock.close();
// Turn off the timer now
                    timer.stop();

// Do whatever has to be done for the new client
                    handleNewClient(sock);

                    return;

               } catch (Exception e) {
               }
          }
     }

// This class interfaces with the CGI program in a kludgy way - it
// writes information to the output stream. The CGI program then
// gets an input stream to our output stream and reads the information.
// This method writes out the port number

     public void sendPortNumber(int port)
     {
          responseStream.println(port);
     }

// This method writes out the session key for the CGI program to read
     public void sendSessionKey()
     {
          responseStream.println(keyString(sessionKey));
     }

// This function is provided in the Integer class in JDK 1.0.2
// My poor Linux version is only 1.0.1, so I had to hack one up.

     public static String toHexString(int i)
     {
          char hexBytes[] = new char[2];

          hexBytes[0] = "0123456789abcdef".charAt((i >> 4)&0xf);
          hexBytes[1] = "0123456789abcdef".charAt(i&0xf);

          return new String(hexBytes);
     }

// This method converts a binary session key into a string of hex digits
     public static String keyString(byte[] key)
     {
          String returnVal = "";

          for (int i=0; i < key.length; i++) {
               returnVal += toHexString(key[i]&0xff);
          }

          return returnVal;
     }

// tick is called by the timer when it goes off. The timer is built to
// fire immediately, and then wait for a specific interval before
// going off again. We use the tick count to figure out if this is the
// immediate time, or if the time has elapsed.

     public void tick()
     {
// if tickCount is 1 after we increment it, this is just the first tick
// so don't do anything
          if (++tickCount == 1) return;

// otherwise, assume the client isn't connecting
          stop();
     }
     
// start does everything we need - it creates the socket, writes out
// the port number, creates the session key, writes it out, and then
// waits for an incoming client

     public void run()
     {
          serverSock = null;

// Create the socket we're going to listen on
          try {
               serverSock = createSocket();
          } catch (Exception e) {
               return;
          }

// tell the CGI program what the port number is
          sendPortNumber(serverSock.getLocalPort());

// create the session key
          makeSessionKey();

// tell the CGI program what the session key is
          sendSessionKey();

// make sure the CGI program gets all the information
          responseStream.flush();

// wait for a client to connect
          waitForClient();
     }

     public void start()
     {
          thread = new Thread(this);
          thread.start();
     }

     public void stop()
     {
          try {
               serverSock.close();
          } catch (Exception e) {
          }
          thread.stop();
          thread = null;
     }

// handleNewClient does something with the incoming socket, it's
// up to you to decide what
     public abstract void handleNewClient(Socket sock);

// makeSessionKey generates a session key.
     public abstract void makeSessionKey();
}

All your CGI program needs to do is start the server program, read two lines, and generate an HTML page that tells the requesting browser where to get the applet. While you could write the CGI program in any language you choose, Java seems like the ideal choice for this book. Listing 28.2 shows a CGI program that starts up a secure telnet server. This server uses an excellent Java Telnet applet written by Bret Dahlgren. The source code to the applet can be found on the World Wide Web at http://w3.gwis. com/~thorn/telnet/. In order to support secure telnet sessions, the Telnet application had to be modified slightly. It now supports a sessionKey parameter which, if present, tells it to use encryption for the session. The encryption used is DES3, provided by the Acme cryptography library.


Listing 28.2  Source Code for SecureLoginStartup.java
import java.net.*;
import java.io.*;

// This is a CGI program that starts up a secure Telnet session
// using a subclass of the SingleSecureServer class. It generates
// an HTML response that refers to a Telnet applet and passes the
// port number and session key to the telnet applet.
// It reads the port number and session key from the SingleSecureServer
// (actually the SecureLoginServer).

public class SecureLoginStartup extends Object
{

// Send an error response to the web browser - something went wrong
     public static void sendErrorResponse(String error)
     {
          System.out.println("Content-type: text/html");

// Gen the HTML on-the-fly. We put it in a string so we can
// compute the content length and be real polite

          String response = "<HTML><HEAD>\n";
          response += "<TITLE> Secure Login Error </TITLE>\n";
          response += "<BODY>\n";
          response += "<H1>Error establishing login.</H1>\n";
          response += "<P>"+error+"\n";
          response += "</BODY></HTML>";

          System.out.println("Content-length: "+response.length());
          System.out.println();
          System.out.println(response);
     }

// sendNormalResponse sends a web page that loads up the telnet
// applet for a specific port and session key

     public static void sendNormalResponse(int port, String key)
     {
          System.out.println("Content-type: text/html");

// Gen the HTML on-the-fly. We put it in a string so we can
// compute the content length and be real polite

          String response = "<HTML><HEAD>\n";
          response += "<TITLE> Secure Login Session </TITLE>\n";
          response += "<BODY>\n";
          response += "<H1>Secure Login</H1>\n";
          response += "<APPLET codebase=\"/classes\" ";
          response += "code=\"Telnet.class\" ";
          response += "width=600 height=400>\n";
          response += "<PARAM name=\"fields\" value=\"off\">\n";
                response += "<PARAM name=\"host\" value=\"";
                try {
               response += InetAddress.getLocalHost().getHostName();
          } catch (Exception e) {
               response += "localhost";
          }
          response += "\">\n";
          response += "<PARAM name=\"port\" value=\""+port+"\">\n";
          response += "<PARAM name=\"sessionKey\" value=\""+key+"\">\n";
          response += "You need Java for secure logins.\n";
          response += "</APPLET>\n";
          response += "</BODY></HTML>";

          System.out.println("Content-length: "+response.length());
          System.out.println();
          System.out.println(response);
     }
          
     public static void main(String[] args)
     {
          try {
// Start up the secure server program. You'll probably have to change
// this for your system.
               Process externProcess = Runtime.getRuntime().exec(
                    "/usr/local/java/bin/java SecureLoginServer");

// create an input stream for reading the parameters back from the server
               DataInputStream in = new DataInputStream(
                    externProcess.getInputStream());

// Read the port
               String portLine = in.readLine();
               int port = Integer.parseInt(portLine);

// Read the session key
               String sessionKey = in.readLine();

// Send the web page to the browser
               sendNormalResponse(port, sessionKey);

          } catch (Exception e) {
               sendErrorResponse(e.toString());
               return;
          }
     }
}         

Listing 28.3 shows the HTML information generated by this CGI program:


Listing 28.3  Output from SecureLoginStartup
Content-type: text/html
Content-length: 378
 
<HTML><HEAD>
<TITLE> Secure Login Session </TITLE>
<BODY>
<H1>Secure Login</H1>
<APPLET codebase="/classes" code="Telnet.class" width=600 height=400>
<PARAM name="fields" value="off">
<PARAM name="host" value="flamingo">
<PARAM name="port" value="1126">
<PARAM name="sessionKey" value="8b4243347b3a69b8aa12594153c15c8c">
You need Java for secure logins.
</APPLET>
</BODY></HTML>
The CGI program is started by a small shell script that sets the Java CLASSPATH variable before running:
#!/bin/sh
export CLASSPATH=/usr/local/etc/httpd/htdocs/classes:/usr/local/java/lib/classes.zip
/usr/local/java/bin/java SecureLoginStartup

The startup sequence for the secure Telnet applet is as follows:

  1. The Web browser opens up the URL for the CGI program (actually, the startup script for the CGI program).
  2. The CGI program creates an instance of a SingleSecureServer and initializes the server.
  3. The server opens up a ServerSocket to listen for incoming connections and creates a random session key. It returns both the port number and the session key to the CGI program via System.out.
  4. The CGI program generates an HTML page containing an <APPLET> tag for the Telnet applet and all the important parameters, including the session key.
  5. The Telnet applet starts up, connects to the server, and, using the session key for encryption, engages in an encrypted telnet session.

The class that actually creates the telnet connection and passes the information back and forth to the encryption routines is shown in Listing 28.4. It is fairly short. Essentially, it creates the telnet connection, and then uses a simple bridging class to link two streams together.


Listing 28.4  Source Code for SecureLoginClient.java
import java.io.*;
import java.net.*;
import Acme.Crypto.*;

// This class sets up an encrypted telnet session. It uses the StreamBridge
// class to transfer data between the telnet streams and the encrypted
// streams.

public class SecureLoginClient extends Object implements BridgeCloseCallback
{
     Socket socket;
     StreamBridge bridge1;
     StreamBridge bridge2;
     byte[] sessionKey;

     public SecureLoginClient(Socket socket, byte[] sessionKey)
     {
          this.socket = socket;
          this.sessionKey = sessionKey;
          start();
     }

     public void start()
     {
          try {

// Connect to the telnet port on the local host
               Socket telnetSock = new Socket(
                    InetAddress.getLocalHost(), 23);

// It is vital that you create the encryption streams in the reverse
// order from the other end. In other words, if you create the encrypted
//  output stream first here, you must create the encrypted input stream
// first at the other end. This is because the block cipher streams
// require some initial data over the stream. If you create the input
// streams first, both sides will be waiting for input.

// Connect the output from the telnet stream to the encrypted output
// stream

               bridge2 = new StreamBridge(
                    telnetSock.getInputStream(),
                    new EncryptedOutputStream(
                         new Des3Cipher(sessionKey),
                         socket.getOutputStream()), this, true);

// Connect the output from the encrypted stream to the telnet stream

               bridge1 = new StreamBridge(
                    new EncryptedInputStream(
                         new Des3Cipher(sessionKey),
                         socket.getInputStream()),
                    telnetSock.getOutputStream(), this, false);

               bridge1.start();
               bridge2.start();

          } catch (Exception e) {
               try {
                    socket.close();
               } catch (Exception ignore) {
               }
               return;
          }
     }

// bridgeClosed is called by the StreamBridge class whenever a
// stream closes. We just close off the socket to make sure
// everything will shut down properly

     public synchronized void bridgeClosed()
     {
          try {
               socket.close();
          } catch (Exception e) {
          }
     }
}

The SecureLoginServer class, which is a subclass of SingleSecureServer, does little more than create a session key and create the SecureLoginClient to handle the client connection. Most subclasses of SingleSecureServer will probably be this simple. Listing 28.5 shows the SecureLoginServer class.

Note
When you generate a random session key, make sure you use a cryptographically secure random number generator. Most of the random number generators you find in programming languages are not good for cryptography because they are too predictable. For instance, if your random number generator has a period of 2^32, it repeats its pattern after 2^32 (about 4 billion) numbers. This may seem like a lot to you, but in terms of breaking codes, it's very small. Even if you generate 128-bit keys, there are only 2^32 possible 128-bit patterns that the random number generator could create, meaning your key is logically only 32 bits. In other words, if someone wanted to try every possible key, he or she would have to try only 2^32 combinations instead of the 2^128 combinations you would expect with a 128-bit key. In addition, most random number generators have some predictability, where a truly secure generator has the appearance of being completely random.


Listing 28.5  Source Code for SecureLoginServer.java
import java.net.*;
import java.io.*;

// This class is responsible for creating a random session
// key and for creating the class to handle a new client.

public class SecureLoginServer extends SingleSecureServer
{
     public SecureLoginServer(OutputStream out)
     {
          super(out);
     }

// Create a SecureLoginClient to handle the client connection

     public void handleNewClient(Socket sock)
     {
          SecureLoginClient client = new SecureLoginClient(sock,
               sessionKey);
     }

// Generate a random session key
     public void makeSessionKey()
     {
          sessionKey = new byte[16];

          Acme.Crypto.CryptoUtils.randomBlock(sessionKey);
     }

// Start the server with the responses going to System.out, these
// will be picked up by the CGI program.

     public static void main(String[] args)
     {
          SecureLoginServer server = new SecureLoginServer(
               System.out);

          server.start();
     }
}

Finally, the bridging mechanism to link two streams together is very simple. The StreamBridge class, shown in Listing 28.6, sets up a thread and constantly reads data from one stream and writes it to another. It can operate in either block mode or single character mode. The single character mode is necessary for the encryption streams, because if you do a block mode read on the encryption stream, it won't return until it fills the entire block. You don't necessarily want it that way.

Tip
The StreamBridge class is like a connector for streams. It connects the input of one stream to the output of another stream, and is a little like a pipe in that it results in two streams being connected together, but is unlike a pipe in that you don't have to do any work to send the data over the connected streams.


Listing 28.6  Source Code for StreamBridge.java
import java.io.*;

// This is a generic class for connecting one stream to another.
// It has a callback to notify you when a stream closes, and
// will operate in either single-character or block-read mode

public class StreamBridge extends Object implements Runnable
{
     InputStream in;
     OutputStream out;
     BridgeCloseCallback callback;
     Thread bridgeThread;
     boolean blockRead;

     public StreamBridge(InputStream in, OutputStream out,
          BridgeCloseCallback callback, boolean blockRead)
     {
          this.in = in;
          this.out = out;
          this.callback = callback;
          this.blockRead = blockRead;
     }

     public void run()
     {
          int ch;

          try {

// If we support block read, create a block and read as much as
// we can into it each time

               if (blockRead) {
                    byte[] block = new byte[1024];
                    int len = 0;

// Keep reading blocks. We flush the output just in case.
                    while ((len = in.read(block)) > 0) {
                         out.write(block, 0, len);
                         out.flush();
                    }
               } else {

// If we aren't in block-read mode, read a character, write a character
// and flush the stream after writing each char.

                    while ((ch = in.read()) >= 0) {
                         out.write((char)ch);
                         out.flush();
                    }
               }
          } catch (Exception error) {
          }

          callback.bridgeClosed();

          stop();
     }

     public void start()
     {
          bridgeThread = new Thread(this);
          bridgeThread.start();
     }

     public void stop()
     {
          bridgeThread.stop();
          bridgeThread = null;
     }
}

You can use this same framework to implement other secure protocols. For example, you could use the POP3 and SMTP classes from Chapter 11, "Sending E-Mail from an Applet," and create a secure mail system. This framework is essentially the "Poor Man's SSL." It uses SSL to get the initial setup information back and forth, and then uses other encryption for the rest of the session.

Implementing a Multiclient Secure Server

If you want to implement a secure server that handles multiple simultaneous sessions, you have to do a little more work. As it turns out, however, if you are careful in the design of your single-client server, you can reuse large amounts of it.

The difference between the single-client and multiclient approach is that the multiclient server is always running, whereas the single-client server is spawned by the CGI program.

The interesting thing here is that the CGI program has to communicate with both servers in a similar way. It needs to know the port number and session key regardless of the type of server implementation. Similarities like this are usually an indication that there may be some code reuse brewing somewhere.

For a multiclient server, your CGI program can use a socket connection to get the port number and session key. Since the SingleSecureServer is already set up to run as a thread, it is ridiculously simple to make a multiclient server that just spawns new instances of a SingleSecureServer every time a CGI program connects to it.

It's ridiculously simple now, but it wasn't in the first design of the SingleSecureServer. Originally, the SingleSecureServer didn't run as a thread. Its run method was called something else, and it didn't implement Runnable. When it came time to create a multiclient server, I realized that it took only a few changes to adapt the single-client server so it could run either way. This is what I call "reuse by re-engineering."

Sometimes, you go to design a new system and discover that there is another class that almost works for you, but not quite. With a little change, the class could do what you need and still perform its previous functions. You run the risk of introducing bugs back into the original system, but it surely beats having to keep two copies of almost identical code.

As you make these small design changes, try to make a note of what you had to change. Not necessarily a detailed description, but a general one. You will start to see common themes-design strategies that you have taken in the past that inhibit code reuse. The next time you design a class, keep those previous problems in mind, and maybe your class will not need to be changed. This accumulated knowledge is what really makes a good object-oriented designer.

To support multiple simultaneous clients using the SingleSecureServer framework, you need a server that listens for socket connections from the CGI program and then spawns new SingleSecureServer threads. Listing 28.7 shows the MultiLoginServer class that does this.


Listing 28.7  Source Code for MultiLoginServer.java
import java.net.*;
import java.io.*;

// This class is responsible for creating a random session
// key and for creating the class to handle a new client.

public class MultiLoginServer extends Object implements Runnable
{
     protected Thread thread;
     protected ServerSocket serverSock;

     public MultiLoginServer(int listenPort)
     throws IOException
     {
// Create the socket that CGI program will connect to
          serverSock = new ServerSocket(listenPort);
     }

     public void run()
     {
          while (true) {
               Socket sock = null;

// Accept a new client connection
               try {
                    sock = serverSock.accept();
               } catch (Exception ignore) {
                    continue;
               }

// Spawn a server to handle the new connection
               try {
                    SecureLoginServer server =
                         new SecureLoginServer(
                              sock.getOutputStream());
                    server.start();

// If there was an error, close down the socket
               } catch (Exception oops) {
                    try {
                         sock.close();
                    } catch (Exception ignore) {
                    }
               }
          }
     }
                    
     public void start()
     {
          Thread thread = new Thread(this);
          thread.start();
     }

     public void stop()
     {
          thread.stop();
          thread = null;
     }

     public static void main(String[] args)
     {
          int port = 1234;

// Allow the port address to be set as a property

          String portStr = System.getProperty("port");

// Parse the port address
          try {
               port = Integer.parseInt(portStr);
          } catch (Exception ignore) {
          }

// Start the server
          try {
               MultiLoginServer server = new MultiLoginServer(port);
               server.start();
          } catch (Exception e) {
               e.printStackTrace();
               System.exit(1);
          }
     }
}

Finally, the CGI program that connects to the multiclient server is similar to the single-client CGI program. The only difference between the two programs is in their main methods. This being the case, it makes sense to just create a subclass of the single-client CGI program and create a new main. Listing 28.8 shows the multiclient CGI program.


Listing 28.8  Source Code for MultiLoginStartup.java
import java.net.*;
import java.io.*;

// This is a CGI program that starts up a secure Telnet session
// using a multi-client server that should already be running.
// It connects to the server to get the port number and session
// key which it passes back to the client.

public class MultiLoginStartup extends SecureLoginStartup
{
     public static void main(String[] args)
     {
          try {

               int port = 1234;

// Allow the port to be set as a system property
               String portStr = System.getProperty("port");

// Parse the port value
               try {
                    port = Integer.parseInt(portStr);
               } catch (Exception e) {
               }
     
// Connect to the login server
               Socket sock = new Socket(InetAddress.getLocalHost(),
                    port);

// create an input stream for reading the parameters back from the server
               DataInputStream in = new DataInputStream(
                    sock.getInputStream());

// Read the client port
               String portLine = in.readLine();
               int clientPort = Integer.parseInt(portLine);

// Read the session key
               String sessionKey = in.readLine();

// Send the web page to the browser
               sendNormalResponse(clientPort, sessionKey);

          } catch (Exception e) {
               sendErrorResponse(e.toString());
               return;
          }
     }
}

Creating Other Secure Remote Access Programs

You can use the SingleSecureServer class as a framework for implementing other secure remote access programs. Frankly, this solution is not the optimal one, because you have to work on the socket level. With RMI and CORBA available in Java, you shouldn't have to open up raw sockets for simple communications. They are still useful when you need to grab a large chunk of raw data, but for passing messages back and forth, sockets are a step backwards.

Be on the lookout for an ORB product that supports secure communications. If there isn't one now, there should be soon. Security is a growing issue on the Internet.