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.net.BindException; 23 import java.net.InetAddress; 24 import java.net.ServerSocket; 25 import java.net.Socket; 26 import java.net.SocketException; 27 import java.net.UnknownHostException; 28 import java.nio.charset.StandardCharsets; 29 30 import org.apache.commons.io.IOUtils; 31 import org.apache.commons.net.io.SocketInputStream; 32 33 /** 34 * RCommandClient is very similar to {@link org.apache.commons.net.bsd.RExecClient}, from which it is derived, and implements the rcmd() facility that first 35 * appeared in 4.2BSD Unix. rcmd() is the facility used by the rsh (rshell) and other commands to execute a command on another machine from a trusted host 36 * without issuing a password. The trust relationship between two machines is established by the contents of a machine's /etc/hosts.equiv file and a user's 37 * .rhosts file. These files specify from which hosts and accounts on those hosts rcmd() requests will be accepted. The only additional measure for establishing 38 * trust is that all client connections must originate from a port between 512 and 1023. Consequently, there is an upper limit to the number of rcmd connections 39 * that can be running simultaneously. The required ports are reserved ports on Unix systems, and can only be bound by a process running with root permissions 40 * (to accomplish this rsh, rlogin, and related commands usualy have the suid bit set). Therefore, on a Unix system, you will only be able to successfully use 41 * the RCommandClient class if the process runs as root. However, there is no such restriction on Windows95 and some other systems. The security risks are 42 * obvious. However, when carefully used, rcmd() can be very useful when used behind a firewall. 43 * <p> 44 * As with virtually all the client classes in org.apache.commons.net, this class derives from SocketClient. But it overrides most of its connection methods 45 * so that the local Socket will originate from an acceptable rshell port. The way to use RCommandClient is to first connect to the server, call the 46 * {@link #rcommand rcommand()} method, and then fetch the connection's input, output, and optionally error streams. Interaction with the remote command is 47 * controlled entirely through the I/O streams. Once you have finished processing the streams, you should invoke 48 * {@link org.apache.commons.net.bsd.RExecClient#disconnect disconnect()} to clean up properly. 49 * </p> 50 * <p> 51 * By default, the standard output and standard error streams of the remote process are transmitted over the same connection, readable from the input stream 52 * returned by {@link org.apache.commons.net.bsd.RExecClient#getInputStream getInputStream()} . However, it is possible to tell the rshd daemon to return the 53 * standard error stream over a separate connection, readable from the input stream returned by {@link org.apache.commons.net.bsd.RExecClient#getErrorStream 54 * getErrorStream()} . You can specify that a separate connection should be created for standard error by setting the boolean 55 * {@code separateErrorStream} parameter of {@link #rcommand rcommand()} to {@code true}. The standard input of the remote process can be written 56 * to through the output stream returned by {@link org.apache.commons.net.bsd.RExecClient#getOutputStream getOutputStream()}. 57 * </p> 58 * 59 * @see org.apache.commons.net.SocketClient 60 * @see RExecClient 61 * @see RLoginClient 62 */ 63 public class RCommandClient extends RExecClient { 64 65 /** 66 * The default rshell port. Set to 514 in BSD Unix. 67 */ 68 public static final int DEFAULT_PORT = 514; 69 70 /** 71 * The smallest port number a rcmd client may use. By BSD convention this number is 512. 72 */ 73 public static final int MIN_CLIENT_PORT = 512; 74 75 /** 76 * The largest port number a rcmd client may use. By BSD convention this number is 1023. 77 */ 78 public static final int MAX_CLIENT_PORT = 1023; 79 80 /** 81 * The default RCommandClient constructor. Initializes the default port to {@code DEFAULT_PORT}. 82 */ 83 public RCommandClient() { 84 setDefaultPort(DEFAULT_PORT); 85 } 86 87 /** 88 * Opens a Socket connected to a remote host at the specified port and originating from the current host at a port in a range acceptable to the BSD rshell 89 * daemon. Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_()} is called to perform connection initialization 90 * actions. 91 * 92 * @param host The remote host. 93 * @param port The port to connect to on the remote host. 94 * @throws SocketException If the socket timeout could not be set. 95 * @throws BindException If all acceptable rshell ports are in use. 96 * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived from 97 * it. 98 */ 99 @Override 100 public void connect(final InetAddress host, final int port) throws SocketException, IOException { 101 connect(host, port, InetAddress.getLocalHost()); 102 } 103 104 /** 105 * Opens a Socket connected to a remote host at the specified port and originating from the specified local address using a port in a range acceptable to 106 * the BSD rshell daemon. Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_()} is called to perform connection 107 * initialization actions. 108 * 109 * @param host The remote host. 110 * @param port The port to connect to on the remote host. 111 * @param localAddr The local address to use. 112 * @throws SocketException If the socket timeout could not be set. 113 * @throws BindException If all acceptable rshell ports are in use. 114 * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived from 115 * it. 116 */ 117 public void connect(final InetAddress host, final int port, final InetAddress localAddr) throws SocketException, BindException, IOException { 118 int localPort; 119 120 for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort) { 121 try { 122 _socket_ = _socketFactory_.createSocket(host, port, localAddr, localPort); 123 break; 124 } catch (final SocketException e) { 125 continue; 126 } 127 } 128 129 if (localPort < MIN_CLIENT_PORT) { 130 throw new BindException("All ports in use or insufficient permssion."); 131 } 132 133 _connectAction_(); 134 } 135 136 /** 137 * Opens a Socket connected to a remote host at the specified port and originating from the specified local address and port. The local port must lie 138 * between {@code MIN_CLIENT_PORT} and {@code MAX_CLIENT_PORT} or an IllegalArgumentException will be thrown. Before returning, 139 * {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_()} is called to perform connection initialization actions. 140 * 141 * @param host The remote host. 142 * @param port The port to connect to on the remote host. 143 * @param localAddr The local address to use. 144 * @param localPort The local port to use. 145 * @throws SocketException If the socket timeout could not be set. 146 * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is 147 * derived from it. 148 * @throws IllegalArgumentException If an invalid local port number is specified. 149 */ 150 @Override 151 public void connect(final InetAddress host, final int port, final InetAddress localAddr, final int localPort) 152 throws SocketException, IOException, IllegalArgumentException { 153 if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT) { 154 throw new IllegalArgumentException("Invalid port number " + localPort); 155 } 156 super.connect(host, port, localAddr, localPort); 157 } 158 159 /** 160 * Opens a Socket connected to a remote host at the specified port and originating from the current host at a port in a range acceptable to the BSD rshell 161 * daemon. Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_()} is called to perform connection initialization 162 * actions. 163 * 164 * @param hostname The name of the remote host. 165 * @param port The port to connect to on the remote host. 166 * @throws SocketException If the socket timeout could not be set. 167 * @throws BindException If all acceptable rshell ports are in use. 168 * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived 169 * from it. 170 * @throws UnknownHostException If the hostname cannot be resolved. 171 */ 172 @Override 173 public void connect(final String hostname, final int port) throws SocketException, IOException, UnknownHostException { 174 connect(InetAddress.getByName(hostname), port, InetAddress.getLocalHost()); 175 } 176 177 /** 178 * Opens a Socket connected to a remote host at the specified port and originating from the specified local address using a port in a range acceptable to 179 * the BSD rshell daemon. Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_()} is called to perform connection 180 * initialization actions. 181 * 182 * @param hostname The remote host. 183 * @param port The port to connect to on the remote host. 184 * @param localAddr The local address to use. 185 * @throws SocketException If the socket timeout could not be set. 186 * @throws BindException If all acceptable rshell ports are in use. 187 * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived from 188 * it. 189 */ 190 public void connect(final String hostname, final int port, final InetAddress localAddr) throws SocketException, IOException { 191 connect(InetAddress.getByName(hostname), port, localAddr); 192 } 193 194 /** 195 * Opens a Socket connected to a remote host at the specified port and originating from the specified local address and port. The local port must lie 196 * between {@code MIN_CLIENT_PORT} and {@code MAX_CLIENT_PORT} or an IllegalArgumentException will be thrown. Before returning, 197 * {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_()} is called to perform connection initialization actions. 198 * 199 * @param hostname The name of the remote host. 200 * @param port The port to connect to on the remote host. 201 * @param localAddr The local address to use. 202 * @param localPort The local port to use. 203 * @throws SocketException If the socket timeout could not be set. 204 * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is 205 * derived from it. 206 * @throws UnknownHostException If the hostname cannot be resolved. 207 * @throws IllegalArgumentException If an invalid local port number is specified. 208 */ 209 @Override 210 public void connect(final String hostname, final int port, final InetAddress localAddr, final int localPort) 211 throws SocketException, IOException, IllegalArgumentException, UnknownHostException { 212 if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT) { 213 throw new IllegalArgumentException("Invalid port number " + localPort); 214 } 215 super.connect(hostname, port, localAddr, localPort); 216 } 217 218 /** 219 * Overrides method in RExecClient in order to implement proper port number limitations. 220 */ 221 @Override 222 InputStream createErrorStream() throws IOException { 223 final Socket socket; 224 try (ServerSocket server = createServer()) { 225 _output_.write(Integer.toString(server.getLocalPort()).getBytes(StandardCharsets.UTF_8)); 226 _output_.write(NULL_CHAR); 227 _output_.flush(); 228 socket = server.accept(); 229 } 230 if (isRemoteVerificationEnabled() && !verifyRemote(socket)) { 231 final String hostAddress = getHostAddress(socket); 232 IOUtils.closeQuietly(socket); 233 throw new IOException("Security violation: unexpected connection attempt by " + hostAddress); 234 } 235 return new SocketInputStream(socket, socket.getInputStream()); 236 } 237 238 private ServerSocket createServer() throws IOException { 239 for (int localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort) { 240 try { 241 return _serverSocketFactory_.createServerSocket(localPort, 1, getLocalAddress()); 242 } catch (final SocketException e) { 243 continue; 244 } 245 } 246 throw new BindException("All ports in use."); 247 } 248 249 /** 250 * Same as {@code rcommand(localUserName, remoteUserName, command, false);} 251 * 252 * @param localUser the local user 253 * @param remoteUser the remote user 254 * @param command the command 255 * @throws IOException on error 256 */ 257 public void rcommand(final String localUser, final String remoteUser, final String command) throws IOException { 258 rcommand(localUser, remoteUser, command, false); 259 } 260 261 /** 262 * Remotely executes a command through the rshd daemon on the server to which the RCommandClient is connected. After calling this method, you may interact 263 * 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 264 * after reaching end of file on its standard output (accessible through {@link #getInputStream getInputStream()}). Disconnecting from the server or closing 265 * the process streams before reaching end of file will not necessarily terminate the remote process. 266 * <p> 267 * If a separate error stream is requested, the remote server will connect to a local socket opened by RCommandClient, providing an independent stream 268 * through which standard error will be transmitted. The local socket must originate from a secure port (512 - 1023), and rcommand() ensures that this will 269 * be so. RCommandClient will also do a simple security check when it accepts a connection for this error stream. If the connection does not originate from 270 * the remote server, an IOException will be thrown. This serves as a simple protection against possible hijacking of the error stream by an attacker 271 * monitoring the rexec() negotiation. You may disable this behavior with {@link org.apache.commons.net.bsd.RExecClient#setRemoteVerificationEnabled 272 * setRemoteVerificationEnabled()}. 273 * </p> 274 * 275 * @param localUser The user account on the local machine that is requesting the command execution. 276 * @param remoteUser The account name on the server through which to execute the command. 277 * @param command The command, including any arguments, to execute. 278 * @param separateErrorStream True if you would like the standard error to be transmitted through a different stream than standard output. False if not. 279 * @throws IOException If the rcommand() attempt fails. The exception will contain a message indicating the nature of the failure. 280 */ 281 public void rcommand(final String localUser, final String remoteUser, final String command, final boolean separateErrorStream) throws IOException { 282 rexec(localUser, remoteUser, command, separateErrorStream); 283 } 284 285 }