1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.net.bsd;
17
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.net.ServerSocket;
21 import java.net.Socket;
22 import org.apache.commons.net.io.SocketInputStream;
23 import org.apache.commons.net.SocketClient;
24 import java.io.OutputStream;
25
26 /***
27 * RExecClient implements the rexec() facility that first appeared in
28 * 4.2BSD Unix. This class will probably only be of use for connecting
29 * to Unix systems and only when the rexecd daemon is configured to run,
30 * which is a rarity these days because of the security risks involved.
31 * However, rexec() can be very useful for performing administrative tasks
32 * on a network behind a firewall.
33 * <p>
34 * As with virtually all of the client classes in org.apache.commons.net, this
35 * class derives from SocketClient, inheriting its connection methods.
36 * The way to use RExecClient is to first connect
37 * to the server, call the {@link #rexec rexec() } method, and then
38 * fetch the connection's input, output, and optionally error streams.
39 * Interaction with the remote command is controlled entirely through the
40 * I/O streams. Once you have finished processing the streams, you should
41 * invoke {@link #disconnect disconnect() } to clean up properly.
42 * <p>
43 * By default the standard output and standard error streams of the
44 * remote process are transmitted over the same connection, readable
45 * from the input stream returned by
46 * {@link #getInputStream getInputStream() }. However, it is
47 * possible to tell the rexecd daemon to return the standard error
48 * stream over a separate connection, readable from the input stream
49 * returned by {@link #getErrorStream getErrorStream() }. You
50 * can specify that a separate connection should be created for standard
51 * error by setting the boolean <code> separateErrorStream </code>
52 * parameter of {@link #rexec rexec() } to <code> true </code>.
53 * The standard input of the remote process can be written to through
54 * the output stream returned by
55 * {@link #getOutputStream getOutputSream() }.
56 * <p>
57 * <p>
58 * @author Daniel F. Savarese
59 * @see SocketClient
60 * @see RCommandClient
61 * @see RLoginClient
62 ***/
63
64 public class RExecClient extends SocketClient
65 {
66 /***
67 * The default rexec port. Set to 512 in BSD Unix.
68 ***/
69 public static final int DEFAULT_PORT = 512;
70
71 private boolean __remoteVerificationEnabled;
72
73 /***
74 * If a separate error stream is requested, <code>_errorStream_</code>
75 * will point to an InputStream from which the standard error of the
76 * remote process can be read (after a call to rexec()). Otherwise,
77 * <code> _errorStream_ </code> will be null.
78 ***/
79 protected InputStream _errorStream_;
80
81
82
83 InputStream _createErrorStream() throws IOException
84 {
85 ServerSocket server;
86 Socket socket;
87
88 server = _socketFactory_.createServerSocket(0, 1, getLocalAddress());
89
90 _output_.write(Integer.toString(server.getLocalPort()).getBytes());
91 _output_.write('\0');
92 _output_.flush();
93
94 socket = server.accept();
95 server.close();
96
97 if (__remoteVerificationEnabled && !verifyRemote(socket))
98 {
99 socket.close();
100 throw new IOException(
101 "Security violation: unexpected connection attempt by " +
102 socket.getInetAddress().getHostAddress());
103 }
104
105 return (new SocketInputStream(socket, socket.getInputStream()));
106 }
107
108
109 /***
110 * The default RExecClient constructor. Initializes the
111 * default port to <code> DEFAULT_PORT </code>.
112 ***/
113 public RExecClient()
114 {
115 _errorStream_ = null;
116 setDefaultPort(DEFAULT_PORT);
117 }
118
119
120 /***
121 * Returns the InputStream from which the standard outputof the remote
122 * process can be read. The input stream will only be set after a
123 * successful rexec() invocation.
124 * <p>
125 * @return The InputStream from which the standard output of the remote
126 * process can be read.
127 ***/
128 public InputStream getInputStream()
129 {
130 return _input_;
131 }
132
133
134 /***
135 * Returns the OutputStream through which the standard input of the remote
136 * process can be written. The output stream will only be set after a
137 * successful rexec() invocation.
138 * <p>
139 * @return The OutputStream through which the standard input of the remote
140 * process can be written.
141 ***/
142 public OutputStream getOutputStream()
143 {
144 return _output_;
145 }
146
147
148 /***
149 * Returns the InputStream from which the standard error of the remote
150 * process can be read if a separate error stream is requested from
151 * the server. Otherwise, null will be returned. The error stream
152 * will only be set after a successful rexec() invocation.
153 * <p>
154 * @return The InputStream from which the standard error of the remote
155 * process can be read if a separate error stream is requested from
156 * the server. Otherwise, null will be returned.
157 ***/
158 public InputStream getErrorStream()
159 {
160 return _errorStream_;
161 }
162
163
164 /***
165 * Remotely executes a command through the rexecd daemon on the server
166 * to which the RExecClient is connected. After calling this method,
167 * you may interact with the remote process through its standard input,
168 * output, and error streams. You will typically be able to detect
169 * the termination of the remote process after reaching end of file
170 * on its standard output (accessible through
171 * {@link #getInputStream getInputStream() }. Disconnecting
172 * from the server or closing the process streams before reaching
173 * end of file will not necessarily terminate the remote process.
174 * <p>
175 * If a separate error stream is requested, the remote server will
176 * connect to a local socket opened by RExecClient, providing an
177 * independent stream through which standard error will be transmitted.
178 * RExecClient will do a simple security check when it accepts a
179 * connection for this error stream. If the connection does not originate
180 * from the remote server, an IOException will be thrown. This serves as
181 * a simple protection against possible hijacking of the error stream by
182 * an attacker monitoring the rexec() negotiation. You may disable this
183 * behavior with {@link #setRemoteVerificationEnabled setRemoteVerificationEnabled()}
184 * .
185 * <p>
186 * @param username The account name on the server through which to execute
187 * the command.
188 * @param password The plain text password of the user account.
189 * @param command The command, including any arguments, to execute.
190 * @param separateErrorStream True if you would like the standard error
191 * to be transmitted through a different stream than standard output.
192 * False if not.
193 * @exception IOException If the rexec() attempt fails. The exception
194 * will contain a message indicating the nature of the failure.
195 ***/
196 public void rexec(String username, String password,
197 String command, boolean separateErrorStream)
198 throws IOException
199 {
200 int ch;
201
202 if (separateErrorStream)
203 {
204 _errorStream_ = _createErrorStream();
205 }
206 else
207 {
208 _output_.write('\0');
209 }
210
211 _output_.write(username.getBytes());
212 _output_.write('\0');
213 _output_.write(password.getBytes());
214 _output_.write('\0');
215 _output_.write(command.getBytes());
216 _output_.write('\0');
217 _output_.flush();
218
219 ch = _input_.read();
220 if (ch > 0)
221 {
222 StringBuffer buffer = new StringBuffer();
223
224 while ((ch = _input_.read()) != -1 && ch != '\n')
225 buffer.append((char)ch);
226
227 throw new IOException(buffer.toString());
228 }
229 else if (ch < 0)
230 {
231 throw new IOException("Server closed connection.");
232 }
233 }
234
235
236 /***
237 * Same as <code> rexec(username, password, command, false); </code>
238 ***/
239 public void rexec(String username, String password,
240 String command)
241 throws IOException
242 {
243 rexec(username, password, command, false);
244 }
245
246 /***
247 * Disconnects from the server, closing all associated open sockets and
248 * streams.
249 * <p>
250 * @exception IOException If there an error occurs while disconnecting.
251 ***/
252 public void disconnect() throws IOException
253 {
254 if (_errorStream_ != null)
255 _errorStream_.close();
256 _errorStream_ = null;
257 super.disconnect();
258 }
259
260
261 /***
262 * Enable or disable verification that the remote host connecting to
263 * create a separate error stream is the same as the host to which
264 * the standard out stream is connected. The default is for verification
265 * to be enabled. You may set this value at any time, whether the
266 * client is currently connected or not.
267 * <p>
268 * @param enable True to enable verification, false to disable verification.
269 ***/
270 public final void setRemoteVerificationEnabled(boolean enable)
271 {
272 __remoteVerificationEnabled = enable;
273 }
274
275 /***
276 * Return whether or not verification of the remote host providing a
277 * separate error stream is enabled. The default behavior is for
278 * verification to be enabled.
279 * <p>
280 * @return True if verification is enabled, false if not.
281 ***/
282 public final boolean isRemoteVerificationEnabled()
283 {
284 return __remoteVerificationEnabled;
285 }
286
287 }
288