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