Imported both gpgdir and gpgwrap projects.
authorthialme-guest <thialme-guest@b513b33f-fedd-0310-b452-c3deb5f4c849>
Sat, 24 Jan 2009 11:54:16 +0000 (11:54 +0000)
committerthialme-guest <thialme-guest@b513b33f-fedd-0310-b452-c3deb5f4c849>
Sat, 24 Jan 2009 11:54:16 +0000 (11:54 +0000)
git-svn-id: svn://svn.debian.org/pgp-tools/trunk@396 b513b33f-fedd-0310-b452-c3deb5f4c849

50 files changed:
debian/changelog
gpgdir/CREDITS [new file with mode: 0644]
gpgdir/ChangeLog [new file with mode: 0644]
gpgdir/ChangeLog.svn [new file with mode: 0644]
gpgdir/INSTALL [new file with mode: 0644]
gpgdir/LICENSE [new file with mode: 0644]
gpgdir/README [new file with mode: 0644]
gpgdir/VERSION [new file with mode: 0644]
gpgdir/bump_version.pl [new file with mode: 0755]
gpgdir/gpgdir [new file with mode: 0755]
gpgdir/gpgdir.1 [new file with mode: 0644]
gpgdir/install.pl [new file with mode: 0755]
gpgdir/packaging/cd_rpmbuilder [new file with mode: 0755]
gpgdir/packaging/gpgdir-nodeps.spec [new file with mode: 0644]
gpgdir/packaging/gpgdir.SlackBuild [new file with mode: 0755]
gpgdir/packaging/gpgdir.spec [new file with mode: 0644]
gpgdir/test/conf/broken.pw [new file with mode: 0644]
gpgdir/test/conf/test-gpg/pubring.gpg [new file with mode: 0644]
gpgdir/test/conf/test-gpg/secring.gpg [new file with mode: 0644]
gpgdir/test/conf/test-gpg/trustdb.gpg [new file with mode: 0644]
gpgdir/test/conf/test.pw [new file with mode: 0644]
gpgdir/test/data-dir/.hidden [new file with mode: 0644]
gpgdir/test/data-dir/dir2/.hidden [new file with mode: 0644]
gpgdir/test/data-dir/dir2/dir4/.hidden [new file with mode: 0644]
gpgdir/test/data-dir/dir2/dir4/somefile [new file with mode: 0644]
gpgdir/test/data-dir/dir2/dir4/somefile.txt [new file with mode: 0644]
gpgdir/test/data-dir/dir2/new-ascii [new file with mode: 0644]
gpgdir/test/data-dir/dir2/new-ascii.txt [new file with mode: 0644]
gpgdir/test/data-dir/dir3/.hidden [new file with mode: 0644]
gpgdir/test/data-dir/dir3/dir4/.hidden [new file with mode: 0644]
gpgdir/test/data-dir/dir3/dir4/gpgdir-copy [new file with mode: 0755]
gpgdir/test/data-dir/dir3/dir4/gpgdir-copy.pl [new file with mode: 0755]
gpgdir/test/data-dir/dir3/dir4/random-binary-data [new file with mode: 0644]
gpgdir/test/data-dir/dir3/dir4/random-binary-data.bin [new file with mode: 0644]
gpgdir/test/data-dir/dir3/file1 [new file with mode: 0644]
gpgdir/test/data-dir/dir3/file2 [new file with mode: 0644]
gpgdir/test/data-dir/multi-line-ascii [new file with mode: 0644]
gpgdir/test/data-dir/multi-line-ascii.txt [new file with mode: 0644]
gpgdir/test/data-dir/random-binary-data [new file with mode: 0644]
gpgdir/test/data-dir/random-binary-data.bin [new file with mode: 0644]
gpgdir/test/gpgdir_test.pl [new file with mode: 0755]
gpgdir/test/output/README [new file with mode: 0644]
gpgwrap/LICENSE [new file with mode: 0644]
gpgwrap/Makefile [new file with mode: 0644]
gpgwrap/NEWS [new file with mode: 0644]
gpgwrap/README [new file with mode: 0644]
gpgwrap/doc/gpgwrap.1 [new file with mode: 0644]
gpgwrap/src/Makefile [new file with mode: 0644]
gpgwrap/src/gpgwrap.c [new file with mode: 0644]
gpgwrap/src/version.h [new file with mode: 0644]

index aeeadb1e1e354d3602e39a2fb80a1bfac5655efe..dd1c7e328bc66ea01f25244ea2dd3d8b73e7082c 100644 (file)
@@ -1,5 +1,9 @@
 signing-party (1.1-1) UNRELEASED; urgency=low
 
