teln.c   [plain text]


/* Copyright (c) 1993-2000
 *      Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
 *      Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de)
 * Copyright (c) 1987 Oliver Laumann
 *
 * This program 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.
 *
 * This program 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 (see the file COPYING); if not, write to the
 * Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
 *
 ****************************************************************
 */

#include "rcs.h"
RCS_ID("$Id: teln.c,v 1.1.1.1 2001/12/14 22:08:29 bbraun Exp $ FAU")

#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netdb.h>

#include "config.h"

#ifdef BUILTIN_TELNET

#include "screen.h"
#include "extern.h"

extern struct win *fore;
extern struct layer *flayer;
extern int visual_bell;
extern char screenterm[];

static void TelReply __P((struct win *, char *, int));
static void TelDocmd __P((struct win *, int, int));
static void TelDosub __P((struct win *));

#define TEL_DEFPORT	23
#define TEL_CONNECTING	(-2)

#define TC_IAC          255
#define TC_DONT         254
#define TC_DO           253
#define TC_WONT         252
#define TC_WILL         251
#define TC_SB           250
#define TC_BREAK        243
#define TC_SE           240

#define TC_S "S  b      swWdDc"

#define TO_BINARY       0
#define TO_ECHO         1
#define TO_SGA          3
#define TO_TM           6
#define TO_TTYPE        24
#define TO_NAWS         31
#define TO_TSPEED       32
#define TO_LFLOW        33
#define TO_XDISPLOC     35
#define TO_NEWENV       39

#define TO_S "be c                    t      wsf xE  E"


static unsigned char tn_init[] = {
  TC_IAC, TC_DO, TO_SGA,
  TC_IAC, TC_WILL, TO_TTYPE,
  TC_IAC, TC_WILL, TO_NAWS,
  TC_IAC, TC_WILL, TO_LFLOW,
};

static void
tel_connev_fn(ev, data)
struct event *ev;
char *data;
{
  struct win *p = (struct win *)data;
  if (connect(p->w_ptyfd, (struct sockaddr *)&p->w_telsa, sizeof(p->w_telsa)) && errno != EISCONN)
    {
      char buf[1024];
      buf[0] = ' ';
      strncpy(buf + 1, strerror(errno), sizeof(buf) - 2);
      buf[sizeof(buf) - 1] = 0;
      WriteString(p, buf, strlen(buf));
      WindowDied(p);
      return;
    }
  WriteString(p, "connected.\r\n", 12);
  evdeq(&p->w_telconnev);
  p->w_telstate = 0;
}

int
TelOpen(args)
char **args;
{
  int fd;
  int on = 1;

  if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
    {
      Msg(errno, "TelOpen: socket");
      return -1;
    }
  if (setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)))
    Msg(errno, "TelOpen: setsockopt SO_OOBINLINE");
  return fd;
}

int
TelConnect(p)
struct win *p;
{
  int port = TEL_DEFPORT;
  struct hostent *hp;
  char **args;
  char buf[256];

  args = p->w_cmdargs + 1;

