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.BindException;
21  import java.net.InetAddress;
22  import java.net.ServerSocket;
23  import java.net.Socket;
24  import java.net.SocketException;
25  
26  import org.apache.commons.net.io.SocketInputStream;
27  
28  /***
29   * RCommandClient is very similar to
30   * {@link org.apache.commons.net.bsd.RExecClient},
31   * from which it is derived, and implements the rcmd() facility that
32   * first appeared in 4.2BSD Unix.  rcmd() is the facility used by the rsh
33   * (rshell) and other commands to execute a command on another machine
34   * from a trusted host without issuing a password.  The trust relationship
35   * between two machines is established by the contents of a machine's
36   * /etc/hosts.equiv file and a user's .rhosts file.  These files specify
37   * from which hosts and accounts on those hosts rcmd() requests will be
38   * accepted.  The only additional measure for establishing trust is that
39   * all client connections must originate from a port between 512 and 1023.
40   * Consequently, there is an upper limit to the number of rcmd connections
41   * that can be running simultaneously.   The required ports are reserved
42   * ports on Unix systems, and can only be bound by a
43   * process running with root permissions (to accomplish this rsh, rlogin,
44   * and related commands usualy have the suid bit set).  Therefore, on a
45   * Unix system, you will only be able to successfully use the RCommandClient
46   * class if the process runs as root.  However, there is no such restriction
47   * on Windows95 and some other systems.  The security risks are obvious.
48   * However, when carefully used, rcmd() can be very useful when used behind
49   * a firewall.
50   * <p>
51   * As with virtually all of the client classes in org.apache.commons.net, this
52   * class derives from SocketClient.  But it overrides most of its connection
53   * methods so that the local Socket will originate from an acceptable
54   * rshell port.  The way to use RCommandClient is to first connect
55   * to the server, call the {@link #rcommand  rcommand() } method,
56   * and then
57   * fetch the connection's input, output, and optionally error streams.
58   * Interaction with the remote command is controlled entirely through the
59   * I/O streams.  Once you have finished processing the streams, you should
60   * invoke {@link org.apache.commons.net.bsd.RExecClient#disconnect disconnect() }
61   *  to clean up properly.
62   * <p>
63   * By default the standard output and standard error streams of the
64   * remote process are transmitted over the same connection, readable
65   * from the input stream returned by
66   * {@link org.apache.commons.net.bsd.RExecClient#getInputStream getInputStream() }
67   * .  However, it is
68   * possible to tell the rshd daemon to return the standard error
69   * stream over a separate connection, readable from the input stream
70   * returned by {@link org.apache.commons.net.bsd.RExecClient#getErrorStream getErrorStream() }
71   * .  You
72   * can specify that a separate connection should be created for standard
73   * error by setting the boolean <code> separateErrorStream </code>
74   * parameter of {@link #rcommand  rcommand() } to <code> true </code>.
75   * The standard input of the remote process can be written to through
76   * the output stream returned by
77   * {@link org.apache.commons.net.bsd.RExecClient#getOutputStream getOutputStream() }
78   * .
79   * <p>
80   * <p>
81   * @author Daniel F. Savarese
82   * @see org.apache.commons.net.SocketClient
83   * @see RExecClient
84   * @see RLoginClient
85   ***/
86  
87  public class RCommandClient extends RExecClient
88  {
89      /***
90       * The default rshell port.  Set to 514 in BSD Unix.
91       ***/
92      public static final int DEFAULT_PORT = 514;
93  
94      /***
95       * The smallest port number an rcmd client may use.  By BSD convention
96       * this number is 512.
97       ***/
98      public static final int MIN_CLIENT_PORT = 512;
99  
100     /***
101      * The largest port number an rcmd client may use.  By BSD convention
102      * this number is 1023.
103      ***/
104     public static final int MAX_CLIENT_PORT = 1023;
105 
106     // Overrides method in RExecClient in order to implement proper
107     // port number limitations.
108     InputStream _createErrorStream() throws IOException
109     {
110         int localPort;
111         ServerSocket server;
112         Socket socket;
113 
114         localPort = MAX_CLIENT_PORT;
115         server = null; // Keep compiler from barfing
116 
117         for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort)
118         {
119             try
120             {
121                 server = _socketFactory_.createServerSocket(localPort, 1,
122                          getLocalAddress());
123             }
124             catch (SocketException e)
125             {
126                 continue;
127             }
128             break;
129         }
130 
131         if (localPort < MIN_CLIENT_PORT)
132             throw new BindException("All ports in use.");
133 
134         _output_.write(Integer.toString(server.getLocalPort()).getBytes());
135         _output_.write('\0');
136         _output_.flush();
137 
138         socket = server.accept();
139         server.close();
140 
141         if (isRemoteVerificationEnabled() && !verifyRemote(socket))
142         {
143             socket.close();
144             throw new IOException(
145                 "Security violation: unexpected connection attempt by " +
146                 socket.getInetAddress().getHostAddress());
147         }
148 
149         return (new SocketInputStream(socket, socket.getInputStream()));
150     }
151 
152     /***
153      * The default RCommandClient constructor.  Initializes the
154      * default port to <code> DEFAULT_PORT </code>.
155      ***/
156     public RCommandClient()
157     {
158         setDefaultPort(DEFAULT_PORT);
159     }
160 
161 
162     /***
163      * Opens a Socket connected to a remote host at the specified port and
164      * originating from the specified local address using a port in a range
165      * acceptable to the BSD rshell daemon.
166      * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_  _connectAction_() }
167      * is called to perform connection initialization actions.
168      * <p>
169      * @param host  The remote host.
170      * @param port  The port to connect to on the remote host.
171      * @param localAddr  The local address to use.
172      * @exception SocketException If the socket timeout could not be set.
173      * @exception BindException If all acceptable rshell ports are in use.
174      * @exception IOException If the socket could not be opened.  In most
175      *  cases you will only want to catch IOException since SocketException is
176      *  derived from it.
177      ***/
178     public void connect(InetAddress host, int port, InetAddress localAddr)
179     throws SocketException, BindException, IOException
180     {
181         int localPort;
182 
183         localPort = MAX_CLIENT_PORT;
184 
185         for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort)
186         {
187             try
188             {
189                 _socket_ =
190                     _socketFactory_.createSocket(host, port, localAddr, localPort);
191             }
192             catch (SocketException e)
193             {
194                 continue;
195             }
196             break;
197         }
198 
199         if (localPort < MIN_CLIENT_PORT)
200             throw new BindException("All ports in use or insufficient permssion.");
201 
202         _connectAction_();
203     }
204 
205 
206 
207     /***
208      * Opens a Socket connected to a remote host at the specified port and
209      * originating from the current host at a port in a range acceptable
210      * to the BSD rshell daemon.
211      * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_  _connectAction_() }
212      * is called to perform connection initialization actions.
213      * <p>
214      * @param host  The remote host.
215      * @param port  The port to connect to on the remote host.
216      * @exception SocketException If the socket timeout could not be set.
217      * @exception BindException If all acceptable rshell ports are in use.
218      * @exception IOException If the socket could not be opened.  In most
219      *  cases you will only want to catch IOException since SocketException is
220      *  derived from it.
221      ***/
222     public void connect(InetAddress host, int port)
223     throws SocketException, IOException
224     {
225         connect(host, port, InetAddress.getLocalHost());
226     }
227 
228 
229     /***
230      * Opens a Socket connected to a remote host at the specified port and
231      * originating from the current host at a port in a range acceptable
232      * to the BSD rshell daemon.
233      * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_  _connectAction_() }
234      * is called to perform connection initialization actions.
235      * <p>
236      * @param hostname  The name of the remote host.
237      * @param port  The port to connect to on the remote host.
238      * @exception SocketException If the socket timeout could not be set.
239      * @exception BindException If all acceptable rshell ports are in use.
240      * @exception IOException If the socket could not be opened.  In most
241      *  cases you will only want to catch IOException since SocketException is
242      *  derived from it.
243      * @exception UnknownHostException If the hostname cannot be resolved.
244      ***/
245     public void connect(String hostname, int port)
246     throws SocketException, IOException
247     {
248         connect(InetAddress.getByName(hostname), port, InetAddress.getLocalHost());
249     }
250 
251 
252     /***
253      * Opens a Socket connected to a remote host at the specified port and
254      * originating from the specified local address using a port in a range
255      * acceptable to the BSD rshell daemon.
256      * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_  _connectAction_() }
257      * is called to perform connection initialization actions.
258      * <p>
259      * @param hostname  The remote host.
260      * @param port  The port to connect to on the remote host.
261      * @param localAddr  The local address to use.
262      * @exception SocketException If the socket timeout could not be set.
263      * @exception BindException If all acceptable rshell ports are in use.
264      * @exception IOException If the socket could not be opened.  In most
265      *  cases you will only want to catch IOException since SocketException is
266      *  derived from it.
267      ***/
268     public void connect(String hostname, int port, InetAddress localAddr)
269     throws SocketException, IOException
270     {
271         connect(InetAddress.getByName(hostname), port, localAddr);
272     }
273 
274 
275     /***
276      * Opens a Socket connected to a remote host at the specified port and
277      * originating from the specified local address and port. The
278      * local port must lie between <code> MIN_CLIENT_PORT </code> and
279      * <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will
280      * be thrown.
281      * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_  _connectAction_() }
282      * is called to perform connection initialization actions.
283      * <p>
284      * @param host  The remote host.
285      * @param port  The port to connect to on the remote host.
286      * @param localAddr  The local address to use.
287      * @param localPort  The local port to use.
288      * @exception SocketException If the socket timeout could not be set.
289      * @exception IOException If the socket could not be opened.  In most
290      *  cases you will only want to catch IOException since SocketException is
291      *  derived from it.
292      * @exception IllegalArgumentException If an invalid local port number
293      *            is specified.
294      ***/
295     public void connect(InetAddress host, int port,
296                         InetAddress localAddr, int localPort)
297     throws SocketException, IOException, IllegalArgumentException
298     {
299         if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT)
300             throw new IllegalArgumentException("Invalid port number " + localPort);
301         super.connect(host, port, localAddr, localPort);
302     }
303 
304 
305     /***
306      * Opens a Socket connected to a remote host at the specified port and
307      * originating from the specified local address and port. The
308      * local port must lie between <code> MIN_CLIENT_PORT </code> and
309      * <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will
310      * be thrown.
311      * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_  _connectAction_() }
312      * is called to perform connection initialization actions.
313      * <p>
314      * @param hostname  The name of the remote host.
315      * @param port  The port to connect to on the remote host.
316      * @param localAddr  The local address to use.
317      * @param localPort  The local port to use.
318      * @exception SocketException If the socket timeout could not be set.
319      * @exception IOException If the socket could not be opened.  In most
320      *  cases you will only want to catch IOException since SocketException is
321      *  derived from it.
322      * @exception UnknownHostException If the hostname cannot be resolved.
323      * @exception IllegalArgumentException If an invalid local port number
324      *            is specified.
325      ***/
326     public void connect(String hostname, int port,
327                         InetAddress localAddr, int localPort)
328     throws SocketException, IOException, IllegalArgumentException
329     {
330         if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT)
331             throw new IllegalArgumentException("Invalid port number " + localPort);
332         super.connect(hostname, port, localAddr, localPort);
333     }
334 
335 
336     /***
337      * Remotely executes a command through the rshd daemon on the server
338      * to which the RCommandClient is connected.  After calling this method,
339      * you may interact with the remote process through its standard input,
340      * output, and error streams.  You will typically be able to detect
341      * the termination of the remote process after reaching end of file
342      * on its standard output (accessible through
343      * {@link #getInputStream  getInputStream() }.  Disconnecting
344      * from the server or closing the process streams before reaching
345      * end of file will not necessarily terminate the remote process.
346      * <p>
347      * If a separate error stream is requested, the remote server will
348      * connect to a local socket opened by RCommandClient, providing an
349      * independent stream through which standard error will be transmitted.
350      * The local socket must originate from a secure port (512 - 1023),
351      * and rcommand() ensures that this will be so.
352      * RCommandClient will also do a simple security check when it accepts a
353      * connection for this error stream.  If the connection does not originate
354      * from the remote server, an IOException will be thrown.  This serves as
355      * a simple protection against possible hijacking of the error stream by
356      * an attacker monitoring the rexec() negotiation.  You may disable this
357      * behavior with
358      * {@link org.apache.commons.net.bsd.RExecClient#setRemoteVerificationEnabled setRemoteVerificationEnabled()}
359      * .
360      * <p>
361      * @param localUsername  The user account on the local machine that is
362      *        requesting the command execution.
363      * @param remoteUsername  The account name on the server through which to
364      *        execute the command.
365      * @param command   The command, including any arguments, to execute.
366      * @param separateErrorStream True if you would like the standard error
367      *        to be transmitted through a different stream than standard output.
368      *        False if not.
369      * @exception IOException If the rcommand() attempt fails.  The exception
370      *            will contain a message indicating the nature of the failure.
371      ***/
372     public void rcommand(String localUsername, String remoteUsername,
373                          String command, boolean separateErrorStream)
374     throws IOException
375     {
376         rexec(localUsername, remoteUsername, command, separateErrorStream);
377     }
378 
379 
380     /***
381      * Same as
382      * <code> rcommand(localUsername, remoteUsername, command, false); </code>
383      ***/
384     public void rcommand(String localUsername, String remoteUsername,
385                          String command)
386     throws IOException
387     {
388         rcommand(localUsername, remoteUsername, command, false);
389     }
390 
391 }
392