+package App::EdwardNG;
+
+use 5.014000;
+use strict;
+use warnings;
+use parent qw/Exporter/;
+our $VERSION = '0.001';
+our @EXPORT = qw/process_message/;
+
+use Email::Sender::Simple qw/sendmail/;
+use File::Slurp qw/read_file/;
+use Getopt::Long;
+use MIME::Entity;
+use MIME::Parser;
+use Mail::GnuPG;
+use PerlX::Maybe;
+use Template;
+use Try::Tiny;
+
+sub debug { say STDERR @_ if $ENV{EDWARDNG_DEBUG} }
+sub stringify ($) { join '', map {; '>', $_ } @{$_[0]} }
+sub mg {
+ Mail::GnuPG->new(
+ key => $ENV{EDWARDNG_KEY},
+ maybe always_trust => $ENV{EDWARDNG_ALWAYS_TRUST},
+ maybe keydir => $ENV{EDWARDNG_KEYDIR},
+ maybe passphrase => $ENV{EDWARDNG_PASSPHRASE},
+ maybe use_agent => $ENV{EDWARDNG_USE_AGENT},
+ @_);
+}
+
+sub first_part{
+ my ($ent) = @_;
+ return first_part $ent->parts(0) if $ent->parts;
+ $ent->bodyhandle->as_string
+}
+
+sub process_message {
+ my ($msg) = @_;
+ my $parser = MIME::Parser->new;
+ $parser->decode_bodies(0);
+ $parser->output_to_core(1);
+
+ if (ref $msg eq 'MIME::Entity') {
+ debug 'Got MIME::Entity';
+ } elsif (ref $msg eq 'IO') {
+ debug 'Parsing from filehandle';
+ $msg = $parser->parse($msg)
+ } elsif (ref $msg eq 'SCALAR') {
+ debug 'Parsing from string';
+ $msg = $parser->parse_data($$msg)
+ } elsif (!ref $msg) {
+ debug "Parsing from file $msg";
+ $msg = $parser->parse_open($msg)
+ } else {
+ die "Don't know how to parse $msg"
+ }
+
+ if ($msg->mime_type ne 'multipart/signed' && $msg->mime_type ne 'multipart/encrypted') {
+ # PGP/Inline requires decoding
+ $parser->decode_bodies(1);
+ $msg = $parser->parse_data($msg->stringify)
+ }
+
+ my $gpg = mg;
+ if ($gpg->is_signed($msg)) {
+ debug 'This mail looks signed';
+ my ($code, $keyid, $email) = $gpg->verify($msg);
+ return sign_error => (
+ message => stringify $gpg->{last_message}) if $code;
+ return sign => (
+ keyid => $keyid,
+ email => $email,
+ message => stringify $gpg->{last_message});
+ }
+
+ if ($gpg->is_encrypted($msg)) {
+ debug 'This mail looks encrypted';
+ my ($code, $keyid, $email) = $gpg->decrypt($msg);
+ return encrypt_error => (
+ message => stringify $gpg->{last_message}) if $code;
+ return encrypt => (
+ plaintext => stringify $gpg->{plaintext},
+ decrypted => $gpg->{decrypted},
+ message => stringify $gpg->{last_message}) unless defined $keyid;
+ return signencrypt => (
+ keyid => $keyid,
+ email => $email,
+ plaintext => stringify $gpg->{plaintext},
+ decrypted => $gpg->{decrypted},
+ message => stringify $gpg->{last_message});
+ }
+
+ debug 'This mail doesn\'t seem to be signed or encrypted';
+ return 'plain'
+}
+
+sub run {
+ GetOptions(
+ 'always-trust!' => \$ENV{EDWARDNG_ALWAYS_TRUST},
+ 'debug!' => \$ENV{EDWARDNG_DEBUG},
+ 'from=s' => \$ENV{EDWARDNG_FROM},
+ 'key=s' => \$ENV{EDWARDNG_KEY},
+ 'keydir=s' => \$ENV{EDWARDNG_KEYDIR},
+ 'passphrase=s' => \$ENV{EDWARDNG_PASSPHRASE},
+ 'use-agent!' => \$ENV{EDWARDNG_USE_AGENT},
+ );
+
+ my $parser = MIME::Parser->new;
+ $parser->decode_bodies(0);
+ $parser->output_to_core(1);
+ my $in = $parser->parse(\*STDIN);
+
+ my ($tmpl, %params);
+ try {
+ ($tmpl, %params) = process_message $in
+ } catch {
+ ($tmpl, %params) = (error => message => $_)
+ };
+
+ $params{plaintext} = first_part $params{decrypted} if $params{decrypted};
+
+ my $tt = Template->new(INCLUDE_PATH => 'tmpl/en');
+ my ($data, $subject);
+ $tt->process($tmpl, \%params, \$data);
+ $tt->process('subject', undef, \$subject);
+ my $email = MIME::Entity->build(
+ From => $ENV{EDWARDNG_FROM},
+ To => $in->get('From'),
+ Subject => $subject,
+ Data => $data);
+
+ my $mg = mg always_trust => 1;
+ $mg->mime_signencrypt($email, $in->get('From') =~ /<(.*)>/) and debug 'Could not encrypt message. GnuPG said ', stringify $mg->{last_message};
+ sendmail $email
+}
+
+1;
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+App::EdwardNG - GnuPG email sign/encrypt testing bot
+
+=head1 SYNOPSIS
+
+ use App::EdwardNG;
+ my ($status, %params) = process_message '/path/to/message';
+ if ($status eq 'signencrypt') {
+ say 'This message is encrypted and signed with key ', $params{keyid}, ' from ', $params{email};
+ say 'Its contents are: ', $params{plaintext};
+ } elsif ($status eq 'encrypt') {
+ say 'This message is encrypted but not signed';
+ say 'Its contents are: ', $params{plaintext};
+ } elsif ($status eq 'encrypt_error') {
+ say 'This message is encrypted but I was unable to decrypt it. GnuPG output: ', $params{message};
+ } elsif ($status eq 'sign') {
+ say 'This message is signed with key ', $params{keyid}, ' from ', $params{email};
+ } elsif ($status eq 'sign_error') {
+ say 'This message is signed but I was unable to verify the signature. GnuPG output: ', $params{message};
+ } elsif ($status eq 'plain') {
+ say 'This message is neither signed nor encrypted';
+ } elsif ($status eq 'error') {
+ say 'There was an error processing the message: ', $params{message};
+ }
+
+=head1 DESCRIPTION
+
+EdwardNG is a reimplementation of the Edward reply bot referenced in L<https://emailselfdefense.fsf.org/>.
+
+It takes mail messages, checks them for PGP signatures and encryption, then replies appropriately.
+
+This module exports a single function, B<process_message>, which takes a single parameter representing the message. This parameter can be:
+
+=over
+
+=item A filehandle reference, e.g. C<\*STDIN>.
+
+=item A reference to a scalar which holds the message contents.
+
+=item A scalar which represents a path to a message.
+
+=item A L<MIME::Entity> object
+
+=back
+
+The function returns a status followed by a hash. Possible results:
+
+=over
+
+=item plain
+
+The message is neither signed nor encrypted.
+
+=item sign_error, message => $message
+
+The message is signed but the signature could not be verified. GnuPG output is $message.
+
+=item sign, keyid => $keyid, email => $email, message => $message
+
+The message is signed with key $keyid from $email. GnuPG output is $message.
+
+=item encrypt_error, message => $message
+
+The message is encrypted and unable to be decrypted. GnuPG output is $message.
+
+=item encrypt, plaintext => $plaintext, decrypted => $decrypted, message => $message
+
+The 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.
+
+=item signencrypt, plaintext => $plaintext, decrypted => $decrypted, keyid => $keyid, email => $email, message => $message
+
+The 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.
+
+=item error, message => $message
+
+There was an error while processing the message. The error can be found in $message.
+
+=back
+
+=head1 ENVIRONMENT
+
+This module is configured via the %ENV hash. See the L<edwardng(1)> manpage for more information.
+
+=head1 SEE ALSO
+
+L<edwardng(1)>
+
+=head1 AUTHOR
+
+Marius Gavrilescu, E<lt>marius@ieval.roE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2014 by Marius Gavrilescu
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.18.2 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut