diff options
author | piaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204> | 2007-12-13 01:47:03 +0800 |
---|---|---|
committer | piaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204> | 2007-12-13 01:47:03 +0800 |
commit | a752a92e27ab75785c9c13fbf7e5fb47e0f06a02 (patch) | |
tree | 204beef0b9f3dd20ffd88135a3cf026f1aed5d5e /mbbsd/telnet.c | |
parent | d45cf2b2910628b10aa71f68a5eb0fc88722e517 (diff) | |
download | pttbbs-a752a92e27ab75785c9c13fbf7e5fb47e0f06a02.tar pttbbs-a752a92e27ab75785c9c13fbf7e5fb47e0f06a02.tar.gz pttbbs-a752a92e27ab75785c9c13fbf7e5fb47e0f06a02.tar.bz2 pttbbs-a752a92e27ab75785c9c13fbf7e5fb47e0f06a02.tar.lz pttbbs-a752a92e27ab75785c9c13fbf7e5fb47e0f06a02.tar.xz pttbbs-a752a92e27ab75785c9c13fbf7e5fb47e0f06a02.tar.zst pttbbs-a752a92e27ab75785c9c13fbf7e5fb47e0f06a02.zip |
- isolate telnet protocol. moved from mbbsd.c
git-svn-id: http://opensvn.csie.org/pttbbs/trunk/pttbbs@3679 63ad8ddf-47c3-0310-b6dd-a9e9d9715204
Diffstat (limited to 'mbbsd/telnet.c')
-rw-r--r-- | mbbsd/telnet.c | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/mbbsd/telnet.c b/mbbsd/telnet.c new file mode 100644 index 00000000..aa985471 --- /dev/null +++ b/mbbsd/telnet.c @@ -0,0 +1,327 @@ +/* + * piaip's simplified implementation of TELNET protocol + */ +#ifdef DEBUG +#define TELOPTS +#define TELCMDS +#endif + +#include "bbs.h" + +#ifdef DETECT_CLIENT +void UpdateClientCode(unsigned char c); // see mbbsd.c +#endif + +unsigned int telnet_handler(unsigned char c) ; +void telnet_init(void); +ssize_t tty_read(unsigned char *buf, size_t max); + +enum TELNET_IAC_STATES { + IAC_NONE, + IAC_COMMAND, + IAC_WAIT_OPT, + IAC_WAIT_SE, + IAC_PROCESS_OPT, + IAC_ERROR +}; + +static unsigned char iac_state = 0; /* as byte to reduce memory */ + +#define TELNET_IAC_MAXLEN (16) +/* We don't reply to most commands, so this maxlen can be minimal. + * Warning: if you want to support ENV passing or other long commands, + * remember to increase this value. Howver, some poorly implemented + * terminals like xxMan may not follow the protocols and user will hang + * on those terminals when IACs were sent. + */ + +void +telnet_init(void) +{ + /* We are the boss. We don't respect to client. + * It's client's responsibility to follow us. + * Please write these codes in i-dont-care opt handlers. + */ + const char telnet_init_cmds[] = { + /* retrieve terminal type and throw away. + * why? because without this, clients enter line mode. + */ + IAC, DO, TELOPT_TTYPE, + IAC, SB, TELOPT_TTYPE, TELQUAL_SEND, IAC, SE, + + /* i'm a smart term with resize ability. */ + IAC, DO, TELOPT_NAWS, + + /* i will echo. */ + IAC, WILL, TELOPT_ECHO, + /* supress ga. */ + IAC, WILL, TELOPT_SGA, + /* 8 bit binary. */ + IAC, WILL, TELOPT_BINARY, + IAC, DO, TELOPT_BINARY, + }; + + raw_connection = 1; + write(0, telnet_init_cmds, sizeof(telnet_init_cmds)); +} + +/* tty_read + * read from tty, process telnet commands if raw connection. + * return: >0 = length, <=0 means read more, abort/eof is automatically processed. + */ +ssize_t +tty_read(unsigned char *buf, size_t max) +{ + ssize_t l = read(0, buf, max); + + if(l == 0 || (l < 0 && !(errno == EINTR || errno == EAGAIN))) + abort_bbs(0); + + if(!raw_connection) + return l; + + /* process buffer */ + if (l > 0) { + unsigned char *buf2 = buf; + size_t i = 0, i2 = 0; + + /* prescan. because IAC is rare, + * this cost is worthy. */ + if (iac_state == IAC_NONE && memchr(buf, IAC, l) == NULL) + return l; + + /* we have to look into the buffer. */ + for (i = 0; i < l; i++, buf++) + if(telnet_handler(*buf) == 0) + *(buf2++) = *buf; + else + i2 ++; + l = (i2 == l) ? -1L : l - i2; + } + return l; +} + +/* input: raw character + * output: telnet command if c was handled, otherwise zero. + */ +unsigned int +telnet_handler(unsigned char c) +{ + static unsigned char iac_quote = 0; /* as byte to reduce memory */ + static unsigned char iac_opt_req = 0; + + static unsigned char iac_buf[TELNET_IAC_MAXLEN]; + static unsigned int iac_buflen = 0; + + /* we have to quote all IACs. */ + if(c == IAC && !iac_quote) { + iac_quote = 1; + return NOP; + } + +#ifdef DETECT_CLIENT + /* hash client telnet sequences */ + if(cuser.userid[0]==0) { + if(iac_state == IAC_WAIT_SE) { + // skip suboption + } else { + if(iac_quote) + UpdateClientCode(IAC); + UpdateClientCode(c); + } + } +#endif + + /* a special case is the top level iac. otherwise, iac is just a quote. */ + if (iac_quote) { + if(iac_state == IAC_NONE) + iac_state = IAC_COMMAND; + if(iac_state == IAC_WAIT_SE && c == SE) + iac_state = IAC_PROCESS_OPT; + iac_quote = 0; + } + + /* now, let's process commands by state */ + switch(iac_state) { + + case IAC_NONE: + return 0; + + case IAC_COMMAND: +#if 0 // def DEBUG + { + int cx = c; /* to make compiler happy */ + write(0, "-", 1); + if(TELCMD_OK(cx)) + write(0, TELCMD(c), strlen(TELCMD(c))); + write(0, " ", 1); + } +#endif + iac_state = IAC_NONE; /* by default we restore state. */ + switch(c) { + case IAC: + return 0; + + /* we don't want to process these. or maybe in future. */ + case BREAK: /* break */ + case ABORT: /* Abort process */ + case SUSP: /* Suspend process */ + case AO: /* abort output--but let prog finish */ + case IP: /* interrupt process--permanently */ + case EOR: /* end of record (transparent mode) */ + case DM: /* data mark--for connect. cleaning */ + case xEOF: /* End of file: EOF is already used... */ + return NOP; + + case NOP: /* nop */ + return NOP; + + /* we should process these, but maybe in future. */ + case GA: /* you may reverse the line */ + case EL: /* erase the current line */ + case EC: /* erase the current character */ + return NOP; + + /* good */ + case AYT: /* are you there */ + { + const char *alive = "I'm still alive, loading: "; + char buf[STRLEN]; + + /* respond as fast as we can */ + write(0, alive, strlen(alive)); + cpuload(buf); + write(0, buf, strlen(buf)); + write(0, "\r\n", 2); + } + return NOP; + + case DONT: /* you are not to use option */ + case DO: /* please, you use option */ + case WONT: /* I won't use option */ + case WILL: /* I will use option */ + iac_opt_req = c; + iac_state = IAC_WAIT_OPT; + return NOP; + + case SB: /* interpret as subnegotiation */ + iac_state = IAC_WAIT_SE; + iac_buflen = 0; + return NOP; + + case SE: /* end sub negotiation */ + default: + return NOP; + } + return 1; + + case IAC_WAIT_OPT: +#if 0 // def DEBUG + write(0, "-", 1); + if(TELOPT_OK(c)) + write(0, TELOPT(c), strlen(TELOPT(c))); + write(0, " ", 1); +#endif + iac_state = IAC_NONE; + /* + * According to RFC, there're some tricky steps to prevent loop. + * However because we have a poor term which does not allow + * most abilities, let's be a strong boss here. + * + * Although my old imeplementation worked, it's even better to follow this: + * http://www.tcpipguide.com/free/t_TelnetOptionsandOptionNegotiation-3.htm + */ + switch(c) { + /* i-dont-care: i don't care about what client is. + * these should be clamed in init and + * client must follow me. */ + case TELOPT_TTYPE: /* termtype or line. */ + case TELOPT_NAWS: /* resize terminal */ + case TELOPT_SGA: /* supress GA */ + case TELOPT_ECHO: /* echo */ + case TELOPT_BINARY: /* we are CJK. */ + break; + + /* i-dont-agree: i don't understand/agree these. + * according to RFC, saying NO stopped further + * requests so there'll not be endless loop. */ + case TELOPT_RCP: /* prepare to reconnect */ + default: + if (iac_opt_req == WILL || iac_opt_req == DO) + { + /* unknown option, reply with won't */ + unsigned char cmd[3] = { IAC, DONT, 0 }; + if(iac_opt_req == DO) cmd[1] = WONT; + cmd[2] = c; + write(0, cmd, sizeof(cmd)); + } + break; + } + return 1; + + case IAC_WAIT_SE: + iac_buf[iac_buflen++] = c; + /* no need to convert state because previous quoting will do. */ + + if(iac_buflen == TELNET_IAC_MAXLEN) { + /* may be broken protocol? + * whether finished or not, break for safety + * or user may be frozen. + */ + iac_state = IAC_NONE; + return 0; + } + return 1; + + case IAC_PROCESS_OPT: + iac_state = IAC_NONE; +#if 0 // def DEBUG + write(0, "-", 1); + if(TELOPT_OK(iac_buf[0])) + write(0, TELOPT(iac_buf[0]), strlen(TELOPT(iac_buf[0]))); + write(0, " ", 1); +#endif + switch(iac_buf[0]) { + + /* resize terminal */ + case TELOPT_NAWS: + { + int w = (iac_buf[1] << 8) + (iac_buf[2]); + int h = (iac_buf[3] << 8) + (iac_buf[4]); + term_resize(w, h); +#ifdef DETECT_CLIENT + if(cuser.userid[0]==0) { + UpdateClientCode(iac_buf[0]); + if(w==80 && h==24) + UpdateClientCode(1); + else if(w==80) + UpdateClientCode(2); + else if(h==24) + UpdateClientCode(3); + else + UpdateClientCode(4); + UpdateClientCode(IAC); + UpdateClientCode(SE); + } +#endif + } + break; + + default: +#ifdef DETECT_CLIENT + if(cuser.userid[0]==0) { + int i; + for(i=0;i<iac_buflen;i++) + UpdateClientCode(iac_buf[i]); + UpdateClientCode(IAC); + UpdateClientCode(SE); + } +#endif + break; + } + return 1; + } + return 1; /* never reached */ +} + +// vim: sw=4 |