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   * <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(getCharsetName())); // Java 1.6 can use getCharset()
219         _output_.write(NULL_CHAR);
220         _output_.write(password.getBytes(getCharsetName())); // Java 1.6 can use getCharset()
221         _output_.write(NULL_CHAR);
222         _output_.write(command.getBytes(getCharsetName())); // Java 1.6 can use 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