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: * The default rshell port. Set to 514 in BSD Unix.
090: ***/
091: public static final int DEFAULT_PORT = 514;
092:
093: /***
094: * The smallest port number an rcmd client may use. By BSD convention
095: * this number is 512.
096: ***/
097: public static final int MIN_CLIENT_PORT = 512;
098:
099: /***
100: * The largest port number an rcmd client may use. By BSD convention
101: * this number is 1023.
102: ***/
103: public static final int MAX_CLIENT_PORT = 1023;
104:
105: // Overrides method in RExecClient in order to implement proper
106: // port number limitations.
107: InputStream _createErrorStream() throws IOException {
108: int localPort;
109: ServerSocket server;
110: Socket socket;
111:
112: localPort = MAX_CLIENT_PORT;
113: server = null; // Keep compiler from barfing
114:
115: for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort) {
116: try {
117: server = _socketFactory_.createServerSocket(localPort,
118: 1, getLocalAddress());
119: } catch (SocketException e) {
120: continue;
121: }
122: break;
123: }
124:
125: if (localPort < MIN_CLIENT_PORT)
126: throw new BindException("All ports in use.");
127:
128: _output_.write(Integer.toString(server.getLocalPort())
129: .getBytes());
130: _output_.write('\0');
131: _output_.flush();
132:
133: socket = server.accept();
134: server.close();
135:
136: if (isRemoteVerificationEnabled() && !verifyRemote(socket)) {
137: socket.close();
138: throw new IOException(
139: "Security violation: unexpected connection attempt by "
140: + socket.getInetAddress().getHostAddress());
141: }
142:
143: return (new SocketInputStream(socket, socket.getInputStream()));
144: }
145:
146: /***
147: * The default RCommandClient constructor. Initializes the
148: * default port to <code> DEFAULT_PORT </code>.
149: ***/
150: public RCommandClient() {
151: setDefaultPort(DEFAULT_PORT);
152: }
153:
154: /***
155: * Opens a Socket connected to a remote host at the specified port and
156: * originating from the specified local address using a port in a range
157: * acceptable to the BSD rshell daemon.
158: * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() }
159: * is called to perform connection initialization actions.
160: * <p>
161: * @param host The remote host.
162: * @param port The port to connect to on the remote host.
163: * @param localAddr The local address to use.
164: * @exception SocketException If the socket timeout could not be set.
165: * @exception BindException If all acceptable rshell ports are in use.
166: * @exception IOException If the socket could not be opened. In most
167: * cases you will only want to catch IOException since SocketException is
168: * derived from it.
169: ***/
170: public void connect(InetAddress host, int port,
171: InetAddress localAddr) throws SocketException,
172: BindException, IOException {
173: int localPort;
174:
175: localPort = MAX_CLIENT_PORT;
176:
177: for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort) {
178: try {
179: _socket_ = _socketFactory_.createSocket(host, port,
180: localAddr, localPort);
181: } catch (SocketException e) {
182: continue;
183: }
184: break;
185: }
186:
187: if (localPort < MIN_CLIENT_PORT)
188: throw new BindException(
189: "All ports in use or insufficient permssion.");
190:
191: _connectAction_();
192: }
193:
194: /***
195: * Opens a Socket connected to a remote host at the specified port and
196: * originating from the current host at a port in a range acceptable
197: * to the BSD rshell daemon.
198: * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() }
199: * is called to perform connection initialization actions.
200: * <p>
201: * @param host The remote host.
202: * @param port The port to connect to on the remote host.
203: * @exception SocketException If the socket timeout could not be set.
204: * @exception BindException If all acceptable rshell ports are in use.
205: * @exception IOException If the socket could not be opened. In most
206: * cases you will only want to catch IOException since SocketException is
207: * derived from it.
208: ***/
209: public void connect(InetAddress host, int port)
210: throws SocketException, IOException {
211: connect(host, port, InetAddress.getLocalHost());
212: }
213:
214: /***
215: * Opens a Socket connected to a remote host at the specified port and
216: * originating from the current host at a port in a range acceptable
217: * to the BSD rshell daemon.
218: * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() }
219: * is called to perform connection initialization actions.
220: * <p>
221: * @param hostname The name of the remote host.
222: * @param port The port to connect to on the remote host.
223: * @exception SocketException If the socket timeout could not be set.
224: * @exception BindException If all acceptable rshell ports are in use.
225: * @exception IOException If the socket could not be opened. In most
226: * cases you will only want to catch IOException since SocketException is
227: * derived from it.
228: * @exception UnknownHostException If the hostname cannot be resolved.
229: ***/
230: public void connect(String hostname, int port)
231: throws SocketException, IOException {
232: connect(InetAddress.getByName(hostname), port, InetAddress
233: .getLocalHost());
234: }
235:
236: /***
237: * Opens a Socket connected to a remote host at the specified port and
238: * originating from the specified local address using a port in a range
239: * acceptable 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 remote host.
244: * @param port The port to connect to on the remote host.
245: * @param localAddr The local address to use.
246: * @exception SocketException If the socket timeout could not be set.
247: * @exception BindException If all acceptable rshell ports are in use.
248: * @exception IOException If the socket could not be opened. In most
249: * cases you will only want to catch IOException since SocketException is
250: * derived from it.
251: ***/
252: public void connect(String hostname, int port, InetAddress localAddr)
253: throws SocketException, IOException {
254: connect(InetAddress.getByName(hostname), port, localAddr);
255: }
256:
257: /***
258: * Opens a Socket connected to a remote host at the specified port and
259: * originating from the specified local address and port. The
260: * local port must lie between <code> MIN_CLIENT_PORT </code> and
261: * <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will
262: * be thrown.
263: * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() }
264: * is called to perform connection initialization actions.
265: * <p>
266: * @param host The remote host.
267: * @param port The port to connect to on the remote host.
268: * @param localAddr The local address to use.
269: * @param localPort The local port to use.
270: * @exception SocketException If the socket timeout could not be set.
271: * @exception IOException If the socket could not be opened. In most
272: * cases you will only want to catch IOException since SocketException is
273: * derived from it.
274: * @exception IllegalArgumentException If an invalid local port number
275: * is specified.
276: ***/
277: public void connect(InetAddress host, int port,
278: InetAddress localAddr, int localPort)
279: throws SocketException, IOException,
280: IllegalArgumentException {
281: if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT)
282: throw new IllegalArgumentException("Invalid port number "
283: + localPort);
284: super .connect(host, port, localAddr, localPort);
285: }
286:
287: /***
288: * Opens a Socket connected to a remote host at the specified port and
289: * originating from the specified local address and port. The
290: * local port must lie between <code> MIN_CLIENT_PORT </code> and
291: * <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will
292: * be thrown.
293: * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() }
294: * is called to perform connection initialization actions.
295: * <p>
296: * @param hostname The name of the remote host.
297: * @param port The port to connect to on the remote host.
298: * @param localAddr The local address to use.
299: * @param localPort The local port to use.
300: * @exception SocketException If the socket timeout could not be set.
301: * @exception IOException If the socket could not be opened. In most
302: * cases you will only want to catch IOException since SocketException is
303: * derived from it.
304: * @exception UnknownHostException If the hostname cannot be resolved.
305: * @exception IllegalArgumentException If an invalid local port number
306: * is specified.
307: ***/
308: public void connect(String hostname, int port,
309: InetAddress localAddr, int localPort)
310: throws SocketException, IOException,
311: IllegalArgumentException {
312: if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT)
313: throw new IllegalArgumentException("Invalid port number "
314: + localPort);
315: super .connect(hostname, port, localAddr, localPort);
316: }
317:
318: /***
319: * Remotely executes a command through the rshd daemon on the server
320: * to which the RCommandClient is connected. After calling this method,
321: * you may interact with the remote process through its standard input,
322: * output, and error streams. You will typically be able to detect
323: * the termination of the remote process after reaching end of file
324: * on its standard output (accessible through
325: * {@link #getInputStream getInputStream() }. Disconnecting
326: * from the server or closing the process streams before reaching
327: * end of file will not necessarily terminate the remote process.
328: * <p>
329: * If a separate error stream is requested, the remote server will
330: * connect to a local socket opened by RCommandClient, providing an
331: * independent stream through which standard error will be transmitted.
332: * The local socket must originate from a secure port (512 - 1023),
333: * and rcommand() ensures that this will be so.
334: * RCommandClient will also do a simple security check when it accepts a
335: * connection for this error stream. If the connection does not originate
336: * from the remote server, an IOException will be thrown. This serves as
337: * a simple protection against possible hijacking of the error stream by
338: * an attacker monitoring the rexec() negotiation. You may disable this
339: * behavior with
340: * {@link org.apache.commons.net.bsd.RExecClient#setRemoteVerificationEnabled setRemoteVerificationEnabled()}
341: * .
342: * <p>
343: * @param localUsername The user account on the local machine that is
344: * requesting the command execution.
345: * @param remoteUsername The account name on the server through which to
346: * execute the command.
347: * @param command The command, including any arguments, to execute.
348: * @param separateErrorStream True if you would like the standard error
349: * to be transmitted through a different stream than standard output.
350: * False if not.
351: * @exception IOException If the rcommand() attempt fails. The exception
352: * will contain a message indicating the nature of the failure.
353: ***/
354: public void rcommand(String localUsername, String remoteUsername,
355: String command, boolean separateErrorStream)
356: throws IOException {
357: rexec(localUsername, remoteUsername, command,
358: separateErrorStream);
359: }
360:
361: /***
362: * Same as
363: * <code> rcommand(localUsername, remoteUsername, command, false); </code>
364: ***/
365: public void rcommand(String localUsername, String remoteUsername,
366: String command) throws IOException {
367: rcommand(localUsername, remoteUsername, command, false);
368: }
369:
370: }
|