root/gpgdir/tags/gpgdir-1.8/gpgdir

Revision 275, 40.3 kB (checked in by mbr, 8 months ago)

1.8 release

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1 #!/usr/bin/perl -w
2 #
3 ###########################################################################
4 #
5 # File: gpgdir
6 #
7 # URL: http://www.cipherdyne.org/gpgdir/
8 #
9 # Purpose:  To encrypt/decrypt whole directories
10 #
11 # Author: Michael Rash (mbr@cipherdyne.com)
12 #
13 # Version: 1.8
14 #
15 # Copyright (C) 2002-2007 Michael Rash (mbr@cipherdyne.org)
16 #
17 # License (GNU General Public License):
18 #
19 #    This program is distributed in the hope that it will be useful,
20 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
21 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 #    GNU General Public License for more details.
23 #
24 #    You should have received a copy of the GNU General Public License
25 #    along with this program; if not, write to the Free Software
26 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
27 #    USA
28 #
29 ###########################################################################
30 #
31 # $Id$
32 #
33
34 use lib '/usr/lib/gpgdir';
35 use File::Find;
36 use File::Copy;
37 use Term::ReadKey;
38 use GnuPG::Interface;
39 use IO::File;
40 use IO::Handle;
41 use Getopt::Long;
42 use Cwd;
43 use strict;
44
45 ### set the current gpgdir version and file revision numbers
46 my $version = '1.8';
47 my $revision_svn = '$Revision$';
48 my $rev_num = '1';
49 ($rev_num) = $revision_svn =~ m|\$Rev.*:\s+(\S+)|;
50
51 ### establish some defaults
52 my $encrypt_user    = '';
53 my $gpg_homedir     = '';
54 my $dir             = '';
55 my $pw              = '';
56 my $encrypt_dir     = '';
57 my $decrypt_dir     = '';
58 my $homedir         = '';
59 my $exclude_pat     = '';
60 my $exclude_file    = '';
61 my $include_pat     = '';
62 my $include_file    = '';
63 my $total_encrypted = 0;
64 my $total_decrypted = 0;
65 my $norecurse       = 0;
66 my $printver        = 0;
67 my $no_delete       = 0;
68 my $no_fs_times     = 0;
69 my $test_and_exit   = 0;
70 my $trial_run       = 0;
71 my $skip_test_mode  = 0;
72 my $verbose         = 0;
73 my $quiet           = 0;
74 my $use_gpg_agent   = 0;  ### use gpg-agent for passwords
75 my $gpg_agent_info  = '';
76 my $force_mode      = 0;
77 my $help            = 0;
78 my $wipe_mode       = 0;
79 my $encrypt_mode    = 0;
80 my $use_default_key = 0;
81 my $pw_file         = '';
82 my $wipe_cmd        = '/usr/bin/wipe';
83 my $wipe_cmdline    = '';
84 my $wipe_interactive = 0;
85 my $interactive_mode = 0;
86 my $ascii_armor_mode = 0;
87 my @exclude_patterns = ();
88 my @include_patterns = ();
89 my %files            = ();
90 my %options          = ();
91 my %obfuscate_ctrs   = ();
92 my %obfuscated_dirs  = ();
93 my $total_mapped_files = 0;
94 my $have_obfuscated_file = 0;
95 my $cmdline_no_password = 0;
96 my $obfuscate_mode = 0;
97 my $obfuscate_map_filename  = '.gpgdir_map_file';
98 my $overwrite_encrypted = 0;
99 my $overwrite_decrypted = 0;
100 my $symmetric_mode  = 0;
101 my $DEL_SOURCE_FILE = 1;
102 my $NO_DEL_SOURCE_FILE = 0;
103
104 ### for user answers
105 my $ACCEPT_YES_DEFAULT = 1;
106 my $ACCEPT_NO_DEFAULT  = 2;
107
108 unless ($< == $>) {
109     die "[*] Real and effective uid must be the same.  Make sure\n",
110         "    gpgdir has not been installed as a SUID binary.\n",
111         "Exiting.";
112 }
113
114 my @args_cp = @ARGV;
115
116 ### make Getopts case sensitive
117 Getopt::Long::Configure('no_ignore_case');
118
119 die "[-] Use --help for usage information.\n" unless(GetOptions (
120     'encrypt=s'      => \$encrypt_dir,     # Encrypt files in this directory.
121     'decrypt=s'      => \$decrypt_dir,     # Decrypt files in this directory.
122     'gnupg-dir=s'    => \$gpg_homedir,     # Path to /path/to/.gnupg directory.
123     'pw-file=s'      => \$pw_file,         # Read password out of this file.
124     'agent'          => \$use_gpg_agent,   # Use gpg-agent for passwords.
125     'Agent-info=s'   => \$gpg_agent_info,  # Specify GnuPG agent connection
126                                            # information.
127     'Wipe'           => \$wipe_mode,       # Securely delete unencrypted files.
128     'wipe-path=s'    => \$wipe_cmd,        # Path to wipe command.
129     'wipe-interactive' => \$wipe_interactive, # Disable "wipe -I"
130     'wipe-cmdline=s' => \$wipe_cmdline,    # Specify wipe command line.
131     'Obfuscate-filenames' => \$obfuscate_mode, # substitute real filenames
132                                            # with manufactured ones.
133     'obfuscate-map-file=s' => \$obfuscate_map_filename, # path to mapping file.
134     'Force'          => \$force_mode,      # Continue if files can't be deleted.
135     'overwrite-encrypted' => \$overwrite_encrypted, # Overwrite encrypted files
136                                                     # even if they exist.
137     'overwrite-decrypted' => \$overwrite_decrypted, # Overwrite decrypted files
138                                                     # even if they exist.
139     'Exclude=s'      => \$exclude_pat,     # Exclude a pattern from encrypt/decrypt
140                                            # cycle.
141     'Exclude-from=s' => \$exclude_file,    # Exclude patterns in <file> from
142                                            # encrypt decrypt cycle.
143     'Include=s'      => \$include_pat,     # Specify a pattern used to restrict
144                                            # encrypt/decrypt operation to.
145     'Include-from=s' => \$include_file,    # Specify a file of include patterns to
146                                            # restrict all encrypt/decrypt
147                                            # operations to.
148     'test-mode'      => \$test_and_exit,   # Run encrypt -> decrypt test only and
149                                            # exit.
150     'Trial-run'      => \$trial_run,       # Don't modify any files; just show what
151                                            # would have happened.
152     'quiet'          => \$quiet,           # Print as little as possible to
153                                            # stdout.
154     'Interactive'    => \$interactive_mode, # Query the user before encrypting/
155                                             # decrypting/deleting any files.
156     'Key-id=s'       => \$encrypt_user,    # Specify encrypt/decrypt key
157     'Default-key'    => \$use_default_key, # Assume that default-key is set within
158                                            # ~/.gnupg/options.
159     'Symmetric'      => \$symmetric_mode, # encrypt using symmetric cipher.
160                                                   # (this option is not required to
161                                                   # also decrypt, GnuPG handles
162                                                   # that automatically).
163     'Plain-ascii'    => \$ascii_armor_mode, # Ascii armor mode (creates non-binary
164                                             # encrypted files).
165     'skip-test'      => \$skip_test_mode,  # Skip encrypt -> decrypt test.
166     'no-recurse'     => \$norecurse,       # Don't encrypt/decrypt files in
167                                            # subdirectories.
168     'no-delete'      => \$no_delete,       # Don't delete files once they have
169                                            # been encrypted.
170     'no-password'    => \$cmdline_no_password, # Do not query for a password (only
171                                                # useful for when the gpg literally
172                                                # has no password).
173     'user-homedir=s' => \$homedir,         # Path to home directory.
174     'no-preserve-times' => \$no_fs_times,  # Don't preserve mtimes or atimes.
175     'verbose'        => \$verbose,         # Verbose mode.
176     'Version'        => \$printver,        # Print version
177     'help'           => \$help             # Print help
178 ));
179 &usage_and_exit() if $help;
180
181 print "[+] gpgdir v$version (file revision: $rev_num)\n",
182     "      by Michael Rash <mbr\@cipherdyne.org>\n"
183     and exit 0 if $printver;
184
185 if ($symmetric_mode and ($use_gpg_agent or $gpg_agent_info)) {
186     die "[*] gpg-agent incompatible with --Symmetric mode";
187 }
188
189 if ($encrypt_dir and $overwrite_decrypted) {
190     die "[*] The -e and --overwrite-decrypted options are incompatible.";
191 }
192 if ($decrypt_dir and $overwrite_encrypted) {
193     die "[*] The -d and --overwrite-encrypted options are incompatible.";
194 }
195
196 if ($wipe_mode) {
197     unless (-e $wipe_cmd) {
198         die "[*] Can't find wipe command at: $wipe_cmd,\n",
199             "    use --wipe-path to specify path.";
200     }
201     unless (-e $wipe_cmd) {
202         die "[*] Can't execute $wipe_cmd";
203     }
204 }
205
206 my $initial_dir = cwd or die "[*] Could not get CWD: $!";
207
208 if ($gpg_homedir) {
209     ### it was specified on the comamnd line
210     if ($gpg_homedir !~ m|^/|) {
211         $gpg_homedir = $initial_dir . '/' . $gpg_homedir;
212     }
213 }
214
215 ### build up GnuPG options hash
216 if ($verbose) {
217     %options = ('homedir' => $gpg_homedir);
218 } else {
219     %options = (
220         'batch'   => 1,
221         'homedir' => $gpg_homedir
222     );
223 }
224
225 $options{'armor'} = 1 if $ascii_armor_mode;
226
227 ### get the path to the user's home directory
228 $homedir = &get_homedir() unless $homedir;
229
230 unless ($symmetric_mode) {
231     unless ($gpg_homedir) {
232         $gpg_homedir = "${homedir}/.gnupg"
233             if -d "${homedir}/.gnupg";
234     }
235     unless (-d $gpg_homedir) {
236         die "[*] GnuPG directory: $gpg_homedir does not exist. Please\n",
237             "    create it by executing: \"gpg --gen-key\".  Exiting.\n";
238     }
239
240     ### get the key identifier from ~/.gnupg
241     $encrypt_user = &get_key() unless $encrypt_user or $use_default_key;
242 }
243
244 if ($decrypt_dir and $encrypt_dir) {
245     die "[*] You cannot encrypt and decrypt the same directory, see --help\n";
246 }
247
248 unless ($decrypt_dir or $encrypt_dir or $test_and_exit) {
249     print "[*] Please specify -e <dir>, -d <dir>, or --test-mode, see --help\n";
250 }
251
252 ### exclude file pattern
253 push @exclude_patterns, $exclude_pat if $exclude_pat;
254
255 if ($exclude_file) {
256     open P, "< $exclude_file" or die "[*] Could not open file: $exclude_file";
257     my @lines = <P>;
258     close P;
259     for my $line (@lines) {
260         next unless $line =~ /\S/;
261         chomp $line;
262         push @exclude_patterns, qr{$line};
263     }
264 }
265
266 ### include file pattern
267 push @include_patterns, $include_pat if $include_pat;
268
269 if ($include_file) {
270     open P, "< $include_file" or die "[*] Could not open file: $include_file";
271     my @lines = <P>;
272     close P;
273     for my $line (@lines) {
274         next unless $line =~ /\S/;
275         chomp $line;
276         push @include_patterns, qr{$line};
277     }
278 }
279
280 if ($encrypt_dir) {
281     $dir = $encrypt_dir;
282     $encrypt_mode = 1;
283 } elsif ($decrypt_dir) {
284     $dir = $decrypt_dir;
285     $encrypt_mode = 0;
286 }
287
288 if ($dir) {
289     die "[*] Directory does not exist: $dir" unless -e $dir;
290     die "[*] Not a directory: $dir" unless -d $dir;
291 }
292
293 ### don't need to test encrypt/decrypt ability if we are running
294 ### in --Trial-run mode.
295 $skip_test_mode = 1 if $trial_run;
296
297 if ($symmetric_mode) {
298     &get_password();
299 } else {
300     &get_password() unless $encrypt_mode and $skip_test_mode;
301 }
302
303 if ($dir eq '.') {
304     $dir = $initial_dir;
305 } elsif ($dir !~ m|^/|) {
306     $dir = $initial_dir . '/' . $dir;
307 }
308 $dir =~ s|/$||;  ### remove any trailing slash
309
310 ### run a test to make sure gpgdir and encrypt and decrypt a file
311 unless ($skip_test_mode) {
312     my $rv = &test_mode();
313     exit $rv if $test_and_exit;
314 }
315
316 if ($encrypt_mode) {
317     print "[+] Encrypting directory: $dir\n" unless $quiet;
318 } else {
319     print "[+] Decrypting directory: $dir\n" unless $quiet;
320 }
321
322 ### build a hash of file paths to work against
323 &get_files($dir);
324
325 ### perform the gpg operation (encrypt/decrypt)
326 &gpg_operation();
327
328 &obfuscated_mapping_files() if $obfuscate_mode;
329
330 unless ($obfuscate_mode) {
331     if ($have_obfuscated_file) {
332         print "[-] Obfuscated filenames detected, try decrypting with -O.\n"
333             unless $quiet;
334     }
335 }
336
337 if ($encrypt_mode) {
338     print "[+] Total number of files encrypted: " .
339         "$total_encrypted\n" unless $quiet;
340 } else {
341     print "[+] Total number of files decrypted: " .
342         "$total_decrypted\n" unless $quiet;
343 }
344
345 exit 0;
346 #==================== end main =====================
347
348 sub encrypt_file() {
349     my ($in_file, $out_file, $del_flag) = @_;
350
351     my $gpg = GnuPG::Interface->new();
352     $gpg->options->hash_init(%options);
353
354     die "[*] Could not create new gpg object with ",
355         "homedir: $gpg_homedir" unless $gpg;
356
357     unless ($symmetric_mode or $use_default_key) {
358         $gpg->options->default_key($encrypt_user);
359         $gpg->options->push_recipients($encrypt_user);
360     }
361
362     my ($input_fh, $output_fh, $error_fh, $pw_fh, $status_fh) =
363         (IO::File->new($in_file),
364         IO::File->new("> $out_file"),
365         IO::Handle->new(),
366         IO::Handle->new(),
367         IO::Handle->new());
368
369     my $handles = GnuPG::Handles->new(
370         stdin  => $input_fh,
371         stdout => $output_fh,
372         stderr => $error_fh,
373         passphrase => $pw_fh,
374         status => $status_fh
375     );
376     $handles->options('stdin')->{'direct'}  = 1;
377     $handles->options('stdout')->{'direct'} = 1;
378
379     my $pid;
380
381     if ($use_gpg_agent or $gpg_agent_info) {
382
383         ### set environment explicitly if --Agent was specified
384         if ($gpg_agent_info) {
385             $ENV{'GPG_AGENT_INFO'} = $gpg_agent_info;
386         }
387
388         $pid = $gpg->encrypt('handles' => $handles,
389             'command_args' => [ qw( --use-agent ) ]);
390
391     } else {
392         if ($symmetric_mode) {
393             $pid = $gpg->encrypt_symmetrically('handles' => $handles);
394         } else {
395             $pid = $gpg->encrypt('handles' => $handles);
396         }
397     }
398
399     print $pw_fh $pw;
400     close $pw_fh;
401
402     my @errors = <$error_fh>;
403
404     if ($verbose) {
405         print for @errors;
406     } else {
407         for (@errors) {
408             print if /bad\s+pass/;
409         }
410     }
411
412     close $input_fh;
413     close $output_fh;
414     close $error_fh;
415     close $status_fh;
416
417     waitpid $pid, 0;
418
419     if (-s $out_file == 0) {
420         &delete_file($out_file);
421         &delete_file($in_file) if $del_flag == $DEL_SOURCE_FILE;
422         if ($use_gpg_agent) {
423             die "[*] Created zero-size file: $out_file\n",
424 "    Maybe gpg-agent does not yet have the password for that key?\n",
425 "    Try re-running with -v.";
426         } else {
427             die "[*] Created zero-size file: $out_file\n",
428                 "    Bad password? Try re-running with -v.";
429         }
430     }
431
432     return;
433 }
434
435 sub decrypt_file() {
436     my ($in_file, $out_file, $del_flag) = @_;
437
438     my $gpg = GnuPG::Interface->new();
439     $gpg->options->hash_init(%options);
440
441     die "[*] Could not create new gpg object with ",
442         "homedir: $gpg_homedir" unless $gpg;
443
444     unless ($symmetric_mode or $use_default_key) {
445         $gpg->options->default_key($encrypt_user);
446         $gpg->options->push_recipients($encrypt_user);
447     }
448
449     my ($input_fh, $output_fh, $error_fh, $pw_fh, $status_fh) =
450         (IO::File->new($in_file),
451         IO::File->new("> $out_file"),
452         IO::Handle->new(),
453         IO::Handle->new(),
454         IO::Handle->new());
455
456     my $handles = GnuPG::Handles->new(
457         stdin  => $input_fh,
458         stdout => $output_fh,
459         stderr => $error_fh,
460         passphrase => $pw_fh,
461         status => $status_fh
462     );
463     $handles->options('stdin')->{'direct'}  = 1;
464     $handles->options('stdout')->{'direct'} = 1;
465
466     my $pid;
467
468     if ($use_gpg_agent) {
469         $pid = $gpg->decrypt('handles' => $handles,
470             'command_args' => [ qw( --use-agent ) ]);
471     } else {
472         $pid = $gpg->decrypt('handles' => $handles);
473     }
474
475     print $pw_fh $pw;
476     close $pw_fh;
477
478     my @errors = <$error_fh>;
479
480     if ($verbose) {
481         print for @errors;
482     } else {
483         for (@errors) {
484             print if /bad\s+pass/;
485         }
486     }
487
488     close $input_fh;
489     close $output_fh;
490     close $error_fh;
491     close $status_fh;
492
493     waitpid $pid, 0;
494
495     if (-s $out_file == 0) {
496         &delete_file($out_file);
497         &delete_file($in_file) if $del_flag == $DEL_SOURCE_FILE;
498         if ($use_gpg_agent) {
499             die "[*] Created zero-size file: $out_file\n",
500 "    Maybe gpg-agent does not yet have the password for that key?\n",
501 "    Try re-running with -v.";
502         } else {
503             die "[*] Created zero-size file: $out_file\n",
504                 "    Bad password? Try re-running with -v.";
505         }
506     }
507     return;
508 }
509
510 sub delete_file() {
511     my $file = shift;
512
513     return if $no_delete;
514     return unless -e $file;
515
516     if ($wipe_mode) {
517         my $cmd = $wipe_cmd;
518         if ($wipe_cmdline) {
519             $cmd .= " $wipe_cmdline ";
520         } else {
521             if ($wipe_interactive) {
522                 $cmd .= ' -i ';
523             } else {
524                 $cmd .= ' -I -s ';
525             }
526         }
527         $cmd .= $file;
528         if ($verbose) {
529             print "    Executing: $cmd\n";
530         }
531
532         ### wipe the file
533         system $cmd;
534
535     } else {
536         unlink $file;
537     }
538
539     if (-e $file) {
540         my $msg = "[-] Could not delete file: $file\n";
541         if ($force_mode) {
542             print $msg unless $quiet;
543         } else {
544             die $msg unless $quiet;
545         }
546     }
547     return;
548 }
549
550 sub gpg_operation() {
551
552     ### sort by oldest to youngest mtime
553     FILE: for my $file (sort
554             {$files{$a}{'mtime'} <=> $files{$b}{'mtime'}} keys %files) {
555
556         ### see if we have an exclusion pattern that implies
557         ### we should skip this file
558         if (@exclude_patterns and &exclude_file($file)) {
559             print "[+] Skipping excluded file: $file\n"
560                 if $verbose and not $quiet;
561             next FILE;
562         }
563
564         ### see if we have an inclusion pattern that implies
565         ### we should process this file
566         if (@include_patterns and not &include_file($file)) {
567             print "[+] Skipping non-included file: $file\n"
568                 if $verbose and not $quiet;
569             next FILE;
570         }
571
572         ### dir is always a full path
573         my ($dir, $filename) = ($file =~ m|(.*)/(.*)|);
574
575         unless (chdir($dir)) {
576             print "[-] Could not chdir $dir, skipping.\n" unless $quiet;
577             next FILE;
578         }
579
580         my $mtime = $files{$file}{'mtime'};
581         my $atime = $files{$file}{'atime'};
582
583         if ($encrypt_mode) {
584
585             my $encrypt_filename = "$filename.gpg";
586
587             if ($obfuscate_mode) {
588
589                 unless (defined $obfuscate_ctrs{$dir}) {
590
591                     ### create a new gpgdir mapping file for obfuscated file
592                     ### names, but preserve any previously encrypted file
593                     ### name mappings
594                     &handle_old_obfuscated_map_file();
595
596                     ### make obfuscated file names start at 1 for each
597                     ### directory
598                     $obfuscate_ctrs{$dir} = 1;
599                 }
600
601                 $encrypt_filename = 'gpgdir_' . $$ . '_'
602                         . $obfuscate_ctrs{$dir} . '.gpg';
603             }
604
605             if ($ascii_armor_mode) {
606                 $encrypt_filename = "$filename.asc";
607             }
608
609             if (-e $encrypt_filename and not $overwrite_encrypted) {
610                 print "[-] Encrypted file $dir/$encrypt_filename already ",
611                     "exists, skipping.\n" unless $quiet;
612                 next FILE;
613             }
614
615             if ($interactive_mode) {
616                 next FILE unless (&query_yes_no(
617                     "    Encrypt: $file ([y]/n)?  ", $ACCEPT_YES_DEFAULT));
618             }
619
620             print "[+] Encrypting:  $file\n" unless $quiet;
621
622             unless ($trial_run) {
623
624                 &encrypt_file($filename, $encrypt_filename,
625                         $NO_DEL_SOURCE_FILE);
626
627                 if (-e $encrypt_filename && -s $encrypt_filename != 0) {
628                     ### set the atime and mtime to be the same as the
629                     ### original file.
630                     unless ($no_fs_times) {
631                         if (defined $mtime and $mtime and
632                                 defined $atime and $atime) {
633                             utime $atime, $mtime, $encrypt_filename;
634                         }
635                     }
636                     ### only delete the original file if
637                     ### the encrypted one exists
638                     if ($wipe_mode and not $quiet) {
639                         print "    Securely deleting file: $file\n";
640                     }
641                     &delete_file($filename);
642
643                     if ($obfuscate_mode) {
644
645                         ### record the original file name mapping
646                         &append_obfuscated_mapping($filename,
647                             $encrypt_filename);
648
649                         $obfuscate_ctrs{$dir}++;
650                     }
651
652                     $total_encrypted++;
653
654                 } else {
655                     print "[-] Could not encrypt file: $file\n" unless $quiet;
656                     next FILE;
657                 }
658             }
659
660         } else {
661
662             ### allow filenames with spaces
663             my $decrypt_filename = '';
664             if ($filename =~ /^(.+)\.gpg$/) {
665                 $decrypt_filename = $1;
666             } elsif ($filename =~ /^(.+)\.asc$/) {
667                 $decrypt_filename = $1;
668             }
669
670             if ($obfuscate_mode) {
671
672                 &import_obfuscated_file_map($dir)
673                     unless defined $obfuscated_dirs{$dir};
674
675                 if (defined $obfuscated_dirs{$dir}{$filename}) {
676                     $decrypt_filename = $obfuscated_dirs{$dir}{$filename};
677                 } else {
678                     ###
679                     print "[-] Obfuscated file map does not exist for ",
680                         "$filename in\n    $obfuscate_map_filename, ",
681                         "skipping.\n" unless $quiet;
682                     next FILE;
683                 }
684
685             } else {
686                 if (not $force_mode and $file =~ /gpgdir_\d+_\d+.gpg/) {
687                     ### be careful not to decrypt obfuscated file unless we
688                     ### are running in -O mode.  This ensures that the
689                     ### original file names will be acquired from the
690                     ### /some/dir/.gpgdir_map_file
691                     $have_obfuscated_file = 1;
692                     next FILE;
693                 }
694             }
695
696             ### length() allows files named "0"
697             next FILE unless length($decrypt_filename) > 0;
698
699             ### don't decrypt a file on top of a normal file of
700             ### the same name
701             if (-e $decrypt_filename and not $overwrite_decrypted) {
702                 print "[-] Decrypted file $dir/$decrypt_filename ",
703                     "already exists. Skipping.\n" unless $quiet;
704                 next FILE;
705             }
706
707             if ($interactive_mode) {
708                 next FILE unless (&query_yes_no(
709                     "    Decrypt: $file ([y]/n)?  ", $ACCEPT_YES_DEFAULT));
710             }
711
712             unless ($trial_run) {
713
714                 print "[+] Decrypting:  $dir/$filename\n" unless $quiet;
715                 &decrypt_file($filename, $decrypt_filename,
716                         $NO_DEL_SOURCE_FILE);
717
718                 if (-e $decrypt_filename && -s $decrypt_filename != 0) {
719                     ### set the atime and mtime to be the same as the
720                     ### original file.
721                     unless ($no_fs_times) {
722                         if (defined $mtime and $mtime and
723                                 defined $atime and $atime) {
724                             utime $atime, $mtime, $decrypt_filename;
725                         }
726                     }
727                     if ($wipe_mode and not $quiet) {
728                         print "    Securely deleting file: $file\n";
729                     }
730                     ### only delete the original encrypted
731                     ### file if the decrypted one exists
732                     &delete_file($filename);
733
734                     $total_decrypted++;
735
736                 } else {
737                     print "[-] Could not decrypt file: $file\n" unless $quiet;
738                     next FILE;
739                 }
740             }
741         }
742     }
743     print "\n" unless $quiet;
744     chdir $initial_dir or die "[*] Could not chdir: $initial_dir\n";
745     return;
746 }
747
748 sub get_files() {
749     my $dir = shift;
750
751     print "[+] Building file list...\n" unless $quiet;
752     if ($norecurse) {
753         opendir D, $dir or die "[*] Could not open $dir: $!";
754         my @files = readdir D;
755         closedir D;
756
757         for my $file (@files) {
758             next if $file eq '.';
759             next if $file eq '..';
760             &check_file_criteria("$dir/$file");
761         }
762     } else {
763         ### get all files in all subdirectories
764         find(\&find_files, $dir);
765     }
766     return;
767 }
768
769 sub exclude_file() {
770     my $file = shift;
771     for my $pat (@exclude_patterns) {
772         if ($file =~ m|$pat|) {
773             print "[+] Skipping $file (matches exclude pattern: $pat)\n"
774                 if $verbose and not $quiet;
775             return 1;
776         }
777     }
778     return 0;
779 }
780
781 sub include_file() {
782     my $file = shift;
783     for my $pat (@include_patterns) {
784         if ($file =~ m|$pat|) {
785             print "[+] Including $file (matches include pattern: $pat)\n"
786                 if $verbose and not $quiet;
787             return 1;
788         }
789     }
790     return 0;
791 }
792
793 sub obfuscated_mapping_files() {
794
795     my $dirs_h