blob: f0124ea34daea8cec6b95e497340aff90a9bf7bd [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $Id$ */
2/*
3 * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20#include <pjlib-util/cli_imp.h>
21#include <pjlib-util/cli_telnet.h>
22#include <pj/activesock.h>
23#include <pj/assert.h>
24#include <pj/errno.h>
25#include <pj/log.h>
26#include <pj/os.h>
27#include <pj/pool.h>
28#include <pj/string.h>
29#include <pj/except.h>
30#include <pjlib-util/errno.h>
31#include <pjlib-util/scanner.h>
32#include <pj/addr_resolv.h>
33#include <pj/compat/socket.h>
34
35#if (defined(PJ_WIN32) && PJ_WIN32!=0) || \
36 (defined(PJ_WIN64) && PJ_WIN64!=0) || \
37 (defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0)
38
39#define EADDRINUSE WSAEADDRINUSE
40
41#endif
42
43#define CLI_TELNET_BUF_SIZE 256
44
45#define CUT_MSG "<..data truncated..>\r\n"
46#define MAX_CUT_MSG_LEN 25
47
48#if 1
49 /* Enable some tracing */
50 #define THIS_FILE "cli_telnet.c"
51 #define TRACE_(arg) PJ_LOG(3,arg)
52#else
53 #define TRACE_(arg)
54#endif
55
56#define MAX_CLI_TELNET_OPTIONS 256
57/** Maximum retry on Telnet Restart **/
58#define MAX_RETRY_ON_TELNET_RESTART 100
59/** Minimum number of millisecond to wait before retrying to re-bind on
60 * telnet restart **/
61#define MIN_WAIT_ON_TELNET_RESTART 20
62/** Maximum number of millisecod to wait before retrying to re-bind on
63 * telnet restart **/
64#define MAX_WAIT_ON_TELNET_RESTART 1000
65
66/**
67 * This specify the state for the telnet option negotiation.
68 */
69enum cli_telnet_option_states
70{
71 OPT_DISABLE, /* Option disable */
72 OPT_ENABLE, /* Option enable */
73 OPT_EXPECT_DISABLE, /* Already send disable req, expecting resp */
74 OPT_EXPECT_ENABLE, /* Already send enable req, expecting resp */
75 OPT_EXPECT_DISABLE_REV, /* Already send disable req, expecting resp,
76 * need to send enable req */
77 OPT_EXPECT_ENABLE_REV /* Already send enable req, expecting resp,
78 * need to send disable req */
79};
80
81/**
82 * This structure contains information for telnet session option negotiation.
83 * It contains the local/peer option config and the option negotiation state.
84 */
85typedef struct cli_telnet_sess_option
86{
87 /**
88 * Local setting for the option.
89 * Default: FALSE;
90 */
91 pj_bool_t local_is_enable;
92
93 /**
94 * Remote setting for the option.
95 * Default: FALSE;
96 */
97 pj_bool_t peer_is_enable;
98
99 /**
100 * Local state of the option negotiation.
101 */
102 enum cli_telnet_option_states local_state;
103
104 /**
105 * Remote state of the option negotiation.
106 */
107 enum cli_telnet_option_states peer_state;
108} cli_telnet_sess_option;
109
110/**
111 * This specify the state of input character parsing.
112 */
113typedef enum cmd_parse_state
114{
115 ST_NORMAL,
116 ST_CR,
117 ST_ESC,
118 ST_VT100,
119 ST_IAC,
120 ST_DO,
121 ST_DONT,
122 ST_WILL,
123 ST_WONT
124} cmd_parse_state;
125
126typedef enum cli_telnet_command
127{
128 SUBNEGO_END = 240, /* End of subnegotiation parameters. */
129 NOP = 241, /* No operation. */
130 DATA_MARK = 242, /* Marker for NVT cleaning. */
131 BREAK = 243, /* Indicates that the "break" key was hit. */
132 INT_PROCESS = 244, /* Suspend, interrupt or abort the process. */
133 ABORT_OUTPUT = 245, /* Abort output, abort output stream. */
134 ARE_YOU_THERE = 246, /* Are you there. */
135 ERASE_CHAR = 247, /* Erase character, erase the current char. */
136 ERASE_LINE = 248, /* Erase line, erase the current line. */
137 GO_AHEAD = 249, /* Go ahead, other end can transmit. */
138 SUBNEGO_BEGIN = 250, /* Subnegotiation begin. */
139 WILL = 251, /* Accept the use of option. */
140 WONT = 252, /* Refuse the use of option. */
141 DO = 253, /* Request to use option. */
142 DONT = 254, /* Request to not use option. */
143 IAC = 255 /* Interpret as command */
144} cli_telnet_command;
145
146enum cli_telnet_options
147{
148 TRANSMIT_BINARY = 0, /* Transmit Binary. */
149 TERM_ECHO = 1, /* Echo. */
150 RECONNECT = 2, /* Reconnection. */
151 SUPPRESS_GA = 3, /* Suppress Go Aheah. */
152 MESSAGE_SIZE_NEGO = 4, /* Approx Message Size Negotiation. */
153 STATUS = 5, /* Status. */
154 TIMING_MARK = 6, /* Timing Mark. */
155 RTCE_OPTION = 7, /* Remote Controlled Trans and Echo. */
156 OUTPUT_LINE_WIDTH = 8, /* Output Line Width. */
157 OUTPUT_PAGE_SIZE = 9, /* Output Page Size. */
158 CR_DISPOSITION = 10, /* Carriage-Return Disposition. */
159 HORI_TABSTOPS = 11, /* Horizontal Tabstops. */
160 HORI_TAB_DISPO = 12, /* Horizontal Tab Disposition. */
161 FF_DISP0 = 13, /* Formfeed Disposition. */
162 VERT_TABSTOPS = 14, /* Vertical Tabstops. */
163 VERT_TAB_DISPO = 15, /* Vertical Tab Disposition. */
164 LF_DISP0 = 16, /* Linefeed Disposition. */
165 EXT_ASCII = 17, /* Extended ASCII. */
166 LOGOUT = 18, /* Logout. */
167 BYTE_MACRO = 19, /* Byte Macro. */
168 DE_TERMINAL = 20, /* Data Entry Terminal. */
169 SUPDUP_PROTO = 21, /* SUPDUP Protocol. */
170 SUPDUP_OUTPUT = 22, /* SUPDUP Output. */
171 SEND_LOC = 23, /* Send Location. */
172 TERM_TYPE = 24, /* Terminal Type. */
173 EOR = 25, /* End of Record. */
174 TACACS_UID = 26, /* TACACS User Identification. */
175 OUTPUT_MARKING = 27, /* Output Marking. */
176 TTYLOC = 28, /* Terminal Location Number. */
177 USE_3270_REGIME = 29, /* Telnet 3270 Regime. */
178 USE_X3_PAD = 30, /* X.3 PAD. */
179 WINDOW_SIZE = 31, /* Window Size. */
180 TERM_SPEED = 32, /* Terminal Speed. */
181 REM_FLOW_CONTROL = 33, /* Remote Flow Control. */
182 LINE_MODE = 34, /* Linemode. */
183 X_DISP_LOC = 35, /* X Display Location. */
184 ENVIRONMENT = 36, /* Environment. */
185 AUTH = 37, /* Authentication. */
186 ENCRYPTION = 38, /* Encryption Option. */
187 NEW_ENVIRONMENT = 39, /* New Environment. */
188 TN_3270E = 40, /* TN3270E. */
189 XAUTH = 41, /* XAUTH. */
190 CHARSET = 42, /* CHARSET. */
191 REM_SERIAL_PORT = 43, /* Telnet Remote Serial Port. */
192 COM_PORT_CONTROL = 44, /* Com Port Control. */
193 SUPP_LOCAL_ECHO = 45, /* Telnet Suppress Local Echo. */
194 START_TLS = 46, /* Telnet Start TLS. */
195 KERMIT = 47, /* KERMIT. */
196 SEND_URL = 48, /* SEND-URL. */
197 FWD_X = 49, /* FORWARD_X. */
198 EXT_OPTIONS = 255 /* Extended-Options-List */
199};
200
201enum terminal_cmd
202{
203 TC_ESC = 27,
204 TC_UP = 65,
205 TC_DOWN = 66,
206 TC_RIGHT = 67,
207 TC_LEFT = 68,
208 TC_END = 70,
209 TC_HOME = 72,
210 TC_CTRL_C = 3,
211 TC_CR = 13,
212 TC_BS = 8,
213 TC_TAB = 9,
214 TC_QM = 63,
215 TC_BELL = 7,
216 TC_DEL = 127
217};
218
219/**
220 * This specify the state of output character parsing.
221 */
222typedef enum out_parse_state
223{
224 OP_NORMAL,
225 OP_TYPE,
226 OP_SHORTCUT,
227 OP_CHOICE
228} out_parse_state;
229
230/**
231 * This structure contains the command line shown to the user.
232 * The telnet also needs to maintain and manage command cursor position.
233 * Due to that reason, the insert/delete character process from buffer will
234 * consider its current cursor position.
235 */
236typedef struct telnet_recv_buf {
237 /**
238 * Buffer containing the characters, NULL terminated.
239 */
240 unsigned char rbuf[PJ_CLI_MAX_CMDBUF];
241
242 /**
243 * Current length of the command line.
244 */
245 unsigned len;
246
247 /**
248 * Current cursor position.
249 */
250 unsigned cur_pos;
251} telnet_recv_buf;
252
253/**
254 * This structure contains the command history executed by user.
255 * Besides storing the command history, it is necessary to be able
256 * to browse it.
257 */
258typedef struct cmd_history
259{
260 PJ_DECL_LIST_MEMBER(struct cmd_history);
261 pj_str_t command;
262} cmd_history;
263
264typedef struct cli_telnet_sess
265{
266 pj_cli_sess base;
267 pj_pool_t *pool;
268 pj_activesock_t *asock;
269 pj_bool_t authorized;
270 pj_ioqueue_op_key_t op_key;
271 pj_mutex_t *smutex;
272 cmd_parse_state parse_state;
273 cli_telnet_sess_option telnet_option[MAX_CLI_TELNET_OPTIONS];
274 cmd_history *history;
275 cmd_history *active_history;
276
277 telnet_recv_buf *rcmd;
278 unsigned char buf[CLI_TELNET_BUF_SIZE + MAX_CUT_MSG_LEN];
279 unsigned buf_len;
280} cli_telnet_sess;
281
282typedef struct cli_telnet_fe
283{
284 pj_cli_front_end base;
285 pj_pool_t *pool;
286 pj_cli_telnet_cfg cfg;
287 pj_bool_t own_ioqueue;
288 pj_cli_sess sess_head;
289
290 pj_activesock_t *asock;
291 pj_thread_t *worker_thread;
292 pj_bool_t is_quitting;
293 pj_mutex_t *mutex;
294} cli_telnet_fe;
295
296/* Forward Declaration */
297static pj_status_t telnet_sess_send2(cli_telnet_sess *sess,
298 const unsigned char *str, int len);
299
300static pj_status_t telnet_sess_send(cli_telnet_sess *sess,
301 const pj_str_t *str);
302
303static pj_status_t telnet_start(cli_telnet_fe *fe);
304static pj_status_t telnet_restart(cli_telnet_fe *tfe);
305
306/**
307 * Return the number of characters between the current cursor position
308 * to the end of line.
309 */
310static unsigned recv_buf_right_len(telnet_recv_buf *recv_buf)
311{
312 return (recv_buf->len - recv_buf->cur_pos);
313}
314
315/**
316 * Insert character to the receive buffer.
317 */
318static pj_bool_t recv_buf_insert(telnet_recv_buf *recv_buf,
319 unsigned char *data)
320{
321 if (recv_buf->len+1 >= PJ_CLI_MAX_CMDBUF) {
322 return PJ_FALSE;
323 } else {
324 if (*data == '\t' || *data == '?' || *data == '\r') {
325 /* Always insert to the end of line */
326 recv_buf->rbuf[recv_buf->len] = *data;
327 } else {
328 /* Insert based on the current cursor pos */
329 unsigned cur_pos = recv_buf->cur_pos;
330 unsigned rlen = recv_buf_right_len(recv_buf);
331 if (rlen > 0) {
332 /* Shift right characters */
333 pj_memmove(&recv_buf->rbuf[cur_pos+1],
334 &recv_buf->rbuf[cur_pos],
335 rlen+1);
336 }
337 recv_buf->rbuf[cur_pos] = *data;
338 }
339 ++recv_buf->cur_pos;
340 ++recv_buf->len;
341 recv_buf->rbuf[recv_buf->len] = 0;
342 }
343 return PJ_TRUE;
344}
345
346/**
347 * Delete character on the previous cursor position of the receive buffer.
348 */
349static pj_bool_t recv_buf_backspace(telnet_recv_buf *recv_buf)
350{
351 if ((recv_buf->cur_pos == 0) || (recv_buf->len == 0)) {
352 return PJ_FALSE;
353 } else {
354 unsigned rlen = recv_buf_right_len(recv_buf);
355 if (rlen) {
356 unsigned cur_pos = recv_buf->cur_pos;
357 /* Shift left characters */
358 pj_memmove(&recv_buf->rbuf[cur_pos-1], &recv_buf->rbuf[cur_pos],
359 rlen);
360 }
361 --recv_buf->cur_pos;
362 --recv_buf->len;
363 recv_buf->rbuf[recv_buf->len] = 0;
364 }
365 return PJ_TRUE;
366}
367
368static int compare_str(void *value, const pj_list_type *nd)
369{
370 cmd_history *node = (cmd_history*)nd;
371 return (pj_strcmp((pj_str_t *)value, &node->command));
372}
373
374/**
375 * Insert the command to history. If the entered command is not on the list,
376 * a new entry will be created. All entered command will be moved to
377 * the first entry of the history.
378 */
379static pj_status_t insert_history(cli_telnet_sess *sess,
380 char *cmd_val)
381{
382 cmd_history *in_history;
383 pj_str_t cmd;
384 cmd.ptr = cmd_val;
385 cmd.slen = pj_ansi_strlen(cmd_val)-1;
386
387 if (cmd.slen == 0)
388 return PJ_SUCCESS;
389
390 PJ_ASSERT_RETURN(sess, PJ_EINVAL);
391
392 /* Find matching history */
393 in_history = pj_list_search(sess->history, (void*)&cmd, compare_str);
394 if (!in_history) {
395 if (pj_list_size(sess->history) < PJ_CLI_MAX_CMD_HISTORY) {
396 char *data_history;
397 in_history = PJ_POOL_ZALLOC_T(sess->pool, cmd_history);
398 pj_list_init(in_history);
399 data_history = (char *)pj_pool_calloc(sess->pool,
400 sizeof(char), PJ_CLI_MAX_CMDBUF);
401 in_history->command.ptr = data_history;
402 in_history->command.slen = 0;
403 } else {
404 /* Get the oldest history */
405 in_history = sess->history->prev;
406 }
407 } else {
408 pj_list_insert_nodes_after(in_history->prev, in_history->next);
409 }
410 pj_strcpy(&in_history->command, pj_strtrim(&cmd));
411 pj_list_push_front(sess->history, in_history);
412 sess->active_history = sess->history;
413
414 return PJ_SUCCESS;
415}
416
417/**
418 * Get the next or previous history of the shown/active history.
419 */
420static pj_str_t* get_prev_history(cli_telnet_sess *sess, pj_bool_t is_forward)
421{
422 pj_str_t *retval;
423 pj_size_t history_size;
424 cmd_history *node;
425 cmd_history *root;
426
427 PJ_ASSERT_RETURN(sess, NULL);
428
429 node = sess->active_history;
430 root = sess->history;
431 history_size = pj_list_size(sess->history);
432
433 if (history_size == 0) {
434 return NULL;
435 } else {
436 if (is_forward) {
437 node = (node->next==root)?node->next->next:node->next;
438 } else {
439 node = (node->prev==root)?node->prev->prev:node->prev;
440 }
441 retval = &node->command;
442 sess->active_history = node;
443 }
444 return retval;
445}
446
447/*
448 * This method is used to send option negotiation command.
449 * The commands dealing with option negotiation are
450 * three byte sequences, the third byte being the code for the option
451 * referenced - (RFC-854).
452 */
453static pj_bool_t send_telnet_cmd(cli_telnet_sess *sess,
454 cli_telnet_command cmd,
455 unsigned char option)
456{
457 unsigned char buf[3];
458 PJ_ASSERT_RETURN(sess, PJ_FALSE);
459
460 buf[0] = IAC;
461 buf[1] = cmd;
462 buf[2] = option;
463 telnet_sess_send2(sess, buf, 3);
464
465 return PJ_TRUE;
466}
467
468/**
469 * This method will handle sending telnet's ENABLE option negotiation.
470 * For local option: send WILL.
471 * For remote option: send DO.
472 * This method also handle the state transition of the ENABLE
473 * negotiation process.
474 */
475static pj_bool_t send_enable_option(cli_telnet_sess *sess,
476 pj_bool_t is_local,
477 unsigned char option)
478{
479 cli_telnet_sess_option *sess_option;
480 enum cli_telnet_option_states *state;
481 PJ_ASSERT_RETURN(sess, PJ_FALSE);
482
483 sess_option = &sess->telnet_option[option];
484 state = is_local?(&sess_option->local_state):(&sess_option->peer_state);
485 switch (*state) {
486 case OPT_ENABLE:
487 /* Ignore if already enabled */
488 break;
489 case OPT_DISABLE:
490 *state = OPT_EXPECT_ENABLE;
491 send_telnet_cmd(sess, (is_local?WILL:DO), option);
492 break;
493 case OPT_EXPECT_ENABLE:
494 *state = OPT_DISABLE;
495 break;
496 case OPT_EXPECT_DISABLE:
497 *state = OPT_EXPECT_DISABLE_REV;
498 break;
499 case OPT_EXPECT_ENABLE_REV:
500 *state = OPT_EXPECT_ENABLE;
501 break;
502 case OPT_EXPECT_DISABLE_REV:
503 *state = OPT_DISABLE;
504 break;
505 default:
506 return PJ_FALSE;
507 }
508 return PJ_TRUE;
509}
510
511static pj_bool_t send_cmd_do(cli_telnet_sess *sess,
512 unsigned char option)
513{
514 return send_enable_option(sess, PJ_FALSE, option);
515}
516
517static pj_bool_t send_cmd_will(cli_telnet_sess *sess,
518 unsigned char option)
519{
520 return send_enable_option(sess, PJ_TRUE, option);
521}
522
523/**
524 * This method will handle receiving telnet's ENABLE option negotiation.
525 * This method also handle the state transition of the ENABLE
526 * negotiation process.
527 */
528static pj_bool_t receive_enable_option(cli_telnet_sess *sess,
529 pj_bool_t is_local,
530 unsigned char option)
531{
532 cli_telnet_sess_option *sess_opt;
533 enum cli_telnet_option_states *state;
534 pj_bool_t opt_ena;
535 PJ_ASSERT_RETURN(sess, PJ_FALSE);
536
537 sess_opt = &sess->telnet_option[option];
538 state = is_local?(&sess_opt->local_state):(&sess_opt->peer_state);
539 opt_ena = is_local?sess_opt->local_is_enable:sess_opt->peer_is_enable;
540 switch (*state) {
541 case OPT_ENABLE:
542 /* Ignore if already enabled */
543 break;
544 case OPT_DISABLE:
545 if (opt_ena) {
546 *state = OPT_ENABLE;
547 send_telnet_cmd(sess, is_local?WILL:DO, option);
548 } else {
549 send_telnet_cmd(sess, is_local?WONT:DONT, option);
550 }
551 break;
552 case OPT_EXPECT_ENABLE:
553 *state = OPT_ENABLE;
554 break;
555 case OPT_EXPECT_DISABLE:
556 *state = OPT_DISABLE;
557 break;
558 case OPT_EXPECT_ENABLE_REV:
559 *state = OPT_EXPECT_DISABLE;
560 send_telnet_cmd(sess, is_local?WONT:DONT, option);
561 break;
562 case OPT_EXPECT_DISABLE_REV:
563 *state = OPT_EXPECT_DISABLE;
564 break;
565 default:
566 return PJ_FALSE;
567 }
568 return PJ_TRUE;
569}
570
571/**
572 * This method will handle receiving telnet's DISABLE option negotiation.
573 * This method also handle the state transition of the DISABLE
574 * negotiation process.
575 */
576static pj_bool_t receive_disable_option(cli_telnet_sess *sess,
577 pj_bool_t is_local,
578 unsigned char option)
579{
580 cli_telnet_sess_option *sess_opt;
581 enum cli_telnet_option_states *state;
582
583 PJ_ASSERT_RETURN(sess, PJ_FALSE);
584
585 sess_opt = &sess->telnet_option[option];
586 state = is_local?(&sess_opt->local_state):(&sess_opt->peer_state);
587
588 switch (*state) {
589 case OPT_ENABLE:
590 /* Disabling option always need to be accepted */
591 *state = OPT_DISABLE;
592 send_telnet_cmd(sess, is_local?WONT:DONT, option);
593 break;
594 case OPT_DISABLE:
595 /* Ignore if already enabled */
596 break;
597 case OPT_EXPECT_ENABLE:
598 case OPT_EXPECT_DISABLE:
599 *state = OPT_DISABLE;
600 break;
601 case OPT_EXPECT_ENABLE_REV:
602 *state = OPT_DISABLE;
603 send_telnet_cmd(sess, is_local?WONT:DONT, option);
604 break;
605 case OPT_EXPECT_DISABLE_REV:
606 *state = OPT_EXPECT_ENABLE;
607 send_telnet_cmd(sess, is_local?WILL:DO, option);
608 break;
609 default:
610 return PJ_FALSE;
611 }
612 return PJ_TRUE;
613}
614
615static pj_bool_t receive_do(cli_telnet_sess *sess, unsigned char option)
616{
617 return receive_enable_option(sess, PJ_TRUE, option);
618}
619
620static pj_bool_t receive_dont(cli_telnet_sess *sess, unsigned char option)
621{
622 return receive_disable_option(sess, PJ_TRUE, option);
623}
624
625static pj_bool_t receive_will(cli_telnet_sess *sess, unsigned char option)
626{
627 return receive_enable_option(sess, PJ_FALSE, option);
628}
629
630static pj_bool_t receive_wont(cli_telnet_sess *sess, unsigned char option)
631{
632 return receive_disable_option(sess, PJ_FALSE, option);
633}
634
635static void set_local_option(cli_telnet_sess *sess,
636 unsigned char option,
637 pj_bool_t enable)
638{
639 sess->telnet_option[option].local_is_enable = enable;
640}
641
642static void set_peer_option(cli_telnet_sess *sess,
643 unsigned char option,
644 pj_bool_t enable)
645{
646 sess->telnet_option[option].peer_is_enable = enable;
647}
648
649static pj_bool_t is_local_option_state_ena(cli_telnet_sess *sess,
650 unsigned char option)
651{
652 return (sess->telnet_option[option].local_state == OPT_ENABLE);
653}
654
655static void send_return_key(cli_telnet_sess *sess)
656{
657 telnet_sess_send2(sess, (unsigned char*)"\r\n", 2);
658}
659
660static void send_bell(cli_telnet_sess *sess) {
661 static const unsigned char bell = 0x07;
662 telnet_sess_send2(sess, &bell, 1);
663}
664
665static void send_prompt_str(cli_telnet_sess *sess)
666{
667 pj_str_t send_data;
668 char data_str[128];
669 cli_telnet_fe *fe = (cli_telnet_fe *)sess->base.fe;
670
671 send_data.ptr = data_str;
672 send_data.slen = 0;
673
674 pj_strcat(&send_data, &fe->cfg.prompt_str);
675
676 telnet_sess_send(sess, &send_data);
677}
678
679/*
680 * This method is used to send error message to client, including
681 * the error position of the source command.
682 */
683static void send_err_arg(cli_telnet_sess *sess,
684 const pj_cli_exec_info *info,
685 const pj_str_t *msg,
686 pj_bool_t with_return,
687 pj_bool_t with_last_cmd)
688{
689 pj_str_t send_data;
690 char data_str[256];
691 pj_size_t len;
692 unsigned i;
693 cli_telnet_fe *fe = (cli_telnet_fe *)sess->base.fe;
694
695 send_data.ptr = data_str;
696 send_data.slen = 0;
697
698 if (with_return)
699 pj_strcat2(&send_data, "\r\n");
700
701 len = fe->cfg.prompt_str.slen + info->err_pos;
702
703 /* Set the error pointer mark */
704 for (i=0;i<len;++i) {
705 pj_strcat2(&send_data, " ");
706 }
707 pj_strcat2(&send_data, "^");
708 pj_strcat2(&send_data, "\r\n");
709 pj_strcat(&send_data, msg);
710 pj_strcat(&send_data, &fe->cfg.prompt_str);
711 if (with_last_cmd)
712 pj_strcat2(&send_data, (char *)sess->rcmd->rbuf);
713
714 telnet_sess_send(sess, &send_data);
715}
716
717static void send_inv_arg(cli_telnet_sess *sess,
718 const pj_cli_exec_info *info,
719 pj_bool_t with_return,
720 pj_bool_t with_last_cmd)
721{
722 static const pj_str_t ERR_MSG = {"%Error : Invalid Arguments\r\n", 28};
723 send_err_arg(sess, info, &ERR_MSG, with_return, with_last_cmd);
724}
725
726static void send_too_many_arg(cli_telnet_sess *sess,
727 const pj_cli_exec_info *info,
728 pj_bool_t with_return,
729 pj_bool_t with_last_cmd)
730{
731 static const pj_str_t ERR_MSG = {"%Error : Too Many Arguments\r\n", 29};
732 send_err_arg(sess, info, &ERR_MSG, with_return, with_last_cmd);
733}
734
735static void send_hint_arg(cli_telnet_sess *sess,
736 pj_str_t *send_data,
737 const pj_str_t *desc,
738 pj_ssize_t cmd_len,
739 pj_ssize_t max_len)
740{
741 if ((desc) && (desc->slen > 0)) {
742 int j;
743
744 for (j=0;j<(max_len-cmd_len);++j) {
745 pj_strcat2(send_data, " ");
746 }
747 pj_strcat2(send_data, " ");
748 pj_strcat(send_data, desc);
749 telnet_sess_send(sess, send_data);
750 send_data->slen = 0;
751 }
752}
753
754/*
755 * This method is used to notify to the client that the entered command
756 * is ambiguous. It will show the matching command as the hint information.
757 */
758static void send_ambi_arg(cli_telnet_sess *sess,
759 const pj_cli_exec_info *info,
760 pj_bool_t with_return,
761 pj_bool_t with_last_cmd)
762{
763 unsigned i;
764 pj_size_t len;
765 pj_str_t send_data;
766 char data[1028];
767 cli_telnet_fe *fe = (cli_telnet_fe *)sess->base.fe;
768 const pj_cli_hint_info *hint = info->hint;
769 out_parse_state parse_state = OP_NORMAL;
770 pj_ssize_t max_length = 0;
771 pj_ssize_t cmd_length = 0;
772 const pj_str_t *cmd_desc = 0;
773 static const pj_str_t sc_type = {"sc", 2};
774 static const pj_str_t choice_type = {"choice", 6};
775 send_data.ptr = data;
776 send_data.slen = 0;
777
778 if (with_return)
779 pj_strcat2(&send_data, "\r\n");
780
781 len = fe->cfg.prompt_str.slen + info->err_pos;
782
783 for (i=0;i<len;++i) {
784 pj_strcat2(&send_data, " ");
785 }
786 pj_strcat2(&send_data, "^");
787 /* Get the max length of the command name */
788 for (i=0;i<info->hint_cnt;++i) {
789 if ((&hint[i].type) && (hint[i].type.slen > 0)) {
790 if (pj_stricmp(&hint[i].type, &sc_type) == 0) {
791 if ((i > 0) && (!pj_stricmp(&hint[i-1].desc, &hint[i].desc))) {
792 cmd_length += (hint[i].name.slen + 3);
793 } else {
794 cmd_length = hint[i].name.slen;
795 }
796 } else {
797 cmd_length = hint[i].name.slen;
798 }
799 } else {
800 cmd_length = hint[i].name.slen;
801 }
802
803 if (cmd_length > max_length) {
804 max_length = cmd_length;
805 }
806 }
807
808 cmd_length = 0;
809 /* Build hint information */
810 for (i=0;i<info->hint_cnt;++i) {
811 if ((&hint[i].type) && (hint[i].type.slen > 0)) {
812 if (pj_stricmp(&hint[i].type, &sc_type) == 0) {
813 parse_state = OP_SHORTCUT;
814 } else if (pj_stricmp(&hint[i].type, &choice_type) == 0) {
815 parse_state = OP_CHOICE;
816 } else {
817 parse_state = OP_TYPE;
818 }
819 } else {
820 parse_state = OP_NORMAL;
821 }
822
823 if (parse_state != OP_SHORTCUT) {
824 pj_strcat2(&send_data, "\r\n ");
825 cmd_length = hint[i].name.slen;
826 }
827
828 switch (parse_state) {
829 case OP_CHOICE:
830 /* Format : "[Choice Value] description" */
831 pj_strcat2(&send_data, "[");
832 pj_strcat(&send_data, &hint[i].name);
833 pj_strcat2(&send_data, "]");
834 break;
835 case OP_TYPE:
836 /* Format : "<Argument Type> description" */
837 pj_strcat2(&send_data, "<");
838 pj_strcat(&send_data, &hint[i].name);
839 pj_strcat2(&send_data, ">");
840 break;
841 case OP_SHORTCUT:
842 /* Format : "Command | sc | description" */
843 {
844 cmd_length += hint[i].name.slen;
845 if ((i > 0) && (!pj_stricmp(&hint[i-1].desc, &hint[i].desc))) {
846 pj_strcat2(&send_data, " | ");
847 cmd_length += 3;
848 } else {
849 pj_strcat2(&send_data, "\r\n ");
850 }
851 pj_strcat(&send_data, &hint[i].name);
852 }
853 break;
854 default:
855 /* Command */
856 pj_strcat(&send_data, &hint[i].name);
857 cmd_desc = &hint[i].desc;
858 break;
859 }
860
861 if ((parse_state == OP_TYPE) || (parse_state == OP_CHOICE) ||
862 ((i+1) >= info->hint_cnt) ||
863 (pj_strncmp(&hint[i].desc, &hint[i+1].desc, hint[i].desc.slen)))
864 {
865 /* Add description info */
866 send_hint_arg(sess, &send_data,
867 &hint[i].desc, cmd_length,
868 max_length);
869
870 cmd_length = 0;
871 }
872 }
873 pj_strcat2(&send_data, "\r\n");
874 pj_strcat(&send_data, &fe->cfg.prompt_str);
875 if (with_last_cmd)
876 pj_strcat2(&send_data, (char *)sess->rcmd->rbuf);
877
878 telnet_sess_send(sess, &send_data);
879}
880
881/*
882 * This method is to send command completion of the entered command.
883 */
884static void send_comp_arg(cli_telnet_sess *sess,
885 pj_cli_exec_info *info)
886{
887 pj_str_t send_data;
888 char data[128];
889
890 pj_strcat2(&info->hint[0].name, " ");
891
892 send_data.ptr = data;
893 send_data.slen = 0;
894
895 pj_strcat(&send_data, &info->hint[0].name);
896
897 telnet_sess_send(sess, &send_data);
898}
899
900/*
901 * This method is to process the alfa numeric character sent by client.
902 */
903static pj_bool_t handle_alfa_num(cli_telnet_sess *sess, unsigned char *data)
904{
905 if (is_local_option_state_ena(sess, TERM_ECHO)) {
906 if (recv_buf_right_len(sess->rcmd) > 0) {
907 /* Cursor is not at EOL, insert character */
908 unsigned char echo[5] = {0x1b, 0x5b, 0x31, 0x40, 0x00};
909 echo[4] = *data;
910 telnet_sess_send2(sess, echo, 5);
911 } else {
912 /* Append character */
913 telnet_sess_send2(sess, data, 1);
914 }
915 return PJ_TRUE;
916 }
917 return PJ_FALSE;
918}
919
920/*
921 * This method is to process the backspace character sent by client.
922 */
923static pj_bool_t handle_backspace(cli_telnet_sess *sess, unsigned char *data)
924{
925 unsigned rlen = recv_buf_right_len(sess->rcmd);
926 if (recv_buf_backspace(sess->rcmd)) {
927 if (rlen) {
928 /*
929 * Cursor is not at the end of line, move the characters
930 * after the cursor to left
931 */
932 unsigned char echo[5] = {0x00, 0x1b, 0x5b, 0x31, 0x50};
933 echo[0] = *data;
934 telnet_sess_send2(sess, echo, 5);
935 } else {
936 const static unsigned char echo[3] = {0x08, 0x20, 0x08};
937 telnet_sess_send2(sess, echo, 3);
938 }
939 return PJ_TRUE;
940 }
941 return PJ_FALSE;
942}
943
944/*
945 * Syntax error handler for parser.
946 */
947static void on_syntax_error(pj_scanner *scanner)
948{
949 PJ_UNUSED_ARG(scanner);
950 PJ_THROW(PJ_EINVAL);
951}
952
953/*
954 * This method is to process the backspace character sent by client.
955 */
956static pj_status_t get_last_token(pj_str_t *cmd, pj_str_t *str)
957{
958 pj_scanner scanner;
959 PJ_USE_EXCEPTION;
960 pj_scan_init(&scanner, cmd->ptr, cmd->slen, PJ_SCAN_AUTOSKIP_WS,
961 &on_syntax_error);
962 PJ_TRY {
963 while (!pj_scan_is_eof(&scanner)) {
964 pj_scan_get_until_chr(&scanner, " \t\r\n", str);
965 }
966 }
967 PJ_CATCH_ANY {
968 pj_scan_fini(&scanner);
969 return PJ_GET_EXCEPTION();
970 }
971 PJ_END;
972 return PJ_SUCCESS;
973}
974
975/*
976 * This method is to process the tab character sent by client.
977 */
978static pj_bool_t handle_tab(cli_telnet_sess *sess)
979{
980 pj_status_t status;
981 pj_bool_t retval = PJ_TRUE;
982 unsigned len;
983
984 pj_pool_t *pool;
985 pj_cli_cmd_val *cmd_val;
986 pj_cli_exec_info info;
987 pool = pj_pool_create(sess->pool->factory, "handle_tab",
988 PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC,
989 NULL);
990
991 cmd_val = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_val);
992
993 status = pj_cli_sess_parse(&sess->base, (char *)&sess->rcmd->rbuf, cmd_val,
994 pool, &info);
995
996 len = (unsigned)pj_ansi_strlen((char *)sess->rcmd->rbuf);
997
998 switch (status) {
999 case PJ_CLI_EINVARG:
1000 send_inv_arg(sess, &info, PJ_TRUE, PJ_TRUE);
1001 break;
1002 case PJ_CLI_ETOOMANYARGS:
1003 send_too_many_arg(sess, &info, PJ_TRUE, PJ_TRUE);
1004 break;
1005 case PJ_CLI_EMISSINGARG:
1006 case PJ_CLI_EAMBIGUOUS:
1007 send_ambi_arg(sess, &info, PJ_TRUE, PJ_TRUE);
1008 break;
1009 case PJ_SUCCESS:
1010 if (len > sess->rcmd->cur_pos)
1011 {
1012 /* Send the cursor to EOL */
1013 unsigned rlen = len - sess->rcmd->cur_pos+1;
1014 unsigned char *data_sent = &sess->rcmd->rbuf[sess->rcmd->cur_pos-1];
1015 telnet_sess_send2(sess, data_sent, rlen);
1016 }
1017 if (info.hint_cnt > 0) {
1018 /* Complete command */
1019 pj_str_t cmd = pj_str((char *)sess->rcmd->rbuf);
1020 pj_str_t last_token;
1021
1022 if (get_last_token(&cmd, &last_token) == PJ_SUCCESS) {
1023 /* Hint contains the match to the last command entered */
1024 pj_str_t *hint_info = &info.hint[0].name;
1025 pj_strtrim(&last_token);
1026 if (hint_info->slen >= last_token.slen) {
1027 hint_info->slen -= last_token.slen;
1028 pj_memmove(hint_info->ptr,
1029 &hint_info->ptr[last_token.slen],
1030 hint_info->slen);
1031 }
1032 send_comp_arg(sess, &info);
1033
1034 pj_memcpy(&sess->rcmd->rbuf[len], info.hint[0].name.ptr,
1035 info.hint[0].name.slen);
1036
1037 len += (unsigned)info.hint[0].name.slen;
1038 sess->rcmd->rbuf[len] = 0;
1039 }
1040 } else {
1041 retval = PJ_FALSE;
1042 }
1043 break;
1044 }
1045 sess->rcmd->len = len;
1046 sess->rcmd->cur_pos = sess->rcmd->len;
1047
1048 pj_pool_release(pool);
1049 return retval;
1050}
1051
1052/*
1053 * This method is to process the return character sent by client.
1054 */
1055static pj_bool_t handle_return(cli_telnet_sess *sess)
1056{
1057 pj_status_t status;
1058 pj_bool_t retval = PJ_TRUE;
1059
1060 pj_pool_t *pool;
1061 pj_cli_exec_info info;
1062
1063 send_return_key(sess);
1064 insert_history(sess, (char *)&sess->rcmd->rbuf);
1065
1066 pool = pj_pool_create(sess->pool->factory, "handle_return",
1067 PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC,
1068 NULL);
1069
1070 status = pj_cli_sess_exec(&sess->base, (char *)&sess->rcmd->rbuf,
1071 pool, &info);
1072
1073 switch (status) {
1074 case PJ_CLI_EINVARG:
1075 send_inv_arg(sess, &info, PJ_FALSE, PJ_FALSE);
1076 break;
1077 case PJ_CLI_ETOOMANYARGS:
1078 send_too_many_arg(sess, &info, PJ_FALSE, PJ_FALSE);
1079 break;
1080 case PJ_CLI_EAMBIGUOUS:
1081 case PJ_CLI_EMISSINGARG:
1082 send_ambi_arg(sess, &info, PJ_FALSE, PJ_FALSE);
1083 break;
1084 case PJ_CLI_EEXIT:
1085 retval = PJ_FALSE;
1086 break;
1087 case PJ_SUCCESS:
1088 send_prompt_str(sess);
1089 break;
1090 }
1091 if (retval) {
1092 sess->rcmd->rbuf[0] = 0;
1093 sess->rcmd->len = 0;
1094 sess->rcmd->cur_pos = sess->rcmd->len;
1095 }
1096
1097 pj_pool_release(pool);
1098 return retval;
1099}
1100
1101/*
1102 * This method is to process the right key character sent by client.
1103 */
1104static pj_bool_t handle_right_key(cli_telnet_sess *sess)
1105{
1106 if (recv_buf_right_len(sess->rcmd)) {
1107 unsigned char *data = &sess->rcmd->rbuf[sess->rcmd->cur_pos++];
1108 telnet_sess_send2(sess, data, 1);
1109 return PJ_TRUE;
1110 }
1111 return PJ_FALSE;
1112}
1113
1114/*
1115 * This method is to process the left key character sent by client.
1116 */
1117static pj_bool_t handle_left_key(cli_telnet_sess *sess)
1118{
1119 static const unsigned char move_cursor_left = 0x08;
1120 if (sess->rcmd->cur_pos) {
1121 telnet_sess_send2(sess, &move_cursor_left, 1);
1122 --sess->rcmd->cur_pos;
1123 return PJ_TRUE;
1124 }
1125 return PJ_FALSE;
1126}
1127
1128/*
1129 * This method is to process the up/down key character sent by client.
1130 */
1131static pj_bool_t handle_up_down(cli_telnet_sess *sess, pj_bool_t is_up)
1132{
1133 pj_str_t *history;
1134
1135 PJ_ASSERT_RETURN(sess, PJ_FALSE);
1136
1137 history = get_prev_history(sess, is_up);
1138 if (history) {
1139 pj_str_t send_data;
1140 char str[PJ_CLI_MAX_CMDBUF];
1141 enum {
1142 MOVE_CURSOR_LEFT = 0x08,
1143 CLEAR_CHAR = 0x20
1144 };
1145 send_data.ptr = str;
1146 send_data.slen = 0;
1147
1148 /* Move cursor position to the beginning of line */
1149 if (sess->rcmd->cur_pos > 0) {
1150 pj_memset(send_data.ptr, MOVE_CURSOR_LEFT, sess->rcmd->cur_pos);
1151 send_data.slen = sess->rcmd->cur_pos;
1152 }
1153
1154 if (sess->rcmd->len > (unsigned)history->slen) {
1155 /* Clear the command currently shown*/
1156 unsigned buf_len = sess->rcmd->len;
1157 pj_memset(&send_data.ptr[send_data.slen], CLEAR_CHAR, buf_len);
1158 send_data.slen += buf_len;
1159
1160 /* Move cursor position to the beginning of line */
1161 pj_memset(&send_data.ptr[send_data.slen], MOVE_CURSOR_LEFT,
1162 buf_len);
1163 send_data.slen += buf_len;
1164 }
1165 /* Send data */
1166 pj_strcat(&send_data, history);
1167 telnet_sess_send(sess, &send_data);
1168 pj_ansi_strncpy((char*)&sess->rcmd->rbuf, history->ptr, history->slen);
1169 sess->rcmd->rbuf[history->slen] = 0;
1170 sess->rcmd->len = (unsigned)history->slen;
1171 sess->rcmd->cur_pos = sess->rcmd->len;
1172 return PJ_TRUE;
1173 }
1174 return PJ_FALSE;
1175}
1176
1177static pj_status_t process_vt100_cmd(cli_telnet_sess *sess,
1178 unsigned char *cmd)
1179{
1180 pj_status_t status = PJ_TRUE;
1181 switch (*cmd) {
1182 case TC_ESC:
1183 break;
1184 case TC_UP:
1185 status = handle_up_down(sess, PJ_TRUE);
1186 break;
1187 case TC_DOWN:
1188 status = handle_up_down(sess, PJ_FALSE);
1189 break;
1190 case TC_RIGHT:
1191 status = handle_right_key(sess);
1192 break;
1193 case TC_LEFT:
1194 status = handle_left_key(sess);
1195 break;
1196 case TC_END:
1197 break;
1198 case TC_HOME:
1199 break;
1200 case TC_CTRL_C:
1201 break;
1202 case TC_CR:
1203 break;
1204 case TC_BS:
1205 break;
1206 case TC_TAB:
1207 break;
1208 case TC_QM:
1209 break;
1210 case TC_BELL:
1211 break;
1212 case TC_DEL:
1213 break;
1214 };
1215 return status;
1216}
1217
1218PJ_DEF(void) pj_cli_telnet_cfg_default(pj_cli_telnet_cfg *param)
1219{
1220 pj_assert(param);
1221
1222 pj_bzero(param, sizeof(*param));
1223 param->port = PJ_CLI_TELNET_PORT;
1224 param->log_level = PJ_CLI_TELNET_LOG_LEVEL;
1225}
1226
1227/*
1228 * Send a message to a telnet session
1229 */
1230static pj_status_t telnet_sess_send(cli_telnet_sess *sess,
1231 const pj_str_t *str)
1232{
1233 pj_ssize_t sz;
1234 pj_status_t status = PJ_SUCCESS;
1235
1236 sz = str->slen;
1237 if (!sz)
1238 return PJ_SUCCESS;
1239
1240 pj_mutex_lock(sess->smutex);
1241
1242 if (sess->buf_len == 0)
1243 status = pj_activesock_send(sess->asock, &sess->op_key,
1244 str->ptr, &sz, 0);
1245 /* If we cannot send now, append it at the end of the buffer
1246 * to be sent later.
1247 */
1248 if (sess->buf_len > 0 ||
1249 (status != PJ_SUCCESS && status != PJ_EPENDING))
1250 {
1251 int clen = (int)sz;
1252
1253 if (sess->buf_len + clen > CLI_TELNET_BUF_SIZE)
1254 clen = CLI_TELNET_BUF_SIZE - sess->buf_len;
1255 if (clen > 0)
1256 pj_memmove(sess->buf + sess->buf_len, str->ptr, clen);
1257 if (clen < sz) {
1258 pj_ansi_snprintf((char *)sess->buf + CLI_TELNET_BUF_SIZE,
1259 MAX_CUT_MSG_LEN, CUT_MSG);
1260 sess->buf_len = (unsigned)(CLI_TELNET_BUF_SIZE +
1261 pj_ansi_strlen((char *)sess->buf+
1262 CLI_TELNET_BUF_SIZE));
1263 } else
1264 sess->buf_len += clen;
1265 } else if (status == PJ_SUCCESS && sz < str->slen) {
1266 pj_mutex_unlock(sess->smutex);
1267 return PJ_CLI_ETELNETLOST;
1268 }
1269
1270 pj_mutex_unlock(sess->smutex);
1271
1272 return PJ_SUCCESS;
1273}
1274
1275/*
1276 * Send a message to a telnet session with formatted text
1277 * (add single linefeed character with carriage return)
1278 */
1279static pj_status_t telnet_sess_send_with_format(cli_telnet_sess *sess,
1280 const pj_str_t *str)
1281{
1282 pj_scanner scanner;
1283 pj_str_t out_str;
1284 static const pj_str_t CR_LF = {("\r\n"), 2};
1285 int str_len = 0;
1286 char *str_begin = 0;
1287
1288 PJ_USE_EXCEPTION;
1289
1290 pj_scan_init(&scanner, str->ptr, str->slen,
1291 PJ_SCAN_AUTOSKIP_WS, &on_syntax_error);
1292
1293 str_begin = scanner.begin;
1294
1295 PJ_TRY {
1296 while (!pj_scan_is_eof(&scanner)) {
1297 pj_scan_get_until_ch(&scanner, '\n', &out_str);
1298 str_len = (int)(scanner.curptr - str_begin);
1299 if (*scanner.curptr == '\n') {
1300 if ((str_len > 1) && (out_str.ptr[str_len-2] == '\r'))
1301 {
1302 continue;
1303 } else {
1304 int str_pos = (int)(str_begin - scanner.begin);
1305
1306 if (str_len > 0) {
1307 pj_str_t s;
1308 pj_strset(&s, &str->ptr[str_pos], str_len);
1309 telnet_sess_send(sess, &s);
1310 }
1311 telnet_sess_send(sess, &CR_LF);
1312
1313 if (!pj_scan_is_eof(&scanner)) {
1314 pj_scan_advance_n(&scanner, 1, PJ_TRUE);
1315 str_begin = scanner.curptr;
1316 }
1317 }
1318 } else {
1319 pj_str_t s;
1320 int str_pos = (int)(str_begin - scanner.begin);
1321
1322 pj_strset(&s, &str->ptr[str_pos], str_len);
1323 telnet_sess_send(sess, &s);
1324 }
1325 }
1326 }
1327 PJ_CATCH_ANY {
1328 pj_scan_fini(&scanner);
1329 return (PJ_GET_EXCEPTION());
1330 }
1331 PJ_END;
1332
1333 return PJ_SUCCESS;
1334}
1335
1336static pj_status_t telnet_sess_send2(cli_telnet_sess *sess,
1337 const unsigned char *str, int len)
1338{
1339 pj_str_t s;
1340
1341 pj_strset(&s, (char *)str, len);
1342 return telnet_sess_send(sess, &s);
1343}
1344
1345static void telnet_sess_destroy(pj_cli_sess *sess)
1346{
1347 cli_telnet_sess *tsess = (cli_telnet_sess *)sess;
1348 pj_mutex_t *mutex = ((cli_telnet_fe *)sess->fe)->mutex;
1349
1350 pj_mutex_lock(mutex);
1351 pj_list_erase(sess);
1352 pj_mutex_unlock(mutex);
1353
1354 pj_mutex_lock(tsess->smutex);
1355 pj_mutex_unlock(tsess->smutex);
1356 pj_activesock_close(tsess->asock);
1357 pj_mutex_destroy(tsess->smutex);
1358 pj_pool_release(tsess->pool);
1359}
1360
1361static void telnet_fe_write_log(pj_cli_front_end *fe, int level,
1362 const char *data, pj_size_t len)
1363{
1364 cli_telnet_fe *tfe = (cli_telnet_fe *)fe;
1365 pj_cli_sess *sess;
1366
1367 pj_mutex_lock(tfe->mutex);
1368
1369 sess = tfe->sess_head.next;
1370 while (sess != &tfe->sess_head) {
1371 cli_telnet_sess *tsess = (cli_telnet_sess *)sess;
1372
1373 sess = sess->next;
1374 if (tsess->base.log_level > level) {
1375 pj_str_t s;
1376
1377 pj_strset(&s, (char *)data, len);
1378 telnet_sess_send_with_format(tsess, &s);
1379 }
1380 }
1381
1382 pj_mutex_unlock(tfe->mutex);
1383}
1384
1385static void telnet_fe_destroy(pj_cli_front_end *fe)
1386{
1387 cli_telnet_fe *tfe = (cli_telnet_fe *)fe;
1388 pj_cli_sess *sess;
1389
1390 tfe->is_quitting = PJ_TRUE;
1391 if (tfe->worker_thread) {
1392 pj_thread_join(tfe->worker_thread);
1393 }
1394
1395 pj_mutex_lock(tfe->mutex);
1396
1397 /* Destroy all the sessions */
1398 sess = tfe->sess_head.next;
1399 while (sess != &tfe->sess_head) {
1400 (*sess->op->destroy)(sess);
1401 sess = tfe->sess_head.next;
1402 }
1403
1404 pj_mutex_unlock(tfe->mutex);
1405
1406 pj_activesock_close(tfe->asock);
1407 if (tfe->own_ioqueue)
1408 pj_ioqueue_destroy(tfe->cfg.ioqueue);
1409
1410 if (tfe->worker_thread) {
1411 pj_thread_destroy(tfe->worker_thread);
1412 tfe->worker_thread = NULL;
1413 }
1414
1415 pj_mutex_destroy(tfe->mutex);
1416 pj_pool_release(tfe->pool);
1417}
1418
1419static int poll_worker_thread(void *p)
1420{
1421 cli_telnet_fe *fe = (cli_telnet_fe *)p;
1422
1423 while (!fe->is_quitting) {
1424 pj_time_val delay = {0, 50};
1425 pj_ioqueue_poll(fe->cfg.ioqueue, &delay);
1426 }
1427
1428 return 0;
1429}
1430
1431static pj_bool_t telnet_sess_on_data_sent(pj_activesock_t *asock,
1432 pj_ioqueue_op_key_t *op_key,
1433 pj_ssize_t sent)
1434{
1435 cli_telnet_sess *sess = (cli_telnet_sess *)
1436 pj_activesock_get_user_data(asock);
1437
1438 PJ_UNUSED_ARG(op_key);
1439
1440 if (sent <= 0) {
1441 TRACE_((THIS_FILE, "Error On data send"));
1442 pj_cli_sess_end_session(&sess->base);
1443 return PJ_FALSE;
1444 }
1445
1446 pj_mutex_lock(sess->smutex);
1447
1448 if (sess->buf_len) {
1449 int len = sess->buf_len;
1450
1451 sess->buf_len = 0;
1452 if (telnet_sess_send2(sess, sess->buf, len) != PJ_SUCCESS) {
1453 pj_mutex_unlock(sess->smutex);
1454 pj_cli_sess_end_session(&sess->base);
1455 return PJ_FALSE;
1456 }
1457 }
1458
1459 pj_mutex_unlock(sess->smutex);
1460
1461 return PJ_TRUE;
1462}
1463
1464static pj_bool_t telnet_sess_on_data_read(pj_activesock_t *asock,
1465 void *data,
1466 pj_size_t size,
1467 pj_status_t status,
1468 pj_size_t *remainder)
1469{
1470 cli_telnet_sess *sess = (cli_telnet_sess *)
1471 pj_activesock_get_user_data(asock);
1472 cli_telnet_fe *tfe = (cli_telnet_fe *)sess->base.fe;
1473 unsigned char *cdata = (unsigned char*)data;
1474 pj_status_t is_valid = PJ_TRUE;
1475
1476 PJ_UNUSED_ARG(size);
1477 PJ_UNUSED_ARG(remainder);
1478
1479 if (tfe->is_quitting)
1480 return PJ_FALSE;
1481
1482 if (status != PJ_SUCCESS && status != PJ_EPENDING) {
1483 TRACE_((THIS_FILE, "Error on data read %d", status));
1484 return PJ_FALSE;
1485 }
1486
1487 pj_mutex_lock(sess->smutex);
1488
1489 switch (sess->parse_state) {
1490 case ST_CR:
1491 sess->parse_state = ST_NORMAL;
1492 if (*cdata == 0 || *cdata == '\n')
1493 pj_mutex_unlock(sess->smutex);
1494 is_valid = handle_return(sess);
1495 if (!is_valid)
1496 return PJ_FALSE;
1497 pj_mutex_lock(sess->smutex);
1498 break;
1499 case ST_NORMAL:
1500 if (*cdata == IAC) {
1501 sess->parse_state = ST_IAC;
1502 } else if (*cdata == 127) {
1503 is_valid = handle_backspace(sess, cdata);
1504 } else if (*cdata == 27) {
1505 sess->parse_state = ST_ESC;
1506 } else {
1507 if (recv_buf_insert(sess->rcmd, cdata)) {
1508 if (*cdata == '\r') {
1509 sess->parse_state = ST_CR;
1510 } else if ((*cdata == '\t') || (*cdata == '?')) {
1511 is_valid = handle_tab(sess);
1512 } else if (*cdata > 31 && *cdata < 127) {
1513 is_valid = handle_alfa_num(sess, cdata);
1514 }
1515 } else {
1516 is_valid = PJ_FALSE;
1517 }
1518 }
1519 break;
1520 case ST_ESC:
1521 if (*cdata == 91) {
1522 sess->parse_state = ST_VT100;
1523 } else {
1524 sess->parse_state = ST_NORMAL;
1525 }
1526 break;
1527 case ST_VT100:
1528 sess->parse_state = ST_NORMAL;
1529 is_valid = process_vt100_cmd(sess, cdata);
1530 break;
1531 case ST_IAC:
1532 switch ((unsigned) *cdata) {
1533 case DO:
1534 sess->parse_state = ST_DO;
1535 break;
1536 case DONT:
1537 sess->parse_state = ST_DONT;
1538 break;
1539 case WILL:
1540 sess->parse_state = ST_WILL;
1541 break;
1542 case WONT:
1543 sess->parse_state = ST_WONT;
1544 break;
1545 default:
1546 sess->parse_state = ST_NORMAL;
1547 break;
1548 }
1549 break;
1550 case ST_DO:
1551 receive_do(sess, *cdata);
1552 sess->parse_state = ST_NORMAL;
1553 break;
1554 case ST_DONT:
1555 receive_dont(sess, *cdata);
1556 sess->parse_state = ST_NORMAL;
1557 break;
1558 case ST_WILL:
1559 receive_will(sess, *cdata);
1560 sess->parse_state = ST_NORMAL;
1561 break;
1562 case ST_WONT:
1563 receive_wont(sess, *cdata);
1564 sess->parse_state = ST_NORMAL;
1565 break;
1566 default:
1567 sess->parse_state = ST_NORMAL;
1568 break;
1569 }
1570 if (!is_valid) {
1571 send_bell(sess);
1572 }
1573
1574 pj_mutex_unlock(sess->smutex);
1575
1576 return PJ_TRUE;
1577}
1578
1579static pj_bool_t telnet_fe_on_accept(pj_activesock_t *asock,
1580 pj_sock_t newsock,
1581 const pj_sockaddr_t *src_addr,
1582 int src_addr_len,
1583 pj_status_t status)
1584{
1585 cli_telnet_fe *fe = (cli_telnet_fe *) pj_activesock_get_user_data(asock);
1586
1587 pj_status_t sstatus;
1588 pj_pool_t *pool;
1589 cli_telnet_sess *sess = NULL;
1590 pj_activesock_cb asock_cb;
1591
1592 PJ_UNUSED_ARG(src_addr);
1593 PJ_UNUSED_ARG(src_addr_len);
1594
1595 if (fe->is_quitting)
1596 return PJ_FALSE;
1597
1598 if (status != PJ_SUCCESS && status != PJ_EPENDING) {
1599 TRACE_((THIS_FILE, "Error on data accept %d", status));
1600 if (status == PJ_ESOCKETSTOP)
1601 telnet_restart(fe);
1602
1603 return PJ_FALSE;
1604 }
1605
1606 /* An incoming connection is accepted, create a new session */
1607 pool = pj_pool_create(fe->pool->factory, "telnet_sess",
1608 PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC,
1609 NULL);
1610 if (!pool) {
1611 TRACE_((THIS_FILE,
1612 "Not enough memory to create a new telnet session"));
1613 return PJ_TRUE;
1614 }
1615
1616 sess = PJ_POOL_ZALLOC_T(pool, cli_telnet_sess);
1617 sess->pool = pool;
1618 sess->base.fe = &fe->base;
1619 sess->base.log_level = fe->cfg.log_level;
1620 sess->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_sess_op);
1621 sess->base.op->destroy = &telnet_sess_destroy;
1622 pj_bzero(&asock_cb, sizeof(asock_cb));
1623 asock_cb.on_data_read = &telnet_sess_on_data_read;
1624 asock_cb.on_data_sent = &telnet_sess_on_data_sent;
1625 sess->rcmd = PJ_POOL_ZALLOC_T(pool, telnet_recv_buf);
1626 sess->history = PJ_POOL_ZALLOC_T(pool, struct cmd_history);
1627 pj_list_init(sess->history);
1628 sess->active_history = sess->history;
1629
1630 sstatus = pj_mutex_create_recursive(pool, "mutex_telnet_sess",
1631 &sess->smutex);
1632 if (sstatus != PJ_SUCCESS)
1633 goto on_exit;
1634
1635 sstatus = pj_activesock_create(pool, newsock, pj_SOCK_STREAM(),
1636 NULL, fe->cfg.ioqueue,
1637 &asock_cb, sess, &sess->asock);
1638 if (sstatus != PJ_SUCCESS) {
1639 TRACE_((THIS_FILE, "Failure creating active socket"));
1640 goto on_exit;
1641 }
1642
1643 pj_memset(sess->telnet_option, 0, sizeof(sess->telnet_option));
1644 set_local_option(sess, TRANSMIT_BINARY, PJ_TRUE);
1645 set_local_option(sess, STATUS, PJ_TRUE);
1646 set_local_option(sess, SUPPRESS_GA, PJ_TRUE);
1647 set_local_option(sess, TIMING_MARK, PJ_TRUE);
1648 set_local_option(sess, TERM_SPEED, PJ_TRUE);
1649 set_local_option(sess, TERM_TYPE, PJ_TRUE);
1650
1651 set_peer_option(sess, TRANSMIT_BINARY, PJ_TRUE);
1652 set_peer_option(sess, SUPPRESS_GA, PJ_TRUE);
1653 set_peer_option(sess, STATUS, PJ_TRUE);
1654 set_peer_option(sess, TIMING_MARK, PJ_TRUE);
1655 set_peer_option(sess, TERM_ECHO, PJ_TRUE);
1656
1657 send_cmd_do(sess, SUPPRESS_GA);
1658 send_cmd_will(sess, TERM_ECHO);
1659 send_cmd_will(sess, STATUS);
1660 send_cmd_will(sess, SUPPRESS_GA);
1661
1662 /* Send prompt string */
1663 telnet_sess_send(sess, &fe->cfg.prompt_str);
1664
1665 /* Start reading for input from the new telnet session */
1666 sstatus = pj_activesock_start_read(sess->asock, pool, 1, 0);
1667 if (sstatus != PJ_SUCCESS) {
1668 TRACE_((THIS_FILE, "Failure reading active socket"));
1669 goto on_exit;
1670 }
1671
1672 pj_ioqueue_op_key_init(&sess->op_key, sizeof(sess->op_key));
1673 pj_mutex_lock(fe->mutex);
1674 pj_list_push_back(&fe->sess_head, &sess->base);
1675 pj_mutex_unlock(fe->mutex);
1676
1677 return PJ_TRUE;
1678
1679on_exit:
1680 if (sess->asock)
1681 pj_activesock_close(sess->asock);
1682 else
1683 pj_sock_close(newsock);
1684
1685 if (sess->smutex)
1686 pj_mutex_destroy(sess->smutex);
1687
1688 pj_pool_release(pool);
1689
1690 return PJ_TRUE;
1691}
1692
1693PJ_DEF(pj_status_t) pj_cli_telnet_create(pj_cli_t *cli,
1694 pj_cli_telnet_cfg *param,
1695 pj_cli_front_end **p_fe)
1696{
1697 cli_telnet_fe *fe;
1698 pj_pool_t *pool;
1699 pj_status_t status;
1700
1701 PJ_ASSERT_RETURN(cli, PJ_EINVAL);
1702
1703 pool = pj_pool_create(pj_cli_get_param(cli)->pf, "telnet_fe",
1704 PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC,
1705 NULL);
1706 fe = PJ_POOL_ZALLOC_T(pool, cli_telnet_fe);
1707 if (!fe)
1708 return PJ_ENOMEM;
1709
1710 fe->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_front_end_op);
1711
1712 if (!param)
1713 pj_cli_telnet_cfg_default(&fe->cfg);
1714 else
1715 pj_memcpy(&fe->cfg, param, sizeof(*param));
1716
1717 pj_list_init(&fe->sess_head);
1718 fe->base.cli = cli;
1719 fe->base.type = PJ_CLI_TELNET_FRONT_END;
1720 fe->base.op->on_write_log = &telnet_fe_write_log;
1721 fe->base.op->on_destroy = &telnet_fe_destroy;
1722 fe->pool = pool;
1723
1724 if (!fe->cfg.ioqueue) {
1725 /* Create own ioqueue if application doesn't supply one */
1726 status = pj_ioqueue_create(pool, 8, &fe->cfg.ioqueue);
1727 if (status != PJ_SUCCESS)
1728 goto on_exit;
1729 fe->own_ioqueue = PJ_TRUE;
1730 }
1731
1732 status = pj_mutex_create_recursive(pool, "mutex_telnet_fe", &fe->mutex);
1733 if (status != PJ_SUCCESS)
1734 goto on_exit;
1735
1736 /* Start telnet daemon */
1737 status = telnet_start(fe);
1738 if (status != PJ_SUCCESS)
1739 goto on_exit;
1740
1741 pj_cli_register_front_end(cli, &fe->base);
1742
1743 if (p_fe)
1744 *p_fe = &fe->base;
1745
1746 return PJ_SUCCESS;
1747
1748on_exit:
1749 if (fe->own_ioqueue)
1750 pj_ioqueue_destroy(fe->cfg.ioqueue);
1751
1752 if (fe->mutex)
1753 pj_mutex_destroy(fe->mutex);
1754
1755 pj_pool_release(pool);
1756 return status;
1757}
1758
1759static pj_status_t telnet_start(cli_telnet_fe *fe)
1760{
1761 pj_sock_t sock = PJ_INVALID_SOCKET;
1762 pj_activesock_cb asock_cb;
1763 pj_sockaddr_in addr;
1764 pj_status_t status;
1765 int val;
1766 int restart_retry;
1767 unsigned msec;
1768
1769 /* Start telnet daemon */
1770 status = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &sock);
1771
1772 if (status != PJ_SUCCESS)
1773 goto on_exit;
1774
1775 pj_sockaddr_in_init(&addr, NULL, fe->cfg.port);
1776
1777 val = 1;
1778 status = pj_sock_setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
1779 &val, sizeof(val));
1780
1781 if (status != PJ_SUCCESS) {
1782 PJ_LOG(3, (THIS_FILE, "Failed setting socket options"));
1783 }
1784
1785 /* The loop is silly, but what else can we do? */
1786 for (msec=MIN_WAIT_ON_TELNET_RESTART, restart_retry=0;
1787 restart_retry < MAX_RETRY_ON_TELNET_RESTART;
1788 ++restart_retry, msec=(msec<MAX_WAIT_ON_TELNET_RESTART?
1789 msec*2 : MAX_WAIT_ON_TELNET_RESTART))
1790 {
1791 status = pj_sock_bind(sock, &addr, sizeof(addr));
1792 if (status != PJ_STATUS_FROM_OS(EADDRINUSE))
1793 break;
1794 PJ_LOG(4,(THIS_FILE, "Address is still in use, retrying.."));
1795 pj_thread_sleep(msec);
1796 }
1797
1798 if (status == PJ_SUCCESS) {
1799 int addr_len = sizeof(addr);
1800
1801 status = pj_sock_getsockname(sock, &addr, &addr_len);
1802 if (status != PJ_SUCCESS)
1803 goto on_exit;
1804
1805 fe->cfg.port = pj_sockaddr_in_get_port(&addr);
1806
1807 if (fe->cfg.prompt_str.slen == 0) {
1808 pj_str_t prompt_sign = {"> ", 2};
1809 char *prompt_data = pj_pool_alloc(fe->pool,
1810 pj_gethostname()->slen+2);
1811 fe->cfg.prompt_str.ptr = prompt_data;
1812
1813 pj_strcpy(&fe->cfg.prompt_str, pj_gethostname());
1814 pj_strcat(&fe->cfg.prompt_str, &prompt_sign);
1815 }
1816 } else {
1817 PJ_LOG(3, (THIS_FILE, "Failed binding the socket"));
1818 goto on_exit;
1819 }
1820
1821 status = pj_sock_listen(sock, 4);
1822 if (status != PJ_SUCCESS)
1823 goto on_exit;
1824
1825 pj_bzero(&asock_cb, sizeof(asock_cb));
1826 asock_cb.on_accept_complete2 = &telnet_fe_on_accept;
1827 status = pj_activesock_create(fe->pool, sock, pj_SOCK_STREAM(),
1828 NULL, fe->cfg.ioqueue,
1829 &asock_cb, fe, &fe->asock);
1830 if (status != PJ_SUCCESS)
1831 goto on_exit;
1832
1833 status = pj_activesock_start_accept(fe->asock, fe->pool);
1834 if (status != PJ_SUCCESS)
1835 goto on_exit;
1836
1837 if (fe->own_ioqueue) {
1838 /* Create our own worker thread */
1839 status = pj_thread_create(fe->pool, "worker_telnet_fe",
1840 &poll_worker_thread, fe, 0, 0,
1841 &fe->worker_thread);
1842 if (status != PJ_SUCCESS)
1843 goto on_exit;
1844 }
1845
1846 return PJ_SUCCESS;
1847
1848on_exit:
1849 if (fe->cfg.on_started) {
1850 (*fe->cfg.on_started)(status);
1851 }
1852
1853 if (fe->asock)
1854 pj_activesock_close(fe->asock);
1855 else if (sock != PJ_INVALID_SOCKET)
1856 pj_sock_close(sock);
1857
1858 if (fe->own_ioqueue)
1859 pj_ioqueue_destroy(fe->cfg.ioqueue);
1860
1861 if (fe->mutex)
1862 pj_mutex_destroy(fe->mutex);
1863
1864 pj_pool_release(fe->pool);
1865 return status;
1866}
1867
1868static pj_status_t telnet_restart(cli_telnet_fe *fe)
1869{
1870 pj_status_t status;
1871 pj_cli_sess *sess;
1872
1873 fe->is_quitting = PJ_TRUE;
1874 if (fe->worker_thread) {
1875 pj_thread_join(fe->worker_thread);
1876 }
1877
1878 pj_mutex_lock(fe->mutex);
1879
1880 /* Destroy all the sessions */
1881 sess = fe->sess_head.next;
1882 while (sess != &fe->sess_head) {
1883 (*sess->op->destroy)(sess);
1884 sess = fe->sess_head.next;
1885 }
1886
1887 pj_mutex_unlock(fe->mutex);
1888
1889 /** Close existing activesock **/
1890 status = pj_activesock_close(fe->asock);
1891 if (status != PJ_SUCCESS)
1892 goto on_exit;
1893
1894 if (fe->worker_thread) {
1895 pj_thread_destroy(fe->worker_thread);
1896 fe->worker_thread = NULL;
1897 }
1898
1899 fe->is_quitting = PJ_FALSE;
1900
1901 /** Start Telnet **/
1902 status = telnet_start(fe);
1903 if (status == PJ_SUCCESS) {
1904 if (fe->cfg.on_started) {
1905 (*fe->cfg.on_started)(status);
1906 }
1907 TRACE_((THIS_FILE, "Telnet Restarted"));
1908 }
1909on_exit:
1910 return status;
1911}
1912
1913PJ_DEF(pj_status_t) pj_cli_telnet_get_info(pj_cli_front_end *fe,
1914 pj_cli_telnet_info *info)
1915{
1916 pj_sockaddr hostip;
1917 pj_status_t status;
1918 cli_telnet_fe *tfe = (cli_telnet_fe*) fe;
1919
1920 PJ_ASSERT_RETURN(fe && (fe->type == PJ_CLI_TELNET_FRONT_END) && info,
1921 PJ_EINVAL);
1922
1923 pj_strset(&info->ip_address, info->buf_, 0);
1924
1925 status = pj_gethostip(pj_AF_INET(), &hostip);
1926 if (status != PJ_SUCCESS)
1927 return status;
1928
1929 pj_strcpy2(&info->ip_address, pj_inet_ntoa(hostip.ipv4.sin_addr));
1930
1931 info->port = tfe->cfg.port;
1932
1933 return PJ_SUCCESS;
1934}