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