summaryrefslogtreecommitdiff
path: root/pjlib-util/src/pjlib-util/cli_telnet.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjlib-util/src/pjlib-util/cli_telnet.c')
-rw-r--r--pjlib-util/src/pjlib-util/cli_telnet.c1786
1 files changed, 1786 insertions, 0 deletions
diff --git a/pjlib-util/src/pjlib-util/cli_telnet.c b/pjlib-util/src/pjlib-util/cli_telnet.c
new file mode 100644
index 00000000..c6a8cf16
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/cli_telnet.c
@@ -0,0 +1,1786 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <pjlib-util/cli_imp.h>
+#include <pjlib-util/cli_telnet.h>
+#include <pj/activesock.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/except.h>
+#include <pjlib-util/errno.h>
+#include <pjlib-util/scanner.h>
+
+#define CLI_TELNET_BUF_SIZE 256
+
+#define CUT_MSG "<..data truncated..>\r\n"
+#define MAX_CUT_MSG_LEN 25
+
+#if 1
+ /* Enable some tracing */
+ #define THIS_FILE "cli_telnet.c"
+ #define TRACE_(arg) PJ_LOG(3,arg)
+#else
+ #define TRACE_(arg)
+#endif
+
+#define MAX_CLI_TELNET_OPTIONS 256
+
+/**
+ * This specify the state for the telnet option negotiation.
+ */
+enum cli_telnet_option_states
+{
+ OPT_DISABLE, /* Option disable */
+ OPT_ENABLE, /* Option enable */
+ OPT_EXPECT_DISABLE, /* Already send disable req, expecting resp */
+ OPT_EXPECT_ENABLE, /* Already send enable req, expecting resp */
+ OPT_EXPECT_DISABLE_REV, /* Already send disable req, expecting resp,
+ * need to send enable req */
+ OPT_EXPECT_ENABLE_REV /* Already send enable req, expecting resp,
+ * need to send disable req */
+};
+
+/**
+ * This structure contains information for telnet session option negotiation.
+ * It contains the local/peer option config and the option negotiation state.
+ */
+typedef struct cli_telnet_sess_option
+{
+ /**
+ * Local setting for the option.
+ * Default: FALSE;
+ */
+ pj_bool_t local_is_enable;
+
+ /**
+ * Remote setting for the option.
+ * Default: FALSE;
+ */
+ pj_bool_t peer_is_enable;
+
+ /**
+ * Local state of the option negotiation.
+ */
+ enum cli_telnet_option_states local_state;
+
+ /**
+ * Remote state of the option negotiation.
+ */
+ enum cli_telnet_option_states peer_state;
+} cli_telnet_sess_option;
+
+/**
+ * This specify the state of input character parsing.
+ */
+typedef enum cmd_parse_state
+{
+ ST_NORMAL,
+ ST_CR,
+ ST_ESC,
+ ST_VT100,
+ ST_IAC,
+ ST_DO,
+ ST_DONT,
+ ST_WILL,
+ ST_WONT
+} cmd_parse_state;
+
+typedef enum cli_telnet_command
+{
+ SUBNEGO_END = 240, /* End of subnegotiation parameters. */
+ NOP = 241, /* No operation. */
+ DATA_MARK = 242, /* Marker for NVT cleaning. */
+ BREAK = 243, /* Indicates that the "break" key was hit. */
+ INT_PROCESS = 244, /* Suspend, interrupt or abort the process. */
+ ABORT_OUTPUT = 245, /* Abort output, abort output stream. */
+ ARE_YOU_THERE = 246, /* Are you there. */
+ ERASE_CHAR = 247, /* Erase character, erase the current char. */
+ ERASE_LINE = 248, /* Erase line, erase the current line. */
+ GO_AHEAD = 249, /* Go ahead, other end can transmit. */
+ SUBNEGO_BEGIN = 250, /* Subnegotiation begin. */
+ WILL = 251, /* Accept the use of option. */
+ WONT = 252, /* Refuse the use of option. */
+ DO = 253, /* Request to use option. */
+ DONT = 254, /* Request to not use option. */
+ IAC = 255 /* Interpret as command */
+} cli_telnet_command;
+
+enum cli_telnet_options
+{
+ TRANSMIT_BINARY = 0, /* Transmit Binary. */
+ ECHO = 1, /* Echo. */
+ RECONNECT = 2, /* Reconnection. */
+ SUPPRESS_GA = 3, /* Suppress Go Aheah. */
+ MESSAGE_SIZE_NEGO = 4, /* Approx Message Size Negotiation. */
+ STATUS = 5, /* Status. */
+ TIMING_MARK = 6, /* Timing Mark. */
+ RTCE_OPTION = 7, /* Remote Controlled Trans and Echo. */
+ OUTPUT_LINE_WIDTH = 8, /* Output Line Width. */
+ OUTPUT_PAGE_SIZE = 9, /* Output Page Size. */
+ CR_DISPOSITION = 10, /* Carriage-Return Disposition. */
+ HORI_TABSTOPS = 11, /* Horizontal Tabstops. */
+ HORI_TAB_DISPO = 12, /* Horizontal Tab Disposition. */
+ FF_DISP0 = 13, /* Formfeed Disposition. */
+ VERT_TABSTOPS = 14, /* Vertical Tabstops. */
+ VERT_TAB_DISPO = 15, /* Vertical Tab Disposition. */
+ LF_DISP0 = 16, /* Linefeed Disposition. */
+ EXT_ASCII = 17, /* Extended ASCII. */
+ LOGOUT = 18, /* Logout. */
+ BYTE_MACRO = 19, /* Byte Macro. */
+ DE_TERMINAL = 20, /* Data Entry Terminal. */
+ SUPDUP_PROTO = 21, /* SUPDUP Protocol. */
+ SUPDUP_OUTPUT = 22, /* SUPDUP Output. */
+ SEND_LOC = 23, /* Send Location. */
+ TERM_TYPE = 24, /* Terminal Type. */
+ EOR = 25, /* End of Record. */
+ TACACS_UID = 26, /* TACACS User Identification. */
+ OUTPUT_MARKING = 27, /* Output Marking. */
+ TTYLOC = 28, /* Terminal Location Number. */
+ USE_3270_REGIME = 29, /* Telnet 3270 Regime. */
+ USE_X3_PAD = 30, /* X.3 PAD. */
+ WINDOW_SIZE = 31, /* Window Size. */
+ TERM_SPEED = 32, /* Terminal Speed. */
+ REM_FLOW_CONTROL = 33, /* Remote Flow Control. */
+ LINE_MODE = 34, /* Linemode. */
+ X_DISP_LOC = 35, /* X Display Location. */
+ ENVIRONMENT = 36, /* Environment. */
+ AUTH = 37, /* Authentication. */
+ ENCRYPTION = 38, /* Encryption Option. */
+ NEW_ENVIRONMENT = 39, /* New Environment. */
+ TN_3270E = 40, /* TN3270E. */
+ XAUTH = 41, /* XAUTH. */
+ CHARSET = 42, /* CHARSET. */
+ REM_SERIAL_PORT = 43, /* Telnet Remote Serial Port. */
+ COM_PORT_CONTROL = 44, /* Com Port Control. */
+ SUPP_LOCAL_ECHO = 45, /* Telnet Suppress Local Echo. */
+ START_TLS = 46, /* Telnet Start TLS. */
+ KERMIT = 47, /* KERMIT. */
+ SEND_URL = 48, /* SEND-URL. */
+ FWD_X = 49, /* FORWARD_X. */
+ EXT_OPTIONS = 255 /* Extended-Options-List */
+};
+
+enum terminal_cmd
+{
+ TC_ESC = 27,
+ TC_UP = 65,
+ TC_DOWN = 66,
+ TC_RIGHT = 67,
+ TC_LEFT = 68,
+ TC_END = 70,
+ TC_HOME = 72,
+ TC_CTRL_C = 3,
+ TC_CR = 13,
+ TC_BS = 8,
+ TC_TAB = 9,
+ TC_QM = 63,
+ TC_BELL = 7,
+ TC_DEL = 127
+};
+
+/**
+ * This specify the state of output character parsing.
+ */
+typedef enum out_parse_state
+{
+ OP_NORMAL,
+ OP_TYPE,
+ OP_SHORTCUT,
+ OP_CHOICE
+} out_parse_state;
+
+/**
+ * This structure contains the command line shown to the user.
+ * The telnet also needs to maintain and manage command cursor position.
+ * Due to that reason, the insert/delete character process from buffer will
+ * consider its current cursor position.
+ */
+typedef struct telnet_recv_buf {
+ /**
+ * Buffer containing the characters, NULL terminated.
+ */
+ unsigned char rbuf[PJ_CLI_MAX_CMDBUF];
+
+ /**
+ * Current length of the command line.
+ */
+ unsigned len;
+
+ /**
+ * Current cursor position.
+ */
+ unsigned cur_pos;
+} telnet_recv_buf;
+
+/**
+ * This structure contains the command history executed by user.
+ * Besides storing the command history, it is necessary to be able
+ * to browse it.
+ */
+typedef struct cmd_history
+{
+ PJ_DECL_LIST_MEMBER(struct cmd_history);
+ pj_str_t command;
+} cmd_history;
+
+typedef struct cli_telnet_sess
+{
+ pj_cli_sess base;
+ pj_pool_t *pool;
+ pj_activesock_t *asock;
+ pj_bool_t authorized;
+ pj_ioqueue_op_key_t op_key;
+ pj_mutex_t *smutex;
+ cmd_parse_state parse_state;
+ cli_telnet_sess_option telnet_option[MAX_CLI_TELNET_OPTIONS];
+ cmd_history *history;
+ cmd_history *active_history;
+
+ telnet_recv_buf *rcmd;
+ unsigned char buf[CLI_TELNET_BUF_SIZE + MAX_CUT_MSG_LEN];
+ unsigned buf_len;
+} cli_telnet_sess;
+
+struct cli_telnet_fe
+{
+ pj_cli_front_end base;
+ pj_pool_t *pool;
+ pj_cli_telnet_cfg cfg;
+ pj_bool_t own_ioqueue;
+ pj_cli_sess sess_head;
+
+ pj_activesock_t *asock;
+ pj_thread_t *worker_thread;
+ pj_bool_t is_quitting;
+ pj_mutex_t *mutex;
+};
+
+/* Forward Declaration */
+static pj_status_t telnet_sess_send2(cli_telnet_sess *sess,
+ const unsigned char *str, int len);
+
+static pj_status_t telnet_sess_send(cli_telnet_sess *sess,
+ const pj_str_t *str);
+
+/**
+ * Return the number of characters between the current cursor position
+ * to the end of line.
+ */
+static unsigned recv_buf_right_len(telnet_recv_buf *recv_buf)
+{
+ return (recv_buf->len - recv_buf->cur_pos);
+}
+
+/**
+ * Insert character to the receive buffer.
+ */
+static pj_bool_t recv_buf_insert(telnet_recv_buf *recv_buf,
+ unsigned char *data)
+{
+ if (recv_buf->len+1 >= PJ_CLI_MAX_CMDBUF) {
+ return PJ_FALSE;
+ } else {
+ if (*data == '\t' || *data == '?' || *data == '\r') {
+ /* Always insert to the end of line */
+ recv_buf->rbuf[recv_buf->len] = *data;
+ } else {
+ /* Insert based on the current cursor pos */
+ unsigned cur_pos = recv_buf->cur_pos;
+ unsigned rlen = recv_buf_right_len(recv_buf);
+ if (rlen > 0) {
+ /* Shift right characters */
+ pj_memmove(&recv_buf->rbuf[cur_pos+1],
+ &recv_buf->rbuf[cur_pos],
+ rlen+1);
+ }
+ recv_buf->rbuf[cur_pos] = *data;
+ }
+ ++recv_buf->cur_pos;
+ ++recv_buf->len;
+ recv_buf->rbuf[recv_buf->len] = 0;
+ }
+ return PJ_TRUE;
+}
+
+/**
+ * Delete character on the previous cursor position of the receive buffer.
+ */
+static pj_bool_t recv_buf_backspace(telnet_recv_buf *recv_buf)
+{
+ if ((recv_buf->cur_pos == 0) || (recv_buf->len == 0)) {
+ return PJ_FALSE;
+ } else {
+ unsigned rlen = recv_buf_right_len(recv_buf);
+ if (rlen) {
+ unsigned cur_pos = recv_buf->cur_pos;
+ /* Shift left characters */
+ pj_memmove(&recv_buf->rbuf[cur_pos-1], &recv_buf->rbuf[cur_pos],
+ rlen);
+ }
+ --recv_buf->cur_pos;
+ --recv_buf->len;
+ recv_buf->rbuf[recv_buf->len] = 0;
+ }
+ return PJ_TRUE;
+}
+
+static int compare_str(void *value, const pj_list_type *nd)
+{
+ cmd_history *node = (cmd_history*)nd;
+ return (pj_strcmp((pj_str_t *)value, &node->command));
+}
+
+/**
+ * Insert the command to history. If the entered command is not on the list,
+ * a new entry will be created. All entered command will be moved to
+ * the first entry of the history.
+ */
+static pj_status_t insert_history(cli_telnet_sess *sess,
+ char *cmd_val)
+{
+ cmd_history *in_history;
+ pj_str_t cmd;
+ cmd.ptr = cmd_val;
+ cmd.slen = pj_ansi_strlen(cmd_val)-1;
+
+ if (cmd.slen == 0)
+ return PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+
+ /* Find matching history */
+ in_history = pj_list_search(sess->history, (void*)&cmd, compare_str);
+ if (!in_history) {
+ if (pj_list_size(sess->history) < PJ_CLI_MAX_CMD_HISTORY) {
+ char *data_history;
+ in_history = PJ_POOL_ZALLOC_T(sess->pool, cmd_history);
+ pj_list_init(in_history);
+ data_history = (char *)pj_pool_calloc(sess->pool,
+ sizeof(char), PJ_CLI_MAX_CMDBUF);
+ in_history->command.ptr = data_history;
+ in_history->command.slen = 0;
+ } else {
+ /* Get the oldest history */
+ in_history = sess->history->prev;
+ }
+ } else {
+ pj_list_insert_nodes_after(in_history->prev, in_history->next);
+ }
+ pj_strcpy(&in_history->command, pj_strtrim(&cmd));
+ pj_list_push_front(sess->history, in_history);
+ sess->active_history = sess->history;
+
+ return PJ_SUCCESS;
+}
+
+/**
+ * Get the next or previous history of the shown/active history.
+ */
+static pj_str_t* get_prev_history(cli_telnet_sess *sess, pj_bool_t is_forward)
+{
+ pj_str_t *retval;
+ pj_size_t history_size;
+ cmd_history *node;
+ cmd_history *root;
+
+ PJ_ASSERT_RETURN(sess, NULL);
+
+ node = sess->active_history;
+ root = sess->history;
+ history_size = pj_list_size(sess->history);
+
+ if (history_size == 0) {
+ return NULL;
+ } else {
+ if (is_forward) {
+ node = (node->next==root)?node->next->next:node->next;
+ } else {
+ node = (node->prev==root)?node->prev->prev:node->prev;
+ }
+ retval = &node->command;
+ sess->active_history = node;
+ }
+ return retval;
+}
+
+/*
+ * This method is used to send option negotiation command.
+ * The commands dealing with option negotiation are
+ * three byte sequences, the third byte being the code for the option
+ * referenced - (RFC-854).
+ */
+static pj_bool_t send_telnet_cmd(cli_telnet_sess *sess,
+ cli_telnet_command cmd,
+ unsigned char option)
+{
+ unsigned char buf[3];
+ PJ_ASSERT_RETURN(sess, PJ_FALSE);
+
+ buf[0] = IAC;
+ buf[1] = cmd;
+ buf[2] = option;
+ telnet_sess_send2(sess, buf, 3);
+
+ return PJ_TRUE;
+}
+
+/**
+ * This method will handle sending telnet's ENABLE option negotiation.
+ * For local option: send WILL.
+ * For remote option: send DO.
+ * This method also handle the state transition of the ENABLE
+ * negotiation process.
+ */
+static pj_bool_t send_enable_option(cli_telnet_sess *sess,
+ pj_bool_t is_local,
+ unsigned char option)
+{
+ cli_telnet_sess_option *sess_option;
+ enum cli_telnet_option_states *state;
+ PJ_ASSERT_RETURN(sess, PJ_FALSE);
+
+ sess_option = &sess->telnet_option[option];
+ state = is_local?(&sess_option->local_state):(&sess_option->peer_state);
+ switch (*state) {
+ case OPT_ENABLE:
+ /* Ignore if already enabled */
+ break;
+ case OPT_DISABLE:
+ *state = OPT_EXPECT_ENABLE;
+ send_telnet_cmd(sess, (is_local?WILL:DO), option);
+ break;
+ case OPT_EXPECT_ENABLE:
+ *state = OPT_DISABLE;
+ break;
+ case OPT_EXPECT_DISABLE:
+ *state = OPT_EXPECT_DISABLE_REV;
+ break;
+ case OPT_EXPECT_ENABLE_REV:
+ *state = OPT_EXPECT_ENABLE;
+ break;
+ case OPT_EXPECT_DISABLE_REV:
+ *state = OPT_DISABLE;
+ break;
+ default:
+ return PJ_FALSE;
+ }
+ return PJ_TRUE;
+}
+
+static pj_bool_t send_cmd_do(cli_telnet_sess *sess,
+ unsigned char option)
+{
+ return send_enable_option(sess, PJ_FALSE, option);
+}
+
+static pj_bool_t send_cmd_will(cli_telnet_sess *sess,
+ unsigned char option)
+{
+ return send_enable_option(sess, PJ_TRUE, option);
+}
+
+/**
+ * This method will handle receiving telnet's ENABLE option negotiation.
+ * This method also handle the state transition of the ENABLE
+ * negotiation process.
+ */
+static pj_bool_t receive_enable_option(cli_telnet_sess *sess,
+ pj_bool_t is_local,
+ unsigned char option)
+{
+ cli_telnet_sess_option *sess_opt;
+ enum cli_telnet_option_states *state;
+ pj_bool_t opt_ena;
+ PJ_ASSERT_RETURN(sess, PJ_FALSE);
+
+ sess_opt = &sess->telnet_option[option];
+ state = is_local?(&sess_opt->local_state):(&sess_opt->peer_state);
+ opt_ena = is_local?sess_opt->local_is_enable:sess_opt->peer_is_enable;
+ switch (*state) {
+ case OPT_ENABLE:
+ /* Ignore if already enabled */
+ break;
+ case OPT_DISABLE:
+ if (opt_ena) {
+ *state = OPT_ENABLE;
+ send_telnet_cmd(sess, is_local?WILL:DO, option);
+ } else {
+ send_telnet_cmd(sess, is_local?WONT:DONT, option);
+ }
+ break;
+ case OPT_EXPECT_ENABLE:
+ *state = OPT_ENABLE;
+ break;
+ case OPT_EXPECT_DISABLE:
+ *state = OPT_DISABLE;
+ break;
+ case OPT_EXPECT_ENABLE_REV:
+ *state = OPT_EXPECT_DISABLE;
+ send_telnet_cmd(sess, is_local?WONT:DONT, option);
+ break;
+ case OPT_EXPECT_DISABLE_REV:
+ *state = OPT_EXPECT_DISABLE;
+ break;
+ default:
+ return PJ_FALSE;
+ }
+ return PJ_TRUE;
+}
+
+/**
+ * This method will handle receiving telnet's DISABLE option negotiation.
+ * This method also handle the state transition of the DISABLE
+ * negotiation process.
+ */
+static pj_bool_t receive_disable_option(cli_telnet_sess *sess,
+ pj_bool_t is_local,
+ unsigned char option)
+{
+ cli_telnet_sess_option *sess_opt;
+ enum cli_telnet_option_states *state;
+
+ PJ_ASSERT_RETURN(sess, PJ_FALSE);
+
+ sess_opt = &sess->telnet_option[option];
+ state = is_local?(&sess_opt->local_state):(&sess_opt->peer_state);
+
+ switch (*state) {
+ case OPT_ENABLE:
+ /* Disabling option always need to be accepted */
+ *state = OPT_DISABLE;
+ send_telnet_cmd(sess, is_local?WONT:DONT, option);
+ break;
+ case OPT_DISABLE:
+ /* Ignore if already enabled */
+ break;
+ case OPT_EXPECT_ENABLE:
+ case OPT_EXPECT_DISABLE:
+ *state = OPT_DISABLE;
+ break;
+ case OPT_EXPECT_ENABLE_REV:
+ *state = OPT_DISABLE;
+ send_telnet_cmd(sess, is_local?WONT:DONT, option);
+ break;
+ case OPT_EXPECT_DISABLE_REV:
+ *state = OPT_EXPECT_ENABLE;
+ send_telnet_cmd(sess, is_local?WILL:DO, option);
+ break;
+ default:
+ return PJ_FALSE;
+ }
+ return PJ_TRUE;
+}
+
+static pj_bool_t receive_do(cli_telnet_sess *sess, unsigned char option)
+{
+ return receive_enable_option(sess, PJ_TRUE, option);
+}
+
+static pj_bool_t receive_dont(cli_telnet_sess *sess, unsigned char option)
+{
+ return receive_disable_option(sess, PJ_TRUE, option);
+}
+
+static pj_bool_t receive_will(cli_telnet_sess *sess, unsigned char option)
+{
+ return receive_enable_option(sess, PJ_FALSE, option);
+}
+
+static pj_bool_t receive_wont(cli_telnet_sess *sess, unsigned char option)
+{
+ return receive_disable_option(sess, PJ_FALSE, option);
+}
+
+static void set_local_option(cli_telnet_sess *sess,
+ unsigned char option,
+ pj_bool_t enable)
+{
+ sess->telnet_option[option].local_is_enable = enable;
+}
+
+static void set_peer_option(cli_telnet_sess *sess,
+ unsigned char option,
+ pj_bool_t enable)
+{
+ sess->telnet_option[option].peer_is_enable = enable;
+}
+
+static pj_bool_t is_local_option_state_ena(cli_telnet_sess *sess,
+ unsigned char option)
+{
+ return (sess->telnet_option[option].local_state == OPT_ENABLE);
+}
+
+static void send_return_key(cli_telnet_sess *sess)
+{
+ telnet_sess_send2(sess, (unsigned char*)"\r\n", 2);
+}
+
+static void send_bell(cli_telnet_sess *sess) {
+ static const unsigned char bell = 0x07;
+ telnet_sess_send2(sess, &bell, 1);
+}
+
+static void send_prompt_str(cli_telnet_sess *sess)
+{
+ pj_str_t send_data;
+ char data_str[128];
+ struct cli_telnet_fe *fe = (struct cli_telnet_fe *)sess->base.fe;
+
+ send_data.ptr = &data_str[0];
+ send_data.slen = 0;
+
+ pj_strcat(&send_data, &fe->cfg.prompt_str);
+
+ telnet_sess_send(sess, &send_data);
+}
+
+/*
+ * This method is used to send error message to client, including
+ * the error position of the source command.
+ */
+static void send_err_arg(cli_telnet_sess *sess,
+ const pj_cli_exec_info *info,
+ const pj_str_t *msg,
+ pj_bool_t with_return,
+ pj_bool_t with_last_cmd)
+{
+ pj_str_t send_data;
+ char data_str[256];
+ unsigned len;
+ unsigned i;
+ struct cli_telnet_fe *fe = (struct cli_telnet_fe *)sess->base.fe;
+
+ send_data.ptr = &data_str[0];
+ send_data.slen = 0;
+
+ if (with_return)
+ pj_strcat2(&send_data, "\r\n");
+
+ len = fe->cfg.prompt_str.slen + info->err_pos;
+
+ /* Set the error pointer mark */
+ for (i=0;i<len;++i) {
+ pj_strcat2(&send_data, " ");
+ }
+ pj_strcat2(&send_data, "^");
+ pj_strcat2(&send_data, "\r\n");
+ pj_strcat(&send_data, msg);
+ pj_strcat(&send_data, &fe->cfg.prompt_str);
+ if (with_last_cmd)
+ pj_strcat2(&send_data, (char *)&sess->rcmd->rbuf[0]);
+
+ telnet_sess_send(sess, &send_data);
+}
+
+static void send_inv_arg(cli_telnet_sess *sess,
+ const pj_cli_exec_info *info,
+ pj_bool_t with_return,
+ pj_bool_t with_last_cmd)
+{
+ static const pj_str_t ERR_MSG = {"%Error : Invalid Arguments\r\n", 28};
+ send_err_arg(sess, info, &ERR_MSG, with_return, with_last_cmd);
+}
+
+static void send_too_many_arg(cli_telnet_sess *sess,
+ const pj_cli_exec_info *info,
+ pj_bool_t with_return,
+ pj_bool_t with_last_cmd)
+{
+ static const pj_str_t ERR_MSG = {"%Error : Too Many Arguments\r\n", 29};
+ send_err_arg(sess, info, &ERR_MSG, with_return, with_last_cmd);
+}
+
+static void send_hint_arg(cli_telnet_sess *sess,
+ pj_str_t *send_data,
+ const pj_str_t *desc,
+ pj_ssize_t cmd_len,
+ pj_ssize_t max_len)
+{
+ if ((desc) && (desc->slen > 0)) {
+ int j;
+
+ for (j=0;j<(max_len-cmd_len);++j) {
+ pj_strcat2(send_data, " ");
+ }
+ pj_strcat2(send_data, " ");
+ pj_strcat(send_data, desc);
+ telnet_sess_send(sess, send_data);
+ send_data->slen = 0;
+ }
+}
+
+/*
+ * This method is used to notify to the client that the entered command
+ * is ambiguous. It will show the matching command as the hint information.
+ */
+static void send_ambi_arg(cli_telnet_sess *sess,
+ const pj_cli_exec_info *info,
+ pj_bool_t with_return,
+ pj_bool_t with_last_cmd)
+{
+ unsigned i;
+ unsigned len;
+ pj_str_t send_data;
+ char data[1028];
+ struct cli_telnet_fe *fe = (struct cli_telnet_fe *)sess->base.fe;
+ const pj_cli_hint_info *hint = info->hint;
+ out_parse_state parse_state = OP_NORMAL;
+ pj_ssize_t max_length = 0;
+ pj_ssize_t cmd_length = 0;
+ const pj_str_t *cmd_desc = 0;
+ static const pj_str_t sc_type = {"sc", 2};
+ static const pj_str_t choice_type = {"choice", 6};
+ send_data.ptr = &data[0];
+ send_data.slen = 0;
+
+ if (with_return)
+ pj_strcat2(&send_data, "\r\n");
+
+ len = fe->cfg.prompt_str.slen + info->err_pos;
+
+ for (i=0;i<len;++i) {
+ pj_strcat2(&send_data, " ");
+ }
+ pj_strcat2(&send_data, "^");
+ /* Get the max length of the command name */
+ for (i=0;i<info->hint_cnt;++i) {
+ if ((&hint[i].type) && (hint[i].type.slen > 0)) {
+ if (pj_stricmp(&hint[i].type, &sc_type) == 0) {
+ //Additional delimiter " | "
+ cmd_length += (hint[i].name.slen + 3);
+ } else {
+ cmd_length = hint[i].name.slen;
+ }
+ } else {
+ cmd_length = hint[i].name.slen;
+ }
+
+ if (cmd_length > max_length) {
+ max_length = cmd_length;
+ }
+ }
+
+ cmd_length = 0;
+ /* Build hint information */
+ for (i=0;i<info->hint_cnt;++i) {
+ if ((&hint[i].type) && (hint[i].type.slen > 0)) {
+ if (pj_stricmp(&hint[i].type, &sc_type) == 0) {
+ parse_state = OP_SHORTCUT;
+ } else if (pj_stricmp(&hint[i].type, &choice_type) == 0) {
+ parse_state = OP_CHOICE;
+ } else {
+ parse_state = OP_TYPE;
+ }
+ } else {
+ parse_state = OP_NORMAL;
+ }
+
+ if ((parse_state != OP_SHORTCUT)) {
+ if (cmd_desc) {
+ /* Print data if previous hint is shortcut */
+ send_hint_arg(sess, &send_data,
+ cmd_desc, cmd_length,
+ max_length);
+ cmd_desc = 0;
+ }
+ pj_strcat2(&send_data, "\r\n ");
+ cmd_length = hint[i].name.slen;
+ }
+
+ switch (parse_state) {
+ case OP_CHOICE:
+ /* Format : "[Choice Value] description" */
+ pj_strcat2(&send_data, "[");
+ pj_strcat(&send_data, &hint[i].name);
+ pj_strcat2(&send_data, "]");
+ break;
+ case OP_TYPE:
+ /* Format : "<Argument Type> description" */
+ pj_strcat2(&send_data, "<");
+ pj_strcat(&send_data, &hint[i].name);
+ pj_strcat2(&send_data, ">");
+ break;
+ case OP_SHORTCUT:
+ /* Format : "Command | sc | description" */
+ pj_strcat2(&send_data, " | ");
+ pj_strcat(&send_data, &hint[i].name);
+ cmd_length += (hint[i].name.slen + 3);
+ break;
+ default:
+ /* Command */
+ pj_strcat(&send_data, &hint[i].name);
+ cmd_desc = &hint[i].desc;
+ break;
+ }
+
+ if ((parse_state == OP_TYPE) || (parse_state == OP_CHOICE) ||
+ ((i+1) >= info->hint_cnt))
+ {
+ /* Add description info */
+ send_hint_arg(sess, &send_data,
+ &hint[i].desc, cmd_length,
+ max_length);
+ }
+ }
+ pj_strcat2(&send_data, "\r\n");
+ pj_strcat(&send_data, &fe->cfg.prompt_str);
+ if (with_last_cmd)
+ pj_strcat2(&send_data, (char *)&sess->rcmd->rbuf[0]);
+
+ telnet_sess_send(sess, &send_data);
+}
+
+/*
+ * This method is to send command completion of the entered command.
+ */
+static void send_comp_arg(cli_telnet_sess *sess,
+ pj_cli_exec_info *info)
+{
+ pj_str_t send_data;
+ char data[128];
+
+ pj_strcat2(&info->hint[0].name, " ");
+
+ send_data.ptr = &data[0];
+ send_data.slen = 0;
+
+ pj_strcat(&send_data, &info->hint[0].name);
+
+ telnet_sess_send(sess, &send_data);
+}
+
+/*
+ * This method is to process the alfa numeric character sent by client.
+ */
+static pj_bool_t handle_alfa_num(cli_telnet_sess *sess, unsigned char *data)
+{
+ if (is_local_option_state_ena(sess, ECHO)) {
+ if (recv_buf_right_len(sess->rcmd) > 0) {
+ /* Cursor is not at EOL, insert character */
+ unsigned char echo[5] = {0x1b, 0x5b, 0x31, 0x40, 0x00};
+ echo[4] = *data;
+ telnet_sess_send2(sess, &echo[0], 5);
+ } else {
+ /* Append character */
+ telnet_sess_send2(sess, data, 1);
+ }
+ return PJ_TRUE;
+ }
+ return PJ_FALSE;
+}
+
+/*
+ * This method is to process the backspace character sent by client.
+ */
+static pj_bool_t handle_backspace(cli_telnet_sess *sess, unsigned char *data)
+{
+ unsigned rlen = recv_buf_right_len(sess->rcmd);
+ if (recv_buf_backspace(sess->rcmd)) {
+ if (rlen) {
+ /*
+ * Cursor is not at the end of line, move the characters
+ * after the cursor to left
+ */
+ unsigned char echo[5] = {0x00, 0x1b, 0x5b, 0x31, 0x50};
+ echo[0] = *data;
+ telnet_sess_send2(sess, &echo[0], 5);
+ } else {
+ const static unsigned char echo[3] = {0x08, 0x20, 0x08};
+ telnet_sess_send2(sess, &echo[0], 3);
+ }
+ return PJ_TRUE;
+ }
+ return PJ_FALSE;
+}
+
+/*
+ * Syntax error handler for parser.
+ */
+static void on_syntax_error(pj_scanner *scanner)
+{
+ PJ_UNUSED_ARG(scanner);
+ PJ_THROW(PJ_EINVAL);
+}
+
+/*
+ * This method is to process the backspace character sent by client.
+ */
+static pj_status_t get_last_token(pj_str_t *cmd, pj_str_t *str)
+{
+ pj_scanner scanner;
+ PJ_USE_EXCEPTION;
+ pj_scan_init(&scanner, cmd->ptr, cmd->slen, PJ_SCAN_AUTOSKIP_WS,
+ &on_syntax_error);
+ PJ_TRY {
+ while (!pj_scan_is_eof(&scanner)) {
+ pj_scan_get_until_chr(&scanner, " \t\r\n", str);
+ }
+ }
+ PJ_CATCH_ANY {
+ pj_scan_fini(&scanner);
+ return PJ_GET_EXCEPTION();
+ }
+ PJ_END;
+ return PJ_SUCCESS;
+}
+
+/*
+ * This method is to process the tab character sent by client.
+ */
+static pj_bool_t handle_tab(cli_telnet_sess *sess)
+{
+ pj_status_t status;
+ pj_bool_t retval = PJ_TRUE;
+ unsigned len;
+
+ pj_pool_t *pool;
+ pj_cli_cmd_val *cmd_val;
+ pj_cli_exec_info info;
+ pool = pj_pool_create(sess->pool->factory, "handle_tab",
+ PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC,
+ NULL);
+
+ cmd_val = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_val);
+
+ status = pj_cli_sess_parse(&sess->base, (char *)&sess->rcmd->rbuf, cmd_val,
+ pool, &info);
+
+ len = pj_ansi_strlen((char *)&sess->rcmd->rbuf[0]);
+
+ switch (status) {
+ case PJ_CLI_EINVARG:
+ send_inv_arg(sess, &info, PJ_TRUE, PJ_TRUE);
+ break;
+ case PJ_CLI_ETOOMANYARGS:
+ send_too_many_arg(sess, &info, PJ_TRUE, PJ_TRUE);
+ break;
+ case PJ_CLI_EMISSINGARG:
+ case PJ_CLI_EAMBIGUOUS:
+ send_ambi_arg(sess, &info, PJ_TRUE, PJ_TRUE);
+ break;
+ case PJ_SUCCESS:
+ if (len > sess->rcmd->cur_pos)
+ {
+ /* Send the cursor to EOL */
+ unsigned rlen = len - sess->rcmd->cur_pos+1;
+ unsigned char *data_sent = &sess->rcmd->rbuf[sess->rcmd->cur_pos-1];
+ telnet_sess_send2(sess, data_sent, rlen);
+ }
+ if (info.hint_cnt > 0) {
+ /* Complete command */
+ pj_str_t cmd = pj_str((char *)&sess->rcmd->rbuf[0]);
+ pj_str_t last_token;
+
+ if (get_last_token(&cmd, &last_token) == PJ_SUCCESS) {
+ /* Hint contains the match to the last command entered */
+ pj_str_t *hint_info = &info.hint[0].name;
+ pj_strtrim(&last_token);
+ if (hint_info->slen >= last_token.slen) {
+ hint_info->slen -= last_token.slen;
+ pj_memmove(&hint_info->ptr[0],
+ &hint_info->ptr[last_token.slen],
+ hint_info->slen);
+ }
+ send_comp_arg(sess, &info);
+
+ pj_memcpy(&sess->rcmd->rbuf[len], &info.hint[0].name.ptr[0],
+ info.hint[0].name.slen);
+
+ len += info.hint[0].name.slen;
+ sess->rcmd->rbuf[len] = 0;
+ }
+ } else {
+ retval = PJ_FALSE;
+ }
+ break;
+ }
+ sess->rcmd->len = len;
+ sess->rcmd->cur_pos = sess->rcmd->len;
+
+ pj_pool_release(pool);
+ return retval;
+}
+
+/*
+ * This method is to process the return character sent by client.
+ */
+static pj_bool_t handle_return(cli_telnet_sess *sess)
+{
+ pj_status_t status;
+ pj_bool_t retval = PJ_TRUE;
+
+ pj_pool_t *pool;
+ pj_cli_exec_info info;
+
+ send_return_key(sess);
+ insert_history(sess, (char *)&sess->rcmd->rbuf);
+
+ pool = pj_pool_create(sess->pool->factory, "handle_return",
+ PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC,
+ NULL);
+
+ status = pj_cli_sess_exec(&sess->base, (char *)&sess->rcmd->rbuf,
+ pool, &info);
+
+ switch (status) {
+ case PJ_CLI_EINVARG:
+ send_inv_arg(sess, &info, PJ_FALSE, PJ_FALSE);
+ break;
+ case PJ_CLI_ETOOMANYARGS:
+ send_too_many_arg(sess, &info, PJ_FALSE, PJ_FALSE);
+ break;
+ case PJ_CLI_EAMBIGUOUS:
+ case PJ_CLI_EMISSINGARG:
+ send_ambi_arg(sess, &info, PJ_FALSE, PJ_FALSE);
+ break;
+ case PJ_CLI_EEXIT:
+ retval = PJ_FALSE;
+ break;
+ case PJ_SUCCESS:
+ send_prompt_str(sess);
+ break;
+ }
+ if (retval) {
+ sess->rcmd->rbuf[0] = 0;
+ sess->rcmd->len = 0;
+ sess->rcmd->cur_pos = sess->rcmd->len;
+ }
+
+ pj_pool_release(pool);
+ return retval;
+}
+
+/*
+ * This method is to process the right key character sent by client.
+ */
+static pj_bool_t handle_right_key(cli_telnet_sess *sess)
+{
+ if (recv_buf_right_len(sess->rcmd)) {
+ unsigned char *data = &sess->rcmd->rbuf[sess->rcmd->cur_pos++];
+ telnet_sess_send2(sess, data, 1);
+ return PJ_TRUE;
+ }
+ return PJ_FALSE;
+}
+
+/*
+ * This method is to process the left key character sent by client.
+ */
+static pj_bool_t handle_left_key(cli_telnet_sess *sess)
+{
+ static const unsigned char move_cursor_left = 0x08;
+ if (sess->rcmd->cur_pos) {
+ telnet_sess_send2(sess, &move_cursor_left, 1);
+ --sess->rcmd->cur_pos;
+ return PJ_TRUE;
+ }
+ return PJ_FALSE;
+}
+
+/*
+ * This method is to process the up/down key character sent by client.
+ */
+static pj_bool_t handle_up_down(cli_telnet_sess *sess, pj_bool_t is_up)
+{
+ pj_str_t *history;
+
+ PJ_ASSERT_RETURN(sess, PJ_FALSE);
+
+ history = get_prev_history(sess, is_up);
+ if (history) {
+ pj_str_t send_data;
+ char str[PJ_CLI_MAX_CMDBUF];
+ enum {
+ MOVE_CURSOR_LEFT = 0x08,
+ CLEAR_CHAR = 0x20
+ };
+ send_data.ptr = &str[0];
+ send_data.slen = 0;
+
+ /* Move cursor position to the beginning of line */
+ if (sess->rcmd->cur_pos > 0) {
+ pj_memset(send_data.ptr, MOVE_CURSOR_LEFT, sess->rcmd->cur_pos);
+ send_data.slen = sess->rcmd->cur_pos;
+ }
+
+ if (sess->rcmd->len > (unsigned)history->slen) {
+ /* Clear the command currently shown*/
+ unsigned buf_len = sess->rcmd->len;
+ pj_memset(&send_data.ptr[send_data.slen], CLEAR_CHAR, buf_len);
+ send_data.slen += buf_len;
+
+ /* Move cursor position to the beginning of line */
+ pj_memset(&send_data.ptr[send_data.slen], MOVE_CURSOR_LEFT,
+ buf_len);
+ send_data.slen += buf_len;
+ }
+ /* Send data */
+ pj_strcat(&send_data, history);
+ telnet_sess_send(sess, &send_data);
+ pj_ansi_strncpy((char*)&sess->rcmd->rbuf, history->ptr, history->slen);
+ sess->rcmd->rbuf[history->slen] = 0;
+ sess->rcmd->len = history->slen;
+ sess->rcmd->cur_pos = sess->rcmd->len;
+ return PJ_TRUE;
+ }
+ return PJ_FALSE;
+}
+
+static pj_status_t process_vt100_cmd(cli_telnet_sess *sess,
+ unsigned char *cmd)
+{
+ pj_status_t status = PJ_TRUE;
+ switch (*cmd) {
+ case TC_ESC:
+ break;
+ case TC_UP:
+ status = handle_up_down(sess, PJ_TRUE);
+ break;
+ case TC_DOWN:
+ status = handle_up_down(sess, PJ_FALSE);
+ break;
+ case TC_RIGHT:
+ status = handle_right_key(sess);
+ break;
+ case TC_LEFT:
+ status = handle_left_key(sess);
+ break;
+ case TC_END:
+ break;
+ case TC_HOME:
+ break;
+ case TC_CTRL_C:
+ break;
+ case TC_CR:
+ break;
+ case TC_BS:
+ break;
+ case TC_TAB:
+ break;
+ case TC_QM:
+ break;
+ case TC_BELL:
+ break;
+ case TC_DEL:
+ break;
+ };
+ return status;
+}
+
+PJ_DEF(void) pj_cli_telnet_cfg_default(pj_cli_telnet_cfg *param)
+{
+ pj_assert(param);
+
+ pj_bzero(param, sizeof(*param));
+ param->port = PJ_CLI_TELNET_PORT;
+ param->log_level = PJ_CLI_TELNET_LOG_LEVEL;
+}
+
+/*
+ * Send a message to a telnet session
+ */
+static pj_status_t telnet_sess_send(cli_telnet_sess *sess,
+ const pj_str_t *str)
+{
+ pj_ssize_t sz;
+ pj_status_t status = PJ_SUCCESS;
+
+ sz = str->slen;
+ if (!sz)
+ return PJ_SUCCESS;
+
+ pj_mutex_lock(sess->smutex);
+
+ if (sess->buf_len == 0)
+ status = pj_activesock_send(sess->asock, &sess->op_key,
+ str->ptr, &sz, 0);
+ /* If we cannot send now, append it at the end of the buffer
+ * to be sent later.
+ */
+ if (sess->buf_len > 0 ||
+ (status != PJ_SUCCESS && status != PJ_EPENDING))
+ {
+ int clen = (int)sz;
+
+ if (sess->buf_len + clen > CLI_TELNET_BUF_SIZE)
+ clen = CLI_TELNET_BUF_SIZE - sess->buf_len;
+ if (clen > 0)
+ pj_memmove(sess->buf + sess->buf_len, str->ptr, clen);
+ if (clen < sz) {
+ pj_ansi_snprintf((char *)sess->buf + CLI_TELNET_BUF_SIZE,
+ MAX_CUT_MSG_LEN, CUT_MSG);
+ sess->buf_len = CLI_TELNET_BUF_SIZE +
+ pj_ansi_strlen((char *)sess->buf+
+ CLI_TELNET_BUF_SIZE);
+ } else
+ sess->buf_len += clen;
+ } else if (status == PJ_SUCCESS && sz < str->slen) {
+ pj_mutex_unlock(sess->smutex);
+ return PJ_CLI_ETELNETLOST;
+ }
+
+ pj_mutex_unlock(sess->smutex);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Send a message to a telnet session with formatted text
+ * (add single linefeed character with carriage return)
+ */
+static pj_status_t telnet_sess_send_with_format(cli_telnet_sess *sess,
+ const pj_str_t *str)
+{
+ pj_scanner scanner;
+ pj_str_t out_str;
+ static const pj_str_t CR_LF = {("\r\n"), 2};
+ int str_len = 0;
+ char *str_begin = 0;
+
+ PJ_USE_EXCEPTION;
+
+ pj_scan_init(&scanner, str->ptr, str->slen,
+ PJ_SCAN_AUTOSKIP_WS, &on_syntax_error);
+
+ str_begin = scanner.begin;
+
+ PJ_TRY {
+ while (!pj_scan_is_eof(&scanner)) {
+ pj_scan_get_until_ch(&scanner, '\n', &out_str);
+ str_len = scanner.curptr - str_begin;
+ if (*scanner.curptr == '\n') {
+ if ((str_len > 1) && (out_str.ptr[str_len-2] == '\r'))
+ {
+ continue;
+ } else {
+ int str_pos = str_begin - scanner.begin;
+
+ if (str_len > 0) {
+ pj_str_t s;
+ pj_strset(&s, &str->ptr[str_pos], str_len);
+ telnet_sess_send(sess, &s);
+ }
+ telnet_sess_send(sess, &CR_LF);
+
+ if (!pj_scan_is_eof(&scanner)) {
+ pj_scan_advance_n(&scanner, 1, PJ_TRUE);
+ str_begin = scanner.curptr;
+ }
+ }
+ } else {
+ pj_str_t s;
+ int str_pos = str_begin - scanner.begin;
+
+ pj_strset(&s, &str->ptr[str_pos], str_len);
+ telnet_sess_send(sess, &s);
+ }
+ }
+ }
+ PJ_CATCH_ANY {
+ pj_scan_fini(&scanner);
+ return (PJ_GET_EXCEPTION());
+ }
+ PJ_END;
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t telnet_sess_send2(cli_telnet_sess *sess,
+ const unsigned char *str, int len)
+{
+ pj_str_t s;
+
+ pj_strset(&s, (char *)str, len);
+ return telnet_sess_send(sess, &s);
+}
+
+static void telnet_sess_destroy(pj_cli_sess *sess)
+{
+ cli_telnet_sess *tsess = (cli_telnet_sess *)sess;
+ pj_mutex_t *mutex = ((struct cli_telnet_fe *)sess->fe)->mutex;
+
+ pj_mutex_lock(mutex);
+ pj_list_erase(sess);
+ pj_mutex_unlock(mutex);
+
+ pj_mutex_lock(tsess->smutex);
+ pj_mutex_unlock(tsess->smutex);
+ pj_activesock_close(tsess->asock);
+ pj_mutex_destroy(tsess->smutex);
+ pj_pool_release(tsess->pool);
+}
+
+static void telnet_fe_write_log(pj_cli_front_end *fe, int level,
+ const char *data, int len)
+{
+ struct cli_telnet_fe * tfe = (struct cli_telnet_fe *)fe;
+ pj_cli_sess *sess;
+
+ pj_mutex_lock(tfe->mutex);
+
+ sess = tfe->sess_head.next;
+ while (sess != &tfe->sess_head) {
+ cli_telnet_sess *tsess = (cli_telnet_sess *)sess;
+
+ sess = sess->next;
+ if (tsess->base.log_level > level) {
+ pj_str_t s;
+
+ pj_strset(&s, (char *)data, len);
+ telnet_sess_send_with_format(tsess, &s);
+ }
+ }
+
+ pj_mutex_unlock(tfe->mutex);
+}
+
+static void telnet_fe_destroy(pj_cli_front_end *fe)
+{
+ struct cli_telnet_fe *tfe = (struct cli_telnet_fe *)fe;
+ pj_cli_sess *sess;
+
+ tfe->is_quitting = PJ_TRUE;
+ if (tfe->worker_thread) {
+ pj_thread_join(tfe->worker_thread);
+ }
+
+ pj_mutex_lock(tfe->mutex);
+
+ /* Destroy all the sessions */
+ sess = tfe->sess_head.next;
+ while (sess != &tfe->sess_head) {
+ (*sess->op->destroy)(sess);
+ sess = tfe->sess_head.next;
+ }
+
+ pj_mutex_unlock(tfe->mutex);
+
+ pj_activesock_close(tfe->asock);
+ if (tfe->own_ioqueue)
+ pj_ioqueue_destroy(tfe->cfg.ioqueue);
+
+ if (tfe->worker_thread) {
+ pj_thread_destroy(tfe->worker_thread);
+ tfe->worker_thread = NULL;
+ }
+
+ pj_mutex_destroy(tfe->mutex);
+ pj_pool_release(tfe->pool);
+}
+
+static int poll_worker_thread(void *p)
+{
+ struct cli_telnet_fe *fe = (struct cli_telnet_fe *)p;
+
+ while (!fe->is_quitting) {
+ pj_time_val delay = {0, 50};
+ pj_ioqueue_poll(fe->cfg.ioqueue, &delay);
+ }
+
+ return 0;
+}
+
+static pj_bool_t telnet_sess_on_data_sent(pj_activesock_t *asock,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t sent)
+{
+ cli_telnet_sess *sess = (cli_telnet_sess *)
+ pj_activesock_get_user_data(asock);
+
+ PJ_UNUSED_ARG(op_key);
+
+ if (sent <= 0) {
+ pj_cli_sess_end_session(&sess->base);
+ return PJ_FALSE;
+ }
+
+ pj_mutex_lock(sess->smutex);
+
+ if (sess->buf_len) {
+ int len = sess->buf_len;
+
+ sess->buf_len = 0;
+ if (telnet_sess_send2(sess, sess->buf, len) != PJ_SUCCESS) {
+ pj_mutex_unlock(sess->smutex);
+ pj_cli_sess_end_session(&sess->base);
+ return PJ_FALSE;
+ }
+ }
+
+ pj_mutex_unlock(sess->smutex);
+
+ return PJ_TRUE;
+}
+
+static pj_bool_t telnet_sess_on_data_read(pj_activesock_t *asock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder)
+{
+ cli_telnet_sess *sess = (cli_telnet_sess *)
+ pj_activesock_get_user_data(asock);
+ struct cli_telnet_fe *tfe = (struct cli_telnet_fe *)sess->base.fe;
+ unsigned char *cdata = (unsigned char*)data;
+ pj_status_t is_valid = PJ_TRUE;
+
+ PJ_UNUSED_ARG(size);
+ PJ_UNUSED_ARG(remainder);
+
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ pj_cli_sess_end_session(&sess->base);
+ return PJ_FALSE;
+ }
+
+ if (tfe->is_quitting)
+ return PJ_FALSE;
+
+ pj_mutex_lock(sess->smutex);
+
+ switch (sess->parse_state) {
+ case ST_CR:
+ sess->parse_state = ST_NORMAL;
+ if (*cdata == 0 || *cdata == '\n')
+ pj_mutex_unlock(sess->smutex);
+ is_valid = handle_return(sess);
+ if (!is_valid)
+ return PJ_FALSE;
+ pj_mutex_lock(sess->smutex);
+ break;
+ case ST_NORMAL:
+ if (*cdata == IAC) {
+ sess->parse_state = ST_IAC;
+ } else if (*cdata == 127) {
+ is_valid = handle_backspace(sess, cdata);
+ } else if (*cdata == 27) {
+ sess->parse_state = ST_ESC;
+ } else {
+ if (recv_buf_insert(sess->rcmd, cdata)) {
+ if (*cdata == '\r') {
+ sess->parse_state = ST_CR;
+ } else if ((*cdata == '\t') || (*cdata == '?')) {
+ is_valid = handle_tab(sess);
+ } else if (*cdata > 31 && *cdata < 127) {
+ is_valid = handle_alfa_num(sess, cdata);
+ }
+ } else {
+ is_valid = PJ_FALSE;
+ }
+ }
+ break;
+ case ST_ESC:
+ if (*cdata == 91) {
+ sess->parse_state = ST_VT100;
+ } else {
+ sess->parse_state = ST_NORMAL;
+ }
+ break;
+ case ST_VT100:
+ sess->parse_state = ST_NORMAL;
+ is_valid = process_vt100_cmd(sess, cdata);
+ break;
+ case ST_IAC:
+ switch ((unsigned) *cdata) {
+ case DO:
+ sess->parse_state = ST_DO;
+ break;
+ case DONT:
+ sess->parse_state = ST_DONT;
+ break;
+ case WILL:
+ sess->parse_state = ST_WILL;
+ break;
+ case WONT:
+ sess->parse_state = ST_WONT;
+ break;
+ default:
+ sess->parse_state = ST_NORMAL;
+ break;
+ }
+ break;
+ case ST_DO:
+ receive_do(sess, *cdata);
+ sess->parse_state = ST_NORMAL;
+ break;
+ case ST_DONT:
+ receive_dont(sess, *cdata);
+ sess->parse_state = ST_NORMAL;
+ break;
+ case ST_WILL:
+ receive_will(sess, *cdata);
+ sess->parse_state = ST_NORMAL;
+ break;
+ case ST_WONT:
+ receive_wont(sess, *cdata);
+ sess->parse_state = ST_NORMAL;
+ break;
+ default:
+ sess->parse_state = ST_NORMAL;
+ break;
+ }
+ if (!is_valid) {
+ send_bell(sess);
+ }
+
+ pj_mutex_unlock(sess->smutex);
+
+ return PJ_TRUE;
+}
+
+static pj_bool_t telnet_fe_on_accept(pj_activesock_t *asock,
+ pj_sock_t newsock,
+ const pj_sockaddr_t *src_addr,
+ int src_addr_len)
+{
+ struct cli_telnet_fe *fe = (struct cli_telnet_fe *)
+ pj_activesock_get_user_data(asock);
+ pj_status_t sstatus;
+ pj_pool_t *pool;
+ cli_telnet_sess *sess;
+ pj_activesock_cb asock_cb;
+
+ PJ_UNUSED_ARG(src_addr);
+ PJ_UNUSED_ARG(src_addr_len);
+
+ if (fe->is_quitting)
+ return PJ_FALSE;
+
+ /* An incoming connection is accepted, create a new session */
+ pool = pj_pool_create(fe->pool->factory, "telnet_sess",
+ PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC,
+ NULL);
+ if (!pool) {
+ TRACE_((THIS_FILE,
+ "Not enough memory to create a new telnet session"));
+ return PJ_TRUE;
+ }
+
+ sess = PJ_POOL_ZALLOC_T(pool, cli_telnet_sess);
+ sess->pool = pool;
+ sess->base.fe = &fe->base;
+ sess->base.log_level = fe->cfg.log_level;
+ sess->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_sess_op);
+ sess->base.op->destroy = &telnet_sess_destroy;
+ pj_bzero(&asock_cb, sizeof(asock_cb));
+ asock_cb.on_data_read = &telnet_sess_on_data_read;
+ asock_cb.on_data_sent = &telnet_sess_on_data_sent;
+ sess->rcmd = PJ_POOL_ZALLOC_T(pool, telnet_recv_buf);
+ sess->history = PJ_POOL_ZALLOC_T(pool, struct cmd_history);
+ pj_list_init(sess->history);
+ sess->active_history = sess->history;
+
+ sstatus = pj_mutex_create_recursive(pool, "mutex_telnet_sess",
+ &sess->smutex);
+ if (sstatus != PJ_SUCCESS)
+ goto on_exit;
+
+ sstatus = pj_activesock_create(pool, newsock, pj_SOCK_STREAM(),
+ NULL, fe->cfg.ioqueue,
+ &asock_cb, sess, &sess->asock);
+ if (sstatus != PJ_SUCCESS) {
+ TRACE_((THIS_FILE, "Failure creating active socket"));
+ goto on_exit;
+ }
+
+ pj_memset(sess->telnet_option, 0, sizeof(sess->telnet_option));
+ set_local_option(sess, TRANSMIT_BINARY, PJ_TRUE);
+ set_local_option(sess, STATUS, PJ_TRUE);
+ set_local_option(sess, SUPPRESS_GA, PJ_TRUE);
+ set_local_option(sess, TIMING_MARK, PJ_TRUE);
+ set_local_option(sess, TERM_SPEED, PJ_TRUE);
+ set_local_option(sess, TERM_TYPE, PJ_TRUE);
+
+ set_peer_option(sess, TRANSMIT_BINARY, PJ_TRUE);
+ set_peer_option(sess, SUPPRESS_GA, PJ_TRUE);
+ set_peer_option(sess, STATUS, PJ_TRUE);
+ set_peer_option(sess, TIMING_MARK, PJ_TRUE);
+ set_peer_option(sess, ECHO, PJ_TRUE);
+
+ send_cmd_do(sess, SUPPRESS_GA);
+ send_cmd_will(sess, ECHO);
+ send_cmd_will(sess, STATUS);
+ send_cmd_will(sess, SUPPRESS_GA);
+
+ /* Send prompt string */
+ telnet_sess_send(sess, &fe->cfg.prompt_str);
+
+ /* Start reading for input from the new telnet session */
+ sstatus = pj_activesock_start_read(sess->asock, pool, 1, 0);
+ if (sstatus != PJ_SUCCESS) {
+ TRACE_((THIS_FILE, "Failure reading active socket"));
+ goto on_exit;
+ }
+
+ pj_ioqueue_op_key_init(&sess->op_key, sizeof(sess->op_key));
+ pj_mutex_lock(fe->mutex);
+ pj_list_push_back(&fe->sess_head, &sess->base);
+ pj_mutex_unlock(fe->mutex);
+
+ return PJ_TRUE;
+
+on_exit:
+ if (sess->asock)
+ pj_activesock_close(sess->asock);
+ else
+ pj_sock_close(newsock);
+
+ if (sess->smutex)
+ pj_mutex_destroy(sess->smutex);
+
+ pj_pool_release(pool);
+
+ return PJ_TRUE;
+}
+
+PJ_DEF(pj_status_t) pj_cli_telnet_create(pj_cli_t *cli,
+ pj_cli_telnet_cfg *param,
+ pj_cli_front_end **p_fe)
+{
+ struct cli_telnet_fe *fe;
+ pj_pool_t *pool;
+ pj_sock_t sock = PJ_INVALID_SOCKET;
+ pj_activesock_cb asock_cb;
+ pj_sockaddr_in addr;
+ pj_status_t sstatus;
+
+ PJ_ASSERT_RETURN(cli, PJ_EINVAL);
+
+ pool = pj_pool_create(pj_cli_get_param(cli)->pf, "telnet_fe",
+ PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC,
+ NULL);
+ fe = PJ_POOL_ZALLOC_T(pool, struct cli_telnet_fe);
+ if (!fe)
+ return PJ_ENOMEM;
+
+ fe->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_front_end_op);
+
+ if (!param)
+ pj_cli_telnet_cfg_default(&fe->cfg);
+ else
+ pj_memcpy(&fe->cfg, param, sizeof(*param));
+
+ pj_list_init(&fe->sess_head);
+ fe->base.cli = cli;
+ fe->base.type = PJ_CLI_TELNET_FRONT_END;
+ fe->base.op->on_write_log = &telnet_fe_write_log;
+ fe->base.op->on_destroy = &telnet_fe_destroy;
+ fe->pool = pool;
+
+ if (!fe->cfg.ioqueue) {
+ /* Create own ioqueue if application doesn't supply one */
+ sstatus = pj_ioqueue_create(pool, 8, &fe->cfg.ioqueue);
+ if (sstatus != PJ_SUCCESS)
+ goto on_exit;
+ fe->own_ioqueue = PJ_TRUE;
+ }
+
+ sstatus = pj_mutex_create_recursive(pool, "mutex_telnet_fe", &fe->mutex);
+ if (sstatus != PJ_SUCCESS)
+ goto on_exit;
+
+ /* Start telnet daemon */
+ sstatus = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0,
+ &sock);
+ if (sstatus != PJ_SUCCESS)
+ goto on_exit;
+
+ pj_sockaddr_in_init(&addr, NULL, fe->cfg.port);
+
+ sstatus = pj_sock_bind(sock, &addr, sizeof(addr));
+ if (sstatus == PJ_SUCCESS) {
+ pj_sockaddr_in addr;
+ int addr_len = sizeof(addr);
+
+ sstatus = pj_sock_getsockname(sock, &addr, &addr_len);
+ if (sstatus != PJ_SUCCESS)
+ goto on_exit;
+
+ fe->cfg.port = pj_sockaddr_in_get_port(&addr);
+ if (param)
+ param->port = fe->cfg.port;
+
+ PJ_LOG(3, (THIS_FILE, "CLI telnet daemon listening at port %d",
+ fe->cfg.port));
+
+ if (fe->cfg.prompt_str.slen == 0) {
+ pj_str_t prompt_sign = {"> ", 2};
+ char *prompt_data = pj_pool_alloc(fe->pool,
+ pj_gethostname()->slen+2);
+ fe->cfg.prompt_str.ptr = prompt_data;
+
+ pj_strcpy(&fe->cfg.prompt_str, pj_gethostname());
+ pj_strcat(&fe->cfg.prompt_str, &prompt_sign);
+ }
+ } else {
+ PJ_LOG(3, (THIS_FILE, "Failed binding the socket"));
+ goto on_exit;
+ }
+
+ sstatus = pj_sock_listen(sock, 4);
+ if (sstatus != PJ_SUCCESS)
+ goto on_exit;
+
+ pj_bzero(&asock_cb, sizeof(asock_cb));
+ asock_cb.on_accept_complete = &telnet_fe_on_accept;
+ sstatus = pj_activesock_create(pool, sock, pj_SOCK_STREAM(),
+ NULL, fe->cfg.ioqueue,
+ &asock_cb, fe, &fe->asock);
+ if (sstatus != PJ_SUCCESS)
+ goto on_exit;
+
+ sstatus = pj_activesock_start_accept(fe->asock, pool);
+ if (sstatus != PJ_SUCCESS)
+ goto on_exit;
+
+ if (fe->own_ioqueue) {
+ /* Create our own worker thread */
+ sstatus = pj_thread_create(pool, "worker_telnet_fe",
+ &poll_worker_thread, fe, 0, 0,
+ &fe->worker_thread);
+ if (sstatus != PJ_SUCCESS)
+ goto on_exit;
+ }
+
+ pj_cli_register_front_end(cli, &fe->base);
+
+ if (p_fe)
+ *p_fe = &fe->base;
+
+ return PJ_SUCCESS;
+
+on_exit:
+ if (fe->asock)
+ pj_activesock_close(fe->asock);
+ else if (sock != PJ_INVALID_SOCKET)
+ pj_sock_close(sock);
+
+ if (fe->own_ioqueue)
+ pj_ioqueue_destroy(fe->cfg.ioqueue);
+
+ if (fe->mutex)
+ pj_mutex_destroy(fe->mutex);
+
+ pj_pool_release(pool);
+ return sstatus;
+}