[comp.sources.amiga] v02i020: secure uuencode and uudecode

bryce%cogsci.Berkeley.EDU@ucbvax.Berkeley.EDU (Bryce Nesbitt) (09/08/87)

    This is the uuencode that will be used for this newsgroup unless
otherwise noted.  I have reviewed a couple of versions and have decided
that this one is the most compatible with the current uuencode.
    Thanx to Bryce for his help on this!
	-Doc

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	uudecode.c
#	uuencode.c
#	uuencode.1
# This archive created: Tue Sep  8 13:00:35 1987
# By:	Craig Norborg (Purdue University Computing Center)
cat << \SHAR_EOF > uudecode.c
/* #ifndef lint
static char sccsid[] = "@(#)uudecode.c  5.3-1 (Berkeley) 4/10/85";
#endif */

/* Written by Mark Horton */
/* Modified by ajr (Alan J Rosenthatl,flaps@utcsri.UUCP) to use checksums */
/* Modified by fnf (Fred Fish,well!fnf) to use Keith Pyle's suggestion for
   compatibility */
/* Modified by bcn (Bryce Nesbitt,ucbvax!cogsci!bryce) to fix some very
   misleading error messages on the Amiga port, to fix a bug that prevented
   decoding certain files, to work even if trailing spaces have been
   removed from a file, to check the filesize (if present), to add some
   error checking, and to loop for multiple decodes from a single file.
   Also kludged around a missing string function in Aztec C */

/*
 * uudecode [input]
 *
 * Decode a file encoded with uuencode.
 */

#include <stdio.h>
#include <ctype.h>

#ifdef AMIGA
#define AMIGA_LATTICE	    /* Set for Amiga Lattice C */
#define MCH_AMIGA
#define MPU68000
#endif

#ifdef unix
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif

#define SUMSIZE 64
#define DEC(c)	(((c) - ' ') & 077)    /* single character decode */

main(argc, argv)
char **argv;
{
FILE	*in, *out;
int	through_loop=0; /* Dejavu indicator */
int	mode;		/* file's mode (from header) */
long	filesize;	/* theoretical file size (from header) */
char	dest[128];
char	buf[80];

#ifdef AMIGA_LATTICE
extern	int Enable_Abort;
	Enable_Abort=1;
#endif

    /* A filename can be specified to be uudecoded, or nothing can
    be specified, and the input will come from STDIN */

    switch (argc)
	{
	case 1:
	in=stdin;
	break;

	case 2:
	if ((in = fopen(argv[1], "r")) == NULL)
	    {
	    fprintf(stderr, "ERROR: can't find %s\n", argv[1]);
	    fprintf(stderr, "USAGE: uudecode [infile]\n");
	    exit(10);
	    }
	break;

	default:
	fprintf(stderr, "USAGE: uudecode [infile]\n");
	exit(11);
	break;
	}

    /* Loop through file, searching for headers.  Decode anything with a
       header, complain if there where no headers. */

for (;;)
{
    /* search file for header line */
    for (;;)
	{
	if (fgets(buf, sizeof buf, in) == NULL)
	    {
	    if (!through_loop)
		{
		fprintf(stderr, "ERROR: no `begin' line!\n");
		exit(12);
		}
	    else
		{
		exit(0);
		}
	    }
	if (strncmp(buf, "begin ", 6) == 0)
	    break;
	}
    sscanf(buf, "begin %o %s", &mode, dest);

#ifdef unix
    /* handle ~user/file format */
    if (dest[0] == '~')
	{
	char *sl;
	struct passwd *getpwnam();
	char *index();
	struct passwd *user;
	char dnbuf[100];

	sl = index(dest, '/');
	if (sl == NULL)
	    {
	    fprintf(stderr, "Illegal ~user\n");
		exit(13);
	    }
	*sl++ = 0;
	user = getpwnam(dest+1);
	if (user == NULL)
	    {
	    fprintf(stderr, "No such user as %s\n", dest);
	    exit(14);
	    }
	strcpy(dnbuf, user->pw_dir);
	strcat(dnbuf, "/");
	strcat(dnbuf, sl);
	strcpy(dest, dnbuf);
	}
#endif

    /* create output file */
    if ((out = fopen(dest, "w")) == NULL)
	{
	fprintf(stderr, "ERROR: can't open output file %s\n", dest);
	exit(15);
	}
#ifdef unix
    chmod(dest, mode);
#endif

    decode(in, out, dest);

    if (fgets(buf, sizeof buf, in) == NULL || strncmp(buf,"end",3))
	{	       /* don't be overly picky about newline ^ */
	fprintf(stderr, "ERROR: no `end' line\n");
	exit(16);
	}

    if (!(fgets(buf,sizeof buf,in) == NULL || strncmp(buf,"size ",3)))
	{
	sscanf(buf, "size %ld", &filesize);
	if (ftell(out) != filesize)
	    {
	    fprintf(stderr, "ERROR: file should have been %ld bytes long but was %ld.\n", filesize, ftell(out));
	    exit(17);
	    }
	}
    through_loop = 1;
}   /* forever */
}   /* main */

