Utils/Console: Telnet protocol implementation (including NAWS and TERMINAL-TYPE options)
[senf.git] / Utils / Console / Telnet.cc
1 // $Id$
2 //
3 // Copyright (C) 2008 
4 // Fraunhofer Institute for Open Communication Systems (FOKUS)
5 // Competence Center NETwork research (NET), St. Augustin, GERMANY
6 //     Stefan Bund <g0dil@berlios.de>
7 //
8 // This program is free software; you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation; either version 2 of the License, or
11 // (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the
20 // Free Software Foundation, Inc.,
21 // 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22
23 /** \file
24     \brief Telnet non-inline non-template implementation */
25
26 #include "Telnet.hh"
27 //#include "Telnet.ih"
28
29 // Custom includes
30 #include "../membind.hh"
31 #include "../Logger/SenfLog.hh"
32
33 //#include "Telnet.mpp"
34 #define prefix_
35 ///////////////////////////////cc.p////////////////////////////////////////
36
37 prefix_ senf::console::detail::BaseTelnetProtocol::BaseTelnetProtocol(Handle handle)
38     : handle_ (handle), charState_ (NORMAL), command_ (CMD_NONE), option_ (0),
39       inputEvent_ ("senf::console::detail::BaseTelnetProtocol::input",
40                    senf::membind(&BaseTelnetProtocol::readHandler, this), handle, 
41                    senf::scheduler::FdEvent::EV_READ),
42       outputEvent_ ("senf::console::detail::BaseTelnetProtocol::output",
43                     senf::membind(&BaseTelnetProtocol::writeHandler, this), handle,
44                     senf::scheduler::FdEvent::EV_WRITE, false)
45 {}
46
47 prefix_ senf::console::detail::BaseTelnetProtocol::BaseTelnetProtocol()
48     : handle_ (), charState_ (NORMAL), command_ (CMD_NONE), option_ (0),
49       inputEvent_ ("senf::console::detail::BaseTelnetProtocol::input", 0),
50       outputEvent_ ("senf::console::detail::BaseTelnetProtocol::output", 0)
51 {}
52
53 prefix_ void senf::console::detail::BaseTelnetProtocol::write(std::string const & s)
54 {
55     for (std::string::const_iterator i (s.begin()); i != s.end(); ++i)
56         write(*i);
57 }
58
59 prefix_ void senf::console::detail::BaseTelnetProtocol::write(char c)
60 {
61     switch (c) {
62     case '\r':
63         transmit('\r');
64         transmit('\0');
65         break;
66     case '\n':
67         transmit('\r');
68         transmit('\n');
69         break;
70     case '\xff':
71         transmit('\xff');
72         transmit('\xff');
73         break;
74     default:
75         transmit(c);
76         break;
77     }
78 }
79
80 prefix_ void
81 senf::console::detail::BaseTelnetProtocol::sendOptionParameters(option_type option,
82                                                                 std::string const & data)
83 {
84     transmit(CMD_IAC);
85     transmit(CMD_SB);
86     transmit(option);
87     for (std::string::const_iterator i (data.begin()); i != data.end(); ++i)
88         if (*i == '\xff') {
89             transmit('\xff');
90             transmit('\xff');
91         }
92         else
93             transmit(*i);
94     transmit(CMD_IAC);
95     transmit(CMD_SE);
96 }
97
98 prefix_ void senf::console::detail::BaseTelnetProtocol::v_handleNOP()
99 {}
100
101 prefix_ void senf::console::detail::BaseTelnetProtocol::v_handleBRK()
102 {}
103
104 prefix_ void senf::console::detail::BaseTelnetProtocol::v_handleIP()
105 {}
106
107 prefix_ void senf::console::detail::BaseTelnetProtocol::v_handleAO()
108 {}
109
110 prefix_ void senf::console::detail::BaseTelnetProtocol::v_handleAYT()
111 {}
112
113 prefix_ void senf::console::detail::BaseTelnetProtocol::v_handleEC()
114 {}
115
116 prefix_ void senf::console::detail::BaseTelnetProtocol::v_handleEL()
117 {}
118
119 prefix_ void senf::console::detail::BaseTelnetProtocol::v_handleGA()
120 {}
121
122 prefix_ void senf::console::detail::BaseTelnetProtocol::handleChar(char c)
123 {
124     switch (charState_) {
125     case NORMAL:
126         handleNormalChar(c);
127         break;
128     case IAC_SEEN:
129         handleCommand(static_cast<unsigned char>(c));
130         break;
131     case EXPECT_OPTION:
132         handleOption(c);
133         break;
134     case CR_SEEN:
135         handleCR(c);
136         break;
137     case SB_OPTION:
138         handleSBOption(c);
139         break;
140     case SB_DATA:
141         handleSBData(c);
142         break;
143     case SB_IAC_SEEN:
144         handleSBIAC(c);
145         break;
146     }
147 }
148
149 prefix_ void senf::console::detail::BaseTelnetProtocol::handleNormalChar(char c)
150 {
151     switch (c) {
152     case '\r':
153         charState_ = CR_SEEN;
154         break;
155     case '\xff':
156         charState_ = IAC_SEEN;
157         break;
158     default:
159         emit(c);
160         break;
161     }
162 }
163
164 prefix_ void senf::console::detail::BaseTelnetProtocol::handleCommand(char c)
165 {
166     switch (c) {
167     case CMD_SE:
168         // Ignore spurious SE commands .. they should only occur while in subnegotiation mode
169         charState_ = NORMAL;
170         break;
171     case CMD_NOP:
172     case CMD_DM:
173     case CMD_BRK:
174     case CMD_IP:
175     case CMD_AO:
176     case CMD_AYT:
177     case CMD_EC:
178     case CMD_EL:
179     case CMD_GA:
180         command_ = Command(static_cast<unsigned char>(c));
181         processCommand();
182         charState_ = NORMAL;
183         break;
184     case CMD_SB:
185         command_ = CMD_SB;
186         charState_ = SB_OPTION;
187         break;
188     case CMD_WILL:
189     case CMD_WONT:
190     case CMD_DO:
191     case CMD_DONT:
192         command_ = Command(static_cast<unsigned char>(c));
193         charState_ = EXPECT_OPTION;
194         break;
195     case CMD_IAC:
196         charState_ = NORMAL;
197         emit(CMD_IAC);
198         break;
199     default:
200         emit(CMD_IAC);
201         charState_ = NORMAL;
202         handleChar(c);
203         break;
204     }
205 }        
206
207 prefix_ void senf::console::detail::BaseTelnetProtocol::handleOption(char c)
208 {
209     option_ = c;
210     processCommand();
211     charState_ = NORMAL;
212 }
213
214 prefix_ void senf::console::detail::BaseTelnetProtocol::handleCR(char c)
215 {
216     switch (c) {
217     case '\0':
218         emit('\r');
219         charState_ = NORMAL;
220         break;
221     case '\n':
222         emit('\n');
223         charState_ = NORMAL;
224         break;
225     default:
226         emit('\r');
227         charState_ = NORMAL;
228         handleChar(c);
229         break;
230     }
231 }
232
233 prefix_ void senf::console::detail::BaseTelnetProtocol::handleSBOption(char c)
234 {
235     option_ = c;
236     charState_ = SB_DATA;
237     data_.clear();
238 }
239
240 prefix_ void senf::console::detail::BaseTelnetProtocol::handleSBData(char c)
241 {
242     if (c == '\xff')
243         charState_ = SB_IAC_SEEN;
244     else
245         data_.push_back(c);
246 }
247
248 prefix_ void senf::console::detail::BaseTelnetProtocol::handleSBIAC(char c)
249 {
250     switch (c) {
251     case CMD_IAC:
252         data_.push_back(c);
253         charState_ = SB_DATA;
254         break;
255     case CMD_SE:
256         processCommand();
257         charState_ = NORMAL;
258         break;
259     default:
260         charState_ = IAC_SEEN;
261         handleChar(c);
262         break;
263     }
264 }
265
266 prefix_ void senf::console::detail::BaseTelnetProtocol::processCommand()
267 {
268     switch (command_) {
269     case CMD_NONE:
270     case CMD_SE:
271     case CMD_DM:
272     case CMD_IAC:
273         break;
274     case CMD_NOP:
275         v_handleNOP();
276         break;
277     case CMD_BRK:
278         v_handleBRK();
279         break;
280     case CMD_IP:
281         v_handleIP();
282         break;
283     case CMD_AO:
284         v_handleAO();
285         break;
286     case CMD_AYT:
287         v_handleAYT();
288         break;
289     case CMD_EC:
290         v_handleEC();
291         break;
292     case CMD_EL:
293         v_handleEL();
294         break;
295     case CMD_GA:
296         v_handleGA();
297         break;
298     case CMD_SB:
299     {
300         OptionHandlerMap::const_iterator i (handlers_.find(option_));
301         if (i != handlers_.end())
302             i->second->v_handleOptionParameters(data_);
303         break;
304     }
305     case CMD_WILL:
306     case CMD_WONT:
307         response(getOption(false, option_), command_ == CMD_WILL);
308         break;
309     case CMD_DO:
310     case CMD_DONT:
311         response(getOption(true, option_),  command_ == CMD_DO);
312         break;
313     }
314 }
315
316 prefix_ void senf::console::detail::BaseTelnetProtocol::transmit(char c)
317 {
318     sendQueue_.push_back(c);
319     outputEvent_.enable();
320 }
321
322 prefix_ void senf::console::detail::BaseTelnetProtocol::readHandler(int state)
323 {
324     if (state != senf::scheduler::FdEvent::EV_READ || handle_.eof()) {
325         inputEvent_.disable();
326         v_eof();
327         return;
328     }
329     std::string data;
330     handle_.read(data, 0u);
331     for (std::string::const_iterator i (data.begin()); i != data.end(); ++i)
332         handleChar(*i);
333 }
334
335 prefix_ void senf::console::detail::BaseTelnetProtocol::writeHandler(int state)
336
337     if (state != senf::scheduler::FdEvent::EV_WRITE) {
338         outputEvent_.disable();
339         inputEvent_.disable();
340         v_eof();
341         return;
342     }
343     sendQueue_.erase(sendQueue_.begin(), 
344                      handle_.write(std::make_pair(sendQueue_.begin(), sendQueue_.end())));
345     if (sendQueue_.empty())
346         outputEvent_.disable();
347 }
348
349 prefix_ senf::console::detail::BaseTelnetProtocol::OptInfo &
350 senf::console::detail::BaseTelnetProtocol::getOption(bool local, option_type option)
351 {
352     OptionsMap::iterator i (options_.find(std::make_pair(local, option)));
353     if (i == options_.end())
354         i = options_.insert(std::make_pair(std::make_pair(local, option),
355                                            OptInfo(local, option))).first;
356     return i->second;
357 }
358
359 prefix_ void senf::console::detail::BaseTelnetProtocol::request(OptInfo & info, bool enabled)
360 {
361     info.wantState = enabled ? OptInfo::WANTED : OptInfo::DISABLED;
362     if (enabled != info.enabled) {
363         transmit(CMD_IAC);
364         transmit((info.local ? CMD_WILL : CMD_DO) + (enabled ? 0 : 1));
365         transmit(info.option);
366         info.optionState = OptInfo::REQUEST_SENT;
367     }
368 }
369
370 prefix_ void senf::console::detail::BaseTelnetProtocol::response(OptInfo & info, bool enabled)
371 {
372     // If this is a response, we need to unconditionally accept it.  If this is a remote
373     // configuration request, we accept it if wantState is wither WANTED or ACCEPTED.  If this is a
374     // response, we never send out a reply. If it is a remote request we send out a reply only if
375     // either a) we reject the request or b) we accept it AND we have changed our own mode.
376     if (info.optionState == OptInfo::REQUEST_SENT) {
377         // This is a response
378         info.optionState = OptInfo::ACKNOWLEDGED;
379         info.enabled = enabled;
380     }
381     else if (enabled != info.enabled) {
382         // Request to change the current state
383         bool accept (enabled);
384         if (!enabled || 
385             enabled && (info.wantState == OptInfo::WANTED || info.wantState == OptInfo::ACCEPTED)) {
386             // Accept the request
387             info.optionState = OptInfo::ACKNOWLEDGED;
388             info.enabled = enabled;
389         }
390         else
391             // Reject the request
392             accept = info.enabled;
393         transmit(CMD_IAC);
394         transmit((info.local ? CMD_WILL : CMD_DO) + (accept ? 0 : 1));
395         transmit(info.option);
396     }
397     else
398         return;
399     if (info.enabled) {
400         OptionHandlerMap::const_iterator i (handlers_.find(info.option));
401         if (i != handlers_.end())
402             i->second->v_init();
403     }
404 }
405
406 ///////////////////////////////////////////////////////////////////////////
407 // senf::console::detail::telnethandler::TerminalType
408
409 prefix_ senf::console::detail::telnethandler::TerminalType::TerminalType()
410 {
411     registerHandler(this);
412 }
413
414 prefix_ void senf::console::detail::telnethandler::TerminalType::nextTerminalType()
415 {
416     sendOptionParameters(telnetopt::TERMINAL_TYPE, "\x01");
417 }
418
419 prefix_ void senf::console::detail::telnethandler::TerminalType::
420 v_handleOptionParameters(std::string const & data)
421 {
422     if (data.size() <= 0)
423         return;
424     if (data[0] == '\x00')
425         v_handleTerminalType(data.substr(1));
426 }
427
428 prefix_ void senf::console::detail::telnethandler::TerminalType::v_init()
429 {
430     nextTerminalType();
431 }
432
433 ///////////////////////////////////////////////////////////////////////////
434 // senf::console::detail::telnethandler::NAWS
435
436 prefix_ senf::console::detail::telnethandler::NAWS::NAWS()
437 {
438     registerHandler(this);
439 }
440
441 prefix_ void senf::console::detail::telnethandler::NAWS::v_init()
442 {}
443
444 prefix_ void
445 senf::console::detail::telnethandler::NAWS::v_handleOptionParameters(std::string const & data)
446 {
447     if (data.size() != 4)
448         return;
449     v_handleWindowSize(
450         (static_cast<unsigned char>(data[0])<<8)+static_cast<unsigned char>(data[1]),
451         (static_cast<unsigned char>(data[2])<<8)+static_cast<unsigned char>(data[3]));
452 }
453
454 ///////////////////////////////cc.e////////////////////////////////////////
455 #undef prefix_
456 //#include "Telnet.mpp"
457
458 \f
459 // Local Variables:
460 // mode: c++
461 // fill-column: 100
462 // comment-column: 40
463 // c-file-style: "senf"
464 // indent-tabs-mode: nil
465 // ispell-local-dictionary: "american"
466 // compile-command: "scons -u test"
467 // End: