From 7969bce43d9c445d87a68080587298728d0d3e38 Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Thu, 5 Oct 2017 18:06:47 +1300 Subject: Rename/separate out into app and module This still needs a lot more work before release. In particular, I have to figure out what I'm going to do about the `passphrase` option. It's probably better to both not require it, in which case no signing is done (only encryption), and to instead allow a path to a file to be specified. The other big puzzle would be how on earth to write automated tests for it... I may end up imitating however Mail::GnuPG is testing itself. --- Changes | 8 ++ LICENSE | 157 ------------------------- MANIFEST | 12 +- MANIFEST.SKIP | 2 +- Makefile.PL | 9 +- README | 8 +- README.markdown | 45 ------- bin/croncrypt | 112 ------------------ bin/runcrypt | 254 +++++++++++++++++++++++++++++++++++++++ lib/Mail/Cron/Crypt.pm | 141 ---------------------- lib/Mail/Run/Crypt.pm | 287 +++++++++++++++++++++++++++++++++++++++++++++ share/man/man1/croncrypt.1 | 27 ----- t/00-load.t | 13 -- t/manifest.t | 15 --- t/pod-coverage.t | 24 ---- t/pod.t | 16 --- xt/boilerplate.t | 57 --------- xt/manifest.t | 15 +++ xt/pod-coverage.t | 24 ++++ xt/pod.t | 16 +++ 20 files changed, 620 insertions(+), 622 deletions(-) create mode 100644 Changes delete mode 100644 LICENSE delete mode 100644 README.markdown delete mode 100755 bin/croncrypt create mode 100755 bin/runcrypt delete mode 100644 lib/Mail/Cron/Crypt.pm create mode 100644 lib/Mail/Run/Crypt.pm delete mode 100644 share/man/man1/croncrypt.1 delete mode 100644 t/00-load.t delete mode 100644 t/manifest.t delete mode 100644 t/pod-coverage.t delete mode 100644 t/pod.t delete mode 100644 xt/boilerplate.t create mode 100644 xt/manifest.t create mode 100644 xt/pod-coverage.t create mode 100644 xt/pod.t diff --git a/Changes b/Changes new file mode 100644 index 0000000..29a6124 --- /dev/null +++ b/Changes @@ -0,0 +1,8 @@ +Revision history for Perl module Mail::Run::Crypt + +This project began life as a monolithic Perl script. It does much the same +thing now but it's refactored out into an application and a module. + +0.01 2017-10-05 + + - First perlmodded release, refactoring and rewriting much older code. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f23c229..0000000 --- a/LICENSE +++ /dev/null @@ -1,157 +0,0 @@ -Artistic License 2.0 - -Copyright (c) 2000-2006, The Perl Foundation. - -Everyone is permitted to copy and distribute verbatim copies of this license -document, but changing it is not allowed. Preamble - -This license establishes the terms under which a given free software Package may -be copied, modified, distributed, and/or redistributed. The intent is that the -Copyright Holder maintains some artistic control over the development of that -Package while still keeping the Package available as open source and free -software. - -You are always permitted to make arrangements wholly outside of this license -directly with the Copyright Holder of a given Package. If the terms of this -license do not permit the full use that you propose to make of the Package, you -should contact the Copyright Holder and seek a different licensing arrangement. -Definitions - -"Copyright Holder" means the individual(s) or organization(s) named in the -copyright notice for the entire Package. - -"Contributor" means any party that has contributed code or other material to the -Package, in accordance with the Copyright Holder's procedures. - -"You" and "your" means any person who would like to copy, distribute, or modify -the Package. - -"Package" means the collection of files distributed by the Copyright Holder, and -derivatives of that collection and/or of those files. A given Package may -consist of either the Standard Version, or a Modified Version. - -"Distribute" means providing a copy of the Package or making it accessible to -anyone else, or in the case of a company or organization, to others outside of -your company or organization. - -"Distributor Fee" means any fee that you charge for Distributing this Package or -providing support for this Package to another party. It does not mean licensing -fees. - -"Standard Version" refers to the Package if it has not been modified, or has -been modified only in ways explicitly requested by the Copyright Holder. - -"Modified Version" means the Package, if it has been changed, and such changes -were not explicitly requested by the Copyright Holder. - -"Original License" means this Artistic License as Distributed with the Standard -Version of the Package, in its current version or as it may be modified by The -Perl Foundation in the future. - -"Source" form means the source code, documentation source, and configuration -files for the Package. - -"Compiled" form means the compiled bytecode, object code, binary, or any other -form resulting from mechanical transformation or translation of the Source form. -Permission for Use and Modification Without Distribution - -(1) You are permitted to use the Standard Version and create and use Modified -Versions for any purpose without restriction, provided that you do not -Distribute the Modified Version. Permissions for Redistribution of the Standard -Version - -(2) You may Distribute verbatim copies of the Source form of the Standard -Version of this Package in any medium without restriction, either gratis or for -a Distributor Fee, provided that you duplicate all of the original copyright -notices and associated disclaimers. At your discretion, such verbatim copies may -or may not include a Compiled form of the Package. - -(3) You may apply any bug fixes, portability changes, and other modifications -made available from the Copyright Holder. The resulting Package will still be -considered the Standard Version, and as such will be subject to the Original -License. Distribution of Modified Versions of the Package as Source - -(4) You may Distribute your Modified Version as Source (either gratis or for a -Distributor Fee, and with or without a Compiled form of the Modified Version) -provided that you clearly document how it differs from the Standard Version, -including, but not limited to, documenting any non-standard features, -executables, or modules, and provided that you do at least ONE of the following: - -(a) make the Modified Version available to the Copyright Holder of the Standard -Version, under the Original License, so that the Copyright Holder may include -your modifications in the Standard Version. (b) ensure that installation of your -Modified Version does not prevent the user installing or running the Standard -Version. In addition, the Modified Version must bear a name that is different -from the name of the Standard Version. (c) allow anyone who receives a copy of -the Modified Version to make the Source form of the Modified Version available -to others under (i) the Original License or (ii) a license that permits the -licensee to freely copy, modify and redistribute the Modified Version using the -same licensing terms that apply to the copy that the licensee received, and -requires that the Source form of the Modified Version, and of any works derived -from it, be made freely available in that license fees are prohibited but -Distributor Fees are allowed. Distribution of Compiled Forms of the Standard -Version or Modified Versions without the Source - -(5) You may Distribute Compiled forms of the Standard Version without the -Source, provided that you include complete instructions on how to get the Source -of the Standard Version. Such instructions must be valid at the time of your -distribution. If these instructions, at any time while you are carrying out such -distribution, become invalid, you must provide new instructions on demand or -cease further distribution. If you provide valid instructions or cease -distribution within thirty days after you become aware that the instructions are -invalid, then you do not forfeit any of your rights under this license. - -(6) You may Distribute a Modified Version in Compiled form without the Source, -provided that you comply with Section 4 with respect to the Source of the -Modified Version. Aggregating or Linking the Package - -(7) You may aggregate the Package (either the Standard Version or Modified -Version) with other packages and Distribute the resulting aggregation provided -that you do not charge a licensing fee for the Package. Distributor Fees are -permitted, and licensing fees for other components in the aggregation are -permitted. The terms of this license apply to the use and Distribution of the -Standard or Modified Versions as included in the aggregation. - -(8) You are permitted to link Modified and Standard Versions with other works, -to embed the Package in a larger work of your own, or to build stand-alone -binary or bytecode versions of applications that include the Package, and -Distribute the result without restriction, provided the result does not expose a -direct interface to the Package. Items That are Not Considered Part of a -Modified Version - -(9) Works (including, but not limited to, modules and scripts) that merely -extend or make use of the Package, do not, by themselves, cause the Package to -be a Modified Version. In addition, such works are not considered parts of the -Package itself, and are not subject to the terms of this license. General -Provisions - -(10) Any use, modification, and distribution of the Standard or Modified -Versions is governed by this Artistic License. By using, modifying or -distributing the Package, you accept this license. Do not use, modify, or -distribute the Package, if you do not accept this license. - -(11) If your Modified Version has been derived from a Modified Version made by -someone other than you, you are nevertheless required to ensure that your -Modified Version complies with the requirements of this license. - -(12) This license does not grant you the right to use any trademark, service -mark, tradename, or logo of the Copyright Holder. - -(13) This license includes the non-exclusive, worldwide, free-of-charge patent -license to make, have made, use, offer to sell, sell, import and otherwise -transfer the Package with respect to any patent claims licensable by the -Copyright Holder that are necessarily infringed by the Package. If you institute -patent litigation (including a cross-claim or counterclaim) against any party -alleging that the Package constitutes direct or contributory patent -infringement, then this Artistic License to you shall terminate on the date that -such litigation is filed. - -(14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND -CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR -NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. -UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY -OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - diff --git a/MANIFEST b/MANIFEST index f83b7fa..f04da1b 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,9 +1,9 @@ Changes -lib/Mail/Cron/Crypt.pm +bin/runcrypt +lib/Mail/Run/Crypt.pm Makefile.PL -MANIFEST This list of files +MANIFEST README -t/00-load.t -t/manifest.t -t/pod-coverage.t -t/pod.t +xt/manifest.t +xt/pod-coverage.t +xt/pod.t diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index ad901be..40c4b8d 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -1,5 +1,5 @@ ^\.git -^Mail-Cron-Crypt-.+\.tar\.gz$ +^Mail-Run-Crypt-.+\.tar\.gz$ ^MANIFEST\.SKIP$ ^MYMETA\. ^Makefile$ diff --git a/Makefile.PL b/Makefile.PL index 91827fc..299a4b6 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -4,10 +4,10 @@ use warnings; use ExtUtils::MakeMaker; WriteMakefile( - NAME => 'Mail::Cron::Crypt', + NAME => 'Mail::Run::Crypt', AUTHOR => q{Tom Ryder }, - VERSION_FROM => 'lib/Mail/Cron/Crypt.pm', - ABSTRACT_FROM => 'lib/Mail/Cron/Crypt.pm', + VERSION_FROM => 'lib/Mail/Run/Crypt.pm', + ABSTRACT_FROM => 'lib/Mail/Run/Crypt.pm', LICENSE => 'artistic_2', PL_FILES => {}, MIN_PERL_VERSION => '5.006', @@ -21,6 +21,7 @@ WriteMakefile( #'ABC' => '1.6', #'Foo::Bar::Module' => '5.0401', }, + EXE_FILES => ['bin/runcrypt'], dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, - clean => { FILES => 'Mail-Cron-Crypt-*' }, + clean => { FILES => 'Mail-Run-Crypt-*' }, ); diff --git a/README b/README index 5aa4913..436e7d9 100644 --- a/README +++ b/README @@ -1,7 +1,7 @@ -Mail-Cron-Crypt +Mail-Run-Crypt -Wrapper to sign and encrypt `cron(8)` output and errors with PGP/MIME before -sending them to the `MAILTO` destination. +Wrapper to sign and encrypt cron(8) output and errors with PGP/MIME +before sending them to the MAILTO destination. INSTALLATION @@ -18,7 +18,7 @@ SUPPORT AND DOCUMENTATION After installing, you can find documentation for this module with the perldoc command. - perldoc Mail::Cron::Crypt + perldoc Mail::Run::Crypt LICENSE AND COPYRIGHT diff --git a/README.markdown b/README.markdown deleted file mode 100644 index be1457e..0000000 --- a/README.markdown +++ /dev/null @@ -1,45 +0,0 @@ -Croncrypt -========= - -Wrapper to sign and encrypt `cron(8)` output and errors with PGP/MIME before -sending them to the `MAILTO` destination. - - $ crontab -l - CRONCRYPT_KEYID=0x0A1B2C3D4E5F6G7H - CRONCRYPT_PASSPHRASE=hibbityboo - MAILTO=me@mynet - - 0 1 * * * croncrypt rsync /home/tom/important-file /home/backups - -The main design goal is simplicity; just whack `croncrypt` in front of all your -`crontab(5)` entries, provided they don’t use pipes or `stderr`/`stdout` -redirects, in which case you should consider putting it all into a script file -anyway. - -Don’t use your own GPG key for signing! I recommend you create a dedicated key -just for Croncrypt, and sign it locally with `gpg --lsign` so that your -software trusts it locally. - -Installation ------------- - -Put the `croncrypt` binary somewhere in your `crontab`’s `PATH`, and install -the following Perl modules: - -* `Carp` -* `IPC::Run3` -* `Mail::GnuPG` -* `MIME::Entity` - -On Debian-derived systems, this should do the trick: - - # aptitude install perl-base perl-modules libmail-gnupg-perl \ - libmime-tools-perl libipc-run3-perl - -License -------- - -Copyright (c) [Tom Ryder][1]. Distributed under [Artistic License 2.0][2]. - -[1]: https://sanctum.geek.nz/ -[2]: http://opensource.org/licenses/artistic-license-2.0 diff --git a/bin/croncrypt b/bin/croncrypt deleted file mode 100755 index 002dd56..0000000 --- a/bin/croncrypt +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env perl - -# -# croncrypt: Wrapper to sign and encrypt cron output and errors with PGP/MIME -# before sending them to the default MAILTO destination. -# -# CRONCRYPT_KEYID=0x0A1B2C3D4E5F6G7H -# CRONCRYPT_PASSPHRASE=hibbityboo -# MAILTO=tom@sanctum.geek.nz -# 0 1 * * * tom croncrypt rsync /home/tom/important-file /home/backups -# -# The main design goal is simplicity; just whack a «croncrypt» in front of all -# your cron tasks, provided they don't use pipes or stderr/stdout redirects, -# in which case you should consider putting it all into a script file anyway. -# -# Don't use your own GPG key for signing! Create a dedicated key just for -# croncrypt, and sign it locally with «gpg --lsign» maybe. -# -# Author: Tom Ryder -# Copyright: 2014 Sanctum -# License: Artistic 2.0 -# -# $Id$ -# -package Sanctum::Croncrypt; - -# Force me to write this properly -use strict; -use warnings; -use utf8; -use autodie; - -# Decree minimum Perl version required (v5.8). -use 5.008; - -# Decree package version to pacify Perl::Critic -our $VERSION = 0.1; - -# Pull in some required modules -use Carp; -use IPC::Run3; -use Mail::GnuPG; -use MIME::Entity; - -# Bail if run without arguments -if ( !@ARGV ) { - printf "%s\n", 'USAGE: croncrypt '; - exit 1; -} - -# Bail if we don't have the environment variables we need -my @fails; -if ( !exists $ENV{'CRONCRYPT_KEYID'} ) { - push @fails, 'CRONCRYPT_KEYID is not set; set it to your key ID.'; -} -if ( !exists $ENV{'CRONCRYPT_PASSPHRASE'} ) { - push @fails, - 'CRONCRYPT_PASSPHRASE is not set; set it to your key\'s passphrase.'; -} -if ( !exists $ENV{'MAILTO'} ) { - push @fails, 'MAILTO is not set; set it to the message\'s destination.'; -} -if (@fails) { - foreach my $fail (@fails) { - printf {*STDERR} "croncrypt: FAIL: %s\n", $fail; - } - exit 1; -} - -# Read details from environment -my $recipient = $ENV{MAILTO}; -my $key = $ENV{CRONCRYPT_KEYID}; -my $passphrase = $ENV{CRONCRYPT_PASSPHRASE}; - -# Run the command in the arguments and wait for it to finish -my ( @output, @errors ); -run3( \@ARGV, undef, \@output, \@errors ); - -# If there was output, mail it -if (@output) { - my $subject = sprintf 'croncrypt output: %s', join q{ }, @ARGV; - mail( $subject, \@output ); -} - -# If there were errors, mail them -if (@errors) { - my $subject = sprintf 'croncrypt errors: %s', join q{ }, @ARGV; - mail( $subject, \@errors ); -} - -# Send the message to the address in $ENV{MAILTO} -sub mail { - my ( $subject, $content ) = @_; - - # Build MIME object with plaintext message - my $mime = MIME::Entity->build( - To => $recipient, - Subject => $subject, - Data => $content, - ); - - # Encrypt the MIME object - my $mgpg = Mail::GnuPG->new( - key => $key, - passphrase => $passphrase, - ); - $mgpg->mime_signencrypt( $mime, $recipient ); - - # Send it - return $mime->send(); -} - diff --git a/bin/runcrypt b/bin/runcrypt new file mode 100755 index 0000000..d9a24fc --- /dev/null +++ b/bin/runcrypt @@ -0,0 +1,254 @@ +#!/usr/bin/env perl +package main; + +# Force me to write this properly +use strict; +use warnings; +use utf8; + +# Target Debian-Squeeze-era Perl +# We may be able to go older; not sure yet +use 5.010; + +# Import required modules +use Carp; +use Getopt::Long::Descriptive; +use Mail::Run::Crypt; + +# Specify package version +our $VERSION = '0.01'; + +# Name ourselves +our $SELF = 'runcrypt'; + +# Read command-line options +my ( $opt, $usage ) = describe_options( + "$SELF %o COMMAND [ARG1...]", + + # Key ID defaults to environment RUNCRYPT_KEYID if set + [ + 'keyid|k=s', + 'OpenPGP key ID', + { default => $ENV{RUNCRYPT_KEYID} // undef }, + ], + + # Key passphrase defaults to environment RUNCRYPT_PASSPHRASE if set + [ + 'passphrase|p=s', + 'OpenPGP passphrase', + { default => $ENV{RUNCRYPT_PASSPHRASE} // undef }, + ], + + # MAILTO address defaults to environment MAILTO if set + [ + 'mailto|m=s', + 'Mail destination address (MAILTO)', + { default => $ENV{RUNCRYPT_MAILTO} // $ENV{MAILTO} // undef }, + ], + + # Instance name (for email subjects) defaults to $SELF + [ + 'name|n=s', + 'Instance name (included in subject lines)', + { default => $SELF }, + ], + + # Newline + [], + + # Help option + [ 'help', 'print usage message and exit', { shortcircuit => 1 } ], +); + +# Print help if requested +if ( $opt->help ) { + print $usage->text + or carp 'Failed stdout write'; + exit 0; +} + +# Bail if run without arguments +if ( !@ARGV ) { + printf {*STDERR} $usage->text + or carp 'Failed stderr write'; + exit 2; +} + +# Create an MCC object +my $mrc = Mail::Run::Crypt->new( + keyid => $opt->keyid, + passphrase => $opt->passphrase, + mailto => $opt->mailto, + name => $opt->name, +); + +# Run the command given in the arguments, exiting appropriately +$mrc->run(@ARGV); +exit $mrc->bail; + +__END__ + +=pod + +=encoding utf8 + +=for stopwords +runcrypt decrypt stdout stderr GPG OpenPGP tradename licensable MERCHANTABILITY + +=head1 NAME + +runcrypt - Encrypt and mail output from command in arguments + +=head1 USAGE + + runcrypt + [--keyid KEYID] + [--passphrase PASSPHRASE] + [--mailto MAILTO] + [--name NAME] + COMMAND [ARG1 ...] + +=head1 DESCRIPTION + +This program applies C to run a command and send any output +or error content to the specified address. More information is available in the +documentation for that module. + +=head1 REQUIRED ARGUMENTS + +The arguments beyond the options are used as the command name to run: + + runcrypt rsync -a /mnt/a remote:mnt/b + +=head1 OPTIONS + +=over 4 + +=item C<--keyid> + +The GnuPG key ID that should be used to sign and encrypt the messages. This +defaults to the value of the environment variable C. + +=item C<--passphrase> + +The passphrase use to decrypt the key. This defaults to the value of the +environment variable C. + +=item C<--mailto> + +The recipient address for the encryption portion of the email. This defaults to +the value of the environment variable C if that is set, or +C failing that, to make it suitable for use in a C file. + +=item C<--name> + +The name for this instance of the module, which will be used as the first word +of the subject line of any email messages it sends. This defaults to +C, which is probably good enough in most cases. + +=head1 DIAGNOSTICS + +=over 4 + +=item Failed stdout write + +Usage information could not be written to the standard output stream. + +=item Failed stderr write + +Usage information could not be written to the standard error stream. + +=head1 EXIT STATUS + +The program exits with the same exit value of the command that it ran, or 127 +if the command could not be run at all. See the C method in +C. + +=head1 CONFIGURATION + +You will need to have a functioning GnuPG public key setup for this to work, +including the secret key. You should definitely not use your personal key; +generate one specifically for mail signing and encryption instead. + +=head1 DEPENDENCIES + +=over 4 + +=item * + +Perl 5.10 or newer + +=item * + +C + +=item * + +C + +=item * + +C + +=back + +=head1 INCOMPATIBILITIES + +This module uses C and other GPG-specific code, so it won't work +with any other OpenPGP implementations. + +=head1 BUGS AND LIMITATIONS + +Providing the C directly from an environment variable is not great. +The documentation needs to make that clear and offer a less bad alternative, +probably specifying the path to a secure file that it refuses to use unless it +has sufficiently restrictive permissions. + +This is very hard to test and the author has not yet figured out how to do +that. + +=head1 AUTHOR + +Tom Ryder C<< >> + +=head1 LICENSE AND COPYRIGHT + +Copyright (C) 2017 Tom Ryder + +This program is free software; you can redistribute it and/or modify it under +the terms of the Artistic License (2.0). You may obtain a copy of the full +license at: + +L + +Any use, modification, and distribution of the Standard or Modified Versions is +governed by this Artistic License. By using, modifying or distributing the +Package, you accept this license. Do not use, modify, or distribute the +Package, if you do not accept this license. + +If your Modified Version has been derived from a Modified Version made by +someone other than you, you are nevertheless required to ensure that your +Modified Version complies with the requirements of this license. + +This license does not grant you the right to use any trademark, service mark, +tradename, or logo of the Copyright Holder. + +This license includes the non-exclusive, worldwide, free-of-charge patent +license to make, have made, use, offer to sell, sell, import and otherwise +transfer the Package with respect to any patent claims licensable by the +Copyright Holder that are necessarily infringed by the Package. If you +institute patent litigation (including a cross-claim or counterclaim) against +any party alleging that the Package constitutes direct or contributory patent +infringement, then this Artistic License to you shall terminate on the date +that such litigation is filed. + +Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND +CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR +NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. +UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY +OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +=cut diff --git a/lib/Mail/Cron/Crypt.pm b/lib/Mail/Cron/Crypt.pm deleted file mode 100644 index adf1c26..0000000 --- a/lib/Mail/Cron/Crypt.pm +++ /dev/null @@ -1,141 +0,0 @@ -package Mail::Cron::Crypt; - -use 5.006; -use strict; -use warnings; - -=head1 NAME - -Mail::Cron::Crypt - The great new Mail::Cron::Crypt! - -=head1 VERSION - -Version 0.01 - -=cut - -our $VERSION = '0.01'; - - -=head1 SYNOPSIS - -Quick summary of what the module does. - -Perhaps a little code snippet. - - use Mail::Cron::Crypt; - - my $foo = Mail::Cron::Crypt->new(); - ... - -=head1 EXPORT - -A list of functions that can be exported. You can delete this section -if you don't export anything, such as for a purely object-oriented module. - -=head1 SUBROUTINES/METHODS - -=head2 function1 - -=cut - -sub function1 { -} - -=head2 function2 - -=cut - -sub function2 { -} - -=head1 AUTHOR - -Tom Ryder, C<< >> - -=head1 BUGS - -Please report any bugs or feature requests to C, or through -the web interface at L. I will be notified, and then you'll -automatically be notified of progress on your bug as I make changes. - - - - -=head1 SUPPORT - -You can find documentation for this module with the perldoc command. - - perldoc Mail::Cron::Crypt - - -You can also look for information at: - -=over 4 - -=item * RT: CPAN's request tracker (report bugs here) - -L - -=item * AnnoCPAN: Annotated CPAN documentation - -L - -=item * CPAN Ratings - -L - -=item * Search CPAN - -L - -=back - - -=head1 ACKNOWLEDGEMENTS - - -=head1 LICENSE AND COPYRIGHT - -Copyright 2017 Tom Ryder. - -This program is free software; you can redistribute it and/or modify it -under the terms of the the Artistic License (2.0). You may obtain a -copy of the full license at: - -L - -Any use, modification, and distribution of the Standard or Modified -Versions is governed by this Artistic License. By using, modifying or -distributing the Package, you accept this license. Do not use, modify, -or distribute the Package, if you do not accept this license. - -If your Modified Version has been derived from a Modified Version made -by someone other than you, you are nevertheless required to ensure that -your Modified Version complies with the requirements of this license. - -This license does not grant you the right to use any trademark, service -mark, tradename, or logo of the Copyright Holder. - -This license includes the non-exclusive, worldwide, free-of-charge -patent license to make, have made, use, offer to sell, sell, import and -otherwise transfer the Package with respect to any patent claims -licensable by the Copyright Holder that are necessarily infringed by the -Package. If you institute patent litigation (including a cross-claim or -counterclaim) against any party alleging that the Package constitutes -direct or contributory patent infringement, then this Artistic License -to you shall terminate on the date that such litigation is filed. - -Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER -AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. -THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY -YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR -CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR -CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, -EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -=cut - -1; # End of Mail::Cron::Crypt diff --git a/lib/Mail/Run/Crypt.pm b/lib/Mail/Run/Crypt.pm new file mode 100644 index 0000000..c37a622 --- /dev/null +++ b/lib/Mail/Run/Crypt.pm @@ -0,0 +1,287 @@ +package Mail::Run::Crypt; + +# Force me to write this properly +use strict; +use warnings; +use utf8; + +# Target Debian-Squeeze-era Perl +# We may be able to go older; not sure yet +use 5.010; + +# Import required modules +use Carp; +use Const::Fast; +use English '-no_match_vars'; +use IPC::Run3; +use Mail::GnuPG; +use MIME::Entity; + +# Specify package verson +our $VERSION = '0.01'; + +# Default exit value +const our $DEFAULT_EXIT => 127; + +# Oldschool constructor +sub new { + my ( $class, %opts ) = @_; + + # Blindly slurp in all the options given + my $self = {%opts}; + + # We must have a key ID and a recipient, but not necessarily a passphrase + for my $req (qw(keyid mailto)) { + $self->{$req} // croak "$req required"; + } + + # Default the instance name to the package name if it wasn't given; + # croncrypt(1p) will pass it in + $self->{name} //= $class; + + # Return objectified self + return bless $self, $class; +} + +# Run a given command +sub run { + my ( $self, @command ) = @_; + + # Run the command and wait for it to finish; keep its exit value for later + my ( @out, @err ); + eval { run3 \@command, undef, \@out, \@err } + or carp "Command failed: $EVAL_ERROR"; + $self->{exit} = $CHILD_ERROR >> 8; + + # If there was output, mail it + if (@out) { + my $command = join q{ }, @command; + my $subject = "$self->{name} output: $command"; + $self->_mail( $subject, \@out ); + } + + # If there were errors, mail them + if (@err) { + my $command = join q{ }, @command; + my $subject = "$self->{name} errors: $command"; + $self->_mail( $subject, \@err ); + } + + # Return status reflecting the command exit value + return $self->{exit} == 0; +} + +# Return the value of the most recently run command, or 1 otherwise +sub bail { return shift->{exit} // $DEFAULT_EXIT } + +# Send the message to the address in $ENV{MAILTO} +sub _mail { + my ( $self, $subject, $content ) = @_; + + # Build MIME object with plaintext message + my $mime = MIME::Entity->build( + To => $self->{mailto}, + Subject => $subject, + Data => $content, + ); + + # Encrypt the MIME object + my $mgpg = Mail::GnuPG->new( + key => $self->{keyid}, + passphrase => $self->{passphrase}, + ); + $mgpg->mime_signencrypt( $mime, $self->{mailto} ); + + # Send it + return $mime->send(); +} + +1; + +__END__ + +=pod + +=for stopwords +mailserver decrypt croncrypt GPG OpenPGP tradename licensable MERCHANTABILITY + +=encoding utf8 + +=head1 NAME + +Mail::Run::Crypt - Encrypt and mail output from command runs + +=head1 VERSION + +Version 0.01 + +=head1 DESCRIPTION + +This module runs commands with C, and collects any standard +output and standard error it emits. If there is any standard output or standard +error content, it is mailed (each stream separately) to a specified recipient +address. + +The idea is to allow you to view the output of automated commands while having +the content encrypted as it passes through to your mailserver, and have some +assurance that the content was actually generated by the server concerned. +C scripts are the ideal use case, but this would also with C or +anything else that might non-interactively run jobs for which output is +significant. + +You probably want to call this with the C program installed by +this distribution, which provides a means to set the properties for the module +via environment variables or command-line options. + +=head1 SYNOPSIS + + my $mrc = Mail::Run::Crypt->new( + keyid => 0x1234DEAD, + passphrase => 'extremely_insecure', + mailto => 'you@example.net', + ); + $mrc->run(qw(rsync -a /mnt/a/ remote:mnt/b)); + +=head1 SUBROUTINES/METHODS + +=head2 B + +Constructor accepts the following named parameters: + +=over 4 + +=item C + +The GnuPG key ID that should be used to encrypt the messages. + +=item C + +The passphrase used to decrypt the key. + +=item C + +The recipient email address for the content. + +=item C + +(Optional) The name of the object. When called from the C +program, this will be the string "croncrypt". + +=head2 B + +Run the specified arguments as a command with C, and email +any output or error content to the email recipient. + +=head2 B + +Return the exit status of the most recently run command, or 127 if no command +has been successfully run. + +=head1 DIAGNOSTICS + +=over 4 + +=item %s required + +One of the required properties was not passed to the constructor. + +=head1 CONFIGURATION AND ENVIRONMENT + +You will need to have a functioning GnuPG public key setup for this to work, +including the secret key. You should definitely not use your personal key; +generate one specifically for mail signing and encryption instead. + +=head1 DEPENDENCIES + +=over 4 + +=item * + +Perl v5.10.0 or newer + +=item * + +C + +=item * + +C + +=item * + +C + +=item * + +C + +=item * + +C + +=item * + +C + +=back + +=head1 INCOMPATIBILITIES + +This module uses C and other GPG-specific code, so it won't work +with any other OpenPGP implementations. + +=head1 BUGS AND LIMITATIONS + +Providing the C directly from an environment variable is not great. +The documentation needs to make that clear and offer a less bad alternative, +probably specifying the path to a secure file that it refuses to use unless it +has sufficiently restrictive permissions. + +This is very hard to test and the author has not yet figured out how to do +that. + +=head1 AUTHOR + +Tom Ryder C<< >> + +=head1 LICENSE AND COPYRIGHT + +Copyright (C) 2017 Tom Ryder + +This program is free software; you can redistribute it and/or modify it under +the terms of the Artistic License (2.0). You may obtain a copy of the full +license at: + +L + +Any use, modification, and distribution of the Standard or Modified Versions is +governed by this Artistic License. By using, modifying or distributing the +Package, you accept this license. Do not use, modify, or distribute the +Package, if you do not accept this license. + +If your Modified Version has been derived from a Modified Version made by +someone other than you, you are nevertheless required to ensure that your +Modified Version complies with the requirements of this license. + +This license does not grant you the right to use any trademark, service mark, +tradename, or logo of the Copyright Holder. + +This license includes the non-exclusive, worldwide, free-of-charge patent +license to make, have made, use, offer to sell, sell, import and otherwise +transfer the Package with respect to any patent claims licensable by the +Copyright Holder that are necessarily infringed by the Package. If you +institute patent litigation (including a cross-claim or counterclaim) against +any party alleging that the Package constitutes direct or contributory patent +infringement, then this Artistic License to you shall terminate on the date +that such litigation is filed. + +Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND +CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR +NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. +UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY +OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +=cut diff --git a/share/man/man1/croncrypt.1 b/share/man/man1/croncrypt.1 deleted file mode 100644 index dd5b508..0000000 --- a/share/man/man1/croncrypt.1 +++ /dev/null @@ -1,27 +0,0 @@ -.TH CRONCRYPT 1 "May 2014" "Manual page for croncrypt" -.SH NAME -.B croncrypt -\- sign and encrypt cron(8) output with PGP/MIME -.SH SYNOPSIS - $ crontab -l - CRONCRYPT_KEYID=0x0A1B2C3D4E5F6G7H - CRONCRYPT_PASSPHRASE=hibbityboo - MAILTO=me@mynet - 0 1 * * * croncrypt rsync /home/tom/important-file /home/backups -.SH DESCRIPTION -.B croncrypt -signs and encrypts cron(8) output and errors with PGP/MIME before emailing it -to the MAILTO address. -.PP -The main design goal is simplicity; just whack it in front of all your -crontab(5) entries, provided they don't use pipes or stderr/stdout redirects, -in which case you should consider putting it all into a script file anyway. -.PP -Don't use your own GPG key for signing! I recommend you create a dedicated key -just for Croncrypt, and sign it locally with gpg --lsign so that your software -trusts it locally. -.SH SEE ALSO -gpg(1), gpg-agent(1), IPC::Run3(3pm), Mail::GnuPG(3pm), MIME::Entity(3pm) -.SH AUTHOR -Tom Ryder - diff --git a/t/00-load.t b/t/00-load.t deleted file mode 100644 index db7a435..0000000 --- a/t/00-load.t +++ /dev/null @@ -1,13 +0,0 @@ -#!perl -T -use 5.006; -use strict; -use warnings; -use Test::More; - -plan tests => 1; - -BEGIN { - use_ok( 'Mail::Cron::Crypt' ) || print "Bail out!\n"; -} - -diag( "Testing Mail::Cron::Crypt $Mail::Cron::Crypt::VERSION, Perl $], $^X" ); diff --git a/t/manifest.t b/t/manifest.t deleted file mode 100644 index e0b558e..0000000 --- a/t/manifest.t +++ /dev/null @@ -1,15 +0,0 @@ -#!perl -T -use 5.006; -use strict; -use warnings; -use Test::More; - -unless ( $ENV{RELEASE_TESTING} ) { - plan( skip_all => "Author tests not required for installation" ); -} - -my $min_tcm = 0.9; -eval "use Test::CheckManifest $min_tcm"; -plan skip_all => "Test::CheckManifest $min_tcm required" if $@; - -ok_manifest(); diff --git a/t/pod-coverage.t b/t/pod-coverage.t deleted file mode 100644 index f5728a5..0000000 --- a/t/pod-coverage.t +++ /dev/null @@ -1,24 +0,0 @@ -#!perl -T -use 5.006; -use strict; -use warnings; -use Test::More; - -unless ( $ENV{RELEASE_TESTING} ) { - plan( skip_all => "Author tests not required for installation" ); -} - -# Ensure a recent version of Test::Pod::Coverage -my $min_tpc = 1.08; -eval "use Test::Pod::Coverage $min_tpc"; -plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage" - if $@; - -# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version, -# but older versions don't recognize some common documentation styles -my $min_pc = 0.18; -eval "use Pod::Coverage $min_pc"; -plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage" - if $@; - -all_pod_coverage_ok(); diff --git a/t/pod.t b/t/pod.t deleted file mode 100644 index 4d3a0ce..0000000 --- a/t/pod.t +++ /dev/null @@ -1,16 +0,0 @@ -#!perl -T -use 5.006; -use strict; -use warnings; -use Test::More; - -unless ( $ENV{RELEASE_TESTING} ) { - plan( skip_all => "Author tests not required for installation" ); -} - -# Ensure a recent version of Test::Pod -my $min_tp = 1.22; -eval "use Test::Pod $min_tp"; -plan skip_all => "Test::Pod $min_tp required for testing POD" if $@; - -all_pod_files_ok(); diff --git a/xt/boilerplate.t b/xt/boilerplate.t deleted file mode 100644 index 4a007ec..0000000 --- a/xt/boilerplate.t +++ /dev/null @@ -1,57 +0,0 @@ -#!perl -T -use 5.006; -use strict; -use warnings; -use Test::More; - -plan tests => 3; - -sub not_in_file_ok { - my ($filename, %regex) = @_; - open( my $fh, '<', $filename ) - or die "couldn't open $filename for reading: $!"; - - my %violated; - - while (my $line = <$fh>) { - while (my ($desc, $regex) = each %regex) { - if ($line =~ $regex) { - push @{$violated{$desc}||=[]}, $.; - } - } - } - - if (%violated) { - fail("$filename contains boilerplate text"); - diag "$_ appears on lines @{$violated{$_}}" for keys %violated; - } else { - pass("$filename contains no boilerplate text"); - } -} - -sub module_boilerplate_ok { - my ($module) = @_; - not_in_file_ok($module => - 'the great new $MODULENAME' => qr/ - The great new /, - 'boilerplate description' => qr/Quick summary of what the module/, - 'stub function definition' => qr/function[12]/, - ); -} - -TODO: { - local $TODO = "Need to replace the boilerplate text"; - - not_in_file_ok(README => - "The README is used..." => qr/The README is used/, - "'version information here'" => qr/to provide version information/, - ); - - not_in_file_ok(Changes => - "placeholder date/time" => qr(Date/time) - ); - - module_boilerplate_ok('lib/Mail/Cron/Crypt.pm'); - - -} - diff --git a/xt/manifest.t b/xt/manifest.t new file mode 100644 index 0000000..e0b558e --- /dev/null +++ b/xt/manifest.t @@ -0,0 +1,15 @@ +#!perl -T +use 5.006; +use strict; +use warnings; +use Test::More; + +unless ( $ENV{RELEASE_TESTING} ) { + plan( skip_all => "Author tests not required for installation" ); +} + +my $min_tcm = 0.9; +eval "use Test::CheckManifest $min_tcm"; +plan skip_all => "Test::CheckManifest $min_tcm required" if $@; + +ok_manifest(); diff --git a/xt/pod-coverage.t b/xt/pod-coverage.t new file mode 100644 index 0000000..f5728a5 --- /dev/null +++ b/xt/pod-coverage.t @@ -0,0 +1,24 @@ +#!perl -T +use 5.006; +use strict; +use warnings; +use Test::More; + +unless ( $ENV{RELEASE_TESTING} ) { + plan( skip_all => "Author tests not required for installation" ); +} + +# Ensure a recent version of Test::Pod::Coverage +my $min_tpc = 1.08; +eval "use Test::Pod::Coverage $min_tpc"; +plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage" + if $@; + +# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version, +# but older versions don't recognize some common documentation styles +my $min_pc = 0.18; +eval "use Pod::Coverage $min_pc"; +plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage" + if $@; + +all_pod_coverage_ok(); diff --git a/xt/pod.t b/xt/pod.t new file mode 100644 index 0000000..4d3a0ce --- /dev/null +++ b/xt/pod.t @@ -0,0 +1,16 @@ +#!perl -T +use 5.006; +use strict; +use warnings; +use Test::More; + +unless ( $ENV{RELEASE_TESTING} ) { + plan( skip_all => "Author tests not required for installation" ); +} + +# Ensure a recent version of Test::Pod +my $min_tp = 1.22; +eval "use Test::Pod $min_tp"; +plan skip_all => "Test::Pod $min_tp required for testing POD" if $@; + +all_pod_files_ok(); -- cgit v1.2.3