--- pgp/src/fileio.c 2018/04/24 16:37:53 1.1 +++ pgp/src/fileio.c 2018/04/24 16:44:32 1.1.1.7 @@ -1,984 +1,1400 @@ -/* 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. - 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. - - Modified 16 Apr 92 - HAJK - Mods for support of VAX/VMS file system -*/ - -#include -#include -#include -#include -#if defined(UNIX) -#include -#if !defined(NeXT) -#include -#endif -#endif -#include "random.h" -#include "mpilib.h" -#include "mpiio.h" -#include "fileio.h" -#include "language.h" -#include "pgp.h" -#ifdef MSDOS -#include -#include -#endif - -/* 1st character of temporary file extension */ -#define TMP_EXT '$' /* extensions are '.$##' */ - -/* The PGPPATH environment variable */ - -char PGPPATH[] = "PGPPATH"; - -/* Disk buffers, used here and in crypto.c */ -byte textbuf[DISKBUFSIZE]; -static byte textbuf2[2*DISKBUFSIZE]; - -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 */ - -boolean is_regular_file(char *filename) -{ -#ifdef S_ISREG - struct stat st; - 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; - - fseek(f, 0L, SEEK_END); - flength = 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 */ - - -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...*/ -#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 */ - wipeout(f); - fclose(f); - 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. - */ - -#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 */ - int lf = strlen(filename); - int lx = strlen(extension); - - if (lf <= lx) - 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((p = strrchr(path, '.')) != NULL && - p[1] == TMP_EXT && strlen(p) == 4); -} - -boolean no_extension(char *filename) -/* Returns TRUE if user left off file extension, allowing default. */ -{ -#ifdef MULTIPLE_DOTS /* filename can have more than one dot */ - if (has_extension(filename, CTX_EXTENSION) || - has_extension(filename, ASC_EXTENSION) || - has_extension(filename, PGP_EXTENSION) || - has_extension(filename, SIG_EXTENSION) || - is_tempfile(filename)) - 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); -#else -#ifdef VMS - char *slashPos = strrchr(filename,']'); /* Locate end of directory spec */ - - /* 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 */ - - -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 */ - -#ifndef MAX_NAMELEN -#if defined(AMIGA) || defined(NeXT) -#define MAX_NAMELEN 255 -#endif -#endif - -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; - -#ifdef MAX_NAMELEN /* overrides the use of pathconf() */ - namemax = MAX_NAMELEN -#else -#ifdef _PC_NAME_MAX - strcpy(dir, path); - if ((p = strrchr(dir, '/')) == NULL) - strcpy(dir, "."); - else - { if (p == dir) - ++p; - *p = '\0'; - } - if ((namemax = pathconf(dir, _PC_NAME_MAX)) <= ext_len) - return; -#else - namemax = 14; -#endif -#endif /* MAX_NAMELEN */ - - if ((p = strrchr(path, '/')) == NULL) - p = path; - else - ++p; - if (strlen(p) > namemax - ext_len) - { - if (verbose) - fprintf(pgpout, "Truncating filename '%s' ", path); - p[namemax - ext_len] = '\0'; - if (verbose) - fprintf(pgpout, "to '%s'\n", path); - } -#endif /* UNIX */ -} - -void force_extension(char *filename, char *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 */ - - -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]; - 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 */ - - - -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); - force_extension(newname,extension); - if (!file_exists(newname)) - { fprintf(pgpout,PSTR("\nShould '%s' be renamed to '%s' [Y/n]? "), - filename,newname); - if (getyesno('y')) - rename(filename,newname); - } - } -} /* 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); - - 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 */ - -int build_path(char *path, char *fileName, char *origPath) -/* Build a path for fileName based on origPath */ -{ int i, lastSlash = 0; - -#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 - - /* 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 */ - } - - strncpy(path,origPath,lastSlash); /* Add path component */ - strcpy(path+lastSlash,fileName); /* Append filename */ - return(0); /* normal return */ -} /* build_path */ - - -void file_to_canon(char *filename) -{ /* Convert filename to canonical form, with slashes as separators */ -#ifdef BSLASH - while (*filename) { - if (*filename == '\\') - *filename = '/'; - ++filename; - } -#endif -} - - -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) -{ -#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)); -#endif -} - - -int copyfile(FILE *f, FILE *g, word32 longcount) -{ /* copy file f to file g, for longcount bytes */ - int count, status = 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) - { - 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 */ - status = -1; - break; - } - longcount -= count; - } - /* if text block was short, exit loop */ - } while (count==DISKBUFSIZE); - burn(textbuf); /* burn sensitive data on stack */ - return(status); -} /* copyfile */ - -int copyfilepos (FILE *f, FILE *g, word32 longcount, word32 fpos) -/* Like copyfile, but takes a position for file f. Returns with - * f and g pointing just past the copied data. - */ -{ - fseek (f, fpos, SEEK_SET); - return copyfile (f, g, longcount); -} - - -#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. */ - int count, status = 0; - byte c, *tb1, *tb2; - int i, nbytes; - 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 */ - tb1 = textbuf; - tb2 = textbuf2; - for (i=0; i0) - { /* Convert by removing CR's */ - tb1 = textbuf; - tb2 = textbuf2; - for (i=0; i= 0) - { wipeout(f); /* Zap source file */ - fclose(f); - remove(srcFile); - fclose(g); - } - else - { if (is_regular_file(destFile)) - { wipeout(g); /* Zap destination file */ - fclose(g); - remove(destFile); - } else - fclose(g); - fclose(f); - } - } - return(status); -} - - -int readPhantomInput(char *filename) -/* read the data from stdin to the phantom input file */ -{ FILE *outFilePtr; - byte buffer[ 512 ]; - int bytesRead; - - if ((outFilePtr = fopen(filename,"wb")) == NULL) - return(-1); - -#ifdef MSDOS - /* Under DOS must set input stream to binary mode to avoid data mangling */ - setmode(fileno(stdin), O_BINARY); -#endif /* MSDOS */ - while ((bytesRead = fread (buffer, 1, 512, stdin)) > 0) - fwrite (buffer, 1, bytesRead, outFilePtr); - fclose (outFilePtr); -#ifdef MSDOS - setmode(fileno(stdin), O_TEXT); /* Reset stream */ -#endif /* MSDOS */ - return(0); -} - - -void writePhantomOutput(char *filename) -/* write the data from the phantom output file to stdout */ -{ FILE *outFilePtr; - byte buffer[ 512 ]; - int bytesRead; - - /* this can't fail since we just created the file */ - outFilePtr = fopen(filename,"rb"); - -#ifdef MSDOS - setmode(fileno(stdout), O_BINARY); -#endif - while ((bytesRead = fread (buffer, 1, 512, outFilePtr)) > 0) - fwrite (buffer, 1, bytesRead, stdout); - fclose (outFilePtr); - fflush(stdout); -#ifdef MSDOS - setmode(fileno(stdout), O_TEXT); -#endif - - /* finally, delete the phantom file */ - wipefile(filename); - remove(filename); -} - -/* Return the size from the current position of file f to the end */ -word32 fsize (FILE *f) -{ - long fpos = ftell (f); - long fpos2; - - fseek (f, 0L, SEEK_END); - fpos2 = ftell (f); - fseek (f, fpos, SEEK_SET); - return (word32)(fpos2 - fpos); -} - -/* Return TRUE if file filename looks like a pure text file */ -int is_text_file (char *filename) -{ - FILE *f = fopen(filename,"rb"); - 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 */ - i = n = fread (buf, 1, sizeof(buf), f); - fclose(f); - if (n <= 0) - return(FALSE); /* empty file or error, not a text file */ - if (compressSignature(buf) >= 0) - 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 */ - } - if (strcmp(language, "ru")) - return(TRUE); - /* assume binary if more than 1/4 bytes have 8th bit set */ - return(bit8 < n / 4); -} /* is_text_file */ - -void *xmalloc(unsigned size) -{ void *p; - if (size == 0) - ++size; - if ((p = malloc(size)) == NULL) - { fprintf(stderr, PSTR("\n\007Out of memory.\n")); - exitPGP(1); - } - return(p); -} - -/*---------------------------------------------------------------------- - * temporary file routines - */ - - -#define MAXTMPF 8 - -#define TMP_INUSE 2 - -static struct -{ char path[MAX_PATH]; - int flags; -} tmpf[MAXTMPF]; - -extern char outdir[]; -extern char basename[]; - -/* - * return a unique temporary file name - */ -char *tempfile(int flags) -{ - int i; - int num = 0; - - for (i = 0; i < MAXTMPF; ++i) - if (tmpf[i].flags == 0) - break; - - if (i == MAXTMPF) - { /* message only for debugging, no need for PSTR */ - fprintf(stderr, "\n\007Out of temporary files\n"); - return(NULL); - } - - do - 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); - - 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(stderr, "\n\007tempfile: cannot find unique name\n"); - return(NULL); - } - - tmpf[i].flags = flags | TMP_INUSE; - if (verbose) - fprintf(stderr, "tempfile: created '%s'\n", tmpf[i].path); - return(tmpf[i].path); -} /* tempfile */ - -/* - * remove temporary file, wipe if necessary. - */ -void rmtemp(char *name) -{ - int i; - - for (i = 0; i < MAXTMPF; ++i) - 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 (verbose) - fprintf(stderr, "rmtemp: removing '%s'\n", name); - if (tmpf[i].flags & TMP_WIPE) - wipefile(name); - remove(name); - tmpf[i].flags = 0; - } else if (verbose) - fprintf(stderr, "rmtemp: not removing '%s'\n", name); - } -} /* rmtemp */ - -/* - * make temporary file permanent, returns the new name. - */ -char *savetemp(char *name, char *newname) -{ - int i, overwrite; - static char buf[MAX_PATH]; - - if (strcmp(name, newname) == 0) - 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", - name, newname); - 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'); - } - else - { fprintf(pgpout,PSTR("\n\007Output file '%s' already exists.\n"),newname); - overwrite = FALSE; - } - - if (!overwrite) - { fprintf(pgpout, PSTR("\nEnter new file name: ")); - getstring(buf, MAX_PATH - 1, TRUE); - if (buf[0] == '\0') - return(NULL); - newname = buf; - } - 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); - } - if (i < MAXTMPF) - tmpf[i].flags = 0; - return(newname); -} /* savetemp */ - -/* - * like savetemp(), only make backup of destname if it exists - */ -int savetempbak(char *tmpname, char *destname) -{ - char bakpath[MAX_PATH]; - - if (strcmp(destname, SCRATCH_KEYRING_PATH) == 0 || - is_tempfile(destname)) - remove(destname); - else - { if (file_exists(destname)) - { - strcpy(bakpath, destname); - force_extension(bakpath, BAK_EXTENSION); - remove(bakpath); - if (rename(destname, bakpath) == -1) - return(-1); - } - } - if (savetemp(tmpname, destname) == NULL) - return(-1); - return(0); -} - -/* - * remove all temporary files and wipe them if necessary - */ -void cleanup_tmpf(void) -{ - int i; - - for (i = 0; i < MAXTMPF; ++i) - if (tmpf[i].flags) - rmtemp(tmpf[i].path); -} /* cleanup_tmpf */ +/* fileio.c - I/O routines for PGP. + PGP: Pretty Good(tm) Privacy - public key cryptography for the masses. + + (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. + + 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 +#include +#ifdef UNIX +#include +#include +#include +#ifdef _BSD +#include +#endif +extern int errno; +#endif /* UNIX */ +#ifdef VMS +#include +#include +#endif +#include "random.h" +#include "mpilib.h" +#include "mpiio.h" +#include "fileio.h" +#include "language.h" +#include "pgp.h" +#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__) || defined(OS2) +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 is not portable, this has the same effect */ +#include "Unknown OS" +#endif + + +/* 1st character of temporary file extension */ +#define TMP_EXT '$' /* extensions are '.$##' */ + +/* The PGPPATH environment variable */ + +static char PGPPATH[] = "PGPPATH"; + +/* Disk buffers, used here and in crypto.c */ +byte textbuf[DISKBUFSIZE]; +static unsigned textbuf2[2 * DISKBUFSIZE / sizeof(unsigned)]; + +boolean file_exists(char *filename) +/* Returns TRUE iff file exists. */ +{ + return access(filename, F_OK) == 0; +} /* file_exists */ + +static boolean is_regular_file(char *filename) +{ +#ifdef S_ISREG + struct stat st; + return stat(filename, &st) != -1 && S_ISREG(st.st_mode); +#else + return TRUE; +#endif +} + + +/* + * 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 void wipeout(FILE * f) +{ + unsigned *p1, *p2, *p3; + unsigned long len; + unsigned long t; + int i; + + /* Get the file size */ + fseek(f, 0L, SEEK_END); + len = ftell(f); + rewind(f); + + /* 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; + } + } +} + + +/* + * Completely overwrite and erase file, so that no sensitive + * information is left on the disk. + */ +int wipefile(char *filename) +{ + FILE *f; + /* open file f for read/write, in binary (not text) mode... */ + if ((f = fopen(filename, FOPRWBIN)) == NULL) + return -1; /* error - file can't be opened */ + wipeout(f); + fclose(f); + return 0; /* normal return */ +} /* wipefile */ + +/* + * Returns the part of a filename after all directory specifiers. + */ +char *file_tail(char *filename) +{ + char *p; + char const *s = DIRSEPS; + + while (*s) { + p = strrchr(filename, *s); + if (p) + filename = p + 1; + s++; + } + + return filename; +} + + +/* return TRUE if extension matches the end of filename */ +boolean has_extension(char *filename, char *extension) +{ + int lf = strlen(filename); + int lx = strlen(extension); + + if (lf <= lx) + return FALSE; + return !strcmp(filename + lf - lx, extension); +} + +/* return TRUE if path is a filename created by tempfile() */ +/* Filename matches "*.$[0-9][0-9]" */ +boolean is_tempfile(char *path) +{ + char *p = strrchr(path, '.'); + + return p != NULL && p[1] == TMP_EXT && + isdigit(p[2]) && isdigit(p[3]) && p[4] == '\0'; +} + +/* + * 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. + */ +boolean no_extension(char *filename) +{ +#ifdef MULTIPLE_DOTS /* filename can have more than one dot */ + if (has_extension(filename, ASC_EXTENSION) || + has_extension(filename, PGP_EXTENSION) || + has_extension(filename, SIG_EXTENSION) || + is_tempfile(filename)) + return FALSE; + else + return TRUE; +#else + filename = file_tail(filename); + + return strrchr(filename, '.') == NULL; +#endif +} /* no_extension */ + + +/* deletes trailing ".xxx" file extension after the period. */ +void drop_extension(char *filename) +{ + if (!no_extension(filename)) + *strrchr(filename, '.') = '\0'; +} /* drop_extension */ + + +/* append filename extension if there isn't one already. */ +void default_extension(char *filename, char *extension) +{ + if (no_extension(filename)) + strcat(filename, extension); +} /* default_extension */ + +#ifndef MAX_NAMELEN +#if defined(AMIGA) || defined(NeXT) || (defined(BSD) && BSD > 41) || (defined(sun) && defined(i386)) +#define MAX_NAMELEN 255 +#else +#include +#endif +#endif + +/* truncate the filename so that an extension can be tacked on. */ +static void truncate_name(char *path, int ext_len) +{ +#ifdef UNIX /* for other systems this is a no-op */ + char *p; +#ifdef MAX_NAMELEN /* overrides the use of pathconf() */ + int namemax = MAX_NAMELEN; +#else + int namemax; +#ifdef _PC_NAME_MAX + char dir[MAX_PATH]; + + strcpy(dir, path); + if ((p = strrchr(dir, '/')) == NULL) { + strcpy(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 /* 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 (verbose) + fprintf(pgpout, "Truncating filename '%s' ", path); + p[namemax - ext_len] = '\0'; + if (verbose) + fprintf(pgpout, "to '%s'\n", path); + } +#endif /* UNIX */ +} + +/* change the filename extension. */ +void force_extension(char *filename, char *extension) +{ + drop_extension(filename); /* out with the old */ + truncate_name(filename, strlen(extension)); + strcat(filename, extension); /* in with the new */ +} /* force_extension */ + + +/* + * 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. + */ +boolean getyesno(char default_answer) +{ + char buf[8]; + static char yes[8], no[8]; + + 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 */ + +/* if user consents to it, change the filename extension. */ +char *maybe_force_extension(char *filename, char *extension) +{ + static char newname[MAX_PATH]; + if (!has_extension(filename, extension)) { + strcpy(newname, filename); + force_extension(newname, extension); + if (!file_exists(newname)) { + fprintf(pgpout, LANG("\nShould '%s' be renamed to '%s' [Y/n]? "), + filename, newname); + if (getyesno('y')) + return newname; + } + } + return NULL; +} /* maybe_force_extension */ + +/* + * Add a trailing directory separator to a name, if absent. + */ +static void addslash(char *name) +{ + int i = strlen(name); + + if (i != 0 && !strchr(DIRSEPS, name[i - 1])) { + name[i] = DIRSEPS[0]; + name[i + 1] = '\0'; + } +} + +/* + * Builds a filename with a complete path specifier from the environmental + * variable PGPPATH. + */ +char *buildfilename(char *result, char *fname) +{ + char const *s = getenv(PGPPATH); + + result[0] = '\0'; + + 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 */ + +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; +} + + +/* Convert filename to canonical form, with slashes as separators */ +void file_to_canon(char *filename) +{ +#ifdef BSLASH + while (*filename) { + if (*filename == '\\') + *filename = '/'; + ++filename; + } +#endif +} + + +int write_error(FILE * f) +{ + 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; +} + +/* copy file f to file g, for longcount bytes */ +int copyfile(FILE * f, FILE * g, word32 longcount) +{ + int count, status = 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) { + 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 */ + status = -1; + break; + } + longcount -= count; + } + /* if text block was short, exit loop */ + } while (count == DISKBUFSIZE); + burn(textbuf); /* burn sensitive data on stack */ + return status; +} /* copyfile */ + +/* + * Like copyfile, but takes a position for file f. Returns with + * f and g pointing just past the copied data. + */ +int copyfilepos(FILE * f, FILE * g, word32 longcount, word32 fpos) +{ + fseek(f, fpos, SEEK_SET); + return copyfile(f, g, 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. + */ +int copyfile_to_canon(FILE * f, FILE * g, word32 longcount) +{ + int count, status = 0; + byte c, *tb1, *tb2; + int i, nbytes; + 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 */ + tb1 = textbuf; + tb2 = (byte *) textbuf2; + for (i = 0; i < count; ++i) { + switch (CONVERSION) { + case EXT_CONV: + c = EXT_C(*tb1++); + break; + case INT_CONV: + c = INT_C(*tb1++); + break; + default: + c = *tb1++; + } + if (strip_spaces) { + if (c == ' ') { + /* Don't output spaces yet */ + nspaces += 1; + } else { + if (c == '\n') { + *tb2++ = '\r'; + nspaces = 0; /* Delete trailing spaces */ + } + if (nspaces) { + /* Put out spaces now */ + do + *tb2++ = ' '; + while (--nspaces); + } + *tb2++ = c; + } + } else { + if (c == '\n') + *tb2++ = '\r'; + *tb2++ = c; + } + } + nbytes = tb2 - (byte *) textbuf2; + if (fwrite(textbuf2, 1, nbytes, g) != nbytes) { + /* Problem: return error value */ + status = -1; + break; + } + longcount -= count; + } + /* if text block was short, exit loop */ + } while (count == DISKBUFSIZE); + burn(textbuf); /* burn sensitive data on stack */ + burn(textbuf2); + return status; +} /* copyfile_to_canon */ + + +/* copy file f to file g, for longcount bytes. Convert from + * canonical to local form as we go. g is open in text mode. Canonical + * form uses crlf's as line separators. + */ +int copyfile_from_canon(FILE * f, FILE * g, word32 longcount) +{ + int count, status = 0; + byte c, *tb1, *tb2; + int i, nbytes; + 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 removing CR's */ + tb1 = textbuf; + tb2 = (byte *) textbuf2; + for (i = 0; i < count; ++i) { + switch (CONVERSION) { + case EXT_CONV: + c = EXT_C(*tb1++); + break; + case INT_CONV: + c = INT_C(*tb1++); + break; + default: + c = *tb1++; + } + if (c != '\r') + *tb2++ = c; + } + nbytes = tb2 - (byte *) textbuf2; + if (fwrite(textbuf2, 1, nbytes, g) != nbytes) { + /* Problem: return error value */ + status = -1; + break; + } + longcount -= count; + } + /* if text block was short, exit loop */ + } while (count == DISKBUFSIZE); + burn(textbuf); /* burn sensitive data on stack */ + burn(textbuf2); + return status; +} /* copyfile_from_canon */ + +/* Copy srcFile to destFile */ +int copyfiles_by_name(char *srcFile, char *destFile) +{ + FILE *f, *g; + int status = 0; + long fileLength; + + f = fopen(srcFile, FOPRBIN); + if (f == NULL) + return -1; + g = fopen(destFile, FOPWBIN); + if (g == NULL) { + fclose(f); + return -1; + } + /* Get file length and copy it */ + fseek(f, 0L, SEEK_END); + fileLength = ftell(f); + rewind(f); + status = copyfile(f, g, fileLength); + fclose(f); + if (write_error(g)) + status = -1; + fclose(g); + return status; +} /* copyfiles_by_name */ + +/* Copy srcFile to destFile, converting to canonical text form */ +int make_canonical(char *srcFile, char *destFile) +{ + FILE *f, *g; + int status = 0; + long fileLength; + + if (((f = fopen(srcFile, FOPRTXT)) == NULL) || + ((g = fopen(destFile, FOPWBIN)) == NULL)) + /* Can't open files */ + return -1; + + /* Get file length and copy it */ + fseek(f, 0L, SEEK_END); + fileLength = ftell(f); + rewind(f); + CONVERSION = INT_CONV; + status = copyfile_to_canon(f, g, fileLength); + CONVERSION = NO_CONV; + fclose(f); + if (write_error(g)) + status = -1; + fclose(g); + return status; +} /* make_canonical */ + +/* + * Like rename() but will try to copy the file if the rename fails. + * This is because under OS's with multiple physical volumes if the + * source and destination are on different volumes the rename will fail + */ +int rename2(char *srcFile, char *destFile) +{ + FILE *f, *g; + int status = 0; + long fileLength; + +#ifdef VMS + if (rename(srcFile, destFile) != 0) +#else + if (rename(srcFile, destFile) == -1) +#endif + { + /* Rename failed, try a copy */ + if (((f = fopen(srcFile, FOPRBIN)) == NULL) || + ((g = fopen(destFile, FOPWBIN)) == NULL)) + /* Can't open files */ + return -1; + + /* Get file length and copy it */ + fseek(f, 0L, SEEK_END); + fileLength = ftell(f); + rewind(f); + status = copyfile(f, g, fileLength); + if (write_error(g)) + status = -1; + + /* Zap source file if the copy went OK, otherwise zap the (possibly + incomplete) destination 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 */ + fclose(g); + remove(destFile); + } else { + fclose(g); + } + fclose(f); + } + } + return status; +} + +/* read the data from stdin to the phantom input file */ +int readPhantomInput(char *filename) +{ + FILE *outFilePtr; + byte buffer[512]; + int bytesRead, status = 0; + + if (verbose) + fprintf(pgpout, "writing stdin to file %s\n", filename); + if ((outFilePtr = fopen(filename, FOPWBIN)) == NULL) + return -1; + +#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 || OS2 */ + while ((bytesRead = fread(buffer, 1, 512, stdin)) > 0) + if (fwrite(buffer, 1, bytesRead, outFilePtr) != bytesRead) { + status = -1; + break; + } + if (write_error(outFilePtr)) + status = -1; + fclose(outFilePtr); +#if defined(MSDOS) || defined(OS2) + setmode(fileno(stdin), O_TEXT); /* Reset stream */ +#endif /* MSDOS || OS2 */ + return status; +} + +/* write the data from the phantom output file to stdout */ +int writePhantomOutput(char *filename) +{ + FILE *outFilePtr; + byte buffer[512]; + 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, FOPRBIN); + +#if defined(MSDOS) || defined(OS2) + setmode(fileno(stdout), O_BINARY); +#endif /* MSDOS || OS2 */ + while ((bytesRead = fread(buffer, 1, 512, outFilePtr)) > 0) + if (fwrite(buffer, 1, bytesRead, stdout) != bytesRead) { + status = -1; + break; + } + fclose(outFilePtr); + fflush(stdout); + if (ferror(stdout)) { + status = -1; + fprintf(pgpout, LANG("\007Write error on stdout.\n")); + } +#if defined(MSDOS) || defined(OS2) + setmode(fileno(stdout), O_TEXT); +#endif /* MSDOS || OS2 */ + + return status; +} + +/* Return the size from the current position of file f to the end */ +word32 fsize(FILE * f) +{ + long fpos = ftell(f); + long fpos2; + + fseek(f, 0L, SEEK_END); + fpos2 = ftell(f); + fseek(f, fpos, SEEK_SET); + return (word32) (fpos2 - fpos); +} + +/* Return TRUE if file filename looks like a pure text file */ +int is_text_file(char *filename) +{ + 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 */ + i = n = fread(buf, 1, sizeof(buf), f); + fclose(f); + if (n <= 0) + return FALSE; /* empty file or error, not a text file */ + if (compressSignature(buf) >= 0) + 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 */ + } + if (strcmp(language, "ru") == 0) + return TRUE; + /* assume binary if more than 1/4 bytes have 8th bit set */ + return bit8 < n / 4; +} /* is_text_file */ + +VOID *xmalloc(unsigned size) +{ + VOID *p; + if (size == 0) + ++size; + p = malloc(size); + if (p == NULL) { + fprintf(stderr, LANG("\n\007Out of memory.\n")); + exitPGP(1); + } + return p; +} + +/*---------------------------------------------------------------------- + * temporary file routines + */ + + +#define MAXTMPF 8 + +#define TMP_INUSE 2 + +static struct { + char path[MAX_PATH]; + int flags; + int num; +} tmpf[MAXTMPF]; + +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, 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 LANG */ + fprintf(stderr, "\n\007Out of temporary files\n"); + return NULL; + } + 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 ? tmpdir : outdir), + tmpbasename, TMP_EXT, num); + if (!file_exists(tmpf[i].path)) + break; + } + while (++num < 100); + + if (num == 100) { + fprintf(pgpout, "\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(pgpout, "tempfile: created '%s'\n", tmpf[i].path); + return tmpf[i].path; +} /* tempfile */ + +/* + * remove temporary file, wipe if necessary. + */ +void rmtemp(char *name) +{ + int i; + + for (i = 0; i < MAXTMPF; ++i) + 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 (verbose) + fprintf(pgpout, "rmtemp: removing '%s'\n", name); + if (tmpf[i].flags & TMP_WIPE) + wipefile(name); + if (!remove(name)) { + tmpf[i].flags = 0; + } else if (verbose) { + fprintf(stderr, "\nrmtemp: Failed to remove %s", name); + perror("\nError"); + } + } else if (verbose) + fprintf(pgpout, "rmtemp: not removing '%s'\n", name); + } +} /* rmtemp */ + +/* + * make temporary file permanent, returns the new name. + */ +char *savetemp(char *name, char *newname) +{ + int i, overwrite; + static char buf[MAX_PATH]; + + if (strcmp(name, newname) == 0) + 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(pgpout, "savetemp: not renaming '%s' to '%s'\n", + name, newname); + return name; /* return original file name */ + } + } + while (file_exists(newname)) { + if (batchmode && !force_flag) { + fprintf(pgpout, LANG("\n\007Output file '%s' already exists.\n"), + newname); + return NULL; + } + 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, LANG("\nEnter new file name: ")); + getstring(buf, MAX_PATH - 1, TRUE); + if (buf[0] == '\0') + return NULL; + newname = buf; + } else { + remove(newname); + } + } + if (verbose) + 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 */ + +/* + * like savetemp(), only make backup of destname if it exists + */ +int savetempbak(char *tmpname, char *destname) +{ + char bakpath[MAX_PATH]; +#ifdef UNIX + int mode = -1; +#endif + + if (is_tempfile(destname)) { + remove(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) +#endif + return -1; + } + } + if (savetemp(tmpname, destname) == NULL) + return -1; +#ifdef UNIX + if (mode != -1) + chmod(destname, mode); +#endif + return 0; +} + +/* + * remove all temporary files and wipe them if necessary + */ +void cleanup_tmpf(void) +{ + int i; + + for (i = 0; i < MAXTMPF; ++i) + if (tmpf[i].flags) + rmtemp(tmpf[i].path); +} /* cleanup_tmpf */ + +/* + * Routines to search for the manuals. + * + * 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? + */ + +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); + +#ifndef VMS + /* + * Optimization: if directory doesn't exist, stop. But access() + * (used internally by file_exists()) doesn't work on dirs under VMS. + */ + if (prefix[0] && !file_exists(prefix)) /* Directory doesn't exist? */ + return mask; +#endif /* VMS */ + 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 'pgp26' whenever you update rel_version past 2.6 */ +char const *const manual_dirs[] = +{ +#if defined(VMS) + "$PGPPATH", "", "[pgp]", "[pgp26]", "[pgp262]", + PGP_SYSTEM_DIR, "SYS$LOGIN:", "SYS$LOGIN:[pgp]", + "SYS$LOGIN:[pgp26]", "SYS$LOGIN:[pgp262]", "[-]", +#elif defined(UNIX) + "$PGPPATH", "", "pgp", "pgp26", "pgp262", PGP_SYSTEM_DIR, + "$HOME/.pgp", "$HOME", "$HOME/pgp", "$HOME/pgp26", "..", +#elif defined(AMIGA) + "$PGPPATH", "", "pgp", "pgp26", ":pgp", ":pgp26", ":pgp262", + ":", "/", +#else /* MSDOS or ATARI */ + "$PGPPATH", "", "pgp", "pgp26", "\\pgp", "\\pgp26", "\\pgp262", + "\\", "..", "c:\\pgp", "c:\\pgp26", +#endif + 0}; + +unsigned manuals_missing(void) +{ + char buf[256]; + unsigned mask = ~((unsigned)0); + 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? + * + * See block of comments above. + */