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