/*
 * @(#)Rlogin.java
 *
 * Copyright (c) 2001-2002 JScape
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JScape. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JScape.
 */
package com.jscape.inet.bsd;

import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;


/**
 * Implements the functionality of a BSD rloginsh client.
 * <P>
 * Example Usage:
 * <P>
 * <pre>
 * Rlogin rlogin = new Rlogin("remote.host.com", "johndoe", "johndoe", "vt100");
 * try
 * {
 *   rlogin.connect();
 *   rlogin.execute();
 *   OutputReader reader = new OutputReader(rlogin.getInputStream(), System.out);
 *   InputWriter writer = new InputWriter(System.in, rlogin.getOutputStream());
 *   reader.setPriority(Thread.currentThread().getPriority() + 1);
 *   reader.start();
 *   writer.setDaemon(true);
 *   writer.start();
 *   try
 *   {
 *     reader.join();
 *     writer.interrupt();
 *   }
 *   catch(InterruptedException e)
 *   {
 *   }
 * )
 * catch(Exception e)
 * {
 *   System.out.println(e);
 * }
 * </pre>
 * </p>
 */
public class Rlogin
        extends Rexec
        implements Serializable {
    private String localUsername = "";
    private String terminalType = "";
    private int terminalSpeed = 0;

    /**
     * Initializes the Rlogin object with port set to 513. All connection
     * parameters must be  set using member functions.
     */
    public Rlogin() {
        super(false);
        port = 513;
    }

    /**
     * Initializes the Rlogin object with port set to 513 and all connection
     * parameters.
     * <p/>
     * <p></p>
     *
     * @param remoteHost    the Internet name of the remote host.
     * @param username      the username of the user, whose credentials are to be
     *                      used to execute the <code>command</code>.
     * @param localUsername the username of the user who is initiating the
     *                      connection.
     * @param terminalType  the terminal type.
     */
    public Rlogin(String remoteHost, String username, String localUsername,
                  String terminalType) {
        super(false);
        setRemoteHost(remoteHost);
        setUsername(username);
        setLocalUsername(localUsername);
        setTerminalType(terminalType);
        port = 513;
    }

    /**
     * Initializes input and output streams to handle stdin and stdout of the
     * remote host. Before calling this method, all connection parameters must
     * be set.
     *
     * @throws BsdException if connection can not be established.
     */
    public void connect()
            throws BsdException {
        synchronized (this) {
            int localPort;
            InetAddress localHost;

            try {
                localHost = InetAddress.getLocalHost();
            } catch (UnknownHostException e) {
                throw new BsdException(e.toString());
            }

            for (localPort = 1023; localPort >= 512; --localPort) {
                try {
                    rexecSocket = new Socket(remoteHost, port, localHost,
                                             localPort);
                    input = rexecSocket.getInputStream();
                    output = rexecSocket.getOutputStream();
                    fireBsdConnected(getRemoteHost());
                } catch (SocketException e) {
                    continue;
                } catch (java.io.IOException e) {
                    throw new BsdException(e.toString());
                }

                break;
            }

            if (localPort < 512) {
                throw new BsdException(
                        "Can not initialize connection - No free local ports between 512 and 1023, or insufficient permission.");
            }
        }
    }

    /**
     * Executes rlogin negotiation with remote host. After calling this method,
     * stdout of the remote host is received through an
     * <code>InputStream</code> returned by <code>getInputStream()</code>
     * method. stdin can be accessed through an <code>OutputStream</code>
     * returned by <code>getOutputStream()</code>. Further interaction with
     * rlogin service is exclusively done through these two streams.
     * <p/>
     * <p>
     * To properly terminate connection, use <code>disconnect()</code> method.
     * Beware that remote process will finish after EOF is sent to stdout, so
     * disconnecting too early will not terminate remote process.
     * </p>
     * <p/>
     * <p></p>
     *
     * @throws BsdException if rsh negotiation fails.
     */
    public void execute()
            throws BsdException {
        synchronized (this) {
            try {
                output.write('\0');
                output.write(getLocalUsername().getBytes());
                output.write('\0');
                output.write(getUsername().getBytes());
                output.write('\0');
                output.write(getTerminalType().getBytes());

                if (terminalSpeed > 0) {
                    output.write(("/" + getTerminalSpeed()).getBytes());
                }

                output.write('\0');
                output.flush();
            } catch (IOException e) {
                throw new BsdException(e.toString());
            }
        }
    }

    /**
     * Sets the username of the user originating the connection. Unix
     * restrictions apply - only 8 characters are allowed. Any excess is
     * removed.
     *
     * @param localUsername the local username to be sent to the remote host.
     */
    public void setLocalUsername(String localUsername) {
        synchronized (this) {
            if (localUsername.length() > 8) {
                localUsername = localUsername.substring(0, 7);
            }

            this.localUsername = localUsername;
        }
    }

    /**
     * Gets the local username to be sent to the remote host.
     *
     * @return local username to be sent.
     */
    public String getLocalUsername() {
        return localUsername;
    }

    /**
     * Sets the terminal type to be used to distinguish between formating
     * characters and normal text. Blank string for terminal type  indicates
     * "dumb" terminal type on most unices.
     * <p/>
     * <p>
     * Unix restrictions apply - only 8 characters are allowed. Any excess is
     * removed.
     * </p>
     *
     * @param terminalType the terminal type to be sent to the remote host.
     */
    public void setTerminalType(String terminalType) {
        synchronized (this) {
            if (terminalType.length() > 8) {
                terminalType = terminalType.substring(0, 7);
            }

            this.terminalType = terminalType;
        }
    }

    /**
     * Gets the terminal type to be used to distinguish between formating
     * characters and normal text.
     *
     * @return the terminal type to be used.
     */
    public String getTerminalType() {
        return terminalType;
    }

    /**
     * Sets the speed of terminal emulation. The remote host will restrict
     * data flow to this speed. Leaving this parameter to zero will cause
     * default terminal speed to be used (system dependent).
     *
     * @param terminalSpeed the terminal speed to be used.
     */
    public void setTerminalSpeed(int terminalSpeed) {
        if (terminalSpeed >= 0) {
            this.terminalSpeed = terminalSpeed;
        }
    }

    /**
     * Gets the speed of terminal emulation.
     *
     * @return the terminal speed to be used.
     */
    public int getTerminalSpeed() {
        return terminalSpeed;
    }
}