  if (!*args)
    {
      Msg(0, "Usage: screen //telnet host [port]");
      return -1;
    }
  if (args[1])
    port = atoi(args[1]);
  p->w_telsa.sin_family = AF_INET;
  if((p->w_telsa.sin_addr.s_addr = inet_addr(*args)) == -1)
    {
      if ((hp = gethostbyname(*args)) == NULL)
        {
	  Msg(0, "unknown host: %s", *args);
	  return -1;
        }
      bcopy((char *)hp->h_addr,(char *)&p->w_telsa.sin_addr.s_addr, hp->h_length);
      p->w_telsa.sin_family = hp->h_addrtype;
    }
  p->w_telsa.sin_port = htons(port);
    if (port != TEL_DEFPORT)
    sprintf(buf, "Trying %s %d...", inet_ntoa(p->w_telsa.sin_addr), port);
  else
    sprintf(buf, "Trying %s...", inet_ntoa(p->w_telsa.sin_addr));
  WriteString(p, buf, strlen(buf));
  if (connect(p->w_ptyfd, (struct sockaddr *)&p->w_telsa, sizeof(p->w_telsa)))
    {
      if (errno == EINPROGRESS)
        {
	  p->w_telstate = TEL_CONNECTING;
	  p->w_telconnev.fd = p->w_ptyfd;
	  p->w_telconnev.handler = tel_connev_fn;
	  p->w_telconnev.data = (char *)p;
	  p->w_telconnev.type = EV_WRITE;
	  p->w_telconnev.pri = 1;
	  debug("telnet connect in progress...\n");
	  evenq(&p->w_telconnev);
	}
      else
        {
	  Msg(errno, "TelOpen: connect");
	  return -1;
	}
    }
  else
    WriteString(p, "connected.\r\n", 12);
  if (port == TEL_DEFPORT)
    TelReply(p, (char *)tn_init, sizeof(tn_init));
  return 0;
}

int
TelIsline(p)
struct win *p;
{
  return !fore->w_telropts[TO_SGA];
}

void
TelProcessLine(bufpp, lenp)
char **bufpp;
int *lenp;
{
  int echo = !fore->w_telropts[TO_ECHO];
  unsigned char c;
  char *tb;
  int tl;

  char *buf = *bufpp;
  int l = *lenp;
  while (l--)
    {
      c = *(unsigned char *)buf++;
      if (fore->w_telbufl + 2 >= IOSIZE)
	{
	  WBell(fore, visual_bell);
	  continue;
	}
      if (c == '\r')
	{
	  if (echo)
	    WriteString(fore, "\r\n", 2);
	  fore->w_telbuf[fore->w_telbufl++] = '\r';
	  fore->w_telbuf[fore->w_telbufl++] = '\n';
	  tb = fore->w_telbuf;
	  tl = fore->w_telbufl;
	  LayProcess(&tb, &tl);
	  fore->w_telbufl = 0;
	  continue;
	}
      if (c == '\b' && fore->w_telbufl > 0)
	{
	  if (echo)
	    {
	      WriteString(fore, (char *)&c, 1);
	      WriteString(fore, " ", 1);
	      WriteString(fore, (char *)&c, 1);
	    }
	  fore->w_telbufl--;
	}
      if ((c >= 0x20 && c <= 0x7e) || (c >= 0xa0 && c <= 0xff))
	{
	  if (echo)
	    WriteString(fore, (char *)&c, 1);
	  fore->w_telbuf[fore->w_telbufl++] = c;
	}
    }
  *lenp = 0;
}

int
DoTelnet(buf, lenp, f)
char *buf;
int *lenp;
int f;
{
  int echo = !fore->w_telropts[TO_ECHO];
  int cmode = fore->w_telropts[TO_SGA];
  int bin = fore->w_telropts[TO_BINARY];
  char *p = buf, *sbuf;
  int trunc = 0;
  int c;
  int l = *lenp;

  sbuf = p;
  while (l-- > 0)
    {
      c = *(unsigned char *)p++;
      if (c == TC_IAC || (c == '\r' && (l ==0 || *p != '\n') && cmode && !bin))
	{
	  if (cmode && echo)
	    {
	      WriteString(fore, sbuf, p - sbuf);
	      sbuf = p;
	    }
	  if (f-- <= 0)
	    {
	      trunc++;
	      l--;
	    }
	  if (l < 0)
	    {
	      p--;	/* drop char */
	      break;
	    }
	  if (l)
	    bcopy(p, p + 1, l);
	  if (c == TC_IAC)
	    *p++ = c;
	  else if (c == '\r')
	    *p++ = 0;
	  else if (c == '\n')
	    {
	      p[-1] = '\r';
	      *p++ = '\n';
	    }
	}
    }
  *lenp = p - buf;
  return trunc;
}

