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

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)