#!/usr/bin/env perl use v5.16; use feature qw/say signatures/; use strict; use utf8; use warnings; use Data::UUID; use Email::Address; use Email::Simple; use File::Basename; use File::Copy; use File::Path; use File::Slurp; use Net::IMAP::Simple; use Net::SMTP; use Sys::Hostname::FQDN; use App::Localmail; # I've been at this crossroads before. This is the cpan command to run when # Perl updates... # # cpan -f -i Data::UUID Sys::Hostname::FQDN YAML::XS MIME::Base64 Authen::SASL sub main (@args) { my $action = shift @args; return 1 unless defined $action; my %actions = ( fetch => \&fetch, send => \&send, ); return 2 unless exists $actions{$action}; return $actions{$action}(App::Localmail::base_config(), @args); } sub fetch ($config, @args) { # Get all passwords App::Localmail::config_passwords($config); # Fetch email for each account my $uuid = Data::UUID->new; my $maildir = $config->{localmail}{maildir}; for my $account (keys %{$config->{accounts}}) { my $current = $config->{accounts}{$account}{incoming}; if (!defined $current->{password}{plain}) { say "No password for '$account'"; next; } say "Fetching mail for $account"; say " Connecting to $current->{server}:" . ($current->{port} // 993); my $connection = Net::IMAP::Simple->new( $current->{server}, #port => $current->{port} // 993, use_ssl => $current->{ssl} // 1, timeout => $current->{timeout} // 5, ); if (!$connection) { say " Failed: ". $Net::IMAP::Simple::errstr."\n"; next; } if ($current->{starttls}) { say ' Applying StartTLS'; $connection->starttls; } say ' Authenticating'; if (!$connection->login($current->{username}, $current->{password}{plain})) { say ' Failed: '.$connection->errstr."\n"; next; } say ' Fetching mailboxes'; for my $mbox ($connection->mailboxes) { $connection->select($mbox); my @messages = keys(%{$connection->list}); my $count = 0; say " $mbox (".(scalar @messages).")"; for my $message (@messages) { $count++; my $current_uuid = fc($uuid->to_string($uuid->create)); say " $current_uuid ($count)"; open my $fh, '>', "$maildir/tmp/$current_uuid.eml" or die $!; print $fh $connection->fetch($message); close $fh; move("$maildir/tmp/$current_uuid.eml", "$maildir/new/$current_uuid.eml"); $connection->delete($message); } $connection->close; } say ' Closing connection'; $connection->quit; } return 0; } sub send ($config, @args) { # Get all passwords App::Localmail::config_passwords($config); my $uuid = Data::UUID->new; my $maildir = $config->{localmail}{maildir}; # Create a hash of sender addresses and related accounts my %senders; for my $account (keys %{$config->{accounts}}) { for my $handler (@{$config->{accounts}{$account}{outgoing}{handles}}) { $senders{$handler} = $account; } } # Walk through the queue of emails to send for my $item (glob("$maildir/queue/*")) { next unless -d $item; next unless -f "$item/message.eml"; my ($id, $path) = File::Basename::fileparse($item); say "Sending $id"; my $body = File::Slurp::read_file("$item/message.eml"); my $email = Email::Simple->new($body); my ($from) = Email::Address->parse($email->header('From')); my @recipients = File::Slurp::read_file("$item/recipients"); if (!$from || !exists $senders{$from->address}) { say " No account handles mail from '$from'"; next; } my $current = $config->{accounts}{$senders{$from->address}}{outgoing}; my $port = $current->{port} // ($current->{ssl} ? 465 : 25); say " Connecting to ".$current->{server}.':'.$port; # Send the email my $connection = Net::SMTP->new( $current->{server}, Port => $port, SSL => $current->{ssl} // 0, Timeout => $current->{timeout} // 30, Hello => Sys::Hostname::FQDN::fqdn(), Debug => 1, ); if (!$connection) { say " Failed: ".$@; next; } if ($current->{starttls}) { say ' StartTLS'; $connection->starttls(); } say ' Authenticating'; if (!$connection->auth($current->{username}, $current->{password}{plain})) { say " Failed: ".$connection->message; next; } say ' MAIL FROM'; if (!$connection->mail($email->header('From'))) { say " Failed: ".$connection->message; next; } say ' RCPT TO'; if (!$connection->recipient(@recipients)) { say " Failed: ".$connection->message; next; } say ' DATA'; if (!$connection->data($body)) { say " Failed: ".$connection->message; next; } $connection->quit; say ' Sent!'; if (open my $fh, '>', "$maildir/sent/$id.eml") { say ' Moving to sentdir'; print $fh $email->as_string; close $fh; File::Path::rmtree($item); } } return 0; } exit main(@ARGV);