Files
inetutils/telnetd/utility.c
2025-11-27 00:33:35 +01:00

1925 lines
40 KiB
C

/*
Copyright (C) 1993-2025 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 3 of the License, 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 A 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 this program. If not, see `http://www.gnu.org/licenses/'. */
#include <config.h>
#define TELOPTS
#define TELCMDS
#define SLC_NAMES
#include "telnetd.h"
#include <stdarg.h>
#ifdef HAVE_TERMIO_H
# include <termio.h>
#endif
#include <time.h>
#if defined AUTHENTICATION || defined ENCRYPTION
# include <libtelnet/misc.h>
# define NET_ENCRYPT net_encrypt
#else
# define NET_ENCRYPT()
#endif
#ifdef HAVE_TERMCAP_TGETENT
# include <termcap.h>
#elif defined HAVE_CURSES_TGETENT
# include <curses.h>
# ifndef _XOPEN_CURSES
# include <term.h>
# endif
#endif
#if defined HAVE_STREAMSPTY && defined HAVE_GETMSG \
&& defined HAVE_STROPTS_H
# include <stropts.h>
#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;
extern 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;
# ifdef HAVE_TERMIO_H
struct termio *tp;
# endif
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;
# ifdef HAVE_TERMIO_H
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;
# endif/* HAVE_TERMIO_H */
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 (void)
{
pfrontp = pbackp = ptyobuf;
nfrontp = nbackp = netobuf;
#ifdef ENCRYPTION
nclearto = 0;
#endif
netip = netibuf;
ptyip = ptyibuf;
}
void
set_neturg (void)
{
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 (void)
{
return ncc;
}
int
net_output_level (void)
{
return nfrontp - nbackp;
}
int
net_buffer_is_full (void)
{
return (&netobuf[BUFSIZ] - nfrontp) < 2;
}
int
net_get_char (int peek)
{
if (peek)
return *netip;
else if (ncc > 0)
{
ncc--;
return *netip++ & 0377;
}
return 0;
}
int
net_read (void)
{
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);
/* NOT REACHED */
}
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 (void)
{
return (&ptyobuf[BUFSIZ] - pfrontp) < 2;
}
void
pty_output_byte (int c)
{
*pfrontp++ = c;
}
void
pty_output_datalen (const void *data, size_t len)
{
if ((size_t) (&ptyobuf[BUFSIZ] - pfrontp) > len)
ptyflush ();
memcpy (pfrontp, data, len);
pfrontp += len;
}
int
pty_input_level (void)
{
return pcc;
}
int
pty_output_level (void)
{
return pfrontp - pbackp;
}
void
ptyflush (void)
{
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);
/* NOT REACHED */
}
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;
}
return 0;
}
int
pty_input_putback (const char *str, size_t len)
{
if (len > (size_t) (&ptyibuf[BUFSIZ] - ptyip))
len = &ptyibuf[BUFSIZ] - ptyip;
strncpy (ptyip, str, len);
pcc += len;
return 0;
}
/* pty_read()
*
* Read errors EWOULDBLOCK, EAGAIN, and EIO are
* tweaked into reporting zero bytes input.
* In particular, EIO is known to appear when
* reading off the master side, before having
* an active slave side.
*/
int
pty_read (void)
{
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 ("td: ptyread %d chars\r\n", pcc));
DEBUG (debug_pty_data, 1, printdata ("pd", 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 (void)
{
fd_set rfds;
DEBUG (debug_report, 1, debug_output_data ("td: ttloop\r\n"));
if (nfrontp - nbackp > 0)
netflush ();
FD_ZERO (&rfds);
FD_SET (net, &rfds);
if (1 != select (net + 1, &rfds, NULL, NULL, NULL))
{
syslog (LOG_INFO, "ttloop: select: %m\n");
exit (EXIT_FAILURE);
}
ncc = read (net, netibuf, sizeof netibuf);
if (ncc < 0)
{
syslog (LOG_INFO, "ttloop: read: %m\n");
exit (EXIT_FAILURE);
}
else if (ncc == 0)
{
syslog (LOG_INFO, "ttloop: peer died: %m\n");
exit (EXIT_FAILURE);
}
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, 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, const char *endp)
{
if (current >= endp)
return NULL;
if ((*current & 0xff) != IAC)
return current + 1;
if (current + 1 >= endp)
return NULL;
switch (*(current + 1) & 0xff)
{
case DO:
case DONT:
case WILL:
case WONT:
return current + 3 <= endp ? current + 3 : NULL;
case SB: /* loop forever looking for the SE */
{
char *look = current + 2;
while (look < endp)
if ((*look++ & 0xff) == IAC && look < endp
&& (*look++ & 0xff) == SE)
return look;
return NULL;
}
default:
return current + 2 <= endp ? current + 2 : NULL;
}
} /* 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) && \
(nfrontp > p + 1 && (((*(p + 1) & 0xff) != EC) && \
((*(p + 1) & 0xff) != EL))))
void
netclear (void)
{
char *thisitem, *next;
char *good;
#ifdef ENCRYPTION
thisitem = nclearto > netobuf ? nclearto : netobuf;
#else /* ENCRYPTION */
thisitem = netobuf;
#endif /* ENCRYPTION */
while ((next = nextitem (thisitem, nbackp)) != NULL && next <= 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 (thisitem != NULL && nfrontp > thisitem)
{
if (wewant (thisitem))
{
int length;
for (next = thisitem;
next != NULL && wewant (next) && nfrontp > next;
next = nextitem (next, nfrontp))
;
if (next == NULL)
next = nfrontp;
length = next - thisitem;
memmove (good, thisitem, length);
good += length;
thisitem = next;
}
else
{
thisitem = nextitem (thisitem, nfrontp);
}
}
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 (void)
{
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);
/* NOT REACHED */
}
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 (EXIT_FAILURE);
}
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 (void)
{
if (his_state_is_wont (TELOPT_TTYPE))
return;
settimer (baseline);
net_output_datalen (ttytype_sbbuf, sizeof ttytype_sbbuf);
ttloop (sequenceIs (ttypesubopt, baseline));
}
/*
* Changes terminaltype.
*/
int
getterminaltype (char *uname, size_t len)
{
int retval = -1;
settimer (baseline);
#if defined AUTHENTICATION
/*
* Handle the Authentication option before we do anything else.
* Distinguish the available modes by level:
*
* off: Authentication is forbidden.
* none: Voluntary authentication.
* user, valid, other: Mandatory authentication only.
*/
if (auth_level < 0)
send_wont (TELOPT_AUTHENTICATION, 1);
else
{
if (auth_level > 0)
send_do (TELOPT_AUTHENTICATION, 1);
else
send_will (TELOPT_AUTHENTICATION, 1);
ttloop (his_will_wont_is_changing (TELOPT_AUTHENTICATION));
if (his_state_is_will (TELOPT_AUTHENTICATION))
retval = auth_wait (uname, len);
}
#else /* !AUTHENTICATION */
(void) uname; /* Silence warning. */
(void) len; /* Silence warning. */
#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))
{
free (first);
first = xstrdup (terminaltype);
for (;;)
{
/* Save the unknown name, and request the next name. */
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;
}
}
}
free (first);
free (last);
}
return retval;
}
/*
* Exit status:
*
* 1 Accepted terminal type, or inconclusive,
* 0 Explicitly unsupported type.
*/
int
terminaltypeok (char *s)
{
#ifdef HAVE_TGETENT
char buf[2048];
if (terminaltype == NULL)
return 1;
if (tgetent (buf, s) == 0)
return 0;
#endif /* HAVE_TGETENT */
return 1;
}
/* ************************************************************************* */
/* Debugging support */
static FILE *debug_fp = NULL;
static int
debug_open (void)
{
int um = umask (077);
if (!debug_fp)
debug_fp = fopen ("/tmp/telnet.debug", "a");
umask (um);
return debug_fp == NULL;
}
static int
debug_close (void)
{
if (debug_fp)
fclose (debug_fp);
debug_fp = NULL;
return 0;
}
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 (char *fmt, 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)
{
int i = 0;
#if defined AUTHENTICATION || defined ENCRYPTION
char buf[512];
#endif
/* Silence unwanted debugging to '/tmp/telnet.debug'.
*
* XXX: Better location?
*/
if ((pointer[0] == TELOPT_AUTHENTICATION && debug_level[debug_auth] < 1)
|| (pointer[0] == TELOPT_ENCRYPT && debug_level[debug_encr] < 1))
return;
if (direction)
{
debug_output_data ("td: %s suboption ",
direction == '<' ? "recv" : "send");
if (length >= 3)
{
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]);
/* Protocol enforced duplication of IAC depends on pre
* and post modification of data, hence differs between
* in, out, and no direction, i.e., recursive mode.
* Some systems assign _POSIX_VDISABLE and IAC the same
* value! Heuristic experiments led to the following.
* Recursive mode needs both steps as written here.
*/
if ((pointer[i + SLC_VALUE] == IAC) &&
(pointer[i + SLC_VALUE + 1] == IAC) && (direction != '<'))
i++;
if ((pointer[i + SLC_VALUE] == IAC) &&
(pointer[i + SLC_VALUE + 1] == IAC) && !direction)
i += 2;
}
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[sizeof ("|EDIT|TRAPSIG|SOFT_TAB|LIT_ECHO|ACK")];
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[0] ? &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:
{
char *cp;
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:
{
char *quote = "";
for (i = 2; i < length; i++)
{
switch (pointer[i])
{
case NEW_ENV_VAR:
debug_output_data ("%sVAR ", quote);
quote = "";
break;
case NEW_ENV_VALUE:
debug_output_data ("%sVALUE ", quote);
quote = "";
break;
case ENV_ESC:
debug_output_data ("%sESC ", quote);
quote = "";
break;
case ENV_USERVAR:
debug_output_data ("%sUSERVAR ", quote);
quote = "";
break;
default:
if (isprint (pointer[i]) && pointer[i] != '"')
{
if (strcmp (quote, "") == 0)
{
debug_output_data ("\"");
quote = "\" ";
}
debug_output_datalen ((char *) &pointer[i], 1);
}
else
{
debug_output_data ("%s%03o ", quote, pointer[i]);
quote = "";
}
break;
}
}
if (strcmp (quote, "\" ") == 0)
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]) && AUTHTYPE_NAME (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]) && AUTHTYPE_NAME (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 ((char *) &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]) && ENCTYPE_NAME (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]) && ENCTYPE_NAME (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[0]);
for (i = 1; i < length; i++)
debug_output_data (" %d", pointer[i]);
break;
}
/* Without direction, we are doing a recursive suboption printing.
* Suppress NL, which would otherwise misplace the SE marker.
*/
if (direction)
debug_output_data ("\r\n");
}
/*
* Dump a data buffer in hex and ascii to the output data stream.
*/
void
printdata (char *tag, char *ptr, int cnt)
{
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", (unsigned char) *ptr);
xbuf[i] = isprint ((int) *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 (void)
{
# 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 (void)
{
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 (struct line_expander *exp);
static char *_var_long_name (struct line_expander *exp,
char *start, int length);
static char *_expand_var (struct line_expander *exp);
static void _expand_cond (struct line_expander *exp);
static void _skip_block (struct line_expander *exp);
static void _expand_block (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 'L':
return xstrdup (line);
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;
case 'U':
return getenv ("USER") ? xstrdup (getenv ("USER")) : xstrdup ("");
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)
{
(void) start; /* Silence warnings until implemented. */
(void) 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:
%?<var>{true-stmt}[{false-stmt}]
<var> 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 = (char *) line;
exp.cp = (char *) 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;
}