diff options
author | Mike Buland <eichlan@xagasoft.com> | 2007-10-14 22:27:51 +0000 |
---|---|---|
committer | Mike Buland <eichlan@xagasoft.com> | 2007-10-14 22:27:51 +0000 |
commit | e72d6077b475bc6142afc3b5967db113922c76f5 (patch) | |
tree | 45bdb7b69ec4c1dbcfadc1fa568b1d6b341a6f0f /src | |
parent | 44eac9521632f8da42f73085db945bdba45f8311 (diff) | |
download | libbu++-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.cpp | 20 | ||||
-rw-r--r-- | src/hash.h | 14 | ||||
-rw-r--r-- | src/protocolhttp.h | 9 | ||||
-rw-r--r-- | src/protocoltelnet.cpp | 532 | ||||
-rw-r--r-- | src/protocoltelnet.h | 151 | ||||
-rw-r--r-- | src/tests/telnetsrv.cpp | 85 |
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 | ||
3 | namespace Bu { subExceptionDef( HashException ) } | 3 | namespace Bu { subExceptionDef( HashException ) } |
4 | 4 | ||
5 | template<> uint32_t Bu::__calcHashCode<int>( const int &k ) | ||
6 | { | ||
7 | return k; | ||
8 | } | ||
9 | |||
10 | template<> bool Bu::__cmpHashKeys<int>( const int &a, const int &b ) | ||
11 | { | ||
12 | return a == b; | ||
13 | } | ||
14 | |||
15 | template<> uint32_t Bu::__calcHashCode<unsigned int>( const unsigned int &k ) | ||
16 | { | ||
17 | return k; | ||
18 | } | ||
19 | |||
20 | template<> bool Bu::__cmpHashKeys<unsigned int>( const unsigned int &a, const unsigned int &b ) | ||
21 | { | ||
22 | return a == b; | ||
23 | } | ||
24 | |||
25 | template<> | 5 | template<> |
26 | uint32_t Bu::__calcHashCode<const char *>( const char * const &k ) | 6 | uint32_t Bu::__calcHashCode<const char *>( const char * const &k ) |
27 | { | 7 | { |
@@ -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 @@ | |||
11 | namespace Bu | 11 | namespace 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 | ||
25 | Bu::ProtocolTelnet::ProtocolTelnet() : | 60 | Bu::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 | ||
35 | void Bu::ProtocolTelnet::onNewConnection( Bu::Client *pClient ) | 74 | void Bu::ProtocolTelnet::onNewConnection( Bu::Client *pClient ) |
36 | { | 75 | { |
76 | this->pClient = pClient; | ||
37 | } | 77 | } |
38 | 78 | ||
39 | void Bu::ProtocolTelnet::onNewData( Bu::Client *pClient ) | 79 | void 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 | |||
259 | void Bu::ProtocolTelnet::setCanonical( bool bCon ) | ||
260 | { | ||
261 | bCanonical = bCon; | ||
262 | } | ||
263 | |||
264 | bool Bu::ProtocolTelnet::isCanonical() | ||
265 | { | ||
266 | return bCanonical; | ||
267 | } | ||
268 | |||
269 | void Bu::ProtocolTelnet::write( const Bu::FString &sData ) | ||
270 | { | ||
271 | pClient->write( sData ); | ||
272 | } | ||
273 | |||
274 | void Bu::ProtocolTelnet::write( char *pData, int iSize ) | ||
275 | { | ||
276 | pClient->write( pData, iSize ); | ||
277 | } | ||
278 | |||
279 | void Bu::ProtocolTelnet::write( char cData ) | ||
280 | { | ||
281 | pClient->write( &cData, 1 ); | ||
282 | } | ||
283 | |||
284 | void 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 | |||
309 | void 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 | |||
326 | void 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 | |||
351 | void 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 | ||
368 | void 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 | |||
391 | void 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 | ||
434 | void 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 | |||
459 | void 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 | ||
45 | Bu::ProtocolTelnet::Option::Option( Bu::ProtocolTelnet &rPT, char cCode ) : | 481 | Bu::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 | ||
102 | void Bu::ProtocolTelnet::Option::remoteSet( bool bSet ) | 550 | void 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 | ||
7 | namespace Bu | 10 | namespace 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 | |||
5 | class MyTelnet : public Bu::ProtocolTelnet | ||
6 | { | ||
7 | public: | ||
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 | |||
38 | private: | ||
39 | |||
40 | }; | ||
41 | |||
42 | class TelServer : public Bu::Server | ||
43 | { | ||
44 | public: | ||
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 | |||
67 | private: | ||
68 | |||
69 | }; | ||
70 | |||
71 | int 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 | |||