/* Copyright (C) 1998,2001 Free Software Foundation, Inc. This file is part of GNU Inetutils. GNU Inetutils is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU Inetutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Inetutils; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #define TELOPTS #define TELCMDS #define SLC_NAMES #include "telnetd.h" #include #ifdef HAVE_TERMIO_H # include #endif #if defined(AUTHENTICATION) || defined(ENCRYPTION) # include # define NET_ENCRYPT net_encrypt #else # define NET_ENCRYPT() #endif static char netobuf[BUFSIZ+NETSLOP], *nfrontp, *nbackp; static char *neturg; /* one past last byte of urgent data */ #ifdef ENCRYPTION static char *nclearto; #endif static char ptyobuf[BUFSIZ+NETSLOP], *pfrontp, *pbackp; static char netibuf[BUFSIZ], *netip; static int ncc; static char ptyibuf[BUFSIZ], *ptyip; static int pcc; int not42; static int readstream (int p, char *ibuf, int bufsize) { #ifndef HAVE_STREAMSPTY return read (p, ibuf, bufsize); #else int flags = 0; int ret = 0; struct termios *tsp; struct termio *tp; struct iocblk *ip; char vstop, vstart; int ixon; int newflow; struct strbuf strbufc, strbufd; unsigned char ctlbuf[BUFSIZ]; static int flowstate = -1; strbufc.maxlen = BUFSIZ; strbufc.buf = (char *)ctlbuf; strbufd.maxlen = bufsize-1; strbufd.len = 0; strbufd.buf = ibuf+1; ibuf[0] = 0; ret = getmsg(p, &strbufc, &strbufd, &flags); if (ret < 0) /* error of some sort -- probably EAGAIN */ return -1; if (strbufc.len <= 0 || ctlbuf[0] == M_DATA) { /* data message */ if (strbufd.len > 0) /* real data */ return strbufd.len + 1; /* count header char */ else { /* nothing there */ errno = EAGAIN; return -1; } } /* * It's a control message. Return 1, to look at the flag we set */ switch (ctlbuf[0]) { case M_FLUSH: if (ibuf[1] & FLUSHW) ibuf[0] = TIOCPKT_FLUSHWRITE; return 1; case M_IOCTL: ip = (struct iocblk *) (ibuf+1); switch (ip->ioc_cmd) { case TCSETS: case TCSETSW: case TCSETSF: tsp = (struct termios *) (ibuf + 1 + sizeof(struct iocblk)); vstop = tsp->c_cc[VSTOP]; vstart = tsp->c_cc[VSTART]; ixon = tsp->c_iflag & IXON; break; case TCSETA: case TCSETAW: case TCSETAF: tp = (struct termio *) (ibuf + 1 + sizeof(struct iocblk)); vstop = tp->c_cc[VSTOP]; vstart = tp->c_cc[VSTART]; ixon = tp->c_iflag & IXON; break; default: errno = EAGAIN; return -1; } newflow = (ixon && (vstart == 021) && (vstop == 023)) ? 1 : 0; if (newflow != flowstate) /* it's a change */ { flowstate = newflow; ibuf[0] = newflow ? TIOCPKT_DOSTOP : TIOCPKT_NOSTOP; return 1; } } /* nothing worth doing anything about */ errno = EAGAIN; return -1; #endif /* HAVE_STREAMSPTY */ } /* ************************************************************************* */ /* Net and PTY I/O functions */ void io_setup () { pfrontp = pbackp = ptyobuf; nfrontp = nbackp = netobuf; #ifdef ENCRYPTION nclearto = 0; #endif netip = netibuf; ptyip = ptyibuf; } void set_neturg () { neturg = nfrontp - 1; } /* net-buffers */ void net_output_byte (int c) { *nfrontp++ = c; } int net_output_data (const char *format,...) { va_list args; size_t remaining, ret; va_start (args, format); remaining = BUFSIZ - (nfrontp - netobuf); /* try a netflush() if the room is too low */ if (strlen (format) > remaining || BUFSIZ / 4 > remaining) { netflush (); remaining = BUFSIZ - (nfrontp - netobuf); } ret = vsnprintf (nfrontp, remaining, format, args); nfrontp += ((ret < remaining - 1) ? ret : remaining - 1); va_end (args); return ret; } int net_output_datalen (const void *buf, size_t l) { size_t remaining; remaining = BUFSIZ - (nfrontp - netobuf); if (remaining < l) { netflush (); remaining = BUFSIZ - (nfrontp - netobuf); } if (remaining < l) return -1; memmove (nfrontp, buf, l); nfrontp += l; return (int) l; } int net_input_level () { return ncc; } int net_output_level () { return nfrontp - nbackp; } int net_buffer_is_full () { return (&netobuf[BUFSIZ] - nfrontp) < 2; } int net_get_char (int peek) { if (peek) return *netip; else if (ncc > 0) { ncc--; return *netip++ & 0377; } } int net_read () { ncc = read (net, netibuf, sizeof (netibuf)); if (ncc < 0 && errno == EWOULDBLOCK) ncc = 0; else if (ncc == 0) { syslog (LOG_INFO, "telnetd: peer died"); cleanup(0); } else if (ncc > 0) { netip = netibuf; DEBUG(debug_report,1, debug_output_data ("td: netread %d chars\r\n", ncc)); DEBUG(debug_net_data,1, printdata("nd", netip, ncc)); } return ncc; } /* PTY buffer functions */ int pty_buffer_is_full () { return (&ptyobuf[BUFSIZ] - pfrontp) < 2; } void pty_output_byte (int c) { *pfrontp++ = c; } void pty_output_datalen (const void *data, size_t len) { if ((&ptyobuf[BUFSIZ] - pfrontp) > len) ptyflush (); memcpy (pfrontp, data, len); pfrontp += len; } int pty_input_level () { return pcc; } int pty_output_level () { return pfrontp - pbackp; } void ptyflush() { int n; if ((n = pfrontp - pbackp) > 0) { DEBUG(debug_report, 1, debug_output_data ("td: ptyflush %d chars\r\n", n)); DEBUG(debug_pty_data, 1, printdata("pd", pbackp, n)); n = write(pty, pbackp, n); } if (n < 0) { if (errno == EWOULDBLOCK || errno == EINTR) return; cleanup(0); } pbackp += n; if (pbackp == pfrontp) pbackp = pfrontp = ptyobuf; } int pty_get_char (int peek) { if (peek) return *ptyip; else if (pcc > 0) { pcc--; return *ptyip++ & 0377; } } int pty_input_putback (const char *str, size_t len) { if (len > &ptyibuf[BUFSIZ] - ptyip) len = &ptyibuf[BUFSIZ] - ptyip; strncpy (ptyip, str, len); pcc += len; } int pty_read () { pcc = readstream (pty, ptyibuf, BUFSIZ); if (pcc < 0 && (errno == EWOULDBLOCK #ifdef EAGAIN || errno == EAGAIN #endif || errno == EIO)) pcc = 0; ptyip = ptyibuf; DEBUG(debug_report,1, debug_output_data ("ptyread %d chars\r\n", pcc)); DEBUG(debug_pty_data,1, printdata("pty", ptyip, pcc)); return pcc; } /* ************************************************************************* */ /* io_drain () * * * A small subroutine to flush the network output buffer, get some data * from the network, and pass it through the telnet state machine. We * also flush the pty input buffer (by dropping its data) if it becomes * too full. */ void io_drain () { DEBUG(debug_report, 1, debug_output_data("td: ttloop\r\n")); if (nfrontp - nbackp > 0) netflush (); again: ncc = read (net, netibuf, sizeof netibuf); if (ncc < 0) { if (errno == EAGAIN) { syslog (LOG_INFO, "ttloop: retrying"); goto again; } syslog (LOG_INFO, "ttloop: read: %m\n"); exit (1); } else if (ncc == 0) { syslog (LOG_INFO, "ttloop: peer died: %m\n"); exit (1); } DEBUG(debug_report, 1, debug_output_data ("td: ttloop read %d chars\r\n", ncc)); netip = netibuf; telrcv (); /* state machine */ if (ncc > 0) { pfrontp = pbackp = ptyobuf; telrcv (); } } /* end of ttloop */ /* * Check a descriptor to see if out of band data exists on it. */ /* int s; socket number */ int stilloob (int s) { static struct timeval timeout = { 0 }; fd_set excepts; int value; do { FD_ZERO (&excepts); FD_SET (s, &excepts); value = select (s+1, (fd_set *)0, (fd_set *)0, &excepts, &timeout); } while (value == -1 && errno == EINTR); if (value < 0) fatalperror (pty, "select"); return FD_ISSET (s, &excepts); } /* * nextitem() * * Return the address of the next "item" in the TELNET data * stream. This will be the address of the next character if * the current address is a user data character, or it will * be the address of the character following the TELNET command * if the current address is a TELNET IAC ("I Am a Command") * character. */ char * nextitem (char *current) { if ((*current&0xff) != IAC) return current+1; switch (*(current+1)&0xff) { case DO: case DONT: case WILL: case WONT: return current+3; case SB: /* loop forever looking for the SE */ { register char *look = current+2; for (;;) if ((*look++&0xff) == IAC && (*look++&0xff) == SE) return look; default: return current+2; } } } /* end of nextitem */ /* * netclear() * * We are about to do a TELNET SYNCH operation. Clear * the path to the network. * * Things are a bit tricky since we may have sent the first * byte or so of a previous TELNET command into the network. * So, we have to scan the network buffer from the beginning * until we are up to where we want to be. * * A side effect of what we do, just to keep things * simple, is to clear the urgent data pointer. The principal * caller should be setting the urgent data pointer AFTER calling * us in any case. */ #define wewant(p) \ ((nfrontp > p) && ((*p&0xff) == IAC) && \ ((*(p+1)&0xff) != EC) && ((*(p+1)&0xff) != EL)) void netclear () { register char *thisitem, *next; char *good; #ifdef ENCRYPTION thisitem = nclearto > netobuf ? nclearto : netobuf; #else /* ENCRYPTION */ thisitem = netobuf; #endif /* ENCRYPTION */ while ((next = nextitem (thisitem)) <= nbackp) thisitem = next; /* Now, thisitem is first before/at boundary. */ #ifdef ENCRYPTION good = nclearto > netobuf ? nclearto : netobuf; #else /* ENCRYPTION */ good = netobuf; /* where the good bytes go */ #endif /* ENCRYPTION */ while (nfrontp > thisitem) { if (wewant (thisitem)) { int length; for (next = thisitem; wewant (next) && nfrontp > next; next = nextitem(next)) ; length = next - thisitem; memmove (good, thisitem, length); good += length; thisitem = next; } else { thisitem = nextitem (thisitem); } } nbackp = netobuf; nfrontp = good; /* next byte to be sent */ neturg = 0; } /* end of netclear */ /* * netflush * Send as much data as possible to the network, * handling requests for urgent data. */ void netflush () { int n; if ((n = nfrontp - nbackp) > 0) { NET_ENCRYPT (); /* * if no urgent data, or if the other side appears to be an * old 4.2 client (and thus unable to survive TCP urgent data), * write the entire buffer in non-OOB mode. */ if (!neturg || !not42) n = write (net, nbackp, n); /* normal write */ else { n = neturg - nbackp; /* * In 4.2 (and 4.3) systems, there is some question about * what byte in a sendOOB operation is the "OOB" data. * To make ourselves compatible, we only send ONE byte * out of band, the one WE THINK should be OOB (though * we really have more the TCP philosophy of urgent data * rather than the Unix philosophy of OOB data). */ if (n > 1) n = send (net, nbackp, n-1, 0); /* send URGENT all by itself */ else n = send (net, nbackp, n, MSG_OOB); /* URGENT data */ } } if (n < 0) { if (errno == EWOULDBLOCK || errno == EINTR) return; cleanup (0); } nbackp += n; #ifdef ENCRYPTION if (nbackp > nclearto) nclearto = 0; #endif /* ENCRYPTION */ if (nbackp >= neturg) neturg = 0; if (nbackp == nfrontp) { nbackp = nfrontp = netobuf; #ifdef ENCRYPTION nclearto = 0; #endif /* ENCRYPTION */ } DEBUG(debug_report, 1, debug_output_data ("td: netflush %d chars\r\n", n)); } /* end of netflush */ /* * miscellaneous functions doing a variety of little jobs follow ... */ void fatal (int f, char *msg) { char buf[BUFSIZ]; snprintf (buf, sizeof buf, "telnetd: %s.\r\n", msg); #ifdef ENCRYPTION if (encrypt_output) { /* * Better turn off encryption first.... * Hope it flushes... */ encrypt_send_end (); netflush (); } #endif /* ENCRYPTION */ write (f, buf, (int)strlen (buf)); sleep (1); /*FIXME*/ exit (1); } void fatalperror (int f, char *msg) { char buf[BUFSIZ]; snprintf (buf, sizeof buf, "%s: %s", msg, strerror (errno)); fatal (f, buf); } /* ************************************************************************* */ /* Terminal determination */ static unsigned char ttytype_sbbuf[] = { IAC, SB, TELOPT_TTYPE, TELQUAL_SEND, IAC, SE }; static void _gettermname () { if (his_state_is_wont (TELOPT_TTYPE)) return; settimer (baseline); net_output_datalen (ttytype_sbbuf, sizeof ttytype_sbbuf); ttloop (sequenceIs (ttypesubopt, baseline)); } /* FIXME: should be getterminaltype (char *user_name, size_t size) Changes terminaltype. */ int getterminaltype (char *user_name) { int retval = -1; settimer (baseline); #if defined(AUTHENTICATION) /* * Handle the Authentication option before we do anything else. */ send_do (TELOPT_AUTHENTICATION, 1); ttloop (his_will_wont_is_changing (TELOPT_AUTHENTICATION)); if (his_state_is_will (TELOPT_AUTHENTICATION)) retval = auth_wait (user_name); #endif #ifdef ENCRYPTION send_will (TELOPT_ENCRYPT, 1); #endif /* ENCRYPTION */ send_do (TELOPT_TTYPE, 1); send_do (TELOPT_TSPEED, 1); send_do (TELOPT_XDISPLOC, 1); send_do (TELOPT_NEW_ENVIRON, 1); send_do (TELOPT_OLD_ENVIRON, 1); #ifdef ENCRYPTION ttloop (his_do_dont_is_changing (TELOPT_ENCRYPT) || his_will_wont_is_changing (TELOPT_TTYPE) || his_will_wont_is_changing (TELOPT_TSPEED) || his_will_wont_is_changing (TELOPT_XDISPLOC) || his_will_wont_is_changing (TELOPT_NEW_ENVIRON) || his_will_wont_is_changing (TELOPT_OLD_ENVIRON)); #else ttloop (his_will_wont_is_changing (TELOPT_TTYPE) || his_will_wont_is_changing (TELOPT_TSPEED) || his_will_wont_is_changing (TELOPT_XDISPLOC) || his_will_wont_is_changing (TELOPT_NEW_ENVIRON) || his_will_wont_is_changing (TELOPT_OLD_ENVIRON)); #endif #ifdef ENCRYPTION if (his_state_is_will (TELOPT_ENCRYPT)) encrypt_wait (); #endif if (his_state_is_will (TELOPT_TSPEED)) { static unsigned char sb[] = {IAC, SB, TELOPT_TSPEED, TELQUAL_SEND, IAC, SE}; net_output_datalen (sb, sizeof sb); } if (his_state_is_will (TELOPT_XDISPLOC)) { static unsigned char sb[] = {IAC, SB, TELOPT_XDISPLOC, TELQUAL_SEND, IAC, SE}; net_output_datalen (sb, sizeof sb); } if (his_state_is_will (TELOPT_NEW_ENVIRON)) { static unsigned char sb[] = {IAC, SB, TELOPT_NEW_ENVIRON, TELQUAL_SEND, IAC, SE}; net_output_datalen (sb, sizeof sb); } else if (his_state_is_will (TELOPT_OLD_ENVIRON)) { static unsigned char sb[] = {IAC, SB, TELOPT_OLD_ENVIRON, TELQUAL_SEND, IAC, SE}; net_output_datalen (sb, sizeof sb); } if (his_state_is_will (TELOPT_TTYPE)) net_output_datalen (ttytype_sbbuf, sizeof ttytype_sbbuf); if (his_state_is_will (TELOPT_TSPEED)) ttloop (sequenceIs (tspeedsubopt, baseline)); if (his_state_is_will (TELOPT_XDISPLOC)) ttloop (sequenceIs (xdisplocsubopt, baseline)); if (his_state_is_will (TELOPT_NEW_ENVIRON)) ttloop (sequenceIs (environsubopt, baseline)); if (his_state_is_will (TELOPT_OLD_ENVIRON)) ttloop (sequenceIs (oenvironsubopt, baseline)); if (his_state_is_will (TELOPT_TTYPE)) { char *first = NULL, *last = NULL; ttloop (sequenceIs (ttypesubopt, baseline)); /* * If the other side has already disabled the option, then * we have to just go with what we (might) have already gotten. */ if (his_state_is_will (TELOPT_TTYPE) && !terminaltypeok (terminaltype)) { if (first) free (first); first = xstrdup (terminaltype); for (;;) { /* Save the unknown name, and request the next name. */ if (last) free (last); last = xstrdup (terminaltype); _gettermname (); if (terminaltypeok (terminaltype)) break; if ((strcmp (last, terminaltype) == 0) || his_state_is_wont (TELOPT_TTYPE)) { /* * We've hit the end. If this is the same as * the first name, just go with it. */ if (strcmp (first, terminaltype) == 0) break; /* * Get the terminal name one more time, so that * RFC1091 compliant telnets will cycle back to * the start of the list. */ _gettermname (); if (strcmp (first, terminaltype) != 0) { free (terminaltype); terminaltype = xstrdup (first); } break; } } } if (first) free (first); if (last) free (last); } return retval; } int terminaltypeok (char *s) { char buf[1024]; if (terminaltype == NULL) return 1; if (tgetent (buf, s) == 0) return 0; return 1; } /* ************************************************************************* */ /* Debugging support */ static FILE *debug_fp = NULL; static int debug_open () { int um = umask(077); if (!debug_fp) debug_fp = fopen ("/tmp/telnet.debug", "a"); umask(um); return debug_fp == NULL; } static int debug_close () { if (debug_fp) fclose (debug_fp); debug_fp = NULL; } void debug_output_datalen (const char *data, size_t len) { if (debug_open ()) return; fwrite (data, 1, len, debug_fp); debug_close (); } void debug_output_data (const char *fmt, ...) { va_list ap; if (debug_open ()) return; va_start (ap, fmt); vfprintf (debug_fp, fmt, ap); va_end (ap); debug_close (); } /* * Print telnet options and commands in plain text, if possible. */ void printoption(register char *fmt, register int option) { if (TELOPT_OK(option)) debug_output_data ("%s %s\r\n", fmt, TELOPT(option)); else if (TELCMD_OK(option)) debug_output_data ("%s %s\r\n", fmt, TELCMD(option)); else debug_output_data ("%s %d\r\n", fmt, option); } /* char direction; '<' or '>' */ /* unsigned char *pointer; where suboption data sits */ /* int length; length of suboption data */ void printsub(int direction, unsigned char *pointer, int length) { register int i; char buf[512]; if (direction) { debug_output_data ("td: %s suboption ", direction == '<' ? "recv" : "send"); if (length >= 3) { register int j; i = pointer[length-2]; j = pointer[length-1]; if (i != IAC || j != SE) { debug_output_data ("(terminated by "); if (TELOPT_OK(i)) debug_output_data ("%s ", TELOPT(i)); else if (TELCMD_OK(i)) debug_output_data ("%s ", TELCMD(i)); else debug_output_data ("%d ", i); if (TELOPT_OK(j)) debug_output_data ("%s", TELOPT(j)); else if (TELCMD_OK(j)) debug_output_data ("%s", TELCMD(j)); else debug_output_data ("%d", j); debug_output_data (", not IAC SE!) "); } } length -= 2; } if (length < 1) { debug_output_data ("(Empty suboption??\?)"); return; } switch (pointer[0]) { case TELOPT_TTYPE: debug_output_data ("TERMINAL-TYPE "); switch (pointer[1]) { case TELQUAL_IS: debug_output_data ("IS \"%.*s\"", length-2, (char *)pointer+2); break; case TELQUAL_SEND: debug_output_data ("SEND"); break; default: debug_output_data ("- unknown qualifier %d (0x%x).", pointer[1], pointer[1]); } break; case TELOPT_TSPEED: debug_output_data ("TERMINAL-SPEED"); if (length < 2) { debug_output_data (" (empty suboption??\?)"); break; } switch (pointer[1]) { case TELQUAL_IS: debug_output_data (" IS %.*s", length-2, (char *)pointer+2); break; default: if (pointer[1] == 1) debug_output_data (" SEND"); else debug_output_data (" %d (unknown)", pointer[1]); for (i = 2; i < length; i++) { debug_output_data (" ?%d?", pointer[i]); } break; } break; case TELOPT_LFLOW: debug_output_data ("TOGGLE-FLOW-CONTROL"); if (length < 2) { debug_output_data (" (empty suboption??\?)"); break; } switch (pointer[1]) { case LFLOW_OFF: debug_output_data (" OFF"); break; case LFLOW_ON: debug_output_data (" ON"); break; case LFLOW_RESTART_ANY: debug_output_data (" RESTART-ANY"); break; case LFLOW_RESTART_XON: debug_output_data (" RESTART-XON"); break; default: debug_output_data (" %d (unknown)", pointer[1]); } for (i = 2; i < length; i++) debug_output_data (" ?%d?", pointer[i]); break; case TELOPT_NAWS: debug_output_data ("NAWS"); if (length < 2) { debug_output_data (" (empty suboption??\?)"); break; } if (length == 2) { debug_output_data (" ?%d?", pointer[1]); break; } debug_output_data (" %d %d (%d)", pointer[1], pointer[2], (int)((((unsigned int)pointer[1])<<8)|((unsigned int)pointer[2]))); if (length == 4) { debug_output_data (" ?%d?", pointer[3]); break; } debug_output_data (" %d %d (%d)", pointer[3], pointer[4], (int)((((unsigned int)pointer[3])<<8)|((unsigned int)pointer[4]))); for (i = 5; i < length; i++) debug_output_data (" ?%d?", pointer[i]); break; case TELOPT_LINEMODE: debug_output_data ("LINEMODE "); if (length < 2) { debug_output_data (" (empty suboption??\?)"); break; } switch (pointer[1]) { case WILL: debug_output_data ("WILL "); goto common; case WONT: debug_output_data ("WONT "); goto common; case DO: debug_output_data ("DO "); goto common; case DONT: debug_output_data ("DONT "); common: if (length < 3) { debug_output_data ("(no option??\?)"); break; } switch (pointer[2]) { case LM_FORWARDMASK: debug_output_data ("Forward Mask"); for (i = 3; i < length; i++) debug_output_data (" %x", pointer[i]); break; default: debug_output_data ("%d (unknown)", pointer[2]); for (i = 3; i < length; i++) debug_output_data (" %d", pointer[i]); break; } break; case LM_SLC: debug_output_data ("SLC"); for (i = 2; i < length - 2; i += 3) { if (SLC_NAME_OK(pointer[i+SLC_FUNC])) debug_output_data (" %s", SLC_NAME(pointer[i+SLC_FUNC])); else debug_output_data (" %d", pointer[i+SLC_FUNC]); switch (pointer[i+SLC_FLAGS]&SLC_LEVELBITS) { case SLC_NOSUPPORT: debug_output_data (" NOSUPPORT"); break; case SLC_CANTCHANGE: debug_output_data (" CANTCHANGE"); break; case SLC_VARIABLE: debug_output_data (" VARIABLE"); break; case SLC_DEFAULT: debug_output_data (" DEFAULT"); break; } debug_output_data ("%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)) debug_output_data ("(0x%x)", pointer[i+SLC_FLAGS]); debug_output_data (" %d;", pointer[i+SLC_VALUE]); if ((pointer[i+SLC_VALUE] == IAC) && (pointer[i+SLC_VALUE+1] == IAC)) i++; } for (; i < length; i++) debug_output_data (" ?%d?", pointer[i]); break; case LM_MODE: debug_output_data ("MODE "); if (length < 3) { debug_output_data ("(no mode??\?)"); break; } { char tbuf[32]; snprintf(tbuf, sizeof(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" : ""); debug_output_data ("%s", tbuf[1] ? &tbuf[1] : "0"); } if (pointer[2]&~(MODE_EDIT|MODE_TRAPSIG|MODE_ACK)) debug_output_data (" (0x%x)", pointer[2]); for (i = 3; i < length; i++) debug_output_data (" ?0x%x?", pointer[i]); break; default: debug_output_data ("%d (unknown)", pointer[1]); for (i = 2; i < length; i++) debug_output_data (" %d", pointer[i]); } break; case TELOPT_STATUS: { register char *cp; register int j, k; debug_output_data ("STATUS"); switch (pointer[1]) { default: if (pointer[1] == TELQUAL_SEND) debug_output_data (" SEND"); else debug_output_data (" %d (unknown)", pointer[1]); for (i = 2; i < length; i++) debug_output_data (" ?%d?", pointer[i]); break; case TELQUAL_IS: debug_output_data (" IS\r\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(pointer[i])) debug_output_data (" %s %s\r\n", cp, TELOPT(pointer[i])); else debug_output_data (" %s %d\r\n", cp, pointer[i]); break; case SB: debug_output_data (" 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) { debug_output_data (" SE"); i = j; } else i = j - 1; debug_output_data ("\r\n"); break; default: debug_output_data (" %d", pointer[i]); break; } } break; } break; } case TELOPT_XDISPLOC: debug_output_data ("X-DISPLAY-LOCATION "); switch (pointer[1]) { case TELQUAL_IS: debug_output_data ("IS \"%.*s\"", length-2, (char *)pointer+2); break; case TELQUAL_SEND: debug_output_data ("SEND"); break; default: debug_output_data ("- unknown qualifier %d (0x%x).", pointer[1], pointer[1]); } break; case TELOPT_NEW_ENVIRON: debug_output_data ("NEW-ENVIRON "); goto env_common1; case TELOPT_OLD_ENVIRON: debug_output_data ("OLD-ENVIRON"); env_common1: switch (pointer[1]) { case TELQUAL_IS: debug_output_data ("IS "); goto env_common; case TELQUAL_SEND: debug_output_data ("SEND "); goto env_common; case TELQUAL_INFO: debug_output_data ("INFO "); env_common: { register int noquote = 2; for (i = 2; i < length; i++ ) { switch (pointer[i]) { case NEW_ENV_VAR: debug_output_data ("\" VAR " + noquote); noquote = 2; break; case NEW_ENV_VALUE: debug_output_data ("\" VALUE " + noquote); noquote = 2; break; case ENV_ESC: debug_output_data ("\" ESC " + noquote); noquote = 2; break; case ENV_USERVAR: debug_output_data ("\" USERVAR " + noquote); noquote = 2; break; default: def_case: if (isprint(pointer[i]) && pointer[i] != '"') { if (noquote) { debug_output_data ("\""); noquote = 0; } debug_output_datalen (&pointer[i], 1); } else { debug_output_data ("\" %03o " + noquote, pointer[i]); noquote = 2; } break; } } if (!noquote) debug_output_data ("\""); break; } } break; #if defined(AUTHENTICATION) case TELOPT_AUTHENTICATION: debug_output_data ("AUTHENTICATION"); if (length < 2) { debug_output_data (" (empty suboption??\?)"); break; } switch (pointer[1]) { case TELQUAL_REPLY: case TELQUAL_IS: debug_output_data (" %s ", (pointer[1] == TELQUAL_IS) ? "IS" : "REPLY"); if (AUTHTYPE_NAME_OK(pointer[2])) debug_output_data ("%s ", AUTHTYPE_NAME(pointer[2])); else debug_output_data ("%d ", pointer[2]); if (length < 3) { debug_output_data ("(partial suboption??\?)"); break; } debug_output_data ("%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)); debug_output_data ("%s", buf); break; case TELQUAL_SEND: i = 2; debug_output_data (" SEND "); while (i < length) { if (AUTHTYPE_NAME_OK(pointer[i])) debug_output_data ("%s ", AUTHTYPE_NAME(pointer[i])); else debug_output_data ("%d ", pointer[i]); if (++i >= length) { debug_output_data ("(partial suboption??\?)"); break; } debug_output_data ("%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; debug_output_data (" NAME \""); debug_output_datalen (&pointer[i], length); i += length; debug_output_data ("\""); break; default: for (i = 2; i < length; i++) debug_output_data (" ?%d?", pointer[i]); break; } break; #endif #ifdef ENCRYPTION case TELOPT_ENCRYPT: debug_output_data ("ENCRYPT"); if (length < 2) { debug_output_data (" (empty suboption??\?)"); break; } switch (pointer[1]) { case ENCRYPT_START: debug_output_data (" START"); break; case ENCRYPT_END: debug_output_data (" END"); break; case ENCRYPT_REQSTART: debug_output_data (" REQUEST-START"); break; case ENCRYPT_REQEND: debug_output_data (" REQUEST-END"); break; case ENCRYPT_IS: case ENCRYPT_REPLY: debug_output_data (" %s ", (pointer[1] == ENCRYPT_IS) ? "IS" : "REPLY"); if (length < 3) { debug_output_data (" (partial suboption??\?)"); break; } if (ENCTYPE_NAME_OK(pointer[2])) debug_output_data ("%s ", ENCTYPE_NAME(pointer[2])); else debug_output_data (" %d (unknown)", pointer[2]); encrypt_printsub(&pointer[1], length - 1, buf, sizeof(buf)); debug_output_data ("%s", buf); break; case ENCRYPT_SUPPORT: i = 2; debug_output_data (" SUPPORT "); while (i < length) { if (ENCTYPE_NAME_OK(pointer[i])) debug_output_data ("%s ", ENCTYPE_NAME(pointer[i])); else debug_output_data ("%d ", pointer[i]); i++; } break; case ENCRYPT_ENC_KEYID: debug_output_data (" ENC_KEYID", pointer[1]); goto encommon; case ENCRYPT_DEC_KEYID: debug_output_data (" DEC_KEYID", pointer[1]); goto encommon; default: debug_output_data (" %d (unknown)", pointer[1]); encommon: for (i = 2; i < length; i++) debug_output_data (" %d", pointer[i]); break; } break; #endif /* ENCRYPTION */ default: if (TELOPT_OK(pointer[0])) debug_output_data ("%s (unknown)", TELOPT(pointer[0])); else debug_output_data ("%d (unknown)", pointer[i]); for (i = 1; i < length; i++) debug_output_data (" %d", pointer[i]); break; } debug_output_data ("\r\n"); } /* * Dump a data buffer in hex and ascii to the output data stream. */ void printdata(register char *tag, register char *ptr, register int cnt) { register int i; char xbuf[30]; while (cnt) { /* add a line of output */ debug_output_data ("%s: ", tag); for (i = 0; i < 20 && cnt; i++) { debug_output_data ("%02x", *ptr); xbuf[i] = isprint(*ptr) ? *ptr : '.'; if (i % 2) debug_output_data (" "); cnt--; ptr++; } xbuf[i] = '\0'; debug_output_data (" %s\r\n", xbuf ); } } #if defined(AUTHENTICATION) || defined(ENCRYPTION) int net_write (unsigned char *str, int len) { return net_output_datalen (str, len); } void net_encrypt () { #ifdef ENCRYPTION char *s = (nclearto > nbackp) ? nclearto : nbackp; if (s < nfrontp && encrypt_output) (*encrypt_output)((unsigned char *)s, nfrontp - s); nclearto = nfrontp; #endif /* ENCRYPTION */ } int telnet_spin() { io_drain (); return 0; } #endif /* ************************************************************************* */ /* String expansion functions */ #define EXP_STATE_CONTINUE 0 #define EXP_STATE_SUCCESS 1 #define EXP_STATE_ERROR 2 struct line_expander { int state; /* Current state */ int level; /* The nesting level */ char *source; /* The source string */ char *cp; /* Current position in the source */ struct obstack stk; /* Obstack for expanded version */ }; static char *_var_short_name P((struct line_expander *exp)); static char *_var_long_name P((struct line_expander *exp, char *start, int length)); static char *_expand_var P((struct line_expander *exp)); static void _expand_cond P((struct line_expander *exp)); static void _skip_block P((struct line_expander *exp)); static void _expand_block P((struct line_expander *exp)); /* Expand a variable referenced by its short one-symbol name. Input: exp->cp points to the variable name. FIXME: not implemented */ char * _var_short_name (struct line_expander *exp) { char *q; char timebuf[64]; time_t t; switch (*exp->cp++) { case 'a': #ifdef AUTHENTICATION if (auth_level >= 0 && autologin == AUTH_VALID) return xstrdup ("ok"); #endif return NULL; case 'd': time (&t); strftime(timebuf, sizeof (timebuf), "%l:%M%P on %A, %d %B %Y", localtime(&t)); return xstrdup (timebuf); case 'h': return xstrdup (remote_hostname); case 'l': return xstrdup (local_hostname); case 't': q = strchr (line + 1, '/'); if (q) q++; else q = line; return xstrdup (q); case 'T': return terminaltype ? xstrdup (terminaltype) : NULL; case 'u': return user_name ? xstrdup (user_name) : NULL; default: exp->state = EXP_STATE_ERROR; return NULL; } } /* Expand a variable referenced by its long name. Input: exp->cp points to initial '(' FIXME: not implemented */ char * _var_long_name (struct line_expander *exp, char *start, int length) { exp->state = EXP_STATE_ERROR; return NULL; } /* Expand a variable to its value. Input: exp->cp points one character _past_ % (or ?) */ char * _expand_var (struct line_expander *exp) { char *p; switch (*exp->cp) { case '{': /* Collect variable name */ for (p = ++exp->cp; *exp->cp && *exp->cp != '}'; exp->cp++) ; if (*exp->cp == 0) { exp->cp = p; exp->state = EXP_STATE_ERROR; break; } p = _var_long_name (exp, p, exp->cp - p); exp->cp++; break; default: p = _var_short_name (exp); break; } return p; } /* Expand a conditional block. A conditional block is: %?{true-stmt}[{false-stmt}] may be either a one-symbol variable name or (string). The latter is not handled yet. On input exp->cp points to % character */ void _expand_cond (struct line_expander *exp) { char *p; if (*++exp->cp == '?') { /* condition */ exp->cp++; p = _expand_var (exp); if (p) { _expand_block (exp); _skip_block (exp); } else { _skip_block (exp); _expand_block (exp); } free (p); } else { p = _expand_var (exp); if (p) obstack_grow (&exp->stk, p, strlen (p)); free (p); } } /* Skip the block. If the exp->cp does not point to the beginning of a block ({ character), the function does nothing */ void _skip_block (struct line_expander *exp) { int level = exp->level; if (*exp->cp != '{') return; for (; *exp->cp; exp->cp++) { switch (*exp->cp) { case '{': exp->level++; break; case '}': exp->level--; if (exp->level == level) { exp->cp++; return; } } } } /* Expand a block within the formatted line. Stops either when end of source line was reached or the nesting reaches the initial value */ void _expand_block (struct line_expander *exp) { int level = exp->level; if (*exp->cp == '{') { exp->level++; exp->cp++;/*FIXME?*/ } while (exp->state == EXP_STATE_CONTINUE) { for (; *exp->cp && *exp->cp != '%'; exp->cp++) { switch (*exp->cp) { case '{': exp->level++; break; case '}': exp->level--; if (exp->level == level) { exp->cp++; return; } break; case '\\': exp->cp++; break; } obstack_1grow (&exp->stk, *exp->cp); } if (*exp->cp == 0) { obstack_1grow (&exp->stk, 0); exp->state = EXP_STATE_SUCCESS; break; } else if (*exp->cp == '%' && exp->cp[1] == '%') { obstack_1grow (&exp->stk, *exp->cp); exp->cp += 2; continue; } _expand_cond (exp); } } /* Expand a format line */ char * expand_line (const char *line) { char *p = NULL; struct line_expander exp; exp.state = EXP_STATE_CONTINUE; exp.level = 0; exp.source = line; exp.cp = line; obstack_init (&exp.stk); _expand_block (&exp); if (exp.state == EXP_STATE_SUCCESS) p = xstrdup (obstack_finish (&exp.stk)); else { syslog (LOG_ERR, "can't expand line: %s", line); syslog (LOG_ERR, "stopped near %s", exp.cp ? exp.cp : "(END)"); } obstack_free (&exp.stk, NULL); return p; }