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 018 package org.apache.commons.net.bsd; 019 020 import java.io.IOException; 021 import java.io.InputStream; 022 import java.net.BindException; 023 import java.net.InetAddress; 024 import java.net.ServerSocket; 025 import java.net.Socket; 026 import java.net.SocketException; 027 028 import org.apache.commons.net.io.SocketInputStream; 029 030 /*** 031 * RCommandClient is very similar to 032 * {@link org.apache.commons.net.bsd.RExecClient}, 033 * from which it is derived, and implements the rcmd() facility that 034 * first appeared in 4.2BSD Unix. rcmd() is the facility used by the rsh 035 * (rshell) and other commands to execute a command on another machine 036 * from a trusted host without issuing a password. The trust relationship 037 * between two machines is established by the contents of a machine's 038 * /etc/hosts.equiv file and a user's .rhosts file. These files specify 039 * from which hosts and accounts on those hosts rcmd() requests will be 040 * accepted. The only additional measure for establishing trust is that 041 * all client connections must originate from a port between 512 and 1023. 042 * Consequently, there is an upper limit to the number of rcmd connections 043 * that can be running simultaneously. The required ports are reserved 044 * ports on Unix systems, and can only be bound by a 045 * process running with root permissions (to accomplish this rsh, rlogin, 046 * and related commands usualy have the suid bit set). Therefore, on a 047 * Unix system, you will only be able to successfully use the RCommandClient 048 * class if the process runs as root. However, there is no such restriction 049 * on Windows95 and some other systems. The security risks are obvious. 050 * However, when carefully used, rcmd() can be very useful when used behind 051 * a firewall. 052 * <p> 053 * As with virtually all of the client classes in org.apache.commons.net, this 054 * class derives from SocketClient. But it overrides most of its connection 055 * methods so that the local Socket will originate from an acceptable 056 * rshell port. The way to use RCommandClient is to first connect 057 * to the server, call the {@link #rcommand rcommand() } method, 058 * and then 059 * fetch the connection's input, output, and optionally error streams. 060 * Interaction with the remote command is controlled entirely through the 061 * I/O streams. Once you have finished processing the streams, you should 062 * invoke {@link org.apache.commons.net.bsd.RExecClient#disconnect disconnect() } 063 * to clean up properly. 064 * <p> 065 * By default the standard output and standard error streams of the 066 * remote process are transmitted over the same connection, readable 067 * from the input stream returned by 068 * {@link org.apache.commons.net.bsd.RExecClient#getInputStream getInputStream() } 069 * . However, it is 070 * possible to tell the rshd daemon to return the standard error 071 * stream over a separate connection, readable from the input stream 072 * returned by {@link org.apache.commons.net.bsd.RExecClient#getErrorStream getErrorStream() } 073 * . You 074 * can specify that a separate connection should be created for standard 075 * error by setting the boolean <code> separateErrorStream </code> 076 * parameter of {@link #rcommand rcommand() } to <code> true </code>. 077 * The standard input of the remote process can be written to through 078 * the output stream returned by 079 * {@link org.apache.commons.net.bsd.RExecClient#getOutputStream getOutputStream() } 080 * . 081 * <p> 082 * <p> 083 * @author Daniel F. Savarese 084 * @see org.apache.commons.net.SocketClient 085 * @see RExecClient 086 * @see RLoginClient 087 ***/ 088 089 public class RCommandClient extends RExecClient 090 { 091 /*** 092 * The default rshell port. Set to 514 in BSD Unix. 093 ***/ 094 public static final int DEFAULT_PORT = 514; 095 096 /*** 097 * The smallest port number an rcmd client may use. By BSD convention 098 * this number is 512. 099 ***/ 100 public static final int MIN_CLIENT_PORT = 512; 101 102 /*** 103 * The largest port number an rcmd client may use. By BSD convention 104 * this number is 1023. 105 ***/ 106 public static final int MAX_CLIENT_PORT = 1023; 107 108 // Overrides method in RExecClient in order to implement proper 109 // port number limitations. 110 @Override 111 InputStream _createErrorStream() throws IOException 112 { 113 int localPort; 114 ServerSocket server; 115 Socket socket; 116 117 localPort = MAX_CLIENT_PORT; 118 server = null; // Keep compiler from barfing 119 120 for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort) 121 { 122 try 123 { 124 server = _serverSocketFactory_.createServerSocket(localPort, 1, 125 getLocalAddress()); 126 } 127 catch (SocketException e) 128 { 129 continue; 130 } 131 break; 132 } 133 134 if (localPort < MIN_CLIENT_PORT) 135 throw new BindException("All ports in use."); 136 137 _output_.write(Integer.toString(server.getLocalPort()).getBytes()); 138 _output_.write('\0'); 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 * <p> 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 * @exception SocketException If the socket timeout could not be set. 176 * @exception BindException If all acceptable rshell ports are in use. 177 * @exception 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(InetAddress host, int port, 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 (BindException be) { 196 continue; 197 } 198 catch (SocketException e) 199 { 200 continue; 201 } 202 break; 203 } 204 205 if (localPort < MIN_CLIENT_PORT) 206 throw new BindException("All ports in use or insufficient permssion."); 207 208 _connectAction_(); 209 } 210 211 212 213 /*** 214 * Opens a Socket connected to a remote host at the specified port and 215 * originating from the current host at a port in a range acceptable 216 * to the BSD rshell daemon. 217 * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } 218 * is called to perform connection initialization actions. 219 * <p> 220 * @param host The remote host. 221 * @param port The port to connect to on the remote host. 222 * @exception SocketException If the socket timeout could not be set. 223 * @exception BindException If all acceptable rshell ports are in use. 224 * @exception IOException If the socket could not be opened. In most 225 * cases you will only want to catch IOException since SocketException is 226 * derived from it. 227 ***/ 228 @Override 229 public void connect(InetAddress host, int port) 230 throws SocketException, IOException 231 { 232 connect(host, port, InetAddress.getLocalHost()); 233 } 234 235 236 /*** 237 * Opens a Socket connected to a remote host at the specified port and 238 * originating from the current host at a port in a range acceptable 239 * to the BSD rshell daemon. 240 * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } 241 * is called to perform connection initialization actions. 242 * <p> 243 * @param hostname The name of the remote host. 244 * @param port The port to connect to on the remote host. 245 * @exception SocketException If the socket timeout could not be set. 246 * @exception BindException If all acceptable rshell ports are in use. 247 * @exception IOException If the socket could not be opened. In most 248 * cases you will only want to catch IOException since SocketException is 249 * derived from it. 250 * @exception UnknownHostException If the hostname cannot be resolved. 251 ***/ 252 @Override 253 public void connect(String hostname, int port) 254 throws SocketException, IOException 255 { 256 connect(InetAddress.getByName(hostname), port, InetAddress.getLocalHost()); 257 } 258 259 260 /*** 261 * Opens a Socket connected to a remote host at the specified port and 262 * originating from the specified local address using a port in a range 263 * acceptable to the BSD rshell daemon. 264 * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } 265 * is called to perform connection initialization actions. 266 * <p> 267 * @param hostname The remote host. 268 * @param port The port to connect to on the remote host. 269 * @param localAddr The local address to use. 270 * @exception SocketException If the socket timeout could not be set. 271 * @exception BindException If all acceptable rshell ports are in use. 272 * @exception IOException If the socket could not be opened. In most 273 * cases you will only want to catch IOException since SocketException is 274 * derived from it. 275 ***/ 276 public void connect(String hostname, int port, InetAddress localAddr) 277 throws SocketException, IOException 278 { 279 connect(InetAddress.getByName(hostname), port, localAddr); 280 } 281 282 283 /*** 284 * Opens a Socket connected to a remote host at the specified port and 285 * originating from the specified local address and port. The 286 * local port must lie between <code> MIN_CLIENT_PORT </code> and 287 * <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will 288 * be thrown. 289 * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } 290 * is called to perform connection initialization actions. 291 * <p> 292 * @param host The remote host. 293 * @param port The port to connect to on the remote host. 294 * @param localAddr The local address to use. 295 * @param localPort The local port to use. 296 * @exception SocketException If the socket timeout could not be set. 297 * @exception IOException If the socket could not be opened. In most 298 * cases you will only want to catch IOException since SocketException is 299 * derived from it. 300 * @exception IllegalArgumentException If an invalid local port number 301 * is specified. 302 ***/ 303 @Override 304 public void connect(InetAddress host, int port, 305 InetAddress localAddr, int localPort) 306 throws SocketException, IOException, IllegalArgumentException 307 { 308 if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT) 309 throw new IllegalArgumentException("Invalid port number " + localPort); 310 super.connect(host, port, localAddr, localPort); 311 } 312 313 314 /*** 315 * Opens a Socket connected to a remote host at the specified port and 316 * originating from the specified local address and port. The 317 * local port must lie between <code> MIN_CLIENT_PORT </code> and 318 * <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will 319 * be thrown. 320 * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } 321 * is called to perform connection initialization actions. 322 * <p> 323 * @param hostname The name of the remote host. 324 * @param port The port to connect to on the remote host. 325 * @param localAddr The local address to use. 326 * @param localPort The local port to use. 327 * @exception SocketException If the socket timeout could not be set. 328 * @exception IOException If the socket could not be opened. In most 329 * cases you will only want to catch IOException since SocketException is 330 * derived from it. 331 * @exception UnknownHostException If the hostname cannot be resolved. 332 * @exception IllegalArgumentException If an invalid local port number 333 * is specified. 334 ***/ 335 @Override 336 public void connect(String hostname, int port, 337 InetAddress localAddr, int localPort) 338 throws SocketException, IOException, IllegalArgumentException 339 { 340 if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT) 341 throw new IllegalArgumentException("Invalid port number " + localPort); 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 * @exception IOException If the rcommand() attempt fails. The exception 380 * will contain a message indicating the nature of the failure. 381 ***/ 382 public void rcommand(String localUsername, String remoteUsername, 383 String command, 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 ***/ 394 public void rcommand(String localUsername, String remoteUsername, 395 String command) 396 throws IOException 397 { 398 rcommand(localUsername, remoteUsername, command, false); 399 } 400 401 } 402