1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
#!/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 <tom@sanctum.geek.nz>
# Copyright: 2014 Sanctum
# License: Artistic <http://dev.perl.org/licenses/artistic.html>
#
# $Id$
#
package Sanctum::Croncrypt;
# Force me to write this properly
use strict;
use utf8;
use warnings;
# Decree package version to pacify Perl::Critic
our $VERSION = 0.1;
# Pull in some required modules
use Carp;
use IPC::Open3;
use Mail::GnuPG;
use MIME::Entity;
use Symbol 'gensym';
# Bail if run without arguments
if ( !@ARGV ) {
printf "%s\n", 'USAGE: croncrypt <command>';
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};
# Establish filehandles; need to specifically create symbol for stderr
my ( $stdin, $stdout, $stderr );
$stderr = gensym;
# Run the command in the arguments and wait for it to finish
my $pid = open3( $stdin, $stdout, $stderr, @ARGV );
waitpid $pid, 0;
# Read any and all output and errors
my @output = <$stdout>;
my @errors = <$stderr>;
# Close the filehandles, placating Perl::Critic with return checks
close $stdin
or croak('Could not close stdin');
close $stdout
or croak('Could not close stdout');
close $stderr
or croak('Could not close stderr');
# 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();
}
|