void handle_telnet(const unsigned char *bytes, uint16_t frame_len) { print1("TELNET "); print_ips_from_last_header_v1(); printf1("%u bytes\n", frame_len); const unsigned char *end = bytes + frame_len; while(bytes < end) { if(*bytes & 0xFF) { bytes++; uint8_t command = *bytes++; printf3(" cmd %s (%u): ", TELCMD_OK(command) ? TELCMD(command) : "CMD?", command); switch(command) { case DO: case DONT: case WONT: case WILL: { uint8_t option = *bytes++; printf3("%s (%u)", TELOPT(option), option); break; } case SB: { uint8_t suboption = *bytes++; printf3("%s (%u)", TELOPT(suboption), suboption); switch(suboption) { case TELOPT_TSPEED: printf3(" = %u", *bytes++); break; case TELOPT_NAWS: printf3(" = %u x %u", ntohs(*(uint16_t*)&bytes[0]), ntohs(*(uint16_t*)&bytes[2])); bytes += 4; break; default: break; } break; } case SE: print3("end of suboptions"); break; default: print3("unknown command"); break; } putchar3('\n'); if(command == SE) break; // end of options } else { bytes++; } } }
/* Remove all IAC's from the buffer pointed to by bf (recieved IACs are ignored and must be removed so as to not be interpreted by the terminal). Make an uninterrupted string of characters fit for the terminal. Do this by packing all characters meant for the terminal sequentially towards the end of bf. Return a pointer to the beginning of the characters meant for the terminal. and make *processed equal to the number of characters that were actually processed and *num_totty the number of characters that should be sent to the terminal. Note - If an IAC (3 byte quantity) starts before (bf + len) but extends past (bf + len) then that IAC will be left unprocessed and *processed will be less than len. FIXME - if we mean to send 0xFF to the terminal then it will be escaped, what is the escape character? We aren't handling that situation here. */ static char * remove_iacs(unsigned char *bf, int len, int *processed, int *num_totty) { unsigned char *ptr = bf; unsigned char *totty = bf; unsigned char *end = bf + len; while (ptr < end) { if (*ptr != IAC) { *totty++ = *ptr++; } else { if ((ptr+2) < end) { /* the entire IAC is contained in the buffer we were asked to process. */ DEBUG_OUT("Ignoring IAC 0x%02x, %s, %s\n", *ptr, TELCMD(*(ptr+1)), TELOPT(*(ptr+2))); ptr += 3; } else { /* only the beginning of the IAC is in the buffer we were asked to process, we can't process this char. */ break; } } } *processed = ptr - bf; *num_totty = totty - bf; /* move the chars meant for the terminal towards the end of the buffer. */ return memmove(ptr - *num_totty, bf, *num_totty); }
void printoption(const char *direction, int cmd, int option) { if (!showoptions) return; if (cmd == IAC) { if (TELCMD_OK(option)) fprintf(NetTrace, "%s IAC %s", direction, TELCMD(option)); else fprintf(NetTrace, "%s IAC %d", direction, option); } else { const char *fmt; fmt = (cmd == WILL) ? "WILL" : (cmd == WONT) ? "WONT" : (cmd == DO) ? "DO" : (cmd == DONT) ? "DONT" : 0; if (fmt) { fprintf(NetTrace, "%s %s ", direction, fmt); if (TELOPT_OK(option)) fprintf(NetTrace, "%s", TELOPT(option)); else if (option == TELOPT_EXOPL) fprintf(NetTrace, "EXOPL"); else fprintf(NetTrace, "%d", option); } else fprintf(NetTrace, "%s %d %d", direction, cmd, option); } if (NetTrace == stdout) { fprintf(NetTrace, "\r\n"); fflush(NetTrace); } else { fprintf(NetTrace, "\n"); } return; }
/* Write some buf1 data to pty, processing IACs. * Update wridx1 and size1. Return < 0 on error. * Buggy if IAC is present but incomplete: skips them. */ static ssize_t safe_write_to_pty_decode_iac(struct tsession *ts) { unsigned wr; ssize_t rc; unsigned char *buf; unsigned char *found; buf = TS_BUF1(ts) + ts->wridx1; wr = MIN(BUFSIZE - ts->wridx1, ts->size1); /* wr is at least 1 here */ if (ts->buffered_IAC_for_pty) { /* Last time we stopped on a "dangling" IAC byte. * We removed it from the buffer back then. * Now pretend it's still there, and jump to IAC processing. */ ts->buffered_IAC_for_pty = 0; wr++; ts->size1++; buf--; /* Yes, this can point before the buffer. It's ok */ ts->wridx1--; goto handle_iac; } found = memchr(buf, IAC, wr); if (found != buf) { /* There is a "prefix" of non-IAC chars. * Write only them, and return. */ if (found) wr = found - buf; /* We map \r\n ==> \r for pragmatic reasons: * many client implementations send \r\n when * the user hits the CarriageReturn key. * See RFC 1123 3.3.1 Telnet End-of-Line Convention. */ rc = wr; found = memchr(buf, '\r', wr); if (found) rc = found - buf + 1; rc = safe_write(ts->ptyfd, buf, rc); if (rc <= 0) return rc; if (rc < wr /* don't look past available data */ && buf[rc-1] == '\r' /* need this: imagine that write was _short_ */ && (buf[rc] == '\n' || buf[rc] == '\0') ) { rc++; } goto update_and_return; } /* buf starts with IAC char. Process that sequence. * Example: we get this from our own (bbox) telnet client: * read(5, "\377\374\1""\377\373\37""\377\372\37\0\262\0@\377\360""\377\375\1""\377\375\3"): * IAC WONT ECHO, IAC WILL NAWS, IAC SB NAWS <cols> <rows> IAC SE, IAC DO SGA * Another example (telnet-0.17 from old-netkit): * read(4, "\377\375\3""\377\373\30""\377\373\37""\377\373 ""\377\373!""\377\373\"""\377\373'" * "\377\375\5""\377\373#""\377\374\1""\377\372\37\0\257\0I\377\360""\377\375\1"): * IAC DO SGA, IAC WILL TTYPE, IAC WILL NAWS, IAC WILL TSPEED, IAC WILL LFLOW, IAC WILL LINEMODE, IAC WILL NEW_ENVIRON, * IAC DO STATUS, IAC WILL XDISPLOC, IAC WONT ECHO, IAC SB NAWS <cols> <rows> IAC SE, IAC DO ECHO */ if (wr <= 1) { /* Only the single IAC byte is in the buffer, eat it * and set a flag "process the rest of the sequence * next time we are here". */ //bb_error_msg("dangling IAC!"); ts->buffered_IAC_for_pty = 1; rc = 1; goto update_and_return; } handle_iac: /* 2-byte commands (240..250 and 255): * IAC IAC (255) Literal 255. Supported. * IAC SE (240) End of subnegotiation. Treated as NOP. * IAC NOP (241) NOP. Supported. * IAC BRK (243) Break. Like serial line break. TODO via tcsendbreak()? * IAC AYT (246) Are you there. Send back evidence that AYT was seen. TODO (send NOP back)? * These don't look useful: * IAC DM (242) Data mark. What is this? * IAC IP (244) Suspend, interrupt or abort the process. (Ancient cousin of ^C). * IAC AO (245) Abort output. "You can continue running, but do not send me the output". * IAC EC (247) Erase character. The receiver should delete the last received char. * IAC EL (248) Erase line. The receiver should delete everything up tp last newline. * IAC GA (249) Go ahead. For half-duplex lines: "now you talk". * Implemented only as part of NAWS: * IAC SB (250) Subnegotiation of an option follows. */ if (buf[1] == IAC) { /* Literal 255 (emacs M-DEL) */ //bb_error_msg("255!"); rc = safe_write(ts->ptyfd, &buf[1], 1); /* * If we went through buffered_IAC_for_pty==1 path, * bailing out on error like below messes up the buffer. * EAGAIN is highly unlikely here, other errors will be * repeated on next write, let's just skip error check. */ #if 0 if (rc <= 0) return rc; #endif rc = 2; goto update_and_return; } if (buf[1] >= 240 && buf[1] <= 249) { /* NOP (241). Ignore (putty keepalive, etc) */ /* All other 2-byte commands also treated as NOPs here */ rc = 2; goto update_and_return; } if (wr <= 2) { /* BUG: only 2 bytes of the IAC is in the buffer, we just eat them. * This is not a practical problem since >2 byte IACs are seen only * in initial negotiation, when buffer is empty */ rc = 2; goto update_and_return; } if (buf[1] == SB) { if (buf[2] == TELOPT_NAWS) { /* IAC SB, TELOPT_NAWS, 4-byte, IAC SE */ struct winsize ws; if (wr <= 6) { /* BUG: incomplete, can't process */ rc = wr; goto update_and_return; } memset(&ws, 0, sizeof(ws)); /* pixel sizes are set to 0 */ ws.ws_col = (buf[3] << 8) | buf[4]; ws.ws_row = (buf[5] << 8) | buf[6]; ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws); rc = 7; /* trailing IAC SE will be eaten separately, as 2-byte NOP */ goto update_and_return; } /* else: other subnegs not supported yet */ } /* Assume it is a 3-byte WILL/WONT/DO/DONT 251..254 command and skip it */ #if DEBUG fprintf(stderr, "Ignoring IAC %s,%s\n", TELCMD(buf[1]), TELOPT(buf[2])); #endif rc = 3; update_and_return: ts->wridx1 += rc; if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */ ts->wridx1 = 0; ts->size1 -= rc; /* * Hack. We cannot process IACs which wrap around buffer's end. * Since properly fixing it requires writing bigger code, * we rely instead on this code making it virtually impossible * to have wrapped IAC (people don't type at 2k/second). * It also allows for bigger reads in common case. */ if (ts->size1 == 0) { /* very typical */ //bb_error_msg("zero size1"); ts->rdidx1 = 0; ts->wridx1 = 0; return rc; } wr = ts->wridx1; if (wr != 0 && wr < ts->rdidx1) { /* Buffer is not wrapped yet. * We can easily move it to the beginning. */ //bb_error_msg("moved %d", wr); memmove(TS_BUF1(ts), TS_BUF1(ts) + wr, ts->size1); ts->rdidx1 -= wr; ts->wridx1 = 0; } return rc; }
/* Remove all IAC's from buf1 (received IACs are ignored and must be removed so as to not be interpreted by the terminal). Make an uninterrupted string of characters fit for the terminal. Do this by packing all characters meant for the terminal sequentially towards the end of buf. Return a pointer to the beginning of the characters meant for the terminal and make *num_totty the number of characters that should be sent to the terminal. Note - if an IAC (3 byte quantity) starts before (bf + len) but extends past (bf + len) then that IAC will be left unprocessed and *processed will be less than len. CR-LF ->'s CR mapping is also done here, for convenience. NB: may fail to remove iacs which wrap around buffer! */ static unsigned char * remove_iacs(struct tsession *ts, int *pnum_totty) { unsigned char *ptr0 = TS_BUF1(ts) + ts->wridx1; unsigned char *ptr = ptr0; unsigned char *totty = ptr; unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1); int num_totty; while (ptr < end) { if (*ptr != IAC) { char c = *ptr; *totty++ = c; ptr++; /* We map \r\n ==> \r for pragmatic reasons. * Many client implementations send \r\n when * the user hits the CarriageReturn key. * See RFC 1123 3.3.1 Telnet End-of-Line Convention. */ if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0')) ptr++; continue; } if ((ptr+1) >= end) break; if (ptr[1] == NOP) { /* Ignore? (putty keepalive, etc.) */ ptr += 2; continue; } if (ptr[1] == IAC) { /* Literal IAC? (emacs M-DEL) */ *totty++ = ptr[1]; ptr += 2; continue; } /* * TELOPT_NAWS support! */ if ((ptr+2) >= end) { /* Only the beginning of the IAC is in the buffer we were asked to process, we can't process this char */ break; } /* * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE */ if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) { struct winsize ws; if ((ptr+8) >= end) break; /* incomplete, can't process */ ws.ws_col = (ptr[3] << 8) | ptr[4]; ws.ws_row = (ptr[5] << 8) | ptr[6]; ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws); ptr += 9; continue; } /* skip 3-byte IAC non-SB cmd */ #if DEBUG fprintf(stderr, "Ignoring IAC %s,%s\n", TELCMD(ptr[1]), TELOPT(ptr[2])); #endif ptr += 3; } num_totty = totty - ptr0; *pnum_totty = num_totty; /* The difference between ptr and totty is number of iacs we removed from the stream. Adjust buf1 accordingly */ if ((ptr - totty) == 0) /* 99.999% of cases */ return ptr0; ts->wridx1 += ptr - totty; ts->size1 -= ptr - totty; /* Move chars meant for the terminal towards the end of the buffer */ return memmove(ptr - num_totty, ptr0, num_totty); }
/* Remove all IAC's from the buffer pointed to by bf (recieved IACs are ignored and must be removed so as to not be interpreted by the terminal). Make an uninterrupted string of characters fit for the terminal. Do this by packing all characters meant for the terminal sequentially towards the end of bf. Return a pointer to the beginning of the characters meant for the terminal. and make *num_totty the number of characters that should be sent to the terminal. Note - If an IAC (3 byte quantity) starts before (bf + len) but extends past (bf + len) then that IAC will be left unprocessed and *processed will be less than len. FIXME - if we mean to send 0xFF to the terminal then it will be escaped, what is the escape character? We aren't handling that situation here. CR-LF ->'s CR mapping is also done here, for convenience */ static char * remove_iacs(struct tsession *ts, int *pnum_totty) { unsigned char *ptr0 = ts->buf1 + ts->wridx1; unsigned char *ptr = ptr0; unsigned char *totty = ptr; unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1); int processed; int num_totty; while (ptr < end) { if (*ptr != IAC) { int c = *ptr; *totty++ = *ptr++; /* We now map \r\n ==> \r for pragmatic reasons. * Many client implementations send \r\n when * the user hits the CarriageReturn key. */ if (c == '\r' && (*ptr == '\n' || *ptr == 0) && ptr < end) ptr++; } else { /* * TELOPT_NAWS support! */ if ((ptr+2) >= end) { /* only the beginning of the IAC is in the buffer we were asked to process, we can't process this char. */ break; } /* * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE */ else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) { struct winsize ws; if ((ptr+8) >= end) break; /* incomplete, can't process */ ws.ws_col = (ptr[3] << 8) | ptr[4]; ws.ws_row = (ptr[5] << 8) | ptr[6]; (void) ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws); ptr += 9; } else { /* skip 3-byte IAC non-SB cmd */ #ifdef DEBUG fprintf(stderr, "Ignoring IAC %s,%s\n", TELCMD(*(ptr+1)), TELOPT(*(ptr+2))); #endif ptr += 3; } } } processed = ptr - ptr0; num_totty = totty - ptr0; /* the difference between processed and num_to tty is all the iacs we removed from the stream. Adjust buf1 accordingly. */ ts->wridx1 += processed - num_totty; ts->size1 -= processed - num_totty; *pnum_totty = num_totty; /* move the chars meant for the terminal towards the end of the buffer. */ return memmove(ptr - num_totty, ptr0, num_totty); }
/* int length; length of suboption data */ void printsub (char direction, unsigned char *pointer, int length) { register int i; extern int want_status_response; #if defined AUTHENTICATION || defined ENCRYPTION char buf[512]; #endif if (showoptions || direction == 0 || (want_status_response && (pointer[0] == TELOPT_STATUS))) { if (direction) { fprintf (NetTrace, "%s IAC SB ", (direction == '<') ? "RCVD" : "SENT"); if (length >= 3) { register int j; i = pointer[length - 2]; j = pointer[length - 1]; if (i != IAC || j != SE) { fprintf (NetTrace, "(terminated by "); if (TELOPT_OK (i)) fprintf (NetTrace, "%s ", TELOPT (i)); else if (TELCMD_OK (i)) fprintf (NetTrace, "%s ", TELCMD (i)); else fprintf (NetTrace, "%d ", i); if (TELOPT_OK (j)) fprintf (NetTrace, "%s", TELOPT (j)); else if (TELCMD_OK (j)) fprintf (NetTrace, "%s", TELCMD (j)); else fprintf (NetTrace, "%d", j); fprintf (NetTrace, ", not IAC SE!) "); } } length -= 2; } if (length < 1) { fprintf (NetTrace, "(Empty suboption??\?)"); if (NetTrace == stdout) fflush (NetTrace); return; } switch (pointer[0]) { case TELOPT_TTYPE: fprintf (NetTrace, "TERMINAL-TYPE "); switch (pointer[1]) { case TELQUAL_IS: fprintf (NetTrace, "IS \"%.*s\"", length - 2, (char *) pointer + 2); break; case TELQUAL_SEND: fprintf (NetTrace, "SEND"); break; default: fprintf (NetTrace, "- unknown qualifier %d (0x%x).", pointer[1], pointer[1]); } break; case TELOPT_TSPEED: fprintf (NetTrace, "TERMINAL-SPEED"); if (length < 2) { fprintf (NetTrace, " (empty suboption??\?)"); break; } switch (pointer[1]) { case TELQUAL_IS: fprintf (NetTrace, " IS "); fprintf (NetTrace, "%.*s", length - 2, (char *) pointer + 2); break; default: if (pointer[1] == 1) fprintf (NetTrace, " SEND"); else fprintf (NetTrace, " %d (unknown)", pointer[1]); for (i = 2; i < length; i++) fprintf (NetTrace, " ?%d?", pointer[i]); break; } break; case TELOPT_LFLOW: fprintf (NetTrace, "TOGGLE-FLOW-CONTROL"); if (length < 2) { fprintf (NetTrace, " (empty suboption??\?)"); break; } switch (pointer[1]) { case LFLOW_OFF: fprintf (NetTrace, " OFF"); break; case LFLOW_ON: fprintf (NetTrace, " ON"); break; case LFLOW_RESTART_ANY: fprintf (NetTrace, " RESTART-ANY"); break; case LFLOW_RESTART_XON: fprintf (NetTrace, " RESTART-XON"); break; default: fprintf (NetTrace, " %d (unknown)", pointer[1]); } for (i = 2; i < length; i++) fprintf (NetTrace, " ?%d?", pointer[i]); break; case TELOPT_NAWS: fprintf (NetTrace, "NAWS"); if (length < 2) { fprintf (NetTrace, " (empty suboption??\?)"); break; } if (length == 2) { fprintf (NetTrace, " ?%d?", pointer[1]); break; } fprintf (NetTrace, " %d %d (%d)", pointer[1], pointer[2], (int) ((((unsigned int) pointer[1]) << 8) | ((unsigned int) pointer[2]))); if (length == 4) { fprintf (NetTrace, " ?%d?", pointer[3]); break; } fprintf (NetTrace, " %d %d (%d)", pointer[3], pointer[4], (int) ((((unsigned int) pointer[3]) << 8) | ((unsigned int) pointer[4]))); for (i = 5; i < length; i++) fprintf (NetTrace, " ?%d?", pointer[i]); break; #if defined AUTHENTICATION case TELOPT_AUTHENTICATION: fprintf (NetTrace, "AUTHENTICATION"); if (length < 2) { fprintf (NetTrace, " (empty suboption??\?)"); break; } switch (pointer[1]) { case TELQUAL_REPLY: case TELQUAL_IS: fprintf (NetTrace, " %s ", (pointer[1] == TELQUAL_IS) ? "IS" : "REPLY"); if (AUTHTYPE_NAME_OK (pointer[2]) && AUTHTYPE_NAME (pointer[2])) fprintf (NetTrace, "%s ", AUTHTYPE_NAME (pointer[2])); else fprintf (NetTrace, "%d ", pointer[2]); if (length < 3) { fprintf (NetTrace, "(partial suboption??\?)"); break; } fprintf (NetTrace, "%s|%s", ((pointer[3] & AUTH_WHO_MASK) == AUTH_WHO_CLIENT) ? "CLIENT" : "SERVER", ((pointer[3] & AUTH_HOW_MASK) == AUTH_HOW_MUTUAL) ? "MUTUAL" : "ONE-WAY"); auth_printsub (&pointer[1], length - 1, buf, sizeof (buf)); fprintf (NetTrace, "%s", buf); break; case TELQUAL_SEND: i = 2; fprintf (NetTrace, " SEND "); while (i < length) { if (AUTHTYPE_NAME_OK (pointer[i]) && AUTHTYPE_NAME (pointer[i])) fprintf (NetTrace, "%s ", AUTHTYPE_NAME (pointer[i])); else fprintf (NetTrace, "%d ", pointer[i]); if (++i >= length) { fprintf (NetTrace, "(partial suboption??\?)"); break; } fprintf (NetTrace, "%s|%s ", ((pointer[i] & AUTH_WHO_MASK) == AUTH_WHO_CLIENT) ? "CLIENT" : "SERVER", ((pointer[i] & AUTH_HOW_MASK) == AUTH_HOW_MUTUAL) ? "MUTUAL" : "ONE-WAY"); ++i; } break; case TELQUAL_NAME: i = 2; fprintf (NetTrace, " NAME \""); while (i < length) putc (pointer[i++], NetTrace); putc ('"', NetTrace); break; default: for (i = 2; i < length; i++) fprintf (NetTrace, " ?%d?", pointer[i]); break; } break; #endif #ifdef ENCRYPTION case TELOPT_ENCRYPT: fprintf (NetTrace, "ENCRYPT"); if (length < 2) { fprintf (NetTrace, " (empty suboption??\?)"); break; } switch (pointer[1]) { case ENCRYPT_START: fprintf (NetTrace, " START"); break; case ENCRYPT_END: fprintf (NetTrace, " END"); break; case ENCRYPT_REQSTART: fprintf (NetTrace, " REQUEST-START"); break; case ENCRYPT_REQEND: fprintf (NetTrace, " REQUEST-END"); break; case ENCRYPT_IS: case ENCRYPT_REPLY: fprintf (NetTrace, " %s ", (pointer[1] == ENCRYPT_IS) ? "IS" : "REPLY"); if (length < 3) { fprintf (NetTrace, " (partial suboption??\?)"); break; } if (ENCTYPE_NAME_OK (pointer[2]) && ENCTYPE_NAME (pointer[2])) fprintf (NetTrace, "%s ", ENCTYPE_NAME (pointer[2])); else fprintf (NetTrace, " %d (unknown)", pointer[2]); encrypt_printsub (&pointer[1], length - 1, buf, sizeof (buf)); fprintf (NetTrace, "%s", buf); break; case ENCRYPT_SUPPORT: i = 2; fprintf (NetTrace, " SUPPORT "); while (i < length) { if (ENCTYPE_NAME_OK (pointer[i]) && ENCTYPE_NAME (pointer[i])) fprintf (NetTrace, "%s ", ENCTYPE_NAME (pointer[i])); else fprintf (NetTrace, "%d ", pointer[i]); i++; } break; case ENCRYPT_ENC_KEYID: fprintf (NetTrace, " ENC_KEYID "); goto encommon; case ENCRYPT_DEC_KEYID: fprintf (NetTrace, " DEC_KEYID "); goto encommon; default: fprintf (NetTrace, " %d (unknown)", pointer[1]); encommon: for (i = 2; i < length; i++) fprintf (NetTrace, " %d", pointer[i]); break; } break; #endif /* ENCRYPTION */ case TELOPT_LINEMODE: fprintf (NetTrace, "LINEMODE "); if (length < 2) { fprintf (NetTrace, " (empty suboption??\?)"); break; } switch (pointer[1]) { case WILL: fprintf (NetTrace, "WILL "); goto common; case WONT: fprintf (NetTrace, "WONT "); goto common; case DO: fprintf (NetTrace, "DO "); goto common; case DONT: fprintf (NetTrace, "DONT "); common: if (length < 3) { fprintf (NetTrace, "(no option??\?)"); break; } switch (pointer[2]) { case LM_FORWARDMASK: fprintf (NetTrace, "Forward Mask"); for (i = 3; i < length; i++) fprintf (NetTrace, " %x", pointer[i]); break; default: fprintf (NetTrace, "%d (unknown)", pointer[2]); for (i = 3; i < length; i++) fprintf (NetTrace, " %d", pointer[i]); break; } break; case LM_SLC: fprintf (NetTrace, "SLC"); for (i = 2; i < length - 2; i += 3) { if (SLC_NAME_OK (pointer[i + SLC_FUNC])) fprintf (NetTrace, " %s", SLC_NAME (pointer[i + SLC_FUNC])); else fprintf (NetTrace, " %d", pointer[i + SLC_FUNC]); switch (pointer[i + SLC_FLAGS] & SLC_LEVELBITS) { case SLC_NOSUPPORT: fprintf (NetTrace, " NOSUPPORT"); break; case SLC_CANTCHANGE: fprintf (NetTrace, " CANTCHANGE"); break; case SLC_VARIABLE: fprintf (NetTrace, " VARIABLE"); break; case SLC_DEFAULT: fprintf (NetTrace, " DEFAULT"); break; } fprintf (NetTrace, "%s%s%s", (pointer[i + SLC_FLAGS] & SLC_ACK) ? "|ACK" : "", (pointer[i + SLC_FLAGS] & SLC_FLUSHIN) ? "|FLUSHIN" : "", (pointer[i + SLC_FLAGS] & SLC_FLUSHOUT) ? "|FLUSHOUT" : ""); if (pointer[i + SLC_FLAGS] & ~(SLC_ACK | SLC_FLUSHIN | SLC_FLUSHOUT | SLC_LEVELBITS)) fprintf (NetTrace, "(0x%x)", pointer[i + SLC_FLAGS]); fprintf (NetTrace, " %d;", pointer[i + SLC_VALUE]); if ((pointer[i + SLC_VALUE] == IAC) && (pointer[i + SLC_VALUE + 1] == IAC)) i++; } for (; i < length; i++) fprintf (NetTrace, " ?%d?", pointer[i]); break; case LM_MODE: fprintf (NetTrace, "MODE "); if (length < 3) { fprintf (NetTrace, "(no mode??\?)"); break; } { char tbuf[64]; sprintf (tbuf, "%s%s%s%s%s", pointer[2] & MODE_EDIT ? "|EDIT" : "", pointer[2] & MODE_TRAPSIG ? "|TRAPSIG" : "", pointer[2] & MODE_SOFT_TAB ? "|SOFT_TAB" : "", pointer[2] & MODE_LIT_ECHO ? "|LIT_ECHO" : "", pointer[2] & MODE_ACK ? "|ACK" : ""); fprintf (NetTrace, "%s", tbuf[1] ? &tbuf[1] : "0"); } if (pointer[2] & ~(MODE_MASK)) fprintf (NetTrace, " (0x%x)", pointer[2]); for (i = 3; i < length; i++) fprintf (NetTrace, " ?0x%x?", pointer[i]); break; default: fprintf (NetTrace, "%d (unknown)", pointer[1]); for (i = 2; i < length; i++) fprintf (NetTrace, " %d", pointer[i]); } break; case TELOPT_STATUS: { register char *cp; register int j, k; fprintf (NetTrace, "STATUS"); switch (pointer[1]) { default: if (pointer[1] == TELQUAL_SEND) fprintf (NetTrace, " SEND"); else fprintf (NetTrace, " %d (unknown)", pointer[1]); for (i = 2; i < length; i++) fprintf (NetTrace, " ?%d?", pointer[i]); break; case TELQUAL_IS: if (--want_status_response < 0) want_status_response = 0; if (NetTrace == stdout) fprintf (NetTrace, " IS\r\n"); else fprintf (NetTrace, " IS\n"); for (i = 2; i < length; i++) { switch (pointer[i]) { case DO: cp = "DO"; goto common2; case DONT: cp = "DONT"; goto common2; case WILL: cp = "WILL"; goto common2; case WONT: cp = "WONT"; goto common2; common2: i++; if (TELOPT_OK ((int) pointer[i])) fprintf (NetTrace, " %s %s", cp, TELOPT (pointer[i])); else fprintf (NetTrace, " %s %d", cp, pointer[i]); if (NetTrace == stdout) fprintf (NetTrace, "\r\n"); else fprintf (NetTrace, "\n"); break; case SB: fprintf (NetTrace, " SB "); i++; j = k = i; while (j < length) { if (pointer[j] == SE) { if (j + 1 == length) break; if (pointer[j + 1] == SE) j++; else break; } pointer[k++] = pointer[j++]; } printsub (0, &pointer[i], k - i); if (i < length) { fprintf (NetTrace, " SE"); i = j; } else i = j - 1; if (NetTrace == stdout) fprintf (NetTrace, "\r\n"); else fprintf (NetTrace, "\n"); break; default: fprintf (NetTrace, " %d", pointer[i]); break; } } break; } break; } case TELOPT_XDISPLOC: fprintf (NetTrace, "X-DISPLAY-LOCATION "); switch (pointer[1]) { case TELQUAL_IS: fprintf (NetTrace, "IS \"%.*s\"", length - 2, (char *) pointer + 2); break; case TELQUAL_SEND: fprintf (NetTrace, "SEND"); break; default: fprintf (NetTrace, "- unknown qualifier %d (0x%x).", pointer[1], pointer[1]); } break; case TELOPT_NEW_ENVIRON: fprintf (NetTrace, "NEW-ENVIRON "); #ifdef OLD_ENVIRON goto env_common1; case TELOPT_OLD_ENVIRON: fprintf (NetTrace, "OLD-ENVIRON"); env_common1: #endif switch (pointer[1]) { case TELQUAL_IS: fprintf (NetTrace, "IS "); goto env_common; case TELQUAL_SEND: fprintf (NetTrace, "SEND "); goto env_common; case TELQUAL_INFO: fprintf (NetTrace, "INFO "); env_common: { const char *quote = ""; #if defined ENV_HACK && defined OLD_ENVIRON extern int old_env_var, old_env_value; #endif for (i = 2; i < length; i++) { switch (pointer[i]) { case NEW_ENV_VALUE: #ifdef OLD_ENVIRON /* case NEW_ENV_OVAR: */ if (pointer[0] == TELOPT_OLD_ENVIRON) { # ifdef ENV_HACK if (old_env_var == OLD_ENV_VALUE) fprintf (NetTrace, "%s(VALUE) ", quote); else # endif fprintf (NetTrace, "%sVAR ", quote); } else #endif /* OLD_ENVIRON */ fprintf (NetTrace, "%sVALUE ", quote); quote = ""; break; case NEW_ENV_VAR: #ifdef OLD_ENVIRON /* case OLD_ENV_VALUE: */ if (pointer[0] == TELOPT_OLD_ENVIRON) { # ifdef ENV_HACK if (old_env_value == OLD_ENV_VAR) fprintf (NetTrace, "%s(VAR) ", quote); else # endif fprintf (NetTrace, "%sVALUE ", quote); } else #endif /* OLD_ENVIRON */ fprintf (NetTrace, "%sVAR ", quote); quote = ""; break; case ENV_ESC: fprintf (NetTrace, "%sESC ", quote); quote = ""; break; case ENV_USERVAR: fprintf (NetTrace, "%sUSERVAR ", quote); quote = ""; break; default: if (isprint (pointer[i]) && pointer[i] != '"') { if (quote[0] == '\0') { putc ('"', NetTrace); quote = "\" "; } putc (pointer[i], NetTrace); } else { fprintf (NetTrace, "%s%03o ", quote, pointer[i]); quote = ""; } break; } } if (quote[0] != '\0') putc ('"', NetTrace); break; } } break; default: if (TELOPT_OK (pointer[0])) fprintf (NetTrace, "%s (unknown)", TELOPT (pointer[0])); else fprintf (NetTrace, "%d (unknown)", pointer[0]); for (i = 1; i < length; i++) fprintf (NetTrace, " %d", pointer[i]); break; } if (direction) { if (NetTrace == stdout) fprintf (NetTrace, "\r\n"); else fprintf (NetTrace, "\n"); } if (NetTrace == stdout) fflush (NetTrace); } }
void optionstatus (void) { register int i; extern char will_wont_resp[], do_dont_resp[]; for (i = 0; i < 256; i++) { if (do_dont_resp[i]) { if (TELOPT_OK (i)) printf ("resp DO_DONT %s: %d\n", TELOPT (i), do_dont_resp[i]); else if (TELCMD_OK (i)) printf ("resp DO_DONT %s: %d\n", TELCMD (i), do_dont_resp[i]); else printf ("resp DO_DONT %d: %d\n", i, do_dont_resp[i]); if (my_want_state_is_do (i)) { if (TELOPT_OK (i)) printf ("want DO %s\n", TELOPT (i)); else if (TELCMD_OK (i)) printf ("want DO %s\n", TELCMD (i)); else printf ("want DO %d\n", i); } else { if (TELOPT_OK (i)) printf ("want DONT %s\n", TELOPT (i)); else if (TELCMD_OK (i)) printf ("want DONT %s\n", TELCMD (i)); else printf ("want DONT %d\n", i); } } else { if (my_state_is_do (i)) { if (TELOPT_OK (i)) printf (" DO %s\n", TELOPT (i)); else if (TELCMD_OK (i)) printf (" DO %s\n", TELCMD (i)); else printf (" DO %d\n", i); } } if (will_wont_resp[i]) { if (TELOPT_OK (i)) printf ("resp WILL_WONT %s: %d\n", TELOPT (i), will_wont_resp[i]); else if (TELCMD_OK (i)) printf ("resp WILL_WONT %s: %d\n", TELCMD (i), will_wont_resp[i]); else printf ("resp WILL_WONT %d: %d\n", i, will_wont_resp[i]); if (my_want_state_is_will (i)) { if (TELOPT_OK (i)) printf ("want WILL %s\n", TELOPT (i)); else if (TELCMD_OK (i)) printf ("want WILL %s\n", TELCMD (i)); else printf ("want WILL %d\n", i); } else { if (TELOPT_OK (i)) printf ("want WONT %s\n", TELOPT (i)); else if (TELCMD_OK (i)) printf ("want WONT %s\n", TELCMD (i)); else printf ("want WONT %d\n", i); } } else { if (my_state_is_will (i)) { if (TELOPT_OK (i)) printf (" WILL %s\n", TELOPT (i)); else if (TELCMD_OK (i)) printf (" WILL %s\n", TELCMD (i)); else printf (" WILL %d\n", i); } } } }
int __attribute__((noreturn)) telnet_input_task(struct telnet_svc * tn) { struct tcp_pcb * svc; struct tcp_pcb * tp; char buf[128]; int sb_len; int len; char * src; int rem; int binary; int state; int c; struct tn_opt opt; unsigned int head; svc = tn->svc; for (;;) { INF("TELNET wating for connection."); DCC_LOG(LOG_TRACE, "TELNET: waiting for connection..."); if ((tp = tcp_accept(svc)) == NULL) { DCC_LOG(LOG_ERROR, "tcp_accept()."); break; } INF("TELNET connection accepted."); DCC_LOG(LOG_TRACE, "TELNET: accepted."); tn->tp = tp; tn_opt_clr(&opt); sb_len = 0; binary = 0; state = TN_DATA; tn_opt_will(tp, &opt, TELOPT_SGA); tn_opt_do(tp, &opt, TELOPT_SGA); tn_opt_will(tp, &opt, TELOPT_ECHO); tn_opt_will(tp, &opt, TELOPT_BINARY); tn_opt_do(tp, &opt, TELOPT_BINARY); head = tn->rx.head; for (;;) { if (head != tn->rx.tail) { /* update the head */ tn->rx.head = head; DCC_LOG1(LOG_TRACE, "rx nonempty: head=%d", head); /* signal the head update */ thinkos_flag_give(tn->rx.nonempty_flag); } /* receive data form network */ if ((len = tcp_recv(tp, buf, 128)) <= 0) { DCC_LOG1(LOG_WARNING, "tcp_recv(): %d", len); break; } DCC_LOG1(LOG_TRACE, "recv: %d", len); /* set the input processing pointer */ src = buf; /* input remaining (to be processed) bytes */ rem = len; while (rem > 0) { c = *src++; rem--; if (state == TN_DATA) { if (c == IAC) { state = TN_IAC_RCVD; } else { if ((binary) || ((c >= 3) && (c < 127))) { /* ASCII characters */ DCC_LOG1(LOG_TRACE, "rx nonempty: head=%d", head); /* buffer is full */ if (head == (tn->rx.tail + TELNET_SVC_RX_BUF_LEN)) { /* update the head */ tn->rx.head = head; /* signal the head update */ thinkos_flag_give(tn->rx.nonempty_flag); /* wait for space in the input buffer */ while (1) { if (head < (tn->rx.tail + TELNET_SVC_RX_BUF_LEN)) { break; } thinkos_flag_take(tn->rx.nonfull_flag); } } tn->rx.buf[head++ % TELNET_SVC_RX_BUF_LEN] = c; } } continue; } /* handles TELNET inputs options */ switch (state) { case TN_IAC_RCVD: switch (c) { case IAC: state = TN_DATA; break; case DONT: state = TN_DONT_RCVD; break; case DO: state = TN_DO_RCVD; break; case WONT: state = TN_WONT_RCVD; break; case WILL: state = TN_WILL_RCVD; break; case SB: state = TN_SUBOPTION_ID; break; case EL: case EC: case AYT: case AO: case IP: case BREAK: case DM: case NOP: case SE: case EOR: case ABORT: case SUSP: case xEOF: default: state = TN_DATA; break; } break; case TN_DONT_RCVD: DCC_LOG1(LOG_TRACE, "DONT %s", TELOPT(c)); tn_opt_wont(tp, &opt, c); state = TN_DATA; break; case TN_DO_RCVD: DCC_LOG1(LOG_TRACE, "DO %s", TELOPT(c)); switch (c) { case TELOPT_SGA: tn_opt_will(tp, &opt, c); break; case TELOPT_ECHO: tn_opt_will(tp, &opt, c); break; case TELOPT_BINARY: tn_opt_will(tp, &opt, c); break; default: tn_opt_wont(tp, &opt, c); } state = TN_DATA; break; case TN_WONT_RCVD: DCC_LOG1(LOG_TRACE, "WONT %s", TELOPT(c)); tn_opt_dont(tp, &opt, c); state = TN_DATA; break; case TN_WILL_RCVD: DCC_LOG1(LOG_TRACE, "WILL %s", TELOPT(c)); switch (c) { case TELOPT_ECHO: tn_opt_dont(tp, &opt, c); break; case TELOPT_SGA: tn_opt_do(tp, &opt, c); break; case TELOPT_BINARY: tn_opt_do(tp, &opt, c); binary = 1; break; default: tn_opt_dont(tp, &opt, c); } state = TN_DATA; break; case TN_SUBOPTION_ID: state = TN_SUBOPTION; break; case TN_SUBOPTION: if (c == IAC) state = TN_SB_IAC_RCVD; if (sb_len < TN_SB_BUF_LEN) { DCC_LOG1(LOG_TRACE, "suboption: %d", c); } // sb_buf[sb_len++] = c; break; case TN_SB_IAC_RCVD: if (c == SE) { state = TN_DATA; // tn_suboption(cpc, sb_buf, sb_len); } else { state = TN_SUBOPTION; // sb_buf[sb_len++] = c; } break; case TN_INVALID_SUBOPTION: if (c == IAC) state = TN_INVALID_SB_IAC_RCVD; break; case TN_INVALID_SB_IAC_RCVD: if (c == SE) state = TN_DATA; else state = TN_INVALID_SUBOPTION; break; default: DCC_LOG1(LOG_WARNING, "invalid state: %d!!", state); break; } } } DCC_LOG(LOG_TRACE, "close..."); tcp_close(tp); INF("TELNET connection closed."); tn->tp = NULL; } DCC_LOG(LOG_ERROR, "thread loop break!!!"); for(;;); }
/* 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 allow IACs as input. return 1; /* we don't want to process these. or maybe in future. */ case BREAK: /* break */ #ifdef DBG_OUTRPT fakeEscape = !fakeEscape; return NOP; #endif 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); buf[0] = '0'; // TODO: cpuload 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 */ }