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 034 * {@link org.apache.commons.net.bsd.RExecClient}, 035 * from which it is derived, and implements the rcmd() facility that 036 * first appeared in 4.2BSD Unix. rcmd() is the facility used by the rsh 037 * (rshell) and other commands to execute a command on another machine 038 * from a trusted host without issuing a password. The trust relationship 039 * between two machines is established by the contents of a machine's 040 * /etc/hosts.equiv file and a user's .rhosts file. These files specify 041 * from which hosts and accounts on those hosts rcmd() requests will be 042 * accepted. The only additional measure for establishing trust is that 043 * all client connections must originate from a port between 512 and 1023. 044 * Consequently, there is an upper limit to the number of rcmd connections 045 * that can be running simultaneously. The required ports are reserved 046 * ports on Unix systems, and can only be bound by a 047 * process running with root permissions (to accomplish this rsh, rlogin, 048 * and related commands usualy have the suid bit set). Therefore, on a 049 * Unix system, you will only be able to successfully use the RCommandClient 050 * class if the process runs as root. However, there is no such restriction 051 * on Windows95 and some other systems. The security risks are obvious. 052 * However, when carefully used, rcmd() can be very useful when used behind 053 * a firewall. 054 * <p> 055 * As with virtually all of the client classes in org.apache.commons.net, this 056 * class derives from SocketClient. But it overrides most of its connection 057 * methods so that the local Socket will originate from an acceptable 058 * rshell port. The way to use RCommandClient is to first connect 059 * to the server, call the {@link #rcommand rcommand() } method, 060 * and then 061 * fetch the connection's input, output, and optionally error streams. 062 * Interaction with the remote command is controlled entirely through the 063 * I/O streams. Once you have finished processing the streams, you should 064 * invoke {@link org.apache.commons.net.bsd.RExecClient#disconnect disconnect() } 065 * to clean up properly. 066 * <p> 067 * By default the standard output and standard error streams of the 068 * remote process are transmitted over the same connection, readable 069 * from the input stream returned by 070 * {@link org.apache.commons.net.bsd.RExecClient#getInputStream getInputStream() } 071 * . However, it is 072 * possible to tell the rshd daemon to return the standard error 073 * stream over a separate connection, readable from the input stream 074 * returned by {@link org.apache.commons.net.bsd.RExecClient#getErrorStream getErrorStream() } 075 * . You 076 * can specify that a separate connection should be created for standard 077 * error by setting the boolean <code> separateErrorStream </code> 078 * parameter of {@link #rcommand rcommand() } to <code> true </code>. 079 * The standard input of the remote process can be written to through 080 * the output stream returned by 081 * {@link org.apache.commons.net.bsd.RExecClient#getOutputStream getOutputStream() } 082 * . 083 * @see org.apache.commons.net.SocketClient 084 * @see RExecClient 085 * @see RLoginClient 086 */ 087 088public class RCommandClient extends RExecClient 089{ 090 /** 091 * The default rshell port. Set to 514 in BSD Unix. 092 */ 093 public static final int DEFAULT_PORT = 514; 094 095 /** 096 * The smallest port number an rcmd client may use. By BSD convention 097 * this number is 512. 098 */ 099 public static final int MIN_CLIENT_PORT = 512; 100 101 /** 102 * The largest port number an rcmd client may use. By BSD convention 103 * this number is 1023. 104 */ 105 public static final int MAX_CLIENT_PORT = 1023; 106 107 // Overrides method in RExecClient in order to implement proper 108 // port number limitations. 109 @Override 110 InputStream createErrorStream() throws IOException 111 { 112 int localPort; 113 ServerSocket server; 114 final Socket socket; 115 116 localPort = MAX_CLIENT_PORT; 117 server = null; // Keep compiler from barfing 118 119 for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort) 120 { 121 try 122 { 123 server = _serverSocketFactory_.createServerSocket(localPort, 1, 124 getLocalAddress()); 125 break; // got a socket 126 } 127 catch (final SocketException e) 128 { 129 continue; 130 } 131 } 132 133 if (server == null) { 134 throw new BindException("All ports in use."); 135 } 136 137 _output_.write(Integer.toString(server.getLocalPort()).getBytes(StandardCharsets.UTF_8)); // $NON-NLS 138 _output_.write(NULL_CHAR); 139 _output_.flush(); 140 141 socket = server.accept(); 142 server.close(); 143 144 if (isRemoteVerificationEnabled() && !verifyRemote(socket)) 145 { 146 socket.close(); 147 throw new IOException( 148 "Security violation: unexpected connection attempt by " + 149 socket.getInetAddress().getHostAddress()); 150 } 151 152 return new SocketInputStream(socket, socket.getInputStream()); 153 } 154 155 /** 156 * The default RCommandClient constructor. Initializes the 157 * default port to <code> DEFAULT_PORT </code>. 158 */ 159 public RCommandClient() 160 { 161 setDefaultPort(DEFAULT_PORT); 162 } 163 164 165 /** 166 * Opens a Socket connected to a remote host at the specified port and 167 * originating from the specified local address using a port in a range 168 * acceptable to the BSD rshell daemon. 169 * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } 170 * is called to perform connection initialization actions. 171 * 172 * @param host The remote host. 173 * @param port The port to connect to on the remote host. 174 * @param localAddr The local address to use. 175 * @throws SocketException If the socket timeout could not be set. 176 * @throws BindException If all acceptable rshell ports are in use. 177 * @throws IOException If the socket could not be opened. In most 178 * cases you will only want to catch IOException since SocketException is 179 * derived from it. 180 */ 181 public void connect(final InetAddress host, final int port, final InetAddress localAddr) 182 throws SocketException, BindException, IOException 183 { 184 int localPort; 185 186 localPort = MAX_CLIENT_PORT; 187 188 for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort) 189 { 190 try 191 { 192 _socket_ = 193 _socketFactory_.createSocket(host, port, localAddr, localPort); 194 } 195 catch (final SocketException e) 196 { 197 continue; 198 } 199 break; 200 } 201 202 if (localPort < MIN_CLIENT_PORT) { 203 throw new BindException("All ports in use or insufficient permssion."); 204 } 205 206 _connectAction_(); 207 } 208 209 210 211 /** 212 * Opens a Socket connected to a remote host at the specified port and 213 * originating from the current host at a port in a range acceptable 214 * to the BSD rshell daemon. 215 * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } 216 * is called to perform connection initialization actions. 217 * 218 * @param host The remote host. 219 * @param port The port to connect to on the remote host. 220 * @throws SocketException If the socket timeout could not be set. 221 * @throws BindException If all acceptable rshell ports are in use. 222 * @throws IOException If the socket could not be opened. In most 223 * cases you will only want to catch IOException since SocketException is 224 * derived from it. 225 */ 226 @Override 227 public void connect(final InetAddress host, final int port) 228 throws SocketException, IOException 229 { 230 connect(host, port, InetAddress.getLocalHost()); 231 } 232 233 234 /** 235 * Opens a Socket connected to a remote host at the specified port and 236 * originating from the current host at a port in a range acceptable 237 * to the BSD rshell daemon. 238 * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } 239 * is called to perform connection initialization actions. 240 * 241 * @param hostname The name of the remote host. 242 * @param port The port to connect to on the remote host. 243 * @throws SocketException If the socket timeout could not be set. 244 * @throws BindException If all acceptable rshell ports are in use. 245 * @throws IOException If the socket could not be opened. In most 246 * cases you will only want to catch IOException since SocketException is 247 * derived from it. 248 * @throws UnknownHostException If the hostname cannot be resolved. 249 */ 250 @Override 251 public void connect(final String hostname, final int port) 252 throws SocketException, IOException, UnknownHostException 253 { 254 connect(InetAddress.getByName(hostname), port, InetAddress.getLocalHost()); 255 } 256 257 258 /** 259 * Opens a Socket connected to a remote host at the specified port and 260 * originating from the specified local address using a port in a range 261 * acceptable to the BSD rshell daemon. 262 * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } 263 * is called to perform connection initialization actions. 264 * 265 * @param hostname The remote host. 266 * @param port The port to connect to on the remote host. 267 * @param localAddr The local address to use. 268 * @throws SocketException If the socket timeout could not be set. 269 * @throws BindException If all acceptable rshell ports are in use. 270 * @throws IOException If the socket could not be opened. In most 271 * cases you will only want to catch IOException since SocketException is 272 * derived from it. 273 */ 274 public void connect(final String hostname, final int port, final InetAddress localAddr) 275 throws SocketException, IOException 276 { 277 connect(InetAddress.getByName(hostname), port, localAddr); 278 } 279 280 281 /** 282 * Opens a Socket connected to a remote host at the specified port and 283 * originating from the specified local address and port. The 284 * local port must lie between <code> MIN_CLIENT_PORT </code> and 285 * <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will 286 * be thrown. 287 * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } 288 * is called to perform connection initialization actions. 289 * 290 * @param host The remote host. 291 * @param port The port to connect to on the remote host. 292 * @param localAddr The local address to use. 293 * @param localPort The local port to use. 294 * @throws SocketException If the socket timeout could not be set. 295 * @throws IOException If the socket could not be opened. In most 296 * cases you will only want to catch IOException since SocketException is 297 * derived from it. 298 * @throws IllegalArgumentException If an invalid local port number 299 * is specified. 300 */ 301 @Override 302 public void connect(final InetAddress host, final int port, 303 final InetAddress localAddr, final int localPort) 304 throws SocketException, IOException, IllegalArgumentException 305 { 306 if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT) { 307 throw new IllegalArgumentException("Invalid port number " + localPort); 308 } 309 super.connect(host, port, localAddr, localPort); 310 } 311 312 313 /** 314 * Opens a Socket connected to a remote host at the specified port and 315 * originating from the specified local address and port. The 316 * local port must lie between <code> MIN_CLIENT_PORT </code> and 317 * <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will 318 * be thrown. 319 * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } 320 * is called to perform connection initialization actions. 321 * 322 * @param hostname The name of the remote host. 323 * @param port The port to connect to on the remote host. 324 * @param localAddr The local address to use. 325 * @param localPort The local port to use. 326 * @throws SocketException If the socket timeout could not be set. 327 * @throws IOException If the socket could not be opened. In most 328 * cases you will only want to catch IOException since SocketException is 329 * derived from it. 330 * @throws UnknownHostException If the hostname cannot be resolved. 331 * @throws IllegalArgumentException If an invalid local port number 332 * is specified. 333 */ 334 @Override 335 public void connect(final String hostname, final int port, 336 final InetAddress localAddr, final int localPort) 337 throws SocketException, IOException, IllegalArgumentException, UnknownHostException 338 { 339 if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT) { 340 throw new IllegalArgumentException("Invalid port number " + localPort); 341 } 342 super.connect(hostname, port, localAddr, localPort); 343 } 344 345 346 /** 347 * Remotely executes a command through the rshd daemon on the server 348 * to which the RCommandClient is connected. After calling this method, 349 * you may interact with the remote process through its standard input, 350 * output, and error streams. You will typically be able to detect 351 * the termination of the remote process after reaching end of file 352 * on its standard output (accessible through 353 * {@link #getInputStream getInputStream() }. Disconnecting 354 * from the server or closing the process streams before reaching 355 * end of file will not necessarily terminate the remote process. 356 * <p> 357 * If a separate error stream is requested, the remote server will 358 * connect to a local socket opened by RCommandClient, providing an 359 * independent stream through which standard error will be transmitted. 360 * The local socket must originate from a secure port (512 - 1023), 361 * and rcommand() ensures that this will be so. 362 * RCommandClient will also do a simple security check when it accepts a 363 * connection for this error stream. If the connection does not originate 364 * from the remote server, an IOException will be thrown. This serves as 365 * a simple protection against possible hijacking of the error stream by 366 * an attacker monitoring the rexec() negotiation. You may disable this 367 * behavior with 368 * {@link org.apache.commons.net.bsd.RExecClient#setRemoteVerificationEnabled setRemoteVerificationEnabled()} 369 * . 370 * <p> 371 * @param localUsername The user account on the local machine that is 372 * requesting the command execution. 373 * @param remoteUsername The account name on the server through which to 374 * execute the command. 375 * @param command The command, including any arguments, to execute. 376 * @param separateErrorStream True if you would like the standard error 377 * to be transmitted through a different stream than standard output. 378 * False if not. 379 * @throws IOException If the rcommand() attempt fails. The exception 380 * will contain a message indicating the nature of the failure. 381 */ 382 public void rcommand(final String localUsername, final String remoteUsername, 383 final String command, final boolean separateErrorStream) 384 throws IOException 385 { 386 rexec(localUsername, remoteUsername, command, separateErrorStream); 387 } 388 389 390 /** 391 * Same as 392 * <code> rcommand(localUsername, remoteUsername, command, false); </code> 393 * @param localUsername the local user 394 * @param remoteUsername the remote user 395 * @param command the command 396 * @throws IOException on error 397 */ 398 public void rcommand(final String localUsername, final String remoteUsername, 399 final String command) 400 throws IOException 401 { 402 rcommand(localUsername, remoteUsername, command, false); 403 } 404 405} 406