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