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 * @author Daniel F. Savarese
062 * @see SocketClient
063 * @see RCommandClient
064 * @see RLoginClient
065 ***/
066
067 public class RExecClient extends SocketClient
068 {
069 /***
070 * The default rexec port. Set to 512 in BSD Unix.
071 ***/
072 public static final int DEFAULT_PORT = 512;
073
074 private boolean __remoteVerificationEnabled;
075
076 /***
077 * If a separate error stream is requested, <code>_errorStream_</code>
078 * will point to an InputStream from which the standard error of the
079 * remote process can be read (after a call to rexec()). Otherwise,
080 * <code> _errorStream_ </code> will be null.
081 ***/
082 protected InputStream _errorStream_;
083
084 // This can be overridden in local package to implement port range
085 // limitations of rcmd and rlogin
086 InputStream _createErrorStream() throws IOException
087 {
088 ServerSocket server;
089 Socket socket;
090
091 server = _serverSocketFactory_.createServerSocket(0, 1, getLocalAddress());
092
093 _output_.write(Integer.toString(server.getLocalPort()).getBytes());
094 _output_.write('\0');
095 _output_.flush();
096
097 socket = server.accept();
098 server.close();
099
100 if (__remoteVerificationEnabled && !verifyRemote(socket))
101 {
102 socket.close();
103 throw new IOException(
104 "Security violation: unexpected connection attempt by " +
105 socket.getInetAddress().getHostAddress());
106 }
107
108 return (new SocketInputStream(socket, socket.getInputStream()));
109 }
110
111
112 /***
113 * The default RExecClient constructor. Initializes the
114 * default port to <code> DEFAULT_PORT </code>.
115 ***/
116 public RExecClient()
117 {
118 _errorStream_ = null;
119 setDefaultPort(DEFAULT_PORT);
120 }
121
122
123 /***
124 * Returns the InputStream from which the standard outputof the remote
125 * process can be read. The input stream will only be set after a
126 * successful rexec() invocation.
127 * <p>
128 * @return The InputStream from which the standard output of the remote
129 * process can be read.
130 ***/
131 public InputStream getInputStream()
132 {
133 return _input_;
134 }
135
136
137 /***
138 * Returns the OutputStream through which the standard input of the remote
139 * process can be written. The output stream will only be set after a
140 * successful rexec() invocation.
141 * <p>
142 * @return The OutputStream through which the standard input of the remote
143 * process can be written.
144 ***/
145 public OutputStream getOutputStream()
146 {
147 return _output_;
148 }
149
150
151 /***
152 * Returns the InputStream from which the standard error of the remote
153 * process can be read if a separate error stream is requested from
154 * the server. Otherwise, null will be returned. The error stream
155 * will only be set after a successful rexec() invocation.
156 * <p>
157 * @return The InputStream from which the standard error of the remote
158 * process can be read if a separate error stream is requested from
159 * the server. Otherwise, null will be returned.
160 ***/
161 public InputStream getErrorStream()
162 {
163 return _errorStream_;
164 }
165
166
167 /***
168 * Remotely executes a command through the rexecd daemon on the server
169 * to which the RExecClient is connected. After calling this method,
170 * you may interact with the remote process through its standard input,
171 * output, and error streams. You will typically be able to detect
172 * the termination of the remote process after reaching end of file
173 * on its standard output (accessible through
174 * {@link #getInputStream getInputStream() }. Disconnecting
175 * from the server or closing the process streams before reaching
176 * end of file will not necessarily terminate the remote process.
177 * <p>
178 * If a separate error stream is requested, the remote server will
179 * connect to a local socket opened by RExecClient, providing an
180 * independent stream through which standard error will be transmitted.
181 * RExecClient will do a simple security check when it accepts a
182 * connection for this error stream. If the connection does not originate
183 * from the remote server, an IOException will be thrown. This serves as
184 * a simple protection against possible hijacking of the error stream by
185 * an attacker monitoring the rexec() negotiation. You may disable this
186 * behavior with {@link #setRemoteVerificationEnabled setRemoteVerificationEnabled()}
187 * .
188 * <p>
189 * @param username The account name on the server through which to execute
190 * the command.
191 * @param password The plain text password of the user account.
192 * @param command The command, including any arguments, to execute.
193 * @param separateErrorStream True if you would like the standard error
194 * to be transmitted through a different stream than standard output.
195 * False if not.
196 * @exception IOException If the rexec() attempt fails. The exception
197 * will contain a message indicating the nature of the failure.
198 ***/
199 public void rexec(String username, String password,
200 String command, boolean separateErrorStream)
201 throws IOException
202 {
203 int ch;
204
205 if (separateErrorStream)
206 {
207 _errorStream_ = _createErrorStream();
208 }
209 else
210 {
211 _output_.write('\0');
212 }
213
214 _output_.write(username.getBytes());
215 _output_.write('\0');
216 _output_.write(password.getBytes());
217 _output_.write('\0');
218 _output_.write(command.getBytes());
219 _output_.write('\0');
220 _output_.flush();
221
222 ch = _input_.read();
223 if (ch > 0)
224 {
225 StringBuffer buffer = new StringBuffer();
226
227 while ((ch = _input_.read()) != -1 && ch != '\n')
228 buffer.append((char)ch);
229
230 throw new IOException(buffer.toString());
231 }
232 else if (ch < 0)
233 {
234 throw new IOException("Server closed connection.");
235 }
236 }
237
238
239 /***
240 * Same as <code> rexec(username, password, command, false); </code>
241 ***/
242 public void rexec(String username, String password,
243 String command)
244 throws IOException
245 {
246 rexec(username, password, command, false);
247 }
248
249 /***
250 * Disconnects from the server, closing all associated open sockets and
251 * streams.
252 * <p>
253 * @exception IOException If there an error occurs while disconnecting.
254 ***/
255 @Override
256 public void disconnect() throws IOException
257 {
258 if (_errorStream_ != null)
259 _errorStream_.close();
260 _errorStream_ = null;
261 super.disconnect();
262 }
263
264
265 /***
266 * Enable or disable verification that the remote host connecting to
267 * create a separate error stream is the same as the host to which
268 * the standard out stream is connected. The default is for verification
269 * to be enabled. You may set this value at any time, whether the
270 * client is currently connected or not.
271 * <p>
272 * @param enable True to enable verification, false to disable verification.
273 ***/
274 public final void setRemoteVerificationEnabled(boolean enable)
275 {
276 __remoteVerificationEnabled = enable;
277 }
278
279 /***
280 * Return whether or not verification of the remote host providing a
281 * separate error stream is enabled. The default behavior is for
282 * verification to be enabled.
283 * <p>
284 * @return True if verification is enabled, false if not.
285 ***/
286 public final boolean isRemoteVerificationEnabled()
287 {
288 return __remoteVerificationEnabled;
289 }
290
291 }
292