View Javadoc
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  
18  package org.apache.commons.net.bsd;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.net.ServerSocket;
24  import java.net.Socket;
25  import java.nio.charset.StandardCharsets;
26  
27  import org.apache.commons.net.SocketClient;
28  import org.apache.commons.net.io.SocketInputStream;
29  import org.apache.commons.net.util.NetConstants;
30  
31  /**
32   * 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
33   * 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
34   * for performing administrative tasks on a network behind a firewall.
35   * <p>
36   * As with virtually all the client classes in org.apache.commons.net, this class derives from SocketClient, inheriting its connection methods. The way to
37   * 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
38   * error streams. Interaction with the remote command is controlled entirely through the I/O streams. Once you have finished processing the streams, you should
39   * invoke {@link #disconnect disconnect()} to clean up properly.
40   * <p>
41   * By default, the standard output and standard error streams of the remote process are transmitted over the same connection, readable from the input stream
42   * returned by {@link #getInputStream getInputStream()}. However, it is possible to tell the rexecd daemon to return the standard error stream over a separate
43   * connection, readable from the input stream returned by {@link #getErrorStream getErrorStream()}. You can specify that a separate connection should be created
44   * for standard error by setting the boolean <code>separateErrorStream</code> parameter of {@link #rexec rexec()} to <code>true</code>. The standard input
45   * of the remote process can be written to through the output stream returned by {@link #getOutputStream getOutputSream()}.
46   *
47   * @see SocketClient
48   * @see RCommandClient
49   * @see RLoginClient
50   */
51  public class RExecClient extends SocketClient {
52  
53      /**
54       * @since 3.3
55       */
56      protected static final char NULL_CHAR = '\0';
57  
58      /**
59       * The default rexec port. Set to 512 in BSD Unix.
60       */
61      public static final int DEFAULT_PORT = 512;
62  
63      private boolean remoteVerificationEnabled;
64  
65      /**
66       * 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
67       * be read (after a call to rexec()). Otherwise, <code>_errorStream_</code> will be null.
68       */
69      protected InputStream _errorStream_;
70  
71      /**
72       * The default RExecClient constructor. Initializes the default port to <code>DEFAULT_PORT</code>.
73       */
74      public RExecClient() {
75          _errorStream_ = null;
76          setDefaultPort(DEFAULT_PORT);
77      }
78  
79      // This can be overridden in local package to implement port range
80      // limitations of rcmd and rlogin
81      InputStream createErrorStream() throws IOException {
82          final Socket socket;
83  
84          try (final ServerSocket server = _serverSocketFactory_.createServerSocket(0, 1, getLocalAddress())) {
85              _output_.write(Integer.toString(server.getLocalPort()).getBytes(StandardCharsets.UTF_8)); // $NON-NLS-1$
86              _output_.write(NULL_CHAR);
87              _output_.flush();
88              socket = server.accept();
89          }
90  
91          if (remoteVerificationEnabled && !verifyRemote(socket)) {
92              socket.close();
93              throw new IOException("Security violation: unexpected connection attempt by " + socket.getInetAddress().getHostAddress());
94          }
95  
96          return new SocketInputStream(socket, socket.getInputStream());
97      }
98  
99      /**
100      * Disconnects from the server, closing all associated open sockets and streams.
101      *
102      * @throws IOException If an error occurs while disconnecting.
103      */
104     @Override
105     public void disconnect() throws IOException {
106         if (_errorStream_ != null) {
107             _errorStream_.close();
108         }
109         _errorStream_ = null;
110         super.disconnect();
111     }
112 
113     /**
114      * 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.
115      * Otherwise, null will be returned. The error stream will only be set after a successful rexec() invocation.
116      *
117      * @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.
118      *         Otherwise, null will be returned.
119      */
120     public InputStream getErrorStream() {
121         return _errorStream_;
122     }
123 
124     /**
125      * 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()
126      * invocation.
127      *
128      * @return The InputStream from which the standard output of the remote process can be read.
129      */
130     public InputStream getInputStream() {
131         return _input_;
132     }
133 
134     /**
135      * 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
136      * rexec() invocation.
137      *
138      * @return The OutputStream through which the standard input of the remote process can be written.
139      */
140     public OutputStream getOutputStream() {
141         return _output_;
142     }
143 
144     /**
145      * Return whether or not verification of the remote host providing a separate error stream is enabled. The default behavior is for verification to be
146      * enabled.
147      *
148      * @return True if verification is enabled, false if not.
149      */
150     public final boolean isRemoteVerificationEnabled() {
151         return remoteVerificationEnabled;
152     }
153 
154     /**
155      * Same as <code>rexec(user, password, command, false);</code>
156      *
157      * @param user the user name
158      * @param password the password
159      * @param command  the command to run
160      * @throws IOException if an error occurs
161      */
162     public void rexec(final String user, final String password, final String command) throws IOException {
163         rexec(user, password, command, false);
164     }
165 
166     /**
167      * Remotely executes a command through the rexecd daemon on the server to which the RExecClient is connected. After calling this method, you may interact
168      * 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
169      * after reaching end of file on its standard output (accessible through {@link #getInputStream getInputStream()}). Disconnecting from the server or closing
170      * the process streams before reaching end of file will not necessarily terminate the remote process.
171      * <p>
172      * If a separate error stream is requested, the remote server will connect to a local socket opened by RExecClient, providing an independent stream through
173      * which standard error will be transmitted. RExecClient will do a simple security check when it accepts a connection for this error stream. If the
174      * connection does not originate from the remote server, an IOException will be thrown. This serves as a simple protection against possible hijacking of the
175      * error stream by an attacker monitoring the rexec() negotiation. You may disable this behavior with {@link #setRemoteVerificationEnabled
176      * setRemoteVerificationEnabled()} .
177      *
178      * @param user            The account name on the server through which to execute the command.
179      * @param password            The plain text password of the user account.
180      * @param command             The command, including any arguments, to execute.
181      * @param separateErrorStream True if you would like the standard error to be transmitted through a different stream than standard output. False if not.
182      * @throws IOException If the rexec() attempt fails. The exception will contain a message indicating the nature of the failure.
183      */
184     public void rexec(final String user, final String password, final String command, final boolean separateErrorStream) throws IOException {
185         int ch;
186 
187         if (separateErrorStream) {
188             _errorStream_ = createErrorStream();
189         } else {
190             _output_.write(NULL_CHAR);
191         }
192 
193         _output_.write(user.getBytes(getCharset()));
194         _output_.write(NULL_CHAR);
195         _output_.write(password.getBytes(getCharset()));
196         _output_.write(NULL_CHAR);
197         _output_.write(command.getBytes(getCharset()));
198         _output_.write(NULL_CHAR);
199         _output_.flush();
200 
201         ch = _input_.read();
202         if (ch > 0) {
203             final StringBuilder buffer = new StringBuilder();
204 
205             while ((ch = _input_.read()) != NetConstants.EOS && ch != '\n') {
206                 buffer.append((char) ch);
207             }
208 
209             throw new IOException(buffer.toString());
210         }
211         if (ch < 0) {
212             throw new IOException("Server closed connection.");
213         }
214     }
215 
216     /**
217      * 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
218      * 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.
219      *
220      * @param enable True to enable verification, false to disable verification.
221      */
222     public final void setRemoteVerificationEnabled(final boolean enable) {
223         remoteVerificationEnabled = enable;
224     }
225 
226 }