/* modifies data in-place, returns new length */
int
TelIn(p, buf, len, free)
struct win *p;
char *buf;
int len;
int free;
{
  char *rp, *wp;
  int c;

  rp = wp = buf;
  while (len-- > 0)
    {
      c = *(unsigned char *)rp++;

      if (p->w_telstate >= TC_WILL && p->w_telstate <= TC_DONT)
	{
	  TelDocmd(p, p->w_telstate, c);
	  p->w_telstate = 0;
	  continue;
	}
      if (p->w_telstate == TC_SB || p->w_telstate == TC_SE)
	{
	  if (p->w_telstate == TC_SE && c == TC_IAC)
	    p->w_telsubidx--;
	  if (p->w_telstate == TC_SE && c == TC_SE)
	    {
	      p->w_telsubidx--;
	      TelDosub(p);
	      p->w_telstate = 0;
	      continue;
	    }
	  if (p->w_telstate == TC_SB && c == TC_IAC)
	    p->w_telstate = TC_SE;
	  else
	    p->w_telstate = TC_SB;
	  p->w_telsubbuf[p->w_telsubidx] = c;
	  if (p->w_telsubidx < sizeof(p->w_telsubbuf) - 1)
	    p->w_telsubidx++;
	  continue;
	}
      if (p->w_telstate == TC_IAC)
	{
	  if ((c >= TC_WILL && c <= TC_DONT) || c == TC_SB)
	    {
	      p->w_telsubidx = 0;
	      p->w_telstate = c;
	      continue;
	    }
	  p->w_telstate = 0;
	  if (c != TC_IAC)
	    continue;
	}
      else if (c == TC_IAC)
	{
	  p->w_telstate = c;
	  continue;
	}
      if (p->w_telstate == '\r')
	{
	  p->w_telstate = 0;
	  if (c == 0)
	    continue;		/* suppress trailing \0 */
	}
      else if (c == '\n' && !p->w_telropts[TO_SGA])
	{
	  /* oops... simulate terminal line mode: insert \r */
	  if (wp + 1 == rp)
	    {
	      if (free-- > 0)
		{
		  if (len)
		    bcopy(rp, rp + 1, len);
		  rp++;
		  *wp++ = '\r';
		}
	    }
	  else
	    *wp++ = '\r';
	}
      if (c == '\r')
        p->w_telstate = c;
      *wp++ = c;
    }
  return wp - buf;
}

static void
TelReply(p, str, len)
struct win *p;
char *str;
int len;
{
  if (len <= 0)
    return;
  if (p->w_inlen + len > IOSIZE)
    {
      Msg(0, "Warning: telnet protocol overrun!");
      return;
    }
  bcopy(str, p->w_inbuf + p->w_inlen, len);
  p->w_inlen += len;
}

static void
TelDocmd(p, cmd, opt)
struct win *p;
int cmd, opt;
{
  unsigned char b[3];
  int repl = 0;

  if (cmd == TC_WONT)
    debug2("[WONT %c %d]\n", TO_S[opt], opt);
  if (cmd == TC_WILL)
    debug2("[WILL %c %d]\n", TO_S[opt], opt);
  if (cmd == TC_DONT)
    debug2("[DONT %c %d]\n", TO_S[opt], opt);
  if (cmd == TC_DO)
    debug2("[DO  %c %d]\n", TO_S[opt], opt);

  switch(cmd)
    {
    case TC_WILL:
      if (p->w_telropts[opt] || opt == TO_TM)
	return;
      repl = TC_DONT;
      if (opt == TO_ECHO || opt == TO_SGA || opt == TO_BINARY)
	{
	  p->w_telropts[opt] = 1;
	  /* setcon(); */
	  repl = TC_DO;
	}
      break;
    case TC_WONT:
      if (!p->w_telropts[opt] || opt == TO_TM)
	return;
      repl = TC_DONT;
#if 0
      if (opt == TO_ECHO || opt == TO_SGA)
	setcon();
#endif
      p->w_telropts[opt] = 0;
      break;
    case TC_DO:
      if (p->w_telmopts[opt])
	return;
      repl = TC_WONT;
      if (opt == TO_TTYPE || opt == TO_SGA || opt == TO_BINARY || opt == TO_NAWS || opt == TO_TM || opt == TO_LFLOW)
	{
	  repl = TC_WILL;
	  p->w_telmopts[opt] = 1;
	}
      p->w_telmopts[TO_TM] = 0;
      break;
    case TC_DONT:
      if (!p->w_telmopts[opt])
	return;
      repl = TC_WONT;
      p->w_telmopts[opt] = 0;
      break;
    }
  b[0] = TC_IAC;
  b[1] = repl;
  b[2] = opt;
  TelReply(p, (char *)b, 3);
  if (cmd == TC_DO && opt == TO_NAWS)
    TelWindowSize(p);
}


