1 /* Copyright (C) 2000-2018 Peter Selinger.
2    This file is part of ccrypt. It is free software and it is covered
3    by the GNU general public license. See the file COPYING for details. */
4 
5 /* user interface for ccrypt: encrypt and decrypt files and streams */
6 
7 /* ccrypt is a tool for encrypting and decrypting files and streams.
8    It can operate as a filter, or it can operate directly on files in
9    the manner of gzip; it can overwrite files in-place on media that
10    support read/write access. Encryption is based on the Rijndael
11    block cipher, a version of which is also used in the Advanced
12    Encryption Standard. */
13 
14 #ifdef HAVE_CONFIG_H
15 #include <config.h>  /* generated by configure */
16 #endif
17 
18 #include <stdlib.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <getopt.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include <locale.h>
26 
27 #include "main.h"
28 #include "readkey.h"
29 #include "ccrypt.h"
30 #include "traverse.h"
31 #include "xalloc.h"
32 #include "unixcryptlib.h"
33 #include "platform.h"
34 
35 #include "gettext.h"
36 #define _(String) gettext (String)
37 
38 cmdline cmd;
39 
40 /* print usage information */
41 
usage(FILE * fout)42 static void usage(FILE *fout) {
43   fprintf(fout, _("%s %s. Secure encryption and decryption of files and streams.\n"), NAMECCRYPT, VERSION);
44   fprintf(fout, "\n");
45   fprintf(fout,
46 _("Usage: %s [mode] [options] [file...]\n"
47 "       %s [options] [file...]\n"
48 "       %s [options] [file...]\n"
49 "       %s [options] file...\n\n"), NAMECCRYPT, NAMEENCRYPT, NAMEDECRYPT, NAMECAT);
50   fprintf(fout,
51 _("Modes:\n"
52 "    -e, --encrypt         encrypt\n"
53 "    -d, --decrypt         decrypt\n"
54 "    -c, --cat             cat; decrypt files to stdout\n"
55 "    -x, --keychange       change key\n"
56 "    -u, --unixcrypt       decrypt old unix crypt files\n"
57 "\n"
58 "Options:\n"
59 "    -h, --help            print this help message and exit\n"
60 "    -V, --version         print version info and exit\n"
61 "    -L, --license         print license info and exit\n"
62 "    -v, --verbose         print progress information to stderr\n"
63 "    -q, --quiet           run quietly; suppress warnings\n"
64 "    -f, --force           overwrite existing files without asking\n"
65 "    -m, --mismatch        allow decryption with non-matching key\n"
66 "    -E, --envvar var      read keyword from environment variable (unsafe)\n"
67 "    -K, --key key         give keyword on command line (unsafe)\n"
68 "    -k, --keyfile file    read keyword(s) as first line(s) from file\n"
69 "    -P, --prompt prompt   use this prompt instead of default\n"
70 "    -S, --suffix .suf     use suffix .suf instead of default %s\n"
71 "    -s, --strictsuffix    refuse to encrypt files which already have suffix\n"
72 "    -F, --envvar2 var     as -E for second keyword (for keychange mode)\n"
73 "    -H, --key2 key        as -K for second keyword (for keychange mode)\n"
74 "    -Q, --prompt2 prompt  as -P for second keyword (for keychange mode)\n"
75 "    -t, --timid           prompt twice for encryption keys (default)\n"
76 "    -b, --brave           prompt only once for encryption keys\n"
77 "    -y, --keyref file     encryption key must match this encrypted file\n"
78 "    -r, --recursive       recurse through directories\n"
79 "    -R, --rec-symlinks    follow symbolic links as subdirectories\n"
80 "    -l, --symlinks        dereference symbolic links\n"
81 "    -T, --tmpfiles        use temporary files instead of overwriting (unsafe)\n"
82 "    --                    end of options, filenames follow\n"),
83 	  SUF);
84 }
85 
86 /* print version and copyright information */
version(FILE * fout)87 static void version(FILE *fout) {
88   fprintf(fout, _("%s %s. Secure encryption and decryption of files and streams.\n"), NAMECCRYPT, VERSION);
89   fprintf(fout, _("Copyright (C) 2000-2018 Peter Selinger.\n"));
90 }
91 
license(FILE * fout)92 static void license(FILE *fout) {
93   fprintf(fout, _("%s %s. Secure encryption and decryption of files and streams.\n"), NAMECCRYPT, VERSION);
94   fprintf(fout, _("Copyright (C) 2000-2018 Peter Selinger.\n"));
95   fprintf(fout, "\n");
96   fprintf(fout,
97   _("For the full text of the GNU General Public License, see the file\n"
98   "COPYING distributed with this software.\n"
99   "\n"
100   "This program is free software; you can redistribute it and/or modify\n"
101   "it under the terms of the GNU General Public License as published by\n"
102   "the Free Software Foundation; either version 2 of the License, or\n"
103   "(at your option) any later version.\n"
104   "\n"
105   "This program is distributed in the hope that it will be useful,\n"
106   "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
107   "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
108   "GNU General Public License for more details.\n"
109   "\n"
110   "You should have received a copy of the GNU General Public License\n"
111   "along with this program; if not, write to the Free Software Foundation,\n"
112   "Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n")
113   );
114 }
115 
116 /* ---------------------------------------------------------------------- */
117 /* read the command line */
118 
output_commandline(cmdline cmd,FILE * fout)119 static void output_commandline(cmdline cmd, FILE *fout) {
120   const char *recursive[] = {"no", "dirs, not symlinks", "dirs and symlinks"};
121   const char *verbosity[] = {"quiet", "normal", "verbose"};
122   const char *mode[] = {"encrypt", "decrypt", "keychange", "cat", "unixcrypt"};
123 
124   fprintf(fout, _("\nCommand line:\n"));
125   fprintf(fout, "name = %s\n", cmd.name);
126   fprintf(fout, "verbosity = %s\n", verbosity[cmd.verbose+1]);
127   fprintf(fout, "debug = %d\n", cmd.debug);
128   fprintf(fout, "keyword = %s\n", cmd.keyword ? _("(known)") : _("(unknown)"));
129   fprintf(fout, "keyword2 = %s\n", cmd.keyword2 ? _("(known)") : _("(unknown)"));
130   fprintf(fout, "mode = %s\n", mode[cmd.mode]);
131   fprintf(fout, "filter = %s\n", cmd.filter ? "yes" : "no");
132   fprintf(fout, "tmpfiles = %s\n", cmd.tmpfiles ? "yes" : "no");
133   fprintf(fout, "suffix = %s\n", cmd.suffix);
134   fprintf(fout, "prompt = %s\n", cmd.prompt ? cmd.prompt : _("(none)"));
135   fprintf(fout, "prompt2 = %s\n", cmd.prompt2 ? cmd.prompt2 : _("(none)"));
136   fprintf(fout, "recursive = %s\n", recursive[cmd.recursive]);
137   fprintf(fout, "symlinks = %s\n", cmd.symlinks ? "yes" : "no");
138   fprintf(fout, "force = %s\n", cmd.force ? "yes" : "no");
139   fprintf(fout, "mismatch = %s\n", cmd.mismatch ? "yes" : "no");
140   fprintf(fout, "keyfile = %s\n", cmd.keyfile ? cmd.keyfile : _("(none)"));
141   fprintf(fout, "timid = %s\n", cmd.timid ? "yes" : "no");
142   fprintf(fout, "keyref = %s\n", cmd.keyref ? cmd.keyref : _("(none)"));
143   fprintf(fout, "strictsuffix = %s\n", cmd.strictsuffix ? "yes" : "no");
144   fprintf(fout, "infiles:");
145   while (cmd.count-- > 0)
146     fprintf(fout, " %s", *(cmd.infiles++));
147   fprintf(fout, "\n\n");
148 }
149 
150 static struct option longopts[] = {
151   {"encrypt",      0, 0, 'e'},
152   {"decrypt",      0, 0, 'd'},
153   {"cat",          0, 0, 'c'},
154   {"keychange",    0, 0, 'x'},
155   {"unixcrypt",    0, 0, 'u'},
156   {"help",         0, 0, 'h'},
157   {"version",      0, 0, 'V'},
158   {"license",      0, 0, 'L'},
159   {"verbose",      0, 0, 'v'},
160   {"quiet",        0, 0, 'q'},
161   {"debug",        0, 0, 'D'},
162   {"force",        0, 0, 'f'},
163   {"mismatch",     0, 0, 'm'},
164   {"envvar",       1, 0, 'E'},
165   {"key",          1, 0, 'K'},
166   {"keyfile",      1, 0, 'k'},
167   {"prompt",       1, 0, 'P'},
168   {"suffix",       1, 0, 'S'},
169   {"strictsuffix", 0, 0, 's'},
170   {"envvar2",      1, 0, 'F'},
171   {"key2",         1, 0, 'H'},
172   {"prompt2",      1, 0, 'Q'},
173   {"timid",        0, 0, 't'},
174   {"brave",        0, 0, 'b'},
175   {"keyref",       1, 0, 'y'},
176   {"recursive",    0, 0, 'r'},
177   {"rec-symlinks", 0, 0, 'R'},
178   {"symlinks",     0, 0, 'l'},
179   {"tmpfiles",     0, 0, 'T'},
180   {0, 0, 0, 0}
181 };
182 
183 static const char *shortopts = "edcxuhVLvqDfmE:K:k:F:H:S:sP:Q:tby:rRlT-";
184 
read_commandline(int ac,char * av[])185 static cmdline read_commandline(int ac, char *av[]) {
186   cmdline cmd;
187   int c;
188   char *p;
189 
190   /* defaults: */
191   cmd.verbose = 0;
192   cmd.debug = 0;
193   cmd.keyword = NULL;
194   cmd.keyword2 = NULL;
195   cmd.mode = ENCRYPT;
196   cmd.suffix = SUF;
197   cmd.prompt = NULL;
198   cmd.prompt2 = NULL;
199   cmd.recursive = 0;
200   cmd.symlinks = 0;
201   cmd.force = 0;
202   cmd.mismatch = 0;
203   cmd.filter = 1;
204   cmd.infiles = NULL;
205   cmd.count = 0;
206   cmd.keyfile = NULL;
207   cmd.timid = 1;
208   cmd.keyref = NULL;
209   cmd.strictsuffix = 0;
210   cmd.tmpfiles = 0;
211 
212   /* find the basename with which we were invoked */
213   cmd.name = strrchr(av[0], '/');
214   cmd.name = cmd.name ? cmd.name+1 : av[0];
215 
216   if (!strcmp(cmd.name, NAMEENCRYPT)) {
217     cmd.mode = ENCRYPT;
218   } else if (!strcmp(cmd.name, NAMEDECRYPT)) {
219     cmd.mode = DECRYPT;
220   } else if (!strcmp(cmd.name, NAMECAT)) {
221     cmd.mode = CAT;
222   } else {
223     cmd.name = NAMECCRYPT;
224     av[0] = strdup(cmd.name);
225     if (!av[0]) {
226       goto mem_error;
227     }
228   }
229 
230   while ((c = getopt_long(ac, av, shortopts, longopts, NULL)) != -1) {
231     switch (c) {
232     case 'h':
233       usage(stdout);
234       exit(0);
235       break;
236     case 'V':
237       version(stdout);
238       exit(0);
239       break;
240     case 'L':
241       license(stdout);
242       exit(0);
243       break;
244     case 'v':
245       cmd.verbose=1;
246       break;
247     case 'q':
248       cmd.verbose=-1;
249       break;
250     case 'D':
251       cmd.debug++;
252       break;
253     case 'E':
254     case 'F':
255       p = getenv(optarg);
256       if (p==NULL) {
257 	fprintf(stderr, _("%s: environment variable %s does not exist.\n"),
258 		cmd.name, optarg);
259 	exit(9);
260       }
261       if (c == 'E') {
262 	free(cmd.keyword);
263 	cmd.keyword = strdup(p);
264 	if (!cmd.keyword) {
265 	  goto mem_error;
266 	}
267       } else {
268 	free(cmd.keyword2);
269 	cmd.keyword2 = strdup(p);
270 	if (!cmd.keyword2) {
271 	  goto mem_error;
272 	}
273       }
274       /* attempt to erase keyword from the environment, so that
275          subsequent calls to 'ps' don't display it */
276       for (; *p; p++) {
277 	*p = 0;
278       }
279       break;
280     case 'K':
281       free(cmd.keyword);
282       cmd.keyword = strdup(optarg);
283       if (!cmd.keyword) {
284 	goto mem_error;
285       }
286       /* attempt to erase keyword from command line so that subsequent
287          calls to 'ps' don't display it */
288       for (p=optarg; *p; p++) {
289 	*p = 0;
290       }
291       break;
292     case 'H':
293       free(cmd.keyword2);
294       cmd.keyword2 = strdup(optarg);
295       if (!cmd.keyword2) {
296 	goto mem_error;
297       }
298       /* attempt to erase keyword from command line so that subsequent
299          calls to 'ps' don't display it */
300       for (p=optarg; *p; p++) {
301 	*p = 0;
302       }
303       break;
304     case 'k':
305       cmd.keyfile = optarg;
306       break;
307     case 'S':
308       cmd.suffix = optarg;
309       break;
310     case 's':
311       cmd.strictsuffix = 1;
312       break;
313     case 'P':
314       cmd.prompt = optarg;
315       break;
316     case 'Q':
317       cmd.prompt2 = optarg;
318       break;
319     case 'e':
320       cmd.mode = ENCRYPT;
321       break;
322     case 'd':
323       cmd.mode = DECRYPT;
324       break;
325     case 'c':
326       cmd.mode = CAT;
327       break;
328     case 'x':
329       cmd.mode = KEYCHANGE;
330       break;
331     case 'u':
332       cmd.mode = UNIXCRYPT;
333       break;
334     case 't':
335       cmd.timid = 1;
336       break;
337     case 'b':
338       cmd.timid = 0;
339       break;
340     case 'y':
341       cmd.keyref = optarg;
342       cmd.timid = 0;
343       break;
344     case 'r':
345       cmd.recursive = 1;
346       break;
347     case 'R':
348       cmd.recursive = 2;
349       break;
350     case 'l':
351       cmd.symlinks = 1;
352       break;
353     case 'f':
354       cmd.force = 1;
355       break;
356     case 'm':
357       cmd.mismatch = 1;
358       break;
359     case 'T':
360       cmd.tmpfiles = 1;
361       break;
362     case '?':
363       fprintf(stderr, _("Try --help for more information.\n"));
364       exit(1);
365       break;
366     default:
367       fprintf(stderr, _("%s: unimplemented option -- %c\n"), cmd.name, c);
368       exit(1);
369     }
370   }
371 
372   cmd.infiles = &av[optind];
373   cmd.count = ac-optind;
374 
375   /* figure out if there are some filenames. Even an empty list of
376      filenames is considered "some" filenames if "--" was used */
377 
378   if (cmd.count > 0 || (optind > 1 && strcmp(av[optind-1], "--") == 0)) {
379     cmd.filter = 0;
380   }
381 
382   /* in certain modes, allow symlinks by default */
383   if (cmd.mode == CAT || cmd.mode == UNIXCRYPT) {
384     cmd.symlinks = 1;
385   }
386 
387   if (cmd.debug) {
388     output_commandline(cmd, stderr);
389   }
390 
391   /* and now check that options are consistent */
392 
393   /* don't allow killer combination of -m and destructive update */
394   if (cmd.mismatch && !cmd.filter && cmd.mode!=CAT && cmd.mode!=UNIXCRYPT) {
395     fprintf(stderr, _("%s: option -m can only be used with -c or when running as a filter.\n"), cmd.name);
396     exit(1);
397   }
398 
399   /* if not in filter mode, and 0 filenames follow, don't bother continuing */
400   if (!cmd.filter && cmd.count==0) {
401     if (cmd.verbose>=0) {
402       fprintf(stderr, _("%s: warning: empty list of filenames given\n"), cmd.name);
403     }
404     exit(0);
405   }
406 
407   /* check that we are not reading or writing encrypted data from/to a
408      terminal, unless -f given */
409   if (cmd.filter && !cmd.force) {
410     if ((cmd.mode==ENCRYPT || cmd.mode==KEYCHANGE)
411 	&& isatty(fileno(stdout))) {
412       fprintf(stderr, _("%s: encrypted data not written to a terminal. "
413 	      "Use -f to force encryption.\n"
414 	      "Try --help for more information.\n"), cmd.name);
415       exit(1);
416     }
417     if ((cmd.mode==DECRYPT || cmd.mode==KEYCHANGE || cmd.mode==CAT
418 	 || cmd.mode==UNIXCRYPT)
419 	&& isatty(fileno(stdin))) {
420       fprintf(stderr, _("%s: encrypted data not read from a terminal. "
421 	      "Use -f to force decryption.\n"
422 	      "Try --help for more information.\n"), cmd.name);
423       exit(1);
424     }
425   }
426 
427   return cmd;
428 
429  mem_error:
430   fprintf(stderr, "%s: %s\n", cmd.name, strerror(errno));
431   free(cmd.keyword);
432   free(cmd.keyword2);
433   exit(2);
434 }
435 
436 /* ---------------------------------------------------------------------- */
437 
main(int ac,char * av[])438 int main(int ac, char *av[]) {
439   int r;
440   FILE *f;
441 
442 #if ENABLE_NLS
443   setlocale (LC_ALL, "");
444   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
445   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
446   textdomain(GETTEXT_PACKAGE);
447 #endif
448 
449   /* read command line */
450   cmd = read_commandline(ac, av);
451 
452   /* if --keyfile requested, read one (normal mode) or two (change key
453      mode) keywords from file, which may be "-" for stdin. Note that
454      in this case, we ignore any keywords given on the command line
455      etc. */
456 
457   if (cmd.keyfile) {
458     if (strcmp(cmd.keyfile, "-")==0) {
459       f = stdin;
460     } else {
461       f = fopen(cmd.keyfile, "r");
462       if (!f) {
463 	fprintf(stderr, _("%s: could not read key from %s: %s\n"), cmd.name, cmd.keyfile, strerror(errno));
464 	exit(9);
465       }
466     }
467 
468     free(cmd.keyword);
469     cmd.keyword = xreadline(f, cmd.name);
470     if (cmd.keyword==NULL) {  /* end of file */
471       fprintf(stderr, _("%s: error reading keyfile\n"), cmd.name);
472       exit(9);
473     }
474     if (cmd.mode==KEYCHANGE) {
475       free(cmd.keyword2);
476       cmd.keyword2 = xreadline(f, cmd.name);
477       if (cmd.keyword2==NULL) { /* end of file */
478 	fprintf(stderr, _("%s: error reading keyfile\n"), cmd.name);
479 	exit(9);
480       }
481     }
482     if (strcmp(cmd.keyfile, "-")!=0) {
483       fclose(f);
484     }
485   }
486 
487   /* read keyword from terminal if necessary */
488   if (cmd.keyword==NULL) {
489     if (!cmd.prompt) {
490       switch (cmd.mode) {
491 
492       case ENCRYPT: default:
493 	cmd.prompt = _("Enter encryption key: ");
494 	break;
495 
496       case DECRYPT: case CAT:
497 	cmd.prompt = _("Enter decryption key: ");
498 	break;
499 
500       case KEYCHANGE:
501 	cmd.prompt = _("Enter old key: ");
502 	break;
503 
504       case UNIXCRYPT:
505 	cmd.prompt = _("Enter key: ");
506 	break;
507       }
508     }
509     free(cmd.keyword);
510     cmd.keyword = readkey(cmd.prompt, "", cmd.name, 0);
511     if (cmd.keyword==NULL) {  /* end of file: exit gracefully */
512       fprintf(stderr, _("%s: no key given\n"), cmd.name);
513       exit(9);
514     }
515     /* in some circumstances, prompt for the key a second time */
516     if (cmd.timid && cmd.mode==ENCRYPT) {
517       char *repeat;
518 
519       repeat = readkey(cmd.prompt, _("(repeat) "), cmd.name, 0);
520       if (repeat==NULL || strcmp(repeat, cmd.keyword)!=0) {
521 	fprintf(stderr, _("Sorry, the keys you entered did not match.\n"));
522 	exit(7);
523       }
524     }
525   }
526 
527   /* read keyword2 from terminal if necessary */
528   if (cmd.mode==KEYCHANGE && cmd.keyword2==NULL) {
529     if (cmd.prompt2 == NULL) {
530       cmd.prompt2 = _("Enter new key: ");
531     }
532     free(cmd.keyword2);
533     cmd.keyword2 = readkey(cmd.prompt2, "", cmd.name, 0);
534     if (cmd.keyword2==NULL) {  /* end of file: exit gracefully */
535       fprintf(stderr, _("%s: no key given\n"), cmd.name);
536       exit(9);
537     }
538     /* in some circumstances, prompt for the key a second time */
539     if (cmd.timid) {
540       char *repeat;
541 
542       repeat = readkey(cmd.prompt2, _("(repeat) "), cmd.name, 0);
543       if (repeat==NULL || strcmp(repeat, cmd.keyword2)!=0) {
544 	fprintf(stderr, _("Sorry, the keys you entered did not match.\n"));
545 	exit(7);
546       }
547     }
548   }
549 
550   /* reset stdin/stdout to binary mode under Windows */
551   setmode(0,O_BINARY);
552   setmode(1,O_BINARY);
553 
554   /* if --keyref given, check encryption keys against named file */
555   if (cmd.keyref && (cmd.mode == ENCRYPT || cmd.mode == KEYCHANGE)) {
556     f = fopen(cmd.keyref, "rb");
557     if (!f) {
558       fprintf(stderr, _("%s: could not open %s: %s\n"), cmd.name, cmd.keyref, strerror(errno));
559       exit(10);
560     }
561     if (cmd.mode == ENCRYPT) {
562       r = keycheck_stream(f, cmd.keyword);
563     } else {
564       r = keycheck_stream(f, cmd.keyword2);
565     }
566     if (r == -2 && (ccrypt_errno == CCRYPT_EFORMAT || ccrypt_errno == CCRYPT_EMISMATCH)) {
567       fprintf(stderr, _("The encryption key does not match the reference file.\n"));
568       exit(10);
569     } else if (r) {
570       fprintf(stderr, "%s: %s: %s\n", cmd.name, cmd.keyref, ccrypt_error(r));
571       if (r == -3) {  /* i/o error */
572 	exit(10);
573       } else {
574 	exit(2);
575       }
576     }
577   }
578 
579   /* filter mode */
580 
581   if (cmd.filter) {
582     switch (cmd.mode) {
583 
584     case ENCRYPT: default:
585       r = ccencrypt_streams(stdin, stdout, cmd.keyword);
586       break;
587 
588     case DECRYPT: case CAT:
589       r = ccdecrypt_streams(stdin, stdout, cmd.keyword);
590       break;
591 
592     case KEYCHANGE:
593       r = cckeychange_streams(stdin, stdout, cmd.keyword, cmd.keyword2);
594       break;
595 
596     case UNIXCRYPT:
597       r = unixcrypt_streams(stdin, stdout, cmd.keyword);
598       break;
599     }
600 
601     free(cmd.keyword);
602     free(cmd.keyword2);
603 
604     if (r) {
605       fprintf(stderr, "%s: %s\n", cmd.name, ccrypt_error(r));
606       if (r==-2 && (ccrypt_errno==CCRYPT_EFORMAT || ccrypt_errno==CCRYPT_EMISMATCH)) {
607 	return 4;
608       } else if (r == -3) {
609 	return 3;
610       } else {
611 	return 2;
612       }
613     }
614     r = fflush(stdout);
615     if (r == EOF) {
616       fprintf(stderr, "%s: %s\n", cmd.name, strerror(errno));
617       return 3;
618     }
619     return 0;
620   }
621 
622   /* non-filter mode: traverse files */
623   r = traverse_toplevel(cmd.infiles, cmd.count);
624 
625   free(cmd.keyword);
626   free(cmd.keyword2);
627 
628   if (r==1) {
629     return 4;
630   } else if (r) {
631     return 8;
632   } else {
633     return 0;
634   }
635 }
636