/* * 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 #include #include #include #include #include /* * 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(); }