static void
TelDosub(p)
struct win *p;
{
  char trepl[20 + 6 + 1];
  int l;

  switch(p->w_telsubbuf[0])
    {
    case TO_TTYPE:
      if (p->w_telsubidx != 2 || p->w_telsubbuf[1] != 1)
	return;
      l = strlen(screenterm);
      if (l >= 20)
	break;
      sprintf(trepl, "%c%c%c%c%s%c%c", TC_IAC, TC_SB, TO_TTYPE, 0, screenterm, TC_IAC, TC_SE);
      TelReply(p, trepl, l + 6);
      break;
    case TO_LFLOW:
      if (p->w_telsubidx != 2)
	return;
      debug1("[FLOW %d]\r\n", p->w_telsubbuf[1]);
      break;
    default:
      break;
   }
}

void
TelBreak(p)
struct win *p;
{
  static unsigned char tel_break[] = { TC_IAC, TC_BREAK };
  TelReply(p, (char *)tel_break, 2);
}

void
TelWindowSize(p)
struct win *p;
{
  char s[20], trepl[20], *t;
  int i;

  debug2("TelWindowSize %d %d\n", p->w_width, p->w_height);
  if (p->w_width == 0 || p->w_height == 0 || !p->w_telmopts[TO_NAWS])
    return;
  sprintf(s, "%c%c%c%c%c%c%c%c%c", TC_SB, TC_SB, TO_NAWS, p->w_width / 256, p->w_width & 255, p->w_height / 256, p->w_height & 255, TC_SE, TC_SE);
  t = trepl;
  for (i = 0; i < 9; i++)
    if ((unsigned char)(*t++ = s[i]) == TC_IAC)
      *t++ = TC_IAC;
  trepl[0] = TC_IAC;
  t[-2] = TC_IAC;
  debug(" - sending");
  for (i = 0; trepl + i < t; i++)
    debug1(" %02x", (unsigned char)trepl[i]);
  debug("\n");
  TelReply(p, trepl, t - trepl);
}

static char tc_s[] = TC_S;
static char to_s[] = TO_S;

void
TelStatus(p, buf, l)
struct win *p;
char *buf;
int l;
{
  int i;

  *buf++ = '[';
  for (i = 0; to_s[i]; i++)
    {
      if (to_s[i] == ' ' || p->w_telmopts[i] == 0)
	continue;
      *buf++ = to_s[i];
    }
  *buf++ = ':';
  for (i = 0; to_s[i]; i++)
    {
      if (to_s[i] == ' ' || p->w_telropts[i] == 0)
	continue;
      *buf++ = to_s[i];
    }
  if (p->w_telstate == TEL_CONNECTING)
    buf[-1] = 'C';
  else if (p->w_telstate && p->w_telstate != '\r')
    {
      *buf++ = ':';
      *buf++ = tc_s[p->w_telstate - TC_SE];
    }
  *buf++ = ']';
  *buf = 0;
  return;
}

#endif /* BUILTIN_TELNET */