1 /* fileio.c - I/O routines for PGP.
2 PGP: Pretty Good(tm) Privacy - public key cryptography for the masses.
3
4 (c) Copyright 1990-1996 by Philip Zimmermann. All rights reserved.
5 The author assumes no liability for damages resulting from the use
6 of this software, even if the damage results from defects in this
7 software. No warranty is expressed or implied.
8
9 Note that while most PGP source modules bear Philip Zimmermann's
10 copyright notice, many of them have been revised or entirely written
11 by contributors who frequently failed to put their names in their
12 code. Code that has been incorporated into PGP from other authors
13 was either originally published in the public domain or is used with
14 permission from the various authors.
15
16 PGP is available for free to the public under certain restrictions.
17 See the PGP User's Guide (included in the release package) for
18 important information about licensing, patent restrictions on
19 certain algorithms, trademarks, copyrights, and export controls.
20
21 Modified 16 Apr 92 - HAJK
22 Mods for support of VAX/VMS file system
23
24 Modified 17 Nov 92 - HAJK
25 Change to temp file stuff for VMS.
26 */
27
28 #include <ctype.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <errno.h>
33 #ifdef UNIX
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 #ifdef _BSD
38 #include <sys/param.h>
39 #endif
40 extern int errno;
41 #endif /* UNIX */
42 #ifdef VMS
43 #include <file.h>
44 #include <assert.h>
45 #endif
46 #include "random.h"
47 #include "mpilib.h"
48 #include "mpiio.h"
49 #include "fileio.h"
50 #include "language.h"
51 #include "pgp.h"
52 #include "exitpgp.h"
53 #include "charset.h"
54 #include "system.h"
55 #if defined(MSDOS) || defined(OS2) || defined (WIN32)
56 #include <io.h>
57 #include <fcntl.h>
58 #endif
59 #ifdef MACTC5
60 #include "crypto.h" /* for get_header_info_from_file() */
61 #include "AEStuff.h"
62 #include "AppGlobals.h"
63 #include "MacPGP.h"
64 #include "Macutil2.h"
65 #include "Macutil3.h"
66 #define MULTIPLE_DOTS
67 extern Boolean AEProcessing;
68 pascal Boolean idleProc(EventRecord * eventIn, long *sleep, RgnHandle * mouseRgn);
69 #endif
70
71 char *ck_dup_output(char *, boolean, boolean);
72
73 #ifndef F_OK
74 #define F_OK 0
75 #define X_OK 1
76 #define W_OK 2
77 #define R_OK 4
78 #endif /* !F_OK */
79
80 /*
81 * DIRSEPS is a string of possible directory-separation characters
82 * The first one is the preferred one, which goes in between
83 * PGPPATH and the file name if PGPPATH is not terminated with a
84 * directory separator.
85 */
86
87 #if defined(MSDOS) || defined(__MSDOS__) || defined(OS2) || defined (WIN32)
88 static char const DIRSEPS[] = "\\/:";
89 #define BSLASH
90
91 #elif defined(ATARI)
92 static char const DIRSEPS[] = "\\/:";
93 #define BSLASH
94
95 #elif defined(UNIX)
96 static char const DIRSEPS[] = "/";
97 #define MULTIPLE_DOTS
98
99 #elif defined(AMIGA)
100 static char const DIRSEPS[] = "/:";
101 #define MULTIPLE_DOTS
102
103 #elif defined(VMS)
104 static char const DIRSEPS[] = "]:";
105
106 #elif defined(EBCDIC)
107 static char const DIRSEPS[] = "("; /* Any more? */
108 #define MULTIPLE_DOTS
109
110 #elif defined(MACTC5)
111 #define MULTIPLE_DOTS
112 static char const DIRSEPS[] = ":";
113
114 #else
115 /* #error is not portable, this has the same effect */
116 #include "Unknown OS"
117 #endif
118
119 #ifdef __PUREC__
120 #include <ext.h>
access(const char * name,int flag)121 int access(const char *name,int flag)
122 {
123 struct ffblk dummy;
124 return findfirst(name,&dummy,-1);
125 }
126 #endif
127
128 /* 1st character of temporary file extension */
129 #define TMP_EXT '$' /* extensions are '.$##' */
130
131 /* The PGPPATH environment variable */
132
133 static char PGPPATH[] = "PGPPATH";
134
135 /* Disk buffers, used here and in crypto.c */
136 byte textbuf[DISKBUFSIZE];
137 static unsigned textbuf2[2 * DISKBUFSIZE / sizeof(unsigned)];
138
file_exists(char * filename)139 boolean file_exists(char *filename)
140 /* Returns TRUE iff file exists. */
141 {
142 #ifdef MACTC5
143 FILE *f;
144 /* open file f for read, in binary (not text) mode...*/
145 if ((f = fopen(filename,FOPRBIN)) == NULL)
146 return(FALSE);
147 fclose(f);
148 return(TRUE);
149 #else
150 return access(filename, F_OK) == 0;
151 #endif
152 } /* file_exists */
153
is_regular_file(char * filename)154 static boolean is_regular_file(char *filename)
155 {
156 #ifdef S_ISREG
157 struct stat st;
158 return stat(filename, &st) != -1 && S_ISREG(st.st_mode);
159 #else
160 return TRUE;
161 #endif
162 }
163
164
165 /*
166 * This wipes a file with pseudo-random data. The purpose of this is to
167 * make sure no sensitive information is left on the disk. The use
168 * of pseudo-random data is to defeat disk compression drivers (such as
169 * Stacker and dblspace) so that we are guaranteed that the entire file
170 * has been overwritten.
171 *
172 * Note that the file MUST be open for read/write.
173 *
174 * It may not work to eliminate everything from non-volatile storage
175 * if the OS you're using does its own paging or swapping. Then
176 * it's an issue of how the OS's paging device is wiped, and you can
177 * only hope that the space will be reused within a few seconds.
178 *
179 * Also, some file systems (in particular, the Logging File System
180 * for Sprite) do not write new data in the same place as old data,
181 * defeating this wiping entirely. Fortunately, such systems
182 * usually don't need a swap file, and for small temp files, they
183 * do write-behind, so if you create and delete a file fast enough,
184 * it never gets written to disk at all.
185 */
186
187 /*
188 * The data is randomly generated with the size of the file as a seed.
189 * The data should be random and not leak information. If someone is
190 * examining deleted files, presumably they can reconstruct the file size,
191 * so that's not a secret. H'm... this wiping algorithm makes it easy to,
192 * given a block of data, find the size of the file it came from
193 * and the offset of this block within it. That in turn reveals
194 * something about the state of the disk's allocation tables when the
195 * file was used, possibly making it easier to find other files created
196 * at neaby times - such as plaintext files. Is this acceptable?
197 */
198
199 /*
200 * Theory of operation: We use the additive congruential RNG
201 * r[i] = r[i-24] + r[i-55], from Knuth, Vol. 2. This is fast
202 * and has a long enough period that there should be no repetitions
203 * in even a huge file. It is seeded with r[-55] through r[-1]
204 * using another polynomial-based RNG. We seed a linear feedback
205 * shift register (CRC generator) with the size of the swap file,
206 * and clock in 0 bits. Each 32 bits, the value of the generator is
207 * taken as the next integer. This is just to ensure a reasonably
208 * even mix of 1's and 0's in the initialization vector.
209 */
210
211 /*
212 * This is the CRC-32 polynomial, which should be okay for random
213 * number generation.
214 * 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
215 * = 1 0000 0100 1100 0001 0001 1101 1011 0111
216 * = 0x04c11db7
217 */
218 #define POLY 0x04c11db7
219
wipeout(FILE * f)220 static void wipeout(FILE * f)
221 {
222 unsigned *p1, *p2, *p3;
223 unsigned long len;
224 unsigned long t;
225 int i;
226
227 /* Get the file size */
228 fseek(f, 0L, SEEK_END);
229 len = ftell(f);
230 #ifdef MACTC5
231 len = len + 4096 - (len % 4096);
232 #endif
233 rewind(f);
234
235 /* Seed of first RNG. Inverted to get more 1 bits */
236 t = ~len;
237
238 /* Initialize first 55 words of buf with pseudo-random stuff */
239 p1 = (unsigned *) textbuf2 + 55;
240 do {
241 for (i = 0; i < 32; i++)
242 t = (t & 0x80000000) ? t << 1 ^ POLY : t << 1;
243 *--p1 = (unsigned) t;
244 } while (p1 > (unsigned *) textbuf2);
245
246 while (len) {
247 /* Fill buffer with pseudo-random integers */
248
249 p3 = (unsigned *) textbuf2 + 55;
250 p2 = (unsigned *) textbuf2 + 24;
251 p1 = (unsigned *) textbuf2 + sizeof(textbuf2) / sizeof(*p1);
252 do {
253 *--p1 = *--p2 + *--p3;
254 } while (p2 > (unsigned *) textbuf2);
255
256 p2 = (unsigned *) textbuf2 + sizeof(textbuf2) / sizeof(*p1);
257 do {
258 *--p1 = *--p2 + *--p3;
259 } while (p3 > (unsigned *) textbuf2);
260
261 p3 = (unsigned *) textbuf2 + sizeof(textbuf2) / sizeof(*p3);
262 do {
263 *--p1 = *--p2 + *--p3;
264 } while (p1 > (unsigned *) textbuf2);
265
266 /* Write it out - yes, we're ignoring errors */
267 if (len > sizeof(textbuf2)) {
268 fwrite((char const *) textbuf2, sizeof(textbuf2), 1, f);
269 len -= sizeof(textbuf2);
270 #ifdef MACTC5
271 mac_poll_for_break();
272 #endif
273 } else {
274 fwrite((char const *) textbuf2, len, 1, f);
275 len = 0;
276 }
277 }
278 }
279
280
281 /*
282 * Completely overwrite and erase file, so that no sensitive
283 * information is left on the disk.
284 */
wipefile(char * filename)285 int wipefile(char *filename)
286 {
287 FILE *f;
288 /* open file f for read/write, in binary (not text) mode... */
289 if ((f = fopen(filename, FOPRWBIN)) == NULL)
290 return -1; /* error - file can't be opened */
291 wipeout(f);
292 fclose(f);
293 return 0; /* normal return */
294 } /* wipefile */
295
296 /*
297 * Returns the part of a filename after all directory specifiers.
298 */
file_tail(char * filename)299 char *file_tail(char *filename)
300 {
301 char *p;
302 char const *s = DIRSEPS;
303
304 while (*s) {
305 p = strrchr(filename, *s);
306 if (p)
307 filename = p + 1;
308 s++;
309 }
310
311 return filename;
312 }
313
314
315 /* return TRUE if extension matches the end of filename */
has_extension(char * filename,char * extension)316 boolean has_extension(char *filename, char *extension)
317 {
318 int lf = strlen(filename);
319 int lx = strlen(extension);
320
321 if (lf <= lx)
322 return FALSE;
323 return !strcmp(filename + lf - lx, extension);
324 }
325
326 /* return TRUE if path is a filename created by tempfile() */
327 /* Filename matches "*.$[0-9][0-9]" */
is_tempfile(char * path)328 boolean is_tempfile(char *path)
329 {
330 char *p = strrchr(path, '.');
331
332 return p != NULL && p[1] == TMP_EXT &&
333 isdigit(p[2]) && isdigit(p[3]) && p[4] == '\0';
334 }
335
336 /*
337 * Returns TRUE if user left off file extension, allowing default.
338 * Note that the name is misleading if multiple dots are allowed.
339 * not_pgp_extension or something would be better.
340 */
no_extension(char * filename)341 boolean no_extension(char *filename)
342 {
343 #ifdef MULTIPLE_DOTS /* filename can have more than one dot */
344 if (has_extension(filename, ASC_EXTENSION) ||
345 has_extension(filename, PGP_EXTENSION) ||
346 has_extension(filename, SIG_EXTENSION) ||
347 #ifdef MACTC5
348 has_extension(filename,".tmp") ||
349 #endif
350 is_tempfile(filename))
351 return FALSE;
352 else
353 return TRUE;
354 #else
355 filename = file_tail(filename);
356
357 return strrchr(filename, '.') == NULL;
358 #endif
359 } /* no_extension */
360
361
362 /* deletes trailing ".xxx" file extension after the period. */
drop_extension(char * filename)363 void drop_extension(char *filename)
364 {
365 if (!no_extension(filename))
366 *strrchr(filename, '.') = '\0';
367 } /* drop_extension */
368
369
370 /* append filename extension if there isn't one already. */
default_extension(char * filename,char * extension)371 void default_extension(char *filename, char *extension)
372 {
373 if (no_extension(filename))
374 strcat(filename, extension);
375 } /* default_extension */
376
377 #ifndef MAX_NAMELEN
378 #if defined(AMIGA) || defined(NeXT) || (defined(BSD) && BSD > 41) || (defined(sun) && defined(i386))
379 #define MAX_NAMELEN 255
380 #else
381 #ifdef MACTC5
382 #define MAX_NAMELEN 31
383 #else
384 #include <limits.h>
385 #endif
386 #endif
387 #endif
388
389 /* truncate the filename so that an extension can be tacked on. */
truncate_name(char * path,int ext_len)390 static void truncate_name(char *path, int ext_len)
391 {
392 #ifdef UNIX /* for other systems this is a no-op */
393 char *p;
394 #ifdef MAX_NAMELEN /* overrides the use of pathconf() */
395 int namemax = MAX_NAMELEN;
396 #else
397 int namemax;
398 #ifdef _PC_NAME_MAX
399 char dir[MAX_PATH];
400
401 strcpy(dir, path);
402 if ((p = strrchr(dir, '/')) == NULL) {
403 strcpy(dir, ".");
404 } else {
405 if (p == dir)
406 ++p;
407 *p = '\0';
408 }
409 if ((namemax = pathconf(dir, _PC_NAME_MAX)) <= ext_len)
410 return;
411 #else
412 #ifdef NAME_MAX
413 namemax = NAME_MAX;
414 #else
415 namemax = 14;
416 #endif /* NAME_MAX */
417 #endif /* _PC_NAME_MAX */
418 #endif /* MAX_NAMELEN */
419
420 if ((p = strrchr(path, '/')) == NULL)
421 p = path;
422 else
423 ++p;
424 if (strlen(p) > namemax - ext_len) {
425 if (verbose)
426 fprintf(pgpout, "Truncating filename '%s' ", path);
427 p[namemax - ext_len] = '\0';
428 if (verbose)
429 fprintf(pgpout, "to '%s'\n", path);
430 }
431 #else
432 #ifdef MACTC5
433 char *p;
434 p = file_tail(path);
435 if (verbose)
436 fprintf(pgpout, LANG("Truncating filename '%s' "), path);
437 if (strlen(p) + ext_len > MAX_NAMELEN) p[MAX_NAMELEN - ext_len] = '\0';
438 #endif /* MACTC5 */
439 #endif /* UNIX */
440 }
441
442 /* change the filename extension. */
force_extension(char * filename,char * extension)443 void force_extension(char *filename, char *extension)
444 {
445 drop_extension(filename); /* out with the old */
446 truncate_name(filename, strlen(extension));
447 strcat(filename, extension); /* in with the new */
448 } /* force_extension */
449
450
451 /*
452 * Get yes/no answer from user, returns TRUE for yes, FALSE for no.
453 * First the translations are checked, if they don't match 'y' and 'n'
454 * are tried.
455 */
456 #ifdef MACTC5
457
getyesno(char default_answer)458 boolean getyesno(char default_answer)
459 {
460 extern FILE *logfile;
461 short alertid,i,large,err;
462 char dfault[8], ndfault[8];
463 ProcessSerialNumber psn;
464 if (batchmode)
465 return(default_answer == 'y' ? TRUE : FALSE);
466 if (strlen(Yes_No_Message)<72) large=0;
467 else large=100;
468 strcpy(dfault,default_answer == 'y' ? LANG("y") : LANG("n"));
469 strcpy(ndfault,default_answer == 'n' ? LANG("y") : LANG("n"));
470 for(i=0;i<strlen(Yes_No_Message);i++)
471 if (Yes_No_Message[i]<' ' && Yes_No_Message[i]>=0) Yes_No_Message[i]=' '; /* It's a signed char! */
472 InitCursor();
473 alertid=(default_answer == 'n' ? 211+large : 212+large);
474 c2pstr(Yes_No_Message);
475 ParamText((uchar *)Yes_No_Message,(uchar *)"", \
476 (uchar *)"",(uchar *)"");
477 if (AEProcessing) {
478 if (gHasProcessManager)
479 GetFrontProcess(&psn);
480 if(MyInteractWithUser())
481 return default_answer;
482 if (gHasProcessManager)
483 SetFrontProcess(&psn);
484 }
485 if (CautionAlert(alertid,nil)==1){
486 p2cstr((uchar *)Yes_No_Message);
487 fputs(strcat(Yes_No_Message,dfault),stderr);
488 fputc('\n',stderr);
489 fflush(stderr);
490 return(default_answer == 'y' ? TRUE : FALSE);
491 }
492 p2cstr((uchar *)Yes_No_Message);
493 fputs(strcat(Yes_No_Message,ndfault),stderr);
494 fputc('\n',stderr);
495 fflush(stderr);
496 return(default_answer == 'n' ? TRUE : FALSE);
497 }
498 #else
499
getyesno(char default_answer)500 boolean getyesno(char default_answer)
501 {
502 char buf[8];
503 static char yes[8], no[8];
504
505 if (yes[0] == '\0') {
506 strncpy(yes, LANG("y"), 7);
507 strncpy(no, LANG("n"), 7);
508 }
509 if (!batchmode) { /* return default answer in batchmode */
510 getstring(buf, 6, TRUE); /* echo keyboard input */
511 strlwr(buf);
512 if (!strncmp(buf, no, strlen(no)))
513 return FALSE;
514 if (!strncmp(buf, yes, strlen(yes)))
515 return TRUE;
516 if (buf[0] == 'n')
517 return FALSE;
518 if (buf[0] == 'y')
519 return TRUE;
520 }
521 return default_answer == 'y' ? TRUE : FALSE;
522 } /* getyesno */
523 #endif
524
525 /* if user consents to it, change the filename extension. */
maybe_force_extension(char * filename,char * extension)526 char *maybe_force_extension(char *filename, char *extension)
527 {
528 static char newname[MAX_PATH];
529 if (!batchmode && !has_extension(filename, extension)) {
530 strcpy(newname, filename);
531 force_extension(newname, extension);
532 if (!file_exists(newname)) {
533 fprintf(pgpout, LANG("\nShould '%s' be renamed to '%s' (Y/n)? "),
534 filename, newname);
535 if (getyesno('y'))
536 return newname;
537 }
538 }
539 return NULL;
540 } /* maybe_force_extension */
541
542 /*
543 * Add a trailing directory separator to a name, if absent.
544 */
addslash(char * name)545 static void addslash(char *name)
546 {
547 int i = strlen(name);
548
549 if (i != 0 && !strchr(DIRSEPS, name[i - 1])) {
550 name[i] = DIRSEPS[0];
551 name[i + 1] = '\0';
552 }
553 }
554
555 /*
556 * Builds a filename with a complete path specifier from the environmental
557 * variable PGPPATH.
558 */
buildfilename(char * result,char * fname)559 char *buildfilename(char *result, char *fname)
560 {
561 #ifdef MACTC5
562 char const *s;
563 #else
564 char const *s = getenv(PGPPATH);
565 #endif
566 result[0] = '\0';
567 #ifdef MACTC5
568 return(strcpy(result,fname));
569 #endif
570
571 if (s && strlen(s) <= 50) {
572 strcpy(result, s);
573 }
574 #ifdef UNIX
575 /* On Unix, default to $HOME/.pgp, otherwise, current directory. */
576 else {
577 s = getenv("HOME");
578 if (s && strlen(s) <= 50) {
579 strcpy(result, s);
580 addslash(result);
581 strcat(result, ".pgp");
582 }
583 }
584 #endif /* UNIX */
585
586 addslash(result);
587 strcat(result, fname);
588 return result;
589 } /* buildfilename */
590
buildsysfilename(char * result,char * fname)591 char *buildsysfilename(char *result, char *fname)
592 {
593 #ifdef PGP_SYSTEM_DIR
594 strcpy(result, PGP_SYSTEM_DIR);
595 strcat(result, fname);
596 if (file_exists(result))
597 return result;
598 #endif
599 buildfilename(result, fname); /* Put name back for error */
600 return result;
601 }
602
603
604 /* Convert filename to canonical form, with slashes as separators */
file_to_canon(char * filename)605 void file_to_canon(char *filename)
606 {
607 #ifdef EBCDIC
608 CONVERT_TO_CANONICAL_CHARSET(filename);
609 #endif
610 #ifdef BSLASH
611 while (*filename) {
612 if (*filename == '\\')
613 *filename = '/';
614 ++filename;
615 }
616 #else /* 203a */
617 #ifdef MACTC5
618 while (*filename) {
619 if (*filename == ':')
620 *filename = '/';
621 ++filename;
622 }
623 #endif
624 #endif
625 }
626
627 #ifdef EBCDIC
628 /* Convert filename from canonical form */
file_from_canon(char * filename)629 void file_from_canon(char *filename)
630 {
631 strcpy( filename, LOCAL_CHARSET(filename) );
632 }
633 #endif /* EBCDIC */
634
635
write_error(FILE * f)636 int write_error(FILE * f)
637 {
638 fflush(f);
639 if (ferror(f)) {
640 #ifdef ENOSPC
641 if (errno == ENOSPC)
642 fprintf(pgpout, LANG("\nDisk full.\n"));
643 else
644 #endif
645 fprintf(pgpout, LANG("\nFile write error.\n"));
646 return -1;
647 }
648 return 0;
649 }
650
651 /* copy file f to file g, for longcount bytes */
copyfile(FILE * f,FILE * g,word32 longcount)652 int copyfile(FILE * f, FILE * g, word32 longcount)
653 {
654 int count, status = 0;
655 do { /* read and write the whole file... */
656 if (longcount < (word32) DISKBUFSIZE)
657 count = (int) longcount;
658 else
659 count = DISKBUFSIZE;
660 count = fread(textbuf, 1, count, f);
661 if (count > 0) {
662 if (CONVERSION != NO_CONV) {
663 int i;
664 for (i = 0; i < count; i++)
665 textbuf[i] = (CONVERSION == EXT_CONV) ?
666 EXT_C(textbuf[i]) :
667 INT_C(textbuf[i]);
668 }
669 if (fwrite(textbuf, 1, count, g) != count) {
670 /* Problem: return error value */
671 status = -1;
672 break;
673 }
674 longcount -= count;
675 #ifdef MACTC5
676 mac_poll_for_break();
677 #endif
678 }
679 /* if text block was short, exit loop */
680 } while (count == DISKBUFSIZE);
681 burn(textbuf); /* burn sensitive data on stack */
682 return status;
683 } /* copyfile */
684
685 /*
686 * Like copyfile, but takes a position for file f. Returns with
687 * f and g pointing just past the copied data.
688 */
copyfilepos(FILE * f,FILE * g,word32 longcount,word32 fpos)689 int copyfilepos(FILE * f, FILE * g, word32 longcount, word32 fpos)
690 {
691 fseek(f, fpos, SEEK_SET);
692 return copyfile(f, g, longcount);
693 }
694
695
696 /* copy file f to file g, for longcount bytes. Convert to
697 * canonical form as we go. f is open in text mode. Canonical
698 * form uses crlf's as line separators.
699 */
copyfile_to_canon(FILE * f,FILE * g,word32 longcount)700 int copyfile_to_canon(FILE * f, FILE * g, word32 longcount)
701 {
702 int count, status = 0;
703 byte c, *tb1, *tb2;
704 int i, nbytes;
705 int nspaces = 0;
706 #ifdef MACTC5
707 Boolean warning = true; /* MACTC5 */
708 #endif
709 do { /* read and write the whole file... */
710 if (longcount < (word32) DISKBUFSIZE)
711 count = (int) longcount;
712 else
713 count = DISKBUFSIZE;
714 count = fread(textbuf, 1, count, f);
715 if (count > 0) {
716 /* Convert by adding CR before LF */
717 tb1 = textbuf;
718 tb2 = (byte *) textbuf2;
719 for (i = 0; i < count; ++i) {
720 switch (CONVERSION) {
721 case EXT_CONV:
722 c = EXT_C(*tb1++);
723 break;
724 case INT_CONV:
725 c = INT_C(*tb1++);
726 break;
727 default:
728 c = *tb1++;
729 }
730 #ifdef MACTC5
731 if ( (((uchar) c) < ' ') && (c != '\n') && (c != '\t') && warning) {
732 warning = false;
733 fprintf(stdout, "\aWarning text file contains control characters!\n");
734 }
735 #endif
736 if (strip_spaces) {
737 if (c == ' ') {
738 /* Don't output spaces yet */
739 nspaces += 1;
740 } else {
741 if (c == '\n') {
742 *tb2++ = '\r';
743 nspaces = 0; /* Delete trailing spaces */
744 }
745 if (nspaces) {
746 /* Put out spaces now */
747 do
748 *tb2++ = ' ';
749 while (--nspaces);
750 }
751 *tb2++ = c;
752 }
753 } else {
754 if (c == '\n')
755 *tb2++ = '\r';
756 *tb2++ = c;
757 }
758 }
759 nbytes = tb2 - (byte *) textbuf2;
760 if (fwrite(textbuf2, 1, nbytes, g) != nbytes) {
761 /* Problem: return error value */
762 status = -1;
763 break;
764 }
765 longcount -= count;
766 }
767 /* if text block was short, exit loop */
768 } while (count == DISKBUFSIZE);
769 burn(textbuf); /* burn sensitive data on stack */
770 burn(textbuf2);
771 return status;
772 } /* copyfile_to_canon */
773
774
775 /* copy file f to file g, for longcount bytes. Convert from
776 * canonical to local form as we go. g is open in text mode. Canonical
777 * form uses crlf's as line separators.
778 */
copyfile_from_canon(FILE * f,FILE * g,word32 longcount)779 int copyfile_from_canon(FILE * f, FILE * g, word32 longcount)
780 {
781 int count, status = 0;
782 byte c, *tb1, *tb2;
783 int i, nbytes;
784 do { /* read and write the whole file... */
785 if (longcount < (word32) DISKBUFSIZE)
786 count = (int) longcount;
787 else
788 count = DISKBUFSIZE;
789 count = fread(textbuf, 1, count, f);
790 if (count > 0) {
791 /* Convert by removing CR's */
792 tb1 = textbuf;
793 tb2 = (byte *) textbuf2;
794 for (i = 0; i < count; ++i) {
795 switch (CONVERSION) {
796 case EXT_CONV:
797 c = EXT_C(*tb1++);
798 break;
799 case INT_CONV:
800 c = INT_C(*tb1++);
801 break;
802 default:
803 c = *tb1++;
804 }
805 if (c != '\r')
806 *tb2++ = c;
807 }
808 nbytes = tb2 - (byte *) textbuf2;
809 if (fwrite(textbuf2, 1, nbytes, g) != nbytes) {
810 /* Problem: return error value */
811 status = -1;
812 break;
813 }
814 longcount -= count;
815 }
816 /* if text block was short, exit loop */
817 } while (count == DISKBUFSIZE);
818 burn(textbuf); /* burn sensitive data on stack */
819 burn(textbuf2);
820 return status;
821 } /* copyfile_from_canon */
822
823 /* Copy srcFile to destFile */
copyfiles_by_name(char * srcFile,char * destFile)824 int copyfiles_by_name(char *srcFile, char *destFile)
825 {
826 FILE *f, *g;
827 int status = 0;
828 long fileLength;
829
830 f = fopen(srcFile, FOPRBIN);
831 if (f == NULL)
832 return -1;
833 g = fopen(destFile, FOPWBIN);
834 if (g == NULL) {
835 fclose(f);
836 return -1;
837 }
838 /* Get file length and copy it */
839 fseek(f, 0L, SEEK_END);
840 fileLength = ftell(f);
841 rewind(f);
842 status = copyfile(f, g, fileLength);
843 fclose(f);
844 if (write_error(g))
845 status = -1;
846 fclose(g);
847 return status;
848 } /* copyfiles_by_name */
849
850 /* Copy srcFile to destFile, converting to canonical text form */
make_canonical(char * srcFile,char * destFile)851 int make_canonical(char *srcFile, char *destFile)
852 {
853 FILE *f, *g;
854 int status = 0;
855 long fileLength;
856
857 if (((f = fopen(srcFile, FOPRTXT)) == NULL) ||
858 ((g = fopen(destFile, FOPWBIN)) == NULL))
859 /* Can't open files */
860 return -1;
861
862 /* Get file length and copy it */
863 fseek(f, 0L, SEEK_END);
864 fileLength = ftell(f);
865 rewind(f);
866 CONVERSION = INT_CONV;
867 status = copyfile_to_canon(f, g, fileLength);
868 CONVERSION = NO_CONV;
869 fclose(f);
870 if (write_error(g))
871 status = -1;
872 fclose(g);
873 return status;
874 } /* make_canonical */
875
876 /*
877 * Like rename() but will try to copy the file if the rename fails.
878 * This is because under OS's with multiple physical volumes if the
879 * source and destination are on different volumes the rename will fail
880 */
rename2(char * srcFile,char * destFile)881 int rename2(char *srcFile, char *destFile)
882 {
883 FILE *f, *g;
884 #ifdef MACTC5
885 int copy=-1;
886 #endif
887 int status = 0;
888 long fileLength;
889
890 #ifdef MACTC5
891 copy=MoveRename(srcFile,destFile);
892 if (copy)
893 #else
894 #if defined(VMS) || defined(C370)
895 if (rename(srcFile, destFile) != 0)
896 #else
897 if (rename(srcFile, destFile) == -1)
898 #endif
899 #endif
900 {
901 /* Rename failed, try a copy */
902 if (((f = fopen(srcFile, FOPRBIN)) == NULL) ||
903 ((g = fopen(destFile, FOPWBIN)) == NULL))
904 /* Can't open files */
905 return -1;
906
907 #ifdef MACTC5
908 {
909 FInfo finderInfo;
910 c2pstr(srcFile);
911 c2pstr(destFile);
912 if(GetFInfo((uchar *)srcFile,0,&finderInfo)==0)
913 SetFInfo((uchar *)destFile,0,&finderInfo);
914 p2cstr((uchar *)srcFile);
915 p2cstr((uchar *)destFile);
916 }
917 #endif
918
919 /* Get file length and copy it */
920 fseek(f, 0L, SEEK_END);
921 fileLength = ftell(f);
922 rewind(f);
923 status = copyfile(f, g, fileLength);
924 if (write_error(g))
925 status = -1;
926
927 /* Zap source file if the copy went OK, otherwise zap the (possibly
928 incomplete) destination file */
929 if (status >= 0) {
930 wipeout(f); /* Zap source file */
931 fclose(f);
932 remove(srcFile);
933 fclose(g);
934 } else {
935 if (is_regular_file(destFile)) {
936 wipeout(g); /* Zap destination file */
937 fclose(g);
938 remove(destFile);
939 } else {
940 fclose(g);
941 }
942 fclose(f);
943 }
944 }
945 return status;
946 }
947
948 /* read the data from stdin to the phantom input file */
readPhantomInput(char * filename)949 int readPhantomInput(char *filename)
950 {
951 FILE *outFilePtr;
952 byte buffer[512];
953 int bytesRead, status = 0;
954
955 if (verbose)
956 fprintf(pgpout, "writing stdin to file %s\n", filename);
957 if ((outFilePtr = fopen(filename, FOPWBIN)) == NULL)
958 return -1;
959
960 #if defined(MSDOS) || defined(OS2) || defined (WIN32)
961 /* Under DOS must set input stream to binary mode to avoid data mangling */
962 setmode(fileno(stdin), O_BINARY);
963 #endif /* MSDOS || OS2 */
964 while ((bytesRead = fread(buffer, 1, 512, stdin)) > 0)
965 if (fwrite(buffer, 1, bytesRead, outFilePtr) != bytesRead) {
966 status = -1;
967 break;
968 }
969 if (write_error(outFilePtr))
970 status = -1;
971 fclose(outFilePtr);
972 #if defined(MSDOS) || defined(OS2) || defined (WIN32)
973 setmode(fileno(stdin), O_TEXT); /* Reset stream */
974 #endif /* MSDOS || OS2 */
975 return status;
976 }
977
978 /* write the data from the phantom output file to stdout */
writePhantomOutput(char * filename)979 int writePhantomOutput(char *filename)
980 {
981 FILE *outFilePtr;
982 byte buffer[512];
983 int bytesRead, status = 0;
984
985 if (verbose)
986 fprintf(pgpout, "writing file %s to stdout\n", filename);
987 /* this can't fail since we just created the file */
988 outFilePtr = fopen(filename, FOPRBIN);
989
990 #if defined(MSDOS) || defined(OS2) || defined (WIN32)
991 setmode(fileno(stdout), O_BINARY);
992 #endif /* MSDOS || OS2 */
993 while ((bytesRead = fread(buffer, 1, 512, outFilePtr)) > 0)
994 if (fwrite(buffer, 1, bytesRead, stdout) != bytesRead) {
995 status = -1;
996 break;
997 }
998 fclose(outFilePtr);
999 fflush(stdout);
1000 if (ferror(stdout)) {
1001 status = -1;
1002 fprintf(pgpout, LANG("\007Write error on stdout.\n"));
1003 }
1004 #if defined(MSDOS) || defined(OS2) || defined (WIN32)
1005 setmode(fileno(stdout), O_TEXT);
1006 #endif /* MSDOS || OS2 */
1007
1008 return status;
1009 }
1010
1011 /* Return the size from the current position of file f to the end */
fsize(FILE * f)1012 word32 fsize(FILE * f)
1013 {
1014 long fpos = ftell(f);
1015 long fpos2;
1016
1017 fseek(f, 0L, SEEK_END);
1018 fpos2 = ftell(f);
1019 fseek(f, fpos, SEEK_SET);
1020 return (word32) (fpos2 - fpos);
1021 }
1022
1023 /* Return TRUE if file filename looks like a pure text file */
is_text_file(char * filename)1024 int is_text_file (char *filename) /* EWS */
1025 {
1026 FILE *f = fopen(filename,"r"); /* FOPRBIN gives problem with VMS */
1027 int i, n, lfctr = 0;
1028 unsigned char buf[512];
1029 unsigned char *bufptr = buf;
1030 unsigned char c;
1031
1032 if (!f)
1033 return FALSE; /* error opening it, so not a text file */
1034 i = n = fread (buf, 1, sizeof(buf), f);
1035 fclose(f);
1036 if (n <= 0)
1037 return FALSE; /* empty file or error, not a text file */
1038 if (compressSignature(buf) >= 0)
1039 return FALSE;
1040 while (i--) {
1041 c = *bufptr++;
1042 if (c == '\n' || c == '\r')
1043 lfctr=0;
1044 else /* allow BEL BS HT LF VT FF CR EOF ESC control characters */
1045 {
1046 #ifdef EBCDIC
1047 if (iscntrl(c) && c!=BEL && c!=BS && c!=HT && c!=LF && c!=VT && c!=FF && c!=CR && c!=EOF && c!=ESC)
1048 #else
1049 if (c < '\007' || (c > '\r' && c < ' ' && c != '\032' && c != '\033'))
1050 #endif
1051 return FALSE; /* not a text file */
1052 lfctr++;
1053 }
1054 }
1055 return TRUE;
1056 } /* is_text_file */
1057
xmalloc(unsigned size)1058 VOID *xmalloc(unsigned size)
1059 {
1060 VOID *p;
1061 if (size == 0)
1062 ++size;
1063 p = malloc(size);
1064 if (p == NULL) {
1065 fprintf(stderr, LANG("\n\007Out of memory.\n"));
1066 exitPGP(1);
1067 }
1068 return p;
1069 }
1070
1071 /*----------------------------------------------------------------------
1072 * temporary file routines
1073 */
1074
1075
1076 #define MAXTMPF 8
1077
1078 #define TMP_INUSE 2
1079
1080 static struct {
1081 char path[MAX_PATH];
1082 int flags;
1083 int num;
1084 } tmpf[MAXTMPF];
1085
1086 static char tmpdir[256]; /* temporary file directory */
1087 static char outdir[256]; /* output directory */
1088 static char tmpbasename[64] = "pgptemp"; /* basename for
1089 temporary files */
1090
1091
1092 /*
1093 * set directory for temporary files. path will be stored in
1094 * tmpdir[] with an appropriate trailing path separator.
1095 */
settmpdir(char * path)1096 void settmpdir(char *path)
1097 {
1098 char *p;
1099
1100 if (path == NULL || *path == '\0') {
1101 tmpdir[0] = '\0';
1102 return;
1103 }
1104 strcpy(tmpdir, path);
1105 p = tmpdir + strlen(tmpdir) - 1;
1106 #ifdef MACTC5
1107 if (*p != '/' && *p != '\\' && *p != ']' && *p != ':')
1108 { /* append path separator, either / or \ */
1109 if ((p = strchr(tmpdir, '/')) == NULL &&
1110 (p = strchr(tmpdir, '\\')) == NULL &&
1111 (p = strchr(tmpdir, ':')) == NULL)
1112 p = ":"; /* path did not contain / or \ or :, use : */
1113 strncat(tmpdir, p, 1);
1114 #else
1115 if (*p != '/' && *p != '\\' && *p != ']' && *p != ':') {
1116 /* append path separator, either / or \ */
1117 if ((p = strchr(tmpdir, '/')) == NULL &&
1118 (p = strchr(tmpdir, '\\')) == NULL)
1119 p = "/"; /* path did not contain / or \, use / */
1120 strncat(tmpdir, p, 1);
1121 #endif
1122 }
1123 }
1124
1125 /*
1126 * set output directory to avoid a file copy when temp file is renamed to
1127 * output file. the argument filename must be a valid path for a file, not
1128 * a directory.
1129 */
1130 void setoutdir(char *filename)
1131 {
1132 char *p;
1133
1134 if (filename == NULL) {
1135 strcpy(outdir, tmpdir);
1136 return;
1137 }
1138 strcpy(outdir, filename);
1139 p = file_tail(outdir);
1140 strcpy(tmpbasename, p);
1141 *p = '\0';
1142 drop_extension(tmpbasename);
1143 #if !defined(BSD42) && !defined(BSD43) && !defined(sun)
1144 /*
1145 * we don't depend on pathconf here, if it returns an incorrect value
1146 * for NAME_MAX (like Linux 0.97 with minix FS) finding a unique name
1147 * for temp files can fail.
1148 */
1149 tmpbasename[10] = '\0'; /* 14 char limit */
1150 #endif
1151 }
1152
1153 /*
1154 * return a unique temporary file name
1155 */
1156 char *tempfile(int flags)
1157 {
1158 int i, j;
1159 int num;
1160 int fd;
1161 #ifndef UNIX
1162 FILE *fp;
1163 #endif
1164
1165 for (i = 0; i < MAXTMPF; ++i)
1166 if (tmpf[i].flags == 0)
1167 break;
1168
1169 if (i == MAXTMPF) {
1170 /* message only for debugging, no need for LANG */
1171 fprintf(stderr, "\n\007Out of temporary files\n");
1172 return NULL;
1173 }
1174 again:
1175 num = 0;
1176 do {
1177 for (j = 0; j < MAXTMPF; ++j)
1178 if (tmpf[j].flags && tmpf[j].num == num)
1179 break;
1180 if (j < MAXTMPF)
1181 continue; /* sequence number already in use */
1182 sprintf(tmpf[i].path, "%s%s.%c%02d",
1183 ((flags & TMP_TMPDIR) && *tmpdir ? tmpdir : outdir),
1184 tmpbasename, TMP_EXT, num);
1185 if (!file_exists(tmpf[i].path))
1186 break;
1187 }
1188 while (++num < 100);
1189
1190 if (num == 100) {
1191 fprintf(pgpout, "\n\007tempfile: cannot find unique name\n");
1192 return NULL;
1193 }
1194 #if defined(UNIX) || defined(VMS)
1195 if ((fd = open(tmpf[i].path, O_EXCL | O_RDWR | O_CREAT, 0600)) != -1)
1196 close(fd);
1197 #else
1198 if ((fp = fopen(tmpf[i].path, "w")) != NULL)
1199 fclose(fp);
1200 fd = (fp == NULL ? -1 : 0);
1201 #endif
1202
1203 if (fd == -1) {
1204 if (!(flags & TMP_TMPDIR)) {
1205 flags |= TMP_TMPDIR;
1206 goto again;
1207 }
1208 #ifdef UNIX
1209 else if (tmpdir[0] == '\0') {
1210 strcpy(tmpdir, "/tmp/");
1211 goto again;
1212 }
1213 #endif
1214 }
1215 if (fd == -1) {
1216 fprintf(pgpout, LANG("\n\007Cannot create temporary file '%s'\n"),
1217 tmpf[i].path);
1218 user_error();
1219 }
1220 #if defined(VMS) || defined(C370)
1221 remove(tmpf[i].path);
1222 #endif
1223
1224 tmpf[i].num = num;
1225 tmpf[i].flags = flags | TMP_INUSE;
1226 if (verbose)
1227 fprintf(pgpout, "tempfile: created '%s'\n", tmpf[i].path);
1228 return tmpf[i].path;
1229 } /* tempfile */
1230
1231 /*
1232 * remove temporary file, wipe if necessary.
1233 */
1234 void rmtemp(char *name)
1235 {
1236 int i;
1237
1238 for (i = 0; i < MAXTMPF; ++i)
1239 if (tmpf[i].flags && strcmp(tmpf[i].path, name) == 0)
1240 break;
1241
1242 if (i < MAXTMPF) {
1243 if (strlen(name) > 3 && name[strlen(name) - 3] == TMP_EXT) {
1244 /* only remove file if name hasn't changed */
1245 if (verbose)
1246 fprintf(pgpout, "rmtemp: removing '%s'\n", name);
1247 if (tmpf[i].flags & TMP_WIPE)
1248 wipefile(name);
1249 if (!remove(name)) {
1250 tmpf[i].flags = 0;
1251 } else if (verbose) {
1252 fprintf(stderr, "\nrmtemp: Failed to remove %s", name);
1253 perror("\nError");
1254 }
1255 } else if (verbose)
1256 fprintf(pgpout, "rmtemp: not removing '%s'\n", name);
1257 }
1258 } /* rmtemp */
1259
1260 /*
1261 * make temporary file permanent, returns the new name.
1262 */
1263 char *savetemp(char *name, char *newname)
1264 {
1265 int i, overwrite;
1266
1267 if (strcmp(name, newname) == 0)
1268 return name;
1269
1270 for (i = 0; i < MAXTMPF; ++i)
1271 if (tmpf[i].flags && strcmp(tmpf[i].path, name) == 0)
1272 break;
1273
1274 if (i < MAXTMPF) {
1275 if (strlen(name) < 4 || name[strlen(name) - 3] != TMP_EXT) {
1276 if (verbose)
1277 fprintf(pgpout, "savetemp: not renaming '%s' to '%s'\n",
1278 name, newname);
1279 return name; /* return original file name */
1280 }
1281 }
1282
1283 newname = ck_dup_output(newname, FALSE, TRUE);
1284 if (newname==NULL)
1285 return(NULL);
1286
1287 if (verbose)
1288 fprintf(pgpout, "savetemp: renaming '%s' to '%s'\n", name, newname);
1289 if (rename2(name, newname) < 0) {
1290 /* errorLvl = UNKNOWN_FILE_ERROR; */
1291 fprintf(pgpout, LANG("Can't create output file '%s'\n"), newname);
1292 return NULL;
1293 }
1294 if (i < MAXTMPF)
1295 tmpf[i].flags = 0;
1296 return newname;
1297 } /* savetemp */
1298
1299 char *ck_dup_output(char *newname, boolean notest, boolean delete_dup)
1300 {
1301 int overwrite;
1302 static char buf[MAX_PATH];
1303
1304 while (file_exists(newname)) {
1305 if (batchmode && !force_flag) {
1306 fprintf(pgpout,LANG("\n\007Output file '%s' already exists.\n"),
1307 newname);
1308 return NULL;
1309 }
1310 if (is_regular_file(newname)) {
1311 if (force_flag) {
1312 /* remove without asking */
1313 if (delete_dup) remove(newname);
1314 break;
1315 }
1316 fprintf(pgpout,
1317 LANG("\n\007Output file '%s' already exists. Overwrite (y/N)? "),
1318 newname);
1319 overwrite = getyesno('n');
1320 } else {
1321 fprintf(pgpout,
1322 LANG("\n\007Output file '%s' already exists.\n"),newname);
1323 if (force_flag) /* never remove special file */
1324 return NULL;
1325 overwrite = FALSE;
1326 }
1327
1328 if (!overwrite) {
1329 fprintf(pgpout, "\n");
1330 fprintf(pgpout, LANG("Enter new file name:"));
1331 fprintf(pgpout, " ");
1332 #ifdef MACTC5
1333 if (!GetFilePath(LANG("Enter new file name:"), buf, PUTFILE))
1334 return(NULL);
1335 strcpy(newname, buf);
1336 fprintf(pgpout, "%s\n",buf);
1337 #else
1338 getstring(buf, MAX_PATH - 1, TRUE);
1339 if (buf[0] == '\0')
1340 return(NULL);
1341 newname = buf;
1342 #endif
1343 } else if (delete_dup)
1344 remove(newname);
1345 else
1346 break;
1347
1348 if (notest) break;
1349 }
1350 return(newname);
1351 } /* ck_dup_output */
1352
1353 /*
1354 * like savetemp(), only make backup of destname if it exists
1355 */
1356 int savetempbak(char *tmpname, char *destname)
1357 {
1358 char bakpath[MAX_PATH];
1359 #ifdef MACTC5
1360 byte header[8];
1361 #endif
1362 #ifdef UNIX
1363 int mode = -1;
1364 #endif
1365
1366 if (is_tempfile(destname)) {
1367 remove(destname);
1368 } else {
1369 if (file_exists(destname)) {
1370 #ifdef UNIX
1371 struct stat st;
1372 if (stat(destname, &st) != -1)
1373 mode = st.st_mode & 07777;
1374 #endif
1375 strcpy(bakpath, destname);
1376 force_extension(bakpath, BAK_EXTENSION);
1377 remove(bakpath);
1378 #if defined(VMS) || defined(C370)
1379 if (rename(destname, bakpath) != 0)
1380 #else
1381 if (rename(destname, bakpath) == -1)
1382 #endif
1383 return -1;
1384 #ifdef MACTC5
1385 get_header_info_from_file(bakpath, header, 8 );
1386 if (header[0] == CTB_CERT_SECKEY)
1387 PGPSetFinfo(bakpath,'SKey','MPGP');
1388 if (header[0] == CTB_CERT_PUBKEY)
1389 PGPSetFinfo(bakpath,'PKey','MPGP');
1390 #endif
1391 }
1392 }
1393 if (savetemp(tmpname, destname) == NULL)
1394 return -1;
1395 #if defined(UNIX)
1396 if (mode != -1)
1397 chmod(destname, mode);
1398 #elif defined(MACTC5)
1399 get_header_info_from_file(destname, header, 8 );
1400 if (header[0] == CTB_CERT_SECKEY)
1401 PGPSetFinfo(destname,'SKey','MPGP');
1402 if (header[0] == CTB_CERT_PUBKEY)
1403 PGPSetFinfo(destname,'PKey','MPGP');
1404 #endif
1405 return 0;
1406 }
1407
1408 /*
1409 * remove all temporary files and wipe them if necessary
1410 */
1411 void cleanup_tmpf(void)
1412 {
1413 int i;
1414
1415 for (i = 0; i < MAXTMPF; ++i)
1416 if (tmpf[i].flags)
1417 rmtemp(tmpf[i].path);
1418 } /* cleanup_tmpf */
1419
1420 #ifdef MACTC5
1421 void mac_cleanup_tmpf(void)
1422 {
1423 int i,err;
1424 HFileParam pb;
1425 char fname[256];
1426 for (i = 0; i < MAXTMPF; ++i)
1427 if (tmpf[i].flags)
1428 {
1429 strcpy(fname,tmpf[i].path);
1430 pb.ioCompletion=nil;
1431 c2pstr(fname);
1432 pb.ioNamePtr=(uchar *)fname;
1433 pb.ioVRefNum=0;
1434 pb.ioFDirIndex=0;
1435 pb.ioFRefNum=0;
1436 pb.ioDirID=0;
1437 err=PBHGetFInfo((HParmBlkPtr)&pb,false);
1438 if (pb.ioFRefNum!=0){
1439 strcpy(fname,tmpf[i].path);
1440 pb.ioCompletion=nil;
1441 c2pstr(fname);
1442 pb.ioNamePtr=(uchar *)fname;
1443 pb.ioVRefNum=0;
1444 pb.ioDirID=0;
1445 err=PBClose((ParmBlkPtr)&pb,false);
1446 }
1447 rmtemp(tmpf[i].path);
1448 }
1449 } /* mac_cleanup_tmpf */
1450 #endif
1451
1452 /*
1453 * Routines to search for the manuals.
1454 *
1455 * Why all this code?
1456 *
1457 * Some people may object to PGP insisting on finding the manual somewhere
1458 * in the neighborhood to generate a key. They bristle against this
1459 * seemingly authoritarian attitude. Some people have even modified PGP
1460 * to defeat this feature, and redistributed their hotwired version to
1461 * others. That creates problems for me (PRZ).
1462 *
1463 * Here is the problem. Before I added this feature, there were maimed
1464 * versions of the PGP distribution package floating around that lacked
1465 * the manual. One of them was uploaded to Compuserve, and was
1466 * distributed to countless users who called me on the phone to ask me why
1467 * such a complicated program had no manual. It spread out to BBS systems
1468 * around the country. And a freeware distributor got hold of the package
1469 * from Compuserve and enshrined it on CD-ROM, distributing thousands of
1470 * copies without the manual. What a mess.
1471 *
1472 * Please don't make my life harder by modifying PGP to disable this
1473 * feature so that others may redistribute PGP without the manual. If you
1474 * run PGP on a palmtop with no memory for the manual, is it too much to
1475 * ask that you type one little extra word on the command line to do a key
1476 * generation, a command that is seldom used by people who already know
1477 * how to use PGP? If you can't stand even this trivial inconvenience,
1478 * can you suggest a better method of reducing PGP's distribution without
1479 * the manual?
1480 */
1481
1482 static unsigned ext_missing(char *prefix)
1483 {
1484 static char const *const extensions[] =
1485 #ifdef VMS
1486 { ".doc", ".txt", ".man", ".tex", ".", 0 };
1487 #else
1488 { ".doc", ".txt", ".man", ".tex", "", 0 };
1489 #endif
1490 char const *const *p;
1491 char *end = prefix + strlen(prefix);
1492
1493 for (p = extensions; *p; p++) {
1494 strcpy(end, *p);
1495 #if 0 /* Debugging code */
1496 fprintf(pgpout, "Looking for \"%s\"\n", prefix);
1497 #endif
1498 if (file_exists(prefix))
1499 return 0;
1500 }
1501 return 1;
1502 }
1503
1504 /*
1505 * Returns mask of files missing
1506 */
1507 static unsigned files_missing(char *prefix)
1508 {
1509 /* This changed to incorporate the changes in the Documentation subdirectory */
1510 #ifdef MACTC5
1511 static char const *const names[] =
1512 {"Volume I", "Volume II", 0};
1513 #else
1514 static char const *const names[] =
1515 {"pgpdoc1", "pgpdoc2", 0};
1516 #endif
1517 char const *const *p;
1518 unsigned bit, mask = 3;
1519 int len = strlen(prefix);
1520
1521 #ifndef MACTC5
1522 /* Cannot do this on the macintosh because file_exists returns false on
1523 directories */
1524 #ifndef VMS
1525 /*
1526 * Optimization: if directory doesn't exist, stop. But access()
1527 * (used internally by file_exists()) doesn't work on dirs under VMS.
1528 */
1529 if (prefix[0] && !file_exists(prefix)) /* Directory doesn't exist? */
1530 return mask;
1531 #endif /* VMS */
1532 #endif /* MACTC5 */
1533 if (len && strchr(DIRSEPS, prefix[len - 1]) == 0)
1534 prefix[len++] = DIRSEPS[0];
1535 for (p = names, bit = 1; *p; p++, bit <<= 1) {
1536 strcpy(prefix + len, *p);
1537 if (!ext_missing(prefix))
1538 mask &= ~bit;
1539 }
1540
1541 return mask; /* Bitmask of which files exist */
1542 }
1543
1544 /*
1545 * Search prefix directory and doc subdirectory.
1546 */
1547 static unsigned doc_missing(char *prefix)
1548 {
1549 unsigned mask;
1550 int len = strlen(prefix);
1551
1552 mask = files_missing(prefix);
1553 if (!mask)
1554 return 0;
1555 #if defined(VMS)
1556 if (len && prefix[len - 1] == ']') {
1557 strcpy(prefix + len - 1, ".doc]");
1558 } else {
1559 assert(!len || prefix[len - 1] == ':');
1560 strcpy(prefix + len, "[doc]");
1561 }
1562 #elif defined(MACTC5)
1563 /* on the macintosh we must look for the documents in
1564 Documentation:PGP User's Guide: folder */
1565 if (len && prefix[len - 1] != DIRSEPS[0])
1566 prefix[len++] = DIRSEPS[0];
1567 strcpy(prefix + len, "Documentation");
1568 len = strlen(prefix);
1569 mask &= files_missing(prefix);
1570 if (!mask)
1571 return 0;
1572 if (len && prefix[len - 1] != DIRSEPS[0])
1573 prefix[len++] = DIRSEPS[0];
1574 strcpy(prefix + len, "PGP User's Guide");
1575 mask &= files_missing(prefix);
1576 if (!mask)
1577 return 0;
1578 #else
1579 if (len && prefix[len - 1] != DIRSEPS[0])
1580 prefix[len++] = DIRSEPS[0];
1581 strcpy(prefix + len, "doc");
1582 #endif
1583
1584 mask &= files_missing(prefix);
1585
1586 prefix[len] = '\0';
1587 return mask;
1588 }
1589
1590 /*
1591 * Expands a leading environment variable. Returns 0 on success;
1592 * <0 if there is an error.
1593 */
1594 static int expand_env(char const *src, char *dest)
1595 {
1596 char const *var, *suffix;
1597 unsigned len;
1598
1599 if (*src != '$') {
1600 strcpy(dest, src);
1601 return 0;
1602 }
1603 /* Find end of variable */
1604 if (src[1] == '{') { /* ${FOO} form */
1605 var = src + 2;
1606 len = strchr(var, '}') - (char*) var;
1607 suffix = src + 2 + len + 1;
1608 } else { /* $FOO form - allow $ for VMS */
1609 var = src + 1;
1610 len = strspn(var, "ABCDEFGHIJKLMNOPQRSTUVWXYZ$_");
1611 suffix = src + 1 + len;
1612 }
1613
1614 memcpy(dest, var, len); /* Copy name */
1615 dest[len] = '\0'; /* Null-terminate */
1616
1617 var = getenv(dest);
1618 if (!var || !*var)
1619 return -1; /* No env variable */
1620
1621 /* Copy expanded form to destination */
1622 strcpy(dest, var);
1623
1624 /* Add tail */
1625 strcat(dest, suffix);
1626
1627 return 0;
1628 }
1629
1630 /* Don't forget to change 'pgp26' whenever you update rel_version past 2.6 */
1631 char const *const manual_dirs[] =
1632 {
1633 #if defined(VMS)
1634 "$PGPPATH", "", "[pgp]", "[pgp26]", "[pgp263]",
1635 PGP_SYSTEM_DIR, "SYS$LOGIN:", "SYS$LOGIN:[pgp]",
1636 "SYS$LOGIN:[pgp26]", "SYS$LOGIN:[pgp263]", "[-]",
1637 #elif defined(UNIX)
1638 "$PGPPATH", "", "pgp", "pgp26", "pgp263", PGP_SYSTEM_DIR,
1639 "$HOME/.pgp", "$HOME", "$HOME/pgp", "$HOME/pgp26", "..",
1640 #elif defined(AMIGA)
1641 "$PGPPATH", "", "pgp", "pgp26", ":pgp", ":pgp26", ":pgp263",
1642 ":", "/",
1643 #else /* MSDOS or ATARI */
1644 "$PGPPATH", "", "pgp", "pgp26", "\\pgp", "\\pgp26", "\\pgp263",
1645 "\\", "..", "c:\\pgp", "c:\\pgp26",
1646 #endif
1647 0};
1648
1649 #ifdef MACTC5
1650 extern char appPathName[];
1651 #endif
1652
1653 unsigned manuals_missing(void)
1654 {
1655 char buf[256];
1656 unsigned mask = ~((unsigned)0);
1657 char const *const *p;
1658
1659 #ifdef MACTC5
1660 strcpy(buf, appPathName);
1661 mask &= doc_missing(buf);
1662 return mask;
1663 #endif /* MACTC5 */
1664 for (p = manual_dirs; *p; p++) {
1665 if (expand_env(*p, buf) < 0)
1666 continue; /* Ignore */
1667 mask &= doc_missing(buf);
1668 if (!mask)
1669 break;
1670 }
1671
1672 return mask;
1673 }
1674
1675 /*
1676 * Why all this code?
1677 *
1678 * See block of comments above.
1679 */
1680