summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMike Buland <eichlan@xagasoft.com>2007-10-14 22:27:51 +0000
committerMike Buland <eichlan@xagasoft.com>2007-10-14 22:27:51 +0000
commite72d6077b475bc6142afc3b5967db113922c76f5 (patch)
tree45bdb7b69ec4c1dbcfadc1fa568b1d6b341a6f0f /src
parent44eac9521632f8da42f73085db945bdba45f8311 (diff)
downloadlibbu++-e72d6077b475bc6142afc3b5967db113922c76f5.tar.gz
libbu++-e72d6077b475bc6142afc3b5967db113922c76f5.tar.bz2
libbu++-e72d6077b475bc6142afc3b5967db113922c76f5.tar.xz
libbu++-e72d6077b475bc6142afc3b5967db113922c76f5.zip
Fixed an interesting ideosyncacy in Bu::Hash in a safe way, I should try to do
this with the Bu::Archive next. Basically, there's one generic template function that will convert anything that can safely cast to a uint32_t and that supports direct comparisson, and doesn't have it's own override already to be a Hash key, such as char, uint8_t, uint64_t, etc. The Telnet protocol handler does everything I need it too for now, next up for it is escape sequence handling, it would be nice to make this general too, by using the termcap database or something, but there is an ANSI/ISO standard now, I may just go ahead and use that. Also, it looks like it'd be pretty easy to make the canonical mode editing functions be pluggable to facilitate different types of editing, but that can be done down the road as well.
Diffstat (limited to 'src')
-rw-r--r--src/hash.cpp20
-rw-r--r--src/hash.h14
-rw-r--r--src/protocolhttp.h9
-rw-r--r--src/protocoltelnet.cpp532
-rw-r--r--src/protocoltelnet.h151
-rw-r--r--src/tests/telnetsrv.cpp85
6 files changed, 733 insertions, 78 deletions
diff --git a/src/hash.cpp b/src/hash.cpp
index a207c29..9b8a1c1 100644
--- a/src/hash.cpp
+++ b/src/hash.cpp
@@ -2,26 +2,6 @@
2 2
3namespace Bu { subExceptionDef( HashException ) } 3namespace Bu { subExceptionDef( HashException ) }
4 4
5template<> uint32_t Bu::__calcHashCode<int>( const int &k )
6{
7 return k;
8}
9
10template<> bool Bu::__cmpHashKeys<int>( const int &a, const int &b )
11{
12 return a == b;
13}
14
15template<> uint32_t Bu::__calcHashCode<unsigned int>( const unsigned int &k )
16{
17 return k;
18}
19
20template<> bool Bu::__cmpHashKeys<unsigned int>( const unsigned int &a, const unsigned int &b )
21{
22 return a == b;
23}
24
25template<> 5template<>
26uint32_t Bu::__calcHashCode<const char *>( const char * const &k ) 6uint32_t Bu::__calcHashCode<const char *>( const char * const &k )
27{ 7{
diff --git a/src/hash.h b/src/hash.h
index 62b19c9..be57786 100644
--- a/src/hash.h
+++ b/src/hash.h
@@ -1008,12 +1008,16 @@ namespace Bu
1008 challoc ca; 1008 challoc ca;
1009 sizecalc szCalc; 1009 sizecalc szCalc;
1010 }; 1010 };
1011
1012 template<typename T> uint32_t __calcHashCode( const T &k )
1013 {
1014 return static_cast<uint32_t>( k );
1015 }
1011 1016
1012 template<> uint32_t __calcHashCode<int>( const int &k ); 1017 template<typename T> bool __cmpHashKeys( const T &a, const T &b )
1013 template<> bool __cmpHashKeys<int>( const int &a, const int &b ); 1018 {
1014 1019 return (a == b);
1015 template<> uint32_t __calcHashCode<unsigned int>( const unsigned int &k ); 1020 }
1016 template<> bool __cmpHashKeys<unsigned int>( const unsigned int &a, const unsigned int &b );
1017 1021
1018 template<> uint32_t __calcHashCode<const char *>( const char * const &k ); 1022 template<> uint32_t __calcHashCode<const char *>( const char * const &k );
1019 template<> bool __cmpHashKeys<const char *>( const char * const &a, const char * const &b ); 1023 template<> bool __cmpHashKeys<const char *>( const char * const &a, const char * const &b );
diff --git a/src/protocolhttp.h b/src/protocolhttp.h
index e2612f5..85510e3 100644
--- a/src/protocolhttp.h
+++ b/src/protocolhttp.h
@@ -11,7 +11,14 @@
11namespace Bu 11namespace Bu
12{ 12{
13 /** 13 /**
14 * 14 * An HTTP Protocol handler. Yes, I know that HTTP stands for Hyper Text
15 * Transfer Protocol, and that the Protocol part is redundant, but in this
16 * case the word Protocol is refering to the Libbu++ construct Bu::Protocol,
17 * and not a means of encoding conversations. Anyway, this class represents
18 * a general HTTP server processor. Every time a request comes in it calls
19 * the onRequest function in a subclass with the method and URI that were
20 * requested. The sub-class can then do whatever it needs to to send back
21 * a response.
15 */ 22 */
16 class ProtocolHttp : public Protocol 23 class ProtocolHttp : public Protocol
17 { 24 {
diff --git a/src/protocoltelnet.cpp b/src/protocoltelnet.cpp
index b0209db..e4fc926 100644
--- a/src/protocoltelnet.cpp
+++ b/src/protocoltelnet.cpp
@@ -1,30 +1,69 @@
1#include "bu/protocoltelnet.h" 1#include "bu/protocoltelnet.h"
2#include "bu/client.h" 2#include "bu/client.h"
3 3
4#define CODE_SE '\xf0' /**< End of subnegotiation params. */ 4/* We apparently at least want defs for the lower 13, not sure we care about
5#define CODE_NOP '\xf1' /**< No operation (keep-alive). */ 5 * the rest of the chars, maybe escape.
6#define CODE_DM '\xf2' /**< Datastream side of a Synch. */ 6 */
7#define CODE_BRK '\xf3' /**< Break character. */ 7#define CH_NUL '\x00' /* NUL */
8#define CODE_IP '\xf4' /**< Interrupt Process character. */ 8#define CH_SOH '\x01' /* Start Of Heading */
9#define CODE_AO '\xf5' /**< Abort Output character. */ 9#define CH_STX '\x02' /* Start of Text */
10#define CODE_AYT '\xf6' /**< Are You There? character. */ 10#define CH_ETX '\x03' /* End of Text */
11#define CODE_EC '\xf7' /**< Erase Character character. */ 11#define CH_EOT '\x04' /* End of transmission */
12#define CODE_EL '\xf8' /**< Erase Line character. */ 12#define CH_ENQ '\x05' /* Enquiery */
13#define CODE_GA '\xf9' /**< Go Ahead signal. */ 13#define CH_ACK '\x06' /* Acknowledge */
14#define CODE_SB '\xfa' /**< Begin subnegotiation options. */ 14#define CH_BEL '\x07' /* Bell */
15#define CODE_WILL '\xfb' /**< Desire to do something. */ 15#define CH_BS '\x08' /* Backspace */
16#define CODE_WONT '\xfc' /**< Refuse to perform. */ 16#define CH_TAB '\x09' /* Horizontal Tab */
17#define CODE_DO '\xfd' /**< Request option. */ 17#define CH_LF '\x0A' /* NL Line feed, new line */
18#define CODE_DONT '\xfe' /**< Demand a stop. */ 18#define CH_VT '\x0B' /* Vertical Tab */
19 19#define CH_FF '\x0C' /* Form feed, new page */
20#define CODE_IAC '\xff' /**< Interpret-As-Command. */ 20#define CH_CR '\x0D' /* Carriage return */
21 21#define CH_ESC '\x1B' /* Escape */
22#define OPT_BINARY '\x00' /**< Binary mode (file transfers?). */ 22#define CH_DEL '\x7F' /* Delete */
23#define OPT_ECHO '\x01' /**< (local) Echo mode. */ 23
24#define CODE_SE '\xf0' /* End of subnegotiation params. */
25#define CODE_NOP '\xf1' /* No operation (keep-alive). */
26#define CODE_DM '\xf2' /* Datastream side of a Synch. */
27#define CODE_BRK '\xf3' /* Break character. */
28#define CODE_IP '\xf4' /* Interrupt Process character. */
29#define CODE_AO '\xf5' /* Abort Output character. */
30#define CODE_AYT '\xf6' /* Are You There? character. */
31#define CODE_EC '\xf7' /* Erase Character character. */
32#define CODE_EL '\xf8' /* Erase Line character. */
33#define CODE_GA '\xf9' /* Go Ahead signal. */
34#define CODE_SB '\xfa' /* Begin subnegotiation options. */
35#define CODE_WILL '\xfb' /* Desire to do something. */
36#define CODE_WONT '\xfc' /* Refuse to perform. */
37#define CODE_DO '\xfd' /* Request option. */
38#define CODE_DONT '\xfe' /* Demand a stop. */
39
40#define CODE_IAC '\xff' /* Interpret-As-Command. */
41
42#define OPT_BINARY '\x00' /* Binary mode (file transfers?). */
43#define OPT_ECHO '\x01' /* (local) Echo mode. */
44#define OPT_SUPGA '\x03' /* Suppress Go Ahead signals. */
45#define OPT_STATUS '\x05' /* Allow status messages. */
46#define OPT_TIMING '\x06' /* Place a timing mark in the code. */
47#define OPT_EXASCII '\x11' /* Extended ASCII. */
48#define OPT_LOGOUT '\x12' /* Logout. */
49#define OPT_TTYPE '\x18' /* Terminal Type. */
50#define OPT_NAWS '\x1f' /* Negotiate about window size. */
51#define OPT_TSPEED '\x20' /* Terminal Speed. */
52#define OPT_NEWENV '\x27' /* New Environment Option. */
53#define OPT_EXOPL '\xff' /* Can we, will we, handle extended options. */
54
55#ifndef __TELNET_DEBUG
56# define printCode( a ) (void)0
57# define printOpt( a ) (void)0
58#endif
24 59
25Bu::ProtocolTelnet::ProtocolTelnet() : 60Bu::ProtocolTelnet::ProtocolTelnet() :
26 oBinary( *this, OPT_BINARY ), 61 oBinary( *this, OPT_BINARY ),
27 oEcho( *this, OPT_ECHO ) 62 oEcho( *this, OPT_ECHO ),
63 oNAWS( *this, OPT_NAWS ),
64 oSuppressGA(*this, OPT_SUPGA ),
65 bCanonical( true ),
66 bSubOpt( false )
28{ 67{
29} 68}
30 69
@@ -34,13 +73,410 @@ Bu::ProtocolTelnet::~ProtocolTelnet()
34 73
35void Bu::ProtocolTelnet::onNewConnection( Bu::Client *pClient ) 74void Bu::ProtocolTelnet::onNewConnection( Bu::Client *pClient )
36{ 75{
76 this->pClient = pClient;
37} 77}
38 78
39void Bu::ProtocolTelnet::onNewData( Bu::Client *pClient ) 79void Bu::ProtocolTelnet::onNewData( Bu::Client *pClient )
40{ 80{
81 char bc;
82 int iLeft;
83 while( (iLeft = pClient->getInputSize()) )
84 {
85 if( bSubOpt )
86 {
87 pClient->peek( &bc, 1 );
88 if( bc == CODE_IAC )
89 {
90 if( iLeft <= 1 ) return;
91 char bc2;
92 printCode( CODE_IAC );
93 pClient->peek( &bc2, 1, 1 );
94 printCode( bc2 );
95 if( bc2 == CODE_SE )
96 {
97 bSubOpt = false;
98 onSubOpt();
99 }
100 else if( bc2 == CODE_IAC )
101 {
102 sSubBuf += CODE_IAC;
103 }
104 else
105 {
106 // Error of some sort.
107 }
108 pClient->seek( 1 );
109 }
110 else
111 {
112 sSubBuf += bc;
113 }
114 pClient->seek( 1 );
115 }
116 else
117 {
118 pClient->peek( &bc, 1 );
119 if( bc == CODE_IAC )
120 {
121 if( iLeft <= 1 ) return;
122 char bc2;
123 pClient->peek( &bc2, 1, 1 );
124 printCode( bc );
125 printCode( bc2 );
126
127 switch( bc2 )
128 {
129 case CODE_WILL:
130 if( iLeft <= 2 ) return;
131 {
132 char bc3;
133 pClient->peek( &bc3, 1, 2 );
134 pClient->seek( 1 );
135 printOpt( bc3 );
136 onWill( bc3 );
137 }
138 break;
139
140 case CODE_WONT:
141 if( iLeft <= 2 ) return;
142 {
143 char bc3;
144 pClient->peek( &bc3, 1, 2 );
145 pClient->seek( 1 );
146 printOpt( bc3 );
147 onWont( bc3 );
148 }
149 break;
150
151 case CODE_DO:
152 if( iLeft <= 2 ) return;
153 {
154 char bc3;
155 pClient->peek( &bc3, 1, 2 );
156 pClient->seek( 1 );
157 printOpt( bc3 );
158 onDo( bc3 );
159 }
160 break;
161
162 case CODE_DONT:
163 if( iLeft <= 2 ) return;
164 {
165 char bc3;
166 pClient->peek( &bc3, 1, 2 );
167 pClient->seek( 1 );
168 printOpt( bc3 );
169 onDont( bc3 );
170 }
171 break;
172
173 case CODE_SB:
174 if( iLeft <= 2 ) return;
175 {
176 pClient->peek( &cSubOpt, 1, 2 );
177 pClient->seek( 1 );
178 printOpt( cSubOpt );
179 bSubOpt = true;
180 }
181 break;
182
183 case CODE_IAC:
184 sDataBuf += CODE_IAC;
185 printCode( CODE_IAC );
186 break;
187 }
188 pClient->seek( 1 );
189#ifdef __TELNET_DEBUG
190 printf("\n");
191#endif
192 }
193 else if( bc == CODE_SB )
194 {
195 }
196 else
197 {
198 // This is where control code handling goes
199 // Also, possibly, character code conversion, although I'm not
200 // sure that really matters anymore, go ASCII/UTF-8
201 if( bCanonical )
202 {
203 if( bc < 0x20 || bc >= CH_DEL )
204 {
205 if( bc == CH_CR )
206 {
207 if( iLeft <= 1 ) return;
208 char bc2;
209 pClient->peek( &bc2, 1, 1 );
210 if( bc2 == CH_NUL || bc2 == CH_LF )
211 {
212 onCtlChar( bc );
213 gotLine( sDataBuf );
214 sDataBuf.clear();
215 }
216 pClient->seek( 1 );
217 }
218 else
219 {
220 onCtlChar( bc );
221 }
222 }
223 else
224 {
225 sDataBuf += bc;
226 if( oEcho.isLocalSet() )
227 {
228 pClient->write( &bc, 1 );
229#ifdef __TELNET_DEBUG
230 printf("%c", bc );
231 fflush( stdout );
232#endif
233 }
234 }
235 }
236 else
237 {
238 sDataBuf += bc;
239 if( oEcho.isLocalSet() )
240 {
241 pClient->write( &bc, 1 );
242 }
243 }
244 }
245 pClient->seek( 1 );
246 }
247 }
248
249 // It's true, this code will not be executed if we only have half of an
250 // IAC code or multibyte escape sequence or something, but then again, it
251 // shouldn't be called then, and really, shouldn't be, it'll be called soon
252 // enough, when we get the rest of that code.
253 if( !bCanonical )
254 {
255 gotData( sDataBuf );
256 }
257}
258
259void Bu::ProtocolTelnet::setCanonical( bool bCon )
260{
261 bCanonical = bCon;
262}
263
264bool Bu::ProtocolTelnet::isCanonical()
265{
266 return bCanonical;
267}
268
269void Bu::ProtocolTelnet::write( const Bu::FString &sData )
270{
271 pClient->write( sData );
272}
273
274void Bu::ProtocolTelnet::write( char *pData, int iSize )
275{
276 pClient->write( pData, iSize );
277}
278
279void Bu::ProtocolTelnet::write( char cData )
280{
281 pClient->write( &cData, 1 );
282}
283
284void Bu::ProtocolTelnet::onWill( char cCode )
285{
286 try
287 {
288 Option *pOpt = hOpts[cCode];
289 if( pOpt->isRemoteEnabled() )
290 {
291 pOpt->fOpts |= Option::fRemoteIs;
292 char buf[3] = { CODE_IAC, CODE_DO, cCode };
293 pClient->write( buf, 3 );
294 }
295 else
296 {
297 char buf[3] = { CODE_IAC, CODE_DONT, cCode };
298 pClient->write( buf, 3 );
299 }
300
301 }
302 catch( Bu::HashException &e )
303 {
304 char buf[3] = { CODE_IAC, CODE_DONT, cCode };
305 pClient->write( buf, 3 );
306 }
307}
308
309void Bu::ProtocolTelnet::onWont( char cCode )
310{
311 try
312 {
313 Option *pOpt = hOpts[cCode];
314
315 pOpt->fOpts &= ~Option::fRemoteIs;
316 char buf[3] = { CODE_IAC, CODE_DONT, cCode };
317 pClient->write( buf, 3 );
318 }
319 catch( Bu::HashException &e )
320 {
321 char buf[3] = { CODE_IAC, CODE_DONT, cCode };
322 pClient->write( buf, 3 );
323 }
324}
325
326void Bu::ProtocolTelnet::onDo( char cCode )
327{
328 try
329 {
330 Option *pOpt = hOpts[cCode];
331 if( pOpt->isLocalEnabled() )
332 {
333 pOpt->fOpts |= Option::fLocalIs;
334 char buf[3] = { CODE_IAC, CODE_WILL, cCode };
335 pClient->write( buf, 3 );
336 }
337 else
338 {
339 char buf[3] = { CODE_IAC, CODE_WONT, cCode };
340 pClient->write( buf, 3 );
341 }
342
343 }
344 catch( Bu::HashException &e )
345 {
346 char buf[3] = { CODE_IAC, CODE_WONT, cCode };
347 pClient->write( buf, 3 );
348 }
349}
350
351void Bu::ProtocolTelnet::onDont( char cCode )
352{
353 try
354 {
355 Option *pOpt = hOpts[cCode];
356
357 pOpt->fOpts &= ~Option::fLocalIs;
358 char buf[3] = { CODE_IAC, CODE_DONT, cCode };
359 pClient->write( buf, 3 );
360 }
361 catch( Bu::HashException &e )
362 {
363 char buf[3] = { CODE_IAC, CODE_DONT, cCode };
364 pClient->write( buf, 3 );
365 }
41} 366}
42 367
368void Bu::ProtocolTelnet::onSubOpt()
369{
370 switch( cSubOpt )
371 {
372 case OPT_NAWS:
373 {
374 uint16_t iWidth, iHeight;
375 ((char *)&iWidth)[1] = sSubBuf[0];
376 ((char *)&iWidth)[0] = sSubBuf[1];
377 ((char *)&iHeight)[1] = sSubBuf[2];
378 ((char *)&iHeight)[0] = sSubBuf[3];
379 onSubNAWS( iWidth, iHeight );
380 }
381 break;
43 382
383 default:
384 onSubUnknown( cSubOpt, sSubBuf );
385 break;
386 }
387
388 sSubBuf.clear();
389}
390
391void Bu::ProtocolTelnet::onCtlChar( char cChr )
392{
393#ifdef __TELNET_DEBUG
394 switch( cChr )
395 {
396 case CH_NUL: printf("NUL "); break;
397 case CH_SOH: printf("SOH "); break;
398 case CH_STX: printf("STX "); break;
399 case CH_ETX: printf("ETX "); break;
400 case CH_EOT: printf("EOT "); break;
401 case CH_ENQ: printf("ENQ "); break;
402 case CH_ACK: printf("ACK "); break;
403 case CH_BEL: printf("BEL "); break;
404 case CH_BS: printf("BS "); break;
405 case CH_TAB: printf("TAB "); break;
406 case CH_LF: printf("LF "); break;
407 case CH_VT: printf("VT "); break;
408 case CH_FF: printf("FF "); break;
409 case CH_CR: printf("CR "); break;
410 case CH_ESC: printf("ESC "); break;
411 case CH_DEL: printf("DEL "); break;
412 default: printf("!![%02x] ", cChr ); break;
413 }
414 fflush( stdout );
415#endif
416
417 switch( cChr )
418 {
419 case CH_DEL:
420 {
421 if( sDataBuf.getSize() > 0 )
422 {
423 sDataBuf.resize( sDataBuf.getSize()-1 );
424 char buf[3] = { CH_BS, ' ', CH_BS };
425 pClient->write( buf, 3 );
426 }
427 }
428 break;
429
430 }
431}
432
433#ifdef __TELNET_DEBUG
434void Bu::ProtocolTelnet::printCode( char cCode )
435{
436 switch( cCode )
437 {
438 case CODE_SE: printf("SE "); break;
439 case CODE_NOP: printf("NOP "); break;
440 case CODE_DM: printf("DM "); break;
441 case CODE_BRK: printf("BRK "); break;
442 case CODE_IP: printf("IP "); break;
443 case CODE_AO: printf("AO "); break;
444 case CODE_AYT: printf("AYT "); break;
445 case CODE_EC: printf("EC "); break;
446 case CODE_EL: printf("EL "); break;
447 case CODE_GA: printf("GA "); break;
448 case CODE_SB: printf("SB "); break;
449 case CODE_WILL: printf("WILL "); break;
450 case CODE_WONT: printf("WONT "); break;
451 case CODE_DO: printf("DO "); break;
452 case CODE_DONT: printf("DONT "); break;
453 case CODE_IAC: printf("IAC "); break;
454 default: printf("??%02x ", cCode ); break;
455 }
456 fflush( stdout );
457}
458
459void Bu::ProtocolTelnet::printOpt( char cOpt )
460{
461 switch( cOpt )
462 {
463 case OPT_BINARY: printf("BINARY "); break;
464 case OPT_ECHO: printf("ECHO "); break;
465 case OPT_SUPGA: printf("SUPGA "); break;
466 case OPT_STATUS: printf("STATUS "); break;
467 case OPT_TIMING: printf("TIMING "); break;
468 case OPT_EXASCII: printf("EXASCII "); break;
469 case OPT_LOGOUT: printf("LOGOUT "); break;
470 case OPT_TTYPE: printf("TTYPE "); break;
471 case OPT_NAWS: printf("NAWS "); break;
472 case OPT_TSPEED: printf("TSPEED "); break;
473 case OPT_NEWENV: printf("NEWENV "); break;
474 case OPT_EXOPL: printf("EXOPL "); break;
475 default: printf("??%02x ", cOpt); break;
476 }
477 fflush( stdout );
478}
479#endif
44 480
45Bu::ProtocolTelnet::Option::Option( Bu::ProtocolTelnet &rPT, char cCode ) : 481Bu::ProtocolTelnet::Option::Option( Bu::ProtocolTelnet &rPT, char cCode ) :
46 rPT( rPT ), 482 rPT( rPT ),
@@ -68,19 +504,31 @@ void Bu::ProtocolTelnet::Option::localSet( bool bSet )
68{ 504{
69 if( bSet == (bool)(fOpts&fLocalIs) ) return; 505 if( bSet == (bool)(fOpts&fLocalIs) ) return;
70 506
71 char buf[2]; 507 char buf[3] = { CODE_IAC, 0, cCode };
72 508
73 if( bSet ) 509 if( bSet )
74 { 510 {
75 buf[0] = CODE_WILL; 511 buf[1] = CODE_WILL;
76 buf[1] = cCode; 512 rPT.pClient->write( buf, 3 );
77 rPT.pClient->write( buf, 2 ); 513#ifdef __TELNET_DEBUG
514 printf("<= ");
515 rPT.printCode( buf[0] );
516 rPT.printCode( buf[1] );
517 rPT.printOpt( buf[2] );
518 printf("\n");
519#endif
78 } 520 }
79 else 521 else
80 { 522 {
81 buf[0] = CODE_WONT; 523 buf[1] = CODE_WONT;
82 buf[1] = cCode; 524 rPT.pClient->write( buf, 3 );
83 rPT.pClient->write( buf, 2 ); 525#ifdef __TELNET_DEBUG
526 printf("<= ");
527 rPT.printCode( buf[0] );
528 rPT.printCode( buf[1] );
529 rPT.printOpt( buf[2] );
530 printf("\n");
531#endif
84 } 532 }
85} 533}
86 534
@@ -101,21 +549,33 @@ void Bu::ProtocolTelnet::Option::remoteEnable( bool bSet )
101 549
102void Bu::ProtocolTelnet::Option::remoteSet( bool bSet ) 550void Bu::ProtocolTelnet::Option::remoteSet( bool bSet )
103{ 551{
104 if( bSet == (bool)(fOpts&fRemoteIs) ) return; 552 //if( bSet == (bool)(fOpts&fRemoteIs) ) return;
105 553
106 char buf[2]; 554 char buf[3] = { CODE_IAC, 0, cCode };
107 555
108 if( bSet ) 556 if( bSet )
109 { 557 {
110 buf[0] = CODE_DO; 558 buf[1] = CODE_DO;
111 buf[1] = cCode; 559 rPT.pClient->write( buf, 3 );
112 rPT.pClient->write( buf, 2 ); 560#ifdef __TELNET_DEBUG
561 printf("<= ");
562 rPT.printCode( buf[0] );
563 rPT.printCode( buf[1] );
564 rPT.printOpt( buf[2] );
565 printf("\n");
566#endif
113 } 567 }
114 else 568 else
115 { 569 {
116 buf[0] = CODE_DONT; 570 buf[1] = CODE_DONT;
117 buf[1] = cCode; 571 rPT.pClient->write( buf, 3 );
118 rPT.pClient->write( buf, 2 ); 572#ifdef __TELNET_DEBUG
573 printf("<= ");
574 rPT.printCode( buf[0] );
575 rPT.printCode( buf[1] );
576 rPT.printOpt( buf[2] );
577 printf("\n");
578#endif
119 } 579 }
120} 580}
121 581
diff --git a/src/protocoltelnet.h b/src/protocoltelnet.h
index 3a606b5..f773f1e 100644
--- a/src/protocoltelnet.h
+++ b/src/protocoltelnet.h
@@ -3,35 +3,131 @@
3 3
4#include "bu/protocol.h" 4#include "bu/protocol.h"
5#include "bu/hash.h" 5#include "bu/hash.h"
6#include "bu/fstring.h"
7
8// #define __TELNET_DEBUG
6 9
7namespace Bu 10namespace Bu
8{ 11{
12 /**
13 * Telnet Protocol handler. This attempts to provide useful and general
14 * support for most of the most commonly used Telnet extensions in a simple
15 * and easy to use way. The Option variables control the settings that can
16 * be used on the line, and control which virtual "callbacks" will be called
17 * when different events happen.
18 *
19 * To setup initial values and to disable any options you wish override the
20 * onNewConnection function in your own class, like this:
21 *@code
22 class MyTelnet : public Bu::ProtocolTelnet
23 {
24 public:
25 ...
26
27 virtual void onNewConnection( class Bu::Client *pClient )
28 {
29 // Call the parent class' onNewConnection to get everything all
30 // set up.
31 Bu::ProtocolTelnet::onNewConnection( pClient );
32
33 // These functions disable the option to send files via telnet,
34 // disabling the remote option means that we won't accept this
35 // option (binary data being sent to us) from the client.
36 //
37 // Disabling the local option means that the client cannot ask us
38 // to send them binary data.
39 oBinary.enableRemote( false );
40 oBinary.enableLocal( false );
41
42 // This requests that the client send us window size updates
43 // whenever the size of their window changes, and an initial set to
44 // boot.
45 //
46 // To see if this option is set later, try oNAWS.isRemoteSet(), but
47 // wait a little while, asking immediatly will always return false,
48 // since the remote side has yet to receive our request.
49 oNAWS.remoteSet();
50 }
51 }
52 @endcode
53 *
54 */
9 class ProtocolTelnet : public Protocol 55 class ProtocolTelnet : public Protocol
10 { 56 {
11 public: 57 public:
12 ProtocolTelnet(); 58 ProtocolTelnet();
13 virtual ~ProtocolTelnet(); 59 virtual ~ProtocolTelnet();
14 60
61 /**
62 * If you override this function in a child class, make sure to call
63 * this version of it as the very first thing that you do, before you
64 * set any options. See the example in the class docs.
65 */
15 virtual void onNewConnection( class Bu::Client *pClient ); 66 virtual void onNewConnection( class Bu::Client *pClient );
67
68 /**
69 * You should never override this function unless you really, really
70 * know what you're doing. If you want to get data after each line
71 * entered (in canonical mode) or after any data arrives (non canonical
72 * mode) then override the gotLine and gotData functions, respectively.
73 */
16 virtual void onNewData( class Bu::Client *pClient ); 74 virtual void onNewData( class Bu::Client *pClient );
17 75
18 enum OptMode 76 /**
19 { 77 * Override this function to be notified of lines being submitted by
20 optOff, 78 * the client. This function is only called in canonical mode, after
21 optOn, 79 * all edits are performed on the data. In this mode weather you use
22 optDesire, 80 * the line or not, the data will be cleared from the buffer when this
23 optRefuse 81 * function returns, any changes made to the buffer will be destroyed.
24 }; 82 */
83 virtual void gotLine( Bu::FString &sLine ){};
25 84
26 OptMode getLocalOptBinary(); 85 /**
27 void setLocalOptBinary( OptMode eMode ); 86 * Override this function to be notified of any new data that comes in
28 OptMode getRemoteOptBinary(); 87 * from the client. This function is only called in non-canonical mode,
29 void setRemoteOptBinary( OptMode eMode ); 88 * and includes all raw data minus telnet control codes and ansi
89 * escape sequences. In this mode control of the buffer is up to the
90 * child class in this function, the buffer will never be cleared unless
91 * it happens in this function's override.
92 */
93 virtual void gotData( Bu::FString &sData ){};
30 94
31 OptMode getLocalOptEcho(); 95 /**
32 void setLocalOptEcho( OptMode eMode ); 96 * Using this function to enable or disable canonical mode only affects
33 OptMode getRemoteOptEcho(); 97 * the way the data is processed and which virtual functions are called
34 void setRemoteOptEcho( OptMode eMode ); 98 * during processing. It does not affect options set locally or
99 * remotely. Setting this to false will enable char-at-a-time mode,
100 * effectively disabling internal line-editing code. Characters
101 * such as backspace that are detected will not be handled and will be
102 * sent to the user override. The subclass will also be notified every
103 * time new data is available, not just whole lines.
104 *
105 * When set to true (the default), line editing control codes will be
106 * interpreted and used, and the subclass will only be notified when
107 * complete lines are available in the buffer.
108 */
109 void setCanonical( bool bCon=true );
110 bool isCanonical();
111
112 void write( const Bu::FString &sData );
113 void write( char *pData, int iSize );
114 void write( char cData );
115
116 public:
117 /**
118 * If you wish to know the current dimensions of the client window,
119 * override this function, it will be called whenever the size changes.
120 */
121 virtual void onSubNAWS( uint16_t iWidth, uint16_t iHeight ){};
122
123 /**
124 * This function is called whenever an unknown sub negotiation option is
125 * sent over the line. This doesn't mean that it's malformatted, it
126 * just means that this class doesn't support that option yet, but you
127 * can handle it yourself if you'd like. Feel free to change the
128 * sSubBuf, it will be cleared as soon as this function returns anyway.
129 */
130 virtual void onSubUnknown( char cSubOpt, Bu::FString &sSubBuf ){};
35 131
36 private: 132 private:
37 /** 133 /**
@@ -75,15 +171,38 @@ namespace Bu
75 char fOpts; 171 char fOpts;
76 char cCode; 172 char cCode;
77 }; 173 };
174 friend class Bu::ProtocolTelnet::Option;
78 175
79 Hash<char, Option *> hOpts; 176 Hash<char, Option *> hOpts;
80 177
178 public:
81 Option oBinary; 179 Option oBinary;
82 Option oEcho; 180 Option oEcho;
181 Option oNAWS;
182 Option oSuppressGA;
83 183
184 private:
185 void onWill( char cCode );
186 void onWont( char cCode );
187 void onDo( char cCode );
188 void onDont( char cCode );
189 void onSubOpt();
190 void onCtlChar( char cChr );
191
192#ifdef __TELNET_DEBUG
193 void printCode( char cCode );
194 void printOpt( char cOpt );
195#endif
196
197 private:
84 Client *pClient; 198 Client *pClient;
85 199
86 friend class Bu::ProtocolTelnet::Option; 200 Bu::FString sDataBuf; /**< Buffer for regular line data. */
201 Bu::FString sSubBuf; /**< Buffer for subnegotiation data. */
202 char cSubOpt; /**< Which suboption are we processing. */
203
204 bool bCanonical; /**< Are we canonicalizing incoming data? */
205 bool bSubOpt; /**< Are we processing a suboption right now? */
87 }; 206 };
88} 207}
89 208
diff --git a/src/tests/telnetsrv.cpp b/src/tests/telnetsrv.cpp
new file mode 100644
index 0000000..39e3217
--- /dev/null
+++ b/src/tests/telnetsrv.cpp
@@ -0,0 +1,85 @@
1#include "bu/server.h"
2#include "bu/protocoltelnet.h"
3#include "bu/client.h"
4
5class MyTelnet : public Bu::ProtocolTelnet
6{
7public:
8 MyTelnet()
9 {
10 }
11
12 virtual ~MyTelnet()
13 {
14 }
15
16 virtual void onNewConnection( Bu::Client *pClient )
17 {
18 Bu::ProtocolTelnet::onNewConnection( pClient );
19
20 //oNAWS.remoteSet();
21 oEcho.localSet();
22 oSuppressGA.remoteSet( true );
23 oSuppressGA.localSet( true );
24 setCanonical();
25 }
26
27 virtual void onSubNAWS( uint16_t iWidth, uint16_t iHeight )
28 {
29 printf("New dim = (%dx%d)\n", iWidth, iHeight );
30 }
31
32 virtual void gotLine( Bu::FString &sLine )
33 {
34 printf("Line: \"%s\"\n", sLine.getStr() );
35 write("\n\r", 2 );
36 }
37
38private:
39
40};
41
42class TelServer : public Bu::Server
43{
44public:
45 TelServer()
46 {
47 }
48
49 virtual ~TelServer()
50 {
51 }
52
53 virtual void onNewConnection( Bu::Client *pClient, int iPort )
54 {
55 printf("New connection.\n");
56
57 pClient->setProtocol( new MyTelnet() );
58 }
59
60 virtual void onClosedConnection( Bu::Client *pClient )
61 {
62 printf("Lost connection.\n");
63
64 delete pClient->getProtocol();
65 }
66
67private:
68
69};
70
71int main( int argc, char *argv[] )
72{
73 TelServer ts;
74
75 ts.addPort( 4000 );
76 ts.setTimeout( 0, 5000 );
77
78 printf("Initializing server on port: 4000\n");
79
80 for(;;)
81 {
82 ts.scan();
83 }
84}
85