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