diff --git a/Makefile b/Makefile index f8469df..762d345 100644 --- a/Makefile +++ b/Makefile @@ -85,6 +85,7 @@ SRC = \ tr.c \ true.c \ tty.c \ + uudecode.c \ uuencode.c \ uname.c \ uniq.c \ diff --git a/uudecode.1 b/uudecode.1 new file mode 100644 index 0000000..62aff99 --- /dev/null +++ b/uudecode.1 @@ -0,0 +1,19 @@ +.TH UUDECODE 1 sbase\-VERSION +.SH NAME +uudecode \- decode a uuencoded file +.SH SYNOPSIS +.B uudecode +.RI [file] +.SH DESCRIPTION +.B uudecode +reads file (or by default, the standard input) and writes a decoded +version to the file specified in the uuencoded header. In case that +the file already exists, it is truncated. Otherwise a new file is +created. After the operation the permissions of the created/accessed +are changed to reflect the mode in the header. +.SH NOTES +This version of uudecode does not currently support the base64 +encoding algorithm. +For safety currently uudecode operates only on regular files and +stdout. Trying to uudecode to a link, directory, or special file +yields an error. diff --git a/uudecode.c b/uudecode.c new file mode 100644 index 0000000..1644abe --- /dev/null +++ b/uudecode.c @@ -0,0 +1,245 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "util.h" +#include "text.h" + +static int uudecode(FILE *, FILE*); +static int checkheader(FILE *, const char *, mode_t *, char **, char **); +static int checkmode(const char *, mode_t *); +static FILE *checkfile(const char *); + +static void +usage(void) +{ + eprintf("usage: %s [file]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp, *nfp; + char *fname, *headerr; + mode_t mode = 0; + int chmodtest; + + ARGBEGIN { + case 'm': + eprintf("-m not implemented\n"); + default: + usage(); + } ARGEND; + + if (argc > 1) + usage(); + + if (argc == 0) { + if (checkheader(stdin, "begin ", &mode, &fname, &headerr) < 0) + eprintf("%s\n",headerr); + + nfp = checkfile(fname); + if (nfp == NULL) + eprintf("fopen %s:", fname); + + if (uudecode(stdin, nfp) < 0) + goto fail; + + } else { + if (!(fp = fopen(argv[0], "r"))) + eprintf("fopen %s:", argv[0]); + + if (checkheader(fp, "begin ", &mode, &fname, &headerr) < 0) { + fclose(fp); + eprintf("%s\n",headerr); + } + nfp = checkfile(fname); + if (nfp == NULL) { + fclose(fp); + eprintf("fopen %s:", fname); + } + if (uudecode(fp, nfp) < 0) { + fclose(fp); + goto fail; + } + } + chmodtest=fchmod(fileno(nfp),mode); + fclose(fp); + fclose(nfp); + if (chmodtest == -1) + eprintf("chmod %s:%s",fname,strerror(errno)); + + return EXIT_SUCCESS; +fail: + fclose(nfp); + return EXIT_FAILURE; +} + +static FILE * +checkfile(const char *fname) +{ + struct stat st; + int ret; + if (strcmp(fname,"/dev/stdout") == 0) + return stdout; + + ret = lstat(fname, &st); + if (ret < 0 && errno == ENOENT) + goto tropen; /* new file, try to open it */ + + if (ret < 0) { + weprintf("stat: %s:", fname); + return NULL; + } + + if (!S_ISREG(st.st_mode)) { + weprintf("for safety uudecode operates only on regular files and /dev/stdout\n"); + return NULL; + } +tropen: + return fopen(fname,"w"); +} + +static int +checkheader(FILE *fp, const char *header, mode_t *mode, char **fname, char **headerr) +{ + char bufs[PATH_MAX + 11]; /* len header + mode + maxname */ + char *p, *q, *bufp = NULL; + size_t n; + fgets(bufs, sizeof(bufs), fp); + if (bufp == NULL || bufp == '\0') { /* empty or null str */ + *headerr = "empty or null header string"; + return -1; + } + if ((p = strchr(bufs, '\n')) == NULL) { /* line too long or last line on file */ + *headerr = "header string too long or non-newline terminated file"; + return -1; + } + p = bufs; + if (strncmp(bufs, header, strlen(header)) != 0) { + *headerr = "malformed header prefix"; + return -1; + } + p += strlen(header); + if ((q = strchr(p, ' ')) == NULL) { /* malformed mode */ + *headerr = "malformed mode string in header"; + return -1; + } + *q++ = '\0'; /* now mode should be null terminated,q points to fname */ + if (checkmode(p,mode) < 0) { /* error from checkmode */ + *headerr = "invalid mode in header"; + return -1; + } + n = strlen(q); + while (n > 0 && (q[n-1] == '\n' || q[n-1] == '\r')) + q[--n] = '\0'; + + if (n > 0) + *fname = q; + + return 1; +} + + +static int +checkmode(const char *str,mode_t *validmode) +{ + char *end; + int octal; + if (str == NULL || str == '\0') + return -1; /* null str */ + + octal = strtol(str, &end, 8); + if (*end == '\0') { /* successful conversion from a valid str */ + if (octal >= 0 && octal <= 07777) { + if(octal & 04000) *validmode |= S_ISUID; + if(octal & 02000) *validmode |= S_ISGID; + if(octal & 01000) *validmode |= S_ISVTX; + if(octal & 00400) *validmode |= S_IRUSR; + if(octal & 00200) *validmode |= S_IWUSR; + if(octal & 00100) *validmode |= S_IXUSR; + if(octal & 00040) *validmode |= S_IRGRP; + if(octal & 00020) *validmode |= S_IWGRP; + if(octal & 00010) *validmode |= S_IXGRP; + if(octal & 00004) *validmode |= S_IROTH; + if(octal & 00002) *validmode |= S_IWOTH; + if(octal & 00001) *validmode |= S_IXOTH; + *validmode &= 07777; + return 1; + } + } + return -1; /* malformed mode */ +} + +static int +uudecode(FILE *fp, FILE *outfp) +{ + char *bufb=NULL, *p,*nl; + size_t n=0; + int ch , i; +#define DEC(c) (((c) - ' ') & 077) /* single character decode */ +#define IS_DEC(c) ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) ) +#define OUT_OF_RANGE(c) do { \ + weprintf("character %c out of range: [%d-%d]",(c), 1+' ',077+' '+1); \ + return -1; \ + } while (0) + + while (afgets(&bufb,&n,fp)) { + p = bufb; + if ((nl=strchr(bufb, '\n')) != NULL) { /* trim newlines */ + *nl = '\0'; + } else { + weprintf("no newline found, aborting\n"); + return -1; + } + if ((i = DEC(*p)) <= 0) /* check for last line */ + break; + + 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(*p); + + ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; + putc(ch, outfp); + ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; + putc(ch, outfp); + ch = DEC(p[2]) << 6 | DEC(p[3]); + putc(ch, outfp); + } else { + if (i >= 1) { + if (!(IS_DEC(*p) && IS_DEC(*(p + 1)))) + OUT_OF_RANGE(*p); + + ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; + putc(ch, outfp); + } + if (i >= 2) { + if (!(IS_DEC(*(p + 1)) && + IS_DEC(*(p + 2)))) + OUT_OF_RANGE(*p); + + ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; + putc(ch, outfp); + } + } + } + if (ferror(fp)) { + weprintf("read error"); + return -1; + } + } + /* check for end or fail*/ + afgets(&bufb,&n,fp); + if (strnlen(bufb,3) < 3 || strncmp(bufb, "end", 3) != 0 || bufb[3] != '\n') { + weprintf("valid uudecode footer \"end\" not found\n"); + return -1; + } + return 1; +}