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

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)