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