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