File Coverage

blib/lib/Mail/Run/Crypt.pm
Criterion Covered Total %
statement 39 61 63.9
branch 15 28 53.5
condition 0 3 0.0
subroutine 11 13 84.6
pod 3 3 100.0
total 68 108 62.9


line stmt bran cond sub pod time code
1             package Mail::Run::Crypt;
2              
3             # Force me to write this properly
4 6     6   417303 use strict;
  6         55  
  6         182  
5 6     6   34 use warnings;
  6         12  
  6         142  
6 6     6   29 use utf8;
  6         9  
  6         35  
7              
8             # Require this version of Perl
9 6     6   252 use 5.008_001;
  6         20  
10              
11             # Import required modules
12 6     6   72 use Carp;
  6         23  
  6         421  
13 6     6   2116 use English '-no_match_vars';
  6         14405  
  6         36  
14 6     6   5320 use IPC::Run3;
  6         190833  
  6         387  
15 6     6   3393 use Mail::GnuPG;
  6         3018823  
  6         286  
16 6     6   63 use MIME::Entity;
  6         12  
  6         3218  
17              
18             # Specify package verson
19             our $VERSION = '0.12';
20              
21             # Default exit value
22             our $DEFAULT_EXIT = 127; ## no critic (ProhibitMagicNumbers)
23              
24             # Oldschool constructor
25             sub new {
26 7     7 1 1386 my ( $class, %opts ) = @_;
27              
28             # Blindly slurp in all the options given
29 7         30 my $self = {%opts};
30              
31             # We must have a recipient
32             defined $self->{mailto}
33 7 100       196 or croak 'MAILTO required';
34              
35             # Default the instance name to the package name if it wasn't given;
36             # runcrypt(1) will pass it in
37 6 100       28 defined $self->{name} or $self->{name} = $class;
38              
39             # We default to encrypting but not signing
40 6 100       25 defined $self->{encrypt} or $self->{encrypt} = 1;
41 6 100       23 defined $self->{sign} or $self->{sign} = 0;
42              
43             # If signing, we need a key ID and a passphrase
44 6 100       23 if ( $self->{sign} ) {
45             defined $self->{keyid}
46 3 100       217 or croak 'Key ID required for signing';
47             defined $self->{passphrase}
48 2 100       128 or croak 'Passphrase required for signing';
49             }
50              
51             # Return objectified self
52 4         31 return bless $self, $class;
53             }
54              
55             # Run a given command
56             sub run {
57 0     0 1 0 my ( $self, @command ) = @_;
58              
59             # Run the command and wait for it to finish; keep its exit value for later
60 0         0 my ( @out, @err );
61 0 0       0 eval { run3 \@command, undef, \@out, \@err }
  0         0  
62             or warn "Command failed: $EVAL_ERROR\n";
63 0         0 $self->{exit} = $CHILD_ERROR >> 8;
64              
65             # If there was output, mail it
66 0 0       0 if (@out) {
67 0         0 my $command = join q{ }, @command;
68 0         0 my $subject = "$self->{name} output: $command";
69 0         0 $self->_mail( $subject, \@out );
70             }
71              
72             # If there were errors, mail them
73 0 0       0 if (@err) {
74 0         0 my $command = join q{ }, @command;
75 0         0 my $subject = "$self->{name} errors: $command";
76 0         0 $self->_mail( $subject, \@err );
77             }
78              
79             # Return status reflecting the command exit value
80 0         0 return $self->{exit} == 0;
81             }
82              
83             # Return the value of the most recently run command, or 1 otherwise
84             sub bail {
85 2     2 1 2539 my $self = shift;
86             my $exit =
87             defined $self->{exit}
88             ? $self->{exit}
89 2 50       18 : $DEFAULT_EXIT;
90 2         9 return $exit;
91             }
92              
93             # Send the message to the address in $ENV{MAILTO}
94             sub _mail {
95 0     0     my ( $self, $subject, $content ) = @_;
96              
97             # Build MIME object with plaintext message
98             my $mime = MIME::Entity->build(
99             To => $self->{mailto},
100 0           Subject => $subject,
101             Data => $content,
102             );
103              
104             # Encrypt the MIME object
105             my $mgpg = Mail::GnuPG->new(
106             key => $self->{keyid},
107             passphrase => $self->{passphrase},
108 0           );
109              
110             # Sign and/or encrypt as appropriate
111 0 0 0       if ( $self->{sign} and $self->{encrypt} ) {
    0          
    0          
112 0           $mgpg->mime_signencrypt( $mime, $self->{mailto} );
113             }
114             elsif ( $self->{sign} ) {
115 0           $mgpg->mime_sign( $mime, $self->{mailto} );
116             }
117             elsif ( $self->{encrypt} ) {
118 0           $mgpg->mime_encrypt( $mime, $self->{mailto} );
119             }
120              
121             # Send it
122 0           return $mime->send();
123             }
124              
125             1;
126              
127             __END__
128              
129             =pod
130              
131             =for stopwords
132             mailserver decrypt runcrypt GPG OpenPGP tradename licensable MERCHANTABILITY
133             mailto keyid
134              
135             =head1 NAME
136              
137             Mail::Run::Crypt - Encrypt and mail output from command runs
138              
139             =head1 VERSION
140              
141             Version 0.12
142              
143             =head1 DESCRIPTION
144              
145             This module runs a system command with L<IPC::Run3|IPC::Run3>, and collects any
146             standard output and standard error it emits. If there is any standard output or
147             standard error content, it is encrypted and optionally signed with GnuPG, and
148             then each stream's content is mailed separately to a specified recipient
149             address.
150              
151             The idea is to allow you to view the output of automated commands while having
152             the content encrypted as it passes through to your mailserver, and optionally
153             to have some assurance that the content was actually generated by the server
154             concerned. B<cron(8)> scripts are the ideal use case, but this would also work
155             with B<at(1)>, or anything else that might non-interactively run jobs for which
156             output is significant.
157              
158             You will probably want to call this with the L<B<runcrypt(1)>|runcrypt> program
159             provided by this distribution, which includes a means to set the properties for
160             the module via environment variables or command line options.
161              
162             =head1 SYNOPSIS
163              
164             use Mail::Run::Crypt;
165             ...
166             my $mrc = Mail::Run::Crypt->new(
167             mailto => 'you@example.net',
168             );
169             $mrc->run($command, @args);
170             ...
171             my $mrc = Mail::Run::Crypt->new(
172             sign => 1,
173             keyid => '0x1234DEAD5678BEEF',
174             passphrase => 'able was i ere i saw elba',
175             mailto => 'you@example.net',
176             );
177             $mrc->run($command, @args);
178            
179             =head1 SUBROUTINES/METHODS
180              
181             =head2 C<new(%opts)>
182              
183             Constructor method; accepts the following named parameters:
184              
185             =over 4
186              
187             =item C<mailto>
188              
189             The recipient email address for the content. This is always required.
190              
191             =item C<encrypt>
192              
193             Whether to encrypt the command output. This defaults to 1.
194              
195             =item C<sign>
196              
197             Whether to sign the command output. This defaults to 0. A C<keyid> and
198             C<passphrase> will be required for signing.
199              
200             It is I<strongly> recommended that a dedicated key and passphrase be used for
201             signatures if this is needed. You should carefully consider the consequences of
202             a compromised key.
203              
204             =item C<keyid>
205              
206             The GnuPG key ID that should be used to sign messages. This is required for
207             signing, and has no effect without C<sign>. It can be any means of identifying
208             the key acceptable to GnuPG; the key's 8-byte ("long") hexadecimal ID prefixed
209             with C<0x> is probably the best way.
210              
211             =item C<passphrase>
212              
213             The passphrase used to decrypt the key. This is required for signing, and has
214             no effect without C<sign>.
215              
216             =item C<name>
217              
218             (Optional) The name of the object. When called from the
219             L<B<runcrypt(1)>|runcrypt> program, this will be the string "runcrypt".
220             Otherwise, it will default to this package's name.
221              
222             =back
223              
224             =head2 C<run(@command)>
225              
226             Run the specified arguments as a command with L<IPC::Run3|IPC::Run3>, and email
227             any output or error content to the email recipient, encrypting and/or signing
228             as configured. Returns 1 if the command succeeded, 0 otherwise. Use
229             L<C<bail()>|/bail()> to get the actual exit code if needed.
230              
231             =head2 C<bail()>
232              
233             Return the exit status of the most recently run command, or 127 if no command
234             has been successfully run.
235              
236             =head1 DIAGNOSTICS
237              
238             =over 4
239              
240             =item C<mailto required>
241              
242             The required C<mailto> property was not passed in the constructor.
243              
244             =item C<keyid required for signing>
245              
246             Signing was specified, but no C<keyid> attribute was passed in the constructor.
247              
248             =item C<passphrase required for signing>
249              
250             Signing was specified, but no C<passphrase> attribute was passed in the constructor.
251              
252             =item C<command failed: %s>
253              
254             The command could not be run at all, raising the given error string. This is
255             typically due to problems finding the executable.
256              
257             =back
258              
259             =head1 CONFIGURATION AND ENVIRONMENT
260              
261             You will need to have a functioning GnuPG public key setup for this to work,
262             including stored keys or a key retrieval system for your recipients. You will
263             also need a secret key if you want to sign the messages.
264              
265             You should I<definitely not> use your personal key for this; generate one
266             specifically for mail signing and encryption instead.
267              
268             I wrote a tutorial on GnuPG key setup, including agent configuration, as part
269             of this series:
270              
271             L<https://sanctum.geek.nz/arabesque/series/gnu-linux-crypto/>
272              
273             =head1 DEPENDENCIES
274              
275             =over 4
276              
277             =item *
278              
279             Perl v5.8.1 or newer
280              
281             =item *
282              
283             L<Carp|Carp>
284              
285             =item *
286              
287             L<English|English>
288              
289             =item *
290              
291             L<IPC::Run3|IPC::Run3>
292              
293             =item *
294              
295             L<Mail::GnuPG|Mail::GnuPG>
296              
297             =item *
298              
299             L<MIME::Entity|MIME::Entity>
300              
301             =back
302              
303             =head1 INCOMPATIBILITIES
304              
305             This module uses L<Mail::GnuPG|Mail::GnuPG> and other GnuPG-specific code, so
306             it won't work with other OpenPGP implementations.
307              
308             =head1 BUGS AND LIMITATIONS
309              
310             Definitely. This code is not production-ready. The test suite coverage is still
311             not great, but should improve in newer versions.
312              
313             =head1 AUTHOR
314              
315             Tom Ryder C<< <tom@sanctum.geek.nz> >>
316              
317             =head1 LICENSE AND COPYRIGHT
318              
319             Copyright (C) 2017 Tom Ryder
320              
321             This program is free software; you can redistribute it and/or modify it under
322             the terms of the Artistic License (2.0). You may obtain a copy of the full
323             license at:
324              
325             L<http://www.perlfoundation.org/artistic_license_2_0>
326              
327             Any use, modification, and distribution of the Standard or Modified Versions is
328             governed by this Artistic License. By using, modifying or distributing the
329             Package, you accept this license. Do not use, modify, or distribute the
330             Package, if you do not accept this license.
331              
332             If your Modified Version has been derived from a Modified Version made by
333             someone other than you, you are nevertheless required to ensure that your
334             Modified Version complies with the requirements of this license.
335              
336             This license does not grant you the right to use any trademark, service mark,
337             tradename, or logo of the Copyright Holder.
338              
339             This license includes the non-exclusive, worldwide, free-of-charge patent
340             license to make, have made, use, offer to sell, sell, import and otherwise
341             transfer the Package with respect to any patent claims licensable by the
342             Copyright Holder that are necessarily infringed by the Package. If you
343             institute patent litigation (including a cross-claim or counterclaim) against
344             any party alleging that the Package constitutes direct or contributory patent
345             infringement, then this Artistic License to you shall terminate on the date
346             that such litigation is filed.
347              
348             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND
349             CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
350             WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
351             NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW.
352             UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR
353             ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY
354             OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
355             DAMAGE.
356              
357             =cut