001    /*
002     * Copyright 2001-2005 The Apache Software Foundation
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.apache.commons.net.bsd;
017    
018    import java.io.IOException;
019    import java.io.InputStream;
020    import java.net.BindException;
021    import java.net.InetAddress;
022    import java.net.ServerSocket;
023    import java.net.Socket;
024    import java.net.SocketException;
025    
026    import org.apache.commons.net.io.SocketInputStream;
027    
028    /***
029     * RCommandClient is very similar to
030     * {@link org.apache.commons.net.bsd.RExecClient},
031     * from which it is derived, and implements the rcmd() facility that
032     * first appeared in 4.2BSD Unix.  rcmd() is the facility used by the rsh
033     * (rshell) and other commands to execute a command on another machine
034     * from a trusted host without issuing a password.  The trust relationship
035     * between two machines is established by the contents of a machine's
036     * /etc/hosts.equiv file and a user's .rhosts file.  These files specify
037     * from which hosts and accounts on those hosts rcmd() requests will be
038     * accepted.  The only additional measure for establishing trust is that
039     * all client connections must originate from a port between 512 and 1023.
040     * Consequently, there is an upper limit to the number of rcmd connections
041     * that can be running simultaneously.   The required ports are reserved
042     * ports on Unix systems, and can only be bound by a
043     * process running with root permissions (to accomplish this rsh, rlogin,
044     * and related commands usualy have the suid bit set).  Therefore, on a
045     * Unix system, you will only be able to successfully use the RCommandClient
046     * class if the process runs as root.  However, there is no such restriction
047     * on Windows95 and some other systems.  The security risks are obvious.
048     * However, when carefully used, rcmd() can be very useful when used behind
049     * a firewall.
050     * <p>
051     * As with virtually all of the client classes in org.apache.commons.net, this
052     * class derives from SocketClient.  But it overrides most of its connection
053     * methods so that the local Socket will originate from an acceptable
054     * rshell port.  The way to use RCommandClient is to first connect
055     * to the server, call the {@link #rcommand  rcommand() } method,
056     * and then
057     * fetch the connection's input, output, and optionally error streams.
058     * Interaction with the remote command is controlled entirely through the
059     * I/O streams.  Once you have finished processing the streams, you should
060     * invoke {@link org.apache.commons.net.bsd.RExecClient#disconnect disconnect() }
061     *  to clean up properly.
062     * <p>
063     * By default the standard output and standard error streams of the
064     * remote process are transmitted over the same connection, readable
065     * from the input stream returned by
066     * {@link org.apache.commons.net.bsd.RExecClient#getInputStream getInputStream() }
067     * .  However, it is
068     * possible to tell the rshd daemon to return the standard error
069     * stream over a separate connection, readable from the input stream
070     * returned by {@link org.apache.commons.net.bsd.RExecClient#getErrorStream getErrorStream() }
071     * .  You
072     * can specify that a separate connection should be created for standard
073     * error by setting the boolean <code> separateErrorStream </code>
074     * parameter of {@link #rcommand  rcommand() } to <code> true </code>.
075     * The standard input of the remote process can be written to through
076     * the output stream returned by
077     * {@link org.apache.commons.net.bsd.RExecClient#getOutputStream getOutputStream() }
078     * .
079     * <p>
080     * <p>
081     * @author Daniel F. Savarese
082     * @see org.apache.commons.net.SocketClient
083     * @see RExecClient
084     * @see RLoginClient
085     ***/
086    
087    public class RCommandClient extends RExecClient
088    {
089        /***
090         * The default rshell port.  Set to 514 in BSD Unix.
091         ***/
092        public static final int DEFAULT_PORT = 514;
093    
094        /***
095         * The smallest port number an rcmd client may use.  By BSD convention
096         * this number is 512.
097         ***/
098        public static final int MIN_CLIENT_PORT = 512;
099    
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