/* * Copyright (c) 2005, Swedish Institute of Computer Science * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This file is part of the Contiki operating system. * * @(#)$Id: ctk-termtelnet.c,v 1.1 2007/05/26 21:46:28 oliverschmidt Exp $ */ #include "contiki.h" #include "loader.h" #include "memb.h" #include "ctk-term.h" #include "contiki-conf.h" /*-----------------------------------------------------------------------------------*/ /* * #defines and enums */ /*-----------------------------------------------------------------------------------*/ /* Telnet special characters */ #define TN_NULL 0 #define TN_BL 7 #define TN_BS 8 #define TN_HT 9 #define TN_LF 10 #define TN_VT 11 #define TN_FF 12 #define TN_CR 13 /* Commands preceeded by TN_IAC */ #define TN_SE 240 #define TN_NOP 241 #define TN_DM 242 #define TN_BRK 243 #define TN_IP 244 #define TN_AO 245 #define TN_AYT 246 #define TN_EC 247 #define TN_EL 248 #define TN_GA 249 #define TN_SB 250 #define TN_WILL 251 #define TN_WONT 252 #define TN_DO 253 #define TN_DONT 254 #define TN_IAC 255 #define TNO_BIN 0 #define TNO_ECHO 1 #define TNO_SGA 3 #define TNO_NAWS 31 /* Telnet parsing states */ enum { TNS_IDLE, TNS_IAC, TNS_OPT, TNS_SB, TNS_SBIAC }; /* Telnet option negotiation states */ enum { TNOS_NO, TNOS_WANTNO_EMPTY, TNOS_WANTNO_OPPOSITE, TNOS_WANTYES_EMPTY, TNOS_WANTYES_OPPOSITE, TNOS_YES }; /* Telnet session states */ enum { TTS_FREE, /* Not allocated */ TTS_IDLE, /* No data to send and nothing sent */ TTS_SEND_TNDATA, /* Sending telnet data */ TTS_SEND_APPDATA /* Sending data from upper layers */ }; /* Number of options supported (we only need ECHO(1) and SGA(3) options) */ #define TNSM_MAX_OPTIONS 4 /* Max option replies in output queue */ #define TNQLEN 20 /* Number of option buffer */ #define OPTION_POOL_SIZE 20 /* Maximum number of telnet sessions */ #ifdef CTK_TERM_CONF_MAX_TELNET_CLIENTS #define NUM_CONNS CTK_TERM_CONF_MAX_TELNET_CLIENTS #else #define NUM_CONNS 1 #endif #ifdef CTK_TERM_CONF_TELNET_PORT #define PORT CTK_TERM_CONF_TELNET_PORT #else #define PORT 23 #endif /*-----------------------------------------------------------------------------------*/ /* * Structures */ /*-----------------------------------------------------------------------------------*/ /* Telnet option state structure */ struct TNOption { unsigned char state; unsigned char wants; }; /* Telnet handling state structure */ struct TNSMState { struct TNOption myOpt[TNSM_MAX_OPTIONS]; struct TNOption hisOpt[TNSM_MAX_OPTIONS]; unsigned char cmd; unsigned char state; }; /* Telnet session state */ struct telnet_state { unsigned char state; unsigned char* sendq[TNQLEN]; struct TNSMState tnsm; struct ctk_term_state* termstate; }; /*-----------------------------------------------------------------------------------*/ /* * Local variables */ /*-----------------------------------------------------------------------------------*/ /*static DISPATCHER_UIPCALL(ctk_termtelnet_appcall, state);*/ static void ctk_termtelnet_appcall(void *state); EK_EVENTHANDLER(eventhandler, ev, data); EK_PROCESS(p, "CTK telnet server", EK_PRIO_NORMAL, eventhandler, NULL, NULL); /*static struct dispatcher_proc p = {DISPATCHER_PROC("CTK telnet server", NULL, NULL, ctk_termtelnet_appcall)};*/ static ek_id_t id = EK_ID_NONE; /* Option negotiation buffer pool */ struct size_3 { char size[3]; }; MEMB(telnetbuf, struct size_3, OPTION_POOL_SIZE); static int i,j; static struct telnet_state states[NUM_CONNS]; /*-----------------------------------------------------------------------------------*/ /* * Send an option reply on a connection */ /*-----------------------------------------------------------------------------------*/ static void Reply(struct telnet_state* tns, unsigned char cmd, unsigned char opt) { unsigned char* buf = (unsigned char*)memb_alloc(&telnetbuf); if (buf != 0) { buf[0]=TN_IAC; buf[1]=cmd; buf[2]=opt; for (i=0; i < TNQLEN; i++) { if (tns->sendq[i] == 0) { tns->sendq[i] = buf; return; } } /* Queue is full. Drop it */ memb_free(&telnetbuf, (char*)buf); } } /*-----------------------------------------------------------------------------------*/ /* * Prepare for enabling one of remote side options. */ /*-----------------------------------------------------------------------------------*/ static void EnableHisOpt(struct telnet_state* tns, unsigned char opt) { switch(tns->tnsm.hisOpt[opt].state) { case TNOS_NO: tns->tnsm.hisOpt[opt].wants = 1; tns->tnsm.hisOpt[opt].state = TNOS_WANTYES_EMPTY; Reply(tns, TN_DO, opt); break; case TNOS_WANTNO_EMPTY: tns->tnsm.hisOpt[opt].state = TNOS_WANTNO_OPPOSITE; break; case TNOS_WANTNO_OPPOSITE: break; case TNOS_WANTYES_EMPTY: tns->tnsm.hisOpt[opt].state = TNOS_YES; break; case TNOS_WANTYES_OPPOSITE: tns->tnsm.hisOpt[opt].state = TNOS_WANTYES_EMPTY; break; case TNOS_YES: break; } } /*-----------------------------------------------------------------------------------*/ /* * Prepare for enabling one of my options */ /*-----------------------------------------------------------------------------------*/ static void EnableMyOpt(struct telnet_state* tns, unsigned char opt) { if (opt < TNSM_MAX_OPTIONS) { switch(tns->tnsm.myOpt[opt].state) { case TNOS_NO: tns->tnsm.myOpt[opt].wants = 1; tns->tnsm.myOpt[opt].state = TNOS_WANTYES_EMPTY; Reply(tns, TN_WILL, opt); break; case TNOS_WANTNO_EMPTY: tns->tnsm.myOpt[opt].state = TNOS_WANTNO_OPPOSITE; break; case TNOS_WANTNO_OPPOSITE: break; case TNOS_WANTYES_EMPTY: tns->tnsm.myOpt[opt].state = TNOS_YES; break; case TNOS_WANTYES_OPPOSITE: tns->tnsm.myOpt[opt].state = TNOS_WANTYES_EMPTY; break; case TNOS_YES: break; } } } /*-----------------------------------------------------------------------------------*/ /* * Implementation of option negotiation using the Q-method */ /*-----------------------------------------------------------------------------------*/ static void HandleCommand(struct telnet_state* tns, unsigned char cmd, unsigned char opt) { if (opt < TNSM_MAX_OPTIONS) { /* Handling according to RFC 1143 "Q Method" */ switch(cmd) { case TN_WILL: switch(tns->tnsm.hisOpt[opt].state) { case TNOS_NO: if (tns->tnsm.hisOpt[opt].wants) { tns->tnsm.hisOpt[opt].state = TNOS_YES; Reply(tns, TN_DO, opt); } else { Reply(tns, TN_DONT, opt); } break; case TNOS_WANTNO_EMPTY: tns->tnsm.hisOpt[opt].state = TNOS_NO; break; case TNOS_WANTNO_OPPOSITE: tns->tnsm.hisOpt[opt].state = TNOS_YES; break; case TNOS_WANTYES_EMPTY: tns->tnsm.hisOpt[opt].state = TNOS_YES; break; case TNOS_WANTYES_OPPOSITE: tns->tnsm.hisOpt[opt].state = TNOS_WANTNO_EMPTY; Reply(tns, TN_DONT, opt); break; case TNOS_YES: break; } break; case TN_WONT: switch(tns->tnsm.hisOpt[opt].state) { case TNOS_NO: break; case TNOS_WANTNO_EMPTY: case TNOS_WANTYES_EMPTY: case TNOS_WANTYES_OPPOSITE: tns->tnsm.hisOpt[opt].state = TNOS_NO; break; case TNOS_WANTNO_OPPOSITE: tns->tnsm.hisOpt[opt].state = TNOS_WANTYES_EMPTY; Reply(tns, TN_DO, opt); break; case TNOS_YES: tns->tnsm.hisOpt[opt].state = TNOS_NO; Reply(tns, TN_DONT, opt); break; } break; case TN_DO: switch(tns->tnsm.myOpt[opt].state) { case TNOS_NO: if (tns->tnsm.myOpt[opt].wants) { tns->tnsm.myOpt[opt].state = TNOS_YES; Reply(tns, TN_WILL, opt); } else { Reply(tns, TN_WONT, opt); } break; case TNOS_WANTNO_EMPTY: tns->tnsm.myOpt[opt].state = TNOS_NO; break; case TNOS_WANTNO_OPPOSITE: tns->tnsm.myOpt[opt].state = TNOS_YES; break; case TNOS_WANTYES_EMPTY: tns->tnsm.myOpt[opt].state = TNOS_YES; break; case TNOS_WANTYES_OPPOSITE: tns->tnsm.myOpt[opt].state = TNOS_WANTNO_EMPTY; Reply(tns, TN_WONT, opt); break; case TNOS_YES: break; } break; case TN_DONT: switch(tns->tnsm.myOpt[opt].state) { case TNOS_NO: break; case TNOS_WANTNO_EMPTY: case TNOS_WANTYES_EMPTY: case TNOS_WANTYES_OPPOSITE: tns->tnsm.myOpt[opt].state = TNOS_NO; break; case TNOS_WANTNO_OPPOSITE: tns->tnsm.myOpt[opt].state = TNOS_WANTYES_EMPTY; Reply(tns, TN_WILL, opt); break; case TNOS_YES: tns->tnsm.myOpt[opt].state = TNOS_NO; Reply(tns, TN_WONT, opt); break; } break; } } else { switch(cmd) { case TN_WILL: Reply(tns, TN_DONT, opt); break; case TN_WONT: break; case TN_DO: Reply(tns, TN_WONT, opt); break; case TN_DONT: break; } } } /*-----------------------------------------------------------------------------------*/ /* * Telnet data parsing */ /*-----------------------------------------------------------------------------------*/ static unsigned char parse_input(struct telnet_state* tns, unsigned char b) { unsigned char ret = 0; switch(tns->tnsm.state) { case TNS_IDLE: if (b == TN_IAC) tns->tnsm.state = TNS_IAC; else ret = 1; break; case TNS_IAC: switch(b) { case TN_SE: case TN_NOP: case TN_DM: case TN_BRK: case TN_IP: case TN_AO: case TN_AYT: case TN_EC: case TN_EL: case TN_GA: tns->tnsm.state = TNS_IDLE; break; case TN_SB: tns->tnsm.state = TNS_SB; break; case TN_WILL: case TN_WONT: case TN_DO: case TN_DONT: tns->tnsm.cmd = b; tns->tnsm.state = TNS_OPT; break; case TN_IAC: tns->tnsm.state = TNS_IDLE; ret = 1; break; default: /* Drop unknown IACs */ tns->tnsm.state = TNS_IDLE; break; } break; case TNS_OPT: HandleCommand(tns, tns->tnsm.cmd, b); tns->tnsm.state = TNS_IDLE; break; case TNS_SB: if (b == TN_IAC) { tns->tnsm.state = TNS_SBIAC; } break; case TNS_SBIAC: if (b == TN_IAC) { tns->tnsm.state = TNS_SB; } else if (b == TN_SE) { tns->tnsm.state = TNS_IDLE; } else { tns->tnsm.state = TNS_IDLE; } break; } return ret; } /*-----------------------------------------------------------------------------------*/ /* * Initialize telnet machine */ /*-----------------------------------------------------------------------------------*/ static void telnet_init(struct telnet_state* tns) { int i; for (i = 0; i < TNSM_MAX_OPTIONS; i++) { tns->tnsm.myOpt[i].state = TNOS_NO; tns->tnsm.myOpt[i].wants = 0; tns->tnsm.hisOpt[i].state = TNOS_NO; tns->tnsm.hisOpt[i].wants = 0; } tns->tnsm.state = TNS_IDLE; } /*-----------------------------------------------------------------------------------*/ /* * Allocate a telnet session structure (including terminal state) */ /*-----------------------------------------------------------------------------------*/ static struct telnet_state* alloc_state() { for (i=0; i < NUM_CONNS; i++) { if (states[i].state == TTS_FREE) { states[i].termstate = ctk_term_alloc_state(); if (states[i].termstate != NULL) { for (j = 0; j < TNQLEN; j++) { states[i].sendq[j] = 0; } telnet_init(&states[i]); states[i].state = TTS_IDLE; return &(states[i]); } } } return NULL; } /*-----------------------------------------------------------------------------------*/ /* * Free a telnet session structure (including terminal state) */ /*-----------------------------------------------------------------------------------*/ static void free_state(struct telnet_state* tns) { if (tns != NULL) { ctk_term_dealloc_state(tns->termstate); tns->state = TTS_FREE; } } /*-----------------------------------------------------------------------------------*/ /* * A packet is successfully sent */ /*-----------------------------------------------------------------------------------*/ static void acked(struct telnet_state* tns) { /* Were we sending a telnet option packet? */ if (tns->state == TTS_SEND_TNDATA) { /* Yes, free it and update queue */ if (tns->sendq[0] != 0) { memb_free(&telnetbuf, (char*)(tns->sendq[0])); for (i=1; i < TNQLEN; i++) { tns->sendq[i-1] = tns->sendq[i]; } tns->sendq[TNQLEN-1] = 0; /* No options left. Go idle */ if (tns->sendq[0] == 0) { tns->state = TTS_IDLE; } } } /* Or were we sending application date ? */ else if (tns->state == TTS_SEND_APPDATA) { /* Inform application that data is sent successfully */ ctk_term_sent(tns->termstate); tns->state = TTS_IDLE; } } /*-----------------------------------------------------------------------------------*/ /* * Send data on a connections */ /*-----------------------------------------------------------------------------------*/ static void senddata(struct telnet_state* tns) { /* Check if there are any option packets to send */ if (tns->state == TTS_IDLE || tns->state == TTS_SEND_TNDATA) { if (tns->sendq[0] != 0) { tns->state = TTS_SEND_TNDATA; uip_send(tns->sendq[0],3); } } /* Check if terminal wants to send any data */ if (tns->state == TTS_IDLE || tns->state == TTS_SEND_APPDATA) { u16_t len = ctk_term_send(tns->termstate, (unsigned char*)uip_appdata, (unsigned short)uip_mss()); if (len > 0) { tns->state = TTS_SEND_APPDATA; uip_send(uip_appdata, len); } } } /*-----------------------------------------------------------------------------------*/ /* * uIP callback */ /*-----------------------------------------------------------------------------------*/ static void ctk_termtelnet_appcall(void *state) { struct telnet_state *tns; tns = (struct telnet_state*)(state); if(uip_connected()) { if(tns == NULL) { tns = alloc_state(); if(tns == NULL) { uip_close(); return; } tcp_markconn(uip_conn, (void *)tns); } /* Try to negotiate some options */ EnableHisOpt(tns, TNO_SGA); EnableMyOpt(tns,TNO_SGA); EnableMyOpt(tns,TNO_ECHO); /* Request update of screen */ ctk_term_redraw(tns->termstate); senddata(tns); } else if(uip_closed() || uip_aborted()) { free_state(tns); return; } if (uip_acked()) { acked(tns); } if (uip_newdata()) { for(j = 0; j < uip_datalen(); j++) { if (parse_input(tns, uip_appdata[j])) { /* Pass it uppwards */ ctk_term_input(tns->termstate, uip_appdata[j]); } } } if(uip_rexmit() || uip_newdata() || uip_acked()) { senddata(tns); } else if(uip_poll()) { if (tns->state == TTS_IDLE) { senddata(tns); } } } /*-----------------------------------------------------------------------------------*/ /* * Init function */ /*-----------------------------------------------------------------------------------*/ LOADER_INIT_FUNC(ctk_termtelnet_init, arg) { arg_free(arg); if(id == EK_ID_NONE) { memb_init(&telnetbuf); for (i=0; i < NUM_CONNS; i++) { states[i].state = TTS_FREE; } id = ek_start(&p); } } /*-----------------------------------------------------------------------------------*/ EK_EVENTHANDLER(eventhandler, ev, data) { if(ev == EK_EVENT_INIT) { tcp_listen(HTONS(PORT)); } else if(ev == tcpip_event) { ctk_termtelnet_appcall(data); } }