689 lines
12 KiB
C
689 lines
12 KiB
C
/*
|
|
ttn.c
|
|
*/
|
|
|
|
#ifndef _POSIX_SOURCE
|
|
#define _POSIX_SOURCE 1
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <termios.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <net/hton.h>
|
|
#include <net/netlib.h>
|
|
#include <net/gen/in.h>
|
|
#include <net/gen/inet.h>
|
|
#include <net/gen/netdb.h>
|
|
#include <net/gen/tcp.h>
|
|
#include <net/gen/tcp_io.h>
|
|
#include "ttn.h"
|
|
|
|
#if __STDC__
|
|
#define PROTOTYPE(func,args) func args
|
|
#else
|
|
#define PROTOTYPE(func,args) func()
|
|
#endif
|
|
|
|
static int do_read(int fd, char *buf, unsigned len);
|
|
static void screen(void);
|
|
static void keyboard(void);
|
|
static void send_brk(void);
|
|
static int process_opt (char *bp, int count);
|
|
static void do_option (int optsrt);
|
|
static void dont_option (int optsrt);
|
|
static void will_option (int optsrt);
|
|
static void wont_option (int optsrt);
|
|
static int writeall (int fd, char *buffer, int buf_size);
|
|
static int sb_termtype (char *sb, int count);
|
|
static void fatal(char *fmt, ...);
|
|
static void usage(void);
|
|
|
|
#if DEBUG
|
|
#define where() (fprintf(stderr, "%s %d:", __FILE__, __LINE__))
|
|
#endif
|
|
|
|
static char *prog_name;
|
|
static tcp_fd;
|
|
static char *term_env;
|
|
static int esc_char= '~';
|
|
static enum { LS_NORM, LS_BOL, LS_ESC } line_state= LS_BOL;
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
struct hostent *hostent;
|
|
struct servent *servent;
|
|
ipaddr_t host;
|
|
tcpport_t port;
|
|
int pid, ppid;
|
|
nwio_tcpconf_t tcpconf;
|
|
int c, r;
|
|
nwio_tcpcl_t tcpconnopt;
|
|
struct termios termios;
|
|
char *tcp_device, *remote_name, *port_name;
|
|
char *e_arg;
|
|
|
|
(prog_name=strrchr(argv[0],'/')) ? prog_name++ : (prog_name=argv[0]);
|
|
|
|
e_arg= NULL;
|
|
while (c= getopt(argc, argv, "?e:"), c != -1)
|
|
{
|
|
switch(c)
|
|
{
|
|
case '?': usage();
|
|
case 'e': e_arg= optarg; break;
|
|
default:
|
|
fatal("Optind failed: '%c'", c);
|
|
}
|
|
}
|
|
|
|
if (optind >= argc)
|
|
usage();
|
|
remote_name= argv[optind++];
|
|
if (optind < argc)
|
|
port_name= argv[optind++];
|
|
else
|
|
port_name= NULL;
|
|
if (optind != argc)
|
|
usage();
|
|
|
|
if (e_arg)
|
|
{
|
|
switch(strlen(e_arg))
|
|
{
|
|
case 0: esc_char= -1; break;
|
|
case 1: esc_char= e_arg[0]; break;
|
|
default: fatal("Invalid escape character '%s'", e_arg);
|
|
}
|
|
}
|
|
|
|
hostent= gethostbyname(remote_name);
|
|
if (!hostent)
|
|
fatal("Unknown host %s", remote_name);
|
|
host= *(ipaddr_t *)(hostent->h_addr);
|
|
|
|
if (!port_name)
|
|
port= htons(TCPPORT_TELNET);
|
|
else
|
|
{
|
|
servent= getservbyname (port_name, "tcp");
|
|
if (!servent)
|
|
{
|
|
port= htons(strtol(port_name, (char **)0, 0));
|
|
if (!port)
|
|
fatal("Unknown port %s", port_name);
|
|
}
|
|
else
|
|
port= (tcpport_t)(servent->s_port);
|
|
}
|
|
|
|
fprintf(stderr, "Connecting to %s:%u...\n",
|
|
inet_ntoa(host), ntohs(port));
|
|
|
|
tcp_device= getenv("TCP_DEVICE");
|
|
if (tcp_device == NULL)
|
|
tcp_device= TCP_DEVICE;
|
|
tcp_fd= open (tcp_device, O_RDWR);
|
|
if (tcp_fd == -1)
|
|
fatal("Unable to open %s: %s", tcp_device, strerror(errno));
|
|
|
|
tcpconf.nwtc_flags= NWTC_LP_SEL | NWTC_SET_RA | NWTC_SET_RP;
|
|
tcpconf.nwtc_remaddr= host;
|
|
tcpconf.nwtc_remport= port;
|
|
|
|
r= ioctl (tcp_fd, NWIOSTCPCONF, &tcpconf);
|
|
if (r == -1)
|
|
fatal("NWIOSTCPCONF failed: %s", strerror(errno));
|
|
|
|
tcpconnopt.nwtcl_flags= 0;
|
|
do
|
|
{
|
|
r= ioctl (tcp_fd, NWIOTCPCONN, &tcpconnopt);
|
|
if (r == -1 && errno == EAGAIN)
|
|
{
|
|
fprintf(stderr, "%s: Got EAGAIN, sleeping(1s)\n",
|
|
prog_name);
|
|
sleep(1);
|
|
}
|
|
} while (r == -1 && errno == EAGAIN);
|
|
if (r == -1)
|
|
fatal("Unable to connect: %s", strerror(errno));
|
|
printf("Connected\n");
|
|
ppid= getpid();
|
|
pid= fork();
|
|
switch(pid)
|
|
{
|
|
case 0:
|
|
keyboard();
|
|
#if DEBUG
|
|
fprintf(stderr, "killing %d with %d\r\n", ppid, SIGKILL);
|
|
#endif
|
|
kill(ppid, SIGKILL);
|
|
break;
|
|
case -1:
|
|
fprintf(stderr, "%s: fork failed: %s\r\n", argv[0],
|
|
strerror(errno));
|
|
exit(1);
|
|
break;
|
|
default:
|
|
tcgetattr(0, &termios);
|
|
screen();
|
|
#if DEBUG
|
|
fprintf(stderr, "killing %d with %d\r\n", pid, SIGKILL);
|
|
#endif
|
|
kill(pid, SIGKILL);
|
|
tcsetattr(0, TCSANOW, &termios);
|
|
break;
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
static int do_read(fd, buf, len)
|
|
int fd;
|
|
char *buf;
|
|
unsigned len;
|
|
{
|
|
nwio_tcpopt_t tcpopt;
|
|
int count;
|
|
|
|
for (;;)
|
|
{
|
|
count= read (fd, buf, len);
|
|
if (count <0)
|
|
{
|
|
if (errno == EURG || errno == ENOURG)
|
|
{
|
|
/* Toggle urgent mode. */
|
|
tcpopt.nwto_flags= errno == EURG ?
|
|
NWTO_RCV_URG : NWTO_RCV_NOTURG;
|
|
if (ioctl(tcp_fd, NWIOSTCPOPT, &tcpopt) == -1)
|
|
{
|
|
return -1;
|
|
}
|
|
continue;
|
|
}
|
|
return -1;
|
|
}
|
|
return count;
|
|
}
|
|
}
|
|
|
|
static void screen()
|
|
{
|
|
char buffer[1024], *bp, *iacptr;
|
|
int count, optsize;
|
|
|
|
for (;;)
|
|
{
|
|
count= do_read (tcp_fd, buffer, sizeof(buffer));
|
|
#if DEBUG && 0
|
|
{ where(); fprintf(stderr, "read %d bytes\r\n", count); }
|
|
#endif
|
|
if (count <0)
|
|
{
|
|
perror ("read");
|
|
return;
|
|
}
|
|
if (!count)
|
|
return;
|
|
bp= buffer;
|
|
do
|
|
{
|
|
iacptr= memchr (bp, IAC, count);
|
|
if (!iacptr)
|
|
{
|
|
write(1, bp, count);
|
|
count= 0;
|
|
}
|
|
if (iacptr && iacptr>bp)
|
|
{
|
|
#if DEBUG
|
|
{ where(); fprintf(stderr, "iacptr-bp= %d\r\n", iacptr-bp); }
|
|
#endif
|
|
write(1, bp, iacptr-bp);
|
|
count -= (iacptr-bp);
|
|
bp= iacptr;
|
|
continue;
|
|
}
|
|
if (iacptr)
|
|
{
|
|
assert (iacptr == bp);
|
|
optsize= process_opt(bp, count);
|
|
#if DEBUG && 0
|
|
{ where(); fprintf(stderr, "process_opt(...)= %d\r\n", optsize); }
|
|
#endif
|
|
if (optsize<0)
|
|
return;
|
|
assert (optsize);
|
|
bp += optsize;
|
|
count -= optsize;
|
|
}
|
|
} while (count);
|
|
}
|
|
}
|
|
|
|
static void keyboard()
|
|
{
|
|
char c, buffer[1024];
|
|
int count;
|
|
|
|
for (;;)
|
|
{
|
|
count= read (0, buffer, 1 /* sizeof(buffer) */);
|
|
if (count == -1)
|
|
fatal("Read: %s\r\n", strerror(errno));
|
|
if (!count)
|
|
return;
|
|
|
|
if (line_state != LS_NORM)
|
|
{
|
|
c= buffer[0];
|
|
if (line_state == LS_BOL)
|
|
{
|
|
if (c == esc_char)
|
|
{
|
|
line_state= LS_ESC;
|
|
continue;
|
|
}
|
|
line_state= LS_NORM;
|
|
}
|
|
else if (line_state == LS_ESC)
|
|
{
|
|
line_state= LS_NORM;
|
|
if (c == '.')
|
|
return;
|
|
if (c == '#')
|
|
{
|
|
send_brk();
|
|
continue;
|
|
}
|
|
|
|
/* Not a valid command or a repeat of the
|
|
* escape char
|
|
*/
|
|
if (c != esc_char)
|
|
{
|
|
c= esc_char;
|
|
write(tcp_fd, &c, 1);
|
|
}
|
|
}
|
|
}
|
|
if (buffer[0] == '\n')
|
|
write(tcp_fd, "\r", 1);
|
|
count= write(tcp_fd, buffer, count);
|
|
if (buffer[0] == '\r')
|
|
{
|
|
line_state= LS_BOL;
|
|
write(tcp_fd, "\0", 1);
|
|
}
|
|
if (count<0)
|
|
{
|
|
perror("write");
|
|
fprintf(stderr, "errno= %d\r\n", errno);
|
|
return;
|
|
}
|
|
if (!count)
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void send_brk(void)
|
|
{
|
|
int r;
|
|
unsigned char buffer[2];
|
|
|
|
buffer[0]= IAC;
|
|
buffer[1]= IAC_BRK;
|
|
|
|
r= writeall(tcp_fd, (char *)buffer, 2);
|
|
if (r == -1)
|
|
fatal("Error writing to TCP connection: %s", strerror(errno));
|
|
}
|
|
|
|
#define next_char(var) \
|
|
if (offset<count) { (var) = bp[offset++]; } \
|
|
else if (do_read(tcp_fd, (char *)&(var), 1) <= 0) \
|
|
{ perror ("read"); return -1; }
|
|
|
|
static int process_opt (char *bp, int count)
|
|
{
|
|
unsigned char iac, command, optsrt, sb_command;
|
|
int offset, result; ;
|
|
#if DEBUG && 0
|
|
{ where(); fprintf(stderr, "process_opt(bp= 0x%x, count= %d)\r\n",
|
|
bp, count); }
|
|
#endif
|
|
|
|
offset= 0;
|
|
assert (count);
|
|
next_char(iac);
|
|
assert (iac == IAC);
|
|
next_char(command);
|
|
switch(command)
|
|
{
|
|
case IAC_NOP:
|
|
break;
|
|
case IAC_DataMark:
|
|
/* Ought to flush input queue or something. */
|
|
break;
|
|
case IAC_BRK:
|
|
fprintf(stderr, "got a BRK\r\n");
|
|
break;
|
|
case IAC_IP:
|
|
fprintf(stderr, "got a IP\r\n");
|
|
break;
|
|
case IAC_AO:
|
|
fprintf(stderr, "got a AO\r\n");
|
|
break;
|
|
case IAC_AYT:
|
|
fprintf(stderr, "got a AYT\r\n");
|
|
break;
|
|
case IAC_EC:
|
|
fprintf(stderr, "got a EC\r\n");
|
|
break;
|
|
case IAC_EL:
|
|
fprintf(stderr, "got a EL\r\n");
|
|
break;
|
|
case IAC_GA:
|
|
fprintf(stderr, "got a GA\r\n");
|
|
break;
|
|
case IAC_SB:
|
|
next_char(sb_command);
|
|
switch (sb_command)
|
|
{
|
|
case OPT_TERMTYPE:
|
|
#if DEBUG && 0
|
|
fprintf(stderr, "got SB TERMINAL-TYPE\r\n");
|
|
#endif
|
|
result= sb_termtype(bp+offset, count-offset);
|
|
if (result<0)
|
|
return result;
|
|
else
|
|
return result+offset;
|
|
default:
|
|
fprintf(stderr, "got an unknown SB (skiping)\r\n");
|
|
for (;;)
|
|
{
|
|
next_char(iac);
|
|
if (iac != IAC)
|
|
continue;
|
|
next_char(optsrt);
|
|
if (optsrt == IAC)
|
|
continue;
|
|
if (optsrt != IAC_SE)
|
|
fprintf(stderr, "got IAC %d\r\n", optsrt);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case IAC_WILL:
|
|
next_char(optsrt);
|
|
will_option(optsrt);
|
|
break;
|
|
case IAC_WONT:
|
|
next_char(optsrt);
|
|
wont_option(optsrt);
|
|
break;
|
|
case IAC_DO:
|
|
next_char(optsrt);
|
|
do_option(optsrt);
|
|
break;
|
|
case IAC_DONT:
|
|
next_char(optsrt);
|
|
dont_option(optsrt);
|
|
break;
|
|
case IAC:
|
|
fprintf(stderr, "got a IAC\r\n");
|
|
break;
|
|
default:
|
|
fprintf(stderr, "got unknown command (%d)\r\n", command);
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
static void do_option (int optsrt)
|
|
{
|
|
unsigned char reply[3];
|
|
int result;
|
|
|
|
switch (optsrt)
|
|
{
|
|
case OPT_TERMTYPE:
|
|
if (WILL_terminal_type)
|
|
return;
|
|
if (!WILL_terminal_type_allowed)
|
|
{
|
|
reply[0]= IAC;
|
|
reply[1]= IAC_WONT;
|
|
reply[2]= optsrt;
|
|
}
|
|
else
|
|
{
|
|
WILL_terminal_type= TRUE;
|
|
term_env= getenv("TERM");
|
|
if (!term_env)
|
|
term_env= "unknown";
|
|
reply[0]= IAC;
|
|
reply[1]= IAC_WILL;
|
|
reply[2]= optsrt;
|
|
}
|
|
break;
|
|
default:
|
|
#if DEBUG
|
|
fprintf(stderr, "got a DO (%d)\r\n", optsrt);
|
|
fprintf(stderr, "WONT (%d)\r\n", optsrt);
|
|
#endif
|
|
reply[0]= IAC;
|
|
reply[1]= IAC_WONT;
|
|
reply[2]= optsrt;
|
|
break;
|
|
}
|
|
result= writeall(tcp_fd, (char *)reply, 3);
|
|
if (result<0)
|
|
perror("write");
|
|
}
|
|
|
|
static void will_option (int optsrt)
|
|
{
|
|
unsigned char reply[3];
|
|
int result;
|
|
|
|
switch (optsrt)
|
|
{
|
|
case OPT_ECHO:
|
|
if (DO_echo)
|
|
break;
|
|
if (!DO_echo_allowed)
|
|
{
|
|
reply[0]= IAC;
|
|
reply[1]= IAC_DONT;
|
|
reply[2]= optsrt;
|
|
}
|
|
else
|
|
{
|
|
struct termios termios;
|
|
|
|
tcgetattr(0, &termios);
|
|
termios.c_iflag &= ~(ICRNL|IGNCR|INLCR|IXON|IXOFF);
|
|
termios.c_oflag &= ~(OPOST);
|
|
termios.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN|ISIG);
|
|
tcsetattr(0, TCSANOW, &termios);
|
|
|
|
DO_echo= TRUE;
|
|
reply[0]= IAC;
|
|
reply[1]= IAC_DO;
|
|
reply[2]= optsrt;
|
|
}
|
|
result= writeall(tcp_fd, (char *)reply, 3);
|
|
if (result<0)
|
|
perror("write");
|
|
break;
|
|
case OPT_SUPP_GA:
|
|
if (DO_suppress_go_ahead)
|
|
break;
|
|
if (!DO_suppress_go_ahead_allowed)
|
|
{
|
|
reply[0]= IAC;
|
|
reply[1]= IAC_DONT;
|
|
reply[2]= optsrt;
|
|
}
|
|
else
|
|
{
|
|
DO_suppress_go_ahead= TRUE;
|
|
reply[0]= IAC;
|
|
reply[1]= IAC_DO;
|
|
reply[2]= optsrt;
|
|
}
|
|
result= writeall(tcp_fd, (char *)reply, 3);
|
|
if (result<0)
|
|
perror("write");
|
|
break;
|
|
default:
|
|
#if DEBUG
|
|
fprintf(stderr, "got a WILL (%d)\r\n", optsrt);
|
|
fprintf(stderr, "DONT (%d)\r\n", optsrt);
|
|
#endif
|
|
reply[0]= IAC;
|
|
reply[1]= IAC_DONT;
|
|
reply[2]= optsrt;
|
|
result= writeall(tcp_fd, (char *)reply, 3);
|
|
if (result<0)
|
|
perror("write");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int writeall (fd, buffer, buf_size)
|
|
int fd;
|
|
char *buffer;
|
|
int buf_size;
|
|
{
|
|
int result;
|
|
|
|
while (buf_size)
|
|
{
|
|
result= write (fd, buffer, buf_size);
|
|
if (result <= 0)
|
|
return -1;
|
|
assert (result <= buf_size);
|
|
buffer += result;
|
|
buf_size -= result;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void dont_option (int optsrt)
|
|
{
|
|
switch (optsrt)
|
|
{
|
|
default:
|
|
#if DEBUG
|
|
fprintf(stderr, "got a DONT (%d)\r\n", optsrt);
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void wont_option (int optsrt)
|
|
{
|
|
switch (optsrt)
|
|
{
|
|
default:
|
|
#if DEBUG
|
|
fprintf(stderr, "got a WONT (%d)\r\n", optsrt);
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int sb_termtype (char *bp, int count)
|
|
{
|
|
unsigned char command, iac, optsrt;
|
|
unsigned char buffer[4];
|
|
int offset, result;
|
|
|
|
offset= 0;
|
|
next_char(command);
|
|
if (command == TERMTYPE_SEND)
|
|
{
|
|
buffer[0]= IAC;
|
|
buffer[1]= IAC_SB;
|
|
buffer[2]= OPT_TERMTYPE;
|
|
buffer[3]= TERMTYPE_IS;
|
|
result= writeall(tcp_fd, (char *)buffer,4);
|
|
if (result<0)
|
|
return result;
|
|
count= strlen(term_env);
|
|
if (!count)
|
|
{
|
|
term_env= "unknown";
|
|
count= strlen(term_env);
|
|
}
|
|
result= writeall(tcp_fd, term_env, count);
|
|
if (result<0)
|
|
return result;
|
|
buffer[0]= IAC;
|
|
buffer[1]= IAC_SE;
|
|
result= writeall(tcp_fd, (char *)buffer,2);
|
|
if (result<0)
|
|
return result;
|
|
|
|
}
|
|
else
|
|
{
|
|
#if DEBUG
|
|
where();
|
|
#endif
|
|
fprintf(stderr, "got an unknown command (skipping)\r\n");
|
|
}
|
|
for (;;)
|
|
{
|
|
next_char(iac);
|
|
if (iac != IAC)
|
|
continue;
|
|
next_char(optsrt);
|
|
if (optsrt == IAC)
|
|
continue;
|
|
if (optsrt != IAC_SE)
|
|
{
|
|
#if DEBUG
|
|
where();
|
|
#endif
|
|
fprintf(stderr, "got IAC %d\r\n", optsrt);
|
|
}
|
|
break;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
static void fatal(char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
fprintf(stderr, "%s: ", prog_name);
|
|
vfprintf(stderr, fmt, ap);
|
|
fprintf(stderr, "\n");
|
|
va_end(ap);
|
|
|
|
exit(1);
|
|
}
|
|
|
|
static void usage(void)
|
|
{
|
|
fprintf(stderr, "Usage: %s [-e esc-char] host [port]\r\n",
|
|
prog_name);
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* $PchId: ttn.c,v 1.5 2002/05/07 12:06:41 philip Exp $
|
|
*/
|