+  [ Franck Joncourt ]
+  * Imported gpgdir. (Closes: #498167)
+  * Imported gpgwrap. (Closes: #454074)
+
   [ Thijs Kinkhorst ]
   * Checked for policy 3.8.0, no changes.
   * caff: Fix pod syntax problems (Closes: #485653).
diff --git a/gpgdir/CREDITS b/gpgdir/CREDITS
new file mode 100644 (file)
index 0000000..cacc582
--- /dev/null
@@ -0,0 +1,49 @@
+Per Ronny Westin
+    - Found PLAINTEXT vs. DECRYPTION_OKAY return code bug for GnuPG 1.2.6.
+    - Reported directory decryption bug in gpgdir-1.6.  The result was the
+      addition of the gpgdir test suite.
+
+Kai Raven
+    - Bugfix in man page for file compression/decompression wording.
+
+Craig Needs
+    - Suggested --gnupg-dir option, testing help.
+
+Chris P
+    - Found bug where gpgdir would not decrypt files that contained spaces.
+
+Ian Scott
+    - Reported "protocol error: expected SHM_GET_XXX got GOOD_PASSPHRASE"
+      bug in GnuPG module.
+
+Mate Wierdl
+    - Contributed patch (originally for the psad project) for building the
+      RPM on x86_64 platforms.
+
+pyllyukko
+    - Added the gpgdir.SlackBuild script (adapted from the psad project).
+
+Anthony Chivetta
+    - Submitted patch to fix a bug where files named "0.gpg" could not be
+      decrypted.
+    - Submitted patch to implement the --overwrite-encrypted command line
+      argument to allow previously encrypted files to be overwritten. This
+      is useful for updating an encrypted directory with new versions of
+      the previously encrypted files.
+
+Fermin Manzanedo
+    - Suggested the --Symmetric option so that files can be encrypted/
+      decrypted via a symmetric cipher (GnuPG supports CAST5 by default).
+
+Franck Joncourt
+    - Performed analysis of locale settings for fwknop installer and suggested
+      using the LC_ALL environmental variable instead of the LANG variable
+      (which is superseded by LC_* vars).
+    - Suggested moving perl modules to the deps/ directory.  This is to
+      support the integration of the Cipherdyne projects with Debian.
+    - Added Short description to the gpgdir man page.  This fixes the
+      following lintian warning:
+
+        http://lintian.debian.org/tags/manpage-has-bad-whatis-entry.html
+    - Suggested the appropriate bugfix to interface non-interactively with the
+      wipe program (-f instead of -I in later versions).
diff --git a/gpgdir/ChangeLog b/gpgdir/ChangeLog
new file mode 100644 (file)
index 0000000..2b29dfc
--- /dev/null
@@ -0,0 +1,257 @@
+gpgdir-1.9.3 (11/05/2008):
+    - Bugfix for using -f instead of -I for non-interactive file erasure
+      (Franck Joncourt).
+    - Simplified test suite code by creating a set of default arguments for
+      the gpgdir command line as each test is executed.
+
+gpgdir-1.9.2 (08/31/2008):
+    - Added new modes '--sign <dir>' and '--verify <dir>' to allow all files
+      in the specified directory to be signed or verified instead of encrypted
+      or decrypted.  All GnuPG signatures are created as "<file>.asc", and the
+      original file is not removed in --sign mode.  In --verify mode, if any
+      file does not match the expected .asc signature, then a warning like the
+      following will be generated:
+
+        [+] Verifying:  /home/mbr/src/gpgdir/test/data-dir/multi-line-ascii.asc
+        [GNUPG:] BADSIG 9EDEEEEBA742EEEF Some User <someuser@domain.org>
+
+    - Bugfix to not die() when files that are encrypted with a different GnuPG
+      key are encountered in a directory that is being decrypted. A warning
+      message (see below) is now generated and the file is skipped:
+
+      [+] Decrypting:  /home/mbr/tmp/gpgdir/a.gpg
+      [GNUPG:] BAD_PASSPHRASE CF16F0FCFFF3FF4F
+      [-] Skipping file encrypted with different GnuPG key: a.gpg
+
+    - Updated to use the status output from GnuPG::Interface to detect a bad
+      passphrase and whether a file is encrypted with the expected GnuPG key.
+    - Moved the GnuPG::Interface, Class::MethodMaker, and Term::ReadKey
+      modules to the deps/ directory, and updated the installer and RPM spec
+      file to account for the path change.  This change was suggested by
+      Franck Joncourt for the other cipherdyne.org projects.
+    - Updated the test suite to generate files in the output/ directory
+      according to test number and append the result of each test within each
+      file.  This makes it easy to tell which tests have failed with a simple
+      'grep fail output/*test'.
+    - Added the gpgdir-nodeps.spec file to allow an RPM to be built that does
+      not contain any perl modules dependencies.
+    - Updated gpgdir to import perl modules via 'require' statements instead
+      of 'use' statements so that the path to the modules directory can be
+      changed via the --Lib-dir command line argument.  Also updated to use
+      the 'auto' heuristic (first implemented in the fwknop project) to detect
+      perl module directories that should be used in the --Lib-dir directory
+      to import perl modules from.
+
+gpgdir-1.9.1 (06/07/2008):
+    - Updated to Class::MethodMaker 2.11 from CPAN.  This helps with systems
+      running perl-5.10.0 and greater (such as Fedora 9).
+    - Updated to always set the LC_ALL environmental variable to the "C"
+      locale.  This can be set to other locales with a new argument --locale,
+      or the default locale can be used by using --no-locale argument.
+
+gpgdir-1.9 (05/31/2008):
+    - Changed --Obfuscate-filenames format to not include the gpgdir PID.
+      This allows directories to be encrypted/decrypted under -O multiple
+      times without creating new filenames (which would pollute encrypted
+      directories under rsync to other systems).  The new -O encrypted
+      filename format is just "gpgdir_<num>.gpg".
+    - Added PID locking against directories so that multiple gpgdir processes
+      cannot operate against the same top-level directory simultaneously.
+      This is useful for users that typically operate with multiple shells
+      and might launch gpgdir from any of them.
+
+gpgdir-1.8 (04/04/2008):
+    - Updated the test suite to validate the gpgdir --Obfuscate-filenames
+      mode to ensure that files are encrypted as "gpgdir_<pid>_<num>.gpg".
+    - Minor bug fix to remove the .gpgdir_map_file in --Obfuscate-filenames
+      mode after a successful decryption cycle.
+    - Updated to version 0.36 of CPAN GnuPG::Interface module.
+
+gpgdir-1.7 (02/18/2008):
+    - Bugfix to ensure that encrypted directories can actually be decrypted.
+      This bug was reported by Per Ronny Westin.
+    - Updated to use the ".asc" extension for encrypted files in --Plain-ascii
+      mode.
+    - Added gpgdir test suite.  All future gpgdir releases (and including this
+      1.7 release) require that all gpgdir tests pass on the systems where
+      gpgdir is developed.
+
+gpgdir-1.6 (02/17/2008):
+    - Bugfix to not include previously encrypted files (i.e. those with a .gpg
+      extension) in the encryption/decryption file list.  This bug was
+      introduced in gpgdir-1.5 when a change was made to ignore ascii-armored
+      files.
+    - Added added LC_ALL=C locale setting for the install.pl script (this
+      should help to ensure gpgdir is properly installed on most systems). Two
+      new command line arguments --LC_ALL and --no-LC_ALL also allow the
+      locale setting to be changed or not used at all.
+    - Added --Exclude-mod-regex option to the install.pl script so that it is
+      possible to force the exclusion of perl modules that gpgdir would
+      normally install. This is useful for ensuring that gpgdir references
+      perl modules that are already installed in the system perl library tree
+      instead of using those that are installed in /usr/lib/gpgdir.
+    - Updated to display command line usage warnings without automatically
+      displaying the entire usage() page (which is quite long).
+
+gpgdir-1.5 (08/31/2007):
+    - Added the --Symmetric option so that files can be encrypted/decrypted
+      via a symmetric encryption algorithm (GnuPG commonly uses CAST5 for
+      this).
+    - Added the --Plain-ascii option so that GnuPG is invoked with the -a
+      option so that encrypted files are ascii armored instead of encrypted in
+      binary form.
+    - Bugfix to ensure not to delete zero-size files if a bad password is
+      given (gpgdir now just throws a warning and exits in this case).
+    - Minor code enhancements to provide a consistent hash_init() invocation
+      with the same options hash.
+    - Updated to exclude .asc files from the encryption/decryption process.
+
+gpgdir-1.4 (07/20/2007):
+    - (Anthony Chivetta) Submitted patch to implement the
+      --overwrite-encrypted command line argument to allow previously
+      encrypted files to be overwritten. This is useful for updating an
+      encrypted directory with new versions of the previously encrypted files.
+      Also added the --overwrite-decrypted command line argument to perform
+      the same function for previously decrypted files.
+    - (Anthony Chivetta) Submitted patch to fix a bug where a filename of
+      "0.gpg" could not be decrypted because "0" does not evaluate to a true
+      value.
+
+gpgdir-1.3 (06/09/2007):
+    - Added --Obfuscate mode so that the files within a directory can be
+      altered into unrecognizable names (which are stored within the file
+      .gpgdir_map_file within each sub-directory, and this file is itself
+      encrypted).  The obfuscated file names are reversed when a directory
+      is decrypted.
+    - Added the --Agent-info command line argument so that the value of the
+      GPG_AGENT_INFO environment variable can be specified on the gpgdir
+      command line.
+
+gpgdir-1.2 (05/28/2007):
+    - Added support for installing gpgdir on Windows under Cygwin (via the
+      install.pl script). Installing gpgdir on FreeBSD systems also works.
+    - Added support for installing gpgdir within a user home directory without
+      the need for root access (this requires installing gpgdir with the
+      install.pl script).
+    - Added --agent to have gpgdir acquire gpg key password from a running
+      gpg-agent instance.
+    - Added --no-password so gpgdir can use a gpg key with no associated
+      password (this is not common).  The user is not prompted for a password
+      in this case.
+
+gpgdir-1.1 (05/21/2007):
+    - Added the ability to securely delete the original versions of files with
+      the 'wipe' program (after they have been successfully encrypted).  Also
+      added --wipe-path to specify a path to the wipe binary (the default is
+      /usr/bin/wipe), --wipe-interactive to force the wipe program to prompt
+      the user before a file is deleted, and --wipe-cmdline to allow the user
+      to build a set of command line arguments that are passed to the wipe
+      program.
+    - Added --Force to have gpgdir skip over the error condition where a file
+      cannot be deleted (because of a permissions issue for example).
+    - Added --Trial-run to allow the user to see what actions gpgdir would
+      take to encrypt or decrypt files, but no files are actually modified.
+    - Added --Interactive to have gpgdir prompt the user before every file is
+      encrypted, decrypted.
+    - Added the gpgdir.SlackBuild script (contributed by pyllyukko originally
+      for the psad project) for building gpgdir on Slackware systems.
+
+gpgdir-1.0.3 (09/17/2006):
+    - Minor bugfix to correct 1.0.1 version number (which should have been set
+      to 1.0.2) in the gpgdir RPM spec file.
+
+gpgdir-1.0.2 (09/17/2006):
+    - Minor bugfix to correct 1.0 version number (which should have been set
+      to 1.0.1).  The result is the 1.0.2 release.
+
+gpgdir-1.0.1 (09/16/2006):
+    - Added --quiet option to have gpgdir print as little as possible to the
+      screen when encrypting or decrypting a directory.
+    - Added x86_64 RPM (original patch from Mate Wierdl adapted for gpgdir).
+
+gpgdir-1.0 (09/13/2006):
+    - Added --Key-id command line argument so that use_key can be overridden
+      from the command line
+    - Made the argument to use_key not have to strictly be a keyID since GnuPG
+      allows a unique string match on keys in the key ring
+    - Added --Default-key to allow the user to have gpgdir use the default
+      key that is defined by GnuPG within the ~/.gnupg/options file.
+    - Updated the .gpgdirrc file to include the line "default_key" to allow
+      the user to have gpgdir prefer to use the GnuPG default key.
+    - Added the ChangeLog.svn file to show exactly which files have been
+      changed from release to release, and what the corresponding Subversion
+      log messages are.
+    - Minor documentation updates.
+
+gpgdir-0.9.9 (09/07/2006):
+    - Added RPM .spec file to build gpgdir as an RPM.
+    - Added the --Skip-mod-install command line argument to install.pl to
+      allow all perl module installs to be skipped.
+    - Added the --force-mod-regex command line argument to install.pl to allow
+      a regex match on perl module names to force matching modules to be
+      installed.
+    - Updated to TermReadKey-2.30 from 2.21.
+
+gpgdir-0.9.8 (07/03/2006):
+    - Updated to use GnuPG::Interface instead of GnuPG module.  This should
+      fix the incompatibility issues seen between the GnuPG module and some
+      GnuPG installations.
+    - Added perl module installation code from fwknop (see
+      http://www.cipherdyne.org/fwknop/).  This allows gpgdir to
+      preferentially use any perl modules that are already be installed on the
+      system.
+
+gpgdir-0.9.4 (10/12/2005):
+    - Updated test mode to encrypt and decrypt a testing file within the
+      directory to be encrypted or decrypted.  This file is located at
+      <dir>/gpgdir_test, and is removed after the test is completed.
+    - Bugfix for "protocol error: expected SHM_GET_XXX got GOOD_PASSPHRASE"
+      error in GnuPG module.
+
+gpgdir-0.9.3 (02/20/2005):
+    - Added --Include and --Include-from options to allow inclusion
+      regular expressions to be specified.
+    - Bugfix for not decrypting filesnames that contain spaces.
+
+gpgdir-0.9.2 (01/05/2005):
+    - Added preservation of file mtime and atime values (may be disabled
+      with the --no-preserve-times option).
+    - Added testing encryption and decryption of dummy file (may be
+      disabled with --skip-test) by default for both encrypt and decrypt
+      modes.
+    - Added --test-mode to run encrypt -> decrypt test and exit.
+    - Removed unnecessary compression options.
+    - Updated get_homedir() to reference HOME environmental variable if
+      the /etc/passwd file does not exist (OS X being a good example).
+    - Added --verbose mode.
+    - Updated output to generate errors on a per-file basis instead of
+      dumping them at the end of an encrypt/decrypt operation.
+
+gpgdir-0.9.1 (11/11/2004):
+    - Updated GnuPG.pm perl module to handle return code of PLAINTEXT
+      which seems to be returned by GunPG now (as of version 1.2.6)
+      instead of DECRYPTION_OKAY upon a successful decryption.
+
+gpgdir-0.9 (09/12/2004):
+    - Added --gnupg-dir option to allow a user to specify a different
+      user's .gnupg directory for encryption keys.
+    - Switched to "[+]" (and related) message prefixes.
+
+gpgdir-0.8 (05/29/2004):
+    - Added --Exclude and --Exclude-from options to allow files to be
+      excluded based on regex matches.
+    - Reworked error messages so they contain the filename associated
+      with each error.
+
+gpgdir-0.4 (04/23/2004):
+    - Added --pw-file option so that a decryption password can be
+      read out of a file.
+    - Better directory validation (filesystem -e and -d checks).
+    - Added INSTALL file.
+    - Updated man page and README file.
+
+gpgdir-0.3 (09/27/2003):
+    - Bundled perl modules GnuPG and TermReadKey with gpgdir.
+    - Modified install.pl and gpgdir to install and use GnuPG and
+      TermReadKey modules from the /usr/lib/gpgdir directory.
+    - Added check_commands() subroutine from psad.
diff --git a/gpgdir/ChangeLog.svn b/gpgdir/ChangeLog.svn
new file mode 100644 (file)
index 0000000..4c2abc0
--- /dev/null
@@ -0,0 +1,65 @@
+------------------------------------------------------------------------
+r329 | mbr | 2008-11-05 00:12:52 -0500 (Wed, 05 Nov 2008) | 1 line
+Changed paths:
+   A /gpgdir/branches/gpgdir-1.9.3 (from /gpgdir/trunk:328)
+
+created gpgdir-1.9.3 branch
+------------------------------------------------------------------------
+r328 | mbr | 2008-11-05 00:04:20 -0500 (Wed, 05 Nov 2008) | 1 line
+Changed paths:
+   M /gpgdir/trunk/ChangeLog
+   M /gpgdir/trunk/VERSION
+   M /gpgdir/trunk/gpgdir
+   M /gpgdir/trunk/packaging/gpgdir-nodeps.spec
+   M /gpgdir/trunk/packaging/gpgdir.spec
+   M /gpgdir/trunk/test/gpgdir_test.pl
+
+1.9.3 release
+------------------------------------------------------------------------
+r326 | mbr | 2008-10-13 21:55:56 -0400 (Mon, 13 Oct 2008) | 1 line
+Changed paths:
+   M /gpgdir/trunk/VERSION
+   M /gpgdir/trunk/gpgdir
+   M /gpgdir/trunk/test/gpgdir_test.pl
+
+bumped version to 1.9.3-pre1
+------------------------------------------------------------------------
+r325 | mbr | 2008-10-03 00:04:46 -0400 (Fri, 03 Oct 2008) | 3 lines
+Changed paths:
+   M /gpgdir/trunk/CREDITS
+   M /gpgdir/trunk/ChangeLog
+   M /gpgdir/trunk/gpgdir
+
+- Bugfix for using -f instead of -I for non-interactive file erasure
+(Franck Joncourt).
+
+------------------------------------------------------------------------
+r324 | mbr | 2008-10-02 23:57:33 -0400 (Thu, 02 Oct 2008) | 1 line
+Changed paths:
+   M /gpgdir/trunk/ChangeLog
+
+minor date update
+------------------------------------------------------------------------
+r323 | mbr | 2008-10-02 23:57:11 -0400 (Thu, 02 Oct 2008) | 3 lines
+Changed paths:
+   M /gpgdir/trunk/ChangeLog
+   M /gpgdir/trunk/gpgdir
+   M /gpgdir/trunk/test/gpgdir_test.pl
+
+Simplified test suite code by creating a set of default arguments for
+the gpgdir command line as each test is executed
+
+------------------------------------------------------------------------
+r322 | mbr | 2008-09-10 23:40:35 -0400 (Wed, 10 Sep 2008) | 1 line
+Changed paths:
+   M /gpgdir/trunk/CREDITS
+   M /gpgdir/trunk/gpgdir.1
+
+applied gpgdir man page fix from Franck
+------------------------------------------------------------------------
+r321 | mbr | 2008-09-01 00:24:35 -0400 (Mon, 01 Sep 2008) | 1 line
+Changed paths:
+   M /gpgdir/trunk/gpgdir
+
+merged in 'signed' vs. 'verified' text fix
+------------------------------------------------------------------------
diff --git a/gpgdir/INSTALL b/gpgdir/INSTALL
new file mode 100644 (file)
index 0000000..546ea32
--- /dev/null
@@ -0,0 +1,15 @@
+Installation notes:
+
+QUICK AND EASY INSTALLATION INSTRUCTIONS:
+
+Just run the gpgdir installation script "install.pl" from the gpgdir
+sources directory:
+
+# ./install.pl
+
+If you are not installing as root, the install.pl script will install gpgdir
+along with required perl modules within your home directory.  If you are
+installing as root, required perl modules will be installed in /usr/lib/gpgdir
+so as to not pollute the system perl library tree.  The required perl modules
+are GnuPG::Interface, Class::MethodMaker, and Term::ReadKey, and these modules
+are placed in the deps/ directory.
diff --git a/gpgdir/LICENSE b/gpgdir/LICENSE
new file mode 100644 (file)
index 0000000..2b7b643
--- /dev/null
@@ -0,0 +1,340 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    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
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/gpgdir/README b/gpgdir/README
new file mode 100644 (file)
index 0000000..03b722a
--- /dev/null
@@ -0,0 +1,25 @@
+File:     gpgdir
+Author:   Michael Rash <mbr@cipherdyne.org>
+Download: http://www.cipherdyne.org/gpgdir
+License:  GNU General Public License
+Version:  0.9.8
+
+gpgdir is a perl script that uses the CPAN GnuPG::Interface perl module to
+encrypt and decrypt directories using a gpg key specified in ~/.gpgdirrc.
+Gpgdir recursively descends through a directory in order to make sure it
+encrypts or decrypts every file in a directory and all of its subdirectories.
+By default the mtime and atime values of all files will be preserved upon
+encryption and decryption (this can be disabled with the --no-preserve-times
+option).  Note that in --encrypt mode, gpgdir will delete the original files
+that it successfully encrypts (unless the --no-delete option is given).
+However, upon startup gpgdir first asks for the decryption password to be
+sure that a dummy file can successfully be encrypted and decrypted.  The
+initial test can be disabled with the --skip-test option so that a directory
+can easily be encrypted without having to also specify a password (this is
+consistent with gpg behavior).  Also, note that gpgdir is careful not encrypt
+hidden files and directories.  After all, you probably don't want your
+~/.gnupg directory or ~/.bashrc file to be encrypted.
+
+Installation:
+    Just run the install.pl script (as root) that comes with the gpgdir
+    sources.
diff --git a/gpgdir/VERSION b/gpgdir/VERSION
new file mode 100644 (file)
index 0000000..77fee73
--- /dev/null
@@ -0,0 +1 @@
+1.9.3
diff --git a/gpgdir/bump_version.pl b/gpgdir/bump_version.pl
new file mode 100755 (executable)
index 0000000..cf2c93f
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/perl -w
+#
+#############################################################################
+#
+# File: bump_version.pl
+#
+# Purpose: Minor script to enforce consistency in gpgdir version tags.
+#
+#############################################################################
+#
+# $Id: bump_version.pl 1055 2008-05-21 02:57:17Z mbr $
+#
+
+use strict;
+
+my @files = qw(
+    gpgdir
+    test/gpgdir_test.pl
+);
+
+my $new_version = $ARGV[0] or die "[*] $0 <new version>";
+
+open F, '< VERSION' or die "[*] Could not open VERSION file: $!";
+my $old_version = <F>;
+close F;
+chomp $old_version;
+
+print "[+] Updating software versions...\n";
+for my $file (@files) {
+    if ($file =~ /\.c/) {
+        ###*  Version: 1.8.4-pre2
+        my $search_re   = qr/^\*\s+Version:\s+$old_version/;
+        my $replace_str = '*  Version: ' . $new_version;
+        system qq{perl -p -i -e 's|$search_re|} .
+            qq{$replace_str|' $file};
+    } else {
+        ### Version: 1.8.4
+        my $search_re   = qr/#\s+Version:\s+$old_version/;
+        my $replace_str = '# Version: ' . $new_version;
+        system qq{perl -p -i -e 's|$search_re|$replace_str|' $file};
+        ### my $version = '1.8.4';
+        $search_re   = qr/^my\s+\x24version\s+=\s+'$old_version';/;
+        $replace_str = q|my \x24version = '| . $new_version . q|';|;
+        system qq{perl -p -i -e "s|$search_re|$replace_str|" $file};
+    }
+}
+system qq{perl -p -i -e 's|$old_version|$new_version|' VERSION};
+
+exit 0;
diff --git a/gpgdir/gpgdir b/gpgdir/gpgdir
new file mode 100755 (executable)
index 0000000..0075215
--- /dev/null
@@ -0,0 +1,1508 @@
+#!/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;
+}
diff --git a/gpgdir/gpgdir.1 b/gpgdir/gpgdir.1
new file mode 100644 (file)
index 0000000..eebd7bd
--- /dev/null
@@ -0,0 +1,315 @@
+.\" Process this file with
+.\" groff -man -Tascii foo.1
+.\"
+.TH GPGDIR 1 "May, 2007" Linux
+.SH NAME
+.B gpgdir
+\- recursive directory encryption with GnuPG
+.SH SYNOPSIS
+.B gpgdir \-e|\-d <directory> [options]
+.SH DESCRIPTION
+.B gpgdir
+is a perl script that uses the CPAN GnuPG::Interface perl module to recursively
+encrypt and decrypt directories using gpg.
+.B gpgdir
+recursively descends through a directory in order to make sure it encrypts or
+decrypts every file in a directory and all of its subdirectories.  By default
+the mtime and atime values of all files will be preserved upon encryption and
+decryption (this can be disabled with the
+.B \-\-no-preserve-times
+option).  Note that in
+.B \-\-encrypt
+mode, gpgdir will delete the original files that
+it successfully encrypts (unless the
+.B \-\-no-delete
+option is given).  However,
+upon startup gpgdir first asks for a the decryption password to be sure that a
+dummy file can successfully be encrypted and decrypted.  The initial test can
+be disabled with the
+.B \-\-skip-test
+option so that a directory can easily be encrypted without having to also
+specify a password (this is consistent with
+.B gpg
+behavior).  Also, note that gpgdir is careful not encrypt hidden files and
+directories.  After all, you probably don't want your ~/.gnupg directory or
+~/.bashrc file to be encrypted.  The key
+.B gpgdir
+uses to encrypt/decrypt a directory is specified in ~/.gpgdirrc.
+
+Finally,
+.B gpgdir
+can use the
+.B wipe
+program with the
+.B \-\-Wipe
+command line option to securely delete the original unencrypted files after they
+have been successfully encrypted.  This elevates the security stance of gpgdir
+since it is more difficult to recover the unencrypted data associated with
+files from the filesystem after they are encrypted (unlink() does not erase data
+blocks even though a file is removed).
+
+.SH OPTIONS
+.TP
+.BR \-e ", " \-\^\-encrypt\ \<directory>
+Recursively encrypt all files in the directory specified on the command line.
+All original files will be deleted (a password check is performed first to make
+sure that the correct password to unlock the private GnuPG key is known to the
+user).
+.TP
+.BR \-d ", " \-\^\-decrypt\ \<directory>
+Recursively decrypt all files in the directory specified on the command line.
+The encrypted .gpg version of each file will be deleted.
+.TP
+.BR \-\^\-sign\ \<directory>
+Recursively sign all files in the directory specified on the command line.  For
+each file, a detached .asc signature will be created.
+.TP
+.BR \-\^\-verify\ \<directory>
+Recursively verify all .asc signatures for files in the directory specified on the
+command line.
+.TP
+.BR \-g ", " \-\^\-gnupg-dir\ \<directory>
+Specify which .gnupg directory will be used to find GnuPG keys.  The default
+is ~/.gnupg if this option is not used.  This option allows gpgdir to be
+run as one user but use the keys of another user (assuming permissions are
+setup correctly, etc.).
+.TP
+.BR \-p ", " \-\^\-pw-file\ \<pw-file>
+Read decryption password from
+.B pw-file
+instead of typing it on the command line.
+.TP
+.BR \-t ", " \-\^\-test-mode
+Run an encryption and decryption test against a dummy file and exit.  This
+test is always run by default in both
+.B \-\-encrypt
+and
+.B \-\-decrypt
+mode.
+.TP
+.BR \-S ", " \-\^\-Symmetric
+Instruct
+.B gpgdir
+to encrypt to decrypt files using a symmetric cipher supported by GnuPG
+(CAST5 is commonly used).  This results in a significant speed up for the
+encryption/decryption process.
+.TP
+.BR \-T ", " \-\^\-Trial-run
+Show what encrypt/decrypt actions would take place without actually doing
+them.  The filesystem is not changed in any way in this mode.
+.TP
+.BR \-I ", " \-\^\-Interactive
+Prompt the user before actually encrypting or decrypting each file.  This
+is useful to have fine-grained control over
+.B gpgdir
+operations as it recurses through a directory structure.
+.TP
+.BR \-F ", " \-\^\-Force
+Tell
+.B gpgdir
+to ignore non-fatal error conditions, such as the inability to encrypt or
+decrypt individual files because of permissions errors.
+.TP
+.BR \-\^\-Exclude\ \<pattern>
+Instruct gpgdir to skip all files that match
+.B pattern
+as a regex match against each filename.  This is similar to the
+.B \-\-exclude
+option in the standard GNU tar command.
+.TP
+.BR \-\^\-Exclude-from\ \<file>
+Instruct gpgdir to exclude all files matched by patterns listed in
+.B file.
+This is similar to the
+.B \-\-exclude-from
+the GNU tar command.
+.TP
+.BR \-\^\-Include\ \<pattern>
+Instruct gpgdir to only include files that match
+.B pattern
+as a regex match against each filename.
+.TP
+.BR \-\^\-Include-from\ \<file>
+Instruct gpgdir to only include files matched by patterns listed in
+.B file.
+.TP
+.BR \-W ", " \-\^\-Wipe
+Use the
+.B wipe
+program to securely delete files after they have been successfully encrypted.
+.TP
+.BR \-O ", " \-\^\-Obfuscate-filename
+Tell
+.B gpgdir
+to obfuscate the file names of files that it encrypts (in \-e mode).  The
+names of each file are stored within the file .gpgdir_map_file for every
+sub-directory, and this file is itself encrypted.  In decryption mode (\-d),
+the \-O argument reverses the process so that the original files are
+restored.
+.TP
+.BR \-\^\-overwrite-encrypted
+Overwrite encrypted files even if a previous <file>.gpg file
+already exists.
+.TP
+.BR \-\^\-overwrite-decrypted
+Overwrite decrypted files even if the previous unencrypted file already exists.
+.TP
+.BR \-K ", " \-\^\-Key-id\ \<id>
+Manually specify a GnuPG key ID from the command line.  Because GnuPG
+supports matching keys with a string,
+.B id
+does not strictly have to be a key ID; it can be a string that uniquely
+matches a key in the GnuPG key ring.
+.TP
+.BR \-D ", " \-\^\-Default-key
+Use the key that GnuPG defines as the default, i.e. the key that is specified
+by the
+.B default-key
+variable in ~/.gnupg/options.  If the default-key variable is not defined
+within ~/.gnupg/options, then GnuPG tries to use the first suitable key on
+its key ring (the initial encrypt/decrypt test makes sure that the user
+knows the corresponding password for the key).
+.TP
+.BR \-a ", " " \-\^\-agent
+Instruct
+.B gpgdir
+to acquire gpg key password from a running
+.B gpg-agent
+instance.
+.TP
+.BR \-A ", " \-\^\-Agent-info\ \<connection\ \info>
+Specify the value of the GPG_AGENT_INFO environment variable as returned
+by the
+.B gpg-agent \-\-daemon
+command. If the
+.B gpgdir \-\-agent
+command line argument is used instead of
+.B \-\-Agent-info,
+then gpgdir assumes that the GPG_AGENT_INFO environment variable has already
+been set in the current shell.
+.TP
+.BR \-s ", " " \-\^\-skip-test
+Skip encryption and decryption test.  This will allow
+.B gpgdir
+to be used to encrypt a directory without specifying a password (which
+normally gets used in encryption mode to test to make sure decryption
+against a dummy file works properly).
+.TP
+.BR \-q ", " \-\^\-quiet
+Print as little as possible to the screen when encrypting or decrypting
+a directory.
+.TP
+.BR \-\^\-no-recurse
+Instruct gpgdir to not recurse through any subdirectories of the directory
+that is being encrypted or decrypted.
+.TP
+.BR \-\^\-no-password
+Instruct gpgdir to not ask the user for a password.  This is only useful
+when a gpg key literally has no associated password (this is not common).
+.TP
+.BR \-\^\-no-delete
+Instruct gpgdir to not delete original files at encrypt time.
+.TP
+.BR \-\^\-no-preserve times
+Instruct gpgdir to not preserve original file mtime and atime values
+upon encryption or decryption.
+.TP
+.BR \-l ", " " \-\^\-locale\ \<locale>
+Provide a locale setting other than the default "C" locale.
+.TP
+.BR \-\^\-no-locale
+Do not set the locale at all so that the default system locale will apply.
+.TP
+.BR \-v ", " \-\^\-verbose
+Run in verbose mode.
+.TP
+.BR \-V ", " \-\^\-Version
+Print version number and exit.
+.TP
+.BR \-h ", " \-\^\-help
+Print usage information and exit.
+.SH FILES
+.B ~/.gpgdirrc
+.RS
+Contains the key id of the user gpg key that will be used to encrypt
+or decrypt the files within a directory.
+.RE
+.PP
+.SH EXAMPLES
+The following examples illustrate the command line arguments that could
+be supplied to gpgdir in a few situations:
+.PP
+To encrypt a directory:
+.PP
+.B $ gpgdir \-e /some/dir
+.PP
+To encrypt a directory, and use the wipe command to securely delete the original
+unencrypted files:
+.PP
+.B $ gpgdir \-W \-e /some/dir
+.PP
+To encrypt a directory with the default GnuPG key defined in ~/.gnupg/options:
+.PP
+.B $ gpgdir \-e /some/dir \-\-Default-key
+.PP
+To decrypt a directory with a key specified in ~/.gpgdirrc:
+.PP
+.B $ gpgdir \-d /some/dir
+.PP
+To encrypt a directory but skip all filenames that contain the string "host":
+.PP
+.B $ gpgdir \-e /some/dir \-\-Exclude host
+.PP
+To encrypt a directory but only encrypt those files that contain the string "passwd":
+.PP
+.B $ gpgdir \-e /some/dir \-\-Include passwd
+.PP
+To acquire the GnuPG key password from a running gpg-agent daemon in order to decrypt
+a directory (this requires that gpg-agent has the password):
+.PP
+.B $ gpgdir \-A /tmp/gpg-H4DBhc/S.gpg-agent:7046:1 \-d /some/dir
+.PP
+To encrypt a directory but skip the encryption/decryption test (so you will
+not be prompted for a decryption password):
+.PP
+.B $ gpgdir \-e /some/dir \-s
+.PP
+To encrypt a directory and no subdirectories:
+.PP
+.B $ gpgdir \-e /some/dir \-\-no-recurse
+.PP
+To encrypt root's home directory, but use the GnuPG keys associated with the user "bob":
+.PP
+.B # gpgdir \-e /root \-g /home/bob/.gnupg
+.PP
+.SH DEPENDENCIES
+.B gpgdir
+requires that gpg, the Gnu Privacy Guard (http://www.gnupg.org) is installed.
+.B gpgdir
+also requires the GnuPG::Interface perl module from CPAN, but it is bundled with
+.B gpgdir
+and is installed in /usr/lib/gpgdir at install-time so it does not pollute the
+system perl library tree.
+
+.SH "SEE ALSO"
+.BR gpg (1)
+
+.SH AUTHOR
+Michael Rash <mbr@cipherdyne.org>
+
+.SH CONTRIBUTORS
+Many people who are active in the open source community have contributed to gpgdir;
+see the
+.B CREDITS
+file in the gpgdir sources.
+
+
+.SH BUGS
+Send bug reports to mbr@cipherdyne.org. Suggestions and/or comments are
+always welcome as well.
+
+.SH DISTRIBUTION
+.B gpgdir
+is distributed under the GNU General Public License (GPL), and the latest
+version may be downloaded from
+.B http://www.cipherdyne.org
diff --git a/gpgdir/install.pl b/gpgdir/install.pl
new file mode 100755 (executable)
index 0000000..95def39
--- /dev/null
@@ -0,0 +1,450 @@
+#!/usr/bin/perl -w
+#
+####################################################################
+#
+# File: install.pl
+#
+# Purpose: To install gpgdir on a Linux system.
+#
+# Author: Michael Rash (mbr@cipherdyne.org)
+#
+# Copyright (C) 2002-2008 Michael Rash (mbr@cipherdyne.org)
+#
+# License (GNU Public License):
+#
+#    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: install.pl 311 2008-08-31 23:11:12Z mbr $
+#
+
+use Cwd;
+use File::Copy;
+use Getopt::Long;
+use strict;
+
+#======================= config =======================
+my $install_dir = '/usr/bin';
+my $libdir      = '/usr/lib/gpgdir';
+my $manpage     = 'gpgdir.1';
+
+### only used it $ENV{'HOME'} is not set for some reason
+my $config_homedir = '';
+
+### system binaries
+my $gzipCmd = '/usr/bin/gzip';
+my $perlCmd = '/usr/bin/perl';
+my $makeCmd = '/usr/bin/make';
+#===================== end config =====================
+
+my $print_help = 0;
+my $uninstall  = 0;
+my $force_mod_re  = '';
+my $exclude_mod_re  = '';
+my $skip_module_install   = 0;
+my $cmdline_force_install = 0;
+my $locale = 'C';  ### default LC_ALL env variable
+my $no_locale = 0;
+my $deps_dir  = 'deps';
+
+my %cmds = (
+    'gzip' => $gzipCmd,
+    'perl' => $perlCmd,
+    'make' => $makeCmd
+);
+
+### map perl modules to versions
+my %required_perl_modules = (
+    'Class::MethodMaker' => {
+        'force-install' => 0,
+        'mod-dir' => 'Class-MethodMaker'
+    },
+    'GnuPG::Interface' => {
+        'force-install' => 0,
+        'mod-dir' => 'GnuPG-Interface'
+    },
+    'Term::ReadKey' => {
+        'force-install' => 0,
+        'mod-dir' => 'TermReadKey'
+    }
+);
+
+### make Getopts case sensitive
+Getopt::Long::Configure('no_ignore_case');
+
+&usage(1) unless (GetOptions(
+    'force-mod-install' => \$cmdline_force_install,  ### force install of all modules
+    'Force-mod-regex=s' => \$force_mod_re,  ### force specific mod install with regex
+    'Exclude-mod-regex=s' => \$exclude_mod_re, ### exclude a particular perl module
+    'Skip-mod-install'  => \$skip_module_install,
+    'home-dir=s'        => \$config_homedir, ### force a specific home dir
+    'LC_ALL=s'          => \$locale,
+    'locale=s'          => \$locale,
+    'no-LC_ALL'         => \$no_locale,
+    'no-locale'         => \$no_locale,  ### synonym
+    'uninstall' => \$uninstall,      # Uninstall gpgdir.
+    'help'      => \$print_help      # Display help.
+));
+&usage(0) if $print_help;
+
+### set LC_ALL env variable
+$ENV{'LC_ALL'} = $locale unless $no_locale;
+
+$force_mod_re = qr|$force_mod_re| if $force_mod_re;
+$exclude_mod_re = qr|$exclude_mod_re| if $exclude_mod_re;
+
+### check to see if we are installing in a Cygwin environment
+my $non_root_user = 0;
+if (&is_cygwin()) {
+
+    print
+"[+] It looks like you are installing gpgdir in a Cygwin environment.\n";
+    $non_root_user = 1;
+
+} else {
+
+    unless ($< == 0 && $> == 0) {
+        print
+"[+] It looks like you are installing gpgdir as a non-root user, so gpgdir\n",
+"    will be installed in your local home directory.\n\n";
+
+        $non_root_user = 1;
+    }
+}
+
+if ($non_root_user) {
+
+    ### we are installing as a normal user instead of root, so see
+    ### if it is ok to install within the user's home directory
+    my $homedir = '';
+    if ($config_homedir) {
+        $homedir = $config_homedir;
+    } else {
+        $homedir = $ENV{'HOME'} or die '[*] Could not get home ',
+            "directory, set the $config_homedir var.";
+    }
+
+    print
+"    gpgdir will be installed at $homedir/bin/gpgdir, and a few\n",
+"    perl modules needed by gpgdir will be installed in $homedir/lib/gpgdir/.\n\n",
+
+    mkdir "$homedir/lib" unless -d "$homedir/lib";
+    $libdir = "$homedir/lib/gpgdir";
+    $install_dir = "$homedir/bin";
+}
+
+### make sure we can find the system binaries
+### in the expected locations.
+&check_commands();
+
+my $src_dir = getcwd() or die "[*] Could not get current working directory.";
+
+### create directories, make sure executables exist, etc.
+&setup();
+
+print "[+] Installing gpgdir in $install_dir\n";
+&install_gpgdir();
+
+### install perl modules
+unless ($skip_module_install) {
+    for my $module (keys %required_perl_modules) {
+        &install_perl_module($module);
+    }
+}
+chdir $src_dir or die "[*] Could not chdir $src_dir: $!";
+
+print "[+] Installing man page.\n";
+&install_manpage();
+
+print "\n    It is highly recommended to run the test suite in the test/\n",
+    "    directory to ensure proper gpgdir operation.\n",
+    "\n[+] gpgdir has been installed!\n";
+
+exit 0;
+#===================== end main =======================
+
+sub install_gpgdir() {
+    die "[*] gpgdir does not exist.  Download gpgdir from " .
+        "http://www.cipherdyne.org/gpgdir" unless -e 'gpgdir';
+    copy 'gpgdir', "${install_dir}/gpgdir" or die "[*] Could not copy " .
+        "gpgdir to $install_dir: $!";
+
+    if ($non_root_user) {
+        open F, "< ${install_dir}/gpgdir" or die "[*] Could not open ",
+            "${install_dir}/gpgdir: $!";
+        my @lines = <F>;
+        close F;
+        open P, "> ${install_dir}/gpgdir.tmp" or die "[*] Could not open ",
+            "${install_dir}/gpgdir.tmp: $!";
+        for my $line (@lines) {
+            ### change the lib dir to new homedir path
+            if ($line =~ m|^\s*use\s+lib\s+\'/usr/lib/gpgdir\';|) {
+                print P "use lib '", $libdir, "';\n";
+            } else {
+                print P $line;
+            }
+        }
+        close P;
+        move "${install_dir}/gpgdir.tmp", "${install_dir}/gpgdir" or
+            die "[*] Could not move ${install_dir}/gpgdir.tmp -> ",
+                "${install_dir}/gpgdir: $!";
+
+        chmod 0700, "${install_dir}/gpgdir" or die "[*] Could not set " .
+            "permissions on gpgdir to 0755";
+    } else {
+        chmod 0755, "${install_dir}/gpgdir" or die "[*] Could not set " .
+            "permissions on gpgdir to 0755";
+        chown 0, 0, "${install_dir}/gpgdir" or
+            die "[*] Could not chown 0,0,${install_dir}/gpgdir: $!";
+    }
+    return;
+}
+
+sub install_perl_module() {
+    my $mod_name = shift;
+
+    chdir $src_dir or die "[*] Could not chdir $src_dir: $!";
+    chdir $deps_dir or die "[*] Could not chdir($deps_dir): $!";
+
+    die '[*] Missing force-install key in required_perl_modules hash.'
+        unless defined $required_perl_modules{$mod_name}{'force-install'};
+    die '[*] Missing mod-dir key in required_perl_modules hash.'
+        unless defined $required_perl_modules{$mod_name}{'mod-dir'};
+
+    if ($exclude_mod_re and $exclude_mod_re =~ /$mod_name/) {
+        print "[+] Excluding installation of $mod_name module.\n";
+        return;
+    }
+
+    my $version = '(NA)';
+
+    my $mod_dir = $required_perl_modules{$mod_name}{'mod-dir'};
+
+    if (-e "$mod_dir/VERSION") {
+        open F, "< $mod_dir/VERSION" or
+            die "[*] Could not open $mod_dir/VERSION: $!";
+        $version = <F>;
+        close F;
+        chomp $version;
+    } else {
+        print "[-] Warning: VERSION file does not exist in $mod_dir\n";
+    }
+
+    my $install_module = 0;
+
+    if ($required_perl_modules{$mod_name}{'force-install'}
+            or $cmdline_force_install) {
+        ### install regardless of whether the module may already be
+        ### installed
+        $install_module = 1;
+    } elsif ($force_mod_re and $force_mod_re =~ /$mod_name/) {
+        print "[+] Forcing installation of $mod_name module.\n";
+        $install_module = 1;
+    } else {
+        if (has_perl_module($mod_name)) {
+            print "[+] Module $mod_name is already installed in the ",
+                "system perl tree, skipping.\n";
+        } else {
+            ### install the module in the /usr/lib/gpgdir directory because
+            ### it is not already installed.
+            $install_module = 1;
+        }
+    }
+
+    if ($install_module) {
+        unless (-d $libdir) {
+            print "[+] Creating $libdir\n";
+            mkdir $libdir, 0755 or die "[*] Could not mkdir $libdir: $!";
+        }
+        print "[+] Installing the $mod_name $version perl " .
+            "module in $libdir/\n";
+        my $mod_dir = $required_perl_modules{$mod_name}{'mod-dir'};
+        chdir $mod_dir or die "[*] Could not chdir to ",
+            "$mod_dir: $!";
+        unless (-e 'Makefile.PL') {
+            die "[*] Your $mod_name source directory appears to be incomplete!\n",
+                "    Download the latest sources from ",
+                "http://www.cipherdyne.org/\n";
+        }
+        system "$cmds{'make'} clean" if -e 'Makefile';
+        system "$cmds{'perl'} Makefile.PL PREFIX=$libdir LIB=$libdir";
+        system $cmds{'make'};
+#        system "$cmds{'make'} test";
+        system "$cmds{'make'} install";
+        chdir $src_dir or die "[*] Could not chdir $src_dir: $!";
+
+        print "\n\n";
+    }
+    chdir $src_dir or die "[*] Could not chdir $src_dir: $!";
+    return;
+}
+
+sub has_perl_module() {
+    my $module = shift;
+
+    # 5.8.0 has a bug with require Foo::Bar alone in an eval, so an
+    # extra statement is a workaround.
+    my $file = "$module.pm";
+    $file =~ s{::}{/}g;
+    eval { require $file };
+
+    return $@ ? 0 : 1;
+}
+
+sub install_manpage() {
+
+    if ($non_root_user) {
+        print
+"[+] Because this is a non-root install, the man page will not be installed\n",
+"    but you can download it here:  http://www.cipherdyne.org/gpgdir\n\n";
+        return;
+    }
+
+    die "[*] man page: $manpage does not exist.  Download gpgdir " .
+        "from http://www.cipherdyne.org/gpgdir" unless -e $manpage;
+    ### default location to put the gpgdir man page, but check with
+    ### /etc/man.config
+    my $mpath = '/usr/share/man/man1';
+    if (-e '/etc/man.config') {
+        ### prefer to install $manpage in /usr/local/man/man1 if
+        ### this directory is configured in /etc/man.config
+        open M, '< /etc/man.config' or
+            die "[*] Could not open /etc/man.config: $!";
+        my @lines = <M>;
+        close M;
+        ### prefer the path "/usr/share/man"
+        my $found = 0;
+        for my $line (@lines) {
+            chomp $line;
+            if ($line =~ m|^MANPATH\s+/usr/share/man|) {
+                $found = 1;
+                last;
+            }
+        }
+        ### try to find "/usr/local/man" if we didn't find /usr/share/man
+        unless ($found) {
+            for my $line (@lines) {
+                chomp $line;
+                if ($line =~ m|^MANPATH\s+/usr/local/man|) {
+                    $mpath = '/usr/local/man/man1';
+                    $found = 1;
+                    last;
+                }
+            }
+        }
+        ### if we still have not found one of the above man paths,
+        ### just select the first one out of /etc/man.config
+        unless ($found) {
+            for my $line (@lines) {
+                chomp $line;
+                if ($line =~ m|^MANPATH\s+(\S+)|) {
+                    $mpath = $1;
+                    last;
+                }
+            }
+        }
+    }
+    mkdir $mpath, 0755 unless -d $mpath;
+    my $mfile = "${mpath}/${manpage}";
+    print "[+] Installing $manpage man page as: $mfile\n";
+    copy $manpage, $mfile or die "[*] Could not copy $manpage to " .
+        "$mfile: $!";
+    chmod 0644, $mfile or die "[*] Could not set permissions on ".
+        "$mfile to 0644";
+    chown 0, 0, $mfile or
+        die "[*] Could not chown 0,0,$mfile: $!";
+    print "[+] Compressing man page: $mfile\n";
+    ### remove the old one so gzip doesn't prompt us
+    unlink "${mfile}.gz" if -e "${mfile}.gz";
+    system "$cmds{'gzip'} $mfile";
+    return;
+}
+
+### check paths to commands and attempt to correct if any are wrong.
+sub check_commands() {
+    my @path = qw(
+        /bin
+        /sbin
+        /usr/bin
+        /usr/sbin
+        /usr/local/bin
+        /usr/local/sbin
+    );
+    CMD: for my $cmd (keys %cmds) {
+        unless (-x $cmds{$cmd}) {
+            my $found = 0;
+            PATH: for my $dir (@path) {
+                if (-x "${dir}/${cmd}") {
+                    $cmds{$cmd} = "${dir}/${cmd}";
+                    $found = 1;
+                    last PATH;
+                }
+            }
+            unless ($found) {
+                die "[*] Could not find $cmd anywhere!!!  ",
+                    "Please edit the config section to include the path to ",
+                    "$cmd.\n";
+            }
+        }
+        unless (-x $cmds{$cmd}) {
+            die "[*] $cmd is located at ",
+                "$cmds{$cmd} but is not executable by uid: $<\n";
+        }
+    }
+    return;
+}
+
+sub is_cygwin() {
+
+    my $rv = 0;
+
+    ### get OS output from uname
+    open UNAME, "uname -o |" or return $rv;
+    while (<UNAME>) {
+        $rv = 1 if /Cygwin/;
+    }
+    close UNAME;
+
+    return $rv;
+}
+
+
+sub setup() {
+    unless (-d $libdir) {
+        mkdir $libdir, 0755 or die "[*] Could not create $libdir: $!"
+    }
+    return;
+}
+
+sub usage() {
+    my $exit_status = shift;
+    print <<_HELP_;
+
+Usage: install.pl [options]
+
+    -u,  --uninstall             - Uninstall gpgdir.
+    -f, --force-mod-install      - Force all perl modules to be installed
+                                   even if some already exist in the system
+                                   /usr/lib/perl5 tree.
+    -F, --Force-mod-regex <re>   - Specify a regex to match a module name
+                                   and force the installation of such modules.
+    -E, --Exclude-mod-regex <re> - Exclude a perl module that matches this
+                                   regular expression.
+    -S, --Skip-mod-install       - Do not install any perl modules.
+
+    -L, --LANG <locale>          - Specify LANG env variable (actually the
+                                   LC_ALL variable).
+    -h  --help                   - Prints this help message.
+
+_HELP_
+    exit $exit_status;
+}
diff --git a/gpgdir/packaging/cd_rpmbuilder b/gpgdir/packaging/cd_rpmbuilder
new file mode 100755 (executable)
index 0000000..deec580
--- /dev/null
@@ -0,0 +1,262 @@
+#!/usr/bin/perl -w
+#
+#############################################################################
+#
+# File: cd_rpmbuilder "CipherDyne Rpm Builder"
+#
+# Purpose: Provides a consistent way to build RPMs of CipherDyne open source
+#          projects (psad, fwsnort, fwsknop, and gpgdir).
+#
+# Author: Michael Rash
+#
+# Copyright (C) 2006-2008 Michael Rash (mbr@cipherdyne.org)
+#
+# License (GNU Public License - 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: cd_rpmbuilder 1864 2008-08-22 03:16:19Z mbr $
+#
+
+use File::Find;
+use File::Copy;
+use Getopt::Long 'GetOptions';
+use strict;
+
+#============================ config =============================
+my $rpm_root_dir   = '/usr/src/redhat';
+my $build_url_base = 'http://www.cipherdyne.org';
+
+### commands
+my $rpmbuildCmd = '/usr/bin/rpmbuild';
+my $wgetCmd     = '/usr/bin/wget';
+#========================== end config ===========================
+
+my $version = '0.9';
+
+my $project = '';
+my $build_version = '';
+my $print_version = 0;
+my $nodeps = 0;
+my $verbose = 0;
+my $help = 0;
+
+my @rpm_paths = ();
+
+my $RM    = 1;
+my $PRINT = 2;
+
+my %projects = (
+    'psad'    => '',
+    'fwknop'  => '',
+    'fwsnort' => '',
+    'gpgdir'  => ''
+);
+
+Getopt::Long::Configure('no_ignore_case');
+&usage() unless (GetOptions(
+    'project=s' => \$project,
+    'build-version=s' => \$build_version,
+    'rpm-build-dir=s' => \$rpm_root_dir,
+    'no-deps' => \$nodeps,
+    'verbose' => \$verbose,
+    'Version' => \$print_version,
+    'help'    => \$help
+));
+&usage() if $help;
+
+if ($print_version) {
+    print "[+] cd_rpmbuilder by Michael Rash <mbr\@cipherdyne.org>\n";
+    exit 0;
+}
+
+if ($project) {
+    unless (defined $projects{$project}) {
+        print "[*] Unrecognized project: $project; must be one of:\n";
+        print $_, "\n" for keys %projects;
+        exit 1;
+    }
+} else {
+    die "[*] Must specify a project with -p <project>\n";
+}
+
+die "[*] $wgetCmd is not a valid path to wget, update the config section."
+    unless -x $wgetCmd;
+die "[*] $rpmbuildCmd is not a valid path to rpmbuild, update the config " .
+    "section." unless -x $rpmbuildCmd;
+
+chdir "$rpm_root_dir/SPECS" or die "[*] Could not chdir $rpm_root_dir/SPECS";
+
+unless ($build_version) {
+    ### we need to get the latest version from cipherdyne.org
+    &get_latest_version();
+}
+
+my $spec_file = "$project-$build_version.spec";
+my $tar_file  = "$project-$build_version.tar.gz";
+
+if ($nodeps) {
+    $spec_file = "$project-nodeps-$build_version.spec";
+    $tar_file  = "$project-nodeps-$build_version.tar.gz";
+}
+
+### remove old RPMS
+&find_rpms($RM);
+
+### get the remote spec file
+&download_file($spec_file);
+&md5_check($spec_file);
+
+### get the remote source tarball and md5 sum file
+&download_file($tar_file);
+&md5_check($tar_file);
+
+if ($nodeps) {
+    move $tar_file, "../SOURCES/$project-$build_version.tar.gz" or die $!;
+} else {
+    move $tar_file, '../SOURCES' or die $!;
+}
+
+### build the rpm
+&build_rpm();
+
+### print the paths to the new RPMS
+&find_rpms($PRINT);
+
+exit 0;
+#======================= end main ========================
+
+sub find_rpms() {
+    my $action = shift;
+    @rpm_paths = ();
+    find(\&get_rpms, "$rpm_root_dir/SRPMS");
+    find(\&get_rpms, "$rpm_root_dir/RPMS");
+    if ($action == $PRINT) {
+        if (@rpm_paths) {
+            print "[+] The following RPMS were successfully built:\n\n";
+        } else {
+            print "[-] No RPMS were successfully built; try running ",
+                "with --verbose\n";
+        }
+    }
+    for my $rpm_file (@rpm_paths) {
+        if ($action == $RM) {
+            unlink $rpm_file or die "[*] Could not unlink $rpm_file: $!";
+        } elsif ($action == $PRINT) {
+            if ($rpm_file =~ /\.src\.rpm/) {
+                print "      $rpm_file (source RPM)\n";
+            } else {
+                print "      $rpm_file\n";
+            }
+        }
+    }
+    print "\n" if $action == $PRINT;
+    return;
+}
+
+sub get_rpms() {
+    my $file = $File::Find::name;
+    if ($file =~ /$project-$build_version-.*\.rpm$/) {
+        push @rpm_paths, $file;
+    }
+    return;
+}
+
+sub download_file() {
+    my $file = shift;
+    unlink $file if -e $file;
+    print "[+] Downloading file:\n",
+        "      $build_url_base/$project/download/$file\n";
+    my $cmd = "$wgetCmd $build_url_base/$project/download/$file";
+    unless ($verbose) {
+        $cmd .= ' > /dev/null 2>&1';
+    }
+    system $cmd;
+    die "[*] Could not download $file, try running with -v"
+        unless -e $file;
+    return;
+
+}
+
+sub md5_check() {
+    my $file = shift;
+    &download_file("$file.md5");
+    ### check MD5 sum
+    open MD5, "md5sum -c $file.md5 |"
+        or die $!;
+    my $sum_line = <MD5>;
+    close MD5;
+    unless ($sum_line =~ m/$file:\s+OK/) {
+        die "[*] MD5 sum check failed for $file, ",
+            "exiting.";
+    }
+    print "[+] Valid md5 sum check for $file\n";
+    unlink "$file.md5";
+    return;
+}
+
+sub build_rpm() {
+    print
+"[+] Building RPM, this may take a little while (try -v if you want\n",
+"    to see all of the steps)...\n\n";
+    my $cmd = "$rpmbuildCmd -ba $spec_file";
+    unless ($verbose) {
+        $cmd .= ' > /dev/null 2>&1';
+    }
+    system $cmd;
+    return;
+}
+
+sub get_latest_version() {
+    unlink "$project-latest" if -e "$project-latest";
+    print "[+] Getting latest version file:\n",
+        "      $build_url_base/$project/$project-latest\n";
+    my $cmd = "$wgetCmd $build_url_base/$project/$project-latest";
+    unless ($verbose) {
+        $cmd .= ' > /dev/null 2>&1';
+    }
+    system $cmd;
+    open F, "< $project-latest" or
+            die "[*] Could not open $project-latest: $!";
+    my $line = <F>;
+    close F;
+    chomp $line;
+    $build_version = $line;
+    die "[*] Could not get build version" unless $build_version;
+    unlink "$project-latest" if -e "$project-latest";
+    return;
+}
+
+sub usage() {
+    print <<_HELP_;
+
+cd_rpmbuilder; the CipherDyne RPM builder
+[+] Version: $version
+[+] By Michael Rash (mbr\@cipherdyne.org, http://www.cipherdyne.org)
+
+Usage: cd_rpmbuilder -p <project> [options]
+
+Options:
+    -p, --project <name>       - This can be one of "psad", "fwknop",
+                                 "gpgdir", or "fwsnort".
+    -b, --build-version <ver>  - Build a specific project version.
+    -r, --rpm-build-dir <dir>  - Change the RPM build directory from the
+                                 default of $rpm_root_dir.
+    -n, --no-deps              - Build the specified project without any
+                                 dependencies (such as perl modules).
+    -v, --verbose              - Run in verbose mode.
+    -V, --Version              - Print version and exit.
+    -h, --help                 - Display usage information.
+_HELP_
+    exit 0;
+}
diff --git a/gpgdir/packaging/gpgdir-nodeps.spec b/gpgdir/packaging/gpgdir-nodeps.spec
new file mode 100644 (file)
index 0000000..148e606
--- /dev/null
@@ -0,0 +1,110 @@
+%define name gpgdir
+%define version 1.9.3
+%define release 1
+%define gpgdirlibdir %_libdir/%name
+
+Summary: Gpgdir recursively encrypts/decrypts directories with GnuPG.
+Name: %name
+Version: %version
+Release: %release
+License: GPL
+Group: Applications/Cryptography
+Url: http://www.cipherdyne.org/gpgdir/
+Source: %name-%version.tar.gz
+BuildRoot: %_tmppath/%{name}-buildroot
+#Prereq: rpm-helper
+
+%description
+gpgdir is a perl script that uses the CPAN GnuPG::Interface perl module to encrypt
+and decrypt directories using a gpg key specified in ~/.gpgdirrc. gpgdir recursively
+descends through a directory in order to make sure it encrypts or decrypts every file
+in a directory and all of its subdirectories. By default the mtime and atime values
+of all files will be preserved upon encryption and decryption (this can be disabled
+with the --no-preserve-times option). Note that in --encrypt mode, gpgdir will
+delete the original files that it successfully encrypts (unless the --no-delete
+option is given). However, upon startup gpgdir first asks for a the decryption pass-
+word to be sure that a dummy file can successfully be encrypted and decrypted. The
+initial test can be disabled with the --skip-test option so that a directory can eas-
+ily be encrypted without having to also specify a password (this is consistent with
+gpg behavior). Also, note that gpgdir is careful not encrypt hidden files and direc-
+tories. After all, you probably don't want your ~/.gnupg directory or ~/.bashrc file
+to be encrypted.
+
+%prep
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
+
+%setup -q
+
+%build
+
+%install
+
+install -m 755 gpgdir $RPM_BUILD_ROOT%_bindir/
+install -m 644 gpgdir.1 $RPM_BUILD_ROOT%{_mandir}/man1/
+
+%clean
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
+
+%pre
+
+%post
+
+%preun
+
+%files
+%defattr(-,root,root)
+%_bindir/*
+%{_mandir}/man1/*
+%_libdir/%name
+
+%changelog
+* Wed Nov 11 2008 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.9.3 release
+
+* Sun Aug 31 2008 Michael Rash <mbr@cipherdyne.org>
+- This spec file omits installing any perl module dependencies.
+- gpgdir-1.9.2 release
+
+* Sat Jun 07 2008 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.9.1 release
+
+* Sat May 31 2008 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.9 release
+
+* Mon Feb 18 2008 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.8 release
+
+* Mon Feb 18 2008 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.7 release
+
+* Sun Feb 17 2008 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.6 release
+
+* Fri Aug 31 2007 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.5 release
+
+* Sat Jul 20 2007 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.4 release
+
+* Sat Jun 09 2007 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.3 release
+
+* Mon May 28 2007 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.2 release
+
+* Mon May 21 2007 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.1 release
+
+* Sun Sep 17 2006 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.0.3 release (1.0.2 was skipped accidentally).
+
+* Sat Sep 16 2006 Michael Rash <mbr@cipherdyne.org>
+- Added x86_64 RPM.
+- Removed iptables as a prerequisite.
+- gpgdir-1.0.1 release
+
+* Wed Sep 13 2006 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.0 release
+
+* Thu Sep 09 2006 Michael Rash <mbr@cipherdyne.org>
+- Initial RPM release of gpgdir-0.9.9
diff --git a/gpgdir/packaging/gpgdir.SlackBuild b/gpgdir/packaging/gpgdir.SlackBuild
new file mode 100755 (executable)
index 0000000..223003f
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/bash
+################################################################################
+# gpgdir.SlackBuild -- pyllyukko@maimed.org -- 26.1.2007 (originally for psad) #
+################################################################################
+declare -r  RPM_BUILDER="http://www.cipherdyne.org/scripts/cd_rpmbuilder.tar.gz"
+declare -r  RPM_ROOT_DIR="/usr/src/rpm"
+declare -r  ARCH="i386"
+declare -ri BUILD=1
+GPGDIR_VERSION=`wget --no-verbose --output-document=- http://www.cipherdyne.org/gpgdir/gpgdir-latest` || {
+  echo "error!" 1>&2
+  exit 1
+}
+################################################################################
+wget --no-verbose --output-document=- "${RPM_BUILDER}" | tar xz --to-stdout | perl -- - -p gpgdir -r "${RPM_ROOT_DIR}"
+[ $[ ${PIPESTATUS[0]} | ${PIPESTATUS[1]} | ${PIPESTATUS[2]} ] -ne 0 ] && {
+  echo "error!" 1>&2
+  exit 1
+}
+[ ! -f "${RPM_ROOT_DIR}/RPMS/${ARCH}/gpgdir-${GPGDIR_VERSION}-${BUILD}.${ARCH}.rpm" ] && {
+  echo "error: file \`gpgdir-${GPGDIR_VERSION}-${BUILD}.${ARCH}.rpm' doesn't exist!" 1>&2
+  exit 1
+}
+pushd "${RPM_ROOT_DIR}/RPMS/${ARCH}"                   || exit 1
+rpm2tgz "gpgdir-${GPGDIR_VERSION}-${BUILD}.${ARCH}.rpm"        || exit 1
+mv -v "gpgdir-${GPGDIR_VERSION}-${BUILD}.${ARCH}.tgz" "gpgdir-${GPGDIR_VERSION}-${ARCH}-${BUILD}.tgz" || exit 1
+ls -l "${RPM_ROOT_DIR}/RPMS/${ARCH}/gpgdir-${GPGDIR_VERSION}-${ARCH}-${BUILD}.tgz"
+exit ${?}
diff --git a/gpgdir/packaging/gpgdir.spec b/gpgdir/packaging/gpgdir.spec
new file mode 100644 (file)
index 0000000..5af2887
--- /dev/null
@@ -0,0 +1,167 @@
+%define name gpgdir
+%define version 1.9.3
+%define release 1
+%define gpgdirlibdir %_libdir/%name
+
+### get the first @INC directory that includes the string "linux".
+### This may be 'i386-linux', or 'i686-linux-thread-multi', etc.
+%define gpgdirmoddir `perl -e '$path='i386-linux'; for (@INC) { if($_ =~ m|.*/(.*linux.*)|) {$path = $1; last; }} print $path'`
+
+Summary: Gpgdir recursively encrypts/decrypts directories with GnuPG.
+Name: %name
+Version: %version
+Release: %release
+License: GPL
+Group: Applications/Cryptography
+Url: http://www.cipherdyne.org/gpgdir/
+Source: %name-%version.tar.gz
+BuildRoot: %_tmppath/%{name}-buildroot
+#Prereq: rpm-helper
+
+%description
+gpgdir is a perl script that uses the CPAN GnuPG::Interface perl module to encrypt
+and decrypt directories using a gpg key specified in ~/.gpgdirrc. gpgdir recursively
+descends through a directory in order to make sure it encrypts or decrypts every file
+in a directory and all of its subdirectories. By default the mtime and atime values
+of all files will be preserved upon encryption and decryption (this can be disabled
+with the --no-preserve-times option). Note that in --encrypt mode, gpgdir will
+delete the original files that it successfully encrypts (unless the --no-delete
+option is given). However, upon startup gpgdir first asks for a the decryption pass-
+word to be sure that a dummy file can successfully be encrypted and decrypted. The
+initial test can be disabled with the --skip-test option so that a directory can eas-
+ily be encrypted without having to also specify a password (this is consistent with
+gpg behavior). Also, note that gpgdir is careful not encrypt hidden files and direc-
+tories. After all, you probably don't want your ~/.gnupg directory or ~/.bashrc file
+to be encrypted.
+
+%prep
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
+
+%setup -q
+for i in $(grep -r "use lib" . | cut -d: -f1); do
+    awk '/use lib/ { sub("/usr/lib/gpgdir", "%_libdir/%name") } { print }' $i > $i.tmp
+    mv $i.tmp $i
+done
+
+cd deps
+cd Class-MethodMaker && perl Makefile.PL PREFIX=%gpgdirlibdir LIB=%gpgdirlibdir
+cd ..
+cd GnuPG-Interface && perl Makefile.PL PREFIX=%gpgdirlibdir LIB=%gpgdirlibdir
+cd ..
+cd TermReadKey && perl Makefile.PL PREFIX=%gpgdirlibdir LIB=%gpgdirlibdir
+cd ../..
+
+%build
+
+### build perl modules used by gpgdir
+cd deps
+make OPTS="$RPM_OPT_FLAGS" -C Class-MethodMaker
+make OPTS="$RPM_OPT_FLAGS" -C GnuPG-Interface
+make OPTS="$RPM_OPT_FLAGS" -C TermReadKey
+cd ..
+
+%install
+
+### gpgdir module dirs
+cd deps
+mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Term/ReadKey
+mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/array
+mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/Engine
+mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/hash
+mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/scalar
+mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Class/MethodMaker
+mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Term
+mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/auto/GnuPG/Interface
+mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/GnuPG
+mkdir -p $RPM_BUILD_ROOT%_bindir
+mkdir -p $RPM_BUILD_ROOT%{_mandir}/man1
+mkdir -p $RPM_BUILD_ROOT%_sbindir
+cd ..
+
+install -m 755 gpgdir $RPM_BUILD_ROOT%_bindir/
+install -m 644 gpgdir.1 $RPM_BUILD_ROOT%{_mandir}/man1/
+
+### install perl modules used by gpgdir
+cd deps
+install -m 444 Class-MethodMaker/blib/lib/auto/Class/MethodMaker/array/*.* $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/array/
+install -m 444 Class-MethodMaker/blib/lib/auto/Class/MethodMaker/scalar/*.* $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/scalar/
+install -m 444 Class-MethodMaker/blib/lib/auto/Class/MethodMaker/hash/*.* $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/hash/
+install -m 444 Class-MethodMaker/blib/lib/auto/Class/MethodMaker/Engine/*.* $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/Engine/
+install -m 444 Class-MethodMaker/blib/arch/auto/Class/MethodMaker/MethodMaker.bs $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/MethodMaker.bs
+install -m 444 Class-MethodMaker/blib/arch/auto/Class/MethodMaker/MethodMaker.so $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/MethodMaker.so
+install -m 444 Class-MethodMaker/blib/lib/Class/MethodMaker.pm $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Class/MethodMaker.pm
+install -m 444 Class-MethodMaker/blib/lib/Class/MethodMaker/*.pm $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Class/MethodMaker
+install -m 444 GnuPG-Interface/blib/lib/auto/GnuPG/Interface/*.* $RPM_BUILD_ROOT%gpgdirlibdir/auto/GnuPG/Interface/
+install -m 444 GnuPG-Interface/blib/lib/GnuPG/*.pm $RPM_BUILD_ROOT%gpgdirlibdir/GnuPG/
+install -m 444 TermReadKey/blib/lib/Term/ReadKey.pm $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Term/ReadKey.pm
+install -m 444 TermReadKey/blib/lib/auto/Term/ReadKey/autosplit.ix $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Term/ReadKey/autosplit.ix
+install -m 444 TermReadKey/blib/arch/auto/Term/ReadKey/ReadKey.bs $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Term/ReadKey/ReadKey.bs
+install -m 444 TermReadKey/blib/arch/auto/Term/ReadKey/ReadKey.so $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Term/ReadKey/ReadKey.so
+cd ..
+
+%clean
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
+
+%pre
+
+%post
+
+%preun
+
+%files
+%defattr(-,root,root)
+%_bindir/*
+%{_mandir}/man1/*
+%_libdir/%name
+
+%changelog
+* Wed Nov 11 2008 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.9.3 release
+
+* Sun Aug 31 2008 Michael Rash <mbr@cipherdyne.org>
+- Updated to use the deps/ directory for all perl module sources.
+- gpgdir-1.9.2 release
+
+* Sat Jun 07 2008 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.9.1 release
+
+* Sat May 31 2008 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.9 release
+
+* Mon Feb 18 2008 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.8 release
+
+* Mon Feb 18 2008 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.7 release
+
+* Sun Feb 17 2008 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.6 release
+
+* Fri Aug 31 2007 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.5 release
+
+* Sat Jul 20 2007 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.4 release
+
+* Sat Jun 09 2007 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.3 release
+
+* Mon May 28 2007 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.2 release
+
+* Mon May 21 2007 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.1 release
+
+* Sun Sep 17 2006 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.0.3 release (1.0.2 was skipped accidentally).
+
+* Sat Sep 16 2006 Michael Rash <mbr@cipherdyne.org>
+- Added x86_64 RPM.
+- Removed iptables as a prerequisite.
+- gpgdir-1.0.1 release
+
+* Wed Sep 13 2006 Michael Rash <mbr@cipherdyne.org>
+- gpgdir-1.0 release
+
+* Thu Sep 09 2006 Michael Rash <mbr@cipherdyne.org>
+- Initial RPM release of gpgdir-0.9.9
diff --git a/gpgdir/test/conf/broken.pw b/gpgdir/test/conf/broken.pw
new file mode 100644 (file)
index 0000000..cfa37ea
--- /dev/null
@@ -0,0 +1 @@
+boguspassword
diff --git a/gpgdir/test/conf/test-gpg/pubring.gpg b/gpgdir/test/conf/test-gpg/pubring.gpg
new file mode 100644 (file)
index 0000000..30cf6e3
Binary files /dev/null and b/gpgdir/test/conf/test-gpg/pubring.gpg differ
diff --git a/gpgdir/test/conf/test-gpg/secring.gpg b/gpgdir/test/conf/test-gpg/secring.gpg
new file mode 100644 (file)
index 0000000..cb2bf28
Binary files /dev/null and b/gpgdir/test/conf/test-gpg/secring.gpg differ
diff --git a/gpgdir/test/conf/test-gpg/trustdb.gpg b/gpgdir/test/conf/test-gpg/trustdb.gpg
new file mode 100644 (file)
index 0000000..9e85502
Binary files /dev/null and b/gpgdir/test/conf/test-gpg/trustdb.gpg differ
diff --git a/gpgdir/test/conf/test.pw b/gpgdir/test/conf/test.pw
new file mode 100644 (file)
index 0000000..0ff3195
--- /dev/null
@@ -0,0 +1 @@
+gpgdirtest
diff --git a/gpgdir/test/data-dir/.hidden b/gpgdir/test/data-dir/.hidden
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gpgdir/test/data-dir/dir2/.hidden b/gpgdir/test/data-dir/dir2/.hidden
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gpgdir/test/data-dir/dir2/dir4/.hidden b/gpgdir/test/data-dir/dir2/dir4/.hidden
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gpgdir/test/data-dir/dir2/dir4/somefile b/gpgdir/test/data-dir/dir2/dir4/somefile
new file mode 100644 (file)
index 0000000..feba70c
--- /dev/null
@@ -0,0 +1,6 @@
+This is a file that contains multiple lines of ascii text,
+but there is no file extension on this one.
+
+gppdir should encrypt this file under the test suite.
+
+This file is in the top-level data-dir directory.
diff --git a/gpgdir/test/data-dir/dir2/dir4/somefile.txt b/gpgdir/test/data-dir/dir2/dir4/somefile.txt
new file mode 100644 (file)
index 0000000..701d68e
--- /dev/null
@@ -0,0 +1,7 @@
+This is a file that contains multiple lines of ascii text,
+and this file has a .txt extension (which gpgdir should handle
+without issues).
+
+gppdir should encrypt this file under the test suite.
+
+This file is in the top-level data-dir directory.
diff --git a/gpgdir/test/data-dir/dir2/new-ascii b/gpgdir/test/data-dir/dir2/new-ascii
new file mode 100644 (file)
index 0000000..feba70c
--- /dev/null
@@ -0,0 +1,6 @@
+This is a file that contains multiple lines of ascii text,
+but there is no file extension on this one.
+
+gppdir should encrypt this file under the test suite.
+
+This file is in the top-level data-dir directory.
diff --git a/gpgdir/test/data-dir/dir2/new-ascii.txt b/gpgdir/test/data-dir/dir2/new-ascii.txt
new file mode 100644 (file)
index 0000000..701d68e
--- /dev/null
@@ -0,0 +1,7 @@
+This is a file that contains multiple lines of ascii text,
+and this file has a .txt extension (which gpgdir should handle
+without issues).
+
+gppdir should encrypt this file under the test suite.
+
+This file is in the top-level data-dir directory.
diff --git a/gpgdir/test/data-dir/dir3/.hidden b/gpgdir/test/data-dir/dir3/.hidden
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gpgdir/test/data-dir/dir3/dir4/.hidden b/gpgdir/test/data-dir/dir3/dir4/.hidden
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gpgdir/test/data-dir/dir3/dir4/gpgdir-copy b/gpgdir/test/data-dir/dir3/dir4/gpgdir-copy
new file mode 100755 (executable)
index 0000000..cf91bbf
--- /dev/null
@@ -0,0 +1,1235 @@
+#!/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.7
+#
+# Copyright (C) 2002-2007 Michael Rash (mbr@cipherdyne.org)
+#
+# License (GNU General Public License):
+#
+#    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 246 2008-02-18 14:29:16Z mbr $
+#
+
+use lib '/usr/lib/gpgdir';
+use File::Find;
+use File::Copy;
+use Term::ReadKey;
+use GnuPG::Interface;
+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.7';
+my $revision_svn = '$Revision: 246 $';
+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 $homedir         = '';
+my $exclude_pat     = '';
+my $exclude_file    = '';
+my $include_pat     = '';
+my $include_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 $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 $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;
+
+### for user answers
+my $ACCEPT_YES_DEFAULT = 1;
+my $ACCEPT_NO_DEFAULT  = 2;
+
+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=s'      => \$encrypt_dir,     # Encrypt files in this directory.
+    'decrypt=s'      => \$decrypt_dir,     # Decrypt 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.
+    'verbose'        => \$verbose,         # Verbose mode.
+    'Version'        => \$printver,        # Print version
+    'help'           => \$help             # Print help
+));
+&usage_and_exit() if $help;
+
+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";
+}
+
+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.";
+}
+
+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";
+    }
+}
+
+### 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;
+
+### get the path to the user's home directory
+$homedir = &get_homedir() unless $homedir;
+
+unless ($symmetric_mode) {
+    if ($gpg_homedir) {  ### specified on the command line with --gnupg-dir
+        unless ($gpg_homedir =~ /\.gnupg$/) {
+            die "[*] Must specify the path to a user .gnupg directory ",
+                "e.g. /home/username/.gnupg\n";
+        }
+    } else {
+        if (-d "${homedir}/.gnupg") {
+            $gpg_homedir = "${homedir}/.gnupg";
+        }
+    }
+    unless (-d $gpg_homedir) {
+        die "[*] GnuPG directory: ${homedir}/.gnupg 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 "[*] You cannot encrypt and decrypt the same directory.\n";
+    &usage_and_exit();
+}
+
+unless ($decrypt_dir or $encrypt_dir or $test_and_exit) {
+    print "[*] Please specify -e <dir>, -d <dir>, or --test-mode\n";
+    &usage_and_exit();
+}
+
+### 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;
+
+my $initial_dir = cwd or die "[*] Could not get CWD: $!";
+
+if ($symmetric_mode) {
+    &get_password();
+} else {
+    &get_password() unless $encrypt_mode and $skip_test_mode;
+}
+
+if ($dir eq '.') {
+    $dir = $initial_dir;
+} elsif ($dir !~ m|^/|) {
+    $dir = $initial_dir . '/' . $dir;
+}
+$dir =~ s|/$||;  ### remove any trailing slash
+
+### 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 ($encrypt_mode) {
+    print "[+] Encrypting directory: $dir\n" unless $quiet;
+} else {
+    print "[+] Decrypting 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 ($encrypt_mode) {
+    print "[+] Total number of files encrypted: " .
+        "$total_encrypted\n" unless $quiet;
+} else {
+    print "[+] Total number of files decrypted: " .
+        "$total_decrypted\n" unless $quiet;
+}
+
+exit 0;
+#==================== end main =====================
+
+sub encrypt_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);
+        } else {
+            $pid = $gpg->encrypt('handles' => $handles);
+        }
+    }
+
+    print $pw_fh $pw;
+    close $pw_fh;
+
+    my @errors = <$error_fh>;
+
+    if ($verbose) {
+        print for @errors;
+    } else {
+        for (@errors) {
+            print if /bad\s+pass/;
+        }
+    }
+
+    close $input_fh;
+    close $output_fh;
+    close $error_fh;
+    close $status_fh;
+
+    waitpid $pid, 0;
+
+    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 re-running with -v.";
+        } else {
+            die "[*] Created zero-size file: $out_file\n",
+                "    Bad password? Try re-running with -v.";
+        }
+    }
+
+    return;
+}
+
+sub decrypt_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) {
+        $pid = $gpg->decrypt('handles' => $handles,
+            'command_args' => [ qw( --use-agent ) ]);
+    } else {
+        $pid = $gpg->decrypt('handles' => $handles);
+    }
+
+    print $pw_fh $pw;
+    close $pw_fh;
+
+    my @errors = <$error_fh>;
+
+    if ($verbose) {
+        print for @errors;
+    } else {
+        for (@errors) {
+            print if /bad\s+pass/;
+        }
+    }
+
+    close $input_fh;
+    close $output_fh;
+    close $error_fh;
+    close $status_fh;
+
+    waitpid $pid, 0;
+
+    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 re-running with -v.";
+        } else {
+            die "[*] Created zero-size file: $out_file\n",
+                "    Bad password? Try re-running with -v.";
+        }
+    }
+    return;
+}
+
+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 .= ' -I -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) {
+                $encrypt_filename = "$filename.asc";
+            }
+
+            if (-e $encrypt_filename and not $overwrite_encrypted) {
+                print "[-] Encrypted file $dir/$encrypt_filename already ",
+                    "exists, skipping.\n" unless $quiet;
+                next FILE;
+            }
+
+            if ($interactive_mode) {
+                next FILE unless (&query_yes_no(
+                    "    Encrypt: $file ([y]/n)?  ", $ACCEPT_YES_DEFAULT));
+            }
+
+            print "[+] Encrypting:  $file\n" unless $quiet;
+
+            unless ($trial_run) {
+
+                &encrypt_file($filename, $encrypt_filename,
+                        $NO_DEL_SOURCE_FILE);
+
+                if (-e $encrypt_filename && -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;
+                        }
+                    }
+                    ### 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 {
+                    print "[-] Could not encrypt 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";
+                    next FILE;
+                }
+
+            } else {
+                if (not $force_mode and $file =~ /gpgdir_\d+_\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;
+
+            ### 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) {
+                next FILE unless (&query_yes_no(
+                    "    Decrypt: $file ([y]/n)?  ", $ACCEPT_YES_DEFAULT));
+            }
+
+            unless ($trial_run) {
+
+                print "[+] Decrypting:  $dir/$filename\n" unless $quiet;
+                &decrypt_file($filename, $decrypt_filename,
+                        $NO_DEL_SOURCE_FILE);
+
+                if (-e $decrypt_filename && -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_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_file("$obfuscate_map_filename.gpg",
+                    $obfuscate_map_filename, $NO_DEL_SOURCE_FILE);
+
+                unlink "$obfuscate_map_filename.gpg";
+            }
+        }
+    }
+    return;
+}
+
+sub handle_old_obfuscated_map_file() {
+    return unless -e "$obfuscate_map_filename.gpg";
+
+    &decrypt_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, $_;
+            }
+        }
+    }
+    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_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;
+        }
+    }
+    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;
+    print
+"[*] Please edit $homedir/.gpgdirrc to include your gpg key identifier,\n",
+"    or use the default GnuPG key defined in ~/.gnupg/options.  Exiting.\n";
+    exit 0;
+}
+
+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) {
+            if ($file =~ m|\.gpg| or $file =~ m|\.asc|) {
+                print "[-] Skipping encrypted file: $file\n" unless $quiet;
+                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 ($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 (! $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_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_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 1;
+}
+
+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 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>   - Encrypt <directory> and all of its
+                                  subdirectories.
+    -d, --decrypt <directory>   - Decrypt <directory> and all of its
+                                  subdirectories.
+    -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).
+    -p, --pw-file <file>        - Read password in from <file>.
+    -s, --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>.
+    -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).
+    -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.
+    -v, --verbose               - Run in verbose mode.
+    -V, --Version               - print version.
+    -h, --help                  - print help.
+_HELP_
+    exit 0;
+}
diff --git a/gpgdir/test/data-dir/dir3/dir4/gpgdir-copy.pl b/gpgdir/test/data-dir/dir3/dir4/gpgdir-copy.pl
new file mode 100755 (executable)
index 0000000..cf91bbf
--- /dev/null
@@ -0,0 +1,1235 @@
+#!/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.7
+#
+# Copyright (C) 2002-2007 Michael Rash (mbr@cipherdyne.org)
+#
+# License (GNU General Public License):
+#
+#    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 246 2008-02-18 14:29:16Z mbr $
+#
+
+use lib '/usr/lib/gpgdir';
+use File::Find;
+use File::Copy;
+use Term::ReadKey;
+use GnuPG::Interface;
+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.7';
+my $revision_svn = '$Revision: 246 $';
+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 $homedir         = '';
+my $exclude_pat     = '';
+my $exclude_file    = '';
+my $include_pat     = '';
+my $include_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 $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 $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;
+
+### for user answers
+my $ACCEPT_YES_DEFAULT = 1;
+my $ACCEPT_NO_DEFAULT  = 2;
+
+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=s'      => \$encrypt_dir,     # Encrypt files in this directory.
+    'decrypt=s'      => \$decrypt_dir,     # Decrypt 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.
+    'verbose'        => \$verbose,         # Verbose mode.
+    'Version'        => \$printver,        # Print version
+    'help'           => \$help             # Print help
+));
+&usage_and_exit() if $help;
+
+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";
+}
+
+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.";
+}
+
+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";
+    }
+}
+
+### 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;
+
+### get the path to the user's home directory
+$homedir = &get_homedir() unless $homedir;
+
+unless ($symmetric_mode) {
+    if ($gpg_homedir) {  ### specified on the command line with --gnupg-dir
+        unless ($gpg_homedir =~ /\.gnupg$/) {
+            die "[*] Must specify the path to a user .gnupg directory ",
+                "e.g. /home/username/.gnupg\n";
+        }
+    } else {
+        if (-d "${homedir}/.gnupg") {
+            $gpg_homedir = "${homedir}/.gnupg";
+        }
+    }
+    unless (-d $gpg_homedir) {
+        die "[*] GnuPG directory: ${homedir}/.gnupg 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 "[*] You cannot encrypt and decrypt the same directory.\n";
+    &usage_and_exit();
+}
+
+unless ($decrypt_dir or $encrypt_dir or $test_and_exit) {
+    print "[*] Please specify -e <dir>, -d <dir>, or --test-mode\n";
+    &usage_and_exit();
+}
+
+### 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;
+
+my $initial_dir = cwd or die "[*] Could not get CWD: $!";
+
+if ($symmetric_mode) {
+    &get_password();
+} else {
+    &get_password() unless $encrypt_mode and $skip_test_mode;
+}
+
+if ($dir eq '.') {
+    $dir = $initial_dir;
+} elsif ($dir !~ m|^/|) {
+    $dir = $initial_dir . '/' . $dir;
+}
+$dir =~ s|/$||;  ### remove any trailing slash
+
+### 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 ($encrypt_mode) {
+    print "[+] Encrypting directory: $dir\n" unless $quiet;
+} else {
+    print "[+] Decrypting 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 ($encrypt_mode) {
+    print "[+] Total number of files encrypted: " .
+        "$total_encrypted\n" unless $quiet;
+} else {
+    print "[+] Total number of files decrypted: " .
+        "$total_decrypted\n" unless $quiet;
+}
+
+exit 0;
+#==================== end main =====================
+
+sub encrypt_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);
+        } else {
+            $pid = $gpg->encrypt('handles' => $handles);
+        }
+    }
+
+    print $pw_fh $pw;
+    close $pw_fh;
+
+    my @errors = <$error_fh>;
+
+    if ($verbose) {
+        print for @errors;
+    } else {
+        for (@errors) {
+            print if /bad\s+pass/;
+        }
+    }
+
+    close $input_fh;
+    close $output_fh;
+    close $error_fh;
+    close $status_fh;
+
+    waitpid $pid, 0;
+
+    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 re-running with -v.";
+        } else {
+            die "[*] Created zero-size file: $out_file\n",
+                "    Bad password? Try re-running with -v.";
+        }
+    }
+
+    return;
+}
+
+sub decrypt_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) {
+        $pid = $gpg->decrypt('handles' => $handles,
+            'command_args' => [ qw( --use-agent ) ]);
+    } else {
+        $pid = $gpg->decrypt('handles' => $handles);
+    }
+
+    print $pw_fh $pw;
+    close $pw_fh;
+
+    my @errors = <$error_fh>;
+
+    if ($verbose) {
+        print for @errors;
+    } else {
+        for (@errors) {
+            print if /bad\s+pass/;
+        }
+    }
+
+    close $input_fh;
+    close $output_fh;
+    close $error_fh;
+    close $status_fh;
+
+    waitpid $pid, 0;
+
+    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 re-running with -v.";
+        } else {
+            die "[*] Created zero-size file: $out_file\n",
+                "    Bad password? Try re-running with -v.";
+        }
+    }
+    return;
+}
+
+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 .= ' -I -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) {
+                $encrypt_filename = "$filename.asc";
+            }
+
+            if (-e $encrypt_filename and not $overwrite_encrypted) {
+                print "[-] Encrypted file $dir/$encrypt_filename already ",
+                    "exists, skipping.\n" unless $quiet;
+                next FILE;
+            }
+
+            if ($interactive_mode) {
+                next FILE unless (&query_yes_no(
+                    "    Encrypt: $file ([y]/n)?  ", $ACCEPT_YES_DEFAULT));
+            }
+
+            print "[+] Encrypting:  $file\n" unless $quiet;
+
+            unless ($trial_run) {
+
+                &encrypt_file($filename, $encrypt_filename,
+                        $NO_DEL_SOURCE_FILE);
+
+                if (-e $encrypt_filename && -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;
+                        }
+                    }
+                    ### 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 {
+                    print "[-] Could not encrypt 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";
+                    next FILE;
+                }
+
+            } else {
+                if (not $force_mode and $file =~ /gpgdir_\d+_\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;
+
+            ### 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) {
+                next FILE unless (&query_yes_no(
+                    "    Decrypt: $file ([y]/n)?  ", $ACCEPT_YES_DEFAULT));
+            }
+
+            unless ($trial_run) {
+
+                print "[+] Decrypting:  $dir/$filename\n" unless $quiet;
+                &decrypt_file($filename, $decrypt_filename,
+                        $NO_DEL_SOURCE_FILE);
+
+                if (-e $decrypt_filename && -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_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_file("$obfuscate_map_filename.gpg",
+                    $obfuscate_map_filename, $NO_DEL_SOURCE_FILE);
+
+                unlink "$obfuscate_map_filename.gpg";
+            }
+        }
+    }
+    return;
+}
+
+sub handle_old_obfuscated_map_file() {
+    return unless -e "$obfuscate_map_filename.gpg";
+
+    &decrypt_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, $_;
+            }
+        }
+    }
+    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_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;
+        }
+    }
+    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;
+    print
+"[*] Please edit $homedir/.gpgdirrc to include your gpg key identifier,\n",
+"    or use the default GnuPG key defined in ~/.gnupg/options.  Exiting.\n";
+    exit 0;
+}
+
+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) {
+            if ($file =~ m|\.gpg| or $file =~ m|\.asc|) {
+                print "[-] Skipping encrypted file: $file\n" unless $quiet;
+                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 ($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 (! $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_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_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 1;
+}
+
+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 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>   - Encrypt <directory> and all of its
+                                  subdirectories.
+    -d, --decrypt <directory>   - Decrypt <directory> and all of its
+                                  subdirectories.
+    -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).
+    -p, --pw-file <file>        - Read password in from <file>.
+    -s, --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>.
+    -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).
+    -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.
+    -v, --verbose               - Run in verbose mode.
+    -V, --Version               - print version.
+    -h, --help                  - print help.
+_HELP_
+    exit 0;
+}
diff --git a/gpgdir/test/data-dir/dir3/dir4/random-binary-data b/gpgdir/test/data-dir/dir3/dir4/random-binary-data
new file mode 100644 (file)
index 0000000..b60436c
Binary files /dev/null and b/gpgdir/test/data-dir/dir3/dir4/random-binary-data differ
diff --git a/gpgdir/test/data-dir/dir3/dir4/random-binary-data.bin b/gpgdir/test/data-dir/dir3/dir4/random-binary-data.bin
new file mode 100644 (file)
index 0000000..b60436c
Binary files /dev/null and b/gpgdir/test/data-dir/dir3/dir4/random-binary-data.bin differ
diff --git a/gpgdir/test/data-dir/dir3/file1 b/gpgdir/test/data-dir/dir3/file1
new file mode 100644 (file)
index 0000000..6f7089f
--- /dev/null
@@ -0,0 +1 @@
+some lines
diff --git a/gpgdir/test/data-dir/dir3/file2 b/gpgdir/test/data-dir/dir3/file2
new file mode 100644 (file)
index 0000000..c2cf88f
--- /dev/null
@@ -0,0 +1 @@
+more lines
diff --git a/gpgdir/test/data-dir/multi-line-ascii b/gpgdir/test/data-dir/multi-line-ascii
new file mode 100644 (file)
index 0000000..feba70c
--- /dev/null
@@ -0,0 +1,6 @@
+This is a file that contains multiple lines of ascii text,
+but there is no file extension on this one.
+
+gppdir should encrypt this file under the test suite.
+
+This file is in the top-level data-dir directory.
diff --git a/gpgdir/test/data-dir/multi-line-ascii.txt b/gpgdir/test/data-dir/multi-line-ascii.txt
new file mode 100644 (file)
index 0000000..701d68e
--- /dev/null
@@ -0,0 +1,7 @@
+This is a file that contains multiple lines of ascii text,
+and this file has a .txt extension (which gpgdir should handle
+without issues).
+
+gppdir should encrypt this file under the test suite.
+
+This file is in the top-level data-dir directory.
diff --git a/gpgdir/test/data-dir/random-binary-data b/gpgdir/test/data-dir/random-binary-data
new file mode 100644 (file)
index 0000000..b60436c
Binary files /dev/null and b/gpgdir/test/data-dir/random-binary-data differ
diff --git a/gpgdir/test/data-dir/random-binary-data.bin b/gpgdir/test/data-dir/random-binary-data.bin
new file mode 100644 (file)
index 0000000..b60436c
Binary files /dev/null and b/gpgdir/test/data-dir/random-binary-data.bin differ
diff --git a/gpgdir/test/gpgdir_test.pl b/gpgdir/test/gpgdir_test.pl
new file mode 100755 (executable)
index 0000000..fafda65
--- /dev/null
@@ -0,0 +1,619 @@
+#!/usr/bin/perl -w
+#
+#############################################################################
+#
+# File: gpgdir_test.pl
+#
+# Purpose: This program provides a testing infrastructure for the gpgdir
+#          Single Packet Authorization client and server.
+#
+# Author: Michael Rash (mbr@cipherdyne.org)
+#
+# Version: 1.9.3
+#
+# Copyright (C) 2008 Michael Rash (mbr@cipherdyne.org)
+#
+# License (GNU Public License):
+#
+#    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_test.pl 328 2008-11-05 05:04:20Z mbr $
+#
+
+use Digest::MD5 'md5_base64';
+use File::Find;
+use File::Copy;
+use Getopt::Long;
+use strict;
+
+#=================== config defaults ==============
+my $gpgdirCmd = '../gpgdir';
+
+my $conf_dir   = 'conf';
+my $output_dir = 'output';
+my $logfile    = 'test.log';
+my $tarfile    = 'gpgdir_test.tar.gz';
+my $data_dir   = 'data-dir';
+
+my $gpg_dir = "$conf_dir/test-gpg";
+my $pw_file = "$conf_dir/test.pw";
+my $broken_pw_file = "$conf_dir/broken.pw";
+my $key_id  = '375D7DB9';
+#==================== end config ==================
+
+my $help = 0;
+my $test_num  = 0;
+my $PRINT_LEN = 68;
+my $APPEND    = 1;
+my $NO_APPEND = 0;
+my $failed_tests = 0;
+my $prepare_results = 0;
+my $successful_tests = 0;
+my $current_test_file = "$output_dir/$test_num.test";
+my $previous_test_file = '';
+my @data_dir_files = ();
+my %md5sums = ();
+
+my $default_args = "--gnupg-dir $gpg_dir " .
+    "--Key-id $key_id --pw-file $pw_file";
+
+die "[*] Use --help" unless GetOptions(
+    'Prepare-results' => \$prepare_results,
+    'help'            => \$help
+);
+
+exit &prepare_results() if $prepare_results;
+
+&setup();
+
+&collect_md5sums();
+
+&logr("\n[+] ==> Running gpgdir test suite <==\n\n");
+
+### execute the tests
+&test_driver('(Setup) gpgdir program compilation', \&perl_compilation);
+&test_driver('(Setup) Command line argument processing', \&getopt_test);
+&test_driver('(Test mode) gpgdir basic test mode', \&test_mode);
+
+### encrypt/decrypt
+&test_driver('(Encrypt dir) gpgdir directory encryption', \&encrypt);
+&test_driver('(Encrypt dir) Files recursively encrypted',
+    \&recursively_encrypted);
+&test_driver('(Encrypt dir) Exclude hidden files/dirs',
+    \&skipped_hidden_files_dirs);
+&test_driver('(Decrypt dir) gpgdir directory decryption', \&decrypt);
+&test_driver('(Decrypt dir) Files recursively decrypted',
+    \&recursively_decrypted);
+&test_driver('(MD5 digest) match across encrypt/decrypt cycle',
+    \&md5sum_validation);
+
+### ascii encrypt/decrypt
+&test_driver('(Ascii-armor dir) gpgdir directory encryption',
+    \&ascii_encrypt);
+&test_driver('(Ascii-armor dir) Files recursively encrypted',
+    \&ascii_recursively_encrypted);
+&test_driver('(Ascii-armor dir) Exclude hidden files/dirs',
+    \&skipped_hidden_files_dirs);
+&test_driver('(Decrypt dir) gpgdir directory decryption', \&decrypt);
+&test_driver('(Decrypt dir) Files recursively decrypted',
+    \&ascii_recursively_decrypted);
+&test_driver('(MD5 digest) match across encrypt/decrypt cycle',
+    \&md5sum_validation);
+
+### obfuscate filenames encrypt/decrypt cycle
+&test_driver('(Obfuscate filenames) gpgdir directory encryption',
+    \&obf_encrypt);
+&test_driver('(Obfuscate filenames) Files recursively encrypted',
+    \&obf_recursively_encrypted);
+&test_driver('(Obfuscate filenames) Exclude hidden files/dirs',
+    \&obf_skipped_hidden_files_dirs);
+&test_driver('(Decrypt dir) gpgdir directory decryption',
+    \&obf_decrypt);
+&test_driver('(Decrypt dir) Files recursively decrypted',
+    \&obf_recursively_decrypted);  ### same as ascii_recursively_decrypted()
+&test_driver('(MD5 digest) match across encrypt/decrypt cycle',
+    \&md5sum_validation);
+
+### sign/verify cycle
+&test_driver('(Sign/verify dir) gpgdir directory signing', \&sign);
+&test_driver('(Sign/verify dir) Files recursively signed',
+    \&recursively_signed);
+&test_driver('(Sign/verify dir) Exclude hidden files/dirs',
+    \&skipped_hidden_files_dirs);
+&test_driver('(Sign/verify dir) Broken signature detection',
+    \&broken_sig_detection);
+&test_driver('(Sign/verify dir) gpgdir directory verification', \&verify);
+&test_driver('(Sign/verify dir) Files recursively verified',
+    \&recursively_verified);
+
+### bad password detection
+&test_driver('(Bad passphrase) detect broken passphrase',
+    \&broken_passphrase);
+
+&logr("\n");
+if ($successful_tests) {
+    &logr("[+] ==> Passed $successful_tests/$test_num tests " .
+        "against gpgdir. <==\n");
+}
+if ($failed_tests) {
+    &logr("[+] ==> Failed $failed_tests/$test_num tests " .
+        "against gpgdir. <==\n");
+}
+&logr("[+] This console output has been stored in: $logfile\n\n");
+
+exit 0;
+#======================== end main =========================
+
+sub test_driver() {
+    my ($msg, $func_ref) = @_;
+
+    my $test_status = 'pass';
+    &dots_print($msg);
+    if (&{$func_ref}) {
+        &pass();
+    } else {
+        $test_status = 'fail';
+        $failed_tests++;
+    }
+
+    open C, ">> $current_test_file"
+        or die "[*] Could not open $current_test_file: $!";
+    print C "\nTEST: $msg, STATUS: $test_status\n";
+    close C;
+
+    $previous_test_file = $current_test_file;
+    $test_num++;
+    $current_test_file = "$output_dir/$test_num.test";
+    return;
+}
+
+sub broken_passphrase() {
+    if (not &run_cmd("$gpgdirCmd --gnupg-dir $gpg_dir " .
+            " --pw-file $broken_pw_file --Key-id $key_id -e $data_dir",
+            $NO_APPEND)) {
+        my $found_bad_pass = 0;
+        open F, "< $current_test_file" or die $!;
+        while (<F>) {
+            if (/BAD_?PASS/) {
+                $found_bad_pass = 1;
+            }
+        }
+        close F;
+        if ($found_bad_pass) {
+            return 1;
+        }
+    }
+    return &print_errors("[-] Accepted broken passphrase");
+}
+
+sub encrypt() {
+    if (&run_cmd("$gpgdirCmd $default_args -e $data_dir", $NO_APPEND)) {
+        return 1;
+    }
+    return &print_errors("[-] Directory encryption");
+}
+
+sub ascii_encrypt() {
+    if (&run_cmd("$gpgdirCmd $default_args --Plain-ascii -e $data_dir",
+            $NO_APPEND)) {
+        return 1;
+    }
+    return &print_errors("[-] Directory encryption");
+}
+
+sub obf_encrypt() {
+    if (&run_cmd("$gpgdirCmd $default_args -O -e $data_dir",
+            $NO_APPEND)) {
+        return 1;
+    }
+    return &print_errors("[-] Directory encryption");
+}
+
+sub sign() {
+    if (&run_cmd("$gpgdirCmd $default_args --sign $data_dir",
+            $NO_APPEND)) {
+        return 1;
+    }
+    return &print_errors("[-] Directory signing");
+}
+
+sub decrypt() {
+    if (&run_cmd("$gpgdirCmd $default_args -d $data_dir",
+            $NO_APPEND)) {
+        return 1;
+    }
+    return &print_errors("[-] Directory decryption");
+}
+
+sub obf_decrypt() {
+    if (&run_cmd("$gpgdirCmd $default_args -O -d $data_dir",
+            $NO_APPEND)) {
+        return 1;
+    }
+    return &print_errors("[-] Directory decryption");
+}
+
+sub verify() {
+    if (&run_cmd("$gpgdirCmd $default_args --verify $data_dir",
+            $NO_APPEND)) {
+        return 1;
+    }
+    return &print_errors("[-] Directory verification");
+}
+
+sub recursively_encrypted() {
+    @data_dir_files = ();
+    find(\&find_files, $data_dir);
+    for my $file (@data_dir_files) {
+        if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
+            unless ($file =~ m|\.gpg$|) {
+                return &print_errors("[-] File $file not encrypted");
+            }
+        }
+    }
+    return 1;
+}
+
+sub recursively_signed() {
+    @data_dir_files = ();
+    find(\&find_files, $data_dir);
+    for my $file (@data_dir_files) {
+        if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
+            if ($file !~ m|\.asc$|) {
+                unless (-e "$file.asc") {
+                    return &print_errors("[-] File $file not signed");
+                }
+            }
+        }
+    }
+    return 1;
+}
+
+sub recursively_decrypted() {
+    @data_dir_files = ();
+    find(\&find_files, $data_dir);
+    for my $file (@data_dir_files) {
+        if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
+            if ($file =~ m|\.gpg$|) {
+                return &print_errors("[-] File $file not encrypted");
+            }
+        }
+    }
+    return 1;
+}
+
+sub broken_sig_detection() {
+    move "$data_dir/multi-line-ascii", "$data_dir/multi-line-ascii.orig"
+        or die $!;
+    open F, "> $data_dir/multi-line-ascii" or die $!;
+    print F "bogus data\n";
+    close F;
+
+    &run_cmd("$gpgdirCmd $default_args --verify $data_dir",
+            $NO_APPEND);
+
+    my $found_bad_sig = 0;
+    open F, "< $current_test_file" or die $!;
+    while (<F>) {
+        if (/BADSIG/) {
+            $found_bad_sig = 1;
+        }
+    }
+    close F;
+
+    if ($found_bad_sig) {
+        unlink "$data_dir/multi-line-ascii";
+        move "$data_dir/multi-line-ascii.orig", "$data_dir/multi-line-ascii"
+            or die $!;
+        return 1;
+    }
+    return &print_errors("[-] Could not find bad signature");
+}
+
+sub recursively_verified() {
+
+    ### search for signature verification errors here
+    my $found_bad_sig = 0;
+    open F, "< $previous_test_file" or die $!;
+    while (<F>) {
+        if (/BADSIG/) {
+            $found_bad_sig = 1;
+        }
+    }
+    close F;
+
+    if ($found_bad_sig) {
+        return &print_errors("[-] Bad signature generated");
+    }
+
+    ### now remove signature files
+    @data_dir_files = ();
+    find(\&find_files, $data_dir);
+    for my $file (@data_dir_files) {
+        if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
+            if ($file =~ m|\.asc$|) {
+                unlink $file;
+            }
+        }
+    }
+    return 1;
+}
+
+sub ascii_recursively_encrypted() {
+    @data_dir_files = ();
+    find(\&find_files, $data_dir);
+    for my $file (@data_dir_files) {
+        if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
+            unless ($file =~ m|\.asc$|) {
+                return &print_errors("[-] File $file not encrypted");
+            }
+        }
+    }
+    return 1;
+}
+
+sub obf_recursively_encrypted() {
+    @data_dir_files = ();
+    find(\&find_files, $data_dir);
+    for my $file (@data_dir_files) {
+        if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
+            ### gpgdir_1.gpg
+            unless ($file =~ m|gpgdir_\d+\.gpg$|) {
+                return &print_errors("[-] File $file not " .
+                    "encrypted and obfuscated");
+            }
+        }
+    }
+    return 1;
+}
+
+sub ascii_recursively_decrypted() {
+    @data_dir_files = ();
+    find(\&find_files, $data_dir);
+    for my $file (@data_dir_files) {
+        if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
+            if ($file =~ m|\.asc$|) {
+                return &print_errors("[-] File $file not encrypted");
+            }
+        }
+    }
+    return 1;
+}
+
+sub obf_recursively_decrypted() {
+    @data_dir_files = ();
+    find(\&find_files, $data_dir);
+    for my $file (@data_dir_files) {
+        if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) {
+            if ($file =~ m|\.asc$|) {
+                return &print_errors("[-] File $file not encrypted");
+            }
+        }
+    }
+    return 1;
+}
+
+sub skipped_hidden_files_dirs() {
+    @data_dir_files = ();
+    find(\&find_files, $data_dir);
+    for my $file (@data_dir_files) {
+        if ($file =~ m|^\.| or $file =~ m|/\.|) {
+            ### check for any .gpg or .asc extensions except
+            ### for the gpgdir_map_file
+            if ($file =~ m|\.gpg$| or $file =~ m|\.asc$|) {
+                return &print_errors("[-] Encrypted hidden file");
+            }
+        }
+    }
+    return 1;
+}
+
+sub obf_skipped_hidden_files_dirs() {
+    @data_dir_files = ();
+    find(\&find_files, $data_dir);
+    for my $file (@data_dir_files) {
+        if ($file =~ m|^\.| or $file =~ m|/\.|) {
+            ### check for any .gpg or .asc extensions except
+            ### for the gpgdir_map_file
+            if ($file !~ m|gpgdir_map_file| and ($file =~ m|\.gpg$|
+                    or $file =~ m|\.asc$|)) {
+                return &print_errors("[-] Encrypted hidden file");
+            }
+        }
+    }
+    return 1;
+}
+
+
+sub find_files() {
+    my $file = $File::Find::name;
+    push @data_dir_files, $file;
+    return;
+}
+
+sub collect_md5sums() {
+    @data_dir_files = ();
+    find(\&find_files, $data_dir);
+    for my $file (@data_dir_files) {
+        if (-f $file) {
+            $md5sums{$file} = md5_base64($file);
+        }
+    }
+    return 1;
+}
+
+sub md5sum_validation() {
+    @data_dir_files = ();
+    find(\&find_files, $data_dir);
+    for my $file (@data_dir_files) {
+        if (-f $file) {
+            if (not defined $md5sums{$file}
+                    or $md5sums{$file} ne md5_base64($file)) {
+                return &print_errors("[-] MD5 sum mis-match for $file");
+            }
+        }
+    }
+    return 1;
+}
+
+sub test_mode() {
+    if (&run_cmd("$gpgdirCmd $default_args --test", $NO_APPEND)) {
+        my $found = 0;
+        open F, "< $current_test_file"
+            or die "[*] Could not open $current_test_file: $!";
+        while (<F>) {
+            if (/Decrypted\s+content\s+matches\s+original/i) {
+                $found = 1;
+                last;
+            }
+        }
+        close F;
+        return 1 if $found;
+    }
+    return &print_errors("[-] Encrypt/decrypt basic --test mode");
+}
+
+sub perl_compilation() {
+    unless (&run_cmd("perl -c $gpgdirCmd", $NO_APPEND)) {
+        return &print_errors("[-] $gpgdirCmd does not compile");
+    }
+    return 1;
+}
+
+sub getopt_test() {
+    if (&run_cmd("$gpgdirCmd --no-such-argument", $NO_APPEND)) {
+        return &print_errors("[-] $gpgdirCmd " .
+                "allowed --no-such-argument on the command line");
+    }
+    return 1;
+}
+
+sub dots_print() {
+    my $msg = shift;
+    &logr($msg);
+    my $dots = '';
+    for (my $i=length($msg); $i < $PRINT_LEN; $i++) {
+        $dots .= '.';
+    }
+    &logr($dots);
+    return;
+}
+
+sub print_errors() {
+    my $msg = shift;
+    &logr("fail ($test_num)\n$msg\n");
+    if (-e $current_test_file) {
+        &logr("    STDOUT and STDERR available in: " .
+            "$current_test_file file.\n");
+        open F, ">> $current_test_file"
+            or die "[*] Could not open $current_test_file: $!";
+        print F "MSG: $msg\n";
+        close F;
+    }
+    return 0;
+}
+
+sub run_cmd() {
+    my ($cmd, $append) = @_;
+
+    if ($append == $APPEND) {
+        open F, ">> $current_test_file"
+            or die "[*] Could not open $current_test_file: $!";
+        print F "CMD: $cmd\n";
+        close F;
+    } else {
+        open F, "> $current_test_file"
+            or die "[*] Could not open $current_test_file: $!";
+        print F "CMD: $cmd\n";
+        close F;
+    }
+    my $rv = ((system "$cmd >> $current_test_file 2>&1") >> 8);
+    if ($rv == 0) {
+        return 1;
+    }
+    return 0;
+}
+
+sub prepare_results() {
+    my $rv = 0;
+    die "[*] $output_dir does not exist" unless -d $output_dir;
+    die "[*] $logfile does not exist, has gpgdir_test.pl been executed?"
+        unless -e $logfile;
+    if (-e $tarfile) {
+        unlink $tarfile or die "[*] Could not unlink $tarfile: $!";
+    }
+
+    ### create tarball
+    system "tar cvfz $tarfile $logfile $output_dir";
+    print "[+] Test results file: $tarfile\n";
+    if (-e $tarfile) {
+        $rv = 1;
+    }
+    return $rv;
+}
+
+sub setup() {
+
+    $|++; ### turn off buffering
+
+    die "[*] $conf_dir directory does not exist." unless -d $conf_dir;
+    unless (-d $output_dir) {
+        mkdir $output_dir or die "[*] Could not mkdir $output_dir: $!";
+    }
+
+    die "[*] Password file $pw_file does not exist" unless -f $pw_file;
+    die "[*] Broken password file $broken_pw_file does not exist"
+        unless -f $broken_pw_file;
+    die "[*] $data_dir/multi-line-ascii file does not exist"
+        unless -f "$data_dir/multi-line-ascii";
+
+    for my $file (glob("$output_dir/cmd*")) {
+        unlink $file or die "[*] Could not unlink($file)";
+    }
+
+    for my $file (glob("$output_dir/*.test")) {
+        unlink $file or die "[*] Could not unlink($file)";
+    }
+
+    for my $file (glob("$output_dir/*.warn")) {
+        unlink $file or die "[*] Could not unlink($file)";
+    }
+
+    for my $file (glob("$output_dir/*.die")) {
+        unlink $file or die "[*] Could not unlink($file)";
+    }
+
+    die "[*] $gpgdirCmd does not exist" unless -e $gpgdirCmd;
+    die "[*] $gpgdirCmd not executable" unless -x $gpgdirCmd;
+
+    if (-e $logfile) {
+        unlink $logfile or die $!;
+    }
+    return;
+}
+
+sub pass() {
+    &logr("pass ($test_num)\n");
+    $successful_tests++;
+    return;
+}
+
+sub logr() {
+    my $msg = shift;
+
+    print STDOUT $msg;
+    open F, ">> $logfile" or die $!;
+    print F $msg;
+    close F;
+    return;
+}
diff --git a/gpgdir/test/output/README b/gpgdir/test/output/README
new file mode 100644 (file)
index 0000000..711063b
--- /dev/null
@@ -0,0 +1,11 @@
+This directory is used by the gpgdir test suite to store test output (both
+stdout and stderr) from various tests against gpgdir.  The gpgdir test suite
+creates files in this directory with a ".N" extension according to each test
+number.
+
+The files in this directory are useful for debugging purposes, and if there is
+a problem running gpgdir on a particular system then the information in this
+directory along with the output of the test suite may provide a clues as to
+why.  If gpgdir appears to not be working properly and you want additional
+help to diagnose the problem, you can tar up the output/ directory and send it
+to Michael Rash at the following email address: mbr@cipherdyne.org.
diff --git a/gpgwrap/LICENSE b/gpgwrap/LICENSE
new file mode 100644 (file)
index 0000000..84c2682
--- /dev/null
@@ -0,0 +1,281 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
diff --git a/gpgwrap/Makefile b/gpgwrap/Makefile
new file mode 100644 (file)
index 0000000..30064df
--- /dev/null
@@ -0,0 +1,9 @@
+MAKE=make
+
+.PHONY: all clean
+
+all:
+       cd src && ${MAKE} all DIET="${DIET}"
+
+clean:
+       cd src && ${MAKE} clean
diff --git a/gpgwrap/NEWS b/gpgwrap/NEWS
new file mode 100644 (file)
index 0000000..5e507e1
--- /dev/null
@@ -0,0 +1,19 @@
+gpgwrap 0.04-20060904
+       * new option: -P
+       * in previous versions the --passphrase-fd option was appended after
+         --homedir <arg> or --options <arg>, now also --homedir=<arg> and
+         --options=<arg> should be handled correctly
+
+gpgwrap 0.03-20050321
+       * new options: -o, -c
+       * do not always ignore the exit code of child processes as in
+         version 0.02 (see manpage for more)
+       * format of verbose output changed
+
+gpgwrap 0.02-20041014
+       * new options: -V, -F, -v, -i, -a
+       * the passphrase may also be given via stdin, via environment
+         variable or via prompt
+
+gpgwrap 0.01-20040601
+       * first official release
diff --git a/gpgwrap/README b/gpgwrap/README
new file mode 100644 (file)
index 0000000..cb3d9d2
--- /dev/null
@@ -0,0 +1,35 @@
+Author:   Karsten Scheibler
+Homepage: http://unusedino.de/gpgwrap/
+eMail:    gpgwrap@unusedino.de
+
+
+  =======================
+  [1] GENERAL INFORMATION
+  =======================
+
+
+See doc/gpgwrap.1 for more
+
+
+  ============================
+  [2] SHORT BUILD INSTRUCTIONS
+  ============================
+
+
+To build gpgwrap a simple 'make' should be enough. You may build gpgwrap with
+dietlibc, just set and export the environment variable DIET to the location
+of your 'diet' binary and run 'make'. Dietlibc is a libc that is optimized
+for small size, look at http://www.fefe.de/dietlibc/ for more.
+
+
+  ===================================
+  [3] SHORT INSTALLATION INSTRUCTIONS
+  ===================================
+
+
+Copy the files from bin/ and doc/ to your favorite directories, for example:
+
+chown root bin/* doc/*.1
+chgrp root bin/* doc/*.1
+(cd bin && tar cf - *)   | (cd /usr/bin && tar xvf -)
+(cd doc && tar cf - *.1) | (cd /usr/man/man1 && tar xvf -)
diff --git a/gpgwrap/doc/gpgwrap.1 b/gpgwrap/doc/gpgwrap.1
new file mode 100644 (file)
index 0000000..98cfe9e
--- /dev/null
@@ -0,0 +1,232 @@
+.ds Q" ""
+.de Vb
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve
+.ft R
+
+.fi
+..
+
+.TH gpgwrap 1 "gpgwrap 0.04"
+
+.SH NAME
+gpgwrap \- a small wrapper for gpg
+
+.SH SYNOPSIS
+.B gpgwrap
+\-V
+
+.B gpgwrap
+\-P
+[\-v]
+[\-i]
+[\-a]
+[\-p \fI<file>\fR]
+
+.B gpgwrap
+\-F
+[\-v]
+[\-i]
+[\-a]
+[\-c]
+[\-p \fI<file>\fR]
+[\-o \fI<name>\fR]
+[\-\-]
+\fI<file>\fR [\fI<file>\fR ... ]
+
+.B gpgwrap
+[\-v]
+[\-i]
+[\-a]
+[\-p \fI<file>\fR]
+[\-o \fI<name>\fR]
+[\-\-]
+\fBgpg\fR [gpg options]
+
+.SH DESCRIPTION
+.PP
+The GNU Privacy Guard (\fBgpg\fR) supplies the option \-\-passphrase\-fd. This instructs \fBgpg\fR to read the passphrase from the given file descriptor. Usually this file descriptor is opened before \fBgpg\fR is executed via \fBexecvp(3)\fR. Exactly that is what \fBgpgwrap\fR is doing. The passphrase may be passed to \fBgpgwrap\fR in 4 ways:
+.RS
+.IP * 2
+as file path, whereat the passphrase is stored as plain text in the file
+.IP * 2
+it is piped from another program to the stdin of \fBgpgwrap\fR
+.IP * 2
+through the \fBGPGWRAP_PASSPHRASE\fR environment variable
+.IP * 2
+\fBgpgwrap\fR prompts for it
+.RE
+
+With no precautions the first point undermines the secure infrastructure \fBgpg\fR provides. But in pure batch oriented environments this may be what you want. Otherwise if you are willing to enter passphrases once and don't want them to be stored as plain text in a file \fBgpg\-agent\fR is what you are looking for. Another security objection could be the use of the environment variable \fBGPGWRAP_PASSPHRASE\fR which contains the passphrase and may be read by other processes of the same user.
+
+.SH OPTIONS
+.IP "\-V, \-\-version" 8
+Print out version and exit.
+.IP "\-P, \-\-print" 8
+Get the passphrase and print it mangled to stdout.
+.IP "\-F, \-\-file" 8
+Read \fBgpg\fR commands from the given files. If \fI<file>\fR is \- it is read from stdin. Exactly one command per line is expected. The given line is handled in the following way:
+.RS
+.IP * 2
+In the first place the passphrase is mangled. This means that unusual characters are replaced by their backslash escaped octal numbers.
+.IP * 2
+Secondly the mangled passphrase is stored in the environment variable \fBGPGWRAP_PASSPHRASE\fR.
+.IP * 2
+\*(Q"exec gpgwrap \-\- \*(Q" is prepended to each line, before the result is passed as argument to \*(Q"sh \-c\*(Q".
+.RE
+.IP "\-h, \-\-help" 8
+Print out usage information.
+.IP "\-v, \-\-verbose" 8
+Increase verbosity level.
+.IP "\-i, \-\-interactive" 8
+Always prompt for passphrase (ignores \-p and the environment variable).
+.IP "\-a, \-\-ask\-twice" 8
+Ask twice if prompting for a passphrase.
+.IP "\-c, \-\-check\-exit\-code" 8
+While reading gpg commands from a file, \fBgpgwrap\fR ignores per default the exit code of its child processes. This option enables the check of the exit code. If a child terminates abnormal or with an exit code not equal 0 \fBgpgwrap\fR stops immediately and does return with this exit code. See also section \fBBUGS\fR.
+.IP "\-p \fI<file>\fR, \-\-passphrase\-file \fI<file>\fR" 8
+Read passphrase from \fI<file>\fR. If \fI<file>\fR is \- it is read from stdin. The passphrase is expected to be in plain text. If this option is not given the passphrase will be taken either from the environment variable \fBGPGWRAP_PASSPHRASE\fR or it will be prompted on the controlling tty if the environment variable is not set.
+.IP "\-o \fI<name>\fR, \-\-option\-name \fI<name>\fR" 8
+Specify the name of the \*(Q"\-\-passphrase\-fd\*(Q" option understood by the program to be executed. This is useful if you want to use \fBgpgwrap\fR in combination with other programs than \fBgpg\fR.
+
+.SH LIMITATIONS
+The given passphrase is subject to several limitations depending on the way it was passed to \fBgpgwrap\fR:
+.RS
+.IP * 2
+There is a size limitation: the passphrase should be not larger than some kilobytes (examine the source code for the exact limit).
+.IP * 2
+\fBgpgwrap\fR allows you to use all characters in a passphrase even \\000, but this does not mean that \fBgpg\fR will accept it. \fBgpg\fR may reject your passphrase or may only read a part of it, if it contains characters like \\012 (in C also known as \\n).
+.IP * 2
+If you set the environment variable \fBGPGWRAP_PASSPHRASE\fR you should take special care with the backslash character, because \fBgpgwrap\fR uses backslash to escape octal numbers, (see option \-F). Therefore write backslash itself as octal number: \\134.
+.RE
+
+.SH EXAMPLES
+.IP "1." 8
+.Vb
+\&\fBgpgwrap\fR \-p /path/to/a/secret/file  \\
+\&\fBgpg\fR \-c \-z 0 \-\-batch \-\-no\-tty  \\
+\&    \-\-cipher\-algo blowfish < infile > outfile
+.Ve
+Read passphrase from /path/to/a/secret/file and execute \fBgpg\fR to do symmetric encryption of infile and write it to outfile.
+
+.IP "2." 8
+.Vb
+\&\fBgpgwrap\fR \-i \-a  \\
+\&\fBgpg\fR \-c \-z 0 \-\-batch \-\-no\-tty  \\
+\&    \-\-cipher\-algo blowfish < infile > outfile
+.Ve
+Same as above except that \fBgpgwrap\fR prompts twice for the passphrase.
+
+.IP "3." 8
+.Vb
+\&\fBgpgwrap\fR \-F \-i \- <<EOL
+\&\fBgpg\fR \-\-decrypt \-\-batch \-\-no\-tty < \*(Q"$HOME/infile1\*(Q" > \*(Q"$HOME/outfile1\*(Q"
+\&\fBgpg\fR \-\-decrypt \-\-batch \-\-no\-tty < \*(Q"$HOME/infile2\*(Q" > \*(Q"$HOME/outfile2\*(Q"
+\&\fBgpg\fR \-\-decrypt \-\-batch \-\-no\-tty < \*(Q"$HOME/infile3\*(Q" > \*(Q"$HOME/outfile3\*(Q"
+\&\fBgpg\fR \-\-decrypt \-\-batch \-\-no\-tty < \*(Q"$HOME/infile4\*(Q" > \*(Q"$HOME/outfile4\*(Q"
+\&EOL
+.Ve
+\fBgpgwrap\fR prompts for the passphrase and executes four instances of \fBgpg\fR to decrypt the given files.
+
+.IP "4." 8
+.Vb
+\&\fBGPGWRAP_PASSPHRASE\fR=\*(Q"mysecretpassphrase\*(Q"
+\&export \fBGPGWRAP_PASSPHRASE\fR
+\&\fBgpgwrap\fR \-F \-c \-v /tmp/cmdfile1 \- /tmp/cmdfile2 <<EOL
+\&\fBgpg\fR \-\-decrypt \-\-batch \-\-no\-tty < \*(Q"$HOME/infile1\*(Q" > \*(Q"$HOME/outfile1\*(Q"
+\&\fBgpg\fR \-\-decrypt \-\-batch \-\-no\-tty < \*(Q"$HOME/infile2\*(Q" > \*(Q"$HOME/outfile2\*(Q"
+\&\fBgpg\fR \-\-decrypt \-\-batch \-\-no\-tty < \*(Q"$HOME/infile3\*(Q" > \*(Q"$HOME/outfile3\*(Q"
+\&\fBgpg\fR \-\-decrypt \-\-batch \-\-no\-tty < \*(Q"$HOME/infile4\*(Q" > \*(Q"$HOME/outfile4\*(Q"
+\&EOL
+.Ve
+Same as above except that \fBgpgwrap\fR gets the passphrase via the environment variable, reads commands additionally from other files and checks the exit code of every \fBgpg\fR instance. This means if one \fBgpg\fR command has a non zero exit code, no further commands are executed. Furthermore \fBgpgwrap\fR produces verbose output.
+
+.IP "5." 8
+.Vb
+\&\fBGPGWRAP_PASSPHRASE\fR=\*(Q"$(\fBgpgwrap\fR \-P \-i \-a)\*(Q"
+\&export \fBGPGWRAP_PASSPHRASE\fR
+\&
+\&\fBfind\fR . \-maxdepth 1 \-type f |
+\&while read FILE; do
+\&    FILE2=\*(Q"$FILE.bz2.gpg\*(Q"
+\&    \fBbzip2\fR \-c \*(Q"$FILE\*(Q" |
+\&    \fBgpgwrap\fR \fBgpg\fR \-c \-z 0 \-\-batch \-\-no\-tty  \\
+\&        \-\-cipher\-algo blowfish > \*(Q"$FILE2\*(Q" &&
+\&    \fBtouch\fR \-r \*(Q"$FILE\*(Q" \*(Q"$FILE2\*(Q" &&
+\&    \fBrm\fR \-f \*(Q"$FILE\*(Q"
+\&done
+.Ve
+Read in passphrase, compress all files in the current directory, encrypt them and keep date from original file.
+
+.IP "6." 8
+.Vb
+\&\fBfind\fR . \-maxdepth 1 \-type f \-name '*.bz2.gpg' |
+\&\fBawk\fR '{
+\&    printf(\*(Q"gpg \-\-decrypt \-\-batch \-\-no\-tty \-\-quiet \*(Q");
+\&    printf(\*(Q"\-\-no\-secmem\-warning < %s\\n\*(Q", $0);
+\&    }' |
+\&\fBgpgwrap\fR \-F \-i \-c \- |
+\&\fBbzip2\fR \-d \-c \- |
+\&\fBgrep\fR \-i 'data'
+.Ve
+Decrypt all *.bz2.gpg files in the current directory, decompress them and print out all occurances of data. If you pipe the result to \fBless\fR you get into trouble because \fBgpgwrap\fR and \fBless\fR try to read from the TTY at the same time. In such a case it is better to use the environment variable to give the passphrase (the example above shows how to do this).
+
+.IP "7." 8
+.Vb
+\&\fBGPGWRAP_PASSPHRASE\fR=\*(Q"$(\fBgpgwrap\fR \-P \-i \-a)\*(Q"
+\&export \fBGPGWRAP_PASSPHRASE\fR
+\&
+\&\fBgpgwrap\fR \-P |
+\&\fBssh\fR \-C \-x \-P \-l user host \*(Q"
+\&    \fBGPGWRAP_PASSPHRASE\fR=\\\*(Q"\\$(\fBcat\fR)\\\*(Q"
+\&    ...
+\&    \*(Q"
+.Ve
+Prompt for a passphrase twice and write it to the \fBGPGWRAP_PASSPHRASE\fR environment variable.
+
+.IP "8." 8
+.Vb
+\&\fBecho\fR \-n \*(Q"Passphrase: \*(Q"
+\&\fBstty\fR \-echo
+\&read \fBGPGWRAP_PASSPHRASE\fR
+\&\fBecho\fR
+\&\fBstty\fR echo
+\&export \fBGPGWRAP_PASSPHRASE\fR
+.Ve
+Another way to prompt manually for the passphrase. It was needed in combination with older versions of \fBgpgwrap\fR, because they did not upport \-P. Be aware that with this method no automatic conversion to backslash escaped octal numbers takes place.
+
+.IP "9." 8
+.Vb
+\&\fBecho\fR \*(Q"mysecretpassphrase\*(Q" |
+\&\fBgpg\fR \-\-batch \-\-no\-tty \-\-passphrase\-fd 0  \\
+\&    \-\-output outfile \-\-decrypt infile
+.Ve
+Cheap method to give passphrase to \fBgpg\fR without \fBgpgwrap\fR. Note that you can't use stdin to pass a file to \fBgpg\fR, because stdin is already used for the passphrase.
+
+.IP "10." 8
+.Vb
+\&\fBgpg\fR \-\-batch \-\-no\-tty  \\
+\&    \-\-passphrase\-fd 3 3< /path/to/a/secret/file  \\
+\&    < infile > outfile
+.Ve
+This is a more advanced method to give the passphrase, it is equivalent to Option \-p of \fBgpgwrap\fR. This example should at least work with the bash.
+
+.IP "11." 8
+.Vb
+\&\fBgpg\fR \-\-batch \-\-no\-tty \-\-passphrase\-fd 3  \\
+\&    3< <(echo \*(Q"mysecretpassphrase\*(Q")  \\
+\&    < infile > outfile
+.Ve
+Like above, but the passphrase is given directly. This example should at least work with the bash.
+
+.SH BUGS
+In version 0.02 of \fBgpgwrap\fR the exit code of \fBgpg\fR was only returned if \fBgpgwrap\fR read the passphrase from a file. Since version 0.03, only \-F omits exit code checking by default, but it can be enabled with \-c.
+
+.SH "SEE ALSO"
+\fBgpg\fR, \fBgpg\-agent\fR
+
+.SH AUTHOR
+Karsten Scheibler
diff --git a/gpgwrap/src/Makefile b/gpgwrap/src/Makefile
new file mode 100644 (file)
index 0000000..2527174
--- /dev/null
@@ -0,0 +1,15 @@
+CC=${DIET} gcc -s -Wall -O2 -fomit-frame-pointer
+STRIP=strip -R .note -R .comment
+RM=rm -f
+TARGET=../bin/gpgwrap
+
+.PHONY: all clean
+
+all: ${TARGET}
+
+${TARGET}: gpgwrap.c
+       ${CC} -o ${TARGET} gpgwrap.c
+       ${STRIP} ${TARGET} 2>/dev/null || true
+
+clean:
+       ${RM} ${TARGET}
diff --git a/gpgwrap/src/gpgwrap.c b/gpgwrap/src/gpgwrap.c
new file mode 100644 (file)
index 0000000..4cfc9a8
--- /dev/null
@@ -0,0 +1,989 @@
+/****************************************************************************
+ ****************************************************************************
+ *
+ * gpgwrap.c
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+
+
+
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "version.h"
+
+
+
+#define PROGRAM_NAME                   "gpgwrap"
+#define VERSION_STRING                 PROGRAM_NAME " " VERSION "-" VERSION_DATE
+#define EXEC_ARGV_SIZE                 1024
+#define PASSPHRASE_BUFFER_SIZE         0x10000
+#define LIST_BUFFER_SIZE               0x10000
+#define CMDLINE_MAX_FILES              1024
+#define GPGWRAP_MODE_DEFAULT           0
+#define GPGWRAP_MODE_VERSION           1
+#define GPGWRAP_MODE_FILE              2
+#define GPGWRAP_MODE_PRINT             3
+
+
+
+static char                            program_name[] = PROGRAM_NAME;
+static char                            environ_name[] = "GPGWRAP_PASSPHRASE";
+static int                             mode = GPGWRAP_MODE_DEFAULT;
+static int                             verbose = 0;
+static int                             interactive = 0;
+static int                             ask_twice = 0;
+static int                             check_exit_code = 0;
+static char                            *calling_path = NULL;
+static char                            *environ_var = NULL;
+static char                            *passphrase_file = NULL;
+static char                            *option_name = "--passphrase-fd";
+static char                            *files[CMDLINE_MAX_FILES];
+static int                             nfiles = 0;                             
+static char                            **gpg_cmd = NULL;
+
+
+
+/****************************************************************************
+ * do_perror
+ ****************************************************************************/
+static void
+do_perror(
+       void)
+
+       {
+       perror(program_name);
+       exit(1);
+       }
+
+
+
+/****************************************************************************
+ * do_error
+ ****************************************************************************/
+#define do_error(args...)                                              \
+       do                                                              \
+               {                                                       \
+               fprintf(stderr, "%s: ", program_name);                  \
+               fprintf(stderr, args);                                  \
+               fprintf(stderr, "\n");                                  \
+               exit(1);                                                \
+               }                                                       \
+       while (0)
+
+
+
+/****************************************************************************
+ * do_warning
+ ****************************************************************************/
+#define do_warning(args...)                                            \
+       do                                                              \
+               {                                                       \
+               fprintf(stderr, "%s: ", program_name);                  \
+               fprintf(stderr, args);                                  \
+               fprintf(stderr, "\n");                                  \
+               }                                                       \
+       while (0)
+
+
+
+/****************************************************************************
+ * do_error_oom
+ ****************************************************************************/
+static void
+do_error_oom(
+       void)
+
+       {
+       do_error("could not allocate memory");
+       }
+
+
+
+/****************************************************************************
+ * do_error_too_long
+ ****************************************************************************/
+static void
+do_error_too_long(
+       void)
+
+       {
+       do_error("passphrase too long");
+       }
+
+
+
+/****************************************************************************
+ * do_verbose
+ ****************************************************************************/
+#define do_verbose(level, args...)                                     \
+       do                                                              \
+               {                                                       \
+               if (verbose < level) break;                             \
+               fprintf(stderr, "%s[%d]: ", program_name, getpid());    \
+               fprintf(stderr, args);                                  \
+               fprintf(stderr, "\n");                                  \
+               }                                                       \
+       while (0)
+
+
+
+/****************************************************************************
+ * do_verbose_start
+ ****************************************************************************/
+#define do_verbose_start(level, args...)                               \
+       do                                                              \
+               {                                                       \
+               if (verbose < level) break;                             \
+               fprintf(stderr, "%s[%d] ", program_name, getpid());     \
+               fprintf(stderr, args);                                  \
+               }                                                       \
+       while (0)
+
+
+
+/****************************************************************************
+ * do_verbose_append
+ ****************************************************************************/
+#define do_verbose_append(level, args...)                              \
+       do                                                              \
+               {                                                       \
+               if (verbose < level) break;                             \
+               fprintf(stderr, args);                                  \
+               }                                                       \
+       while (0)
+
+
+
+/****************************************************************************
+ * do_snprintf
+ ****************************************************************************/
+#define do_snprintf(string, max, args...)      do_snprintf2(snprintf(string, max, args), max)
+
+
+
+/****************************************************************************
+ * do_snprintf2
+ ****************************************************************************/
+static int
+do_snprintf2(
+       int                             len,
+       int                             max)
+
+       {
+       if ((len == -1) || (len >= max)) do_error("do_snprintf() size exceeded");
+       return (len);
+       }
+
+
+
+/****************************************************************************
+ * mangle_passphrase
+ ****************************************************************************/
+static int
+mangle_passphrase(
+       char                            *buffer,
+       int                             size,
+       char                            *mbuffer,
+       int                             msize)
+
+       {
+       char                            c;
+       int                             i, j, c1;
+
+       /*
+        * look for "unusual" characters and convert them to
+        * backslash escaped octal numbers
+        */
+
+       for (i = j = 0, msize--; i < size; i++)
+               {
+               c = buffer[i];
+               if (j >= msize) goto error;
+               if ((c < '+') || ((c > ';') && (c < 'A')) ||
+                       ((c > 'Z') && (c != '_') && (c < 'a')) ||
+                       ((c > 'z') && (c != '~')))
+                       {
+                       c1 = (unsigned char) c;
+                       if (j >= msize - 4) goto error;
+                       mbuffer[j++] = '\\';
+                       mbuffer[j++] = '0' + (c1 >> 6);
+                       mbuffer[j++] = '0' + ((c1 >> 3) & 7);
+                       mbuffer[j++] = '0' + (c1 & 7);
+                       }
+               else mbuffer[j++] = c;
+               }
+       mbuffer[j] = '\0';
+       return (j);
+error:
+       do_error("could not mangle passphrase");
+       }
+
+
+
+/****************************************************************************
+ * unmangle_passphrase
+ ****************************************************************************/
+static int
+unmangle_passphrase(
+       char                            *buffer,
+       int                             size)
+
+       {
+       char                            c;
+       int                             i, j, c1, c2, c3;
+
+       /* replace backslash escaped octal numbers */
+
+       for (i = j = 0; j < size; i++)
+               {
+               c = buffer[j++];
+               if (c == '\\')
+                       {
+                       if (j > size - 3) goto error;
+                       c1 = buffer[j++];
+                       c2 = buffer[j++];
+                       c3 = buffer[j++];
+                       if ((c1 < '0') || (c1 > '3') || (c2 < '0') || (c2 > '7') ||
+                               (c3 < '0') || (c3 > '7')) goto error;
+                       c1 -= '0';
+                       c2 -= '0';
+                       c3 -= '0';
+                       c = (char) (((c1 << 6) | (c2 << 3) | c3) & 0xff);
+                       }
+               buffer[i] = c;
+               }
+       return (i);
+error:
+       do_error("could not unmangle passphrase");
+       }
+
+
+
+/****************************************************************************
+ * read_passphrase
+ ****************************************************************************/
+static int
+read_passphrase(
+       char                            *buffer,
+       int                             size)
+
+       {
+       int                             fd, len, i;
+
+       do_verbose(2, "reading passphrase from file '%s'", passphrase_file);
+       if (strcmp(passphrase_file, "-") == 0) fd = STDIN_FILENO;
+       else fd = open(passphrase_file, O_RDONLY);
+       if (fd == -1) do_perror();
+       for (len = 0; (i = read(fd, buffer, size)) > 0; len += i)
+               {
+               buffer += i;
+               size -= i;
+               if (size == 0) do_error_too_long();
+               }
+       if (i == -1) do_perror();
+       if (close(fd) == -1) do_perror();
+       return (len);
+       }
+
+
+
+/****************************************************************************
+ * prompt_passphrase
+ ****************************************************************************/
+static int
+prompt_passphrase(
+       char                            *buffer,
+       int                             size)
+
+       {
+       int                             len, len2;
+       int                             fd;
+       struct termios                  t, tt;
+       char                            tty[] = "/dev/tty";
+       char                            pp[] = "Passphrase: ";
+       char                            pp2[] = "\nPassphrase (again): ";
+       char                            *buffer2;
+
+       /*
+        * don't touch stdin, just open the controlling tty and ask for the
+        * passphrase
+        */
+
+       do_verbose(2, "opening '%s' to prompt for passphrase", tty);
+       fd = open(tty, O_RDWR);
+       if (fd == -1) do_perror();
+       write(fd, pp, strlen(pp));
+       tcgetattr(fd, &t);
+       tt = t;
+       tt.c_lflag &= ~ECHO;
+       tcsetattr(fd, TCSAFLUSH, &tt);
+       len = read(fd, buffer, size);
+       if (len == -1) do_perror();
+       if ((ask_twice) && (len < size))
+               {
+               buffer2 = (char *) alloca(sizeof (char) * size);
+               if (buffer2 == NULL) do_error_oom();
+               write(fd, pp2, strlen(pp2));
+               len2 = read(fd, buffer2, size);
+               if (len2 == -1) do_perror();
+               write(fd, "\n", 1);
+               tcsetattr(fd, TCSAFLUSH, &t);
+               if ((len != len2) || (memcmp(buffer, buffer2, len) != 0)) do_error("passphrases are not the same");
+               }
+       else
+               {
+               write(fd, "\n", 1);
+               tcsetattr(fd, TCSAFLUSH, &t);
+
+               /*
+                * if the above read() returns with len == size, we don't
+                * know if there are more bytes, so we assume passphrase is
+                * too long
+                */
+
+               if (len >= size) do_error_too_long();
+               }
+       if (close(fd) == -1) do_perror();
+
+       /* ignore trailing \012 */
+
+       return (len - 1);
+       }
+
+
+
+/****************************************************************************
+ * environ_or_prompt
+ ****************************************************************************/
+static int
+environ_or_prompt(
+       char                            *buffer,
+       int                             size)
+
+       {
+       int                             len, len2;
+       char                            *env;
+
+       env = getenv(environ_name);
+       if ((env != NULL) && (! interactive))
+               {
+               do_verbose(2, "got passphrase from environment variable: %s=%s", environ_name, env);
+
+               /*
+                * first unmangle the content of the environment
+                * variable inplace, then clear the memory
+                */
+
+               len2 = strlen(env);
+               len = unmangle_passphrase(env, len2);
+               if (len > size) do_error_too_long();
+               memcpy(buffer, env, len);
+               memset(env, 0, len2);
+               }
+       else len = prompt_passphrase(buffer, size);
+       return (len);
+       }
+
+
+
+/****************************************************************************
+ * do_wait
+ ****************************************************************************/
+static void
+do_wait(
+       void)
+
+       {
+       int                             status, value = 1;
+
+       do_verbose(2, "waiting for child");
+       wait(&status);
+       if (! check_exit_code) return;
+       do_verbose(2, "checking child exit code");
+       if (! WIFEXITED(status)) goto out;
+       value = WEXITSTATUS(status);
+       if (value == 0) return;
+       do_verbose(2, "child process terminated abnormal, exiting");
+out:
+       exit(value);
+       }
+
+
+
+/****************************************************************************
+ * do_fork
+ ****************************************************************************/
+static int
+do_fork(
+       char                            *buffer,
+       int                             size)
+
+       {
+       int                             fds[2], i;
+
+       /*
+        * parent will write passphrase to the opened pipe, child will
+        * pass the fd to gpg
+        */
+
+       if (pipe(fds) == -1) do_perror();
+       do_verbose(2, "forking");
+       switch (fork())
+               {
+               case -1:
+                       do_perror();
+               case 0:
+                       /* child */
+
+                       if (close(fds[1]) == -1) do_perror();
+                       return (fds[0]);
+               default:
+                       break;
+               }
+
+       /* parent */
+
+       signal(SIGPIPE, SIG_IGN);
+       if (close(fds[0]) == -1) do_perror();
+       while (size > 0)
+               {
+               i = write(fds[1], buffer, size);
+               if ((i == -1) && (errno == EPIPE)) break;
+               if (i == -1) do_perror();
+               buffer += i;
+               size -= i;
+               }
+       if (size > 0) do_warning("only partial passphrase written");
+       if (close(fds[1]) == -1) do_perror();
+       do_wait();
+       return (-1);
+       }
+
+
+
+/****************************************************************************
+ * get_passphrase_fd
+ ****************************************************************************/
+static int
+get_passphrase_fd(
+       void)
+
+       {
+       int                             fd, len;
+       char                            buffer[PASSPHRASE_BUFFER_SIZE];
+
+       if ((passphrase_file == NULL) || (interactive))
+               {
+               len = environ_or_prompt(buffer, sizeof (buffer));
+               fd = do_fork(buffer, len);
+               }
+       else if (strcmp(passphrase_file, "-") == 0)
+               {
+               len = read_passphrase(buffer, sizeof (buffer));
+               fd = do_fork(buffer, len);
+               }
+       else
+               {
+               do_verbose(2, "opening file '%s' to pass fd", passphrase_file);
+               fd = open(passphrase_file, O_RDONLY);
+               if (fd == -1) do_perror();
+               }
+       return (fd);
+       }
+
+
+
+/****************************************************************************
+ * get_passphrase
+ ****************************************************************************/
+static int
+get_passphrase(
+       char                            *buffer,
+       int                             size)
+
+       {
+       int                             len;
+
+       if ((passphrase_file == NULL) || (interactive)) len = environ_or_prompt(buffer, size);
+       else len = read_passphrase(buffer, size);
+       return (len);
+       }
+
+
+
+/****************************************************************************
+ * do_putenv
+ ****************************************************************************/
+static void
+do_putenv(
+       char                            *buffer,
+       int                             len)
+
+       {
+       int                             size, len2;
+       char                            *old_var;
+
+       /*
+        * putenv() only stores the given pointer in **environ, so we have
+        * to use malloc here
+        */
+
+       size = strlen(environ_name) + (4 * len) + 2;
+       old_var = environ_var;
+       environ_var = (char *) malloc(sizeof (char) * size);
+       if (environ_var == NULL) do_error_oom();
+       len2 = do_snprintf(environ_var, size, "%s=", environ_name);
+       if ((buffer != NULL) && (len > 0)) mangle_passphrase(buffer, len, &environ_var[len2], size - len2);
+       do_verbose(2, "setting environment variable: %s", environ_var);
+       if (putenv(environ_var) == -1) do_perror();
+       if (old_var != NULL) free(old_var);
+       }
+
+
+
+/****************************************************************************
+ * do_exec
+ ****************************************************************************/
+static void
+do_exec(
+       char                            **argv,
+       int                             clear)
+
+       {
+       if (clear) do_putenv(NULL, 0);
+       if (verbose > 0)
+               {
+               int                     i;
+
+               do_verbose_start(1, "executing:");
+               for (i = 0; argv[i] != NULL; i++) do_verbose_append(1, " %s", argv[i]);
+               do_verbose_append(1, "\n");
+               }
+       execvp(argv[0], argv);
+
+       /* only reached if execvp fails */
+
+       do_perror();
+       }
+
+
+
+/****************************************************************************
+ * exec_gpg
+ ****************************************************************************/
+static void
+exec_gpg(
+       void)
+
+       {
+       int                             fd;
+       int                             i, j, k;
+       char                            fd_num[32];
+       char                            *argv[EXEC_ARGV_SIZE];
+       char                            homedir_eq[] = "--homedir=";
+       char                            options_eq[] = "--options=";
+
+       /*
+        * get fd to read passphrase from, parent will return with fd == -1
+        * after fork
+        */
+
+       fd = get_passphrase_fd();
+       if (fd == -1) return;
+
+       /* create argv for execvp */
+
+       do_snprintf(fd_num, sizeof (fd_num), "%d", fd);
+       for (i = 0, j = 0, k = 1; gpg_cmd[i] != NULL; i++, k--)
+               {
+
+               /*
+                * check if there is enough space to store option_name
+                * and fd_num
+                */
+
+               if (i >= (EXEC_ARGV_SIZE - 4)) do_error("too many gpg arguments specified");
+               if (strcmp(gpg_cmd[i], option_name) == 0) do_error("gpg command already has a '%s' option", option_name);
+               if (k == 0)
+                       {
+                       if ((strncmp(gpg_cmd[i], homedir_eq, sizeof (homedir_eq) - 1) == 0) || (strncmp(gpg_cmd[i], options_eq, sizeof (options_eq) - 1) == 0)) k = 1;
+                       else if ((strcmp(gpg_cmd[i], "--homedir") == 0) || (strcmp(gpg_cmd[i], "--options") == 0)) k = 2;
+                       else
+                               {
+                               argv[j++] = option_name;
+                               argv[j++] = fd_num;
+                               }
+                       }
+               argv[j++] = gpg_cmd[i];
+               }
+       if (k >= 0)
+               {
+               argv[j++] = option_name;
+               argv[j++] = fd_num;
+               }
+       argv[j] = NULL;
+       do_exec(argv, 1);
+       }
+
+
+
+/****************************************************************************
+ * exec_line
+ ****************************************************************************/
+static void
+exec_line(
+       char                            *line)
+
+       {
+       char                            shell_cmd[LIST_BUFFER_SIZE];
+       char                            verbose_string[128] = "";
+       char                            *argv[] = { "sh", "-c", NULL, NULL };
+       int                             fds[2], i;
+
+       /* fork a child and disallow it to read stdin from parent */
+
+       if (pipe(fds) == -1) do_perror();
+       do_verbose(1, "forking");
+       switch (fork())
+               {
+               case -1:
+                       do_perror();
+               case 0:
+                       break;
+               default:
+                       /* parent */
+
+                       if (close(fds[0]) == -1) do_perror();
+                       if (close(fds[1]) == -1) do_perror();
+                       do_wait();
+                       return;
+               }
+
+       /* child */
+
+       if (close(fds[1]) == -1) do_perror();
+       if (fds[0] != STDIN_FILENO) dup2(fds[0], STDIN_FILENO);
+
+       /* create argv for execvp */
+
+       for (i = 0; i < verbose; i++)
+               {
+               if (strlen(verbose_string) >= sizeof (verbose_string) - 4) break;
+               strcat(verbose_string, " -v");
+               }
+       do_snprintf(shell_cmd, sizeof (shell_cmd), "exec %s%s -o %s -- %s",
+               calling_path, verbose_string, option_name, line);
+       argv[2] = shell_cmd;
+       do_exec(argv, 0);
+       }
+
+
+
+/****************************************************************************
+ * exec_list
+ ****************************************************************************/
+static void
+exec_list(
+       char                            *path,
+       char                            *buffer,
+       int                             len)
+
+       {
+       int                             fd;
+       char                            lbuffer[LIST_BUFFER_SIZE];
+       int                             inuse, start, free, nread, llen;
+       char                            *line, *next_line;
+
+       /* open file */
+
+       do_verbose(1, "reading gpg commands from file: '%s'", path);
+       if (strcmp(path, "-") == 0) fd = STDIN_FILENO;
+       else fd = open(path, O_RDONLY);
+       if (fd == -1) do_perror();
+
+       /* export passphrase to environment */
+
+       do_putenv(buffer, len);
+
+       /* read gpg commands */
+
+       for (inuse = 0, free = LIST_BUFFER_SIZE; (nread = read(fd, &lbuffer[inuse], free)) > 0; )
+               {
+               inuse += nread;
+               for (line = lbuffer; (next_line = memchr(line, '\n', inuse)) != NULL; )
+                       {
+                       *next_line = '\0';
+                       llen = (int) (next_line - line) + 1;
+                       if (llen != strlen(line) + 1) do_error("line contains \\0 character");
+                       exec_line(line);
+                       inuse -= llen;
+                       line = next_line + 1;
+                       }
+               start = (int) (line - lbuffer);
+               if ((start == 0) && (inuse == LIST_BUFFER_SIZE)) do_error("line too long");
+               if ((start > 0) && (inuse > 0)) memmove(lbuffer, &lbuffer[start], inuse);
+               free = LIST_BUFFER_SIZE - inuse;
+               }
+
+       /* check for error while read() */
+
+       if (nread == -1) do_perror();
+       if (close(fd) == -1) do_perror();
+
+       /* check if there are bytes left */
+
+       if (inuse > 0) do_error("last line incomplete");
+       }
+
+
+
+/****************************************************************************
+ * cmdline_fill_space
+ ****************************************************************************/
+static void
+cmdline_fill_space(
+       char                            *s)
+
+       {
+       while (*s != '\0') *s++ = ' ';
+       }
+
+
+
+/****************************************************************************
+ * cmdline_usage
+ ****************************************************************************/
+static void
+cmdline_usage(
+       void)
+
+       {
+       char                            space1[] = VERSION_STRING;
+       char                            space2[] = PROGRAM_NAME;
+
+       cmdline_fill_space(space1);
+       cmdline_fill_space(space2);
+       printf(VERSION_STRING " | written by Karsten Scheibler\n"
+               "%s | http://unusedino.de/gpgwrap/\n"
+               "%s | gpgwrap@unusedino.de\n\n"
+               "Usage: %s -V\n"
+               "or:    %s -P [-v] [-i] [-a] [-p <file>]\n"
+               "or:    %s -F [-v] [-i] [-a] [-c] [-p <file>] [-o <name>]\n"
+               "       %s    [--] <file> [<file> ... ]\n"
+               "or:    %s [-v] [-i] [-a] [-p <file>] [-o <name>]\n"
+               "       %s [--] gpg [gpg options]\n\n"
+               "  -V          print out version\n"
+               "  -P          get the passphrase and print it mangled to stdout\n"
+               "  -F          read gpg commands from file\n"
+               "  -v          be more verbose\n"
+               "  -i          be interactive, always prompt for passphrase\n"
+               "  -a          ask twice if prompting for passphrase\n"
+               "  -c          check exit code of child processes\n"
+               "  -p <file>   read passphrase from <file>\n"
+               "  -o <name>   specify name of \"--passphrase-fd\" option\n"
+               "  -h          this help\n",
+               space1, space1, program_name, program_name, program_name,
+               space2, program_name, space2);
+       exit(0);
+       }
+
+
+
+/****************************************************************************
+ * cmdline_check_arg
+ ****************************************************************************/
+static char *
+cmdline_check_arg(
+       char                            *msg,
+       char                            *file)
+
+       {
+       if (file == NULL) do_error("%s expects a file name", msg);
+       return (file);
+       }
+
+
+
+/****************************************************************************
+ * cmdline_check_stdin
+ ****************************************************************************/
+static char *
+cmdline_check_stdin(
+       char                            *msg,
+       char                            *file)
+
+       {
+       static int                      stdin_count = 0;
+
+       cmdline_check_arg(msg, file);
+       if (strcmp(file, "-") == 0) stdin_count++;
+       if (stdin_count > 1) do_error("%s used stdin although already used before", msg);
+       return (file);
+       }
+
+
+
+/****************************************************************************
+ * cmdline_parse
+ ****************************************************************************/
+static void
+cmdline_parse(
+       int                             argc,
+       char                            **argv)
+
+       {
+       char                            *arg;
+       int                             args;
+       int                             ignore = 0;
+
+       calling_path = argv[0];
+       for (args = 0, argv++; (arg = *argv++) != NULL; args++)
+               {
+               if ((arg[0] != '-') || (ignore))
+                       {
+                       if (mode == GPGWRAP_MODE_FILE) goto get_file;
+                       gpg_cmd = argv - 1;
+                       break;
+                       }
+               else if ((strcmp(arg, "-") == 0) && (mode == GPGWRAP_MODE_FILE))
+                       {
+               get_file:
+                       if (nfiles >= CMDLINE_MAX_FILES) do_error("too many files specified");
+                       files[nfiles++] = cmdline_check_stdin("-F/--file", arg);
+                       }
+               else if (strcmp(arg, "--") == 0)
+                       {
+                       ignore = 1;
+                       }
+               else if ((strcmp(arg, "-h") == 0) || (strcmp(arg, "--help") == 0))
+                       {
+                       cmdline_usage();
+                       }
+               else if (((strcmp(arg, "-V") == 0) || (strcmp(arg, "--version") == 0)) && (args == 0))
+                       {
+                       mode = GPGWRAP_MODE_VERSION;
+                       }
+               else if (((strcmp(arg, "-F") == 0) || (strcmp(arg, "--file") == 0)) && (args == 0))
+                       {
+                       mode = GPGWRAP_MODE_FILE;
+                       }
+               else if (((strcmp(arg, "-P") == 0) || (strcmp(arg, "--print") == 0)) && (args == 0))
+                       {
+                       mode = GPGWRAP_MODE_PRINT;
+                       }
+               else if (mode == GPGWRAP_MODE_VERSION)
+                       {
+                       goto bad_option;
+                       }
+               else if ((strcmp(arg, "-v") == 0) || (strcmp(arg, "--verbose") == 0))
+                       {
+                       verbose++;
+                       }
+               else if ((strcmp(arg, "-i") == 0) || (strcmp(arg, "--interactive") == 0))
+                       {
+                       interactive = 1;
+                       }
+               else if ((strcmp(arg, "-a") == 0) || (strcmp(arg, "--ask-twice") == 0))
+                       {
+                       ask_twice = 1;
+                       }
+               else if ((strcmp(arg, "-p") == 0) || (strcmp(arg, "--passphrase-file") == 0))
+                       {
+                       if (passphrase_file != NULL) do_error("-p/--passphrase-file specified more than once");
+                       passphrase_file = cmdline_check_stdin("-p/--passphrase-file", *argv++);
+                       }
+               else if (mode == GPGWRAP_MODE_PRINT)
+                       {
+                       goto bad_option;
+                       }
+               else if ((strcmp(arg, "-o") == 0) || (strcmp(arg, "--option-name") == 0))
+                       {
+                       option_name = cmdline_check_arg("-o/--option-name", *argv++);
+                       }
+               else if (mode != GPGWRAP_MODE_FILE)
+                       {
+                       goto bad_option;
+                       }
+               else if ((strcmp(arg, "-c") == 0) || (strcmp(arg, "--check-exit-code") == 0))
+                       {
+                       check_exit_code = 1;
+                       }
+               else
+                       {
+               bad_option:
+                       do_error("unrecognized option '%s'", arg);
+                       }
+               }
+       if ((mode == GPGWRAP_MODE_DEFAULT) && (nfiles == 0) && (gpg_cmd == NULL)) do_error("no gpg command specified");
+       if ((mode == GPGWRAP_MODE_FILE) && (nfiles == 0)) do_error("no files to process");
+       if ((mode == GPGWRAP_MODE_PRINT) && (nfiles > 0)) do_error("no additional arguments allowed");
+       if (mode != GPGWRAP_MODE_FILE) check_exit_code = 1;
+       }
+
+
+
+/****************************************************************************
+ * main
+ ****************************************************************************/
+int
+main(
+       int                             argc,
+       char                            **argv)
+
+       {
+
+       /*
+        * we need setlinebuf(), because otherwise do_verbose() output of
+        * parent and child processes may get mixed in some cases
+        */
+
+       setlinebuf(stderr);
+
+       /* parse cmdline */
+
+       cmdline_parse(argc, argv);
+
+       /* do it */
+
+       if (mode == GPGWRAP_MODE_VERSION)
+               {
+               printf(VERSION_STRING "\n");
+               }
+       else if (mode == GPGWRAP_MODE_FILE)
+               {
+               int                     i, len;
+               char                    buffer[PASSPHRASE_BUFFER_SIZE];
+
+               len = get_passphrase(buffer, sizeof (buffer));
+               for (i = 0; i < nfiles; i++) exec_list(files[i], buffer, len);
+               }
+       else if (mode == GPGWRAP_MODE_PRINT)
+               {
+               char                    buffer[PASSPHRASE_BUFFER_SIZE];
+               char                    mbuffer[PASSPHRASE_BUFFER_SIZE];
+               int                     len;
+
+               len = get_passphrase(buffer, sizeof (buffer));
+               mangle_passphrase(buffer, len, mbuffer, sizeof (mbuffer));
+               printf("%s\n", mbuffer);
+               }
+       else exec_gpg();
+
+       /* done */
+
+       return (0);
+       }
+/******************************************************** Karsten Scheibler */
diff --git a/gpgwrap/src/version.h b/gpgwrap/src/version.h
new file mode 100644 (file)
index 0000000..3a3e78e
--- /dev/null
@@ -0,0 +1,22 @@
+/****************************************************************************
+ ****************************************************************************
+ *
+ * version.h
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+
+
+
+
+#ifndef VERSION_H
+#define VERSION_H
+
+#define VERSION                                "0.04"
+#define VERSION_DATE                   "20060904"
+
+
+
+#endif /* !VERSION_H */
+/******************************************************** Karsten Scheibler */