--- pgp/src/armor.c 2018/04/24 16:38:54 1.1.1.2 +++ pgp/src/armor.c 2018/04/24 16:40:28 1.1.1.4 @@ -26,6 +26,15 @@ #include "mpiio.h" #include "language.h" #include "pgp.h" +#include "crypto.h" +#include "armor.h" + +static int dpem_file(char *infile, char *outfile); +static crcword crchware(byte ch, crcword poly, crcword accum); +static int pem_file(char *infilename, char *outfilename, char *clearfilename); +static int pemdecode(FILE *in, FILE *out); +static void mk_crctbl(crcword poly); +static boolean is_pemfile(char *infile); /* Begin PEM routines. This converts a binary file into printable ASCII characters, in a @@ -36,12 +45,14 @@ /* Index this array by a 6 bit value to get the character corresponding * to that value. */ +static unsigned char bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\ abcdefghijklmnopqrstuvwxyz0123456789+/"; /* Index this array by a 7 bit value to get the 6-bit binary field * corresponding to that value. Any illegal characters return high bit set. */ +static unsigned char asctobin[] = { 0200,0200,0200,0200,0200,0200,0200,0200, 0200,0200,0200,0200,0200,0200,0200,0200, @@ -73,8 +84,6 @@ static long infile_line; /* Current lin #define byte unsigned char #define CRCBITS 24 /* may be 16, 24, or 32 */ -/* #define crcword unsigned short */ /* if CRCBITS is 16 */ -#define crcword unsigned long /* if CRCBITS is 24 or 32 */ /* #define maskcrc(crc) ((crcword)(crc)) */ /* if CRCBITS is 16 or 32 */ #define maskcrc(crc) ((crc) & 0xffffffL) /* if CRCBITS is 24 */ #define CRCHIBIT ((crcword) (1L<<(CRCBITS-1))) /* 0x8000 if CRCBITS is 16 */ @@ -105,56 +114,55 @@ static long infile_line; /* Current lin #define PRZCRC 0x864cfbL /* PRZ's 24-bit CRC generator polynomial */ #define CRCINIT 0xB704CEL /* Init value for CRC accumulator */ +static crcword crctable[256]; /* Table for speeding up CRC's */ -/* crchware simulates CRC hardware circuit. Generates true CRC - directly, without requiring extra NULL bytes to be appended - to the message. - Returns new updated CRC accumulator. -*/ -crcword crchware(byte ch, crcword poly, crcword accum) -{ int i; - crcword data; - data = ch; - data <<= CRCSHIFTS; /* shift data to line up with MSB of accum */ - i = 8; /* counts 8 bits of data */ - do - { /* if MSB of (data XOR accum) is TRUE, shift and subtract poly */ - if ((data ^ accum) & CRCHIBIT) - accum = (accum<<1) ^ poly; - else - accum <<= 1; - data <<= 1; - } while (--i); /* counts 8 bits of data */ - return (maskcrc(accum)); -} /* crchware */ - - /* mk_crctbl derives a CRC lookup table from the CRC polynomial. - The table is used later by crcupdate function given below. + The table is used later by the crcbytes function given below. mk_crctbl only needs to be called once at the dawn of time. + + The theory behind mk_crctbl is that table[i] is initialized + with the CRC of i, and this is related to the CRC of i>>1, + so the CRC of i>>1 (pointed to by p) can be used to derive + the CRC of i (pointed to by q). */ +static void mk_crctbl(crcword poly) { int i; - for (i=0; i<256; i++) - crctable[i] = crchware((byte) i, poly, 0); -} /* mk_crctbl */ - - -/* crcupdate calculates a CRC using the fast table-lookup method. - Returns new updated CRC accumulator. -*/ -crcword crcupdate(byte data, register crcword accum) -{ byte combined_value; + crcword t, *p, *q; + p = q = crctable; + *q++ = 0; + *q++ = poly; + for (i = 1; i < 128; i++) + { t = *++p; + if (t & CRCHIBIT) + { t <<= 1; + *q++ = t ^ poly; + *q++ = t; + } + else + { t <<= 1; + *q++ = t; + *q++ = t ^ poly; + } + } +} - /* XOR the MSByte of the accum with the data byte */ - combined_value = (accum >> CRCSHIFTS) ^ data; - accum = (accum << 8) ^ crctable[combined_value]; - return (maskcrc(accum)); -} /* crcupdate */ +/* + * Accumulate a buffer's worth of bytes into a CRC accumulator, + * returning the new CRC value. + */ +crcword +crcbytes(byte *buf, unsigned len, register crcword accum) +{ + do { + accum = accum<<8 ^ crctable[(byte)(accum>>CRCSHIFTS) ^ *buf++]; + } while (--len); + return maskcrc(accum); +} /* crcbytes */ /* Initialize the CRC table using our codes */ -void init_crc() +void init_crc(void) { mk_crctbl(PRZCRC); } @@ -206,39 +214,199 @@ static void outcrc (word32 crc, FILE *ou putc('\n',outFile); } /* outcrc */ -/* Return filename for output (text mode), but replace last letter of - * filename with the ascii for num (last two letters if num > 10). +/* Return filename for output (text mode), but replace last letter(s) of + * filename with the ascii for num. It will use the appropriate number + * of digits for ofnum when converting num, so if ofnum < 10, use 1 digit, + * >= 10 and < 100 use 2 digits, >= 100 and < 1000 use 3 digits. If its + * >= 1000, then we have other problems to worry about, and this might do + * weird things. */ -static char *numFilename( char *fname, int num) +static char *numFilename( char *fname, int num, int ofnum) { static char fnamenum[MAX_PATH]; int len; + int offset = 1; strcpy (fnamenum, fname); len = strlen (fnamenum); - if (num < 10) - fnamenum[len-1] = '0' + num; - else /* If num > 100, this will be slightly screwy */ - { fnamenum[len-2] = '0' + (num / 10); - fnamenum[len-1] = '0' + (num % 10); - } + do { + fnamenum[len-offset] = '0' + (num%10); + num /= 10; + ofnum /= 10; + offset++; + } while (ofnum >= 1 && offset < 4); return(fnamenum); } -/* Encode a file in sections. 64 ASCII bytes * 720 lines = 46K, - recommended max. Usenet message size is 50K so this leaves a nice - margin for .signature. In the interests of orthogonality and - programmer laziness no check is made for a message containing only - a few lines (or even just an 'end') after a section break. -*/ +/* + * Reads and discards a line from the given file. Returns -1 on error or + * EOF, 0 if the line is blank, and 1 if the line is not blank. + */ +static int +skipline(FILE *f) +{ + int state, flag, c; + + state = 0; + flag = 0; + for (;;) { + c = getc(f); + if (c == '\n') + return flag; + if (state) + { ungetc(c, f); + return flag; + } + if (c == EOF) + return -1; + if (c == '\r') + state = 1; + else if (c != ' ') + flag = 1; + } +} /* skipline */ + +/* + * Copies a line from the input file to the output. Does NOT copy the + * trailing newline. Returns -1 on EOF or error, 0 if the line was terminated + * by EOF, and 1 if the line was terminated with a newline sequence. + */ +static int +copyline(FILE *in, FILE *out) +{ + int state, flag, c; + + state = 0; + for (;;) { + c = getc(in); + if (c == '\n') + return 1; + if (state) + { ungetc(c, in); + return 1; + } + if (c == EOF) + return 0; + if (c == '\r') + state = 1; + else + putc(c, out); + } +} /* copyline */ + +/* + * Reads a line from file f, up to the size of the buffer. The line in the + * buffer will NOT include line termination, although any of (CR, LF, CRLF) + * is accepted on input. The return value is -1 on error, 0 if the line + * was terminated abnormally (EOF, error, or out of buffer space), and + * 1 if the line was terminated normally. + * + * Passing in a buffer less than 2 characters long is not a terribly bright + * idea. + */ +static int +getline(char *buf, int n, FILE *f) +{ + int state; + char *p; + int c; + + state = 0; + p = buf; + for (;;) + { c = getc(f); + if (c == '\n') + { *p = 0; + return 1; /* Line terminated with \n or \r\n */ + } + if (state) + { ungetc(c, f); + *p = 0; + return 1; /* Line terminated with \r */ + } + if (c == EOF) + { *p = 0; + return (p == buf) ? -1 : 0; /* Error */ + } + if (c == '\r') + state = 1; + else if (--n > 0) + *p++ = c; + else + { + ungetc(c, f); + *p = 0; + return 0; /* Out of buffer space */ + } + } /* for (;;) */ +} /* getline */ + +/* + * Read a line from file f, buf must be able to hold at least 80 characters. + * Strips trailing spaces and line terminator, can read LF, CRLF and CR + * textfiles. Anything after 80 characters is ignored. It can't be ASCII + * armor anyway. + */ +static char * +get_armor_line(char *buf, FILE *f) +{ + int c, n = 79; + char *p = buf; + + do { + c = getc(f); + if (c == '\n' || c == '\r' || c == EOF) + break; + *p++ = c; + } while (--n > 0); + if (p == buf && c == EOF) + { *buf = '\0'; + return NULL; + } + /* skip to end of line */ + while (c != '\n' && c != '\r' && c != EOF) + c = getc(f); + if (c == '\r' && (c = getc(f)) != '\n') + ungetc(c, f); + while (--p >= buf && *p == ' ') + ; + *++p = '\0'; + return buf; +} + + +/* + * Encode a file in sections. 64 ASCII bytes * 720 lines = 46K, + * recommended max. Usenet message size is 50K so this leaves a nice + * margin for .signature. In the interests of orthogonality and + * programmer laziness no check is made for a message containing only + * a few lines (or even just an 'end') after a section break. + */ #define LINE_LEN 48L int pem_lines = 720; #define BYTES_PER_SECTION (LINE_LEN * pem_lines) +#if 1 +/* This limit is advisory only; longer lines are handled properly. + * The only requirement is that this be at least as long as the longest + * delimiter string used by PGP + * (e.g. "-----BEGIN PGP MESSAGE, PART %02d/%02d-----\n") + */ +#define MAX_LINE_SIZE 80 +#else #ifdef MSDOS /* limited stack space */ #define MAX_LINE_SIZE 256 #else #define MAX_LINE_SIZE 1024 #endif +#endif + +#ifndef VMS +/* armored files are opened in binary mode so that CRLF/LF/CR files + can be handled by all systems */ +#define FOPRPEM FOPRBIN +#else +#define FOPRPEM FOPRTXT +#endif extern boolean verbose; /* Undocumented command mode in PGP.C */ extern boolean filter_mode; @@ -250,10 +418,11 @@ extern boolean filter_mode; * If clearfilename is non-NULL, first output that file preceded by a * special header line. */ +static int pem_file(char *infilename, char *outfilename, char *clearfilename) { char buffer[MAX_LINE_SIZE]; - int i,rc,bytesRead,lines = 0; + int i, rc, bytesRead, lines = 0; int noSections, currentSection = 1; long fileLen; crcword crc; @@ -274,6 +443,12 @@ int pem_file(char *infilename, char *out fileLen = ftell(inFile); rewind(inFile); noSections = (fileLen + BYTES_PER_SECTION - 1) / BYTES_PER_SECTION; + if (noSections > 99) + { + pem_lines = ((fileLen+LINE_LEN-1)/LINE_LEN + 98) / 99; + noSections = (fileLen + BYTES_PER_SECTION - 1) / BYTES_PER_SECTION; + fprintf(pgpout, "value for \"armorlines\" is too low, using %d\n", pem_lines); + } } if (outfilename == NULL) @@ -281,7 +456,7 @@ int pem_file(char *infilename, char *out else { if (noSections > 1) { force_extension(outfilename, ASC_EXTENSION); - outFile = fopen (numFilename (outfilename, 1), FOPWTXT); + outFile = fopen (numFilename (outfilename, 1, noSections), FOPWTXT); } else outFile = fopen(outfilename,FOPWTXT); @@ -300,11 +475,20 @@ int pem_file(char *infilename, char *out return(1); } fprintf (outFile, "-----BEGIN PGP SIGNED MESSAGE-----\n\n"); - while (fgets(buffer, sizeof(buffer), clearFile) != NULL) - { /* Quote lines beginning with '-' */ - if (buffer[0] == '-') + while ((i = getline(buffer, sizeof buffer, clearFile)) >= 0) + { + /* Quote lines beginning with '-' as per RFC1113; + * Also quote lines beginning with "From "; this is + * for Unix mailers which add ">" to such lines. + */ + if (buffer[0] == '-' || strncmp(buffer, "From ", 5)==0) fputs("- ", outFile); fputs(buffer, outFile); + /* If there is more on this line, copy it */ + if (i == 0) + if (copyline(clearFile, outFile) <= 0) + break; + fputc('\n', outFile); } fclose (clearFile); putc('\n', outFile); @@ -335,18 +519,10 @@ int pem_file(char *infilename, char *out if (bytesRead < LINE_LEN) fill0 (buffer+bytesRead, LINE_LEN-bytesRead); - for (i=0; i= buf && (*bp == ' ' || *bp == '\r')) - --bp; - bp[1] = '\n'; /* Terminate line cleanly */ - bp[2] = '\0'; -} - - +static int dpem_buffer(char *inbuf, char *outbuf, int *outlength) { unsigned char *bp; @@ -436,13 +592,12 @@ int dpem_buffer(char *inbuf, char *outbu unsigned int c1,c2,c3,c4; register int j; - strip_trailing(inbuf); length = 0; bp = (unsigned char *)inbuf; /* FOUR input characters go into each THREE output charcters */ - while (*bp!='\0' && *bp!='\n' && *bp!='\r') + while (*bp!='\0') { if (*bp&0x80 || (c1=asctobin[*bp])&0x80) return -1; ++bp; @@ -451,7 +606,13 @@ int dpem_buffer(char *inbuf, char *outbu if (*++bp == PAD) { c3 = c4 = 0; length += 1; - if (*++bp != PAD) + if (c2 & 15) + return -1; + if (strcmp((char *)bp, "==") == 0) + bp += 1; + else if (strcmp((char *)bp, "=3D=3D") == 0) + bp += 5; + else return -1; } else if (*bp&0x80 || (c3=asctobin[*bp])&0x80) @@ -460,6 +621,14 @@ int dpem_buffer(char *inbuf, char *outbu { if (*++bp == PAD) { c4 = 0; length += 2; + if (c3 & 3) + return -1; + if (strcmp((char *)bp, "=") == 0) + ; /* All is well */ + else if (strcmp((char *)bp, "=3D") == 0) + bp += 2; + else + return -1; } else if (*bp&0x80 || (c4=asctobin[*bp])&0x80) return -1; @@ -486,7 +655,7 @@ static char pemfilename[MAX_PATH]; * the sequence number is expected at the end of the file name */ static FILE * -open_next() +open_next(void) { char *p, *s, c; FILE *fp; @@ -497,7 +666,7 @@ open_next() if (*p != '9') { ++*p; - return(fopen(pemfilename, FOPRTXT)); + return(fopen(pemfilename, FOPRPEM)); } *p = '0'; } @@ -507,7 +676,7 @@ open_next() { /* try replacing character ( .as0 -> .a10 ) */ c = *p; *p = '1'; - if ((fp = fopen(pemfilename, FOPRTXT)) != NULL) + if ((fp = fopen(pemfilename, FOPRPEM)) != NULL) return(fp); *p = c; /* restore original character */ } @@ -516,16 +685,17 @@ open_next() s[1] = *s; *p = '1'; /* insert digit ( fn0 -> fn10 ) */ - return(fopen(pemfilename, FOPRTXT)); + return(fopen(pemfilename, FOPRPEM)); } /* * Copy from in to out, decoding as you go, with handling for multiple * 500-line blocks of encoded data. */ +static int pemdecode(FILE *in, FILE *out) { -char inbuf[MAX_LINE_SIZE]; +char inbuf[80]; char outbuf[80]; int i, n, status; @@ -533,23 +703,23 @@ int line; int section, currentSection = 1; int noSections = 0; int gotcrc = 0; -long crc=CRCINIT, chkcrc; +long crc=CRCINIT, chkcrc = -1; char crcbuf[4]; int ret_code = 0; +int end_of_message; init_crc(); for (line = 1; ; line++) /* for each input line */ { - if (fgets(inbuf, sizeof(inbuf), in) == NULL) - { - fprintf(pgpout,PSTR("ERROR: ASCII armor decode input ended unexpectedly!\n")); - return(18); + if (get_armor_line(inbuf, in) == NULL) + end_of_message = 1; + else + { end_of_message = (strncmp(inbuf,"-----END PGP MESSAGE,", 21) == 0); + ++infile_line; } - ++infile_line; - if (currentSection!=noSections && - strncmp(inbuf,"-----END PGP MESSAGE,", 21) == 0) + if (currentSection!=noSections && end_of_message) { /* End of this section */ if (gotcrc) { if (chkcrc != crc) @@ -563,7 +733,7 @@ int ret_code = 0; /* Try and find start of next section */ do - { if (fgets(inbuf,sizeof(inbuf),in) == NULL) + { if (get_armor_line(inbuf,in) == NULL) { FILE *nextf; if ((nextf = open_next()) != NULL) { @@ -597,13 +767,13 @@ int ret_code = 0; /* Skip header after BEGIN line */ do { ++infile_line; - if (fgets(inbuf, sizeof inbuf, in) == NULL) + if (get_armor_line(inbuf, in) == NULL) { fprintf(pgpout,PSTR("ERROR: Hit EOF in header of section %d.\n"), currentSection); return(-1); } - } while (inbuf[0] != '\n' && inbuf[0] != '\r'); + } while (inbuf[0] != '\0'); /* Continue decoding */ continue; @@ -612,8 +782,18 @@ int ret_code = 0; /* Quit when hit the -----END PGP MESSAGE----- line or a blank, or handle checksum */ if (inbuf[0] == PAD) /* Checksum lines start with PAD char */ - { status = dpem_buffer (inbuf+1,crcbuf,&n); - if (status==-1 || n!=3) + { + /* If the already-armored file is sent through MIME + * and gets armored again, '=' will become '=3D'. + * To make life easier, we detect and work around this + * idiosyncracy. + */ + if (strlen(inbuf) == 7 && + inbuf[1] == '3' && inbuf[2] == 'D') + status = dpem_buffer(inbuf+3, crcbuf, &n); + else + status = dpem_buffer(inbuf+1, crcbuf, &n); + if ( status < 0 || n != 3 ) { fprintf(pgpout,PSTR("ERROR: Badly formed ASCII armor checksum, line %d.\n"),line); return -1; } @@ -622,7 +802,7 @@ int ret_code = 0; gotcrc = 1; continue; } - if (inbuf[0] == '\n' || inbuf[0] == '\r') + if (inbuf[0] == '\0') { fprintf(pgpout,PSTR("WARNING: No ASCII armor `END' line.\n")); break; } @@ -643,8 +823,7 @@ int ret_code = 0; return -1; } - for (i=0; i= buf && *p == '\n') - { nline = TRUE; - /* remove \n, original file may have ended in unterminated line */ - *p = '\0'; - } - /* De-quote lines starting with '- ' */ - fputs(buf + ((buf[0]=='-'&&buf[1]==' ')?2:0), litout); - } + + if (strncmp(buf,"-----BEGIN PGP ", 15) == 0) + break; + if (nline) + putc('\n', litout); + /* De-quote lines starting with '- ' */ + fputs(buf + ((buf[0]=='-'&&buf[1]==' ')?2:0), litout); + /* Copy trailing part of line, if any. */ + if (!status) + status = copyline(in, litout); + /* Ignore error; getline will discover it again */ } fflush (litout); if (ferror(litout)) @@ -822,6 +1002,7 @@ char *litfile = NULL; } fclose (litout); canonfile = tempfile(TMP_WIPE|TMP_TMPDIR); + strip_spaces = TRUE; make_canonical (litfile, canonfile); rmtemp (litfile); litfile = canonfile; @@ -831,7 +1012,7 @@ char *litfile = NULL; do { ++infile_line; fpos = ftell(in); - if (fgets(buf, sizeof buf, in) == NULL) + if (get_armor_line(buf, in) == NULL) { fprintf(pgpout,PSTR("ERROR: Hit EOF in header.\n")); fclose(in); @@ -844,7 +1025,7 @@ char *litfile = NULL; break; } #endif - } while (buf[0] != '\n'); + } while (buf[0] != '\0'); if ((out = fopen(outfile, FOPWBIN)) == NULL) { fprintf(pgpout,PSTR("\n\007Unable to write ciphertext output file '%s'.\n"), outfile); @@ -859,7 +1040,7 @@ char *litfile = NULL; char lit_mode=MODE_TEXT; word32 dummystamp = 0; FILE *f = fopen(litfile,FOPRBIN); - write_ctb_len (out, CTB_LITERAL_TYPE, fsize(f), FALSE); + write_ctb_len (out, CTB_LITERAL2_TYPE, fsize(f)+6, FALSE); fwrite ( &lit_mode, 1, 1, out ); /* write lit_mode */ fputc ('\0', out); /* No filename */ fwrite ( &dummystamp, 1, sizeof(dummystamp), out); /* dummy timestamp */