/*
 * Copy from in to out, decoding as you go.
 * If a return or newline is encountered too early in a line, it is
 * assumed that means that some editor has truncated trailing spaces.
 */
decode(in, out, dest)
FILE *in;
FILE *out;
char *dest;
{
char buf[80];
char *bp;
int nosum=0;
#ifndef unix
extern errno;
#endif
register int j;
register int n;
int checksum, line;

    for (line = 1; ; line++)	/* for each input line */
	{
	if (fgets(buf, sizeof buf, in) == NULL)
	    {
	    fprintf(stderr, "ERROR: input ended unexpectedly!\n");
	    exit(18);
	    }

	/* Pad end of lines in case some editor truncated trailing
	   spaces */

	for (n=0;n<79;n++)  /* search for first \r, \n or \000 */
	    {
	    if(buf[n]=='\r'||buf[n]=='\n'||buf[n]=='\000')
		break;
	    }
	for (;n<79;n++)     /* when found, fill rest of line with space */
	    {
	    buf[n]=' ';
	    }
	buf[79]=0;	    /* terminate new string */

	checksum = 0;
	n = DEC(buf[0]);
	if (n <= 0)
	    break;	/* 0 bytes on a line??	Must be the last line */

	bp = &buf[1];

	/* FOUR input characters go into each THREE output charcters */

	while (n >= 4)
	    {
	    j = DEC(bp[0]) << 2 | DEC(bp[1]) >> 4; putc(j, out); checksum += j;
	    j = DEC(bp[1]) << 4 | DEC(bp[2]) >> 2; putc(j, out); checksum += j;
	    j = DEC(bp[2]) << 6 | DEC(bp[3]);	   putc(j, out); checksum += j;
	    checksum = checksum % SUMSIZE;
	    bp += 4;
	    n -= 3;
	    }

	    j = DEC(bp[0]) << 2 | DEC(bp[1]) >> 4;
		checksum += j;
		if (n >= 1)
		    putc(j, out);
	    j = DEC(bp[1]) << 4 | DEC(bp[2]) >> 2;
		checksum += j;
		if (n >= 2)
		    putc(j, out);
	    j = DEC(bp[2]) << 6 | DEC(bp[3]);
		checksum += j;
		if (n >= 3)
		    putc(j, out);
	    checksum = checksum % SUMSIZE;
	    bp += 4;
	    n -= 3;

#ifndef unix
	 /* Error checking under UNIX??? You must be kidding... */
	 /* Check if an error occured while writing to that last line */
	if (errno)
	    {
	    fprintf(stderr, "ERROR: error writing to %s\n",dest);
	    exit(19);
	    }
#endif

	/* The line has been decoded; now check that sum */

	nosum |= !isspace(*bp);
	if (nosum)			/* Is there a checksum at all?? */
	    {
	    if (checksum != DEC(*bp))	/* Does that checksum match? */
		{
		fprintf(stderr, "ERROR: checksum mismatch decoding %s, line %d.\n",dest, line);
		}
	    }	/* sum */
    }	/* line */
}   /* function */

#ifdef unix
/*
 * Return the ptr in sp at which the character c appears;
 * 0 if not found
 */
char *
index(sp, c)
register char *sp, c;
{
    do
	{
	if (*sp == c)
	    return(sp);
	}
    while (*sp++);

    return(0);
}
#endif unix

SHAR_EOF
cat << \SHAR_EOF > uuencode.c
/* #ifndef lint
static char sccsid[] = "@(#)uuencode.c  5.3-1 (Berkeley) 1/22/85";
#endif */

/* Written by Mark Horton */
/* Modified by ajr (Alan J Rosenthatl,flaps@utcsri.UUCP) to use checksums */
/* Modified by fnf (Fred Fish,well!fnf) to use Keith Pyle's suggestion for
   compatibility */
/* Modified by bcn (Bryce Nesbitt,ucbvax!cogsci!bryce) to fix a very
   misleading error message on the Amiga port, enable CTRL-C for LATTICE,
   and add a transparant file size trailer for later check. */

/*
 * uuencode >outfile infile name
 *
 * Encode a file so it can be mailed to a remote system.  This version
 * transparantly adds line checksums and a file size for sanity checks.
 *
 */

#include <stdio.h>

#ifdef AMIGA
#define AMIGA_LATTICE		/* Set for Amiga Lattice C */
#define MCH_AMIGA
#define MPU68000
#endif

#ifdef unix
#include <sys/types.h>
#include <sys/stat.h>
#endif

#define SUMSIZE 64  /* 6 bits */
/* ENC is the basic 1 character encode function to make a char printing */
/* Each output character represents 6 bits of input */
#define ENC(c) ((c) ? ((c) & 077) + ' ': '`')
long	totalsize=0;	/* Used to count the file size because ftell() does
			   not return sane results for pipes */

