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 }