--- pgp/src/pgp.c 2018/04/24 16:37:52 1.1 +++ pgp/src/pgp.c 2018/04/24 16:45:56 1.1.1.10 @@ -1,3833 +1,2953 @@ -/* - Pretty Good(tm) Privacy - RSA public key cryptography for the masses - Written by Philip Zimmermann, Phil's Pretty Good(tm) Software. - Beta test version 1.0 - Last revised 5 Jun 91 by PRZ - - PGP combines the convenience of the Rivest-Shamir-Adleman (RSA) - public key cryptosystem with the speed of fast conventional - cryptographic algorithms, fast message digest algorithms, data - compression, and sophisticated key management. And PGP performs - the RSA functions faster than most other software implementations. - PGP is RSA public key cryptography for the masses. - - Uses RSA Data Security, Inc. MD4 Message Digest Algorithm - for signatures. Uses the LZHUF algorithm for compression. - Uses my own algorithm, BassOmatic, for conventional encryption. - - (c) Copyright 1990 by Philip Zimmermann. All rights reserved. - The author assumes no liability for damages resulting from the use - of this software, even if the damage results from defects in this - software. No warranty is expressed or implied. - - All the source code I wrote for PGP is available for free under - the "Copyleft" General Public License from the Free Software - Foundation. A copy of that license agreement is included in the - source release package of PGP. The source code for the MD4 - functions and the LZHUF functions were separately placed in the - public domain by their respective authors. See the PGP User's - Guide for more complete information about licensing, patent - restrictions on the RSA algorithm, trademarks, copyrights, and - export controls. Technical assistance from me is available for - an hourly fee. - - - PGP generally zeros its used stack and memory areas before exiting. - This avoids leaving sensitive information in RAM where other users - could find it later. The RSA library and keygen routines also - sanitize their own stack areas. This stack sanitizing has not been - checked out under all the error exit conditions, when routines exit - abnormally. Also, we must find a way to clear the C I/O library - file buffers, and the MSDOS disk buffers. - - The code in this source file (pgp.c) was hastily written, and it - shows. It has a lot of redundant code, developed by ad-hoc - "accretion" rather than by well-planned design. It isn't buggy, but - it needs to be reorganized to make it cleaner, clearer, and more - succinct. Maybe someday. Better and more typical examples of my - programming style can be seen in the RSA library code in rsalib.c - and keygen.c, and in the BassOmatic conventional encryption routines - in basslib.c and related files. - - If you modify this code, PLEASE preserve the style of indentation - used for {begin...end} blocks. It drives me bats to have to deal - with more than one style in the same program. - -*/ - - -#include /* for exit(), malloc(), free(), etc. */ -#include /* for printf(), tmpfile(), etc. */ -#include /* for timestamps and performance measurement */ -#include /* for strcat(), etc. */ -#include -#include /* for kbhit() */ - -#include "md4.h" /* for MD4 message digest stuff */ -#include "rsalib.h" -#include "rsaio.h" -#include "keygen.h" -#include "random.h" -#include "basslib.h" -#include "basslib2.h" +/* #define TEMP_VERSION /* if defined, temporary experimental + version of PGP */ +/* pgp.c -- main module for PGP. + PGP: Pretty Good(tm) Privacy - public key cryptography for the masses. + + Synopsis: PGP uses public-key encryption to protect E-mail. + Communicate securely with people you've never met, with no secure + channels needed for prior exchange of keys. PGP is well featured and + fast, with sophisticated key management, digital signatures, data + compression, and good ergonomic design. + + The original PGP version 1.0 was written by Philip Zimmermann, of + Phil's Pretty Good(tm) Software. Many parts of later versions of + PGP were developed by an international collaborative effort, + involving a number of contributors, including major efforts by: + Branko Lankester + Hal Finney <74076.1041@compuserve.com> + Peter Gutmann + Other contributors who ported or translated or otherwise helped include: + Jean-loup Gailly in France + Hugh Kennedy in Germany + Lutz Frank in Germany + Cor Bosman in The Netherlands + Felipe Rodriquez Svensson in The Netherlands + Armando Ramos in Spain + Miguel Angel Gallardo Ortiz in Spain + Harry Bush and Maris Gabalins in Latvia + Zygimantas Cepaitis in Lithuania + Alexander Smishlajev + Peter Suchkow and Andrew Chernov in Russia + David Vincenzetti in Italy + ...and others. + + + (c) Copyright 1990-1996 by Philip Zimmermann. All rights reserved. + The author assumes no liability for damages resulting from the use + of this software, even if the damage results from defects in this + software. No warranty is expressed or implied. + + Note that while most PGP source modules bear Philip Zimmermann's + copyright notice, many of them have been revised or entirely written + by contributors who frequently failed to put their names in their + code. Code that has been incorporated into PGP from other authors + was either originally published in the public domain or is used with + permission from the various authors. + + PGP is available for free to the public under certain restrictions. + See the PGP User's Guide (included in the release package) for + important information about licensing, patent restrictions on + certain algorithms, trademarks, copyrights, and export controls. + + + Philip Zimmermann may be reached at: + Boulder Software Engineering + 3021 Eleventh Street + Boulder, Colorado 80304 USA + (303) 541-0140 (voice or FAX) + email: prz@acm.org + + + PGP will run on MSDOS, Sun Unix, VAX/VMS, Ultrix, Atari ST, + Commodore Amiga, and OS/2. Note: Don't try to do anything with + this source code without looking at the PGP User's Guide. + + PGP combines the convenience of the Rivest-Shamir-Adleman (RSA) + public key cryptosystem with the speed of fast conventional + cryptographic algorithms, fast message digest algorithms, data + compression, and sophisticated key management. And PGP performs + the RSA functions faster than most other software implementations. + PGP is RSA public key cryptography for the masses. + + Uses RSA Data Security, Inc. MD5 Message Digest Algorithm + as a hash for signatures. Uses the ZIP algorithm for compression. + Uses the ETH IPES/IDEA algorithm for conventional encryption. + + PGP generally zeroes its used stack and memory areas before exiting. + This avoids leaving sensitive information in RAM where other users + could find it later. The RSA library and keygen routines also + sanitize their own stack areas. This stack sanitizing has not been + checked out under all the error exit conditions, when routines exit + abnormally. Also, we must find a way to clear the C I/O library + file buffers, the disk buffers, and cache buffers. + + Revisions: + Version 1.0 - 5 Jun 91 + Version 1.4 - 19 Jan 92 + Version 1.5 - 12 Feb 92 + Version 1.6 - 24 Feb 92 + Version 1.7 - 29 Mar 92 + Version 1.8 - 23 May 92 + Version 2.0 - 2 Sep 92 + Version 2.1 - 6 Dec 92 + Version 2.2 - 6 Mar 93 + Version 2.3 - 13 Jun 93 + Version 2.3a- 1 Jul 93 + Version 2.4 - 6 Nov 93 + Version 2.5 - 5 May 94 + Version 2.6 - 22 May 94 + Version 2.6.1 - 29 Aug 94 + Version 2.6.2 - 11 Oct 94 + Version 2.6.2i - 7 May 95 + Version 2.6.3(i) - 18 Jan 96 -#define KEYFRAGSIZE 8 /* # of bytes in key ID modulus fragment */ -#define SIZEOF_TIMESTAMP 4 /* 32-bit timestamp */ + */ -/* This macro is for burning sensitive data (byte arrays only) on stack */ -#define burn(x) fill0(x,sizeof(x)) -/* -********************************************************************** -*/ +#include +#ifndef AMIGA +#include +#endif +#include +#include +#include + +/* Hack to get strncasecmp to work */ +#if defined(WIN32) +#define strncasecmp _strnicmp +#elif defined(AMIGA) || defined(OS2) || defined(__PUREC__) +#define strncasecmp strnicmp +#elif defined(MSDOS) +# if defined(__TURBOC__) +# define strncasecmp strncmpi +# else /* MSC */ +# define strncasecmp strnicmp +# endif +#endif -/* Cipher Type Byte (CTB) definitions follow...*/ -#define CTB_DESIGNATOR 0x80 -#define is_ctb(c) (((c) & CTB_DESIGNATOR)==CTB_DESIGNATOR) -#define CTB_TYPE_MASK 0x7c -#define CTB_LLEN_MASK 0x03 - -/* length of length field of packet, in bytes (1, 2, 4, 8 bytes): */ -#define ctb_llength(ctb) ((int) 1 << (int) ((ctb) & CTB_LLEN_MASK)) - -#define is_ctb_type(ctb,type) (((ctb) & CTB_TYPE_MASK)==(4*type)) -#define CTB_BYTE(type,llen) (CTB_DESIGNATOR + (4*type) + llen) - -#define CTB_PKE_TYPE 1 /* packet encrypted with RSA public key */ -#define CTB_SKE_TYPE 2 /* packet signed with RSA secret key */ -#define CTB_MD_TYPE 3 /* message digest packet */ -#define CTB_CONKEY_TYPE 4 /* conventional key packet */ -#define CTB_CERT_SECKEY_TYPE 5 /* secret key certificate */ -#define CTB_CERT_PUBKEY_TYPE 6 /* public key certificate */ -#define CTB_COMPRESSED_TYPE 8 /* compressed data packet */ -#define CTB_CKE_TYPE 9 /* conventional-key-encrypted data */ -#define CTB_LITERAL_TYPE 12 /* raw data */ - -/* Unimplemented CTB packet types follow... */ -/* #define CTB_RAW1_TYPE 13 /* raw data, with filename, date, crc32 prefix */ -/* #define CTB_PATTERN_TYPE 14 /* unique file prefix autorecognition pattern */ -/* #define CTB_EXTENDED_TYPE 15 /* 2-byte CTB, 256 extra CTB types */ - -#define CTB_PKE CTB_BYTE(CTB_PKE_TYPE,1) - /* CTB_PKE len16 keyID mpi(RSA(CONKEYPKT)) */ - /* 1 2 SIZE countbytes()+2 */ -#define CTB_SKE CTB_BYTE(CTB_SKE_TYPE,1) - /* CTB_SKE len16 keyID mpi(RSA(MDPKT)) */ - /* 1 2 SIZE countbytes()+2 */ -#define CTB_MD CTB_BYTE(CTB_MD_TYPE,0) - /* CTB_MD len8 algorithm MD timestamp */ -#define CTB_CONKEY CTB_BYTE(CTB_CONKEY_TYPE,0) - /* CTB_CONKEY len8 algorithm key */ -#define CTB_CERT_SECKEY CTB_BYTE(CTB_CERT_SECKEY_TYPE,1) - /* CTB_CERT_SECKEY len16 timestamp userID mpi(n) mpi(e) mpi(d) mpi(p) mpi(q) mpi(u) crc16 */ -#define CTB_CERT_PUBKEY CTB_BYTE(CTB_CERT_PUBKEY_TYPE,1) - /* CTB_CERT_PUBKEY len16 timestamp userID mpi(n) mpi(e) crc16 */ - -/* Note that a "secret key compromised" certificate is exactly the same - as a public key certificate, but with mpi(e)==0. */ - -#define CTB_CKE CTB_BYTE(CTB_CKE_TYPE,3) - /* CTB_CKE ciphertext */ - -#define CTB_LITERAL CTB_BYTE(CTB_LITERAL_TYPE,3) - /* CTB_LITERAL data */ - -#define CTB_COMPRESSED CTB_BYTE(CTB_COMPRESSED_TYPE,3) - /* CTB_COMPRESSED compressedtext */ - -#define CTB_PATTERN CTB_BYTE(CTB_PATTERN_TYPE,0) - /* Unique 40-bit auto-recognition prefix pattern: B8 03 'P' 'R' 'Z' */ - -/* Conventional encryption algorithm selector bytes. */ -#define DES_ALGORITHM_BYTE 1 /* use the DES (unimplemented) */ -#define BASS_ALGORITHM_BYTE 2 /* use the BassOmatic */ - -/* Message digest algorithm selector bytes. */ -#define MD4_ALGORITHM_BYTE 1 /* MD4 message digest algorithm */ +#ifdef UNIX +#include +#endif -/* Data compression algorithm selector bytes. */ -#define LZH_ALGORITHM_BYTE 1 /* LZH compression algorithm */ +#include "system.h" +#include "mpilib.h" +#include "random.h" +#include "crypto.h" +#include "fileio.h" +#include "keymgmt.h" +#include "language.h" +#include "pgp.h" +#include "exitpgp.h" +#include "charset.h" +#include "getopt.h" +#include "config.h" +#include "keymaint.h" +#include "keyadd.h" +#include "rsaglue.h" +#include "noise.h" + +#ifdef MACTC5 +#include "Macutil.h" +#include "Macutil2.h" +#include "Macutil3.h" +#include "Macutil4.h" +#include "Macbinary.h" +#include "Binhex.h" +#include "MacPGP.h" +#include "mystr.h" +void AddOutputFiles(char *filepath); +void ReInitKeyMaint(void); +extern char appPathName[]; +void ReInitGlobals(void); +extern int level,method; +extern Boolean explicit_plainfile; +extern long infile_line; +extern int eofonce; +extern boolean savedwashed; +extern char *special; +char *Outputfile = NULL; +void check_expiration_date(void); +#define BEST -1 +#define strncasecmp strncmpi +#define exit Exit +void Exit(int x); +#endif -#define is_secret_key(ctb) is_ctb_type(ctb,CTB_CERT_SECKEY_TYPE) +#ifdef M_XENIX +char *strstr(); +long time(); +#endif -#define MAX_SIGCERT_LENGTH (1+2 + KEYFRAGSIZE + 2+MAX_BYTE_PRECISION) +#ifdef MSDOS +#ifdef __ZTC__ /* Extend stack for Zortech C */ +unsigned _stack_ = 24 * 1024; +#endif +#ifdef __TURBOC__ +unsigned _stklen = 24 * 1024; +#endif +#endif +#define STACK_WIPE 4096 -#define MAX_KEYCERT_LENGTH (1+2+4+256 + 5*(2+MAX_BYTE_PRECISION)) +#ifdef AMIGA +#ifdef __SASC_60 +/* Let the compiler allocate us an appropriate stack. */ +extern long __stack = 32768L; +#endif + /* Add the appropriate AmigaOS version string, depending on the + * compiler flags. + */ +#ifdef USA +static const char __DOSVer[] = "$VER: PGP 2.6.3 (18.01.96)" +# ifdef _M68020 + " Amiga 68020 version by Rob Knop "; +# else + " Amiga 68000 version by Rob Knop "; +# endif +#else +static const char __DOSVer[] = "$VER: PGP 2.6.3i (18.01.96)" +# ifdef _M68020 + " Amiga 68020 version by Peter Simons "; +# else + " Amiga 68000 version by Peter Simons "; +# endif +#endif /* USA */ +#endif /* AMIGA */ /* Global filenames and system-wide file extensions... */ -char CTX_EXTENSION[] = ".ctx"; -char PUB_EXTENSION[] = ".pub"; -char SEC_EXTENSION[] = ".sec"; -char SCRATCH_CTX_FILENAME[] = "_pgptemp.ctx"; -char SCRATCH_PTX_FILENAME[] = "_pgptemp.ptx"; -char SCRATCH_KEYRING_FILENAME[] = "_tmpring.pub"; /* gets modified */ -char PGPPATH[] = "PGPPATH"; /* environmental variable */ +#ifdef USA +char rel_version[] = _LANG("2.6.3"); /* release version */ +#else +char rel_version[] = _LANG("2.6.3i"); /* release version */ +#endif +char rel_date[] = "1996-01-18"; /* release date */ +char PGP_EXTENSION[] = ".pgp"; +char ASC_EXTENSION[] = ".asc"; +char SIG_EXTENSION[] = ".sig"; +char BAK_EXTENSION[] = ".bak"; +static char HLP_EXTENSION[] = ".hlp"; +char CONSOLE_FILENAME[] = "_CONSOLE"; +#ifdef MACTC5 +char HELP_FILENAME[256] = "pgp.hlp"; +#else +static char HELP_FILENAME[] = "pgp.hlp"; +#endif /* These files use the environmental variable PGPPATH as a default path: */ -char PUBLIC_KEYRING_FILENAME[32] = "keyring.pub"; -char SECRET_KEYRING_FILENAME[32] = "keyring.sec"; -char RANDSEED_FILENAME[32] = "randseed.pgp"; - -boolean verbose = FALSE; /* -l option: display maximum information */ - -/* -********************************************************************** -*/ - - - -boolean pkzipSignature( byte *header ) -{ - /* - ** Return TRUE if header begins with the PKzip signature - ** Useful for MSDOS only. - */ - - if ((header[0] == 'P') && (header[1] == 'K') - && (header[2] == '\03') && (header[3] == '\04')) - return(TRUE); - return(FALSE); -} /* pkzipSignature */ - - -/* -** Convert to or from external byte order. -** Note that hilo_swap does nothing if this is a LSB-first CPU. -*/ - -#define convert2(x,lx) hilo_swap( (byteptr)&(x), (lx) ) -#define convert(x) convert2( (x), sizeof(x) ) - -word16 fetch_word16(byte *buf) -/* Fetches a 16-bit word from where byte pointer is pointing. - buf points to external-format byteorder array, assuming LSB-first. -*/ -{ word16 w0,w1; - w0 = *buf++; - w1 = *buf++; - return((w1<<8) + w0); -} /* fetch_word16 */ - - -void get_timestamp(byte *timestamp) -/* Returns timestamp byte array, in internal byteorder */ -{ word32 t; - t = time(0); - timestamp[0] = t; /* fill array in external byte order */ - timestamp[1] = t>>8; - timestamp[2] = t>>16; - timestamp[3] = t>>24; - /* Note that hilo_swap does nothing if this is a LSB-first CPU. */ - hilo_swap(timestamp,4); /* convert to internal byteorder */ -} /* get_timestamp */ - - -void CToPascal(char *s) -{ /* "xyz\0" --> "\3xyz" ... converts C string to Pascal string */ - int i,j; - j = string_length(s); - for (i=j; i!=0; i--) - s[i] = s[i-1]; /* move everything 1 byte to the right */ - s[0] = j; /* Pascal length byte at beginning */ -} /* CToPascal */ - - -void PascalToC( char *s ) -{ /* "\3xyz" --> "xyz\0" ... converts Pascal string to C string */ - int i,j; - for (i=0,j=s[0]; i 0L) - { /* write zeros to the whole file... */ - if (flength < (word32) diskbufsize) - count = flength; - else - count = diskbufsize; - fwrite(textbuf,1,count,f); - flength -= count; - } - rewind(f); /* maybe this isn't necessary */ - return(0); /* normal return */ -} /* wipeout */ - - -int wipefile(char *filename) -{ /* Completely overwrite and erase file, so that no sensitive - information is left on the disk. - */ - FILE *f; - /* open file f for read/write, in binary (not text) mode...*/ - if ((f = fopen(filename,"rb+")) == NULL) - return(-1); /* error - file can't be opened */ - wipeout(f); - fclose(f); - return(0); /* normal return */ -} /* wipefile */ - - - -#define strhas(s,c) (strchr((s),(c)) != NULL) - -boolean strhasany( char *s1, char *s2 ) -{ /* Searches s1 for any of the characters in s2. - Returns TRUE if found. - */ - while (*s2) - { if (strhas(s1,*s2)) - return(TRUE); - s2++; - } - return(FALSE); -} /* strhasany */ - - -boolean strcontains( char *s1, char *s2 ) -{ /* - ** Searches s1 for s2, without case sensitivity. - ** Return TRUE if found. - ** - ** If s2 is an empty string then return TRUE. This is because, - ** at least in the world of mathematics, the empty set is contained - ** in all other sets. The Microsoft C version 6.0 strstr function - ** behaves this way but version 5.1 does not, so we need to - ** explicitly test for the situation. -- ALH 91/2/17 - */ - - if (s2[0] != '\0') - { - char buf1[256], buf2[256]; /* scratch buffers */ - - strncpy( buf1, s1, 256 ); strlwr( buf1 ); /* converts to lower case */ - strncpy( buf2, s2, 256 ); strlwr( buf2 ); /* converts to lower case */ - - if (strstr( buf1, buf2 ) == NULL) - return( FALSE ); /* string not found */ - } - return(TRUE); -} /* strcontains */ - - -void translate_spaces(char *s) -/* Changes all the underlines to spaces in a string. */ -{ while (strchr(s,'_') != NULL) - *strchr(s,'_') = ' '; -} - - -boolean no_extension(char *filename) -/* Returns TRUE if user left off file extension, allowing default. */ -{ if (strrchr(filename,'.')==NULL) - return(TRUE); - /* see if the last '.' is followed by a backslash...*/ - if (*(strrchr(filename,'.')+1) == '\\') - return(TRUE); /* just a "..\filename" construct */ - return(FALSE); /* user specified extension, even if a blank one */ -} /* no_extension */ - - -void drop_extension(char *filename) -{ /* deletes trailing ".xxx" file extension after the period. */ - if (!no_extension(filename)) - *strrchr(filename,'.') = '\0'; -} /* drop_extension */ - - -void default_extension(char *filename, char *extension) -{ /* append filename extension if there isn't one already. */ - if (no_extension(filename)) - strcat(filename,extension); -} /* default_extension */ - - -void force_extension(char *filename, char *extension) -{ /* change the filename extension. */ - drop_extension(filename); /* out with the old */ - strcat(filename,extension); /* in with the new */ -} /* force_extension */ - - -boolean getyesno(char default_answer) -{ /* Get yes/no answer from user, returns TRUE for yes, FALSE for no. */ - char buf[8]; - while (keypress()) /* flush typahead buffer */ - getkey(); - getstring(buf,6,TRUE); /* echo keyboard input */ - if (strlen(buf)==0) /* if user didn't give an answer... */ - buf[0] = default_answer; /* assume default answer */ - buf[0] = tolower(buf[0]); - return(buf[0]=='y'); -} /* getyesno */ - - -void maybe_force_extension(char *filename, char *extension) -{ /* if user consents to it, change the filename extension. */ - char newname[64]; - if (!strcontains(filename,extension)) - { strcpy(newname,filename); - force_extension(newname,extension); - if (!file_exists(newname)) - { fprintf(stderr,"\nShould '%s' be renamed to '%s' [Y/n]? ", - filename,newname); - if (getyesno('y')) - rename(filename,newname); - } - } -} /* maybe_force_extension */ - - -/*---------------------------------------------------------------------*/ -/* Begin uuencode routines. - This converts a binary file into printable ASCII characters, in a - form compatible with the Unix uuencode utility. - This makes it easier to send encrypted files over a 7-bit channel. -*/ - -/* ENC is the basic 1 character encoding function to make a char printing */ -#define ENC(c) (((c) & 077) + ' ') - -/* - * output one group of 3 bytes, pointed at by p, on file f. +char globalPubringName[MAX_PATH]; +char globalSecringName[MAX_PATH]; +char globalRandseedName[MAX_PATH]; +char globalCommentString[128]; + +/* Flags which are global across the driver code files */ +boolean verbose = FALSE; /* -l option: display maximum information */ +FILE *pgpout; /* Place for routine messages */ + +static void usage(void); +static void key_usage(void); +static void arg_error(void); +static void initsigs(void); +static int do_keyopt(char); +static int do_decrypt(char *); +static void do_armorfile(char *); +char ** ParseRecipients(char **); +void hashpass (char *keystring, int keylen, byte *hash); + +/* Various compression signatures: PKZIP, Zoo, GIF, Arj, and HPACK. + Lha(rc) is handled specially in the code; it is missing from the + compressSig structure intentionally. If more formats are added, + put them before lharc to keep the code consistent. + + 27-Jun-95 simons@peti.rhein.de (Peter Simons) + Added support for lh5 archive as generated by Lha. Unfortunately, + Lha requires special treatment also. I inserted the check right + _before_ lharc, because lh5/lha is a special type of an lharc + archive. */ -void outdec(char *p, FILE *f) +static char *compressSig[] = +{"PK\03\04", "ZOO ", "GIF8", "\352\140", + "HPAK", "\037\213", "\037\235", "\032\013", "\032HP%" + /* lharc is special, must be last */ }; +static char *compressName[] = +{"PKZIP", "Zoo", "GIF", "Arj", + "Hpack", "gzip", "compressed", "PAK", "Hyper", + "Lha", "Lharc"}; +static char *compressExt[] = +{".zip", ".zoo", ".gif", ".arj", + ".hpk", ".gz", ".Z", ".pak", ".hyp", + ".lha", ".lzh"}; + +/* "\032\0??", "ARC", ".arc" */ + +/* Returns file signature type from a number of popular compression formats + or -1 if no match */ +int compressSignature(byte * header) { - int c1, c2, c3, c4; - - c1 = *p >> 2; - c2 = (*p << 4) & 060 | (p[1] >> 4) & 017; - c3 = (p[1] << 2) & 074 | (p[2] >> 6) & 03; - c4 = p[2] & 077; - putc(ENC(c1), f); - putc(ENC(c2), f); - putc(ENC(c3), f); - putc(ENC(c4), f); -} /* outdec */ - - -/* fr: like read but stdio */ -int fr(FILE *fd, char *buf, int cnt) -{ - int c, i; - - for (i=0; i= 0) + return FALSE; /* probably not compressible */ + return TRUE; /* possibly compressible */ +} /* compressible */ + + +/* Possible error exit codes - not all of these are used. Note that we + don't use the ANSI EXIT_SUCCESS and EXIT_FAILURE. To make things + easier for compilers which don't support enum we use #defines */ + +#define EXIT_OK 0 +#define INVALID_FILE_ERROR 1 +#define FILE_NOT_FOUND_ERROR 2 +#define UNKNOWN_FILE_ERROR 3 +#define NO_BATCH 4 +#define BAD_ARG_ERROR 5 +#define INTERRUPT 6 +#define OUT_OF_MEM 7 + +/* Keyring errors: Base value = 10 */ +#define KEYGEN_ERROR 10 +#define NONEXIST_KEY_ERROR 11 +#define KEYRING_ADD_ERROR 12 +#define KEYRING_EXTRACT_ERROR 13 +#define KEYRING_EDIT_ERROR 14 +#define KEYRING_VIEW_ERROR 15 +#define KEYRING_REMOVE_ERROR 16 +#define KEYRING_CHECK_ERROR 17 +#define KEY_SIGNATURE_ERROR 18 +#define KEYSIG_REMOVE_ERROR 19 + +/* Encode errors: Base value = 20 */ +#define SIGNATURE_ERROR 20 +#define RSA_ENCR_ERROR 21 +#define ENCR_ERROR 22 +#define COMPRESS_ERROR 23 + +/* Decode errors: Base value = 30 */ +#define SIGNATURE_CHECK_ERROR 30 +#define RSA_DECR_ERROR 31 +#define DECR_ERROR 32 +#define DECOMPRESS_ERROR 33 -#define SUMSIZE 64 -#define DEC(c) (((c) - ' ') & 077) /* single character decode */ +#ifdef SIGINT -int uud_buffer(char *inbuf, char *outbuf, int *outlength) -{ - char *bp; - boolean has_checksum=FALSE; - - register int j; - register int n; - int checksum; - int status; - - - status = 0; - *outlength = 0; - - /* Pad end of lines in case some editor truncated trailing - spaces */ - - for (n=0; n<79; n++) /* search for first \r, \n or \000 */ - { - if (inbuf[n]=='\176') /* If BITNET made a twiddle, */ - inbuf[n]='\136'; /* we make a caret */ - if (inbuf[n]=='\r' || inbuf[n]=='\n' || inbuf[n]=='\000') - break; - if ((inbuf[n] < '\040') || (inbuf[n] > '\137')) - status = -1; /* illegal uudecode character */ - } - for (; n<79; n++) /* when found, fill rest of line with space */ - inbuf[n]=' '; - - inbuf[79]=0; /* terminate new string */ - - checksum = 0; - n = DEC(inbuf[0]); - if (n == 0) - return(0); /* 0 bytes on a line?? Must be the last line */ - - if (status) - return(status); /* bad character, out of range */ - - bp = &inbuf[1]; - - /* FOUR input characters go into each THREE output charcters */ - - while (n >= 4) - { - j = DEC(bp[0]) << 2 | DEC(bp[1]) >> 4; - checksum += j; - outbuf[(*outlength)++]=j; - j = DEC(bp[1]) << 4 | DEC(bp[2]) >> 2; - checksum += j; - outbuf[(*outlength)++]=j; - j = DEC(bp[2]) << 6 | DEC(bp[3]); - checksum += j; - outbuf[(*outlength)++]=j; - checksum = checksum % SUMSIZE; - bp += 4; - n -= 3; - } - - j = DEC(bp[0]) << 2 | DEC(bp[1]) >> 4; - checksum += j; - if (n >= 1) - outbuf[(*outlength)++]=j; - j = DEC(bp[1]) << 4 | DEC(bp[2]) >> 2; - checksum += j; - if (n >= 2) - outbuf[(*outlength)++]=j; - j = DEC(bp[2]) << 6 | DEC(bp[3]); - checksum += j; - if (n >= 3) - outbuf[(*outlength)++]=j; - checksum = checksum % SUMSIZE; - bp += 4; - n -= 3; - - /* The line has been decoded; now check that sum */ - - has_checksum |= !isspace(*bp); - if (has_checksum) /* Is there a checksum at all?? */ - if (checksum != DEC(*bp)) /* Does that checksum match? */ - return(-2); /* checksum error */ - - return(status); /* normal return */ - -} /* uud_buffer */ - -/* - * Copy from in to out, decoding as you go. - * If a return or newline is encountered too early in a line, it is - * assumed that means that some editor has truncated trailing spaces. +/* This function is called if a BREAK signal is sent to the program. In this + case we zap the temporary files. */ -int uudecode(FILE *in, FILE *out) +void breakHandler(int sig) { -char inbuf[81]; -char outbuf[81]; -char *bp; -boolean has_checksum=FALSE; - -register int j; -int n, status; -int checksum, line; - - for (line = 1; ; line++) /* for each input line */ - { - if (fgets(inbuf, sizeof inbuf, in) == NULL) - { - fprintf(stderr,"ERROR: uudecode input ended unexpectedly!\n"); - return(18); - } - - status = uud_buffer(inbuf,outbuf,&n); - - if (status == -1) - fprintf(stderr,"ERROR: bad uudecode character decoding line %d.\n", line); - if (status == -2) - fprintf(stderr,"ERROR: checksum mismatch decoding line %d.\n", line); - if (n==0) /* zero-length line is the end. */ - break; - - fwrite(outbuf,1,n,out); - - } /* line */ - - return(0); /* normal return */ -} /* uudecode */ - +#ifdef UNIX + if (sig == SIGPIPE) { + signal(SIGPIPE, SIG_IGN); + exitPGP(INTERRUPT); + } + if (sig != SIGINT) + fprintf(stderr, "\nreceived signal %d\n", sig); + else +#endif + fprintf(pgpout, LANG("\nStopped at user request\n")); + exitPGP(INTERRUPT); +} +#endif -boolean is_uufile(char *infile) +/* Clears screen and homes the cursor. */ +static void clearscreen(void) { - FILE *in; - char inbuf[80]; - char outbuf[80]; - int i, n, status; - - if ((in = fopen(infile, "r")) == NULL) - { /* can't open file */ - return(FALSE); - } + fprintf(pgpout, "\n\033[0;0H\033[J\r \r"); /* ANSI sequence. */ + fflush(pgpout); +} - /* search file for header line */ - for (i=0; i<50; i++) /* give up after 50 lines of garbage */ - { - if (fgets(inbuf, sizeof inbuf, in) == NULL) - break; - else - { - if (strncmp(inbuf, "begin ", 6) == 0) - { - if (fgets(inbuf, sizeof inbuf, in) == NULL) - break; - status = uud_buffer(inbuf,outbuf,&n); - if (status < 0) - break; - fclose(in); - return(TRUE); - } - } - } +/* We had to process the config file first to possibly select the + foreign language to translate the sign-on line that follows... */ +static void signon_msg(void) +{ + word32 tstamp; + /* display message only once to allow calling multiple times */ + static boolean printed = FALSE; + + if (quietmode || printed) + return; + printed = TRUE; + fprintf(stderr, +LANG("Pretty Good Privacy(tm) %s - Public-key encryption for the masses.\n"), + rel_version); +#ifdef TEMP_VERSION + fputs( +"Internal development version only - not for general release.\n", stderr); +#endif + fputs(LANG("(c) 1990-96 Philip Zimmermann, Phil's Pretty Good Software."), + stderr); + fprintf(stderr, " %s\n",LANG(rel_date)); +#ifdef USA + fputs(LANG(signon_legalese), stderr); +#endif + fputs( +#ifdef USA +LANG("Export of this software may be restricted by the U.S. government.\n"), +#else +LANG("International version - not for use in the USA. Does not use RSAREF.\n"), +#endif + stderr); - fclose(in); - return(FALSE); + get_timestamp((byte *) & tstamp); /* timestamp points to tstamp */ + fprintf(pgpout, LANG("Current time: %s\n"), ctdate(&tstamp)); +} -} /* is_uufile */ +#ifdef TEMP_VERSION /* temporary experimental version of PGP */ +#include +#define CREATION_DATE 0x30FE3640ul + /* CREATION_DATE is + Thu Jan 18, 1996 1200 hours UTC */ +#define LIFESPAN ((unsigned long) 60L * (unsigned long) 86400L) + /* LIFESPAN is 60 days */ -int uud_file(char *infile, char *outfile) +/* If this is an experimental version of PGP, cut its life short */ +void check_expiration_date(void) { -FILE *in, *out; -int mode; /* file's mode (from header) */ -long filesize; /* theoretical file size (from header) */ -char buf[80]; -int status; - - if ((in = fopen(infile, "r")) == NULL) - { - fprintf(stderr,"ERROR: can't find %s\n", infile); - return(10); + if (get_timestamp(NULL) > (CREATION_DATE + LIFESPAN)) { + fprintf(stderr, + "\n\007This experimental version of PGP has expired.\n"); + exit(-1); /* error exit */ } +} /* check_expiration_date */ +#else /* no expiration date */ +#define check_expiration_date() /* null statement */ +#endif /* TEMP_VERSION */ + +/* -f means act as a unix-style filter */ +/* -i means internalize extended file attribute information, only supported + * between like (or very compatible) operating systems. */ +/* -l means show longer more descriptive diagnostic messages */ +/* -m means display plaintext output on screen, like unix "more" */ +/* -d means decrypt only, leaving inner signature wrapping intact */ +/* -t means treat as pure text and convert to canonical text format */ + +/* Used by getopt function... */ +#define OPTIONS "abcdefghiklmo:prstu:vwxz:ABCDEFGHIKLMO:PRSTU:VWX?" +extern int optind; +extern char *optarg; + +#define INCLUDE_MARK "-@" +#define INCLUDE_MARK_LEN sizeof(INCLUDE_MARK)-1 /* skip the \0 */ + +boolean emit_radix_64 = FALSE; /* set by config file */ +static boolean sign_flag = FALSE; +boolean moreflag = FALSE; +boolean filter_mode = FALSE; +static boolean preserve_filename = FALSE; +static boolean decrypt_only_flag = FALSE; +static boolean de_armor_only = FALSE; +static boolean strip_sig_flag = FALSE; +boolean clear_signatures = TRUE; +boolean strip_spaces; +static boolean c_flag = FALSE; +static boolean u_flag = FALSE; /* Did I get my_name from -u? */ +boolean encrypt_to_self = FALSE; /* should I encrypt messages to myself? */ +boolean sign_new_userids = TRUE; +boolean batchmode = FALSE; /* if TRUE: don't ask questions */ +boolean quietmode = FALSE; +boolean force_flag = FALSE; /* overwrite existing file without asking */ +#ifdef VMS /* kludge for those stupid VMS variable-length + text records */ +char literal_mode = MODE_TEXT; /* MODE_TEXT or MODE_BINARY for literal + packet */ +#else /* not VMS */ +char literal_mode = MODE_BINARY; /* MODE_TEXT or MODE_BINARY for literal + packet */ +#endif /* not VMS */ +/* my_name is substring of default userid for secret key to make signatures */ +char my_name[256] = "\0"; /* null my_name means take first userid + in ring */ +boolean keepctx = FALSE; /* TRUE means keep .ctx file on decrypt */ +/* Ask for each key separately if it should be added to the keyring */ +boolean interactive_add = FALSE; +boolean compress_enabled = TRUE; /* attempt compression before encryption */ +long timeshift = 0L; /* seconds from GMT timezone */ +int version_byte = VERSION_BYTE_NEW; +boolean nomanual = 0; +/* If non-zero, initialize file to this many random bytes */ +int makerandom = 0; + + +static char *outputfile = NULL; +#ifndef MACTC5 +static int errorLvl = EXIT_OK; +#else +int errorLvl = EXIT_OK; +#endif +static char mcguffin[256]; /* userid search tag */ +boolean signature_checked = FALSE; +int checksig_pass = 0; +boolean use_charset_header; +char charset_header[16] = ""; +char plainfile[MAX_PATH]; +int myArgc = 2; +char **myArgv; +struct hashedpw *passwds = 0, *keypasswds = 0; +static struct hashedpw **passwdstail = &passwds; - /* Loop through file, searching for header. Decode anything with a - header, complain if there where no headers. */ +#ifdef MACTC5 +extern unsigned long PGPStart, WNECalls; - /* search file for header line */ - for (;;) - { - if (fgets(buf, sizeof buf, in) == NULL) - { - fprintf(stderr,"ERROR: no `begin' line!\n"); - fclose(in); - return(12); - } - if (strncmp(buf, "begin ", 6) == 0) - break; - } - - /* Ignore filename and mode. Use outfile instead of dest. */ - /* sscanf(buf, "begin %o %s", &mode, dest); */ - - /* create output file */ - if ((out = fopen(outfile, "wb")) == NULL) - { - fprintf(stderr,"ERROR: can't open output file %s\n", outfile); - fclose(in); - return(15); - } - - status = uudecode(in, out); - if (status != 0) - { fclose(in); - fclose(out); - return(status); - } - - if (fgets(buf, sizeof buf, in) == NULL || strncmp(buf,"end",3)) - { /* don't be overly picky about newline ^ */ - fprintf(stderr,"ERROR: no `end' line\n"); - fclose(in); - fclose(out); - return(16); - } - - if (!(fgets(buf,sizeof buf,in) == NULL || strncmp(buf,"size ",3))) - { - sscanf(buf, "size %ld", &filesize); - if (ftell(out) != filesize) - { - fprintf(stderr,"ERROR: file should have been %ld bytes long but was %ld.\n", filesize, ftell(out)); - return(17); - } - } - fclose(out); - fclose(in); - return(0); /* normal return */ -} /* uud_file */ - - -/* End uudecode routines. */ -/*---------------------------------------------------------------------*/ - - - -boolean legal_ctb(byte ctb) -{ /* Used to determine if nesting should be allowed. */ - boolean legal; - byte ctbtype; - if (!is_ctb(ctb)) /* not even a bonafide CTB */ - return(FALSE); - /* Sure hope CTB internal bit definitions don't change... */ - ctbtype = (ctb & CTB_TYPE_MASK) >> 2; - /* Only allow these CTB types to be nested... */ - legal = ( - (ctbtype==CTB_PKE_TYPE) - || (ctbtype==CTB_SKE_TYPE) - || (ctbtype==CTB_CERT_SECKEY_TYPE) - || (ctbtype==CTB_CERT_PUBKEY_TYPE) - || (ctbtype==CTB_LITERAL_TYPE) - || (ctbtype==CTB_COMPRESSED_TYPE) - || (ctbtype==CTB_CKE_TYPE) - /* || (ctbtype==CTB_CONKEY_TYPE) */ - /* || (ctbtype==CTB_MD_TYPE) */ - ); - return(legal); -} /* legal_ctb */ - - -/*======================================================================*/ - -/* MDfile0(MD, f) -** Computes and returns the message digest from a file position to eof. -** Uses RSA Data Security, Inc. MD4 Message Digest Algorithm. -*/ -int MDfile0(MDstruct *MD, FILE *f) -{ byte X[64]; - int bytecount; - - MDbegin(MD); - /* Process 512 bits, or 64 bytes, at a time... */ - while ((bytecount = fread(X, 1, 64, f)) != 0) - MDupdate(MD, X, bytecount<<3); /* pass bitcount */ - MDupdate(MD, X, 0); /* finish with a bitcount of 0 */ - /* MDprint(MD); */ - - return(0); /* normal return */ -} /* MDfile0 */ - - -/* MDfile(MD, filename) -** Computes and returns the message digest for a specified file. -*/ -int MDfile(MDstruct *MD, char *filename) -{ FILE *f; - f = fopen(filename,"rb"); - if (f == NULL) - { fprintf(stderr,"Can't open file '%s'\n",filename); - return(-1); /* error return */ - } - MDfile0(MD, f); - fclose(f); - return(0); /* normal return */ -} /* MDfile */ - - -/* MD_of_buffer(MD, s, len) -** Computes and returns the message digest for buffer s. -** len is the length in bytes of buffer s. -** Uses RSA Data Security, Inc. MD4 Message Digest Algorithm. -*/ -void MD_of_buffer(MDstruct *MD, byte *s, int len) -{ int i; - - MDbegin(MD); - /* Process 512 bits, or 64 bytes, at a time... */ - for (i=0; i+64<=len; i+=64) - MDupdate(MD, s+i, 512); - MDupdate(MD, s+i, (len-i)<<3); /* finish with short block */ - /* MDprint(MD); */ - -} /* MD_of_buffer */ - - -boolean equal_buffers(byte *buf1, byte *buf2, word16 count) -/* Compares two byte buffers. */ -{ while (count--) - if (*buf1++ != *buf2++) - return(FALSE); /* mismatch. */ - return(TRUE); /* compares OK */ -} /* equal_buffers */ - - -char *buildfilename(char *result, char *fname) -/* Builds a filename with a complete path specifier from the environmental - variable PGPPATH. Assumes MSDOS pathname conventions. -*/ -{ char *s = getenv(PGPPATH); - if (strlen(s) > 50) /* too long to use */ - s = ""; - strcpy(result,s); - if (strlen(result) != 0) - if (result[strlen(result)-1] != '\\') - strcat(result,"\\"); - strcat(result,fname); - return(result); -} /* buildfilename */ - - -int strong_pseudorandom(byte *buf, int bufsize) -/* Reads BassOmatic random key and random number seed from file, - cranks the the seed through the bassrand strong pseudorandom - number generator, and writes them back out. This is used for - generation of cryptographically strong pseudorandom numbers. - This is mainly to save the user the trouble of having to - type in lengthy keyboard sequences for generation of truly - random numbers every time we want to make a random BassOmatic - session key. This pseudorandom generator will only work if - the file containing the random seed exists and is not empty. - If it doesn't exist, it will be automatically created. - If it exists and is empty or nearly empty, it will not be used. -*/ -{ char seedfile[64]; /* Random seed filename */ - FILE *f; - byte key[64]; /* not to exceed 256 byes in length */ - byte seed[256]; /* not to exceed 256 byes in length */ +void ReInitGlobals() +{ int i; - word32 tstamp; byte *timestamp = (byte *) &tstamp; - - buildfilename(seedfile,RANDSEED_FILENAME); - - if (!file_exists(seedfile)) /* No seed file. Start one... */ - { f = fopen(seedfile,"wb"); /* open for writing binary */ - if (f==NULL) /* failed to create seedfile */ - return(-1); /* error: no random number seed file available */ - fclose(f); /* close new empty seed file */ - /* kickstart the generator with true random numbers */ - fprintf(stderr,"Initializing random seed file..."); - randaccum(8*(sizeof(key)+32)); - for (i=1; i0) - { fwrite(textbuf,1,count,g); - longcount -= count; - } - /* if text block was short, exit loop */ - } while (count==diskbufsize); - burn(textbuf); /* burn sensitive data on stack */ -} /* copyfile */ - - -word32 getpastlength(byte ctb, FILE *f) -/* Returns the length of a packet according to the CTB and - the length field. */ -{ word32 length; - int llength; /* length of length */ - byte buf[8]; - - fill0(buf,sizeof(buf)); - length = 0L; - /* Use ctb length-of-length field... */ - llength = ctb_llength(ctb); /* either 1, 2, 4, or 8 */ - if (llength==8) /* 8 means no length field, assume huge length */ - return(-1L); /* return huge length */ - - /* now read in the actual length field... */ - if (fread((byteptr) buf,1,llength,f) < llength) - return (-2L); /* error -- read failure or premature eof */ - /* convert length from external LSB-first format... */ - while (llength--) - { length <<= 8; - length += buf[llength]; - } - return(length); -} /* getpastlength */ - - - -int bass_file(byte *basskey, int lenbasskey, boolean decryp, - FILE *f, FILE *g) -/* Use BassOmatic in cipher feedback (CFB) mode to encrypt - or decrypt a file. Encrypted key check bytes determine - if correct BassOmatic key was used to decrypt ciphertext. -*/ -{ int count; - byte textbuf[diskbufsize], iv[256]; -#define KEYCHECKLENGTH 4 - - /* init CFB BassOmatic key */ - fill0(iv,256); /* define initialization vector IV as 0 */ - if ( initcfb(iv,basskey,lenbasskey,decryp) < 0 ) - return(-1); /* Error return should be impossible. */ - - if (!decryp) /* encrypt-- insert key check bytes */ - { /* key check bytes are 2 copies of 16 random bits */ - textbuf[0] = randombyte(); - textbuf[1] = randombyte(); - textbuf[2] = textbuf[0]; - textbuf[3] = textbuf[1]; - basscfb(textbuf,KEYCHECKLENGTH); - fwrite(textbuf,1,KEYCHECKLENGTH,g); - } - else /* decrypt-- check for key check bytes */ - { /* See if there are 2 copies of 16 random bits */ - count = fread(textbuf,1,KEYCHECKLENGTH,f); - if (count==KEYCHECKLENGTH) - { basscfb(textbuf,KEYCHECKLENGTH); - if ((textbuf[0] != textbuf[2]) - || (textbuf[1] != textbuf[3])) - { return(-2); /* bad key error */ - } - } - else /* file too short for key check bytes */ - return(-3); /* error of the weird kind */ +#ifdef MACTC5 + ReInitGlobals(); +#endif +#ifdef DEBUG1 + verbose = TRUE; +#endif + /* The various places one can get passwords from. + * We accumulate them all into two lists. One is + * to try on keys only, and is stored in no particular + * order, while the other is of unknown purpose so + * far (they may be used for conventional encryption + * or decryption as well), and are kept in a specific + * order. If any password in the general list is found + * to decode a key, it is moved to the key list. + * The general list is not grown after initialization, + * so the tail pointer is not used after this. + */ + +#ifndef MACTC5 + if ((p = getenv("PGPPASS")) != NULL) { + hpw = xmalloc(sizeof(struct hashedpw)); + hashpass(p, strlen(p), hpw->hash); + /* Add to linked list of key passwords */ + hpw->next = keypasswds; + keypasswds = hpw; + } + /* The -z "password" option should be used instead of PGPPASS if + * the environment can be displayed with the ps command (eg. BSD). + * If the system does not allow overwriting of the command line + * argument list but if it has a "hidden" environment, PGPPASS + * should be used. + */ + for (opt = 1; opt < argc; ++opt) { + p = argv[opt]; + if (p[0] != '-' || p[1] != 'z') + continue; + /* Accept either "-zpassword" or "-z password" */ + p += 2; + if (!*p) + p = argv[++opt]; + /* p now points to password */ + if (!p) + break; /* End of arg list - ignore */ + hpw = xmalloc(sizeof(struct hashedpw)); + hashpass(p, strlen(p), hpw->hash); + /* Wipe password */ + while (*p) + *p++ = ' '; + /* Add to tail of linked list of passwords */ + hpw->next = 0; + *passwdstail = hpw; + passwdstail = &hpw->next; + } + /* + * If PGPPASSFD is set in the environment try to read the password + * from this file descriptor. If you set PGPPASSFD to 0 pgp will + * use the first line read from stdin as password. + */ + if ((p = getenv("PGPPASSFD")) != NULL) { + int passfd; + if (*p && (passfd = atoi(p)) >= 0) { + char pwbuf[256]; + p = pwbuf; + while (read(passfd, p, 1) == 1 && *p != '\n') + ++p; + hpw = xmalloc(sizeof(struct hashedpw)); + hashpass(pwbuf, p - pwbuf, hpw->hash); + memset(pwbuf, 0, p - pwbuf); + /* Add to tail of linked list of passwords */ + hpw->next = 0; + *passwdstail = hpw; + passwdstail = &hpw->next; } + } + /* Process the config file. The following override each other: + - Hard-coded defaults + - The system config file + - Hard-coded defaults for security-critical things + - The user's config file + - Environment variables + - Command-line options. + */ + opt = 0; /* Number of config files read */ +#ifdef PGP_SYSTEM_DIR + strcpy(mcguffin, PGP_SYSTEM_DIR); + strcat(mcguffin, "config.txt"); + if (access(mcguffin, 0) == 0) { + opt++; + /* + * Note: errors here are NOT fatal, so that people + * can use PGP with a corrputed system file. + */ + processConfigFile(mcguffin); + } +#endif + /* + * These must be personal; the system config file may not + * influence them. + */ + buildfilename(globalPubringName, "pubring.pgp"); + buildfilename(globalSecringName, "secring.pgp"); + buildfilename(globalRandseedName, "randseed.bin"); + my_name[0] = '\0'; + + /* Process the config file first. Any command-line arguments will + override the config file settings */ +#if defined(UNIX) || defined(MSDOS) || defined(OS2) || defined (WIN32) + /* Try "pgp.ini" on MS-DOS or ".pgprc" on Unix */ +#ifdef UNIX + buildfilename(mcguffin, ".pgprc"); +#else + buildfilename(mcguffin, "pgp.ini"); +#endif + if (access(mcguffin, 0) != 0) + buildfilename(mcguffin, "config.txt"); +#else + buildfilename(mcguffin, "config.txt"); +#endif + if (access(mcguffin, 0) == 0) { + opt++; + if (processConfigFile(mcguffin) < 0) + exit(BAD_ARG_ERROR); + } + if (!opt) + fprintf(pgpout, LANG("\007No configuration file found.\n")); - do /* read and write the whole file in CFB mode... */ - { count = fread(textbuf,1,diskbufsize,f); - if (count>0) - { basscfb(textbuf,count); - fwrite(textbuf,1,count,g); - } - /* if text block was short, exit loop */ - } while (count==diskbufsize); - - closebass(); /* release BassOmatic resources */ - burn(textbuf); /* burn sensitive data on stack */ - return(0); /* should always take normal return */ -} /* bass_file */ - - - -int read_mpi(unitptr r, FILE *f, boolean adjust_precision, boolean scrambled) -/* Read a mutiprecision integer from a file. - adjust_precision is TRUE iff we should call set_precision to the - size of the number read in. - scrambled is TRUE iff field is encrypted (protects secret key fields). - Returns the bitcount of the number read in, or returns a negative - number if an error is detected. -*/ -{ byte buf[MAX_BYTE_PRECISION+2]; - int count; - word16 bytecount,bitcount,lowcount,highcount; - - mp_init(r,0); - - if ((count = fread(buf,1,2,f)) < 2) - return (-1); /* error -- read failure or premature eof */ - - /* Assumes external format is LSB-first */ - bitcount = (((word16) buf[1]) << 8) + (word16) buf[0]; - if (bits2units(bitcount) > global_precision) - return(-1); /* error -- possible corrupted bitcount */ - - bytecount = bits2bytes(bitcount); - - count = fread(buf+2,1,bytecount,f); - if (count < bytecount) - return(-1); /* error -- premature eof */ - - if (scrambled) /* decrypt the field */ - basscfb(buf+2,bytecount); - - /* We assume that the bitcount prefix we read is an exact - bitcount, not rounded up to the next byte boundary. - Otherwise we would have to call mpi2reg, then call - countbits, then call set_precision, then recall mpi2reg - again. - */ - if (adjust_precision && bytecount) - { /* set the precision to that specified by the number read. */ - set_precision(bits2units(bitcount+SLOP_BITS)); - /* Now that precision is optimally set, call mpi2reg */ - } - - mpi2reg(r,buf); /* convert to internal format */ - burn(buf); /* burn sensitive data on stack */ - return (bitcount); -} /* read_mpi */ - - - -void write_mpi(unitptr n, FILE *f, boolean scrambled) -/* Write a multiprecision integer to a file. - scrambled is TRUE iff we should scramble field on the way out, - which is used to protect secret key fields. -*/ -{ byte buf[MAX_BYTE_PRECISION+2]; - short bytecount; - bytecount = reg2mpi(buf,n); - if (scrambled) /* encrypt the field, skipping over the bitcount */ - basscfb(buf+2,bytecount); - fwrite(buf,1,bytecount+2,f); - burn(buf); /* burn sensitive data on stack */ -} /* write_mpi */ - - - -void showkeyID(byte *buf) -/* Print key fragment, which is an abbreviation or "fingerprint" - of the key. - Show LEAST significant 64 bits (KEYFRAGSIZE bytes) of modulus, - LSB last. Yes, that's LSB LAST. -*/ -{ short i,j; - /* fputc('[',stderr); */ - j = KEYFRAGSIZE; - for (i=KEYFRAGSIZE-1; i>=0; i--) /* print LSB last */ - { if (--j < 3) /* only show bottom 3 bytes of keyID */ - fprintf(stderr,"%02X",buf[i]); - } - /* fputc(']',stderr); */ -} /* showkeyID */ - - - -void extract_keyID(byteptr keyID, unitptr n) -/* Extract key fragment from modulus n. keyID byte array must be - at least KEYFRAGSIZE bytes long. -*/ -{ byte buf[MAX_BYTE_PRECISION+2]; - short i, j; - - fill0(buf,KEYFRAGSIZE+2); /* in case n is too short */ - reg2mpi(buf,n); /* MUST be at least KEYFRAGSIZE long */ - /* For low-byte-first keyID format, start of keyID is: */ - i = 2; /* skip over the 2 bytes of bitcount */ - for (j=0; j MAX_KEYCERT_LENGTH-3) - return(-3); /* bad length */ - - fread(timestamp,1,4,f); /* read certificate timestamp */ - /* note that hilo_swap does nothing if this is a LSB-first CPU */ - hilo_swap(timestamp,4); /* convert from external LSB-first form */ - count = fread(userid,1,1,f); /* read user ID length byte */ - if (count==0) return(-1); /* premature eof */ - fread(userid+1,1,userid[0],f); /* read rest of user ID */ - /*** End certificate header fields ***/ - - /* We're past certificate headers, now look at some key material...*/ - - if (read_mpi(n,f,TRUE,FALSE) < 0) - return(-4); /* data corrupted, return error */ - - /* Note that precision was adjusted for n */ - - if (read_mpi(e,f,FALSE,FALSE) < 0) - return(-4); /* data corrupted, error return */ - - cert_length -= SIZEOF_TIMESTAMP + userid[0]+1 + - (countbytes(n)+2) + (countbytes(e)+2); - - if (d==NULL) /* skip rest of this key certificate */ - { fseek(f, cert_length, SEEK_CUR); - cert_length = 0; /* because we are skipping secret fields */ - } - else /* d is not NULL */ - { if (is_secret_key(ctb)) - { - if (read_mpi(d,f,FALSE,hidekey) < 0) - return(-4); /* data corrupted, error return */ - if (read_mpi(p,f,FALSE,hidekey) < 0) - return(-4); /* data corrupted, error return */ - if (read_mpi(q,f,FALSE,hidekey) < 0) - return(-4); /* data corrupted, error return */ - - /* use register 'u' briefly as scratchpad */ - mp_mult(u,p,q); /* compare p*q against n */ - if (mp_compare(n,u)!=0) /* bad pass phrase? */ - return(-5); /* possible bad pass phrase, error return */ - /* now read in real u */ - if (read_mpi(u,f,FALSE,hidekey) < 0) - return(-4); /* data corrupted, error return */ - - cert_length -= (countbytes(d)+2) + (countbytes(p)+2) - + (countbytes(q)+2) + (countbytes(u)+2); - - } /* secret key */ - else /* not a secret key */ - { mp_init(d,0); - mp_init(p,0); - mp_init(q,0); - mp_init(u,0); - } - } /* d != NULL */ - - if (cert_length != 0) - { fprintf(stderr,"\n\aCorrupted key. Bad length, off by %d bytes.\n", - (signed int) cert_length); - return(-4); /* data corrupted, error return */ - } - - return(0); /* normal return */ - -} /* readkeypacket */ - - - -int getpublickey(boolean giveup, boolean showkey, char *keyfile, - long *file_position, int *pktlen, byte *keyID, byte *timestamp, - byte *userid, unitptr n, unitptr e) -/* keyID contains key fragment we expect to find in keyfile. - If keyID is NULL, then userid contains a C string search target of - userid to find in keyfile. - keyfile is the file to begin search in, and it may be modified - to indicate true filename of where the key was found. It can be - either a public key file or a secret key file. - file_position is returned as the byte offset within the keyfile - that the key was found at. - giveup is TRUE iff we are just going to do a single file search only. -*/ -{ - int keytype; /* 1 for secret key, 0 for public key */ - byte ctb; /* returned by readkeypacket */ - FILE *f; - int status; - boolean keyfound = FALSE; - boolean secret; /* indicates we are called by getsecretkey */ - char userid0[256]; /* C string format */ - - userid0[0] = '\0'; - secret = strcontains(keyfile,SEC_EXTENSION); - - if (keyID==NULL) /* then userid has search target */ - strcpy(userid0,userid); - -top: - if (secret) - default_extension(keyfile,SEC_EXTENSION); - else - default_extension(keyfile,PUB_EXTENSION); - - if (!file_exists(keyfile)) - { if (giveup) - return(-1); /* give up, error return */ - fprintf(stderr,"\nKeyring file '%s' does not exist. ",keyfile); - goto nogood; - } - if (verbose) fprintf(stderr,"\nSearching key ring file '%s'.\n",keyfile); - - /* open file f for read, in binary (not text) mode...*/ - if ((f = fopen(keyfile,"rb")) == NULL) - return(-1); /* error return */ - - while (TRUE) - { - *file_position = ftell(f); - status = readkeypacket(f,FALSE,&ctb,timestamp,userid,n,e, - NULL,NULL,NULL,NULL); - /* Note that readkeypacket has called set_precision */ - - if (status == -1) /* end of file */ - break; - - if (status < -1) - { fprintf(stderr,"\n\aCould not read key from file '%s'.\n", - keyfile); - fclose(f); /* close key file */ - return(-1); - } - - /* keyID contains key fragment. Check it against n from keyfile. */ - if (keyID!=NULL) - keyfound = checkkeyID(keyID,n); - else - { /* userid0 is already a C string */ - PascalToC(userid); /* for C string functions */ - keyfound = strcontains(userid,userid0); /* any matching subset? */ - /* keyfound = (strcmp(userid,userid0)==0); /* exact match? */ - CToPascal(userid); - } - - if (keyfound) - { *pktlen = (ftell(f) - *file_position); - if (showkey) - { PascalToC(userid); /* for display */ - fprintf(stderr,"\nKey for user ID: %s\n",userid); - CToPascal(userid); - fprintf(stderr,"%d-bit key, Key ID ",countbits(n)); - showkeyID2(n); - fprintf(stderr,", created %s",ctime((long *)timestamp)); - } - fclose(f); - return(0); /* normal return */ - } - } /* while TRUE */ + init_charset(); +#endif /* MACTC5 */ - fclose(f); /* close key file */ +#ifdef MSDOS /* only on MSDOS systems */ + if ((p = getenv("TZ")) == NULL || *p == '\0') { + fprintf(pgpout,LANG("\007WARNING: Environmental variable TZ is not \ +defined, so GMT timestamps\n\ +may be wrong. See the PGP User's Guide to properly define TZ\n\ +in AUTOEXEC.BAT file.\n")); + } +#endif /* MSDOS */ - if (giveup) - return(-1); /* give up, error return */ +#ifdef VMS +#define TEMP "SYS$SCRATCH" +#else +#define TEMP "TMP" +#endif /* VMS */ + if ((p = getenv(TEMP)) != NULL && *p != '\0') + settmpdir(p); + + if ((myArgv = (char **) malloc((argc + 2) * sizeof(char **))) == NULL) { + fprintf(stderr, LANG("\n\007Out of memory.\n")); + exitPGP(7); + } + myArgv[0] = NULL; + myArgv[1] = NULL; - if (keyID!=NULL) - { - fprintf(stderr,"\n\aKey matching expected Key ID "); - showkeyID(keyID); - fprintf(stderr," not found in file '%s'.\n",keyfile); - } - else - { fprintf(stderr,"\n\aKey matching userid '%s' not found in file '%s'.\n", - userid0,keyfile); + /* Process all the command-line option switches: */ + while (optind < argc) { + /* + * Allow random order of options and arguments (like GNU getopt) + * NOTE: this does not work with GNU getopt, use getopt.c from + * the PGP distribution. + */ + if ((!strncasecmp(argv[optind], INCLUDE_MARK, INCLUDE_MARK_LEN)) || + ((opt = pgp_getopt(argc, argv, OPTIONS)) == EOF)) { + if (optind == argc) /* -- at end */ + break; + myArgv[myArgc++] = argv[optind++]; + continue; + } + opt = to_lower(opt); + if (keyflag && (keychar == '\0' || (keychar == 'v' && opt == 'v'))) { + if (keychar == 'v') + keychar = 'V'; + else + keychar = opt; + continue; + } + switch (opt) { + case 'a': + armor_flag = TRUE; + emit_radix_64 = 1; + break; + case 'b': + separate_signature = strip_sig_flag = TRUE; + break; + case 'c': + encrypt_flag = conventional_flag = TRUE; + c_flag = TRUE; + break; + case 'd': + decrypt_only_flag = TRUE; + break; + case 'e': + encrypt_flag = TRUE; + break; +#ifdef MACTC5 + case 'f': + if (macbin_flag == FALSE) filter_mode = TRUE; + break; +#else + case 'f': + filter_mode = TRUE; + break; +#endif + case '?': + case 'h': + usage(); + break; +#ifdef VMS + case 'i': + literal_mode = MODE_LOCAL; + break; +#else +#ifdef MACTC5 + case 'i': + macbin_flag = TRUE; + moreflag = FALSE; + literal_mode = MODE_BINARY; + filter_mode = FALSE; + break; +#endif /* MACTC5 */ +#endif /* VMS */ + case 'k': + keyflag = TRUE; + break; + case 'l': + verbose = TRUE; + break; +#ifdef MACTC5 + case 'm': + if( macbin_flag == FALSE ) + moreflag = TRUE; + break; +#else + case 'm': + moreflag = TRUE; + break; +#endif + case 'p': + preserve_filename = TRUE; + break; + case 'o': + outputfile = optarg; + break; + case 's': + sign_flag = TRUE; + break; +#ifdef MACTC5 + case 't': + if( macbin_flag == FALSE ) + literal_mode = MODE_TEXT; + break; +#else + case 't': + literal_mode = MODE_TEXT; + break; +#endif + case 'u': + strncpy(my_name, optarg, sizeof(my_name) - 1); + CONVERT_TO_CANONICAL_CHARSET(my_name); + u_flag = TRUE; + break; + case 'w': + wipeflag = TRUE; + break; + case 'z': + break; + /* '+' special option: does not require - */ + case '+': + if (processConfigLine(optarg) == 0) { + if (!strncasecmp(optarg,"charset",7)) + init_charset(); + break; + } + fprintf(stderr, "\n"); + /* fallthrough */ + default: + arg_error(); } + } + myArgv[myArgc] = NULL; /* Just to make it NULL terminated */ -nogood: - if (giveup) - return(-1); /* give up, error return */ - - if (secret) - fprintf(stderr,"Enter secret key filename: "); - else - fprintf(stderr,"Enter public key filename: "); - - getstring(keyfile,59,TRUE); /* echo keyboard input */ - if (strlen(keyfile) > 0) - goto top; - - return(-1); /* give up, error return */ + if (keyflag && keychar == '\0') + key_usage(); -} /* getpublickey */ + signon_msg(); + check_expiration_date(); /* hobble any experimental version */ + /* + * Write to stdout if explicitly asked to, or in filter mode and + * no explicit file name was given. + */ + output_stdout = outputfile ? strcmp(outputfile, "-") == 0 : filter_mode; + +#if 1 + /* At request of Peter Simons, use stderr always. Sounds reasonable. */ + /* JIS: Put this code back in... removing it broke too many things */ + if (!output_stdout) + pgpout = stdout; +#endif -int getsecretkey(byte *keyID, byte *timestamp, byte *userid, - unitptr n, unitptr e, unitptr d, unitptr p, unitptr q, unitptr u) -/* keyID contains key fragment we expect to find in keyfile. - If keyID is NULL, then userid contains search target of - userid to find in keyfile. -*/ -{ - byte ctb; /* returned by readkeypacket */ - FILE *f; - char keyfile[64]; /* for getpublickey */ - long file_position; - int pktlen; /* unused, just to satisfy getpublickey */ - int status; - boolean hidekey = FALSE; /* TRUE iff secret key is encrypted */ - char passphrase[256]; - byte iv[256]; /* for BassOmatic CFB mode */ - int guesses = 3; +#if defined(UNIX) || defined(VMS) + umask(077); /* Make files default to private */ +#endif - buildfilename(keyfile,SECRET_KEYRING_FILENAME); /* use default pathname */ + initsigs(); /* Catch signals */ + noise(); /* Start random number generation */ - status = getpublickey(FALSE, TRUE, keyfile, &file_position, &pktlen, - keyID, timestamp, userid, n, e); + if (keyflag) { + status = do_keyopt(keychar); if (status < 0) - return(status); /* error return */ - - /* open file f for read, in binary (not text) mode...*/ - if ((f = fopen(keyfile,"rb")) == NULL) - return(-1); /* error return */ - - /* First guess is null password, so hidekey is FALSE */ - - do /* until good password */ - { /* init CFB BassOmatic key */ - if (hidekey) - { fill0(iv,256); /* define initialization vector IV as 0 */ - if ( initcfb(iv,passphrase,string_length(passphrase),TRUE) < 0 ) - { fclose(f); /* close key file */ - return(-1); - } - } - burn(passphrase); /* burn sensitive data on stack */ - fseek(f,file_position,SEEK_SET); /* reposition file to key */ - status = readkeypacket(f,hidekey,&ctb,timestamp,userid,n,e,d,p,q,u); - if (hidekey) - closebass(); /* release BassOmatic resources */ - - if (status == -5) /* bad pass phrase status */ - { if (guesses!=3) /* not first guess of null password? */ - fprintf(stderr,"\n\aUnreadable secret key. Possible bad pass phrase.\n"); - if (--guesses) /* not ran out of guesses yet */ - { fprintf(stderr,"\nYou need a pass phrase to unlock your RSA secret key. "); - hidekey = (getpassword(passphrase,1,0x0f) > 0); - continue; /* take it from the top */ - } /* more guesses to go */ - } - if (status < 0) - { fprintf(stderr,"\n\aCould not read key from file '%s'.\n", - keyfile); - fclose(f); /* close key file */ - return(-1); - } - } while (status < 0); /* until key reads OK, with good password */ - - fclose(f); /* close key file */ - - if (!hidekey) - fprintf(stderr,"\nAdvisory warning: This RSA secret key is not protected by a passphrase.\n"); - else - fprintf(stderr,"Pass phrase is good. "); - - /* Note that readkeypacket has called set_precision */ - - if (testeq(d,0)) /* didn't get secret key components */ - { fprintf(stderr,"\n\aKey file '%s' is not a secret key file.\n",keyfile); - return(-1); - } - - return(0); /* normal return */ - -} /* getsecretkey */ - - - -int make_signature_certificate(byte *certificate, MDstruct *MD, - byte *userid, unitptr n, unitptr d, unitptr p, unitptr q, unitptr u) -/* Constructs a signed message digest in a signature certificate. - Returns total certificate length in bytes, or returns negative - error status. -*/ -{ - byte inbuf[MAX_BYTE_PRECISION], outbuf[MAX_BYTE_PRECISION]; - byte mdpacket[32]; - byte *mdbufptr; - int i,j,certificate_length,blocksize,bytecount; - word16 useridlength,certsig_length,mdp_length,ske_length; - word32 tstamp; byte *timestamp = (byte *) &tstamp; - byte keyID[KEYFRAGSIZE]; - - /* Note that RSA key must be at least big enough to encipher a - complete message digest packet in a single RSA block. */ - - blocksize = countbytes(n)-1; /* size of a plaintext block */ - if (blocksize < 31) - { fprintf(stderr,"\n\aError: RSA key length must be at least 256 bits.\n"); - return(-1); - } - - get_timestamp(timestamp); /* Timestamp when signature was made */ - hilo_swap(timestamp,4); /* convert to external LSB-first form */ - - fill0(mdpacket,sizeof(mdpacket)); - mdpacket[0] = CTB_MD; /* Message Digest type */ - /* mdp_length includes algorithm byte, MD, and timestamp. */ - mdp_length = 1+16+4; /* message digest packet length */ - /* MD packet length does not include itself or CTB prefix: */ - mdpacket[1] = mdp_length; - mdpacket[2] = MD4_ALGORITHM_BYTE; /* select MD4 algorithm */ - - mdbufptr = (byte *) (MD->buffer); /* point at actual message digest */ - for (i=0; i<16; i++) - mdpacket[i+3] = *mdbufptr++; /* Assumes LSB-first order */ - /* Stick a timestamp in here, before signing... */ - /* timestamp already in external format */ - for (j=0; j> 8) & 0xff; - - /* Now append keyID... */ - extract_keyID(keyID, n); /* gets keyID */ - for (i=0; i MAX_SIGCERT_LENGTH-3) - { fprintf(stderr,"\n\aSignature file '%s' has huge packet length field.\n",infile); - goto err1; + if (filter_mode) { + inputfile = "stdin"; + } else if (makerandom > 0) { /* Create the input file */ + /* + * +makerandom=: Create an input file consisting of + * cryptographically strong random bytes, before applying the + * encryption options of PGP. This is an advanced option, so + * assume the user knows what he's doing and don't bother about + * overwriting questions. E.g. + * pgp +makerandom=24 foofile + * Create "foofile" with 24 random bytes in it. + * pgp +makerandom=24 -ea foofile recipient + * The same, but also encrypt it to "recipient", creating + * foofile.asc as well. + * This feature was created to allow PGP to create and send keys + * around for other applications to use. + */ + status = cryptRandWriteFile(inputfile, (struct IdeaCfbContext *)0, + (unsigned)makerandom); + if (status < 0) { + fprintf(stderr,"Error writing file \"%s\"\n",inputfile); + exitPGP(INVALID_FILE_ERROR); + } + fprintf(pgpout, LANG("File %s created containing %d random bytes.\n"), + inputfile, makerandom); + /* If we aren't encrypting, don't bother trying to decrypt this! */ + if (decrypt_mode) + exitPGP(EXIT_OK); + + /* This is obviously NOT a text file */ + literal_mode = MODE_BINARY; + } else { + if (decrypt_mode && no_extension(inputfile)) { + strcpy(cipherfile, inputfile); + force_extension(cipherfile, ASC_EXTENSION); + if (file_exists(cipherfile)) { + inputfile = cipherfile; + } else { + force_extension(cipherfile, PGP_EXTENSION); + if (file_exists(cipherfile)) { + inputfile = cipherfile; + } else { + force_extension(cipherfile, SIG_EXTENSION); + if (file_exists(cipherfile)) + inputfile = cipherfile; + } + } } - - /* read whole certificate: */ - if (fread((byteptr) certificate, 1, cert_length, f) < cert_length) - { fprintf(stderr,"\n\aSignature file '%s' has bad packet length field.\n",infile); - goto err1; + if (!file_exists(inputfile)) { + fprintf(pgpout, + LANG("\n\007File '%s' does not exist.\n"), inputfile); + errorLvl = FILE_NOT_FOUND_ERROR; + user_error(); } + } - if (!is_ctb_type(ctb,CTB_SKE_TYPE)) - { fprintf(stderr,"\n\a'%s' is not a signature file.\n",infile); - goto err1; - } + if (strlen(inputfile) >= (unsigned) MAX_PATH - 4) { + fprintf(pgpout, + LANG("\007Invalid filename: '%s' too long\n"), inputfile); + errorLvl = INVALID_FILE_ERROR; + user_error(); + } + strcpy(plainfile, inputfile); - for (i=0; i +static char *dos_errlst[] = { - byte ctb; - byte ctbCKE = CTB_CKE; - byte randompad[MAX_BYTE_PRECISION]; /* buffer of random pad bytes */ - int i,blocksize,ckp_length,PKElength,bytecount; - FILE *f; - FILE *g; - FILE *t; - byte header[4]; - unit n[MAX_UNIT_PRECISION], e[MAX_UNIT_PRECISION]; - byte inbuf[MAX_BYTE_PRECISION]; - byte outbuf[MAX_BYTE_PRECISION]; - word32 tstamp; byte *timestamp = (byte *) &tstamp; /* key certificate timestamp */ - byte userid[256]; - byte basskey[64]; /* must be big enough for make_random_basskey */ - int basskeylen; /* must get no bigger than sizeof(basskey)-2 */ - char keyfile[64]; /* for getpublickey */ - long fp; /* unused, just to satisfy getpublickey */ - int pktlen; /* unused, just to satisfy getpublickey */ - - - buildfilename(keyfile,PUBLIC_KEYRING_FILENAME); /* use default pathname */ - - if (verbose) - fprintf(stderr,"\nPlaintext file: %s, ciphertext file: %s\n", - infile,outfile); - - strcpy(userid,mcguffin); /* Who we are looking for (C string) */ - - /* Get and validate public key from a key file: */ - if (getpublickey(FALSE, TRUE, keyfile, &fp, &pktlen, NULL, timestamp, userid, n, e) < 0) - { return(-1); - } - - if (testeq(e,0)) /* Means secret key has been compromised */ - { PascalToC(userid); - fprintf(stderr,"\n\aWarning: Secret key compromised for userid \"%s\".",userid); - fprintf(stderr,"\nThus this public key cannot be used.\n"); - return(-1); - } - - - /* set_precision has been properly called by getpublickey */ - - /* Note that RSA key must be at least big enough to encipher a - complete conventional key packet in a single RSA block. - The BassOmatic key packet is 28 bytes long, which requires - an RSA key 32 bytes (256 bits) long. - If we implemented DES, the DES key packet is 37 bytes long - (with IV, prewhitener and postwhitener), requiring an RSA - key 41 bytes (328 bits) long. - */ - - blocksize = countbytes(n)-1; /* size of a plaintext block */ - if (blocksize < 31) - { fprintf(stderr,"\n\aError: RSA key length must be at least 256 bits.\n"); - return(-1); - } - - /* open file f for read, in binary (not text) mode...*/ - if ((f = fopen( infile, "rb" )) == NULL) - { - fprintf(stderr,"\n\aCan't open plaintext file '%s'\n", infile ); - return(-1); - } - - /* open file g for write, in binary (not text) mode...*/ - if ((g = fopen( outfile, "wb" )) == NULL) - { - fprintf(stderr,"\n\aCan't create ciphertext file '%s'\n", outfile ); - fclose(f); - return(-1); - } - - /* Now we have to time some user keystrokes to get some random - bytes for generating a random BassOmatic key. - We would have to solicit fewer keystrokes for random BassOmatic - key generation if we had already accumulated some keystrokes - incidental to some other purpose, such as asking for a password - to decode an RSA secret key so that a signature could be applied - to the message before encrypting it. - */ - - basskeylen = 32; /* Default is big BassOmatic key */ - if (blocksize < 64) /* <= 512 bits */ - basskeylen = 24; - if (blocksize < 36) /* <= 288 bits */ - basskeylen = 16; - ckp_length = make_random_basskey(basskey,basskeylen); - /* Returns a basskeylen+1 byte random BassOmatic key */ - - outbuf[0] = CTB_CONKEY; /* conventional key packet */ - - ckp_length += 1; /* add length of algorithm field */ - /* Conventional key packet length does not include itself or CTB prefix: */ - outbuf[1] = ckp_length; - - outbuf[2] = BASS_ALGORITHM_BYTE; /* select BassOmatic algorithm */ - - for (i=0; i= 0) + ++success; + } else { + if (charset_header[0]) /* Check signature with charset from Charset: header */ + checksig_pass = 1; + if (do_decrypt(tempf) >= 0) + ++success; + rmtemp(tempf); + if (charset_header[0]) { + if (checksig_pass == 2) { /* Sigcheck failed: try again with local charset */ + tempf = tempfile(0); + use_charset_header = FALSE; + linepos = lastpos; + de_armor_file(armorfile, tempf, &linepos); + if (do_decrypt(tempf) >= 0) + ++success; + rmtemp(tempf); + } + checksig_pass = 0; + } } - /* open file g for write, in binary (not text) mode... */ - - if ((g = fopen( outfile, "wb" )) == NULL) - { fprintf(stderr, "\n\aCan't create plaintext file '%s'\n", outfile ); - goto err1; + if (!is_armor_file(armorfile, linepos)) { + if (!success) /* print error msg if we didn't + decrypt anything */ + user_error(); + return; } - - copyfile( f, g, LITlength ); /* copy rest of literal plaintext file */ - - fclose(g); - fclose(f); - return(0); /* normal return */ - -err1: - fclose(f); - return(-1); /* error return */ - -} /* strip_literal */ - - -/*======================================================================*/ + fprintf(pgpout, + LANG("\nLooking for next packet in '%s'...\n"), armorfile); + } +} /* do_armorfile */ -int decryptfile(char *infile, char *outfile) +static int do_decrypt(char *cipherfile) { - byte ctb; /* Cipher Type Byte */ - byte ctbCKE; /* Cipher Type Byte */ - FILE *f; - FILE *g; - int count, status; - word32 PKElength, CKElength; - unit n[MAX_UNIT_PRECISION], e[MAX_UNIT_PRECISION], d[MAX_UNIT_PRECISION]; - unit p[MAX_UNIT_PRECISION], q[MAX_UNIT_PRECISION], u[MAX_UNIT_PRECISION]; - byte inbuf[MAX_BYTE_PRECISION]; - byte outbuf[MAX_BYTE_PRECISION]; - byte keyID[KEYFRAGSIZE]; - word32 tstamp; byte *timestamp = (byte *) &tstamp; /* key certificate timestamp */ - byte userid[256]; - - set_precision(MAX_UNIT_PRECISION); /* safest opening assumption */ - - if (verbose) - fprintf(stderr,"\nCiphertext file: %s, plaintext file: %s\n", - infile,outfile); - - /* open file f for read, in binary (not text) mode...*/ - if ((f = fopen(infile,"rb")) == NULL) - { fprintf(stderr,"\n\aCan't open ciphertext file '%s'\n",infile); - return(-1); + char *outfile = NULL; + int status, i; + boolean nested_info = FALSE; + char ringfile[MAX_PATH]; + byte ctb; + byte header[8]; /* used to classify file type at the end. */ + char preserved_name[MAX_PATH]; + char *newname; + + /* will be set to the original file name after processing a + literal packet */ + preserved_name[0] = '\0'; + + do { /* while nested parsable info present */ + if (nested_info) { + rmtemp(cipherfile); /* never executed on first pass */ + cipherfile = outfile; + } + if (get_header_info_from_file(cipherfile, &ctb, 1) < 0) { + fprintf(pgpout, +LANG("\n\007Can't open ciphertext file '%s'\n"), cipherfile); + errorLvl = FILE_NOT_FOUND_ERROR; + return -1; } + if (!is_ctb(ctb)) /* not a real CTB -- complain */ + break; - fread(&ctb,1,1,f); /* read Cipher Type Byte */ - if (!is_ctb(ctb)) - { fprintf(stderr,"\n\a'%s' is not a cipher file.\n",infile); - fclose(f); - return(-1); - } + if (moreflag) + outfile = tempfile(TMP_WIPE | TMP_TMPDIR); + else + outfile = tempfile(TMP_WIPE); /* PKE is Public Key Encryption */ - if (!is_ctb_type(ctb,CTB_PKE_TYPE)) - { fprintf(stderr,"\n\a'%s' is not enciphered with a public key.\n",infile); - fclose(f); - return(-1); - } + if (is_ctb_type(ctb, CTB_PKE_TYPE)) { - PKElength = getpastlength(ctb, f); /* read packet length */ - - fread(keyID,1,KEYFRAGSIZE,f); /* read key ID */ - /* Use keyID prefix to look up key. */ - - /* Get and validate secret key from a key file: */ - if (getsecretkey(keyID, timestamp, userid, n, e, d, p, q, u) < 0) - { fclose(f); - return(-1); - } - - /* Note that RSA key must be at least big enough to encipher a - complete conventional key packet in a single RSA block. */ - - /*==================================================================*/ - /* read ciphertext block, converting to internal format: */ - read_mpi((unitptr)inbuf, f, FALSE, FALSE); - - fprintf(stderr,"Just a moment-- "); /* RSA will take a while. */ - - rsa_decrypt((unitptr)outbuf, (unitptr)inbuf, d, p, q, u); - - if ((count = postunblock(outbuf, (unitptr)outbuf, n, TRUE, TRUE)) < 0) - { fprintf(stderr,"\n\aBad RSA decrypt: checksum or pad error during unblocking.\n"); - fclose(f); - return(-1); - } + if (!quietmode) + fprintf(pgpout, +LANG("\nFile is encrypted. Secret key is required to read it. ")); + + /* Decrypt to scratch file since we may have a LITERAL2 */ + status = decryptfile(cipherfile, outfile); + + if (status < 0) { /* error return */ + errorLvl = RSA_DECR_ERROR; + return -1; + } + nested_info = (status > 0); - fputc('.',stderr); /* Signal RSA completion. */ + } else if (is_ctb_type(ctb, CTB_SKE_TYPE)) { - /* outbuf should contain random BassOmatic key packet */ - /*==================================================================*/ - /* Look at nested stuff within RSA block... */ + if (decrypt_only_flag) { + /* swap file names instead of just copying the file */ + rmtemp(outfile); + outfile = cipherfile; + cipherfile = NULL; + if (!quietmode) + fprintf(pgpout, +LANG("\nThis file has a signature, which will be left in place.\n")); + nested_info = FALSE; + break; /* Do no more */ + } + if (!quietmode && checksig_pass<=1) + fprintf(pgpout, +LANG("\nFile has signature. Public key is required to check signature.\n")); + + status = check_signaturefile(cipherfile, outfile, + strip_sig_flag, preserved_name); + + if (status < 0) { /* error return */ + errorLvl = SIGNATURE_CHECK_ERROR; + return -1; + } + nested_info = (status > 0); - ctb = outbuf[0]; /* get nested CTB, should be CTB_CONKEY */ + if (strcmp(preserved_name, "/dev/null") == 0) { + rmtemp(outfile); + fprintf(pgpout, "\n"); + return 0; + } + } else if (is_ctb_type(ctb, CTB_CKE_TYPE)) { - if (!is_ctb_type(ctb,CTB_CONKEY_TYPE)) - { fprintf(stderr,"\aNested info is not a conventional key packet.\n"); - goto err1; - } + /* Conventional Key Encrypted ciphertext. */ + /* Tell user it's encrypted here, and prompt for + password in subroutine. */ + if (!quietmode) + fprintf(pgpout, LANG("\nFile is conventionally encrypted. ")); + /* Decrypt to scratch file since it may be a LITERAL2 */ + status = idea_decryptfile(cipherfile, outfile); + if (status < 0) { /* error return */ + errorLvl = DECR_ERROR; + return -1; /* error exit status */ + } + nested_info = (status > 0); - /* Test the Conventional Key Packet for supported algorithms. - (currently, just the BassOmatic is supported) */ + } else if (is_ctb_type(ctb, CTB_COMPRESSED_TYPE)) { - if ( outbuf[2] != BASS_ALGORITHM_BYTE ) - { fprintf(stderr,"\a\nUnrecognized conventional encryption algorithm.\n"); - goto err1; - } + /* Compressed text. */ + status = decompress_file(cipherfile, outfile); + if (status < 0) { /* error return */ + errorLvl = DECOMPRESS_ERROR; + return -1; + } + /* Always assume nested information... */ + nested_info = TRUE; - if (file_exists( outfile )) - { fprintf(stderr, "\n\aOutput file '%s' already exists. Overwrite (y/N)? ", outfile ); - if (! getyesno( 'n' )) - goto err1; /* user said don't do it - abort operation */ - } + } else if (is_ctb_type(ctb, CTB_LITERAL_TYPE) || + is_ctb_type(ctb, CTB_LITERAL2_TYPE)) { /* Raw plaintext. + Just copy it. + No more nesting. + */ + + /* Strip off CTB_LITERAL prefix byte from file: */ + /* strip_literal may alter plainfile; will set mode */ + status = strip_literal(cipherfile, outfile, + preserved_name, &literal_mode); + if (status < 0) { /* error return */ + errorLvl = UNKNOWN_FILE_ERROR; + return -1; + } + nested_info = FALSE; + } else if (ctb == CTB_CERT_SECKEY || ctb == CTB_CERT_PUBKEY) { - /* open file g for write, in binary (not text) mode... */ - if ((g = fopen( outfile, "wb" )) == NULL) - { fprintf(stderr, "\n\aCan't create plaintext file '%s'\n", outfile ); - goto err1; - } + rmtemp(outfile); + if (decrypt_only_flag) { + /* swap file names instead of just copying the file */ + outfile = cipherfile; + cipherfile = NULL; + nested_info = FALSE; /* No error */ + break; /* no further processing */ + } + /* Key ring. View it. */ + fprintf(pgpout, +LANG("\nFile contains key(s). Contents follow...")); + if (view_keyring(NULL, cipherfile, TRUE, FALSE) < 0) { + errorLvl = KEYRING_VIEW_ERROR; + return -1; + } + /* filter mode explicit requested with -f */ + if (filter_mode && !preserve_filename) + return 0; /* No output file */ + if (batchmode) + return 0; + if (ctb == CTB_CERT_SECKEY) + strcpy(ringfile, globalSecringName); + else + strcpy(ringfile, globalPubringName); + /* Ask if it should be put on key ring */ + fprintf(pgpout, +LANG("\nDo you want to add this keyfile to keyring '%s' (y/N)? "), ringfile); + if (!getyesno('n')) + return 0; + status = addto_keyring(cipherfile, ringfile); + if (status < 0) { + fprintf(pgpout, LANG("\007Keyring add error. ")); + errorLvl = KEYRING_ADD_ERROR; + return -1; + } + return 0; /* No output file */ - fread(&ctbCKE,1,1,f); /* read Cipher Type Byte, should be CTB_CKE */ - if (ctbCKE != CTB_CKE) - { /* Should never get here. */ - fprintf(stderr,"\a\nBad or missing CTB_CKE byte.\n"); - goto err1; /* Abandon ship! */ + } else { /* Unrecognized CTB */ + break; } - CKElength = getpastlength(ctbCKE, f); /* read packet length */ - - status = bass_file( outbuf+3, count-3, TRUE, f, g ); /* Decrypt ciphertext file */ + } while (nested_info); + /* No more nested parsable information */ - fclose(g); - fclose(f); - burn(inbuf); /* burn sensitive data on stack */ - burn(outbuf); /* burn sensitive data on stack */ - mp_burn(d); /* burn sensitive data on stack */ - mp_burn(p); /* burn sensitive data on stack */ - mp_burn(q); /* burn sensitive data on stack */ - mp_burn(u); /* burn sensitive data on stack */ - if (status < 0) /* if bass_file failed, then error return */ - return(status); - return(1); /* always indicate output file has nested stuff in it. */ - -err1: - fclose(f); - burn(inbuf); /* burn sensitive data on stack */ - burn(outbuf); /* burn sensitive data on stack */ - mp_burn(d); /* burn sensitive data on stack */ - mp_burn(p); /* burn sensitive data on stack */ - mp_burn(q); /* burn sensitive data on stack */ - mp_burn(u); /* burn sensitive data on stack */ - return(-1); /* error return */ - -} /* decryptfile */ - - - -int bass_decryptfile(char *infile, char *outfile) -{ - byte ctb; /* Cipher Type Byte */ - FILE *f; - FILE *g; - word32 CKElength; - byte basskey[256]; - int basskeylen; /* must get no bigger than sizeof(basskey)-2 */ - int status; - - if (verbose) - fprintf(stderr,"\nCiphertext file: %s, plaintext file: %s\n", - infile,outfile); + /* Stopped early due to error */ + if (nested_info) { + fprintf(pgpout, +"\7\nERROR: Nested data has unexpected format. CTB=0x%02X\n", ctb); + if (outfile) + rmtemp(outfile); + if (cipherfile) + rmtemp(cipherfile); + errorLvl = UNKNOWN_FILE_ERROR; + return -1; + } + if (outfile == NULL) { /* file was not encrypted */ + if (!filter_mode && !moreflag) { + fprintf(pgpout, +LANG("\007\nError: '%s' is not a ciphertext, signature, or key file.\n"), + cipherfile); + errorLvl = UNKNOWN_FILE_ERROR; + return -1; + } + outfile = cipherfile; + } else { + if (cipherfile) + rmtemp(cipherfile); + } - /* open file f for read, in binary (not text) mode...*/ - if ((f = fopen(infile,"rb")) == NULL) - { fprintf(stderr,"\n\aCan't open ciphertext file '%s'\n",infile); - return(-1); + if (moreflag || (strcmp(preserved_name, CONSOLE_FILENAME) == 0)) { + /* blort to screen */ + if (strcmp(preserved_name, CONSOLE_FILENAME) == 0) { + fprintf(pgpout, +LANG("\n\nThis message is marked \"For your eyes only\". Display now \ +(Y/n)? ")); + if (batchmode +#ifdef UNIX + || !isatty(fileno(stdout)) /* stdout is redirected! */ +#endif + || filter_mode || !getyesno('y')) { + /* no -- abort display, and clean up */ + fprintf(pgpout, "\n"); + rmtemp(outfile); + return 0; + } } - - fread(&ctb,1,1,f); /* read Cipher Type Byte, should be CTB_CKE */ - - if (!is_ctb(ctb) || !is_ctb_type(ctb,CTB_CKE_TYPE)) - { /* Should never get here. */ - fprintf(stderr,"\a\nBad or missing CTB_CKE byte.\n"); - goto err1; /* Abandon ship! */ + if (!quietmode) + fprintf(pgpout, LANG("\n\nPlaintext message follows...\n")); + else + putc('\n', pgpout); + fprintf(pgpout, "------------------------------\n"); + more_file(outfile, strcmp(preserved_name, CONSOLE_FILENAME) == 0); + /* Disallow saving to disk if outfile is console-only: */ + if (strcmp(preserved_name, CONSOLE_FILENAME) == 0) { + clearscreen(); /* remove all evidence */ + } else if (!quietmode && !batchmode) { + fprintf(pgpout, LANG("Save this file permanently (y/N)? ")); + if (getyesno('n')) { + char moreFilename[256]; + fprintf(pgpout, LANG("Enter filename to save file as: ")); +#ifdef AMIGA + requesterdesc=LANG("Enter filename to save file as: "); +#endif + if (preserved_name[0]) { + fprintf(pgpout, "[%s]: ", file_tail(preserved_name)); +#ifdef AMIGA + strcat(requesterdesc, "["); + strcat(requesterdesc, file_tail(preserved_name)); + strcat(requesterdesc, "]:"); +#endif + } +#ifdef MACTC5 + if(!GetFilePath(LANG("Enter filename to save file as:"),moreFilename,PUTFILE)) + strcpy(moreFilename,""); + else + fprintf(pgpout, "%s\n",moreFilename); +#else + getstring(moreFilename, 255, TRUE); +#endif + if (*moreFilename == '\0') { + if (*preserved_name != '\0') + savetemp(outfile, file_tail(preserved_name)); + else + rmtemp(outfile); + } else + savetemp(outfile, moreFilename); + return 0; + } } + rmtemp(outfile); + return 0; + } /* blort to screen */ + if (outputfile) { + if (!strcmp(outputfile, "/dev/null")) { + rmtemp(outfile); + return 0; + } + filter_mode = (strcmp(outputfile, "-") == 0); + strcpy(plainfile, outputfile); + } else { +#ifdef VMS + /* VMS null extension has to be ".", not "" */ + force_extension(plainfile, "."); +#else /* not VMS */ + drop_extension(plainfile); +#endif /* not VMS */ + } - CKElength = getpastlength(ctb, f); /* read packet length */ - /* The packet length is ignored. Assume it's huge. */ - - if (file_exists( outfile )) - { fprintf(stderr, "\n\aOutput file '%s' already exists. Overwrite (y/N)? ", outfile ); - if (! getyesno( 'n' )) - goto err1; /* user said don't do it - abort operation */ + if (!preserve_filename && filter_mode) { + if (writePhantomOutput(outfile) < 0) { + errorLvl = UNKNOWN_FILE_ERROR; + return -1; } + rmtemp(outfile); + return 0; + } + if (preserve_filename && preserved_name[0] != '\0') + strcpy(plainfile, file_tail(preserved_name)); - /* open file g for write, in binary (not text) mode... */ - if ((g = fopen( outfile, "wb" )) == NULL) - { fprintf(stderr, "\n\aCan't create plaintext file '%s'\n", outfile ); - goto err1; + if (quietmode) { + if (savetemp(outfile, plainfile) == NULL) { + errorLvl = UNKNOWN_FILE_ERROR; + return -1; } + return 0; + } + if (!verbose) /* if other filename messages were suppressed */ + fprintf(pgpout, LANG("\nPlaintext filename: %s"), plainfile); - /* Get BassOmatic password with leading BassOmatic control byte: */ - /* Default is Military grade BassOmatic key control byte */ - if (getpassword(basskey,NOECHO1,0x1f) <= 0) - return(-1); - - basskeylen = strlen(basskey); - - status = bass_file( basskey, basskeylen, TRUE, f, g ); /* decrypt file */ - - burn(basskey); /* burn sensitive data on stack */ - - fclose(g); - fclose(f); - if (status < 0) /* if bass_file failed, then complain */ - { fprintf(stderr,"\n\aError: Bad pass phrase. "); - remove(outfile); /* throw away our mistake */ - return(status); /* error return */ - } - return(1); /* always indicate output file has nested stuff in it. */ +/*---------------------------------------------------------*/ -err1: - fclose(f); - return(-1); /* error return */ + /* One last thing-- let's attempt to classify some of the more + frequently occurring cases of plaintext output files, as an + aid to the user. -} /* bass_decryptfile */ + For example, if output file is a public key, it should have + the right extension on the filename. + Also, it will likely be common to encrypt files created by + various archivers, so they should be renamed with the archiver + extension. + */ + get_header_info_from_file(outfile, header, 8); + newname = NULL; +#ifdef MACTC5 + if (header[0] == CTB_CERT_SECKEY) + PGPSetFinfo(plainfile,'SKey','MPGP'); +#endif + if (header[0] == CTB_CERT_PUBKEY) { + /* Special case--may be public key, worth renaming */ +#ifdef MACTC5 + PGPSetFinfo(plainfile,'PKey','MPGP'); +#endif + fprintf(pgpout, +LANG("\nPlaintext file '%s' looks like it contains a public key."), + plainfile); + newname = maybe_force_extension(plainfile, PGP_EXTENSION); + } + /* Possible public key output file */ + else if ((i = compressSignature(header)) >= 0) { + /* Special case--may be an archived/compressed file, + worth renaming + */ + fprintf(pgpout, LANG("\nPlaintext file '%s' looks like a %s file."), + plainfile, compressName[i]); + newname = maybe_force_extension(plainfile, compressExt[i]); + } else if (is_ctb(header[0]) && + (is_ctb_type(header[0], CTB_PKE_TYPE) + || is_ctb_type(header[0], CTB_SKE_TYPE) + || is_ctb_type(header[0], CTB_CKE_TYPE))) { + /* Special case--may be another ciphertext file, worth renaming */ + fprintf(pgpout, +LANG("\n\007Output file '%s' may contain more ciphertext or signature."), + plainfile); + newname = maybe_force_extension(plainfile, PGP_EXTENSION); + } /* Possible ciphertext output file */ +#ifdef MACTC5 + if( (newname = savetemp(outfile, (newname ? newname : plainfile))) == NULL) { +#else + if (savetemp(outfile, (newname ? newname : plainfile)) == NULL) { +#endif + errorLvl = UNKNOWN_FILE_ERROR; + return -1; + } +#ifdef MACTC5 + else if( strcmp(newname, plainfile) != 0 ) /* 203a */ + strcpy(plainfile, newname); +#endif + fprintf(pgpout, "\n"); + return 0; +} /* do_decrypt */ -int decompress_file(char *infile, char *outfile) +static int do_keyopt(char keychar) { - byte ctb; - FILE *f; - FILE *g; - word32 compress_pkt_length; - extern void lzhDecode( FILE *, FILE * ); - if (verbose) fprintf(stderr, "Decompressing plaintext..." ); - - /* open file f for read, in binary (not text) mode...*/ - if ((f = fopen(infile,"rb")) == NULL) - { fprintf(stderr,"\n\aCan't open compressed file '%s'\n",infile); - return(-1); - } - - fread(&ctb,1,1,f); /* read and skip over Cipher Type Byte */ - if (!is_ctb_type( ctb, CTB_COMPRESSED_TYPE )) - { /* Shouldn't get here, or why were we called to begin with? */ - fprintf(stderr,"\a\nBad or missing CTB_COMPRESSED byte.\n"); - goto err1; /* Abandon ship! */ - } - - compress_pkt_length = getpastlength(ctb, f); /* read packet length */ - /* The packet length is ignored. Assume it's huge. */ - - fread(&ctb,1,1,f); /* read and skip over compression algorithm byte */ - if (ctb != LZH_ALGORITHM_BYTE) - { /* We only know one compression algorithm */ - fprintf(stderr,"\a\nUnrecognized compression algorithm.\n"); - goto err1; /* Abandon ship! */ - } - - /* open file g for write, in binary (not text) mode... */ - if ((g = fopen( outfile, "wb" )) == NULL) - { fprintf(stderr, "\n\aCan't create decompressed file '%s'\n", outfile ); - goto err1; - } - - lzhDecode( f, g ); - if (verbose) fprintf(stderr, "done. " ); - fclose(g); - fclose(f); - return(1); /* always indicate output file has nested stuff in it. */ -err1: - fclose(f); - return(-1); /* error return */ - -} /* decompress_file */ - - - -int view_keyring(char *mcguffin, char *ringfile) -/* Lists all entries in keyring that have mcguffin string in userid. - mcguffin is a null-terminated C string. -*/ -{ FILE *f; - long file_position,fp; - byte ctb; - int status; - unit n[MAX_UNIT_PRECISION], e[MAX_UNIT_PRECISION]; - byte keyID[KEYFRAGSIZE]; - byte userid[256]; /* key certificate userid */ - word32 tstamp; - byte *timestamp = (byte *) &tstamp; /* key certificate timestamp */ - int keycounter = 0; - - /* open file f for read, in binary (not text) mode...*/ - if ((f = fopen(ringfile,"rb")) == NULL) - { fprintf(stderr,"\n\aCan't open key ring file '%s'\n",ringfile); - return(-1); - } - -/* Here's a good format for display of key or signature certificates: -Type bits/keyID Date User ID -pub 990/xxxxxx dd-mmm-yy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -sec 990/xxxxxx dd-mmm-yy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -sig 990/xxxxxx dd-mmm-yy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -com 990/xxxxxx dd-mmm-yy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -*/ + char keyfile[MAX_PATH]; + char ringfile[MAX_PATH]; + char *workfile; + int status; + + if ((filter_mode || batchmode) + && (keychar == 'g' || keychar == 'e' || keychar == 'd' + || (keychar == 'r' && sign_flag))) { + errorLvl = NO_BATCH; + arg_error(); /* interactive process, no go in batch mode */ + } + /* + * If we're not doing anything that uses stdout, produce output there, + * in case user wants to redirect it. + */ + if (!filter_mode) + pgpout = stdout; + + switch (keychar) { + +/*-------------------------------------------------------*/ + case 'g': + { /* Key generation + Arguments: bitcount, bitcount + */ + char keybits[6], ebits[6], *username = NULL; + + /* + * Why all this code? + * + * Some people may object to PGP insisting on finding the + * manual somewhere in the neighborhood to generate a key. + * They bristle against this seemingly authoritarian + * attitude. Some people have even modified PGP to defeat + * this feature, and redistributed their hotwired version to + * others. That creates problems for me (PRZ). + * + * Here is the problem. Before I added this feature, there + * were maimed versions of the PGP distribution package + * floating around that lacked the manual. One of them was + * uploaded to Compuserve, and was distributed to countless + * users who called me on the phone to ask me why such a + * complicated program had no manual. It spread out to BBS + * systems around the country. And a freeware distributor got + * hold of the package from Compuserve and enshrined it on + * CD-ROM, distributing thousands of copies without the + * manual. What a mess. + * + * Please don't make my life harder by modifying PGP to + * disable this feature so that others may redistribute PGP + * without the manual. If you run PGP on a palmtop with no + * memory for the manual, is it too much to ask that you type + * one little extra word on the command line to do a key + * generation, a command that is seldom used by people who + * already know how to use PGP? If you can't stand even this + * trivial inconvenience, can you suggest a better method of + * reducing PGP's distribution without the manual? + * + * PLEASE DO NOT DISABLE THIS CHECK IN THE SOURCE CODE + * WITHOUT AT LEAST CALLING PHILIP ZIMMERMANN + * (+1 303 541-0140, or prz@acm.org) TO DISCUSS IT. + */ + if (!nomanual && manuals_missing()) { + char const *const *dir; + fputs(LANG("\a\nError: PGP User's Guide not found.\n\ +PGP looked for it in the following directories:\n"), pgpout); +#ifdef MACTC5 + fprintf(pgpout, "\t\"%s\"\n", appPathName); +#else + for (dir = manual_dirs; *dir; dir++) + fprintf(pgpout, "\t\"%s\"\n", *dir); +#endif /* MACTC5 */ + fputs( +LANG("and the doc subdirectory of each of the above. Please put a copy of\n\ +both volumes of the User's Guide in one of these directories.\n\ +\n\ +Under NO CIRCUMSTANCES should PGP ever be distributed without the PGP\n\ +User's Guide, which is included in the standard distribution package.\n\ +If you got a copy of PGP without the manual, please inform whomever you\n\ +got it from that this is an incomplete package that should not be\n\ +distributed further.\n\ +\n\ +PGP will not generate a key without finding the User's Guide.\n\ +There is a simple way to override this restriction. See the\n\ +PGP User's Guide for details on how to do it.\n\ +\n"), pgpout); + return KEYGEN_ERROR; + } + if (myArgc > 2) + strncpy(keybits, myArgv[2], sizeof(keybits) - 1); + else + keybits[0] = '\0'; + + if (myArgc > 3) + strncpy(ebits, myArgv[3], sizeof(ebits) - 1); + else + ebits[0] = '\0'; + + /* If the -u option is given, use that username */ + if (u_flag && my_name != NULL && *my_name != '\0') + username = my_name; + + /* dokeygen writes the keys out to the key rings... */ + status = dokeygen(keybits, ebits, username); + + if (status < 0) { + fprintf(pgpout, LANG("\007Keygen error. ")); + errorLvl = KEYGEN_ERROR; + } +#ifdef MACTC5 + else { + strcpy(ringfile, globalPubringName ); + PGPSetFinfo(ringfile,'PKey','MPGP'); + strcpy(ringfile, globalSecringName ); + PGPSetFinfo(ringfile,'SKey','MPGP'); + } +#endif + return status; + } /* Key generation */ + +/*-------------------------------------------------------*/ + case 'c': + { /* Key checking + Arguments: userid, ringfile + */ + + if (myArgc < 3) { /* Default to all user ID's */ + mcguffin[0] = '\0'; + } else { + strcpy(mcguffin, myArgv[2]); + if (strcmp(mcguffin, "*") == 0) + mcguffin[0] = '\0'; + } + CONVERT_TO_CANONICAL_CHARSET(mcguffin); - fprintf(stderr,"\nKey ring: '%s'",ringfile); - if (strlen(mcguffin) > 0) - fprintf(stderr,", looking for user ID \"%s\".",mcguffin); - fprintf(stderr,"\nType bits/keyID Date User ID\n"); - do - { - status = readkeypacket(f,FALSE,&ctb,timestamp,userid,n,e, - NULL,NULL,NULL,NULL); - /* Note that readkeypacket has called set_precision */ - if (status== -1 ) break; /* eof reached */ - if (status < 0) - { fprintf(stderr,"\n\aCould not read key from file '%s'.\n", - ringfile); - fclose(f); /* close key file */ - return(-1); - } + if (myArgc < 4) /* default key ring filename */ + strcpy(ringfile, globalPubringName); + else + strncpy(ringfile, myArgv[3], sizeof(ringfile) - 1); + + if ((myArgc < 4 && myArgc > 2) /* Allow just key file as arg */ + &&has_extension(myArgv[2], PGP_EXTENSION)) { + strcpy(ringfile, myArgv[2]); + mcguffin[0] = '\0'; + } + status = dokeycheck(mcguffin, ringfile, CHECK_ALL); - if (!is_ctb_type(ctb,CTB_CERT_PUBKEY_TYPE) - && !is_ctb_type(ctb,CTB_CERT_SECKEY_TYPE)) - { - fprintf(stderr,"\n\aError in file '%s'. Not a key certificate.\n", - ringfile); - return(-1); + if (status < 0) { + fprintf(pgpout, LANG("\007Keyring check error.\n")); + errorLvl = KEYRING_CHECK_ERROR; + } + if (status >= 0 && mcguffin[0] != '\0') + return status; /* just checking a single user, + dont do maintenance */ + + if ((status = maint_check(ringfile, 0)) < 0 && status != -7) { + fprintf(pgpout, LANG("\007Maintenance pass error. ")); + errorLvl = KEYRING_CHECK_ERROR; + } +#ifdef MACTC5 + { + byte ctb; + get_header_info_from_file(ringfile, &ctb, 1); + if (ctb == CTB_CERT_SECKEY) + PGPSetFinfo(ringfile,'SKey','MPGP'); + else if (ctb == CTB_CERT_PUBKEY) + PGPSetFinfo(ringfile,'PKey','MPGP'); } +#endif + return status == -7 ? 0 : status; + } /* Key check */ - keycounter++; - - extract_keyID(keyID, n); - PascalToC(userid); - - if (strcontains(userid,mcguffin)) - { - if (is_ctb_type(ctb,CTB_CERT_PUBKEY_TYPE)) - { - if (testeq(e,0)) /* e==0 means key compromised */ - fprintf(stderr,"com "); /* "key compromised" certificate */ - else - fprintf(stderr,"pub "); /* public key certificate */ - } - else if (is_ctb_type(ctb,CTB_CERT_SECKEY_TYPE)) - fprintf(stderr,"sec "); /* secret key certificate */ - else - fprintf(stderr,"??? "); /* otherwise, who knows? */ - - fprintf(stderr,"%4d/",countbits(n)); - showkeyID(keyID); - fputc(' ',stderr); - show_date((long *)timestamp); - fprintf(stderr," "); - fprintf(stderr,userid); - fputc('\n',stderr); - } /* if it has mcguffin */ - } while (status >= 0); - - fclose(f); /* close key file */ - fprintf(stderr,"%d key(s) examined. ",keycounter); - - return(0); /* normal return */ - -} /* view_keyring */ - - - -int remove_from_keyring(byte *keyID, char *mcguffin, char *ringfile) -/* Remove the first entry in key ring that has mcguffin string in userid. - Or it removes the first matching keyID from the ring. - A non-NULL keyID takes precedence over a mcguffin specifier. - mcguffin is a null-terminated C string. -*/ -{ - FILE *f; - FILE *g; - long file_position,fp,after_key; - int packetlength=0; - byte ctb; - int status; - unit n[MAX_UNIT_PRECISION], e[MAX_UNIT_PRECISION]; - byte userid[256]; /* key certificate userid */ - word32 tstamp; byte *timestamp = (byte *) &tstamp; /* key certificate timestamp */ - - default_extension(ringfile,PUB_EXTENSION); - - if ((keyID==NULL) && (strlen(mcguffin)==0)) - return(-1); /* error, null mcguffin will match everything */ - - strcpy(userid,mcguffin); - - fprintf(stderr,"\nRemoving from key ring: '%s'",ringfile); - if (strlen(mcguffin) > 0) - fprintf(stderr,", userid \"%s\".\n",mcguffin); - - status = getpublickey(TRUE, TRUE, ringfile, &fp, &packetlength, NULL, timestamp, userid, n, e); - if (status < 0) - { fprintf(stderr,"\n\aKey not found in key ring '%s'.\n",ringfile); - return(0); /* normal return */ - } - after_key = fp + packetlength; - - if (testeq(e,0)) /* This is a key compromise certificate. */ - { /* Wish there was a more elegant way to handle this... */ - fprintf(stderr,"\n\aWARNING: This is a \"key compromised\" certificate."); - fprintf(stderr,"\nIt should not be removed from the key ring!\n"); - if (keyID != NULL) /* Decision requires human confirmation. */ - return(-1); - } - - if (keyID==NULL) /* Human confirmation is required. */ - { /* Supposedly the key was fully displayed by getpublickey */ - fprintf(stderr,"\nAre you sure you want this key removed (y/N)? "); - if (!getyesno('n')) - return(-1); /* user said "no" */ - } - - /* open file f for read, in binary (not text) mode...*/ - if ((f = fopen(ringfile,"rb")) == NULL) - { fprintf(stderr,"\n\aCan't open key ring file '%s'\n",ringfile); - return(-1); - } - - remove(SCRATCH_KEYRING_FILENAME); - /* open file g for writing, in binary (not text) mode...*/ - if ((g = fopen(SCRATCH_KEYRING_FILENAME,"wb")) == NULL) - { fclose(f); - return(-1); - } - rewind(f); - copyfile(f,g,fp); /* copy file f to g up to position fp */ - fseek(f,after_key,SEEK_SET); /* reposition file to after key */ - copyfile(f,g,-1UL); /* copy rest of file from file f to g */ - fclose(g); /* close scratch file */ - fclose(f); /* close key file */ - remove(ringfile); /* dangerous. sure hope rename works... */ - rename(SCRATCH_KEYRING_FILENAME,ringfile); - fprintf(stderr,"\nKey removed from key ring. "); - - return(0); /* normal return */ - -} /* remove_from_keyring */ - - - -int addto_keyring(char *keyfile, char *ringfile) -/* Adds (prepends) key file to key ring file */ -{ FILE *f; - FILE *g; - long file_position,fp; - int pktlen; /* unused, just to satisfy getpublickey */ - byte ctb; - int status; - unit n[MAX_UNIT_PRECISION], e[MAX_UNIT_PRECISION]; - byte keyID[KEYFRAGSIZE]; - byte userid[256]; /* key certificate userid */ - word32 tstamp; byte *timestamp = (byte *) &tstamp; /* key certificate timestamp */ - boolean keycompromised; +/*-------------------------------------------------------*/ + case 'm': + { /* Maintenance pass + Arguments: ringfile + */ + + if (myArgc < 3) /* default key ring filename */ + strcpy(ringfile, globalPubringName); + else + strcpy(ringfile, myArgv[2]); - if (strcontains(ringfile,SEC_EXTENSION)) - force_extension(SCRATCH_KEYRING_FILENAME,SEC_EXTENSION); - else - force_extension(SCRATCH_KEYRING_FILENAME,PUB_EXTENSION); +#ifdef MSDOS + strlwr(ringfile); +#endif + if (!file_exists(ringfile)) + default_extension(ringfile, PGP_EXTENSION); - /* open file f for read, in binary (not text) mode...*/ - if ((f = fopen(keyfile,"rb")) == NULL) - { fprintf(stderr,"\n\aCan't open key file '%s'\n",keyfile); - return(-1); - } + if ((status = maint_check(ringfile, + MAINT_VERBOSE | (c_flag ? MAINT_CHECK : 0))) < 0) { + if (status == -7) + fprintf(pgpout, + LANG("File '%s' is not a public keyring\n"), + ringfile); + fprintf(pgpout, LANG("\007Maintenance pass error. ")); + errorLvl = KEYRING_CHECK_ERROR; + } +#ifdef MACTC5 + PGPSetFinfo(ringfile,'PKey','MPGP'); +#endif + return status; + } /* Maintenance pass */ - /* Check to see if the keyID is already in key ring before we add it in. */ +/*-------------------------------------------------------*/ + case 's': + { /* Key signing + Arguments: her_id, keyfile + */ + + if (myArgc >= 4) + strncpy(keyfile, myArgv[3], sizeof(keyfile) - 1); + else + strcpy(keyfile, globalPubringName); + + if (myArgc >= 3) { + strcpy(mcguffin, myArgv[2]); /* Userid to sign */ + } else { + fprintf(pgpout, +LANG("\nA user ID is required to select the public key you want to sign. ")); + if (batchmode) /* not interactive, userid + must be on command line */ + return -1; + fprintf(pgpout, LANG("\nEnter the public key's user ID: ")); +#ifdef AMIGA + requesterdesc=LANG("\nEnter the public key's user ID: "); +#endif + getstring(mcguffin, 255, TRUE); /* echo keyboard */ + } + CONVERT_TO_CANONICAL_CHARSET(mcguffin); - file_position = ftell(f); - status = readkeypacket(f,FALSE,&ctb,timestamp,userid,n,e, - NULL,NULL,NULL,NULL); - /* Note that readkeypacket has called set_precision */ - if (status < 0) - { fprintf(stderr,"\n\aCould not read key from file '%s'.\n", - keyfile); - fclose(f); /* close key file */ - return(-1); - } - - if (!is_ctb_type(ctb,CTB_CERT_PUBKEY_TYPE) - && !is_ctb_type(ctb,CTB_CERT_SECKEY_TYPE)) - { fprintf(stderr,"\n\aError in file '%s'. Not a key certificate.\n", - keyfile); - return(-1); - } - - extract_keyID(keyID, n); /* from keyfile, not ringfile */ - - if (!file_exists(ringfile)) - { /* ringfile does not exist. Can it be created? */ - /* open file g for writing, in binary (not text) mode...*/ - g = fopen(ringfile,"wb"); - if (g==NULL) - { fprintf(stderr,"\n\aKey ring file '%s' cannot be created.\n",ringfile); - fclose(f); - return(-1); - } - fclose(g); - } - - /* See if we are adding a "secret key compromised" certificate: */ - keycompromised = testeq(e,0); - - /* If this is a key compromise certificate, maybe we should - remove the real public key from the key ring if it's on the - key ring before adding the key compromise certificate. - Probably not, though, because the prepended key compromise - certificate will take search order precedence. - And it may be nice to keep the original public key certificate - around for its timestamp, to check old signatures. - It should not be possible to later add the same public key to - the ring again if the key compromise certificate was there first. - - These tests for duplicates should have to be applied for all - the keys being added to the ring, in case the added key file - is itself a multikey ring. Fix this later. - */ + if (my_name[0] == '\0') { + fprintf(pgpout, +LANG("\nA secret key is required to make a signature. ")); + fprintf(pgpout, +LANG("\nYou specified no user ID to select your secret key,\n\ +so the default user ID and key will be the most recently\n\ +added key on your secret keyring.\n")); + } + status = signkey(mcguffin, my_name, keyfile); - /* Check for duplicate key in key ring: */ - if (getpublickey(TRUE, TRUE, ringfile, &fp, &pktlen, keyID, timestamp, userid, n, e) >= 0) - { fprintf(stderr,"\n\aKey already included in key ring '%s'.\n",ringfile); - if (!keycompromised) /* allows duplicate if key compromised */ - { fclose(f); /* close key file */ - return(0); /* normal return */ + if (status >= 0) { + status = maint_update(keyfile, 0); + if (status == -7) { /* ringfile is a keyfile or + secret keyring */ + fprintf(pgpout, + "Warning: '%s' is not a public keyring\n", + keyfile); + return 0; } - } + if (status < 0) + fprintf(pgpout, LANG("\007Maintenance pass error. ")); + } + if (status < 0) { + fprintf(pgpout, LANG("\007Key signature error. ")); + errorLvl = KEY_SIGNATURE_ERROR; + } +#ifdef MACTC5 + PGPSetFinfo(keyfile,'PKey','MPGP'); +#endif + return status; + } /* Key signing */ - if (keycompromised) - fprintf(stderr,"\nAdding \"key compromise\" certificate '%s' to key ring '%s'.\n", - keyfile,ringfile); - else - fprintf(stderr,"\nAdding key certificate '%s' to key ring '%s'.\n",keyfile,ringfile); - /* The key is prepended to the ring to give it search precedence - over other keys with that same userid. */ +/*-------------------------------------------------------*/ + case 'd': + { /* disable/revoke key + Arguments: userid, keyfile + */ + + if (myArgc >= 4) + strncpy(keyfile, myArgv[3], sizeof(keyfile) - 1); + else + strcpy(keyfile, globalPubringName); + + if (myArgc >= 3) { + strcpy(mcguffin, myArgv[2]); /* Userid to sign */ + } else { + fprintf(pgpout, +LANG("\nA user ID is required to select the key you want to revoke or \ +disable. ")); + fprintf(pgpout, LANG("\nEnter user ID: ")); +#ifdef AMIGA + requesterdesc=LANG("\nEnter user ID: "); +#endif + getstring(mcguffin, 255, TRUE); /* echo keyboard */ + } + CONVERT_TO_CANONICAL_CHARSET(mcguffin); - fseek(f,file_position,SEEK_SET); /* reposition file to key */ + status = disable_key(mcguffin, keyfile); - remove(SCRATCH_KEYRING_FILENAME); - /* open file g for writing, in binary (not text) mode...*/ - if ((g = fopen(SCRATCH_KEYRING_FILENAME,"wb")) == NULL) - { fclose(f); - return(-1); - } - copyfile(f,g,-1UL); /* copy rest of file from file f to g */ - fclose(f); + if (status >= 0) { + status = maint_update(keyfile, 0); + if (status == -7) { /* ringfile is a keyfile or + secret keyring */ + fprintf(pgpout, "Warning: '%s' is not a public keyring\n", + keyfile); + return 0; + } + if (status < 0) + fprintf(pgpout, LANG("\007Maintenance pass error. ")); + } + if (status < 0) + errorLvl = KEY_SIGNATURE_ERROR; +#ifdef MACTC5 + PGPSetFinfo(keyfile,'PKey','MPGP'); +#endif + return status; + } /* Key compromise */ +/*-------------------------------------------------------*/ + case 'e': + { /* Key editing + Arguments: userid, ringfile + */ + + if (myArgc >= 4) + strncpy(ringfile, myArgv[3], sizeof(ringfile) - 1); + else /* default key ring filename */ + strcpy(ringfile, globalPubringName); + + if (myArgc >= 3) { + strcpy(mcguffin, myArgv[2]); /* Userid to edit */ + } else { + fprintf(pgpout, +LANG("\nA user ID is required to select the key you want to edit. ")); + fprintf(pgpout, LANG("\nEnter the key's user ID: ")); +#ifdef AMIGA + requesterdesc=LANG("\nEnter the key's user ID: "); +#endif + getstring(mcguffin, 255, TRUE); /* echo keyboard */ + } + CONVERT_TO_CANONICAL_CHARSET(mcguffin); - /* open file f for reading, in binary (not text) mode...*/ - if ((f = fopen(ringfile,"rb")) != NULL) - { copyfile(f,g,-1UL); /* copy rest of file from file f to g */ - fclose(f); - } - fclose(g); + status = dokeyedit(mcguffin, ringfile); - remove(ringfile); /* dangerous. sure hope rename works... */ - rename(SCRATCH_KEYRING_FILENAME,ringfile); + if (status >= 0) { + status = maint_update(ringfile, 0); + if (status == -7) + status = 0; /* ignore "not a public keyring" error */ + if (status < 0) + fprintf(pgpout, LANG("\007Maintenance pass error. ")); + } + if (status < 0) { + fprintf(pgpout, LANG("\007Keyring edit error. ")); + errorLvl = KEYRING_EDIT_ERROR; + } +#ifdef MACTC5 + { + byte ctb; + get_header_info_from_file(ringfile, &ctb, 1); + if (ctb == CTB_CERT_SECKEY) + PGPSetFinfo(ringfile,'SKey','MPGP'); + else if (ctb == CTB_CERT_PUBKEY) + PGPSetFinfo(ringfile,'PKey','MPGP'); + } +#endif + return status; + } /* Key edit */ - return(0); /* normal return */ +/*-------------------------------------------------------*/ + case 'a': + { /* Add key to key ring + Arguments: keyfile, ringfile + */ -} /* addto_keyring */ + if (myArgc < 3 && !filter_mode) + arg_error(); + if (!filter_mode) { /* Get the keyfile from args */ + strncpy(keyfile, myArgv[2], sizeof(keyfile) - 1); -/*======================================================================*/ +#ifdef MSDOS + strlwr(keyfile); +#endif + if (!file_exists(keyfile)) + default_extension(keyfile, PGP_EXTENSION); + if (!file_exists(keyfile)) { + fprintf(pgpout, + LANG("\n\007Key file '%s' does not exist.\n"), + keyfile); + errorLvl = NONEXIST_KEY_ERROR; + return -1; + } + workfile = keyfile; + + } else { + workfile = tempfile(TMP_WIPE | TMP_TMPDIR); + readPhantomInput(workfile); + } + if (myArgc < (filter_mode ? 3 : 4)) { /* default key ring + filename */ + byte ctb; + get_header_info_from_file(workfile, &ctb, 1); + if (ctb == CTB_CERT_SECKEY) + strcpy(ringfile, globalSecringName); + else + strcpy(ringfile, globalPubringName); + } else { + strncpy(ringfile, myArgv[(filter_mode ? 2 : 3)], + sizeof(ringfile) - 1); + default_extension(ringfile, PGP_EXTENSION); + } +#ifdef MSDOS + strlwr(ringfile); +#endif -int dokeygen(char *keyfile, char *numstr, char *numstr2) -/* Do an RSA key pair generation, and write them out to a pair of files. - The keyfile filename string must not have a file extension. - numstr is a decimal string, the desired bitcount for the modulus n. - numstr2 is a decimal string, the desired bitcount for the exponent e. -*/ -{ unit n[MAX_UNIT_PRECISION], e[MAX_UNIT_PRECISION], d[MAX_UNIT_PRECISION], - p[MAX_UNIT_PRECISION], q[MAX_UNIT_PRECISION], u[MAX_UNIT_PRECISION]; - char fname[64]; - char ringfile[64]; - byte iv[256]; /* for BassOmatic CFB mode, to protect RSA secret key */ - byte userid[256]; - short keybits,ebits,i; - word32 tstamp; byte *timestamp = (byte *) &tstamp; /* key certificate timestamp */ - boolean hidekey; /* TRUE iff secret key is encrypted */ + status = addto_keyring(workfile, ringfile); - strcpy(fname,keyfile); - if (strlen(fname)==0) - { fprintf(stderr,"\nKey file name is required for RSA key pair: "); - getstring(fname,sizeof(fname)-4,TRUE); - } + if (filter_mode) + rmtemp(workfile); - if (strlen(numstr)==0) - { fprintf(stderr,"\nPick your RSA key size: " - "\n 1) 288 bits- Casual grade, fast but less secure" - "\n 2) 512 bits- Commercial grade, medium speed, good security" - "\n 3) 992 bits- Military grade, very slow, highest security" - "\nChoose 1, 2, or 3, or enter desired number of bits: "); - numstr = userid; /* use userid buffer as scratchpad */ - getstring(numstr,5,TRUE); /* echo keyboard */ - } - - keybits = 0; - while ((*numstr>='0') && (*numstr<='9')) - keybits = keybits*10 + (*numstr++ - '0'); + if (status < 0) { + fprintf(pgpout, LANG("\007Keyring add error. ")); + errorLvl = KEYRING_ADD_ERROR; + } +#ifdef MACTC5 + { + byte ctb; + get_header_info_from_file(ringfile, &ctb, 1); + if (ctb == CTB_CERT_SECKEY) + PGPSetFinfo(ringfile,'SKey','MPGP'); + else if (ctb == CTB_CERT_PUBKEY) + PGPSetFinfo(ringfile,'PKey','MPGP'); + } +#endif + return status; + } /* Add key to key ring */ - /* Standard default key sizes: */ - if (keybits==1) keybits=286; /* Casual grade */ - if (keybits==2) keybits=510; /* Commercial grade */ - if (keybits==3) keybits=990; /* Military grade */ +/*-------------------------------------------------------*/ + case 'x': + { /* Extract key from key ring + Arguments: mcguffin, keyfile, ringfile + */ + + if (myArgc >= (filter_mode ? 4 : 5)) /* default key ring + filename */ + strncpy(ringfile, myArgv[(filter_mode ? 3 : 4)], + sizeof(ringfile) - 1); + else + strcpy(ringfile, globalPubringName); + + if (myArgc >= (filter_mode ? 2 : 3)) { + if (myArgv[2]) + /* Userid to extract */ + strcpy(mcguffin, myArgv[2]); + else + strcpy(mcguffin, ""); + } else { + fprintf(pgpout, +LANG("\nA user ID is required to select the key you want to extract. ")); + if (batchmode) /* not interactive, userid + must be on command line */ + return -1; + fprintf(pgpout, LANG("\nEnter the key's user ID: ")); +#ifdef AMIGA + requesterdesc=LANG("\nEnter the key's user ID: "); +#endif + getstring(mcguffin, 255, TRUE); /* echo keyboard */ + } + CONVERT_TO_CANONICAL_CHARSET(mcguffin); - /* minimum RSA keysize for BassOmatic bootstrap: */ - if (keybits<286) keybits=286; + if (!filter_mode) { + if (myArgc >= 4) + strncpy(keyfile, myArgv[3], sizeof(keyfile) - 1); + else + keyfile[0] = '\0'; - ebits = 0; /* number of bits in e */ - while ((*numstr2>='0') && (*numstr2<='9')) - ebits = ebits*10 + (*numstr2++ - '0'); + workfile = keyfile; + } else { + workfile = tempfile(TMP_WIPE | TMP_TMPDIR); + } - fprintf(stderr,"\nGenerating an RSA key with a %d-bit modulus... ",keybits); +#ifdef MSDOS + strlwr(workfile); + strlwr(ringfile); +#endif - fprintf(stderr,"\nEnter a user ID for your public key (your name): "); - getstring(userid,255,TRUE); /* echo keyboard input */ - CToPascal(userid); /* convert to length-prefixed string */ + default_extension(ringfile, PGP_EXTENSION); - { char passphrase[256]; - fprintf(stderr,"\nYou need a pass phrase to protect your RSA secret key. "); - hidekey = (getpassword(passphrase,2,0x0f) > 0); - /* init CFB BassOmatic key */ - if (hidekey) - { fill0(iv,256); /* define initialization vector IV as 0 */ - if ( initcfb(iv,passphrase,string_length(passphrase),FALSE) < 0 ) - return(-1); - burn(passphrase); /* burn sensitive data on stack */ + status = extract_from_keyring(mcguffin, workfile, + ringfile, (filter_mode ? FALSE : + emit_radix_64)); + + if (status < 0) { + fprintf(pgpout, LANG("\007Keyring extract error. ")); + errorLvl = KEYRING_EXTRACT_ERROR; + if (filter_mode) + rmtemp(workfile); + return status; + } + if (filter_mode && !status) { + if (emit_radix_64) { + /* NULL for outputfile means write to stdout */ + if (armor_file(workfile, NULL, NULL, NULL, FALSE) != 0) { + errorLvl = UNKNOWN_FILE_ERROR; + return -1; + } + } else { + if (writePhantomOutput(workfile) < 0) { + errorLvl = UNKNOWN_FILE_ERROR; + return -1; + } } - } - - fprintf(stderr,"\nNote that key generation is a VERY lengthy process.\n"); - - if (keygen(n,e,d,p,q,u,keybits,ebits) < 0) - { fprintf(stderr,"\n\aKeygen failed!\n"); - return(-1); /* error return */ - } - - if (verbose) - { - fprintf(stderr,"Key ID "); - showkeyID2(n); fputc('\n',stderr); + rmtemp(workfile); + } +#ifdef MACTC5 + if (status) + return KEYRING_EXTRACT_ERROR; + if ((!emit_radix_64)&&(strlen(keyfile)>0)) { + byte ctb; + get_header_info_from_file(keyfile, &ctb, 1); + if (ctb == CTB_CERT_SECKEY) + PGPSetFinfo(ringfile,'SKey','MPGP'); + else if (ctb == CTB_CERT_PUBKEY) + PGPSetFinfo(ringfile,'PKey','MPGP'); + } +#endif + return 0; + } /* Extract key from key ring */ - mp_display(" modulus n = ",n); - mp_display("exponent e = ",e); +/*-------------------------------------------------------*/ + case 'r': + { /* Remove keys or selected key signatures from userid keys + Arguments: userid, ringfile + */ + + if (myArgc >= 4) + strcpy(ringfile, myArgv[3]); + else /* default key ring filename */ + strcpy(ringfile, globalPubringName); + + if (myArgc >= 3) { + strcpy(mcguffin, myArgv[2]); /* Userid to work on */ + } else { + if (sign_flag) { + fprintf(pgpout, +LANG("\nA user ID is required to select the public key you want to\n\ +remove certifying signatures from. ")); + } else { + fprintf(pgpout, +LANG("\nA user ID is required to select the key you want to remove. ")); + } + if (batchmode) /* not interactive, userid must be on + command line */ + return -1; + fprintf(pgpout, LANG("\nEnter the key's user ID: ")); +#ifdef AMIGA + requesterdesc=LANG("\nEnter the key's user ID: "); +#endif + getstring(mcguffin, 255, TRUE); /* echo keyboard */ + } + CONVERT_TO_CANONICAL_CHARSET(mcguffin); - mp_display("exponent d = ",d); - mp_display(" prime p = ",p); - mp_display(" prime q = ",q); - mp_display(" inverse u = ",u); - } +#ifdef MSDOS + strlwr(ringfile); +#endif + if (!file_exists(ringfile)) + default_extension(ringfile, PGP_EXTENSION); - get_timestamp(timestamp); /* Timestamp when key was generated */ + if (sign_flag) { /* Remove signatures */ + if (remove_sigs(mcguffin, ringfile) < 0) { + fprintf(pgpout, LANG("\007Key signature remove error. ")); + errorLvl = KEYSIG_REMOVE_ERROR; + return -1; + } + } else { /* Remove keyring */ +#ifdef MACTC5 + if (remove_from_keyring( NULL, mcguffin, ringfile, + (boolean)!strcmp(ringfile, globalPubringName))) { +#else + if (remove_from_keyring(NULL, mcguffin, ringfile, + (boolean) (myArgc < 4)) < 0) { +#endif + fprintf(pgpout, LANG("\007Keyring remove error. ")); + errorLvl = KEYRING_REMOVE_ERROR; + return -1; + } + } +#ifdef MACTC5 + { + byte ctb; + get_header_info_from_file(ringfile, &ctb, 1); + if (ctb == CTB_CERT_SECKEY) + PGPSetFinfo(ringfile,'SKey','MPGP'); + else if (ctb == CTB_CERT_PUBKEY) + PGPSetFinfo(ringfile,'PKey','MPGP'); + PGPSetFinfo(globalPubringName,'PKey','MPGP'); + } +#endif + return 0; + } /* remove key signatures from userid */ - fputc('\a',stderr); /* sound the bell when done with lengthy process */ +/*-------------------------------------------------------*/ + case 'v': + case 'V': /* -kvv */ + { /* View or remove key ring entries, + with userid match + Arguments: userid, ringfile + */ + + if (myArgc < 4) /* default key ring filename */ + strcpy(ringfile, globalPubringName); + else + strcpy(ringfile, myArgv[3]); + + if (myArgc > 2) { + strcpy(mcguffin, myArgv[2]); + if (strcmp(mcguffin, "*") == 0) + mcguffin[0] = '\0'; + } else { + *mcguffin = '\0'; + } - force_extension(fname,SEC_EXTENSION); - writekeyfile(fname,hidekey,timestamp,userid,n,e,d,p,q,u); - force_extension(fname,PUB_EXTENSION); - writekeyfile(fname,FALSE,timestamp,userid,n,e,NULL,NULL,NULL,NULL); - - if (hidekey) /* done with Bassomatic to protect RSA secret key */ - closebass(); + if ((myArgc == 3) && has_extension(myArgv[2], PGP_EXTENSION)) { + strcpy(ringfile, myArgv[2]); + mcguffin[0] = '\0'; + } + CONVERT_TO_CANONICAL_CHARSET(mcguffin); - mp_burn(d); /* burn sensitive data on stack */ - mp_burn(p); /* burn sensitive data on stack */ - mp_burn(q); /* burn sensitive data on stack */ - mp_burn(u); /* burn sensitive data on stack */ - mp_burn(e); /* burn sensitive data on stack */ - mp_burn(n); /* burn sensitive data on stack */ - burn(iv); /* burn sensitive data on stack */ +#ifdef MSDOS + strlwr(ringfile); +#endif + if (!file_exists(ringfile)) + default_extension(ringfile, PGP_EXTENSION); - force_extension(fname,PUB_EXTENSION); - buildfilename(ringfile,PUBLIC_KEYRING_FILENAME); - fprintf(stderr,"\nAdd public key to key ring '%s' (y/N)? ",ringfile); - if (getyesno('n')) - addto_keyring(fname,ringfile); - force_extension(fname,SEC_EXTENSION); - buildfilename(ringfile,SECRET_KEYRING_FILENAME); - fprintf(stderr,"Add secret key to key ring '%s' (y/N)? ",ringfile); - if (getyesno('n')) - addto_keyring(fname,ringfile); + /* If a second 'v' (keychar = V), show signatures too */ + status = view_keyring(mcguffin, ringfile, + (boolean) (keychar == 'V'), c_flag); + if (status < 0) { + fprintf(pgpout, LANG("\007Keyring view error. ")); + errorLvl = KEYRING_VIEW_ERROR; + } +#ifdef MACTC5 + { + byte ctb; + get_header_info_from_file(ringfile, &ctb, 1); + if (ctb == CTB_CERT_SECKEY) + PGPSetFinfo(ringfile,'SKey','MPGP'); + else if (ctb == CTB_CERT_PUBKEY) + PGPSetFinfo(ringfile,'PKey','MPGP'); + } +#endif + return status; + } /* view key ring entries, with userid match */ - /* Force initialization of cryptographically strong pseudorandom - number generator seed file for later use... - */ - strong_pseudorandom(iv,1); + default: + arg_error(); + } + return 0; +} /* do_keyopt */ - return(0); /* normal return */ -} /* dokeygen */ +/* comes here if user made a boo-boo. */ +void user_error() +{ + fprintf(pgpout, LANG("\nFor a usage summary, type: pgp -h\n")); + fprintf(pgpout, + LANG("For more detailed help, consult the PGP User's Guide.\n")); + exitPGP(errorLvl ? errorLvl : 127); /* error exit */ +} +#if defined(DEBUG) && defined(linux) +#include +#endif -/*======================================================================*/ +/* + * exitPGP: wipes and removes temporary files, also tries to wipe + * the stack. + */ +void exitPGP(int returnval) +{ + char buf[STACK_WIPE]; + struct hashedpw *hpw; + if (verbose) + fprintf(pgpout, "exitPGP: exitcode = %d\n", returnval); + for (hpw = passwds; hpw; hpw = hpw->next) + memset(hpw->hash, 0, sizeof(hpw->hash)); + for (hpw = keypasswds; hpw; hpw = hpw->next) + memset(hpw->hash, 0, sizeof(hpw->hash)); +#ifdef MACTC5 + mac_cleanup_tmpf(); +#else + cleanup_tmpf(); +#endif + /* Merge any entropy we collected into the randseed.bin file */ + if (cryptRandOpen((struct IdeaCfbContext *)0) >= 0) + cryptRandSave((struct IdeaCfbContext *)0); +#if defined(DEBUG) && defined(linux) + if (verbose) { + struct mstats mstat; + mstat = mstats(); + printf("%d chunks used (%d bytes) %d bytes total\n", + mstat.chunks_used, mstat.bytes_used, mstat.bytes_total); + } +#endif + memset(buf, 0, sizeof(buf)); /* wipe stack */ +#ifdef VMS +/* + * Fake VMS style error returns with severity in bottom 3 bits + */ + if (returnval) + returnval = (returnval << 3) | 0x10000002; + else + returnval = 0x10000001; +#endif /* VMS */ + exit(returnval); +} -void main(int argc, char *argv[]) -{ char keyfile[64], plainfile[64], cipherfile[64], ringfile[64], tempfile[64]; - int status,i; - boolean nestflag = FALSE; - boolean uu_emit = FALSE; - boolean wipeflag = FALSE; - byte ctb; - byte header[6]; /* used to classify file type at the end. */ - char mcguffin[256]; /* userid search tag */ +static void arg_error() +{ + signon_msg(); + fprintf(pgpout, LANG("\nInvalid arguments.\n")); + errorLvl = BAD_ARG_ERROR; + user_error(); +} -#ifdef DEBUG1 - verbose = TRUE; +/* + * Check for language specific help files in PGPPATH, then the system + * directory. If that fails, check for the default pgp.hlp, again + * first a private copy, then the system-wide one. + * + * System-wide copies currently only exist on Unix. + */ +static void build_helpfile(char *helpfile, char const *extra) +{ + if (strcmp(language, "en")) { + buildfilename(helpfile, language); + strcat(helpfile, extra); + force_extension(helpfile, HLP_EXTENSION); + if (file_exists(helpfile)) + return; +#ifdef PGP_SYSTEM_DIR + strcpy(helpfile, PGP_SYSTEM_DIR); + strcat(helpfile, language); + strcat(helpfile, extra); + force_extension(helpfile, HLP_EXTENSION); + if (file_exists(helpfile)) + return; #endif + } + buildfilename(helpfile, "pgp"); + strcat(helpfile, extra); + force_extension(helpfile, HLP_EXTENSION); +#ifdef PGP_SYSTEM_DIR + if (file_exists(helpfile)) + return; + strcpy(helpfile, PGP_SYSTEM_DIR); + strcat(helpfile, "pgp"); + strcat(helpfile, extra); + force_extension(helpfile, HLP_EXTENSION); +#endif +} - fprintf(stderr,"Pretty Good Privacy 1.0 - RSA public key cryptography for the masses.\n" - "(c) Copyright 1990 Philip Zimmermann, Phil's Pretty Good Software. 5 Jun 91\n"); - - if (argc <= 1) - { fprintf(stderr, - "\nFor details on free licensing and distribution, see the PGP User's Guide." - "\nFor other cryptography products and custom development services, contact:" - "\nPhilip Zimmermann, 3021 11th St, Boulder CO 80304 USA, phone (303)444-4541" - ); - goto usage; +static void usage() +{ + char helpfile[MAX_PATH]; + char *tmphelp = helpfile; + extern unsigned char *ext_c_ptr; + + signon_msg(); + build_helpfile(helpfile, ""); + + if (ext_c_ptr) { + /* conversion to external format necessary */ + tmphelp = tempfile(TMP_TMPDIR); + CONVERSION = EXT_CONV; + if (copyfiles_by_name(helpfile, tmphelp) < 0) { + rmtemp(tmphelp); + tmphelp = helpfile; } + CONVERSION = NO_CONV; + } + /* built-in help if pgp.hlp is not available */ + if (more_file(tmphelp, FALSE) < 0) + fprintf(pgpout, LANG("\nUsage summary:\ +\nTo encrypt a plaintext file with recipent's public key, type:\ +\n pgp -e textfile her_userid [other userids] (produces textfile.pgp)\ +\nTo sign a plaintext file with your secret key:\ +\n pgp -s textfile [-u your_userid] (produces textfile.pgp)\ +\nTo sign a plaintext file with your secret key, and then encrypt it\ +\n with recipent's public key, producing a .pgp file:\ +\n pgp -es textfile her_userid [other userids] [-u your_userid]\ +\nTo encrypt with conventional encryption only:\ +\n pgp -c textfile\ +\nTo decrypt or check a signature for a ciphertext (.pgp) file:\ +\n pgp ciphertextfile [-o plaintextfile]\ +\nTo produce output in ASCII for email, add the -a option to other options.\ +\nTo generate your own unique public/secret key pair: pgp -kg\ +\nFor help on other key management functions, type: pgp -k\n")); + if (ext_c_ptr) + rmtemp(tmphelp); + exit(BAD_ARG_ERROR); /* error exit */ +} - /* Make sure arguments will fit into filename strings: */ - for (i = 1; i <= argc; i++) - { - if (strlen(argv[i]) >= sizeof(cipherfile)-4) - { - fprintf(stderr, "\aInvalid filename: [%s] too long\n", argv[i] ); - goto user_error; - } +static void key_usage() +{ + char helpfile[MAX_PATH]; + char *tmphelp = helpfile; + extern unsigned char *ext_c_ptr; + + signon_msg(); + build_helpfile(helpfile, "key"); + + if (ext_c_ptr) { + /* conversion to external format necessary */ + tmphelp = tempfile(TMP_TMPDIR); + CONVERSION = EXT_CONV; + if (copyfiles_by_name(helpfile, tmphelp) < 0) { + rmtemp(tmphelp); + tmphelp = helpfile; } + CONVERSION = NO_CONV; + } + /* built-in help if key.hlp is not available */ + if (more_file(tmphelp, FALSE) < 0) + /* only use built-in help if there is no helpfile */ + fprintf(pgpout, LANG("\nKey management functions:\ +\nTo generate your own unique public/secret key pair:\ +\n pgp -kg\ +\nTo add a key file's contents to your public or secret key ring:\ +\n pgp -ka keyfile [keyring]\ +\nTo remove a key or a user ID from your public or secret key ring:\ +\n pgp -kr userid [keyring]\ +\nTo edit your user ID or pass phrase:\ +\n pgp -ke your_userid [keyring]\ +\nTo extract (copy) a key from your public or secret key ring:\ +\n pgp -kx userid keyfile [keyring]\ +\nTo view the contents of your public key ring:\ +\n pgp -kv[v] [userid] [keyring]\ +\nTo check signatures on your public key ring:\ +\n pgp -kc [userid] [keyring]\ +\nTo sign someone else's public key on your public key ring:\ +\n pgp -ks her_userid [-u your_userid] [keyring]\ +\nTo remove selected signatures from a userid on a keyring:\ +\n pgp -krs userid [keyring]\ +\n")); + if (ext_c_ptr) + rmtemp(tmphelp); + exit(BAD_ARG_ERROR); /* error exit */ +} - if (argv[1][0] == '-') - { - if (strhas(argv[1],'l')) - verbose = TRUE; - - nestflag = strhas(argv[1],'n'); - - uu_emit = strhas(argv[1],'u'); - - wipeflag = strhas(argv[1],'w'); - - /*-------------------------------------------------------*/ - if ( (argc >= 3) - && strhasany(argv[1],"sS") && strhasany(argv[1],"eE") ) - { /* Sign AND encrypt file */ - /* Arguments: plainfile, her_userid, your_userid, cipherfile */ - boolean separate_signature = FALSE; - - strcpy( plainfile, argv[2] ); - - if (argc>=6) /* default signature file extension */ - { strcpy( cipherfile, argv[5] ); - default_extension( cipherfile, CTX_EXTENSION ); - } - else - { /* Default the signature file name */ - strcpy( cipherfile, plainfile ); - /* ...but replace file extension: */ - force_extension( cipherfile, CTX_EXTENSION ); - } - - if (strcmp( plainfile, cipherfile ) == 0) - { fprintf(stderr, "\aFile [%s] must be specified just once.\n", plainfile ); - goto user_error; /* same filenames for both files */ - } - - if (argc>=5) - { strcpy( mcguffin, argv[4] ); /* Userid of signer */ - translate_spaces( mcguffin ); /* change all '_' to ' ' */ - } - else - { fprintf(stderr, "\nEnter userid to look up your secret key for signature: "); - getstring( mcguffin, 255, TRUE ); /* echo keyboard */ - } - - if (nestflag) /* user thinks this file has nested info */ - { get_header_info_from_file( plainfile, &ctb, 1); - if (!legal_ctb(ctb)) - { nestflag = FALSE; - fprintf(stderr,"\n\aNo nestable data in plaintext file '%s'.\n",plainfile); - } - } - - status = signfile( nestflag, separate_signature, - mcguffin, plainfile, SCRATCH_CTX_FILENAME ); - - if (status < 0) /* signfile failed */ - { fprintf(stderr, "\aSignature error\n" ); - goto user_error; - } - - if (wipeflag) - { wipefile(plainfile); /* destroy every trace of plaintext */ - remove(plainfile); - fprintf(stderr,"\nFile %s wiped and deleted. ",plainfile); - } - - if (argc>=4) - { strcpy( mcguffin, argv[3] ); /* Userid of recipient */ - translate_spaces( mcguffin ); /* change all '_' to ' ' */ - } - else - { fprintf(stderr, "\nEnter userid to look up recipient's public key: "); - getstring( mcguffin, 255, TRUE ); /* echo keyboard */ - } - - /* Indicate that encrypted data has nested signature: */ - - status = encryptfile( TRUE, mcguffin, SCRATCH_CTX_FILENAME, cipherfile ); - - wipefile( SCRATCH_CTX_FILENAME ); - remove( SCRATCH_CTX_FILENAME ); - - if (status < 0) - { fprintf(stderr, "\aEncryption error\n" ); - goto user_error; - } - - if (uu_emit) - { status = uue_file(cipherfile, SCRATCH_CTX_FILENAME); - remove(cipherfile); /* dangerous. sure hope rename works... */ - rename(SCRATCH_CTX_FILENAME, cipherfile); - } - - if (!verbose) /* if other filename messages were supressed */ - fprintf(stderr,"\nCiphertext file: %s ", cipherfile); - - exit(0); - } /* Sign AND encrypt file */ - - - /*-------------------------------------------------------*/ - if ( (argc >= 3) && strhasany(argv[1],"sS") ) - { /* Sign file - Arguments: plaintextfile, your_userid, signedtextfile - Two kinds of signature: full signature certificate, - or just an RSA-signed message digest. - */ - - boolean separate_signature = FALSE; - - separate_signature = strhas( argv[1], 'b' ); - - strcpy( plainfile, argv[2] ); - - if (argc>=5) /* default signature file extension */ - { strcpy( cipherfile, argv[4] ); - default_extension( cipherfile, CTX_EXTENSION ); - } - else - { /* Default the signature file name */ - strcpy( cipherfile, plainfile ); - /* ...but replace file extension: */ - force_extension( cipherfile, CTX_EXTENSION ); - } - - if (strcmp( plainfile, cipherfile ) == 0) - { fprintf(stderr, "\aFile [%s] must be specified just once.\n", plainfile ); - goto user_error; /* same filenames for both files */ - } - - if (argc>=4) - { strcpy( mcguffin, argv[3] ); /* Userid of signer */ - translate_spaces( mcguffin ); /* change all '_' to ' ' */ - } - else - { fprintf(stderr, "\nEnter userid to look up your secret key for signature: "); - getstring( mcguffin, 255, TRUE ); /* echo keyboard */ - } - - if (nestflag) /* user thinks this file has nested info */ - { get_header_info_from_file( plainfile, &ctb, 1); - if (!legal_ctb(ctb)) - { nestflag = FALSE; - fprintf(stderr,"\n\aNo nestable data in plaintext file '%s'.\n",plainfile); - } - } - - status = signfile( nestflag, separate_signature, - mcguffin, plainfile, cipherfile ); - - if (status < 0) /* signfile failed */ - { fprintf(stderr, "\aSignature error\n" ); - goto user_error; - } - - if (uu_emit) - { status = uue_file(cipherfile, SCRATCH_CTX_FILENAME); - remove(cipherfile); /* dangerous. sure hope rename works... */ - rename(SCRATCH_CTX_FILENAME, cipherfile); - } - - if (!verbose) /* if other filename messages were supressed */ - fprintf(stderr,"\nSignature file: %s ", cipherfile); - - exit(0); - } /* Sign file */ - - - /*-------------------------------------------------------*/ - if ( (argc >= 3) && strhasany(argv[1],"eE") ) - { /* Encrypt file - Arguments: plaintextfile, her_userid, ciphertextfile - */ - - strcpy( plainfile, argv[2] ); - - if (argc >= 5) /* default cipher file extension */ - { strcpy( cipherfile, argv[4] ); - default_extension( cipherfile, CTX_EXTENSION ); - } - else - { /* Default the cipherfile name */ - strcpy( cipherfile, plainfile ); - force_extension( cipherfile, CTX_EXTENSION ); - } - - if (strcmp( plainfile, cipherfile) == 0) - { fprintf(stderr, "\aFile [%s] must be specified just once.\n", plainfile ); - goto user_error; /* same filenames for both files */ - } - - if (argc >= 4) - { strcpy( mcguffin, argv[3] ); /* Userid of recipient */ - translate_spaces( mcguffin ); /* change all '_' to ' ' */ - } - else - { fprintf(stderr, "\nEnter userid to look up recipient's public key: "); - getstring( mcguffin, 255, TRUE ); /* echo keyboard */ - } - - if (nestflag) /* user thinks this file has nested info */ - { get_header_info_from_file( plainfile, &ctb, 1); - if (!legal_ctb(ctb)) - { nestflag = FALSE; - fprintf(stderr,"\n\aNo nestable data in plaintext file '%s'.\n",plainfile); - } - } - - if (!nestflag) - { /* Prepend CTB_LITERAL byte to plaintext file. - --sure wish this pass could be optimized away. */ - strcpy( tempfile, plainfile ); - strcpy( plainfile, SCRATCH_PTX_FILENAME ); - status = make_literal( tempfile, plainfile ); - } - status = encryptfile( nestflag, mcguffin, plainfile, cipherfile ); - - if (!nestflag) - { wipefile( SCRATCH_PTX_FILENAME ); - remove( SCRATCH_PTX_FILENAME ); - strcpy( plainfile, tempfile ); - } - - if (status < 0) /* encryptfile failed */ - { fprintf(stderr, "\aEncryption error\n" ); - goto user_error; - } - - if (wipeflag) - { wipefile(plainfile); /* destroy every trace of plaintext */ - remove(plainfile); - fprintf(stderr,"\nFile %s wiped and deleted. ",plainfile); - } - - if (uu_emit) - { status = uue_file(cipherfile, SCRATCH_CTX_FILENAME); - remove(cipherfile); /* dangerous. sure hope rename works... */ - rename(SCRATCH_CTX_FILENAME, cipherfile); - } - - if (!verbose) /* if other filename messages were supressed */ - fprintf(stderr,"\nCiphertext file: %s ", cipherfile); - - exit(0); - } /* Encrypt file */ - - - /*-------------------------------------------------------*/ - if ( (argc >= 3) && strhasany(argv[1],"cC") ) - { /* Encrypt file with BassOmatic only - Arguments: plaintextfile, ciphertextfile - */ - - strcpy( plainfile, argv[2] ); - - if (argc >= 4) /* default cipher file extension */ - { strcpy( cipherfile, argv[3] ); - default_extension( cipherfile, CTX_EXTENSION ); - } - else - { /* Default the cipherfile name */ - strcpy( cipherfile, plainfile ); - force_extension( cipherfile, CTX_EXTENSION ); - } - - if (strcmp( plainfile, cipherfile) == 0) - { fprintf(stderr, "\aFile [%s] must be specified just once.\n", plainfile ); - goto user_error; /* same filenames for both files */ - } - - if (nestflag) /* user thinks this file has nested info */ - { get_header_info_from_file( plainfile, &ctb, 1); - if (!legal_ctb(ctb)) - { nestflag = FALSE; - fprintf(stderr,"\n\aNo nestable data in plaintext file '%s'.\n",plainfile); - } - } - - if (!nestflag) - { /* Prepend CTB_LITERAL byte to plaintext file. - --sure wish this pass could be optimized away. */ - strcpy( tempfile, plainfile ); - strcpy( plainfile, SCRATCH_PTX_FILENAME ); - status = make_literal( tempfile, plainfile ); - } - - status = bass_encryptfile( nestflag, plainfile, cipherfile ); - - if (!nestflag) - { wipefile( SCRATCH_PTX_FILENAME ); - remove( SCRATCH_PTX_FILENAME ); - strcpy( plainfile, tempfile ); - } - - if (status < 0) /* encryptfile failed */ - { fprintf(stderr, "\aEncryption error\n" ); - goto user_error; - } - - if (wipeflag) - { wipefile(plainfile); /* destroy every trace of plaintext */ - remove(plainfile); - fprintf(stderr,"\nFile %s wiped and deleted. ",plainfile); - } - - if (uu_emit) - { status = uue_file(cipherfile, SCRATCH_CTX_FILENAME); - remove(cipherfile); /* dangerous. sure hope rename works... */ - rename(SCRATCH_CTX_FILENAME, cipherfile); - } - - if (!verbose) /* if other filename messages were supressed */ - fprintf(stderr,"\nCiphertext file: %s ", cipherfile); - - exit(0); - } /* Encrypt file with BassOmatic only */ - - - /*-------------------------------------------------------*/ - if (argv[1][1] == 'k') - { /* Key generation - Arguments: keyfile, bitcount, bitcount - */ - char keyfile[64], keybits[6], ebits[6]; - - if (argc > 2) - strcpy( keyfile, argv[2] ); - else - strcpy( keyfile, "" ); - - if (argc > 3) - strncpy( keybits, argv[3], sizeof(keybits)-1 ); - else - strcpy( keybits, "" ); - - if (argc > 4) - strncpy( ebits, argv[4], sizeof(ebits)-1 ); - else - strcpy( ebits, "" ); - - status = dokeygen( keyfile, keybits, ebits ); - - if (status < 0) - { fprintf(stderr, "\aKeygen error. " ); - goto user_error; - } - exit(0); - } /* Key generation */ - - /*-------------------------------------------------------*/ - if ((argc >= 3) && (argv[1][1] == 'a')) - { /* Add key to key ring - Arguments: keyfile, ringfile - */ - if (argc < 4) /* default key ring filename */ - buildfilename( ringfile, PUBLIC_KEYRING_FILENAME ); - else - strncpy( ringfile, argv[3], sizeof(ringfile)-1 ); - strncpy( keyfile, argv[2], sizeof(keyfile)-1 ); - - strlwr( keyfile ); - strlwr( ringfile ); - if (! file_exists( keyfile )) - default_extension( keyfile, PUB_EXTENSION ); - - if (strcontains( keyfile, SEC_EXTENSION )) - force_extension( ringfile, SEC_EXTENSION ); - else - force_extension( ringfile, PUB_EXTENSION ); - - if (! file_exists( keyfile )) - { fprintf(stderr, "\n\aKey file '%s' does not exist.\n", keyfile ); - goto user_error; - } - - status = addto_keyring( keyfile, ringfile ); - - if (status < 0) - { fprintf(stderr, "\aKeyring add error. " ); - goto user_error; - } - exit(0); - } /* Add key to key ring */ - - /*-------------------------------------------------------*/ - if ((argc >= 2) - && strhasany( argv[1], "vr" ) ) - { /* View or remove key ring entries, with userid match - Arguments: userid, ringfile - */ - if (argc < 4) /* default key ring filename */ - buildfilename( ringfile, PUBLIC_KEYRING_FILENAME ); - else - strcpy( ringfile, argv[3] ); - - strcpy( mcguffin, argv[2] ); - if (strcmp( mcguffin, "*" ) == 0) - strcpy( mcguffin, "" ); - - translate_spaces( mcguffin ); /* change all '_' to ' ' */ - - if ((argc < 4) - && (strcontains( argv[2], PUB_EXTENSION ) - || strcontains( argv[2], SEC_EXTENSION ))) - { strcpy( ringfile, argv[2] ); - strcpy( mcguffin, "" ); - } - - strlwr( ringfile ); - if (! file_exists( ringfile )) - default_extension( ringfile, PUB_EXTENSION ); - - if (strhas( argv[1], 'v' )) - if (view_keyring( mcguffin, ringfile ) < 0) - { fprintf(stderr, "\aKeyring view error. " ); - goto user_error; - } - if (strhas( argv[1], 'r' )) - if (remove_from_keyring( NULL, mcguffin, ringfile ) < 0) - { fprintf(stderr, "\aKeyring remove error. " ); - goto user_error; - } - exit(0); - } /* view or remove key ring entries, with userid match */ - /*-------------------------------------------------------*/ - - fprintf(stderr, "\aUnrecognizable parameters. " ); - goto user_error; - } /* -options specified */ - - - /*---------------------------------------------------------*/ - /* no options specified */ - - if (argc >= 2) - { /* Decrypt file - Arguments: ciphertextfile, plaintextfile - */ - strcpy( cipherfile, argv[1] ); - default_extension( cipherfile, CTX_EXTENSION ); - - if (argc >= 3) - { strcpy( plainfile, argv[2] ); - default_extension( plainfile, "." ); - } - else - { /* Default the plaintext file name */ - strcpy( plainfile, argv[1] ); - force_extension( plainfile, "." ); - } - - if (strcmp( plainfile, cipherfile ) == 0) - { fprintf(stderr, "\aFile '%s' cannot be both input and output file.\n", plainfile ); - goto user_error; /* error: same filenames for both files */ - } - - if (! file_exists( cipherfile )) - { fprintf(stderr, "\a\nError: Cipher or signature file '%s' does not exist.\n", - cipherfile); - goto user_error; - } - - get_header_info_from_file( cipherfile, header, 4 ); - if (!is_ctb(header[0]) && is_uufile(cipherfile)) - { - if (verbose) fprintf(stderr,"uudecoding %s...",cipherfile); - status = uud_file(cipherfile, SCRATCH_CTX_FILENAME); - if (status==0) - { if (verbose) fprintf(stderr,"...done.\n"); - remove( cipherfile ); /* dangerous. sure hope rename works... */ - rename( SCRATCH_CTX_FILENAME, cipherfile ); - } - else fprintf(stderr,"\n\aError: uudecode failed for file %s\n",cipherfile); - } - - /*---------------------------------------------------------*/ - do /* while nested parsable info present */ - { - if (get_header_info_from_file( cipherfile, &ctb, 1) < 0) - { fprintf(stderr,"\n\aCan't open ciphertext file '%s'\n",cipherfile); - goto user_error; - } - - if (!is_ctb(ctb)) /* not a real CTB -- complain */ - goto reject; - - /* PKE is Public Key Encryption */ - if (is_ctb_type( ctb, CTB_PKE_TYPE )) - { - fprintf(stderr,"\nFile is encrypted. Secret key is required to read it. "); - - status = decryptfile( cipherfile, plainfile ); - - if (status < 0) /* error return */ - goto user_error; - if (status < 1) /* output file has no nested info? */ - break; /* no nested parsable info. exit loop. */ - - /* Nested parsable info indicated. Process it. */ - wipefile( SCRATCH_CTX_FILENAME ); - remove( SCRATCH_CTX_FILENAME ); - rename( plainfile, SCRATCH_CTX_FILENAME ); - strcpy( cipherfile, SCRATCH_CTX_FILENAME ); - continue; /* skip rest of loop */ - } /* outer CTB is PKE type */ - - - if (is_ctb_type( ctb, CTB_SKE_TYPE )) - { - fprintf(stderr,"\nFile has signature. Public key is required to check signature. "); - - status = check_signaturefile( cipherfile, plainfile ); - - if (status < 0) /* error return */ - goto user_error; - - if (status < 1) /* output file has no nested info? */ - break; /* no nested parsable info. exit loop. */ - - /* Nested parsable info indicated. Process it. */ - /* Destroy signed plaintext, if it's in a scratchfile. */ - wipefile( SCRATCH_CTX_FILENAME ); - remove( SCRATCH_CTX_FILENAME ); - rename( plainfile, SCRATCH_CTX_FILENAME ); - strcpy( cipherfile, SCRATCH_CTX_FILENAME ); - continue; /* skip rest of loop */ - } /* outer CTB is SKE type */ - - - if (ctb == CTB_CKE) - { /* Conventional Key Encrypted ciphertext. */ - fprintf(stderr,"\nFile is conventionally encrypted. Pass phrase required to read it. "); - status = bass_decryptfile( cipherfile, plainfile ); - if (status < 0) /* error return */ - goto user_error; - if (status < 1) /* output file has no nested info? */ - break; /* no nested parsable info. exit loop. */ - /* Nested parsable info indicated. Process it. */ - wipefile( SCRATCH_CTX_FILENAME ); - remove( SCRATCH_CTX_FILENAME ); - rename( plainfile, SCRATCH_CTX_FILENAME ); - strcpy( cipherfile, SCRATCH_CTX_FILENAME ); - continue; /* skip rest of loop */ - } /* CTB is CKE type */ - - - if (is_ctb_type( ctb, CTB_COMPRESSED_TYPE )) - { /* Compressed text. */ - status = decompress_file( cipherfile, plainfile ); - if (status < 0) /* error return */ - goto user_error; - /* Always assume nested information... */ - /* Destroy compressed plaintext, if it's in a scratchfile. */ - wipefile( SCRATCH_CTX_FILENAME ); - remove( SCRATCH_CTX_FILENAME ); - rename( plainfile, SCRATCH_CTX_FILENAME ); - strcpy( cipherfile, SCRATCH_CTX_FILENAME ); - continue; /* skip rest of loop */ - } /* CTB is COMPRESSED type */ - - - if (is_ctb_type( ctb, CTB_LITERAL_TYPE )) - { /* Raw plaintext. Just copy it. No more nesting. */ - /* Strip off CTB_LITERAL prefix byte from file: */ - status = strip_literal( cipherfile, plainfile ); - break; /* no nested parsable info. exit loop. */ - } /* CTB is LITERAL type */ - - - if ((ctb == CTB_CERT_SECKEY) - || (ctb == CTB_CERT_PUBKEY)) - { /* Key ring. View it. */ - fprintf(stderr, "\nFile contains key(s). Contents follow..." ); - - if (view_keyring( NULL, cipherfile ) < 0) - goto user_error; - - /* No output file--what should we do with plainfile? - We know that we have already prevented original - cipher filename from being same as plain filename. - */ - - if (strcmp( cipherfile, SCRATCH_CTX_FILENAME ) == 0) - { /* key was nested in signed or enciphered file */ - remove( plainfile ); - rename( cipherfile, plainfile ); - } - exit(0); /* no nested parsable info. */ - /* strcpy(plainfile,""); /* no further nesting */ - /* break; /* no nested parsable info. exit loop. */ - } /* key ring. view it. */ - -reject: fprintf(stderr,"\a\nError: '%s' is not a cipher, signature, or key file.\n", - cipherfile); - goto user_error; - - } while (TRUE); - - /* No more nested parsable information */ - - /* Destroy any sensitive information in scratchfile: */ - wipefile( SCRATCH_CTX_FILENAME ); - remove( SCRATCH_CTX_FILENAME ); - - if (!verbose) /* if other filename messages were supressed */ - fprintf(stderr,"\nPlaintext filename: %s ", plainfile); - - - /*---------------------------------------------------------*/ - - /* One last thing-- let's attempt to classify some of the more - frequently occurring cases of plaintext output files, as an - aid to the user. - - For example, if output file is a public key, it should have - the right extension on the filename. - - Also, it will likely be common to encrypt PKZIP files, so - they should be renamed with the .zip extension. - */ - get_header_info_from_file( plainfile, header, 4 ); - - if (header[0] == CTB_CERT_PUBKEY) - { /* Special case--may be public key, worth renaming */ - fprintf(stderr, "\nPlaintext file '%s' looks like it contains a public key.", - plainfile ); - maybe_force_extension( plainfile, PUB_EXTENSION ); - } /* Possible public key output file */ - - else - if (pkzipSignature( header )) - { /* Special case--may be a PKZIP file, worth renaming */ - fprintf(stderr, "\nPlaintext file '%s' looks like a PKZIP file.", - plainfile ); - maybe_force_extension( plainfile, ".zip" ); - } /* Possible PKZIP output file */ - - else - if ((header[0] == CTB_PKE) - || (header[0] == CTB_SKE) - || (header[0] == CTB_CKE)) - { /* Special case--may be another ciphertext file, worth renaming */ - fprintf(stderr, "\n\aOutput file '%s' may contain more ciphertext or signature.", - plainfile ); - maybe_force_extension( plainfile, ".ctx" ); - } /* Possible ciphertext output file */ - - exit(0); /* no nested parsable info. */ - - } /* Decrypt file, or check signature, or show key */ - - -user_error: /* comes here if user made a boo-boo. */ - fprintf(stderr,"\nFor more help, consult the PGP User's Guide."); - -usage: - fprintf(stderr,"\nUsage summary:"); - fprintf(stderr,"\nTo encrypt a plaintext file with recipent's public key, type:"); - fprintf(stderr,"\n pgp -e textfile her_userid (produces textfile.ctx)"); - fprintf(stderr,"\nTo sign a plaintext file with your secret key, type:"); - fprintf(stderr,"\n pgp -s textfile your_userid (produces textfile.ctx)"); - fprintf(stderr,"\nTo sign a plaintext file with your secret key, and then encrypt it " - "\n with recipent's public key, producing a .ctx file:"); - fprintf(stderr,"\n pgp -es textfile her_userid your_userid"); - fprintf(stderr,"\nTo encrypt with conventional encryption only: pgp -c textfile"); - fprintf(stderr,"\nTo decrypt or check a signature for a ciphertext (.ctx) file:"); - fprintf(stderr,"\n pgp ciphertextfile [plaintextfile]"); - fprintf(stderr,"\nTo generate your own unique public/secret key pair, type: pgp -k"); - fprintf(stderr,"\nTo add a public or secret key file's contents to your public " - "\n or secret key ring: pgp -a keyfile [keyring]"); - fprintf(stderr,"\nTo remove a key from your public key ring: pgp -r userid [keyring]"); - fprintf(stderr,"\nTo view the contents of your public key ring: pgp -v [userid] [keyring] "); - exit(1); /* error exit */ - -} /* main */ - +char **ParseRecipients(char **recipients) +{ + /* + * ParseRecipients() expects an array of pointers to + * characters, usually the array returned by the C startup + * code. Then it will look for entries beginning with the + * string "-@" followed by a filename, which may be appended + * directly or seperated by a blank. + * + * If the file exists and is readable, the routine will load + * the contents and insert it into the command line as if the + * names had been specified there. + * + * Each entry in the file consists of one line. The file line + * will be treated as one argument, no matter whether it + * contains spaces or not. Lines beginning with "#" will be + * ignored and treated as comments. Empty lines will be ignored + * also. Trailing white spaces will be removed. + * + * Currently, ParseRecipients() uses one fixed buffer, meaning, + * that one single line must not be longer than 255 characters. + * The number of included lines is unlimited. + * + * When any kind of problem occurs, PGP will terminate and do + * nothing. No need to test for an error, the result is always + * correct. + * + * 21-Sep-95, Peter Simons + */ + + char **backup = recipients, **new; + int entrynum; + int MAX_RECIPIENTS = 128; /* The name is somewhat wrong. of + * course the memory handling is + * dynamic. + */ + + /* Check whether we need to do something or not. */ + while(*recipients) { + if (!strncasecmp(*recipients, INCLUDE_MARK, INCLUDE_MARK_LEN)) + break; + recipients++; + } + if (!*recipients) + return backup; /* nothin' happened */ + + recipients=backup; + if (!(new = malloc(MAX_RECIPIENTS * sizeof(char *)))) + exitPGP(OUT_OF_MEM); + entrynum = 0; + + while(*recipients) { + if (strncasecmp(*recipients, INCLUDE_MARK, INCLUDE_MARK_LEN)) + { + new[entrynum++] = *recipients++; + if (entrynum == MAX_RECIPIENTS) { + /* Current buffer is too small. + * Use realloc() to largen itt. + */ + MAX_RECIPIENTS += 128; + if (!(new = realloc(new, + MAX_RECIPIENTS * sizeof(char *)))) + exitPGP(OUT_OF_MEM); + } + } + else { + /* We got a hit! Load the file and parse it. */ + FILE *fh; + char *filename, tempbuf[256]; + + if (strlen(*recipients) == INCLUDE_MARK_LEN) + filename = *++recipients; + else + filename = *recipients+INCLUDE_MARK_LEN; + fprintf(pgpout, LANG("\nIncluding \"%s\"...\n"), filename); + if (!(fh = fopen(filename, "r"))) { + perror("PGP"); + exitPGP(UNKNOWN_FILE_ERROR); + } + while(fgets(tempbuf, sizeof(tempbuf)-1, fh)) { + int i = strlen(tempbuf); + + /* Test for comments or empty lines. */ + if (!i || *tempbuf == '#') + continue; + + /* Remove trailing blanks. */ + while (isspace(tempbuf[i-1])) + i--; + tempbuf[i] = '\0'; + + /* Copy new entry to new */ + if (!(new[entrynum++] = strdup(tempbuf))) + exitPGP(OUT_OF_MEM); + if (entrynum == MAX_RECIPIENTS) { + /* Current buffer is too small. + * Use realloc() to largen itt. + */ + MAX_RECIPIENTS += 128; + if (!(new = realloc(new, + MAX_RECIPIENTS * sizeof(char *)))) + exitPGP(OUT_OF_MEM); + } + } + if (ferror(fh)) { + perror("PGP"); + exitPGP(UNKNOWN_FILE_ERROR); + } + fclose(fh); + recipients++; + } + } + + /* + * We have to write one trailing NULL pointer. + * Check array size first. + */ + if (entrynum == MAX_RECIPIENTS) { + if (!(new = realloc(new, (MAX_RECIPIENTS+1) * sizeof(char *)))) + exitPGP(OUT_OF_MEM); + } + new[entrynum] = NULL; + return new; +}