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.ServerSocket; 021 import java.net.Socket; 022 import org.apache.commons.net.io.SocketInputStream; 023 import org.apache.commons.net.SocketClient; 024 import java.io.OutputStream; 025 026 /*** 027 * RExecClient implements the rexec() facility that first appeared in 028 * 4.2BSD Unix. This class will probably only be of use for connecting 029 * to Unix systems and only when the rexecd daemon is configured to run, 030 * which is a rarity these days because of the security risks involved. 031 * However, rexec() can be very useful for performing administrative tasks 032 * on a network behind a firewall. 033 * <p> 034 * As with virtually all of the client classes in org.apache.commons.net, this 035 * class derives from SocketClient, inheriting its connection methods. 036 * The way to use RExecClient is to first connect 037 * to the server, call the {@link #rexec rexec() } method, and then 038 * fetch the connection's input, output, and optionally error streams. 039 * Interaction with the remote command is controlled entirely through the 040 * I/O streams. Once you have finished processing the streams, you should 041 * invoke {@link #disconnect disconnect() } to clean up properly. 042 * <p> 043 * By default the standard output and standard error streams of the 044 * remote process are transmitted over the same connection, readable 045 * from the input stream returned by 046 * {@link #getInputStream getInputStream() }. However, it is 047 * possible to tell the rexecd daemon to return the standard error 048 * stream over a separate connection, readable from the input stream 049 * returned by {@link #getErrorStream getErrorStream() }. You 050 * can specify that a separate connection should be created for standard 051 * error by setting the boolean <code> separateErrorStream </code> 052 * parameter of {@link #rexec rexec() } to <code> true </code>. 053 * The standard input of the remote process can be written to through 054 * the output stream returned by 055 * {@link #getOutputStream getOutputSream() }. 056 * <p> 057 * <p> 058 * @author Daniel F. Savarese 059 * @see SocketClient 060 * @see RCommandClient 061 * @see RLoginClient 062 ***/ 063 064 public class RExecClient extends SocketClient 065 { 066 /*** 067 * The default rexec port. Set to 512 in BSD Unix. 068 ***/ 069 public static final int DEFAULT_PORT = 512; 070 071 private boolean __remoteVerificationEnabled; 072 073 /*** 074 * If a separate error stream is requested, <code>_errorStream_</code> 075 * will point to an InputStream from which the standard error of the 076 * remote process can be read (after a call to rexec()). Otherwise, 077 * <code> _errorStream_ </code> will be null. 078 ***/ 079 protected InputStream _errorStream_; 080 081 // This can be overridden in local package to implement port range 082 // limitations of rcmd and rlogin 083 InputStream _createErrorStream() throws IOException 084 { 085 ServerSocket server; 086 Socket socket; 087 088 server = _socketFactory_.createServerSocket(0, 1, getLocalAddress()); 089 090 _output_.write(Integer.toString(server.getLocalPort()).getBytes()); 091 _output_.write('\0'); 092 _output_.flush(); 093 094 socket = server.accept(); 095 server.close(); 096 097 if (__remoteVerificationEnabled && !verifyRemote(socket)) 098 { 099 socket.close(); 100 throw new IOException( 101 "Security violation: unexpected connection attempt by " + 102 socket.getInetAddress().getHostAddress()); 103 } 104 105 return (new SocketInputStream(socket, socket.getInputStream())); 106 } 107 108 109 /*** 110 * The default RExecClient constructor. Initializes the 111 * default port to <code> DEFAULT_PORT </code>. 112 ***/ 113 public RExecClient() 114 { 115 _errorStream_ = null; 116 setDefaultPort(DEFAULT_PORT); 117 } 118 119 120 /*** 121 * Returns the InputStream from which the standard outputof the remote 122 * process can be read. The input stream will only be set after a 123 * successful rexec() invocation. 124 * <p> 125 * @return The InputStream from which the standard output of the remote 126 * process can be read. 127 ***/ 128 public InputStream getInputStream() 129 { 130 return _input_; 131 } 132 133 134 /*** 135 * Returns the OutputStream through which the standard input of the remote 136 * process can be written. The output stream will only be set after a 137 * successful rexec() invocation. 138 * <p> 139 * @return The OutputStream through which the standard input of the remote 140 * process can be written. 141 ***/ 142 public OutputStream getOutputStream() 143 { 144 return _output_; 145 } 146 147 148 /*** 149 * Returns the InputStream from which the standard error of the remote 150 * process can be read if a separate error stream is requested from 151 * the server. Otherwise, null will be returned. The error stream 152 * will only be set after a successful rexec() invocation. 153 * <p> 154 * @return The InputStream from which the standard error of the remote 155 * process can be read if a separate error stream is requested from 156 * the server. Otherwise, null will be returned. 157 ***/ 158 public InputStream getErrorStream() 159 { 160 return _errorStream_; 161 } 162 163 164 /*** 165 * Remotely executes a command through the rexecd daemon on the server 166 * to which the RExecClient is connected. After calling this method, 167 * you may interact with the remote process through its standard input, 168 * output, and error streams. You will typically be able to detect 169 * the termination of the remote process after reaching end of file 170 * on its standard output (accessible through 171 * {@link #getInputStream getInputStream() }. Disconnecting 172 * from the server or closing the process streams before reaching 173 * end of file will not necessarily terminate the remote process. 174 * <p> 175 * If a separate error stream is requested, the remote server will 176 * connect to a local socket opened by RExecClient, providing an 177 * independent stream through which standard error will be transmitted. 178 * RExecClient will do a simple security check when it accepts a 179 * connection for this error stream. If the connection does not originate 180 * from the remote server, an IOException will be thrown. This serves as 181 * a simple protection against possible hijacking of the error stream by 182 * an attacker monitoring the rexec() negotiation. You may disable this 183 * behavior with {@link #setRemoteVerificationEnabled setRemoteVerificationEnabled()} 184 * . 185 * <p> 186 * @param username The account name on the server through which to execute 187 * the command. 188 * @param password The plain text password of the user account. 189 * @param command The command, including any arguments, to execute. 190 * @param separateErrorStream True if you would like the standard error 191 * to be transmitted through a different stream than standard output. 192 * False if not. 193 * @exception IOException If the rexec() attempt fails. The exception 194 * will contain a message indicating the nature of the failure. 195 ***/ 196 public void rexec(String username, String password, 197 String command, boolean separateErrorStream) 198 throws IOException 199 { 200 int ch; 201 202 if (separateErrorStream) 203 { 204 _errorStream_ = _createErrorStream(); 205 } 206 else 207 { 208 _output_.write('\0'); 209 } 210 211 _output_.write(username.getBytes()); 212 _output_.write('\0'); 213 _output_.write(password.getBytes()); 214 _output_.write('\0'); 215 _output_.write(command.getBytes()); 216 _output_.write('\0'); 217 _output_.flush(); 218 219 ch = _input_.read(); 220 if (ch > 0) 221 { 222 StringBuffer buffer = new StringBuffer(); 223 224 while ((ch = _input_.read()) != -1 && ch != '\n') 225 buffer.append((char)ch); 226 227 throw new IOException(buffer.toString()); 228 } 229 else if (ch < 0) 230 { 231 throw new IOException("Server closed connection."); 232 } 233 } 234 235 236 /*** 237 * Same as <code> rexec(username, password, command, false); </code> 238 ***/ 239 public void rexec(String username, String password, 240 String command) 241 throws IOException 242 { 243 rexec(username, password, command, false); 244 } 245 246 /*** 247 * Disconnects from the server, closing all associated open sockets and 248 * streams. 249 * <p> 250 * @exception IOException If there an error occurs while disconnecting. 251 ***/ 252 public void disconnect() throws IOException 253 { 254 if (_errorStream_ != null) 255 _errorStream_.close(); 256 _errorStream_ = null; 257 super.disconnect(); 258 } 259 260 261 /*** 262 * Enable or disable verification that the remote host connecting to 263 * create a separate error stream is the same as the host to which 264 * the standard out stream is connected. The default is for verification 265 * to be enabled. You may set this value at any time, whether the 266 * client is currently connected or not. 267 * <p> 268 * @param enable True to enable verification, false to disable verification. 269 ***/ 270 public final void setRemoteVerificationEnabled(boolean enable) 271 { 272 __remoteVerificationEnabled = enable; 273 } 274 275 /*** 276 * Return whether or not verification of the remote host providing a 277 * separate error stream is enabled. The default behavior is for 278 * verification to be enabled. 279 * <p> 280 * @return True if verification is enabled, false if not. 281 ***/ 282 public final boolean isRemoteVerificationEnabled() 283 { 284 return __remoteVerificationEnabled; 285 } 286 287 } 288