1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.net.bsd;
  17. import java.io.IOException;
  18. import java.io.InputStream;
  19. import java.net.BindException;
  20. import java.net.InetAddress;
  21. import java.net.ServerSocket;
  22. import java.net.Socket;
  23. import java.net.SocketException;
  24. import org.apache.commons.net.io.SocketInputStream;
  25. /***
  26. * RCommandClient is very similar to
  27. * <a href="org.apache.commons.net.bsd.RExecClient.html"> RExecClient </a>,
  28. * from which it is derived, and implements the rcmd() facility that
  29. * first appeared in 4.2BSD Unix. rcmd() is the facility used by the rsh
  30. * (rshell) and other commands to execute a command on another machine
  31. * from a trusted host without issuing a password. The trust relationship
  32. * between two machines is established by the contents of a machine's
  33. * /etc/hosts.equiv file and a user's .rhosts file. These files specify
  34. * from which hosts and accounts on those hosts rcmd() requests will be
  35. * accepted. The only additional measure for establishing trust is that
  36. * all client connections must originate from a port between 512 and 1023.
  37. * Consequently, there is an upper limit to the number of rcmd connections
  38. * that can be running simultaneously. The required ports are reserved
  39. * ports on Unix systems, and can only be bound by a
  40. * process running with root permissions (to accomplish this rsh, rlogin,
  41. * and related commands usualy have the suid bit set). Therefore, on a
  42. * Unix system, you will only be able to successfully use the RCommandClient
  43. * class if the process runs as root. However, there is no such restriction
  44. * on Windows95 and some other systems. The security risks are obvious.
  45. * However, when carefully used, rcmd() can be very useful when used behind
  46. * a firewall.
  47. * <p>
  48. * As with virtually all of the client classes in org.apache.commons.net, this
  49. * class derives from SocketClient. But it overrides most of its connection
  50. * methods so that the local Socket will originate from an acceptable
  51. * rshell port. The way to use RCommandClient is to first connect
  52. * to the server, call the <a href="#rcommand"> rcommand() </a> method,
  53. * and then
  54. * fetch the connection's input, output, and optionally error streams.
  55. * Interaction with the remote command is controlled entirely through the
  56. * I/O streams. Once you have finished processing the streams, you should
  57. * invoke <a href="org.apache.commons.net.bsd.RExecClient.html#disconnect">
  58. * disconnect() </a> to clean up properly.
  59. * <p>
  60. * By default the standard output and standard error streams of the
  61. * remote process are transmitted over the same connection, readable
  62. * from the input stream returned by
  63. * <a href="org.apache.commons.net.bsd.RExecClient.html#getInputStream">
  64. * getInputStream() </a>. However, it is
  65. * possible to tell the rshd daemon to return the standard error
  66. * stream over a separate connection, readable from the input stream
  67. * returned by <a href="org.apache.commons.net.bsd.RExecClient.html#getErrorStream">
  68. * getErrorStream() </a>. You
  69. * can specify that a separate connection should be created for standard
  70. * error by setting the boolean <code> separateErrorStream </code>
  71. * parameter of <a href="#rcommand"> rcommand() </a> to <code> true </code>.
  72. * The standard input of the remote process can be written to through
  73. * the output stream returned by
  74. * <a href="org.apache.commons.net.bsd.RExecClient.html#getOutputStream">
  75. * getOutputSream() </a>.
  76. * <p>
  77. * <p>
  78. * @author Daniel F. Savarese
  79. * @see org.apache.commons.net.SocketClient
  80. * @see RExecClient
  81. * @see RLoginClient
  82. ***/
  83. public class RCommandClient extends RExecClient
  84. {
  85. /***
  86. * The default rshell port. Set to 514 in BSD Unix.
  87. ***/
  88. public static final int DEFAULT_PORT = 514;
  89. /***
  90. * The smallest port number an rcmd client may use. By BSD convention
  91. * this number is 512.
  92. ***/
  93. public static final int MIN_CLIENT_PORT = 512;
  94. /***
  95. * The largest port number an rcmd client may use. By BSD convention
  96. * this number is 1023.
  97. ***/
  98. public static final int MAX_CLIENT_PORT = 1023;
  99. // Overrides method in RExecClient in order to implement proper
  100. // port number limitations.
  101. InputStream _createErrorStream() throws IOException
  102. {
  103. int localPort;
  104. ServerSocket server;
  105. Socket socket;
  106. localPort = MAX_CLIENT_PORT;
  107. server = null; // Keep compiler from barfing
  108. for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort)
  109. {
  110. try
  111. {
  112. server = _socketFactory_.createServerSocket(localPort, 1,
  113. getLocalAddress());
  114. }
  115. catch (SocketException e)
  116. {
  117. continue;
  118. }
  119. break;
  120. }
  121. if (localPort < MIN_CLIENT_PORT)
  122. throw new BindException("All ports in use.");
  123. _output_.write(Integer.toString(server.getLocalPort()).getBytes());
  124. _output_.write('\0');
  125. _output_.flush();
  126. socket = server.accept();
  127. server.close();
  128. if (isRemoteVerificationEnabled() && !verifyRemote(socket))
  129. {
  130. socket.close();
  131. throw new IOException(
  132. "Security violation: unexpected connection attempt by " +
  133. socket.getInetAddress().getHostAddress());
  134. }
  135. return (new SocketInputStream(socket, socket.getInputStream()));
  136. }
  137. /***
  138. * The default RCommandClient constructor. Initializes the
  139. * default port to <code> DEFAULT_PORT </code>.
  140. ***/
  141. public RCommandClient()
  142. {
  143. setDefaultPort(DEFAULT_PORT);
  144. }
  145. /***
  146. * Opens a Socket connected to a remote host at the specified port and
  147. * originating from the specified local address using a port in a range
  148. * acceptable to the BSD rshell daemon.
  149. * Before returning, <a href="org.apache.commons.net.SocketClient.html#_connectAction_"> _connectAction_() </a>
  150. * is called to perform connection initialization actions.
  151. * <p>
  152. * @param host The remote host.
  153. * @param port The port to connect to on the remote host.
  154. * @param localAddr The local address to use.
  155. * @exception SocketException If the socket timeout could not be set.
  156. * @exception BindException If all acceptable rshell ports are in use.
  157. * @exception IOException If the socket could not be opened. In most
  158. * cases you will only want to catch IOException since SocketException is
  159. * derived from it.
  160. ***/
  161. public void connect(InetAddress host, int port, InetAddress localAddr)
  162. throws SocketException, BindException, IOException
  163. {
  164. int localPort;
  165. localPort = MAX_CLIENT_PORT;
  166. for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort)
  167. {
  168. try
  169. {
  170. _socket_ =
  171. _socketFactory_.createSocket(host, port, localAddr, localPort);
  172. }
  173. catch (SocketException e)
  174. {
  175. continue;
  176. }
  177. break;
  178. }
  179. if (localPort < MIN_CLIENT_PORT)
  180. throw new BindException("All ports in use or insufficient permssion.");
  181. _connectAction_();
  182. }
  183. /***
  184. * Opens a Socket connected to a remote host at the specified port and
  185. * originating from the current host at a port in a range acceptable
  186. * to the BSD rshell daemon.
  187. * Before returning, <a href="org.apache.commons.net.SocketClient.html#_connectAction_"> _connectAction_() </a>
  188. * is called to perform connection initialization actions.
  189. * <p>
  190. * @param host The remote host.
  191. * @param port The port to connect to on the remote host.
  192. * @exception SocketException If the socket timeout could not be set.
  193. * @exception BindException If all acceptable rshell ports are in use.
  194. * @exception IOException If the socket could not be opened. In most
  195. * cases you will only want to catch IOException since SocketException is
  196. * derived from it.
  197. ***/
  198. public void connect(InetAddress host, int port)
  199. throws SocketException, IOException
  200. {
  201. connect(host, port, InetAddress.getLocalHost());
  202. }
  203. /***
  204. * Opens a Socket connected to a remote host at the specified port and
  205. * originating from the current host at a port in a range acceptable
  206. * to the BSD rshell daemon.
  207. * Before returning, <a href="org.apache.commons.net.SocketClient.html#_connectAction_"> _connectAction_() </a>
  208. * is called to perform connection initialization actions.
  209. * <p>
  210. * @param hostname The name of the remote host.
  211. * @param port The port to connect to on the remote host.
  212. * @exception SocketException If the socket timeout could not be set.
  213. * @exception BindException If all acceptable rshell ports are in use.
  214. * @exception IOException If the socket could not be opened. In most
  215. * cases you will only want to catch IOException since SocketException is
  216. * derived from it.
  217. * @exception UnknownHostException If the hostname cannot be resolved.
  218. ***/
  219. public void connect(String hostname, int port)
  220. throws SocketException, IOException
  221. {
  222. connect(InetAddress.getByName(hostname), port, InetAddress.getLocalHost());
  223. }
  224. /***
  225. * Opens a Socket connected to a remote host at the specified port and
  226. * originating from the specified local address using a port in a range
  227. * acceptable to the BSD rshell daemon.
  228. * Before returning, <a href="org.apache.commons.net.SocketClient.html#_connectAction_"> _connectAction_() </a>
  229. * is called to perform connection initialization actions.
  230. * <p>
  231. * @param hostname The remote host.
  232. * @param port The port to connect to on the remote host.
  233. * @param localAddr The local address to use.
  234. * @exception SocketException If the socket timeout could not be set.
  235. * @exception BindException If all acceptable rshell ports are in use.
  236. * @exception IOException If the socket could not be opened. In most
  237. * cases you will only want to catch IOException since SocketException is
  238. * derived from it.
  239. ***/
  240. public void connect(String hostname, int port, InetAddress localAddr)
  241. throws SocketException, IOException
  242. {
  243. connect(InetAddress.getByName(hostname), port, localAddr);
  244. }
  245. /***
  246. * Opens a Socket connected to a remote host at the specified port and
  247. * originating from the specified local address and port. The
  248. * local port must lie between <code> MIN_CLIENT_PORT </code> and
  249. * <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will
  250. * be thrown.
  251. * Before returning, <a href="org.apache.commons.net.SocketClient.html#_connectAction_"> _connectAction_() </a>
  252. * is called to perform connection initialization actions.
  253. * <p>
  254. * @param host The remote host.
  255. * @param port The port to connect to on the remote host.
  256. * @param localAddr The local address to use.
  257. * @param localPort The local port to use.
  258. * @exception SocketException If the socket timeout could not be set.
  259. * @exception IOException If the socket could not be opened. In most
  260. * cases you will only want to catch IOException since SocketException is
  261. * derived from it.
  262. * @exception IllegalArgumentException If an invalid local port number
  263. * is specified.
  264. ***/
  265. public void connect(InetAddress host, int port,
  266. InetAddress localAddr, int localPort)
  267. throws SocketException, IOException, IllegalArgumentException
  268. {
  269. if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT)
  270. throw new IllegalArgumentException("Invalid port number " + localPort);
  271. super.connect(host, port, localAddr, localPort);
  272. }
  273. /***
  274. * Opens a Socket connected to a remote host at the specified port and
  275. * originating from the specified local address and port. The
  276. * local port must lie between <code> MIN_CLIENT_PORT </code> and
  277. * <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will
  278. * be thrown.
  279. * Before returning, <a href="org.apache.commons.net.SocketClient.html#_connectAction_"> _connectAction_() </a>
  280. * is called to perform connection initialization actions.
  281. * <p>
  282. * @param hostname The name of the remote host.
  283. * @param port The port to connect to on the remote host.
  284. * @param localAddr The local address to use.
  285. * @param localPort The local port to use.
  286. * @exception SocketException If the socket timeout could not be set.
  287. * @exception IOException If the socket could not be opened. In most
  288. * cases you will only want to catch IOException since SocketException is
  289. * derived from it.
  290. * @exception UnknownHostException If the hostname cannot be resolved.
  291. * @exception IllegalArgumentException If an invalid local port number
  292. * is specified.
  293. ***/
  294. public void connect(String hostname, int port,
  295. InetAddress localAddr, int localPort)
  296. throws SocketException, IOException, IllegalArgumentException
  297. {
  298. if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT)
  299. throw new IllegalArgumentException("Invalid port number " + localPort);
  300. super.connect(hostname, port, localAddr, localPort);
  301. }
  302. /***
  303. * Remotely executes a command through the rshd daemon on the server
  304. * to which the RCommandClient is connected. After calling this method,
  305. * you may interact with the remote process through its standard input,
  306. * output, and error streams. You will typically be able to detect
  307. * the termination of the remote process after reaching end of file
  308. * on its standard output (accessible through
  309. * <a href="#getInputStream"> getInputStream() </a>. Disconnecting
  310. * from the server or closing the process streams before reaching
  311. * end of file will not necessarily terminate the remote process.
  312. * <p>
  313. * If a separate error stream is requested, the remote server will
  314. * connect to a local socket opened by RCommandClient, providing an
  315. * independent stream through which standard error will be transmitted.
  316. * The local socket must originate from a secure port (512 - 1023),
  317. * and rcommand() ensures that this will be so.
  318. * RCommandClient will also do a simple security check when it accepts a
  319. * connection for this error stream. If the connection does not originate
  320. * from the remote server, an IOException will be thrown. This serves as
  321. * a simple protection against possible hijacking of the error stream by
  322. * an attacker monitoring the rexec() negotiation. You may disable this
  323. * behavior with
  324. * <a href="org.apache.commons.net.bsd.RExecClient.html#setRemoteVerificationEnabled">
  325. * setRemoteVerificationEnabled()</a>.
  326. * <p>
  327. * @param localUsername The user account on the local machine that is
  328. * requesting the command execution.
  329. * @param remoteUsername The account name on the server through which to
  330. * execute the command.
  331. * @param command The command, including any arguments, to execute.
  332. * @param separateErrorStream True if you would like the standard error
  333. * to be transmitted through a different stream than standard output.
  334. * False if not.
  335. * @exception IOException If the rcommand() attempt fails. The exception
  336. * will contain a message indicating the nature of the failure.
  337. ***/
  338. public void rcommand(String localUsername, String remoteUsername,
  339. String command, boolean separateErrorStream)
  340. throws IOException
  341. {
  342. rexec(localUsername, remoteUsername, command, separateErrorStream);
  343. }
  344. /***
  345. * Same as
  346. * <code> rcommand(localUsername, remoteUsername, command, false); </code>
  347. ***/
  348. public void rcommand(String localUsername, String remoteUsername,
  349. String command)
  350. throws IOException
  351. {
  352. rcommand(localUsername, remoteUsername, command, false);
  353. }
  354. }