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