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

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)