Update POD
[app-edwardng.git] / lib / App / EdwardNG.pm
CommitLineData
2f9e679a
MG
1package App::EdwardNG;
2
3use 5.014000;
4use strict;
5use warnings;
6use parent qw/Exporter/;
7our $VERSION = '0.001';
8our @EXPORT = qw/process_message/;
9
10use Email::Sender::Simple qw/sendmail/;
11use File::Slurp qw/read_file/;
b2664811 12use File::Spec::Functions qw/rel2abs/;
2f9e679a
MG
13use Getopt::Long;
14use MIME::Entity;
15use MIME::Parser;
16use Mail::GnuPG;
17use PerlX::Maybe;
18use Template;
19use Try::Tiny;
20
21sub debug { say STDERR @_ if $ENV{EDWARDNG_DEBUG} }
22sub stringify ($) { join '', map {; '>', $_ } @{$_[0]} }
23sub mg {
24 Mail::GnuPG->new(
25 key => $ENV{EDWARDNG_KEY},
26 maybe always_trust => $ENV{EDWARDNG_ALWAYS_TRUST},
27 maybe keydir => $ENV{EDWARDNG_KEYDIR},
28 maybe passphrase => $ENV{EDWARDNG_PASSPHRASE},
29 maybe use_agent => $ENV{EDWARDNG_USE_AGENT},
30 @_);
31}
32
33sub first_part{
34 my ($ent) = @_;
35 return first_part $ent->parts(0) if $ent->parts;
36 $ent->bodyhandle->as_string
37}
38
39sub process_message {
4224c4ce
MG
40 my ($in) = @_;
41 my $msg;
2f9e679a
MG
42 my $parser = MIME::Parser->new;
43 $parser->decode_bodies(0);
44 $parser->output_to_core(1);
45
4224c4ce
MG
46 $msg = $in if ref $in eq 'MIME::Entity';
47 $msg = $parser->parse ($in) if ref $in eq 'IO';
48 $msg = $parser->parse_data ($in) if ref $in eq 'SCALAR';
49 $msg = $parser->parse_open ($in) unless ref $in;
50 die "Don't know how to parse $in" unless $msg;
2f9e679a
MG
51
52 if ($msg->mime_type ne 'multipart/signed' && $msg->mime_type ne 'multipart/encrypted') {
53 # PGP/Inline requires decoding
54 $parser->decode_bodies(1);
55 $msg = $parser->parse_data($msg->stringify)
56 }
57
58 my $gpg = mg;
59 if ($gpg->is_signed($msg)) {
60 debug 'This mail looks signed';
61 my ($code, $keyid, $email) = $gpg->verify($msg);
62 return sign_error => (
63 message => stringify $gpg->{last_message}) if $code;
64 return sign => (
65 keyid => $keyid,
66 email => $email,
67 message => stringify $gpg->{last_message});
68 }
69
70 if ($gpg->is_encrypted($msg)) {
71 debug 'This mail looks encrypted';
72 my ($code, $keyid, $email) = $gpg->decrypt($msg);
73 return encrypt_error => (
74 message => stringify $gpg->{last_message}) if $code;
75 return encrypt => (
76 plaintext => stringify $gpg->{plaintext},
77 decrypted => $gpg->{decrypted},
78 message => stringify $gpg->{last_message}) unless defined $keyid;
79 return signencrypt => (
80 keyid => $keyid,
81 email => $email,
82 plaintext => stringify $gpg->{plaintext},
83 decrypted => $gpg->{decrypted},
84 message => stringify $gpg->{last_message});
85 }
86
87 debug 'This mail doesn\'t seem to be signed or encrypted';
88 return 'plain'
89}
90
91sub run {
92 GetOptions(
93 'always-trust!' => \$ENV{EDWARDNG_ALWAYS_TRUST},
94 'debug!' => \$ENV{EDWARDNG_DEBUG},
95 'from=s' => \$ENV{EDWARDNG_FROM},
96 'key=s' => \$ENV{EDWARDNG_KEY},
97 'keydir=s' => \$ENV{EDWARDNG_KEYDIR},
98 'passphrase=s' => \$ENV{EDWARDNG_PASSPHRASE},
b2664811 99 'tmpl-path=s' => \$ENV{EDWARDNG_TMPL_PATH},
2f9e679a
MG
100 'use-agent!' => \$ENV{EDWARDNG_USE_AGENT},
101 );
b2664811 102 my $tmpl_path = $ENV{EDWARDNG_TMPL_PATH} // 'en';
2f9e679a
MG
103
104 my $parser = MIME::Parser->new;
105 $parser->decode_bodies(0);
106 $parser->output_to_core(1);
107 my $in = $parser->parse(\*STDIN);
108
109 my ($tmpl, %params);
110 try {
111 ($tmpl, %params) = process_message $in
112 } catch {
113 ($tmpl, %params) = (error => message => $_)
114 };
115
116 $params{plaintext} = first_part $params{decrypted} if $params{decrypted};
117
b2664811 118 my $tt = Template->new(INCLUDE_PATH => rel2abs $tmpl_path, 'tmpl');
c3455a5b 119 my $data;
2f9e679a 120 $tt->process($tmpl, \%params, \$data);
2f9e679a
MG
121 my $email = MIME::Entity->build(
122 From => $ENV{EDWARDNG_FROM},
123 To => $in->get('From'),
c3455a5b 124 Subject => 'Re: ' . $in->get('Subject'),
2f9e679a
MG
125 Data => $data);
126
70f9c37d 127 my $email_unencrypted = $email->dup;
2f9e679a 128 my $mg = mg always_trust => 1;
70f9c37d
MG
129 my $encrypt_failed = $mg->mime_signencrypt($email, $in->get('From') =~ /<(.*)>/);
130 debug 'Could not encrypt message, sending unencrypted. GnuPG said ', stringify $mg->{last_message} if $encrypt_failed;
131 sendmail $encrypt_failed ? $email_unencrypted : $email
2f9e679a
MG
132}
133
1341;
135__END__
136
137=encoding utf-8
138
139=head1 NAME
140
141App::EdwardNG - GnuPG email sign/encrypt testing bot
142
143=head1 SYNOPSIS
144
145 use App::EdwardNG;
146 my ($status, %params) = process_message '/path/to/message';
147 if ($status eq 'signencrypt') {
148 say 'This message is encrypted and signed with key ', $params{keyid}, ' from ', $params{email};
149 say 'Its contents are: ', $params{plaintext};
150 } elsif ($status eq 'encrypt') {
151 say 'This message is encrypted but not signed';
152 say 'Its contents are: ', $params{plaintext};
153 } elsif ($status eq 'encrypt_error') {
154 say 'This message is encrypted but I was unable to decrypt it. GnuPG output: ', $params{message};
155 } elsif ($status eq 'sign') {
156 say 'This message is signed with key ', $params{keyid}, ' from ', $params{email};
157 } elsif ($status eq 'sign_error') {
158 say 'This message is signed but I was unable to verify the signature. GnuPG output: ', $params{message};
159 } elsif ($status eq 'plain') {
160 say 'This message is neither signed nor encrypted';
161 } elsif ($status eq 'error') {
162 say 'There was an error processing the message: ', $params{message};
163 }
164
165=head1 DESCRIPTION
166
167EdwardNG is a reimplementation of the Edward reply bot referenced in L<https://emailselfdefense.fsf.org/>.
168
2f9e679a
MG
169This module exports a single function, B<process_message>, which takes a single parameter representing the message. This parameter can be:
170
171=over
172
173=item A filehandle reference, e.g. C<\*STDIN>.
174
175=item A reference to a scalar which holds the message contents.
176
177=item A scalar which represents a path to a message.
178
179=item A L<MIME::Entity> object
180
181=back
182
183The function returns a status followed by a hash. Possible results:
184
185=over
186
187=item plain
188
189The message is neither signed nor encrypted.
190
191=item sign_error, message => $message
192
193The message is signed but the signature could not be verified. GnuPG output is $message.
194
195=item sign, keyid => $keyid, email => $email, message => $message
196
197The message is signed with key $keyid from $email. GnuPG output is $message.
198
199=item encrypt_error, message => $message
200
201The message is encrypted and unable to be decrypted. GnuPG output is $message.
202
203=item encrypt, plaintext => $plaintext, decrypted => $decrypted, message => $message
204
205The message is encrypted and unsigned. $plaintext is the decrypted message as plain text, while $decrypted is a MIME::Entity representing the decrypted message. GnuPG output is $message.
206
207=item signencrypt, plaintext => $plaintext, decrypted => $decrypted, keyid => $keyid, email => $email, message => $message
208
209The message is encrypted and signed with key $keyid from $email. $plaintext is the decrypted message as plain text, while $decrypted is a MIME::Entity representing the decrypted message. GnuPG output is $message.
210
211=item error, message => $message
212
213There was an error while processing the message. The error can be found in $message.
214
215=back
216
217=head1 ENVIRONMENT
218
219This module is configured via the %ENV hash. See the L<edwardng(1)> manpage for more information.
220
221=head1 SEE ALSO
222
223L<edwardng(1)>
224
225=head1 AUTHOR
226
227Marius Gavrilescu, E<lt>marius@ieval.roE<gt>
228
229=head1 COPYRIGHT AND LICENSE
230
1f08c352 231Copyright (C) 2014 by Fundația Ceata
2f9e679a
MG
232
233This library is free software; you can redistribute it and/or modify
234it under the same terms as Perl itself, either Perl version 5.18.2 or,
235at your option, any later version of Perl 5 you may have available.
236
237
238=cut
This page took 0.024237 seconds and 4 git commands to generate.