#include "telnet_server.h"
#include <target/target_request.h>
#include <helper/configuration.h>
+#include <helper/list.h>
static char *telnet_port;
if (connection_write(connection, data, len) == len)
return ERROR_OK;
- t_con->closed = 1;
+ t_con->closed = true;
return ERROR_SERVER_REMOTE_CLOSED;
}
+/* output an audible bell */
+static int telnet_bell(struct connection *connection)
+{
+ /* ("\a" does not work, at least on windows) */
+ return telnet_write(connection, "\x07", 1);
+}
+
static int telnet_prompt(struct connection *connection)
{
struct telnet_connection *t_con = connection->priv;
{
struct connection *connection = priv;
struct telnet_connection *t_con = connection->priv;
- int i;
+ size_t i;
+ size_t tmp;
- /* if there is no prompt, simply output the message */
- if (t_con->line_cursor < 0) {
+ /* If the prompt is not visible, simply output the message. */
+ if (!t_con->prompt_visible) {
telnet_outputline(connection, string);
return;
}
- /* clear the command line */
- for (i = strlen(t_con->prompt) + t_con->line_size; i > 0; i -= 16)
- telnet_write(connection, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", i > 16 ? 16 : i);
- for (i = strlen(t_con->prompt) + t_con->line_size; i > 0; i -= 16)
- telnet_write(connection, " ", i > 16 ? 16 : i);
- for (i = strlen(t_con->prompt) + t_con->line_size; i > 0; i -= 16)
- telnet_write(connection, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", i > 16 ? 16 : i);
+ /* Clear the command line. */
+ tmp = strlen(t_con->prompt) + t_con->line_size;
+
+ for (i = 0; i < tmp; i += 16)
+ telnet_write(connection, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b",
+ MIN(tmp - i, 16));
+
+ for (i = 0; i < tmp; i += 16)
+ telnet_write(connection, " ", MIN(tmp - i, 16));
+
+ for (i = 0; i < tmp; i += 16)
+ telnet_write(connection, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b",
+ MIN(tmp - i, 16));
- /* output the message */
telnet_outputline(connection, string);
- /* put the command line to its previous state */
+ /* Put the command line to its previous state. */
telnet_prompt(connection);
telnet_write(connection, t_con->line, t_con->line_size);
- for (i = t_con->line_size; i > t_con->line_cursor; i--)
+
+ for (i = t_con->line_cursor; i < t_con->line_size; i++)
telnet_write(connection, "\b", 1);
}
char *history = get_home_dir(TELNET_HISTORY);
- if (history == NULL) {
+ if (!history) {
LOG_INFO("unable to get user home directory, telnet history will be disabled");
return;
}
if (histfp) {
- while (fgets(buffer, sizeof(buffer), histfp) != NULL) {
+ while (fgets(buffer, sizeof(buffer), histfp)) {
char *p = strchr(buffer, '\n');
if (p)
char *history = get_home_dir(TELNET_HISTORY);
- if (history == NULL) {
+ if (!history) {
LOG_INFO("unable to get user home directory, telnet history will be disabled");
return;
}
i = t_con->current_history + 1;
i %= TELNET_LINE_HISTORY_SIZE;
- while (t_con->history[i] == NULL && num > 0) {
+ while (!t_con->history[i] && num > 0) {
i++;
i %= TELNET_LINE_HISTORY_SIZE;
num--;
connection->priv = telnet_connection;
/* initialize telnet connection information */
- telnet_connection->closed = 0;
+ telnet_connection->closed = false;
telnet_connection->line_size = 0;
telnet_connection->line_cursor = 0;
- telnet_connection->option_size = 0;
telnet_connection->prompt = strdup("> ");
+ telnet_connection->prompt_visible = true;
telnet_connection->state = TELNET_STATE_DATA;
/* output goes through telnet connection */
{
struct telnet_connection *t_con = connection->priv;
- int last_history = (t_con->current_history > 0) ?
+ size_t last_history = (t_con->current_history > 0) ?
t_con->current_history - 1 :
TELNET_LINE_HISTORY_SIZE-1;
telnet_history_go(connection, last_history);
static void telnet_history_down(struct connection *connection)
{
struct telnet_connection *t_con = connection->priv;
+ size_t next_history;
- int next_history = (t_con->current_history + 1) % TELNET_LINE_HISTORY_SIZE;
+ next_history = (t_con->current_history + 1) % TELNET_LINE_HISTORY_SIZE;
telnet_history_go(connection, next_history);
}
+static int telnet_history_print(struct connection *connection)
+{
+ struct telnet_connection *tc;
+
+ tc = connection->priv;
+
+ for (size_t i = 1; i < TELNET_LINE_HISTORY_SIZE; i++) {
+ char *line;
+
+ /*
+ * The tc->next_history line contains empty string (unless NULL), thus
+ * it is not printed.
+ */
+ line = tc->history[(tc->next_history + i) % TELNET_LINE_HISTORY_SIZE];
+
+ if (line) {
+ telnet_write(connection, line, strlen(line));
+ telnet_write(connection, "\r\n\x00", 3);
+ }
+ }
+
+ tc->line_size = 0;
+ tc->line_cursor = 0;
+
+ /* The prompt is always placed at the line beginning. */
+ telnet_write(connection, "\r", 1);
+
+ return telnet_prompt(connection);
+}
+
+static void telnet_move_cursor(struct connection *connection, size_t pos)
+{
+ struct telnet_connection *tc;
+ size_t tmp;
+
+ tc = connection->priv;
+
+ if (pos < tc->line_cursor) {
+ tmp = tc->line_cursor - pos;
+
+ for (size_t i = 0; i < tmp; i += 16)
+ telnet_write(connection, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b",
+ MIN(tmp - i, 16));
+ } else {
+ tmp = pos - tc->line_cursor;
+
+ for (size_t i = 0; i < tmp; i += 16)
+ telnet_write(connection, tc->line + tc->line_cursor + i,
+ MIN(tmp - i, 16));
+ }
+
+ tc->line_cursor = pos;
+}
+
+/* check buffer size leaving one spare character for string null termination */
+static inline bool telnet_can_insert(struct connection *connection, size_t len)
+{
+ struct telnet_connection *t_con = connection->priv;
+
+ return t_con->line_size + len < TELNET_LINE_MAX_SIZE;
+}
+
+/* write to telnet console, and update the telnet_connection members
+ * this function is capable of inserting in the middle of a line
+ * please ensure that data does not contain special characters (\n, \r, \t, \b ...)
+ *
+ * returns false when it fails to insert the requested data
+ */
+static bool telnet_insert(struct connection *connection, const void *data, size_t len)
+{
+ struct telnet_connection *t_con = connection->priv;
+
+ if (!telnet_can_insert(connection, len)) {
+ telnet_bell(connection);
+ return false;
+ }
+
+ if (t_con->line_cursor < t_con->line_size) {
+ /* we have some content after the cursor */
+ memmove(t_con->line + t_con->line_cursor + len,
+ t_con->line + t_con->line_cursor,
+ t_con->line_size - t_con->line_cursor);
+ }
+
+ strncpy(t_con->line + t_con->line_cursor, data, len);
+
+ telnet_write(connection,
+ t_con->line + t_con->line_cursor,
+ t_con->line_size + len - t_con->line_cursor);
+
+ t_con->line_size += len;
+ t_con->line_cursor += len;
+
+ for (size_t i = t_con->line_cursor; i < t_con->line_size; i++)
+ telnet_write(connection, "\b", 1);
+
+ return true;
+}
+
+static void telnet_auto_complete(struct connection *connection)
+{
+ struct telnet_connection *t_con = connection->priv;
+ struct command_context *command_context = connection->cmd_ctx;
+
+ struct cmd_match {
+ char *cmd;
+ struct list_head lh;
+ };
+
+ LIST_HEAD(matches);
+
+ /* user command sequence, either at line beginning
+ * or we start over after these characters ';', '[', '{' */
+ size_t seq_start = (t_con->line_cursor == 0) ? 0 : (t_con->line_cursor - 1);
+ while (seq_start > 0) {
+ char c = t_con->line[seq_start];
+ if (c == ';' || c == '[' || c == '{') {
+ seq_start++;
+ break;
+ }
+
+ seq_start--;
+ }
+
+ /* user command position in the line, ignore leading spaces */
+ size_t usr_cmd_pos = seq_start;
+ while ((usr_cmd_pos < t_con->line_cursor) && isspace(t_con->line[usr_cmd_pos]))
+ usr_cmd_pos++;
+
+ /* user command length */
+ size_t usr_cmd_len = t_con->line_cursor - usr_cmd_pos;
+
+ /* optimize multiple spaces in the user command,
+ * because info commands does not tolerate multiple spaces */
+ size_t optimized_spaces = 0;
+ char query[usr_cmd_len + 1];
+ for (size_t i = 0; i < usr_cmd_len; i++) {
+ if ((i < usr_cmd_len - 1) && isspace(t_con->line[usr_cmd_pos + i])
+ && isspace(t_con->line[usr_cmd_pos + i + 1])) {
+ optimized_spaces++;
+ continue;
+ }
+
+ query[i - optimized_spaces] = t_con->line[usr_cmd_pos + i];
+ }
+
+ usr_cmd_len -= optimized_spaces;
+ query[usr_cmd_len] = '\0';
+
+ /* filter commands */
+ char *query_cmd = alloc_printf("_telnet_autocomplete_helper {%s*}", query);
+
+ if (!query_cmd) {
+ LOG_ERROR("Out of memory");
+ return;
+ }
+
+ int retval = Jim_EvalSource(command_context->interp, __FILE__, __LINE__, query_cmd);
+ free(query_cmd);
+ if (retval != JIM_OK)
+ return;
+
+ Jim_Obj *list = Jim_GetResult(command_context->interp);
+ Jim_IncrRefCount(list);
+
+ /* common prefix length of the matched commands */
+ size_t common_len = 0;
+ char *first_match = NULL; /* used to compute the common prefix length */
+
+ int len = Jim_ListLength(command_context->interp, list);
+ for (int i = 0; i < len; i++) {
+ Jim_Obj *elem = Jim_ListGetIndex(command_context->interp, list, i);
+ Jim_IncrRefCount(elem);
+
+ char *name = (char *)Jim_GetString(elem, NULL);
+
+ /* validate the command */
+ bool ignore_cmd = false;
+ Jim_Cmd *jim_cmd = Jim_GetCommand(command_context->interp, elem, JIM_NONE);
+
+ if (!jim_cmd) {
+ /* Why we are here? Let's ignore it! */
+ ignore_cmd = true;
+ } else if (jimcmd_is_oocd_command(jim_cmd)) {
+ struct command *cmd = jimcmd_privdata(jim_cmd);
+
+ if (cmd && !cmd->handler && !cmd->jim_handler) {
+ /* Initial part of a multi-word command. Ignore it! */
+ ignore_cmd = true;
+ } else if (cmd && cmd->mode == COMMAND_CONFIG) {
+ /* Not executable after config phase. Ignore it! */
+ ignore_cmd = true;
+ }
+ }
+
+ /* save the command in the prediction list */
+ if (!ignore_cmd) {
+ struct cmd_match *match = calloc(1, sizeof(struct cmd_match));
+ if (!match) {
+ LOG_ERROR("Out of memory");
+ Jim_DecrRefCount(command_context->interp, elem);
+ break; /* break the for loop */
+ }
+
+ if (list_empty(&matches)) {
+ common_len = strlen(name);
+ first_match = name;
+ } else {
+ size_t new_common_len = usr_cmd_len; /* save some loops */
+
+ while (new_common_len < common_len && first_match[new_common_len] == name[new_common_len])
+ new_common_len++;
+
+ common_len = new_common_len;
+ }
+
+ match->cmd = name;
+ list_add_tail(&match->lh, &matches);
+ }
+
+ Jim_DecrRefCount(command_context->interp, elem);
+ }
+ /* end of command filtering */
+
+ /* proceed with auto-completion */
+ if (list_empty(&matches))
+ telnet_bell(connection);
+ else if (common_len == usr_cmd_len && list_is_singular(&matches) && t_con->line_cursor == t_con->line_size)
+ telnet_insert(connection, " ", 1);
+ else if (common_len > usr_cmd_len) {
+ int completion_size = common_len - usr_cmd_len;
+ if (telnet_insert(connection, first_match + usr_cmd_len, completion_size)) {
+ /* in bash this extra space is only added when the cursor in at the end of line */
+ if (list_is_singular(&matches) && t_con->line_cursor == t_con->line_size)
+ telnet_insert(connection, " ", 1);
+ }
+ } else if (!list_is_singular(&matches)) {
+ telnet_write(connection, "\n\r", 2);
+
+ struct cmd_match *match;
+ list_for_each_entry(match, &matches, lh) {
+ telnet_write(connection, match->cmd, strlen(match->cmd));
+ telnet_write(connection, "\n\r", 2);
+ }
+
+ telnet_prompt(connection);
+ telnet_write(connection, t_con->line, t_con->line_size);
+
+ /* restore the terminal visible cursor location */
+ for (size_t i = t_con->line_cursor; i < t_con->line_size; i++)
+ telnet_write(connection, "\b", 1);
+ }
+
+ /* destroy the command_list */
+ struct cmd_match *tmp, *match;
+ list_for_each_entry_safe(match, tmp, &matches, lh)
+ free(match);
+
+ Jim_DecrRefCount(command_context->interp, list);
+}
+
static int telnet_input(struct connection *connection)
{
int bytes_read;
while (bytes_read) {
switch (t_con->state) {
case TELNET_STATE_DATA:
- if (*buf_p == 0xff)
+ if (*buf_p == 0xff) {
t_con->state = TELNET_STATE_IAC;
- else {
+ } else {
if (isprint(*buf_p)) { /* printable character */
- /* watch buffer size leaving one spare character for
- * string null termination */
- if (t_con->line_size == TELNET_LINE_MAX_SIZE-1) {
- /* output audible bell if buffer is full
- * "\a" does not work, at least on windows */
- telnet_write(connection, "\x07", 1);
- } else if (t_con->line_cursor == t_con->line_size) {
- telnet_write(connection, buf_p, 1);
- t_con->line[t_con->line_size++] = *buf_p;
- t_con->line_cursor++;
- } else {
- int i;
- memmove(t_con->line + t_con->line_cursor + 1,
- t_con->line + t_con->line_cursor,
- t_con->line_size - t_con->line_cursor);
- t_con->line[t_con->line_cursor] = *buf_p;
- t_con->line_size++;
- telnet_write(connection,
- t_con->line + t_con->line_cursor,
- t_con->line_size - t_con->line_cursor);
- t_con->line_cursor++;
- for (i = t_con->line_cursor; i < t_con->line_size; i++)
- telnet_write(connection, "\b", 1);
- }
- } else { /* non-printable */
+ telnet_insert(connection, buf_p, 1);
+ } else { /* non-printable */
if (*buf_p == 0x1b) { /* escape */
t_con->state = TELNET_STATE_ESCAPE;
t_con->last_escape = '\x00';
- } else if ((*buf_p == 0xd) || (*buf_p == 0xa)) { /* CR/LF */
+ } else if ((*buf_p == 0xd) || (*buf_p == 0xa)) { /* CR/LF */
int retval;
/* skip over combinations with CR/LF and NUL characters */
telnet_write(connection, "\r\n\x00", 3);
if (strcmp(t_con->line, "history") == 0) {
- int i;
- for (i = 1; i < TELNET_LINE_HISTORY_SIZE; i++) {
- /* the t_con->next_history line contains empty string
- * (unless NULL), thus it is not printed */
- char *history_line = t_con->history[(t_con->
- next_history + i) %
- TELNET_LINE_HISTORY_SIZE];
- if (history_line) {
- telnet_write(connection, history_line,
- strlen(history_line));
- telnet_write(connection, "\r\n\x00", 3);
- }
- }
- t_con->line_size = 0;
- t_con->line_cursor = 0;
+ retval = telnet_history_print(connection);
+
+ if (retval != ERROR_OK)
+ return retval;
+
continue;
}
/* save only non-blank not repeating lines in the history */
char *prev_line = t_con->history[(t_con->current_history > 0) ?
t_con->current_history - 1 : TELNET_LINE_HISTORY_SIZE-1];
- if (*t_con->line && (prev_line == NULL ||
+ if (*t_con->line && (!prev_line ||
strcmp(t_con->line, prev_line))) {
/* if the history slot is already taken, free it */
- if (t_con->history[t_con->next_history])
- free(t_con->history[t_con->next_history]);
+ free(t_con->history[t_con->next_history]);
/* add line to history */
t_con->history[t_con->next_history] = strdup(t_con->line);
t_con->current_history =
t_con->next_history;
- if (t_con->history[t_con->current_history])
- free(t_con->history[t_con->current_history]);
+ free(t_con->history[t_con->current_history]);
t_con->history[t_con->current_history] = strdup("");
}
t_con->line_size = 0;
/* to suppress prompt in log callback during command execution */
- t_con->line_cursor = -1;
+ t_con->prompt_visible = false;
if (strcmp(t_con->line, "shutdown") == 0)
telnet_save_history(t_con);
retval = command_run_line(command_context, t_con->line);
t_con->line_cursor = 0;
+ t_con->prompt_visible = true;
if (retval == ERROR_COMMAND_CLOSE_CONNECTION)
return ERROR_SERVER_REMOTE_CLOSED;
} else if ((*buf_p == 0x7f) || (*buf_p == 0x8)) { /* delete character */
if (t_con->line_cursor > 0) {
if (t_con->line_cursor != t_con->line_size) {
- int i;
+ size_t i;
telnet_write(connection, "\b", 1);
t_con->line_cursor--;
t_con->line_size--;
telnet_write(connection, "\b \b", 3);
}
}
- } else if (*buf_p == 0x15) /* clear line */
+ } else if (*buf_p == 0x15) { /* clear line */
telnet_clear_line(connection, t_con);
- else if (*buf_p == CTRL('B')) { /* cursor left */
+ } else if (*buf_p == CTRL('B')) { /* cursor left */
if (t_con->line_cursor > 0) {
telnet_write(connection, "\b", 1);
t_con->line_cursor--;
}
t_con->state = TELNET_STATE_DATA;
+ } else if (*buf_p == CTRL('C')) { /* interrupt */
+ /* print '^C' at line end, and display a new command prompt */
+ telnet_move_cursor(connection, t_con->line_size);
+ telnet_write(connection, "^C\n\r", 4);
+ t_con->line_cursor = 0;
+ t_con->line_size = 0;
+ telnet_prompt(connection);
} else if (*buf_p == CTRL('F')) { /* cursor right */
if (t_con->line_cursor < t_con->line_size)
telnet_write(connection, t_con->line + t_con->line_cursor++, 1);
t_con->state = TELNET_STATE_DATA;
- } else if (*buf_p == CTRL('P')) /* cursor up */
+ } else if (*buf_p == CTRL('P')) { /* cursor up */
telnet_history_up(connection);
- else if (*buf_p == CTRL('N')) /* cursor down */
+ } else if (*buf_p == CTRL('N')) { /* cursor down */
telnet_history_down(connection);
- else
+ } else if (*buf_p == CTRL('A')) { /* move the cursor to the beginning of the line */
+ telnet_move_cursor(connection, 0);
+ } else if (*buf_p == CTRL('E')) { /* move the cursor to the end of the line */
+ telnet_move_cursor(connection, t_con->line_size);
+ } else if (*buf_p == CTRL('K')) { /* kill line to end */
+ if (t_con->line_cursor < t_con->line_size) {
+ /* overwrite with space, until end of line, move back */
+ for (size_t i = t_con->line_cursor; i < t_con->line_size; i++)
+ telnet_write(connection, " ", 1);
+ for (size_t i = t_con->line_cursor; i < t_con->line_size; i++)
+ telnet_write(connection, "\b", 1);
+ t_con->line[t_con->line_cursor] = '\0';
+ t_con->line_size = t_con->line_cursor;
+ }
+ } else if (*buf_p == '\t') {
+ telnet_auto_complete(connection);
+ } else {
LOG_DEBUG("unhandled nonprintable: %2.2x", *buf_p);
+ }
}
}
break;
telnet_history_up(connection);
} else if (*buf_p == 'B') { /* cursor down */
telnet_history_down(connection);
- } else if (*buf_p == '3')
+ } else if (*buf_p == 'F') { /* end key */
+ telnet_move_cursor(connection, t_con->line_size);
+ t_con->state = TELNET_STATE_DATA;
+ } else if (*buf_p == 'H') { /* home key */
+ telnet_move_cursor(connection, 0);
+ t_con->state = TELNET_STATE_DATA;
+ } else if (*buf_p == '3') {
t_con->last_escape = *buf_p;
- else
+ } else {
t_con->state = TELNET_STATE_DATA;
+ }
} else if (t_con->last_escape == '3') {
/* Remove character */
if (*buf_p == '~') {
if (t_con->line_cursor < t_con->line_size) {
- int i;
+ size_t i;
t_con->line_size--;
/* remove char from line buffer */
memmove(t_con->line + t_con->line_cursor,
break;
default:
LOG_ERROR("unknown telnet state");
- exit(-1);
+ return ERROR_FAIL;
}
bytes_read--;
log_remove_callback(telnet_log_callback, connection);
- if (t_con->prompt) {
- free(t_con->prompt);
- t_con->prompt = NULL;
- }
+ free(t_con->prompt);
+ t_con->prompt = NULL;
/* save telnet history */
telnet_save_history(t_con);
for (i = 0; i < TELNET_LINE_HISTORY_SIZE; i++) {
- if (t_con->history[i]) {
- free(t_con->history[i]);
- t_con->history[i] = NULL;
- }
+ free(t_con->history[i]);
+ t_con->history[i] = NULL;
}
/* if this connection registered a debug-message receiver delete it */
delete_debug_msg_receiver(connection->cmd_ctx, NULL);
- if (connection->priv) {
- free(connection->priv);
- connection->priv = NULL;
- } else
- LOG_ERROR("BUG: connection->priv == NULL");
+ free(connection->priv);
+ connection->priv = NULL;
return ERROR_OK;
}
return ERROR_OK;
}
- struct telnet_service *telnet_service;
-
- telnet_service = malloc(sizeof(struct telnet_service));
+ struct telnet_service *telnet_service =
+ malloc(sizeof(struct telnet_service));
if (!telnet_service) {
LOG_ERROR("Failed to allocate telnet service.");
telnet_service->banner = banner;
- return add_service("telnet",
- telnet_port,
- CONNECTION_LIMIT_UNLIMITED,
- telnet_new_connection,
- telnet_input,
- telnet_connection_closed,
+ int ret = add_service("telnet", telnet_port, CONNECTION_LIMIT_UNLIMITED,
+ telnet_new_connection, telnet_input, telnet_connection_closed,
telnet_service);
+
+ if (ret != ERROR_OK) {
+ free(telnet_service);
+ return ret;
+ }
+
+ return ERROR_OK;
}
/* daemon configuration command telnet_port */
{
.name = "telnet_port",
.handler = handle_telnet_port_command,
- .mode = COMMAND_ANY,
+ .mode = COMMAND_CONFIG,
.help = "Specify port on which to listen "
"for incoming telnet connections. "
"Read help on 'gdb_port'.",
telnet_port = strdup("4444");
return register_commands(cmd_ctx, NULL, telnet_command_handlers);
}
+
+void telnet_service_free(void)
+{
+ free(telnet_port);
+}