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