Commit | Line | Data |
---|---|---|
d4ee6ee2 JM |
1 | /* |
2 | * Copyright (c) 2003, Adam Dunkels. | |
3 | * All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: | |
8 | * 1. Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * 2. Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * 3. The name of the author may not be used to endorse or promote | |
14 | * products derived from this software without specific prior | |
15 | * written permission. | |
16 | * | |
17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS | |
18 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY | |
21 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE | |
23 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |
25 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
28 | * | |
29 | * This file is part of the uIP TCP/IP stack | |
30 | * | |
31 | * $Id: telnetd.c,v 1.2 2006/06/07 09:43:54 adam Exp $ | |
32 | * | |
33 | */ | |
34 | ||
35 | #include "uip.h" | |
36 | #include "telnetd.h" | |
37 | #include "shell.h" | |
38 | ||
39 | #include <string.h> | |
40 | #include <stdlib.h> | |
41 | #include <stdio.h> | |
42 | ||
43 | #define ISO_nl 0x0a | |
44 | #define ISO_cr 0x0d | |
45 | ||
46 | #define STATE_NORMAL 0 | |
47 | #define STATE_IAC 1 | |
48 | #define STATE_WILL 2 | |
49 | #define STATE_WONT 3 | |
50 | #define STATE_DO 4 | |
51 | #define STATE_DONT 5 | |
52 | #define STATE_CLOSE 6 | |
53 | ||
54 | #define TELNET_IAC 255 | |
55 | #define TELNET_WILL 251 | |
56 | #define TELNET_WONT 252 | |
57 | #define TELNET_DO 253 | |
58 | #define TELNET_DONT 254 | |
59 | ||
60 | #define TELNET_LINEMODE 0x22 | |
61 | #define TELNET_GA 0x03 | |
62 | #define TELNET_X_PROMPT 0x55 | |
63 | ||
75eae806 JM |
64 | #define DEBUG_PRINTF(...) |
65 | //#define DEBUG_PRINTF printf | |
d4ee6ee2 JM |
66 | |
67 | static char *alloc_line(int size) | |
68 | { | |
69 | return (char *)malloc(size); | |
70 | } | |
71 | ||
72 | static void dealloc_line(char *line) | |
73 | { | |
74 | free(line); | |
75 | } | |
76 | ||
77 | void Telnetd::close() | |
78 | { | |
79 | state = STATE_CLOSE; | |
80 | } | |
81 | ||
82 | int Telnetd::sendline(char *line) | |
83 | { | |
84 | int i; | |
85 | for (i = 0; i < TELNETD_CONF_NUMLINES; ++i) { | |
86 | if (lines[i] == NULL) { | |
87 | lines[i] = line; | |
88 | return i; | |
89 | } | |
90 | } | |
91 | if (i == TELNETD_CONF_NUMLINES) { | |
92 | dealloc_line(line); | |
93 | } | |
94 | return TELNETD_CONF_NUMLINES; | |
95 | } | |
96 | ||
97 | void Telnetd::output_prompt(const char *str) | |
98 | { | |
99 | if(prompt) output(str); | |
100 | } | |
101 | ||
102 | int Telnetd::output(const char *str) | |
103 | { | |
104 | if(state == STATE_CLOSE) return -1; | |
105 | ||
106 | unsigned chunk = 256; // small chunk size so we don't allocate huge blocks, and must be less than mss | |
107 | unsigned len = strlen(str); | |
108 | char *line; | |
109 | if (len < chunk) { | |
110 | // can be sent in one tcp buffer | |
111 | line = alloc_line(len + 1); | |
112 | if (line != NULL) { | |
113 | strcpy(line, str); | |
114 | return sendline(line); | |
115 | }else{ | |
116 | // out of memory treat like full | |
117 | return TELNETD_CONF_NUMLINES; | |
118 | } | |
119 | } else { | |
120 | // need to split line over multiple send lines | |
121 | int size = chunk; // size to copy | |
122 | int off = 0; | |
123 | int n= 0; | |
124 | while (len >= chunk) { | |
125 | line = alloc_line(chunk + 1); | |
126 | if (line != NULL) { | |
127 | memcpy(line, str + off, size); | |
128 | line[size] = 0; | |
129 | n= sendline(line); | |
130 | len -= size; | |
131 | off += size; | |
132 | }else{ | |
133 | // out of memory treat like full | |
134 | return TELNETD_CONF_NUMLINES; | |
135 | } | |
136 | } | |
137 | if (len > 0) { | |
138 | // send rest | |
139 | line = alloc_line(len + 1); | |
140 | if (line != NULL) { | |
141 | strcpy(line, str + off); | |
142 | n= sendline(line); | |
143 | }else{ | |
144 | // out of memory treat like full | |
145 | return TELNETD_CONF_NUMLINES; | |
146 | } | |
147 | } | |
148 | return n; | |
149 | } | |
150 | } | |
151 | ||
152 | // check if we can queue or if queue is full | |
153 | int Telnetd::can_output() | |
154 | { | |
155 | if(state == STATE_CLOSE) return -1; | |
156 | ||
157 | int i; | |
158 | int cnt = 0; | |
159 | for (i = 0; i < TELNETD_CONF_NUMLINES; ++i) { | |
160 | if (lines[i] == NULL) cnt++; | |
161 | } | |
162 | return cnt < 4 ? 0 : 1; | |
163 | } | |
164 | ||
165 | void Telnetd::acked(void) | |
166 | { | |
167 | while (numsent > 0) { | |
168 | dealloc_line(lines[0]); | |
169 | for (int i = 1; i < TELNETD_CONF_NUMLINES; ++i) { | |
170 | lines[i - 1] = lines[i]; | |
171 | } | |
172 | lines[TELNETD_CONF_NUMLINES - 1] = NULL; | |
173 | --numsent; | |
174 | } | |
175 | } | |
176 | ||
177 | void Telnetd::senddata(void) | |
178 | { | |
179 | // NOTE this sends as many lines as it can fit in one tcp frame | |
180 | // we need to keep the lines under the size of the tcp frame | |
181 | char *bufptr, *lineptr; | |
182 | int buflen, linelen; | |
183 | ||
184 | bufptr = (char *)uip_appdata; | |
185 | buflen = 0; | |
186 | for (numsent = 0; numsent < TELNETD_CONF_NUMLINES && lines[numsent] != NULL ; ++numsent) { | |
187 | lineptr = lines[numsent]; | |
188 | linelen = strlen(lineptr); | |
189 | if (buflen + linelen < uip_mss()) { | |
190 | memcpy(bufptr, lineptr, linelen); | |
191 | bufptr += linelen; | |
192 | buflen += linelen; | |
193 | } else { | |
194 | break; | |
195 | } | |
196 | } | |
197 | uip_send(uip_appdata, buflen); | |
198 | } | |
199 | ||
200 | void Telnetd::get_char(u8_t c) | |
201 | { | |
202 | if (c == ISO_cr) { | |
203 | return; | |
204 | } | |
205 | ||
206 | buf[(int)bufptr] = c; | |
207 | if (buf[(int)bufptr] == ISO_nl || bufptr == sizeof(buf) - 1) { | |
208 | if (bufptr > 0) { | |
209 | buf[(int)bufptr] = 0; | |
210 | } | |
211 | shell->input(buf); | |
212 | bufptr = 0; | |
213 | ||
214 | } else { | |
215 | ++bufptr; | |
216 | } | |
217 | } | |
218 | ||
219 | // static void sendopt(u8_t option, u8_t value) | |
220 | // { | |
221 | // char *line; | |
222 | // line = alloc_line(4); | |
223 | // if (line != NULL) { | |
224 | // line[0] = TELNET_IAC; | |
225 | // line[1] = option; | |
226 | // line[2] = value; | |
227 | // line[3] = 0; | |
228 | // sendline(line); | |
229 | // } | |
230 | // } | |
231 | ||
232 | void Telnetd::newdata(void) | |
233 | { | |
234 | u16_t len; | |
235 | u8_t c; | |
236 | char *dataptr; | |
237 | ||
238 | len = uip_datalen(); | |
239 | dataptr = (char *)uip_appdata; | |
240 | ||
241 | while (len > 0 && bufptr < sizeof(buf)) { | |
242 | c = *dataptr; | |
243 | ++dataptr; | |
244 | --len; | |
245 | switch (state) { | |
246 | case STATE_IAC: | |
247 | if (c == TELNET_IAC) { | |
248 | get_char(c); | |
249 | state = STATE_NORMAL; | |
250 | } else { | |
251 | switch (c) { | |
252 | case TELNET_WILL: | |
253 | state = STATE_WILL; | |
254 | break; | |
255 | case TELNET_WONT: | |
256 | state = STATE_WONT; | |
257 | break; | |
258 | case TELNET_DO: | |
259 | state = STATE_DO; | |
260 | break; | |
261 | case TELNET_DONT: | |
262 | state = STATE_DONT; | |
263 | break; | |
264 | default: | |
265 | state = STATE_NORMAL; | |
266 | break; | |
267 | } | |
268 | } | |
269 | break; | |
270 | case STATE_WILL: | |
271 | if (c == TELNET_LINEMODE) { | |
272 | //sendopt(TELNET_DO, c); | |
273 | } | |
274 | state = STATE_NORMAL; | |
275 | break; | |
276 | ||
277 | case STATE_WONT: | |
278 | /* Reply with a DONT */ | |
279 | //sendopt(TELNET_DONT, c); | |
280 | state = STATE_NORMAL; | |
281 | break; | |
282 | case STATE_DO: | |
283 | if (c == TELNET_X_PROMPT) { | |
284 | prompt= true; | |
285 | }else if (c == TELNET_GA) { | |
286 | // enable prompt if telnet client running | |
287 | prompt= true; | |
d4ee6ee2 JM |
288 | }else{ |
289 | /* Reply with a WONT */ | |
290 | //sendopt(TELNET_WONT, c); | |
291 | } | |
292 | state = STATE_NORMAL; | |
293 | break; | |
294 | case STATE_DONT: | |
295 | if (c == TELNET_X_PROMPT) { | |
296 | prompt= false; | |
297 | }else{ | |
298 | /* Reply with a WONT */ | |
299 | //sendopt(TELNET_WONT, c); | |
300 | } | |
301 | state = STATE_NORMAL; | |
302 | break; | |
303 | case STATE_NORMAL: | |
304 | if (c == TELNET_IAC) { | |
305 | state = STATE_IAC; | |
306 | } else { | |
307 | get_char(c); | |
308 | } | |
309 | break; | |
310 | } | |
311 | } | |
312 | ||
313 | // if the command queue is getting too big we stop TCP | |
314 | if(shell->queue_size() > 20) { | |
315 | DEBUG_PRINTF("Telnet: stopped: %d\n", shell->queue_size()); | |
316 | uip_stop(); | |
317 | } | |
318 | } | |
319 | ||
320 | void Telnetd::poll() | |
321 | { | |
322 | if(first_time) { | |
323 | first_time= false; | |
324 | shell->start(); | |
325 | senddata(); | |
326 | } | |
327 | } | |
328 | ||
329 | Telnetd::Telnetd() | |
330 | { | |
331 | DEBUG_PRINTF("Telnetd: ctor %p\n", this); | |
332 | for (int i = 0; i < TELNETD_CONF_NUMLINES; ++i) { | |
333 | lines[i] = NULL; | |
334 | } | |
335 | ||
336 | first_time= true; | |
337 | bufptr = 0; | |
338 | state = STATE_NORMAL; | |
339 | prompt= false; | |
340 | shell= new Shell(this); | |
341 | } | |
342 | ||
343 | Telnetd::~Telnetd() | |
344 | { | |
345 | DEBUG_PRINTF("Telnetd: dtor %p\n", this); | |
346 | for (int i = 0; i < TELNETD_CONF_NUMLINES; ++i) { | |
347 | if (lines[i] != NULL) dealloc_line(lines[i]); | |
348 | } | |
349 | delete shell; | |
350 | } | |
351 | ||
352 | // static | |
353 | void Telnetd::appcall(void) | |
354 | { | |
355 | Telnetd *instance= reinterpret_cast<Telnetd *>(uip_conn->appstate); | |
356 | ||
357 | if (uip_connected()) { | |
358 | // create a new telnet class instance | |
359 | instance= new Telnetd; | |
360 | DEBUG_PRINTF("Telnetd new instance: %p\n", instance); | |
361 | uip_conn->appstate= instance; // and store it in the appstate of the connection | |
362 | instance->rport= uip_conn->rport; | |
363 | } | |
364 | ||
365 | if (uip_closed() || uip_aborted() || uip_timedout()) { | |
366 | DEBUG_PRINTF("Telnetd: closed: %p\n", instance); | |
367 | if(instance != NULL) { | |
368 | delete instance; | |
369 | uip_conn->appstate= NULL; | |
370 | } | |
371 | return; | |
372 | } | |
373 | ||
374 | // sanity check | |
375 | if(instance == NULL || instance->rport != uip_conn->rport) { | |
376 | DEBUG_PRINTF("Telnetd: ERROR Null instance or rport is wrong: %p - %u, %d\n", instance, HTONS(uip_conn->rport), uip_flags); | |
377 | uip_abort(); | |
378 | return; | |
379 | } | |
380 | ||
381 | if (instance->state == STATE_CLOSE) { | |
382 | uip_close(); | |
383 | } | |
384 | ||
385 | ||
386 | if (uip_acked()) { | |
387 | instance->acked(); | |
388 | } | |
389 | ||
390 | if (uip_newdata()) { | |
391 | instance->newdata(); | |
392 | } | |
393 | ||
394 | if (uip_rexmit() || uip_newdata() || uip_acked() || uip_connected() || uip_poll()) { | |
395 | instance->senddata(); | |
396 | } | |
397 | ||
398 | if(uip_poll() && uip_stopped(uip_conn) && instance->shell->queue_size() < 5) { | |
399 | DEBUG_PRINTF("restarted %d - %p\n", instance->shell->queue_size(), instance); | |
400 | uip_restart(); | |
401 | } | |
402 | ||
403 | if(uip_poll()) { | |
404 | instance->poll(); | |
405 | } | |
406 | } | |
407 | ||
408 | // static | |
409 | void Telnetd::init(void) | |
410 | { | |
411 | uip_listen(HTONS(23)); | |
412 | } |