This commit was manufactured by cvs2svn to create branch 'rh'.
[PerlMail.git] / perlmail-accept
diff --git a/perlmail-accept b/perlmail-accept
deleted file mode 100755 (executable)
index d9a944d..0000000
+++ /dev/null
@@ -1,792 +0,0 @@
-#! /usr/bin/perl
-# 
-#      $Id$
-# Copyright (C) 2002-2003 Jan Kratochvil <project-PerlMail@jankratochvil.net>
-# 
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-# 
-# This program 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 General Public License for more details.
-# 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-
-use vars qw($VERSION);
-$VERSION=do { my @r=(q$Revision$=~/\d+/g); sprintf "%d.".("%03d"x$#r),@r; };
-use strict;
-use warnings;
-
-
-INIT {
-       require Sys::Syslog;
-       Sys::Syslog::openlog("perlmail","pid","mail");
-       my @syslogging_stack;
-       sub syslogging_on_save
-       {
-               push @syslogging_stack,$SIG{"__WARN__"},$SIG{"__DIE__" };
-               $SIG{"__WARN__"}=sub { Sys::Syslog::syslog("warning","WARN: %s",$_[0]); };      # disabled: print STDERR $_[0];
-               $SIG{"__DIE__" }=sub { Sys::Syslog::syslog("crit"   ,"DIE: %s" ,$_[0]); };
-       }
-       syslogging_on_save();
-       sub syslogging_restore
-       {
-               $SIG{"__DIE__" }=pop @syslogging_stack;
-               $SIG{"__WARN__"}=pop @syslogging_stack;
-       }
-       }
-
-
-use File::Basename;
-BEGIN {
-       use lib $ENV{"PERLMAIL_BASEDIR"} || File::Basename::dirname($0);
-       use PerlMail::Config;
-       use PerlMail::Lib;
-       }
-
-use Mail::Audit qw(MAPS);
-require IO::Handle;
-use Carp qw(cluck confess);
-use POSIX qw(WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG WIFSTOPPED WSTOPSIG);
-require POSIX; # for ceil
-use User::Utmp;
-use Getopt::Long;
-require Mail::Address;
-require MIME::Words;
-require Cz::Cstocs;
-require HTML::Entities;
-require MIME::Head;
-require Lingua::EN::Squeeze;
-require Mail::Mailer;
-require HTTP::Cookies;
-require HTTP::Request;
-require LWP::UserAgent;
-use URI::Escape 'uri_escape';
-require WWW::SMS;
-require Authen::SASL;  # Sanity check for &Net::SMTP::auth
-use MIME::Base64;
-use IPC::Open3;
-use POSIX ":sys_wait_h";
-
-
-our($Message,@AuditStored,$DoBell);
-my %alternates_host;   # from @alternates_host
-my %dnsbl_whitelist;   # from @dnsbl_whitelist
-
-# from RedHat "procmail-3.22-5"
-# /i should be only $procmailFROM_DAEMON but how it can hurt to /i all?
-our $procmailTO_        =qr'^((Original-)?(Resent-)?(To|Cc|Bcc)|(X-Envelope|Apparently(-Resent)?)-To):(.*[^-a-zA-Z0-9_.])?'mio;
-our $procmailTO         =qr'^((Original-)?(Resent-)?(To|Cc|Bcc)|(X-Envelope|Apparently(-Resent)?)-To):(.*[^a-zA-Z])?'mio;
-our $procmailFROM_DAEMON=qr'^(Mailing-List:|Precedence:.*(junk|bulk|list)|To: Multiple recipients of |(((Resent-)?(From|Sender)|X-Envelope-From):|>?From )([^>]*[^(.%@a-z0-9])?(Post(ma?(st(e?r)?|n)|office)|(send)?Mail(er)?|daemon|m(mdf|ajordomo)|n?uucp|LIST(SERV|proc)|NETSERV|o(wner|ps)|r(e(quest|sponse)|oot)|b(ounce|bs\.smtp)|echo|mirror|s(erv(ices?|er)|mtp(error)?|ystem)|A(dmin(istrator)?|MMGR|utoanswer))(([^).!:a-z0-9][-_a-z0-9]*)?[%@>       ][^<)]*(\(.*\).*)?)?$([^>]|$))'mio;
-$procmailFROM_MAILER=qr'^(((Resent-)?(From|Sender)|X-Envelope-From):|>?From )[^>]*\b(Post(ma(st(er)?|n)|office)|(send)?Mail(er)?|daemon|mmdf|n?uucp|ops|r(esponse|oot)|(bbs\.)?smtp(error)?|s(erv(ices?|er)|ystem)|A(dmin(istrator)?|MMGR))(([^).!:a-z0-9][-_a-z0-9]*)?[%@>     ][^<)]*(\(.*\).*)?)?$([^>]|$)'mio;
-# perl-5.8.0 does not cope w/original FROM_MAILER on the third '?' character
-# Thus we did '([^>]*[^(.%@a-z0-9])?' -> '[^>]*\b', I hope it is somehow similiar
-# original FROM_MAILER  =qr'^(((Resent-)?(From|Sender)|X-Envelope-From):|>?From )([^>]*[^(.%@a-z0-9])?(Post(ma(st(er)?|n)|office)|(send)?Mail(er)?|daemon|mmdf|n?uucp|ops|r(esponse|oot)|(bbs\.)?smtp(error)?|s(erv(ices?|er)|ystem)|A(dmin(istrator)?|MMGR))(([^).!:a-z0-9][-_a-z0-9]*)?[%@>   ][^<)]*(\(.*\).*)?)?$([^>]|$)'mio;
-
-my $opt_mode;
-my $opt_smstest;       # 1 or $smscount
-my $opt_idle;
-my $opt_dry;
-my $opt_single;
-
-
-sub process;
-
-sub stdin
-{
-       syslogging_restore();   # This is more a debugging session
-       local $/="\n";
-       my $message="";
-       local $_;
-       while (<>) {
-               die "Invalid 'From ' line: $_" if $message eq "" && !/^From /;
-               if (!$opt_single && /^From / && $message) {
-                       process $message;
-                       $message="";
-                       }
-               $message.=$_;
-               }
-       process $message if $message;
-       exit 0;
-}
-
-# FIXME: separate 'perlmail'-transfer together with perlmail-submit away
-sub inetd
-{
-       die "Excessive arguments" if @ARGV;
-
-       IO::Handle::autoflush STDOUT 1;
-
-       while (1) {
-               local $/="\n";
-               my $length=<STDIN>;
-               confess "Unexpected EOF" if !defined $length;
-               confess "Missing EOL" if $length!~s/\n$//s;
-               exit 0 if $length eq "BYE";
-               confess "Unrecognized length: $length" if $length!~/^\d+$/;
-               my $message;
-               local $_;
-               $length==($_=read STDIN,$message,$length) or confess "Got $_ out of required $length bytes";
-               $length==length $message or confess "False read return ".length($message)." instead of $length";
-               {
-                       # Do not: local *STDOUT;        # FIXME: fd's inherited by spawned processes are not closed this way!
-                       #         local *STDERR;        # FIXME: fd's inherited by spawned processes are not closed this way!
-                       # as IPC::Open3 and IPC::Open2 will not redirect the output
-                       # and send it to the original socket instead!
-                       local $DoBell=0;
-                       process $message;
-                       if ($DoBell) {
-                               bell() or warn "Unable to BELL";
-                               }
-                       }
-               print STDOUT "1";
-               }
-       die "NOTREACHED";
-}
-
-sub bell
-{
-       local *BELL;
-       open BELL,">/dev/tty11" or return 0;
-       print BELL "\x07";
-       close BELL or return 0;
-       return 1;
-}
-
-sub useridle
-{
-       return 0 if ! -e "$HOME/away";
-       my %valid_users=map(($_=>1),@ValidUsers);
-       my($idlebest,$linebest);
-       for my $utmp (User::Utmp::getut(),{ "ut_line"=>"psaux" }) {
-               local $_;
-               next if defined($_=$utmp->{"ut_type"}) && $_!=User::Utmp::USER_PROCESS;
-               next if defined($_=$utmp->{"ut_user"}) && !$valid_users{$_};
-               my $line="/dev/".$utmp->{"ut_line"};
-               my $atime=(stat $line)[8];
-               my $what="user \"".($utmp->{"ut_user"} || "<local>")."\", line \"$line\"";
-               warn "Unable to stat $what" and next if !$atime;
-               my $idle=time()-$atime;
-               warn "atime in future for $what" and next if $idle<0;
-               next if $idle>$IdleMax;
-               next if defined $idlebest && $idlebest<=$idle;
-               $idlebest=$idle;
-               $linebest=$line;
-               }
-       return !wantarray() ? $idlebest : ($idlebest,$linebest);
-}
-
-# return only the very (recursive) first part
-sub body_first
-{
-       return $Audit if !$Audit->is_mime();
-       my $first=$Audit;
-       local $_;
-       $first=$_ while $_=$first->parts(0);
-       return $first;
-}
-
-sub mimehead
-{
-my($part)=@_;
-
-       return $Audit->is_mime() ? $part->head()
-                       : MIME::Head->new([ split "\n",$Audit->head()->as_string() ])
-                       ;
-}
-
-sub mimebody
-{
-my($part)=@_;
-
-       # be vary cautious here as most of $part methods will encode it!
-       return join "",@{$Audit->body()} if !$Audit->is_mime();
-       my $bodyhandle=$part->bodyhandle();
-       # If MIME is corrupted we don't get bodyhandle() for this part
-       # It may occur when "boundary" is specified by header but no such boundary is found in the body
-       return $bodyhandle->as_string() if $bodyhandle;
-       warn "MIME corrupted, adapting";
-       return $part->body_as_string();
-}
-
-sub mime_type
-{
-my($part)=@_;
-
-       return $Audit->is_mime() ? $part->effective_type() : mimehead($part)->mime_type();
-}
-
-sub body_simple
-{
-       my $first=body_first();
-       my $r=mimebody($first);
-       my $mime_type=mime_type($first);
-          if ($mime_type eq "text/html") {
-               # HTML::FormatText just does a useless text layouts
-               # PerlIO::via::StripHTML probably needs PerlIO input (?)
-               $r=~s/<[^>]*>//gs;
-               $r=HTML::Entities::decode($r);
-               # FIXME: detect charset from <meta> tag: "Content-type: text/html; charset=<???>"
-               }
-       elsif ($mime_type eq "application/pgp-encrypted"
-              && (my $filename=mimehead($first)->mime_attr("Content-Disposition.filename"))
-              ) {
-               # first part contains just "Version: 1" as of GnuPG v1.0.4 (GNU/Linux)
-               $r="pgp($filename)";
-               }
-       if ((my $charset=mimehead($first)->mime_attr("Content-Type.charset"))) {
-               my $cstocs=Cz::Cstocs->new($charset,"ascii");
-               $r=&$cstocs($r) if $cstocs;     # charset may be unknown
-               }
-       return $r;
-}
-
-sub parts_linear
-{
-my($part)=@_;
-
-       return $Audit if !$part && !$Audit->is_mime();
-       $part||=$Audit;
-       # don't use '!$part->parts()' as even 0-parts-multiparts are still multiparts
-       return $part if $part->bodyhandle();
-       return map { (parts_linear($_)); } $part->parts();
-}
-
-sub smsbuild
-{
-my($smsi,$smscount)=@_;
-
-       return "$smsi/$smscount:" if $smscount>1;
-       return "";
-}
-
-sub smslens
-{
-my($ignorenewmail,$smscount,%args)=@_;
-
-       return map({
-                       my $l=160;
-                       if (!$ignorenewmail) {  # send by mail
-                               $l-=length("Z emailu FIXME SMSmailError: ");
-                               $l-=length(smsbuild($_,$smscount));
-                               }
-                       else {  # send by web
-                               $l-=6;  # 154 is the max length before split; why?
-                               }
-                       $l;
-                       } (0..$smscount-1));
-}
-
-sub smssend_web
-{
-my($squeezed,$smscount,@lens)=@_;
-
-       $smscount=POSIX::ceil($smscount/5);
-       for my $smsi (0..$smscount-1) {
-               my $len=$lens[$smsi];
-               $squeezed=~/^.{0,$len}/s;
-               my $frag=$&;
-               $squeezed=$';
-               return 0 if 3!=@SMSwebRcpt;
-               local *F;
-               open F,"$HOME/priv/WWW-SMS-$SMSwebRcpt_username.pwd" or return 0;
-               my $pwd=<F>;
-               chomp $pwd;
-               close F;
-               my $sms=WWW::SMS->new(@SMSwebRcpt,$frag,"username"=>$SMSwebRcpt_username,"passwd"=>$pwd);
-               for ($sms->gateways("sorted"=>"reliability")) {
-                       last if $sms->send($_);
-                       Sys::Syslog::syslog("warning","Web SMS send failed: %s",$WWW::SMS::Error);
-                       my $void=$WWW::SMS::Error;      # Prevent: Name "WWW::SMS::Error" used only once
-                       }
-               }
-       return 1;
-}
-
-sub smssend_mail
-{
-my($squeezed,$smscount,@lens)=@_;
-
-       return 0;
-}
-
-sub smssend
-{
-my($ignorenewmail,$smscount,%args)=@_;
-
-       my $text=PerlMail::Config::audit_sms(
-                       "subject"=>unmime($Audit->subject()),
-                       "from"=>[ Mail::Address->parse(unmime($Audit->from())) ],
-                       "body"=>substr(body_simple(),0,$MaxBodySMS*(1+0.25*$smscount)),
-                       %args);
-       my $texthead="";
-       ($texthead,$text)=@$text if ref $text;
-       do { print "$texthead\n$text\n"; return; } if $opt_smstest;
-       my @lens=smslens($ignorenewmail,$smscount,%args);
-       my $maxlen=0;
-       $maxlen+=$_ for (@lens);
-       my $squeezed;
-       for my $squeeze (@sms_squeezes) {
-               local $_;
-                Lingua::EN::Squeeze::SqueezeControl($_)    if defined ($_=$squeeze->{"SqueezeControl"});
-               $Lingua::EN::Squeeze::SQZ_OPTIMIZE_LEVEL or 1;  # prevent: Name "$_" used only once: possible typo
-               $Lingua::EN::Squeeze::SQZ_OPTIMIZE_LEVEL=$_ if defined ($_=$squeeze->{"SQZ_OPTIMIZE_LEVEL"});
-               $squeezed=Lingua::EN::Squeeze::SqueezeText($text);
-               chomp $squeezed;
-               last if $maxlen>=length($texthead.$squeezed);
-               }
-       $squeezed=substr $texthead.$squeezed,0,$maxlen; # strip if we passed thru last for() above
-       my $recalclen=0;
-       for ($smscount=0;$recalclen<length $squeezed;$smscount++) {
-               $recalclen+=$lens[$smscount];
-               }
-       my $func=($ignorenewmail ? \&smssend_web : \&smssend_mail);
-       &$func($squeezed,$smscount,@lens);
-}
-
-sub smssend_tryall
-{
-my($ignorenewmail,@args)=@_;
-
-       return if !$opt_smstest && !$opt_idle && defined useridle();
-       local $_;
-       return $_ if                     $_=smssend(1,@args);   # web
-       return $_ if !$ignorenewmail && ($_=smssend(0,@args));  # mail
-       warn "Unable to SMSsend the mail";
-       return 0;
-}
-
-sub cut
-{
-       local $_=$_[0];
-       return "<???>" if !defined($_) || /^\s*$/s;
-       s/^\s*//s;
-       s/\s*$//s;
-       return $_ if length($_)<128;
-       return substr($_,0,128)."...";
-}
-
-our $profile_eval_depth=0;
-# ($name || @$name)
-sub profile_eval
-{
-my($name)=@_;
-
-       die "Nesting profile: $name" if 0x10<=(local $profile_eval_depth=$profile_eval_depth+1);
-       return @$name if ref $name;
-       die "Profile not found: $name" if !exists $audit_profile{$name};
-       my @this=@{$audit_profile{$name}};
-       return (profile_eval($'),@this[1..$#this]) if $this[0] && $this[0]=~/^=/;
-       return @this;
-}
-
-sub address_show
-{
-my($text)=@_;
-
-       return join(",",map({ $_->name() or $_->address(); } Mail::Address->parse($text)));
-}
-
-sub unmime
-{
-my($text)=@_;
-
-       return join "",map({
-                       my $cstocs;
-                       for (${$_}[1],"iso-8859-2") {
-                               last if $_ && ($cstocs=Cz::Cstocs->new($_,"ascii"));
-                               }
-                       &$cstocs(${$_}[0]);
-                       } MIME::Words::decode_mimewords($text));
-}
-
-# $folder: "$folder; comment"
-# $profile as profile_eval($name)
-sub store
-{
-my($folder,$profile,%args)=@_;
-
-       $profile=$store_profile if !$profile;
-       my %do=map({ (!/=/ ? ($_=>1) : ($`=>$')); } profile_eval($profile));
-       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"} || $opt_dry;
-       $folder=~s/;.*$//s;
-       $folder="$Mail/".$' if $folder=~/^=/;
-       push @AuditStored,$folder if $do{"did"};
-       return if $store_ignore || $opt_dry;
-       $DoBell++ if $do{"bell"};
-       write_message($folder) or die;
-       smssend_tryall $store_ignorenewmail,$do{"sms"},%args if $do{"sms"};
-}
-
-our $did_last=0;
-
-# no &$funcref=>did smth in this block
-# &$funcref,@funcargs
-sub did
-{
-my($funcref,@funcargs)=@_;
-
-       return @AuditStored!=$did_last if !$funcref;
-       local $did_last=@AuditStored;
-       &$funcref(@funcargs);
-       return @AuditStored!=$did_last;
-}
-
-# Never use Mail::Audit->store() as it will reformat MIME bodies and possibly corrupt OpenPGP!
-sub write_message
-{
-my($folder)=@_;
-
-       return 1 if $opt_dry;   # simulate OK
-       local *F;
-       open F,">>$folder" or do { warn "Append \"$folder\": $!"; return 0; };
-       {
-               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
-{
-my($message)=@_;
-
-       local $_=$_;
-       my $save_=$_;
-       $message=~s/(\n)(From )/$1>$2/sg;
-       local $Message=$message;
-       # Cannot call 'local' for our-imported variable:
-       my $Audit_save=$Audit;
-       $Audit=Mail::Audit->new(
-                       "emergency"=>"$Mail/emergency",
-                       "data"=>[map("$_\n",split("\n",$message))],
-                       "log"=>"$HOME/.perlmail.log",
-                       "loglevel"=>99,
-                       );
-       local @AuditStored=();
-       do { smssend 0,$opt_smstest; return; } if $opt_smstest;
-       write_message("$Mail/input") or die;
-       PerlMail::Config::audit();
-       warn 'Corrupted $_, repaired' if defined($save_)!=defined($_) || (defined($_) && $save_ ne $_);
-       # restore:
-       $Audit=$Audit_save;
-}
-
-# utility functions:
-
-sub _spamchildcode
-{
-my($err,$isspam)=@_;
-
-       $err=$? if !defined $err;
-       return undef() if !WIFEXITED($?);
-       return undef() if  WIFSIGNALED($?);
-       return undef() if  WIFSTOPPED($?);
-       return $isspam||1 if  WEXITSTATUS($?);  # is-spam
-       return 0;       # not-spam
-}
-
-# return: true (error-message or "1") if is spam
-sub spamassassin
-{
-my($cmd)=@_;
-
-       $cmd||="$HOME/bin/spamassassin --exit-code";
-       # spamassassin has exit code 1 if IS spam, code 0 if NOT spam
-       local *CHILD;
-       local $SIG{"PIPE"}=sub { warn "spamassassin gave me SIGPIPE: broken pipe"; };
-       # prevent Razor2's: Can't call method "log" on unblessed reference at Razor2/Client/Agent.pm line 212.
-       local $ENV{"HOME"}=$HOME;
-       # 2>/dev/null to prevent error messages to corrupt inetd() output of perlmail-accept(1)
-       open CHILD,"|$cmd --mbox >/dev/null 2>/dev/null"
-                       or return 0;
-       print CHILD $Message;
-       close CHILD;
-       return _spamchildcode;
-}
-
-# NOTE: returns undef() if !wantarray and the first header is unrecognized
-# Returns always HOST:IP pair(s).
-sub Received_for
-{
-       my @r=();
-       for my $hdr ($Audit->head->get("Received")) {
-               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]
-# Returns false if valid, code if spam detected.
-sub dnsbl
-{
-my($domain,$full,$timeout)=@_;
-
-       $timeout||=30;  # sec
-       $Mail::Audit::MAPS::host=$domain;
-       for my $host (Received_for()) {
-               next if $host!~/^([^:@]*):/;
-               my $ip=$';
-               # $1 is DNS name, $ip is IP address
-               next if $alternates_host{$1};   # leave only foreign hosts
-               next if $dnsbl_whitelist{$ip};
-               {
-                       package My::Audit::Faked;
-                       sub received { return @{$_[0]->{"received"}}; }
-                       }
-               my $self_faked={
-                               "received"=>["[$ip]"],
-                               };
-               bless $self_faked,"My::Audit::Faked";
-               my $code=Mail::Audit::rblcheck($self_faked,$timeout);
-               next if !$code;
-               # Some 0.0.0.0 etc. found for <linux-kernel@>, see: &Mail::Audit::MAPS::_checkit
-               return $code if $code!='1 Invalid IP address ';
-               return if !$full;
-               }
-}
-
-# Returns true if IS virus; the message will contain the virus name
-sub clamscan
-{
-my($cmd)=@_;
-
-       $cmd||='clamscan --no-summary -';
-       # clamscan has exit code 1 if IS virus , code 0 if NOT virus
-       # Do not use IPC::Open2 as it would try to use our STDERR which is not valid by: local *STDERR;
-       local(*WR,*RD,*ERR);
-       local $SIG{"PIPE"}=sub { warn "clamscan '$cmd' gave me SIGPIPE: broken pipe"; };
-       my $pid=open3(\*WR,\*RD,\*ERR,$cmd.' 2>&1')
-                       or do { cluck "IPC::Open3 $cmd: $!"; return 0; };
-       print WR $Message;
-       close WR or do { cluck "close WR of $cmd: $!"; return 0; };
-       my $status=do { local $/=undef(); <RD>; };
-       close RD or do { cluck "close RD of $cmd: $!"; return 0; };
-       # Do not: $status.=do { local $/=undef(); <ERR>; };
-       #         close ERR or do { cluck "close ERR of $cmd: $!"; return 0; };
-       # (FIXME) as it causes: Use of uninitialized value in <HANDLE>
-       # waitpid fills $? for: &_spamchildcode
-       local $SIG{"ALRM"}=sub { warn "Timeout $clamscan_waitpid_timeout sec waiting for child $cmd"; };
-       alarm $clamscan_waitpid_timeout;
-       # Do not: WNOHANG
-       # as it would not be enough for clamscan(1) even after all close-s above.
-       my $pidcheck=waitpid($pid,0);
-       alarm 0;
-       my $err=$?;
-       $pidcheck && $pidcheck==$pid
-                       or do { cluck "waitpid for $cmd returned $pidcheck!=$pid"; return 0; };
-       $status=~s/^stdin: //;
-       $status=~s/\n$//;
-       return $status if $status ne "OK" && $status;
-       return _spamchildcode $err,$status;
-}
-
-sub muttrc_aliases
-{
-       my %r=();
-       for (muttrc()) {
-               next if !(my $key=(/^alias\s+(\S+)\s+/)[0]);
-               for my $addrobj (Mail::Address->parse($')) {
-                       my $addr=$addrobj->address();
-                       my $ref=\$r{"\L$addr"};
-                       $$ref=$key if !$$ref;   # use always the first occurence to prefer nicks
-                       }
-               }
-       return %r;
-}
-
-# FIXME: host may get multiple recipients and thus not showing "for <...>"
-# FIXME: muttrc_get("from") is too strict
-sub store_muttrc_alternates
-{
-my($prefix,$profile)=@_;
-
-       my $alternates=muttrc_get("alternates") or return;
-       my $alternatesre=qr/$alternates/si;
-       my $From=muttrc_get("from") or return;
-       my $Fromre=qr/^\Q$From\E$/si;
-       my $Fromobj=parseone $From or return;
-       warn "'From' \"$From\" not matched by 'alternates': $alternatesre"
-                       if $From!~/$alternates/si;
-       for my $for (reverse Received_for()) {
-               $for=~s/:.*$//; # strip IP address here
-               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;
-               }
-}
-
-# $header: ref CODE
-# $header: !ref => $Audit->get($header)
-# $maybeaddress: qr/regex/i
-# $maybeaddress: "string"
-# $maybeaddress: "<Regexp:regex>"      # hack :-(
-# $maybeaddress: "<user@host>"
-# $maybeaddress: "<user@>"
-# $maybeaddress: "<@host>"
-sub _headercore
-{
-my($re,$justone,$header,$maybeaddress)=@_;
-
-       if (ref $header) {
-               $header=join(",",&$header());
-               }
-       else {
-               $header=$Audit->get($header);
-               }
-       return 0 if !$header;
-       return $header=~/$maybeaddress/i if "Regexp" eq ref $maybeaddress;
-       return $header=~/$re/i if !defined(my $want=($maybeaddress=~/^\<(.*)\>$/)[0]);
-       my @parsed=Mail::Address->parse($header);
-       warn "'mailto:' forbidden in pattern: $want" if $want=~/^\Qmailto:\E/;
-       return 0 if $justone && 1!=@parsed;
-       return grep {
-                          if ($want=~/^Regexp:/)
-                               { $_->address()=~/$'/i; }
-                       elsif ($want=~/\@$/)
-                               { $_->user()   =~/^(?:\Qmailto:\E)?\Q$`\E/i; }
-                       elsif ($want=~/^\@/)
-                               { $_->host()   =~/^\Q$'\E/i; }
-                       else
-                               { $_->address()=~/^(?:\Qmailto:\E)?\Q$want\E/i; }
-                       } @parsed;
-}
-
-sub headerhas
-{
-my($header,$substr)=@_;
-
-       return _headercore(qr/\Q$substr\E/i,0,$header,$substr);
-}
-
-sub headeris
-{
-my($header,$string)=@_;
-
-       return _headercore(qr/\Q$string\E/i,1,$header,$string);
-}
-
-# $header,%$map
-sub header_remap
-{
-my($header,$map)=@_;
-
-       my $text=$Audit->get($header);
-       my $orig=$text;
-       while (my($from,$to)=each(%$map)) {
-               $text=~s/\b\Q$from\E\b/$to/gsi;
-               }
-       return if $text eq $orig;
-       $Audit->put_header("X-PerlMail-header_remap-$header",$orig);
-       $Audit->replace_header($header,$text);
-}
-
-# LMTP engine:
-use Net::Cmd qw(CMD_OK CMD_MORE);
-{
-       package My::Net::SMTP::LMTP;
-       require Net::SMTP;
-       our @ISA=qw(Net::SMTP);
-       use Net::SMTP;
-       use Net::Cmd qw(CMD_OK);
-       use Carp qw(confess cluck);
-
-       # Do not: sub _HELO
-       # as it would not set {'net_smtp_esmtp'}
-       sub _EHLO { shift->command("LHLO", @_)->response()  == CMD_OK }
-
-       sub clucked
-       {
-       my($self,$func,@args)=@_;
-
-               do { return $_ if defined $_; } for $self->$func(@args);
-               cluck $func;
-               return;
-       }
-}
-
-
-sub lmtp_deliver
-{
-my($admin_user,$admin_pwd,$user_from,$user_to)=@_;
-
-       my $lmtp=My::Net::SMTP::LMTP->clucked("new","localhost","Port"=>"lmtp",
-#                      "Debug"=>1,
-                       ) or return;
-       bless $lmtp,"My::Net::SMTP::LMTP";
-# Prevent:
-# due to:
-#      $lmtp->auth(Authen::SASL->new(
-#                      "mechanism"=>"PLAIN",
-#                      "callback"=>{
-#                                      "user"=>$admin_user,
-#                                      "pass"=>$admin_pwd,
-#                                      # Prevent: "authname"=>$admin_user
-#                                      # as it causes: DIE: Unknown callback: 'authname'. (user|auth|language|pass)
-#                                      }));
-       # FIXME: Authentication hack:
-       $lmtp->command("AUTH PLAIN")->response()==CMD_MORE
-                       or do { cluck "auth announce"; return; };
-       $lmtp->clucked("command",encode_base64($user_from."\x00".$admin_user."\x00".$admin_pwd)) or return;
-       $lmtp->clucked("mail",$user_from) or return;
-       $lmtp->clucked("to",$user_to) or return;
-       $lmtp->clucked("data"); # Do not: or return;
-       # Prevent: 554 5.6.0 Message contains invalid header
-       (my $data=$Message)=~s/\AFrom .*\r?\n//;
-       $lmtp->clucked("datasend",$data) or return;
-       $lmtp->clucked("dataend") or return;
-       $lmtp->clucked("quit") or return;
-}
-
-
-# MAIN
-
-$Getopt::Long::ignorecase=0;
-die "GetOptions error" if !Getopt::Long::GetOptions(
-                 "inetd"    ,sub { $opt_mode=\&inetd; },
-                 "stdin"    ,sub { $opt_mode=\&stdin; },
-                 "single!"  ,\$opt_single,
-                 "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; },
-                 "muttrc"   ,sub { syslogging_restore(); print scalar muttrc(); exit 0; },
-               );
-# "Excessive arguments" checked in &inetd
-die "Missing mode" if !$opt_mode;
-
-%alternates_host=map((lc($_)=>1),@alternates_host);
-%dnsbl_whitelist=map((   $_ =>1),@dnsbl_whitelist);
-
-&$opt_mode();
-die "NOTREACHED";