--- pgp/src/fileio.c 2018/04/24 16:37:53 1.1 +++ pgp/src/fileio.c 2018/04/24 16:40:51 1.1.1.4 @@ -1,35 +1,46 @@ /* fileio.c - I/O routines for PGP. PGP: Pretty Good(tm) Privacy - public key cryptography for the masses. - (c) Copyright 1990-1992 by Philip Zimmermann. All rights reserved. + (c) Copyright 1990-1994 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 Philip Zimmermann 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. Code developed by others for PGP - is also freely available. Other code that has been incorporated into - PGP from other sources was either originally published in the public - domain or was used with permission from the various authors. See the - PGP User's Guide for more complete information about licensing, - patent restrictions on certain algorithms, trademarks, copyrights, - and export controls. + 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. Modified 16 Apr 92 - HAJK Mods for support of VAX/VMS file system + + Modified 17 Nov 92 - HAJK + Change to temp file stuff for VMS. */ #include #include #include #include -#if defined(UNIX) +#include +#ifdef UNIX +#include #include -#if !defined(NeXT) -#include +#include +#ifdef _BSD +#include #endif +extern int errno; +#endif /* UNIX */ +#ifdef VMS +#include #endif #include "random.h" #include "mpilib.h" @@ -37,253 +48,328 @@ #include "fileio.h" #include "language.h" #include "pgp.h" -#ifdef MSDOS +#include "exitpgp.h" +#include "charset.h" +#include "system.h" +#if defined(MSDOS) || defined(OS2) #include #include #endif +#ifndef F_OK +#define F_OK 0 +#define X_OK 1 +#define W_OK 2 +#define R_OK 4 +#endif /* !F_OK */ + +/* + * DIRSEPS is a string of possible directory-separation characters + * The first one is the preferred one, which goes in between + * PGPPATH and the file name if PGPPATH is not terminated with a + * directory separator. + */ + +#if defined(MSDOS) || defined(__MSDOS__) +static char const DIRSEPS[] = "\\/:"; +#define BSLASH + +#elif defined(ATARI) +static char const DIRSEPS[] = "\\/:"; +#define BSLASH + +#elif defined(UNIX) +static char const DIRSEPS[] = "/"; +#define MULTIPLE_DOTS + +#elif defined(AMIGA) +static char const DIRSEPS[] = "/:"; +#define MULTIPLE_DOTS + +#elif defined(VMS) +static char const DIRSEPS[] = "]:"; /* Any more? */ + +#else +#error Unknown OS +#endif + + /* 1st character of temporary file extension */ #define TMP_EXT '$' /* extensions are '.$##' */ /* The PGPPATH environment variable */ -char PGPPATH[] = "PGPPATH"; +static char PGPPATH[] = "PGPPATH"; /* Disk buffers, used here and in crypto.c */ byte textbuf[DISKBUFSIZE]; -static byte textbuf2[2*DISKBUFSIZE]; +static unsigned textbuf2[2*DISKBUFSIZE/sizeof(unsigned)]; boolean file_exists(char *filename) /* Returns TRUE iff file exists. */ { -#ifdef UNIX -#ifndef F_OK -#define F_OK 0 -#endif - return(access(filename, F_OK) == 0); -#else - FILE *f; - /* open file f for read, in binary (not text) mode...*/ - if ((f = fopen(filename,"rb")) == NULL) - return(FALSE); - fclose(f); - return(TRUE); -#endif -} /* file_exists */ - - -boolean file_ok_write(char *filename) -/* Returns TRUE iff file can be opened for writing. - Does not harm file! -*/ -{ FILE *f; - if (file_exists (filename)) - return(TRUE); - if ((f = fopen(filename,"wb")) == NULL) - return(FALSE); - fclose(f); - remove(filename); - return(TRUE); -} /* file_ok_write */ + return access(filename, F_OK) == 0; +} /* file_exists */ -boolean is_regular_file(char *filename) +static boolean is_regular_file(char *filename) { #ifdef S_ISREG struct stat st; - return(stat(filename, &st) != -1 && S_ISREG(st.st_mode)); + return stat(filename, &st) != -1 && S_ISREG(st.st_mode); #else return TRUE; #endif } -int wipeout(FILE *f) -{ /* Completely overwrite and erase file, so that no sensitive - information is left on the disk. - NOTE: File MUST be open for read/write. - */ - long flength; - int count = 0; +/* + * This wipes a file with pseudo-random data. The purpose of this is to + * make sure no sensitive information is left on the disk. The use + * of pseudo-random data is to defeat disk compression drivers (such as + * Stacker and dblspace) so that we are guaranteed that the entire file + * has been overwritten. + * + * Note that the file MUST be open for read/write. + * + * It may not work to eliminate everything from non-volatile storage + * if the OS you're using does its own paging or swapping. Then + * it's an issue of how the OS's paging device is wiped, and you can + * only hope that the space will be reused within a few seconds. + * + * Also, some file systems (in particular, the Logging File System + * for Sprite) do not write new data in the same place as old data, + * defeating this wiping entirely. Fortunately, such systems + * usually don't need a swap file, and for small temp files, they + * do write-behind, so if you create and delete a file fast enough, + * it never gets written to disk at all. + */ + +/* + * The data is randomly generated with the size of the file as a seed. + * The data should be random and not leak information. If someone is + * examining deleted files, presumably they can reconstruct the file size, + * so that's not a secret. H'm... this wiping algorithm makes it easy to, + * given a block of data, find the size of the file it came from + * and the offset of this block within it. That in turn reveals + * something about the state of the disk's allocation tables when the + * file was used, possibly making it easier to find other files created + * at neaby times - such as plaintext files. Is this acceptable? + */ + +/* + * Theory of operation: We use the additive congruential RNG + * r[i] = r[i-24] + r[i-55], from Knuth, Vol. 2. This is fast + * and has a long enough period that there should be no repetitions + * in even a huge file. It is seeded with r[-55] through r[-1] + * using another polynomial-based RNG. We seed a linear feedback + * shift register (CRC generator) with the size of the swap file, + * and clock in 0 bits. Each 32 bits, the value of the generator is + * taken as the next integer. This is just to ensure a reasonably + * even mix of 1's and 0's in the initialization vector. + */ + +/* + * This is the CRC-32 polynomial, which should be okay for random + * number generation. + * x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1 + * = 1 0000 0100 1100 0001 0001 1101 1011 0111 + * = 0x04c11db7 + */ +#define POLY 0x04c11db7 + +static int +wipeout(FILE *f) +{ + unsigned *p1, *p2, *p3; + unsigned long len; + unsigned long t; + int i; + /* Get the file size */ fseek(f, 0L, SEEK_END); - flength = ftell(f); + len = ftell(f); rewind(f); - fill0(textbuf, sizeof(textbuf)); - while (flength > 0L) - { /* write zeros to the whole file... */ - if (flength < (word32) DISKBUFSIZE) - count = (int)flength; - else - count = DISKBUFSIZE; - fwrite(textbuf,1,count,f); - flength -= count; - } - rewind(f); /* maybe this isn't necessary */ - return(0); /* normal return */ -} /* wipeout */ + /* Seed of first RNG. Inverted to get more 1 bits */ + t = ~len; + + /* Initialize first 55 words of buf with pseudo-random stuff */ + p1 = (unsigned *)textbuf2 + 55; + do { + for (i = 0; i < 32; i++) + t = (t & 0x80000000) ? t<<1 ^ POLY : t<<1; + *--p1 = (unsigned)t; + } while (p1 > (unsigned *)textbuf2); + + while (len) { + /* Fill buffer with pseudo-random integers */ + + p3 = (unsigned *)textbuf2 + 55; + p2 = (unsigned *)textbuf2 + 24; + p1 = (unsigned *)textbuf2 + sizeof(textbuf2)/sizeof(*p1); + do { + *--p1 = *--p2 + *--p3; + } while (p2 > (unsigned *)textbuf2); + + p2 = (unsigned *)textbuf2 + sizeof(textbuf2)/sizeof(*p1); + do { + *--p1 = *--p2 + *--p3; + } while (p3 > (unsigned *)textbuf2); + + p3 = (unsigned *)textbuf2 + sizeof(textbuf2)/sizeof(*p3); + do { + *--p1 = *--p2 + *--p3; + } while (p1 > (unsigned *)textbuf2); + + /* Write it out - yes, we're ignoring errors */ + if (len > sizeof(textbuf2)) { + fwrite((char const *)textbuf2, sizeof(textbuf2), 1, f); + len -= sizeof(textbuf2); + } else { + fwrite((char const *)textbuf2, len, 1, f); + len = 0; + } + } +} int wipefile(char *filename) -{ /* Completely overwrite and erase file, so that no sensitive - information is left on the disk. - */ +/* + * 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...*/ -#ifdef VMS - if ((f = fopen(filename,"rb+","ctx=stm")) == NULL) -#else - if ((f = fopen(filename,"rb+")) == NULL) -#endif - return(-1); /* error - file can't be opened */ + if ((f = fopen(filename,FOPRWBIN)) == NULL) + return -1; /* error - file can't be opened */ wipeout(f); fclose(f); - return(0); /* normal return */ -} /* wipefile */ + return 0; /* normal return */ +} /* wipefile */ -char *file_tail (char *filename) -/* Returns the after-slash part of filename. Also skips backslashes, - * colons, and right brackets. */ -{ - char *bslashPos = strrchr(filename, '\\');/* Find last bslash in filename */ - char *slashPos = strrchr(filename, '/');/* Find last slash in filename */ - char *colonPos = strrchr(filename, ':'); - char *rbrakPos = strrchr(filename, ']'); - - if (!slashPos || bslashPos > slashPos) - slashPos = bslashPos; - if (!slashPos || colonPos > slashPos) - slashPos = colonPos; - if (!slashPos || rbrakPos > slashPos) - slashPos = rbrakPos; - return( slashPos?(slashPos+1):filename ); -} -/* Define BSLASH for machines that use backslash, FSLASH for machines - * that use forward slash as separators. +char *file_tail (char *filename) +/* + * Returns the part of a filename after all directory specifiers. */ +{ + char *p; + char const *s = DIRSEPS; + + while (*s) { + p = strrchr(filename, *s); + if (p) + filename = p+1; + s++; + } + + return filename; +} -#ifdef MSDOS -#define BSLASH -#endif -#ifdef ATARI -#define BSLASH -#endif -#ifdef UNIX -#define FSLASH -#define MULTIPLE_DOTS -#endif -#ifdef AMIGA -#define FSLASH -#define MULTIPLE_DOTS -#endif boolean has_extension(char *filename, char *extension) -{ /* return TRUE if extension matches the end of filename */ +/* return TRUE if extension matches the end of filename */ +{ int lf = strlen(filename); int lx = strlen(extension); if (lf <= lx) - return(FALSE); - return(!strcmp(filename + lf - lx, extension)); + return FALSE; + return !strcmp(filename + lf - lx, extension); } boolean is_tempfile(char *path) -{ /* return TRUE if path is a filename created by tempfile() */ - char *p; +/* return TRUE if path is a filename created by tempfile() */ +/* Filename matches "*.$[0-9][0-9]" */ +{ + char *p = strrchr(path, '.'); - return((p = strrchr(path, '.')) != NULL && - p[1] == TMP_EXT && strlen(p) == 4); + return p != NULL && p[1] == TMP_EXT && + isdigit(p[2]) && isdigit(p[3]) && p[4] == '\0'; } boolean no_extension(char *filename) -/* Returns TRUE if user left off file extension, allowing default. */ +/* + * Returns TRUE if user left off file extension, allowing default. + * Note that the name is misleading if multiple dots are allowed. + * not_pgp_extension or something would be better. + */ { #ifdef MULTIPLE_DOTS /* filename can have more than one dot */ - if (has_extension(filename, CTX_EXTENSION) || - has_extension(filename, ASC_EXTENSION) || + if (has_extension(filename, ASC_EXTENSION) || has_extension(filename, PGP_EXTENSION) || has_extension(filename, SIG_EXTENSION) || is_tempfile(filename)) - return(FALSE); + return FALSE; else - return(TRUE); -#else -#ifdef BSLASH - char *slashPos = strrchr(filename, '\\'); /* Find last slash in filename */ - - /* Look for the filename after the last slash if there is one */ - return(strchr((slashPos != NULL ) ? slashPos : filename, '.') == NULL); -#else -#ifdef FSLASH - char *slashPos = strrchr(filename, '/'); /* Find last slash in filename */ - - /* Look for the filename after the last slash if there is one */ - return(strchr((slashPos != NULL ) ? slashPos : filename, '.') == NULL); + return TRUE; #else -#ifdef VMS - char *slashPos = strrchr(filename,']'); /* Locate end of directory spec */ + filename = file_tail(filename); - /* Look for last period in filename */ - return(strrchr((slashPos != NULL) ? slashPos : filename, '.') == NULL ); -#else - return( (strrchr(filename,'.')==NULL) ? TRUE : FALSE ); -#endif /* VMS */ -#endif /* FSLASH */ -#endif /* BSLASH */ -#endif /* MULTIPLE_DOTS */ -} /* no_extension */ + return strrchr(filename, '.') == NULL; +#endif +} /* no_extension */ void drop_extension(char *filename) -{ /* deletes trailing ".xxx" file extension after the period. */ +/* deletes trailing ".xxx" file extension after the period. */ +{ if (!no_extension(filename)) *strrchr(filename,'.') = '\0'; -} /* drop_extension */ +} /* drop_extension */ void default_extension(char *filename, char *extension) -{ /* append filename extension if there isn't one already. */ +/* append filename extension if there isn't one already. */ +{ if (no_extension(filename)) strcat(filename,extension); -} /* default_extension */ +} /* default_extension */ #ifndef MAX_NAMELEN -#if defined(AMIGA) || defined(NeXT) +#if defined(AMIGA) || defined(NeXT) || (defined(BSD) && BSD > 41) || (defined(sun) && defined(i386)) #define MAX_NAMELEN 255 +#else +#include #endif #endif -void truncate_name(char *path, int ext_len) -{ /* truncate the filename so that an extension can be tacked on. */ +static void truncate_name(char *path, int ext_len) +/* truncate the filename so that an extension can be tacked on. */ +{ #ifdef UNIX /* for other systems this is a no-op */ - char dir[MAX_PATH], *p; - int namemax; - + char *p; #ifdef MAX_NAMELEN /* overrides the use of pathconf() */ - namemax = MAX_NAMELEN + int namemax = MAX_NAMELEN; #else + int namemax; #ifdef _PC_NAME_MAX + char dir[MAX_PATH]; + strcpy(dir, path); - if ((p = strrchr(dir, '/')) == NULL) + if ((p = strrchr(dir, '/')) == NULL) { strcpy(dir, "."); - else - { if (p == dir) + } else { + if (p == dir) ++p; *p = '\0'; } if ((namemax = pathconf(dir, _PC_NAME_MAX)) <= ext_len) return; #else +#ifdef NAME_MAX + namemax = NAME_MAX; +#else namemax = 14; -#endif +#endif /* NAME_MAX */ +#endif /* _PC_NAME_MAX */ #endif /* MAX_NAMELEN */ if ((p = strrchr(path, '/')) == NULL) p = path; else ++p; - if (strlen(p) > namemax - ext_len) - { + if (strlen(p) > namemax - ext_len) { if (verbose) fprintf(pgpout, "Truncating filename '%s' ", path); p[namemax - ext_len] = '\0'; @@ -294,118 +380,125 @@ void truncate_name(char *path, int ext_l } void force_extension(char *filename, char *extension) -{ /* change the filename extension. */ +/* change the filename extension. */ +{ drop_extension(filename); /* out with the old */ truncate_name(filename, strlen(extension)); strcat(filename,extension); /* in with the new */ -} /* force_extension */ +} /* force_extension */ boolean getyesno(char default_answer) -/* Get yes/no answer from user, returns TRUE for yes, FALSE for no. - First the translations are checked, if they don't match 'y' and 'n' - are tried. -*/ -{ char buf[8]; +/* + * Get yes/no answer from user, returns TRUE for yes, FALSE for no. + * First the translations are checked, if they don't match 'y' and 'n' + * are tried. + */ +{ + char buf[8]; static char yes[8], no[8]; - if (yes[0] == '\0') - { strncpy(yes, PSTR("y"), 7); - strncpy(no, PSTR("n"), 7); - } - flush_input(); - getstring(buf,6,TRUE); /* echo keyboard input */ - strlwr(buf); - if (!strncmp(buf, no, strlen(no))) - return(FALSE); - if (!strncmp(buf, yes, strlen(yes))) - return(TRUE); - if (buf[0] == 'n') - return(FALSE); - if (buf[0] == 'y') - return(TRUE); - return(default_answer == 'y' ? TRUE : FALSE); -} /* getyesno */ + if (yes[0] == '\0') { + strncpy(yes, LANG("y"), 7); + strncpy(no, LANG("n"), 7); + } + if (!batchmode) { /* return default answer in batchmode */ + getstring(buf,6,TRUE); /* echo keyboard input */ + strlwr(buf); + if (!strncmp(buf, no, strlen(no))) + return FALSE; + if (!strncmp(buf, yes, strlen(yes))) + return TRUE; + if (buf[0] == 'n') + return FALSE; + if (buf[0] == 'y') + return TRUE; + } + return default_answer == 'y' ? TRUE : FALSE; +} /* getyesno */ -void maybe_force_extension(char *filename, char *extension) -{ /* if user consents to it, change the filename extension. */ - char newname[MAX_PATH]; - if (!has_extension(filename,extension)) - { strcpy(newname,filename); +char *maybe_force_extension(char *filename, char *extension) +/* if user consents to it, change the filename extension. */ +{ + static char newname[MAX_PATH]; + if (!has_extension(filename,extension)) { + strcpy(newname,filename); force_extension(newname,extension); - if (!file_exists(newname)) - { fprintf(pgpout,PSTR("\nShould '%s' be renamed to '%s' [Y/n]? "), + if (!file_exists(newname)) { + fprintf(pgpout,LANG("\nShould '%s' be renamed to '%s' [Y/n]? "), filename,newname); if (getyesno('y')) - rename(filename,newname); + return newname; } } -} /* maybe_force_extension */ + return NULL; +} /* maybe_force_extension */ -char *buildfilename(char *result, char *fname) -/* Builds a filename with a complete path specifier from the environmental - variable PGPPATH. -*/ -{ char *s = getenv(PGPPATH); +/* + * Add a trailing directory separator to a name, if absent. + */ +static void +addslash(char *name) +{ + int i = strlen(name); - if ( s==NULL || strlen(s) > 50) /* undefined, or too long to use */ - s=""; - strcpy(result,s); - if (strlen(result) != 0) -#ifdef BSLASH - if (result[strlen(result)-1] != '\\') - strcat(result,"\\"); -#else -#ifdef FSLASH - if (result[strlen(result)-1] != '/') - strcat(result,"/"); -#else -#ifdef VMS - if (result[strlen(result)-1] != ']') - strcat(result,"]"); -#endif -#endif -#endif /* Various OS-specific defines */ - strcat(result,fname); - return(result); -} /* buildfilename */ + if (i != 0 && !strchr(DIRSEPS, name[i-1])) { + name[i] = DIRSEPS[0]; + name[i+1] = '\0'; + } +} -int build_path(char *path, char *fileName, char *origPath) -/* Build a path for fileName based on origPath */ -{ int i, lastSlash = 0; +char *buildfilename(char *result, char *fname) +/* + * Builds a filename with a complete path specifier from the environmental + * variable PGPPATH. + */ +{ + char const *s = getenv(PGPPATH); -#ifdef BSLASH - for (i=0; origPath[i]; i++) - if (origPath[i] == ':' || origPath[i] == '\\') - lastSlash = i + 1; -#else -#ifdef VMS - for (i=0; origPath[i]; i++) - if (origPath[i] == ']') - lastSlash = i + 1; -#else - for (i=0; origPath[i]; i++) - if (origPath[i] == '/') - lastSlash = i + 1; -#endif -#endif + result[0] = '\0'; - /* Sanity check: Make sure the path is of legal length */ - if (i>=MAX_PATH) - { fprintf(pgpout,PSTR("Path '%s' too long\n"), origPath); - return(-1); /* error return */ + if (s && strlen(s) <= 50) { + strcpy(result, s); + } +#ifdef UNIX + /* On Unix, default to $HOME/.pgp, otherwise, current directory. */ + else { + s = getenv("HOME"); + if (s && strlen(s) <= 50) { + strcpy(result, s); + addslash(result); + strcat(result, ".pgp"); + } } +#endif /* UNIX */ + + addslash(result); + strcat(result,fname); + return result; +} /* buildfilename */ - strncpy(path,origPath,lastSlash); /* Add path component */ - strcpy(path+lastSlash,fileName); /* Append filename */ - return(0); /* normal return */ -} /* build_path */ +char *buildsysfilename(char *result, char *fname) +{ + buildfilename(result, fname); +#ifdef PGP_SYSTEM_DIR + if (file_exists(result)) + return result; + strcpy(result, PGP_SYSTEM_DIR); + strcat(result, fname); + if (file_exists(result)) + return result; + buildfilename(result, fname); /* Put name back for error */ +#endif + return result; +} void file_to_canon(char *filename) -{ /* Convert filename to canonical form, with slashes as separators */ +/* Convert filename to canonical form, with slashes as separators */ +{ #ifdef BSLASH while (*filename) { if (*filename == '\\') @@ -416,67 +509,41 @@ void file_to_canon(char *filename) } -void file_from_canon(char *filename) -{ /* Convert filename from canonical to local form */ - /* I think everyone can handle slashes */ - filename = filename; /* Suppress warning */ -} - - -FILE *fopenbin(char *name, char *mode) +int write_error(FILE *f) { -#if !defined(UNIX) - char mode_b[8]; - - strcpy(mode_b, mode); - strcat(mode_b, "b"); - mode = mode_b; -#endif - -#ifdef VMS - if (*mode == 'r') - return(fopen(name, mode, "ctx=stm")); - else - return(fopen(name, mode)); -#else - return(fopen(name, mode)); -#endif -} - -FILE *fopentxt(char *name, char *mode) -{ -#ifdef VMS - if (*mode == 'r') - return(fopen(name, mode, "ctx=stm")); - else - return(fopen(name, mode)); -#else - return(fopen(name, mode)); + fflush(f); + if (ferror(f)) { +#ifdef ENOSPC + if (errno == ENOSPC) + fprintf(pgpout, LANG("\nDisk full.\n")); + else #endif + fprintf(pgpout, LANG("\nFile write error.\n")); + return -1; + } + return 0; } - int copyfile(FILE *f, FILE *g, word32 longcount) -{ /* copy file f to file g, for longcount bytes */ +/* copy file f to file g, for longcount bytes */ +{ int count, status = 0; - do /* read and write the whole file... */ - { + do { /* read and write the whole file... */ if (longcount < (word32) DISKBUFSIZE) count = (int)longcount; else count = DISKBUFSIZE; count = fread(textbuf,1,count,f); - if (count>0) - { - if (CONVERSION != NO_CONV) - { int i; + if (count>0) { + if (CONVERSION != NO_CONV) { + int i; for (i = 0; i < count; i++) textbuf[i] = (CONVERSION == EXT_CONV) ? EXT_C(textbuf[i]) : INT_C(textbuf[i]); } - if (fwrite(textbuf,1,count,g) != count ) - { /* Problem: return error value */ + if (fwrite(textbuf,1,count,g) != count ) { + /* Problem: return error value */ status = -1; break; } @@ -485,11 +552,12 @@ int copyfile(FILE *f, FILE *g, word32 lo /* if text block was short, exit loop */ } while (count==DISKBUFSIZE); burn(textbuf); /* burn sensitive data on stack */ - return(status); -} /* copyfile */ + return status; +} /* copyfile */ int copyfilepos (FILE *f, FILE *g, word32 longcount, word32 fpos) -/* Like copyfile, but takes a position for file f. Returns with +/* + * Like copyfile, but takes a position for file f. Returns with * f and g pointing just past the copied data. */ { @@ -498,27 +566,28 @@ int copyfilepos (FILE *f, FILE *g, word3 } -#ifndef CANONICAL_TEXT int copyfile_to_canon (FILE *f, FILE *g, word32 longcount) -{ /* copy file f to file g, for longcount bytes. Convert to - canonical form as we go. f is open in text mode. Canonical - form uses crlf's as line separators. */ +/* copy file f to file g, for longcount bytes. Convert to + * canonical form as we go. f is open in text mode. Canonical + * form uses crlf's as line separators. + */ +{ int count, status = 0; byte c, *tb1, *tb2; int i, nbytes; - do /* read and write the whole file... */ - { + int nspaces = 0; + do { /* read and write the whole file... */ if (longcount < (word32) DISKBUFSIZE) count = (int)longcount; else count = DISKBUFSIZE; count = fread(textbuf,1,count,f); - if (count>0) - { /* Convert by adding CR before LF */ + if (count>0) { + /* Convert by adding CR before LF */ tb1 = textbuf; - tb2 = textbuf2; - for (i=0; i0) - { /* Convert by removing CR's */ + if (count>0) { + /* Convert by removing CR's */ tb1 = textbuf; - tb2 = textbuf2; - for (i=0; i= 0) - { wipeout(f); /* Zap source file */ + if (status >= 0) { + wipeout(f); /* Zap source file */ fclose(f); remove(srcFile); fclose(g); - } - else - { if (is_regular_file(destFile)) - { wipeout(g); /* Zap destination file */ + } else { + if (is_regular_file(destFile)) { + wipeout(g); /* Zap destination file */ fclose(g); remove(destFile); - } else + } else { fclose(g); + } fclose(f); } } - return(status); + return status; } int readPhantomInput(char *filename) /* read the data from stdin to the phantom input file */ -{ FILE *outFilePtr; +{ + FILE *outFilePtr; byte buffer[ 512 ]; - int bytesRead; + int bytesRead, status = 0; - if ((outFilePtr = fopen(filename,"wb")) == NULL) - return(-1); + if (verbose) + fprintf(pgpout, "writing stdin to file %s\n", filename); + if ((outFilePtr = fopen(filename,FOPWBIN)) == NULL) + return -1; -#ifdef MSDOS +#if defined(MSDOS) || defined(OS2) /* Under DOS must set input stream to binary mode to avoid data mangling */ setmode(fileno(stdin), O_BINARY); -#endif /* MSDOS */ +#endif /* MSDOS || OS2 */ while ((bytesRead = fread (buffer, 1, 512, stdin)) > 0) - fwrite (buffer, 1, bytesRead, outFilePtr); + if (fwrite (buffer, 1, bytesRead, outFilePtr) != bytesRead) { + status = -1; + break; + } + if (write_error(outFilePtr)) + status = -1; fclose (outFilePtr); -#ifdef MSDOS +#if defined(MSDOS) || defined(OS2) setmode(fileno(stdin), O_TEXT); /* Reset stream */ -#endif /* MSDOS */ - return(0); +#endif /* MSDOS || OS2 */ + return status; } -void writePhantomOutput(char *filename) +int writePhantomOutput(char *filename) /* write the data from the phantom output file to stdout */ { FILE *outFilePtr; byte buffer[ 512 ]; - int bytesRead; + int bytesRead, status = 0; + if (verbose) + fprintf(pgpout, "writing file %s to stdout\n", filename); /* this can't fail since we just created the file */ - outFilePtr = fopen(filename,"rb"); + outFilePtr = fopen(filename,FOPRBIN); -#ifdef MSDOS +#if defined(MSDOS) || defined(OS2) setmode(fileno(stdout), O_BINARY); -#endif +#endif /* MSDOS || OS2 */ while ((bytesRead = fread (buffer, 1, 512, outFilePtr)) > 0) - fwrite (buffer, 1, bytesRead, stdout); + if (fwrite (buffer, 1, bytesRead, stdout) != bytesRead) { + status = -1; + break; + } fclose (outFilePtr); fflush(stdout); -#ifdef MSDOS + if (ferror(stdout)) { + status = -1; + fprintf(pgpout, LANG("\007Write error on stdout.\n")); + } +#if defined(MSDOS) || defined(OS2) setmode(fileno(stdout), O_TEXT); -#endif +#endif /* MSDOS || OS2 */ - /* finally, delete the phantom file */ - wipefile(filename); - remove(filename); + return status; } /* Return the size from the current position of file f to the end */ @@ -770,43 +872,44 @@ word32 fsize (FILE *f) /* Return TRUE if file filename looks like a pure text file */ int is_text_file (char *filename) { - FILE *f = fopen(filename,"rb"); + FILE *f = fopen(filename,"r"); /* FOPRBIN gives problem with VMS */ int i, n, bit8 = 0; unsigned char buf[512]; unsigned char *bufptr = buf; unsigned char c; if (!f) - return(FALSE); /* error opening it, so not a text file */ + return FALSE; /* error opening it, so not a text file */ i = n = fread (buf, 1, sizeof(buf), f); fclose(f); if (n <= 0) - return(FALSE); /* empty file or error, not a text file */ + return FALSE; /* empty file or error, not a text file */ if (compressSignature(buf) >= 0) - return(FALSE); - while (i--) - { c = *bufptr++; + return FALSE; + while (i--) { + c = *bufptr++; if (c & 0x80) ++bit8; else /* allow BEL BS HT LF VT FF CR EOF control characters */ if (c < '\007' || (c > '\r' && c < ' ' && c != '\032')) - return(FALSE); /* not a text file */ + return FALSE; /* not a text file */ } - if (strcmp(language, "ru")) - return(TRUE); + if (strcmp(language, "ru") == 0) + return TRUE; /* assume binary if more than 1/4 bytes have 8th bit set */ - return(bit8 < n / 4); + return bit8 < n/4; } /* is_text_file */ -void *xmalloc(unsigned size) -{ void *p; +VOID *xmalloc(unsigned size) +{ VOID *p; if (size == 0) ++size; - if ((p = malloc(size)) == NULL) - { fprintf(stderr, PSTR("\n\007Out of memory.\n")); + p = malloc(size); + if (p == NULL) { + fprintf(stderr, LANG("\n\007Out of memory.\n")); exitPGP(1); } - return(p); + return p; } /*---------------------------------------------------------------------- @@ -821,49 +924,143 @@ void *xmalloc(unsigned size) static struct { char path[MAX_PATH]; int flags; + int num; } tmpf[MAXTMPF]; -extern char outdir[]; -extern char basename[]; +static char tmpdir[256]; /* temporary file directory */ +static char outdir[256]; /* output directory */ +static char tmpbasename[64] = "pgptemp"; /* basename for temporary files */ + + +/* + * set directory for temporary files. path will be stored in + * tmpdir[] with an appropriate trailing path separator. + */ +void settmpdir(char *path) +{ + char *p; + + if (path == NULL || *path == '\0') { + tmpdir[0] = '\0'; + return; + } + strcpy(tmpdir, path); + p = tmpdir + strlen(tmpdir)-1; + if (*p != '/' && *p != '\\' && *p != ']' && *p != ':') { + /* append path separator, either / or \ */ + if ((p = strchr(tmpdir, '/')) == NULL && + (p = strchr(tmpdir, '\\')) == NULL) + p = "/"; /* path did not contain / or \, use / */ + strncat(tmpdir, p, 1); + } +} + +/* + * set output directory to avoid a file copy when temp file is renamed to + * output file. the argument filename must be a valid path for a file, not + * a directory. + */ +void setoutdir(char *filename) +{ + char *p; + + if (filename == NULL) { + strcpy(outdir, tmpdir); + return; + } + strcpy(outdir, filename); + p = file_tail(outdir); + strcpy(tmpbasename, p); + *p = '\0'; + drop_extension(tmpbasename); +#if !defined(BSD42) && !defined(BSD43) && !defined(sun) + /* + * we don't depend on pathconf here, if it returns an incorrect value + * for NAME_MAX (like Linux 0.97 with minix FS) finding a unique name + * for temp files can fail. + */ + tmpbasename[10] = '\0'; /* 14 char limit */ +#endif +} /* * return a unique temporary file name */ char *tempfile(int flags) { - int i; - int num = 0; + int i, j; + int num; + int fd; +#ifndef UNIX + FILE *fp; +#endif for (i = 0; i < MAXTMPF; ++i) if (tmpf[i].flags == 0) break; - if (i == MAXTMPF) - { /* message only for debugging, no need for PSTR */ + if (i == MAXTMPF) { + /* message only for debugging, no need for LANG */ fprintf(stderr, "\n\007Out of temporary files\n"); - return(NULL); + return NULL; } - do +again: + num = 0; + do { + for (j = 0; j < MAXTMPF; ++j) + if (tmpf[j].flags && tmpf[j].num == num) + break; + if (j < MAXTMPF) + continue; /* sequence number already in use */ sprintf(tmpf[i].path, "%s%s.%c%02d", - (flags & TMP_TMPDIR ? tmpdir : outdir), basename, TMP_EXT, num); - while (file_exists(tmpf[i].path) && ++num < 100); + ((flags & TMP_TMPDIR) && *tmpdir ? tmpdir : outdir), + tmpbasename, TMP_EXT, num); + if (!file_exists(tmpf[i].path)) + break; + } + while (++num < 100); - if (!file_ok_write(tmpf[i].path)) - { fprintf(pgpout, PSTR("\n\007Cannot create temporary file '%s'\n"), tmpf[i].path); - user_error(); + if (num == 100) { + fprintf(pgpout, "\n\007tempfile: cannot find unique name\n"); + return NULL; } - if (num == 100) - { fprintf(stderr, "\n\007tempfile: cannot find unique name\n"); - return(NULL); +#if defined(UNIX) || defined(VMS) + if ((fd = open(tmpf[i].path, O_EXCL|O_RDWR|O_CREAT, 0600)) != -1) + close(fd); +#else + if ((fp = fopen(tmpf[i].path, "w")) != NULL) + fclose(fp); + fd = (fp == NULL ? -1 : 0); +#endif + + if (fd == -1) { + if (!(flags & TMP_TMPDIR)) { + flags |= TMP_TMPDIR; + goto again; + } +#ifdef UNIX + else if (tmpdir[0] == '\0') { + strcpy(tmpdir, "/tmp/"); + goto again; + } +#endif } + if (fd == -1) + { fprintf(pgpout, LANG("\n\007Cannot create temporary file '%s'\n"), tmpf[i].path); + user_error(); + } +#ifdef VMS + remove(tmpf[i].path); +#endif + tmpf[i].num = num; tmpf[i].flags = flags | TMP_INUSE; if (verbose) - fprintf(stderr, "tempfile: created '%s'\n", tmpf[i].path); - return(tmpf[i].path); -} /* tempfile */ + fprintf(pgpout, "tempfile: created '%s'\n", tmpf[i].path); + return tmpf[i].path; +} /* tempfile */ /* * remove temporary file, wipe if necessary. @@ -876,19 +1073,23 @@ void rmtemp(char *name) if (tmpf[i].flags && strcmp(tmpf[i].path, name) == 0) break; - if (i < MAXTMPF) - { if (strlen(name) > 3 && name[strlen(name)-3] == TMP_EXT) - { /* only remove file if name hasn't changed */ + if (i < MAXTMPF) { + if (strlen(name) > 3 && name[strlen(name)-3] == TMP_EXT) { + /* only remove file if name hasn't changed */ if (verbose) - fprintf(stderr, "rmtemp: removing '%s'\n", name); + fprintf(pgpout, "rmtemp: removing '%s'\n", name); if (tmpf[i].flags & TMP_WIPE) wipefile(name); - remove(name); - tmpf[i].flags = 0; + if (!remove(name)) { + tmpf[i].flags = 0; + } else if (verbose) { + fprintf(stderr,"\nrmtemp: Failed to remove %s",name); + perror ("\nError"); + } } else if (verbose) - fprintf(stderr, "rmtemp: not removing '%s'\n", name); + fprintf(pgpout, "rmtemp: not removing '%s'\n", name); } -} /* rmtemp */ +} /* rmtemp */ /* * make temporary file permanent, returns the new name. @@ -899,52 +1100,63 @@ char *savetemp(char *name, char *newname static char buf[MAX_PATH]; if (strcmp(name, newname) == 0) - return(name); + return name; for (i = 0; i < MAXTMPF; ++i) if (tmpf[i].flags && strcmp(tmpf[i].path, name) == 0) break; - if (i < MAXTMPF) - { if (strlen(name) < 4 || name[strlen(name)-3] != TMP_EXT) - { if (verbose) - fprintf(stderr, "savetemp: not renaming '%s' to '%s'\n", + if (i < MAXTMPF) { + if (strlen(name) < 4 || name[strlen(name)-3] != TMP_EXT) { + if (verbose) + fprintf(pgpout, "savetemp: not renaming '%s' to '%s'\n", name, newname); - return(name); /* return original file name */ + return name; /* return original file name */ } } - while (file_exists(newname)) - { - if (is_regular_file(newname)) - { fprintf(pgpout,PSTR("\n\007Output file '%s' already exists. Overwrite (y/N)? "),newname); - overwrite = getyesno('n'); + while (file_exists(newname)) { + if (batchmode && !force_flag) { + fprintf(pgpout,LANG("\n\007Output file '%s' already exists.\n"),newname); + return NULL; } - else - { fprintf(pgpout,PSTR("\n\007Output file '%s' already exists.\n"),newname); + if (is_regular_file(newname)) { + if (force_flag) { + /* remove without asking */ + remove(newname); + break; + } + fprintf(pgpout,LANG("\n\007Output file '%s' already exists. Overwrite (y/N)? "), + newname); + overwrite = getyesno('n'); + } else { + fprintf(pgpout,LANG("\n\007Output file '%s' already exists.\n"),newname); + if (force_flag) /* never remove special file */ + return NULL; overwrite = FALSE; } - if (!overwrite) - { fprintf(pgpout, PSTR("\nEnter new file name: ")); + if (!overwrite) { + fprintf(pgpout, LANG("\nEnter new file name: ")); getstring(buf, MAX_PATH - 1, TRUE); if (buf[0] == '\0') - return(NULL); + return NULL; newname = buf; - } - else + } else { remove(newname); + } } if (verbose) - fprintf(stderr, "savetemp: renaming '%s' to '%s'\n", name, newname); - if (rename2(name, newname) < 0) - { /* errorLvl = UNKNOWN_FILE_ERROR; */ - return(NULL); + fprintf(pgpout, "savetemp: renaming '%s' to '%s'\n", name, newname); + if (rename2(name, newname) < 0) { + /* errorLvl = UNKNOWN_FILE_ERROR; */ + fprintf(pgpout, LANG("Can't create output file '%s'\n"), newname); + return NULL; } if (i < MAXTMPF) tmpf[i].flags = 0; - return(newname); -} /* savetemp */ + return newname; +} /* savetemp */ /* * like savetemp(), only make backup of destname if it exists @@ -952,23 +1164,37 @@ char *savetemp(char *name, char *newname int savetempbak(char *tmpname, char *destname) { char bakpath[MAX_PATH]; +#ifdef UNIX + int mode = -1; +#endif - if (strcmp(destname, SCRATCH_KEYRING_PATH) == 0 || - is_tempfile(destname)) + if (is_tempfile(destname)) { remove(destname); - else - { if (file_exists(destname)) - { + } else { + if (file_exists(destname)) { +#ifdef UNIX + struct stat st; + if (stat(destname, &st) != -1) + mode = st.st_mode & 07777; +#endif strcpy(bakpath, destname); force_extension(bakpath, BAK_EXTENSION); remove(bakpath); +#ifdef VMS + if (rename(destname, bakpath) != 0) +#else if (rename(destname, bakpath) == -1) - return(-1); +#endif + return -1; } } if (savetemp(tmpname, destname) == NULL) - return(-1); - return(0); + return -1; +#ifdef UNIX + if (mode != -1) + chmod(destname, mode); +#endif + return 0; } /* @@ -981,4 +1207,183 @@ void cleanup_tmpf(void) for (i = 0; i < MAXTMPF; ++i) if (tmpf[i].flags) rmtemp(tmpf[i].path); -} /* cleanup_tmpf */ +} /* cleanup_tmpf */ + +/* + * Routines to search for the manuals. + * + * Why all this code? + * + * Some dimwits have been distributing versions of PGP (especially on MS-DOS) + * without the manuals. This frustrates users (who call Philip Zimmermann + * at all hours of the day and night), and in addition to depriving them + * of instructions on how to operate the program, also deprives them of + * IMPORTANT legal notices. This is a Bad Thing, so we've gone to the + * trouble of being fascist and *forcing* the manuals to be there. + */ + +static unsigned +ext_missing(char *prefix) +{ + static char const * const extensions[] = { +#ifdef VMS + ".doc", ".txt", ".man", ".tex", ".", 0}; +#else + ".doc", ".txt", ".man", ".tex", "", 0}; +#endif + char const * const *p; + char *end = prefix + strlen(prefix); + + for (p = extensions; *p; p++) { + strcpy(end, *p); +#if 0 /* Debugging code */ + fprintf(pgpout, "Looking for \"%s\"\n", prefix); +#endif + if (file_exists(prefix)) + return 0; + } + return 1; +} + +/* + * Returns mask of files missing + */ +static unsigned +files_missing(char *prefix) +{ + static char const * const names[] = {"pgpdoc1", "pgpdoc2", 0}; + char const * const *p; + unsigned bit, mask = 3; + int len = strlen(prefix); + + if (prefix[0] && !file_exists(prefix)) /* Directory doesn't exist? */ + return mask; + if (len && strchr(DIRSEPS, prefix[len-1]) == 0) + prefix[len++] = DIRSEPS[0]; + for (p = names, bit = 1; *p; p++, bit <<= 1) { + strcpy(prefix+len, *p); + if (!ext_missing(prefix)) + mask &= ~bit; + } + + return mask; /* Bitmask of which files exist */ +} + +/* + * Search prefix directory and doc subdirectory. + */ +static unsigned +doc_missing(char *prefix) +{ + unsigned mask; + int len = strlen(prefix); + + mask = files_missing(prefix); + if (!mask) + return 0; +#ifdef VMS + if (len && prefix[len-1] == ']') { + strcpy(prefix+len-1, ".doc]"); + } else { + assert(!len || prefix[len-1] == ':'); + strcpy(prefix+len, "[doc]"); + } +#else + if (len && prefix[len-1] != DIRSEPS[0]) + prefix[len++] = DIRSEPS[0]; + strcpy(prefix+len, "doc"); +#endif + + mask &= files_missing(prefix); + + prefix[len] = '\0'; + return mask; +} + +/* + * Expands a leading environment variable. Returns 0 on success; + * <0 if there is an error. + */ +static int +expand_env(char const *src, char *dest) +{ + char const *var, *suffix; + unsigned len; + + if (*src != '$') { + strcpy(dest, src); + return 0; + } + + /* Find end of variable */ + if (src[1] == '{') { /* ${FOO} form */ + var = src+2; + len = strchr(var, '}') - var; + suffix = src+2+len+1; + } else { /* $FOO form - allow $ for VMS */ + var = src+1; + len = strspn(var, "ABCDEFGHIJKLMNOPQRSTUVWXYZ$_"); + suffix = src+1+len; + } + + memcpy(dest, var, len); /* Copy name */ + dest[len] = '\0'; /* Null-terminate */ + + var = getenv(dest); + if (!var || !*var) + return -1; /* No env variable */ + + /* Copy expanded form to destination */ + strcpy(dest, var); + + /* Add tail */ + strcat(dest, suffix); + + return 0; +} + +/* Don't forget to change 'pgp25' whenever you update rel_version past 2.5 */ +char const * const manual_dirs[] = { +#if defined(VMS) + "$PGPPATH", "", "[pgp]", "[pgp25]", + PGP_SYSTEM_DIR, "SYS$LOGIN:", "SYS$LOGIN:[pgp]", + "SYS$LOGIN:[pgp25]", "[-]", +#elif defined(UNIX) + "$PGPPATH", "", "pgp", "pgp25", PGP_SYSTEM_DIR, + "$HOME/.pgp", "$HOME", "$HOME/pgp", "$HOME/pgp25", "..", +#elif defined(AMIGA) + "$PGPPATH", "", "pgp", "pgp25", ":pgp", ":pgp25", ":", "/", +#else /* MSDOS or ATARI */ + "$PGPPATH", "", "pgp", "pgp25", "\\pgp", "\\pgp25", "\\", "..", + "c:\\pgp", "c:\\pgp25", +#endif + 0 }; + +unsigned +manuals_missing(void) +{ + char buf[256]; + unsigned mask = ~0u; + char const * const *p; + + for (p = manual_dirs; *p; p++) { + if (expand_env(*p, buf) < 0) + continue; /* Ignore */ + mask &= doc_missing(buf); + if (!mask) + break; + } + + return mask; +} + +/* + * Why all this code? + * + * Some dimwits have been distributing versions of PGP (especially on MS-DOS) + * without the manuals. This frustrates users (who call Philip Zimmermann + * at all hours of the day and night), and in addition to depriving them + * of instructions on how to operate the program, also deprives them of + * IMPORTANT legal notices. This is a Bad Thing, so we've gone to the + * trouble of being fascist and *forcing* the manuals to be there. + */