=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/uudecode/uudecode.c,v retrieving revision 1.13 retrieving revision 1.14 diff -u -r1.13 -r1.14 --- src/usr.bin/uudecode/uudecode.c 2003/12/09 01:31:42 1.13 +++ src/usr.bin/uudecode/uudecode.c 2004/04/09 22:54:02 1.14 @@ -1,5 +1,5 @@ -/* $OpenBSD: uudecode.c,v 1.13 2003/12/09 01:31:42 mickey Exp $ */ -/* $NetBSD: uudecode.c,v 1.6 1994/11/17 07:40:43 jtc Exp $ */ +/* $OpenBSD: uudecode.c,v 1.14 2004/04/09 22:54:02 millert Exp $ */ +/* $FreeBSD: uudecode.c,v 1.49 2003/05/03 19:44:46 obrien Exp $ */ /*- * Copyright (c) 1983, 1993 @@ -30,176 +30,418 @@ * SUCH DAMAGE. */ -char copyright[] = +#ifndef lint +static const char copyright[] = "@(#) Copyright (c) 1983, 1993\n\ The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ #ifndef lint #if 0 -static char sccsid[] = "@(#)uudecode.c 8.2 (Berkeley) 4/2/94"; +static const char sccsid[] = "@(#)uudecode.c 8.2 (Berkeley) 4/2/94"; #endif -static char rcsid[] = "$OpenBSD: uudecode.c,v 1.13 2003/12/09 01:31:42 mickey Exp $"; +static const char rcsid[] = "$OpenBSD: uudecode.c,v 1.14 2004/04/09 22:54:02 millert Exp $"; #endif /* not lint */ /* - * uudecode [-p] [file ...] - * - * create the specified file, decoding as you go. - * used with uuencode. - * - * Write to stdout if '-p' is specified. Use this option if you care about - * security at all. + * Create the specified file, decoding as you go. + * Used with uuencode. */ -#include -#include -#include -#include -#include + #include +#include #include +#include + +#include +#include +#include +#include #include +#include +#include +#include +#include #include -#include -static int decode(int); -static void usage(void); -char *filename; +static const char *infile, *outfile; +static FILE *infp, *outfp; +static int base64, cflag, iflag, oflag, pflag, rflag, sflag; +static void usage(void); +static int decode(void); +static int decode2(void); +static int uu_decode(void); +static int base64_decode(void); + int main(int argc, char *argv[]) { - int rval; - int ch; - int tostdout = 0; + int rval, ch; + extern char *__progname; - setlocale(LC_ALL, ""); + if (strcmp(__progname, "b64decode") == 0) + base64 = 1; - while ((ch = getopt(argc, argv, "p")) != -1) - switch((char)ch) { + setlocale(LC_ALL, ""); + while ((ch = getopt(argc, argv, "cimo:prs")) != -1) { + switch(ch) { + case 'c': + if (oflag || rflag) + usage(); + cflag = 1; /* multiple uudecode'd files */ + break; + case 'i': + iflag = 1; /* ask before override files */ + break; + case 'm': + base64 = 1; + break; + case 'o': + if (cflag || pflag || rflag || sflag) + usage(); + oflag = 1; /* output to the specified file */ + sflag = 1; /* do not strip pathnames for output */ + outfile = optarg; /* set the output filename */ + break; case 'p': - tostdout++; + if (oflag) + usage(); + pflag = 1; /* print output to stdout */ break; - case '?': + case 'r': + if (cflag || oflag) + usage(); + rflag = 1; /* decode raw data */ + break; + case 's': + if (oflag) + usage(); + sflag = 1; /* do not strip pathnames for output */ + break; default: usage(); } + } argc -= optind; argv += optind; if (*argv) { rval = 0; do { - if (!freopen(filename = *argv, "r", stdin)) { + infp = fopen(infile = *argv, "r"); + if (infp == NULL) { warn("%s", *argv); rval = 1; continue; } - rval |= decode(tostdout); + rval |= decode(); + fclose(infp); } while (*++argv); } else { - filename = "stdin"; - rval = decode(tostdout); + infile = "stdin"; + infp = stdin; + rval = decode(); } exit(rval); } static int -decode(int tostdout) +decode(void) { + int r, v; + + if (rflag) { + /* relaxed alternative to decode2() */ + outfile = "/dev/stdout"; + outfp = stdout; + if (base64) + return (base64_decode()); + else + return (uu_decode()); + } + v = decode2(); + if (v == EOF) { + warnx("%s: missing or bad \"begin\" line", infile); + return (1); + } + for (r = v; cflag; r |= v) { + v = decode2(); + if (v == EOF) + break; + } + return (r); +} + +static int +decode2(void) +{ + int flags, fd, mode; + size_t n, m; + char *p, *q; + void *handle; struct passwd *pw; - int n; - char ch, *p; - int mode, n1; + struct stat st; char buf[MAXPATHLEN]; + base64 = 0; /* search for header line */ - do { - if (!fgets(buf, sizeof(buf), stdin)) { - warnx("%s: no \"begin\" line", filename); - return(1); - } - } while (strncmp(buf, "begin ", 6)); - (void)sscanf(buf, "begin %o %1023[^\n\r]", &mode, buf); + for (;;) { + if (fgets(buf, sizeof(buf), infp) == NULL) + return (EOF); + p = buf; + if (strncmp(p, "begin-base64 ", 13) == 0) { + base64 = 1; + p += 13; + } else if (strncmp(p, "begin ", 6) == 0) + p += 6; + else + continue; + /* p points to mode */ + q = strchr(p, ' '); + if (q == NULL) + continue; + *q++ = '\0'; + /* q points to filename */ + n = strlen(q); + while (n > 0 && (q[n-1] == '\n' || q[n-1] == '\r')) + q[--n] = '\0'; + /* found valid header? */ + if (n > 0) + break; + } - /* handle ~user/file format */ - if (buf[0] == '~') { - if (!(p = strchr(buf, '/'))) { - warnx("%s: illegal ~user", filename); - return(1); + handle = setmode(p); + if (handle == NULL) { + warnx("%s: unable to parse file mode", infile); + return (1); + } + mode = getmode(handle, 0) & 0666; + free(handle); + + if (sflag) { + /* don't strip, so try ~user/file expansion */ + p = NULL; + pw = NULL; + if (*q == '~') + p = strchr(q, '/'); + if (p != NULL) { + *p = '\0'; + pw = getpwnam(q + 1); + *p = '/'; } - *p++ = NULL; - if (!(pw = getpwnam(buf + 1))) { - warnx("%s: no user %s", filename, buf); - return(1); + if (pw != NULL) { + n = strlen(pw->pw_dir); + if (buf + n > p) { + /* make room */ + m = strlen(p); + if (sizeof(buf) < n + m) { + warnx("%s: bad output filename", + infile); + return (1); + } + p = memmove(buf + n, p, m); + } + q = memcpy(p - n, pw->pw_dir, n); } - n = strlen(pw->pw_dir); - n1 = strlen(p); - if (n + n1 + 2 > MAXPATHLEN) { - warnx("%s: path too long", filename); - return(1); - } - bcopy(p, buf + n + 1, n1 + 1); - bcopy(pw->pw_dir, buf, n); - buf[n] = '/'; + } else { + /* strip down to leaf name */ + p = strrchr(q, '/'); + if (p != NULL) + q = p + 1; } + if (!oflag) + outfile = q; - if (!tostdout) { - /* create output file, set mode */ - if (!freopen(buf, "w", stdout) || - fchmod(fileno(stdout), mode&0666)) { - warn("%s: %s", buf, filename); - return(1); + /* POSIX says "/dev/stdout" is a 'magic cookie' not a special file. */ + if (pflag || strcmp(outfile, "/dev/stdout") == 0) + outfp = stdout; + else { + flags = O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW; + if (lstat(outfile, &st) == 0) { + if (iflag) { + errno = EEXIST; + warn("%s: %s", infile, outfile); + return (0); + } + switch (st.st_mode & S_IFMT) { + case S_IFREG: + case S_IFLNK: + /* avoid symlink attacks */ + if (unlink(outfile) == 0 || errno == ENOENT) + break; + warn("%s: unlink %s", infile, outfile); + return (1); + case S_IFDIR: + errno = EISDIR; + warn("%s: %s", infile, outfile); + return (1); + default: + if (oflag) { + /* trust command-line names */ + flags &= ~(O_EXCL|O_NOFOLLOW); + break; + } + errno = EEXIST; + warn("%s: %s", infile, outfile); + return (1); + } + } else if (errno != ENOENT) { + warn("%s: %s", infile, outfile); + return (1); } + if ((fd = open(outfile, flags, mode)) < 0 || + (outfp = fdopen(fd, "w")) == NULL) { + warn("%s: %s", infile, outfile); + return (1); + } } + if (base64) + return (base64_decode()); + else + return (uu_decode()); +} + +static int +getline(char *buf, size_t size) +{ + if (fgets(buf, size, infp) != NULL) + return (2); + if (rflag) + return (0); + warnx("%s: %s: short file", infile, outfile); + return (1); +} + +static int +checkend(const char *ptr, const char *end, const char *msg) +{ + size_t n; + + n = strlen(end); + if (strncmp(ptr, end, n) != 0 || + strspn(ptr + n, " \t\r\n") != strlen(ptr + n)) { + warnx("%s: %s: %s", infile, outfile, msg); + return (1); + } + if (fclose(outfp) != 0) { + warn("%s: %s", infile, outfile); + return (1); + } + return (0); +} + +static int +uu_decode(void) +{ + int i, ch; + char *p; + char buf[MAXPATHLEN]; + /* for each input line */ for (;;) { - if (!fgets(p = buf, sizeof(buf), stdin)) { - warnx("%s: short file", filename); - return(1); + switch (getline(buf, sizeof(buf))) { + case 0: + return (0); + case 1: + return (1); } + #define DEC(c) (((c) - ' ') & 077) /* single character decode */ +#define IS_DEC(c) ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) ) + +#define OUT_OF_RANGE do { \ + warnx("%s: %s: character out of range: [%d-%d]", \ + infile, outfile, 1 + ' ', 077 + ' ' + 1); \ + return (1); \ +} while (0) + /* - * `n' is used to avoid writing out all the characters + * `i' is used to avoid writing out all the characters * at the end of the file. */ - if ((n = DEC(*p)) <= 0) + p = buf; + if ((i = DEC(*p)) <= 0) break; - for (++p; n > 0; p += 4, n -= 3) - if (n >= 3) { + for (++p; i > 0; p += 4, i -= 3) + if (i >= 3) { + if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) && + IS_DEC(*(p + 2)) && IS_DEC(*(p + 3)))) + OUT_OF_RANGE; + ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; - putchar(ch); + putc(ch, outfp); ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; - putchar(ch); + putc(ch, outfp); ch = DEC(p[2]) << 6 | DEC(p[3]); - putchar(ch); + putc(ch, outfp); } else { - if (n >= 1) { + if (i >= 1) { + if (!(IS_DEC(*p) && IS_DEC(*(p + 1)))) + OUT_OF_RANGE; ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; - putchar(ch); + putc(ch, outfp); } - if (n >= 2) { + if (i >= 2) { + if (!(IS_DEC(*(p + 1)) && + IS_DEC(*(p + 2)))) + OUT_OF_RANGE; + ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; - putchar(ch); + putc(ch, outfp); } - if (n >= 3) { + if (i >= 3) { + if (!(IS_DEC(*(p + 2)) && + IS_DEC(*(p + 3)))) + OUT_OF_RANGE; ch = DEC(p[2]) << 6 | DEC(p[3]); - putchar(ch); + putc(ch, outfp); } } } - if (!fgets(buf, sizeof(buf), stdin) || strcmp(buf, "end\n")) { - warnx("%s: no \"end\" line", filename); - return(1); + switch (getline(buf, sizeof(buf))) { + case 0: + return (0); + case 1: + return (1); + default: + return (checkend(buf, "end", "no \"end\" line")); } - return(0); } +static int +base64_decode(void) +{ + int n; + char inbuf[MAXPATHLEN]; + unsigned char outbuf[MAXPATHLEN * 4]; + + for (;;) { + switch (getline(inbuf, sizeof(inbuf))) { + case 0: + return (0); + case 1: + return (1); + } + n = b64_pton(inbuf, outbuf, sizeof(outbuf)); + if (n < 0) + break; + fwrite(outbuf, 1, n, outfp); + } + return (checkend(inbuf, "====", + "error decoding base64 input stream")); +} + static void usage(void) { - (void)fprintf(stderr, "usage: uudecode [-p] [file ...]\n"); + (void)fprintf(stderr, + "usage: uudecode [-cimprs] [file ...]\n" + " uudecode [-i] -o output_file [file]\n" + " b64decode [-cimprs] [file ...]\n" + " b64decode [-i] -o output_file [file]\n"); exit(1); }