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