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