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    package org.apache.xbean.terminal.telnet;
018    
019    import java.io.FilterInputStream;
020    import java.io.IOException;
021    import java.io.InputStream;
022    import java.io.OutputStream;
023    
024    public 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    }