    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.net.bsd;
    019
    020import java.io.IOException;
    021import java.io.InputStream;
    022import java.io.OutputStream;
    023import java.net.ServerSocket;
    024import java.net.Socket;
    025
    026import org.apache.commons.net.SocketClient;
    027import org.apache.commons.net.io.SocketInputStream;
    028
    029/***
    030 * RExecClient implements the rexec() facility that first appeared in
    031 * 4.2BSD Unix.  This class will probably only be of use for connecting
    032 * to Unix systems and only when the rexecd daemon is configured to run,
    033 * which is a rarity these days because of the security risks involved.
    034 * However, rexec() can be very useful for performing administrative tasks
    035 * on a network behind a firewall.
    036 * <p>
    037 * As with virtually all of the client classes in org.apache.commons.net, this
    038 * class derives from SocketClient, inheriting its connection methods.
    039 * The way to use RExecClient is to first connect
    040 * to the server, call the {@link #rexec  rexec()} method, and then
    041 * fetch the connection's input, output, and optionally error streams.
    042 * Interaction with the remote command is controlled entirely through the
    043 * I/O streams.  Once you have finished processing the streams, you should
    044 * invoke {@link #disconnect  disconnect()} to clean up properly.
    045 * <p>
    046 * By default the standard output and standard error streams of the
    047 * remote process are transmitted over the same connection, readable
    048 * from the input stream returned by
    049 * {@link #getInputStream  getInputStream()}.  However, it is
    050 * possible to tell the rexecd daemon to return the standard error
    051 * stream over a separate connection, readable from the input stream
    052 * returned by {@link #getErrorStream  getErrorStream()}.  You
    053 * can specify that a separate connection should be created for standard
    054 * error by setting the boolean <code> separateErrorStream </code>
    055 * parameter of {@link #rexec  rexec()} to <code> true </code>.
    056 * The standard input of the remote process can be written to through
    057 * the output stream returned by
    058 * {@link #getOutputStream  getOutputSream()}.
    059 *
    060 * @see SocketClient
    061 * @see RCommandClient
    062 * @see RLoginClient
    063 ***/
    064
    065public class RExecClient extends SocketClient
    066{
    067    /**
    068     * @since 3.3
    069     */
    070    protected static final char NULL_CHAR = '\0';
    071
    072    /***
    073     * The default rexec port.  Set to 512 in BSD Unix.
    074     ***/
    075    public static final int DEFAULT_PORT = 512;
    076
    077    private boolean remoteVerificationEnabled;
    078
    079    /***
    080     * If a separate error stream is requested, <code>_errorStream_</code>
    081     * will point to an InputStream from which the standard error of the
    082     * remote process can be read (after a call to rexec()).  Otherwise,
    083     * <code> _errorStream_ </code> will be null.
    084     ***/
    085    protected InputStream _errorStream_;
    086
    087    // This can be overridden in local package to implement port range
    088    // limitations of rcmd and rlogin
    089    InputStream createErrorStream() throws IOException
    090    {
    091        ServerSocket server;
    092        Socket socket;
    093
    094        server = _serverSocketFactory_.createServerSocket(0, 1, getLocalAddress());
    095
    096        _output_.write(Integer.toString(server.getLocalPort()).getBytes("UTF-8")); // $NON-NLS-1$
    097        _output_.write(NULL_CHAR);
    098        _output_.flush();
    099
    100        socket = server.accept();
    101        server.close();
    102
    103        if (remoteVerificationEnabled && !verifyRemote(socket))
    104        {
    105            socket.close();
    106            throw new IOException(
    107                "Security violation: unexpected connection attempt by " +
    108                socket.getInetAddress().getHostAddress());
    109        }
    110
    111        return new SocketInputStream(socket, socket.getInputStream());
    112    }
    113
    114
    115    /***
    116     * The default RExecClient constructor.  Initializes the
    117     * default port to <code> DEFAULT_PORT </code>.
    118     ***/
    119    public RExecClient()
    120    {
    121        _errorStream_ = null;
    122        setDefaultPort(DEFAULT_PORT);
    123    }
    124
    125
    126    /***
    127     * Returns the InputStream from which the standard output of the remote
    128     * process can be read.  The input stream will only be set after a
    129     * successful rexec() invocation.
    130     *
    131     * @return The InputStream from which the standard output of the remote
    132     * process can be read.
    133     ***/
    134    public InputStream getInputStream()
    135    {
    136        return _input_;
    137    }
    138
    139
    140    /***
    141     * Returns the OutputStream through which the standard input of the remote
    142     * process can be written.  The output stream will only be set after a
    143     * successful rexec() invocation.
    144     *
    145     * @return The OutputStream through which the standard input of the remote
    146     * process can be written.
    147     ***/
    148    public OutputStream getOutputStream()
    149    {
    150        return _output_;
    151    }
    152
    153
    154    /***
    155     * Returns the InputStream from which the standard error of the remote
    156     * process can be read if a separate error stream is requested from
    157     * the server.  Otherwise, null will be returned.  The error stream
    158     * will only be set after a successful rexec() invocation.
    159     *
    160     * @return The InputStream from which the standard error of the remote
    161     * process can be read if a separate error stream is requested from
    162     * the server.  Otherwise, null will be returned.
    163     ***/
    164    public InputStream getErrorStream()
    165    {
    166        return _errorStream_;
    167    }
    168
    169
    170    /***
    171     * Remotely executes a command through the rexecd daemon on the server
    172     * to which the RExecClient is connected.  After calling this method,
    173     * you may interact with the remote process through its standard input,
    174     * output, and error streams.  You will typically be able to detect
    175     * the termination of the remote process after reaching end of file
    176     * on its standard output (accessible through
    177     * {@link #getInputStream  getInputStream() }.    Disconnecting
    178     * from the server or closing the process streams before reaching
    179     * end of file will not necessarily terminate the remote process.
    180     * <p>
    181     * If a separate error stream is requested, the remote server will
    182     * connect to a local socket opened by RExecClient, providing an
    183     * independent stream through which standard error will be transmitted.
    184     * RExecClient will do a simple security check when it accepts a
    185     * connection for this error stream.  If the connection does not originate
    186     * from the remote server, an IOException will be thrown.  This serves as
    187     * a simple protection against possible hijacking of the error stream by
    188     * an attacker monitoring the rexec() negotiation.  You may disable this
    189     * behavior with {@link #setRemoteVerificationEnabled setRemoteVerificationEnabled()}
    190     * .
    191     *
    192     * @param username  The account name on the server through which to execute
    193     *                  the command.
    194     * @param password  The plain text password of the user account.
    195     * @param command   The command, including any arguments, to execute.
    196     * @param separateErrorStream True if you would like the standard error
    197     *        to be transmitted through a different stream than standard output.
    198     *        False if not.
    199     * @throws IOException If the rexec() attempt fails.  The exception
    200     *            will contain a message indicating the nature of the failure.
    201     ***/
    202    public void rexec(final String username, final String password,
    203                      final String command, final boolean separateErrorStream)
    204    throws IOException
    205    {
    206        int ch;
    207
    208        if (separateErrorStream)
    209        {
    210            _errorStream_ = createErrorStream();
    211        }
    212        else
    213        {
    214            _output_.write(NULL_CHAR);
    215        }
    216
    217        _output_.write(username.getBytes(getCharset()));
    218        _output_.write(NULL_CHAR);
    219        _output_.write(password.getBytes(getCharset()));
    220        _output_.write(NULL_CHAR);
    221        _output_.write(command.getBytes(getCharset()));
    222        _output_.write(NULL_CHAR);
    223        _output_.flush();
    224
    225        ch = _input_.read();
    226        if (ch > 0) {
    227            final StringBuilder buffer = new StringBuilder();
    228
    229            while ((ch = _input_.read()) != -1 && ch != '\n') {
    230                buffer.append((char)ch);
    231            }
    232
    233            throw new IOException(buffer.toString());
    234        } else if (ch < 0) {
    235            throw new IOException("Server closed connection.");
    236        }
    237    }
    238
    239
    240    /***
    241     * Same as <code> rexec(username, password, command, false); </code>
    242     * @param username the user name
    243     * @param password the password
    244     * @param command the command to run
    245     * @throws IOException if an error occurs
    246     ***/
    247    public void rexec(final String username, final String password,
    248                      final String command)
    249    throws IOException
    250    {
    251        rexec(username, password, command, false);
    252    }
    253
    254    /***
    255     * Disconnects from the server, closing all associated open sockets and
    256     * streams.
    257     *
    258     * @throws IOException If there an error occurs while disconnecting.
    259     ***/
    260    @Override
    261    public void disconnect() throws IOException
    262    {
    263        if (_errorStream_ != null) {
    264            _errorStream_.close();
    265        }
    266        _errorStream_ = null;
    267        super.disconnect();
    268    }
    269
    270
    271    /***
    272     * Enable or disable verification that the remote host connecting to
    273     * create a separate error stream is the same as the host to which
    274     * the standard out stream is connected.  The default is for verification
    275     * to be enabled.  You may set this value at any time, whether the
    276     * client is currently connected or not.
    277     *
    278     * @param enable True to enable verification, false to disable verification.
    279     ***/
    280    public final void setRemoteVerificationEnabled(final boolean enable)
    281    {
    282        remoteVerificationEnabled = enable;
    283    }
    284
    285    /***
    286     * Return whether or not verification of the remote host providing a
    287     * separate error stream is enabled.  The default behavior is for
    288     * verification to be enabled.
    289     *
    290     * @return True if verification is enabled, false if not.
    291     ***/
    292    public final boolean isRemoteVerificationEnabled()
    293    {
    294        return remoteVerificationEnabled;
    295    }
    296
    297}
    298