Add Email plugin
authorMarius Gavrilescu <marius@ieval.ro>
Sat, 2 May 2015 19:59:19 +0000 (22:59 +0300)
committerMarius Gavrilescu <marius@ieval.ro>
Sat, 2 May 2015 20:05:21 +0000 (23:05 +0300)
MANIFEST
Makefile.PL
config.pl
lib/App/FonBot/Daemon.pm
lib/App/FonBot/Plugin/Config.pm
lib/App/FonBot/Plugin/Email.pm [new file with mode: 0644]

index de2e9c6a8d023d3f2cc5fd4f28a243b2249aa86b..cecb0b427aec81cda210fa829a5ad080fb701257 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -10,6 +10,7 @@ t/App-FonBot-Daemon.t
 lib/App/FonBot/Daemon.pm
 lib/App/FonBot/Plugin/IRC.pm
 lib/App/FonBot/Plugin/Config.pm
+lib/App/FonBot/Plugin/Email.pm
 lib/App/FonBot/Plugin/HTTPD.pm
 lib/App/FonBot/Plugin/Common.pm
 lib/App/FonBot/Plugin/BitlBee.pm
index 78517c1f8e0af4933490221839eb91c375c9e152..5e7caf1e5a5a824cdf6b9d50ca6055c9c79ec4c9 100644 (file)
@@ -11,9 +11,14 @@ WriteMakefile(
        SIGN              => 1,
        PREREQ_PM         => {
                qw/Apache2::Authen::Passphrase             0
+                  Email::Sender::Simple                   0
+                  Email::Simple                           0
+                  Email::MIME                             0
+                  File::Slurp                             0
                   HTTP::Status                            0
                   IRC::Utils                              0
                   JSON                                    0
+                  Linux::Inotify2                         0
                   Log::Log4perl                           0
                   POE                                     0
                   POE::Component::IRC                     0
@@ -24,6 +29,7 @@ WriteMakefile(
                   POE::Component::SSLify                  0
 
                   DB_File          0
+                  File::Glob       0
                   MIME::Base64     0
                   Storable         0
                   Text::ParseWords 0/,
index c384d447f927cd56c19c7942acc2b776fcf1da53..2c420341d4babef1a4768eaeecf365be834e8299 100644 (file)
--- a/config.pl
+++ b/config.pl
@@ -55,5 +55,17 @@ $httpd_port = 8888;
 # Read the authentication data from this directory
 $Apache2::Authen::Passphrase::rootdir = "$rootdir/us";
 
+# Email
+# =====
+
+# Delay emails for up to 5 seconds to allow batching
+$email_batch_seconds = 5;
+
+# Send emails from this address
+$email_from = 'FonBot <fonbot@example.com>';
+
+# Send emails with this subject
+$email_subject = 'FonBot message';
+
 # End the configuration file
 return 1;
index 783248cdea778e1bed3ae4abbcf94dea3ef2a989..135681ce1e123b272f7798b318e475043b0a28b8 100644 (file)
@@ -14,10 +14,11 @@ use App::FonBot::Plugin::Common;
 use App::FonBot::Plugin::OFTC;
 use App::FonBot::Plugin::BitlBee;
 use App::FonBot::Plugin::HTTPD;
+use App::FonBot::Plugin::Email;
 
 use sigtrap qw/die normal-signals/;
 
-use constant PLUGINS => map { "App::FonBot::Plugin::$_" } qw/Config Common OFTC BitlBee HTTPD/;
+use constant PLUGINS => map { "App::FonBot::Plugin::$_" } qw/Config Common OFTC BitlBee HTTPD Email/;
 
 ##################################################
 
index 744f06368104e2c8f7bac23f80623bec661663d9..8bcb682e203b9252378cca9f471fd3f64382f840 100644 (file)
@@ -11,13 +11,14 @@ use parent qw/Exporter/;
 use Apache2::Authen::Passphrase;
 use Log::Log4perl qw//;
 
-our @EXPORT_OK=qw/$oftc_enabled $oftc_nick @oftc_channels $oftc_nickserv_password $bitlbee_enabled $bitlbee_nick $bitlbee_server $bitlbee_port $bitlbee_password $dir $user $group @supplementary_groups $httpd_port/;
+our @EXPORT_OK=qw/$oftc_enabled $oftc_nick @oftc_channels $oftc_nickserv_password $bitlbee_enabled $bitlbee_nick $bitlbee_server $bitlbee_port $bitlbee_password $dir $user $group @supplementary_groups $httpd_port $email_batch_seconds $email_from $email_subject/;
 
 ##################################################
 
 our ($oftc_enabled, $oftc_nick, @oftc_channels, $oftc_nickserv_password);
 our ($bitlbee_enabled, $bitlbee_nick, $bitlbee_server, $bitlbee_port, $bitlbee_password);
 our ($dir, $user, $group, @supplementary_groups);
+our ($email_batch_seconds, $email_from, $email_subject);
 
 ##################################################
 
@@ -71,6 +72,11 @@ App::FonBot::Plugin::Config - FonBot plugin for reading configuration files
   # Variables used in App::FonBot::Plugin::HTTPD
   say "The HTTPD listens on port $httpd_port"
 
+  # Variables used in App::FonBot::Plugin::Email
+  say "The email batch delay is $email_batch_seconds";
+  say "The email plugin sends emails from $email_from";
+  say "The email plugin sends emails with subject $email_subject";
+
 =head1 DESCRIPTION
 
 This FonBot plugin reads a configuration file (hardcoded to F</etc/fonbot/config.pl>) and provides configuration variables to the other plugins. It is a required plugin, since all other plugins depend on it.
diff --git a/lib/App/FonBot/Plugin/Email.pm b/lib/App/FonBot/Plugin/Email.pm
new file mode 100644 (file)
index 0000000..ff29dfc
--- /dev/null
@@ -0,0 +1,209 @@
+package App::FonBot::Plugin::Email;
+
+our $VERSION = '0.000_5';
+
+use v5.14;
+use strict;
+use warnings;
+
+use Apache2::Authen::Passphrase qw/pwcheck/;
+use Email::Sender::Simple qw/sendmail/;
+use Email::Simple;
+use Email::MIME;
+use Linux::Inotify2 qw/IN_MOVED_TO/;
+use File::Slurp qw/read_file/;
+use POE;
+use Log::Log4perl qw//;
+
+use File::Glob qw/bsd_glob GLOB_NOSORT/;
+use Text::ParseWords qw/shellwords/;
+
+use App::FonBot::Plugin::Common;
+use App::FonBot::Plugin::Config qw/$email_batch_seconds $email_from $email_subject/;
+
+##################################################
+
+my $log=Log::Log4perl->get_logger(__PACKAGE__);
+
+my %queues;
+my %queue_alarms;
+my $session;
+my $inotify;
+
+sub init{
+       return unless $email_from && $email_subject;
+       $log->info('initializing '.__PACKAGE__);
+       $session = POE::Session->create(
+               inline_states => {
+                       _start => \&email_start,
+                       send_message => \&email_send_message,
+                       flush_queue => \&email_flush_queue,
+                       inotify_readable => sub{
+                               $_[HEAP]{inotify}->poll
+                       },
+                       shutdown => sub{
+                               $_[KERNEL]->select_read($inotify)
+                       }
+               },
+       );
+}
+
+sub fini{
+       $log->info('finishing '.__PACKAGE__);
+       POE::Kernel->post($session, 'shutdown')
+}
+
+
+sub email_handle_new{
+       for my $file (bsd_glob 'Maildir/new/*', GLOB_NOSORT) {
+               my $email=Email::MIME->new(scalar read_file $file);
+
+               #untaint $file
+               $file =~ /^(.*)$/;
+               $file = $1;
+
+               unlink $file;
+               return unless defined $email;
+
+               my $replyto=$email->header('From');
+               return unless defined $replyto;
+
+               my ($user,$password)=split ' ', $email->header('Subject'), 2;
+               chomp $password;
+
+               $log->debug("Processing email from $user");
+
+               eval { pwcheck $user, $password };
+               if ($@) {
+                       $log->debug("Incorrect credentials in email subject from user $user. Exception: $@");
+                       POE::Kernel->yield(send_message => $replyto, "Incorrect credentials");
+                       return
+               }
+
+               $ok_user_addresses{"$user EMAIL $replyto"}=1;
+
+               my $process_email_part = sub {
+                       local *__ANON__ = "process_email_part"; #Name this sub. See http://www.perlmonks.org/?node_id=304883
+
+                       my $part=$_[0];
+                       return unless $part->content_type =~ /text\/plain/;
+
+                       my @lines=split '\n', $part->body;
+
+                       for my $line (@lines) {
+                               last if $line =~ /^--/;
+                               $log->debug("Command received via email from $user: $line");
+                               sendmsg $user, undef, "EMAIL '$replyto'", shellwords $line
+                       }
+               };
+
+               $email->walk_parts($process_email_part);
+       }
+}
+
+sub email_start{
+       $_[KERNEL]->alias_set('EMAIL');
+       $_[HEAP]{inotify} = Linux::Inotify2->new;
+       $_[HEAP]{inotify}->watch('Maildir/new/',IN_MOVED_TO,\&email_handle_new);
+
+       open $inotify,'<&=',$_[HEAP]{inotify}->fileno;
+       $_[KERNEL]->select_read($inotify, 'inotify_readable');
+       email_handle_new
+}
+
+sub email_send_message{
+       my ($address, $message) = @_[ARG0,ARG1];
+
+       $queues{$address}.=$message."\n";
+       if (defined $queue_alarms{$address}) {
+               $_[KERNEL]->delay_adjust($queue_alarms{$address}, $email_batch_seconds)
+       } else {
+               $queue_alarms{$address}=$_[KERNEL]->delay_set(flush_queue => $email_batch_seconds, $address)
+       }
+}
+
+sub email_flush_queue{
+       my ($queue) = $_[ARG0];
+       return unless exists $queues{$queue};
+
+       my $email=Email::Simple->create(
+               header => [
+                       From => $email_from,
+                       To => $queue,
+                       Subject => $email_subject,
+               ],
+               body => $queues{$queue}
+       );
+
+       delete $queues{$queue};
+       delete $queue_alarms{$queue};
+
+       $log->debug("Sending email to $queue");
+
+       eval {
+               sendmail $email
+       } or $log->error("Could not send email: " . $@->message);
+}
+
+1;
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+App::FonBot::Plugin::Email - FonBot plugin for receiving commands and sending messages through emails
+
+=head1 SYNOPSIS
+
+    use App::FonBot::Plugin::Email;
+    App::FonBot::Plugin::Email->init;
+    ...
+    App::FonBot::Plugin::Email->fini;
+
+=head1 DESCRIPTION
+
+This FonBot plugin provides email receiving/sending features to B<fonbotd>. Emails are read from F<Maildir/> and are sent through C<Email::Sender::Simple>.
+
+=head1 CONFIGURATION VARIABLES
+
+These are the L<App::FonBot::Plugin::Config> configuration variables used in this module
+
+=over
+
+=item C<$email_batch_seconds>
+
+When receiving an email send request, C<App::FonBot::Plugin::Email> waits this many seconds for further email send requests to the same email address. The timer is reset for each email send request. When the timer expires, all pending send requests are batched and sent as one email.
+
+=item C<$email_from>
+
+C<From:> header of all emails sent by this plugin. If not set the email plugin is disabled.
+
+=item C<$email_subject>
+
+C<Subject:> header of all emails sent by this plugin. If not set the email plugin is disabled.
+
+=back
+
+=head1 AUTHOR
+
+Marius Gavrilescu C<< marius@ieval.ro >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2013 Marius Gavrilescu
+
+This file is part of fonbotd.
+
+fonbotd is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+fonbotd is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with fonbotd.  If not, see <http://www.gnu.org/licenses/>
This page took 0.016441 seconds and 4 git commands to generate.