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