AUSAM/source/S/log.c
/*
* log remote_tty
*
* File exchange package for remote unices.
*
* Also has transparent mode for talking to
* other operating systems as if local tty.
*
* For the following reasons this program must be
* setuid to root for successful use:
* 1. The tty to be used for communication with other machine
* is to be owned by root and is to have a mode of 600.
* 2. Needs to run at fairly high priority,
* so nice will be used to obtain same.
* 3. The connect system call only allowed to be
* used by root, to prevent abuse.
*
* Multiple opens of the same tty are assumed
* to be taken care of by the appropriate tty
* drivers (eg dz & dj) supporting the notion
* of certain ttys being single open, and that
* ttys used by 'log' for communications are
* setup as single open.
*
* Ian Johnstone - AGSM - 1976
* Peter Ivanov - UNSW - 1976
*
* Modified for flow control
* Piers Lauder - Sydney University - Aug '77
*
* Modified for binary file protocol
* Piers Lauder - Sydney University - Sep '77
*
* Modified for settable quit code & shell escape & default file args
* Piers Lauder - Sydney University - Feb '78
* Ian Johnstone - AGSM - Feb '78
*
* Updated to use 'connect' system call.
* Ian Johnstone - AGSM - MAY '78
*
* Updated to use 'access' to validate file access,
* since 'log' will always be setuid to root from
* this time onwards.
* Ian Johnstone - AGSM - AUG '78
* Piers Lauder - Sydney University - Aug '78
*
* Corrected a timing bug that only showed up when using
* high-speed log lines (say >= 4800 baud). Corrected
* some minor bugs and generally tidied up the program
* (rewrote some grotty procedures!).
* Kevin Hill - UNSW - JAN '79
*
* Enabled handling of multiple lines to remote site. Lines
* to be called (in order of trying) xxx,xxx1,xxx2,...xxx9.
* This enables a user to merely say 'log xxx', to have a
* log attempted on all appropraitely named lines.
* Ian Johnstone - AGSM - AUG '79
*
* To enable realistic use of the new 'disconnect' command
* (disconnect breaks a connection) the log special file
* being used by log is set up to be owned by the real
* user of log so that that user may reasonably break his/her
* own log !!
* Ian Johnstone - AGSM - AUG '79
*
* Fix some more little bugs. Only the super-user may log to
* a device not in the `quits' table. `Access' used if the user
* tries to change directory. Change owner of remote special back
* to root when the logger has finished. Complain if super-user
* tries to log to a non-existent device.
* Kevin Hill - UNSW - AUG 79
*/
#include <local-system>
#include <errnos.h>
#ifdef AUSAM
#include <stat16.h>
/*
* Piers Lauder Feb '78
*
* Use 16-bit user id.
*/
#else
#include <stat.h>
#endif AUSAM
#define HIPRIORITY -40
#define USERPRI 0
/*
* Regulate file transfer with a file protocol as follows:-
*
* Blocks consist of a header followed by data followed by a trailer.
* The header contains a sequence number (0/1) and the size
* (transmitted twice, 2nd as complement of first).
* The trailer contains a longitudinal parity check of the data,
* followed by a sum check of the data.
*
* Block format is
* STX SEQ SIZ ~SIZ ...DATA... LPC SUM
*
* Blocks are acknowledged by a 2 byte reply consisting of
* ACK or NAK followed by the relevant sequence number.
*
* Note: that the DATAZ is really dependant on the accuracy
* of the crc check.
*/
#define REPLYZ 2
#define RTYPE 0
#define RSEQ 1
struct
{
char r_typ;
char r_seq;
}
reply;
#define HEADER 4
#define BSEQ 1
#define TRAILER 2
#define OVERHEAD (HEADER+TRAILER)
#define DATAZ 64
#define DATAZL 64L
#define BUFZ (DATAZ+OVERHEAD)
/*
* Timeouts - these must be tailored to the line speed,
* the basic formulae used being:-
* receive timeout = receive time + turnaround time
* transmit " = 2 * receive timeout
* (all times in seconds)
*/
#define STARTTIMEOUT 10
#define FLUSHTIMEOUT 1
#define TURNAROUND 3
#define XRETRYS 4
#define RRETRYS 5
#define GARBAGE 2 /* max garbage chars tolerated before reset */
#define EXTA 200 /* per DZ11 */
#define EXTB 1920 /* per DZ11 */
#define DEFAULTRATE 120 /* 1200 baud default */
int byterate[16] /* per DH11 */
{
0,5,8,10,15,15,20,30,60,120,
180,240,480,960,EXTA,EXTB
};
int xtimeout, rtimeout;
long xrate; /* currently effective data transfer rate */
long xtotal; /* total bytes received/transmitted */
long xtime; /* total transmission time */
#define SOH 001 /* remote accept */
#define STX 002 /* start of block */
#define ETX 003 /* remote finish */
#define EOT 004 /* remote file access error */
#define ENQ 005 /* remote timeout */
#define ACK 006 /* xfer ok */
#define DLE 020 /* file already exists */
#define NAK 025 /* xfer failed */
#define MSOH "\01\01",2
#define METX "\03\03\03\03",4
#define MEOT "\04\04",2
#define MTIMEOUT "\05\05",2
#define MDLE "\020\020",2
#define SIGTIMEOUT 15
#define SIGQUIT 3
#define SIGINTERRUPT 2
int sav_modes;
#define RAW 040
int ttymode[3];
int narg;
char *cmd[10];
int xfd 2; /* fd for remote port */
char otherm[] "/dev/xxxxxxxxxxxxxxx";
char c;
unsigned uid;
long time();
long filesize;
char exists[] "%s already exists - are you sure? ";
/* defines for use with access calls */
#define R 04 /* read permission */
#define W 02 /* write permission */
#define X 01 /* execute / directory search permission */
#define ESCAPE 021 /* default escape char is ctrl/q */
#define ESCAPED "^q" /* default escape description */
char quit; /* escape code */
char *quitdescrpt; /* escape code description */
struct
{
char *name;
char *descpt; /* description of control code that follows */
char escape; /* default escape code for this line */
char speed; /* sets log line to this speed if non-zero */
}
quits[]
{
#ifdef BASSER
"agsm", "^a", 001, 9,
"cyber", "^d", 004, 7,
#else
"agsm", "^a", 001, 12,
"basser", "^b", 002, 9,
"elec", "^e", 005, 0,
"csu", "^c", 003, 12,
"forty", "^f", 006, 13,
"arch", "^r", 022, 11,
"cyber", "^k", 013, 7,
#endif BASSER
};
struct err
{
int e_count;
char *e_mess;
}
errs[]
{
#define E_RETRYS 0
{ 0, "retrys" },
#define E_SEQUENCE 1
{ 0, "seq" },
#define E_SIZE 2
{ 0, "size" },
#define E_OUTSIZE 3
{ 0, "outsize" },
#define E_TIMEOUTS 4
{ 0, "timeouts" },
#define E_CRC 5
{ 0, "crc" },
#define E_WRITE 6
{ 0, "local write" },
#define E_NAKS 7
{ 0, "naks" },
#define E_SYNC 8
{ 0, "sync" },
#define E_READ 9
{ 0, "local read" }
};
#define NERRS ((sizeof errs)/(sizeof errs[0]))
char buf[BUFZ];
struct
{
int hiword;
unsigned loword;
};
/****************************************************************/
main (argc,argv)
register char **argv;
register argc;
{
signal(SIGINTERRUPT, 1);
signal(SIGQUIT, 1);
nice(HIPRIORITY);
#ifdef AUSAM
uid = getreal();
#else
uid = getuid() & 0377;
#endif AUSAM
sentinel(); /* catch all real-time limit expiries */
switch (**argv)
{
case 'l': if (okuse())
{
log (argc,argv);
errors(); /* tell about the errors encountered */
}
else printf("Illegal usage!\n");
break;
case 's': send (argc,argv);
break;
case 'r': receive (argc,argv);
break;
default: write(2, MEOT);
return(1);
}
}
/*****************************************************************/
log(argc,argv)
char **argv;
int argc;
{
register char *arg;
register char *otp;
register int i;
extern char *fquit();
extern finish();
extern int errno;
if (argc != 2)
{
argerr:
printf("Usage: log remote-special-file\n");
exit(1);
}
arg = argv[1];
otp = &otherm[5];
for (i = 0; *otp++ = *arg++; ) if (++i >= 15) goto argerr;
*otp-- = 0; /* extra-null, address last null of name */
while( (xfd = open(otherm,2)) < 0)
if( (errno == EOPENFAIL) && (*otp!='9') )
{
if( *otp )
(*otp)++;
else
*otp = '1';
}
else
{
if( errno == ENOENT && *otp)
printf("%s busy\n",argv[1]);
else
perror(argv[1]);
exit (2);
}
quit = ESCAPE;
quitdescrpt = ESCAPED;
for (i = 0; i < sizeof quits / sizeof quits[0]; i++)
{
arg = argv[1];
otp = quits[i].name;
while (*otp && *otp == *arg++) otp++;
if (*otp == '\0')
{
quit = quits[i].escape;
quitdescrpt = quits[i].descpt;
break;
}
}
if (i == sizeof quits / sizeof quits[0]) /* ran off the end */
{
if (uid)
{
printf("%s: illegal device\n", argv[1]);
exit(1);
}
init_remote(xfd, 0);
}
else init_remote(xfd, quits[i].speed);
chown(otherm,uid); /* indicate current user of the log line */
/* quicker than a ps */
chmod(otherm,0600); /* protect against other access, especially */
/* from the disconnect command */
signal(SIGINTERRUPT, finish);
printf("* tty\n");
tty();
for (;;)
{
errors();
printf("* ");
switch(getline())
{
case 'p': if (narg == 3) put(); /* put a file to remote */
break;
case 'g': if (narg == 3) get(); /* get a file from remote */
break;
case 't': if (narg == 1) tty(); /* talk to remote as a terminal */
break;
case 's': if (narg == 1) finish(); /* stop this sillyness */
break;
case 'q': if (narg == 2) quitdescrpt = fquit(); /* change quit code */
break;
case '!': sh(); /* local escape to shell */
break;
case 'c': if( narg == 2 )
{
if (access(cmd[1], X) ||
chdir(cmd[1]) == -1 ) perror(cmd[1]);
break;
}
default: help(); /* unknown command */
break;
}
}
}
/**************/
getline()
{
static char line[100];
register char *q;
register *p;
register toggle;
if ((toggle = read(2,line,sizeof line)) == 1) return(-1);
if (toggle <= 0) finish();
if (line[toggle - 1] != '\n')
{
printf("Too verbose!!\n");
do
toggle = read(2, line, sizeof line);
while (toggle > 0 && line[toggle - 1] != '\n');
return -1;
}
for (q = line; q < &line[sizeof line] && *q == ' '; q++);
if (*q == '!')
{
cmd[0] = q;
narg = 1;
q = &line[toggle - 1];
}
else
{
p = cmd; narg = 0; toggle = 1;
for ( ; *q != '\n'; q++)
if (*q == ' ')
{
toggle = 1;
*q = 0;
}
else if (toggle)
{
*p++ = q;
narg++;
toggle = 0;
}
}
*q = 0;
q = cmd[0];
if ((narg == 2) && ((*q == 'p') || (*q == 'g')))
{
*p++ = cmd[1];
*p = 0;
narg++;
}
if (narg == 0) return -1;
else return *q;
}
/*******************/
fini_remote(fd)
register int fd;
{
ttymode[2] = sav_modes;
stty (fd, ttymode);
}
init_remote(fd, spd)
char spd;
register int fd;
{
register speed;
gtty (fd, ttymode);
sav_modes = ttymode[2];
ttymode[2] = RAW;
if (spd) ttymode[0] = (spd << 8) | spd;
stty (fd, ttymode);
if ((speed = byterate[(ttymode[0] & 017)]) == 0)
speed = DEFAULTRATE;
xtimeout = 2 * (rtimeout = BUFZ / speed + TURNAROUND);
xrate = (speed * DATAZL) / BUFZ;
}
set_rate(t)
long t;
{
xtotal =+ filesize;
xtime =+ t;
xrate = xtotal/xtime;
}
/*********************/
finish()
{
fini_remote(xfd);
chown(otherm, 0); /* change back to root */
#ifdef BASSER
chmod(otherm, 0606); /* multi use line */
#endif BASSER
exit(0);
}
/*************************************************************/
char *fquit()
{
static char qs[3];
register char *qp = qs;
if ((quit = *cmd[1] & 0177) == 037) return("US"); /* clear-screen char. */
if (quit < 040)
{
*qp++ = '^';
*qp++ = quit + 0140;
}
else *qp++ = quit;
*qp = 0;
return(qs);
}
/*************************************************************/
sh()
{
char *args[4];
register char **ap = args;
register char *bp;
register i = 0;
static char shf[] "/bin/sh";
cmd[0]++;
*ap++ = shf+5;
*ap++ = "-c";
*ap++ = cmd[0];
*ap = 0;
if (!(i = fork()))
{
nice(USERPRI);
setuid(uid);
execv(shf, args);
perror(shf);
exit(-1);
}
if (i > 0)
{
signal(SIGINTERRUPT, 1);
waitx(ap);
signal(SIGINTERRUPT, finish);
}
else perror("try again");
}
/*************************************************************/
tty()
{
/*
* logically connect local terminal to remote until quit char received
*/
printf(" %s -> %s ('%s'=quit)\n",SYSTEMID,&otherm[5],quitdescrpt);
if (connect(2, xfd, quit) < 0)
{
perror("connect failed");
finish();
}
}
/*************************************************************/
get()
{
register ofd;
register n;
long t;
if ((ofd = accreat(cmd[2], 0)) < 0) return;
transfer("send",cmd[1], 0);
if (sync("open"))
{
printf(" -- receive rate approx. %D bytes/sec.\n", xrate);
t = time();
if (rproto(ofd, xfd) == 0)
{
if (!(t = time() - t)) t++;
set_rate(t);
printf("%s: %D bytes received in %D secs (%D bytes/sec)\n", cmd[2], filesize, t, filesize/t);
}
else printf(" -- transfer aborted after %D bytes\n", filesize);
}
lflush(xfd);
close(ofd);
}
/*************************************************************/
put()
{
register ifd;
register n;
register fred;
long t;
struct statbuf statb;
if (access(cmd[1],R) || ((ifd = open(cmd[1],0)) < 0))
{
perror(cmd[1]);
return;
}
fstat(ifd, &statb);
filesize.loword = statb.sb_size1;
filesize.hiword = statb.sb_size0 & 0377;
transfer("receive",cmd[2], 0);
if ((n = sync("create")))
{
if (n < 0)
{
printf(exists, cmd[2]);
fred = 0;
if ((n = read(2, &c, 1)) == 1 && c == 'y') fred++;
if (n == 1 && c != '\n')
do
n = read(2, &c, 1);
while (n > 0 && c != '\n');
if (fred == 0) return;
lflush(xfd);
transfer("receive", cmd[2], "-");
if (sync("create") != 1) goto home;
}
printf(" -- estimated transmit time: %D secs\n", filesize/xrate);
t = time();
if ((n = xproto(ifd, xfd)) == 0)
{
if (!(t = time() - t)) t++;
set_rate(t);
printf("%s: %D bytes transmitted in %D secs (%D bytes/sec)\n", cmd[2], filesize, t, filesize/t);
}
else printf(" --%stransfer aborted after %D bytes\n", (n == 2) ? " remote terminate -- " : " ", filesize);
}
home:
lflush(xfd);
close (ifd);
}
/*******************************/
transfer(s1, s2, s3)
char *s1,*s2;
{
register char *p, *q;
q = buf;
*q++ = '@';
*q++ = '@'; /* two will ALWAYS do the trick! */
for (p = s1; *q++ = *p++; );
q[-1] = ' ';
for (p = s2; *q++ = *p++; );
if (s3)
{
q[-1] = ' ';
for (p = s3; *q++ = *p++; );
}
q[-1] = '\n';
write (xfd, buf, q-buf);
}
/*******************************/
send(argc,argv)
char **argv;
{
register fd;
init_remote(2, 0);
if (argc != 2 || access(argv[1],R) || ((fd = open(argv[1],0))<0)) write(2, MEOT);
else
{
write(2, MSOH);
sleep(FLUSHTIMEOUT);
xproto(fd, 2);
}
lflush(2);
fini_remote(2);
}
/*******************************/
receive(argc,argv)
char **argv;
{
register fd;
init_remote(2, 0);
if (argc > 3 || (fd = accreat(argv[1], argc == 3 ? 2 : 1)) < 0) write(2, MEOT);
else
{
write(2, MSOH);
rproto(fd, 2);
}
lflush(2);
fini_remote(2);
}
/********************************************************************/
int timedout;
sentinel()
{
signal(SIGTIMEOUT, sentinel);
timedout++;
}
tread(f, b, s, timeout)
{
clktim(timeout);
timedout = 0;
read(f, b, s);
clktim(0);
return(timedout);
}
lflush(fd)
{
while (!tread(fd, &c, 1, FLUSHTIMEOUT));
return(1);
}
/********************************************************************/
sync(remote_err)
char *remote_err;
{
do
{
if (tread(xfd, &c, 1, STARTTIMEOUT))
{
printf("no response from remote!\n");
return(0);
}
if (c == EOT)
{
printf("can't %s remote file!\n", remote_err);
return(0);
}
if (c == DLE) return -1;
} while (c != SOH);
lflush(xfd);
return(1);
}
/******************************************************************/
xproto(local, remote)
{
register n, retrys, sync;
buf[BSEQ] = 1;
filesize = 0;
while ((n = read(local, buf+HEADER, DATAZ)) >= 0)
{
crc(n);
retrys = 0;
do
{
write(remote, buf, n+OVERHEAD);
sync = 0;
while (tread(remote, &reply, 1, xtimeout) == 0)
{
switch (reply.r_typ)
{
default:
errs[E_SYNC].e_count++;
if (++sync > GARBAGE)
break;
else
continue;
case ENQ:
case ACK:
case NAK:
case ETX:
tread(remote, &reply.r_seq, 1, xtimeout);
}
break;
}
if (reply.r_typ == ETX && reply.r_seq == ETX)
return(n ? 2 : n); /* remote finish */
} while (
( (sync > GARBAGE && lflush(remote))
|| (timedout && ++errs[E_TIMEOUTS].e_count)
|| (reply.r_typ != ACK && ++errs[E_NAKS].e_count)
|| (reply.r_seq != buf[BSEQ] && ++errs[E_SEQUENCE].e_count)
)
&& retrys++ < XRETRYS
);
errs[E_RETRYS].e_count =+ retrys;
if (retrys > XRETRYS)
{
errs[E_RETRYS].e_count--;
return(1);
}
if (n == 0) /* eof */
return(n);
filesize =+ n;
}
errs[E_READ].e_count++;
return(3);
}
/*******************************************************************/
/* case labels */
#define NEWBLOCK 0
#define SEQUENCE 1
#define SIZE1 2
#define SIZE2 3
rproto(local, remote)
{
register size, state, x;
int wretrys, lastsize, retrys, garbage;
char lastseq;
state = NEWBLOCK;
retrys = 0;
reply.r_seq = 0;
filesize = 0;
lastseq = -1;
garbage = 0;
lastsize = 0;
for (;;)
{
while (tread(remote, &c, 1, rtimeout))
if (retrys++ < RRETRYS)
{
errs[E_TIMEOUTS].e_count++;
write(remote, MTIMEOUT);
}
else
{
fail:
write(remote, METX);
return(1);
}
x = c & 0377;
switch(state)
{
case NEWBLOCK:
if (x == STX)
{
state = SEQUENCE;
retrys = 0;
garbage = 0;
}
else
{
if (garbage++ >= GARBAGE)
{
lflush(remote);
garbage = 0;
}
}
break;
case SEQUENCE:
if (x & ~1)
{
errs[E_SEQUENCE].e_count++;
fnak:
state = NEWBLOCK;
lflush(remote);
nak:
reply.r_typ = NAK;
errs[E_NAKS].e_count++;
rply:
write(remote, &reply, REPLYZ);
break;
}
reply.r_seq = x;
state = SIZE1;
break;
case SIZE1:
if ((size = x) > DATAZ)
{
errs[E_OUTSIZE].e_count++;
goto fnak;
}
state = SIZE2;
break;
case SIZE2:
state = NEWBLOCK;
if (size != (~x & 0377))
{
errs[E_SIZE].e_count++;
goto fnak;
}
if (size == 0) /* eof */
{
write(remote, METX);
return(0);
}
if ((tread(remote, buf+HEADER, size+TRAILER, rtimeout) && ++errs[E_TIMEOUTS].e_count)
|| (crc(size) && ++errs[E_CRC].e_count)
)
goto nak;
if (reply.r_seq == lastseq && lastsize > 0)
{
seek(local, -lastsize, 1);
filesize =- lastsize;
}
wretrys = 0;
while ((lastsize = write(local, buf+HEADER, size)) != size)
{
errs[E_WRITE].e_count++;
if (wretrys++ >= XRETRYS)
goto fail;
if (lastsize > 0)
seek(local, -lastsize, 1);
}
filesize =+ size;
reply.r_typ = ACK;
lastseq = reply.r_seq;
goto rply;
}
}
}
/********************************************************************/
/*
* not a true crc, but nearly so for small 's' (< 100 say)
*/
crc(s)
{
register char *p;
register char lpc, sum;
char *end;
int error;
p = buf; lpc = 0; sum = 0; error = 0;
*p++ = STX;
*p++ =^ 1; /* flip sequence number */
*p++ = s; *p++ = ~s;
end = buf+HEADER+s;
for ( ; p < end; p++)
{
lpc =^ *p;
sum =+ *p;
}
if (lpc != *p) error++;
*p++ = lpc;
if (sum != *p) error++;
*p++ = sum;
return(error);
}
/**********************************************************************/
errors()
{
register struct err *ep;
register nerrs;
nerrs = 0;
for (ep = errs; ep < &errs[NERRS]; ep++)
if (ep->e_count)
{
if (nerrs++ == 0)
printf(" file transfer protocol errors:-\n");
printf(" %s: %d", ep->e_mess, ep->e_count);
ep->e_count = 0;
}
if (nerrs) printf("\n");
return(nerrs);
}
/***********************************************************************/
help()
{
printf("\nuse:\n");
printf("\tt[ty]\n");
printf("\tp[ut] %s-file [%s-file]\n", SYSTEMID, &otherm[5]);
printf("\tg[et] %s-file [%s-file]\n", &otherm[5], SYSTEMID);
printf("\tq[uit] new-code\n");
printf("\t!{shell command}\n");
printf("\tc[d] directory\n");
printf("\ts[top]\n\n");
}
/*******************************************************************/
#ifdef SPRINTF
#define NUMBERS
#define LONG
#include <sprintf.h>
#endif SPRINTF
/*
** check for appropriate permissions on a file
** passed as 'arg' and creat it if allowable.
*/
accreat(arg, flag)
char *arg;
{
register char *p;
int n;
extern int errno;
if ((n = access(arg, W)) == 0) /* file already exists and have write permission */
{
if (flag == 0)
{
printf(exists, arg);
if (getline() != 'y') return -1;
}
else if (flag == 1)
{
write(2, MDLE);
return -1;
}
}
else if (n && errno == ENOENT)
{ /* isolate directory name and check it */
p = arg;
while (*++p);
while (*--p != '/' && p > arg);
if (*p != '/') n = access("", W|X);
else if (p == arg) n = access("/", W|X);
else
{
*p = 0;
n = access(arg, W|X);
*p = '/';
}
}
if (n == 0)
{ /* creat the file since access is granted */
n = creat(arg, 0600);
chown(arg, uid);
}
if (n < 0)
perror(arg);
return n;
}
/*
* validate that log is not being used with any
* standard input or output redirection - this
* includes asynchronous use.
* The test checks that file descriptors 0, 1, and 2 represent
* the same tty.
*/
okuse()
{
struct
{
int majmin; /* major minor device number for i-node */
int inum; /* inode number on device */
int flags; /* flags from inode */
#define IFMT 060000
#define ICHR 020000
char dumy[30]; /* the rest ... */
}
sbuf[3];
register i;
for (i = 0; i < 3; i++)
if (fstat(i, &sbuf[i]) == -1 || /* stat failed ??? */
sbuf[i].flags&IFMT != ICHR || /* not a tty */
i &&
(sbuf[i].majmin != sbuf[i-1].majmin ||
sbuf[i].inum != sbuf[i-1].inum)) /* some sort of redirection */
return 0;
return 1;
}