Pavel Chromy: hopefully perfection for async output and prompt/partially typed comman...
[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 /* The only way we can detect that the socket is closed is the first time
54 * we write to it, we will fail. Subsequent write operations will
55 * succeed. Shudder!
56 */
57 int telnet_write(connection_t *connection, const void *data, int len)
58 {
59 telnet_connection_t *t_con = connection->priv;
60 if (t_con->closed)
61 return ERROR_SERVER_REMOTE_CLOSED;
62
63 if (write_socket(connection->fd, data, len) == len)
64 {
65 return ERROR_OK;
66 }
67 t_con->closed = 1;
68 return ERROR_SERVER_REMOTE_CLOSED;
69 }
70
71 int telnet_prompt(connection_t *connection)
72 {
73 telnet_connection_t *t_con = connection->priv;
74
75 telnet_write(connection, "\r", 1); /* the prompt is always placed at the line beginning */
76 return telnet_write(connection, t_con->prompt, strlen(t_con->prompt));
77 }
78
79 int telnet_outputline(connection_t *connection, const char *line)
80 {
81 int len;
82
83 /* process lines in buffer */
84 while (*line) {
85 char *line_end = strchr(line, '\n');
86
87 if (line_end)
88 len = line_end-line;
89 else
90 len = strlen(line);
91
92 telnet_write(connection, line, len);
93 if (line_end)
94 {
95 telnet_write(connection, "\r\n\0", 3);
96 line += len+1;
97 }
98 else
99 {
100 line += len;
101 }
102 }
103
104 return ERROR_OK;
105 }
106
107 int telnet_output(struct command_context_s *cmd_ctx, char* line)
108 {
109 connection_t *connection = cmd_ctx->output_handler_priv;
110
111 return telnet_outputline(connection, line);
112 }
113
114 void telnet_log_callback(void *priv, const char *file, int line,
115 const char *function, const char *string)
116 {
117 connection_t *connection = priv;
118 telnet_connection_t *t_con = connection->priv;
119 int i;
120
121 /* clear the command line */
122 telnet_write(connection, "\r", 1);
123 for (i = strlen(t_con->prompt) + t_con->line_size; i>0; i-=16)
124 telnet_write(connection, " ", i>16 ? 16 : i);
125
126 telnet_write(connection, "\r", 1);
127 telnet_outputline(connection, string);
128
129 /* put the command line to its previous state */
130 telnet_prompt(connection);
131 telnet_write(connection, t_con->line, t_con->line_size);
132 for (i=t_con->line_size; i>t_con->line_cursor; i--)
133 telnet_write(connection, "\b", 1);
134 }
135
136 int telnet_target_callback_event_handler(struct target_s *target, enum target_event event, void *priv)
137 {
138 switch (event)
139 {
140 case TARGET_EVENT_HALTED:
141 target_arch_state(target);
142 break;
143 default:
144 break;
145 }
146
147 return ERROR_OK;
148 }
149
150 int telnet_new_connection(connection_t *connection)
151 {
152 telnet_connection_t *telnet_connection = malloc(sizeof(telnet_connection_t));
153 telnet_service_t *telnet_service = connection->service->priv;
154 int i;
155
156 connection->priv = telnet_connection;
157
158 /* initialize telnet connection information */
159 telnet_connection->closed = 0;
160 telnet_connection->line_size = 0;
161 telnet_connection->line_cursor = 0;
162 telnet_connection->option_size = 0;
163 telnet_connection->prompt = strdup("> ");
164 telnet_connection->state = TELNET_STATE_DATA;
165
166 /* output goes through telnet connection */
167 command_set_output_handler(connection->cmd_ctx, telnet_output, connection);
168
169 /* negotiate telnet options */
170 telnet_write(connection, negotiate, strlen(negotiate));
171
172 /* print connection banner */
173 if (telnet_service->banner)
174 {
175 telnet_write(connection, telnet_service->banner, strlen(telnet_service->banner));
176 telnet_write(connection, "\r\n\0", 3);
177 }
178
179 telnet_prompt(connection);
180
181 /* initialize history */
182 for (i = 0; i < TELNET_LINE_HISTORY_SIZE; i++)
183 {
184 telnet_connection->history[i] = NULL;
185 }
186 telnet_connection->next_history = 0;
187 telnet_connection->current_history = 0;
188
189 target_register_event_callback(telnet_target_callback_event_handler, connection->cmd_ctx);
190
191 log_add_callback(telnet_log_callback, connection);
192
193
194
195 return ERROR_OK;
196 }
197
198 void telnet_clear_line(connection_t *connection, telnet_connection_t *t_con)
199 {
200 /* move to end of line */
201 if (t_con->line_cursor < t_con->line_size)
202 {
203 telnet_write(connection, t_con->line + t_con->line_cursor, t_con->line_size - t_con->line_cursor);
204 }
205
206 /* backspace, overwrite with space, backspace */
207 while (t_con->line_size > 0)
208 {
209 telnet_write(connection, "\b \b", 3);
210 t_con->line_size--;
211 }
212 t_con->line_cursor = 0;
213 }
214
215 int telnet_input(connection_t *connection)
216 {
217 int bytes_read;
218 char buffer[TELNET_BUFFER_SIZE];
219 char *buf_p;
220 telnet_connection_t *t_con = connection->priv;
221 command_context_t *command_context = connection->cmd_ctx;
222
223 bytes_read = read_socket(connection->fd, buffer, TELNET_BUFFER_SIZE);
224
225 if (bytes_read == 0)
226 return ERROR_SERVER_REMOTE_CLOSED;
227 else if (bytes_read == -1)
228 {
229 ERROR("error during read: %s", strerror(errno));
230 return ERROR_SERVER_REMOTE_CLOSED;
231 }
232
233 buf_p = buffer;
234 while (bytes_read)
235 {
236 switch (t_con->state)
237 {
238 case TELNET_STATE_DATA:
239 if (*buf_p == '\xff')
240 {
241 t_con->state = TELNET_STATE_IAC;
242 }
243 else
244 {
245 if (isprint(*buf_p)) /* printable character */
246 {
247 telnet_write(connection, buf_p, 1);
248 if (t_con->line_cursor == t_con->line_size)
249 {
250 t_con->line[t_con->line_size++] = *buf_p;
251 t_con->line_cursor++;
252 }
253 else
254 {
255 int i;
256 memmove(t_con->line + t_con->line_cursor + 1, t_con->line + t_con->line_cursor, t_con->line_size - t_con->line_cursor);
257 t_con->line[t_con->line_cursor++] = *buf_p;
258 t_con->line_size++;
259 telnet_write(connection, t_con->line + t_con->line_cursor, t_con->line_size - t_con->line_cursor);
260 for (i = t_con->line_cursor; i < t_con->line_size; i++)
261 {
262 telnet_write(connection, "\b", 1);
263 }
264 }
265 }
266 else /* non-printable */
267 {
268 if (*buf_p == 0x1b) /* escape */
269 {
270 t_con->state = TELNET_STATE_ESCAPE;
271 t_con->last_escape = '\x00';
272 }
273 else if ((*buf_p == 0xd) || (*buf_p == 0xa)) /* CR/LF */
274 {
275 int retval;
276
277 /* skip over combinations with CR/LF + NUL */
278 if (((*(buf_p + 1) == 0xa) || (*(buf_p + 1) == 0xd)) && (bytes_read > 1))
279 {
280 buf_p++;
281 bytes_read--;
282 }
283 if ((*(buf_p + 1) == 0) && (bytes_read > 1))
284 {
285 buf_p++;
286 bytes_read--;
287 }
288 t_con->line[t_con->line_size] = 0;
289
290 telnet_write(connection, "\r\n\x00", 3);
291
292 if (strcmp(t_con->line, "history") == 0)
293 {
294 int i;
295 for (i = 0; i < TELNET_LINE_HISTORY_SIZE; i++)
296 {
297 if (t_con->history[i])
298 {
299 telnet_write(connection, t_con->history[i], strlen(t_con->history[i]));
300 telnet_write(connection, "\r\n\x00", 3);
301 }
302 }
303 t_con->line_size = 0;
304 t_con->line_cursor = 0;
305 continue;
306 }
307
308 /* Save only non-blank lines in the history */
309 if (t_con->line_size > 0)
310 {
311 /* if the history slot is already taken, free it */
312 if (t_con->history[t_con->next_history])
313 {
314 free(t_con->history[t_con->next_history]);
315 }
316
317 /* add line to history */
318 t_con->history[t_con->next_history] = strdup(t_con->line);
319
320 /* wrap history at TELNET_LINE_HISTORY_SIZE */
321 t_con->next_history = (t_con->next_history + 1) % TELNET_LINE_HISTORY_SIZE;
322
323 /* current history line starts at the new entry */
324 t_con->current_history = t_con->next_history;
325
326 if (t_con->history[t_con->current_history])
327 {
328 free(t_con->history[t_con->current_history]);
329 }
330 t_con->history[t_con->current_history] = strdup("");
331 }
332
333 t_con->line_size = 0;
334 t_con->line_cursor = 0;
335
336 retval = command_run_line(command_context, t_con->line);
337 if (retval == ERROR_COMMAND_CLOSE_CONNECTION)
338 return ERROR_SERVER_REMOTE_CLOSED;
339
340 retval = telnet_prompt(connection);
341 if (retval == ERROR_SERVER_REMOTE_CLOSED)
342 return ERROR_SERVER_REMOTE_CLOSED;
343
344 }
345 else if ((*buf_p == 0x7f) || (*buf_p == 0x8)) /* delete character */
346 {
347 if (t_con->line_cursor > 0)
348 {
349 if (t_con->line_cursor != t_con->line_size)
350 {
351 int i;
352 telnet_write(connection, "\b", 1);
353 t_con->line_cursor--;
354 t_con->line_size--;
355 memmove(t_con->line + t_con->line_cursor, t_con->line + t_con->line_cursor + 1, t_con->line_size - t_con->line_cursor);
356
357 telnet_write(connection, t_con->line + t_con->line_cursor, t_con->line_size - t_con->line_cursor);
358 telnet_write(connection, " \b", 2);
359 for (i = t_con->line_cursor; i < t_con->line_size; i++)
360 {
361 telnet_write(connection, "\b", 1);
362 }
363 }
364 else
365 {
366 t_con->line_size--;
367 t_con->line_cursor--;
368 /* back space: move the 'printer' head one char back, overwrite with space, move back again */
369 telnet_write(connection, "\b \b", 3);
370 }
371 }
372 }
373 else if (*buf_p == 0x15) /* clear line */
374 {
375 telnet_clear_line(connection, t_con);
376 }
377 else if (*buf_p == CTRL('B')) /* cursor left */
378 {
379 if (t_con->line_cursor > 0)
380 {
381 telnet_write(connection, "\b", 1);
382 t_con->line_cursor--;
383 }
384 t_con->state = TELNET_STATE_DATA;
385 }
386 else if (*buf_p == CTRL('F')) /* cursor right */
387 {
388 if (t_con->line_cursor < t_con->line_size)
389 {
390 telnet_write(connection, t_con->line + t_con->line_cursor++, 1);
391 }
392 t_con->state = TELNET_STATE_DATA;
393 }
394 else
395 {
396 DEBUG("unhandled nonprintable: %2.2x", *buf_p);
397 }
398 }
399 }
400 break;
401 case TELNET_STATE_IAC:
402 switch (*buf_p)
403 {
404 case '\xfe':
405 t_con->state = TELNET_STATE_DONT;
406 break;
407 case '\xfd':
408 t_con->state = TELNET_STATE_DO;
409 break;
410 case '\xfc':
411 t_con->state = TELNET_STATE_WONT;
412 break;
413 case '\xfb':
414 t_con->state = TELNET_STATE_WILL;
415 break;
416 }
417 break;
418 case TELNET_STATE_SB:
419 break;
420 case TELNET_STATE_SE:
421 break;
422 case TELNET_STATE_WILL:
423 case TELNET_STATE_WONT:
424 case TELNET_STATE_DO:
425 case TELNET_STATE_DONT:
426 t_con->state = TELNET_STATE_DATA;
427 break;
428 case TELNET_STATE_ESCAPE:
429 if (t_con->last_escape == '[')
430 {
431 if (*buf_p == 'D') /* cursor left */
432 {
433 if (t_con->line_cursor > 0)
434 {
435 telnet_write(connection, "\b", 1);
436 t_con->line_cursor--;
437 }
438 t_con->state = TELNET_STATE_DATA;
439 }
440 else if (*buf_p == 'C') /* cursor right */
441 {
442 if (t_con->line_cursor < t_con->line_size)
443 {
444 telnet_write(connection, t_con->line + t_con->line_cursor++, 1);
445 }
446 t_con->state = TELNET_STATE_DATA;
447 }
448 else if (*buf_p == 'A') /* cursor up */
449 {
450 int last_history = (t_con->current_history > 0) ? t_con->current_history - 1 : TELNET_LINE_HISTORY_SIZE-1;
451 if (t_con->history[last_history])
452 {
453 telnet_clear_line(connection, t_con);
454 t_con->line_size = strlen(t_con->history[last_history]);
455 t_con->line_cursor = t_con->line_size;
456 memcpy(t_con->line, t_con->history[last_history], t_con->line_size + 1);
457 telnet_write(connection, t_con->line, t_con->line_size);
458 t_con->current_history = last_history;
459 }
460 t_con->state = TELNET_STATE_DATA;
461 }
462 else if (*buf_p == 'B') /* cursor down */
463 {
464 int next_history = (t_con->current_history + 1) % TELNET_LINE_HISTORY_SIZE;
465 if (t_con->history[next_history])
466 {
467 telnet_clear_line(connection, t_con);
468 t_con->line_size = strlen(t_con->history[next_history]);
469 t_con->line_cursor = t_con->line_size;
470 memcpy(t_con->line, t_con->history[next_history], t_con->line_size + 1);
471 telnet_write(connection, t_con->line, t_con->line_size);
472 t_con->current_history = next_history;
473 }
474 t_con->state = TELNET_STATE_DATA;
475 }
476 else if (*buf_p == '3')
477 {
478 t_con->last_escape = *buf_p;
479 }
480 else
481 {
482 t_con->state = TELNET_STATE_DATA;
483 }
484 }
485 else if (t_con->last_escape == '3')
486 {
487 /* Remove character */
488 if (*buf_p == '~')
489 {
490 if (t_con->line_cursor < t_con->line_size)
491 {
492 int i;
493 t_con->line_size--;
494 /* remove char from line buffer */
495 memmove(t_con->line + t_con->line_cursor, t_con->line + t_con->line_cursor + 1, t_con->line_size - t_con->line_cursor);
496
497 /* print remainder of buffer */
498 telnet_write(connection, t_con->line + t_con->line_cursor, t_con->line_size - t_con->line_cursor);
499 /* overwrite last char with whitespace */
500 telnet_write(connection, " \b", 2);
501
502 /* move back to cursor position*/
503 for (i = t_con->line_cursor; i < t_con->line_size; i++)
504 {
505 telnet_write(connection, "\b", 1);
506 }
507 }
508
509 t_con->state = TELNET_STATE_DATA;
510 }
511 else
512 {
513 t_con->state = TELNET_STATE_DATA;
514 }
515 }
516 else if (t_con->last_escape == '\x00')
517 {
518 if (*buf_p == '[')
519 {
520 t_con->last_escape = *buf_p;
521 }
522 else
523 {
524 t_con->state = TELNET_STATE_DATA;
525 }
526 }
527 else
528 {
529 ERROR("BUG: unexpected value in t_con->last_escape");
530 t_con->state = TELNET_STATE_DATA;
531 }
532
533 break;
534 default:
535 ERROR("unknown telnet state");
536 exit(-1);
537 }
538
539 bytes_read--;
540 buf_p++;
541 }
542
543 return ERROR_OK;
544 }
545
546 int telnet_connection_closed(connection_t *connection)
547 {
548 telnet_connection_t *t_con = connection->priv;
549 int i;
550
551 log_remove_callback(telnet_log_callback, connection);
552
553 if (t_con->prompt)
554 {
555 free(t_con->prompt);
556 t_con->prompt = NULL;
557 }
558
559 for (i = 0; i < TELNET_LINE_HISTORY_SIZE; i++)
560 {
561 if (t_con->history[i])
562 {
563 free(t_con->history[i]);
564 t_con->history[i] = NULL;
565 }
566 }
567
568 /* if this connection registered a debug-message receiver delete it */
569 delete_debug_msg_receiver(connection->cmd_ctx, NULL);
570
571 if (connection->priv)
572 {
573 free(connection->priv);
574 connection->priv = NULL;
575 }
576 else
577 {
578 ERROR("BUG: connection->priv == NULL");
579 }
580
581 target_unregister_event_callback(telnet_target_callback_event_handler, connection->cmd_ctx);
582
583 return ERROR_OK;
584 }
585
586 int telnet_set_prompt(connection_t *connection, char *prompt)
587 {
588 telnet_connection_t *t_con = connection->priv;
589
590 if (t_con->prompt != NULL)
591 free(t_con->prompt);
592
593 t_con->prompt = strdup(prompt);
594
595 return ERROR_OK;
596 }
597
598 int telnet_init(char *banner)
599 {
600 telnet_service_t *telnet_service = malloc(sizeof(telnet_service_t));
601
602 if (telnet_port == 0)
603 {
604 WARNING("no telnet port specified, using default port 4444");
605 telnet_port = 4444;
606 }
607
608 telnet_service->banner = banner;
609
610 add_service("telnet", CONNECTION_TELNET, telnet_port, 1, telnet_new_connection, telnet_input, telnet_connection_closed, telnet_service);
611
612 return ERROR_OK;
613 }
614
615 int telnet_register_commands(command_context_t *command_context)
616 {
617 register_command(command_context, NULL, "exit", handle_exit_command,
618 COMMAND_EXEC, "exit telnet session");
619
620 register_command(command_context, NULL, "telnet_port", handle_telnet_port_command,
621 COMMAND_CONFIG, "");
622
623 return ERROR_OK;
624 }
625
626 /* daemon configuration command telnet_port */
627 int handle_telnet_port_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc)
628 {
629 if (argc == 0)
630 return ERROR_OK;
631
632 telnet_port = strtoul(args[0], NULL, 0);
633
634 return ERROR_OK;
635 }
636
637 int handle_exit_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc)
638 {
639 return ERROR_COMMAND_CLOSE_CONNECTION;
640 }

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)