Sometimes a servlet needs to execute an external program. This is generally important in situations where an external program offers functionality that isn't easily available from within Java. For example, a servlet could call an external program to perform an image manipulation or to check the status of the server. Launching an external program raises a number of security concerns. For this reason, it's an action that can be taken only by servlets running with a fairly lenient security manager--specifically, a security manager that grants permission for the servlet to call the exec() method of java.lang.Runtime.
The finger program queries a (possibly remote) computer for a list of currently logged in users. It's available on virtually all Unix systems and some Windows NT machines with networking capabilities. The finger program works by connecting to a finger daemon (usually named fingerd) that listens on port 79. finger makes its request of fingerd using a custom "finger" protocol, and fingerd replies with the appropriate information. Most Unix systems run fingerd, though many security-conscious administrators turn it off to limit information that could be used for break-in attempts. It's still fairly rare to find fingerd on Windows systems. Run without any arguments, finger reports all users of the local machine. The local machine must be running fingerd. Here's an example:
% finger Login Name TTY Idle When Office jhunter Jason Hunter q0 3:13 Thu 12:13 ktaylor Kristi Taylor q1 Thu 12:18
Run with a username as an argument, finger reports on just that user:
% finger jhunter Login name: jhunter In real life: Jason Hunter Directory: /usr/people/jhunter Shell: /bin/tcsh On since Jan 1 12:13:28 on ttyq0 from :0.0 3 hours 13 minutes Idle Time On since Jan 1 12:13:30 on ttyq2 from :0.0
Run with a hostname as an argument, finger reports all the users of the specified host. The remote host must be running fingerd:
% finger @deimos Login Name TTY Idle When Office bday Bill Day q0 17d Mon 10:45
And, of course, run with a username and hostname, finger reports on the specified user on the specified host:
% finger bday@deimos [deimos.engr.sgi.com] Login name: bday In real life: Bill Day Directory: /usr/people/bday Shell: /bin/tcsh On since Dec 15 10:45:22 on ttyq0 from :0.0 17 days Idle Time
Let's assume that a servlet wants access to the information retrieved by finger. It has two options: it can establish a socket connection to fingerd and make a request for information just like any other finger client, or it can execute the command-line finger program to make the connection on its behalf and read the information from finger's output. We'll show the second technique here.[1]
[1]If you're interested in the code necessary to connect to fingerd, see the FingerServlet example provided with the Java Web Server.
Example 13-6 shows how a servlet can execute the finger command to see who's logged into the local machine. It reads the command's output and prints it to its output stream.
import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import com.oreilly.servlet.ServletUtils; public class Finger extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/plain"); PrintWriter out = res.getWriter(); String command = "finger"; Runtime runtime = Runtime.getRuntime(); Process process = null; try { process = runtime.exec(command); DataInputStream in = new DataInputStream(process.getInputStream()); // Read and print the output String line = null; while ((line = in.readLine()) != null) { out.println(line); } } catch (Exception e) { out.println("Problem with finger: " + ServletUtils.getStackTraceAsString(e)); } } }
This servlet uses the exec() command just like any other Java class would. It executes the finger command, then reads and prints the output. If there's a problem, the servlet catches an exception and prints the stack trace to the user. This servlet assumes the finger command exists in the default search path. If that isn't the case, change the command string to specify the path where finger can be found.
We should point out that, although Java is executing native code when it executes the finger program, it doesn't open itself up to the risks that normally exist when executing native code. The reason is that the finger program executes as a separate process. It can crash or be killed without impacting the server executing the servlet.
Now let's assume we want to pass an argument to the finger command. The usage is slightly different. exec() takes either a single string that specifies a command or an array of strings that specifies a command and the arguments to pass to that command. To run finger jhunter the code looks like Example 13-7.
import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import com.oreilly.servlet.ServletUtils; public class Finger extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/plain"); PrintWriter out = res.getWriter(); String[] command = { "finger", "jhunter" }; // Only change! Runtime runtime = Runtime.getRuntime(); Process process = null; try { process = runtime.exec(command); BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); // Read and print the output String line = null; while ((line = in.readLine()) != null) { out.println(line); } } catch (Exception e) { out.println("Problem with finger: " + ServletUtils.getStackTraceAsString(e)); } } }
The command variable is now the string array {"finger","jhunter"}. The command would not work as the single string "finger jhunter".
Finally, let's assume we want to redirect the output from our finger command. We may want to redirect the output to a file for later use, as in finger jhunter > /tmp/jhunter. Or we may want to redirect the output to the grep program to remove any references to some user, as in finger | grep -v jhunter.
This task is harder than it may appear. If the command variable is set to the string "finger | grep -v jhunter", Java treats this string as the name of as a single program--one that it most assuredly won't find. If the command variable is set to the string array "{"finger","|","grep","-v","jhunter"}", Java executes the finger command and pass it the next four strings as parameters, no doubt thoroughly confusing finger.
The solution requires an understanding that redirection is a feature of the shell. The shell is the program into which you normally type commands. On Unix the most common shells are csh, tcsh, bash, and sh. On Windows 95, the shell is usually command.com. On Windows NT, the shell is either command.com or cmd.exe.
Instead of executing finger directly, we can execute a shell and tell it the command string we want run. That string can contain the finger command and any sort of redirection. The shell can parse the command and correctly recognize and perform the redirection. The exact command needed to execute a shell and program depends on the shell and thus on the operating system. This technique therefore necessarily limits the platform independence of the servlets that use it. On a Unix system, the following command variable asks csh to execute the command finger | grep -v jhunter:
String[] command = { "/bin/csh", "-c", "finger | grep -v jhunter" };
The program Java executes is /bin/csh. csh is passed two arguments: -c, which asks the shell to execute the next parameter, and finger | grep -v jhunter, which is executed by the shell.
On a Windows system, the command variable looks like this:
String[] command = { "command.com", "/c", "finger | grep -v jhunter" };
The /c argument for command.com works the same way -c did for csh and, yes, the .com suffix is necessary. Windows NT users should note that using cmd.exe is problematic because it redirects its output to a new window instead of to the Java runtime that spawned it. In fact, even launching the Java Web Server from a cmd.exe shell can cause the command.com command to fail.
Copyright © 2001 O'Reilly & Associates. All rights reserved.