    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
    018    package org.apache.commons.net.bsd;
    019
    020    import java.io.IOException;
    021    import java.io.InputStream;
    022    import java.io.OutputStream;
    023    import java.net.ServerSocket;
    024    import java.net.Socket;
    025
    026    import org.apache.commons.net.SocketClient;
    027    import 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     * <p>
    060     * <p>
    061     * @see SocketClient
    062     * @see RCommandClient
    063     * @see RLoginClient
    064     ***/
    065
    066    public class RExecClient extends SocketClient
    067    {
    068        /***
    069         * The default rexec port.  Set to 512 in BSD Unix.
    070         ***/
    071        public static final int DEFAULT_PORT = 512;
    072
    073        private boolean __remoteVerificationEnabled;
    074
    075        /***
    076         * If a separate error stream is requested, <code>_errorStream_</code>
    077         * will point to an InputStream from which the standard error of the
    078         * remote process can be read (after a call to rexec()).  Otherwise,
    079         * <code> _errorStream_ </code> will be null.
    080         ***/
    081        protected InputStream _errorStream_;
    082
    083        // This can be overridden in local package to implement port range
    084        // limitations of rcmd and rlogin
    085        InputStream _createErrorStream() throws IOException
    086        {
    087            ServerSocket server;
    088            Socket socket;
    089
    090            server = _serverSocketFactory_.createServerSocket(0, 1, getLocalAddress());
    091
    092            _output_.write(Integer.toString(server.getLocalPort()).getBytes("UTF-8")); // $NON-NLS-1$
    093            _output_.write('\0');
    094            _output_.flush();
    095
    096            socket = server.accept();
    097            server.close();
    098
    099            if (__remoteVerificationEnabled && !verifyRemote(socket))
    100            {
    101                socket.close();
    102                throw new IOException(
    103                    "Security violation: unexpected connection attempt by " +
    104                    socket.getInetAddress().getHostAddress());
    105            }
    106
    107            return (new SocketInputStream(socket, socket.getInputStream()));
    108        }
    109
    110
    111        /***
    112         * The default RExecClient constructor.  Initializes the
    113         * default port to <code> DEFAULT_PORT </code>.
    114         ***/
    115        public RExecClient()
    116        {
    117            _errorStream_ = null;
    118            setDefaultPort(DEFAULT_PORT);
    119        }
    120
    121
    122        /***
    123         * Returns the InputStream from which the standard outputof the remote
    124         * process can be read.  The input stream will only be set after a
    125         * successful rexec() invocation.
    126         * <p>
    127         * @return The InputStream from which the standard output of the remote
    128         * process can be read.
    129         ***/
    130        public InputStream getInputStream()
    131        {
    132            return _input_;
    133        }
    134
    135
    136        /***
    137         * Returns the OutputStream through which the standard input of the remote
    138         * process can be written.  The output stream will only be set after a
    139         * successful rexec() invocation.
    140         * <p>
    141         * @return The OutputStream through which the standard input of the remote
    142         * process can be written.
    143         ***/
    144        public OutputStream getOutputStream()
    145        {
    146            return _output_;
    147        }
    148
    149
    150        /***
    151         * Returns the InputStream from which the standard error of the remote
    152         * process can be read if a separate error stream is requested from
    153         * the server.  Otherwise, null will be returned.  The error stream
    154         * will only be set after a successful rexec() invocation.
    155         * <p>
    156         * @return 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.
    159         ***/
    160        public InputStream getErrorStream()
    161        {
    162            return _errorStream_;
    163        }
    164
    165
    166        /***
    167         * Remotely executes a command through the rexecd daemon on the server
    168         * to which the RExecClient is connected.  After calling this method,
    169         * you may interact with the remote process through its standard input,
    170         * output, and error streams.  You will typically be able to detect
    171         * the termination of the remote process after reaching end of file
    172         * on its standard output (accessible through
    173         * {@link #getInputStream  getInputStream() }.    Disconnecting
    174         * from the server or closing the process streams before reaching
    175         * end of file will not necessarily terminate the remote process.
    176         * <p>
    177         * If a separate error stream is requested, the remote server will
    178         * connect to a local socket opened by RExecClient, providing an
    179         * independent stream through which standard error will be transmitted.
    180         * RExecClient will do a simple security check when it accepts a
    181         * connection for this error stream.  If the connection does not originate
    182         * from the remote server, an IOException will be thrown.  This serves as
    183         * a simple protection against possible hijacking of the error stream by
    184         * an attacker monitoring the rexec() negotiation.  You may disable this
    185         * behavior with {@link #setRemoteVerificationEnabled setRemoteVerificationEnabled()}
    186         * .
    187         * <p>
    188         * @param username  The account name on the server through which to execute
    189         *                  the command.
    190         * @param password  The plain text password of the user account.
    191         * @param command   The command, including any arguments, to execute.
    192         * @param separateErrorStream True if you would like the standard error
    193         *        to be transmitted through a different stream than standard output.
    194         *        False if not.
    195         * @exception IOException If the rexec() attempt fails.  The exception
    196         *            will contain a message indicating the nature of the failure.
    197         ***/
    198        public void rexec(String username, String password,
    199                          String command, boolean separateErrorStream)
    200        throws IOException
    201        {
    202            int ch;
    203
    204            if (separateErrorStream)
    205            {
    206                _errorStream_ = _createErrorStream();
    207            }
    208            else
    209            {
    210                _output_.write('\0');
    211            }
    212
    213            _output_.write(username.getBytes());
    214            _output_.write('\0');
    215            _output_.write(password.getBytes());
    216            _output_.write('\0');
    217            _output_.write(command.getBytes());
    218            _output_.write('\0');
    219            _output_.flush();
    220
    221            ch = _input_.read();
    222            if (ch > 0) {
    223                StringBuilder buffer = new StringBuilder();
    224
    225                while ((ch = _input_.read()) != -1 && ch != '\n') {
    226                    buffer.append((char)ch);
    227                }
    228
    229                throw new IOException(buffer.toString());
    230            } else if (ch < 0) {
    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        @Override
    253        public void disconnect() throws IOException
    254        {
    255            if (_errorStream_ != null) {
    256                _errorStream_.close();
    257            }
    258            _errorStream_ = null;
    259            super.disconnect();
    260        }
    261
    262
    263        /***
    264         * Enable or disable verification that the remote host connecting to
    265         * create a separate error stream is the same as the host to which
    266         * the standard out stream is connected.  The default is for verification
    267         * to be enabled.  You may set this value at any time, whether the
    268         * client is currently connected or not.
    269         * <p>
    270         * @param enable True to enable verification, false to disable verification.
    271         ***/
    272        public final void setRemoteVerificationEnabled(boolean enable)
    273        {
    274            __remoteVerificationEnabled = enable;
    275        }
    276
    277        /***
    278         * Return whether or not verification of the remote host providing a
    279         * separate error stream is enabled.  The default behavior is for
    280         * verification to be enabled.
    281         * <p>
    282         * @return True if verification is enabled, false if not.
    283         ***/
    284        public final boolean isRemoteVerificationEnabled()
    285        {
    286            return __remoteVerificationEnabled;
    287        }
    288
    289    }
    290