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 * http://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.net.BindException; 023import java.net.InetAddress; 024import java.net.ServerSocket; 025import java.net.Socket; 026import java.net.SocketException; 027import java.net.UnknownHostException; 028import java.nio.charset.StandardCharsets; 029 030import org.apache.commons.net.io.SocketInputStream; 031 032/** 033 * RCommandClient is very similar to {@link org.apache.commons.net.bsd.RExecClient}, from which it is derived, and implements the rcmd() facility that first 034 * 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 035 * 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 036 * .rhosts file. These files specify from which hosts and accounts on those hosts rcmd() requests will be accepted. The only additional measure for establishing 037 * 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 038 * 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 039 * (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 040 * 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 041 * obvious. However, when carefully used, rcmd() can be very useful when used behind a firewall. 042 * <p> 043 * As with virtually all of the client classes in org.apache.commons.net, this class derives from SocketClient. But it overrides most of its connection methods 044 * 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 045 * {@link #rcommand rcommand() } method, and then fetch the connection's input, output, and optionally error streams. Interaction with the remote command is 046 * controlled entirely through the I/O streams. Once you have finished processing the streams, you should invoke 047 * {@link org.apache.commons.net.bsd.RExecClient#disconnect disconnect() } to clean up properly. 048 * <p> 049 * By default the standard output and standard error streams of the remote process are transmitted over the same connection, readable from the input stream 050 * returned by {@link org.apache.commons.net.bsd.RExecClient#getInputStream getInputStream() } . However, it is possible to tell the rshd daemon to return the 051 * standard error stream over a separate connection, readable from the input stream returned by {@link org.apache.commons.net.bsd.RExecClient#getErrorStream 052 * getErrorStream() } . You can specify that a separate connection should be created for standard error by setting the boolean 053 * <code> separateErrorStream </code> parameter of {@link #rcommand rcommand() } to <code> true </code>. The standard input of the remote process can be written 054 * to through the output stream returned by {@link org.apache.commons.net.bsd.RExecClient#getOutputStream getOutputStream() } . 055 * 056 * @see org.apache.commons.net.SocketClient 057 * @see RExecClient 058 * @see RLoginClient 059 */ 060 061public class RCommandClient extends RExecClient { 062 /** 063 * The default rshell port. Set to 514 in BSD Unix. 064 */ 065 public static final int DEFAULT_PORT = 514; 066 067 /** 068 * The smallest port number an rcmd client may use. By BSD convention this number is 512. 069 */ 070 public static final int MIN_CLIENT_PORT = 512; 071 072 /** 073 * The largest port number an rcmd client may use. By BSD convention this number is 1023. 074 */ 075 public static final int MAX_CLIENT_PORT = 1023; 076 077 /** 078 * The default RCommandClient constructor. Initializes the default port to <code> DEFAULT_PORT </code>. 079 */ 080 public RCommandClient() { 081 setDefaultPort(DEFAULT_PORT); 082 } 083 084 /** 085 * 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 086 * daemon. Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } is called to perform connection initialization 087 * actions. 088 * 089 * @param host The remote host. 090 * @param port The port to connect to on the remote host. 091 * @throws SocketException If the socket timeout could not be set. 092 * @throws BindException If all acceptable rshell ports are in use. 093 * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived from 094 * it. 095 */ 096 @Override 097 public void connect(final InetAddress host, final int port) throws SocketException, IOException { 098 connect(host, port, InetAddress.getLocalHost()); 099 } 100 101 /** 102 * 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 103 * the BSD rshell daemon. Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } is called to perform connection 104 * initialization actions. 105 * 106 * @param host The remote host. 107 * @param port The port to connect to on the remote host. 108 * @param localAddr The local address to use. 109 * @throws SocketException If the socket timeout could not be set. 110 * @throws BindException If all acceptable rshell ports are in use. 111 * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived from 112 * it. 113 */ 114 public void connect(final InetAddress host, final int port, final InetAddress localAddr) throws SocketException, BindException, IOException { 115 int localPort; 116 117 localPort = MAX_CLIENT_PORT; 118 119 for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort) { 120 try { 121 _socket_ = _socketFactory_.createSocket(host, port, localAddr, localPort); 122 } catch (final SocketException e) { 123 continue; 124 } 125 break; 126 } 127 128 if (localPort < MIN_CLIENT_PORT) { 129 throw new BindException("All ports in use or insufficient permssion."); 130 } 131 132 _connectAction_(); 133 } 134 135 /** 136 * 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 137 * between <code> MIN_CLIENT_PORT </code> and <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will be thrown. Before returning, 138 * {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } is called to perform connection initialization actions. 139 * 140 * @param host The remote host. 141 * @param port The port to connect to on the remote host. 142 * @param localAddr The local address to use. 143 * @param localPort The local port to use. 144 * @throws SocketException If the socket timeout could not be set. 145 * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is 146 * derived from it. 147 * @throws IllegalArgumentException If an invalid local port number is specified. 148 */ 149 @Override 150 public void connect(final InetAddress host, final int port, final InetAddress localAddr, final int localPort) 151 throws SocketException, IOException, IllegalArgumentException { 152 if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT) { 153 throw new IllegalArgumentException("Invalid port number " + localPort); 154 } 155 super.connect(host, port, localAddr, localPort); 156 } 157 158 /** 159 * 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 160 * daemon. Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } is called to perform connection initialization 161 * actions. 162 * 163 * @param hostname The name of the remote host. 164 * @param port The port to connect to on the remote host. 165 * @throws SocketException If the socket timeout could not be set. 166 * @throws BindException If all acceptable rshell ports are in use. 167 * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived 168 * from it. 169 * @throws UnknownHostException If the hostname cannot be resolved. 170 */ 171 @Override 172 public void connect(final String hostname, final int port) throws SocketException, IOException, UnknownHostException { 173 connect(InetAddress.getByName(hostname), port, InetAddress.getLocalHost()); 174 } 175 176 /** 177 * 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 178 * the BSD rshell daemon. Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } is called to perform connection 179 * initialization actions. 180 * 181 * @param hostname The remote host. 182 * @param port The port to connect to on the remote host. 183 * @param localAddr The local address to use. 184 * @throws SocketException If the socket timeout could not be set. 185 * @throws BindException If all acceptable rshell ports are in use. 186 * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived from 187 * it. 188 */ 189 public void connect(final String hostname, final int port, final InetAddress localAddr) throws SocketException, IOException { 190 connect(InetAddress.getByName(hostname), port, localAddr); 191 } 192 193 /** 194 * 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 195 * between <code> MIN_CLIENT_PORT </code> and <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will be thrown. Before returning, 196 * {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } is called to perform connection initialization actions. 197 * 198 * @param hostname The name of the remote host. 199 * @param port The port to connect to on the remote host. 200 * @param localAddr The local address to use. 201 * @param localPort The local port to use. 202 * @throws SocketException If the socket timeout could not be set. 203 * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is 204 * derived from it. 205 * @throws UnknownHostException If the hostname cannot be resolved. 206 * @throws IllegalArgumentException If an invalid local port number is specified. 207 */ 208 @Override 209 public void connect(final String hostname, final int port, final InetAddress localAddr, final int localPort) 210 throws SocketException, IOException, IllegalArgumentException, UnknownHostException { 211 if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT) { 212 throw new IllegalArgumentException("Invalid port number " + localPort); 213 } 214 super.connect(hostname, port, localAddr, localPort); 215 } 216 217 // Overrides method in RExecClient in order to implement proper 218 // port number limitations. 219 @Override 220 InputStream createErrorStream() throws IOException { 221 int localPort; 222 final Socket socket; 223 224 localPort = MAX_CLIENT_PORT; 225 ServerSocket server = null; 226 227 for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort) { 228 try { 229 server = _serverSocketFactory_.createServerSocket(localPort, 1, getLocalAddress()); 230 break; // got a socket 231 } catch (final SocketException e) { 232 continue; 233 } 234 } 235 236 if (server == null) { 237 throw new BindException("All ports in use."); 238 } 239 240 _output_.write(Integer.toString(server.getLocalPort()).getBytes(StandardCharsets.UTF_8)); // $NON-NLS 241 _output_.write(NULL_CHAR); 242 _output_.flush(); 243 244 socket = server.accept(); 245 server.close(); 246 247 if (isRemoteVerificationEnabled() && !verifyRemote(socket)) { 248 socket.close(); 249 throw new IOException("Security violation: unexpected connection attempt by " + socket.getInetAddress().getHostAddress()); 250 } 251 252 return new SocketInputStream(socket, socket.getInputStream()); 253 } 254 255 /** 256 * Same as <code> rcommand(localUsername, remoteUsername, command, false); </code> 257 * 258 * @param localUsername the local user 259 * @param remoteUsername the remote user 260 * @param command the command 261 * @throws IOException on error 262 */ 263 public void rcommand(final String localUsername, final String remoteUsername, final String command) throws IOException { 264 rcommand(localUsername, remoteUsername, command, false); 265 } 266 267 /** 268 * Remotely executes a command through the rshd daemon on the server to which the RCommandClient is connected. After calling this method, you may interact 269 * 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 270 * after reaching end of file on its standard output (accessible through {@link #getInputStream getInputStream() }. Disconnecting from the server or closing 271 * the process streams before reaching end of file will not necessarily terminate the remote process. 272 * <p> 273 * If a separate error stream is requested, the remote server will connect to a local socket opened by RCommandClient, providing an independent stream 274 * through which standard error will be transmitted. The local socket must originate from a secure port (512 - 1023), and rcommand() ensures that this will 275 * 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 276 * the remote server, an IOException will be thrown. This serves as a simple protection against possible hijacking of the error stream by an attacker 277 * monitoring the rexec() negotiation. You may disable this behavior with {@link org.apache.commons.net.bsd.RExecClient#setRemoteVerificationEnabled 278 * setRemoteVerificationEnabled()} . 279 * <p> 280 * 281 * @param localUsername The user account on the local machine that is requesting the command execution. 282 * @param remoteUsername The account name on the server through which to execute the command. 283 * @param command The command, including any arguments, to execute. 284 * @param separateErrorStream True if you would like the standard error to be transmitted through a different stream than standard output. False if not. 285 * @throws IOException If the rcommand() attempt fails. The exception will contain a message indicating the nature of the failure. 286 */ 287 public void rcommand(final String localUsername, final String remoteUsername, final String command, final boolean separateErrorStream) throws IOException { 288 rexec(localUsername, remoteUsername, command, separateErrorStream); 289 } 290 291}