#!/usr/bin/env perl # # Copyright (c) 2022 Mischa Peters # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # use 5.024; use strict; use warnings; use autodie; use Getopt::Std; use DBI; use POSIX qw(strftime); # -c = name SQL config file for OpenSMTPD, located in /etc/mail # -l = logging of virtual vacation parsed report/filter streams and decisions # -v = verbose (extra flag) logging of report stream # -d = debug (extra flag) logging of filter stream getopts('c:lvd'); our($opt_c, $opt_l, $opt_v, $opt_d); my $db_type = 'MariaDB'; my $db_host = ''; my $db_user = ''; my $db_pass = ''; my $db_name = ''; if ($opt_c && -e "/etc/mail/$opt_c") { open (my $fh_config, '<', "/etc/mail/$opt_c"); while (my $line = <$fh_config>) { chomp $line; if ($line =~ /^host\s+(.*)$/) { $db_host = $1; } if ($line =~ m/^username\s+(.*)$/) { $db_user = $1; } if ($line =~ m/^password\s+(.*)$/) { $db_pass = $1; } if ($line =~ m/^database\s+(.*)$/) { $db_name = $1; } } close $fh_config; } else { print "ERROR: UNABLE TO LOCATE CONFIG FILE!\n"; exit 1; } my %ooo; my $email = ''; my $from = ''; my $dbh = DBI->connect("DBI:$db_type:$db_name:$db_host", "$db_user", "$db_pass", {RaiseError => 1}); my $selvacation = $dbh->prepare("SELECT subject,body FROM vacation WHERE email=? and active='1'"); my $selcache = $dbh->prepare("SELECT cache FROM vacation WHERE email=? AND FIND_IN_SET(?, cache)"); my $upcache = $dbh->prepare("UPDATE vacation SET cache=CONCAT(cache,',',?) WHERE email=?"); sub dolog { my ($fh, $msg, $opt) = @_; print $fh (POSIX::strftime("%h %d %H:%M:%S ", localtime) . "Virtual Vacation: $msg\n") if ($opt); } open (my $fh, '>', "/tmp/virtualvacation.log") if ($opt_l || $opt_v || $opt_d); select(STDOUT); $|++; select($fh); $|++; print STDOUT "register|report|smtp-in|tx-mail\n"; print STDOUT "register|report|smtp-in|tx-rcpt\n"; print STDOUT "register|filter|smtp-in|data-line\n"; print STDOUT "register|ready\n"; while (my $line = <>) { next if ($line =~ m/^config/); chomp $line; if ($line =~ m/^report/) { dolog($fh, "$line", $opt_v); my ($stream, $version, $timestamp, $subsystem, $event, $sid, $token, $code, $address) = split /\|/, $line; if ($event eq "tx-mail" && $code eq "ok") { $ooo{$sid} = 1; $from = $address; dolog($fh, "$sid created session", $opt_l); if ($from =~ m/^(postmaster|hostmaster)@|.*(noreply|no-reply).*@|.*bounce.*/i) { $ooo{$sid} = 0; dolog($fh, "$sid from skip $from", $opt_l); } } elsif ($event eq "tx-mail" && $code ne "ok") { $ooo{$sid} = 0; } if ($event eq "tx-rcpt" && $code eq "ok") { $email = $address; } elsif ($event eq "tx-rcpt" && $code ne "ok") { delete $ooo{$sid}; dolog($fh, "$sid removed session - rcpt doesn't exist", $opt_l); } } if ($line =~ m/^filter/) { dolog($fh, "$line", $opt_d); my ($stream, $version, $timestamp, $subsystem, $event, $sid, $token, $data) = split /\|/, $line; if ($line =~ m/data-line/) { if (!$data) { $data = ""; } if ($data =~ m/^(precedence:\s+(bulk|list|junk)|list-(help|id|owner|post|subscribe|unsubscribe)|x-loop:\s+opensmtpd\ admin)/i) { $ooo{$sid} = 0; dolog($fh, "$sid header skip $data", $opt_l); } print STDOUT "filter-dataline|$sid|$token|$data\n"; } if ($line =~ m/data-line/ && $data eq '.' && $ooo{$sid} == 1) { dolog($fh, "$sid to: $email, from: $from", $opt_l); $selvacation->bind_param(1, $email); $selvacation->execute; if ($selvacation->rows == 1) { dolog($fh, "$sid found OOO for $email", $opt_l); my @vacation_msg = $selvacation->fetchrow_array; $selcache->bind_param(1, $email); $selcache->bind_param(2, $from); $selcache->execute; if ($selcache->rows == 0) { dolog($fh, "$sid sending OOO to $from", $opt_l); $upcache->bind_param(1, $from); $upcache->bind_param(2, $email); $upcache->execute; open my $fh_email, "|-", "/usr/sbin/sendmail -t"; print $fh_email "From: $email\n"; print $fh_email "To: $from\n";; print $fh_email "Subject: $vacation_msg[0]\n"; print $fh_email "X-Loop: OpenSMTPD Admin Virtual Vacation\n"; print $fh_email "Content-Type: text/plain; charset=utf-8\n\n"; print $fh_email "$vacation_msg[1]\n"; close $fh_email; delete $ooo{$sid}; dolog($fh, "$sid removed session - OOO done", $opt_l); } else { delete $ooo{$sid}; dolog($fh, "$sid removed session - OOO cache hit", $opt_l); } } else { delete $ooo{$sid}; dolog($fh, "$sid removed session - OOO not found for $email", $opt_l); } } elsif ($line =~ m/data-line/ && $data eq '.' && $ooo{$sid} == 0) { delete $ooo{$sid}; dolog($fh, "$sid removed session - OOO skip", $opt_l); } } } close $fh; $dbh->disconnect(); 0;