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