View Javadoc

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