my $SMScontact='<short@ucw.cz>';
our($Message,$Audit,@AuditStored,$store_ignore,$store_ignorenewmail,$store_profile,$DoBell);
-our(%audit_profile,@sms_squeezes); # imported
+our(%audit_profile,@sms_squeezes,@alternates_host); # imported
+my %alternates_host; # from @alternates_host
# from RedHat "procmail-3.22-5"
# /i should be only $procmailFROM_DAEMON but how it can hurt to /i all?
my $opt_mode;
my $opt_smstest; # 1 or $smscount
my $opt_idle;
+my $opt_dry;
sub process;
{
my($ignorenewmail,$smscount,%args)=@_;
- my %aliases=muttrc_aliases();
my $text=audit_sms(
"subject"=>unmime($Audit->subject()),
- "from"=>[ map({ $_=$_->address(); $_="\L$_"; $aliases{$_} || $_; } Mail::Address->parse(unmime($Audit->from()))) ],
+ "from"=>[ Mail::Address->parse(unmime($Audit->from())) ],
"body"=>substr(body_simple(),0,$MaxBodySMS*(1+0.25*$smscount)),
%args);
my $texthead="";
return "<???>" if !defined($_) || /^\s*$/s;
s/^\s*//s;
s/\s*$//s;
- return $_ if length($_)<64;
- return substr($_,0,64)."...";
+ return $_ if length($_)<128;
+ return substr($_,0,128)."...";
}
our $profile_eval_depth=0;
$profile=$store_profile if !$profile;
my %do=map({ (!/=/ ? ($_=>1) : ($`=>$')); } profile_eval($profile));
- Sys::Syslog::syslog("info","%s%s: %s: %s",
+ Sys::Syslog::syslog("info","%s%s%s: %s: %s",
+ (!$opt_dry ? "" : "--dry: "),
(!$store_ignore ? "" : "IGNORED[$store_ignore]: "),
map({ cut($_); } $folder,address_show(unmime($Audit->from())),unmime($Audit->subject())),
)
- if $do{"syslog"};
- $DoBell++ if $do{"bell"};
+ if $do{"syslog"} || $opt_dry;
$folder=~s/;.*$//s;
$folder="$Mail/".$' if $folder=~/^=/;
- if (!$store_ignore) {
- $Audit->noexit(1);
- $Audit->accept($folder);
- }
- smssend_tryall $store_ignorenewmail,$do{"sms"},%args if $do{"sms"};
push @AuditStored,$folder if $do{"did"};
+ return if $store_ignore || $opt_dry;
+ $DoBell++ if $do{"bell"};
+ write_message($folder);
+ smssend_tryall $store_ignorenewmail,$do{"sms"},%args if $do{"sms"};
}
our $did_last=0;
return @AuditStored!=$did_last;
}
+# Never use Mail::Audit->store() as it will reformat MIME bodies and possibly corrupt OpenPGP!
sub write_message
{
my($folder)=@_;
- local $_;
+ return if $opt_dry;
local *F;
open F,">>$folder" or do { warn "Append \"$folder\": $!"; return 0; };
- do { warn "Lock \"$folder\": $!"; return 0; } if $_=Mail::Audit::audit_get_lock(\*F,$folder);
- seek F,0,IO::Handle::SEEK_END or warn "Seek-end \"$folder\" (ignoring): $!";
- # No 'need_from' here although it is a bit risky to rely on our network peer
- print F $Message or warn "Write to \"$folder\": $!";
- do { print F "\n"; warn "Missing trailing newline, fixed"; } if $Message!~/\n$/s;
- close F or warn "Close \"$folder\"";
- return 1; # some attempt was made, FIXME: proper error detection
+ {
+ local $_;
+ ($_=Mail::Audit::audit_get_lock(\*F,$folder)) and do { warn "Lock \"$folder\": $!"; last; };
+ seek F,0,IO::Handle::SEEK_END or do { warn "Seek-end \"$folder\": $!"; last; };
+ # FIXME: Check for '^From ' to not to rely on our network peer
+ print F $Message or do { warn "Write to \"$folder\": $!"; last; };
+ do { print F "\n"; warn "Missing trailing newline, fixed"; } if $Message!~/\n$/s;
+ close F or do { warn "Close \"$folder\""; last; };
+ return 1; # OK
+ }
+ warn "MAIL DROPPED for folder: $folder";
+ close F;
+ return 0; # failed
}
sub process
local $Audit=Mail::Audit->new(
"emergency"=>"$Mail/emergency",
"data"=>[map("$_\n",split("\n",$message))],
+ "log"=>"$HOME/.lacemail.log",
+ "loglevel"=>99,
);
local @AuditStored=();
- do { smssend $opt_smstest; return; } if $opt_smstest;
+ do { smssend 0,$opt_smstest; return; } if $opt_smstest;
write_message("$Mail/input");
audit();
warn 'Corrupted $_, repaired' if defined($save_)!=defined($_) || (defined($_) && $save_ ne $_);
}
# NOTE: returns undef() if !wantarray and the first header is unrecognized
+# Returns also hosts
sub Received_for
{
my @r=();
my($for)=($hdr=~/\bfor\s+\<?(\S+)\>?\b/);
return $for if !wantarray();
push @r,$for if $for;
+ my($from,$fromaddr)=($hdr=~/\bfrom\s+(?:(\S+)\b.*?)??\[((?:\d{1,3}\.){3}\d{1,3})\]/);
+ $from=$fromaddr if !defined $from;
+ push @r,"$from:$fromaddr" if $from;
}
return @r;
}
+# Extended Mail::Audit::MAPS
+# $domain,$full,[$timeout]
+sub dnsbl
+{
+my($domain,$full,$timeout)=@_;
+
+ $timeout||=30; # sec
+ $Mail::Audit::MAPS::host=$domain;
+ my @hosts=map({ s/^.*://; "[$_]"; } # strip DNS part
+ grep({ /^([^:@]*):/ && !$alternates_host{$1}; } (Received_for())) # leave only foreign hosts
+ );
+ splice @hosts,1 if !$full && @hosts; # "&& @hosts" to prevent: WARN: splice() offset past end of array
+ {
+ package My::Audit::Faked;
+ sub received { return @{$_[0]->{"received"}}; }
+ }
+ my $self_faked={
+ "received"=>[@hosts],
+ };
+ bless $self_faked,"My::Audit::Faked";
+ return Mail::Audit::rblcheck($self_faked,$timeout);
+}
+
our %muttrc_pending=();
sub muttrc
{
for my $addrobj (Mail::Address->parse($')) {
my $addr=$addrobj->address();
my $ref=\$r{"\L$addr"};
- $$ref=$key if !$$ref;
+ $$ref=$key if !$$ref; # use always the first occurence to prefer nicks
}
}
return %r;
}
+# FIXME: Unify
+# BEGIN lacemail-sendmail
+# return: Mail::Address instance or undef()
+sub parseone
+{
+my($line)=@_;
+
+ return undef() if !defined $line;
+ my @r=Mail::Address->parse($line);
+ warn "Got ".scalar(@r)." addresses while wanting just one; when parsing: $line" if 1!=@r;
+ return $r[0];
+}
+# END lacemail-sendmail
+
# FIXME: host may get multiple recipients and thus not showing "for <...>"
# FIXME: muttrc_get("from") is too strict
sub store_muttrc_alternates
my $alternatesre=qr/$alternates/si;
my $From=muttrc_get("from") or return;
my $Fromre=qr/^\Q$From\E$/si;
- warn "'From' \"$From\" not matches by 'alternates': $alternatesre"
+ my $Fromobj=parseone $From or return;
+ warn "'From' \"$From\" not matched by 'alternates': $alternatesre"
if $From!~/$alternates/si;
for my $for (reverse Received_for()) {
- return if $for=~/$From/si;
- next if $for!~/$alternatesre/si;
+ $for=~s/:.*$//; # strip IP address here
+ if ($Fromobj->user() ne "prog-mutt") {
+ next if lc($for) eq lc($From);
+ }
+ else {
+ my $forobj=parseone $for;
+ if ($forobj && $forobj->host()) {
+ # it is 'for' our primary address
+ next if lc($forobj->host()) eq lc($Fromobj->host()); # or 'return'? shouldn't matter
+ }
+ }
+ next if !$alternates_host{lc $for} && $for!~/$alternatesre/si;
store "$prefix\L$for",($profile || []);
return;
}
die "GetOptions error" if !Getopt::Long::GetOptions(
"inetd" ,sub { $opt_mode=\&inetd; },
"stdin" ,sub { $opt_mode=\&stdin; },
+ "dry" ,\$opt_dry,
"smstest:s",sub { $opt_mode=\&stdin; $opt_smstest=($_[1] || 1); },
"idle!" ,\$opt_idle,
"idletest" ,sub { syslogging_restore(); print((defined($_=useridle()) ? $_ : "<undef>")."\n"); exit 0; },
local $/=undef();
eval <AUDIT> or die "eval \"$filenameMyAudit\": $@";
audit_init();
+ %alternates_host=map((lc($_)=>1),@alternates_host);
}
close AUDIT or warn "close \"$filenameMyAudit\": $!";