uuUndo/uundo13.c
2015-02-06 00:56:41 -08:00

1 line
17 KiB
C

/*
* uundo.c: extract a multi-part uuencoded archive Version 1.3a
* written by Aaron Giles 03/08/92
*
* usage:
* uundo [-hLloqv] file1 [file2 [...]]
* uundo [-hLloqv] < file
*
* options:
* -h header: write the article header of the earliest part to target.hdr
* -L lower all: convert all characters in all filenames to lower case
* -l lower: convert only all-upper-case filenames to lower case
* -o overwrite: automatically overwrite existing files without permission
* -q query: allow overwriting of existing files, but ask permission first
* -v verbose: display part numbers and status info
*
* special features:
* - does not require all parts to be in order*
* - attempts to identify missing parts*
* - works on uuencoded files included in shar archives
* - can accept either single or multiple files on the command line
* - can accept stream input from stdin
* (* requires part numbering informating in the subject line of each part)
*
* bugs/requests/modifications to:
* a-giles@uchicago.edu -or-
* gile@midway.uchicago.edu -or-
* giles@hep.uchicago.edu
*
* philosophy:
* I will try my best to keep up with any bug reports (first priority) or
* new feature requests. Note that my primary goal is robustness, so that
* requests which would overly complicate things or result in "flaky" code
* will very likely not be put in.
*
* copyright control:
* This program is public domain, though copyrighted (c) 1992 by its
* author, Aaron Giles. If you wish to distribute modified copies of
* this program, please contact the author first.
*
* standard disclaimer:
* Use this program at your own risk. The author takes no responsibility
* for any loss of data or damage that results from using this program.
*
* known problems/limitations:
* - cannot handle multiple target files in the same input file (sorry!)
* - can't yet accept input piped from rn or trn
*
* to be added in future versions:
* - support for saving header information
* - support for piping, allowing use with rn/trn
*
* version history:
* 1.0 (2/11/92) - initial release
* 1.1 (2/13/92) - added stdin support for preliminary use with rn
* no longer so restrictive on the first line of each part
* added verbose option
* added a warning to overwrite existing files
* added an option to turn off this warning
* now attempts to make file writeable before overwriting
* fixed problem with "BEGIN..." lines fooling the decoder
* 1.2 (2/21/92) - added force lowercase options
* changed procedure dealing with conflicting filenames
* added query option
* now uses process ID in temporary names to avoid conflicts
* output directory can be set via UUNDO environment variable
* 1.21 (2/28/92) - fixed bug that would alter directory attributes if none
* of the input files were found
* 1.3 (3/08/92) - added support for maximum filename length
* - refined handling of jumbled and unlabelled parts
* - fixed the aforementioned bug -- again
* - added option to write header of earliest part to a file
*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
/*
* Platform-specific definitions
*/
#define PATHSEP '/' /* path separator character */
#define MAXNAMELEN 32 /* maximum length of a given filename */
#define TEMPNAME "/usr/tmp/uu" /* prefix for temporary files */
/*
* Arbitrary global definitions
*/
#define MAXLEN 256 /* maximum length of strings/pathnames */
#define MAXPARTS 256 /* maximum number of parts in file */
#define BUFSIZE 32768 /* size of temporary buffer */
/*
* Header storage structure
*/
typedef struct header_str *header_ptr;
struct header_str {
char *line;
header_ptr next;
};
/*
* Global variables
*/
int debug = 0; /* debug flag */
int header = 0; /* header flag */
int Lower = 0; /* all lower case flag */
int lower = 0; /* lower case flag */
int overwrite = 0; /* overwrite flag */
int query = 0; /* query flag */
int verbose = 0; /* verbose flag */
int part, total; /* current part number and total count */
int pending[MAXPARTS]; /* table of pending entries */
char lastsubj[MAXLEN]; /* text of the last subject line read */
int partswritten; /* total number of parts written to target */
long int byteswritten; /* total number of bytes written to target */
FILE *targetfile; /* stream pointer for target file */
char targetname[MAXLEN]; /* name of target file */
char *targetpath; /* pointer to path for output directory */
int targetmode; /* file mode for final target */
header_ptr hdrroot = NULL; /* linked list of header lines */
header_ptr hdrlast = NULL; /* last group of header lines read */
int hdrpart; /* part associated with this header */
pid_t pid; /* process ID of this program */
/*
* tempname:
* put temporary file name for part num into string variable name
*/
char *tempname(int num, char *name) {
strcpy(name, TEMPNAME);
sprintf(name+strlen(name), "%ld.%03i", pid, num);
return name;
}
/*
* filename:
* combine path, filename, and iteration, checking for length
*/
char *filename(char *dest, char *path, char *file, int iter) {
char *p, *q;
char suffix[4];
*dest = 0;
if (path) strcat(dest, path);
if ((!path) || (dest[strlen(dest) - 1] != PATHSEP)) {
dest[strlen(dest) + 1] = 0;
dest[strlen(dest)] = PATHSEP;
}
q = dest + strlen(dest);
for (p = file + strlen(file); (*(p - 1) != PATHSEP) && (p > file); p--);
strncat(dest, p, MAXNAMELEN);
if (iter) {
if (iter > 0) sprintf(suffix, ".%02i", iter);
else strcpy(suffix, ".hdr");
if (strlen(q) > (MAXNAMELEN - 3))
strcpy(q + MAXNAMELEN - 3, suffix);
else
strcpy(q + strlen(q), suffix);
}
return dest;
}
/*
* createfile:
* attempt to create the named file
*/
FILE *createfile(char *name, char *type) {
FILE *file;
if (!(file = fopen(name, "w")))
fprintf(stderr, "uundo: unable to create %s file %s\n", type, name);
return file;
}
/*
* openfile:
* attempt to open the named file for reading
*/
FILE *openfile(char *name, char *type) {
FILE *file;
if (!(file = fopen(name, "r")))
fprintf(stderr, "uundo: unable to open %s file %s\n", type, name);
return file;
}
/*
* deletefile:
* remove the named file
*/
void deletefile(char *name) {
chmod(name, 0666);
if (unlink(name))
fprintf(stderr, "uundo: unable to remove file %s\n", name);
}
/*
* checkfile:
* check for existence of target file before overwriting
*/
int checkfile(char *name) {
FILE *file;
char s[MAXLEN];
int count = 0;
while (file = fopen(filename(targetname, targetpath, name, count++), "r")) {
fclose(file);
if (overwrite) {
deletefile(targetname);
return 1;
} else if (query) {
fprintf(stderr, "uundo: %s already exists: overwrite? ", name);
gets(s);
if ((*s == 'Y') || (*s == 'y')) {
deletefile(targetname);
return 1;
}
}
}
return 1;
}
/*
* readline:
* input a line and strip the newline character
*/
int readline(FILE *file, char *s) {
int l;
if (!fgets(s, MAXLEN, file)) return 0;
if ((l = strlen(s)) == (MAXLEN - 1)) {
fprintf(stderr, "uundo: invalid input file format\n");
exit(1);
}
s[l - 1] = 0;
return 1;
}
/*
* addhdr:
* add a header line to the end of the linked list
*/
header_ptr addhdr(header_ptr root, char *line) {
header_ptr new, cur;
if (new = (header_ptr)malloc(sizeof(struct header_str))) {
new->next = NULL;
if (new->line = (char *)malloc(strlen(line) + 1)) {
strcpy(new->line, line);
if (root) {
for (cur = root; cur->next; cur = cur->next);
cur->next = new;
} else root = new;
return root;
}
}
fprintf(stderr, "uundo: out of memory!");
exit(1);
}
/*
* killhdr:
* kill the linked list of header lines
*/
header_ptr killhdr(header_ptr root) {
header_ptr cur, next;
if (!root) return NULL;
for (cur = root; cur; cur = next) {
free(cur->line);
next = cur->next;
free(cur);
}
return NULL;
}
/*
* writehdr:
* write out the linked list of headers
*/
void writehdr(header_ptr root) {
FILE *hdrfile;
char hdrname[MAXLEN];
if ((!header) || (!root)) return;
filename(hdrname, targetpath, targetname, -1);
if (!(hdrfile = createfile(hdrname, "header"))) exit(1);
for ( ; root->next; root = root->next)
fprintf(hdrfile, "%s\n", root->line);
fclose(hdrfile);
}
/*
* uuline:
* determine if the given line is a correct uuencoded line
*/
int uuline(char *s, int full) {
int l, len, max, min;
if ((strncasecmp(s, "end", 3) == 0) ||
(strncasecmp(s, "begin", 5) == 0) ||
((l = s[0] - ' ') & 0xc0)) return 0;
min = ((l * 4) + 2) / 3;
max = ((min + 3) / 4) * 4 + 1;
len = strlen(s) - 1;
if ((len < min) || (len > max)) return 0;
if (!full && ((l == 45) || (l == 0))) return 1;
for (s++; *s; s++)
if ((*s < ' ') || (*s > '`')) return 0;
return 1;
}
/*
* headerstate:
* determine whether we should be saving (1) or ignoring (0) header lines
*/
int headerstate(char *line, int oldstate) {
if (oldstate) {
if ((line[0] == '#') && (strstr(line, "/bin/sh"))) return 0;
return 1;
} else {
while (!isspace(*line) && *line && (*line != ':')) line++;
if (*line == ':') return 1;
return 0;
}
}
/*
* decode:
* decode a uuencoded line and output the binary data to ouf
*/
void decode(FILE *outf, char *line) {
int count = line[0] - ' ';
int i, j;
if (debug) printf("%s\n", line);
for (j = 1; line[j]; j++) line[j] = (line[j] - ' ') & 0x3f;
for (i = 0, j = 1; i < count; i += 3, j += 4) {
line[i] = ((line[j] << 2) | (line[j+1] >> 4)) & 0xff;
line[i+1] = ((line[j+1] << 4) | (line[j+2] >> 2)) & 0xff;
line[i+2] = ((line[j+2] << 6) | (line[j+3] )) & 0xff;
}
if (count) {
if (outf == targetfile) byteswritten += count;
if (count -= fwrite(line, 1, count, outf)) {
fprintf(stderr, "uundo: unable to write to output file\n");
exit(1);
}
}
}
/*
* unpend:
* append pending file to the target
*/
void unpend(int number) {
int i;
FILE *inf;
char inname[MAXLEN];
char buffer[BUFSIZE];
if (!(inf = openfile(tempname(number, inname), "temporary"))) exit(1);
while (i = fread(buffer, 1, BUFSIZE, inf)) {
if (fwrite(buffer, 1, i, targetfile) != i) {
fprintf(stderr, "uundo: unable to append temporary file %s\n", inname);
exit(1);
} else
byteswritten += i;
}
fclose(inf);
deletefile(inname);
pending[number] = 0;
}
/*
* parse:
* determine part number and total number from subject header line
*/
void parse(char *p) {
char *q;
int i;
part = -1;
for (; *p; p++) {
switch (*p) {
case 'P': case 'p':
if (isalpha(*(p - 1)) || (strncasecmp(p, "part", 4)) ||
isalpha(*(p + 4))) continue;
p += 4;
break;
case '(': case '{': case '[':
p++;
break;
case 'O': case 'o':
if (isalpha(*(p - 1)) || (strncasecmp(p, "of", 2)) ||
isalpha(*(p + 2))) continue;
case '|':
case '/':
for (q = p; isspace(*(q - 1)); q--);
if (!isdigit(*(q - 1))) continue;
while (isdigit(*(q - 1))) q--;
if (isalpha(*(q - 1))) continue;
p = q;
break;
default:
continue;
}
i = (int)strtol(p, &q, 10);
if (p == q) { p--; continue; }
part = i;
for (p = q; isspace(*p); p++);
switch (*p) {
case '|':
case '/':
p++;
break;
case 'o': case 'O':
if (strncasecmp(p, "of", 2) == 0) {
p+=2;
break;
}
default:
p--;
continue;
}
while (isspace(*p)) p++;
i = (int)strtol(p, &q, 10);
if (p == q) { p--; continue; }
total = i;
return;
}
}
/*
* initfile:
* parse the uuencode header line and initialize the output file
*/
int initfile(char *p) {
char *q;
if (partswritten > 0) {
fprintf(stderr, "uundo: found extra part 1, ignored\n");
return 1;
}
while (isspace(*p) && *p) p++;
if (*p) targetmode = (int)strtol(p, (char **)NULL, 8);
else targetmode = 0666;
while (!isspace(*p) && *p) p++;
while (isspace(*p) && *p) p++;
if (!*p) {
strcpy(p, "a.out");
fprintf(stderr, "uundo: filename not found, using a.out instead\n");
}
for (q = p; *q && !islower(*q); q++);
if (Lower || (lower && !*q))
for (q = p; *q; *q = tolower(*q), q++);
if (!checkfile(p)) exit(1);
if (!(targetfile = createfile(targetname, "output"))) exit(1);
partswritten = 0;
return 0;
}
/*
* startpart:
* figure out where to output the newly-found part
*/
FILE *startpart() {
char name[MAXLEN];
FILE *outf;
if (partswritten > 0)
while (pending[partswritten + 1]) unpend(++partswritten);
if ((part == -1) && (partswritten < 0)) {
fprintf(stderr, "uundo: unnumbered part found out of order; ignored\n");
return (FILE *)NULL;
}
if ((part == -1) || (part == (partswritten + 1)))
return targetfile;
if ((part <= partswritten) || (pending[part])) {
fprintf(stderr, "uundo: found extra part %i, ignored\n", part);
return (FILE *)NULL;
}
pending[part] = 1;
if (!(outf = createfile(tempname(part, name), "temporary"))) exit(1);
return outf;
}
/*
* init:
* initialize pending array and do general setup
*/
int init(int argc, char *argv[]) {
int i;
char *p;
pid = getpid();
targetpath = getenv("UUNDO");
strcpy(lastsubj, "");
for (i = 1; argv[i] && (*argv[i] == '-'); i++) {
for (p = argv[i] + 1; *p; p++) {
switch (*p) {
case 'd': debug = 1; break;
case 'h': header = 1; break;
case 'L': Lower = 1; break;
case 'l': lower = 1; break;
case 'o': overwrite = 1; break;
case 'q': query = 1; break;
case 'v': verbose = 1; break;
}
}
}
return i;
}
/*
* initglobals:
* reset all global variables in anticipation of next file
*/
void initglobals() {
int i;
for (i = 0; i < MAXPARTS; pending[i++] = 0);
part = -1;
total = 0;
partswritten = -2;
byteswritten = 0;
targetname[0] = 0;
targetmode = 0;
hdrroot = killhdr(hdrroot);
hdrlast = killhdr(hdrlast);
hdrpart = MAXPARTS;
}
/*
* findstart:
* parse the header and skip garbage before uuencoded data
*/
FILE *findstart(FILE *inf) {
FILE *outf;
char line[MAXLEN];
int ignore = 0, save = 0;
while (readline(inf, line)) {
if (!line[0]) continue;
if ((header) && (save = headerstate(line, save)))
hdrlast = addhdr(hdrlast, line);
if (strncasecmp(line, "Subject:", 8) == 0) {
ignore = 0;
parse(line + 8);
} else if (strncmp(line, "begin ", 6) == 0) {
if (!(ignore = initfile(line + 6)))
return targetfile;
} else if (!ignore && uuline(line, 1) &&
(line[0] != '#') && (line[0] != '-') && (line[0] != ' ')) {
if (outf = startpart()) {
decode(outf, line);
return outf;
} else ignore = 1;
}
}
return (FILE *)NULL;
}
/*
* partstatus:
* display current part number and keep correct header info.
*/
void partstatus() {
if (part < 0) part = partswritten + 1;
if (header) {
if (part < hdrpart) {
hdrroot = killhdr(hdrroot);
hdrroot = hdrlast;
hdrpart = part;
hdrlast = NULL;
} else hdrlast = killhdr(hdrlast);
}
if (verbose) {
if (total)
fprintf(stderr, "uundo: found part %i of %i\n", part, total);
else
fprintf(stderr, "uundo: found part %i\n", part);
}
}
/*
* cleanup:
* clean up our little mess
*/
void cleanup() {
char name[MAXLEN];
int i, found = 0;
for (i = 0; i < MAXPARTS; found += pending[i++]);
if ((partswritten < 0) && (found))
fprintf(stderr, "uundo: missing file part 1\n");
else if (partswritten >= 0) {
while (pending[partswritten + 1]) unpend(++partswritten);
if ((total) && (partswritten < total)) {
fprintf(stderr, "uundo: missing part %i of %i; target removed\n",
partswritten + 1, total);
if (partswritten >= 0) deletefile(targetname);
} else {
if (targetmode) chmod(targetname, targetmode);
if (verbose)
fprintf(stderr, "Wrote file %s (%i parts, %ld bytes)\n", targetname,
partswritten, byteswritten);
}
if (partswritten >= 0) fclose(targetfile);
}
for (i = 0; i < MAXPARTS; i++)
if (pending[i]) deletefile(tempname(i, name));
if (header) {
writehdr(hdrroot);
hdrroot = killhdr(hdrroot);
}
}
/*
* main program loop
*/
int main(int argc, char *argv[]) {
FILE *infile = stdin, *outfile;
int fptr, start;
char line[MAXLEN];
start = init(argc, argv);
initglobals();
for (fptr = start;
(fptr < argc) || ((fptr == start) && (argc == start)); fptr++) {
if ((argc > start) && (!(infile = openfile(argv[fptr], "input"))))
continue;
while (outfile = findstart(infile)) {
partstatus();
while (readline(infile, line)) {
if (!uuline(line, 0)) break;
decode(outfile, line);
}
if (outfile != targetfile)
fclose(outfile);
else
partswritten++;
}
fclose(infile);
}
cleanup();
}