001 /*
002 * Copyright 2001-2005 The Apache Software Foundation
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.apache.commons.net.bsd;
017
018 import java.io.IOException;
019 import java.io.InputStream;
020 import java.net.ServerSocket;
021 import java.net.Socket;
022 import org.apache.commons.net.io.SocketInputStream;
023 import org.apache.commons.net.SocketClient;
024 import java.io.OutputStream;
025
026 /***
027 * RExecClient implements the rexec() facility that first appeared in
028 * 4.2BSD Unix. This class will probably only be of use for connecting
029 * to Unix systems and only when the rexecd daemon is configured to run,
030 * which is a rarity these days because of the security risks involved.
031 * However, rexec() can be very useful for performing administrative tasks
032 * on a network behind a firewall.
033 * <p>
034 * As with virtually all of the client classes in org.apache.commons.net, this
035 * class derives from SocketClient, inheriting its connection methods.
036 * The way to use RExecClient is to first connect
037 * to the server, call the {@link #rexec rexec() } method, and then
038 * fetch the connection's input, output, and optionally error streams.
039 * Interaction with the remote command is controlled entirely through the
040 * I/O streams. Once you have finished processing the streams, you should
041 * invoke {@link #disconnect disconnect() } to clean up properly.
042 * <p>
043 * By default the standard output and standard error streams of the
044 * remote process are transmitted over the same connection, readable
045 * from the input stream returned by
046 * {@link #getInputStream getInputStream() }. However, it is
047 * possible to tell the rexecd daemon to return the standard error
048 * stream over a separate connection, readable from the input stream
049 * returned by {@link #getErrorStream getErrorStream() }. You
050 * can specify that a separate connection should be created for standard
051 * error by setting the boolean <code> separateErrorStream </code>
052 * parameter of {@link #rexec rexec() } to <code> true </code>.
053 * The standard input of the remote process can be written to through
054 * the output stream returned by
055 * {@link #getOutputStream getOutputSream() }.
056 * <p>
057 * <p>
058 * @author Daniel F. Savarese
059 * @see SocketClient
060 * @see RCommandClient
061 * @see RLoginClient
062 ***/
063
064 public class RExecClient extends SocketClient
065 {
066 /***
067 * The default rexec port. Set to 512 in BSD Unix.
068 ***/
069 public static final int DEFAULT_PORT = 512;
070
071 private boolean __remoteVerificationEnabled;
072
073 /***
074 * If a separate error stream is requested, <code>_errorStream_</code>
075 * will point to an InputStream from which the standard error of the
076 * remote process can be read (after a call to rexec()). Otherwise,
077 * <code> _errorStream_ </code> will be null.
078 ***/
079 protected InputStream _errorStream_;
080
081 // This can be overridden in local package to implement port range
082 // limitations of rcmd and rlogin
083 InputStream _createErrorStream() throws IOException
084 {
085 ServerSocket server;
086 Socket socket;
087
088 server = _socketFactory_.createServerSocket(0, 1, getLocalAddress());
089
090 _output_.write(Integer.toString(server.getLocalPort()).getBytes());
091 _output_.write('\0');
092 _output_.flush();
093
094 socket = server.accept();
095 server.close();
096
097 if (__remoteVerificationEnabled && !verifyRemote(socket))
098 {
099 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