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 */
017package org.apache.xbean.terminal.telnet;
018
019import java.io.FilterInputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.OutputStream;
023
024public class TelnetInputStream extends FilterInputStream implements TelnetCodes {
025    // state table for what options have been negotiated
026    private TelnetOption[] options = new TelnetOption[256];
027    private OutputStream out = null;
028
029    /**
030     * We haven yet implemented any Telnet options, so we just explicitly
031     * disable some common options for safety sake.
032     * <p/>
033     * Certain Telnet clients (MS Windows Telnet) are enabling options without
034     * asking first. Shame, shame, shame.
035     *
036     * @throws IOException
037     */
038    public TelnetInputStream(InputStream in, OutputStream out) throws IOException {
039        super(in);
040        this.out = out;
041        negotiateOption(DONT, 1);
042        negotiateOption(DONT, 6);
043        negotiateOption(DONT, 24);
044        negotiateOption(DONT, 33);
045        negotiateOption(DONT, 34);
046    }
047
048    public int read() throws IOException {
049        int b = super.read();
050        if (b == IAC) {
051            // The cosole has a reference
052            // to this input stream
053            processCommand();
054            // Call read recursively as
055            // the next character could
056            // also be a command
057            b = this.read();
058        }
059        //System.out.println("B="+b);
060        return b;
061    }
062
063    /**
064     * This is only called by TelnetInputStream
065     * it is assumed that the IAC byte has already been read from the stream.
066     *
067     * @throws IOException
068     */
069    private void processCommand() throws IOException {
070        // Debug statement
071        print("C: IAC ");
072        int command = super.read();
073        switch (command) {
074            case WILL:
075                senderWillEnableOption(super.read());
076                break;
077            case DO:
078                pleaseDoEnableOption(super.read());
079                break;
080            case WONT:
081                senderWontEnableOption(super.read());
082                break;
083            case DONT:
084                pleaseDontEnableOption(super.read());
085                break;
086            default:
087                unimplementedCommand(command);
088                break;
089        }
090    }
091
092    private void unimplementedCommand(int command) {
093        println(command + ": command not found");
094    }
095
096    /**
097     * Client says: I will enable OptionX
098     * <p/>
099     * If the sender initiated the negotiation of the
100     * option, we must send a reply. Replies can be DO or DON'T.
101     *
102     * @param optionID
103     * @throws IOException
104     */
105    private void senderWillEnableOption(int optionID) throws IOException {
106        // Debug statement
107        println("WILL " + optionID);
108        TelnetOption option = getOption(optionID);
109        if (option.hasBeenNegotiated()) return;
110        if (option.isInNegotiation()) {
111            option.enable();
112        } else if (!option.isInNegotiation() && option.isSupported()) {
113            negotiateOption(DO, optionID);
114            option.enable();
115        } else if (!option.isInNegotiation() && !option.isSupported()) {
116            negotiateOption(DONT, optionID);
117            option.disable();
118        }
119    }
120
121    /**
122     * Client says: Please, do enable OptionX
123     * <p/>
124     * If the sender initiated the negotiation of the
125     * option, we must send a reply.
126     * <p/>
127     * Replies can be WILL or WON'T.
128     *
129     * @param optionID
130     * @throws IOException
131     */
132    private void pleaseDoEnableOption(int optionID) throws IOException {
133        // Debug statement
134        println("DO " + optionID);
135        TelnetOption option = getOption(optionID);
136        if (option.hasBeenNegotiated()) return;
137        if (option.isInNegotiation()) {
138            option.enable();
139        } else if (!option.isInNegotiation() && option.isSupported()) {
140            negotiateOption(WILL, optionID);
141            option.enable();
142        } else if (!option.isInNegotiation() && !option.isSupported()) {
143            negotiateOption(WONT, optionID);
144            option.disable();
145        }
146    }
147
148    /**
149     * Client says: I won't enable OptionX
150     * <p/>
151     * <p/>
152     * If the sender initiated the negotiation of the
153     * option, we must send a reply.
154     * <p/>
155     * Replies can only be DON'T.
156     *
157     * @param optionID
158     * @throws IOException
159     */
160    private void senderWontEnableOption(int optionID) throws IOException {
161        println("WONT " + optionID);
162        TelnetOption option = getOption(optionID);
163        if (option.hasBeenNegotiated()) return;
164        if (!option.isInNegotiation()) {
165            negotiateOption(DONT, optionID);
166        }
167        option.disable();
168    }
169
170    /**
171     * Client says: Please, don't enable OptionX
172     * <p/>
173     * If the sender initiated the negotiation of the
174     * option, we must send a reply.
175     * <p/>
176     * Replies can only be WON'T.
177     *
178     * @param optionID
179     * @throws IOException
180     */
181    private void pleaseDontEnableOption(int optionID) throws IOException {
182        // Debug statement
183        println("DONT " + optionID);
184        TelnetOption option = getOption(optionID);
185        if (option.hasBeenNegotiated()) return;
186        if (!option.isInNegotiation()) {
187            negotiateOption(WONT, optionID);
188        }
189        option.disable();
190    }
191
192    // TODO:0: Replace with actual logging
193    private void println(String s) {
194        // System.out.println(s);
195    }
196
197    // TODO:0: Replace with actual logging
198    private void print(String s) {
199        // System.out.print(s);
200    }
201
202    /**
203     * Send an option negitiation command to the client
204     *
205     * @param negotiate
206     * @param optionID
207     * @throws IOException
208     */
209    private void negotiateOption(int negotiate, int optionID)
210            throws IOException {
211        TelnetOption option = getOption(optionID);
212        option.isInNegotiation(true);
213        String n = null;
214        switch (negotiate) {
215            case WILL:
216                n = "WILL ";
217                break;
218            case DO:
219                n = "DO ";
220                break;
221            case WONT:
222                n = "WONT ";
223                break;
224            case DONT:
225                n = "DONT ";
226                break;
227        }
228        // Debug statement
229        println("S: IAC " + n + optionID);
230        synchronized (out) {
231            out.write(IAC);
232            out.write(negotiate);
233            out.write(optionID);
234        }
235    }
236
237    private TelnetOption getOption(int optionID) {
238        TelnetOption opt = options[optionID];
239        if (opt == null) {
240            opt = new TelnetOption(optionID);
241            options[optionID] = opt;
242        }
243        return opt;
244    }
245}