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   * @author Daniel F. Savarese
62   * @see SocketClient
63   * @see RCommandClient
64   * @see RLoginClient
65   ***/
66  
67  public class RExecClient extends SocketClient
68  {
69      /***
70       * The default rexec port.  Set to 512 in BSD Unix.
71       ***/
72      public static final int DEFAULT_PORT = 512;
73  
74      private boolean __remoteVerificationEnabled;
75  
76      /***
77       * If a separate error stream is requested, <code>_errorStream_</code>
78       * will point to an InputStream from which the standard error of the
79       * remote process can be read (after a call to rexec()).  Otherwise,
80       * <code> _errorStream_ </code> will be null.
81       ***/
82      protected InputStream _errorStream_;
83  
84      // This can be overridden in local package to implement port range
85      // limitations of rcmd and rlogin
86      InputStream _createErrorStream() throws IOException
87      {
88          ServerSocket server;
89          Socket socket;
90  
91          server = _serverSocketFactory_.createServerSocket(0, 1, getLocalAddress());
92  
93          _output_.write(Integer.toString(server.getLocalPort()).getBytes());
94          _output_.write('\0');
95          _output_.flush();
96  
97          socket = server.accept();
98          server.close();
99  
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