001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.net.bsd;
019
020 import java.io.IOException;
021 import java.io.InputStream;
022 import java.io.OutputStream;
023 import java.net.ServerSocket;
024 import java.net.Socket;
025
026 import org.apache.commons.net.SocketClient;
027 import org.apache.commons.net.io.SocketInputStream;
028
029 /***
030 * RExecClient implements the rexec() facility that first appeared in
031 * 4.2BSD Unix. This class will probably only be of use for connecting
032 * to Unix systems and only when the rexecd daemon is configured to run,
033 * which is a rarity these days because of the security risks involved.
034 * However, rexec() can be very useful for performing administrative tasks
035 * on a network behind a firewall.
036 * <p>
037 * As with virtually all of the client classes in org.apache.commons.net, this
038 * class derives from SocketClient, inheriting its connection methods.
039 * The way to use RExecClient is to first connect
040 * to the server, call the {@link #rexec rexec() } method, and then
041 * fetch the connection's input, output, and optionally error streams.
042 * Interaction with the remote command is controlled entirely through the
043 * I/O streams. Once you have finished processing the streams, you should
044 * invoke {@link #disconnect disconnect() } to clean up properly.
045 * <p>
046 * By default the standard output and standard error streams of the
047 * remote process are transmitted over the same connection, readable
048 * from the input stream returned by
049 * {@link #getInputStream getInputStream() }. However, it is
050 * possible to tell the rexecd daemon to return the standard error
051 * stream over a separate connection, readable from the input stream
052 * returned by {@link #getErrorStream getErrorStream() }. You
053 * can specify that a separate connection should be created for standard
054 * error by setting the boolean <code> separateErrorStream </code>
055 * parameter of {@link #rexec rexec() } to <code> true </code>.
056 * The standard input of the remote process can be written to through
057 * the output stream returned by
058 * {@link #getOutputStream getOutputSream() }.
059 * <p>
060 * <p>
061 * @see SocketClient
062 * @see RCommandClient
063 * @see RLoginClient
064 ***/
065
066 public class RExecClient extends SocketClient
067 {
068 /***
069 * The default rexec port. Set to 512 in BSD Unix.
070 ***/
071 public static final int DEFAULT_PORT = 512;
072
073 private boolean __remoteVerificationEnabled;
074
075 /***
076 * If a separate error stream is requested, <code>_errorStream_</code>
077 * will point to an InputStream from which the standard error of the
078 * remote process can be read (after a call to rexec()). Otherwise,
079 * <code> _errorStream_ </code> will be null.
080 ***/
081 protected InputStream _errorStream_;
082
083 // This can be overridden in local package to implement port range
084 // limitations of rcmd and rlogin
085 InputStream _createErrorStream() throws IOException
086 {
087 ServerSocket server;
088 Socket socket;
089
090 server = _serverSocketFactory_.createServerSocket(0, 1, getLocalAddress());
091
092 _output_.write(Integer.toString(server.getLocalPort()).getBytes("UTF-8")); // $NON-NLS-1$
093 _output_.write('\0');
094 _output_.flush();
095
096 socket = server.accept();
097 server.close();
098
099 if (__remoteVerificationEnabled && !verifyRemote(socket))
100 {
101 socket.close();
102 throw new IOException(
103 "Security violation: unexpected connection attempt by " +
104 socket.getInetAddress().getHostAddress());
105 }
106
107 return (new SocketInputStream(socket, socket.getInputStream()));
108 }
109
110
111 /***
112 * The default RExecClient constructor. Initializes the
113 * default port to <code> DEFAULT_PORT </code>.
114 ***/
115 public RExecClient()
116 {
117 _errorStream_ = null;
118 setDefaultPort(DEFAULT_PORT);
119 }
120
121
122 /***
123 * Returns the InputStream from which the standard outputof the remote
124 * process can be read. The input stream will only be set after a
125 * successful rexec() invocation.
126 * <p>
127 * @return The InputStream from which the standard output of the remote
128 * process can be read.
129 ***/
130 public InputStream getInputStream()
131 {
132 return _input_;
133 }
134
135
136 /***
137 * Returns the OutputStream through which the standard input of the remote
138 * process can be written. The output stream will only be set after a
139 * successful rexec() invocation.
140 * <p>
141 * @return The OutputStream through which the standard input of the remote
142 * process can be written.
143 ***/
144 public OutputStream getOutputStream()
145 {
146 return _output_;
147 }
148
149
150 /***
151 * Returns the InputStream from which the standard error of the remote
152 * process can be read if a separate error stream is requested from
153 * the server. Otherwise, null will be returned. The error stream
154 * will only be set after a successful rexec() invocation.
155 * <p>
156 * @return The InputStream from which the standard error of the remote
157 * process can be read if a separate error stream is requested from
158 * the server. Otherwise, null will be returned.
159 ***/
160 public InputStream getErrorStream()
161 {
162 return _errorStream_;
163 }
164
165
166 /***
167 * Remotely executes a command through the rexecd daemon on the server
168 * to which the RExecClient is connected. After calling this method,
169 * you may interact with the remote process through its standard input,
170 * output, and error streams. You will typically be able to detect
171 * the termination of the remote process after reaching end of file
172 * on its standard output (accessible through
173 * {@link #getInputStream getInputStream() }. Disconnecting
174 * from the server or closing the process streams before reaching
175 * end of file will not necessarily terminate the remote process.
176 * <p>
177 * If a separate error stream is requested, the remote server will
178 * connect to a local socket opened by RExecClient, providing an
179 * independent stream through which standard error will be transmitted.
180 * RExecClient will do a simple security check when it accepts a
181 * connection for this error stream. If the connection does not originate
182 * from the remote server, an IOException will be thrown. This serves as
183 * a simple protection against possible hijacking of the error stream by
184 * an attacker monitoring the rexec() negotiation. You may disable this
185 * behavior with {@link #setRemoteVerificationEnabled setRemoteVerificationEnabled()}
186 * .
187 * <p>
188 * @param username The account name on the server through which to execute
189 * the command.
190 * @param password The plain text password of the user account.
191 * @param command The command, including any arguments, to execute.
192 * @param separateErrorStream True if you would like the standard error
193 * to be transmitted through a different stream than standard output.
194 * False if not.
195 * @exception IOException If the rexec() attempt fails. The exception
196 * will contain a message indicating the nature of the failure.
197 ***/
198 public void rexec(String username, String password,
199 String command, boolean separateErrorStream)
200 throws IOException
201 {
202 int ch;
203
204 if (separateErrorStream)
205 {
206 _errorStream_ = _createErrorStream();
207 }
208 else
209 {
210 _output_.write('\0');
211 }
212
213 _output_.write(username.getBytes());
214 _output_.write('\0');
215 _output_.write(password.getBytes());
216 _output_.write('\0');
217 _output_.write(command.getBytes());
218 _output_.write('\0');
219 _output_.flush();
220
221 ch = _input_.read();
222 if (ch > 0) {
223 StringBuilder buffer = new StringBuilder();
224
225 while ((ch = _input_.read()) != -1 && ch != '\n') {
226 buffer.append((char)ch);
227 }
228
229 throw new IOException(buffer.toString());
230 } else if (ch < 0) {
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 @Override
253 public void disconnect() throws IOException
254 {
255 if (_errorStream_ != null) {
256 _errorStream_.close();
257 }
258 _errorStream_ = null;
259 super.disconnect();
260 }
261
262
263 /***
264 * Enable or disable verification that the remote host connecting to
265 * create a separate error stream is the same as the host to which
266 * the standard out stream is connected. The default is for verification
267 * to be enabled. You may set this value at any time, whether the
268 * client is currently connected or not.
269 * <p>
270 * @param enable True to enable verification, false to disable verification.
271 ***/
272 public final void setRemoteVerificationEnabled(boolean enable)
273 {
274 __remoteVerificationEnabled = enable;
275 }
276
277 /***
278 * Return whether or not verification of the remote host providing a
279 * separate error stream is enabled. The default behavior is for
280 * verification to be enabled.
281 * <p>
282 * @return True if verification is enabled, false if not.
283 ***/
284 public final boolean isRemoteVerificationEnabled()
285 {
286 return __remoteVerificationEnabled;
287 }
288
289 }
290