+#!/usr/bin/perl -w
+#
+###########################################################################
+#
+# File: gpgdir
+#
+# URL: http://www.cipherdyne.org/gpgdir/
+#
+# Purpose: To encrypt/decrypt whole directories
+#
+# Author: Michael Rash (mbr@cipherdyne.com)
+#
+# Version: 1.9.3
+#
+# Copyright (C) 2002-2008 Michael Rash (mbr@cipherdyne.org)
+#
+# License: GNU General Public License version 2 (GPLv2)
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+#
+###########################################################################
+#
+# $Id: gpgdir 328 2008-11-05 05:04:20Z mbr $
+#
+
+use File::Find;
+use File::Copy;
+use IO::File;
+use IO::Handle;
+use Getopt::Long;
+use Cwd;
+use strict;
+
+### set the current gpgdir version and file revision numbers
+my $version = '1.9.3';
+my $revision_svn = '$Revision: 328 $';
+my $rev_num = '1';
+($rev_num) = $revision_svn =~ m|\$Rev.*:\s+(\S+)|;
+
+### establish some defaults
+my $encrypt_user = '';
+my $gpg_homedir = '';
+my $dir = '';
+my $pw = '';
+my $encrypt_dir = '';
+my $decrypt_dir = '';
+my $sign_dir = '';
+my $verify_dir = '';
+my $homedir = '';
+my $exclude_pat = '';
+my $exclude_file = '';
+my $include_pat = '';
+my $include_file = '';
+my $lib_dir = '/usr/lib/gpgdir';
+my $pid_file = '';
+my $total_encrypted = 0;
+my $total_decrypted = 0;
+my $norecurse = 0;
+my $printver = 0;
+my $no_delete = 0;
+my $no_fs_times = 0;
+my $test_and_exit = 0;
+my $trial_run = 0;
+my $skip_test_mode = 0;
+my $verbose = 0;
+my $quiet = 0;
+my $use_gpg_agent = 0; ### use gpg-agent for passwords
+my $gpg_agent_info = '';
+my $force_mode = 0;
+my $help = 0;
+my $wipe_mode = 0;
+my $encrypt_mode = 0;
+my $signing_mode = 0;
+my $verify_mode = 0;
+my $use_default_key = 0;
+my $pw_file = '';
+my $wipe_cmd = '/usr/bin/wipe';
+my $wipe_cmdline = '';
+my $wipe_interactive = 0;
+my $interactive_mode = 0;
+my $ascii_armor_mode = 0;
+my @exclude_patterns = ();
+my @include_patterns = ();
+my %files = ();
+my %options = ();
+my %obfuscate_ctrs = ();
+my %obfuscated_dirs = ();
+my $total_mapped_files = 0;
+my $have_obfuscated_file = 0;
+my $cmdline_no_password = 0;
+my $obfuscate_mode = 0;
+my $obfuscate_map_filename = '.gpgdir_map_file';
+my $overwrite_encrypted = 0;
+my $overwrite_decrypted = 0;
+my $symmetric_mode = 0;
+my $DEL_SOURCE_FILE = 1;
+my $NO_DEL_SOURCE_FILE = 0;
+
+my $locale = 'C'; ### default LC_ALL env variable
+my $no_locale = 0;
+
+### for user answers
+my $ACCEPT_YES_DEFAULT = 1;
+my $ACCEPT_NO_DEFAULT = 2;
+
+### turn off buffering
+$| = 1;
+
+unless ($< == $>) {
+ die "[*] Real and effective uid must be the same. Make sure\n",
+ " gpgdir has not been installed as a SUID binary.\n",
+ "Exiting.";
+}
+
+my @args_cp = @ARGV;
+
+### make Getopts case sensitive
+Getopt::Long::Configure('no_ignore_case');
+
+die "[*] Use --help for usage information.\n" unless(GetOptions (
+ 'encrypt-dir=s' => \$encrypt_dir, # Encrypt files in this directory.
+ 'decrypt-dir=s' => \$decrypt_dir, # Decrypt files in this directory.
+ 'sign-dir=s' => \$sign_dir, # Sign files in this directory.
+ 'verify-dir=s' => \$verify_dir, # Verify files in this directory.
+ 'gnupg-dir=s' => \$gpg_homedir, # Path to /path/to/.gnupg directory.
+ 'pw-file=s' => \$pw_file, # Read password out of this file.
+ 'agent' => \$use_gpg_agent, # Use gpg-agent for passwords.
+ 'Agent-info=s' => \$gpg_agent_info, # Specify GnuPG agent connection
+ # information.
+ 'Wipe' => \$wipe_mode, # Securely delete unencrypted files.
+ 'wipe-path=s' => \$wipe_cmd, # Path to wipe command.
+ 'wipe-interactive' => \$wipe_interactive, # Disable "wipe -I"
+ 'wipe-cmdline=s' => \$wipe_cmdline, # Specify wipe command line.
+ 'Obfuscate-filenames' => \$obfuscate_mode, # substitute real filenames
+ # with manufactured ones.
+ 'obfuscate-map-file=s' => \$obfuscate_map_filename, # path to mapping file.
+ 'Force' => \$force_mode, # Continue if files can't be deleted.
+ 'overwrite-encrypted' => \$overwrite_encrypted, # Overwrite encrypted files
+ # even if they exist.
+ 'overwrite-decrypted' => \$overwrite_decrypted, # Overwrite decrypted files
+ # even if they exist.
+ 'Exclude=s' => \$exclude_pat, # Exclude a pattern from encrypt/decrypt
+ # cycle.
+ 'Exclude-from=s' => \$exclude_file, # Exclude patterns in <file> from
+ # encrypt decrypt cycle.
+ 'Include=s' => \$include_pat, # Specify a pattern used to restrict
+ # encrypt/decrypt operation to.
+ 'Include-from=s' => \$include_file, # Specify a file of include patterns to
+ # restrict all encrypt/decrypt
+ # operations to.
+ 'test-mode' => \$test_and_exit, # Run encrypt -> decrypt test only and
+ # exit.
+ 'Trial-run' => \$trial_run, # Don't modify any files; just show what
+ # would have happened.
+ 'quiet' => \$quiet, # Print as little as possible to
+ # stdout.
+ 'Interactive' => \$interactive_mode, # Query the user before encrypting/
+ # decrypting/deleting any files.
+ 'Key-id=s' => \$encrypt_user, # Specify encrypt/decrypt key
+ 'Default-key' => \$use_default_key, # Assume that default-key is set within
+ # ~/.gnupg/options.
+ 'Symmetric' => \$symmetric_mode, # encrypt using symmetric cipher.
+ # (this option is not required to
+ # also decrypt, GnuPG handles
+ # that automatically).
+ 'Plain-ascii' => \$ascii_armor_mode, # Ascii armor mode (creates non-binary
+ # encrypted files).
+ 'skip-test' => \$skip_test_mode, # Skip encrypt -> decrypt test.
+ 'no-recurse' => \$norecurse, # Don't encrypt/decrypt files in
+ # subdirectories.
+ 'no-delete' => \$no_delete, # Don't delete files once they have
+ # been encrypted.
+ 'no-password' => \$cmdline_no_password, # Do not query for a password (only
+ # useful for when the gpg literally
+ # has no password).
+ 'user-homedir=s' => \$homedir, # Path to home directory.
+ 'no-preserve-times' => \$no_fs_times, # Don't preserve mtimes or atimes.
+ 'LC_ALL=s' => \$locale,
+ 'locale=s' => \$locale, # synonym
+ 'no-LC_ALL' => \$no_locale,
+ 'no-locale' => \$no_locale, # synonym
+ 'Lib-dir=s' => \$lib_dir, # Path to perl module path
+ 'verbose' => \$verbose, # Verbose mode.
+ 'Version' => \$printver, # Print version
+ 'help' => \$help # Print help
+));
+&usage_and_exit() if $help;
+
+### set LC_ALL env variable
+$ENV{'LC_ALL'} = $locale unless $no_locale;
+
+print "[+] gpgdir v$version (file revision: $rev_num)\n",
+ " by Michael Rash <mbr\@cipherdyne.org>\n"
+ and exit 0 if $printver;
+
+if ($symmetric_mode and ($use_gpg_agent or $gpg_agent_info)) {
+ die "[*] gpg-agent incompatible with --Symmetric mode";
+}
+
+die "[*] Cannot --sign-dir and --verify-dir"
+ if $sign_dir and $verify_dir;
+
+if ($sign_dir) {
+ $encrypt_dir = $sign_dir;
+ $signing_mode = 1;
+} elsif ($verify_dir) {
+ $decrypt_dir = $verify_dir;
+ $verify_mode = 1;
+}
+
+if ($encrypt_dir and $overwrite_decrypted) {
+ die "[*] The -e and --overwrite-decrypted options are incompatible.";
+}
+if ($decrypt_dir and $overwrite_encrypted) {
+ die "[*] The -d and --overwrite-encrypted options are incompatible.";
+}
+
+### import perl modules (GnuPG::Interface, etc.)
+&import_perl_modules();
+
+if ($wipe_mode) {
+ unless (-e $wipe_cmd) {
+ die "[*] Can't find wipe command at: $wipe_cmd,\n",
+ " use --wipe-path to specify path.";
+ }
+ unless (-e $wipe_cmd) {
+ die "[*] Can't execute $wipe_cmd";
+ }
+}
+
+my $initial_dir = cwd or die "[*] Could not get CWD: $!";
+
+if ($gpg_homedir) {
+ ### it was specified on the comamnd line
+ if ($gpg_homedir !~ m|^/|) {
+ $gpg_homedir = $initial_dir . '/' . $gpg_homedir;
+ }
+}
+
+### build up GnuPG options hash
+if ($verbose) {
+ %options = ('homedir' => $gpg_homedir);
+} else {
+ %options = (
+ 'batch' => 1,
+ 'homedir' => $gpg_homedir
+ );
+}
+
+$options{'armor'} = 1 if $ascii_armor_mode or $signing_mode;
+
+### get the path to the user's home directory
+$homedir = &get_homedir() unless $homedir;
+
+unless ($symmetric_mode) {
+ unless ($gpg_homedir) {
+ $gpg_homedir = "${homedir}/.gnupg"
+ if -d "${homedir}/.gnupg";
+ }
+ unless (-d $gpg_homedir) {
+ die "[*] GnuPG directory: $gpg_homedir does not exist. Please\n",
+ " create it by executing: \"gpg --gen-key\". Exiting.\n";
+ }
+
+ ### get the key identifier from ~/.gnupg
+ $encrypt_user = &get_key() unless $encrypt_user or $use_default_key;
+}
+
+if ($decrypt_dir and $encrypt_dir) {
+ die "[*] Cannot encrypt and decrypt the same directory, see --help\n";
+}
+
+unless ($decrypt_dir or $encrypt_dir or $test_and_exit) {
+ die "[*] Please specify -e <dir>, -d <dir>, or --test-mode, see --help\n";
+}
+
+if ($obfuscate_mode) {
+ if ($sign_dir) {
+ die "[*] -O mode incompatible with --sign-dir";
+ } elsif ($verify_dir) {
+ die "[*] -O mode incompatible with --verify-dir";
+ }
+}
+
+### exclude file pattern
+push @exclude_patterns, $exclude_pat if $exclude_pat;
+
+if ($exclude_file) {
+ open P, "< $exclude_file" or die "[*] Could not open file: $exclude_file";
+ my @lines = <P>;
+ close P;
+ for my $line (@lines) {
+ next unless $line =~ /\S/;
+ chomp $line;
+ push @exclude_patterns, qr{$line};
+ }
+}
+
+### include file pattern
+push @include_patterns, $include_pat if $include_pat;
+
+if ($include_file) {
+ open P, "< $include_file" or die "[*] Could not open file: $include_file";
+ my @lines = <P>;
+ close P;
+ for my $line (@lines) {
+ next unless $line =~ /\S/;
+ chomp $line;
+ push @include_patterns, qr{$line};
+ }
+}
+
+if ($encrypt_dir) {
+ $dir = $encrypt_dir;
+ $encrypt_mode = 1;
+} elsif ($decrypt_dir) {
+ $dir = $decrypt_dir;
+ $encrypt_mode = 0;
+}
+
+if ($dir) {
+ die "[*] Directory does not exist: $dir" unless -e $dir;
+ die "[*] Not a directory: $dir" unless -d $dir;
+}
+
+### don't need to test encrypt/decrypt ability if we are running
+### in --Trial-run mode.
+$skip_test_mode = 1 if $trial_run or $signing_mode or $verify_mode;
+
+if ($dir eq '.') {
+ $dir = $initial_dir;
+} elsif ($dir !~ m|^/|) {
+ $dir = $initial_dir . '/' . $dir;
+}
+$dir =~ s|/$||; ### remove any trailing slash
+
+### make sure another gpgdir process is not trying to operate
+### on the same directory
+$pid_file = "$dir/.gpgdir.pid";
+&unique_pid();
+&write_pid();
+
+if ($symmetric_mode or $signing_mode) {
+ &get_password();
+} else {
+ &get_password() unless (($encrypt_mode and $skip_test_mode)
+ or $verify_mode);
+}
+
+### run a test to make sure gpgdir and encrypt and decrypt a file
+unless ($skip_test_mode) {
+ my $rv = &test_mode();
+ exit $rv if $test_and_exit;
+}
+
+if ($signing_mode) {
+ print "[+] Signing files in directory: $dir\n" unless $quiet;
+} elsif ($encrypt_mode) {
+ print "[+] Encrypting files in directory: $dir\n" unless $quiet;
+} elsif ($verify_mode) {
+ print "[+] Verifying signatures in directory: $dir\n" unless $quiet;
+} else {
+ print "[+] Decrypting files in directory: $dir\n" unless $quiet;
+}
+
+### build a hash of file paths to work against
+&get_files($dir);
+
+### perform the gpg operation (encrypt/decrypt)
+&gpg_operation();
+
+&obfuscated_mapping_files() if $obfuscate_mode;
+
+unless ($obfuscate_mode) {
+ if ($have_obfuscated_file) {
+ print "[-] Obfuscated filenames detected, try decrypting with -O\n"
+ unless $quiet;
+ }
+}
+
+if ($signing_mode) {
+ print "[+] Total number of files signed: " .
+ "$total_encrypted\n" unless $quiet;
+} elsif ($encrypt_mode) {
+ print "[+] Total number of files encrypted: " .
+ "$total_encrypted\n" unless $quiet;
+} elsif ($verify_mode) {
+ print "[+] Total number of files verified: " .
+ "$total_decrypted\n" unless $quiet;
+} else {
+ print "[+] Total number of files decrypted: " .
+ "$total_decrypted\n" unless $quiet;
+}
+
+if (-e $pid_file) {
+ unlink $pid_file or die "[*] Could not remove pid file $pid_file: $!";
+}
+
+exit 0;
+#==================== end main =====================
+
+sub encrypt_or_sign_file() {
+ my ($in_file, $out_file, $del_flag) = @_;
+
+ my $gpg = GnuPG::Interface->new();
+ $gpg->options->hash_init(%options);
+
+ die "[*] Could not create new gpg object with ",
+ "homedir: $gpg_homedir" unless $gpg;
+
+ unless ($symmetric_mode or $use_default_key) {
+ $gpg->options->default_key($encrypt_user);
+ $gpg->options->push_recipients($encrypt_user);
+ }
+
+ my ($input_fh, $output_fh, $error_fh, $pw_fh, $status_fh) =
+ (IO::File->new($in_file),
+ IO::File->new("> $out_file"),
+ IO::Handle->new(),
+ IO::Handle->new(),
+ IO::Handle->new());
+
+ my $handles = GnuPG::Handles->new(
+ stdin => $input_fh,
+ stdout => $output_fh,
+ stderr => $error_fh,
+ passphrase => $pw_fh,
+ status => $status_fh
+ );
+ $handles->options('stdin')->{'direct'} = 1;
+ $handles->options('stdout')->{'direct'} = 1;
+
+ my $pid;
+
+ if ($use_gpg_agent or $gpg_agent_info) {
+
+ ### set environment explicitly if --Agent was specified
+ if ($gpg_agent_info) {
+ $ENV{'GPG_AGENT_INFO'} = $gpg_agent_info;
+ }
+
+ $pid = $gpg->encrypt('handles' => $handles,
+ 'command_args' => [ qw( --use-agent ) ]);
+
+ } else {
+ if ($symmetric_mode) {
+ $pid = $gpg->encrypt_symmetrically('handles' => $handles);
+ } elsif ($signing_mode) {
+ $pid = $gpg->detach_sign('handles' => $handles);
+ } else {
+ $pid = $gpg->encrypt('handles' => $handles);
+ }
+ }
+
+ print $pw_fh $pw;
+ close $pw_fh;
+
+ my @errors = <$error_fh>;
+ close $error_fh;
+
+ my @status = <$status_fh>;
+ close $status_fh;
+
+ close $input_fh;
+ close $output_fh;
+
+ waitpid $pid, 0;
+
+ if ($verbose) {
+ print for @errors;
+ } else {
+ for (@errors) {
+ print if /bad\s+pass/;
+ }
+ }
+
+ if (-s $out_file == 0) {
+ &delete_file($out_file);
+ &delete_file($in_file) if $del_flag == $DEL_SOURCE_FILE;
+ if ($use_gpg_agent) {
+ die "[*] Created zero-size file: $out_file\n",
+" Maybe gpg-agent does not yet have the password for that key?\n",
+" Try with --verbose";
+ } else {
+ die "[*] Created zero-size file: $out_file\n",
+ " Bad password? Try with --verbose";
+ }
+ }
+
+ return 1;
+}
+
+sub decrypt_or_verify_file() {
+ my ($in_file, $out_file, $del_flag) = @_;
+
+ my $pid;
+ my $bad_passphrase = 0;
+ my $bad_signature = 0;
+ my $file_encrypted_with_expected_key = 0;
+ my $input_fh = '';
+ my $output_fh = '';
+ my $error_fh = '';
+ my $pw_fh = '';
+ my $status_fh = '';
+ my $handles = '';
+
+ my $gpg = GnuPG::Interface->new();
+ $gpg->options->hash_init(%options);
+
+ die "[*] Could not create new gpg object with ",
+ "homedir: $gpg_homedir" unless $gpg;
+
+ unless ($verify_mode or $symmetric_mode or $use_default_key) {
+ $gpg->options->default_key($encrypt_user);
+ $gpg->options->push_recipients($encrypt_user);
+ }
+
+ if ($verify_mode) {
+ ($input_fh, $output_fh, $error_fh, $status_fh) =
+ (IO::Handle->new(),
+ IO::Handle->new(),
+ IO::Handle->new(),
+ IO::Handle->new());
+ $handles = GnuPG::Handles->new(
+ stdin => $input_fh,
+ stdout => $output_fh,
+ stderr => $error_fh,
+ status => $status_fh
+ );
+ } else {
+ ($input_fh, $output_fh, $error_fh, $pw_fh, $status_fh) =
+ (IO::File->new($in_file),
+ IO::File->new("> $out_file"),
+ IO::Handle->new(),
+ IO::Handle->new(),
+ IO::Handle->new());
+ $handles = GnuPG::Handles->new(
+ stdin => $input_fh,
+ stdout => $output_fh,
+ stderr => $error_fh,
+ passphrase => $pw_fh,
+ status => $status_fh
+ );
+ $handles->options('stdin')->{'direct'} = 1;
+ $handles->options('stdout')->{'direct'} = 1;
+ }
+
+ if ($use_gpg_agent) {
+ $pid = $gpg->decrypt('handles' => $handles,
+ 'command_args' => [ qw( --use-agent ) ]);
+ } else {
+ if ($verify_mode) {
+ $pid = $gpg->wrap_call(
+ 'commands' => [ qw( --verify ) ],
+ 'command_args' => [ ( $in_file ) ],
+ 'handles' => $handles
+ );
+ } else {
+ $pid = $gpg->decrypt('handles' => $handles);
+ }
+ }
+
+ unless ($verify_mode) {
+ print $pw_fh $pw;
+ close $pw_fh;
+ }
+
+ my @errors = <$error_fh>;
+ close $error_fh;
+
+ my @status = <$status_fh>;
+ close $status_fh;
+
+ close $input_fh;
+ close $output_fh;
+
+ waitpid $pid, 0;
+
+ for (@status) {
+ if ($verify_mode) {
+ ### [GNUPG:] BADSIG 9EEEEE6BEE428EEE Some User <someone@domain.com>
+ $bad_signature = 1 if /BADSIG/;
+ } else {
+ ### [GNUPG:] BAD_PASSPHRASE C326F95CE133EA4E
+ $bad_passphrase = 1 if /BAD_?PASS/;
+ if (/NEED_PASSPHRASE\s\S+\s+\S+$encrypt_user\s/) {
+ ### [GNUPG:] NEED_PASSPHRASE CDE4D7DDFD66DCB9 95D85DDDDD42D39D 16 0
+ $file_encrypted_with_expected_key = 1;
+ } elsif ((length($encrypt_user) == 8)
+ and /USERID_HINT\s+.*$encrypt_user/) {
+ $file_encrypted_with_expected_key = 1;
+ }
+ }
+ }
+
+ if ($verbose) {
+ print " GnuPG errors:\n";
+ print for @errors;
+ print " GnuPG status:\n";
+ print for @status;
+ } else {
+ for (@status) {
+ if (/BAD_?PASS/) {
+ print unless $quiet;
+ } elsif (/BADSIG/) {
+ print unless $quiet;
+ }
+ }
+ }
+
+ if ($bad_passphrase) {
+ if (-s $out_file == 0) {
+ &delete_file($out_file);
+ &delete_file($in_file) if $del_flag == $DEL_SOURCE_FILE;
+ if ($file_encrypted_with_expected_key) {
+ die "[*] Bad passphrase, try gpgdir with -v";
+ } else {
+ print "[-] Skipping file encrypted with different ",
+ "GnuPG key: $in_file\n" unless $quiet;
+ }
+ } else {
+ die
+"[*] Bad passphrase, but created non-zero sized output file, should not\n",
+" happen. Try with --verbose";
+ }
+ } elsif (-s $out_file == 0) {
+ &delete_file($out_file);
+ &delete_file($in_file) if $del_flag == $DEL_SOURCE_FILE;
+ if ($use_gpg_agent) {
+ die "[*] Created zero-size file: $out_file\n",
+" Maybe gpg-agent does not yet have the password for that key?\n",
+" Try with --verbose";
+ } else {
+ die "[*] Created zero-size file: $out_file\n",
+ " Bad password? Try with --verbose";
+ }
+ }
+ if ($bad_signature) {
+ return 0;
+ }
+ return 1;
+}
+
+sub delete_file() {
+ my $file = shift;
+
+ return if $no_delete;
+ return unless -e $file;
+
+ if ($wipe_mode) {
+ my $cmd = $wipe_cmd;
+ if ($wipe_cmdline) {
+ $cmd .= " $wipe_cmdline ";
+ } else {
+ if ($wipe_interactive) {
+ $cmd .= ' -i ';
+ } else {
+ $cmd .= ' -f -s ';
+ }
+ }
+ $cmd .= $file;
+ if ($verbose) {
+ print " Executing: $cmd\n";
+ }
+
+ ### wipe the file
+ system $cmd;
+
+ } else {
+ unlink $file;
+ }
+
+ if (-e $file) {
+ my $msg = "[-] Could not delete file: $file\n";
+ if ($force_mode) {
+ print $msg unless $quiet;
+ } else {
+ die $msg unless $quiet;
+ }
+ }
+ return;
+}
+
+sub gpg_operation() {
+
+ ### sort by oldest to youngest mtime
+ FILE: for my $file (sort
+ {$files{$a}{'mtime'} <=> $files{$b}{'mtime'}} keys %files) {
+
+ ### see if we have an exclusion pattern that implies
+ ### we should skip this file
+ if (@exclude_patterns and &exclude_file($file)) {
+ print "[+] Skipping excluded file: $file\n"
+ if $verbose and not $quiet;
+ next FILE;
+ }
+
+ ### see if we have an inclusion pattern that implies
+ ### we should process this file
+ if (@include_patterns and not &include_file($file)) {
+ print "[+] Skipping non-included file: $file\n"
+ if $verbose and not $quiet;
+ next FILE;
+ }
+
+ ### dir is always a full path
+ my ($dir, $filename) = ($file =~ m|(.*)/(.*)|);
+
+ unless (chdir($dir)) {
+ print "[-] Could not chdir $dir, skipping.\n" unless $quiet;
+ next FILE;
+ }
+
+ my $mtime = $files{$file}{'mtime'};
+ my $atime = $files{$file}{'atime'};
+
+ if ($encrypt_mode) {
+
+ my $encrypt_filename = "$filename.gpg";
+
+ if ($obfuscate_mode) {
+
+ unless (defined $obfuscate_ctrs{$dir}) {
+
+ ### create a new gpgdir mapping file for obfuscated file
+ ### names, but preserve any previously encrypted file
+ ### name mappings
+ &handle_old_obfuscated_map_file();
+
+ ### make obfuscated file names start at 1 for each
+ ### directory
+ $obfuscate_ctrs{$dir} = 1;
+ }
+
+ $encrypt_filename = 'gpgdir_' . $obfuscate_ctrs{$dir} . '.gpg';
+ }
+
+ if ($ascii_armor_mode or $signing_mode) {
+ $encrypt_filename = "$filename.asc";
+ }
+
+ if (-e $encrypt_filename and not $overwrite_encrypted) {
+ my $str = 'Encrypted';
+ $str = 'Signed' if $signing_mode;
+ print "[-] $str file $dir/$encrypt_filename already ",
+ "exists, skipping.\n" unless $quiet;
+ next FILE;
+ }
+
+ if ($interactive_mode) {
+ my $str = 'Encrypt';
+ $str = 'Sign' if $signing_mode;
+ next FILE unless (&query_yes_no(
+ " $str: $file ([y]/n)? ", $ACCEPT_YES_DEFAULT));
+ }
+
+ my $str = 'Encrypting';
+ $str = 'Signing' if $signing_mode;
+ print "[+] $str: $file\n" unless $quiet;
+
+ unless ($trial_run) {
+
+ my $rv = &encrypt_or_sign_file($filename, $encrypt_filename,
+ $NO_DEL_SOURCE_FILE);
+
+ if (-e $encrypt_filename and -s $encrypt_filename != 0) {
+ ### set the atime and mtime to be the same as the
+ ### original file.
+ unless ($no_fs_times) {
+ if (defined $mtime and $mtime and
+ defined $atime and $atime) {
+ utime $atime, $mtime, $encrypt_filename;
+ }
+ }
+
+ unless ($signing_mode) {
+ ### only delete the original file if
+ ### the encrypted one exists
+ if ($wipe_mode and not $quiet) {
+ print " Securely deleting file: $file\n";
+ }
+ &delete_file($filename);
+
+ if ($obfuscate_mode) {
+
+ ### record the original file name mapping
+ &append_obfuscated_mapping($filename,
+ $encrypt_filename);
+
+ $obfuscate_ctrs{$dir}++;
+ }
+ }
+
+ $total_encrypted++;
+
+ } else {
+ my $str = 'encrypt';
+ $str = 'sign' if $signing_mode;
+ print "[-] Could not $str file: $file\n" unless $quiet;
+ next FILE;
+ }
+ }
+
+ } else {
+
+ ### allow filenames with spaces
+ my $decrypt_filename = '';
+ if ($filename =~ /^(.+)\.gpg$/) {
+ $decrypt_filename = $1;
+ } elsif ($filename =~ /^(.+)\.asc$/) {
+ $decrypt_filename = $1;
+ }
+
+ if ($obfuscate_mode) {
+
+ &import_obfuscated_file_map($dir)
+ unless defined $obfuscated_dirs{$dir};
+
+ if (defined $obfuscated_dirs{$dir}{$filename}) {
+ $decrypt_filename = $obfuscated_dirs{$dir}{$filename};
+ } else {
+ ###
+ print "[-] Obfuscated file map does not exist for ",
+ "$filename in\n $obfuscate_map_filename, ",
+ "skipping.\n" unless $quiet;
+ next FILE;
+ }
+
+ } else {
+ if (not $force_mode and ($file =~ /gpgdir_\d+_\d+\.gpg/
+ or $file =~ /gpgdir_\d+\.gpg/)) {
+ ### be careful not to decrypt obfuscated file unless we
+ ### are running in -O mode. This ensures that the
+ ### original file names will be acquired from the
+ ### /some/dir/.gpgdir_map_file
+ $have_obfuscated_file = 1;
+ next FILE;
+ }
+ }
+
+ ### length() allows files named "0"
+ next FILE unless length($decrypt_filename) > 0;
+
+ if ($verify_mode) {
+ unless (-e $decrypt_filename) {
+ print "[-] Original file $decrypt_filename ",
+ "does not exist, skipping.\n";
+ next FILE;
+ }
+ } else {
+ ### don't decrypt a file on top of a normal file of
+ ### the same name
+ if (-e $decrypt_filename and not $overwrite_decrypted) {
+ print "[-] Decrypted file $dir/$decrypt_filename ",
+ "already exists. Skipping.\n" unless $quiet;
+ next FILE;
+ }
+ }
+
+ if ($interactive_mode) {
+ my $str = 'Decrypt';
+ $str = 'Verify' if $verify_mode;
+ next FILE unless (&query_yes_no(
+ " $str: $file ([y]/n)? ", $ACCEPT_YES_DEFAULT));
+ }
+
+ unless ($trial_run) {
+ my $str = 'Decrypting';
+ $str = 'Verifying' if $verify_mode;
+ print "[+] $str: $dir/$filename\n" unless $quiet;
+ my $rv = &decrypt_or_verify_file($filename, $decrypt_filename,
+ $NO_DEL_SOURCE_FILE);
+
+ if ($verify_mode) {
+ $total_decrypted++ if $rv;
+ } else {
+ if (-e $decrypt_filename and -s $decrypt_filename != 0) {
+ ### set the atime and mtime to be the same as the
+ ### original file.
+ unless ($no_fs_times) {
+ if (defined $mtime and $mtime and
+ defined $atime and $atime) {
+ utime $atime, $mtime, $decrypt_filename;
+ }
+ }
+ if ($wipe_mode and not $quiet) {
+ print " Securely deleting file: $file\n";
+ }
+ ### only delete the original encrypted
+ ### file if the decrypted one exists
+ &delete_file($filename);
+
+ $total_decrypted++;
+
+ } else {
+ print "[-] Could not decrypt file: $file\n"
+ unless $quiet;
+ next FILE;
+ }
+ }
+ }
+ }
+ }
+ print "\n" unless $quiet;
+ chdir $initial_dir or die "[*] Could not chdir: $initial_dir\n";
+ return;
+}
+
+sub get_files() {
+ my $dir = shift;
+
+ print "[+] Building file list...\n" unless $quiet;
+ if ($norecurse) {
+ opendir D, $dir or die "[*] Could not open $dir: $!";
+ my @files = readdir D;
+ closedir D;
+
+ for my $file (@files) {
+ next if $file eq '.';
+ next if $file eq '..';
+ &check_file_criteria("$dir/$file");
+ }
+ } else {
+ ### get all files in all subdirectories
+ find(\&find_files, $dir);
+ }
+ return;
+}
+
+sub exclude_file() {
+ my $file = shift;
+ for my $pat (@exclude_patterns) {
+ if ($file =~ m|$pat|) {
+ print "[+] Skipping $file (matches exclude pattern: $pat)\n"
+ if $verbose and not $quiet;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+sub include_file() {
+ my $file = shift;
+ for my $pat (@include_patterns) {
+ if ($file =~ m|$pat|) {
+ print "[+] Including $file (matches include pattern: $pat)\n"
+ if $verbose and not $quiet;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+sub obfuscated_mapping_files() {
+
+ my $dirs_href = {};
+
+ if ($encrypt_mode) {
+ $dirs_href = \%obfuscate_ctrs;
+ } else {
+ $dirs_href = \%obfuscated_dirs;
+ }
+
+ DIR: for my $dir (keys %$dirs_href) {
+ unless (chdir($dir)) {
+ print "[-] Could not chdir $dir, skipping.\n" unless $quiet;
+ next DIR;
+ }
+
+ if ($encrypt_mode) {
+ next DIR unless -e $obfuscate_map_filename;
+ ### encrypt the map file now that we have encrypted
+ ### the directory
+ print "[+] Encrypting mapping file: ",
+ "$dir/$obfuscate_map_filename\n" unless $quiet;
+ unless ($trial_run) {
+ &encrypt_or_sign_file($obfuscate_map_filename,
+ "$obfuscate_map_filename.gpg", $NO_DEL_SOURCE_FILE);
+
+ unlink $obfuscate_map_filename;
+ }
+ } else {
+ next DIR unless -e "$obfuscate_map_filename.gpg";
+ ### delete the map file since we have decrypted
+ ### the directory
+ print "[+] Decrypting mapping file: ",
+ "$dir/$obfuscate_map_filename.gpg\n" unless $quiet;
+ unless ($trial_run) {
+ &decrypt_or_verify_file("$obfuscate_map_filename.gpg",
+ $obfuscate_map_filename, $NO_DEL_SOURCE_FILE);
+
+ unlink "$obfuscate_map_filename.gpg";
+ if ($total_mapped_files == $total_decrypted) {
+ ### we are confident that we decrypted all of them,
+ ### so delete the mapping file.
+ unlink $obfuscate_map_filename;
+ }
+ }
+ }
+ }
+ return;
+}
+
+sub handle_old_obfuscated_map_file() {
+ return unless -e "$obfuscate_map_filename.gpg";
+
+ &decrypt_or_verify_file("$obfuscate_map_filename.gpg",
+ $obfuscate_map_filename, $NO_DEL_SOURCE_FILE);
+
+ unlink "$obfuscate_map_filename.gpg";
+
+ my @existing_obfuscated_files = ();
+
+ open F, "< $obfuscate_map_filename" or die "[*] Could not open ",
+ "$obfuscate_map_filename: $!";
+ while (<F>) {
+ if (/^\s*.*\s+(gpgdir_\d+_\d+\.gpg)/) {
+ if (-e $1) {
+ push @existing_obfuscated_files, $_;
+ }
+ } elsif (/^\s*.*\s+(gpgdir_\d+\.gpg)/) {
+ if (-e $1) {
+ push @existing_obfuscated_files, $_;
+ }
+ }
+ }
+ close F;
+
+ if (@existing_obfuscated_files) {
+ ### there are some obfuscated files from a previous gpgdir
+ ### execution
+ open G, "> $obfuscate_map_filename" or die "[*] Could not open ",
+ "$obfuscate_map_filename: $!";
+ print G for @existing_obfuscated_files;
+ close G;
+ }
+ return;
+}
+
+sub append_obfuscated_mapping() {
+ my ($filename, $encrypt_filename) = @_;
+
+ open G, ">> $obfuscate_map_filename" or die "[*] Could not open ",
+ "$obfuscate_map_filename: $!";
+ print G "$filename $encrypt_filename\n";
+ close G;
+ return;
+}
+
+sub import_obfuscated_file_map() {
+ my $dir = shift;
+
+ $obfuscated_dirs{$dir} = {};
+
+ return unless -e "$obfuscate_map_filename.gpg";
+
+ &decrypt_or_verify_file("$obfuscate_map_filename.gpg",
+ $obfuscate_map_filename, $NO_DEL_SOURCE_FILE);
+
+ open G, "< $obfuscate_map_filename" or die "[*] Could not open ",
+ "$obfuscate_map_filename: $!";
+ while (<G>) {
+ if (/^\s*(.*)\s+(gpgdir_\d+_\d+\.gpg)/) {
+ $obfuscated_dirs{$dir}{$2} = $1;
+ $total_mapped_files++;
+ } elsif (/^\s*(.*)\s+(gpgdir_\d+\.gpg)/) {
+ $obfuscated_dirs{$dir}{$2} = $1;
+ $total_mapped_files++;
+ }
+ }
+ close G;
+
+ return;
+}
+
+sub get_homedir() {
+ my $uid = $<;
+ my $homedir = '';
+ if (-e '/etc/passwd') {
+ open P, '< /etc/passwd' or
+ die "[*] Could not open /etc/passwd. Exiting.\n";
+ my @lines = <P>;
+ close P;
+ for my $line (@lines) {
+ ### mbr:x:222:222:Michael Rash:/home/mbr:/bin/bash
+ chomp $line;
+ if ($line =~ /^(?:.*:){2}$uid:(?:.*:){2}(\S+):/) {
+ $homedir = $1;
+ last;
+ }
+ }
+ } else {
+ $homedir = $ENV{'HOME'} if defined $ENV{'HOME'};
+ }
+ die "[*] Could not determine home directory. Use the -u <homedir> option."
+ unless $homedir;
+ return $homedir;
+}
+
+sub get_key() {
+ if (-e "${homedir}/.gpgdirrc") {
+ open F, "< ${homedir}/.gpgdirrc" or die "[*] Could not open ",
+ "${homedir}/.gpgdirrc. Exiting.\n";
+ my @lines = <F>;
+ close F;
+ my $key = '';
+ for my $line (@lines) {
+ chomp $line;
+ if ($line =~ /^\s*default_key/) {
+ ### prefer to use the default GnuPG key
+ $use_default_key = 1;
+ return '';
+ } elsif ($line =~ /^\s*use_key\s+(.*)$/) {
+ ### GnuPG accepts strings to match the key, so we don't
+ ### have to strictly require a key ID... just a string
+ ### that matches the key
+ return $1;
+ }
+ }
+ die
+"[*] Please edit ${homedir}/.gpgdirrc to include your gpg key identifier\n",
+" (e.g. \"D4696445\"; see the output of \"gpg --list-keys\"), or use the\n",
+" default GnuPG key defined in ~/.gnupg/options";
+ }
+ print "[+] Creating gpgdir rc file: $homedir/.gpgdirrc\n";
+ open F, "> ${homedir}/.gpgdirrc" or die "[*] Could not open " .
+ "${homedir}/.gpgdirrc. Exiting.\n";
+
+ print F <<_CONFIGRC_;
+# Config file for gpgdir.
+#
+# Set the key to use to encrypt files with "use_key <key>", e.g.
+# "use_key D4696445". See "gpg --list-keys" for a list of keys on your
+# GnuPG key ring. Alternatively, if you want gpgdir to always use the
+# default key that is defined by the "default-key" variable in
+# ~/.gnupg/options, then uncomment the "default_key" line below.
+
+# Uncomment to use the GnuPG default key defined in ~/.gnupg/options:
+#default_key
+
+# If you want to use a specific GnuPG key, Uncomment the next line and
+# replace "KEYID" with your real key id:
+#use_key KEYID
+_CONFIGRC_
+
+ close F;
+ die
+"[*] Please edit $homedir/.gpgdirrc to include your gpg key identifier,\n",
+" or use the default GnuPG key defined in ~/.gnupg/options. Exiting.\n";
+}
+
+sub find_files() {
+ my $file = $File::Find::name;
+ &check_file_criteria($file);
+ return;
+}
+
+sub check_file_criteria() {
+ my $file = shift;
+ ### skip all links, zero size files, all hidden
+ ### files (includes .gnupg files), etc.
+ return if -d $file;
+ if (-e $file and not -l $file and -s $file != 0
+ and $file !~ m|/\.|) {
+ if ($encrypt_mode or $signing_mode) {
+ if ($file =~ m|\.gpg| or $file =~ m|\.asc|) {
+ print "[-] Skipping encrypted/signed file: $file\n" unless $quiet;
+ return;
+ }
+ } elsif ($verify_mode) {
+ unless ($file =~ m|\.asc|) {
+ ### only pick up the signature files
+ return;
+ }
+ } else {
+ unless ($file =~ m|\.gpg| or $file =~ m|\.asc|) {
+ print "[-] Skipping unencrypted file: $file\n" unless $quiet;
+ return;
+ }
+ }
+ my ($atime, $mtime) = (stat($file))[8,9];
+ $files{$file}{'atime'} = $atime;
+ $files{$file}{'mtime'} = $mtime;
+ } else {
+ print "[-] Skipping file: $file\n"
+ if $verbose and not $quiet;
+ }
+ return;
+}
+
+sub get_password() {
+
+ ### this is only useful if the gpg key literally has no password
+ ### (usually this is not the case, but gpgdir will support it if
+ ### so).
+ return if $cmdline_no_password;
+
+ ### if we are using gpg-agent for passwords, then return
+ return if $use_gpg_agent;
+
+ if ($pw_file) {
+ open PW, "< $pw_file" or die "[*] Could not open $pw_file: $!";
+ $pw = <PW>;
+ close PW;
+ chomp $pw;
+ } else {
+ print "[+] Executing: gpgdir @args_cp\n" unless $quiet;
+ if ($symmetric_mode) {
+ print " [Symmetric mode]\n" unless $quiet;
+ } else {
+ if ($use_default_key) {
+ print " Using default GnuPG key.\n" unless $quiet;
+ } else {
+ print " Using GnuPG key: $encrypt_user\n" unless $quiet;
+ }
+ }
+ if ($test_and_exit) {
+ print " *** test_mode() ***\n" unless $quiet;
+ }
+ if ($signing_mode) {
+ print " Enter signing password.\n" unless $quiet;
+ } elsif ($encrypt_mode) {
+ print ' Enter password (for initial ' .
+ "encrypt/decrypt test)\n" unless $quiet;
+ }
+ my $msg = 'Password: ';
+ ### get the password without echoing the chars back to the screen
+ ReadMode('noecho');
+ while (not $pw) {
+ print $msg;
+ $pw = ReadLine(0);
+ chomp $pw;
+ }
+ ReadMode('normal');
+ if ($quiet) {
+ print "\n";
+ } else {
+ print "\n\n";
+ }
+ }
+ return;
+}
+
+sub test_mode() {
+ chdir $dir or die "[*] Could not chdir($dir): $!";
+
+ my $test_file = "gpgdir_test.$$";
+ print "[+] test_mode(): Encrypt/Decrypt test of $test_file\n"
+ if (($test_and_exit or $verbose) and not $quiet);
+
+ if (-e $test_file) {
+ &delete_file($test_file) or
+ die "[*] test_mode(): Could not remove $test_file: $!";
+ }
+ if (-e "$test_file.gpg") {
+ &delete_file("$test_file.gpg") or
+ die "[*] test_mode(): Could not remove $test_file.gpg: $!";
+ }
+
+ open G, "> $test_file" or
+ die "[*] test_mode(): Could not create $test_file: $!";
+ print G "gpgdir test\n";
+ close G;
+
+ if (-e $test_file) {
+ print "[+] test_mode(): Created $test_file\n"
+ if (($test_and_exit or $verbose) and not $quiet);
+ } else {
+ die "[*] test_mode(): Could not create $test_file\n";
+ }
+
+ &encrypt_or_sign_file($test_file, "${test_file}.gpg", $DEL_SOURCE_FILE);
+
+ if (-e "$test_file.gpg" and (-s $test_file != 0)) {
+ print "[+] test_mode(): Successful encrypt of $test_file\n"
+ if (($test_and_exit or $verbose) and not $quiet);
+ &delete_file($test_file) if -e $test_file;
+ } else {
+ die "[*] test_mode(): not encrypt $test_file (try adding -v).\n";
+ }
+
+ &decrypt_or_verify_file("${test_file}.gpg", $test_file, $DEL_SOURCE_FILE);
+
+ if (-e $test_file and (-s $test_file != 0)) {
+ print "[+] test_mode(): Successful decrypt of $test_file\n"
+ if (($test_and_exit or $verbose) and not $quiet);
+ } else {
+ die "[*] test_mode(): Could not decrypt $test_file.gpg ",
+ "(try adding -v).\n";
+ }
+ open F, "< $test_file" or
+ die "[*] test_mode(): Could not open $test_file: $!";
+ my $line = <F>;
+ close F;
+
+ if (defined $line and $line =~ /\S/) {
+ chomp $line;
+ if ($line eq 'gpgdir test') {
+ print "[+] test_mode(): Decrypted content matches original.\n",
+ "[+] test_mode(): Success!\n\n"
+ if (($test_and_exit or $verbose) and not $quiet);
+ } else {
+ die "[*] test_mode(): Decrypted content does not match ",
+ "original (try adding -v).";
+ }
+ } else {
+ die "[*] test_mode(): Fail (try adding -v).\n";
+ }
+ &delete_file($test_file) if -e $test_file;
+ &delete_file("$test_file.gpg") if -e "$test_file.gpg";
+
+ chdir $initial_dir or die "[*] Could not chdir($initial_dir)";
+
+ return 0; ### exit status
+}
+
+sub query_yes_no() {
+ my ($msg, $style) = @_;
+ my $ans = '';
+ while ($ans ne 'y' and $ans ne 'n') {
+ print $msg;
+ $ans = lc(<STDIN>);
+ if ($style == $ACCEPT_YES_DEFAULT) {
+ return 1 if $ans eq "\n";
+ } elsif ($style == $ACCEPT_NO_DEFAULT) {
+ return 0 if $ans eq "\n";
+ }
+ chomp $ans;
+ }
+ return 1 if $ans eq 'y';
+ return 0;
+}
+
+sub unique_pid() {
+ return unless -e $pid_file;
+ open P, "< $pid_file" or die "[*] Could not open $pid_file: $!";
+ my $pid = <P>;
+ chomp $pid;
+ close P;
+ if (kill 0, $pid) {
+ die "[*] Another gpgdir process (pid: $pid) is already ",
+ "running against\n $dir";
+ }
+ return;
+}
+
+sub write_pid() {
+ open P, "> $pid_file" or die "[*] Could not open $pid_file: $!";
+ print P $$, "\n";
+ close P;
+ return;
+}
+
+sub import_perl_modules() {
+
+ my $mod_paths_ar = &get_mod_paths();
+
+ if ($#$mod_paths_ar > -1) { ### /usr/lib/gpgdir/ exists
+ push @$mod_paths_ar, @INC;
+ splice @INC, 0, $#$mod_paths_ar+1, @$mod_paths_ar;
+ }
+
+ if ($verbose) {
+ print "[+] import_perl_modules(): The \@INC array:\n";
+ print "$_\n" for @INC;
+ }
+
+ require GnuPG::Interface;
+ require Term::ReadKey;
+
+ Term::ReadKey->import(qw/ReadMode ReadLine/);
+
+ return;
+}
+
+sub get_mod_paths() {
+
+ my @paths = ();
+
+ unless (-d $lib_dir) {
+ my $dir_tmp = $lib_dir;
+ $dir_tmp =~ s|lib/|lib64/|;
+ if (-d $dir_tmp) {
+ $lib_dir = $dir_tmp;
+ } else {
+ return [];
+ }
+ }
+
+ opendir D, $lib_dir or die "[*] Could not open $lib_dir: $!";
+ my @dirs = readdir D;
+ closedir D;
+
+ push @paths, $lib_dir;
+
+ for my $dir (@dirs) {
+ ### get directories like "/usr/lib/gpgdir/x86_64-linux"
+ next unless -d "$lib_dir/$dir";
+ push @paths, "$lib_dir/$dir"
+ if $dir =~ m|linux| or $dir =~ m|thread|
+ or (-d "$lib_dir/$dir/auto");
+ }
+ return \@paths;
+}
+
+sub usage_and_exit() {
+ print <<_HELP_;
+
+gpgdir; Recursive direction encryption and decryption with GnuPG
+
+[+] Version: $version (file revision: $rev_num)
+ By Michael Rash (mbr\@cipherdyne.org)
+ URL: http://www.cipherdyne.org/gpgdir/
+
+Usage: gpgdir -e|-d <directory> [options]
+
+Options:
+ -e, --encrypt <directory> - Recursively encrypt all files in
+ <directory> and all subdirectories.
+ -d, --decrypt <directory> - Recursively decrypt all files in
+ <directory> and all subdirectories.
+ --sign <directory> - Recursively sign all files in <directory>
+ and all subdirectories.
+ --verify <directory> - Recursively verify all GnuPG signatures
+ in <directory>.
+ -K, --Key-id <id> - Specify GnuPG key ID, or key-matching
+ string. This overrides the use_key value
+ in ~/.gpgdirrc
+ -D, --Default-key - Use the key that GnuPG defines as the
+ default (i.e. the key that is specified
+ by the default-key option in
+ ~/.gnupg/options).
+ -a, --agent - Acquire password information from a
+ running instance of gpg-agent.
+ -A, --Agent-info <info> - Specify the value for the GPG_AGENT_INFO
+ environment variable as returned by
+ 'gpg-agent --daemon'.
+ -g, --gnupg-dir <dir> - Specify a path to a .gnupg directory for
+ gpg keys (the default is ~/.gnupg if this
+ option is not used).
+ -S, --Symmetric - Use symmetric encryption instead of the
+ default asymmetric encryption.
+ -p, --pw-file <file> - Read password in from <file>.
+ --skip-test - Skip encrypt -> decrypt test.
+ -t, --test-mode - Run encrypt -> decrypt test and exit.
+ -T, --Trial-run - Show what filesystem actions would take
+ place without actually doing them.
+ -P, --Plain-ascii - Ascii armor mode (creates non-binary
+ encrypted files).
+ --Interactive - Query the user before encrypting,
+ decrypting, or deleting any files.
+ --Exclude <pattern> - Skip all filenames that match <pattern>.
+ --Exclude-from <file> - Skip all filenames that match any pattern
+ contained within <file>.
+ --Include <pattern> - Include only those filenames that match
+ <pattern>.
+ --Include-from <file> - Include only those filenames that match a
+ pattern contained within <file>.
+ -O, --Obfuscate-filenames - Substitute all real filenames in a
+ directory with manufactured ones (the
+ original filenames are preserved in a
+ mapping file and restored when the
+ directory is decrypted).
+ --obfuscate-map_file <file> - Specify path to obfuscated mapping file
+ (in -O mode).
+ -F, --Force - Continue to run even if files cannot be
+ deleted (because of permissions problems
+ for example).
+ --overwrite-encrypted - Overwrite encrypted files even if a
+ previous <file>.gpg file already exists.
+ --overwrite-decrypted - Overwrite decrypted files even if the
+ previous unencrypted file already exists.
+ -q, --quiet - Print as little to the screen as possible
+ -W, --Wipe - Use the 'wipe' command to securely delete
+ unencrypted copies of files after they
+ have been encrypted.
+ --wipe-path <path> - Specify path to the wipe command.
+ --wipe-interactive - Force interactive mode with the wipe
+ command.
+ --wipe-cmdline <args> - Manually specify command line arguments
+ to the wipe command.
+ --no-recurse - Don't recursively encrypt/decrypt
+ subdirectories.
+ --no-delete - Don't delete original unencrypted files.
+ --no-preserve-times - Don't preserve original mtime and atime
+ values on encrypted/decrypted files.
+ --no-password - Assume the gpg key has no password at all
+ (this is not common).
+ -u, --user-homedir <dir> - Path to home directory.
+ -l, --locale <locale> - Manually define a locale setting.
+ --Lib-dir <path> - Path to the perl modules directory (not
+ usually necessary).
+ --no-locale - Don't set the locale to anything (the
+ default is the "C" locale).
+ --verbose - Run in verbose mode.
+ -V, --Version - print version.
+ -h, --help - print help.
+_HELP_
+ exit 0;
+}