- Fix problems with stuck telnet sessions. Thanks to Øyvind Harboe for this patch.
[openocd.git] / src / server / telnet_server.c
1 /***************************************************************************
2 * Copyright (C) 2005 by Dominic Rath *
3 * Dominic.Rath@gmx.de *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "replacements.h"
25
26 #include "telnet_server.h"
27
28 #include "server.h"
29 #include "log.h"
30 #include "command.h"
31 #include "target.h"
32 #include "target_request.h"
33
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <errno.h>
37 #include <string.h>
38 #include <ctype.h>
39
40 static unsigned short telnet_port = 0;
41
42 int handle_exit_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc);
43 int handle_telnet_port_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc);
44
45 static char *negotiate =
46 "\xFF\xFB\x03" /* IAC WILL Suppress Go Ahead */
47 "\xFF\xFB\x01" /* IAC WILL Echo */
48 "\xFF\xFD\x03" /* IAC DO Suppress Go Ahead */
49 "\xFF\xFE\x01"; /* IAC DON'T Echo */
50
51 #define CTRL(c) (c - '@')
52
53
54 /* The only way we can detect that the socket is closed is the first time
55 * we write to it, we will fail. Subsequent write operations will
56 * succeed. Shudder!
57 */
58 int telnet_write(connection_t *connection, void *data, int len)
59 {
60 telnet_connection_t *t_con = connection->priv;
61 if (t_con->closed)
62 return ERROR_SERVER_REMOTE_CLOSED;
63
64 if (write_socket(connection->fd, data, len)==len)
65 {
66 return ERROR_OK;
67 }
68 t_con->closed=1;
69 return ERROR_SERVER_REMOTE_CLOSED;
70 }
71
72
73 int telnet_prompt(connection_t *connection)
74 {
75 telnet_connection_t *t_con = connection->priv;
76
77 return telnet_write(connection, t_con->prompt, strlen(t_con->prompt));
78 }
79
80 int telnet_outputline(connection_t *connection, char* line)
81 {
82 telnet_write(connection, line, strlen(line));
83 return telnet_write(connection, "\r\n\0", 3);
84 }
85
86
87 int telnet_output(struct command_context_s *cmd_ctx, char* line)
88 {
89 connection_t *connection = cmd_ctx->output_handler_priv;
90
91 return telnet_outputline(connection, line);
92 }
93
94 void telnet_log_callback(void *priv, const char *file, int line,
95 const char *function, const char *format, va_list args)
96 {
97 connection_t *connection = priv;
98 char *t=allocPrintf(format, args);
99 if (t==NULL)
100 return;
101
102 telnet_outputline(connection, t);
103
104 free(t);
105 }
106
107
108 int telnet_target_callback_event_handler(struct target_s *target, enum target_event event, void *priv)
109 {
110 struct command_context_s *cmd_ctx = priv;
111 char buffer[512];
112
113 switch (event)
114 {
115 case TARGET_EVENT_HALTED:
116 command_print(cmd_ctx, "Target %i halted", get_num_by_target(target));
117 target->type->arch_state(target, buffer, 512);
118 buffer[511] = 0;
119 command_print(cmd_ctx, "%s", buffer);
120 break;
121 case TARGET_EVENT_RESUMED:
122 command_print(cmd_ctx, "Target %i resumed", get_num_by_target(target));
123 break;
124 default:
125 break;
126 }
127
128 return ERROR_OK;
129 }
130
131 int telnet_new_connection(connection_t *connection)
132 {
133 telnet_connection_t *telnet_connection = malloc(sizeof(telnet_connection_t));
134 telnet_service_t *telnet_service = connection->service->priv;
135 int i;
136
137 connection->priv = telnet_connection;
138
139 /* initialize telnet connection information */
140 telnet_connection->closed = 0;
141 telnet_connection->line_size = 0;
142 telnet_connection->line_cursor = 0;
143 telnet_connection->option_size = 0;
144 telnet_connection->prompt = strdup("> ");
145 telnet_connection->state = TELNET_STATE_DATA;
146
147 /* output goes through telnet connection */
148 command_set_output_handler(connection->cmd_ctx, telnet_output, connection);
149
150 /* negotiate telnet options */
151 telnet_write(connection, negotiate, strlen(negotiate));
152
153 /* print connection banner */
154 if (telnet_service->banner)
155 {
156 telnet_write(connection, telnet_service->banner, strlen(telnet_service->banner));
157 telnet_write(connection, "\r\n\0", 3);
158 }
159
160 telnet_prompt(connection);
161
162 /* initialize history */
163 for (i = 0; i < TELNET_LINE_HISTORY_SIZE; i++)
164 {
165 telnet_connection->history[i] = NULL;
166 }
167 telnet_connection->next_history = 0;
168 telnet_connection->current_history = 0;
169
170 target_register_event_callback(telnet_target_callback_event_handler, connection->cmd_ctx);
171
172 return ERROR_OK;
173 }
174
175 void telnet_clear_line(connection_t *connection, telnet_connection_t *t_con)
176 {
177 /* move to end of line */
178 if (t_con->line_cursor < t_con->line_size)
179 {
180 telnet_write(connection, t_con->line + t_con->line_cursor, t_con->line_size - t_con->line_cursor);
181 }
182
183 /* backspace, overwrite with space, backspace */
184 while (t_con->line_size > 0)
185 {
186 telnet_write(connection, "\b \b", 3);
187 t_con->line_size--;
188 }
189 t_con->line_cursor = 0;
190 }
191
192 int telnet_input(connection_t *connection)
193 {
194 int bytes_read;
195 char buffer[TELNET_BUFFER_SIZE];
196 char *buf_p;
197 telnet_connection_t *t_con = connection->priv;
198 command_context_t *command_context = connection->cmd_ctx;
199
200 bytes_read = read_socket(connection->fd, buffer, TELNET_BUFFER_SIZE);
201
202 if (bytes_read == 0)
203 return ERROR_SERVER_REMOTE_CLOSED;
204 else if (bytes_read == -1)
205 {
206 ERROR("error during read: %s", strerror(errno));
207 return ERROR_SERVER_REMOTE_CLOSED;
208 }
209
210 buf_p = buffer;
211 while (bytes_read)
212 {
213 switch (t_con->state)
214 {
215 case TELNET_STATE_DATA:
216 if (*buf_p == '\xff')
217 {
218 t_con->state = TELNET_STATE_IAC;
219 }
220 else
221 {
222 if (isprint(*buf_p)) /* printable character */
223 {
224 telnet_write(connection, buf_p, 1);
225 if (t_con->line_cursor == t_con->line_size)
226 {
227 t_con->line[t_con->line_size++] = *buf_p;
228 t_con->line_cursor++;
229 }
230 else
231 {
232 int i;
233 memmove(t_con->line + t_con->line_cursor + 1, t_con->line + t_con->line_cursor, t_con->line_size - t_con->line_cursor);
234 t_con->line[t_con->line_cursor++] = *buf_p;
235 t_con->line_size++;
236 telnet_write(connection, t_con->line + t_con->line_cursor, t_con->line_size - t_con->line_cursor);
237 for (i = t_con->line_cursor; i < t_con->line_size; i++)
238 {
239 telnet_write(connection, "\b", 1);
240 }
241 }
242 }
243 else /* non-printable */
244 {
245 if (*buf_p == 0x1b) /* escape */
246 {
247 t_con->state = TELNET_STATE_ESCAPE;
248 t_con->last_escape = '\x00';
249 }
250 else if ((*buf_p == 0xd) || (*buf_p == 0xa)) /* CR/LF */
251 {
252 int retval;
253
254 /* skip over combinations with CR/LF + NUL */
255 if (((*(buf_p + 1) == 0xa) || (*(buf_p + 1) == 0xd)) && (bytes_read > 1))
256 {
257 buf_p++;
258 bytes_read--;
259 }
260 if ((*(buf_p + 1) == 0) && (bytes_read > 1))
261 {
262 buf_p++;
263 bytes_read--;
264 }
265 t_con->line[t_con->line_size] = 0;
266
267 telnet_write(connection, "\r\n\x00", 3);
268
269 if (strcmp(t_con->line, "history") == 0)
270 {
271 int i;
272 for (i = 0; i < TELNET_LINE_HISTORY_SIZE; i++)
273 {
274 if (t_con->history[i])
275 {
276 telnet_write(connection, t_con->history[i], strlen(t_con->history[i]));
277 telnet_write(connection, "\r\n\x00", 3);
278 }
279 }
280 telnet_prompt(connection);
281 t_con->line_size = 0;
282 t_con->line_cursor = 0;
283 continue;
284 }
285
286 log_setCallback(telnet_log_callback, connection);
287
288 if ((retval = command_run_line(command_context, t_con->line)) != ERROR_OK)
289 {
290 if (retval == ERROR_COMMAND_CLOSE_CONNECTION)
291 {
292 return ERROR_SERVER_REMOTE_CLOSED;
293 }
294 }
295
296 /* Save only non-blank lines in the history */
297 if (t_con->line_size > 0)
298 {
299 /* if the history slot is already taken, free it */
300 if (t_con->history[t_con->next_history])
301 {
302 free(t_con->history[t_con->next_history]);
303 }
304
305 /* add line to history */
306 t_con->history[t_con->next_history] = strdup(t_con->line);
307
308 /* wrap history at TELNET_LINE_HISTORY_SIZE */
309 t_con->next_history = (t_con->next_history + 1) % TELNET_LINE_HISTORY_SIZE;
310
311 /* current history line starts at the new entry */
312 t_con->current_history = t_con->next_history;
313
314 if (t_con->history[t_con->current_history])
315 {
316 free(t_con->history[t_con->current_history]);
317 }
318 t_con->history[t_con->current_history] = strdup("");
319 }
320
321 int t=telnet_prompt(connection);
322 if (t==ERROR_SERVER_REMOTE_CLOSED)
323 return t;
324
325 t_con->line_size = 0;
326 t_con->line_cursor = 0;
327 }
328 else if ((*buf_p == 0x7f) || (*buf_p == 0x8)) /* delete character */
329 {
330 if (t_con->line_cursor > 0)
331 {
332 if (t_con->line_cursor != t_con->line_size)
333 {
334 int i;
335 telnet_write(connection, "\b", 1);
336 t_con->line_cursor--;
337 t_con->line_size--;
338 memmove(t_con->line + t_con->line_cursor, t_con->line + t_con->line_cursor + 1, t_con->line_size - t_con->line_cursor);
339
340 telnet_write(connection, t_con->line + t_con->line_cursor, t_con->line_size - t_con->line_cursor);
341 telnet_write(connection, " \b", 2);
342 for (i = t_con->line_cursor; i < t_con->line_size; i++)
343 {
344 telnet_write(connection, "\b", 1);
345 }
346 }
347 else
348 {
349 t_con->line_size--;
350 t_con->line_cursor--;
351 /* back space: move the 'printer' head one char back, overwrite with space, move back again */
352 telnet_write(connection, "\b \b", 3);
353 }
354 }
355 }
356 else if (*buf_p == 0x15) /* clear line */
357 {
358 telnet_clear_line(connection, t_con);
359 }
360 else if (*buf_p == CTRL('B')) /* cursor left */
361 {
362 if (t_con->line_cursor > 0)
363 {
364 telnet_write(connection, "\b", 1);
365 t_con->line_cursor--;
366 }
367 t_con->state = TELNET_STATE_DATA;
368 }
369 else if (*buf_p == CTRL('F')) /* cursor right */
370 {
371 if (t_con->line_cursor < t_con->line_size)
372 {
373 telnet_write(connection, t_con->line + t_con->line_cursor++, 1);
374 }
375 t_con->state = TELNET_STATE_DATA;
376 }
377 else
378 {
379 DEBUG("unhandled nonprintable: %2.2x", *buf_p);
380 }
381 }
382 }
383 break;
384 case TELNET_STATE_IAC:
385 switch (*buf_p)
386 {
387 case '\xfe':
388 t_con->state = TELNET_STATE_DONT;
389 break;
390 case '\xfd':
391 t_con->state = TELNET_STATE_DO;
392 break;
393 case '\xfc':
394 t_con->state = TELNET_STATE_WONT;
395 break;
396 case '\xfb':
397 t_con->state = TELNET_STATE_WILL;
398 break;
399 }
400 break;
401 case TELNET_STATE_SB:
402 break;
403 case TELNET_STATE_SE:
404 break;
405 case TELNET_STATE_WILL:
406 case TELNET_STATE_WONT:
407 case TELNET_STATE_DO:
408 case TELNET_STATE_DONT:
409 t_con->state = TELNET_STATE_DATA;
410 break;
411 case TELNET_STATE_ESCAPE:
412 if (t_con->last_escape == '[')
413 {
414 if (*buf_p == 'D') /* cursor left */
415 {
416 if (t_con->line_cursor > 0)
417 {
418 telnet_write(connection, "\b", 1);
419 t_con->line_cursor--;
420 }
421 t_con->state = TELNET_STATE_DATA;
422 }
423 else if (*buf_p == 'C') /* cursor right */
424 {
425 if (t_con->line_cursor < t_con->line_size)
426 {
427 telnet_write(connection, t_con->line + t_con->line_cursor++, 1);
428 }
429 t_con->state = TELNET_STATE_DATA;
430 }
431 else if (*buf_p == 'A') /* cursor up */
432 {
433 int last_history = (t_con->current_history > 0) ? t_con->current_history - 1 : TELNET_LINE_HISTORY_SIZE-1;
434 if (t_con->history[last_history])
435 {
436 telnet_clear_line(connection, t_con);
437 t_con->line_size = strlen(t_con->history[last_history]);
438 t_con->line_cursor = t_con->line_size;
439 memcpy(t_con->line, t_con->history[last_history], t_con->line_size + 1);
440 telnet_write(connection, t_con->line, t_con->line_size);
441 t_con->current_history = last_history;
442 }
443 t_con->state = TELNET_STATE_DATA;
444 }
445 else if (*buf_p == 'B') /* cursor down */
446 {
447 int next_history = (t_con->current_history + 1) % TELNET_LINE_HISTORY_SIZE;
448 if (t_con->history[next_history])
449 {
450 telnet_clear_line(connection, t_con);
451 t_con->line_size = strlen(t_con->history[next_history]);
452 t_con->line_cursor = t_con->line_size;
453 memcpy(t_con->line, t_con->history[next_history], t_con->line_size + 1);
454 telnet_write(connection, t_con->line, t_con->line_size);
455 t_con->current_history = next_history;
456 }
457 t_con->state = TELNET_STATE_DATA;
458 }
459 else if (*buf_p == '3')
460 {
461 t_con->last_escape = *buf_p;
462 }
463 else
464 {
465 t_con->state = TELNET_STATE_DATA;
466 }
467 }
468 else if (t_con->last_escape == '3')
469 {
470 /* Remove character */
471 if (*buf_p == '~')
472 {
473 if (t_con->line_cursor < t_con->line_size)
474 {
475 int i;
476 t_con->line_size--;
477 /* remove char from line buffer */
478 memmove(t_con->line + t_con->line_cursor, t_con->line + t_con->line_cursor + 1, t_con->line_size - t_con->line_cursor);
479
480 /* print remainder of buffer */
481 telnet_write(connection, t_con->line + t_con->line_cursor, t_con->line_size - t_con->line_cursor);
482 /* overwrite last char with whitespace */
483 telnet_write(connection, " \b", 2);
484
485 /* move back to cursor position*/
486 for (i = t_con->line_cursor; i < t_con->line_size; i++)
487 {
488 telnet_write(connection, "\b", 1);
489 }
490 }
491
492 t_con->state = TELNET_STATE_DATA;
493 }
494 else
495 {
496 t_con->state = TELNET_STATE_DATA;
497 }
498 }
499 else if (t_con->last_escape == '\x00')
500 {
501 if (*buf_p == '[')
502 {
503 t_con->last_escape = *buf_p;
504 }
505 else
506 {
507 t_con->state = TELNET_STATE_DATA;
508 }
509 }
510 else
511 {
512 ERROR("BUG: unexpected value in t_con->last_escape");
513 t_con->state = TELNET_STATE_DATA;
514 }
515
516 break;
517 default:
518 ERROR("unknown telnet state");
519 exit(-1);
520 }
521
522 bytes_read--;
523 buf_p++;
524 }
525
526 return ERROR_OK;
527 }
528
529 int telnet_connection_closed(connection_t *connection)
530 {
531 telnet_connection_t *t_con = connection->priv;
532 int i;
533
534 if (t_con->prompt)
535 {
536 free(t_con->prompt);
537 t_con->prompt = NULL;
538 }
539
540 for (i = 0; i < TELNET_LINE_HISTORY_SIZE; i++)
541 {
542 if (t_con->history[i])
543 {
544 free(t_con->history[i]);
545 t_con->history[i] = NULL;
546 }
547 }
548
549 /* if this connection registered a debug-message receiver delete it */
550 delete_debug_msg_receiver(connection->cmd_ctx, NULL);
551
552 if (connection->priv)
553 {
554 free(connection->priv);
555 connection->priv = NULL;
556 }
557 else
558 {
559 ERROR("BUG: connection->priv == NULL");
560 }
561
562 target_unregister_event_callback(telnet_target_callback_event_handler, connection->cmd_ctx);
563
564 return ERROR_OK;
565 }
566
567 int telnet_set_prompt(connection_t *connection, char *prompt)
568 {
569 telnet_connection_t *t_con = connection->priv;
570
571 t_con->prompt = strdup(prompt);
572
573 return ERROR_OK;
574 }
575
576 int telnet_init(char *banner)
577 {
578 telnet_service_t *telnet_service = malloc(sizeof(telnet_service_t));
579
580 if (telnet_port == 0)
581 {
582 WARNING("no telnet port specified, using default port 4444");
583 telnet_port = 4444;
584 }
585
586 telnet_service->banner = banner;
587
588 add_service("telnet", CONNECTION_TELNET, telnet_port, 1, telnet_new_connection, telnet_input, telnet_connection_closed, telnet_service);
589
590 return ERROR_OK;
591 }
592
593 int telnet_register_commands(command_context_t *command_context)
594 {
595 register_command(command_context, NULL, "exit", handle_exit_command,
596 COMMAND_EXEC, "exit telnet session");
597
598 register_command(command_context, NULL, "telnet_port", handle_telnet_port_command,
599 COMMAND_CONFIG, "");
600
601 return ERROR_OK;
602 }
603
604 /* daemon configuration command telnet_port */
605 int handle_telnet_port_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc)
606 {
607 if (argc == 0)
608 return ERROR_OK;
609
610 /* only if the port wasn't overwritten by cmdline */
611 if (telnet_port == 0)
612 telnet_port = strtoul(args[0], NULL, 0);
613
614 return ERROR_OK;
615 }
616
617 int handle_exit_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc)
618 {
619 return ERROR_COMMAND_CLOSE_CONNECTION;
620 }

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)