main(argc, argv)
char **argv;
{
    FILE *in;
    int mode;
#ifdef unix
    struct stat sbuf;
#endif
#ifdef AMIGA_LATTICE
    extern int Enable_Abort;	/* Enable CTRL-C for Lattice */
    Enable_Abort=1;
#endif

	/* optional 1st argument */
	if (argc > 2) {
		if ((in = fopen(argv[1], "r")) == NULL) {
			fprintf(stderr, "ERROR: can't find %s\n", argv[1]);
			fprintf(stderr, "USAGE: uuencode >outfile infile name\n");
			exit(10);
		}
		argv++; argc--;
	} else
		in = stdin;

	if (argc != 2) {
		fprintf(stderr, "USAGE: uuencode >outfile infile name\n");
		exit(11);
	}

#ifdef unix
	/* figure out the input file mode */
	fstat(fileno(in), &sbuf);
	mode = sbuf.st_mode & 0777;
#else
	mode = 0644;	    /* Default permissions */
#endif

	printf("\nbegin %o %s\n", mode, argv[1]);

	encode(in, stdout);

	printf("end\n");
	printf("size %ld\n",totalsize);
	exit(0);
}

/*
 * copy from in to out, encoding as you go along.
 */
encode(in, out)
FILE *in;
FILE *out;
{
#ifndef unix
extern errno;
#endif
	char buf[80];
	int i, n, checksum;

	for (;;) {
		/* 1 (up to) 45 character line */
		n = fr(in, buf, 45);
		putc(ENC(n), out);

		checksum = 0;
		for (i=0; i<n; i += 3)
		    checksum = (checksum+outdec(&buf[i], out)) % SUMSIZE;

		putc(ENC(checksum), out);
		putc('\n', out);

#ifndef unix
		/* Error checking under UNIX?? You must be kidding! */
		if (errno) {
		    fprintf(stderr, "ERROR: error writing to output\n");
			exit(12);
		    }
#endif
		if (n <= 0)
			break;
	}
}

/*
 * output one group of 3 bytes, pointed at by p, on file f.
 * return the checksum increment.
 */
int outdec(p, f)
char *p;
FILE *f;
{
	int c1, c2, c3, c4;

	c1 = *p >> 2;
	c2 = (*p << 4) & 060 | (p[1] >> 4) & 017;
	c3 = (p[1] << 2) & 074 | (p[2] >> 6) & 03;
	c4 = p[2] & 077;
	putc(ENC(c1), f);
	putc(ENC(c2), f);
	putc(ENC(c3), f);
	putc(ENC(c4), f);

	return((p[0]+p[1]+p[2]) % SUMSIZE);
}

/* fr: like read but stdio */
int
fr(fd, buf, cnt)
FILE *fd;
char *buf;
int cnt;
{
	int c, i;

	for (i=0; i<cnt; i++) {
		c = getc(fd);
		if (c == EOF)
			return(i);
		totalsize++;
		buf[i] = c;
	}
	return (cnt);
}

SHAR_EOF
cat << \SHAR_EOF > uuencode.1
.TH UUENCODE 1 "17 July 1987"
.UC 4
.SH NAME
uuencode,uudecode \- encode/decode a binary file for transmission via
(plain text) mail
.SH SYNOPSIS
.B uuencode
[source] remotedest |
.B mail
sys1!sys2!..!decode
.br
.B uuencode
>outfile.uue source remotedest
.br
.B uuencode
>outfile.uue <source remotedest
.br
.B uudecode
[file]
.SH DESCRIPTION
.I Uuencode
and
.I uudecode
are used to send a binary file via uucp (or other) mail.  This combination can
be used over indirect mail links even when
.IR uusend (1C)
or other direct binary transmission methods are not available.
.PP
.I Uuencode
takes the named source file (default standard input) and produces an encoded
version on the standard output.  The encoding uses only printing ASCII
characters, and includes the mode of the file and the
.I remotedest
for recreation on the remote system.
.PP
.I Uudecode
reads an encoded file, strips off any leading lines added by mailers,
and recreates the original file with the specified mode and name.  If
there are other uuencoded files in the same input, they will be decoded
as well.
.PP
The intent is that all mail to the user ``decode'' should be filtered
through the uudecode program.  This way the file is created automatically
without human intervention.  This is possible on the uucp network by either
using
.I sendmail
or by making
.I rmail
be a link to
.I Mail
instead of
.I mail.
In each case, an alias must be created in a master file to get
the automatic invocation of uudecode.
.PP
If these facilities are not available, the file can be uudecoded manually.
.PP
The encode file has an ordinary text form and can be edited by any text editor
to change the mode or remote name.
.PP
This version of uuencode automatically adds checksums to each line, and
an overall file size to the end.  Old versions of uudecode will ignore this
extra information, new versions will check it and complain if it is in
error.
.SH SEE\ ALSO
uuencode(5), uusend(1C), uucp(1C), uux(1C), mail(1)
.SH AUTHOR
Mark Horton
.br
Checksums and Amiga port by Alan J. Rosenthal
.br
More fixes by Fred Fish
.br
File size check and various refinements by Bryce Nesbitt
.SH BUGS
The file is expanded by 35% (3 bytes become 4, plus control information)
causing it to take longer to transmit.
.PP
The user on the remote system who is invoking
.I uudecode
(often
.I uucp)
must have write permission on the specified file
SHAR_EOF
#	End of shell archive
exit 0