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 * https://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.io.IOUtils; 028import org.apache.commons.net.SocketClient; 029import org.apache.commons.net.io.SocketInputStream; 030import org.apache.commons.net.util.NetConstants; 031 032/** 033 * 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 034 * 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 035 * for performing administrative tasks on a network behind a firewall. 036 * <p> 037 * As with virtually all the client classes in org.apache.commons.net, this class derives from SocketClient, inheriting its connection methods. The way to 038 * 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 039 * error streams. Interaction with the remote command is controlled entirely through the I/O streams. Once you have finished processing the streams, you should 040 * invoke {@link #disconnect disconnect()} to clean up properly. 041 * </p> 042 * <p> 043 * By default, the standard output and standard error streams of the remote process are transmitted over the same connection, readable from the input stream 044 * returned by {@link #getInputStream getInputStream()}. However, it is possible to tell the rexecd daemon to return the standard error stream over a separate 045 * connection, readable from the input stream returned by {@link #getErrorStream getErrorStream()}. You can specify that a separate connection should be created 046 * for standard error by setting the boolean {@code separateErrorStream} parameter of {@link #rexec rexec()} to {@code true}. The standard input 047 * of the remote process can be written to through the output stream returned by {@link #getOutputStream getOutputSream()}. 048 * </p> 049 * 050 * @see SocketClient 051 * @see RCommandClient 052 * @see RLoginClient 053 */ 054public class RExecClient extends SocketClient { 055 056 /** 057 * The {@code NUL} character. 058 * 059 * @since 3.3 060 */ 061 protected static final char NULL_CHAR = '\0'; 062 063 /** 064 * The default rexec port. Set to 512 in BSD Unix. 065 */ 066 public static final int DEFAULT_PORT = 512; 067 068 private boolean remoteVerificationEnabled; 069 070 /** 071 * If a separate error stream is requested, {@code _errorStream_} will point to an InputStream from which the standard error of the remote process can 072 * be read (after a call to rexec()). Otherwise, {@code _errorStream_} will be null. 073 */ 074 protected InputStream _errorStream_; 075 076 /** 077 * The default RExecClient constructor. Initializes the default port to {@code DEFAULT_PORT}. 078 */ 079 public RExecClient() { 080 _errorStream_ = null; 081 setDefaultPort(DEFAULT_PORT); 082 } 083 084 // This can be overridden in local package to implement port range 085 // limitations of rcmd and rlogin 086 InputStream createErrorStream() throws IOException { 087 final Socket socket; 088 try (ServerSocket server = _serverSocketFactory_.createServerSocket(0, 1, getLocalAddress())) { 089 _output_.write(Integer.toString(server.getLocalPort()).getBytes(StandardCharsets.UTF_8)); // $NON-NLS-1$ 090 _output_.write(NULL_CHAR); 091 _output_.flush(); 092 socket = server.accept(); 093 } 094 if (remoteVerificationEnabled && !verifyRemote(socket)) { 095 final String hostAddress = getHostAddress(socket); 096 IOUtils.closeQuietly(socket); 097 throw new IOException("Security violation: unexpected connection attempt by " + hostAddress); 098 } 099 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}