/* * unmunge.c -- Program to convert a munged file to original form * * Copyright (C) 1997 Pretty Good Privacy, Inc. * * Designed by Colin Plumb, Mark H. Weaver, and Philip R. Zimmermann * Written by Mark H. Weaver * * $Id: unmunge.c,v 1.13 1997/11/13 23:27:08 mhw Exp $ */ #include #include #include #include /*#include teun: MS VC wants direct.h for mkdir */ #include #include #include #include #include #include #include "util.h" typedef struct UnMungeState { char const * mungedFileName; char dirName[128]; char fileName[128]; char * fileNameTail; int binaryMode, tabWidth; long productNumber, fileNumber, pageNumber, lineNumber; long manifestLineNumber; word16 hdrFlags; CRC pageCRC, seenPageCRC; FILE * manifest; FILE * file; FILE * out; } UnMungeState; /* Returns number of characters decoded, or -1 on error */ static int Decode4(char const src[4], byte dest[3]) { int i, length; byte srcVal[4]; for (i = 0; i < 4 && src[i] != RADIX64_END_CHAR; i++) if ((srcVal[i] = Radix64DigitValue(src[i])) == (byte) -1) return 1; length = i - 1; if (length < 1) return -1; for (; i < 4; i++) srcVal[0] = 0; dest[0] = (srcVal[0] << 2) | (srcVal[1] >> 4); dest[1] = (srcVal[1] << 4) | (srcVal[2] >> 2); dest[2] = (srcVal[2] << 6) | (srcVal[3]); return length; } /* * Return number of characters decoded, or -1 on error */ static int DecodeLine(char const *src, char *dest, int srclength) { int destlength = 0; int result; if (srclength % 4 || !srclength) return -1; /* Must be a multiple of 4 */ while (srclength -= 4) { if (Decode4(src, dest + destlength) != 3) return -1; src += 4; destlength += 3; } result = Decode4(src, dest + destlength); if (result < 1) return -1; return destlength + result; } int PrintFileError(UnMungeState *state, char const *message) { fprintf(stderr, "%s, %s line %ld\n", message, state->mungedFileName, state->lineNumber); return 1; } int ReadManifest(UnMungeState *state, long fileNumberWanted, char const *fileTailPrefix, long prefixLen) { long fileNumber = 0; long firstMissingFileNum = 0, lastMissingFileNum = 0; char buffer[512]; char * p; if (state->manifest == NULL) { if (fileNumberWanted != 0) { assert(fileTailPrefix != NULL); strncpy(state->fileName, fileTailPrefix, sizeof(state->fileName)); state->fileName[sizeof(state->fileName) - 1] = '\0'; state->fileNameTail = state->fileName; } return 0; } while (fgets(buffer, sizeof(buffer), state->manifest)) { if ((p = strchr(buffer, '\n')) != NULL) *p = '\0'; state->manifestLineNumber++; if (buffer[0] == 'D') { if (buffer[1] != ' ') goto invalidManifest; strncpy(state->dirName, buffer + 2, sizeof(state->dirName)); if (state->dirName[sizeof(state->dirName) - 1] != '\0') goto invalidManifest; } else { fileNumber = strtol(buffer, &p, 10); if (p == buffer || *p != ' ') goto invalidManifest; p++; if (fileNumberWanted == 0 || fileNumber < fileNumberWanted) { if (firstMissingFileNum == 0) firstMissingFileNum = fileNumber; lastMissingFileNum = fileNumber; continue; } else if (fileNumber > fileNumberWanted) break; else { size_t len; len = strlen(state->dirName); assert(sizeof(state->fileName) >= sizeof(state->dirName)); memcpy(state->fileName, state->dirName, len); strncpy(state->fileName + len, p, sizeof(state->fileName) - len); if (strncmp(p, fileTailPrefix, prefixLen) != 0) { fprintf(stderr, "Mismatched filename, headers say '%s',\n" " manifest says '%s'\n", fileTailPrefix, p); return 1; } p = state->dirName; while ((p = strchr(p, '/')) != NULL) { *p = '\0'; mkdir(state->dirName, 0777); *p++ = '/'; } state->fileNameTail = state->fileName + len; break; } } } if (firstMissingFileNum != 0) { fprintf(stderr, "Missing files %ld-%ld\n", firstMissingFileNum, lastMissingFileNum); } if (fileNumberWanted != 0 && fileNumber != fileNumberWanted) { fprintf(stderr, "Can't find file %ld in manifest file\n", fileNumberWanted); return 1; } return 0; invalidManifest: fprintf(stderr, "Error parsing manifest file, line %ld\n", state->manifestLineNumber); return 1; } int UnMungeFile(char const *mungedFileName, char const *manifestFileName, int forceOverwrite, int forcePartialFiles) { UnMungeState * state; EncodeFormat const * fmt = NULL; char buffer[512]; char outbuf[BYTES_PER_LINE+1]; char * line; char * lineData; char * p; int length; int result = 0; int skipPage = 0; CRC lineCRC; word32 num; state = (UnMungeState *)calloc(1, sizeof(*state)); state->mungedFileName = mungedFileName; if (manifestFileName != NULL) { if ((state->manifest = fopen(manifestFileName, "r")) == NULL) goto errnoError; } if ((state->file = fopen(state->mungedFileName, "r")) == NULL) goto errnoError; while (!feof(state->file)) { if (fgets(buffer, sizeof(buffer), state->file) == NULL) { if (feof(state->file)) break; goto fileError; } state->lineNumber++; line = buffer; /* Strip leading whitespace */ while (isspace(*line)) line++; if (*line == '\0') continue; /* Strip trailing whitespace */ p = line + strlen(line); while (p > line && (byte)p[-1] < 128 && isspace(p[-1])) p--; lineData = line + PREFIX_LENGTH; /* Pad up to at least PREFIX_LENGTH */ while (p < lineData) *p++ = ' '; *p++ = '\n'; *p = '\0'; length = p - lineData; if (line[0] == HDR_PREFIX_CHAR) { fmt = FindFormat(line[1]); if (!fmt) { result = PrintFileError(state, "ERROR: Invalid header type"); goto error; } } lineCRC = CalculateCRC(fmt->lineCRC, 0, (byte const *)lineData, length); p = line + EncodedLength(fmt, fmt->runningCRCBits); if (DecodeCheckDigits(fmt, p, NULL, fmt->lineCRC->bits, &num) || lineCRC != num) { result = PrintFileError(state, "ERROR: Line CRC failed"); goto error; } if (line[0] == HDR_PREFIX_CHAR) { int formatVersion; int flags; CRC seenPageCRC; int tabWidth; long productNumber; long fileNumber; long pageNumber; char * fileNameTail; int skipNextPage = 0; char * p; EncodeFormat const * hFmt = &hexFormat; /* Parse header line */ p = lineData; if (DecodeCheckDigits(hFmt, p, &p, HDR_VERSION_BITS, &num)) { invalidHeader: result = PrintFileError(state, "ERROR: Invalid header"); goto error; } formatVersion = num; if (DecodeCheckDigits(hFmt, p, &p, HDR_FLAG_BITS, &num)) goto invalidHeader; flags = num; if (DecodeCheckDigits(hFmt, p, &p, fmt->pageCRC->bits, &num)) goto invalidHeader; seenPageCRC = num; if (DecodeCheckDigits(hFmt, p, &p, HDR_TABWIDTH_BITS, &num)) goto invalidHeader; tabWidth = num; if (DecodeCheckDigits(hFmt, p, &p, HDR_PRODNUM_BITS, &num)) goto invalidHeader; productNumber = num; if (DecodeCheckDigits(hFmt, p, &p, HDR_FILENUM_BITS, &num)) goto invalidHeader; fileNumber = num; if (sscanf(p, " Page %ld of ", &pageNumber) < 1) goto invalidHeader; if (formatVersion > 0) { result = PrintFileError(state, "ERROR: Format too new for " "this version of unmunge"); goto error; } p = strstr(p, " of "); if (p == NULL) goto invalidHeader; fileNameTail = p + 4; p = fileNameTail + strlen(fileNameTail); if (p < fileNameTail + 3 || p[-1] != '\n') goto invalidHeader; else p[-1] = '\0'; if (state->out != NULL && state->pageCRC != state->seenPageCRC) { result = PrintFileError(state, "ERROR: Page CRC mismatch on page before"); goto error; } if ((state->hdrFlags & HDR_FLAG_LASTPAGE) && state->out != NULL) { fclose(state->out); state->out = NULL; } if (state->out != NULL) { if (pageNumber != state->pageNumber + 1 || fileNumber != state->fileNumber || productNumber != state->productNumber || tabWidth != state->tabWidth || strcmp(fileNameTail, state->fileNameTail) != 0) { if (fileNumber == state->fileNumber && pageNumber > state->pageNumber + 1) { (void)PrintFileError(state, "ERROR: Missing pages of this file"); if (forcePartialFiles && !state->binaryMode) { fputs("\n\n@@@@@@ Missing pages here! @@@@@@\n\n", state->out); } else { skipNextPage = 1; fclose(state->out); state->out = NULL; remove(state->fileName); } } else { (void)PrintFileError(state, "ERROR: Missing pages of previous file"); if (forcePartialFiles && !state->binaryMode) { fputs("\n\n@@@@@@ Missing pages here! @@@@@@\n\n", state->out); /* Make it non-fatal, though... */ fclose(state->out); state->out = NULL; } else { fclose(state->out); state->out = NULL; remove(state->fileName); } } } } if (state->out == NULL) { if (pageNumber != 1 && !skipPage) (void)PrintFileError(state, "ERROR: File doesn't begin with page 1"); state->binaryMode = (tabWidth == 0); if (pageNumber != 1 && (state->binaryMode || !forcePartialFiles)) { skipNextPage = 1; } else { /* TODO: Use global filelist to get pathname */ result = ReadManifest(state, fileNumber, fileNameTail, strlen(fileNameTail)); if (result != 0) goto error; if (!forceOverwrite) { FILE * file; /* Make sure file doesn't already exist */ file = fopen(state->fileName, "r"); if (file != NULL) { fclose(file); fprintf(stderr, "ERROR: %s already exists\n", state->fileName); result = 1; goto error; } } state->out = fopen(state->fileName, state->binaryMode ? "wb" : "w"); if (state->out == NULL) goto errnoError; if (pageNumber != 1) fputs("\n\n@@@@@@ Missing pages here! @@@@@@\n\n", state->out); } } state->pageCRC = 0; state->seenPageCRC = seenPageCRC; state->hdrFlags = (word16)flags; state->pageNumber = pageNumber; state->fileNumber = fileNumber; state->productNumber = productNumber; state->tabWidth = tabWidth; skipPage = skipNextPage; } else if (!skipPage) { if (state->out == NULL) { result = PrintFileError(state, "ERROR: Missing header line"); goto error; } /* Normal data line */ state->pageCRC = CalculateCRC(fmt->pageCRC, state->pageCRC, (byte const *)lineData, length); line[2] = '\0'; if (DecodeCheckDigits(fmt, line, NULL, fmt->runningCRCBits, &num) || RunningCRCFromPageCRC(fmt, state->pageCRC) != num) { result = PrintFileError(state, "ERROR: Running CRC failed"); goto error; } if (state->binaryMode) { length = DecodeLine(lineData, outbuf, length-1); if (length < 0 || length > BYTES_PER_LINE) { result = PrintFileError(state, "ERROR: Corrupt radix-64 data"); goto error; } fwrite(outbuf, 1, length, state->out); } else { p = lineData; while (*p != '\0') { if (*p == TAB_CHAR) { p++; putc('\t', state->out); while ((p - lineData) % state->tabWidth) { if (*p == '\n') break; else if (*p == ' ') p++; else { result = PrintFileError(state, "ERROR: Not enough spaces " "after a tab character"); goto error; } } } else if (*p == FORMFEED_CHAR) { p++; if (*p != '\n') { result = PrintFileError(state, "ERROR: Formfeed character " "not at end of line"); goto error; } p++; /* Skip newline */ putc('\f', state->out); } else if (*p == CONTIN_CHAR) { p++; if (*p != '\n') { result = PrintFileError(state, "ERROR: Continuation character " "not at end of line"); goto error; } p++; /* Skip newline */ } else if (*p == SPACE_CHAR) { putc(' ', state->out); p++; } else { putc(*p, state->out); p++; } } } } } if (state->out != NULL) { if (!(state->hdrFlags & HDR_FLAG_LASTPAGE)) { result = PrintFileError(state, "ERROR: Missing pages"); goto error; } if (state->pageCRC != state->seenPageCRC) { result = PrintFileError(state, "ERROR: Page CRC failed on previous page"); goto error; } } /* Check for missing files at the end */ result = ReadManifest(state, 0, NULL, 0); goto done; errnoError: result = errno; goto printError; fileError: result = ferror(state->file); printError: fprintf(stderr, "ERROR: %s\n", strerror(result)); error: done: if (state != NULL) { if (state->out != NULL) fclose(state->out); if (state->file != NULL) fclose(state->file); if (state->manifest != NULL) fclose(state->manifest); free(state); } return result; } void UsageAndExit(int result) { fprintf(stderr, "Usage: unmunge [-fp] []\n" " -f Force overwrites of existing files\n" " -p Force unmunge of partial files\n"); exit(result); } int main(int argc, char *argv[]) { int result = 0; int forceOverwrite = 0; int forcePartialFiles = 0; char * fileName = NULL; char * manifestFileName = NULL; int i, j; InitUtil(); for (i = 1; i < argc && argv[i][0] == '-'; i++) { if (0 == strcmp(argv[i], "--")) { i++; break; } for (j = 1; argv[i][j] != '\0'; j++) { if (argv[i][j] == 'h') UsageAndExit(0); else if (argv[i][j] == 'f') forceOverwrite = 1; else if (argv[i][j] == 'p') forcePartialFiles = 1; else { fprintf(stderr, "ERROR: Unrecognized option -%c\n", argv[i][j]); UsageAndExit(1); } } } if (i < argc) fileName = argv[i++]; if (i < argc) manifestFileName = argv[i++]; if (fileName == NULL || i < argc) UsageAndExit(1); if ((result = UnMungeFile(fileName, manifestFileName, forceOverwrite, forcePartialFiles)) != 0) { /* If result > 0, message should have already been printed */ if (result < 0) fprintf(stderr, "ERROR: %s\n", strerror(result)); exit(1); } return 0; } /* * Local Variables: * tab-width: 4 * End: * vi: ts=4 sw=4 * vim: si */