#!/usr/bin/perl use strict; use warnings; use File::Find; use MIME::Parser; use MIME::Words qw(decode_mimewords); use HTML::Entities; use Net::XMPP; # location to look up mails my $maildir = '/var/vmail'; # file to cache seen mails my $seenFile = '/var/cache/bot/mailnotified'; # which directories will be ignored? my $ignorepattern = qr/^.*\.(junk|spam|trash|sent|draft)$/i; # Jabber account data my $user = 'bot'; my $server = 'example.org'; my $pass = 'JABBERPASSWORD'; my $tls = 1; # use tls? my %users = ( 'mail1@example.org' => 'jid1@example.org', 'mail2@example.org' => 'jid1@example.org', 'mail3@example.org' => 'jid2@example.org', ); my %mails; my @notified; sub findMails() { return find({ wanted => \&wanted, follow => 0, no_chdir => 1, }, $maildir); } sub wanted() { return unless -d _; return unless m#$maildir/([^/]+)/([^/]+)/Maildir/(?:([^/]+)/)?new/(.*)$#; my $user = "$2\@$1"; my $box = defined $3 ? $3 : 'inbox'; my $mail = $4; return if $box =~ /$ignorepattern/; return unless exists $users{$user}; my $jabber = $users{$user}; $mails{$jabber} = [] unless $mails{$jabber}; push @{$mails{$jabber}}, $_; } sub readMail($) { my ($file) = @_; my $parser = new MIME::Parser; $parser->tmp_to_core(1); $parser->use_inner_files(1); $parser->output_to_core(1); return $parser->parse_open($file); } sub stripHtml($) { my ($html) = @_; $html =~ s/<[^>]+>//g; $html = decode_entities($html); return $html; } sub getText($) { my ($mail) = @_; my $parts_num = $mail->parts; my $message = ''; return join('', @{$mail->body}) if $parts_num == 0; for (0 .. $parts_num - 1) { my $part = $mail->parts($_); my $mimetype = $part->mime_type; if ($mimetype eq 'text/plain') { return $part->body_as_string; } elsif($mimetype eq 'text/html') { $message = $part->body_as_string; $message = stripHtml($message); } elsif($mimetype eq 'application/pgp-encrypted' and $message eq '') { $message = "(content is encrypted)"; } } return $message; } sub mailToString($) { my ($mail) = @_; my $head = $mail->head; $head->unfold; $head->decode; my $msg = ''; $msg .= "From: " . ($head->get("From") || '(unset)'); $msg .= "Subject: " . ($head->get("Subject") || '(unsert)'); $msg .= "\n"; my $text = getText($mail); if (length $text > 128) { $text = substr($text, 0, 125) . "..."; } $msg .= $text; return $msg; } sub sendNotifications($%) { my ($jabber, %mails) = @_; for my $jid (keys %mails) { for my $mailfile (@{$mails{$jid}}) { my $mail = readMail($mailfile); $mail = mailToString($mail) . "\n"; sendNotification($jabber, $jid, $mail); } } } sub connectToJabber() { my $jabber = Net::XMPP::Client->new(); $jabber->Connect( hostname => $server, tls => $tls ) or die("Cannot connect to server: " . $jabber->GetErrorCode . "\n"); my @result = $jabber->AuthSend( hostname => $server, username => $user, password => $pass, resource => $0); die "Auth as $user\@$server/$0 failed: $result[1]\n" unless @result && $result[0] eq 'ok'; $jabber->PresenceSend(type => 'unavailable'); return $jabber; } sub sendNotification($$$) { my ($jabber, $jid, $message) = @_; $jabber->MessageSend( to => $jid, body => $message); } sub disconnectFromJabber($) { my ($jabber) = @_; $jabber->Disconnect(); } sub loadSeenFiles() { open(my $fh, $seenFile) or return (); chomp(my @result = <$fh>); close $fh; return @result; } sub saveSeen(@) { open(my $fh, '>', $seenFile) or die "Could not save file: $!"; for(@_) { print $fh "$_\n"; } close $fh; } sub removeAlreadySeen(\@\%) { my ($seen, $mails) = @_; for my $account (keys %{$mails}) { for(my $mail = 0; $mail < @{$mails->{$account}}; ++$mail) { my $file = $mails->{$account}->[$mail]; if (grep $file eq $_, @{$seen}) { splice @{$mails->{$account}}, $mail, 1; --$mail; } } if(@{$mails->{$account}} == 0) { delete $mails->{$account}; } } } sub cleanupSeen(\@) { my ($seen) = @_; for (my $i = 0; $i < @{$seen}; ++$i) { if($seen->[$i] eq '' || ! -f $seen->[$i]) { splice @{$seen}, $i, 1; --$i; } } } sub addNewSeen(\@\%) { my ($seen, $mails) = @_; for my $account (keys %{mails}) { for my $mail (@{$mails->{$account}}) { push @{$seen}, $mail; } } } findMails(); my @seen = loadSeenFiles(); cleanupSeen(@seen); removeAlreadySeen(@seen, %mails); addNewSeen(@seen, %mails); saveSeen(@seen); exit 0 unless (%mails); my $jabber = connectToJabber(); sendNotifications($jabber, %mails); disconnectFromJabber($jabber);