/* $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 #include #include #include #include #include #include #include #include #include #include #include #include #include #if (defined(PJ_WIN32) && PJ_WIN32!=0) || \ (defined(PJ_WIN64) && PJ_WIN64!=0) || \ (defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0) /* Undefine EADDRINUSE first, we want it equal to WSAEADDRINUSE, * while WinSDK 10 defines it to another value. */ #undef EADDRINUSE #define EADDRINUSE WSAEADDRINUSE #endif #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 /** Maximum retry on Telnet Restart **/ #define MAX_RETRY_ON_TELNET_RESTART 100 /** Minimum number of millisecond to wait before retrying to re-bind on * telnet restart **/ #define MIN_WAIT_ON_TELNET_RESTART 20 /** Maximum number of millisecod to wait before retrying to re-bind on * telnet restart **/ #define MAX_WAIT_ON_TELNET_RESTART 1000 /** * 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. */ TERM_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; typedef 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; } cli_telnet_fe; /* 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); static pj_status_t telnet_start(cli_telnet_fe *fe); static pj_status_t telnet_restart(cli_telnet_fe *tfe); /** * 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]; cli_telnet_fe *fe = (cli_telnet_fe *)sess->base.fe; send_data.ptr = data_str; 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]; pj_size_t len; unsigned i; cli_telnet_fe *fe = (cli_telnet_fe *)sess->base.fe; send_data.ptr = data_str; 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;icfg.prompt_str); if (with_last_cmd) pj_strcat2(&send_data, (char *)sess->rcmd->rbuf); 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; pj_size_t len; pj_str_t send_data; char data[1028]; cli_telnet_fe *fe = (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; static const pj_str_t sc_type = {"sc", 2}; static const pj_str_t choice_type = {"choice", 6}; send_data.ptr = data; 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;ihint_cnt;++i) { if (hint[i].type.slen > 0) { if (pj_stricmp(&hint[i].type, &sc_type) == 0) { if ((i > 0) && (!pj_stricmp(&hint[i-1].desc, &hint[i].desc))) { cmd_length += (hint[i].name.slen + 3); } else { cmd_length = hint[i].name.slen; } } 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;ihint_cnt;++i) { if (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) { 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 : " description" */ pj_strcat2(&send_data, "<"); pj_strcat(&send_data, &hint[i].name); pj_strcat2(&send_data, ">"); break; case OP_SHORTCUT: /* Format : "Command | sc | description" */ { cmd_length += hint[i].name.slen; if ((i > 0) && (!pj_stricmp(&hint[i-1].desc, &hint[i].desc))) { pj_strcat2(&send_data, " | "); cmd_length += 3; } else { pj_strcat2(&send_data, "\r\n "); } pj_strcat(&send_data, &hint[i].name); } break; default: /* Command */ pj_strcat(&send_data, &hint[i].name); break; } if ((parse_state == OP_TYPE) || (parse_state == OP_CHOICE) || ((i+1) >= info->hint_cnt) || (pj_strncmp(&hint[i].desc, &hint[i+1].desc, hint[i].desc.slen))) { /* Add description info */ send_hint_arg(sess, &send_data, &hint[i].desc, cmd_length, max_length); cmd_length = 0; } } 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); 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; 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, TERM_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, 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, 5); } else { const static unsigned char echo[3] = {0x08, 0x20, 0x08}; telnet_sess_send2(sess, echo, 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; pj_scan_fini(&scanner); 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 = (unsigned)pj_ansi_strlen((char *)sess->rcmd->rbuf); 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); 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, &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, info.hint[0].name.slen); len += (unsigned)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; 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 = (unsigned)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 = (unsigned)(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 = (int)(scanner.curptr - str_begin); if (*scanner.curptr == '\n') { if ((str_len > 1) && (out_str.ptr[str_len-2] == '\r')) { continue; } else { int str_pos = (int)(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 = (int)(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; pj_scan_fini(&scanner); 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 = ((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, pj_size_t len) { cli_telnet_fe *tfe = (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) { cli_telnet_fe *tfe = (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) { cli_telnet_fe *fe = (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) { TRACE_((THIS_FILE, "Error On data send")); 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); cli_telnet_fe *tfe = (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 (tfe->is_quitting) return PJ_FALSE; if (status != PJ_SUCCESS && status != PJ_EPENDING) { TRACE_((THIS_FILE, "Error on data read %d", status)); 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, pj_status_t status) { cli_telnet_fe *fe = (cli_telnet_fe *) pj_activesock_get_user_data(asock); pj_status_t sstatus; pj_pool_t *pool; cli_telnet_sess *sess = NULL; pj_activesock_cb asock_cb; PJ_UNUSED_ARG(src_addr); PJ_UNUSED_ARG(src_addr_len); if (fe->is_quitting) return PJ_FALSE; if (status != PJ_SUCCESS && status != PJ_EPENDING) { TRACE_((THIS_FILE, "Error on data accept %d", status)); if (status == PJ_ESOCKETSTOP) telnet_restart(fe); 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, TERM_ECHO, PJ_TRUE); send_cmd_do(sess, SUPPRESS_GA); send_cmd_will(sess, TERM_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) { cli_telnet_fe *fe; pj_pool_t *pool; pj_status_t status; 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, 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 */ status = pj_ioqueue_create(pool, 8, &fe->cfg.ioqueue); if (status != PJ_SUCCESS) goto on_exit; fe->own_ioqueue = PJ_TRUE; } status = pj_mutex_create_recursive(pool, "mutex_telnet_fe", &fe->mutex); if (status != PJ_SUCCESS) goto on_exit; /* Start telnet daemon */ status = telnet_start(fe); if (status != 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->own_ioqueue) pj_ioqueue_destroy(fe->cfg.ioqueue); if (fe->mutex) pj_mutex_destroy(fe->mutex); pj_pool_release(pool); return status; } static pj_status_t telnet_start(cli_telnet_fe *fe) { pj_sock_t sock = PJ_INVALID_SOCKET; pj_activesock_cb asock_cb; pj_sockaddr_in addr; pj_status_t status; int val; int restart_retry; unsigned msec; /* Start telnet daemon */ status = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &sock); if (status != PJ_SUCCESS) goto on_exit; pj_sockaddr_in_init(&addr, NULL, fe->cfg.port); val = 1; status = pj_sock_setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); if (status != PJ_SUCCESS) { PJ_PERROR(3, (THIS_FILE, status, "Failed setting socket options")); } /* The loop is silly, but what else can we do? */ for (msec=MIN_WAIT_ON_TELNET_RESTART, restart_retry=0; restart_retry < MAX_RETRY_ON_TELNET_RESTART; ++restart_retry, msec=(mseccfg.port = pj_sockaddr_in_get_port(&addr); 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_PERROR(3, (THIS_FILE, status, "Failed binding the socket")); goto on_exit; } status = pj_sock_listen(sock, 4); if (status != PJ_SUCCESS) goto on_exit; pj_bzero(&asock_cb, sizeof(asock_cb)); asock_cb.on_accept_complete2 = &telnet_fe_on_accept; status = pj_activesock_create(fe->pool, sock, pj_SOCK_STREAM(), NULL, fe->cfg.ioqueue, &asock_cb, fe, &fe->asock); if (status != PJ_SUCCESS) goto on_exit; status = pj_activesock_start_accept(fe->asock, fe->pool); if (status != PJ_SUCCESS) goto on_exit; if (fe->own_ioqueue) { /* Create our own worker thread */ status = pj_thread_create(fe->pool, "worker_telnet_fe", &poll_worker_thread, fe, 0, 0, &fe->worker_thread); if (status != PJ_SUCCESS) goto on_exit; } return PJ_SUCCESS; on_exit: if (fe->cfg.on_started) { (*fe->cfg.on_started)(status); } 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(fe->pool); return status; } static pj_status_t telnet_restart(cli_telnet_fe *fe) { pj_status_t status; pj_cli_sess *sess; fe->is_quitting = PJ_TRUE; if (fe->worker_thread) { pj_thread_join(fe->worker_thread); } pj_mutex_lock(fe->mutex); /* Destroy all the sessions */ sess = fe->sess_head.next; while (sess != &fe->sess_head) { (*sess->op->destroy)(sess); sess = fe->sess_head.next; } pj_mutex_unlock(fe->mutex); /** Close existing activesock **/ status = pj_activesock_close(fe->asock); if (status != PJ_SUCCESS) goto on_exit; if (fe->worker_thread) { pj_thread_destroy(fe->worker_thread); fe->worker_thread = NULL; } fe->is_quitting = PJ_FALSE; /** Start Telnet **/ status = telnet_start(fe); if (status == PJ_SUCCESS) { if (fe->cfg.on_started) { (*fe->cfg.on_started)(status); } TRACE_((THIS_FILE, "Telnet Restarted")); } on_exit: return status; } PJ_DEF(pj_status_t) pj_cli_telnet_get_info(pj_cli_front_end *fe, pj_cli_telnet_info *info) { pj_sockaddr hostip; pj_status_t status; cli_telnet_fe *tfe = (cli_telnet_fe*) fe; PJ_ASSERT_RETURN(fe && (fe->type == PJ_CLI_TELNET_FRONT_END) && info, PJ_EINVAL); pj_strset(&info->ip_address, info->buf_, 0); status = pj_gethostip(pj_AF_INET(), &hostip); if (status != PJ_SUCCESS) return status; pj_sockaddr_print(&hostip, info->buf_, sizeof(info->buf_), 0); pj_strset2(&info->ip_address, info->buf_); info->port = tfe->cfg.port; return PJ_SUCCESS; }