From a752a92e27ab75785c9c13fbf7e5fb47e0f06a02 Mon Sep 17 00:00:00 2001 From: piaip Date: Wed, 12 Dec 2007 17:47:03 +0000 Subject: - isolate telnet protocol. moved from mbbsd.c git-svn-id: http://opensvn.csie.org/pttbbs/trunk/pttbbs@3679 63ad8ddf-47c3-0310-b6dd-a9e9d9715204 --- include/proto.h | 6 +- mbbsd/Makefile | 11 +- mbbsd/mbbsd.c | 324 ++----------------------------------------------------- mbbsd/telnet.c | 327 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 344 insertions(+), 324 deletions(-) create mode 100644 mbbsd/telnet.c diff --git a/include/proto.h b/include/proto.h index f62c1e7e..9a956b96 100644 --- a/include/proto.h +++ b/include/proto.h @@ -378,7 +378,6 @@ void talk_request(int sig); int reply_connection_request(const userinfo_t *uip); int establish_talk_connection(const userinfo_t *uip); void my_talk(userinfo_t * uin, int fri_stat, char defact); -ssize_t tty_read(unsigned char *buf, size_t max); int query_file_money(const fileheader_t *pfh); /* menu */ @@ -401,8 +400,11 @@ void m_sob_brd(char *bname,char *fromdir); /* old more */ int more(char *fpath, int promptend); -/* piaip's new pager */ +/* piaip's new pager, pmore.c */ int pmore(char *fpath, int promptend); +/* piaip's new telnet, telnet.c */ +void telnet_init(void); +ssize_t tty_read(unsigned char *buf, size_t max); /* name */ typedef int (*gnc_comp_func)(int, const char*, int); diff --git a/mbbsd/Makefile b/mbbsd/Makefile index 84468ffc..0998c962 100644 --- a/mbbsd/Makefile +++ b/mbbsd/Makefile @@ -11,12 +11,13 @@ LDFLAGS+= -L/usr/local/lib/mysql -lmysqlclient PROG= mbbsd OBJS= admin.o announce.o args.o assess.o bbs.o board.o cache.o cal.o card.o\ - chat.o chc.o chc_tab.o chicken.o convert.o crypt.o dark.o edit.o fav.o friend.o gamble.o\ - gomo.o guess.o indict.o io.o kaede.o lovepaper.o mail.o mbbsd.o menu.o\ - more.o name.o osdep.o othello.o read.o record.o register.o\ + chat.o chicken.o convert.o crypt.o edit.o fav.o friend.o gamble.o\ + guess.o indict.o io.o kaede.o lovepaper.o mail.o mbbsd.o menu.o\ + more.o name.o osdep.o read.o record.o register.o\ screen.o stuff.o talk.o term.o topsong.o user.o brc.o vice.o vote.o\ - xyz.o voteboard.o syspost.o var.o passwd.o calendar.o go.o file.o \ - pmore.o chess.o reversi.o + xyz.o voteboard.o syspost.o var.o passwd.o calendar.o file.o \ + pmore.o telnet.o\ + chc.o chc_tab.o chess.o go.o gomo.o dark.o reversi.o othello.o .if defined(DIET) OBJS+= random.o time.o alloc.o diff --git a/mbbsd/mbbsd.c b/mbbsd/mbbsd.c index 41a8ab4a..499d0421 100644 --- a/mbbsd/mbbsd.c +++ b/mbbsd/mbbsd.c @@ -1,8 +1,4 @@ /* $Id$ */ -#ifdef DEBUG -#define TELOPTS -#define TELCMDS -#endif #include "bbs.h" #include "banip.h" @@ -36,8 +32,14 @@ static char remoteusername[40] = "?"; static unsigned char enter_uflag; static int use_shell_login_mode = 0; + #ifdef DETECT_CLIENT Fnv32_t client_code=FNV1_32_INIT; + +void UpdateClientCode(unsigned char c) +{ + FNV1A_CHAR(c, client_code); +} #endif #ifdef USE_RFORK @@ -278,7 +280,7 @@ abort_bbs_debug(int sig) /* log */ /* assume vsnprintf() in log_file() is signal-safe, is it? */ log_filef("log/crash.log", LOG_CREAT, - "%ld %d %d %.12s\n", time4(NULL), getpid(), sig, cuser.userid); + "%d %d %d %.12s\n", time4(NULL), getpid(), sig, cuser.userid); /* try logout... not a good idea, maybe crash again. now disabled */ /* @@ -1581,9 +1583,6 @@ 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[]) { @@ -1804,314 +1803,5 @@ static int check_banip(char *host) return uintbsearch(thisip, &banip[1], banip[0]) ? 1 : 0; } -/* ------- piaip's implementation of TELNET protocol ------- */ - -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) - FNV1A_CHAR(IAC, client_code); - FNV1A_CHAR(c, client_code); - } - } -#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: -#ifdef 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: -#ifdef 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; -#ifdef 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) { - FNV1A_CHAR(iac_buf[0], client_code); - if(w==80 && h==24) - FNV1A_CHAR(1, client_code); - else if(w==80) - FNV1A_CHAR(2, client_code); - else if(h==24) - FNV1A_CHAR(3, client_code); - else - FNV1A_CHAR(4, client_code); - FNV1A_CHAR(IAC, client_code); - FNV1A_CHAR(SE, client_code); - } -#endif - } - break; - - default: -#ifdef DETECT_CLIENT - if(cuser.userid[0]==0) { - int i; - for(i=0;i0 = 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