RExecClient.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.net.bsd;

  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.io.OutputStream;
  21. import java.net.ServerSocket;
  22. import java.net.Socket;
  23. import java.nio.charset.StandardCharsets;

  24. import org.apache.commons.net.SocketClient;
  25. import org.apache.commons.net.io.SocketInputStream;
  26. import org.apache.commons.net.util.NetConstants;

  27. /**
  28.  * 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
  29.  * 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
  30.  * for performing administrative tasks on a network behind a firewall.
  31.  * <p>
  32.  * As with virtually all the client classes in org.apache.commons.net, this class derives from SocketClient, inheriting its connection methods. The way to
  33.  * 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
  34.  * error streams. Interaction with the remote command is controlled entirely through the I/O streams. Once you have finished processing the streams, you should
  35.  * invoke {@link #disconnect disconnect()} to clean up properly.
  36.  * <p>
  37.  * By default, the standard output and standard error streams of the remote process are transmitted over the same connection, readable from the input stream
  38.  * returned by {@link #getInputStream getInputStream()}. However, it is possible to tell the rexecd daemon to return the standard error stream over a separate
  39.  * connection, readable from the input stream returned by {@link #getErrorStream getErrorStream()}. You can specify that a separate connection should be created
  40.  * for standard error by setting the boolean <code>separateErrorStream</code> parameter of {@link #rexec rexec()} to <code>true</code>. The standard input
  41.  * of the remote process can be written to through the output stream returned by {@link #getOutputStream getOutputSream()}.
  42.  *
  43.  * @see SocketClient
  44.  * @see RCommandClient
  45.  * @see RLoginClient
  46.  */
  47. public class RExecClient extends SocketClient {

  48.     /**
  49.      * @since 3.3
  50.      */
  51.     protected static final char NULL_CHAR = '\0';

  52.     /**
  53.      * The default rexec port. Set to 512 in BSD Unix.
  54.      */
  55.     public static final int DEFAULT_PORT = 512;

  56.     private boolean remoteVerificationEnabled;

  57.     /**
  58.      * If a separate error stream is requested, <code>_errorStream_</code> will point to an InputStream from which the standard error of the remote process can
  59.      * be read (after a call to rexec()). Otherwise, <code>_errorStream_</code> will be null.
  60.      */
  61.     protected InputStream _errorStream_;

  62.     /**
  63.      * The default RExecClient constructor. Initializes the default port to <code>DEFAULT_PORT</code>.
  64.      */
  65.     public RExecClient() {
  66.         _errorStream_ = null;
  67.         setDefaultPort(DEFAULT_PORT);
  68.     }

  69.     // This can be overridden in local package to implement port range
  70.     // limitations of rcmd and rlogin
  71.     InputStream createErrorStream() throws IOException {
  72.         final Socket socket;

  73.         try (final ServerSocket server = _serverSocketFactory_.createServerSocket(0, 1, getLocalAddress())) {
  74.             _output_.write(Integer.toString(server.getLocalPort()).getBytes(StandardCharsets.UTF_8)); // $NON-NLS-1$
  75.             _output_.write(NULL_CHAR);
  76.             _output_.flush();
  77.             socket = server.accept();
  78.         }

  79.         if (remoteVerificationEnabled && !verifyRemote(socket)) {
  80.             socket.close();
  81.             throw new IOException("Security violation: unexpected connection attempt by " + socket.getInetAddress().getHostAddress());
  82.         }

  83.         return new SocketInputStream(socket, socket.getInputStream());
  84.     }

  85.     /**
  86.      * Disconnects from the server, closing all associated open sockets and streams.
  87.      *
  88.      * @throws IOException If an error occurs while disconnecting.
  89.      */
  90.     @Override
  91.     public void disconnect() throws IOException {
  92.         if (_errorStream_ != null) {
  93.             _errorStream_.close();
  94.         }
  95.         _errorStream_ = null;
  96.         super.disconnect();
  97.     }

  98.     /**
  99.      * Returns the InputStream from which the standard error of the remote process can be read if a separate error stream is requested from the server.
  100.      * Otherwise, null will be returned. The error stream will only be set after a successful rexec() invocation.
  101.      *
  102.      * @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.
  103.      *         Otherwise, null will be returned.
  104.      */
  105.     public InputStream getErrorStream() {
  106.         return _errorStream_;
  107.     }

  108.     /**
  109.      * Returns 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()
  110.      * invocation.
  111.      *
  112.      * @return The InputStream from which the standard output of the remote process can be read.
  113.      */
  114.     public InputStream getInputStream() {
  115.         return _input_;
  116.     }

  117.     /**
  118.      * Returns the OutputStream through which the standard input of the remote process can be written. The output stream will only be set after a successful
  119.      * rexec() invocation.
  120.      *
  121.      * @return The OutputStream through which the standard input of the remote process can be written.
  122.      */
  123.     public OutputStream getOutputStream() {
  124.         return _output_;
  125.     }

  126.     /**
  127.      * Return whether or not verification of the remote host providing a separate error stream is enabled. The default behavior is for verification to be
  128.      * enabled.
  129.      *
  130.      * @return True if verification is enabled, false if not.
  131.      */
  132.     public final boolean isRemoteVerificationEnabled() {
  133.         return remoteVerificationEnabled;
  134.     }

  135.     /**
  136.      * Same as <code>rexec(user, password, command, false);</code>
  137.      *
  138.      * @param user the user name
  139.      * @param password the password
  140.      * @param command  the command to run
  141.      * @throws IOException if an error occurs
  142.      */
  143.     public void rexec(final String user, final String password, final String command) throws IOException {
  144.         rexec(user, password, command, false);
  145.     }

  146.     /**
  147.      * Remotely executes a command through the rexecd daemon on the server to which the RExecClient is connected. After calling this method, you may interact
  148.      * 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
  149.      * after reaching end of file on its standard output (accessible through {@link #getInputStream getInputStream()}). Disconnecting from the server or closing
  150.      * the process streams before reaching end of file will not necessarily terminate the remote process.
  151.      * <p>
  152.      * If a separate error stream is requested, the remote server will connect to a local socket opened by RExecClient, providing an independent stream through
  153.      * which standard error will be transmitted. RExecClient will do a simple security check when it accepts a connection for this error stream. If the
  154.      * connection does not originate from the remote server, an IOException will be thrown. This serves as a simple protection against possible hijacking of the
  155.      * error stream by an attacker monitoring the rexec() negotiation. You may disable this behavior with {@link #setRemoteVerificationEnabled
  156.      * setRemoteVerificationEnabled()} .
  157.      *
  158.      * @param user            The account name on the server through which to execute the command.
  159.      * @param password            The plain text password of the user account.
  160.      * @param command             The command, including any arguments, to execute.
  161.      * @param separateErrorStream True if you would like the standard error to be transmitted through a different stream than standard output. False if not.
  162.      * @throws IOException If the rexec() attempt fails. The exception will contain a message indicating the nature of the failure.
  163.      */
  164.     public void rexec(final String user, final String password, final String command, final boolean separateErrorStream) throws IOException {
  165.         int ch;

  166.         if (separateErrorStream) {
  167.             _errorStream_ = createErrorStream();
  168.         } else {
  169.             _output_.write(NULL_CHAR);
  170.         }

  171.         _output_.write(user.getBytes(getCharset()));
  172.         _output_.write(NULL_CHAR);
  173.         _output_.write(password.getBytes(getCharset()));
  174.         _output_.write(NULL_CHAR);
  175.         _output_.write(command.getBytes(getCharset()));
  176.         _output_.write(NULL_CHAR);
  177.         _output_.flush();

  178.         ch = _input_.read();
  179.         if (ch > 0) {
  180.             final StringBuilder buffer = new StringBuilder();

  181.             while ((ch = _input_.read()) != NetConstants.EOS && ch != '\n') {
  182.                 buffer.append((char) ch);
  183.             }

  184.             throw new IOException(buffer.toString());
  185.         }
  186.         if (ch < 0) {
  187.             throw new IOException("Server closed connection.");
  188.         }
  189.     }

  190.     /**
  191.      * Enable or disable verification that the remote host connecting to create a separate error stream is the same as the host to which the standard out stream
  192.      * 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.
  193.      *
  194.      * @param enable True to enable verification, false to disable verification.
  195.      */
  196.     public final void setRemoteVerificationEnabled(final boolean enable) {
  197.         remoteVerificationEnabled = enable;
  198.     }

  199. }