diff options
author | piaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204> | 2005-04-08 21:43:47 +0800 |
---|---|---|
committer | piaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204> | 2005-04-08 21:43:47 +0800 |
commit | e5371ff83e8481cb29f3b9dc1afcfbfa9178c06a (patch) | |
tree | 9d9aee81cb42ce7aab590706dc05f640f9ce6bca /mbbsd | |
parent | 0666dd02983c5a7ad580a29d7d6deec1cbcd80b5 (diff) | |
download | pttbbs-e5371ff83e8481cb29f3b9dc1afcfbfa9178c06a.tar pttbbs-e5371ff83e8481cb29f3b9dc1afcfbfa9178c06a.tar.gz pttbbs-e5371ff83e8481cb29f3b9dc1afcfbfa9178c06a.tar.bz2 pttbbs-e5371ff83e8481cb29f3b9dc1afcfbfa9178c06a.tar.lz pttbbs-e5371ff83e8481cb29f3b9dc1afcfbfa9178c06a.tar.xz pttbbs-e5371ff83e8481cb29f3b9dc1afcfbfa9178c06a.tar.zst pttbbs-e5371ff83e8481cb29f3b9dc1afcfbfa9178c06a.zip |
"Bye-Bye Flying Horse" patch
- a robust and clean TELNET protocol implementation
- fixed hz lib (autoconvert) utf8 buffer overflow exploit
- enabled term resizing in runtime, and even AYT!
- eliminated the flying-horse delay in connecting stage
git-svn-id: http://opensvn.csie.org/pttbbs/trunk/pttbbs@2691 63ad8ddf-47c3-0310-b6dd-a9e9d9715204
Diffstat (limited to 'mbbsd')
-rw-r--r-- | mbbsd/convert.c | 97 | ||||
-rw-r--r-- | mbbsd/io.c | 138 | ||||
-rw-r--r-- | mbbsd/mbbsd.c | 307 |
3 files changed, 360 insertions, 182 deletions
diff --git a/mbbsd/convert.c b/mbbsd/convert.c index ff9fef5c..a7e1ff31 100644 --- a/mbbsd/convert.c +++ b/mbbsd/convert.c @@ -1,11 +1,8 @@ -/* $Id: convert.c 1374 2003-11-27 14:11:40Z victor $ */ +/* $Id$ */ #include "bbs.h" #ifdef CONVERT -extern read_write_type write_type; -extern read_write_type read_type; - unsigned char *gb2big(unsigned char *, int *, int); unsigned char *big2gb(unsigned char *, int *, int); unsigned char *utf8_uni(unsigned char *, int *, int); @@ -13,56 +10,104 @@ unsigned char *uni_utf8(unsigned char *, int *, int); unsigned char *uni2big(unsigned char *, int *, int); unsigned char *big2uni(unsigned char *, int *, int); -static ssize_t gb_read(int fd, void *buf, size_t count) +static ssize_t +gb_input(void *buf, ssize_t icount) +{ + gb2big((char *)buf, &icount, 0); + return icount; +} + +static ssize_t +gb_read(int fd, void *buf, size_t count) { - int icount = (int)read(fd, buf, count); - if (count > 0) - gb2big((char *)buf, &icount, 0); - return (size_t)icount; + ssize_t icount = read(fd, buf, count); + if (icount > 0) + icount = gb_input(buf, icount); + return icount; } -static ssize_t gb_write(int fd, void *buf, size_t count) +static ssize_t +gb_write(int fd, void *buf, size_t count) { int icount = (int)count; big2gb((char *)buf, &icount, 0); - return write(fd, buf, (size_t)icount); + if(icount > 0) + return write(fd, buf, (size_t)icount); + else + return count; /* fake */ } -static ssize_t utf8_read(int fd, void *buf, size_t count) +static ssize_t +utf8_input (void *buf, ssize_t icount) { - count = read(fd, buf, count); - if (count > 0) { - int icount = (int)count; - utf8_uni(buf, &icount, 0); - uni2big(buf, &icount, 0); - ((char *)buf)[icount] = 0; - return (size_t)icount; - } - return count; + utf8_uni(buf, &icount, 0); + uni2big(buf, &icount, 0); + return icount; +} + +static ssize_t +utf8_read(int fd, void *buf, size_t count) +{ + ssize_t icount = read(fd, buf, count); + if (icount > 0) + icount = utf8_input(buf, icount); + return icount; } -static ssize_t utf8_write(int fd, void *buf, size_t count) +static ssize_t +utf8_write(int fd, void *buf, size_t count) { int icount = (int)count; - big2uni(buf, &icount, 0); - uni_utf8(buf, &icount, 0); - ((char *)buf)[icount] = 0; - return write(fd, buf, (size_t)icount); + static char *mybuf = NULL; + int cmybuf = 0; + + /* utf8 output is a special case because + * we need larger buffer which can be + * tripple or more in size. + * Current implementation uses 128 for each block. + */ + + if(cmybuf < count * 4) { + cmybuf = (count*4+0x80) & (~0x7f) ; + mybuf = (char*) realloc (mybuf, cmybuf); + } + memcpy(mybuf, buf, count); + big2uni(mybuf, &icount, 0); + uni_utf8(mybuf, &icount, 0); + if(icount > 0) + return write(fd, mybuf, (size_t)icount); + else + return count; /* fake */ } +static ssize_t +norm_input(void *buf, ssize_t icount) +{ + return icount; +} + +/* global function pointers */ +read_write_type write_type = (read_write_type)write; +read_write_type read_type = read; +convert_type input_type = norm_input; + void set_converting_type(int which) { if (which == CONV_NORMAL) { read_type = read; write_type = (read_write_type)write; + /* for speed up, NULL is better.. */ + input_type = NULL; /* norm_input; */ } else if (which == CONV_GB) { read_type = gb_read; write_type = gb_write; + input_type = gb_input; } else if (which == CONV_UTF8) { read_type = utf8_read; write_type = utf8_write; + input_type = utf8_input; } } @@ -4,7 +4,10 @@ #define OBUFSIZE 2048 #define IBUFSIZE 128 -static unsigned char outbuf[OBUFSIZE], inbuf[IBUFSIZE]; +/* realXbuf is Xbuf+3 because hz convert library requires buf[-2]. */ +static unsigned char real_outbuf[OBUFSIZE+6] = " ", real_inbuf[IBUFSIZE+6] = " "; +static unsigned char *outbuf = real_outbuf + 3, *inbuf = real_inbuf + 3; + static int obufsize = 0, ibufsize = 0; static int icurrchar = 0; @@ -13,8 +16,18 @@ static int icurrchar = 0; /* ----------------------------------------------------- */ #ifdef CONVERT -read_write_type write_type = (read_write_type)write; -read_write_type read_type = read; +extern read_write_type write_type; +extern read_write_type read_type; +extern convert_type input_type; + +inline static ssize_t input_wrapper(void *buf, ssize_t count) { + /* input_wrapper is a special case. + * because we may do nothing, + * a if-branch is better than a function-pointer call. + */ + if(input_type) return (*input_type)(buf, count); + else return count; +} inline static int read_wrapper(int fd, void *buf, size_t count) { return (*read_type)(fd, buf, count); @@ -112,10 +125,10 @@ num_in_buf(void) * be inconsistent. We try to not segfault here... */ -static int + static int dogetch(void) { - int len; + ssize_t len; static time4_t lastact; if (ibufsize <= icurrchar) { @@ -142,7 +155,7 @@ dogetch(void) STATINC(STAT_SYSSELECT); while ((len = select(i_newfd + 1, &readfds, NULL, NULL, - i_top ? &timeout : NULL)) < 0) { + i_top ? &timeout : NULL)) < 0) { if (errno != EINTR) abort_bbs(0); /* raise(SIGHUP); */ @@ -166,32 +179,32 @@ dogetch(void) return I_OTHERDATA; } } + #ifdef NOKILLWATERBALL - if( currutmp && currutmp->msgcount && !reentrant_write_request ) - write_request(1); + if( currutmp && currutmp->msgcount && !reentrant_write_request ) + write_request(1); #endif -#ifdef SKIP_TELNET_CONTROL_SIGNAL - do{ -#endif + STATINC(STAT_SYSREADSOCKET); - STATINC(STAT_SYSREADSOCKET); + do { + len = tty_read(inbuf, IBUFSIZE); + /* len = 0: abort, < 1: read more */ #ifdef CONVERT - while ((len = read_wrapper(0, inbuf, IBUFSIZE)) <= 0) { -#else - while ((len = read(0, inbuf, IBUFSIZE)) <= 0) { -#endif - if (len == 0 || errno != EINTR) - abort_bbs(0); - /* raise(SIGHUP); */ - + if(len > 0) { + len = input_wrapper(inbuf, len); + if(len == 0) len = -1; } -#ifdef SKIP_TELNET_CONTROL_SIGNAL - } while( inbuf[0] == IAC ); #endif + } while (len < 0); + + if (len == 0) + abort_bbs(0); + ibufsize = len; icurrchar = 0; } + if (currutmp) { #ifdef OUTTA_TIMER now = SHM->GV2.e.now; @@ -207,90 +220,12 @@ dogetch(void) return (unsigned char)inbuf[icurrchar++]; } -enum IAC_STATE { - IAC_NONE, - IAC_WAIT1, - IAC_WAIT_NAWS, - IAC_WAIT_NAWS_IAC, - IAC_WAIT_IAC_SE_1, - IAC_WAIT_IAC_SE_2, - IAC_ERROR -}; - static int water_which_flag = 0; int igetch(void) { register int ch, mode = 0, last = 0; - static int iac_state = IAC_NONE; - static unsigned char nawsbuf[4], inaws = 0; while ((ch = dogetch()) >= 0) { - if(raw_connection) /* only process IAC in raw connection mode */ - switch (iac_state) { - case IAC_NONE: - if(ch == IAC) { - iac_state = IAC_WAIT1; - continue; - } - break; - case IAC_WAIT1: - if(ch == SB) - iac_state = IAC_WAIT_IAC_SE_1; - else - iac_state = IAC_NONE; - if(ch == AYT) { - redoscr(); - outmsg("我還活著喔"); - } - continue; - case IAC_WAIT_IAC_SE_1: - if(ch == IAC) - iac_state = IAC_WAIT_IAC_SE_2; - if(ch == TELOPT_NAWS) { - iac_state = IAC_WAIT_NAWS; - inaws = 0; - } - continue; - case IAC_WAIT_IAC_SE_2: - if(ch == SE) { - iac_state = IAC_NONE; - continue; - } - else - iac_state = IAC_WAIT_IAC_SE_1; - continue; - case IAC_WAIT_NAWS_IAC: - // when we're waiting for NAWS and an IAC comes, orz - iac_state = IAC_WAIT_NAWS; - if (ch == IAC) - nawsbuf[inaws-1] = IAC; - // else? i don't know how to handle sich situation - // there shall not be any other cases - continue; - case IAC_WAIT_NAWS: - nawsbuf[inaws++] = (unsigned char)ch; - if(ch == IAC) { - iac_state = IAC_WAIT_NAWS_IAC; - continue; - } - if(inaws == 4) { - int w = (nawsbuf[0] << 8) + nawsbuf[1]; - int h = (nawsbuf[2] << 8) + nawsbuf[3]; - iac_state = IAC_WAIT_IAC_SE_1; - term_resize(w, h); /* term_resize is safe */ - /* redoscr(); */ /* will not work as we want */ -#ifdef DEBUG - { - char buf[256]; - sprintf(buf, "[%dx%d]", w, h); - outmsg(buf); - } -#endif - iac_state = IAC_WAIT_IAC_SE_1; - } - continue; - } else if (ch == 0) /* in non-raw connection mode, ignore zero. */ - return 0; if (mode == 0 && ch == KEY_ESC) // here is state machine for 2 bytes key mode = 1; @@ -768,4 +703,5 @@ getdata(int line, int col, const char *prompt, char *buf, int len, int echo) return oldgetdata(line, col, prompt, buf, len, echo); } - +/* vim:sw=4 + */ diff --git a/mbbsd/mbbsd.c b/mbbsd/mbbsd.c index e54c0139..6fdba9f8 100644 --- a/mbbsd/mbbsd.c +++ b/mbbsd/mbbsd.c @@ -1122,61 +1122,6 @@ start_client(void) domenu(MMENU, "主功\能表", (currutmp->mailalert ? 'M' : 'C'), cmdlist); } -/* FSA (finite state automata) for telnet protocol */ -static void -telnet_init(void) -{ - const static char svr[] = { - IAC, DO, TELOPT_TTYPE, - IAC, SB, TELOPT_TTYPE, TELQUAL_SEND, IAC, SE, - IAC, DO, TELOPT_NAWS, - IAC, WILL, TELOPT_ECHO, - IAC, WILL, TELOPT_SGA, - }; - const char *cmd; - int n, len; - ssize_t cread = 0; - struct timeval to; - unsigned char buf[64]; - fd_set ReadSet, r; - - raw_connection = 1; - - FD_ZERO(&ReadSet); - FD_SET(0, &ReadSet); - for (n = 0, cmd = svr; n < 5; n++) { - len = (n == 1 ? 6 : 3); - write(0, cmd, len); - cmd += len; - to.tv_sec = 3; - to.tv_usec = 0; - r = ReadSet; - if (select(1, &r, NULL, NULL, &to) > 0) - cread = recv(0, buf, sizeof(buf), 0); - -#define MIN_NAWS (8) -#define GETB() (buf[bi++] == IAC ? buf[bi++] : buf[bi-1]) - if(cread > MIN_NAWS) { /* 8 = minimal NAWS size */ - int bi = 0; - while(bi < cread-MIN_NAWS && !( - buf[bi] == IAC && - buf[bi+1] == SB && - buf[bi+2] == TELOPT_NAWS && - buf[bi+3] == 0)) /* hack: safe term size. */ - bi++; - if(bi < cread-MIN_NAWS) { - /* NAWS handler ? */ - int w, h; - bi += 3; - w = GETB() << 8; w |= GETB(); - h = GETB() << 8; h |= GETB(); - if(buf[bi++] == IAC && buf[bi++] == SE) - term_resize(w, h); - } - } - } -} - /* 取得 remote user name 以判定身份 */ /* * rfc931() speaks a common subset of the RFC 931, AUTH, TAP, IDENT and RFC @@ -1416,6 +1361,9 @@ shell_login(int argc, char *argv[], char *envp[]) return 1; } +void telnet_init(void); +unsigned int telnet_handler(unsigned char c) ; + static int daemon_login(int argc, char *argv[], char *envp[]) { @@ -1601,3 +1549,252 @@ check_ban_and_load(int fd) return 0; } + +/* ------- piaip's implementation of TELNET protocol ------- */ + +enum { + IAC_NONE, + IAC_COMMAND, + IAC_WAIT_OPT, + IAC_WAIT_SE, + IAC_PROCESS_OPT, + IAC_ERROR +} TELNET_IAC_STATES; + +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. + */ + 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, WILL, TELOPT_NAWS, + IAC, DO, TELOPT_NAWS, + + /* i will echo. */ + IAC, WILL, TELOPT_ECHO, + /* supress ga. */ + IAC, WILL, TELOPT_SGA, + }; + + raw_connection = 1; + write(0, telnet_init_cmds, sizeof(telnet_init_cmds)); +} + +/* tty_read + * read from tty, process telnet commands if raw connection. + * return: >1 = length, -1 means read more, 0 = abort/EOF. + */ +ssize_t +tty_read(unsigned char *buf, size_t max) +{ + ssize_t l = read(0, buf, max); + + if(l < 0 && !(errno == EINTR || errno == EAGAIN)) + return 0; /* 0 will abort BBS. */ + + 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; + } + + /* 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: + 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.\r\n"; + write(0, alive, strlen(alive)); + } + 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: + 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. + * + * If client says 'DONT' or DO, + * ignore because we can't really do/don't that. + */ + switch(c) { + case TELOPT_ECHO: /* echo */ + case TELOPT_RCP: /* prepare to reconnect */ + case TELOPT_SGA: /* suppress go ahead */ + if(iac_opt_req == WONT) { + /* we need these options, whether you want or not */ + unsigned char cmd[3] = { IAC, 0, 0 }; + cmd[1] = WILL; + cmd[2] = c; + write(0, cmd, sizeof(cmd)); + } + break; + + case TELOPT_TTYPE: + case TELOPT_NAWS: /* resize terminal */ + /* i don't care the client */ + break; + + default: + if (iac_opt_req == WILL) + { + /* unknown option, reply with won't */ + unsigned char cmd[3] = { IAC, 0, 0 }; + 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; + 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); + } + break; + + default: + break; + } + return 1; + } + return 1; /* never reached */ +} +/* vim: sw=4 + */ |