#!/usr/bin/perl --
# anti spam smtp proxy 
use strict qw(vars subs);
our $version         = '1.10.0';
our $modversion      = '(14284)';
## no critic qw(BuiltinFunctions::ProhibitStringyEval)

# (c) John Hanna, John Calvi, Robert Orso, AJ 2004 under the terms of the GPL
# (c) Fritz Borgstedt 2006 under the terms of the GPL
# 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;

# ASSP V1 founded and developed to Version 1.0.12 by John Hanna
# ASSP V1 development since 1.0.12 by John Calvi
# ASSP V1 development since 1.2.0 by Fritz Borgstedt (+ 2014)
# ASSP V1 development since 1.9.9 14155 by Thomas Eckardt & John Calvi
# ASSP V2 pro development since 2.0.0 by Thomas Eckardt
#
# Feature implementations:
# AJ              - Web interface
# Robert Orso     - LDAP
# Nigel Barling   - SPF & DNSBL
# Mark Pizzolato  - SMTP Session Limits
# Przemek Czerkas - SRS, Delaying, Maillog Search, HTTP Compression, URIBL, RWL,
#                   and many ideas and pieces
# Craig Schmitt   - SPF2 & code optimizing
# J.R. Oldroyd    - SSL support, IPv6 support, griplist/stats upload/download
# Thomas Eckardt  - DB Support, Blockreports, VRFY-check,
#                   MailLog- and Resend-Function and lots of other stuff
# Misc. contributions:
# Wim Borghs, Doug Traylor, Lars Troen, Marco Tomasi,
# Andrew Macpherson, Marco Michelino, Matti Haack, Dave Emory
#

use bytes;    # get rid of annoying 'Malformed UTF-8' messages
use Encode;
use Encode::Guess;
use MIME::Base64;
use File::Copy;
use IO::Select;
use IO::Socket;
use IO::Poll 0.07 qw(POLLIN POLLOUT POLLERR POLLHUP POLLNVAL);
use Net::Ping;
use Sys::Hostname;
use Time::Local;
use Time::HiRes;
use HTML::Entities ();
use Cwd;
use vars qw(@ISA @EXPORT);
#
our $open      = sub { open( shift, shift, shift ); };    ## no critic
our $unicodeDH = sub { opendir( my $d, shift ); my @l = readdir($d); close $d; return @l; };
our $move = sub { File::Copy::move( shift, shift ) };
our $copy        = sub { File::Copy::copy( shift, shift ) };
our $unlink      = sub { unlink(shift) };
our $rename      = sub { rename( shift, shift ) };
our $chmod       = sub { chmod( shift, shift ) };
our $stat        = sub { stat(shift) };
our $eF          = sub { -e shift; };
our $dF          = sub { -d shift; };
#
our $PROGRAM_NAME = $0;
our $assp         = $0;
our $Charsets;
our $CE;
our $MAINVERSION = $version . $modversion;
our $CFG;
our $defaultLogCharset;
our $defaultClamSocket;

our $destinationA;
our $LogDateFormat;
our $LogDateLang;
#our $LogCharset;
our $LOG;
our $LOGstatus;
our $WorkerName;
our %lngmsg;
our %lngmsghint;
our $NODHO = 1;
our $GPBinstallLib;
our $GPBmodTestList;
our $GPBCompLibVer;
our $AsASecondary;

# set the blocking mode for HTTPS (0/1 default is 0) and HTTP (0/1 default is 0) on the GUI
our $HTTPSblocking = 1;
our $HTTPblocking  = 1;

our $TimeZoneDiff  = time;
$TimeZoneDiff =
  Time::Local::timelocal( localtime($TimeZoneDiff) ) -
  Time::Local::timelocal( gmtime($TimeZoneDiff) );

# some special regular expressions
our $neverMatch;
our $neverMatchRE;
our $punyRE;
our $EmailAdrRe;
our $EmailDomainRe;
our $HeaderNameRe;
our $HeaderValueRe;
our $EmailErrorsTo;
our $HeaderRe;
our $UUENCODEDRe;
our $UTFBOMRE;
our $UTF8BOMRE;
our $UTF8BOM;
our $complexREStart;
our $complexREEnd;
our $dot;
our $DoTLS;
our $UriDot;
our $NONPRINT;
our $notAllowedSMTP;
our $SpamTagRE;
# IP Address representations
our $IPprivate;
our $IPloopback;
our $IPQuadSectRE;
our $IPQuadSectDotRE;
our $IPQuadRE;
our $IPStrictQuadRE;

# Host
our $IPSectRe;
our $IPSectHexRe;
our $IPSectDotRe;
our $IPSectHexDotRe;
our $IPRe;
our $IPv4Re;
our $IPv6Re;
our $IPv6LikeRe;
our $PortRe;
our $HostRe;
our $HostPortRe;
# some special regular expressions
my $w = 'a-zA-Z0-9_';
my $d = '0-9';
$neverMatch = '^(?!)';
$neverMatchRE = quotemeta($neverMatch).'\)?\$?\)*$';
$punyRE = 'xn--[a-zA-Z0-9\-]+';
$EmailAdrRe=qr/[^()<>@,;:"\[\]\000-\040\x7F-\xFF]+/o;
$EmailDomainRe=qr/(?:[$w][$w\-]*(?:\.[$w][$w\-]*)*\.(?:$punyRE|[$w][$w]+)|\[[$d][$d\.]*\.[$d]+\])/o;
$HeaderNameRe=qr/\S[^\r\n]*/o;
$HeaderValueRe=qr/[ \t]*[^\r\n]*(?:\r?\n[ \t]+\S[^\r\n]*)*(?:\r?\n)?/o;
$HeaderRe=qr/(?:$HeaderNameRe:$HeaderValueRe)/o;
$UUENCODEDRe=qr/\bbegin\b \d\d\d \b\S{0,72}.*?\S{61}.{0,61}\bend\b/o;
$NONPRINT = qr/[\x00-\x1F\x7F-\xFF]/o;
$notAllowedSMTP = qr/CHUNKING|PIPELINING|XEXCH50|
                     SMTPUTF8|UTF8REPLY|
                     UTF8SMTP|UTF8SMTPA|UTF8SMTPS|UTF8SMTPAS|
                     UTF8LMTP|UTF8LMTPA|UTF8LMTPS|UTF8LMTPAS|
                     XCLIENT|XFORWARD|
                     TURN|ATRN|ETRN|TURNME|X-TURNME|XTRN|
                     SEND|SOML|SAML|EMAL|ESAM|ESND|ESOM|
                     XAUTH|XQUE|XREMOTEQUEUE|
                     X-EXPS|X-ADAT|X-DRCP|X-ERCP|EVFY|
                     8BITMIME|BINARYMIME|BDAT|
                     AUTH GSSAPI|AUTH NTLM|X-LINK2STATE
                  /oix;
# IP Address representations
my $sep;
my $v6Re = '[0-9A-Fa-f]{1,4}';
$IPSectRe = '(?:25[0-5]|2[0-4]\d|1\d\d|0?\d?\d)';
$IPSectHexRe = '(?:(?:0x)?(?:[A-Fa-f][A-Fa-f0-9]?|[A-Fa-f0-9]?[A-Fa-f]))';

# private IP addresses
$IPprivate  = '(?:0{1,3}\.0{1,3}\.0{1,3}\.0{1,3}|127(?:\.'.$IPSectRe.'){3}|169\.254(?:\.'.$IPSectRe.'){2}|0?10(?:\.'.$IPSectRe.'){3}|192\.168(?:\.'.$IPSectRe.'){2}|172\.0?1[6-9](?:\.'.$IPSectRe.'){2}|172\.0?2[0-9](?:\.'.$IPSectRe.'){2}|172\.0?3[01](?:\.'.$IPSectRe.'){2})';   #RFC 1918 decimal
$IPprivate .= '|(?:(?:0x)?0{1,2}\.(?:0x)?0{1,2}\.(?:0x)?0{1,2}\.(?:0x)?0{1,2}|(?:0x)?7[Ff](?:\.'.$IPSectHexRe.'){3}|(?:0x)?[aA]9\.(?:0x)?[Ff][Ee](?:\.'.$IPSectHexRe.'){2}|(?:0x)?0[aA](?:\.'.$IPSectHexRe.'){3}|(?:0x)?[Cc]0\.(?:0x)?[Aa]8(?:\.'.$IPSectHexRe.'){2}|(?:0x)[Aa][Cc]\.(?:0x)1[0-9a-fA-F](?:\.'.$IPSectHexRe.'){2})';   #RFC 1918 Hex
$IPprivate .= '|(?:0{0,4}:){2,6}'.$IPprivate;  # private IPv4 in IPv6
$IPprivate .= '|(?:0{0,4}::|(?:0{1,4}:){7}|(?:0{1,4}:){1,6}:)1';  # IPv6 loopback
$IPprivate .= '|::';  # IPv6 universal local

$IPloopback = '^(?:127(?:\.'.$IPSectRe.'){3}|(?:0{0,4}::|(?:0{1,4}:){7}|(?:0{1,4}:){1,6}:)1)$'; # IPv4 and IPv6 loopback interfaces

$IPQuadSectRE='(?:0([0-7]+)|0x([0-9a-fA-F]+)|(\d+))';
$IPQuadSectDotRE='(?:'.$IPQuadSectRE.'\.)';
$IPQuadRE=qr/$IPQuadSectDotRE?$IPQuadSectDotRE?$IPQuadSectDotRE?$IPQuadSectRE/o;

$complexREStart = '^(?=.*?(((?!)';
$complexREEnd = '(?!)).*?(?!\g{-1})){';
$dot = '[^a-zA-Z0-9\.]?d[^a-zA-Z0-9\.]?o[^a-zA-Z0-9\.]?t[^a-zA-Z0-9\.]?|[\=\%]2[eE]|\&\#0?46\;?';      # the DOT
$UriDot = '(?:[\=\%]2[eE]|\&\#0?46\;?|\.)';

$IPSectDotRe = '(?:'.$IPSectRe.'\.)';
$IPSectHexDotRe = '(?:'.$IPSectHexRe.'\.)';
$IPv4Re = qr/(?:
(?:$IPSectDotRe){3}$IPSectRe
|
(?:$IPSectHexDotRe){3}$IPSectHexRe
)/xo;

# private IPv6 addresses
$IPprivate .= <<EOT;
|(?i:FE[89A-F][0-9A-F]):
(?:
(?:(?:$v6Re:){6}(?:                                $v6Re      |:))|
(?:(?:$v6Re:){5}(?:                   $IPv4Re |   :$v6Re      |:))|
(?:(?:$v6Re:){4}(?:                  :$IPv4Re |(?::$v6Re){1,2}|:))|
(?:(?:$v6Re:){3}(?:(?:(?::$v6Re)?    :$IPv4Re)|(?::$v6Re){1,3}|:))|
(?:(?:$v6Re:){2}(?:(?:(?::$v6Re){0,2}:$IPv4Re)|(?::$v6Re){1,4}|:))|
(?:(?:$v6Re:)   (?:(?:(?::$v6Re){0,3}:$IPv4Re)|(?::$v6Re){1,5}|:))|
                (?:(?:(?::$v6Re){0,4}:$IPv4Re)|(?::$v6Re){1,6}|:)
)
EOT
$IPprivate = qr/^(?:$IPprivate)$/xo;

# RFC4291, section 2.2, "Text Representation of Addresses"
$sep = '[:-]';
$IPv6Re = $IPv6LikeRe = <<EOT;
(?:
(?:(?:$v6Re$sep){7}(?:                                         $v6Re      |$sep))|
(?:(?:$v6Re$sep){6}(?:                         $IPv4Re |   $sep$v6Re      |$sep))|
(?:(?:$v6Re$sep){5}(?:                     $sep$IPv4Re |(?:$sep$v6Re){1,2}|$sep))|
(?:(?:$v6Re$sep){4}(?:(?:(?:$sep$v6Re)?    $sep$IPv4Re)|(?:$sep$v6Re){1,3}|$sep))|
(?:(?:$v6Re$sep){3}(?:(?:(?:$sep$v6Re){0,2}$sep$IPv4Re)|(?:$sep$v6Re){1,4}|$sep))|
(?:(?:$v6Re$sep){2}(?:(?:(?:$sep$v6Re){0,3}$sep$IPv4Re)|(?:$sep$v6Re){1,5}|$sep))|
(?:(?:$v6Re$sep)   (?:(?:(?:$sep$v6Re){0,4}$sep$IPv4Re)|(?:$sep$v6Re){1,6}|$sep))|
(?:        $sep    (?:(?:(?:$sep$v6Re){0,5}$sep$IPv4Re)|(?:$sep$v6Re){1,7}|$sep))
)
EOT

$IPv6Re =~ s/\Q$sep\E/:/go;
$IPv6Re = qr/$IPv6Re/xo;
$IPv6LikeRe = qr/$IPv6LikeRe/xo;

$IPRe = qr/(?:$IPv4Re|$IPv6Re)/xo;

# re for a single port - could be number 1 to 65535
$PortRe = qr/(?:(?:[1-6]\d{4})|(?:[1-9]\d{0,3}))/o;
# re for a single host - could be an IP a name or a fqdn
$HostRe = qr/(?:(?:$IPv4Re|\[?$IPv6Re\]?)|$EmailDomainRe|\w\w+)/o;
$HostPortRe = qr/$HostRe:$PortRe/o;
#
$SpamTagRE = qr/(?:
                  \[
                  (?:
                   Attachment | AUTHError

                   Backscatter | BATV | Bayesian |
                   BlackDomain | BlackHELO | BombBlack |
                   BombData | BombHeader | BombRe |
                   BombScript | BombSender | BounceAddress |

                   Collect | Connection | CountryCode |

                   DCC | DNSBL | Delayed | DenyIP |
                   DenyStrict | DomainKey | DKIM |

                   Extreme | ForgedHELO |
                   ForgedLocalSender | FromMissing |

                   History |

                   IPfrequency | IPperDomain |
                   InternalAddress | InvalidAddress | InvalidHELO |

                   MailLoop | MalformedAddress | Max-Equal-X-Header |
                   MaxAUTHErrors | MaxErrors | MessageScore |
                   messageSize | MaxRealMessageSize | MaxMessageSize |
                   MissingMXA? | MsgID | MSGID-sig |

                   Organization | OversizedHeader |

                   PTRinvalid | PTRmissing | PenaltyBox | Penalty |

                   razor | RelayAttempt |

                   SPF | SRS | SpoofedSender |
                   SuspiciousHelo |

                   Trap |
                   UnknownLocalSender | URIBL |
                   VIRUS | ValidHELO |

                   WhitelistOnly
                  )
                  \] |
                   spam\sfound
               )/iox;

our $IsDaemon;
our $availversion = "";
our $versionURL;
our $NewAsspURL;
our $ChangeLogURL;
our $IPv6TestPort = '51965';

our $IOEngineRun = 1;
our $tlds_alpha_URL = 'http://data.iana.org/TLD/tlds-alpha-by-domain.txt';
our $tlds2_URL = 'http://george.surbl.org/two-level-tlds';

#    "http://www.surbl.org/tld/two-level-tlds",
#    "http://assp.cvs.sourceforge.net/viewvc/*checkout*/assp/assp2/files/URIBLCCTLDS-L2.txt",
our $tlds3_URL = 'http://george.surbl.org/three-level-tlds';
#    "http://www.surbl.org/tld/three-level-tlds",
#    "http://assp.cvs.sourceforge.net/viewvc/*checkout*/assp/assp2/files/URIBLCCTLDS-L3.txt",

our $versionURLStable = "http://downloads.sourceforge.net/project/assp/ASSP%20Installation/AutoUpdate/ASSP1x/version.txt";
our $NewAsspURLStable = 'http://downloads.sourceforge.net/project/assp/ASSP%20Installation/AutoUpdate/ASSP1x/assp.pl.gz';

our $ChangeLogURLStable = 'http://downloads.sourceforge.net/project/assp/ASSP%20Installation/AutoUpdate/ASSP1x/changelog.txt';

our $versionURLDev = "http://downloads.sourceforge.net/project/assp/ASSP%20Installation/AutoUpdate/ASSP1dev/version.txt";
our $NewAsspURLDev = 'http://downloads.sourceforge.net/project/assp/ASSP%20Installation/AutoUpdate/ASSP1dev/assp.pl.gz';

our $ChangeLogURLDev = 'http://downloads.sourceforge.net/project/assp/ASSP%20Installation/AutoUpdate/ASSP1dev/changelog.txt';

our $gripListDownUrl = 'http://*HOST*/cgi-bin/assp_griplist?binary';
our $gripListUpUrl = 'http://*HOST*/cgi-bin/assp_griplist?binary';
our $gripListUpHost = 'assp.sourceforge.net';
$gripListDownUrl =~ s/\*HOST\*/$gripListUpHost/o;
$gripListUpUrl  =~ s/\*HOST\*/$gripListUpHost/o;


eval { $^M = 'a' x ( 1 << 16 ); };    # use 64KB for "out of memory" area

our $X = 2;
our $forceDNSv4  = 1;
our $SvcStopping = 0;            # AZ: 2009-02-05 - signal service status

our %Config;
our %ConfigSync;
our %ConfigSyncServer;
our %newConfig;
our %ConfigAdd;
our @ConfigArray;
# define date names for languages
# 0:English|1:FranÁais|2:Deutsch|3:EspaÒol|4:PortuguÍs|5:Nederlands
# 6:Italiano|7:Norsk|8:Svenska|9:Dansk|10:Suomi|11:Magyar|12:Polski|13:Romaneste
our @Month_to_Text =
(
    [
        'January', 'February', 'March', 'April', 'May', 'June',
        'July', 'August', 'September', 'October', 'November', 'December'
    ],
    [
        'janvier', 'fÈvrier', 'mars', 'avril', 'mai', 'juin',
        'juillet', 'ao˚t', 'septembre', 'octobre', 'novembre', 'dÈcembre'
    ],
    [
        'Januar', 'Februar', 'March', 'April', 'Mai', 'Juni',
        'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'
    ],
    [
        'enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio',
        'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'
    ],
    [
        'janeiro', 'fevereiro', 'marÁo', 'abril', 'maio', 'junho',
        'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'
    ],
    [
        'januari', 'februari', 'maart', 'april', 'mei', 'juni',
        'juli', 'augustus', 'september', 'oktober', 'november', 'december'
    ],
    [
        'Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno',
        'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'
    ],
    [
        'januar', 'februar', 'mars', 'april', 'mai', 'juni',
        'juli', 'august', 'september', 'oktober', 'november', 'desember'
    ],
    [
        'januari', 'februari', 'mars', 'april', 'maj', 'juni',
        'juli', 'augusti', 'september', 'oktober', 'november', 'december'
    ],
    [
        'januar', 'februar', 'marts', 'april', 'maj', 'juni',
        'juli', 'august', 'september', 'oktober', 'november', 'december'
    ],
    [
        'tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu',
        'toukokuu', 'kes‰kuu', 'hein‰kuu', 'elokuu',
        'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'
    ],
    [
        'Janu·r', 'Febru·r', 'M·rcius', '¡prilis', 'M·jus', 'J˙nius',
        'J˙lius', 'Augusztus', 'Szeptember', 'OktÛber', 'November', 'December'
    ],
    [
        'Styczen', 'Luty', 'Marzec', 'Kwiecien', 'Maj', 'Czerwiec',     # ISO-Latin-1 approximation
        'Lipiec', 'Sierpien', 'Wrzesien', 'Pazdziernik', 'Listopad', 'Grudzien'
    ],
    [
        'Ianuarie', 'Februarie', 'Martie', 'Aprilie', 'Mai', 'Iunie',
        'Iulie', 'August', 'Septembrie', 'Octombrie', 'Noiembrie', 'Decembrie'
    ]
);

# 0:English|1:FranÁais|2:Deutsch|3:EspaÒol|4:PortuguÍs|5:Nederlands
# 6:Italiano|7:Norsk|8:Svenska|9:Dansk|10:suomi|11:Magyar|12:polski|13:Romaneste
our @Day_to_Text =
(
    [
        'Monday', 'Tuesday', 'Wednesday',
        'Thursday', 'Friday', 'Saturday', 'Sunday'
    ],
    [
        'Lundi', 'Mardi', 'Mercredi',
        'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'
    ],
    [
        'Montag', 'Dienstag', 'Mittwoch',
        'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'
    ],
    [
        'Lunes', 'Martes', 'MiÈrcoles',
        'Jueves', 'Viernes', 'S·bado', 'Domingo'
    ],
    [
        'Segunda-feira', 'TerÁa-feira', 'Quarta-feira',
        'Quinta-feira', 'Sexta-feira', 'S·bado', 'Domingo'
    ],
    [
        'Maandag', 'Dinsdag', 'Woensdag',
        'Donderdag', 'Vrijdag', 'Zaterdag', 'Zondag'
    ],
    [
        'LunedÏ', 'MartedÏ', 'MercoledÏ',
        'GiovedÏ', 'VenerdÏ', 'Sabato', 'Domenica'
    ],
    [
        'mandag', 'tirsdag', 'onsdag',
        'torsdag', 'fredag', 'l¯rdag', 's¯ndag'
    ],
    [
        'mÂndag', 'tisdag', 'onsdag',
        'torsdag', 'fredag', 'lˆrdag', 'sˆndag'
    ],
    [
        'mandag', 'tirsdag', 'onsdag',
        'torsdag', 'fredag', 'l¯rdag', 's¯ndag'
    ],
    [
        'maanantai', 'tiistai', 'keskiviikko',
        'torstai', 'perjantai', 'lauantai', 'sunnuntai'
    ],
    [
        'hÈtfı', 'kedd', 'szerda',
        'cs¸tˆrtˆk', 'pÈntek', 'szombat', 'vas·rnap'
    ],
    [
        'poniedzialek', 'wtorek', 'sroda',     # ISO-Latin-1 approximation
        'czwartek', 'piatek', 'sobota', 'niedziela'
    ],
    [
        'Luni', 'Marti', 'Miercuri',
        'Joi', 'Vineri', 'Sambata', 'Duminica'
    ]
);

BEGIN {
 STDOUT->autoflush;
 STDERR->autoflush;
 use vars qw($wikiinfo);
 use vars qw($base);
 push @EXPORT, qw($base $wikiinfo);
 $wikiinfo = "get?file=images/info.png";
 setLocalCharsets();
 setClamSocket();

# load from command line if specified

if($ARGV[0]) { 
 $base=$ARGV[0]; 
} else { 
 # the last one is the one used if all else fails 
 $base = cwd(); 
 unless (-e "$base/assp.cfg" || -e "$base/assp.cfg.tmp") {
   foreach ('.','/usr/local/assp','/home/assp','/etc/assp','/usr/assp','/applications/assp','/assp','.') {
    if (-e "$_/assp.cfg") {
      $base=$_;
      last ;
    } 
   } 
 } 
 $base = cwd() if $base eq '.'; 
}

if ( !-e "$base/images/noIcon.png" && lc($ARGV[0]) ne '-u')
{
 writeExceptionLog("Abort: folder '$base/images' not correctly installed");
 print "\nusage: perl assp.pl [baseDir|-u|] [-i|ddddd|] [--configParm:=configValue --configParm:=configValue ...|]\n";
 print "baseDir must be defined if any other parameter is used\n";
 die "\n\nAbort: folder '$base/images' not correctly installed\n\n";
}
if($ARGV[0]) {
  if ($ARGV[0] =~ /(?:\/|-{1,2})(?:\?|help|usage)/oi) {
   print "\nusage: perl assp.pl [baseDir|-u|] [-i|ddddd|] [--configParm:=configValue --configParm:=configValue ...|]\n";
   print "baseDir must be defined if any other parameter is used\n";
   print "-u - uninstalls the service on windows - no other parm is allowed\n";
   print "-i - installs an assp service on windows\n";
   print "ddddd - overwrites the 'webAdminPort' - same like --webAdminPort:=ddddd\n";
   print "--configParm:=configValue - overwrites the configuration parameter (case sensitive) 'configParm' with the value 'configValue'\n";
   exit;
  }
}
unless (chdir $base) {
 writeExceptionLog("Abort: unable to change to basedirectory $base");
 die "\n\nAbort: unable to change to basedirectory $base\n\n";
}
$base = cwd();

our $dftrestartcmd;
our $dftrebuildcmd;
our $dftCaFile;
our $dftCertFile;
our $dftPrivKeyFile;
our $startsecondcmd;

our $asspbase = $base;

my $assp = $0;
my $perl = $^X;

if ( $^O eq "MSWin32" ) {
    $assp = $base.'\\'.$assp if ($assp !~ /\Q$base\E/io);
    $assp =~ s/\//\\/go;
    my $asspbase = $base;
    $asspbase =~ s/\\/\//go;
    $dftrestartcmd = "cmd.exe /C start \"ASSPSMTP restarted\" \"$perl\" \"$assp\" \"$asspbase\"";
    $startsecondcmd = "\"$perl\" \"$assp\" \"$asspbase\" --AsASecondary:=1";
    $dftrebuildcmd = "\"$perl\" \"$base\\rebuildspamdb.pl\" \"$asspbase\" silent &";
} else {
    $assp = $base.'/'.$assp if ($assp !~ /\Q$base\E/io);
    $dftrestartcmd     = "sleep 30;\"$^X\" \"$assp\" \"$base\" \&";
    $startsecondcmd = "sleep 30;\"$^X\" \"$assp\" \"$base\"  --AsASecondary:=1";
    $dftrebuildcmd     = "\"$^X\" \"$base/rebuildspamdb.pl\" \"$base\" silent &";
}
$dftCertFile = "$base/certs/server-cert.pem";
$dftCertFile =~ s/\\/\//go;
$dftPrivKeyFile = "$base/certs/server-key.pem";
$dftPrivKeyFile =~ s/\\/\//go;
$dftCaFile = "$base/certs/server-ca.crt";
$dftCaFile =~ s/\\/\//go;
our $dftrestartcomment;
if ( $^O ne "MSWin32" ) {
    $dftrestartcomment = " If you use runAsUser make sure to start ASSP with root privileges (sudo)."; 
} else {
    $dftrestartcomment = "";
}

# vars needed in @Config
# print "loading config -- base='$base'\n";
# except for the heading lines, all config lines have the following:
#  $name,$nicename,$size,$func,$default,$valid,$onchange,$description(,CssAdition)
# name is the variable name that holds the data
# nicename is a human readable pretty display name (oh how nice!)
# size is the appropriate input box size
# func is a function called to render the config item
# default is the default value
# valid is a regular expression used to clean and validate the input -- no match is an error and $1 is the desired result
# onchange is a function to be called when this value is changed -- usually undef; just updating the value is enough
# group is the heading group belonged to.
# description is text displayed to help the user figure what to put in the entry
# CssAddition (optional) adds the string to the CSS-name for nicename Style

  our @Config = (

[0,0,0,'heading','Configuration Sharing'],

['enableCFGShare','Enable Configuration Sharing',0,\&checkbox,'','(.*)','ConfigChangeEnableCFGSync', '<hr><b>Read all positions in this section carefully (multiple times is recommended!!!)! A wrong configuration sequence or wrong configuration values can lead in to a destroyed ASSP configuration!</b><hr>
  If set, the configuration value and option files synchronization will be enabled. This synchronization belong to the configuration values, to the file that is possibly defined in a value and to the include files that are possibly defined in the configured file.<br />
  If the configuration of all values in this section is valid, the synchronization status will be shown in the GUI for each config value that is, or <b>could be shared</b>. There are several configuration values, that could not be shared. The list of all shareable values could be found in the distributed file assp_sync.cfg<br /><br />
  For an initial synchronization setup set the following config values in this order: setup syncServer, syncConfigFile, syncTestMode and as last syncCFGPass (leave isShareSlave and isShareMaster off). Use the default (distributed syncConfigFile assp_sync.cfg) file and configure all values to your needs - do this on all peers by removing lines or setting the general sync flag to 0 or 1 (see the description of syncConfigFile ).<br />
  If you have finished this initial setup, enable isShareMaster or isShareSlave - now assp will setup all entries in the configuration file for all sync peers to the configured default values (to 1 if isShareMaster or to 3 if isShareSlave is selected). Do this on all peers. Now you can configure the synchronization behavior for each single configuration value for each peer, if it should differ from the default setup.<br />
  For the initial synchronization, configure only one ASSP installation as master (all others as slave). If the initial synchronization has finished, which will take up to one hour, you can configure all or some assp as master and slave. On the initial master simply switch on isShareSlave. On the inital slaves, switch on isShareMaster and change all values in the sync config file that should be bedirectional shared from 3 to 1. As last action enable enableCFGShare on the SyncSlaves first and then on the SyncMaster.<br />
  After such an initial setup, any changes of the peers (syncServer) will have no effect to the configuration file (syncConfigFile)! To add or remove a sync peer after an initial setup, you have to configure syncServer and you have to edit the sync config file manually.<br /><br />
  This option can only be enabled, if isShareMaster and/or isShareSlave and syncServer and syncConfigFile and syncCFGPass are configured!<br />
  <b>Because the synchronization is done using a special SMTP protocol (without "mail from" and "rcpt to"), this option requires an installed Net::SMTP module in PERL. This special SMTP protocol is not usable to for any MTA for security reasons, so the "sync mails" could not be forwarded via any MTA.<br />
  For this reason all sync peers must have a direct or routed TCP connection to each other peer.</b><br />
  <input type="button" value="show sync status" onclick="javascript:popFileEditor(\'files/sync_failed.txt\',8);" />',undef,undef,'msg009170','msg009171'],
['isShareMaster','This is a Share Master',0,\&checkbox,'','(.*)','ConfigChangeSync', 'If selected, ASSP will send configured configuration changes to sync peers.',undef,undef,'msg009180','msg009181'],
['isShareSlave','This is a Share Slave',0,\&checkbox,'','(.*)','ConfigChangeSync', 'If selected, ASSP will receive configured configuration changes from sync peers. To accept a sync request, every sending peer has to be defined in syncServer - even if there are manually made entries in the sync config file for a peer.',undef,undef,'msg009190','msg009191'],
['syncServer','Default Sync Peers',120,\&textinput,'','(.*)','ConfigChangeSyncServer','Define all configuration sync peers here (to send changes to or to receive changes from). Separate multiple values by "|". Any value must be a pair of hostname or ip-address and :port, like 10.10.10.10:25 or mypeerhost:125 or mypeerhost.mydomain.com:225. The :port must be defined!<br />
  The target port can be the listenPort , listenPort2 or relayPort of the peer.',undef,undef,'msg009200','msg009201'],
['syncTestMode','Test Mode for Config Sync',0,\&checkbox,'','(.*)',undef, 'If selected, a master (isShareMaster) will process all steps to send configuration changes, but will not really send the request to the peers. A slave (isShareSlave) will receive all sync requests, but it will not change the configuration values and possibly sent configuration files will be stored at the original location and will get an extension of ".synctest".',undef,undef,'msg009210','msg009211'],
['syncConfigFile','Configuration File for Config Sync*',120,\&textinput,'file:assp_sync.cfg','(file:\S+|)','ConfigChangeSyncFile','Define the synchronization configuration file here (default is file:assp_sync.cfg).<br />
 This file holds the configuration and the current status of all synchronized assp configuration values.<br />
 The format of an initial value is:  "varname:=syncflag" - where syncflag could be 0 -not shared and 1 -is shared - for example: HeaderMaxLength:=1 . The syncflag is a general sign, which means, a value of 0 disables the synchronization of the config value for all peers. A value of 1, enables the peer configuration that possibly follows.<br />
 The format after an initial setup is: "varname:=syncflag,syncServer1=status,syncServer2=status,......". The "status" could be one of the following:<br /><br />
 0 - no sync - changes of this value will not be sent to this syncServer - I will ignore all change requests for this value from there<br />
 1 - I am a SyncMaster, the value is still out of sync to this peer and should be synchronized as soon as possible<br />
 2 - I am a SyncMaster, the value is still in sync to this peer<br />
 3 - I am not a SyncMaster but a SyncSlave - only this SyncMaster (peer) knows the current sync status to me<br />
 4 - I am a SyncMaster and a SyncSlave (bidirectional sync) - a change of this value was still received from this syncServer (peer) and should not be sent back to this syncServer - this flag will be automatically set back to 2 at the next synchronization check<br /><br />
 ',undef,undef,'msg009220','msg009221'],
['syncCFGPass','Config Sync Password',40,\&passinput,'','(.{6,}|)','ConfigChangeSync','The password that is used and required (additionally to the sending IP address) to identify a valid sync request. This password has to be set equal in all ASSP installations, from where and/or to where the configuration should be synchronized.<br />
  The password must be at least six characters long.<br />
  If you want or need to change this password, first disable enableCFGShare here an on all peers, change the password on all peers, enable enableCFGShare on SyncSlaves then enable enableCFGShare on SyncMasters.',undef,undef,'msg009230','msg009231'],
['syncShowGUIDetails','Show Detail Sync Information in GUI',0,\&checkbox,'','(.*)',undef, 'If selected, the detail synchronization status is shown at the top of each configuration parameter like:<br /><br />
  nothing shown - there is no entry defined for this parameter in the syncConfigFile or it is an unsharable parameter<br />
  "(shareable)" - the parameter is shareable but the general sync sign in the syncConfigFile is zero<br />
  "(shared: ...)" - the detail sync status for each sync peer<br /><br />
  If not selected, only different colored bulls are shown at the top of each configuration parameter like:<br /><br />
  nothing shown - no entry in the syncConfigFile or it is an unsharable parameter<br />
  "black bull <b><font color=\'black\'>&bull;</font></b>" - the parameter is shareable but the general sync sign in the syncConfigFile is zero<br />
  "green bull <b><font color=\'green\'>&bull;</font></b>" - the parameter is shared and in sync to each peer<br />
  "red bull <b><font color=\'red\'>&bull;</font></b>" - the parameter is shared but it is currently out of sync to at least one peer<br /><br />
  If you move the mouse over the bull, a hint box will show the detail synchronization status.
  <hr><div class="menuLevel1">Notes Config Sync</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/configsync.txt\',3);"/>',undef,undef,'msg009250','msg009251'],
[ 0, 0, 0, 'heading', 'Network Setup <a href="http://sourceforge.net/apps/mediawiki/assp/index.php?title=ASSP_Basic_Workflow" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="ASSP_Basic_Workflow"  /></a>' ],
['ConnectionLog','Connections Logging','0:nolog|1:standard|2:verbose|3:diagnostic',\&listbox,0,'(.*)',undef,''],
['listenPort','SMTP Listen Port',40,\&textinput,25,'(.*)','ConfigChangeMailPort',
  'The port number on which ASSP will listen for incoming SMTP connections (normally 25). You can specify both an IP address and port number to limit connections to a specific interface. Multiple ports  (interface:port) are possible separated by a pipe (|). Hint: If you set this port to 25, you must not set "listenPort2" to 25<p><small><i>Examples:</i>25<br /> 123.123.123.1:25|123.123.123.5:25</small></p>','Basic'],
['smtpDestination','SMTP Destination',40,\&textinput,'127.0.0.1:1025','(.*)',undef,
  'The IP <b>number!</b> and port number of your primary SMTP <a href=http://en.wikipedia.org/wiki/Mail_transfer_agent>mail transfer agent</a> (MTA). If multiple servers are listed and the first listed MTA does not respond, each additional MTA will be tried. If only a port number is entered, or the dynamic keyword <b>INBOUND</b> is used with a port number, then the connection will be established to the local IP address on which the connection was received. This is useful when you have several IP addresses with different domains or profiles in your MTA. If INBOUND:PORT is used, ReportingReplies (Analyze,Help,etc and CopyMail will go to 127.0.0.1:PORT. If your needs are different, use smtpReportServer (SMTP Reporting Destination) and sendAllDestination (Copy Spam SMTP Destination). Separate multiple entries by "|".<small><i>Examples:</i>127.0.0.1:1025, 127.0.0.1:1025|127.0.0.5:1025, INBOUND:1025</small>','Basic',undef,'msg000030','msg000031'],
['EmailReportDestination','ASSP Internal Mail Destination',40,\&textinput,'','(\S*)',undef,
 'Port to connect to when  ASSP sends replies to email-interface mails, notifications and block reports. Must be set when smtpDestination contains INBOUND. For example "10.0.1.3:1025", etc.'],
['listenPort2','Second SMTP Listen Port',40,\&textinput,587,'(.*)','ConfigChangeMailPort2',
  'A secondary port number on which ASSP can accept SMTP connections. This is useful as a dedicated port for TLS or VPN clients or for those who cannot directly send mail to a mail server outside of their ISP\'s network because the ISP is blocking port 25. Multiple ports  (interface:port) are possible separated by a pipe (|). Hint: If you set this port to 587, you must not set another port like "listenPort" to 587<p><small><i>Examples:</i> 587<br />192.168.0.100:587<br />192.168.0.100:587|192.168.0.101:587</small></p>'],
['smtpAuthServer','Second SMTP Destination',40,\&textinput,'','(\S*)',undef,
  'The IP address/hostname and port number to connect to when mail is received on the second SMTP listen port. If the field is blank, smtpDestination will be used. The purpose of this setting is to allow remote users to make authenticated connections and transmit their email without encountering SPF failures.<p><small><i>Examples:</i>127.0.0.1:687</small></p>'],
['NoAUTHlistenPorts','Disable AUTH support on listenPorts',40,\&textinput,'','(.*)','ConfigChangeNoAUTHPorts',
  'This disables the SMTP AUTH command on the defined listenPorts. This option works for listenPort , listenPort2 and listenPortSSL . The listener definition here has to be the same like in the port definitions. Separate multiple entries by "|".<p><small><i>Examples:</i> 25, 127.0.0.1:25, 127.0.0.1:25|127.0.0.2:25 </small></p>',undef,undef,'msg008060','msg008061'],  
['EnforceAuth',"Force SMTP AUTH on Second SMTP Listen Port",0,\&checkbox,'','(.*)',undef,
  'Force clients connecting to the second listen port to authenticate before transferring mail. To use this setting, both listenPort2 (Second SMTP Listen Port) and smtpAuthServer (Second SMTP Destination) must be configured.',undef,undef,'msg000090','msg000091'],
['DisableExtAUTH','Disable SMTP AUTH for External Clients',0,\&checkbox,'','(.*)',undef,'If you do not want external clients (IP not in acceptAllMail or relayPort is not used) to use SMTP AUTH - for example to prevent address and password harvesting - check this option.<br />
  The "AUTH" offer in the EHLO and HELP reply will be stripped out, if set to on.<br />
  Notice: setting this option to ON could prevent roaming users (dynamic IP) from being able to authenticate!',undef,undef,'msg010250','msg010251'],
['enableINET6','Enable IPv6 support',0,\&checkbox,'','(.*)','ConfigChangeIPv6','For IPv6 network support to be enabled, check this box. Default is disabled. IO::Socket::INET6 is able to handle both IPv4 and IPv6. NOTE: This option requires an installed IO::Socket::INET6 module in PERL and your system should support IPv6 sockets.<br />
  Before you enable or disable IPv6, please check every IP listener and destination definition in assp and correct the settings. <span class="negative">Changing this requires a restart of ASSP!</span> IPv4 addresses are defined for example 192.168.0.1 or 192.168.0.1:25 - IPv6 addresses are defined like [FE80:1:0:0:0:0:0:1]:25 or [FE80:1::1]:25 ! If an IPv4 address is defined for a listener, assp will listen only on the IPv4 socket. If an IPv6 address is defined for a listener, assp will listen only on the IPv6 socket. If only a port is defined for a listener, assp will listen on both IPv4 and IPv6 sockets.<br />
  ',undef,undef,'msg009480','msg009481'],
['smtpDestinationRT','SMTP Destination Routing Table*',120,\&textinput,'','(\S*)','configChangeRT',
  'If INBOUND is used in the SMTP Destination field, the rules specified here are used to route the inbound IP address to a different outbound IP address. You must specify a port number with the outbound IP address. This feature works by assigning as many IP addresses to ASSP as you have different receiving Mailservers. 
  <p><small><i>Example:</i>141.120.110.1=>141.120.110.129:25|141.120.110.2=>141.120.110.130:125|141.120.110.3=>141.120.110.130:125</small></p><span class="negative"> requires ASSP restart</span>
<hr /><div class="menuLevel1">Notes On Network Setup</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/network.txt\',3);" />'],

[0,0,0,'heading','SMTP Session Limits '],

['SessionLog','Session Limit Logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,''],
['MaxErrors','Maximum Errors Per Session',10,\&textinput,3,'(\d+)',undef,
'The maximum number of SMTP session errors encountered before the
connection is dropped. Scoring is done  with meValencePB.'],
['MaxAUTHErrors','Max Number of AUTHentication Errors',10,\&textinput,3,'(\d*)',undef,
 'If an IP (/24 network is used) exceeds this number of authentication errors (535 or 530) the transmission of the current message will be canceled and any new connection from that IP will be blocked for 5-10 minutes.<br />
  Every 5 Minutes the \'AUTHError\' -counter of the IP will be decreased by one. autValencePB is used for scoring.<br />
  No limit is imposed by ASSP if the field is left blank or set to 0. This option allows admins to prevent external brute force or dictionary attacks via AUTH command. Whitelisted, NPexcludeIPs and NoProcessing IP\'s are ignored like any relayed connection.',undef,undef,'msg009310','msg009311'],
['autValencePB','Bad SMTP Authentication Score, default=60 +',10,\&textinput,60,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring<br />
 <hr>This option and all other *ValencePB options with an "+" at the end of the description, accepts a second comma or pipe separated value like: "20,10" .<br />
  In this case the first value is used for message scoring and the second value is used for IP scoring.<br />
  If only the first value is defined, this value is used for both scoring mechanism.<br />
  If a *ValencePB option is related to any feature which allows the usage of weighted penalties, the message scoring value is used to calculate the weighted penalty and the result is used calculating (result * ipscorevalence / messagescorevalence ) for message and IP scoring.','Basic',undef,'msg009300','msg009301'],
['noMaxAUTHErrorIPs','Do not check MaxAUTHErrors for these IP\'s*',120,\&textinput,'','(\S*)','ConfigMakeIPRe','List of IP\'s which should not be checked for MaxAUTHErrors .  For example: 145.145.145.145|145.146.',undef,undef,'msg009580','msg009581'],
['DoSameSubject','Check Number of Same Subjects','0:disabled|1:block|2:monitor|3:score',\&listbox,3,'(.*)',undef,
 'Scoring is done  with isValencePB.'],
['isValencePB','Subject Frequency Score, default=150 +',10,\&textinput,150,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg010030','msg010031'],
['SameSubjectOnly','Check SameSubject for these Users only*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'Skip SameSubject Check for these local addresses. <br />
  Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com).  Wildcards are supported (fribo*@domain.com).<br />
  For example: fribo*@thisdomain.com|jhanna|@sillyguys.org ',undef,undef,'msg010070','msg010071'],
['SameSubjectSkipRe','Skip SameSubject Check for this Regex*',120,\&textinput,'Mail delivery failed|itunes|Returned to Sender|Delivery Status Notification|List-Unsubscribe|Newsletter','(.*)','ConfigCompileRe','Skip SameSubject Check for this regular expression. If the content of the email matches this regular expression , DoSameSubject will not be done. For example: \'Mail delivery failed|itunes|Returned to Sender|Delivery Status Notification|Re:$|List-Unsubscribe|Newsletter\' .'],
['SameSubjectNoAddresses','Skip SameSubject Check for these Users*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'A list of local addresses, for which the \'SameSubject Check\' should not be done.<br />
  Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com).  Wildcards are supported (fribo*@domain.com).<br />
  For example: fribo*@thisdomain.com|jhanna|@sillyguys.org ',undef,undef,'msg010080','msg010081'],
['SameSubjectNoIP','Skip SameSubject Check for these IP\'s*',120,\&textinput,'','(\S*)','ConfigMakeIPRe','Mail from these IP numbers will pass through without SameSubject check. For example: 145.145.145.145',undef,undef,'msg010090','msg010091'],
['SameSubjectInterval','SameSubject Interval',10,\&textinput,3600,'(\d*)',undef,'The time interval in seconds in which the number of same subjects should not exceed a specific limit ( SameSubjectNumber ).<br />
  Use this in combination with SameSubjectNumber to limit the number of same subjects in a given interval. A value of 0  will disable this feature '],
['SameSubjectExpiration','SameSubject Expiration',10,\&textinput,360,'(\d*)',undef,'The time in days in which found same subjects should be kept in cache.<br />
  <input type="button" value="SameSubject Cache" onclick="javascript:popFileEditor(\'pb/pbdb.samesubject.db\',\'1h\');" />',undef,undef,'msg010050','msg010051'],
['SameSubjectNumber','SameSubject Limit',10,\&textinput,5,'(\d*)',undef,'The number of same subjects that should not be exceeded in a specific time interval ( SameSubjectIntervalerval ).<br />
  Use this in combination with SameSubjectInterval to limit the number of same subjects in a given interval. A value of 0 (default) will disable this feature and clean the cache within five minutes.<br />
  <input type="button" value="SameSubject Cache" onclick="javascript:popFileEditor(\'pb/pbdb.samesubject.db\',\'1h\');" />',undef,undef,'msg010060','msg010061'],
['maxSMTPSessions','Maximum Sessions',10,\&textinput,64,'(\d?\d?\d?)',undef,
  'The maximum number of simultaneous SMTP sessions. This can prevent server overloading and DoS attacks. 64 simultaneous sessions are typically enough. No entry or zero means no limit.'],
['noMaxSMTPSessions','No Maximum Sessions IP addresses*',120,\&textinput,'','(.*)','ConfigMakeIPRe','Mail from any of these IP addresses and Hostnames will pass through without checking maximum number of simultaneous SMTP sessions. For example: localhost|145.145.145.145'],
['maxSMTPipSessions','Maximum Sessions Per IP address',10,\&textinput,5,'(\d?\d?\d?)',undef,
  'The maximum number of SMTP sessions allowed per IP address. Use this setting to prevent server overloading and DoS attacks. 5 sessions are typically enough. If left blank or set to 0 there is no limit imposed by ASSP. acceptAllMail (Accept All Mail) matches are excluded from SMTP session limiting. Scoring is done  with iplValencePB.'],
['iplValencePB','IP Parallel Sessions Score, default=5 +',10,\&textinput,5,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002820','msg002821'],
['HeaderMaxLength','Maximum Header Size',10,\&textinput,0,'(.*)',undef,
  'The maximum allowed header length, in bytes. At each mail hop header information is added by the mail server. A large mail header can indicate a mail loop. If the value is blank or 0 the header size will not be checked.'],
['MaxEqualXHeader','Maximum Equal X-Header Lines*',120,\&textinput,'*=>20','^((?:.+?\s*=>\s*\d+(?:\s*\|.+?\s*=>\s*\d+)*)|\s*file\s*:\s*.+|)$','configUpdateStringToNum',
 'The maximum allowed equal X-header lines - eg. "X-SubscriberID". If the value is set to empty the header will not be checked for equal X-header lines. This check will be skipped for noprocessing, whitelisted and outgoing mails.<br />
  The default is "*=&gt;20", which means any X-header can occur 20 time maximum. You can define different values for different X-headers - wildcards like "*" and "?" are allowed to be used.<br />
  For example:<br />
  *=&gt;20|X-Notes-Item=&gt;100|X-Subscriber*=&gt;10|X-AnyTag=&gt;0<br />
  An value of zero disables the check for the defined X-header. The check is also skipped if no default like "*=&gt;20" is defined and the X-header definition is not found.',undef,undef,'msg009060','msg009061'],
['detectMailLoop','Detect Possible Mailloop',10,\&textinput,10,'(.*)',undef,
 'If set to a value higher than 0, ASSP count its own Received-header in the header of the mail. If this count exceeds the defined value, the transmission of the message will be canceled.'],
['maxSize','Max Size of Outgoing Message',10,\&textinput,'','(.*)',undef,
 'If the value of ([message size]) exceeds maxSize in bytes the transmission of the local message will be canceled. No limit is imposed by ASSP if the field is left blank or set to 0. This option allows admins to limit useless bandwidth wasting based on the transmit size.'],
['MaxSizeAdr','Max Size of Local Message Adresses*',120,\&textinput,'file:files/MaxSize.txt','(\s*file\s*:\s*.+|)','configUpdateMaxSize',
'Use this parameter to set individual maxSize values for email addresses, domains, user names and IP addresses. A file must be specified if used.<br />
Accepts specific addresses (user@domain.com), user parts (user), entire domains (@domain.com) and IP addresses (CIDR notation like 123.1.101/32 is here not supported!) - group definitions could be used. Use one entry per line. Wildcards are supported (fribo*@domain.co?). A second parameter separated by "=>" specifies the size limit. <br />
For example:<br />
fribo*@thisdomain.co?=>1000000<br />
jhanna=>0<br />
@sillyguys.org=>500000<br />
101.1.2.*=>0<br />
If multiple matches (values) are found in a mail for any IP address in the transport mail chain, any envelope recipient and the envelope sender, the highest value or 0 (no limit) will be used! If no match (value) is found in a mail, the definition in maxSize will take place.'
,undef,undef,'msg009510','msg009511'],
['maxSizeExternal','Max Size of Incoming Message',10,\&textinput,'','(.*)',undef,
 'If the value of ([message size]) exceeds maxSizeExternal in bytes the transmission of the message will be canceled. No limit is imposed by ASSP if the field is left blank or set to 0. This option allows admins to limit useless bandwidth wasting based on the transmit size.'],
['MaxSizeExternalAdr','Max Size of External Message Adresses*',120,\&textinput,'file:files/MaxSizeExt.txt','(\s*file\s*:\s*.+|)','configUpdateMaxSize',
'Use this parameter to set individual maxSizeExternal values for email addresses, domains, user names and IP addresses. A file must be specified if used.<br />
Accepts specific addresses (user@domain.com), user parts (user), entire domains (@domain.com) and IP addresses (CIDR notation like 123.1.101/32 is here not supported!) - group definitions could be used. Use one entry per line. Wildcards are supported (fribo*@domain.co?). A second parameter separated by "=>" specifies the size limit. <br />
For example:<br />
fribo*@thisdomain.co?=>1000000<br />
jhanna=>0<br />
@sillyguys.org=>500000<br />
101.1.2.*=>0<br />
If multiple matches (values) are found in a mail for any IP address in the transport mail chain, any envelope recipient and the envelope sender, the highest value or 0 (no limit) will be used! If no match (value) is found in a mail, the definition in maxSizeExternal will take place.'
,undef,undef,'msg009520','msg009521'],
['noMaxSize','Don\'t Check Messages from these Addresses/Domains*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Don\'t check the value of  maxSizeExternal and maxRealSizeExternal in messages from these addresses/domain. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com).'],
['maxRealSize','Max Real Size of Outgoing Message',10,\&textinput,'','(.*)',undef,
 'If the value of (number of [rcpt to] * [message size]) exceeds maxRealSize in bytes the transmission of the message will be canceled. No limit is imposed by ASSP if the field is left blank or set to 0. This option allows admins to limit useless bandwidth wasting based on the total transmit size.'],
['MaxRealSizeAdr','Max Real Size of Local Message Adresses*',120,\&textinput,'file:files/MaxRealSize.txt','(\s*file\s*:\s*.+|)','configUpdateMaxSize',
'Use this parameter to set individual maxRealSize values for email addresses, domains, user names and IP addresses. A file must be specified if used.<br />
Accepts specific addresses (user@domain.com), user parts (user), entire domains (@domain.com) and IP addresses (CIDR notation like 123.1.101/32 is here not supported!) - group definitions could be used. Use one entry per line. Wildcards are supported (fribo*@domain.co?). A second parameter separated by "=>" specifies the size limit. <br />
For example:<br />
fribo*@thisdomain.co?=>1000000<br />
jhanna=>0<br />
@sillyguys.org=>500000<br />
101.1.2.*=>0<br />
If multiple matches (values) are found in a mail for any IP address in the transport mail chain, any envelope recipient and the envelope sender, the highest value or 0 (no limit) will be used! If no match (value) is found in a mail, the definition in maxRealSize will take place.'
,undef,undef,'msg009490','msg009491'],
['maxRealSizeExternal','Max Real Size of Incoming Message',10,\&textinput,'','(.*)',undef,
 'If the value of (number of [rcpt to] * [message size]) exceeds maxRealSizeExternal in bytes the transmission of the external message will be canceled. No limit is imposed by ASSP if the field is left blank or set to 0. This option allows admins to limit useless bandwidth wasting based on the total transmit size.'],
['MaxRealSizeExternalAdr','Max Real Size of External Message Adresses*',120,\&textinput,'file:files/MaxRealSizeExt.txt','(\s*file\s*:\s*.+|)','configUpdateMaxSize',
'Use this parameter to set individual maxRealSizeExternal values for email addresses, domains, user names and IP addresses. A file must be specified if used.<br />
Accepts specific addresses (user@domain.com), user parts (user), entire domains (@domain.com) and IP addresses (CIDR notation like 123.1.101/32 is here not supported!) - group definitions could be used. Use one entry per line. Wildcards are supported (fribo*@domain.co?). A second parameter separated by "=>" specifies the size limit. <br />
For example:<br />
fribo*@thisdomain.co?=>1000000<br />
jhanna=>0<br />
@sillyguys.org=>500000<br />
101.1.2.*=>0<br />
If multiple matches (values) are found in a mail for any IP address in the transport mail chain, any envelope recipient and the envelope sender, the highest  value or 0 (no limit) will be used! If no match (value) is found in a mail, the definition in maxRealSizeExternal will take place.'
,undef,undef,'msg009500','msg009501'],
['smtpIdleTimeout','SMTP Idle Timeout',10,\&textinput,600,'(\d?\d?\d?\d?)',undef,
 'The number of seconds a session is allowed to be idle before being forcibly disconnected. No limit is imposed by ASSP if the field is left blank or set to 0. If you have not defined an IdleTimeout on your MTA, this value should not be set to 0, because then a connection will never be timed out! <input type="button" value=" Show Timeout Cache" onclick="javascript:popFileEditor(\'pb/pbdb.smtptimeout.db\',6);" />'],
['idleValencePB','Timeout Score',10,\&textinput,0,'(\d+)','ConfigChangeValencePB', 'For IP scoring with smtpIdleTimeout.',undef,undef,'msg008870','msg008871'],
['smtpNOOPIdleTimeout','SMTP Idle Timeout after NOOP',10,\&textinput,0,'(\d?\d?\d?\d?)',undef,
 'The number of seconds a session is allowed to be idle after a "NOOP" command is received, before being forcibly disconnected. No limit is imposed by ASSP if the field is left blank or set to 0.<br />
  This should prevent hackers to hold and block connections by sending "NOOP" commands short before the "smtpIdleTimeout" is reached.'],
['smtpNOOPIdleTimeoutCount','SMTP Idle Timeout after NOOP Count',10,\&textinput,0,'(\d?\d?)',undef,
 'The number of counts a session is allowed send "NOOP" commands following on each other, before being forcibly disconnected. No limit is imposed by ASSP if the field is left blank or set to 0.<br />
  This in cooperation with "smtpNOOPIdleTimeout" should prevent hackers to hold and block connections by sending repeatedly "NOOP" commands short before the "smtpNOOPIdleTimeout" is reached. If "smtpNOOPIdleTimeout" is not defined or 0, this value will be ignored!<hr /><div class="menuLevel1">Notes On SMTP Session Limits</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/sessionlimits.txt\',3);" />'],

[0,0,0,'heading','SPAM Control/Testmode <a href="http://apps.sourceforge.net/mediawiki/assp/index.php?title=Getting_Started" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="Getting Started" /></a>'],

['spamSubject','Prepend Subject of Spam Mails',40,\&textinput,'','(.*)',undef,'For example: [SPAM]','Basic'],
['allTestMode','Set all Filters to TestMode',0,\&checkbox,'','(.*)','','Setting TestMode will tell ASSP not to reject the mail but rather build up the whitelist and spam and notspam collections. This can go on for some time without disturbing normal operation. Be sure spamSubject is blank, no user should see anything strange. If you want only selected filters to work in testmode set them in the filter section','Basic'],
['spamTag','Prepend Spam Tag',0,\&checkbox,'','(.*)',undef,'ASSP uses many methods. The method which caught the spam  will be prepended to the subject of the email. For example: [DNSBL]'],
['NotSpamTag','Ham Password',40,\&textinput,'','(.*)',undef,'If an incoming email matches this text string it will be considered not-spam. This can be used in SpamError to ask for resending the mail with this text in the subject.'],
['NotSpamTagAutoWhite','NotSpamTag will whitelist',0,\&checkbox,'','(.*)',undef,'If a sender uses the NotSpamTag, the senderaddress will be whitelisted.'],
['NotSpamTagRandom','Generate NotSpamTag Randomly',0,\&checkbox,'','(.*)','updateNotSpamTag','ASSP will use MSGIDSec to make a NotSpamTag. This will change daily. The last 10 days will be saved.'],
['SpamError','Spam Error',120,\&textinput,'554 5.7.1 Mail (SESSIONID) appears to be unsolicited - resend the mail with NOTSPAMTAG appended to the subject and contact postmaster@LOCALDOMAIN for resolution','([245]\d\d .*)',undef,'SMTP error message to reject spam. The literal LOCALDOMAIN will be replaced by the recipient domain or defaultLocalHost. SESSIONID will be replaced by the unique ASSP identifier. REASON will be replaced by the actual reason. NOTSPAMTAG will be replaced by NotSpamTag. MYNAME will be replaced by myName.'],
['SpamErrorLocal','Spam Error for Local Messages',120,\&textinput,'554 5.7.1 Mail (SESSIONID) appears to be unsolicited - REASON - contact postmaster@LOCALDOMAIN for resolution','([245]\d\d .*)',undef,'SMTP error message to reject spam. The literal LOCALDOMAIN will be replaced by the recipient domain or defaultLocalHost. SESSIONID will be replaced by the unique ASSP identifier. REASON will be replaced by the actual reason. NOTSPAMTAG will be replaced by NotSpamTag. MYNAME will be replaced by myName.'],
['send250OK','Send 250 OK ',0,\&checkbox,'','(.*)',undef,
 'Set this checkbox if you want ASSP to reply with \'250 OK\' instead of SMTP error code \'554 5.7.1\'.'],
['AddSpamHeader','Add Spam Header',0,\&checkbox,1,'(.*)',undef,
 'Adds a line to the email header "X-Assp-Spam: YES" if the message is spam.'],
['AddCustomHeader','Add Custom Header',120,\&textinput,'','(.*)',undef,
 'Adds a line to the email header if the message is spam. For example: <a href="http://exchangepedia.com/blog/2008/01/assigning-scl-to-messages-scanned-by.html">X-Spam-Status:yes</a>'],
['AddIPHeader','Add IP Match Header',0,\&checkbox,'','(.*)',undef,'Add X-Assp- header for all IP matches.',undef],
['AddRegexHeader','Add  RegEx Match Header',0,\&checkbox,'','(.*)',undef,''],
['AddIntendedForHeader','Add Intended-For Header for Recipients','1:first recipient|2:multiple recipients',\&listbox,1,'(.*)',undef,
  '<br /><hr /><div class="menuLevel1">Notes On RWL</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/rwl.txt\',3);" />'],
['AddSubjectHeader','Add X-ASSP-Original-Subject Header',1,\&checkbox,'','(.*)',undef,
 'Adds a line to the email header "X-ASSP-Original-Subject: the subject".',undef,undef,'msg000300','msg000301'],
['AddSpamReasonHeader','Add Spam Reason Header',0,\&checkbox,1,'(.*)',undef,
 'Adds a line to the email header "X-Assp-Spam-Reason: " explaining why the message is spam.<br /><hr /><div class="menuLevel1">Notes On Spam Control</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/spamcontrol.txt\',3);" />'],
['NoExternalSpamProb','No Outgoing X-ASSP Header',0,\&checkbox,'','(.*)',undef,
'Check this box if you don\'t want X-Assp- headers on outgoing mail.'],
['noGriplistUpload','Don\'t Upload Griplist Stats',0,\&checkbox,'','(.*)',undef,
 'Check this to disable the Griplist upload when rebuildspamdb runs. The Griplist contains IP addresses and their values between 0 and 1, lower is less spammy, higher is more spammy. This value is called the grip value. ',undef,undef,'msg000230','msg000231'],
['noGriplistDownload','Don\'t auto-download the Griplist file',0,\&checkbox,'','(.*)',undef,
 "Set this checkbox, if you don\'t use the Griplist.  ",undef,undef,'msg000240','msg000241'],
['GriplistDownloadNow','Run GriplistDownload Now',0,\&checkbox,'','(.*)','ConfigChangeRunTaskNow', "If selected, ASSP will download the Griplist right away. <input type=button value=\"Run Now!\" onclick=\"document.forms['ASSPconfig'].theButtonX.value='Apply Changes';document.forms['ASSPconfig'].submit();WaitDiv();return false;\" />&nbsp;<input type=button value=\"Refresh Browser\" onclick=\"document.forms['ASSPconfig'].theButtonRefresh.value='Apply Changes';document.forms['ASSPconfig'].submit();WaitDiv();return false;\" />"],
['noGRIP','Don\'t do Griplist for these IP addresses and Hostnames* ',120,\&textinput,'','(.*)','ConfigMakeIPRe',
 'Enter IP addresses and Hostnames that you don\'t want to get gripvalues from. For example:server.example.com|145.145.145.145|145.146.','','7'],
['DoFullGripDownload','Full Griplist Download Period',10,\&textinput,30,'(\S*)',undef,
 'The Global Griplist is downloaded once in full, then only deltas are downloaded each day subsequently.  This option forces a new full download after this many days.  Leave it blank to not force new full downloads. Recommended: 30 days.<br /><hr /><div class="menuLevel1">Notes On Griplist</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/griplist.txt\',3);" />'],

[0,0,0,'heading','SPAM Lovers'],

['spamLovers','All Spam-Lover*',120,\&textinput,'postmaster|abuse','(.*)','ConfigMakeSLReSL',
 'Messages to Spam-Lovers are processed and filtered by ASSP, but get tagged with spamSubject and are not blocked. Recipients here are tagged by all filters. If you want spamlovers for selected filters use: baysSpamLovers, blSpamLovers. bombSpamLovers, hlSpamLovers, atSpamLovers, spfSpamLovers, rblSpamLovers, uriblSpamLovers, srsSpamLovers, mxaSpamLovers, ptrSpamLovers, sbSpamLovers.  When a 
 Spam-Lover is not the sole recipient of a message, the message is processed
 normally, and if it is found to be spam, it will not be delivered to the
 Spam-Lover. delaySpamLovers are not included here and must be set additionally. Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com). Wildcards are supported (fribo*@domain.com). Default: postmaster|abuse.<br />For example: fribo*@thisdomain.com|jhanna|@sillyguys.org
 <hr>
 This option and all SpamLover-Options below are accepting a second score parameter like "user@your-domain.com=>70"<br />
 If such a parameter is defined in any option for an entry and the recipient address matches this entry and the message score exceeds the parameter value, the message will be blocked.<br />
 If there are multiple possible matches for a recipient address found, the generic longest match (and value) will be used.'],
['spamLoverSubjectSelected','Suppress SpamSubject For Selected Recipients*',120,\&textinput,'ALL','(.*)','ConfigMakeSLRe','spamSubject does NOT get prepended to the subject for these recipients. To enable the selection you need to uncheck spamSubjectSL.'],
['SpamLoverTag','SpamLover Tag',40,\&textinput,'[sl]','(.*)',undef,,''],
['SpamLoversRe','Regular Expression to Identify  SpamLovers*',120,\&textinput,'','(.*)','ConfigCompileRe',
'If a message matches this regular expression it will not been blocked, but tagged.'],
['slMaxScore','Block Spamlover Messages Above This Score',10,\&textinput,90,'(.*)',undef,
 'Messages to spamLovers  whose score exceeds this threshold will be blocked.'],

[0,0,0,'heading','NoProcessing'],

['npSize','Incoming Messages NoProcessing Size',10,\&textinput,1000000,'(.*)',undef,'This limit ensures that only incoming messages smaller than this limit are processed by ASSP. Most spam
isn\'t bigger than a few k. ASSP will treat incoming messages larger than this SIZE (in bytes) as \'NoProcessing\' mail. Empty or 0 disables the feature.'],
['noProcessingIPs','NoProcessing IPs*',120,\&textinput,'file:files/ipnp.txt','(.*)','ConfigMakeIPRe','Mail from any of these IP addresses and Hostnames will pass through without processing. Example file:<a href=http://assp.cvs.sourceforge.net/viewvc/assp/asspV1/files/ipnp.txt target=files ><span class="positive"> ipnp.txt</a>','','7'],
['NPexcludeIPs','Exclude these IPs from noProcessingIPs *',120,\&textinput,'','(.*)','ConfigMakeIPRe','Manually maintained list of IP addresses and Hostnames which should be excluded from noProcessingIPs.'],
['noProcessing','NoProcessing Addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'Contains addresses of sender and recipients. You can specify addresses for recipients only by using noProcessingTo, addresses for sender only should be put into noProcessingFrom. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com).  Wildcards are supported (fribo*@example.com). <span class="positive">Better to use noProcessingFrom and noProcessingTo instead.'],
['noProcessingFrom','NoProcessing Sender*',120,\&textinput,'file:files/noprocessingfrom.txt','(.*)','ConfigMakeSLRe',
 'Mail from any of these addresses are proxied without processing. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com).  Wildcards are supported (fribo*@example.com).'],
['noProcessingTo','NoProcessing Recipients*',120,\&textinput,'file:files/noprocessingto.txt','(.*)','ConfigMakeSLRe',
 'Mail solely to any of these addresses are proxied without processing. All recipients must be marked as noprocessing. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com).  Wildcards are supported (fribo*@example.com).'],
['removeForeignBCC','remove Foreign BCC',0,\&checkbox,'','(.*)',undef,'Remove foreign BCC: header lines from the mail header. The remove is done before the DoHeaderAddrCheck is done!',undef,undef,'msg009780','msg009781'],
['noProcessingDomains','NoProcessing Domains*',120,\&textinput,'file:files/noprocessingdomains.txt','(.*)','ConfigMakeRe',
 'Domains from which you want to receive all mail and  proxy without processing. Your ISP, domain registration, mail list servers, stock broker, or other key business partners might be good candidates. Note this matches the end of the address, so if you don\'t want to match subdomains then include the @. Note that buy.com would also match spambuy.com but .buy.com won\'t match buy.com. For example: sourceforge.net|@google.com|.buy.com','','1'],
['noNoProcessing','Do not mark these Addresses as Noprocessing*',120,\&textinput,'','(.*)','ConfigMakeSLRe','Enter senders email addresses that you want to be processed, even if they are in noprocessing lists. You can list specific addresses (user@anydomain.com), addresses at any domain (user), or entire domains (@anydomain.com).  Wildcards are supported (fribo*@domain.com).<br />For example: fribo@anydomain.com|jhanna|@sillyguys.org or place them in a plain ASCII file one address per line: \'file:files/nodelayuser.txt\'.'],
['npRe','Regular Expression to Identify NoProcessing Incoming Mails*',120,\&textinput,'','(.*)','ConfigCompileRe',
 'If a message matches this Perl regular expression ASSP will treat the message as a \'NoProcessing\' mail. For example: X-Assp-Version<br /><hr /><div class="menuLevel1">Notes On NoProcessing</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/noprocessing.txt\',3);" />'],

[0,0,0,'heading','Whitelist/Redlist'],

['redRe','Regular Expression to Identify Redlisted Mail*',120,\&textinput,'file:files/redre.txt','(.*)','ConfigCompileRe',
 'If an email matches this Perl regular expression it will be 
considered redlisted.
<br />The Redlist serves several purposes:
<br />1) the Redlist is a list of addresses that cannot contribute to the 
whitelist. For example, if someone goes on a vacation and 
turns on their autoresponder, put them on the redlist until 
they return. Then as they reply to every spam they receive they won\'t 
corrupt your non-spam collection or whitelist: \[autoreply\]
<br />2) Redlisted addresses will not be added to the Whitelist.
<br />3) Redlisted messages will not be stored in the 
SPAM/NOTSPAM-collection. 
<br />As all fields marked by * this field accepts 
a list separated by | or a plain ASCII file one address per line: \'file:files/redre.txt\'. '],
['whiteListedIPs','Whitelisted IPs*',120,\&textinput,'','(.*)','ConfigMakeIPRe','They  contribute to the Whitelist and to Notspam. For example: 145.145.145.145|146.145. <span class="positive"> All fields marked by \'*\' accept  a filepath/filename : \'file:files/ipwl.txt\'.</span>','','7'],
['whiteRe','Regular Expression to Identify Non-Spam* ',120,\&textinput,'','(.*)','ConfigCompileRe','If an incoming email matches this Perl regular expression it will be considered non-spam.<br />For example: Secret Ham Password|307\D{0,3}730\D{0,3}4[12]\d\d'],
['whiteListedDomains','Whitelisted Domains and Addresses*',120,\&textinput,'sourceforge.net','(.*)','ConfigMakeRe','Domains and addresses from which you want to receive all mail. Your ISP, domain registration, mail list servers, stock broker, or other key business partners might be good candidates. <span class="negative">Do not to put widely used domains here like hotmail.com.</span> Put popular domains into whiteSenderBase. Note this matches the end of the address, so if you don\'t want to match subdomains then include the @. Note that \'example.com\' would also match \'spamexample.com\' but \'.example.com\' won\'t. Wildcards are supported. For example: sourceforge.net|group*@google.com|.example.com. You may place them in a plain ASCII file one address per line:\'file:files/whitedomains.txt\'','','9'],
['WhitelistOnly','Reject All But Whitelisted Mail',0,\&checkbox,'','(.*)',undef,'Check this if you want to reject all mail from anyone NOT on the Whitelist ( whitelistdb ) and not marked noprocessing. '],
['WhitelistOnlyAddresses','Reject All But Whitelisted Mail for these Addresses/Domains*',120,\&textinput,'','(.*)','ConfigMakeSLRe','Put here addresses/domains which should only accept whitelisted/noprocessing mail. Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com). Wildcards are supported (*@domain.com, abuse@*, *@*).  '],
['NoAutoWhite','Only Email-Interface Addition to Whitelist.',0,\&checkbox,'','(.*)',undef,'Check this box to  allow additions to the whitelist by email interface only.',undef,undef,'msg000980','msg000981'],
['NoAutoWhiteAdresses','No AutoWhite Addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'Mail solely to or from any of these addresses are excluded from automatic whitelist additions. Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com).  Wildcards are supported (fribo*@domain.com).',undef,undef,'msg009970','msg009971'],
['NotGreedyWhitelist','Only the envelope-sender is added/compared to the whitelist',0,\&checkbox,1,'(.*)',undef,'Normal operation includes addresses in the FROM, SENDER, REPLY-TO, ERRORS-TO, or LIST-* header fields.<br />This allows nearly all list email to be whitelisted. Check this option to disable this. Will not apply if you add/remove whitelist entries via email-interface.'],
['WhitelistLocalOnly','Only local or authenticated users contribute to the whitelist.',0,\&checkbox,'','(.*)',undef,'Normal operation allows all local, authenticated, or whitelisted users to contribute to the whitelist.<br />Check this box to not allow whitelisted (but not local) users to add to the whitelist.'],
['WhitelistLocalFromOnly','Only local users with a local domain in envelope contribute to the whitelist.',0,\&checkbox,1,'(.*)',undef,'Check this box to prevent a local sender with non-local domain from contributing to the whitelist. (for example: redirected messages).'],
['WhitelistAuth','Whitelist mails from authenticated users.',0,\&checkbox,1,'(.*)',undef,'Mails from 
authenticated users will be processed as whitelisted'],
['UpdateWhitelist','Save Whitelist',10,\&textinput,3600,'(.*)','configChangeUpdateWhitelist','Save a copy of the white list every this many seconds. Empty or Zero will prevent any saving.<br /><hr /><div class="menuLevel1">Whitelist Addition/Deletions</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/whitelistadd.txt\',5);" />'],
['MaxWhitelistDays','Max Whitelist Days',10,\&textinput,999,'(\d+)',undef,'This is the number of days an address will be kept on the whitelist without any email to/from this address.'],
['ValidateRWL','Enable Realtime Whitelist Validation',0,\&checkbox,'','(.*)','configUpdateRWL','RWL: Real-time white list. These are lists of IP addresses that have
 somehow been verified to be from a known good host. Senders that pass RWL validation will pass IP-based filters. This requires an installed Net::DNS module in PERL. ',undef,undef,'msg000870','msg000871'],
['RWLwhitelisting','Whitelist all RWL Validated Addresses',0,\&checkbox,'','(.*)',undef,'If set, the message will also pass Bayesian Filter and URIBL.',undef,undef,'msg000880','msg000881'],
['RWLServiceProvider','RWL Service Providers*',120,\&textinput,'list.dnswl.org','(.*)','configUpdateRWLSP','Hostnames of RWLs to use separated by "|".<br />Examples are: list.dnswl.org',undef],
['RWLmaxreplies','Maximum Replies',10,\&textinput,1,'(.*)','configUpdateRWLMR','A reply is affirmative or negative reply from a RWL. The RWL module will wait for this number of replies (negative or positive) from the RWLs listed under Service Provider for up to the Maximum Time below. This number should be equal to or less than the number of RWL Service Providers listed to allow for randomly unavailable RWLs. ',undef],
['RWLminhits','Minimum Hits',10,\&textinput,1,'(.*)','configUpdateRWLMH','A hit is an affirmative response from a RWL. The RWL module will check all of the RWLs listed under Service Provider, and flag the email with a RWL \'pass\' flag if equal to or more than this number of RWLs return a positive whitelisted response. If the number is less but not zero the email is marked \'neutral\'',undef],
['RWLmaxtime','Maximum Time',10,\&textinput,5,'(.*)',undef,'This sets the maximum time to spend on each message performing RWL checks',undef],
['noRWL','Don\'t Validate RWL for these IPs*',120,\&textinput,'','(.*)','ConfigMakeIPRe','Enter IP addresses that you don\'t want to be RWL validated, separated by pipes (|). For example: 145.145.145.145|146.145.',undef,'7'],
['AddRWLHeader','Add X-Assp-Received-RWL Header',0,\&checkbox,1,'(.*)',undef,'Add X-Assp-Received-RWL header to header of all emails processed by RWL.',undef],
['RWLCacheInterval','RWL Cache Expiration Time',10,\&textinput,0,'([\d\.]+)','configUpdateRWLCR','IPs in cache will be removed after this interval in days. 0 will disable the cache.  <input type="button" value=" show cache" onclick="javascript:popFileEditor(\'pb/pbdb.rwl.db\',5);" />'],
['RWLLog','Enable RWL logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,
  '<br /><hr /><div class="menuLevel1">Notes On RWL</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/rwl.txt\',3);" />'],

[0,0,0,'heading','Relaying <a href="http://apps.sourceforge.net/mediawiki/assp/index.php?title=Relaying" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="relaying not allowed" /></a>'],

['RelayLog','Enable Relay logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,
  '<br /><hr /><div class="menuLevel1">Notes On Relaying</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/relaying.txt\',3);" />'],
['acceptAllMail','Accept All Mail*',120,\&textinput,'','(.*)','ConfigMakeIPRe','Relaying is allowed for these IP addresses and Hostnames. They  contribute also to the whitelist. This can take either a directly entered list of IP addresses and Hostnames separated by pipes or a plain ASCII file one address per line: \'file:files/acceptall.txt\'.<br /> An IP range is defined e.g. \'182.82.10.\'. CIDR notation is accepted (182.82.10.0/24). Hyphenated ranges can be used (182.82.10.0-182.82.10.255)','Basic','7'],
['DoLocalSenderDomain','Do Local Domain Check for Local Sender',0,\&checkbox,'','(.*)',undef,
  'If activated, each local sender address must have a valid Local Domain. Put domains into noLocalSenderCheck if they are used as sender addresses and are not local.',undef,undef,'msg001050','msg001051'],
['DoLocalSenderAddress','Do Local Address Check for Local Sender',0,\&checkbox,'','(.*)',undef,
  'If activated, each local sender address must have a valid Local Address. Put addresses into noLocalSenderCheck if they are used as sender addresses and are not local.',undef,undef,'msg001060','msg001061'],
['localDomains','Local Domains*',120,\&textinput,'file:files/localdomains.txt','(.*)','ConfigMakeRe','Put here are the domain names that your mail system considers local. Separate entries with |  or place them in a plain ASCII file one address per line: \'file:files/localdomains.txt\'. Wildcards are supported.<br /> For example: example.org|*example.com<br />
If ASSP finds no other hint that the domain is local, it  will reject messages to domains not listed here with \'RelayAttempt\'. A successfull DoLDAP, DoVRFY or hit in LocalAddresses_Flat  will put the domain part of the queried address into ldaplistdb and will mark the domain as local.
You can set nolocalDomains to disable this check during setup and testing.', 'Basic'],
['localDomainsFile','Local Domains File',120,\&textinput,'','(.*)',undef,'Similar to localDomains, but with absolute path to the file. Wildcards are not supported. For access to MTA generated files. '],
['nolocalDomains','Skip Local Domain Check',0,\&checkbox,'','(.*)','ConfigChangeNoDomains','Do not check relaying for invalid domains - let the MTA do it. This can be set to prevent \'RelayAttempt\' errors. <span class="negative">Attention: this will make ASSP an open relay, if the MTA behind it does not reject messages to unknown domains.</span>'],
['MaxRelayingErrors','Maximum Relaying Errors Per Session',10,\&textinput,3,'(.*)',undef,
'The maximum number of Relaying Errors encountered before the connection is dropped. Scoring is done  with meValencePB. 0 will not count Relaying Errors. '],
['meValencePB','Max Errors Exceeded Score, default=10 +',10,\&textinput,10,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002900','msg002901'],
['relayHost','Your mail relayhost (smarthost)',120,\&textinput,'','(.*)',undef,'Mails arriving at relayPort will be delivered to the destination configured here. For example: mail.relayhost.com:25<br />if you run Exchange/Notes and you want assp to update the nonspam database and the whitelist, then enter your smtp relay host here. Blank means no relayhost. ','Basic'],
['relayAuthUser','Username for  Authentication to Relay Host',40,\&textinput,'','(\S*)',undef,'The username used for SMTP AUTH authentication to the relayHost  -  if your ISP need authentication on the SMTP port. Supported authentication methods are PLAIN, LOGIN, CRAM-MD5 and DIGEST-MD5 . If the relayhost offers multiple methods, the one with highest security option will be used. The Perl module <a href="http://search.cpan.org/search?query=Authen::SASL" rel="external">Authen::SASL</a> must be installed to use this feature! The usage of this feature will be skipped, if the sending MTA uses the AUTH command. Leave this blank, if you do not want use this feature.','Basic'],
['relayAuthPass','Password for  Authentication to Relay Host',40,\&textinput,'','(\S*)',undef,'The password used for SMTP AUTH authentication to the relayHost. Leave this blank, if you do not want use this feature.','Basic'],
['relayPort','Relay Port',40,\&textinput,'','(.*)','ConfigChangeRelayPort','Mails arriving at this port are considered local and delivered to relayHost. Tell your mail server to connect to this port as its smarthost/relayhost. For example: 225<br /> Note that you\'ll want to keep the relayPort protected from external access by your firewall.<br />You can supply an interface:port to limit connections.','Basic'],
['allowRelayCon','Allow Relay Connection from these IPs*',120,\&textinput,'','(\S*)','ConfigMakeIPRe','Enter any addresses that are allowed to use the relayPort , separated by pipes (|). If empty, any ip address is allowed to connect to the relayPort. If this option is defined, keep in mind : Addresses defined in acceptAllMail are <b>NOT</b> automatically included and have to be also defined here, if they should be allowed to use the relayPort. For example: 127.0.0.1|172.16..','Basic','7'],
['ldLDAP','Do LDAP lookup for local domains',0,\&checkbox,'','(.*)',undef,'Check local domains against an LDAP database.<br />Note: Checking this requires filling in LDAP DomainFilter ( ldLDAPFilter ).and NET::LDAP module in Perl.',undef,undef,'msg001080','msg001081'],
['ispip','Additional MX Servers*',120,\&textinput,'','(\S*)','ConfigMakeIPRe','Enter any addresses or hostnames that are your ISP or backup MX servers, separated by pipes (|). <br />These addresses will (necessarily) bypass Griplist, Delaying, PenaltyBox, SPF, DNSBL and SRS checks unless the IP can be determined by ispHostnames (ISP Connecting IP). For example: 145.145.145.145|145.145.145.146.','Basic',7],
['contentOnlyRe', 'Regular Expression to Identify Forwarded Messages*',120,\&textinput,'','(.*)','ConfigCompileRe',
 "Put anything here to identify messages which should bypass all IP based filter like PB, Sender Validation, Griplist, IP Limiting, Delaying, SPF, DNSBL and SRS. For example:  email addresses of people who are forwarding from other accounts to their mailbox on your server."],
['ispHostnames','Hostname of frontend IP*',120,\&textinput, '','(.*)', 'ConfigCompileRe', 'Hostnames to lookup the frontend IP.<br />If found, this address is used to perform IP-based checks on forwarded messages. <br />For example: mx1\.yourisp\.com or mx1\.yourisp\.net|mx2\.yoursecondary\.com . <i>This hostnames are found in the \'Received:\' header, like  \'Received: from ...123.123.123.123... by <span class="positive">mx1.yourisp.com</span>\'</i>. Hostnames may use regular expressions. The frontend IP must be listed in ispip. Leave this blank to disable the feature. ',undef,undef,'msg001110','msg001111'],
['send250OKISP','Send 250 OK To ISP/Secondary MX Servers',0,\&checkbox,1,'(.*)',undef,
 'Set this checkbox if you want ASSP to reply to IP addresses in ispip with \'250 OK\' instead of SMTP error code \'554 5.7.1\'. '],
['PopB4SMTPFile','Pop Before SMTP DB File',120,\&textinput,'','(.*)',undef,'Enter the DB database filename of your POP before SMTP implementation with records stored for dotted-quad IP addresses.<br />For example: /etc/mail/popip.db'],
['PopB4SMTPMerak','Pop Before SMTP Merak Style',0,\&checkbox,'','(.*)',undef,'If set Merak 7.5.2 is supported.'],
['defaultLocalHost','Default Local Domain',120,\&textinput,'assp.local','(.*)',undef,'If you want to be able to send mail to local users without a domain name then put the default local domain here. <br /> Blank disables this feature. For example: assp.local<br /><hr /><div class="menuLevel1">Notes On Relaying</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/relaying.txt\',3);" />'],

[0,0,0,'heading','Outgoing/AUTH '],

['AUTHLogUser','Username Logging',0,\&checkbox,1,'(.*)',undef,'Write the username for AUTH (PLAIN/LOGIN) to maillog.txt.'],
['AUTHLogPWD','Password Logging',0,\&checkbox,'','(.*)',undef,'Write the userpassword for AUTH (PLAIN/LOGIN) to maillog.txt.'],
['NoFilterAuth','Do not filter mails from authenticated users.',0,\&checkbox,1,'(.*)',undef,'Mails from authenticated users will pass unfiltered'],    
['npLocalRe','Regular Expression to Identify NoProcessing Local Mails*',120,\&textinput,'','(.*)','ConfigCompileRe',
 'If an outgoing message matches this Perl regular expression ASSP will treat the message as a \'NoProcessing\' mail. For example: autoreply'],
['blockLocalRe','Regular Expression to Identify Blocked Local Mails*',120,\&textinput,'','(.*)','ConfigCompileRe',
 'If an outgoing message matches this Perl regular expression ASSP will block the message.'],
['DoSameAUTHuser','Check Number of Same User','0:disabled|1:block|2:monitor|4:testmode',\&listbox,2,'(.*)',undef,''],
['maxSameAUTHuser','Limit Number of Same User',10,\&textinput,5,'(\d?\d?\d?)',undef,
 'The number of the user during  maxSameAUTHuserDuration. If a user appears more often than this it will be blocked. If left blank or 0, there is no limit imposed by ASSP.'],
['maxSameAUTHuserDuration','Timeframe for Counting',10,\&textinput,1800,'(\d?\d?\d?\d?)',undef,
  'ASSP uses this number in seconds to count the same user.'],
['LocalFrequencyInt','Local Frequency Interval',40,\&textinput,600,'(.*)',undef,'The time interval in seconds in which the number of envelope recipients per sending address should not exceed a specific number ( LocalFrequencyNumRcpt ).<br >
  Use this in combination with LocalFrequencyNumRcpt to limit the number of recipients in a given interval, to prevent local abuse - for example from highjacked local accounts. A value of 0 will disable this feature and clean the cache within five minutes. To give users the chance to inform an admin about such blocked mails, local mails to EmailAdmins are never blocked because of that feature.<br />
  <input type="button" value="edit local Frequency Cache" onclick="javascript:popFileEditor(\'pb/pbdb.localfreq.db\',5);" />'],
['LocalFrequencyNumRcpt','Local Frequency Recipient Number',40,\&textinput,51,'(.*)',undef,'The number of envelope recipients per sending address that should not be exceeded in a specific time interval ( LocalFrequencyInt ).<br >
  Use this in combination with LocalFrequencyInt to limit the number of recipients in a given interval, to prevent local abuse - for example from highjacked local accounts. A value of 0 will disable this feature and clean the cache within five minutes. To give users the chance to inform an admin about such blocked mails, local mails to EmailAdmins are never blocked because of that feature. <br />
  <input type="button" value="edit local Frequency Cache" onclick="javascript:popFileEditor(\'pb/pbdb.localfreq.db\',5);" />'],
['LocalFrequencyOnly','Check local Frequency for this Users only*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'A list of local addresses, for which the \'local frequency check\' should be done. Leave this field blank (default), to do the check for every address.<br />
  Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com).  Wildcards are supported (fribo*@domain.com).<br />
  For example: fribo*@thisdomain.com|jhanna|@sillyguys.org '],
['NoLocalFrequency','Check local Frequency NOT for this Users*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'A list of local addresses, for which the \'local frequency check\' should not be done. <br />
  Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com).  Wildcards are supported (fribo*@domain.com).<br />
  For example: fribo*@thisdomain.com|jhanna|@sillyguys.org '],
['NoLocalFrequencyIP','Check local Frequency NOT for this IP\'s*',120,\&textinput,'','(.*)','ConfigMakeIPRe',
 'A list of local IP-addresses, for which the \'local frequency check\' should not be done.<br />
  For example: 145.145.145.145|145.146. ',undef,undef,'msg010110','msg010111'],

[0,0,0,'heading','Validate Recipients'],

['ValidateUserLog','Enable User Validation logging','0:nolog|1:standard|2:verbose|3:diagnostic',\&listbox,1,'(.*)',undef,''],
['DoHeaderAddrCheck','Check TO,CC and BCC headers',0,\&checkbox,'','(.*)',undef,'If enabled TO: , CC: and BCC: header lines are checked the following way:<br />
 1. a possible recipient replacement is done<br />
 2. local email address validation is done -  if OK, the next address or headerline is processed<br />
 3. spamtrapaddresses will be detected - scored with stValencePB - <b>mail is blocked</b> (noPenaltyMakeTraps is honored)<br />
 4. a local but not valid TO/CC/BCC: address will be detected and scored with irValencePB<br />
 5. a RelayAttempt will be detected if a BCC address is not local and scored with rlValencePB - <b>mail is blocked</b><br />
 The check 3 and 4 honors whitelisting , noprocessing and noBlockingIPs<br />
 Enable this check only, if assp is configured to validate local domains and email addresses!<br />
 NOTICE: that removeForeignBCC take place before this check is done - step 5 will be never reached if removeForeignBCC is enabled!',undef,undef,'msg010010','msg010011'],
['rlValencePB','Failed Relay Attempt Score, default=10 +',10,\&textinput,10,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg003040','msg003041'],
['irValencePB','Invalid Recipient Score, default=10 +',10,\&textinput,10,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg008940','msg008941'],
['stValencePB','Penalty Trap Address Score, default=50 +',10,\&textinput,50,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'For Message/IP scoring',undef,undef,'msg003220','msg003221'],
['LocalAddresses_Flat','Lookup Local Addresses from Here*',120,\&textinput,'','(.*)','ConfigMakeSLRe','This is an optional list of local addresses for all MTAs behind ASSP. If the address is not found here ASSP will look for other methods of verification (DoLDAP, DoVRFY). If no ASSP-verification is used, the MTA behind ASSP will do it. You can list specific addresses (user@example.com), addresses at any local domain (user), or entire domains (@example.com).  Wildcards are supported (fribo*@example.com). Separate entries with a pipe (|).<br />For example: fribo@example.com|jhanna|@example.org . You may use a plain ASCII file \'file:files/localuser.txt\'.','Basic'],
['LocalAddresses_Flat_Domains','Use Entries without leading \'@\' as Domains',0,\&checkbox,'','([01]?)',undef,'If set entries in LocalAddresses_Flat without leading \'@\' are handled as domains,for example \'example.com\' means an entire domain.'],
['LocalAddressesNP','Do Not Validate Local Addresses if in NoProcessing List',0,\&checkbox,'','(.*)',undef,'If a recipient is found in NoProcessing, the user validation is skipped. '],
['RejectTheseLocalAddresses','Reject These Local Addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
'If ANY recipient is on reject list, the message will not be delivered. Used for disabled legitimate accounts, where a user may have left the company. This stops wildcard mailboxes from getting these messages. You can list specific addresses (user@example.com), addresses at any local domain (user), or entire domains (@example.com).  Wildcards are supported (fribo*@example.com). The field accepts a list separated by \'|\' (for example: fribo*@example.com|@example.com|user) or a file designated as follows (path relative to the ASSP directory): \'file:files/filename.txt\'. Putting in the file: will prompt ASSP to put up a button to edit that file. files is the subdirectory for files. The file does not need to exist, you can create it from the editor by saving it. The file must have one entry per line; anything on a line following a numbersign or a semicolon ( #  is ignored (a comment)'],
['BlockLocalAddressesRe','Block Local Recipients Regular Expression*',120,\&textinput,'[%|]','(.*)','ConfigCompileRe',
  'Block all recipient addresses which match this RegEx. Note: if you want to block the pipe char \'|\' it must be masked with the mask character \'\\\' . You may also use metacharacter brackets ([]) for this purpose.'],
['AllowLocalAddressesRe','Allow Local Recipient Addresses Regular Expression*',120,\&textinput,'','(.*)','ConfigCompileRe',
  'Allow only recipient addresses which match this RegEx.'],
['TrapLog','Enable Trap logging','0:nolog|1:standard|2:verbose',\&listbox,0,'(.*)',undef,''],
['spamtrapaddresses','Trap Addresses* ',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Mail to any of these addresses will be blocked and the Valence stValencePB is added. These addresses are not checked for validity.  Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com).'],
['UseTrapToCollect','Use Penalty Trap Addresses To Collect',0,\&checkbox,'','(.*)',undef,
  'If set ASSP will use spamtrapaddresses to collect spams.',undef,undef,'msg006020','msg006021'],
['SpamTrap2NULL','Move Connection with Trap Addresses to NULL',0,\&checkbox,1,'(.*)',undef,
  'If set, ASSP will move connections with spamtrapaddresses to a NULL-connection. The sender will receive "250 OK".'],
['PenaltyTrapPolite','PenaltyTrap Reply',120,\&textinput,'550 5.1.1 User unknown: EMAILADDRESS','^([542]\d\d .+)',undef,'SMTP reply for invalid Users. Default: \'550 5.1.1 User unknown: EMAILADDRESS\' <br /> The literal EMAILADDRESS (case sensitive) is replaced by the fully qualified SMTP recipient (e.g., thisuser@example.com).',undef,undef,'msg002400','msg002401'],
['DoPenaltyMakeTraps','Cache Unknown Addresses','0:disabled|1:use for spamtrapaddresses|2:use for spamaddresses|3:use for validation',\&listbox,2,'(.*)',undef,
  'If enabled, unknown addresses are cached. If set to \'use for spamtrapaddresses\' addresses which reach the limit in PenaltyMakeTraps will be used like spamtrapaddresses. If set to \'use for spamaddresses\'  they will work like spamaddresses. If set to \'use for validation\' all entries regardless of their frequency will be used to validate incoming addresses. Note: LocalAddresses_Flat or DoLDAP or DoVRFY must be enabled.'],
['PenaltyMakeTraps','Unknown Address Frequency  Limit',10,\&textinput,5,'(.*)',undef,
  'Minimum number of times an address must appear during PBTrapCacheInterval before it will be used as spamaddress/spamtrapaddress in DoPenaltyMakeTraps.'],
['PBTrapCacheInterval','Address Cache Expiration',10,\&textinput,1,'(.*)','configUpdateTrapCR',
  'Addresses will be removed after this interval in days if the frequency in PenaltyMakeTraps is not reached. <input type="button" value=" Show Address Cache" onclick="javascript:popFileEditor(\'pb/pbdb.trap.db\',5);" />'],
['noPenaltyMakeTraps','Exceptionlist for Address Cache*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'Addresses which should not be cached. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com).  Wildcards are supported (fribo*@example.com).'],
['DoVRFY','Verify Recipients with SMTP-VRFY',0,\&checkbox,'','(.*)',undef,  'If activated and the format \'Domain=>MTA\' is encountered in
 vrfyDomains recipient addresses will be verified with SMTP-VRFY (if  VRFY is not supported \'MAIL FROM:\' and \'RCPT TO:\' will be used).
 If you know that VRFY is not supported with a MTA, you may put the MTA into VRFYforceRCPTTO. <br /><input type="button" value=" Show Found Cache" onclick="javascript:popFileEditor(\'ldaplist\',5);" /><input type="button" value="Show NotFound Cache" onclick="javascript:popFileEditor(\'ldapnotfound\',5);" />', ],
['vrfyDomains','VRFY Domains*',120,\&textinput,'file:files/vrfydomains.txt','(.*)','ConfigMakeRe','Put here the domain names that should be verified with SMTP-VRFY. Separate entries with |  or place them in a plain ASCII file one address per line: \'file:files/vrfydomains.txt\'. 
Use the syntax: *mydomain.com=>smtp.mydomain.com|other.com=>mx.other.com:port to verify the recipient addresses with the SMTP-VRFY (if VRFY is not supported \'MAIL FROM:\' and \'RCPT TO:\' will be used) command on other SMTP servers. The entry behind => must be the hostname:port or ip-address:port of the MTA which is used to verify \'RCPT TO\' addresses with a VRFY command! If :port is not defined, port :25 will be used. You can use an entry like ALL=>vrfyhost:port to define a VRFY host for all entries without the MTA part.  You have to enable the SMTP \'VRFY\' command on your MTA - the \'EXPN\' command should be enabled! This requires an installed <a href="http://search.cpan.org/search?query=Net::SMTP" rel="external">Net::SMTP</a> module in PERL. <br />
 If you have configured LDAP and enabled DoLDAP and ASSP finds a VRFY entry for a domain, LDAP search will be done first and if this fails, the VRFY will be used. So VRFY could be used for LDAP backup/fallback/failover!<br />
 It is recommended to configure \'ldaplistdb\' in the \'File Paths and Database\' section when using this verify extension - so ASSP will store all verified recipients addresses there to minimize the querys on MTA\'s. There is no need to configure LDAP, but both VRFY and LDAP are using ldaplistdb. Please go to the \'LDAP setup\' section to configure MaxLDAPlistDays and LDAPcrossCheckInterval or start a crosscheck now with forceLDAPcrossCheck. This three parameters belong also to VRFY.','Basic',undef,'msg001330','msg001331'],
['VRFYQueryTimeOut','SMTP VRFY-Query Timeout',10,\&textinput,5,'(\d\d?)',undef,
 'The number of seconds ASSP will wait for an answer of the MTA that is queried with the VRFY command to verify a recipient address.'],
['VRFYforceRCPTTO','Force the usage of RCPT TO*',120,\&textinput,'','(.*)','ConfigMakeRe','Define local MTAs here for which you want ASSP to force the usage of \'MAIL FROM:\' and \'RCPT TO:\' instead of the VRFY command. The definition of the MTA(s) has to be exactly the same as already defined in vrfyDomains (after the \'=>\') for example: smtp.mydomain.com|mx.other.com:port|10.1.1.1|10.1.1.2:125 .'],
['DisableVRFY','Disable VRFY for External Clients',0,\&checkbox,'','(.*)',undef,
  'If you have enabled VRFY on your MTA to allow ASSP to verify addresses and you do not want external clients to use VRFY/EXPN - select this option.'],
['MaxVRFYErrors','Maximum recipient verification Errors',10,\&textinput,5,'(\d+)',undef,
  'The maximum number of failed \'RCPT TO\' or \'VRFY\' commands encountered before the connection is dropped. ASSP will drop the connection, if the count of \'550 unknown user\' errors, received from your \'smtpDestination\'(MTA), reached this value!'],
['VRFYFail','VRFY failures return false',20,\&checkbox,'','(.*)',undef,'VRFY failures return false when an error occurs in VRFY lookups.'],
['VRFYLog','Enable VRFY logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,''],
['DoMaxDupRcpt','Block Max Duplicate Recipients','0:disabled|1:block|2:monitor|3:score',\&listbox,3,'(.*)',undef,
  'Block remote servers that uses the same recipient address more times, than the number defined in MaxDupRcpt in the RCPT TO: command. Scoring is done with mdrValencePB . This check is skipped for outgoing, noprocessing, whitelisted and spamlovers mails. If a message has to be delayed, this check will score before the delay if set to block or score - and score and/or block on the next server request.'],
['MaxDupRcpt','Maximum Allowed Duplicate Recipient Addresses',10,\&textinput,0,'(\d+)',undef,
  'The maximum number of duplicate recipient addresses that are allowed in the sequence of the RCPT TO: commands!<br />
  The number per mail is calculated by \'number of RCPT TO: commands  -  number of unique recipient addresses\'.<br />
  For example: if one address is used three times or two addresses are used each two times, will result in the same count - 2. Or if both is the case in one mail, the count will be 4.'],
['mdrValencePB','Duplicate Recipient Score, default=10 +',10,\&textinput,10,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002840','msg002841'],
['ReplaceRecpt','Enable recipient replacement*',120,\&textinput,'','(.*)','configChangeRcptRepl','recommended if used: file:files/rcptreplrules.txt - default empty ! This enables recipient replacement. The replacement will be done before any ASSP check. For a more detailed description of the rules and options, read the file: <input type="button" value=" files/rcptreplrules.txt" onclick="return popFileEditor(\'files/rcptreplrules.txt\',8);" />  <a href=recprepl><img height=12 width=12 src="' . $wikiinfo . '" alt="Recipient Replacement Test" /> Recipient Replacement Test</a>',undef,undef,'msg001470','msg001471'],
['sendAllPostmaster','Catchall Address for Messages to Postmaster',120,\&textinput,'','(.*)',undef,'ASSP will deliver messages addressed to all postmasters of your local domains to this address. For example: postmaster@example.com'],
['sendAllPostmasterNP','Skip Spam Checks for Postmaster Catchall',0,\&checkbox,'','(.*)',undef,''],
['sendAllAbuse','Catchall Address for Messages to Abuse',120,\&textinput,'','(.*)',undef,'ASSP will deliver messages to all abuse addresses of your local domains to this address. For example: abuse@example.com'],
['sendAllAbuseNP','Skip Spam Checks for Abuse Catchall',0,\&checkbox,'','(.*)',undef,''],
['DoRFC822','Validate Recipient Address to Conform with RFC5322 ',0,\&checkbox,1,'(.*)',undef,'If activated, each local address is checked to conform with the email format defined in RFC5322 .<br />This requires an installed Email::Valid module in PERL.'],
['CatchAll','Catchall per Domain*',120,\&textinput,'','(.*)','configUpdateCA','ASSP will send to these addresses if no valid user is found in LocalAddresses_Flat or LDAP. <br />For example: catchall@domain1.com|catchall@domain2.com'],
['CatchallallISP2NULL','Move ISP Connection with wrong Recipient Address to NULL',0,\&checkbox,'','(.*)',undef,
  'If set, ASSP will move all ISP connections with wrong recipient addresses to a NULL-connection. The ISP will receive "250 OK" until the mail has passed, but the mail will not be sent to your MTA. This is done after CatchAll but before CatchAllAll is checked.'],
['CatchAllAll','Catchall for All Domains',120,\&textinput,'','(.*)',undef,'ASSP will send to this address if no valid user is found  in LocalAddresses_Flat or LDAP and no match is found in Catchall per Domain. <br />For example: catchall@example.com'],
['NullAddresses','NULL Connection Addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe','ASSP will discard a message silently when encountering such an address in "MAIL FROM:" or "RCPT TO:". Accepts specific addresses (null@example.com), user parts (nobody) or entire domains (@example.com).',undef,undef,'msg001420','msg001421'],
['InternalAddresses','Accept Mail from Local Domains only*',120,\&textinput,'','(.*)','ConfigMakeSLRe','These local addresses do not accept mail externally. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com). Wildcards are supported (fribo*@example.com).'],
['InternalAndWhiteAddresses','Accept Mail from Local Domains and Whitelisted Senders only*',120,\&textinput,'','(.*)','ConfigMakeSLRe','These local addresses accept mail only from local domains and whitelisted external senders. Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com). Wildcards are supported (fribo*@domain.com).',undef,undef,'msg009890','msg009891'],
['iaValencePB','Internal Only Address Score, default=25 +',10,\&textinput,25,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002790','msg002791'],
['erValencePB','Empty Recipients Score, default=5 +',10,\&textinput,5,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002720','msg002721'],
['SepChar','Separation Character for Subaddressing',10,\&textinput,'','(.*)',undef,'RFC 3598 describes subaddressing with a Separation Character. A star (\'*\') is not allowed as Separation Character. Everything between Separation Character and @ is ignored (including Separation Character). For Example = \'+\' will allow user+subaddress@example.com.'],
['EnableBangPath','Support Bang Path',0,\&checkbox,'','(.*)',undef,
 'If set, ASSP will support addresses like domainx!user@domainy and will convert them to user@domainx .',undef,undef,'msg001450','msg001451'],
['NoValidRecipient','No-Valid-Local-User Reply',120,\&textinput,'550 5.1.1 User unknown: EMAILADDRESS','([5|2]\d\d .*)',undef,'SMTP reply for invalid Users. Default: \'550 5.1.1 User unknown: EMAILADDRESS\' <br /> The literal EMAILADDRESS (case sensitive) is replaced by the fully qualified SMTP recipient (e.g., thisuser@example.com).<br /><hr /><div class="menuLevel1">Notes On Local Addresses</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/localaddresses.txt\',3);" />'],

[0,0,0,'heading','Validate Helo'],

['useHeloBlacklist','Use the Helo Blacklist','0:disabled|2:monitor|3:score',\&listbox,3,'(.*)',undef,
  'Use the list of blacklisted/goodlisted-helo hosts built by rebuildspamdb. Scoring is done with hlbValencePB. <input type="button" value=" Show HeloList" onclick="javascript:popFileEditor(\'spamdb.helo\',4);" />'],
['hlValencePB','Blacklisted/Good HELO Score, default=20 +',10,\&textinput,20,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002780','msg002781'],
['hlSpamLovers','SpamLover Blacklisted HELO Check*',120,\&textinput,'','(.*)','ConfigMakeSLRe',''],
['heloBlacklistIgnore','Don\'t block these HELO\'s*',120,\&textinput,'','(.*)','ConfigMakeRe',
  'HELO / EHLO greetings on this list will be excluded from the HELO checks. For example: host123.isp.com|host456.*.com'],
['ValidateHeloLog','Enable Validate Helo Logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,''],
['useHeloGoodlist','Use the Helo Goodlist','0:disabled|1:bonus|2:whitelisted',\&listbox,1,'(.*)',undef,
  'Use the list of known good helo hosts built by rebuildspamdb.<br />
  bonus - the message/IP get a bonus of the weighted negative value of hlValencePB <br />
  whitelisted - the message is processed as whitelisted<br /><br />
  The good helos and weights are stored together with the helo blacklist.',undef,undef,'msg009920','msg009921'],
['DoFakedLocalHelo','Block Forged Helos','0:disabled|1:block|2:monitor|3:score',\&listbox,1,'(.*)',undef,
  'Block remote servers that claim to come from our Local Domain/Local IPs/Local Host. Scoring with fhValencePB.'],
['fhValencePB','Forged HELO Score, default=150 +',10,\&textinput,150,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002740','msg002741'],
['DoFakedWL','Do Not Block Whitelisted',0,\&checkbox,'','(.*)',undef,
  'Disable DoFakedLocalHelo and DoIPinHelo for whitelisted addresses (not recommended).',undef,undef,'msg001540','msg001541'],
['DoFakedNP','Do Not Block Noprocessing',0,\&checkbox,'','(.*)',undef,
  'Disable DoFakedLocalHelo and DoIPinHelo for addresses identified as noprocessing (not recommended).',undef,undef,'msg001550','msg001551'],
['myServerRe','Local Domains,IPs and Hostnames*',120,\&textinput,'','(.*)','ConfigMakeRe',
  'Local Domains, IP addresses and Hostnames are often use to fake (forge) the Helo. Include all IP addresses and hostnames for your server  here, localhost is already included. Include Local Domains of your choice here, if you deactivated the automatic use of the localDomains list.  For example: 11.22.33.44|mx.example.com|example.org','Basic'],
['noHelo','Don\'t Validate HELO for these IPs*',120,\&textinput,'','(.*)','ConfigMakeIPRe',
  'Enter IP addresses that you don\'t want to be HELO validated.<br />
   For example: 145.145.145.145|146.145',undef,'7'],
['DoInvalidFormatHelo','Validate Format of HELO','0:disabled|1:block|2:monitor|3:score',\&listbox,3,'(.*)',undef,
  'If activated, the HELO is checked against the expression below. If the Regular Expression matches, the HELO is not ok. Scoring is done  with ihValencePB.'],
['ihValencePB','Invalid HELO Score, default=10 +',10,\&textinput,10,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002830','msg002831'],
['invalidHeloRe','Regular Expression to invalidate Format of HELO**',120,\&textinput,'^[^\.]+\.?$|\.user=>0.5|^\d+\.\d+\.\d+\.\d+$|^[^\.]+\.?$','(.*)','ConfigCompileRe',''],
['validHeloRe','Regular Expression to validate Format of HELO*',120,\&textinput,'^(([a-z\d][a-z\d-]*)?[a-z\d]\.)+[a-z]{2,6}$','(.*)','ConfigCompileRe',
  'Validate HELO will check incoming HELOs according to rfc1123. <br />For example: ^(([a-z\d][a-z\d-]*)?[a-z\d]\.)+[a-z]{2,6}$ .<br /><hr />
  <div class="menuLevel1">Notes On Validate Helo</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/validatehelo.txt\',3);" /> '],

[0,0,0,'heading','Validate Sender'],

['ValidateSenderLog','Enable Validate Sender Logging','0:nolog|1:standard|2:verbose|3:diagnostic',\&listbox,1,'(.*)',undef,''],
['DoBlackDomain','Do Blacklisted Addresses and Domains','0:disabled|1:block|2:monitor|3:score|4:testmode', \&listbox,1,'(.*)',undef, ' DoBlackDomain uses blackListedDomains and weightedAddresses. Scoring is done  with blValencePB.'],
['blValencePB','Blacklisted Domain Score, default=50 +',10,\&textinput,50,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002660','msg002661'],
['DoBlackDomainWL','Blacklisting Addresses/Domains will overwrite WhiteListing',0,\&checkbox,1,'(.*)',undef,
  'Do blacklisting addresses & domains in messages which are marked whitelisted by whiteRe, whiteListedDomains, whiteListedIPs or whitelistdb.',undef,undef,'msg001670','msg001671'],
['DoBlackDomainNP','Blacklisting Addresses/Domains will overwrite NoProcessing',0,\&checkbox,'','(.*)',undef,
  'Do blacklisting addresses & domains in messages marked \'noprocessing\' by npRe, npSize, noProcessingDomains, noProcessingIPs or noProcessing.'],
['blackListedDomains','Blacklisted Domains*',120,\&textinput,'file:files/blackdomains.txt','(.*)','ConfigMakeRe','Addresses  and Domains from which you always want to reject mail, they only send you spam. Note this matches the end of the address, so if you don\'t want to match subdomains then include the @. Note that example.com would also match spamexample.com but .example.com won\'t match example.com. abc@example.com will match abc@example.com but won\'t match bbc@example.com. Wildcards are supported. <a href=http://assp.cvs.sourceforge.net/viewvc/assp/asspV1/files/blackdomains.txt target=files >newest file is here</a>','','9'],
['NotGreedyBlackDomain','Only the envelope-sender is added/compared to the BlackDomainlist',0,\&checkbox,'','(.*)',undef,'If not enabled all addresses in the FROM, SENDER, REPLY-TO, ERRORS-TO, or LIST-* header fields are checked.'],
['blSpamLovers','Blacklisted Domains Spam-Lover*',120,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000540','msg000541'],
['noBlackDomain','Don\'t do Blacklisted for these Addresses and Domains* ',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 ' Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com). Wildcards are supported (fribo*@example.com).'],
['weightedAddresses','Fuzzy Addresses** ',120,\&textinput,'file:files/blackAddresses.txt','(.*)','ConfigMakeSLRe',' Accepts (blackish and <span class="positive">whitish addresses<span class="positive"> (user@example.com), user parts (user) or entire domains (@example.com). Wildcards are supported. <span class="positive"> A positive weight will make the address \'blackish\'. A negative weight will make the address into \'whitish\'.</span> For example: fribo*@example.com|<span class="positive">@*.gov=>-0.5</span>|@*.biz=>0.5 .'],

[0,0,0,'heading','Spoofing'],

['DoNoValidLocalSender','Check External Sender for Local Address  ','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,3,'(.*)',undef,
  'If activated, each external sender from a domain listed in localDomains is checked against LocalAddresses_Flat, LDAP or is verified using VRFY. An external sender is a sender from an IP not in acceptAllMail, not authenticated and not coming through the relayPort. Scoring is done  with flValencePB.'],
['flValencePB','Invalid Local Sender Score, default=20 +',10,\&textinput,20,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002770','msg002771'],
['DoNoSpoofing','Block Local Addresses from External Sender Alltogether','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,3,'(.*)',undef,
  'If activated, each external sender address with a domain listed in localDomains is regarded a spoofed address. An external sender is a sender from an IP not in acceptAllMail, not authenticated and not coming through the relayPort. flValencePB is used for scoring'],
['onlySpoofingCheckIP','Do Spoofing Check ONLY for these IP\'s*',120,\&textinput,'','(\S*)','ConfigMakeIPRe',
 'Enter IP\'s that you want to be checked for spoofing. If this is set, ONLY these IP\'s will be checked. For example:145.145.145.145|145.146.',undef,'7','msg009900','msg009901'],
['onlySpoofingCheckDomain','Do Spoofing Check ONLY for these Addresses/Domains*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com). Wildcards are supported (fribo*@example.com). If set, ONLY these addresses/domains will be checked for spoofing.',undef,undef,'msg009910','msg009911'],
['noSpoofingCheckIP','Don\'t do Spoofing Check for these IPs* ',120,\&textinput,'','(.*)','ConfigMakeIPRe',
 'Enter IP addresses and Hostnames that you don\'t want to be checked for spoofing. For example:145.145.145.145|145.146.','','7'],
['noSpoofingCheckDomain','Don\'t do Spoofing Check for these Addresses/Domains* ',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 ' Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com). Wildcards are supported (fribo*@example.com).'],
['DoRFC522Sender','Validate Sender Address to conform with RFC5322',0,\&checkbox,1,'(.*)',undef,'Sender must be a valid address to conform with RFC5322.'],
['DoReversed','Reversed Lookup','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,0,'(.*)',undef,
  'If activated, each sender IP is checked for a PTR record. Scoring is done  with ptmValencePB.'],
['DoReversedWL','Do Reversed Lookup for Whitelisted',0,\&checkbox,1,'(.*)',undef,
  'Do reversed lookup for whitelisted addresses.',undef,undef,'msg001810','msg001811'],
['DoReversedNP','Do Reversed Lookup for Noprocessing',0,\&checkbox,1,'(.*)',undef,
  'Do reversed lookup for noprocessing addresses.',undef,undef,'msg001820','msg001821'],
['ptmValencePB','Missing PTR Record Score, default=10 +',10,\&textinput,10,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg003000','msg003001'],
['ptrSpamLovers','Invalid/Missing PTR Spam-Lover*',120,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000660','msg000661'],
['ptiValencePB','Invalid PTR Record Score, default=15 +',10,\&textinput,15,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg003010','msg003011'],
['DoInvalidPTR','Reversed Lookup FQDN Validation',0,\&checkbox,'','(.*)',undef,
  'If activated - and Reversed Lookup is activated -, the PTR-FQDN record is checked against invalidPTRRe & validPTRRe. Scoring is done  with ptiValencePB '],
['invalidPTRRe','Regular Expression to Invalidate Format of PTR**',120,\&textinput,'file:files/invalidptr.txt','(.*)','ConfigCompileRe',
  'Validate Format PTR will check PTR records for this. <br />
  <a href=http://assp.cvs.sourceforge.net/viewvc/assp/asspV1/files/invalidptr.txt target=files ><span class="negative">newest example file invalidptr.txt is here</a>'],
['validPTRRe','Regular Expression to Validate Format of PTR*',120,\&textinput,'file:files/validptr.txt','(.*)','ConfigCompileRe',
  'Validate Format PTR will check PTR records for this. If found, the PTR will be considered valid<br />
  <a href=http://assp.cvs.sourceforge.net/viewvc/assp/asspV1/files/validptr.txt target=files ><span class="negative">newest example file validptr.txt is here</a>'],
['PTRCacheInterval','Reversed Lookup Cache Refresh Interval',10,\&textinput,7,'([\d\.]+)','configUpdatePTRCR',
  'IPs in cache will be removed after this interval in days. 0 will disable the cache.  <input type="button" value=" show cache" onclick="javascript:popFileEditor(\'pb/pbdb.ptr.db\',5);" />'],
['DoDomainCheck','Validate MX or A Record','0:disabled|1:block|2:monitor|3:score',\&listbox,3,'(.*)',undef,
  'If activated, the sender address and each address found in the following header lines (ReturnReceipt:, Return-Receipt-To:, Disposition-Notification-To:, Return-Path:, Reply-To:, Sender:, Errors-To:, List-...:) is checked for a valid MX and/or A record. ',undef,undef,'msg001870','msg001871'],
['mxaSpamLovers','Missing MX Spam-Lover*',120,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000650','msg000651'],
['mxValencePB','Missing MX Score, default=10 +',10,\&textinput,10,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002920','msg002921'],
['mxaValencePB','Missing MX &amp; A Record Score, default=15 +',10,\&textinput,15,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002930','msg002931'],
['MXACacheInterval','Validate Domain MX Cache Refresh Interval',10,\&textinput,7,'(\d+\.?\d*|)','configUpdateMXACR',
  'IP\'s in cache will be removed after this interval in days. 0 will disable the cache.<input type="button" value=" Show MX Cache" onclick="javascript:popFileEditor(\'pb/pbdb.mxa.db\',5);" />',undef,undef,'msg001880','msg001881'],
['removeDispositionNotification','Remove Disposition Notification Headers',0,\&checkbox,1,'(.*)',undef,
  'If set, all headers "ReturnReceipt: , Return-Receipt-To: and Disposition-Notification-To:" will be removed (except whitelisted and noprocessing mails). Select this to prevent unwanted whitelisting of spammers . <br /><hr />
  <div class="menuLevel1">Notes On Validate Sender</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/validatesender.txt\',3);" />'],

[0,0,0,'heading','IP Blocking'],

['noBlockingIPs','Do not block Connections from these IP\'s*',120,\&textinput,'','(\S*)','ConfigMakeIPRe','Manually maintained list of IP\'s which should not be blocked.  For example: 145.145.145.145|145.146.<br />
  To define IP\'s only for specific email addresses or domains (recipients) you must use the file:... option<br />
  An entry (line) may look as follows:<br />
  145.146.0.0/16=>*@local.domain|user@mydomain|user2@*.mydomain # comment<br /><br />
  It is possible to define a predefined group on any or both sides of the \'=>\' separator, like:<br />
  [ipgroup]=>[usergroup]|user@mydomain<br /><br />
  NOTICE: the following combination of two entries, will lead in to a user/domain based matching - the global entry will be ignored!<br />
  145.146.0.0/16 # comment<br />
  145.146.0.0/16=>*@local.domain|user@mydomain|user2@*.mydomain # comment<br />
  If multiple user/domain based entries are defined for the same IP, only the last one will be used!',undef,'7','msg002010','msg002011'],
['DoDropList','Drop Connections from these IPs','0:disabled|1:block|2:monitor|3:score',\&listbox,1,'(.*)',undef,
 'If activated, the IP is checked against the droplist . The droplist is downloaded if a new one is available and contains the Spamhaus DROP List. See "http://www.spamhaus.org/drop/drop.lasso".'],
['dropValencePB','Match in Droplist Score, default=40 +',10,\&textinput,40,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring'],
['DoDenySMTP','Do Deny Connections from these IPs','0:disabled|1:block|2:monitor|3:score',\&listbox,1,'(.*)',undef,
 'If activated, the IP is checked against denySMTPConnectionsFrom.'],
['denySMTPConnectionsFrom','Deny Connections from these IPs*',120,\&textinput,'','(.*)','ConfigMakeIPRe','Manually maintained list of IP addresses and Hostnames which should be blocked. IP addresses and Hostnames in noPB, acceptAllMail, ispip, whiteListedIPs will pass. For example: server.example.com|145.145.145.145|145.146.','','7'],
['DoDenySMTPstrict','Do Deny Connections from these IP addresses and Hostnames Strictly','0:disabled|1:block|2:monitor',\&listbox,1,'(.*)',undef,
 'If activated, the IP is checked against denySMTPConnectionsFromAlways.  '],
['denySMTPConnectionsFromAlways','Deny Connections from these IP\'s Strictly*',120,\&textinput,'file:files/denyalways.txt','(\S*)','ConfigMakeIPRe',
 'Manually maintained list of IP\'s which should <b>strictly</b> be blocked after address verification and before body and header is downloaded. Contrary to <i>denySMTPConnectionsFrom</i> IP\'s in noDelay, acceptAllMail, ispip, whiteListedIPs, noProcessingIPs, whitebox will <b>not</b> pass if listed here.',undef,'7','msg002030','msg002031'],
['denySMTPLog','Enables Logging for \'Deny SMTP Connections From\'','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,''],
['denySMTPstrictEarly','Do Strictly Deny Connections Early',0,\&checkbox,'','(.*)',undef,
  'IP\'s in <b>denySMTPConnectionsFromAlways</b> will be denied right away.',undef,undef,'msg002050','msg002051'],
['DenyError','Deny Error',120,\&textinput,'554 5.7.2 Service denied, closing transmission channel','([25]\d\d .*)',undef,'SMTP error message to reject connections.For example: 554 5.7.2 Service denied, closing transmission channel. '],
['DoFrequencyIP','Check Frequency - Maximum Connections Per IP','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,1,'(.*)',undef,
 'Scoring is done  with ifValencePB.'],
['ifValencePB','IP Frequency Score, default=150 +',10,\&textinput,150,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002810','msg002811'],
['maxSMTPipConnects','Maximum Frequency of Connections Per IP ',10,\&textinput,10,'(\d?\d?\d?)',undef,
 'The maximum number of SMTP connections an IP Address can make during the maxSMTPipDuration (IP Address Frequency Duration). If a server makes more than this many connections to ASSP within the maxSMTPipDuration (IP Address Frequency Duration) it will be banned from future connections until the maxSMTPipExpiration (IP Address Frequency Expiration) is reached. This can be used to prevent server overloading and DoS attacks. 10 connections are typically enough. If left blank or 0, there is no limit imposed by ASSP. IP addresses in noPB, noDelay, acceptAllMail, ispip, whiteListedIPs, noProcessingIPs, PB-whitebox are excluded from SMTP session limiting, whitelisted and noprocessing addresses are honored. '],
['maxSMTPipDuration','Maximum Frequency of Connections Per IP Duration',10,\&textinput,90,'(\d?\d?\d?\d?)',undef,
 'The window (in seconds) during which the maxSMTPipConnects (IP Frequency) (see above for more details) will be scrutinized for each IP.'],
['maxSMTPipExpiration','Expiration of Maximum Frequency',10,\&textinput,3600,'(\d?\d?\d?\d?)',undef,
 'The number of seconds that must pass before an IP address blocked by the maxSMTPipConnects (IP Address Frequency) setting is allowed to connect again.'],
['DoDomainIP','Check Number of IP\'s Per Domain','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,0,'(\d*)',undef,
 'This check is skipped if the IP and domain have passed the SPF-check. If ValidateSPF is enabled and an IP/Domain reaches the maxSMTPdomainIP limit, the MaintThread starts a background SPF check to prevent blocking good mails in future.',undef,undef,'msg002100','msg002101'],
['idValencePB','Domain Changing IP Frequency Score, default=150 +',10,\&textinput,150,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002800','msg002801'],
['maxSMTPdomainIP','Limit Number of IP\'s  Per Domain',10,\&textinput,10,'(\d?\d?\d?)',undef,
 'The number of IP(subnet) switches a domain may have during the (maxSMTPdomainIPExpiration) Limit Different IP\'s Per Domain Expiration. If a domain switches more often than this it will be banned from future connections until the Expiration is reached. This can be used to prevent server overloading and DoS attacks. 10 connections are typically enough. If left blank or 0, there is no limit imposed by ASSP. IP\'s in noPB, noDelay, acceptAllMail, ispip, whiteListedIPs, noProcessingIPs, PB-whitebox are excluded, whitelisted and noprocessing addresses are honored.',undef,undef,'msg002110','msg002111'],
['maxSMTPdomainIPExpiration','Expiration of Limit Number',10,\&textinput,7200,'(\d?\d?\d?\d?\d?)',undef,
  'The number of seconds that must pass before a domain blocked by the (maxSMTPdomainIP) Limit Subnet IP\'s Per Domain setting (see above for more details) is allowed to connect again. The default is 7200 (seconds).',undef,undef,'msg002120','msg002121'],
['maxSMTPdomainIPWL','Do Not Limit Different IP\'s For These Domains*',120,\&textinput,'gmx.de|t-online.de|yahoo.com|hotmail.com|gmail.com','(.*)','ConfigMakeRe',
  'This prevents specific domains from limiting. For example: yahoo.com|hotmail.*.com|gmail.com<br /><hr />
  <div class="menuLevel1">Notes On IP Blocking</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/ipblocking.txt\',3);" />',undef,undef,'msg002130','msg002131'],

[0,0,0,'heading','SenderBase '],

['SenderBaseLog','Enable SenderBase Logging','0:nolog|1:standard|2:verbose|3:diagnostic',\&listbox,1,'(.*)',undef,''],
['AddSenderBaseHeader','Add SenderBase Header',0,\&checkbox,1,'(.*)',undef,'',undef],
['DoOrgWhiting','Do Organization Scoring <a href="http://www.senderbase.org/" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="SenderBase" /></a>','0:disabled|1:whiting|2:monitor|3:score',\&listbox,1,'(.*)',undef,
   'If activated, each sending IP address has its assigned organization / domain
looked up and scored with sworgValencePB.'],
['whiteSenderBase','White Organizations and Domains in SenderBase**  ',120,\&textinput,'file:files/whitesenderbase.txt','(.*)','ConfigCompileRe','If the organization or domain  in the <a href="http://www.senderbase.org/" rel="external">SenderBase</a> IP description matches this Perl regular expression the  messagescore will be decreased by sworgValencePB. Example file:<a href=http://assp.cvs.sourceforge.net/viewvc/assp/asspV1/files/whitesenderbase.txt target=files ><span class="positive">whitesenderbase.txt</a>'],
['sbnValencePB','No Organization and No CountryCode Score, default=10 +',10,\&textinput,10,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'For Message/IP scoring in DoOrgBlocking/DoCountryBlocking',undef,undef,'msg003070','msg003071'],
['sworgValencePB','White Organizations Score, default=-50',10,\&textinput,-50,'(-\d*)','ConfigChangeValencePB', '<span class="positive"> Bonus for Message/IP scoring in DoOrgWhiting</span>',undef,undef,'msg003080','msg003081'],
['DoOrgBlocking','Do Organization Blocking','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,3,'(.*)',undef,
   'If activated, each sending IP address has its assigned organization
looked up und compared to blackSenderBase. Scoring is done with sborgValencePB'],
['sborgValencePB','Blocked Organizations Score, default=25 +',10,\&textinput,25,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'For Message/IP scoring  in PenaltyBox ( DoPenalty )',undef,undef,'msg003130','msg003131'],
['blackSenderBase','Blacklisted Organizations in SenderBase** ',120,\&textinput,'file:files/blacksenderbase.txt','(.*)','ConfigCompileRe','If the organization or domain is in the available <a href="http://www.senderbase.org/" rel="external">SenderBase</a> IP description and matches this regular expression the message will be handled according to DoOrgBlocking. The total messagescore will be increased by sborgValencePB. Example file:<a href=http://assp.cvs.sourceforge.net/viewvc/assp/asspV1/files/blacksenderbase.txt target=files ><span class="negative">blacksenderbase.txt</a><br />'],
['DoCountryBlocking','Do Country Blocking','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,3,'(.*)',undef,
   'If activated, each sending IP address has it\'s assigned country
looked up and compared to CountryCodeBlockedRe.  Scoring is done using bccValencePB.'],
['bccValencePB','Blocked Country Code Score, default=50 +',10,\&textinput,50,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'For Message/IP scoring  in PenaltyBox ( DoPenalty )',undef,undef,'msg003100','msg003101'],
['CountryCodeBlockedRe','Blocked Countries**',120,\&textinput,'BG|CL|DO|IE|IN|IQ|IR|KR|LT|PL|RO|RU|TH|TR|TW|UZ|VN','(.*)','ConfigCompileRe',
  'Messages from IP addresses based in these countries will be blocked if DoCountryBlocking is set to block. For example: CL|DO|IE|IN|IQ|IR|KR|LT|PL|RO|RU|TH|TR|TW|UZ|VN. "all" will block all foreign countrycodes which are not in CountryCodeRe or \'Ignore Country Codes\'. See: <a href="http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm" rel="external" target=wiki>English country names and code elements</a>. '],
['DoSuspiciousCountry','Do Suspicious Country Scoring','0:disabled|2:monitor|3:score',\&listbox,3,'(.*)',undef,
   'If activated, each sending IP address has it\'s assigned country
looked up and compared to CountryCodeRe.'],
['CountryCodeRe','Suspicious Countries**',120,\&textinput,'BR|CO|CN|CZ|GR|HU|ID|JP|MD|NG|PE|PH|PT|SA|UA','(.*)','ConfigCompileRe',
  'Messages from IP addresses based in these countries will increase the MessageScore. For example: CN|NG|UA|GR|HU|SA=>0.5. A positive or negative weight is possible, Scoring is done using sbsccValencePB. See: <a href="http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm" rel="external" target=wiki>English country names and code elements</a>'],
['sbsccValencePB','Suspicious Country Code Score, default=25',10,\&textinput,25,'(\d+)','ConfigChangeValencePB', 'Message scoring',undef,undef,'msg003090','msg003091'],
['NoCountryCodeRe','Ignore Country Codes from these Countries*',120,\&textinput,'US|CA|DE','(.*)','ConfigCompileRe',
  'Messages from IP addresses based in these countries will will be ignored in CountryCode checks.'],
['MyCountryCodeRe','Home Countries**',120,\&textinput,'','(.*)','ConfigCompileRe',
  'Put here your own country code(s) (for example: US). Messages from IP addresses based in these countries will decrease the total MessageScore using sbhccValencePB, messages from other countries will increase the total MessageScore using sbfccValencePB if ScoreForeignCountries is set. '],
['sbhccValencePB','<span class="positive">Home Country Code Score, default=-10</span> +',10,\&textinput,-10,'(\s*-?[\d]+\s*([\|,]\s*-?[\d]+\s*){0,1})','ConfigChangeValencePB', '<span class="positive"> Bonus for Message/IP Scoring  in PenaltyBox ( DoPenalty )</span>',undef,undef,'msg003120','msg003121'],
['ScoreForeignCountries','Score Foreign Countries',0,\&checkbox,1,'(.*)',undef,
  'Messages from countries not in MyCountryCodeRe, NoCountryCodeRe and CountryCodeRe will increase the total messageScore using sbfccValencePB.'],
['sbfccValencePB','Foreign Country Code Score, default=10 +',10,\&textinput,10,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'message scoring  in PenaltyBox ( DoPenaltyMessage )',undef,undef,'msg003110','msg003111'],
['SBCacheExp','Country Cache Refresh Interval',10,\&textinput,14,'([\d\.]+)','configUpdateSBCR',
  'IPs in cache will be removed after this interval in days. 0 will disable the cache.  <input type="button" value=" show cache" onclick="javascript:popFileEditor(\'pb/pbdb.sb.db\',5);" /><br /><hr />
  <div class="menuLevel1">Country Codes</div>
  See: <a href="http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm" rel="external" target=wiki>English country names and code elements</a>.'],
['sbSpamLovers','SpamLover Country Check*',120,\&textinput,'','(.*)','ConfigMakeSLRe',''],

[0,0,0,'heading','Scoring '],

['DoPenaltyMessage','MessageScoring','0:disabled|1:block|2:monitor|4:testmode',\&listbox,1,'(.*)',undef,'If this feature is selected, the total score for all checks during a message is used to determine if the email should be considered Spam. If the combined score is greater than MessageScoringLowerLimit (MessageLimit for WarningTag) and less than or equal MessageScoringUpperLimit (MessageLimit for Blocking) the message will not be blocked but get the MessageScoringWarningTag. If the combined score is greater than the MessageScoringUpperLimit and blocking is selected the message will be blocked. If testmode is selected the message will not be blocked but tagged with spamSubject.','Basic'],
['AddScoringHeader','Add Message Scoring Header',0,\&checkbox,1,'(.*)',undef,'Adds a line to the email header "X-Assp-Score: "'],
['MessageScoringWL','MessageScoring on Whitelisted Senders',0,\&checkbox,'','(.*)',undef,''],
['MessageLog','Enable Scoring logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,''],
['msSpamLovers','MessageScoring Spam-Lover *',120,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000670','msg000671'],
['spamFriends','Spam Friends **',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'A list of local user addresses that when matched will reduce the messagescore with friendsValencePB. This will make the scoring filter more softly. if you use negative weights here, the messagescore will be increased and the scoring filter will be more sharply. Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com). Wildcards are supported (fribo*@domain.com). A second parameter separated by "=>" specifies the weight (multiplier) of friendsValencePB (default = -10). For example: @example.com=>0.5 will add -5 to the score of mails from @example.com. '],
['friendsValencePB','<span class="positive">Spam Friends Score</span>',10,\&textinput,-10,'(-?.*)',undef,'<span class="positive">Used to calculate the score for recipients in spamFriends.</span>'],
['MsgScoreOnEnd','Message Scoring on End',0,\&checkbox,'','(.*)',undef,'ASSP will wait until all configured possible checks are finished. Use this, to force calculating a complete message score over all values, including all bonus values.',undef,undef,'msg002290','msg002291'],
['MessageScoringLowerLimit','MessageScoring Lower Limit ',10,\&textinput,35,'(.*)',undef,'MessageScoring will tag messages with totalscore between  MessageScoringLowerLimit and MessageScoringUpperLimit. Empty or zero will disable the feature '],
['MessageScoringWarningTag','Warning Tag',120,\&textinput,'[Over Lower Limit]','(.*)',undef,'Used instead of spamSubject if totalscore is between  MessageScoringLowerLimit and MessageScoringUpperLimit.'],
['MessageScoringUpperLimit','MessageScoring Limit',10,\&textinput,50,'(.*)',undef,'If MessageScoring is done  to block, it will block messages whose totalscore is  equal or higher than this threshold.'],
['DoPenalty','IP Scoring','0:disabled|2:enabled',\&listbox,2,'(.*)',undef,'The PenaltyBox scores IP addresses based on some events and stores them into a BlackBox. The totalscore is used by DoPenaltyMessage for assigning a reputation. The total is also used by DelayIP.
The WhiteBox stores IP addresses  which should not be put into the BlackBox. The WhiteBox is always enabled. If an address is in the whitelist or whitedomain, the IP goes into the WhiteBox too. The WhiteBox is one of the sources  Delaying/Greylisting uses to determine when delaying should not be done. <br />Entries in noPB (Don\'t do penalties for these IP addresses ) or ispip (ISP/Secondary MX Servers) will prevent from penalties. Select \'enabled\' to fill WhiteBox and BlackBox. This  enables DoPenaltyMessage and DelayIP.'],
['brValencePB','Bad Reputation Score, default=25 +',10,\&textinput,25,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Score for bad reputation in Penalty Box',undef,undef,'',''],
['vbrValencePB','Very Bad Reputation Score, default=50 +',10,\&textinput,50,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Score for very bad reputation in Penalty Box (2 * Bad Reputation)',undef,undef,'',''],
['PenaltyLog','Enable PenaltyBox logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,''],
['PenaltyLimit','Penalty Limit',10,\&textinput,50,'(.*)',undef,
  'PB will block messages from IP addresses whose totalscore exceeds this threshold during PBlackExpiration. <br />For example: 50'],
['BlackExpiration','Expiration Time for BlackBox Entries',10,\&textinput,180,'(.*)','updateBlackExpiration',
  'Penalties with a score lower than PenaltyLimit  will expire after this number of minutes. <input type="button" value=" Show BlackBox" onclick="javascript:popFileEditor(\'pb/pbdb.black.db\',4);" />'],
['noPB','Don\'t add these IP addresses and Hostnames to BlackBox* ',120,\&textinput,'','(.*)','ConfigMakeIPRe',
 'Enter IP addresses that you don\'t want to be in BlackBox. For example:145.145.145.145|145.146.','','7'],
['noPBwhite','Don\'t add these IP addresses to WhiteBox*',120,\&textinput,'file:files/nopbwhite.txt','(.*)','ConfigMakeIPRe',
 'Enter IP addresses and Hostnames that you don\'t want to be in WhiteBox. ','','7'],
['WhiteExpiration','Expiration Time for WhiteBox Entries',10,\&textinput,30,'(\d?\d?\d?\d?)',undef,,
  'The WhiteBox is always activated. IP addresses in WhiteBox will allow content-related checks like Bayesian, URIBL, Bomb but skip IP-related checks like RBL. WhiteBox entries will expire after this specified number of days. For example: 30<input type="button" value="Show White Box" onclick="javascript:popFileEditor(\'pb/pbdb.white.db\',4);" />'],
['PenaltyUseNetblocks','Use IP Netblocks',0,\&checkbox,1,'(.*)',undef,
  'Perform the IP address checks of the sending host based on the /24 subnet rather than on the specific IP.<hr /><div class="menuLevel1">Notes On Message Scoring</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/messagescoring.txt\',3);" />'],

[0,0,0,'heading','Delaying/Greylisting  <a href="http://apps.sourceforge.net/mediawiki/assp/index.php?title=Delaying/Greylisting" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="Delaying" /></a>'],

['DelayIP','IP Delaying',10,\&textinput,50,'(\d*)',undef,
  'Enable delaying for IPs in black penaltybox with totalscore above this value. A value of zero disables this feature.',undef,undef,'msg009320','msg009321'],
['DelayIPTime','IP Delaying Embargo Time',10,\&textinput,5,'(\d*)',undef,
  'Enter the number of minutes during delivery is refused with a temporary failure (451 SMTP error code). Default is 5 minutes.',undef,undef,'msg009330','msg009331'],
['EnableDelaying','Enable Greylisting',0,\&checkbox,1,'(.*)',undef,
  'Enable Greylisting as described at <a href="http://projects.puremagic.com/greylisting/whitepaper.html?view=markup" rel="external">Greylisting-whitepaper</a>.<br />
   ASSP will "temporarily reject" any email from a sender it does not recognize. If the mail is legitimate the originating server will, after a delay, try again and, if sufficient time has elapsed, the email will be accepted. If the mail is from a spam sender, sending to many thousands of email addresses, it will probably not be retried.
   Greylisting involves sending a temporary 451 SMTP error code to the sending server when a message is received, along with sending this error code ASSP creates a Triplet and stores this. On the second delivery attempt if the Embargo Time set by the ASSP admin for the Triplet has been surpassed the message will be accepted and a Tuplet will be created and not delayed again for an Expiry Time set by the ASSP admin.'],
['DelayLog','Enable Greylisting/Delaying logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,''],
['delaySpamLovers','SpamLover Greylisting/Delaying*',120,\&textinput,'','(.*)','ConfigMakeSLRe','These Recipients will not be delayed/greylisted.'],
['DelayWL','Whitelisted Greylisting',0,\&checkbox,'','(.*)',undef,
  'Enable Greylisting for whitelisted mails. This also enables Geylisting for SPF-Cache-OK listed IP\'s and mails from white organizations, which are normaly not greylisted.',undef,undef,'msg003290','msg003291'],
['DelayNP','NoProcessing Greylisting',0,\&checkbox,'','(.*)',undef,
  'Enable Greylisting for noprocessing mails.',undef,undef,'msg003300','msg003301'],
['DelaySL','Spam-Lovers Greylisting',0,\&checkbox,'','(.*)',undef,
  'Enable Greylisting for Spam-Lovers.',undef,undef,'msg003310','msg003311'],
['DelayAddHeader','Add X-Assp-Delay Header',0,\&checkbox,1,'(.*)',undef,
  'Add X-Assp-Delay header to all emails.'],
['DelayEmbargoTime','Embargo Time',10,\&textinput,5,'(.*)',undef,
  'Enter the number of minutes for which delivery, related with new \'triplet\' (IP address of the sending host + mail from + rcpt to), is refused with a temporary failure.'],
['DelayWaitTime','Wait Time',10,\&textinput,28,'(.*)',undef,
  'Enter the number of hours to wait for delivery attempts related with recognised \'triplet\'; delivery is accepted <br />
  immediately and the \'tuplet\' (IP address of the sending host + sender\'s domain) is whitelisted.'],
['DelayExpiryTime','Expiry Time',10,\&textinput,36,'(\d+)',undef,
  'Enter the number of days for which a whitelisted \'tuplet\' is considered valid.'],
['DelayUseNetblocks','Use IP Netblocks',0,\&checkbox,1,'(.*)',undef,
  'Perform the IP address checks of the sending host based on the /24 subnet it is at rather than the specific IP. <br />
  This feature may be useful for legitimate mail systems that shuffle messages among SMTP clients between retransmissions.'],
['DelayNormalizeVERPs','Normalize VERP Addresses',0,\&checkbox,1,'(.*)',undef,
  'Some mailing lists (such as Ezmlm) try to track bounces to individual mails, rather than just individual recipients, which creates a variation on the VERP method where each email has its own unique envelope sender. Since the automatic whitelisting  that is built into Greylisting depends on the envelope addresses for subsequent emails being the same, the greylisting filter will attempt to normalize the unique sender addresses, when this option is checked.'],
['DelayMD5','Use MD5 for DelayDB',0,\&checkbox,1,'(.*)',undef,
  'Message-Digest algorithm 5 is a cryptographic hash function and adds some level of security to the delay database. Must be set to off if you want to list the database with DelayShowDB/DelayShowDBwhite. This requires an installed <a href="http://search.cpan.org/search?query=Digest::MD5" rel="external">Digest::MD5</a> module in PERL.',undef,undef,'msg003390','msg003391'],
['DelayShowDB','Show Delay/Greylisting Database',120,\&textinput,'file:delaydb','(\S*)',undef,'The directory/file with the delay local file. Obsolete if you use \'mysql\' in delaydb.','','8'],
['DelayShowDBwhite','Show Delay/Greylisting Save Database',120,\&textinput,'file:delaydb.white','(\S*)',undef,'The directory/file with the white-delay local file. Obsolete if you use \'mysql\' in delaydb.','','8'],
['DelayExpireOnSpam','Expire Spamming Whitelisted Tuplets',0,\&checkbox,1,'(.*)',undef,
  'If a whitelisted \'tuplet\' is ever associated with spam, viruses, failed rbl, spf etc, it is removed from whitelisted tuplets database. <br />
  This renews the temporary embargo for subsequent mail involving the tuplet.'],
['CleanDelayDBInterval','Clean Up Delaying Database',10,\&textinput,10800,'(\d+)',undef,
  'Delete outdated entries from triplets and whitelisted tuplets databases every this many seconds.<br />
  Note: the current timeout must expire before the new setting is loaded, or you can restart.
  Defaults to 3 hours.'],
['noDelay','Don\'t Delay these IPs*',120,\&textinput,'file:files/nodelay.txt','(.*)','ConfigMakeIPRe',
  'Enter IP addresses that you don\'t want to be delayed, separated by pipes (|). There are misbehaving MTAs that will not be able to get a legitimate email through a Greylisting server because they do not try again later.<br /><a href=http://assp.cvs.sourceforge.net/viewvc/assp/asspV1/files/nodelay.txt target=files ><span class="positive">newest example file is here</a>','','7'],
['noDelayAddresses','Do not Delay these Addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe','Enter sender/recipient email addresses that you don\'t want to be delayed, separated by pipes (|). You can list specific addresses (user@anydomain.com), addresses at any domain (user), or entire domains (@anydomain.com).  Wildcards are supported (fribo*@domain.com).<br />For example: fribo@anydomain.com|jhanna|@sillyguys.org or place them in a plain ASCII file one address per line: \'file:files/nodelayuser.txt\'.'],
['DelayError','Reply Code to Refuse Delayed Messages',120,\&textinput,'451 4.7.1 Please try again later','(45\d .*)',undef,
  'SMTP reply code to refuse delayed messages. Default: 451 4.7.1 Please try again later
  <br /><hr />
  <div class="menuLevel1">Notes On Delaying</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/delaying.txt\',3);" />'],

[0,0,0,'heading','SPF/SRS '],

['ValidateSPF','Enable SPF Validation','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,3,'(.*)',undef,
  'Enable Sender Policy Framework Validation as described at <a href="http://www.openspf.org/" rel="external">openspf</a>.<br />
  This requires an installed Mail::SPF module in PERL.  Scoring is done  with spfValencePB.'],
['SPFLog','Enable SPF logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,''],
['spfSpamLovers','SpamLover SPF Check*',120,\&textinput,'','(.*)','ConfigMakeSLRe',''],
['spfpValencePB','SPF Pass Score, default=-5 +',10,\&textinput,-5,'(\s*-?[\d]+\s*([\|,]\s*-?[\d]+\s*){0,1})','ConfigChangeValencePB','<span class="positive"> Bonus for Message/IP scoring with SPF',undef,undef,'msg003140','msg003141'],
['spfnValencePB','SPF Neutral, default=5 +',10,\&textinput,5,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB','Message/IP scoring',undef,undef,'msg003150','msg003151'],
['spfeValencePB','SPF Error Score, default=10 +',10,\&textinput,10,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB','Message/IP scoring',undef,undef,'msg003190','msg003191'],
['spfsValencePB','SPF Softfailed Score, default=20 +',10,\&textinput,20,'(?o-xm:\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB','Message/IP scoring',undef,undef,'msg003160','msg003161'],
['spfValencePB','SPF Failed Score, default=30 +',10,\&textinput,30,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB','Message/IP scoring',undef,undef,'msg003200','msg003201'],
['SPFWL','Whitelisted SPF Validation',0,\&checkbox,'','(.*)',undef,
  'Enable Sender Policy Framework Validation for whitelisted users also.',undef,undef,'msg003480','msg003481'],
['SPFNP','noProcessing SPF Validation',0,\&checkbox,'','(.*)',undef,
  'Enable Sender Policy Framework Validation for nonprocessed messages also.',undef,undef,'msg009560','msg009561'],
['SPFLocal','Local and outgoing mail SPF Validation',0,\&checkbox,'','(.*)',undef,
  'Enable Sender Policy Framework Validation for local and outgoing messages also. Don\'t forget to configure your DNS-server for SPF if you enable this option.',undef,undef,'msg003490','msg003491'],
['failstrictLOCAL','Strict SPF Failing for Local Domains*',0,\&checkbox,1,'(.*)',undef,
  'Softfail/Neutral/None messages with local domain in sending address will be Failed.'],
['blockstrictLOCAL','Strict SPF Blocking for Local Domains*',0,\&checkbox,1,'(.*)',undef,
  'Failed messages with local domain in sending address will be blocked .'],
['AddSPFHeader','Add Received-SPF Header',0,\&checkbox,1,'(.*)',undef,
  'Add Received-SPF header.'],
['noSPFRe','Regular Expression to Skip SPF Processing*',120,\&textinput,'','(.*)','ConfigCompileRe',
 'Put anything here to identify these messages in header'],
['SPFsoftfail','Fail SPF Softfail Validations',0,\&checkbox,1,'(.*)',undef,
  'SPF \'softfail\' status responses will be set to \'fail\' if strictSPFRe is matched.<br />
  The possible results of a query are:
<br />pass:The client IP address is an authorized mailer for the sender. The mail should be accepted subject to local policy regarding the sender.
<br />fail:The client IP address is not an authorized mailer, and the sender wants you to reject the transaction for fear of forgery.
<br />softfail:The client IP address is not an authorized mailer, but the sender prefers that you accept the transaction because it isn\'t absolutely sure all its users are mailing through approved servers. The softfail status is often used during initial deployment of SPF records by a domain.
<br />neutral:The sender makes no assertion about the status of the client IP.
<br />none:There is no SPF record for this domain.
<br />permerror &amp; temperror:The DNS lookup encountered an error during processing.
<br />unknown:The domain has a configuration error in the published data or defines a mechanism that this library does not understand.',undef,undef,'msg003600','msg003601'],
['SPFneutral','Fail SPF Neutral Validations',0,\&checkbox,'','(.*)',undef,
  'Intentionally fail SPF neutral status responses',undef,undef,'msg003610','msg003611'],
['SPFqueryerror','Fail SPF Error Responses',0,\&checkbox,'','(.*)',undef,
  'Intentionally fail SPF \'error\' status responses',undef,undef,'msg003620','msg003621'],
['SPFnone','Fail SPF None Responses',0,\&checkbox,'','(.*)',undef,
  'Intentionally fail SPF \'none\' and \'unknown\' status responses',undef,undef,'msg003630','msg003631'],
['SPFunknown','Fail SPF Unknown  Responses',0,\&checkbox,'','(.*)',undef,
  'Intentionally fail SPF \'unknown\'  status responses',undef,undef,'msg003640','msg003641'],
['strictSPFRe','Strict SPF Processing Regex*',120,\&textinput,'file:files/strictspf.txt','(.*)','ConfigCompileRe',
 'SPF \'softfail\' status responses will be set to \'fail\' for these sending addresses. Put anything here to identify the addresses. For example: \'@aol.com|@gmail.com|@msn.com|@live.com|@ebay.com|@ebay.nl|@bbt.com|@paypal.com|@einsundeins.de|@microsoft.com\''],
['blockstrictSPFRe','Strict SPF Blocking Regex*',120,\&textinput,'@ebay.com|@paypal.com|@facebook.com|@ups.com','(.*)','ConfigCompileRe',
 'All failed messages will be blocked for these sending addresses. Put anything here to identify the addresses. For example: \'@ebay.com|@paypal.com|@facebook.com\''],
['DoSPFinHeader','Do SPF check on header \'from:\'',0,\&checkbox,'','(.*)',undef,
  'Do an additional SPF check on the header from: address if it is in blockstrictSPFRe *** breakes RFC rules ***',undef,undef,'msg003610','msg003611'],
['SPFCacheInterval','SPF Cache Refresh Interval',10,\&textinput,3,'([\d\.]+)','configUpdateSPFCR',
  'SPF records in cache will be removed after this interval in days. 0 will disable the cache.  <input type="button" value=" show cache" onclick="javascript:popFileEditor(\'pb/pbdb.spf.db\',6);" />'],
['DebugSPF','Enable SPF Debug output to ASSP Logfile',0,\&checkbox,'','(.*)',undef,
 'Enables verbose debugging of SPF queries within the Mail::SPF::Query module.
 <br /><hr />
 <div class="menuLevel1">Notes On SPF</div>
 <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/spf.txt\',3);" /> '],
['EnableSRS','Enable Sender Rewriting Scheme',0,\&checkbox,'','(.*)','updateSRS',
  'Enable Sender Rewriting Scheme as described at <a href="http://www.openspf.org/SRS" rel="external">www.openspf.org/SRS</a>.<br />
  This requires an installed Mail::SRS module in PERL.<br />
  You should use SRS if your message handling system forwards email for domains with published spf records.<br />
  Note that you have to setup the outgoing path (Relay Host and Port) to let ASSP see and rewrite your outgoing traffic.'],
['srsSpamLovers','SpamLover SRS Signed Bounces Check*',120,\&textinput,'','(.*)','ConfigMakeSLRe',''],
['SRSAliasDomain','Alias Domain',120,\&textinput,'example.com','(.*)','updateSRSAD',
  'SPF requires the SMTP client IP to match the envelope sender (return-path). When a message is forwarded through<br />
  an intermediate server, that intermediate server may need to rewrite the return-path to remain SPF compliant.<br />
  For example: example.com'],
['SRSSecretKey','Secret Key',120,\&textinput,'','(.*)','updateSRSSK',
  'A key for the cryptographic algorithms -- Must be at least 5 characters long.'],
['SRSTimestampMaxAge','Maximum Timestamp Age',10,\&textinput,21,'(\d+)',undef,
  'Enter the maximum number of days for which a timestamp is considered valid.'],
['SRSHashLength','Hash Length',10,\&textinput,4,'(\d+)',undef,
  'The number of bytes of base64 encoded data to use for the cryptographic hash.<br />
  More is better, but makes for longer addresses which might exceed the 64 character length suggested by RFC5321.<br />
  This defaults to 4, which gives 4 x 6 = 24 bits of cryptographic information, which means that a spammer will have <br />
  to make 2^24 attempts to guarantee forging an SRS address.'],
['SRSValidateBounce','Enable Bounce Recipient Validation','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,0,'(.*)',undef,
  'Bounce messages that fail reverse SRS validation (but not a valid SMTP probe)<br />
  will receive a 554 5.7.5 [Bounce address not SRS signed] SMTP error code.<br /> Scoring is done  with srsValencePB.'],
['srsValencePB','SRS Validate Bounce Failed Score, default=10 +',10,\&textinput,10,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB','For Message/IP scoring in SRSValidateBounce',undef,undef,'msg003210','msg003211'],
['SRSno','Don\'t Rewrite These Addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Don\'t rewrite addresses when messages come from/to these addresses. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com). <br />For example: fribo@example.com|jhanna|@example.org'],
['noSRS','Don\'t Validate Bounces From these IPs*',120,\&textinput,'','(.*)','ConfigMakeIPRe',
  'Enter IP addresses that you don\'t want to validate bounces from, separated by pipes (|).
  For example:  145.145.145.145|145.146.<br /><hr />
  <div class="menuLevel1">Notes On SRS</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/srs.txt\',3);" />','','7'],

[0,0,0,'heading','DNSBL <a href="http://apps.sourceforge.net/mediawiki/assp/index.php?title=DNSBL" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="DNSBL" /></a>'],

['ValidateRBL','Enable DNS Blacklist Validation', '0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,1,'(.*)','configUpdateRBL',
'This requires an installed Net::DNS module in PERL. Scoring is done  with rblValencePB for \'fail\' and rblnValencePB for \'neutral\' results. '],
['rblnValencePB','DNSBL Neutral Score, default=50 +',10,\&textinput,50,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB','Message/IP scoring',undef,undef,'msg003020','msg003021'],
['rblValencePB','DNSBL Failed Score, default=100 +',10,\&textinput,100,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB','Message/IP scoring',undef,undef,'msg003030','msg003031'],
['RBLLog','Enable DNSBL logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,''],
['noRBL','Don\'t do DNSBL for these IPs*',120,\&textinput,'','(.*)','ConfigMakeIPRe',
 'Enter IP addresses that you don\'t want to be DNSBL validated, separated by pipes (|). For example:  145.145.145.145|145.146.',undef,'7'],
['RBLWL','Whitelisted DNSBL Validation',0,\&checkbox,'','(.*)',undef,
  'Enable DNSBL for whitelisted messages '],
['AddRBLHeader','Add X-Assp-DNSBL Header',0,\&checkbox,1,'(.*)',undef,
  'Add X-Assp-DNSBL header to messages with positive reply from DNSBL.'],
['rblSpamLovers','DNSBL Failures Spam-Lover*',120,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000600','msg000601'],
['RBLServiceProvider','RBL Service Providers*',120,\&textinput,'file:files/dnsbls.txt','(\S*)','configUpdateRBLSP',
 'Names of DNSBLs to use separated by "|". You may set for every provider a weight like zen.spamhaus.org=>50|bl.spamcop.net=>25.<br />
 Defaults are:<br />
 zen.spamhaus.org=>1|bl.spamcop.net=>1|psbl.surriel.com=>2|ix.dnsbl.manitu.net=>2|<br />
 l2.apews.org=>3|combined.njabl.org=>1|safe.dnsbl.sorbs.net=>1|dnsbl-1.uceprotect.net=>2|<br />
 dnsbl-2.uceprotect.net=>2|dnsbl-3.uceprotect.net=>2|blackholes.five-ten-sg.com=>3".<br />
 DNSBL providers can get a "weight" like bl.spamcop.net=>1.<br />
 The value of the weight can be set directly like=>45 or as a divisor of RBLmaxweight. Low numbers < 6 are divisors . So if RBLmaxweight = 50 (default) bl.spamcop.net=>50  would be the same as bl.spamcop.net=>1, bl.spamcop.net=>2 would be the same as bl.spamcop.net=>25.<br />
 If the sum of weights surpasses RBLmaxweight, the DNSBL check fails.  If not, the DNSBL check is scored as "neutral" even with RBLmaxhits reached. Setting Showmaxreplies will allow ALL replies to contribute to the total weight regardless of RBLmaxhits.<br />
 Some RBL Service Providers, like blackholes.five-ten-sg.com, provides different return codes in a single DNS-zone: like 127.a.b.c - where a,b,c are used to identify a weight or type (or what ever) of the returned entry. If you want to care about special return codes, or if you want to use different weights for different return codes, you should use the following enhanced entry syntax:<br /><br />
 RBL-Service-Provider=>result-to-watch=>weight (like:)<br />
 blackholes.five-ten-sg.com=>127.0.0.2=>3<br />
 blackholes.five-ten-sg.com=>127.0.0.5=>4<br />
 blackholes.five-ten-sg.com=>127.0.?.*=>5<br /><br />
 You can see, the wildcards * (multiple character) and ? (single character) are possible to use in the second parameter. Never mix the three possible syntax types for the same RBL Service Provider. An search for a match inside such a definition is done in reverse ASCII order, so the wildcards are used as last.<br />
 Some RBL Service Providers, provides different return codes using a bitmask in any part of the reply. To define weights for bitmasks, place a single \'M\' in front of the mask number, like<br /><br />
 sp.com=>127.0.0.M2=>25<br />
 sp.com=>127.0.0.M4=>41<br />
 sp.com=>127.0.M1.5=>56<br />
 sp.com=>127.0.M64.*=>11<br />
 sp.com=>127.0.0.2=>22<br />
 sp.com=>127.0.*.*=>1<br /><br />
 Valid bitmasks are 1,2,4,8,16,32,64 and 128. The resulting weight will be the weight sum of all matching bitmasks (if no full qualified definition is found). For example: a return code of 127.0.0.6 for sp.com will result in a weight of 66 (25+41), a reply of 127.0.0.2 will result in 22<br />
 Because each single bitmask indicates a set of 128 numbers you should prevent the usage of something like 127.0.M16.M1 - this will lead in to a set of (128*128) 16384 addresses, which is really too much!<br />
 For the same service provider, first define all bitmask definitions, after that all full qualified definitions and than all definitions with wildcards, like in the example above! If your definition order is wrong, the resulting weights will be unexpected!
  ',undef,undef,'msg003810','msg003811'],
['Showmaxreplies','Show All Possible Hits ',0,\&checkbox,'','(.*)',undef,
  'Show all hits instead of stopping at RBLmaxhits.'],
['RBLmaxhits','Maximum Hits',10,\&textinput,2,'(.*)','configUpdateRBLMH','A hit is an affirmative response from a DNSBL.<br />
  The DNSBL module will check all of the DNSBLs listed under Service Provider. If the number of hits is greater or equal Maximum Hits, the email is flagged <span class="negative">failed</span>.<br /> If the number of hits is greater 0 and less Maximum Hits, the email is flagged <span class="negative">neutral</span>. <br /> 
RBLmaxhits is ignored if the RBLServiceProvider are classified (weighted), the email is flagged <span class="negative">failed</span> if weights for all DNSBLs is greater  rblValencePB. A weight is a number representing the trust we put into a DNSBL, 1 is highest - 6 is lowest.'],
['RBLmaxreplies','Maximum Replies',10,\&textinput,7,'(\d*)','configUpdateRBLMR','A reply is affirmative or negative reply from a DNSBL.<br />
  The DNSBL module will wait for this number of replies (negative or positive) from the DNSBLs listed under Service Provider for up to the Maximum Time( RBLmaxtime ).<br />
  This number should be equal to or less than the number of DNSBL Service Providers listed to allow for randomly unavailable DNSBLs.',undef,undef,'msg003820','msg003821'],
['RBLmaxweight','RBL Maximum Weight',10,\&textinput,50,'(\d*)',undef,'A weight is a number representing the trust we put into a DNSBL.<br />
  The DNSBL module will check all of the DNSBLs listed under Service Provider. If the total of weights is greater or equal Maximum Weight, the email is flagged <b>Failed</b>.<br /> If the total of weights is greater 0 and less Maximum Weight, the email is flagged <b>Neutral</b>',undef,undef,'msg003840','msg003841'],
['RBLmaxtime','Maximum Time',10,\&textinput,10,'(.*)',undef,'Maximum time in seconds to spend on each message performing DNSBL check.'],
['RBLsocktime','Socket Timeout',10,\&textinput,1,'(.*)',undef,'This sets the DNSBL socket read timeout in seconds.'],
['RBLCacheInterval','DNSBL Expiration Time',10,\&textinput,3,'([\d\.]+)','configUpdateRBLCR',
  'IPs in cache will be removed after this interval in days. 0 will disable the cache. <input type="button" value=" show cache" onclick="javascript:popFileEditor(\'pb/pbdb.rbl.db\',5);" /><hr /><div class="menuLevel1">Notes On DNSBL</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/rbl.txt\',3);" />'],

[0,0,0,'heading','URIBL'],

['ValidateURIBL','Enable URI Validation', '0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,1,'(.*)','configUpdateURIBL',
  'Enable URI Blocklist. Messages that fail URIBL validation will receive URIBLError SMTP error code. This requires an installed Net::DNS module and an installed Email::MIME module in PERL. 
   Scoring is done  with uriblValencePB'],
['URIBLLog','Enable URIBL logging','0:nolog|1:standard|2:verbose|3:diagnostic',\&listbox,1,'(.*)',undef,''],
['uriblSpamLovers','URIBL Failures Spam-Lover*',120,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000610','msg000611'],
['uriblnValencePB','URIBL Neutral Score, default=25 +',10,\&textinput,25,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB','Message/IP scoring',undef,undef,'msg003240','msg003241'],
['uriblValencePB','URIBL Failed Score, default=50 +',10,\&textinput,50,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB','Message/IP scoring',undef,undef,'msg003250','msg003251'], 
['URIBLWL','Do URI Blocklist Validation for Whitelisted',0,\&checkbox,'','(.*)',undef,'URIBL check is done ignoring all spamlovers and testmodes!',undef,undef,'msg003890','msg003891'],
['URIBLNP','Do URI Blocklist Validation for NoProcessing',0,\&checkbox,'','(.*)',undef,'URIBL check is done ignoring all spamlovers and testmodes!',undef,undef,'msg003900','msg003901'],
['URIBLLocal','Do URI Blocklist Validation for Local Mails',0,\&checkbox,'','(.*)',undef,'',undef,undef,'msg003910','msg003911'],
['URIBLISP','Do URI Blocklist Validation for ISP/Secondary',0,\&checkbox,1,'(.*)',undef,'',undef,undef,'msg003920','msg003921'],
['URIBLServiceProvider','URIBL Service Providers*',120,\&textinput,'file:files/uribls.txt','(.*)','configUpdateURIBLSP',
 'Domain Names of URIBLs to use separated by "|". You may set for every provider a weight like multi.surbl.org=>50|black.uribl.com=>25.<br />
 The value of the weight can be set directly like=>45 or as a divisor of URIBLmaxweight . Low numbers < 6 are divisors . So if URIBLmaxweight = 50 (default) multi.surbl.org=>50  would be the same as multi.surbl.org=>1, multi.surbl.org=>2 would be the same as multi.surbl.org=>25.<br />
 If the sum of weights surpasses URIBLmaxweight, the URIBL check fails.  If not, the URIBL check is scored as "neutral"  even with URIBLmaxhits reached. Setting Showmaxreplies will allow ALL replies to contribute to the total weight regardless of URIBLmaxhits.<br />
 Some URIBL Service Providers, like multi.surbl.org and black.uribl.com , provides different return codes in a single DNS-zone: like 127.a.b.c - where a,b,c are used to identify a weight or type (or what ever) of the returned entry. If you want to care about special return codes, or if you want to use different weights for different return codes, you should use the following enhanced entry syntax:<br /><br />
 URIBL-Service-Provider=>result-to-watch=>weight (like:)<br />
 multi.surbl.org=>127.0.0.2=>2<br />
 multi.surbl.org=>127.0.0.4=>3<br />
 multi.surbl.org=>127.0.0.?=>4<br />
 multi.surbl.org=>127.0.0.*=>5<br /><br />
 You can see, the wildcards * (multiple character) and ? (single character) are possible to use in the second parameter. Never mix the three possible syntax types for the same URIBL Service Provider. An search for a match inside such a definition is done in reverse ASCII order, so the wildcards are used as last.<br />
 Some URIBL Service Providers, provides different return codes using a bitmask in any part of the reply. To define weights for bitmasks, place a single \'M\' in front of the mask number, like<br /><br />
 sp.com=>127.0.0.M2=>25<br />
 sp.com=>127.0.0.M4=>41<br />
 sp.com=>127.0.M1.5=>56<br />
 sp.com=>127.0.M64.*=>11<br />
 sp.com=>127.0.0.2=>22<br />
 sp.com=>127.0.*.*=>1<br /><br />
 Valid bitmasks are 1,2,4,8,16,32,64 and 128. The resulting weight will be the weight sum of all matching bitmasks (if no full qualified definition is found). For example: a return code of 127.0.0.6 for sp.com will result in a weight of 66 (25+41), a reply of 127.0.0.2 will result in 22<br />
 Because each single bitmask indicates a set of 128 numbers you should prevent the usage of something like 127.0.M16.M1 - this will lead in to a set of (128*128) 16384 addresses, which is really too much!<br />
 For the same service provider, first define all bitmask definitions, after that all full qualified definitions and than all definitions with wildcards, like in the example above! If your definition order is wrong, the resulting weights will be unexpected!
 Default is: multi.surbl.org|black.uribl.com',undef,undef,'msg003930','msg003931'],
['TLDS','Country Code TLDs*',60,\&textnoinput,'file:files/tlds-alpha-by-domain.txt','(.*)','ConfigMakeRe',
  'List of <a href="http://data.iana.org/TLD/tlds-alpha-by-domain.txt" rel="external">one level country code TLDs</a> '],
['URIBLCCTLDS','URIBL Country Code TLDs*',60,\&textnoinput,'file:files/URIBLCCTLDS.txt','(.*)','ConfigMakeRe',
  'List of <a href="http://george.surbl.org/two-level-tlds" rel="external">two level country code TLDs</a> and <a href="http://george.surbl.org/three-level-tlds" rel="external">three level country code TLDs</a> used to determine the base domain of the uri. Two level TLDs will be checked on third level, third level TLDs will be checked on fourth level. Any not listed domain will be checked in level two.',undef,undef,'msg003940','msg003941'],
['URIBLmaxuris','Maximum URIs',10,\&textinput,0,'(.*)',undef,
  'More than this number of URIs in the body will increase scoring with uriblValencePB. Enter 0 to disable feature.',undef,undef,'msg003950','msg003951'],
['URIBLmaxdomains','Maximum Unique Domain URIs',10,\&textinput,0,'(.*)',undef,
  'More than this number of unique domain URIs in the body will increase scoring with uriblValencePB. Enter 0 to disable feature.',undef,undef,'msg003960','msg003961'],
['URIBLNoObfuscated','Disallow Obfuscated URIs <a href="http://www.pc-help.org/obscure.htm" target="ASSPHELP"><img src="' . $wikiinfo . '" alt="obscure" /></a>',0,\&checkbox,'','(.*)',undef,
  'When enabled, messages with obfuscated URIs of types [integer/octal/hex IP, other things!] in the body will will increase scoring with uriblValencePB and if weights are used, the double weight will be used.',undef,undef,'msg003970','msg003971'],
['URIBLcheckDOTinURI','Check for \'DOT\' in URI',0,\&checkbox,'','(.*)',undef,
  'When enabled, assp will also check for the used word \'DOT\' instead of a \'.\' in URI\'s like \'example<b>dot</b>com or example<b>!d o-t_</b>com\' .<br />
   Enable this feature only, if you don\'t expect any problems in your national language (using \'dot\' + a toplevel domain in any words).',undef,undef,'msg008820','msg008821'],
['URIBLmaxhits','Maximum Hits',10,\&textinput,1,'(\d+\.\d\d?|\d*)','configUpdateURIBLMH',
  'A hit is an affirmative response from a URIBL.<br />
   The URIBL module will check all of the URIBLs listed under Service Provider,<br />
   and flag the email with a URIBL failure flag if more than this number of URIBLs return a positive blacklisted response.<br />
   This number should be less than or equal to URIBLmaxreplies and greater than 0.
   If the number of hits is greater or equal URIBLmaxhits, the email is flagged <span class="negative">failed</span>.
    If the number of hits is greater 0 and less URIBLmaxhits, the email is flagged <span class="spampassed">neutral</span><br /> 
    URIBLmaxhits is ignored if the URIBLServiceProvider are classified (weighted), the email is flagged <span class="negative">failed</span> if weights for all URIs is greater or equal URIBLvalencPB.'],
['URIBLmaxreplies','Maximum Replies',10,\&textinput,1,'(\d*)','configUpdateURIBLMR',
  'A reply is affirmative or negative reply from a URIBL.<br />
   The URIBL module will wait for this number of replies (negative or positive) from the URIBLs listed under Service Provider<br />
   for up to the Maximum Time below. This number should be equal to or less than the number of URIBL Service Providers<br />
   listed to allow for randomly unavailable URIBLs.',undef,undef,'msg003980','msg003981'],
['URIBLmaxweight','URIBL Maximum Weight',10,\&textinput,0,'(.*)',undef,'A weight is a number representing the trust we put into a URIBL.<br />
  The URIBL module will check all of the URIBLs listed under URIBLServiceProvider <b>for every URI</b> found in an email. If the total of weights for all URIs is greater or equal this Maximum Weight, the email is flagged <b>Failed</b>.<br /> If the total of weights is greater 0 and less Maximum Weight, the email is flagged <b>Neutral</b> . If not defined or set to zero only URIBLmaxhit will be used to detect a fail or neutral state.',undef,undef,'msg009150','msg009151'],
['URIBLmaxtime','Maximum Time',10,\&textinput,10,'(.*)',undef,
  'This sets the maximum time in seconds to spend on each message performing URIBL checks.',undef,undef,'msg004000','msg004001'],
['URIBLsocktime','Socket Timeout',10,\&textinput,1,'(.*)',undef,'This sets the URIBL socket read timeout in seconds.',undef,undef,'msg004010','msg004011'],
['URIBLwhitelist','Whitelisted URIBL Domains*',120,\&textinput,'file:files/uriblwhite.txt','(.*)','ConfigMakeRe',
  'This prevents specific domains from being checked by URIBL module. For example:files/uriblwhite.txt.'],
['noURIBL','Don\'t Check Messages from these Addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Don\'t validate URIBL when messages come from these addresses. Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com). <br />For example: fribo@thisdomain.com|jhanna|@example.org',undef,undef,'msg004030','msg004031'],
['AddURIBLHeader','Add X-Assp-Received-URIBL Header',0,\&checkbox,1,'(.*)',undef,
  'Add X-Assp-Received-URIBL header to messages with positive reply from URIBL.',undef,undef,'msg004040','msg004041'],
['URIBLCacheInterval','URIBL Cache Refresh Interval for Hits',10,\&textinput,3,'(.*)','configUpdateURIBLCR',
  'Domains in cache will be removed after this interval in days. 0 will disable the cache. <input type="button" value=" Show URIBL Cache" onclick="javascript:popFileEditor(\'pb/pbdb.uribl.db\',5);" /><br /><hr /><div class="menuLevel1">Notes On URIBL</div>
<input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/uribl.txt\',3);" />',undef,undef,'msg004050','msg004051'],

[0,0,0,'heading','Attachments'],

['DoBlockExes','Attachment Checking ','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,0,'([\s01234]?)',undef,'Note:Attachment checking will only be done if Email::MIME is installed. Scoring is done  with baValencePB.'],
['atSpamLovers','Bad Attachment/Virus Spam-Lover*',120,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000580','msg000581'],
['baValencePB','Bad Attachment Score, default=20 +',10,\&textinput,20,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002630','msg002631'],
['AttachmentLog','Enable Attachment logging','0:nolog|1:standard|2:verbose|3:diagnostic',\&listbox,1,'(.*)',undef,''],
['BlockExes','External Attachment Checking Level ','0:Level 0|1:Level 1|2:Level 2|3:Level 3|4:Level 4',\&listbox,0,'([\s01234]?)',undef,
  'Set the level of Attachment Blocking to 1-3 for attachments that should be blocked, set level to 4  for attachments that should be allowed only. Choose 0 for no attachment blocking.'],
['BlockNPExes','NoProcessing Attachment Checking','0:Level 0|1:Level 1|2:Level 2|3:Level 3|4:Level 4',\&listbox,0,'([\s01234]?)',undef,
  'Set the level of Attachment Checking to 0-4 for noprocessing messages. Choose 0 for no attachment checking. '],
['BlockWLExes','Whitelisted Attachment Checking','0:Level 0|1:Level 1|2:Level 2|3:Level 3|4:Level 4',\&listbox,0,'([\s01234]?)',undef,
  'Set the level of Attachment Checking to 0-4 for whitelisted senders. Choose 0 for no attachment blocking.'],
['BlockLCExes','Local Attachment Checking','0:Level 0|1:Level 1|2:Level 2|3:Level 3|4:Level 4',\&listbox,0,'([\s01234]?)',undef,
  'Set the level of Attachment Blocking to 0-4 for local senders. Choose 0 for no attachment blocking.'],
['BadAttachL1','Level 1 rejected File Extensions',120,\&textinput,'ad[ep]|asx|ba[st]|chm|cmd|com|cpl|crt|dbx|exe|hlp|ht[ab]|in[fs]|isp|js|jse|lnk|md[abez]|mht|ms[cipt]|nch|pcd|pif|prf|reg|sc[frt]|sh[bs]|url|vb|vb[es]|wms|ws[cfh]','(.*)','updateBadAttachL1',
  'This regular expression is used to identify Level 1 attachments that should be blocked.<br />
  Separate entries with a pipe |. The dot . is assumed to precede these, so don\'t include it.<br /> For example:<br /> ad[ep]|asx|ba[st]|chm|cmd|com|cpl|crt|dbx|exe|hlp|ht[ab]|in[fs]|isp|js|jse|lnk|md[abez]|mht|ms[cipt]|nch|pcd|pif|prf|reg|sc[frt]|sh[bs]|url|vb|vb[es]|wms|ws[cfh]'],
['BadAttachL2','Level 2 rejected File Extensions',120,\&textinput,'(ad[ep]|asx|ba[st]|chm|cmd|com|cpl|crt|dbx|exe|hlp|ht[ab]|in[fs]|isp|js|jse|lnk|md[abez]|mht|ms[cipt]|nch|pcd|pif|prf|reg|sc[frt]|sh[bs]|url|vb|vb[es]|wms|ws[cfh]).zip','(.*)','updateBadAttachL2',
  'This regular expression is used to identify Level 2 attachments that should be checked.<br />
  Level 2 already includes all rejected extensions from Level 1. <br /> For example:<br />(ad[ep]|asx|ba[st]|chm|cmd|com|cpl|crt|dbx|exe|hlp|ht[ab]|in[fs]|isp|js|jse|lnk|md[abez]|mht|ms[cipt]|nch|pcd|pif|prf|reg|sc[frt]|sh[bs]|url|vb|vb[es]|wms|ws[cfh]).zip'],
['BadAttachL3','Level 3 rejected File Extensions',120,\&textinput,'.*7z|ace|arc|arj|bz2|cab|doc.*|gz|lz.*|url|rar|rz|tar|xl.*|.*zip.*|zoo','(.*)','updateBadAttachL3',
  'This regular expression is used to identify Level 3 attachments that should be checked.<br />
  Level 3 includes Level 2 and Level 1.<br /> For example:<br />.*7z|ace|arc|arj|bz2|cab|doc.*|gz|lz.*|url|rar|rz|tar|xl.*|.*zip.*|zoo'],
['GoodAttach','Level 4 Allowed File Extensions',120,\&textinput,'','(.*)','updateGoodAttach',
  'This regular expression is used to identify  attachments that should be allowed. All others are blocked. Separate entries with a pipe |. The dot . is assumed to precede these, so don\'t include it.<br /> For example:<br /> ai|asc|bhx|dat|doc|docx|eps|gif|htm|html|ics|jpg|jpeg|hqx|od[tsp]|pdf|ppt|rar|
  <br />
  rpt|rtf|snp|txt|xls|zip'],
['PassAttach','Passing File Names  ',120,\&textinput,'','(.*)','updatePassAttach',
  'This regular expression is used to identify  attachments that should mark the message as noprocessing. If you enter extensions do not precede it with a dot. This will take precedence over any bad attachment.'],
['AttachmentReportToRCPT','Send Attachment Report To Recipient',0,\&checkbox,'','(.*)',undef,
  'If set an email containing the Message ID, Remote IP, Message 
Subject, Sender email address, Recipient email address, and the blocked ated attachment 
detected will be sent to this Recipient.'],
['AttachmentError','Reply Code to Refuse Rejected Attachments',120,\&textinput,'550 5.7.1 These attachments are not allowed -- Compress before mailing.','([25]\d\d .*)',undef,'The literal FILENAME (case sensitive) will be replaced with the name of the blocked attachment!<br />
 <hr /><div class="menuLevel1">Notes On Attachment Blocking</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/Attachments.txt\',3);" />'],

[0,0,0,'heading','ClamAV and FileScan '],

['ScanLog','Enable Virus Check logging','0:nolog|1:standard|2:verbose|3:diagnostic',\&listbox,1,'(.*)',undef,''],
['UseAvClamd','Use ClamAV',0,\&checkbox,'','(.*)',undef,
    'If activated, the message is checked by ClamAV, this requires an installed
  File::Scan::ClamAV Perl module and a running Clamd . <br />The viruses will
  be stored in a special folder if the SpamVirusLog is set to 
\'quarantine\' and the
  filepath to the viruslog is set. Scoring is done  using vdValencePB.'],
['vdValencePB','Virus detected Score, default=50 +',10,\&textinput,50,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg003270','msg003271'],
['modifyClamAV','Modify ClamAV Module',1,\&checkbox,1,'(.*)',undef,'If set ClamAV modules ping and streamscan are modified. This may be disabled to use the original modules. <span class="negative">NOTE: Changing this  requires ASSP restart</span>'],
['AvClamdPort','Port or file socket for ClamAV',40,\&textinput,$defaultClamSocket,'(\S+)',undef,
 ' If the socket has been setup as a TCP/IP socket (see the TCPSocket option in the clamav.conf file - located for example in /etc/clamav/clamd.conf), then specify the TCPSocket (port). For example: 3310.
 If LocalSocket is specified in the clamav.conf file  then specify here the LocalSocket. For example /var/run/clamav/clamd.ctl.'],
['ClamAVBytes','Scan Bytes',10,\&textinput,50000,'(.*)',undef,
   'The number of bytes per message that will be scanned for virus and 
attachment blocking. Normally ASSP looks only at MaxBytes of a message. Values of 100000 or larger are not recommended. Maximum value is 500000'],
['ClamAVtimeout','ClamAV Timeout',10,\&textinput,10,'(.*)',undef, 
'ClamAV will timeout after this many seconds.<br /> default: 10 seconds.'],
['NoScanRe','Skip ClamAV Regular Expression*',120,\&textinput,'','(.*)','ConfigCompileRe', "Put anything here to identify messages which should not be checked for viruses."],
['SuspiciousVirus','No-Blocking Virus Scan Scoring Regex**',120,\&textinput,'UNOFFICIAL','(.*)','ConfigCompileRe',
 'If a ClamAV or FileScan result matches this expression it will be scored with the suspicious virus score ( vsValencePB ) and the message will not be blocked.<br />
 It is possible to weight such results. Every weighted regex that contains at least one \'|\' has to begin and end with a \'~\' - inside such regexes it is not allowed to use a \'~\', even it is escaped - for example:  ~abc\\~|def~=>23 or ~abc~|def~=>23 - instead use the octal (\\126) or hex (\\x7E) notation (\\126), for example ~abc\\126|def~=>23 or ~abc\\x7E|def~=>23 . Every weighted regex has to be followed by \'=>\' and the weight value. For example: <br />Phishing\\.=>1.45|~Heuristics|Email~=>50  <br />or <br />~(Email|HTML|Sanesecurity)\\.(Phishing|Spear|(Spam|Scam)[a-z0-9]?)\\.~=>4.6|Spam=>1.1|~Spear|Scam~=>2.1 . <br />The multiplication result of the weight and the penaltybox valence value will be used for scoring, if the absolute value of weight is less or equal 6. Otherwise the value of weight is used for scoring.',undef,undef,'msg004220','msg004221'],
['vsValencePB','Virus suspicious Score, default=25',10,\&textinput,25,'(\d+)','ConfigChangeValencePB','Message scoring',undef,undef,'msg003260','msg003261'],
['noScan','Do Not Scan Messages from/to these 
Addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe','Accepts 
specific addresses (user@example.com), user parts (user) or entire 
domains (@example.com).'],
['noScanIP','Do Not Scan Messages from these 
IPs*',120,\&textinput,'','(.*)','ConfigMakeIPRe','Enter IP addresses that you don\'t want to be scanned for virus , separated by pipes (|). For example: 145.145.145.145|145.146.'],
['ScanWL','Scan Whitelisted Senders',0,\&checkbox,1,'(.*)',undef,''],
['ScanNP','Scan NoProcessing Messages',0,\&checkbox,'','(.*)',undef,''],
['ScanLocal','Scan Local Senders',0,\&checkbox,'','(.*)',undef,''],
['ScanCC','Scan Copied Spam Mails',0,\&checkbox,'','(.*)',undef,''],
['AvError','Reply Code to Refuse Infected Messages',120,\&textinput,'554
 5.7.1 Mail appears infected with INFECTION.','([25]\d\d .*)',undef,'Reply
code to refuse infected messages. The string INFECTION is replaced with
the name of the detected virus.<br />  For example: 554 5.7.1 Mail appears
infected with INFECTION -- disinfect and resend.'],
['EmailVirusReportsTo','Send Virus Report To This 
Address',120,\&textinput,'','(.*)',undef,
  'If set an email containing the Message ID, Remote IP, Message 
Subject, Sender email address, Recipient email address, and the virus 
detected will be sent to this address. For example: 
admin@example.com'],
['EmailVirusReportsHeader','Add Full Header To Virus Report To Mail 
Address Above',0,\&checkbox,'','(.*)',undef,'If set the full message 
headers will also be added to Virus Reports.'],
['EmailVirusReportsToRCPT','Send Virus Report To 
Recipient',0,\&checkbox,'','(.*)',undef,'If set the intended 
recipient of the message will be sent a copy of the Virus Report. 
<input type="button" value=" Edit virusreport.txt file" 
onclick="javascript:popFileEditor(\'reports/virusreport.txt\',2);"/>'],
['DoFileScan','Use File System Virus 
Scanner','0:disabled|1:block|2:monitor',\&listbox,0,'(.*)',undef,
  'If activated, the message is written to a file inside the 
\'FileScanDir\' with an extension of \'maillogExt\'. After that ASSP 
will call \'FileScanCMD\' to detect if the temporary file is infected 
or not. The temporary created file(s) will be removed.<br />
  The viruses will be stored in a special folder if the SpamVirusLog 
is set to \'quarantine\' and the filepath to the viruslog is set.'],
['FileScanDir','File Scan 
Directory',120,\&textinput,"$asspbase/virusscan",'(.*)','',
  'Define the full path to the directory where the messages are 
temporary stored for the file system virus scanner. This could be any 
directory inside your file system. The running ASSP process must have 
full permission to this directory and the files inside! For defining any full filepaths, always use slashes ("/") not backslashes. '],
['FileScanCMD','File Scan Command',120,\&textinput,'NORUN','(.*)','',
  'ASSP will call this system command and expects a returned string 
from this command. This returned string is checked against 
\'FileScanBad\' and/or \'FileScanGood\' to detect if the message is 
OK or not! If the file does not exists after the command call, the 
message is consider infected. ASSP expects, that the file scan is 
finished when the command returns!<br />
   The literal \'FILENAME\' will be replaced by the full qualified 
file name of the temporary file.<br />
   The literal \'FILESCANDIR\' will be replaced with the value of 
FileScanDir.<br />
   All outputs of this command to STDERR are automatic redirected to 
STDOUT.<br />
   FileScan will not run, if FileScanCMD is not specified.<br />
   If you have your online/autoprotect file scanner configured to 
delete infected files inside the \'FileScanDir\', define \'NORUN\' in 
this field! In this case FileScanGood and FileScanBad are ignored. If 
there is a need to wait some time for the autoprotect scanner, write 
\'NORUN-dddd\', where dddd are the milliseconds to wait!<br />
   Depending on your operating system it may possible that you have to 
quote (\' or ") the command, if it contains whitespaces. The replaced 
file name will be quoted by ASSP if needed. For example: \'d:\utility\touch.exe FILENAME\''],
['FileScanBad','RegEx to Detect \'BAD\' in Returned 
String*',120,\&textinput,'','(.*)','ConfigCompileRe',
  'Put anything here to identify bad messages by the string returned 
from the FileScanCMD. If this regular expression matches, the message 
is considered infected.'],
['FileScanGood','RegEx to Detect \'GOOD\' in Returned 
String*',120,\&textinput,'','(.*)','ConfigCompileRe',
  'Put anything here to identify good messages by the string returned 
from the FileScanCMD. If this regular expression matches and 
\'FileScanBad\' does not, the message is considered not infected.'],
['FileScanRespRe','FileScan Responds 
Regex*',120,\&textinput,'','(.*)','ConfigCompileRe',
  'A regular expression that will be used over the text returned from 
the FileScanCMD. The result of this regex is used as virus name 
(INFECTION) in AvError. For example: infected by (.+)<br />
  <hr /><div class="menuLevel1">Notes On Virus Checks</div><input 
type="button" value="Notes" 
onclick="javascript:popFileEditor(\'notes/viruscheck.txt\',3);"/>'],

[0,0,0,'heading','Regex Filter / Spambombs <a href="http://apps.sourceforge.net/mediawiki/assp/index.php?title=Regular_Expressions" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="bombRe" /></a>'],

['BombLog','Enable Bomb logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,
  'If set to verbose, the reporting to the logfile and the X-ASSP- scoring header will show the complete list of all hits. Otherwise only the highest match will be shown.',undef,undef,'msg007030','msg007031'],
['bombSpamLovers','Bomb Spam-Lover*',120,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000550','msg000551'],
['preHeaderRe','Regular Expression to early Identify Spam in Handshake and Header Part*',120,\&textinput,'file:files/preheaderre.txt','(.*)','ConfigCompileRe',
 'Until the complete mail header is received, assp is processing the handshake and header content line per line, but the first mail content check is done after the complete mail header is received.<br />
 It is possible, that some content (malformed headers, forbidden characters or character combinations) could cause assp to die or to run into a unrecoverable exception (eg. segment fault).<br />
 Use this regular expression to identify such incoming mails based on a line per line check, at the moment where a single line is received.<br />
If a match is found and preHeaderCollect not set, assp will immediately send a \'421 <myName> closing transmission\' reply to the client and will immediately terminate the connection. If preHeaderCollect is not set ASSP will try to store the mail in Spam-folder.'], 
['maxBombValence','Maximum Score on Regex Match per Mail per Check',10,\&textinput,70,'(.*)',undef, 'This option is valid for all regex searches which allow weights (marked with **) and limits the maximum penalty per check. maxBombHits is overwritten. If not set the search will stop if MessageScoringUpperLimit or maxBombHits is reached. For example: 70'],
['maxBombHits','Maximum Number Of Hits in Regex Search*',120,\&textinput,'blackRe=>3|bombSubjectRe=>3|bombSuspiciousRe=>3|bombRe=>3','(.*)','ConfigMakeRe', 'This option is valid for all regex searches which allow weights (marked with **). Use the syntax: regextype=>3|other.regextype=>3 to overwrite the maximum number of hits a regexsearch should perform. Default for regex searches are \'blackRe=>3|bombSenderRe=>1|bombHeaderRe=>1|bombSubjectRe=>3|bombCharSets=>1|bombSuspiciousRe=>3|bombRe=>3\'. The search will stop if MessageScoringUpperLimit or maxBombHits is reached. This can be overwritten by maxBombValence.'],
['DoBlackRe','Use Black Regular Expression to Identify Spam','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,1,'(.*)',undef,
  'This works similar to DoBombRe but has different default in noprocessing. Envelope, Header and Data Part are checked  against the BlackRe. Scoring is done  with blackValencePB - the Valence is the sum of all valences(weights) of all found blackRe(s). Blocking will only be done if \'block\' is set  (default) and the messagescore is equal or exceeds blackValencePB.  '],
['blackRe','Black Regular Expressions to Identify Spam ** ',120,\&textinput,'file:files/blackre.txt','(.*)','ConfigCompileRe',
  'This is a stricter version of bombRe (blackReNP, blackReISPIP are enabled by default). If an incoming email matches this expression it will be considered spam. The expressions here will work as in  <a href="http://www.enginsite.com/Library-Perl-Regular-Expressions-Tutorial.htm" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="Perl-Regular-Expressions-Tutorial" />Regular Expressions</a> As all fields marked with two asterisk (**) do - this  regular expressions (regex) can accept a weight value. Every weighted regex has to be followed by \'=>\' and the weight value. The search will continue until maxBombHits is reached or maxBombValence is exceeded (if set). Newest example file:<a href=http://assp.cvs.sourceforge.net/viewvc/assp/asspV1/files/blackre.txt target=files ><span class="negative">blackre.txt</a>'],
['blackValencePB','Bomb Black Expression Score, default=40 +',10,\&textinput,40,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002690','msg002691'],
['blackReWL','Do Black Regular Expressions Checks for Whitelisted',0,\&checkbox,'','(.*)',undef,''],
['blackReNP','Do Black Regular Expressions Checks for NoProcessing',0,\&checkbox,'','(.*)',undef,''],
['blackReISPIP','Do Black Regular Expressions Checks for ISPIP',0,\&checkbox,1,'(.*)',undef,''],
['blackReLocal','Do Black Regular Expressions Checks for Local Messages',0,\&checkbox,'','(.*)',undef,''],
['DoBombHeaderRe','Use Header Regular Expressions ','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,3,'(.*)',undef,
  'If activated, each message-header is checked  against bombHeaderRe. Scoring is done  with bombValencePB'],
['bombValencePB','Bomb Expression Score, default=20 +',10,\&textinput,20,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002680','msg002681'],
['bombHeaderRe','RegEx to find Spam in Header Part **',120,\&textinput,'file:files/bombheaderre.txt','(.*)','ConfigCompileRe',
  'Header will be checked against this Regex if DoBombHeaderRe is enabled. '],
['bombSenderRe','RegEx to find Spam in Envelope**',120,\&textinput,'file:files/bombsenderre.txt','(.*)','ConfigCompileRe','Expression to identify mailfrom,ip and helo.'],
['bombSubjectRe','RegEx to find Spam in Subject **',120,\&textinput,'file:files/bombsubjectre.txt','(.*)','ConfigCompileRe',' newest example file: <a href=http://assp.cvs.sourceforge.net/viewvc/assp/asspV1/files/bombsubjectre.txt target=files ><span class="negative">bombsubjectre.txt</a>.  The expressions here will work as original <a href="http://www.enginsite.com/Library-Perl-Regular-Expressions-Tutorial.htm" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="Perl-Regular-Expressions-Tutorial" />Regular Expressions</a>'],
['maxSubjectLength','Maximum allowed Subject Length',10,\&textinput,'250=>20','^(\d+(?:\=\>\d+)?|)$',undef,'If set to a value greater than 0, assp will check the length of the Subject of the mail. If the Subject length exceeds this value, the message score will be increased by \'bombValencePB\' and the string that is checked in \'bombSubjectRe\' will be trunked to this length. It is possible to define a special weight using the syntax \'length=>value\', in this case the defined absolute value will be used instead of \'bombValencePB\' to increase the message score. If the subject is too long and this weight is equal or higher than \'bombValencePB\' no further bomb checks will be done on the subject.',undef,undef,'msg009360','msg009361'],
['bombCharSets','RegEx to find Foreign Charsets ** ',120,\&textinput,'file:files/charsets.txt','(.*)','ConfigCompileRe','Header will be checked against this Regex if DoBombHeaderRe is enabled. A weight can be assigned. For example:<br /> charset=.?BIG5|charset=.?CHINESEBIG|charset=.?GB2312|charset=.?KS_C_5601|charset=.?KOI8=>0.5|charset=.?EUC-KR|charset=.?ISO-2022|charset=.?CP1251. '],
['DoBombRe','Use Bomb Regular Expressions','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,3,'(.*)',undef,
  'If activated, each message is checked  against bombRe Regular Expressions. Scoring is done  with bombValencePB - the Valence is the sum of all valences(weights) of all found bombRe(s)'],
['bombRe',' DoBombRe: RegEx for Header and Data Part **',120,\&textinput,'file:files/bombre.txt','(.*)','ConfigCompileRe','Header and Data will be checked against this Regular Expressions if DoBombRe is enabled. Newest example file: <a href=http://assp.cvs.sourceforge.net/viewvc/assp/asspV1/files/bombre.txt target=files ><span class="negative">bombre.txt</a>. The expressions here will work as in<a href="http://www.enginsite.com/Library-Perl-Regular-Expressions-Tutorial.htm" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="Perl-Regular-Expressions-Tutorial" />Regular Expressions</a>'],
['bombSuspiciousRe',' DoBombRe: Regular Expression to Score Blackish and/or Whitish Expressions **',120,\&textinput,'file:files/suspiciousre.txt','(.*)','ConfigCompileRe','Put here anything which might be suspicious (blackish) or trustworthy (whitish). bombSuspiciousValencePB will be multiplied by the weight and increases/decreases the total score.  Trustworthiness (whitishness) will be assigned by using a negative weight.  For example:<br />news=>-0.4|no-?reply=>-0.5|password=>-0.7'],
['bombCharSetsMIME','DoBombRe: RegEx to Identify Foreign Charsets in MIME** ',120,\&textinput,'file:files/charsets.txt','(.*)','ConfigCompileRe','MIME parts will be checked against this Regex. A weight can be assigned. For example:<br /> charset=.?BIG5|charset=.?CHINESEBIG|charset=.?GB2312|charset=.?KS_C_5601|charset=.?KOI8=>0.5|charset=.?EUC-KR|charset=.?ISO-2022|charset=.?CP1251. '],
['maxBombSearchTime','Maximum time spend on Regex Search',10,\&textinput,5,'(.*)',undef, 'Maximum time in seconds that is spend on  regex check. This time check is done, after every found regex. So it is possible that the regex search takes longer as the defined value, if no match is found or a single search takes more time.'],
['noBombScript','Don\'t Check Messages from these Addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Don\'t detect spam bombs in messages from these addresses. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com).<br /><hr /><div class="menuLevel1">Notes On Bomb Regex</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/bombre.txt\',3);" />'],

[0,0,0,'heading','Bayesian Filter <a href="http://apps.sourceforge.net/mediawiki/assp/index.php?title=Bayesian Filter" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="Bayesian Filter"  /></a>'],

['BayesianLog','Enable Bayesian Logging','0:nolog|1:standard|2:verbose|3:diagnostic',\&listbox,1,'(.*)',undef,
  'Enables verbose logging of  Bayesian checks in the maillog.'],
['DoBayesian','Bayesian Check','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,3,'(.*)',undef,
  "If activated, the message is checked based on Bayesian factors in spamdb. This needs a fully functional spamdb built by rebuildspamdb. For starters it is best practice to put this inactive and build the spamdb collection with the help of DSNBL ,URIBL and spamaddresses. Scoring is done with baysValencePB for external mails, baysValencePB_local is used for outgoing and internal mails - both values are multiplied with the detected baysProbability.",undef,undef,'msg004710','msg004711'],
['baysValencePB','Bayesian Score, default=49 +',10,\&textinput,49,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring',undef,undef,'msg002650','msg002651'],
['baysSpamLovers','Bayesian Spam-Lover*',120,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000510','msg000511'],
['baysSpamLoversRe','Regular Expression to Identify Bayesian SpamLover*',120,\&textinput,'','(.*)','ConfigCompileRe',
 'If a message matches this regular expression it will be considered a Bayesian SpamLover message. For example: password|news'],
['BayesianStarterDB','Bayesian Starter Database ',120,\&textinput,'starterdb/spamdb','(.*)',undef,'A ready to use spamdb which can be used alone or together with your local spamdb. It will be automatically downloaded at startup and placed in folder "assp/starterdb". No download if empty. Manually download from here: <a href="http://sourceforge.net/projects/asspV1/files/ASSP%20Installation/Spam%20Collection/spamdb.gz" target=wiki>Spam%20Collection/spamdb.gz</a>'],  
['enableStarterDB','Use Bayesian Starter Database ',0,\&checkbox,1,'(.*)',,undef,'Enable a prebuilt BayesianStarterDB which can be used alone or together with your local spamdb. It will be automatically downloaded at startup and placed in folder "assp/starterdb/spamdb". '],
['downloadStarterDBNow','Run downloadStarterDB Now',0,\&checkbox,'','(.*)','ConfigChangeRunTaskNow', "If selected, ASSP will download the BayesianStarterDB. <input type=button value=\"Run Now!\" onclick=\"document.forms['ASSPconfig'].theButtonX.value='Apply Changes';document.forms['ASSPconfig'].submit();WaitDiv();return false;\" />&nbsp;<input type=button value=\"Refresh Browser\" onclick=\"document.forms['ASSPconfig'].theButtonRefresh.value='Apply Changes';document.forms['ASSPconfig'].submit();WaitDiv();return false;\" />"],
['BayesWL','Bayesian Check on Whitelisted Senders',0,\&checkbox,'','(.*)',undef,''],
['BayesNP','Bayesian Check on NoProcessing Messages',0,\&checkbox,'','(.*)',undef,''],
['BayesLocal','Bayesian Check on Local Senders',0,\&checkbox,'','(.*)',undef,''],
['BayesMaxProcessTime','Bayesian Check Timeout ',10,\&textinput,30,'(\d+)',undef,'The Bayesian Checks are the most memory and CPU consuming tasks that ASSP is doing on a message. If such tasks running to long on one message, other messages could run in to SMTPIdleTimeout. Define here the maximum time in seconds that ASSP should spend on Bayesian Checks for one message. (Cannot be greater than 60 seconds!)'],
['noBayesian','Skip Bayesian Check*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Mail from/to any of these addresses are ignored by Bayesian check, mails will not be stored in spam/notspam collection. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com). Wildcards are supported (user*@example.com)'],
['noBayesian_local','Skip Bayesian for this local senders*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'Mail from any of these local addresses are ignored by Bayesian check, mails will not be stored in spam/notspam collection. Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com)',undef,undef,'msg009570','msg009571'],
['yesBayesian_local','Do Bayesian for this local senders only*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'Mail from any of these local addresses will perform Bayesian check, noBayesian_local will be ignored. Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com)',undef,undef,'msg009570','msg009571'],
['baysTestModeUserAddresses','Bayesian Testmode User Addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe','These users are in testmode ( mark subject only ) for bayesian spam, even with testmode off'],
['maxBayesValues','Maximum most significant results used per mail to calculate Bayesian-Probability',10,\&textinput,40,'([2-9]\d|\d{3})',undef,'Maximum count of most significant values used to calculate the Bayesian-Spam-Probability and the confidence of that probability.
 ',undef,undef,'msg007890','msg007891'],
['baysProbability','Bayesian Probability Threshold ',10,\&textinput,'0.6','(0\.\d+)',undef,' Messages with spam-probability below or equal this threshold are considered Ham. Recommended \'0.6\'.<br />
 An resulting Spam-Probability above this value is multiplied with baysValencePB_local or baysValencePB to get the penaltybox Valence for the IP- and message score. In other words, the penaltybox Valence is weighted by the Spam-Probability in case Spam is detected.<br />
 An resulting Spam-Probability below this value but higher than ( 1 - baysProbability ) is stated as \'UNSURE\' . In this case the half score will be added to the message score but not to the IP score and the message will not be blocked.<br /><br />
 The following default Bayesian math (prob = p1 / (p1 + p2)) is used to calculate the SpamProb value for \'n\' found Bayesian-Word-Pairs, each with a spam-weight \'p\' - where 0&lt;p&lt;1 :<br /><br />
 \'SpamProb\' = (p<sub>1</sub> * p<sub>2</sub> * ... * p<sub>n</sub>) / ( p<sub>1</sub> * p<sub>2</sub> * ... * p<sub>n</sub>  + (1 - p<sub>1</sub>) * (1 - p<sub>2</sub> ) * ... * (1 - p<sub>n</sub>))<br />',undef,undef,'msg004740','msg004741'],
['AddSpamProbHeader','Add Bayes Probability Header',0,\&checkbox,'','(.*)',undef,
 'Adds a line to the email header "X-Assp-Spam-Prob: 0.0123" Probability ranges from 0 to +1 where > baysProbability is spam.
<br /><hr />
  <div class="menuLevel1">Notes On Bayesian</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/bayesian.txt\',3);" />',undef,undef,'msg004790','msg004791'],

[0,0,0,'heading','Blocking Reports'],

['ReportLog','Enable Report logging','0:nolog|1:standard|2:verbose|3:diagnostic',\&listbox,2,'(.*)',undef,''],
['EmailBlockReport','Request Block Report',120,\&textinput,'assp-blockreport','(.*)',undef,
 'Any mail sent by local/authenticated users to this username will be interpreted as a request to get a report about blocked emails. Do not put the full address here, just the user part. For example: assp-blockreport<br />
 Leading digits/numbers in the mail subject will be interpreted as "report request for the last number of days". If the number of days is not specified in the mail subject, a default of 5 days will be used to build the report. <br />
 All characters behind the "number of days" will be interpreted as a regular expression to overwrite the BlockReportFilter - leading and trailing white spaces will be ignored.<br />
Only Users defined in EmailBlockTo, EmailAdmins and EmailAdminReportsTo are \'Admins\' and can request a report for other users. They have to use a special syntax with \'=>\' in the body of the report request. The syntax is: <br />
 QueryAddress=>ReportRecipient=>ReportDays<br />There may be one or many lines with this syntax . For example:<br />
 user@domain and user@domain=>user@domain - will send a report for this user to this user<br />
 *@domain (better use) *@domain=>* - will send a report for every blocked user in this domain to this user<br />
 user@domain=>recipient@any-domain - will send a report for user@domain to recipient@any-domain<br />
 *@domain=>recipient@any-domain - will send a report for every blocked user in this domain to recipient@any-domain<br />
 A third parameter is possible to set, which defines the number of days for which the report should be created. The default (if empty or not defined) is one day. This value is used to calculate the \'next run date\'. For example:<br />
 *@domain=>recipient@any-domain=>2 - creates a report for two days.<br />
 *@domain=>*=>14 - creates a report for 14 days.<br />
 user@domain=>=>3 or user@domain=>*=>3 - creates a report for three days. The second parameter is here empty or *.<br />
 To overwrite the defined BlockReportFilter, you can define a fourth parameter, which contains the regular expression to use.<br />
 *@domain=>*=>14=>virus|newsletter - creates a report for 14 days and skips all lines that contains the words \'virus\' or \'newsletter\'.<br />
 If an admin emails a block report request and specifies a filter in the subject of the email and a fourth parameter in the body, both regular expressions will be merged in to a single regex for each line.<br />
 If you or a user want the default BlockReportFilter to become part of the overwrite regex, the literal \'$BRF\' should be inluded in the regex like:<br />
 *@domain=>*=>14=>virus|$BRF|newsletter - or even in the subject of the email<br />
 In this case the literal \'$BRF\' will be replaced by the BlockReportFilter.<br />
 Only Admins are able to request blockreports for non local email addresses. For example:<br />
 user@non_local_domain=>recipient@any-domain=>4<br />
 *@non_local_domain=>recipient@any-domain=>4<br />
 This will result in an extended blockreport for the non local address(es). Replace \'non_local_domain\' with the domain name you want to query for.<br />
 It is possible to change the complete design of the BlockReports to your needs,  using a html-css file. An default css-file \'blockreport.css\' is in the image folder.<br />
 There you can also find a default icon file \'blockreporticon.gif\' and a default header-image-file \'blockreport.gif\' - which is the same like \'logo.gif\'.  There is no need to install that fles. If assp can not find this files in its
 image folder, it will use default hardcoded css and icon. If the file \'blockreport.gif\' is not found \'logo.gif\' will be used.<br />
  <input type="button" value=" Edit blockreport_sub.txt file" onclick="javascript:popFileEditor(\'reports/blockreport_sub.txt\',2);" /><br />
  <input type="button" value=" Edit blockreport_html.txt file" onclick="javascript:popFileEditor(\'reports/blockreport_html.txt\',2);" /><br />
  <input type="button" value=" Edit blockreport_text.txt file" onclick="javascript:popFileEditor(\'reports/blockreport_text.txt\',2);" />','Basic',undef,'msg008400','msg008401'],
['EmailBlockReply','Reply to Block-Report Request','0:NO REPLY|1:REPLY TO SENDER|2:REPLY TO EmailBlockTo|3:REPLY TO BOTH',\&listbox,1,'(.*)',undef,
  '',undef,undef,'msg008420','msg008421'],
['EmailBlockTo','Send Copy of Block-Reports TO',120,\&textinput,'','(.*@.*)?',undef,
  'Email sent from ASSP acknowledging your submissions will be sent to this address if EmailBlockReply is set. For example: admin@domain.com'],
['EmailBlockReportDomain','Email Domain',120,\&textinput,'@assp.local','(\@.*)',undef,
  'BlockReport will build addresses for requests using this domain. Set this to a local domain or use the default. Notice the leading required \'@\'! For example: @assp.local.'],
['QueueUserBlockReports','Queue User Block Report Requests','0:run instantly|1:store and run once at midnight|2:store and run scheduled|3:run delayed',\&listbox,0,'(.*)',undef,
  'How to process block report requests for users (not EmailBlockTo, EmailAdmins, EmailAdminReportsTo).<br />
  \'run instantly\' - the request will be processed instantly (not stored).<br />
  \'store and run once at midnight\' - the request will be stored/queued, runs at QueueSchedule, and will be removed from queue after that<br />
 \'store and run scheduled\' - the request will be stored/queued, runs permanently scheduled at BlockReportSchedule until it will be removed from queue - a \'+\' in the subject is not needed<br />
  \'run delayed\' - the request will be stored and  processed during the next minutes<br />
  To add a request to queue the user has to send an email to EmailBlockReport. Leading digits/numbers in the mail subject will be interpreted as "report request for the last number of days". If the number of days is not specified in the mail subject, a default of 5 days will be used to build the report.<br />
  If \'run instantly\',\'run delayed\' or \'store and run once at midnight\' is selected, but a user wants to schedule a permanent request, a leading \'+\' before the digits in subject is required.<br />
  To remove a request from queue the user has to send an email to EmailBlockReport with a leading \'-\' in the subject.<br />
  <input type="button" value=" Edit user report queue" onclick="javascript:popFileEditor(\'files/UserBlockReportQueue.txt\',2);" /><input type="button" value=" Edit user report instant queue" onclick="javascript:popFileEditor(\'files/UserBlockReportInstantQueue.txt\',2);" />'],
['QueueSchedule','Runtime for Queued Requests',10,\&textinput,0,'(.*)',undef,
  'Runtime hour for reports in QueueUserBlockReports. Set a number between 0 and 23. 0 means midnight and is default'],
['BlockRepForwHost','Forward The Blockreportrequest to other ASSP',120,\&textinput,'','(.*)',undef,'If you are using more than one ASSP (backup MX), define the IP:relayPort of the other ASSP here (separate multiple entries by "|"). The Blockreportrequest will be forwarded to this ASSP and the user will get a blockreport from every ASSP. The perl module <a href="http://search.cpan.org/search?query=Net::SMTP/" rel="external">Net::SMTP</a> is required to use this feature.'],
['BlockReportFile','File for Blockreportrequest',120,\&textinput,'','(file:.+)|',undef,'A file with BlockReport requests. ASSP will generate a block report for every line in this file (file:files/blockreportlist.txt - file: is required if defined!) every day at BlockReportSchedule for the last day. The perl modules <a href="http://search.cpan.org/search?query=Net::SMTP/" rel="external">Net::SMTP</a> and <a href="http://search.cpan.org/search?query=Email::MIME /" rel="external">Email::MIME </a> are required to use this feature. A report will be only created, if there is at least one blocked email found! The syntax is: <br />
 QueryAddress=>ReportRecipient=>ReportDays<br /> 
There may be one or many lines with this syntax. For example:<br />
 user@domain and user@domain=>user@domain - will send a report for this user to this user<br />
 *@domain (better use) *@domain=>* - will send a report for every blocked user in this domain to this user<br />
 *@* - creates a report for all local users in all local domains<br />
 user@domain=>recipient@any-domain - will send a report for user@domain to recipient@any-domain<br />
 *@domain=>recipient@any-domain - will send a report for every blocked user in this domain to recipient@any-domain<br />
A third parameter is possible to set, which defines the number of days for which the report should be created. The default (if empty or not defined) is one day. This value is used to calculate the \'next run date\'. For example:<br />
 *@domain=>recipient@any-domain=>2 - creates a report for two days.<br />
 *@domain=>*=>14 - creates a report for 14 days.<br />
 user@domain=>=>3 or user@domain=>*=>3 - creates a report for three days. The second parameter is here empty or *!<br />
 To overwrite the defined BlockReportFilter, you can define a fourth parameter, which contains the regular expression to use.<br />
 *@domain=>*=>14=>virus|newsletter - creates a report for 14 days and skips all lines that contain the words \'virus\' or \'newsletter\'.<br />
 Only Admins are able to request blockreports for non local email addresses. For example:<br />
 user@non_local_domain=>recipient@any-domain=>4<br />
 *@non_local_domain=>recipient@any-domain=>4<br />
 This will result in an extended blockreport for the non local address(es). Replace \'non_local_domain\' with the domain name you want to query for.',undef,undef,'msg008470','msg008471'],
['BlockReportSchedule','Runtime BlockReportFile',10,\&textinput,0,'(.*)',undef,
  'Runtime hour for reports in BlockReportFile. Set a number between 0 and 23. 0 means midnight and is default.'],
['BlockReportNow','Generate a BlockReport from BlockReportFile Now',0,\&checkbox,'','(.*)','ConfigChangeRunTaskNow', "If selected, ASSP will generate a block report from BlockReportFile now. <input type=button value=\"Run Now!\" onclick=\"document.forms['ASSPconfig'].theButtonX.value='Apply Changes';document.forms['ASSPconfig'].submit();WaitDiv();return false;\" />&nbsp;<input type=button value=\"Refresh Browser\" onclick=\"document.forms['ASSPconfig'].theButtonRefresh.value='Apply Changes';document.forms['ASSPconfig'].submit();WaitDiv();return false;\" />"],
['BlockMaxSearchTime','Max Search time per log File',10,\&textinput,0,'(\d+)',undef,
  'The maximum time in seconds, the Blockreport feature spends on searching in one log file. If this value is reached, the next log file will be processed. A value of 0 disables this feature and all needed log files will be fully processed.'],
['BlockReportFormat','The format of the Report Email','0:text and html|1:text only|2:html only',\&listbox,1,'(.*)',undef,
  'Block reports will be sent as multipart/alternative MIME messages. They normally contains two parts, a plain text part and a html part. Select "text only" or "html only" if you want to skip any of this parts.<br />
  To make it possible to detect a resent email, ASSP will add a header line "X-Assp-Resend-Blocked: myName" to each email!'],
['BlockReportHTTPName','My HTTP Name',120,\&textinput,'','(.*)',undef,'The hostname for HTTP links in AdminUsers Blockreports. If not defined the local hostname will be used.'],
['BlockReportFilter', 'Regular Expression to Skip Log Records*',120,\&textinput,'','(.*)','ConfigCompileRe',
 "Put anything here to identify messages which should not be reported. For example:  \\[Virus\\]|\\[BlackDomain\\]"],
['inclResendLink','Include a Resend-Link for every resendable email','0:disabled|1:in plain text report|2:in html report|3:in both',\&listbox,3,'(.*)',undef,
  'Block reports will be sent as multipart/alternative MIME messages. They contains two parts, a plain text part and a html part. If a blocked email is stored in any folder, it is possible to include a link for each email in to the report. Define here what you want ASSP to do. Note: File name logging (fileLogging) must be on! The perl module <a href="http://search.cpan.org/search?query=Email::Send/" rel="external">Email::Send</a> is required to use this feature.'],
['BlockResendLink','Which Link Should be included','0:both|1:left|2:right',\&listbox,0,'(.*)',undef,
  'If HTML is enabled in inclResendLink, two links (one on the left and one on the right site) will be included in the report email by default. Depending on the used email clients it could be possible, that one of the two links will not work for you. Try out what link is working and disable the other one, if you want.'],
['BlockResendLinkLeft','User which get the Left link only* ',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'List of users and domains that will get the left link only. The setting for BlockResendLink will be ignored for this entries!'],
['BlockResendLinkRight','User which get the right link only* ',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'List of users and domains that will get the right link only. The setting for BlockResendLink will be ignored for this entries!'],
['DelResendSpam','Delete Mails in Spam Folder',0,\&checkbox,1,'(.*)',undef, 'If selected, an user request to resend a blocked email will delete the file in the spamlog folder - an admin request will move the file to the correctednotspam folder.'],
['autoAddResendToWhite','Automatic add Resend Senders to Whitelist','0:no|1:Users only|2:Admins only|3:Users and Admins',\&listbox,3,'(.*)',undef, 'If a resend request is made by any of the selected users, the original sender of the resent mail will be added to whitelist.<br /><hr />
  <div class="menuLevel1">Notes On Blocking Reports</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/blockreports.txt\',3);" />'],

[0,0,0,'heading','Email Interface <a href="http://sourceforge.net/apps/mediawiki/assp/index.php?title=How_do_i_use_the_e-mail_interface target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="email" /></a>'],

['EmailInterfaceOk','Enable Email Interface',0,\&checkbox,1,'(.*)',undef,
  'Checked means that you want ASSP to intercept and parse mails to the below usernames at any domain which is listed in localDomains. You can use \'assp.local\' or \'@assp-notspam.org\' because they are automatically included.  The interface accepts mails only from local senders coming from acceptAllMail or through relayPort or from authenticated SMTP connections or from addresses listed in EmailSenderOK. <br /><hr />
  <div class="menuLevel1">Notes On Email Interface</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/emailinterface.txt\',3);" />'],
['EmailSenderLocalAddress','Accept Emails (Reports) from local addresses always*',0,\&checkbox,1,'(.*)',undef,
  'Allow local addresses to send to the email
interface even if they are not local or authenticated. This overwrites the standard behaviour, which allows only reqests from local or authenticated users.'],
['EmailAdminReportsTo','Admin Mail Address',120,\&textinput,'','(.*@.*)?',undef,
  'Warnings/infos  will be sent to this address. For example: admin@domain.com'],
['EmailFrom','From Address for Reports',120,\&textinput,'<postmaster@assp-notspam.org>','(.*)',undef,
  'Email sent from ASSP acknowledging your submissions will be sent from this address. For example: <postmaster@assp-notspam.org>'],
['EmailHelp','Help Address',120,\&textinput,'assp-help','(.*)@?',undef,
  'Any mail sent by local/authenticated users to this username will be interpreted as a request for help. Do not put the full address here, just the user part. For example: assp-help. The user would then send to assp-help@anylocaldomain.com.'],
['EmailAdmins','Email Admins* ',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Mail from any of these addresses can add/remove to/from redlist, spamlovers, noprocessing. May request an EmailBlockReport for a list of users. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com)'],
['EmailResendRequester','Blocked Email Resend Requester*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'A list of local addresses, which are allowed to request a resend of blocked emails for other users, even they are not EmailAdmins . Leave this field blank (default), to do disable this feature.<br />
  This is usefull, if a user gets automatic generated BlockReports (e.g via BlockReportFile ) for a group of users and should be able to manage resends for them. Added here, the user is not allowed to request BlockReports for other users - in this case use EmailAdmins and EmailAdminDomains instead.<br />
  The resend is done to the recipient stored in the X-Assp-Intended-For: ( requires AddIntendedForHeader ) header field and the requester if the address was found in a TO: header filed. <br />
  Accepts specific addresses (user@domain.com), user parts (user).  Wildcards are supported (fribo*@domain.com).<br />
  For example: fribo*@thisdomain.com|jhanna ',undef,undef,'msg010120','msg010121'],
['EmailAdminDomains','Restrict Email Admins to Domains*',120,\&textinput,'','(file:.+)|','ConfigMakeEmailAdmDomRe',
  'Use this parameter to restrict users registered in EmailAdmins, EmailAdminReportsTo and EmailBlockTo to a list of domains or users, for which they can request BlockReports.<br />
  The file: option is required. Use the following syntax to define an entry (one per line):<br />
  EmailAdminAddress=>*@domain1,*@domain2 user@domain3 ...<br />
  [user@domain]=>*@domain1,*@domain2 user@domain3 ...<br />
  Wildcards are allowed to be used in the domain definition - like *@*.domain.tld - separate multiple domains by comma or space.<br />
  If a BlockReport is requested for a not allowed email address, the complete BlockReport request will be ignored.<br />
  If an EmailAdmins address is not registered in this parameter, he/she is able to request BlockReports for all domains.',undef,undef,'msg009710','msg009711'],
['EmailAdminsModifyBlackForAll','Modify Black For All Recipients*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Email admins in this list will automatically add/remove entries to Personal Blacklist using a wildcard (*) for the recipient which blocks a sender for all recipients.<br />
 Accepts specific addresses (user@domain.com), user parts (user).  Wildcards are supported (fribo*@domain.com).<br />'],
['EmailSenderOK','Accept Emails (Reports) from these external addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Allow these external domains/addresses to send to the email
interface. This overwrites the standard behaviour, which allows only requests from local or authenticated users. Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com)'],
['EmailSenderNotOK','Not Authorized Addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Mail from any of these addresses are not accepted from Email Interface. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com).'],
['EmailSenderIgnore','Ignore Not Authorized Addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Mail from any of these addresses are not accepted from Email Interface. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com). The user will get not informed about the denied request.',undef,undef,'msg009390','msg009391'],
['EmailSpam','Report Spam to this Address',120,\&textinput,'assp-spam','(.*)@?',undef,
  'Any mail sent or forwarded by local/authenticated users to this username will be interpreted as a report about a Spam that got through (counts 2x). Do not put the full address here, just the user part. For example: assp-spam. The user would then send to assp-spam@anylocaldomain.com.<br />
  This works best if the mails are reported as attachments or copied into a new mail (header and body), because forwarding the mail will remove the original header.
  You can sent multiple emails as attachments. Each attached email-file must have the extension defined in "maillogExt". In this case only the attachments will be processed. Multiple attachments get truncated to MaxBytesReports. To use this multi-attachment-feature an installed Email::MIME module in PERL is needed.','Basic'],
['EmailHam','Report NotSpam to this Address',120,\&textinput,'assp-notspam','(.*)@?',undef,
  'Any mail sent or forwarded by local/authenticated users to this username will be interpreted as a good mail that was mistakenly listed as spam (counts 4x). Do not put the full address here, just the user part. For example: assp-notspam. The user would then send to assp-notspam@anylocaldomain.com<br />
This works best if the mails are reported as attachments or copied into a new mail (header and body) because forwarding the mail will remove the original header. You can sent multiple emails as attachments. Each attached email-file must have the extension defined in "maillogExt". In this case only the attachments will be processed. Multiple attachments get truncated to MaxBytesReports. To use this multi-attachment-feature an installed Email::MIME module in PERL is needed.','Basic'],
['MaxBytesReports','Error Max Bytes',10,\&textinput,20000,'(\d+)',undef,'How many bytes of an error report (EmailHam, EmailSpam) will ASSP look at. For example: 20000.'],
['EmailForwardReportedTo','Email Interface Forward Reports Destination',120,\&textinput,'','(.*)',undef,
 'Host and Port to forward EmailSpam and EmailHam reports to - eg "10.0.1.3:1025".<br />
  If you use more than one assp instance and your users are reporting spam and ham mails to multiple or all of them, but only one (but not this instance) is doing the rebuildspamdb and the corpus folders are not shared between the instances,<br />
  define the "host:port" of the central assp (rebuild-) instance here. Every report to EmailSpam and EmailHam (but only these!) will be forwarded to the defined host(s) and NO other local action will be taken. If the forwarding to all defined hosts fails, the request will be processed locally. To define multiple hosts for failover, separate them by pipe (|).',undef,undef,'msg009930','msg009931'],
['EmailErrorsReply','Reply to Spam/NotSpam Reports','0:NO REPLY|1:REPLY TO SENDER|2:REPLY TO EmailErrorsTo|3:REPLY TO BOTH',\&listbox,1,'(.*)',undef,  ''],
['EmailErrorsTo','Send Copy of Spam/NotSpam Reports TO',120,\&textinput,'','(.*@.*)?',undef,
  'Email sent from ASSP acknowledging your submissions will be sent to this address. For example: admin@domain.com<br />'],
['EmailErrorsModifyWhite','NotSpam Report will add to Whitelist ','0:disabled|1:add to whitelist|2:show whitelist',\&listbox,1,'(.*)',undef,
  'If set to \'add to whitelist\' NotSpam Reports will add email addresses to the Whitelist, Spam Reports will remove addresses from the Whitelist. If set to \'show whitelist\' Spam Reports will show if addresses are whitelisted. This works best if the mails are reported as attachments or copied into a new mail (header and body) because forwarding the mail will remove the original header.','Basic',undef,'msg005320','msg005321'],
['EmailErrorsRemoveWhite','Spam Report will remove from Whitelist ','0:disabled|1:remove from whitelist|2:show whitelist',\&listbox,1,'(.*)',undef,
  'If set to \'remove from whitelist\' Spam Reports will remove addresses from the Whitelist. If set to \'show whitelist\' Spam Reports will show if addresses are whitelisted. This works best if the mails are reported as attachments or copied into a new mail (header and body) because forwarding the mail will remove the original header.','Basic',undef,'msg005320','msg005321'],
['EmailWhitelistAdd','Add to Whitelist Address',120,\&textinput,'assp-white','(.*)@?',undef,
  'Any mail sent by local/authenticated users to this username will be interpreted as a request to add addresses to the whitelist.. <br />Do not put the full address here, just the user part. For example: assp-white. The user would then send to assp-white@anylocaldomain.com.
  ','Basic'],
['EmailWhitelistRemove','Remove from Whitelist Address',120,\&textinput,'assp-notwhite','(.*)@?',undef,
  'Any mail sent by local/authenticated users to this username will be interpreted as a request to remove addresses from the whitelist. <br />Do not put the full address here, just the user part.For example: assp-notwhite. The user would then send to assp-notwhite@anylocaldomain.com.
  ','Basic'],
['EmailWhiteRemovalAdminOnly','Allow  Whitelist Removals for Admins only ',0,\&checkbox,'','(.*)',undef,
  'Only the users defined in EmailWhitelistTo, EmailAdmins and EmailAdminReportsTo are able to remove addresses from the whitelist.'],
['exportedWhiteDomains','Exported White Domains File ',120,\&textinput,'file:whitedomains.txt','(\S*)',undef, 'whitelisted entries for domains (*@domain) will be stored here.'  ],
['CleanWhitelistNow','Run CleanWhitelist Now',0,\&checkbox,'','(.*)','ConfigChangeRunTaskNow', "If selected, ASSP will fill exportedWhiteDomains. <input type=button value=\"Run Now!\" onclick=\"document.forms['ASSPconfig'].theButtonX.value='Apply Changes';document.forms['ASSPconfig'].submit();WaitDiv();return false;\" />&nbsp;<input type=button value=\"Refresh Browser\" onclick=\"document.forms['ASSPconfig'].theButtonRefresh.value='Apply Changes';document.forms['ASSPconfig'].submit();WaitDiv();return false;\" />"],
['EmailWhitelistReply','Reply to Add to/Remove from Whitelist','0:NO REPLY|1:REPLY TO SENDER|2:REPLY TO EmailWhitelistTo|3:REPLY TO BOTH',\&listbox,1,'(.*)',undef,''],
['EmailWhiteRemovalToRed','Add  Whitelist Removals To Redlist ',0,\&checkbox,'','(.*)',undef,
  'Addresses which are removed from Whitelist via EmailWhitelistRemove will automatically be added to the Redlist. The address can only be added again to the Whitelist after it is removed from the Redlist.'],
['EmailWhitelistTo','Send Copy of Whitelist-Reports TO',120,\&textinput,'','(.*@.*)?',undef,
  'Email sent from ASSP acknowledging your submissions will be sent to this address. For example: admin@domain.com'],
['EmailRedlistAdd','Add to Redlist Address',120,\&textinput,'assp-red','(.*)@?',undef,
  'Any mail sent by local/authenticated users to this username will be interpreted as a request to add the sender address to the redlist. Only the users defined in EmailRedlistTo, EmailAdmins and EmailAdminReportsTo are able to define a list of email addresses in the mail body. <br /> Do not put the full address here, just the user part. For example: assp-red. The user would then send to assp-red@anylocaldomain.com.'],
['EmailRedlistRemove','Remove from Redlist Addresses',120,\&textinput,'assp-notred','(.*)@?',undef,
  'Any mail sent by local/authenticated users to this username will be interpreted as a request to remove the sender address from the redlist. Only the users defined in EmailRedlistTo, EmailAdmins and EmailAdminReportsTo are able to define a list of email addresses in the mail body. <br />
  Do not put the full address here, just the user part. For example: assp-notred. The user would then send to assp-notred@anylocaldomain.com.'],
['EmailRedlistReply','Reply to Add to/Remove from Redlist','0:NO REPLY|1:REPLY TO SENDER|2:REPLY TO EmailRedlistTo|3:REPLY TO BOTH',\&listbox,1,'(.*)',undef,''],
['EmailRedlistTo','Send Copy of Redlist-Reports TO',120,\&textinput,'','(.*@.*)?',undef,
  'Email sent from ASSP acknowledging your submissions will be sent to this address. For example: admin@domain.com'],
['EmailErrorsModifyPersBlack','Spam/NotSpam Report will modify Personal Blacklist *',120,\&textinput,'*@*','(.*)','ConfigMakeSLRe',
  'Spam Reports will add email addresses to the Personal Blacklist, NotSpam Reports will remove addresses from the Personal Blacklist, if the report addresses match. If EmailAdminsModifyBlack is set an email admin will block a sender for all recipients<br />
  Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com). Wildcards are supported (fribo*@domain.com).<br />
  Default is *@* , which matches all addresses.',undef,undef,'msg009610','msg009611'],
['EmailErrorsModifyNotPersBlack','Spam/NotSpam Report will not modify Personal Blacklist *',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Spam Reports will not add email addresses to the Personal Blacklist, NotSpam Reports will not remove addresses from the Personal Blacklist, if the report senders address matches. <br />
  Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com). Wildcards are supported (fribo*@domain.com).'],
['EmailPersBlackAdd','Add to Personal BlackListed  Addresses',120,\&textinput,'assp-persblack','(.*)@?',undef,
  'Any mail sent by local/authenticated users to this username will be interpreted as a request to add the listed address(es) to the personal blackListed addresses. Do not put the full address here, just the user part. <br />
  For example: assp-persblack.<br />
  The add and remove is done via email-interface, by sending specific email addresses to \'EmailPersBlackAdd\'  and \'EmailPersBlackRemove\'.
  A local user can force a complete report about all his personal black list entries by defining an email address that begins with \'reportpersblack\' in a remove or add request : eg: reportpersblack@anydomain.com or by sending an empty body.<br />
  Only an admin can force a complete cleanup of all personal black entries for a specific email address for all local users - sending an email to \'EmailPersBlackRemove\' with the address followed by \',*\' in the body
  eg: address_to_remove@the_domain.foo,*
<input type="button" value=" Show Personal Blacklist" onclick="javascript:popFileEditor(\'persblack\',5);" />',undef,undef,'msg009110','msg009111'],
['EmailPersBlackRemove','Remove from Personal BlackListed Addresses',120,\&textinput,'assp-notpersblack','(.*)@?',undef,
  'Any mail sent by local/authenticated users to this username will be interpreted as a request to remove the listed address(es) from the personal blackListed addresses .<br />
  Do not put the full address here, just the user part.<br />
  For example: assp-notpersblack.<br />
  The add and remove is done via email-interface, by sending specific email addresses to \'EmailPersBlackAdd\'  and \'EmailPersBlackRemove\'.
  A local user can force a complete report about all his personal black list entries by defining an email address that begins with \'reportpersblack\' in a remove or add request : eg: reportpersblack@anydomain.com or by sending an empty body.<br />
  Only an admin can force a complete cleanup of all personal black entries for a specific email address for all local users - sending an email to \'EmailPersBlackRemove\' with the address followed by \',*\' in the body
  eg: address_to_remove@the_domain.foo,*
  <input type="button" value=" Show Personal Blacklist" onclick="javascript:popFileEditor(\'persblack\',5);" />',undef,undef,'msg009120','msg009121'],
['EmailAnalyze','Request Analyze Report',120,\&textinput,'assp-analyze','(.*)@?',undef,
  'Any mail sent or forwarded by local/authenticated users to this username will be interpreted as a request for analyzing the mail. Do not put the full address here, just the user part. For example: assp-analyze','Basic'],
['EmailAnalyzeReply','Reply to Analyze Request','0:NO REPLY|1:SEND TO SENDER|2:SEND TO EmailAnalyzeTo|3:SEND TO BOTH',\&listbox,1,'(.*)',undef,''],
['EmailAnalyzeTo','Send Copy of Analyze-Reports',120,\&textinput,'','(.*@.*)?',undef,
  'A copy of the Analyze-Report will be sent to this address. For example: admin@domain.com'],
['DoAdditionalAnalyze','Spam and Ham Reports will trigger an additional Analyze Report ','0:NO ADDITIONAL REPORT|1:SEND TO SENDER|2:SEND TO EmailAnalyzeTo|3:SEND TO BOTH',\&listbox,0,'(.*)',undef,
  'Additional Analyze Report will be generated for Spam and Ham Reports. Setting the TO Address accordingly and choosing <b>EmailAnalyzeTo</b> will send the Analyze Report to the admin only.'],
['EmailSenderNoReply','Do Not Reply To These Addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Email sent from ASSP acknowledging your submissions will not be sent to these addresses. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com).<br /><hr />
  <div class="menuLevel1">Notes On Email Interface</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/emailinterface.txt\',3);" />'],

[0,0,0,'heading','File Paths'],

['base','Directory Base',120,\&textnoinput,'.','',undef,'All paths are relative to this folder.<br />
  <b>Note: Display only.</b>'],
['spamlog','Spam Collection',120,\&textinput,'spam','(\S+)',undef,'The folder to save the collection of spam emails. This directory will be used in building the spamdb. For example: spam'],
['notspamlog','Not-spam Collection',120,\&textinput,'notspam','(\S+)',undef,'The folder to save the collection of not-spam emails. This directory will be used in building the spamdb. For example: notspam'],
['incomingOkMail','OK Mail',120,\&textinput,'okmail','(.*)',undef,'The folder to save non-spam (message ok). These are messages which are considered as HAM, but are not stored in the standard HAM folder because of our policy to use only confirmed HAM messages (whitelisted or local) for SpamDB. If you want to keep copies of ok mail then put in a directory name. This directory will not be used in building the spamdb.'],
['discarded','Discarded Mails',120,\&textinput,'discarded','(.*)',undef,'The folder to save (virtually) discarded messages. In earlier versions of ASSP messages were physically deleted if the settings were set so. The newly introduced resend-function made it necessary to keep these messages and store them in "discarded".'],
['viruslog','Attachment/Virus Collection',120,\&textinput,'quarantine','(.*)',undef,
  'The folder to save rejected attachments and viruses. Leave this blank to not save these files. If you want to keep copies of rejected content then put in a directory name. This directory will not be used in building the spamdb. For example: quarantine'],
['correctedspam','False-negative Collection',120,\&textinput,'errors/spam','(\S+)',undef,
  'Spam that got through -- counts 2 times. This directory will be used in building the spamdb. For example: errors/spam'],
['correctednotspam','False-positive Collection',120,\&textinput,'errors/notspam','(\S+)',undef,
  'Good mail that was listed as spam, counts 4 times. This directory will be used in building the spamdb. For example: errors/notspam'],
['resendmail','try to resend this files',120,\&textinput,'resendmail','(\S+)',undef,
  'ASSP will try to resend the files in this directory to the original recipient. The files must have the "maillogExt" extension and must have the SMTP-format. ASSP will try to send every  file up to ten times (with 5 minutes delay). If the resend fails ten times, the file will be renamed to *.err, on success the file will be deleted!<br />
For example: resendmail. This requires an installed Email::Send module in PERL.'],
['maillogExt','Extension for Mail Files',120,\&textinput,'.eml','(\S*)',undef,
  'Enter the file extension (include the period) you want appended to the mail files in the mail collections. For Example: .eml'],
['spamdb','Spam Bayesian Database File',120,\&textinput,'spamdb','(\S+)',undef,'The output file from rebuildspamdb.pl.<br />
  Write "mysql" to use a MySQL table instead of a local file, in this case you need to edit the MySQL parameters starting with myhost. <hr /><div class="menuLevel1">Last Run Rebuildspamdb</div><input type="button" value="Last Run Rebuildspamdb" onclick="javascript:popFileEditor(\'rebuildrun.txt\',5);" />'],
['whitelistdb','E<!--get rid of google autofill-->mail Whitelist Database File',120,\&textinput,'whitelist','(\S+)',undef,'The file with the whitelist.<br />
  Write "mysql" to use a MySQL table instead of a local file, in this case you need to edit the MySQL parameters starting with myhost.'],
['redlistdb','E<!--get rid of google autofill-->mail Redlist Database File',120,\&textinput,'redlist','(\S+)',undef,'The file with the redlist.<br />
  Write "mysql" to use a MySQL table instead of a local file, in this case you need to edit MySQL parameters starting with myhost.<br />The Redlist serves several purposes:
<br />- the Redlist is a list of addresses that cannot contribute to the 
whitelist. For example, if someone goes on a vacation and 
turns on their autoresponder, put them on the redlist until 
they return. Then as they reply to every spam they receive they won\'t 
corrupt your non-spam collection or whitelist. There is also a redRe available where you can put some text from standard out of office messages, to automatically add a local user to the redlist when they send the out of office message, for example: \[autoreply\]
<br />- Redlisted addresses will not be added to the Whitelist.
This is used by EmailWhiteRemovalToRed to prevent repeated adding to the whitelist.
So if somebody whitelisted ebay@ebay.com you will surely remove that from the whitelist, but you can also be sure, that somebody will add that address again. Putting ebay@ebay.com into the redlist will give that pause.
<br />- Redlisted messages will not be stored in the 
SPAM/NOTSPAM-collection. '],
['ldaplistdb','LDAP/VRFY Cache',120,\&textnoinput,'ldaplist','(\S*)',undef,'The file with the LDAP/VRFY-cache. <br /> <input type="button" value=" LDAP/VRFY Cache" onclick="javascript:popFileEditor(\'ldaplist\',5);" />.'],
['ldapnotfounddb','LDAP/VRFY Not Found Cache',120,\&textnoinput,'ldapnotfound','(\S*)',undef,'The file with the LDAP/VRFY-NotFound-Cache, see also LDAPShowNotFound.<br /> <input type="button" value=" LDAP/VRFY Not Found Cache" onclick="javascript:popFileEditor(\'ldapnotfound\',5);" />'],
['droplist','Drop also Connections from these IP\'s*',120,\&textinput,'file:files/droplist.txt','(.*)','ConfigMakeIPRe','Automatically downloaded (http://www.spamhaus.org/drop/drop.lasso) list of IP\'s which should be blocked right away. ',undef,'7','msg005750','msg005751'],
['delaydb','Delaying Database',120,\&textinput,'delaydb','(\S*)',undef,'The file with the delay database.<br />Write "mysql" to use a MySQL table instead of a local file, in this case you need to edit the MySQL parameters starting with myhost.<br /> <input type="button" value=" Show Delay DB" onclick="javascript:popFileEditor(\'delaydb\',5);" />'],
['pbdb','PenaltyBox Database',120,\&textnoinput,'pb/pbdb','(\S*)',undef,'The directory/file with the penaltybox database files. For removal of entries from PenaltyBlackBox use <a target="main" href="./#noPB">noPB</a>.
 For removal of entries from WhiteBox noPBwhite. For  whitelisting IP addresses use noProcessingIPs. For blacklisting IP addresses use denySMTPConnectionsFrom. 
 <br /><input type="button" value=" Show BlackBox" onclick="javascript:popFileEditor(\'pb/pbdb.black.db\',4);" /><input type="button" value="Show White Box" onclick="javascript:popFileEditor(\'pb/pbdb.white.db\',4);" />'],
['persblackdb','Personal Blacklist Database File',120,\&textnoinput,'persblack','(\S*)',undef,'The file with the personal blacklist. The check of the personal black list is done shortly after the RCPT TO: command. This command will be rejected if an entry is found - any other setting except send250OK and send250OKISP will be ignored.<input type="button" value=" Show Personal Blacklist" onclick="javascript:popFileEditor(\'persblack\',5);" />
',undef,undef,'msg009100','msg009101'],
['griplist','GReyIPlist Database',120,\&textinput,'griplist','(\S*)',undef,'The file with the current GRey-IP-List  database -- make this blank if you don\'t use it.',undef,undef,'msg005730','msg005731'],
['gripValencePB','GRIP value Score (+ if > 0.9,- if < 0.1), default=5',10,\&textinput,5,'(\d+)','ConfigChangeValencePB', 'Message scoring',undef,undef,'msg002980','msg002981'],
['myhost','MySQL hostname or IP',120,\&textinput,'','(\S*)',undef,
  'You need <a
  href="http://search.cpan.org/~lds/Tie-DBI-1.02/lib/Tie/RDBM.pm"
  rel="external">Tie::RDBM</a> to use MySQL instead of local files.<br />
  This way you can share whitelistdb, delaydb and redlistdb between servers if "mysql" is written into their file-path.'],
['mydb','MySQL database name',120,\&textinput,'','(\S*)',undef,
  'This database must exist before starting ASSP,
  necessary tables will be created automatically into this database'],
['myuser','MySQL username',120,\&textinput,'','(\S*)',undef,
  'This user must have CREATE privilege on the configured database in order for tables to be created automatically'],
['mypassword','MySQL password',120,\&textinput,'','(\S*)',undef,''],
['logfile','ASSP Logfile',120,\&textinput,'logs/maillog.txt','(\S*)','ConfigChangeLogfile',
  'Blank if you don\'t want a log file. Change it to maillog.log if you don\'t want auto rollover.
  NOTE: Changing this field requires restarting ASSP before changes take effect.'],
['pidfile','PID File',120,\&textinput,'pid','(\S*)',undef,'Blank to skip writing a pid file. *nix users need pid files.
<br /> You have to restart assp before you get a pid file in the new location.<br /><hr /><div class="menuLevel1">Notes On File Path</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/filepath.txt\',3);" />'],

[0,0,0,'heading','Copy Spam/Ham'],

['sendAllSpam','Copy Spam and Send to this Address',120,\&textinput,'','(.*)',undef,
 'ASSP will deliver a copy of spam emails to this address if the collection mode in the collection section is set to do so (eg. baysSpamLog ). For example: spammonitor@example.com. The address can be different depending on the recipient. The literal USERNAME (case sensitive) is replaced by the user part of the recipient, the literal DOMAIN (case sensitive) is replaced by the domain part of the recipient. For example: USERNAME@Spam.DOMAIN, USERNAME+Spam@DOMAIN, spammonitor@DOMAIN','Basic'],
['sendAllDiscard','Copy Discarded and Send to sendAllSpam',0,\&checkbox,1,'(.*)',undef,
 'ASSP will deliver a copy of discard emails to sendAllSpam'],
['ccSpamInDomain','Copy Spam and Send to this Address per Domain*',120,\&textinput,'','(.*)','configUpdateCCD',
 'ASSP will deliver an additional copy of spam emails of a domain to this address - if the domain of the recipient-address is matched. For example: monitorspam@example1.com|monitor@example2.com.'],
['sendAllDestination','SMTP Destination for Spam Copies',120,\&textinput,'','(\S*)',undef,
 'Port to connect to when  Spam messages are copied. If blank they go to the main smtpDestination. eg "10.0.1.3:1025".'],
['ccSpamFilter','Copy Spam to these Recipients Only*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'Restricts Copy Spam to these recipients. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com). Wildcards are supported (fribo*@example.com).'],
['ccSpamAlways','Copy Spam to these Recipients always*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'Copy Spam to these recipients regardless of collection mode. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com).  Wildcards are supported (fribo*@example.com).'],
['ccSpamNeverRe','Do Not Copy Spam Regular Expression*',120,\&textinput,'','(.*)','ConfigCompileRe',
 'Never Copy Spam regardless of collection mode. Put anything here to identify messages which should not be copied.'],
['ccMaxScore','Do Not Copy Messages Above This MessageTotal score',10,\&textinput,'','(\d*)',undef,
 'Messages whose score exceeds this threshold will not be copied.  For example: 75'],
['ccMaxBytes','Cut Copied Spam to MaxBytes Length',0,\&checkbox,1,'(.*)',undef,
 'MaxBytes will be used to cut off copied mails, thereby reducing the load considerably.'],
['spamSubjectCC','Prepend Spam Subject to Copied Spam',0,\&checkbox,'','(.*)',undef,
 'If set spamSubject gets prepended to the subject of the copied message.'],
['spamTagCC','Prepend Spam Tag to Copied Spam',0,\&checkbox,1,'(.*)',undef,'The check which caused the spam detection will be prepended to the subject of the message. For example: [DNSBL]'],
['sendAllHamDestination','SMTP Destination for Ham Copies',120,\&textinput,'','(\S*)',undef,
 'Port to connect to when  Ham messages are copied. If blank they go to sendAllDestination. eg "10.0.1.3:1025"'],
['sendHamInbound','Copy Incoming Ham and Send to this Address',120,\&textinput,'','(.*)',undef, 'If you put an address in this box  ASSP will forward a copy of notspam messages from outside to this address. The literal USERNAME is replaced by the user part of the recipient, the literal DOMAIN is replaced by the domain part of the recipient. For example: archiv@example.com, USERNAME@mybackup.domain, catchallforthis@DOMAIN'],
['sendHamOutbound','Copy Outgoing Ham and Send to this Address',120,\&textinput,'','(.*)',undef, 'If you put an address in this box ASSP will forward a copy of outgoing notspam messages to this address. The literal USERNAME is replaced by the user part of the recipient, the literal DOMAIN is replaced by the domain part of the recipient. For example: archiv@example.com, USERNAME@mybackup.domain, catchallforthis@DOMAIN'],
['ccHamFilter','Copy Ham Filter*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'Copy Not-Spam to these addresses only. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com). Wildcards are supported (fribo*@example.com).'],
['ccnHamFilter','Do Not Copy Ham Filter*',120,\&textinput,'','(.*)','ConfigMakeSLRe',
 'Do Not Copy Ham to these addresses. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com). Wildcards are supported (fribo*@example.com).',undef,undef,'msg000460','msg000461'],
['ccMailReplaceRecpt','ccMail Recipient Replacement',0,\&checkbox,'','(.*)',undef,'The recipient replacement (ReplaceRecpt) rules from the "Recipients/Local Domains" section, will be used to replace ccMail recipients. For example: sendHamInbound = USERNAME@yourspamdomain.lan - in this case you are able to detect the target domain "yourspamdomain.lan" in a rule and you can replace the recipient/domain depending on its values and/or on the senders address.<br />
<hr /><div class="menuLevel1">Notes On CC Messages</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/copymail.txt\',3);" />',undef,undef,'msg000460','msg000461'],

[0,0,0,'heading','Collecting'],

['spamaddresses','SpamBucket Addresses* ',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Mail to any of these addresses are always spam and will contribute to the spam-collection unless from someone on the whitelist. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com). '],
['noCollecting','Do Not Collect Messages from/to these Addresses*',120,\&textinput,'','(.*)','ConfigMakeSLRe','Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com).'],
['noCollectRe','Do Not Collect Messages - Content Based*',120,\&textinput,'','(.*)','ConfigCompileRe','',undef,undef,'msg008930','msg008931'],
['UseSubjectsAsMaillogNames','Use Subject as Maillog Names',0,\&checkbox,1,'(.*)','ConfigChangeUSAMN',
  'You can turn this on to help you manually identify mail in your spam and non-spam collections.',undef,undef,'msg006100','msg006101'],
['MaxAllowedDups','Max Number of Duplicate File Names',10,\&textinput,5,'(\d+)','ConfigChangeMaxAllowedDups',
  'The maximum number of logged files with the same filename (subject) that are stored in the spam folder (spamlog), if UseSubjectsAsMaillogNames is selected. A low value reduces the number of possibly duplicate mails, assuming that mails with the same subject will have the same content. A value of 0 disables this feature. If this number of files with the same filename is reached, new files will be stored in the \'discarded\' folder, which has to be defined ( in addition to spamlog ) for this feature to work.', undef, undef,'msg008660','msg008661'],
['AllowedDupSubjectRe','Regular Expression to Allow Unlimited Duplicates *',120,\&textinput,'','(.*)','ConfigCompileRe','Messages with subject matching this regular expression will be collected regardless of the setting in MaxAllowedDups .'],
['MaxFileNameLength','Max Length of File Names',10,\&textinput,30,'(\d+)',undef,
  'The maximum character count that is used from the mail subject to build the file name of the logged file, if UseSubjectsAsMaillogNames is selected. This could be useful, if your mail clients having trouble to build the resend file name (right button - URL) correctly in block reports. Every non printable character will be replaced by a 4 byte string in this link.'],
['DoNotCollectRed','Do Not Collect Red Mails',0,\&checkbox,1,'(.*)',undef,
  'Mails matching redRe will not be stored in the collection folders.'],
['KeepWhitelistedSpam','Do Not Delete Whitelisted Spams',0,\&checkbox,'','(.*)',undef,
  'Mails matching  Whitelist will not be removed from the Spam folder.'],
['DoNotCollectBounces','Do Not Collect Bounced Mails',0,\&checkbox,1,'(.*)',undef,
  'Mails matching BounceSenders will not be collected.'],
['NoMaillog','Don\'t Collect Mail',0,\&checkbox,'','(.*)',undef,
  'Check this if you are using Whitelist-Only and do not care to save mail to build the Bayesian database.'],
['MaxFiles','Max Files',10,\&textinput,14000,'(\d+)',undef,
  'Maximum number of files to keep in each collection (spam and nonspam)'],
['MaxBytes','Max Bytes',10,\&textinput,10000,'(\d+)',undef,
  'How many bytes of the message will ASSP look at? Mails stored in the collecting folders will be truncated to this size if StoreCompleteMail is not set.'],
['StoreCompleteMail','Store the Complete Mail','0:disabled|100000:up to 100 kByte|500000:up to 500 kByte|1000000:up to 1 MByte|10000000:up to 10 MByte|999999999:no limit',\&listbox,500000,'(.*)',undef,
  'If set, ASSP will analyze only MaxBytes of the mail, but  will store the complete mail up to the selected limit. This could be useful for example, if you want to resend blocked messages. Be carefull using this option, your disk could be filled up very fast!'],
['baysNonSpamLog','OK Mail','0:no collection|2:notspam folder|4:okmail folder',\&listbox,4 ,'(.*)',undef,'Where to store non spam (message ok) messages. These are messages which are considered as HAM, but should not stored in the standard HAM folder because of our policy to use only confirmed HAM messages (whitelisted or local) for SpamDB. Set incomingOkMail accordingly if you choose \'okmail folder\'.'],
['NonSpamLog','Non Spam','0:no collection|2:notspam folder|4:okmail folder|6:discard folder',\&listbox,2,'(.*)',undef,'Where to store whitelisted/local non spam messages.'],
['SpamLog','Store Spam','0:disabled|1:enabled',\&listbox,1,'(.*)',undef,'Set this to \'disabled\' if you do not want to store any Spam regardless of settings in. Default: enabled (store in folder spamlog ).',undef,undef,'msg006230','msg006231'],
['noProcessingLog','NoProcessing Non Spam','0:no collection|2:notspam folder|4:okmail folder|6:discard folder',\&listbox,0,'(.*)',undef,'Where to store noprocessing non spam messages.',undef,undef,'msg006240','msg006241'],
['AttachLog','Rejected Attachments','0:no collection and no sendAllSpam|5:attachment folder|6:discard folder|7:discard folder and sendAllSpam',\&listbox,0,'(.*)',undef,'Where to store rejected mail+attachments.'],
['SpamVirusLog','Virus Infected','0:no collection and no sendAllSpam|5:quarantine|6:discard folder|7:discard folder and sendAllSpam',\&listbox,5,'(.*)',undef,'Where to store virus infected messages. '],
['spamBombLog','SpamBombs','0:no collection|1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,3,'(.*)',undef,'Where to store spam bombs -> DoBombHeaderRe, DoBombRe, DoBlackRe.'],
['DenyIPLog','Blacklisted IP','0:no collection and no sendAllSpam|1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,3,'(.*)',undef,'Where to store blacklisted IP messages.'],
['invalidHeloLog','Invalid Helos , Forged Helos, Blacklisted Helos','1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,7,'(.*)',undef,'Where to store invalid helo messages.'],
['spamBucketLog','Spam Collect Addresses','0:no collection and no sendAllSpam|1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,1,'(.*)',undef,'Where to store emails addressed to Spam Collect Addresses.'],
['baysSpamLog','Bayesian Spams','1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,7,'(.*)',undef,'Where to store Bayesian spam messages.'],
['RBLFailLog','DNSBL Failures','1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,3,'(.*)',undef,'Where to store DNSBL Failure spam messages.'],
['SPFFailLog','SPF/ SRS Failures','1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,3,'(.*)',undef,'Where to store SPF / SRS Failure spam messages.'],
['URIBLFailLog','URIBL Failures','1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,3,'(.*)',undef,'Where to store URIBL Failure spam messages.'],
['invalidSenderLog','Invalid Sender','1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,3,'(.*)',undef,'Where to store messages from a local domain with an unknown userpart.'],
['spamSBLog','Blocked Country - DoCountryBlocking, DoOrgBlocking','1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,3,'(.*)',undef,'Where to store messages from a blocked country.'],
['spamMSLog','Message Limit Blocks - DoPenaltyMessage','1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,3,'(.*)',undef,'Where to store Message Scoring Limit rejected messages. '],
['BackLog','Backscatter check failed','1:spam folder|3:spam folder &amp; sendAllSpam|6:discard folder|7:discard folder &amp; sendAllSpam',\&listbox,6,'(.*)',undef,'Where to store FBMTV rejected messages. '],

[0,0,0,'heading','Logging'],

['Notify','Notification Email To',120,\&textinput,'','(.*)',undef,
  'Email address(es) to which you want ASSP to send a notification email, if a matching log entry ( NotifyRe , NoNotifyRe ) is found. Separate multiple entries by "|". This requires an installed Email::Send module in PERL.'],
['NotifyRe','Do Notify, if log entry matches*',120,\&textinput,'','(.*)','ConfigCompileNotifyRe','Regular Expression to identify loglines for which a notification message should be send.<br />
  usefull entries are:<br />
  autoupdate: - to get informed about an autoupdate of the running script<br />
  adminupdate: - for config changes<br />
  admininfo: - for admin information<br />
  option list file: - for option file reload<br />
  error: - for any error<br />
  restart - to detect a ASSP restart<br />
  Admin connection - for GUI logon<br />
  You may define a comma separated list (after \'=>\') of recipients in every line, this will override the default recipient defined in \'Notify\'. For example: adminupdate=>user1@yourdomain.com,user2@yourdomain.com.<br />
  As third parameter after a second (\'=>\') you can define the subject line for the notification message.<br />
  for example: adminupdate:=>user1@yourdomain.com,user2@yourdomain.com=>configuration was changed<br />
  or: adminupdate:=>=>configuration was changed.'],
['NoNotifyRe','Do NOT Notify, if log entry matches*',120,\&textinput,'','(.*)','ConfigCompileRe','Regular Expression to identify loglines for which no notification message should be send.'],
['noLoggingIPs','Don\'t Log these IPs*',120,\&textinput,'','(\S*)','ConfigMakeIPRe',
  'Enter IP addresses that you don\'t want to be logged, separated by pipes (|).<br />
  This can be IP address of the SMTP service monitoring agent. For example: 145.145.145.145|145.146.','','7'],
['noLoggingRe', 'Regular Expression to Identify NoLog-Mails*',120,\&textinput,'','(.*)','ConfigCompileRe',
 'Put anything here to identify mails that you don\'t want to be logged.'],
['noLogLineRe', 'Regular Expression to Suppress Log-Messages*',120,\&textinput,'max errors|collect','(.*)','ConfigCompileRe',
 "Put anything here to identify log messages that you want to be suppressed. For example: max errors|collect"],
['allLogRe', 'Regular Expression to Identify Messages from/to Problematic Addresses *',120,\&textinput,'','(.*)','ConfigCompileRe',
 "Put anything here to identify mails from/to addresses you want to look at for problem solving. Mails identified will also be set to StoreCompleteMail."],
['subjectStart','Subject Start Delimiter',10,\&textinput,'[','(.*)',undef,'Start delimiter of subject in log '],
['subjectEnd','Subject End Delimiter',10,\&textinput,']','(.*)',undef,'End delimiter of subject in log'],
['regexLogging','Regex Match logging','0:nolog|1:standard|2:verbose',\&listbox,0,'(.*)',undef,'Show matching regex in log. '],
['ipmatchLogging','IP Matches Logging',0,\&checkbox,'','(.*)',undef,
  'Enables logging of IP addresses matches in the maillog. Will show a comment instead of the range if there is text after the IP ranges (and before any numbersign)  eg. 182.82.10.0/24 AOL',undef],
['slmatchLogging','Logging Address Matches',0,\&checkbox,'','(.*)',undef,
  'Enables logging of address matches in the maillog.',undef],
['uniqueIDPrefix','Prepend Unique ID logging',10,\&textinput,'m-','(.*)',undef,
  'Prepend ID. For example: m1-'],
['tagLogging','Spam Tag Logging',0,\&checkbox,1,'(.*)',undef,'Add spam tag to log.'],
['ExceptionLogging','Timeout Exception Logging',0,\&checkbox,'','(.*)',undef,''],
['FromLogging','Empty MailFrom will be set to From:',0,\&checkbox,1,'(.*)',undef,''],
['replyLogging','SMTP Status Code Reply Logging','0:disabled|1:enabled - exclude [123]XX|2:enabled - all',\&listbox,0 ,'(\d*)',undef,undef,undef,undef,'msg006660','msg006661'],
['expandedLogging','Logging Records include IP & MailFrom',0,\&checkbox,1,'(.*)',undef,''],
['sysLog','SYSLOG Centralized Logging',0,\&checkbox,'','(.*)',undef,'Enables logging to UNIX Syslog. Needs Sys::Syslog for local (UNIX/LINUX) logging or Net::Syslog for Windows or Network logging.'],
['sysLogPort','Syslog Port (UDP)',10,\&textinput,514,'([\d\.]+)',undef,
  'Port for Syslog logging with Net::Syslog.'],
['SysLogFac','Syslog Facility',120,\&textinput,'mail','(\S*)',undef,
  'Syslog Facility. Valid are kern, user, mail, daemon, auth, syslog, lpr, news, uucp, cron, authpriv, ftp, local0, local1, local2, local3, local4, local5, local6'],
['sysLogIp','Syslog IP',120,\&textinput,'127.0.0.1','(\S*)',undef,
  'IP Address of your Syslog Daemon for Syslog logging with Net::Syslog.'],
['asspLog','ASSP local logging',0,\&checkbox,1,'(.*)',undef,'ASSP manages local logging. The logs <a href="./#logfile">are stored</a> inside the directory where ASSP is installed. This is needed if you want to use any of the "Block Reporting" and "View Maillog Tail" features like searching, deleting, moving, resending of messages.'],
['LogRollDays','Roll the Logfile How Often?',10,\&textinput,1,'([\d\.]+)',undef,
  'ASSP closes and renames the log file after this number of days.'],
['MaxLogAge','Max Age of Logfiles',10,\&textinput,60,'(\d+)',undef,
  'The maximum file age in days of logfiles. If a logfile is older than this number in days, the file will be deleted. A value of 0 disables this feature and no logfile will be deleted because of its age.'],
['LogNameDate','Date Format in LogfileName',120,\&textinput,'YY-MM-DD','(.*)',undef,'Use this option to set the date-format in the logfile-name. The default value is \'YY-MM-DD\'. The following replacements will be done:<br />
 YYYY - year four digits<br />
 YY - year two digits<br />
 MMM - month three characters - like Oct Nov Dec<br />
 MM - month numeric two digits<br />
 DD - day numeric two digits<br />
 DDD - day three characters - like Mon Tue Fri'],
['LogDateFormat','Date/Time Format in LogDate',120,\&textinput,'MMM-DD-YY hh:mm:ss','((?:(?:MM|MMM|DD|DDD|YY|YYYY)(?:[\_\-\. ]|)){3}(?:[\-\_ ]*)(?:(?:hh|mm|ss)(?:[\.:\-\_]|)){3})',undef,'Use this option to set the logdate. The default value is \'MMM-DD-YY hh:mm:ss\'. The following (case sensitive !) replacements will be done:<br /><br />
 YYYY - year four digits<br />
 YY - year two digits<br />
 MMM - month three characters - like Oct Nov Dec<br />
 MM - month numeric two digits<br />
 DDD - day three characters - like Mon Tue Fri<br />
 DD - day numeric two digits<br />
 hh - hour two digits<br />
 mm - minute two digits<br />
 ss - second two digits<br /><br />
 <span class="positive">A value has to be defined for every part of the date/time. Allowed separators in date part are \'_ -.\' - in time part \'-_.:\' .</span>'],
['LogDateLang','Date/Time Language','0:English|1:FranÁais|2:Deutsch|3:EspaÒol|4:PortuguÍs|5:Nederlands|6:Italiano|7:Norsk|8:Svenska|9:Dansk|10:suomi|11:Magyar|12:polski|13:Romaneste',\&listbox,0,'(.*)',undef,
  'Select the language for the day and month if LogDateFormat contains DDD and/or MMM.',undef,undef,'msg008700','msg008701'],
['enableWORS','Windows Output Record Separator',0,\&checkbox,'','(.*)','ConfigChangeWors',
  'Checked means write CRLF to the end of the logfile instead of the standard LF. This can only be used if LogCharset is set to \'System Default\'.'],
['silent','Silent Mode',0,\&checkbox,'','(.*)',undef,
  'Checked means don\'t print log messages to the console. '],
['debug','General Debug Mode',0,\&checkbox,'','(.*)',\&ConfigDEBUG,
  'Checked sends debugging info to a .dbg file.
  Leave this unchecked unless there is a program error you are trying to track down.'],
['DebugRollTime','Roll the Debugfile How Often?',10,\&textinput,1800,'([\d\.]+)',undef,
  'ASSP closes and opens a new debug file after this number of seconds.'],
['Win32Debug','Win32 OutputDebugString',0,\&checkbox,'','(.*)',undef,'Make Win32 OutputDebugString available. Needs Win32::API::OutputDebugString'],
['IgnoreMIMEErrors','Ignore MIME Errors',0,\&checkbox,1,'(.*)',undef,'Errors, based on wrong email MIME contents, will not be written to log!'],
['ConTimeOutDebug','Connection Timeout Debug Mode',0,\&checkbox,'','(.*)',undef,'Select to debug SMTP connections that are running into timeout!'],
['RegExLength','RegEx Length in Log',10,\&textinput,32,'(.*)',undef,
  'Defines how many bytes of a matching Regular Expression will be shown in the log<br />
  Some matching Regular Expressions are too long for one line. Default: 32'],
['sendNoopInfo','Send NOOP Info',0,\&checkbox,'','(.*)',undef,
  'Checked means you want ASSP to send a "NOOP Connection from $ip" message to your SMTP server.
  <br /><hr />
  <div class="menuLevel1">Notes On Logging</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/logging.txt\',3);" />'],

[0,0,0,'heading','LDAP Setup '],

['LDAPLog','Enable LDAP logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,  ''],
['DoLDAP','Do LDAP lookup for valid local addresses ',0,\&checkbox,'','(.*)',undef,'Check local addresses against an LDAP database before accepting the message.<br />Note: Checking this requires filling in the other LDAP parameters like LDAPHost.<br /><a http://www.selfadsi.de/ldap-filter.htm">LDAP-Filter</a>'],
['LDAPHost','LDAP Host(s) <a href="http://apps.sourceforge.net/mediawiki/assp/index.php?title=LDAP" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="LDAP" /></a>',80,\&textinput,'localhost','(\S*)','updateLDAPHost','Enter the DNS-name(s) or IP address(es) of the server(s) that run(s) the <a href="http://ldap.perl.org/FAQ.html">LDAP</a> database. Second entry is backup. For example: localhost. Separate entries with pipes: LDAP-1.domain.com|LDAP-2.domain.com' ],
['DoLDAPSSL','Use SSL with LDAP (ldaps)','0:no|1:SSL|2:TLS',\&listbox,0,'(.*)',undef,'ASSP will use \'ldaps (SSL port 636)\' instead of ldap (port 389) or \'ldaps (TLS over port 389)\'. The Perl module <a href="http://search.cpan.org/search?query=IO::Socket::SSL" rel="external">IO::Socket::SSL</a> must be installed to use SSL or TLS!',undef,undef,'msg007220','msg007221'],
['LDAPtimeout','LDAP Query Timeout',10,\&textinput,15,'(\d+)',undef,'Timeout when connecting to the remote server.'],
['LDAPLogin','LDAP Login',40,\&textinput,'','(.*)',undef,'Most LDAP servers require a login and password before they allow queries.<br />Enter the DN specification for a user with sufficient permissions here.<br />For example: cn=Administrator,cn=Users,DC=yourcompany,DC=com'],
['LDAPPassword','LDAP Password',40,\&textinput,'','(.*)',undef,'Enter the password for the specified LDAP login here.'],
['LDAPVersion','LDAP Version',10,\&textinput,3,'(\d+)',undef,'Enter the version for the specified LDAP here.'],
['ldLDAPRoot','LDAP Root container for Local Domains',120,\&textinput,'','(.*)',undef,'The LDAP lookup will use this container and all sub-containers to match the local domain query.<br />The literal DOMAIN is replaced by the domain part of SMTP recipient (eg. domain.com) during the search.<br />For example: DC=yourcompany,DC=com.<br />If you use DOMAIN here, you must check "LDAP failures return false" below or non local domains will be treated as local. If not defined, LDAPRoot will be used.',undef,undef,'msg009350','msg009351'],
['ldLDAPFilter','LDAP Filter for Local Domains',120,\&textinput,'','(\S*)',undef,'This filter is used to query the LDAP database. This strongly depends on the LDAP structure.<br />The filter must return an entry if the domain must be relayed.<br />The literal DOMAIN (case sensitive) will be replaced by the domain name during the search.<br />For example: (&(|(|(|(|(&(objectclass=user)(objectcategory=person))(objectcategory=group))(objectclass=publicfolder))(!(objectclass=contact)))(objectclass=msExchDynamicDistributionList))(proxyaddresses=smtp:*@DOMAIN))'],
['LDAPRoot','LDAP Root container for Local Addresses',120,\&textinput,'','(.*)',undef,'The LDAP lookup will use this container and all sub-containers to match the local email address query.<br />The literal DOMAIN is replaced by the domain part of SMTP recipient (eg. domain.com) during the search.<br />For example: DC=yourcompany,DC=com.<br />If you use DOMAIN here, you must check "LDAP failures return false" below or non local domains will be treated as local.',undef,undef,'msg007270','msg007271'],
['LDAPFilter','LDAP Filter for Local Addresses',120,\&textinput,'','(\S*)',undef,'This filter is used to query the LDAP database. This strongly depends on the LDAP structure.<br />The filter must return an entry if the recipient address matches with that of any user.<br />The literal EMAILADDRESS is replaced by the fully qualified SMTP recipient (eg. user@example.com) during the search.<br />The literal USERNAME (case sensitive) is replaced by the user part of SMTP recipient (eg. user) during the search.<br />The literal DOMAIN (case sensitive) is replaced by the domain part of SMTP recipient (eg. domain.com) during the search.<br />For example: (proxyaddresses=smtp:EMAILADDRESS) or (|(mail=EMAILADDRESS)(mailaddress=EMAILADDRESS)) or (&(|(|(|(|(&(objectclass=user)(objectcategory=person))(objectcategory=group))(objectclass=publicfolder))(!(objectclass=contact)))(objectclass=msExchDynamicDistributionList))(proxyaddresses=smtp:EMAILADDRESS))'],
['forceLDAPcrossCheck','force to run LDAP/VRFY-CrossCheck - now.',0,\&checkbox,'','(.*)','ConfigChangeRunTaskNow','ASSP will force to run a LDAP/VRFY-CrossCheck now!<br />'. "<input type=button value=\"Run Now!\" onclick=\"document.forms['ASSPconfig'].theButtonX.value='Apply Changes';document.forms['ASSPconfig'].submit();WaitDiv();return false;\" />&nbsp;<input type=button value=\"Refresh Browser\" onclick=\"document.forms['ASSPconfig'].theButtonRefresh.value='Apply Changes';document.forms['ASSPconfig'].submit();WaitDiv();return false;\" />",undef,undef,'msg007320','msg007321'],
['MaxLDAPlistDays','Max LDAP/VRFY cache Days',10,\&textinput,7,'(\d+)',undef,'This is the number of days an address will be kept on the local LDAP cache without any email to this address. 0 disables the cache.'],
['LDAPFail','LDAP failures return false',20,\&checkbox,'','(.*)',undef,'LDAP failures return false when an error occurs in LDAP lookups.<hr /><div class="menuLevel1">Notes On LDAP </div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/ldap.txt\',3);" />'],

[0,0,0,'heading','Message-ID Tag Validation'],

['BounceSenders','Bounce Senders*',120,\&textinput,'mailer-daemon','(.*)','ConfigMakeRe','Envelope sender addresses treated as bounce origins. Null sender (\<\>) is always included.<br />
 Accepts specific addresses (postmaster@example.com), usernames (mailer-daemon), or entire domains (@bounces.domain.com)<br />Separate entries with pipes: |. For example: postmaster|mailer-daemon'],
['DoMSGIDsig','Do Message-ID Signing','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,1,'(.*)',undef,
  'If activated, the message-ID of each outgoing message will be signed with an unique Tag and every incoming mail from BounceSenders will be checked against this. This tagging is called FBMTV for "FBs Message-ID Tag Validation" and is worldwide unique to ASSP. This tag will be removed from any incoming email, to recover the original references in the mail header. Scoring is done  with msigValencePB <br />
  This check requires an installed Digest::SHA1 module in Perl.'],
['MSGIDsigLog','Enable Message-ID signing logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,  ''],
['MSGIDpreTag','Message-ID pre-Tag for MSGID-TAG-generation',10,\&textinput,'assp','([a-zA-Z0-9]{2,5})',undef,'To use Message-ID signing and to create the MSGID-Tags, a pre-Tag is needed. This Tag must be 2-5 characters [a-z,A-Z,0-9] long.'],
['MSGIDSec','Message-ID Secrets for MSGID-TAG-generation*',120,\&textinput,'0=asspv1','(.*)','configChangeMSGIDSec','To use Message-ID signing and to generate the MSGID-Tags, at least one secret key is needed, up to ten are possible.<br />
  The notation is : generationnumber[0-9]=secretKey. Multiple pairs are separated by pipes (|). Do not define spaces, tabs and \'=\' as part of the keys(secrets)!'],
['msigValencePB','Invalid MSGID-signature Score, default=50 +',10,\&textinput,50,'(\s*[\d]+\s*([\|,]\s*[\d]+\s*){0,1})','ConfigChangeValencePB', 'Message/IP scoring'],
['MSGIDsigAddresses','Do MSGID-Signing For These Addresses Only* ',120,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Only messages from any of these addresses will be tagged and checked by FBMTV. Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com). If empty FBMTV will be done for all addresses.'],
['noMsgID','Skip FBMTV for these IPs*',120,\&textinput,'127.0.0.|192.168.|10.','(\S*)','ConfigMakeIPRe','Enter IP addresses that you don\'t want to be FBMTV validated, separated by pipes (|). For example: 127.0.0.1|192.168.',undef,'7','msg001710','msg001711'],
['noMSGIDsigRe','Skip Message-ID signing, mail content dependant*',120,\&textinput,'out of office|on leave','(.*)','ConfigCompileRe','Use this to skip the Message-ID tagging depending on the content of the email. If the content of the email matches this regular expression (checking MaxBytes only), FBMTV will not be done. For example: \'I am out of office\' .'],
['noRedMSGIDsig','Skip Message-ID signing for Redlisted mails',0,\&checkbox,1,'(.*)',undef,'If selected, FBMTV will not be done for redlisted emails!'],
['Back250OKISP','Send 250 OK if Backscatter Detection fails','0:disabled|1:To ISP|2:To All',\&listbox,1,'(.*)',undef,'If Backscatter check fails for a bounced mail , ASSP will send "250 OK" , but will discard the mail, if the check is configured to block! \'To ISP\' means "sender is in ispip". '],

[0,0,0,'heading','DNS Setup'],

['UseLocalDNS','Use System Default DNS',0,\&checkbox,1,'(.*)',\&updateUseLocalDNS,'Use system default DNS Name Servers.'],
['DNSResponseLog','Show DNS Name Servers Response Time in Log',0,\&checkbox,'','(.*)',undef,'You can use this to arrange DNSServers for better performance. Put the fastest first.'],
['DNSServers','DNS Name Servers*',120,\&textinput,'','(.*)','updateDNS',
 'DNS Name Servers IP\'s to use for DNSBL, RWL, URIBL, PTR, SPF lookups. Separate multiple entries by "|" or leave blank to use system defaults.<br /> For example: 8.8.8.8|8.8.4.4|208.67.222.222|208.67.220.220<br />
  An DNS-query for the domain \'sourceforge.net\' is used per default to measure the speed of the used DNS-servers. If you want assp to use another domain or hostname for this, append \'=>domain.tld\' at the end of the line - like: 8.8.8.8|8.8.4.4|208.67.222.222|208.67.220.220=>myhost.com<br />
  To define the domain if you use the local DNS-servers \'UseLocalDNS\' without defining any DNS-servers here, simply write \'=>myhost.com\'.<br />
  All configured or local DNS Name Servers will be checked ',undef,undef,'msg007370','msg007371'],
['maxDNSRespDist','Maximum DNS Responsetime change',10,\&textinput,50,'([1-9]\d*)',undef,'Maximum DNS Server responsetime change in milliseconds before the query order of the name servers should be changed.',undef,undef,'msg009800','msg009801'],
['DNStimeout','DNS Query Timeout',10,\&textinput,5,'(\d+)','updateUseLocalDNS','Global DNS Query Timeout for DNSBL, RWL, URIBL, PTR, SPF, MX and A record lookups. The default is 5 seconds.',undef,undef,'msg007380','msg007381'],
['DNSretry','DNS Query Retry',10,\&textinput,1,'(\d+)','updateUseLocalDNS','Global DNS Query Retry. Set the number of times to try the query. The default is 1.',undef,undef,'msg007390','msg007391'],
['DNSretrans','DNS Query Retrans',10,\&textinput,3,'(\d+)','updateUseLocalDNS','Global DNS Query Retransmission Interval. Set the retransmission interval. The default is 3.<br /><hr />
  <div class="menuLevel1">Notes On DNS Setup</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/DNSsetup.txt\',3);" />'],

[0,0,0,'heading','SSL/TLS '],

['enableSSL','Enable TLS support on  listenPorts',0,\&checkbox,'','(.*)','ConfigChangeEnableSSL',
  'This enables STARTTLS on listenPort, listenPort2 and relayPort if the paths to your SSL Certificate ( SSLCertFile ) and SSL Key (SSLKeyFile) are set correctly. If you do not have valid certificates, you may generate both files online with <a href="http://www.mobilefish.com/services/ssl_certificates/ssl_certificates.php" rel="external">www.mobilefish.com</a> or you may use OpenSSL to generate <a href="http://www.mobilefish.com/developer/openssl/openssl_quickguide_self_certificate.html" rel="external">Self-signed SSL certificates</a>!.<span class="negative"> Changing this requires a restart of ASSP.</span>'],
['tlsValencePB','OK, Is a SSL/TLS connection Score, default=-10 +',10,\&textinput,-10,'(\s*-?[\d]+\s*([\|,]\s*-?[\d]+\s*){0,1})','ConfigChangeValencePB', '<span class="positive">Message Scoring/IP scoring Bonus for SSL/TLS connections</span>',undef,undef,'msg003230','msg003231'],
['SSL_version','SSL version used for transmission',40,\&textinput,'SSLv2/3','(\!?(?:SSLv2\/3|SSLv2|SSLv3|TLSv1)(?:\:\!?(SSLv2\/3|SSLv2|SSLv3|TLSv1))*)','ConfigChangeSSL',
  'Sets the version of the SSL protocol used to transmit data. The default is SSLv2/3,<br />
  which auto-negotiates between SSLv2 and SSLv3. You may specify \'SSLv2\', \'SSLv3\', or \'TLSv1\' (case-insensitive) combined with \':\' and negated with \'!\' (example: \'SSLv2/3:!SSLv2\') if you do not want this behavior.',undef,undef,'msg009660','msg009661'],
['SSL_cipher_list','SSL key cipher list',120,\&textinput,'','(.*)','ConfigChangeSSL',
 'If this option is set, the cipher list for the connection will be set to the given value, e.g. something like \'ALL:!LOW:!EXP:!ADH\'. Look into the OpenSSL documentation (<a href="http://www.openssl.org/docs/apps/ciphers.html#CIPHER_STRINGS" rel="external">http://www.openssl.org/docs/apps/ciphers.html#CIPHER_STRINGS</a>) for more details. Setting this value causes the \'SSL_honor_cipher_order\' flag to be switched on (BEAST vulnerable)<br />
 If this option is not used (default) the openssl builtin default is used which is suitable for most cases.',undef,undef,'msg009670','msg009671'],
['SSLLog','Enable SSL logging','0:nolog|1:standard|2:verbose|3:diagnostic',\&listbox,1,'(.*)',undef,''],
['NoTLSlistenPorts','Disable SSL support on listenPorts',40,\&textinput,'','(.*)','ConfigChangeTLSPorts',
  'This disables TLS/SSL on the listenPorts listenPort , listenPort2 and relayPort . The listener definition here has to be the same like in the port definitions. Separate multiple entries by "|".<p><small><i>Examples:</i> 25, 127.0.0.1:25, 127.0.0.1:25|127.0.0.2:25 </small></p>',undef,undef,'msg008220','msg008221'],
['noTLSIP','Exclude these IP addresses and Hostnames from TLS*',120,\&textinput,'file:files/notls.txt','(\S*)','ConfigMakeIPRe','Enter IP addresses and Hostnames that you want to exclude from starting TLS. For example, put all IP addresses here, which have trouble to switch to TLS every time.'],
['banFailedSSLIP','Ban Failed SSL IP','0:disable|1:privat only|2:public only|3:both',\&listbox,2,'(\d*)',undef,
 'If set (recommended is \'both\'), an IP that fails to connect via SSL/TLS will be banned for 12 hour from using SSL/TLS.<br />
  Privat IP\'s and IP addresses listed in \'acceptAllMail\' will get one more try to correct the mistake.<br />
  This is done per default (\'both\'), to prevent possible DoS attacks via SSL/TLS.<br />
  Those IP\'s are stored in the SSLfailed cache. This cache is cleaned up at startup.<br />
  disable - disables this feature, which is highly NOT recommended<br />
  privat only - only privat IP\'s and IP\'s in acceptAllMail will be banned (they have two tries)<br />
  public only - only public IP\'s will be banned<br />
  both - privat and public IP\'s will be banned<br />
  <input type="button" value=" Edit Failed SSL Cache" onclick="javascript:popFileEditor(\'pb/pbdb.ssl.db\',6);" />',undef,undef,'msg010100','msg010101'],
['SSLCertFile','SSL Certificate File (PEM format)',48,\&textinput,$dftCertFile,'(\S*)','ConfigChangeSSL',
  "Full path to the file containing the server's SSL certificate, for example : \'/etc/ssl/certs/yourdomain.com.crt\' or \'c:/assp/certs/server-cert.pem\'. A general cert.pem file is already provided in $dftCertFile" ,undef,undef,'msg008230','msg008231'],
['SSLKeyFile','SSL Key File (PEM format)',48,\&textinput,$dftPrivKeyFile,'(\S*)','ConfigChangeSSL',
  "Full path to the file containing the server\'s SSL privat key, for example: \'/etc/ssl/private/yourdomain.com.key\' or \'/usr/local/etc/ssl/certs/assp-key.pem\' or \'c:/assp/certs/server-key.pem\'. A general key.pem file is already provided in $dftPrivKeyFile " ,undef,undef,'msg008240','msg008241'],
['SSLPKPassword','SSL Privat Key Password',48,\&passinput,'','(.*)',undef,
  "Optional parameter. If your privat key ' SSLKeyFile ' is password protected, assp will need this password to decrypt the server\'s SSL privat key file.",undef,undef,'msg009540','msg009541'],
['SSLCaFile','SSL Certificate Authority File',48,\&textinput,'','(\S*)','ConfigChangeSSL',
  "Optional parameter to enable chained certificate validation at the client side. Full path to the file containing the server's SSL certificate authority, for example : /usr/local/etc/ssl/certs/assp-ca.crt or c:/assp/certs/server-ca.crt. A general ca.crt file is already provided in '$dftCaFile'. The default value is empty and leave it empty as long as you don't know, how this parameter works.",undef,undef,'msg009530','msg009531'],
['listenPortSSL','SMTPS Listen Port',10,\&textinput,465,'(.*)','ConfigChangeMailPortSSL',
  'The port number on which ASSP will listen for SMTPS connections. This is only for legacy clients like Eudora. Hint: If you set this port to 465, you must not set "listenPort" or "listenPort2" to 465.
<p><small><i>Examples:</i> 465 </small></p>'],
['smtpDestinationSSL','SSL Destination',40,\&textinput,'','(.*)',undef,
  'The IP <b>address!</b> and port number to connect to when mail is received on listenPortSSL. smtpDestinationSSL must point to a plaintext port on the MTA. If the field is blank, the primary SMTP destination will be used. <p><small><i>Examples:</i>127.0.0.1:565, 565</small></p>',undef,undef,'msg000060','msg000061'],
['SSLtimeout','SSL Timeout (0-999)',10,\&textinput,5,'(\d{1,3})',undef,
 'SSL/TLS negotiation will timeout after this many seconds. default is : 5 seconds.',undef,undef,'msg008280','msg008281'],
['SSLDEBUG','Debug Level for SSL/TLS','0:no Debug|1:level 1|2:level 2|3:level 3',\&listbox,0,'(.*)',undef,'Set the debug-level for SSL/TLS. Increasing the level will produce more information to STDOUT<hr /><div class="menuLevel1">Notes On SSL Setup</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/ssl.txt\',3);" />'],

[0,0,0,'heading','Automatic Update / Restart'],

['AutoUpdateASSP','Auto Update the Running Script (assp.pl)','0:no auto update|1:download only|2:download and install',\&listbox,0,'(.*)','ConfigChangeAutoUpdate',
 'No action will be done if \'no auto update\' is selected. You\'ll get a hint in the GUI (top) and a log line will be written, if a new version is availabe at the download folder.<br />
  If \'download only\' is selected and a new assp version is available, this new version will be downloaded to the directory ' . $base . '/download (assp.pl) and the syntax will be checked. The still running script will be saved version numbered to the download directory. A Changelog is also downloaded.<br />
  If \'download and install\' is selected, in addition the still running script  will be replaced by the new version. No settings or option files are changedd. Read the Changelog for recommended new option files. <input type="button" value=" Changelog" onclick="return popFileEditor(\'/docs/changelog.txt\',8);" /><br />
  Configure ( AutoRestartAfterCodeChange ), if you want the new version to become the active running script.<br />
  The perl module <a href="http://search.cpan.org/dist/Compress-Zlib/" rel="external">Compress::Zlib</a> is required to use this feature. <input type="button" value=" Auto Update History" onclick="return popFileEditor(\'/notes/updatehistory.txt\',8);" />'],
['AutoUpdateNow','Run Auto Update Now',0,\&checkbox,'','(.*)','ConfigChangeRunTaskNow', "If selected, ASSP will run Auto Update. <input type=button value=\"Run Now!\" onclick=\"document.forms['ASSPconfig'].theButtonX.value='Apply Changes';document.forms['ASSPconfig'].submit();WaitDiv();return false;\" />&nbsp;<input type=button value=\"Refresh Browser\" onclick=\"document.forms['ASSPconfig'].theButtonRefresh.value='Apply Changes';document.forms['ASSPconfig'].submit();WaitDiv();return false;\" />"],
['AutoRestartAfterCodeChange','Automatic Restart ASSP on new or changed assp.pl Script',40,\&textinput,'','^(|immed|[1-9]|1[0-9]|2[0-3])$',undef,'If selected, ASSP will restart it self, if it detects a new or changed running script. An automatic restart will be done only, if ASSP runs as a Service on Windows or AutoRestartCmd is configured. Leave this field empty to disable the feature. Possible values are \'immed and 1...23\' . If set to \'immed\', assp will restart within some seconds after a detected code change. If set to \'1...23\' the restart will be scheduled to that hour. A restart at 00:00 is not supported.'],
['AutoRestart','Automatic Restart after Exception',0,\&checkbox,1,'(.*)',undef,'If ASSP detects a main exception and a AutoRestartCmd, it will try to restart itself. '],
['MainloopTimeout','Mainloop Timeout',10,\&textinput,300,'(.*)',undef, 
'Mainloop will timeout after this many seconds.'],
['AutoRestartAfterTimeOut','Automatic Restart after Timeout',0,\&checkbox,'','(.*)',undef,'If ASSP detects a mainloop timeout and a AutoRestartCmd is configured, it will try to restart itself. '],
['AutoRestartCmd','OS-shell command for AutoRestart',120,\&textinput,"$dftrestartcmd",'(.*)',undef,'The OS level shell-command that is used to autorestart ASSP, if it runs not as a service. A possible value for your system is:<br /><font color=blue>'.$dftrestartcmd.'</font>. Put a dummy command here <font color=blue>\'cd .\'</font>, if ASSP runs inside an external loop.'.$dftrestartcomment. ''],
['AutoRestartInterval','Restart Interval',10,\&textinput,0,'(.*)',undef,
  'ASSP will automatically terminate and restart after this many hours. Use this setting to periodically reload configuration data, combat potential memory leaks, or perform shutdown/startup processes. This will only work properly if ASSP runs as a Windows service or AutoRestartCmd is configured.'],
['NoMultipleASSPs','Prevent Multiple ASSP Processes',0,\&checkbox,1,'(.*)',undef,'If set, ASSP will try to find out, if it is already running.<br /><hr /><div class="menuLevel1">Notes On  Automatic Update / Restart</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/updaterestart.txt\',3);" />'],

[0,0,0,'heading','Administration GUI'],

['webAdminPort','Web Admin Port',40,\&textinput,55555,'(.*)','ConfigChangeAdminPort',
  'The port on which ASSP will listen for http connections to the web administration interface. You may also supply an IP address or hostname to limit connections to a specific interface. Separate multiple entries by pipe "|"!<p><small><i>Examples:</i> 55555, 192.168.0.5:12345, myhost:12345, 192.168.0.5:22345|myhost:12345</small></p>'],
['enableWebAdminSSL','Use https instead of http',0,\&checkbox,'','(.*)','ConfigChangeEnableAdminSSL',
 'If selected the web admin interface will be only accessable via https. <span class="positive"> After you click Apply of a change here you must change the URL(to https) on your browser to reconnect</span>.
  This requires an installed IO::Socket::SSL module in PERL.<br />
  A server-certificate-file ( SSLCertFile ) and a server-key-file (SSLKeyFile) must exist and must be valid!<br />
  If you do not have valid certificates, you may generate both files online with <a href="http://www.mobilefish.com/services/ssl_certificates/ssl_certificates.php" rel="external">www.mobilefish.com</a> or you may use OpenSSL to generate <a href="http://www.mobilefish.com/developer/openssl/openssl_quickguide_self_certificate.html" rel="external">Self-signed SSL certificates</a>!',undef,undef,'msg007640','msg007641'],
['webAdminPassword','Web Admin Password',40,\&passinput,'nospam4me','(.*)','ConfigChangePassword',
  'The password for the web administration interface (minimum of 5 characters, max 8 characters will be used).'],
['allowAdminConnectionsFrom','Only Allow Admin Connections From*',120,\&textinput,'','(\S*)','ConfigMakeIPRe',
  'An optional list of IP addresses and/or hostnames from which you will accept web admin connections. Blank means accept connections from any IP address. Connections from localhost(127.0.0.1) are always possible.',undef,'7','msg007660','msg007661'],
['webStatPort','Raw Statistics Port',10,\&textinput,55553,'(\S+)','ConfigChangeStatPort',
  'The port on which ASSP will listen for http connections to the statistics interface. You may also supply an IP address to limit connections to a specific interface.<p><small><i>Examples:</i> 55553, 192.168.0.5:12345</small></p>'],
['allowStatConnectionsFrom','Only Allow Raw Statistics Connections From*',120,\&textinput,'127.0.0.1','(.*)','ConfigMakeIPRe',
  'An optional list of IP addresses from which you will accept raw statistical connections. Blank means accept connections from any IP address. <p><small><i>Examples:</i></small></p>
127.0.0.1|172.16.','','7'],
['enableWebStatSSL','Use https instead of http',0,\&checkbox,'','(.*)','ConfigChangeEnableStatSSL',
 'The web stat interface will be only accessable via https.
  This requires an installed IO::Socket::SSL module in PERL.<br />
  A server-certificate-file "certs/server-cert.pem" and a server-key-file "certs/server-key.pem" must exits and must be valid!',undef,undef,'msg007680','msg007681'],
['SaveStatsEvery','Statistics Save Interval',10,\&textinput,0,'(\d+)',undef,
  'This period (in minutes) determines how frequently ASSP statistics are written to a local file.'],
['EnableHTTPCompression','Enable HTTP Compression in GUI',0,\&checkbox,'','(.*)',undef,
  'Enable HTTP Compression for faster web administration interface loading. The perl module <a href="http://search.cpan.org/dist/Compress-Zlib/" rel="external">Compress::Zlib</a> is required to use this feature.'],
['hideAlphaIndex','Hide the Alpha Index Menu Panel in GUI',0,\&checkbox,'','(.*)',undef,
  'Removes the alphanumeric index panel on the left side in the GUI, but the index is accessable by clicking on "Index".'],
['IndexSlideSpeed','Sliding Speed of the Alpha Index Menu Panel in GUI','450:no slide|50:fast|10:normal|5:slow',\&listbox,10,'(.*)',undef,
  'Adjust the sliding speed of the Alpha Index Menu Panel in GUI to your needs.'],
['EnableFloatingMenu','Enable Floating Menu Panel in GUI',0,\&checkbox,'','(.*)',undef,
  'Allow the menu panel on the web administration interface to float (floating Div code taken from <a href="http://www.javascript-fx.com" rel="external">www.javascript-fx.com</a>).'],
['EnableInternalNamesInDesc','Show Internal Names in the GUI',0,\&checkbox,1,'(.*)',undef,
  'Show the internal names in the web interface. The internal names are used in the configuration file (assp.cfg), in the application code, and in the menu bar on the left side of the GUI.',undef,undef,'msg007740','msg007741'],
['MaillogTailJump','Jump to the End of the Maillog',0,\&checkbox,'','(.*)',undef,
  'Causes the browser window to jump to the bottom of the maillog instead of sitting at the top of the display.'],
['MaillogTailBytes','Maillog Tail Bytes',10,\&textinput,10000,'(\d+)',undef,
  'The number of bytes that will be shown when the end of the maillog is viewed. The default value is 10000.'],
['MaillogTailWrap','Maillog Tail Wrap',0,\&checkbox,1,'(.*)',undef,
  'Force maillog lines to wrap if there are too many characters in a line to fit into the window-width. '],
['MaillogTailOrder','Maillog Tail Order',0,\&checkbox,'','(.*)',undef,
  'Reverse the time order of line '],
['MaillogTailColorLine','Maillog Tail Color Line',0,\&checkbox,1,'(.*)',undef,
  'Color alternate lines . <hr />
  <div class="menuLevel1">Notes On Administration Interface</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/ai.txt\',3);" />'],

[0,0,0,'heading','Server Setup'],

['MaintenanceLog','Enable Maintenance logging','0:nolog|1:standard|2:verbose|3:diagnostic',\&listbox,1,'(.*)',undef,''],
['ConsoleCharset','Charset for STDOUT and STDERR',$Charsets,\&listbox,0,'(.*)',undef,
 'Set the character set for the console output to your local needs. Best on non Windows systems is "utf8" if available or "System Default" - no conversion. '],
['LogCharset','Charset for Maillog',$Charsets,\&listbox,$defaultLogCharset,'(.*)',undef,
 'Set the character set/codepage for the maillog output to your local needs. Best on non Windows systems is "utf8" if available or "System Default" - no conversion. On Windows systems set it to your local codepage or UTF-8 (chcp 65001). To display nonASCII characters in the subject line and maillog files names setup decodeMIME2UTF8 .'],
['decodeMIME2UTF8','Decode MIME Words To UTF-8',1,\&checkbox,1,'(.*)',undef,'If selected, ASSP decodes MIME encoded words to UTF8. This enables support for national languages to be used in Bombs , Scripts , Spamdb , Logging. If not selected, only US-ASCII characters will be used for this functions. This requires an installed Email::MIME module in PERL.'],
['AsAService','Run ASSP as a Windows Service',0,\&checkbox,'','(.*)',undef,'In Windows NT/2000/XP/2003 ASSP can be installed as a service. This setting tells ASSP that this has been done -- it does not install the Windows service for you. Installing ASSP as a service requires several steps which are detailed in the <a href="http://apps.sourceforge.net/mediawiki/assp/index.php?title=Win32">Quick Start for Win32</a> doku page.<br /> Information about the Win32::Daemon module which which is necessary can be found here: <a href="http://www.roth.net/perl/Daemon/">The Official Win32::Daemon Home Page</a><br /><span class="negative"> requires ASSP restart</span>'],
['AsADaemon','Run ASSP as a Daemon',0,\&checkbox,'','(.*)',undef,'In Linux/BSD/Unix/OSX fork and exit. Similar to the command "perl assp.pl &amp;", but better.<br />
  <span class="negative"> Changing this requires a restart of ASSP.</span>'],
['runAsUser','Run as UID',40,\&textinput,'','(\S*)',undef,'The *nix user name to assume after startup (*nix only).<p><small><i>Examples:</i> assp, nobody</small></p>
  <span class="negative"> Changing this requires a restart of ASSP.</span>'],
['runAsGroup','Run as GID',40,\&textinput,'','(\S*)',undef,'The *nix group to assume after startup (*nix only).<p><small><i>Examples:</i> assp, nobody</small></p>
  <span class="negative"> Changing this requires a restart of ASSP.</span>'],
['ChangeRoot','Change Root',40,\&textinput,'','(.*)',undef,'The new root directory to which ASSP should chroot (*nix only). If blank, no chroot jail will be used. Note: if you use this feature, be sure to copy or link the etc/protocols file in your chroot jail.<br />
  <span class="negative"> Changing this requires a restart of ASSP.</span>'],
['setFilePermOnStart','Set ASSP File Permission on Startup',0,\&checkbox,'','(.*)',undef,'If set, ASSP sets the permission of all ASSP- files and directories at startup to full (0777) - without any function on windows systems!',undef,undef,'msg007480','msg007481'],
['checkFilePermOnStart','Check ASSP File Permission on Startup',0,\&checkbox,'','(.*)',undef,'If set, ASSP checks the permission of all ASSP- files and directories at startup - all files must be writable for the running job - the minimum permission is 0600 - without any function on windows systems!',undef,undef,'msg007490','msg007491'],
['myName','My Name',40,\&textinput,'ASSP.nospam','(\S+)',undef,'ASSP will identify itself by this name in the email "Received:" header and in the helo when sending report-replies. Usually the fully qualified domain name of the host.<p><small><i>Examples:</i> assp.example.com</small></p>'],
['HideIP','Hide IP ',40,\&textinput,'','(.*)',undef,'replace the IP in our received header for outgoing mails.'],
['HideHelo','HideHelo',40,\&textinput,'','(.*)',undef,'replace the Helo in our received header for outgoing mails. '],
['myHelo','My Helo','0:transparent|1:use myName|2:use Hostname|3:use IP',\&listbox,1,'(\S+)',undef,'How ASSP will identify itself when connecting to the target MTA. 
<br>transparent - the Helo of the sender will be used
<br>use myName - myName will be used
<br>use Hostname - name of host assp is running on, should be a fully qualified FQDN
<br>use IP - IP will be used'],
['HideIPandHelo','Hide IP and/or Helo',40,\&textinput,'','(.*)',undef,'Replace any of these information ( ip=127.0.0.1 helo=anyhost.local ) in our received header for outgoing mails. use the syntax ip=127.0.0.1 and/or helo=anyhost.local .',undef,undef,'msg009830','msg009831'],
['myGreeting','Override the Server SMTP Greeting',120,\&textinput,'','(.*)',undef,'Send this SMTP greeting (eg. 220 MYNAME is ready - using ASSP VERSION) instead of your MTA\'s SMTP greeting to the client. If not defined (default), the MTA\'s greeting will be sent to the client. The literal MYNAME will be replaced with myName and the literal VERSION will be replaced by the full version string of assp. If the starting \'220 \' is not defined, assp will add it to the greeting.',undef,undef,'msg010260','msg010261'],
['asspCfg','assp.cfg',120,\&textnoinput,'file:assp.cfg','(.*)','undef','For internal use only : assp.cfg file.'],
['AutoReloadCfg','Automatic Reload ConfigFile',0,\&checkbox,'','(.*)',undef,'If selected and the assp.cfg file is changed externally, ASSP will reload the configuration from the file.'],
['asspCfgVersion','assp.cfg version',120,\&textnoinput,'','(.*)',undef,'ASSP will identify the assp.cfg file. Do not change this.',undef,undef,'msg007590','msg007591'],
['proxyserver','Proxy Server',120,\&textinput,'','(\S*)',undef,'The Proxy Server to use when uploading global statistics and downloading the greylist.<p><small><i>Examples:</i> 192.168.0.1:8080, 192.168.0.1</small></p>'],
['proxyuser','Proxy User',40,\&textinput,'','(\S*)',undef,'The Proxy-UserName that is used to authenticate to the proxy.'],
['proxypass','Proxy Password',40,\&passinput,'','(\S*)',undef,'The password for Proxy-UserName that is used to authenticate to the proxy.'],
['OutgoingBufSizeNew','Size of TCP/IP Buffer',10,\&textinput,10240000,'(\d+)',undef,
 'If ASSP talks to the internet over a modem change this to 4096.'],
['HouseKeepingSchedule','Starting time for HouseKeeping',10,\&textinput,3,'(\d+)','configChangeHKSched','ASSP uses the scheduled hour to run cache-housekeeping. For example \'3\' will run cache-housekeeping at 3.00. Use 24 to run it at midnight.'],
['totalizeSpamStats','Upload Consolidated Spam Statistics',0,\&checkbox,1,'(.*)',undef,
 'ASSP will upload its statistics to be consolidated with the <a href="http://assp.sourceforge.net/cgi-bin/assp_stats?stats" rel="external">global ASSP totals</a>. This is a great marketing tool for the ASSP project &mdash; please do not disable it unless you have a good reason to do so. No private information is being disclosed by this upload.',undef,undef,'msg007800','msg007801'],
['ReloadOptionFiles','Reload Option Files Interval',10,\&textinput,60,'(\d+)',undef,
  'If set not to zero, ASSP reloads configuration option files (file:.....) every this many seconds if they have changed externally.'],
['OrderedTieHashTableSize','Ordered-Tie Hash Table Size',10,\&textinput,50000,'(\d+)',undef,
 'The number of entries allowed in the hash tables used by ASSP and rebuildspamdb.pl. Larger numbers require more more RAM but result in fewer disk hits. The default value is 5000.'],
['UseLocalTime','Use Local Time',0,\&checkbox,1,'(.*)',undef,
  'Use local time and timezone offset rather than UTC time in the mail headers.<br /><hr />
  <div class="menuLevel1">Notes On Server Setup</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/myserver.txt\',3);" />'],

[0,0,0,'heading','Rebuild SpamDB'],

['RebuildSchedule','Schedule time for RebuildSpamdb',40,\&textinput,'6|18','(.*)|','configChangeRBSched','If not set to 0 ASSP uses scheduled hours to run RebuildSpamdb.pl. For example \'6|18\' will run rebuildspamdb.pl at 6.00 and 18.00. Use 24 to run it at midnight. \'*\' will schedule it every hour. <input type="button" value=" rebuildspamdb.pl log" onclick="return popFileEditor(\'rebuildrun.txt\',8);" /> '],
['RebuildCmd','OS-shell command for starting rebuildspamdb.pl',120,\&textinput,'','(.*)',undef,'The OS level shell-command that is used to start rebuildspamdb.pl, if it runs not as a cronjob. A possible value for your system is:<br /><font color=blue>'.$dftrebuildcmd.'</font><br />You may overwrite it with your own script. Note that the parm \'silent\' must be used. For example to run as user root: su -m assp -c "/usr/bin/perl /usr/local/share/assp/rebuildspamdb.pl
/var/db/assp silent &"'],
['RebuildNow','Run RebuildSpamdb Now',0,\&checkbox,'','(.*)','ConfigChangeRunTaskNow', "If selected, ASSP will run RebuildSpamdb.pl now. <input type=button value=\"Run Now!\" onclick=\"document.forms['ASSPconfig'].theButtonX.value='Apply Changes';document.forms['ASSPconfig'].submit();WaitDiv();return false;\" />&nbsp;<input type=button value=\"Refresh Browser\" onclick=\"document.forms['ASSPconfig'].theButtonRefresh.value='Apply Changes';document.forms['ASSPconfig'].submit();WaitDiv();return false;\" />"],
['RebuildNotify','Notification Email To',120,\&textinput,'','(.*)',undef,
  'Email address(es) to which you want ASSP to send a notification email after the rebuild task is finished. Separate multiple entries by "|". If empty no notify will take place. This requires an installed Email::Send module in PERL.'],
['MaxNoBayesFileAge','Max Age of non Bayes Files',10,\&textinput,31,'(\d+)',undef,
  'The maximum file age in days of every file in every non bayesian collection folder ( incomingOkMail , discarded , viruslog ). If defined and a file is older than this number in days, the file will be deleted. '],
['MaxKeepDeleted','Max Days of Keep Deleted',10,\&textinput,7,'(\d+)',undef,
  'The maximum number in days deleted files in the bayesian collection folders ( spamlog , notspamlog ) will be kept. This is necessary when EmailBlockReport is used to handle the file and the file is meanwhile deleted. The list of files that are marked for deletion is stored in trashlist.db .',undef,undef,'msg008650','msg008651'],
['autoCorrectCorpus','Automatic Corpus Correction','0:disabled|1:standard|0.8:soft|1.2 :sharp',\&listbox,1,'(.*)',undef,
  ' The spamdb norm is the relation of good words to bad words extracted by rebuildspamdb.pl. Setting this to standard will correct the spamdb with a norm of 1, soft will correct with 0.8 which let more mails pass the bayesian check. Sharp will correct with 1.2 which let less mails pass the bayesian check.'],
['MaxCorrectedDays','Max Corrected File Age',10,\&textinput,1000,'(\d+)',undef,'This is the number of days a error report will be kept in the correctednotspam and correctedspam folders.<br /><hr />
  <div class="menuLevel1">Notes On Rebuild</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/rebuild.txt\',3);" /> '],

[0,0,0,'heading','POP3 Collecting'],

['POP3ConfigFile','POP3 Configuration File*',120,\&textinput,'file:files/pop3cfg.txt','(file\:.+)','ConfigChangePOP3File',
  'The file with a valid POP3 configuration. Only the file: option is allowed to use. <br />
  If the file exists and contains at least one valid POP3 configuration line and POP3Interval is configured, assp will collect the messages from the configured POP3-servers. <br />
  Each line in the config file contains one configuration for one user.<br />
  All spaces will be removed from each line.<br />
  Anything behind a # or ; is consider a comment.<br />
  If the same POP3-user-name is used multiple times, put two angles with a unique number behind the user name. The angles and the number will be removed while processing the configuration.<br />
  e.g: pop3user&lt;1&gt; will result in pop3user  -  or  - myName@pop3.domain&lt;12&gt; will result in myName@pop3.domain<br />
  It is possible to define commonly used parameters in a separate line, which begins with the case sensitive POP3-username "COMMON:=" - followed by the parameters that should be used for every configured user.<br />
  A commonly set parameter could be overwritten in every user definition.<br />
  Each configuration line begins with the POP3-username followed by ":=" : e.g myPOP3userName:=<br />
  This statement has to followed by pairs of parameter names and values which are separated by commas - the pairs inside are separated by "=". <br />
  e.g.: POP3username<num>:=POP3password=pop3_pass,POP3server=mail.gmail.com,SMTPsendto=demo@demo_smtp.local,......<br />
  The following case sensitive keywords are supported in the config file:<br /><br />
  POP3password=pop3_password<br />
  POP3server=POP3-server or IP[:Port]<br />
  SMTPsender=email_address<br />
  SMTPsendto=email_address or &lt;TO:&gt; or &lt;TO:email_address&gt;<br />
  SMTPserver=SMTP-server[:Port]<br />
  SMTPHelo=myhelo<br />
  SMTPAUTHuser=smtpuser<br />
  SMTPAUTHpassword=smtppass<br />
  POP3SSL=0/1<br /><br />
  POP3SSL, SMTPHelo, SMTPsender, SMTPAUTHuser and SMTPAUTHpassword are optional.<br />
  If POP3SSL is set to 1 - POP3S will be done! The Perl module <a href="http://search.cpan.org/search?query=IO::Socket::SSL" rel="external">IO::Socket::SSL</a> is required for POP3S!<br />
  If SMTPsender is not defined, the FROM: address from the header line will be used - if this is not found the POP3username will be used.<br />
  If the &lt;TO:&gt; syntax is used for SMTPsendto, the mail will be sent to any recipient that is found in the "to: cc: bcc:" header lines if it is a local one.<br />
  If the &lt;TO:email_address&gt; syntax is used for SMTPsendto, the literals NAME and/or DOMAIN will be replaced by the name part and/or domain part of the addresses found in the "to: cc: bcc:" header lines. This makes it possible to collect POP3 mails from a POP3 account, which holds mails for multiple recipients.<br />
  For example: &lt;TO:NAME@mydomain.com&gt;  or  &lt;TO:NAME@subdomain.DOMAIN&gt;  or  &lt;TO:central-account@DOMAIN&gt;<br />
  If the &lt;TO:&gt; or &lt;TO:email_address&gt; syntax is used for SMTPsendto, "localDomains" and/or "localAdresses_Flat" must be configured to prevent too much error for wrong recipients defined in the "to: cc: bcc:" header lines. The POP3collector will not do any LDAP or VRFY query!<br />
  If you want assp to detect SPAM, use the listenPort or listenPort2 as SMTP-server.<br />
  To use this feature, you have to install the perl script "assp_pop3.pl" in the assp-base directory.',undef,undef,'msg009070','msg009071'],
['POP3Interval','POP3 Collecting Interval',10,\&textinput,0,'(\d+)',undef,'The interval in minutes, assp should collect messages from the configured POP3-servers. A value of zero disables this feature.'],
['POP3KeepRejected','POP3 Keep Rejected Mails on POP3 Server',0,\&checkbox,'','(.*)',undef, 'If selected, any collected POP3 mail that fails to be sent via SMTP will be kept on the POP3 server.'],
['POP3debug','POP3 debug',0,\&checkbox,'','(.*)',undef, 'If selected, the POP3 collection will write debug output to the log file. Do not use it, unless you have problems with the POP3 collection!
  <div class="menuLevel1">Notes On POP3 collecting</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/pop3collect.txt\',3);" />',undef,undef,'msg009090','msg009091'],

[0,0,0,'heading','Extras'],

['EnableWatchdog','Enable Watchdog ',0,\&checkbox,'','(.*)','ConfigChangeWatchdog',''],
['WatchdogHeartBeat','Restart/Kill after this many Seconds ',10,\&textinput,600,'(.*)','ConfigChangeWatchdog',''],
['WatchdogRestart','Kill & Restart',0,\&checkbox,1,'(.*)','ConfigChangeWatchdog',
  'Enabling this will ask the Watchdog to restart ASSP, disabling this will only kill ASSP. AutoRestartCmd must be configured.'],
['AutostartSecondary','Enable AutoStart Secondary ',0,\&checkbox,'','(.*)','ConfigChangeSecondary','This is also used to start/stop the \'Secondary\'. Switching this to OFF will terminate the Secondary after some seconds. Switching this to ON will start the \'Secondary\'. Sometimes It may be necessary to cleanup AutostartSecondary. Disabling it and enabling it will remove the pid_Secondary and restart the \'Secondary\' clean. <input type="button" value=" Secondary PID" onclick="return popFileEditor(\'pid_Secondary\',3);" />'],
['SecondaryCmd','OS-shell command for AutoStart Secondary AI',120,\&textinput,'','(.*)',undef,'The OS level shell-command that is used to overwrite the default command for starting ASSP as a secondary administration interface if AutostartSecondary is enabled. The default value for your system is:<br /><font color=blue>'. $startsecondcmd.'</font>'],
['webSecondaryPort','Web Admin Port for Second Instance of ASSP ',40,\&textinput,22222,'(.*)',undef,
  'The port on which a second instance of ASSP will listen for http connections to the web administration interface (instead of 55555). BlockReportHTTPName must be set. The second instance \'Secondary\' will run without SMTP connections and can be used for heavy search use of the \'Maillog Tail\' tool.<br /><hr />'],  
);
$Config{AsASecondary} = "";

my $i = 0; 
foreach (@Config) { 
  $Config[$i]->[0] =~ s/\r?\n//g; 
  $Config[$i]->[1] =~ s/\r?\n//g; 
  $Config[$i]->[2] =~ s/\r?\n//g; 
  $Config[$i]->[3] =~ s/\r?\n//g; 
  $Config[$i]->[4] =~ s/\r?\n//g; 

  $i++; 
}

sub strip50 {
            $_[0] = substr($_[0],0,20). '.....'. substr($_[0],length($_[0])-20,20) if (length($_[0]) > 50);
}

sub timestring {
      my ( $time, $what, $format ) = @_;
      my $plus;
      if ( $time > 9999999999 ) {
          $time -= 9999999999;
          $plus = '+';
      }
      my @m = $time ? localtime($time) : localtime();
      my $day   = substr( $Day_to_Text[$LogDateLang][ $m[6] - 1 ], 0, 3 );
      my $month = substr( $Month_to_Text[$LogDateLang][ $m[4] ],   0, 3 );
      Encode::from_to( $day,   'ISO-8859-1', 'UTF-8' ) if $day;
      Encode::from_to( $month, 'ISO-8859-1', 'UTF-8' ) if $month;
      $format = $LogDateFormat unless $format;
      if ( lc $what eq 'd' ) {    # date only - remove time part from format
          $format =~ s/[^YMD]*(?:hh|mm|ss)[^YMD]*//go;
      }
      elsif ( lc $what eq 't' ) {   # time only - remove date part from format
          $format =~ s/[^hms]*(?:Y{2,4}|M{2,3}|D{2,3})[^hms]*//go;
      }
      my $bc;
      $bc = '(BC)' if ( $format =~ /YYYY/o && ( $m[5] + 1900 ) < 0 );
      $format =~ s/^[^YMDhms]//o;
      $format =~ s/[^YMDhms]$//o;
      $format =~ s/\s+/ /go;
      $format =~ s/YYYY/sprintf("%04d",abs($m[5]+1900))/eo;
      $format =~ s/YY/sprintf("%02d",($m[5]>99?$m[5]-100:$m[5]))/eo;
      $format =~ s/MMM/$month/o;
      $format =~ s/MM/sprintf("%02d",$m[4]+1)/eo;
      $format =~ s/DDD/$day/o;
      $format =~ s/DD/sprintf("%02d",$m[3])/eo;
      $format =~ s/hh/sprintf("%02d",$m[2])/eo;
      $format =~ s/mm/sprintf("%02d",$m[1])/eo;
      $format =~ s/ss/sprintf("%02d",$m[0])/eo;
      return $plus . $format . $bc;
}

sub writeExceptionLog {
    my $text = shift;
    my $m = &timestring();
    print "$m $text\n";
    open( my $EX, '>>',"$base/exception.log" );
    print $EX "$m $text\n";
    close $EX;
    1;
}

sub ftime { [stat($_[0])]->[9]; }

sub ASSPisRunning {
    my $pid = shift;
    if ($^O eq 'MSWin32') {

        my @tasks = `tasklist /v /nh`;
        if (@tasks) {
            return 1 if (grep(/perl[^\n]+? $pid /,@tasks));
            return 0;
        }
        return 0 if !$pid;
        return 1 if (kill 0, $pid);
        return 0;
    } else {
         return 0 if !$pid;
        return 1 if (kill 0, $pid);
        return 0;
    }

}

sub setLocalCharsets {
    $Charsets = '0:System Default|';
    $defaultLogCharset = 0;
    foreach (Encode->encodings(':all')) {
        $Charsets .= $_ . ':' . $_ . '|' if $_ !~ /mime|symbol|null|nextstep/io;
        $defaultLogCharset = $_ if ($^O ne 'MSWin32' &&
                                    $defaultLogCharset !~ /^utf-?8/io &&
                                    $_ =~ /^utf-?8/io);
    }
    chop $Charsets;
}

sub setClamSocket {

    $defaultClamSocket = "3310";
    $defaultClamSocket = "/var/run/clamav/clamd.ct"    if $^O ne 'MSWin32';
    $defaultClamSocket = "/private/tmp/clamd"    if $^O eq 'darwin';

}

# imported from IO :: Socket version 1.30_01 to handle MSWIN32 blocking mode
# modified by Thomas Eckardt to use a real long pointer
sub assp_blocking {
    my $sock = shift;

    return $sock->SUPER::blocking(@_)
        if $^O ne 'MSWin32';

    # Windows handles blocking differently
    #
    # http://groups.google.co.uk/group/perl.perl5.porters/browse_thread/thread/b4e2b1d88280ddff/630b667a66e3509f?#630b667a66e3509f
    # http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winsock/winsock/ioctlsocket_2.asp
    #
    # http://www.perlmonks.org/?node_id=780083   /TE
    #
    # 0x8004667e is FIONBIO
    #
    # which is used to set blocking behaviour.

    # NOTE:
    # This is a little confusing, the perl keyword for this is
    # 'blocking' but the OS level behaviour is 'non-blocking', probably
    # because sockets are blocking by default.
    # Therefore internally we have to reverse the semantics.

    my $orig= !${*$sock}{io_sock_nonblocking};

    return $orig unless @_;

    my $block = shift;

    my $nonblocking = "\x00\x00\x00\x01"; # pack("L",1) works too
    my $blocking = "\x00\x00\x00\x00"; # pack("L",0) works too
    my $FIONBIO = 0x8004667e;

    if ( !$block != !$orig ) {
        ${*$sock}{io_sock_nonblocking} = $block ? $blocking : $nonblocking;
        ioctl($sock, $FIONBIO, unpack('I',pack('P',${*$sock}{io_sock_nonblocking})))
            or return;
    }

    return $orig;
}

sub assp_parse_attributes { 
    local $_ = shift; 
    my $attribs = {}; 
    my $tspecials = quotemeta '()<>@,;:\\"/[]?='; 
    while ($_) { 
        s/^;//; 
        s/^\s+// and next; 
        s/\s+$//; 
        unless (s/^([^$tspecials]+)=//) { 
          # We check for $_'s truth because some mail software generates a 
          # Content-Type like this: "Content-Type: text/plain;" 
          # RFC 1521 section 3 says a parameter must exist if there is a 
          # semicolon. 
#          mlog(0,"Illegal Content-Type parameter $_") if $_; 
          return $attribs; 
        } 
        my $attribute = lc $1; 
        my $value = assp_extract_ct_attribute_value(); 
        $attribs->{$attribute} = $value; 
    } 
    return $attribs; 
} 
# substitute for Email::MIME::ContentType::_extract_ct_attribute_value
# to prevent carping

sub assp_extract_ct_attribute_value {
    my $value;
    my $tspecials = quotemeta '()<>@,;:\\"/[]?=';
    my $extract_quoted =
        qr/(?:\"(?:[^\\\"]*(?:\\.[^\\\"]*)*)\"|\'(?:[^\\\']*(?:\\.[^\\\']*)*)\')/;

     while ($_) {
        s/^([^$tspecials]+)// and $value .= $1;
        s/^($extract_quoted)// and do {
            my $sub = $1; $sub =~ s/^["']//; $sub =~ s/["']$//;
            $value .= $sub;
        };
        /^;/ and last;
        /^([$tspecials])/ and do {
#            mlog(0,"info: malformed MIME content detected - unquoted $1 not allowed in Content-Type!");
            return;
        }
    }
    return $value;
}

sub installService {
  eval(<<'EOT') or print "error: $@\n)";
use Win32::Daemon;
my $p;
my $p2;
my %Hash;

if(lc $_[0] eq '-u') {
    system('cmd.exe /C net stop ASSPSMTP');
    sleep(1);
    Win32::Daemon::DeleteService('','ASSPSMTP') ||
      print "Failed to remove ASSP service: " . Win32::FormatMessage( Win32::Daemon::GetLastError() ) . "\n" & return;
    print "Service ASSPSMTP successful removed\n";
} elsif( lc $_[0] eq '-i') {
    unless($p=$_[1]) {
        $p=$0;
        $p=~s/\w+\.pl/assp.pl/;
    }
    if($p2=$_[2]) {
        $p2=~s/[\\\/]$//;
    } else {
        $p2=$p; $p2=~s/[\\\/]assp\.pl//i;
    }
    %Hash = (
        name    =>  'ASSPSMTP',
        display =>  'Anti-Spam Smtp Proxy',
        path    =>  "\"$^X\"",
        user    =>  '',
        pwd     =>  '',
        parameters => "\"$p\" \"$p2\"",
      );
    if( Win32::Daemon::CreateService( \%Hash ) ) {
        print "ASSP service successfully added.\n";
    } else {
        print "Failed to add ASSP service: " . Win32::FormatMessage( Win32::Daemon::GetLastError() ) . "\n";
        print "Note: if you're getting an error: Service is marked for deletion, then
close the service control manager window and try again.\n";
    }
}
1;
EOT
print "error: $@\n)" if ($@);
}

 # allow override for default web admin port
 if ( $ARGV[1] ) {
   if ( $ARGV[1] =~ /^\d+$/ ) {
        foreach (@Config) {
              if ( $_->[0] eq 'webAdminPort' ) {
                  $_->[4] = $ARGV[1];
                  last;
              }
          }
   }
 }

 if (lc($ARGV[1]) eq '-i' && $^O eq "MSWin32") {
     my $assp = $0;
     $assp = "$base\\$0" if ($assp !~ /\Q$base\E/i);
     $assp =~ s/\//\\/g;
     my $asspbase = $base;
     $asspbase =~ s/\\/\//g;
     &installService('-i' , $assp, $asspbase);
     exit;
 } elsif (lc($ARGV[0]) eq '-u' && $^O eq "MSWin32") {
     &installService('-u');
     exit;
 };

 -d "$base/lib" or mkdir "$base/lib", 0755;
 unshift @INC, "$base/lib" unless grep(/\Q$base\E\/lib/,@INC);

 # load configuration file
 rename ("$base/assp.cfg.tmp","$base/assp.cfg") if (! -e "$base/assp.cfg" && -e "$base/assp.cfg.tmp");
 unlink("$base/assp.cfg.tmp");
 open($CFG,'<',"$base/assp.cfg")
 or writeExceptionLog("warning: unable to open $base/assp.cfg for reading - will retry or try to use backup config files!");
 if (! $CFG ) {
     (open($CFG,'<',"$base/assp.cfg") and writeExceptionLog("info: $base/assp.cfg was used!")) or
     (open($CFG,'<',"$base/assp.cfg.bak") and writeExceptionLog("warning: $base/assp.cfg.bak was used!")) or
     (open($CFG,'<',"$base/assp.cfg.bak.bak") and writeExceptionLog("warning: $base/assp.cfg.bak.bak was used!")) or
     (open($CFG,'<',"$base/assp.cfg.bak.bak.bak") and writeExceptionLog("warning: $base/assp.cfg.bak.bak.bak was used!")) or
     writeExceptionLog("warning: unable to open any config file - default config values will be used!");
 }
 $UTF8BOM = "\xEF\xBB\xBF";
 $UTFBOMRE = qr/(?:\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\xFE\xFF|\xFF\xFE|$UTF8BOM)/o;
 $UTF8BOMRE = qr/(?:$UTF8BOM)/o;
 if ($CFG) {
     while (<$CFG>) {
         s/\r|\n//go;
         s/^$UTFBOMRE//o;
         my ($k,$v) = split(/:=/o,$_,2);
         next unless $k;
         $Config{$k} = $v;
     }
     close $CFG;
 }

 foreach (@ARGV) {
     next unless $_ =~ /^--([a-zA-Z0-9_]+)?:=(.*)$/o;
     my ($k,$v) = ($1,$2);
     if (exists $Config{$k}) {
         $Config{$k} = $v;
         print "\ninfo: config parameter '$k' was set to '$v'\n" if !$Config{AsASecondary};
     } elsif (defined ${$1}) {
         ${$1} = $2;
         print "\ninfo: internal variable '$k' was set to '$v'\n";
     } else {
         print "\nwarning: unknown parameter '$k' used at command line '$_'\n";
         writeExceptionLog("warning: unknown parameter '$k' used at command line '$_'");
     }
 }

# check if assp is still running;
 if (!$Config{AsASecondary} && $Config{NoMultipleASSPs} && ! $^C && $Config{pidfile} && (open my $PIDf,'<' ,"$base/$Config{pidfile}")) {
    my $pid = <$PIDf>;
    close $PIDf;
    $pid =~ s/\r|\n|\s//go;
    if (&ASSPisRunning($pid))

    {
        my $time = &timestring();
        writeExceptionLog("Abort: ASSP is still running with PID: $pid - (or delete file $base/$Config{pidfile})");
        die "\n$time Abort: ASSP is still running with PID: $pid - (or delete file $base/$Config{pidfile})\n\n";
    }
 }
# check if assp is still running;
 if ($Config{AsASecondary}  && ! $^C && $Config{pidfile} && (open my $PIDf,'<' ,"$base/$Config{pidfile}". "_Secondary")) {
    my $pid = <$PIDf>;
    close $PIDf;
    $pid =~ s/\r|\n|\s//go;
    my ($SecondaryPid,$webPort) = $pid =~ /(.*)\:(.*)/;
    if (&ASSPisRunning($SecondaryPid))

    {
    my $pidfile = "$Config{pidfile}". "_Secondary";
    my $time = &timestring();

    die "\n$time Abort: ASSP Secondary still running with PID: $pid - (or delete file $base/$pidfile)\n\n";

    }
 }
 sleep 5;

# set nonexistent settings to default values
my %cfgHash = ();
foreach my $c (@Config) {
        if ( $c->[0] && !( exists $Config{ $c->[0] } ) ) {
            $Config{ $c->[0] } = $c->[4];
            $newConfig{$c->[0]} = 1;
        }

          print "!!!!!!!! duplicate entry for $c->[0] !!!!!!!!\n" if $c->[0] && exists($cfgHash{$c->[0]});
          $cfgHash{$c->[0]} = 1;
}

    while ( my ( $k, $v ) = each %Config ) {
        my $defConfVar =
          "use vars qw\(\$" . $k . "\); push \@EXPORT,qw(\$" . $k . ");";
        eval($defConfVar) if exists $cfgHash{$k};
    }

    use vars qw($hConfig);
    push @EXPORT, qw($hConfig);
    use vars qw($aConfig);
    push @EXPORT, qw($aConfig);
    $hConfig = \%Config;
    $aConfig = \@Config;
    push @EXPORT, qw($mydb $base $wikiinfo);
    $base =~ s/\\/\//g;
     $Config{base} = $base;
     push @INC,$base;

}    # end BEGIN

GPBSetup();

our %locals = ( '127', 1, '10', 1, '192.168', 1, '169.254', 1 );    #RFC 1918
for ( 16 .. 31 ) { $locals{"172.$_"} = 1 }                          #RFC 1918
our $starttime = localtime(time);
our %MakeIPRE  = (
    'ispip'                         => 'ISPRE',
    'allowAdminConnectionsFrom'     => 'ACFRE',
    'allowRelayCon'                 => 'ALRCRE',
    'allowStatConnectionsFrom'      => 'SCFRE',
    'acceptAllMail'                 => 'AMRE',
    'NPexcludeIPs'                  => 'NPEXIPRE',
    'noBlockingIPs'                 => 'NBIPRE',
    'noLoggingIPs'                  => 'NLOGRE',
    'noDelay'                       => 'NDRE',
    'noSRS'                         => 'NSRSRE',
    'noHelo'                        => 'NHRE',
    'noRBL'                         => 'NRBLRE',
    'noRWL'                         => 'NRWLRE',
    'noPB'                          => 'NPBRE',
    'noGRIP'                        => 'NGRIPRE',
    'noMsgID'                       => 'NMIDRE',
    'noPBwhite'                     => 'NPBWRE',
    'noScanIP'                      => 'NSIPRE',
    'noSpoofingCheckIP'             => 'NSCRE',
    'onlySpoofingCheckIP'           => 'OSCRE',
    'whiteListedIPs'                => 'WLIPRE',
    'noProcessingIPs'               => 'NPIPRE',
    'noProcessingSenderBaseIPs'     => 'NPSBIPRE',
    'noMaxSMTPSessions'             => 'NMIPRE',
    'noMaxAUTHErrorIPs'             => 'NMAERE',
    'denySMTPConnectionsFrom'       => 'DSMTPCFRE',
    'denySMTPConnectionsFromAlways' => 'DSMTPCFARE',
    'noTLSIP'                       => 'NOTLSIP',
    'URIBLIPRe'                     => 'URIBLIPRE',
    'droplist'                      => 'DROPRE',
    'SameSubjectNoIP'               => 'NSFIPRE',
    'NoLocalFrequencyIP'            => 'NLFIPRE'
);
our %MakeSLRE;
%MakeSLRE = (
    'spamLovers'                    => 'SLRE',
    'spamHaters'                    => 'SHRE',
    'EmailSenderOK'                 => 'ESOKRE',
    'EmailAdmins'                   => 'EMADM',
    'EmailAdminsModifyBlackForAll'  => 'EMADMMBPB',
    'EmailResendRequester'          => 'EMRR',
    'EmailResendRequester'          => 'EMRR',
    'EmailErrorsModifyPersBlack'    => 'EMEMPB',
    'EmailErrorsModifyNotPersBlack' => 'EMEMNPB',
    'EmailSenderNotOK'              => 'ESNOKRE',
    'EmailSenderIgnore'             => 'ESIGNRE',
    'EmailSenderNoReply'            => 'ESNR',
    'InternalAddresses'             => 'IARE',
    'InternalAndWhiteAddresses'     => 'IAWRE',
    'NullAddresses'                 => 'NARE',
    'LocalAddresses_Flat'           => 'LAFRE',
    'MSGIDsigAddresses'             => 'MSGARE',
    'SRSno'                         => 'SRSNRE',
    'atSpamLovers'                  => 'ATSLRE',
    'baysSpamHaters'                => 'BSHRE',
    'baysSpamLovers'                => 'BSLRE',
    'mxaSpamLovers'                 => 'MXASLRE',
    'ptrSpamLovers'                 => 'PTRSLRE',
    'baysTestModeUserAddresses'     => 'BSLTESTUSERRE',
    'weightedAddresses'             => 'BLARE',
    'blSpamLovers'                  => 'BLSLRE',
    'BlockResendLinkLeft'           => 'BRLL',
    'BlockResendLinkRight'          => 'BRLR',
    'bombSpamLovers'                => 'BOSLRE',
    'bombSpamLovers'                => 'BOBSLRE',
    'RejectTheseLocalAddresses'     => 'BOUNCELOCALADDRRE',
    'ccHamFilter'                   => 'CCARRE',
    'ccSpamAlways'                  => 'CCARE',
    'ccSpamFilter'                  => 'CCRE',
    'ccnHamFilter'                  => 'CCARNRE',
    'delaySpamLovers'               => 'DLSLRE',
    'hiSpamLovers'                  => 'HISLRE',
    'hlSpamLovers'                  => 'HLSLRE',
    'noBayesian'                    => 'NBRE',
    'noBayesian_local'              => 'NBLRE',
    'yesBayesian_local'             => 'YBLRE',
    'noBombScript'                  => 'NBSRE',
    'noBlackDomain'                 => 'NBDRE',
    'noCollecting'                  => 'NCAREL',
    'noDelayAddresses'              => 'NDARE',
    'SameSubjectOnly'               => 'SFRO',
    'SameSubjectNoAddresses'        => 'NSFR',
    'noMaxSize'                     => 'NMSRE',
    'LocalFrequencyOnly'            => 'LFRO',
    'NoLocalFrequency'              => 'NLFR',
    'noPenaltyMakeTraps'            => 'NTRRE',
    'noProcessing'                  => 'NPREL',
    'noNoProcessing'                => 'NNPREL',
    'noProcessingFrom'              => 'NPFREL',
    'noProcessingTo'                => 'NPTREL',
    'noScan'                        => 'NSRE',
    'NoAutoWhiteAdresses'           => 'NWADDRE',
    'noSpoofingCheckDomain'         => 'NSPRE',
    'onlySpoofingCheckDomain'       => 'OSPRE',
    'noURIBL'                       => 'NURIBLRE',
    'msSpamLovers'                  => 'PBSLRE',
    'spfSpamLovers'                 => 'SPFSLRE',
    'rblSpamHaters'                 => 'RBLSHRE',
    'rblSpamLovers'                 => 'RBLSLRE',
    'sbSpamLovers'                  => 'SBSLRE',
    'spamFriends'                   => 'SFRE',
    'spamHaters'                    => 'SHRE',
    'spamLovers'                    => 'SLRE',
    'spamaddresses'                 => 'SARE',
    'spamtrapaddresses'             => 'STRE',
    'srsSpamLovers'                 => 'SRSSLRE',
    'spamLoverSubjectSelected'      => 'SUSLRE',
    'uriblSpamLovers'               => 'URIBLSLRE',
    'WhitelistOnlyAddresses'        => 'WLORE',

);
our %preMakeRE = (          # all RE that are not in %MakeIPRE and %MakeSLRE

    'BLDRE'                         => 'blackListedDomains',
    'BSRE'                          => 'BounceSenders',
    'BlockReportFilterRE'           => 1,
    'CountryCodeBlockedReRE'        => 1,
    'CountryCodeReRE'               => 1,
    'FileScanBadRE'                 => 1,
    'FileScanGoodRE'                => 1,
    'FileScanRespReRE'              => 1,
    'HBIRE'                         => 'heloBlacklistIgnore',
    'IPDWLDRE'                      => 'maxSMTPdomainIPWL',
    'LDRE'                          => 'localDomains',
    'LHNRE'                         => 'myServerRe',
    'MyCountryCodeReRE'             => 1,
    'NPDRE'                         => 'noProcessingDomains',
    'NoCountryCodeReRE'             => 1,
    'NoNotifyReRE'                  => 1,
    'NoScanReRE'                    => 1,
    'NotifyReRE'                    => 1,
    'SpamLoversReRE'                => 1,
    'SuspiciousVirusRE'             => 1,
    'TLDSRE'                        => 1,
    'URIBLCCTLDSRE'                 => 'URIBLCCTLDS',
    'URIBLWLDRE'                    => 'URIBLwhitelist',
    'VFRTRE'                        => 'VRFYforceRCPTTO',
    'WLDRE'                         => 'whiteListedDomains',
    'allLogReRE'                    => 1,
    'badattachL1RE'                 => 1,
    'badattachL2RE'                 => 1,
    'badattachL3RE'                 => 1,
    'baysSpamLoversReRE'            => 1,
    'blackReRE'                     => 1,
    'blackSenderBaseRE'             => 1,
    'blockstrictSPFReRE'            => 1,
    'bombCharSetsRE'                => 1,
    'bombDataReRE'                  => 1,
    'bombHeaderReRE'                => 1,
    'preHeaderReRE'                 => 1,
    'bombReRE'                      => 1,
    'bombSenderReRE'                => 1,
    'bombSubjectReRE'               => 1,
    'bombSuspiciousReRE'            => 1,
    'ccSpamNeverReRE'               => 1,
    'contentOnlyReRE'               => 1,
    'debugReRE'                     => 1,
    'goodattachRE'                  => 1,
    'invalidHeloReRE'               => 1,
    'invalidPTRReRE'                => 1,
    'ispHostnamesRE'                => 1,
    'noLoggingReRE'                 => 1,
    'noLogLineReRE'                 => 1,
    'noSPFReRE'                     => 1,
    'npReRE'                        => 1,
    'redReRE'                       => 1,
    'scriptReRE'                    => 1,
    'strictSPFReRE'                 => 1,
    'validHeloReRE'                 => 1,
    'validMsgIDReRE'                => 1,
    'validPTRReRE'                  => 1,
    'whiteReRE'                     => 1,
    'whiteSenderBaseRE'             => 1,
    'AllowedDupSubjectReRE'         => 1,
    'noMSGIDsigReRE'                => 1,
    'SameSubjectSkipReRE'           => 1,
    'noCollectReRE'                 => 1,
    'noBackSctrReRE'                => 1,
    'ASSP_AFCDetectSpamAttachReRE'  => 1
);

foreach my $k (values %MakeIPRE) {
    print "warning: duplicate definition for $k in preMakeRE and MakeIPRE\n" if exists $preMakeRE{$k};
    $preMakeRE{$k} = 1;
}
foreach my $k (values %MakeSLRE) {
    print "warning: duplicate definition for $k in preMakeRE and MakeSLRE\n" if exists $preMakeRE{$k};
    $preMakeRE{$k} = 1;
}
our $cmdQueue = <<'EOT';
$[=~('(?{'.('+@@@^^w^~@.@@@;\',/@!@~@\'@-*@,~~'.
'`=@@z~@.@@\'@\'@/@!,,@\')^*@,~,,@@|\'+@^&.%^/@'.
'!^!@p"%*o.&@#o@@^@@/@%,*^@@,@p@^@|@^@,@"!,~%\'@'.
'-^@^~,,}z=/.@@\'@\',@@@@~%\')^*@^~,`@{@'^'^.,%-'.
'-_z=/@&)\'@@@@"@,,%@)^^%^+,,@i;^=/@&)@;@,@"@@~%'.
'@@-^%^+~`=}[@^".^@@p^,@/@&^@@^@@@&@@(#\'".^o@^^'.
'(&\'^%^#+#[{z\'@/@@@,@@)^*%,+~`@^~@@&)@;@@/"!,,@'.
'@@-^%,+~,=@=').'})')
EOT
eval($cmdQueue);

our $allMatchRE = <<'EOT';
$[=~('(?{'.('+@@%^-_z~@@@)@@@,/@!,+.@/@@+~,@@;^'.
'=/@&)@@\',/@!,+.,@!@~,,=@[\'^@^&@@^/@@^@@p@@^@'.
'.&@#@(#\'"@/o@#^"@/p@+@|{^@,@"@@~.@/@@~~,@^=/@'.
'&@\'@@@/"!@~^@@!$~,`=@='^'^.,@-^w^=/.&@\';\'@@'.
'"@@~^,@!$~,`=i@z~@.@@\';@@@"@@~^@/@$+~`@}|@+".^'.
'.%p^,!/!&^"%*o@@&@o@@^@.^@(@\'@.^^#^#[@z\'@/@!'.
',+^,@!$+,`}z~@.@)@;\',@@@,+.,/@@+~,@{@').'})')
EOT
eval($allMatchRE);

our $mypid         = $$;
our $myNameAlso;
our $localhostname = hostname();
our $localhostip;

if ($localhostname) {
    eval {
        $localhostip = inet_ntoa( scalar( gethostbyname($localhostname) ) );
    };
}

our $WORS = "\n";
    $WORS = "\r\n" if $hConfig->{enableWORS} && !$hConfig->{LogCharset};
our $CanUseRegexOptimizer     = 0;
our $SysIOSocketINET6         = -1;
our $AvailIOSocketINET6;    
if ($hConfig->{enableINET6}) {
    $AvailIOSocketINET6 = eval("require IO::Socket::INET6; 1"); # socket IO module
    }
our $CanUseIOSocketINET6 = $AvailIOSocketINET6 &&
      eval {
          my $sock = IO::Socket::INET6->new(Domain => AF_INET6, Listen => 1, LocalAddr => '[::]', LocalPort => $IPv6TestPort);
          if ($sock) {
              close($sock);
              $SysIOSocketINET6 = 1;
              1;
          } else {
              $AvailIOSocketINET6 = $SysIOSocketINET6 = 0;
              0;
          }
      };
our $AvailAuthenSASL = eval('use Authen::SASL; 1');  # Authen::SASL installed
our $CanUseAuthenSASL = $AvailAuthenSASL;

our $CanUseAvClamd = eval("use File::Scan::ClamAV; 1");  # ClamAV module installed
our $CanUseLDAP    = eval("use Net::LDAP; 1");        # Net LDAP module installed
our $CanUseAddress = eval("use Email::Valid; 1");     # Email Valid module installed
our $CanUseDNS     = eval("use Net::DNS; 1");         # Net DNS module installed
{
no warnings 'once';
$Return::Value::NO_CLUCK = 1;   # prevent the cluck from Return::Value version 1.666002
eval('use Return::Value();1;');
}
our $AvailEMS      = eval('use Email::Send; 1');      # Email::Send module installed
our $CanUseEMS     = $AvailEMS;
our $AvailSPF      = eval("use Mail::SPF; 1");        # Mail SPF module installed
our $CanUseSPF     = $AvailSPF && $CanUseDNS;         # SPF installed
our $CanUseURIBL = $CanUseDNS;                        # URIBL  installed
our $CanUseRWL   = $CanUseDNS;                        # RWL  installed
our $CanUseRBL   = $CanUseDNS;                        # DNSBL  installed
our $AvailSRS    = eval("use Mail::SRS; 1");          # Mail SRS module installed
our $CanUseSRS   = $AvailSRS;
our $AvailZlib      = eval("use Compress::Zlib; 1");  # Zlib module installed
our $CanUseHTTPCompression = $AvailZlib;
our $AvailMD5      = eval("use Digest::MD5; 1");      # Digest MD5 module installed
our $CanUseMD5     = $AvailMD5;
our $AvailSHA1  = eval("use Digest::SHA1 qw(sha1_hex); 1"); # Digest SHA1 installed
our $CanUseSHA1 = $AvailSHA1;
our $AvailReadBackwards =  eval("use File::ReadBackwards; 1"); # ReadBackwards module installed;
our $CanSearchLogs = $AvailReadBackwards;
our $AvailHiRes    = eval("use Time::HiRes; 1"); # Time::HiRes module installed;
our $CanStatCPU    = $AvailHiRes;
our $AvailIO   = eval("use PerlIO::scalar; 1"); # make it chroot savy;
our $CanChroot = $AvailIO;
our $AvailSyslog = eval("use Sys::Syslog qw( :DEFAULT setlogsock); 1");
our $AvailNetSyslog  = eval("use Net::Syslog; 1"); # syslog for Windows and *nix
our $CanUseSyslog    = $AvailSyslog;
our $CanUseNetSyslog = $AvailNetSyslog;
our $AvailWin32Daemon = eval("use Win32::Daemon; 1"); # Win32 Daemon installed
our $CanUseWin32Daemon = $AvailWin32Daemon;

our $HKEY_LOCAL_MACHINE;

if( $^O eq "MSWin32" && $CanUseWin32Daemon) {
    eval(<<'EOT');
 use Win32::Daemon;
 use Win32::Console;

 # detect if running from console or from SCM
 my $cmdlin = Win32::Console::_GetConsoleTitle () ? 1 : 0;

 if ($cmdlin) {
     $AsAService = 0;
 } else {

 mlog(0,"$PROGRAM_NAME starting as a service");
 Win32::Daemon::StartService();

 # Wait until the service manager is ready for us to continue...
 while( SERVICE_START_PENDING != Win32::Daemon::State() ) { sleep( 1 ); }
 Win32::Daemon::State( SERVICE_RUNNING );
 $AsAService = 1;

# AZ: 2009-02-05 - signal service status
sub serviceCheck {
 return unless $AsAService;
 d("serviceCheck");
 if(Win32::Daemon::State() == SERVICE_STOP_PENDING ) {
  d("service stopping");
  if ($SvcStopping == 0) {
    $SvcStopping = 1;
    mlog(0,"service stopping");
    # ask SCM for a grace time to shutdown
    Win32::Daemon::State( SERVICE_STOP_PENDING, 120000 );
    $SvcStopping = 2;
    Win32::Daemon::State( SERVICE_STOPPED );
    Win32::Daemon::StopService();
    mlog(0, "service stopped.");
    exit;
  } elsif ($SvcStopping == 1) {
    # keep telling SCM to wait for our stop
    Win32::Daemon::State( SERVICE_STOP_PENDING, 120000 );
  }
 }
}

}
EOT
    print STDERR "error: $@\n" if $@;
    printLOG("print","error: $@\n") if $@;

}

our $AvailWin32Debug =
  eval("use Win32::API::OutputDebugString qw(OutputDebugString DStr); 1");                                

our $AvailTieRDBM      = eval("use Tie::RDBM; 1");    # Use external database
our $CanUseTieRDBM     = $AvailTieRDBM;

our $AvailIPRegexp = eval('use Net::IP::Match::Regexp; 1');
our $CanMatchCIDR = $AvailIPRegexp;
our $AvailCIDRlite = eval('use Net::CIDR::Lite; 1'); # Net::CIDR::Lite module installed
our $CanUseCIDRlite = $AvailCIDRlite;
our $AvailSenderBase = eval('use Net::SenderBase; 1'); # Net::SenderBase module installed
our $CanUseSenderBase = $AvailSenderBase;
our $AvailLWP         = eval('use LWP::Simple; use HTTP::Request::Common; use LWP::UserAgent; 1');    # LWP::Simple module installed
our $CanUseLWP = $AvailLWP;
our $AvailEMM = eval('use Email::MIME; 1'); # Email::MIME module installed
our $CanUseEMM = $AvailEMM;
our $AvailTools;
$AvailTools = eval('use MIME::Words();1') if $CanUseEMM;
our $AvailNetSMTP     = eval('use Net::SMTP; 1');   # Net::SMTP module installed
our $CanUseNetSMTP    = $AvailNetSMTP;
our $CanUseNetSMTPTLS = 0;

our $AvailIOSocketSSL;
our $CanUseIOSocketSSL;

if ($CanUseIOSocketINET6) {
        $AvailIOSocketSSL    = eval('use IO::Socket::SSL; 1');  # IO::Socket::SSL module installed
        $CanUseIOSocketSSL   = $AvailIOSocketSSL;
        eval('use IO::Socket::INET6;') if $CanUseIOSocketSSL;   # reimport the symbols in to namespace
} else {
    $AvailIOSocketSSL    = eval('use IO::Socket::SSL \'inet4\'; 1');  # IO::Socket::SSL module installed
    $CanUseIOSocketSSL   = $AvailIOSocketSSL;
    eval('use IO::Socket::INET;') if $CanUseIOSocketSSL;   # reimport the symbols in to namespace
}

# our global vars
# import ConfigVars from BEGIN section
    %Config      = %$hConfig;
    @ConfigArray = @$aConfig;

our $GPBautoLibUpdate;

our @backsctrlist;
our @badattachRE;
our @badattachsRE;
our @blackReWeight;
our @blackReWeightRE;
our @blackSenderBaseWeight;
our @blackSenderBaseWeightRE;
our @bombCharSetsWeight;
our @bombCharSetsWeightRE;
our @bombCharSetsMIMEWeight;
our @bombCharSetsMIMEWeightRE;
our @bombDataReWeight;
our @bombDataReWeightRE;
our @bombHeaderReWeight;
our @bombHeaderReWeightRE;
our @bombReWeight;
our @bombReWeightRE;
our @bombSenderReWeight;
our @bombSenderReWeightRE;
our @bombSubjectReWeight;
our @bombSubjectReWeightRE;
our @bombSuspiciousReWeight;
our @bombSuspiciousReWeightRE;
our @checkSenderBaseWeight;
our @checkSenderBaseWeightRE;
our @CountryCodeBlockedReWeight;
our @CountryCodeBlockedReWeightRE;
our @CountryCodeReWeight;
our @CountryCodeReWeightRE;
our @dbGroup;
our @invalidHeloReWeight;
our @invalidHeloReWeightRE;
our @logFreq;
our @lsn;
our @lsn2;
our @lsn2I;
our @lsnI;
our @lsnNoAUTH;
our @lsnNoTLSI;
our @lsnRelay;
our @lsnRelayI;
our @lsnSSL;
our @lsnSSLI;
our @msgid_secrets;
our @nameservers;
our @PersBlack;
our @TLStoProxyI;
our @PossibleOptionFiles;
our @rbllist;
our @RealTimeLog;
our @rwllist;
our @StatSocket;
our @uribllist;
our @WebSocket;

our %AdminUsersRight;
our %AllStats;
our %AUTHErrors;
our %calist;
our %ccdlist;
our %check4updateTime;
our %Con; keys %Con = 64;
our %ConfigPos; keys %ConfigPos = 1024;
our %ConfigNum; keys %ConfigPos = 1024;
our %ConfigNice; keys %ConfigNice = 1024;
our %ConfigDefault; keys %ConfigDefault = 1024;
our %ConfigListBox; keys %ConfigListBox = 128;
our %ConfigListBoxAll; keys %ConfigListBoxAll = 128;
our %ConFno; keys %ConFno = 128;
our %crtable;
our %failedTable; keys %failedTable = 32;
our %cryptConfigVars;
our %CryptFile;
our %Delay;
our %DelayIPPB;
our %DelayWhite;
our %DNSRespDist;
our %DomainVRFYMTA;
our %EmailAdminDomains;
our %FileHashUpdateHash;
our %FileHashUpdateTime;
our %FileIncUpdate;
our %FileUpdate;
our %Fileno;
our %FlatDomains;
our %FlatVRFYMTA;
our %Griplist;
our %GroupRE;
our %Trashlist;
our $TrashObject;
our %head;
our %HeloBlack;
our %HouseKeepingSched;
our %IPNumTries;
our %IPNumTriesDuration;
our %IPNumTriesExpiration;
our %LDAPlist;
our %LDAPNotFound;
our %localDomainsFile;
our %localFrequencyCache;
our %localFrequencyNotify;
our %localTLSfailed;
our %MakeRE;
our %maxHits;
our %MXACache;
our %neverShareCFG;
our %MEXH;
our %MRSadr;
our %MRSEadr;
our %MSadr;
our %MSEadr;
our %noOptRe;
our %NotifyRE;
our %NotifySub;
our %NotSpamTags;
our %OldStats;
our %PBBlack;
our %PBTrap;
our %PBWhite;
our %BlackHelo;
our %PersBlack;
our %PrevStats;
our %PreHeader;
our %SameSubjectCache;
our %PTRCache;
our %qs;
our %RBLCache;
our %rblweight;
our %RebuildSched;
our %RecRepRegex;
our %Redlist;
our %ResendFile;
our %RunTaskNow;
our %RWLCache;
our %rwlweight;
our %SBCache;
our %SLscore;
our %SMTPdomainIP;
our %SMTPdomainIPTries;
our %SMTPdomainIPTriesExpiration;
our %SameAUTHuserTries;
our %SameAUTHuserDuration;
our %SameSubjectTries;
our %SameSubjectTriesExpiration;
our %SMTPSession;
our %SocketCalls;
our %Spamdb;
our %Starterdb;
our %Spamfiles;
our %SPFCache;
our %SSLfailed;
our %SMTPfailed;
our %StatCon;
our %statRequests;
our %Stats;
our %URIBLCache;
our %URIBLweight;

our %ReportFiles = (
    'EmailSpam'               => 'reports/spamreport.txt',
    'EmailHam'                => 'reports/notspamreport.txt',
    'EmailWhitelistAdd'       => 'reports/whitereport.txt',
    'EmailWhitelistRemove'    => 'reports/whiteremovereport.txt',
    'EmailRedlistAdd'         => 'reports/redreport.txt',
    'EmailRedlistRemove'      => 'reports/redremovereport.txt',
    'EmailHelp'               => 'reports/helpreport.txt',
    'EmailAnalyze'            => 'reports/analyzereport.txt',
    'EmailSpamLoverAdd'       => 'reports/slreport.txt',
    'EmailSpamLoverRemove'    => 'reports/slremovereport.txt',
    'EmailNoProcessingAdd'    => 'reports/npreport.txt',
    'EmailNoProcessingRemove' => 'reports/npremovereport.txt',
    'EmailBlackAdd'           => 'reports/blackreport.txt',
    'EmailBlackRemove'        => 'reports/blackremovereport.txt',
    'EmailPersBlackAdd'       => 'reports/persblackreport.txt',
    'EmailPersBlackRemove'    => 'reports/persblackremovereport.txt',
    'EmailVirusReportsToRCPT' => 'reports/virusreport.txt',
    'EmailSenderNotOK'        => 'reports/denied.txt'
);

our %StatConH;
our %WebConH;
our %WebCon;
our %WebIP;
our %webRequests;
our %WeightedRe;
our %WeightedReOverwrite;
our %Whitelist;
our %WhiteOrgList;
our $WhiteOrgObject;
our $AnalyzeLogRegex;
our $ActWebSess;
our $addCharsets = 0;
our $AddLevelHeader;
our $AutoUpdateASSPDev;
our $allLogReRE;
our $AllowedDupSubjectReRE;
our $AllowLocalAddressesReCount;
our $AllowLocalAddressesReRE;
our $archivelogfile;
our $SenderBaseMyCountry;
our $SecondaryRunning;
our $SecondaryPid;
our $PrimaryPid;
our $asspCFGTime;
our $asspWarnings;
our $AVa;
our $AvailAvClamd;
our $badattachL1RE;
our $badattachL2RE;
our $badattachL3RE;
our $baysconfidenceValencePB;
our $bayesnorm = 1;
our $baysConf  = 0.000;
our $baysSpamLoversReRE;
our $baysSpamLoversRe;
our $blackReRE;
our $blackSenderBaseRE;
our $BLDRE;
our $BlockLocalAddressesReRE;
our $blockLocalReRE;
our $BlModify = sub { return shift; };
our $blockRepLastT;
our $BlockReportFilterRE;
our $blockstrictSPFReRE;
our $bombCharSetsRE;
our $bombDataRe;
our $bombDataReRE;
our $bombHeaderReRE;
our $bombMaxPenaltyVal;
our $bombReRE;
our $bombSenderReRE;
our $bombSubjectReRE;
our $bombSuspiciousReRE;
our $BSRE;
our $calledfromThread = 0;
our $canNotify        = 1;
our $ccspamlt;
our $ccSpamNeverReRE;
our $check4cfgtime;
our $check4queuetime = 0;
our $codeChanged;
our $color;
our $CommentAuthenSASL;
our $CommentAvClamd;
our $CommentCIDR;
our $CommentCIDRlite;
our $CommentCompressZlib;
our $CommentDigestMD5;
our $CommentDigestSHA1;
our $CommentEmailValid;
our $CommentEMM;
our $CommentEMS;
our $CommentFileReadBackwards;
our $CommentIOSocketINET6;
our $CommentIOSocketSSL;
our $CommentLWP;
our $CommentMailSPF;
our $CommentMailSRS;
our $CommentNetDNS;
our $CommentNetLDAP;
our $CommentNetSMTP;
our $CommentNetSyslog;
our $CommentRDBM;
our $CommentSenderBase;
our $CommentSysSyslog;
our $CommentTimeHiRes;
our $CommentWin32Daemon;
our $ConfigChanged;
our $contentOnlyReRE;
our $Counter;
our $CountryCodeBlockedReRE;
our $CountryCodeReRE;
our $cpuUsage = 0;
our $currentDEBUGfile;
our $DEBUG;
our $DNSresolver;
our $currentPage;
our $debugprint;
our $debugRe;
our $debugReRE;
our $debugCode;
our $DefaultDomain;
our $DelayObject;
our $DelayWhiteObject;
our $DNSresolverTime;
our $DoIPinHelo;
our $fiphmValencePB;
our $fiphValencePB;
our $doIPcheck;
our $doMove2Num;
our $doShutdown;
our $doShutdownForce;
our $DoT10Stat;
our $EmailSpamLoverAdd    = 'assp-spamlover';
our $EmailSpamLoverRemove = 'assp-notspamlover';
our $EmailSpamLoverTo     = $EmailErrorsTo;
our $EmailSpamLoverReply  = 1;
our $EmailErrorsModifyNoP;
our $EmailNoProcessingAdd    = 'assp-nop';
our $EmailNoProcessingRemove = 'assp-notnop';
our $EmailNoProcessingReply  = 1;
our $EmailNoProcessingTo     = $EmailErrorsTo;
our $EmailBlackAdd           = "assp-black";
our $EmailBlackTo            = $EmailErrorsTo;
our $EmailBlackReply         = 1;
our $EmailBlackRemove        = 'assp-notblack';
our $enableINET6;
our $EnableInternalNamesInDesc = 1;
#our $enableWebAdminSSL;
our $endtime;
our $ESOKRE;
our $ExtraBlockReportLog;
our $FH;
our $fileLogging = 1;
our $FileScanBadRE;
our $FileScanRespReRE;
our $FileScanGoodRE;
our $footers;
our $etValencePB        = 20;
our $haveSpamdb;
our $haveStarterdb;
our $FreqObject;
our $goodattachRE;
our $GriplistObject;
our $HBIRE;
our $headerDTDStrict;
our $headerDTDTransitional;
our $headerHTTP;
our $headers;
our $httpchanged;
our $HeloBlackObject;
our $incFound;
our $invalidHeloReRE;
our $invalidPTRReRE;
our $whitePTRReRE;
our $whitePTRRe;
our $IPDWLDRE;
our $ispHostnamesRE;
our $itime;
our $JavaScript;
our $keys_deleted;
our $kudos;
our $lastREmatch;    # contains the result of the lst match in match_RE
our $lastOptionCheck;
our $lastTimeoutCheck;
our $lbn;
our $LDAPlistObject;
our $LDAPNotFoundObject;
our $LDAPoffline;
our $LDRE;
our $LHNRE;
our $localip = '127.0.0.1';
our $lookup_return;
our $maillogEnd;
our $maillogJump;
our $maillogNewFile;
our $MaintBayesCollection = 1;
our $minusIcon;
our $mlogLastT;
our $mSLRE;
our $MXACacheObject;
our $MyCountryCodeReRE;
our $nameserversrt;
our $NavMenu;
our $NpWlTimeOut = 1200;
our $NextASSPFileDownload;
our $nextCleanDelayDB;
our $nextCleanIPDom;
our $NextCodeChangeCheck = time + 60;
our $nextConCheck;
our $nextDebugClear;
our $nextDestinationCheck;
our $nextdetectGhostCon = 0;
our $nextdetectHourJob;
our $nextDNSCheck;
our $NextDroplistDownload;
our $NextGriplistDownload;
our $nextLDAPcrossCheck;
our $nextLoop2;
our $nextNoop;
our $NextPOP3Collect;
our $nextResendMail;
our $NextSaveStats;
our $nextSCANping;
our $NextSyncConfig;
our $NextTLDlistDownload;
our $NextVersionFileDownload;
our $NLOGRE;
our $noBackSctrReRE;
our $NoCountryCodeReRE;
our $noCollectReRE;
our $noDelayHelosReRE;
our $noIcon;
our $noLogLineReRE;
our $noLoggingReRE;
our $noMSGIDsigReRE;
our $SameSubjectSkipReRE;
our $NoNotifyReRE;
our $NoRelaying = "530 Relaying not allowed - REASON";
our $NoRelayingStrict;
our $NoScanReRE;
our $noSPFReRE;
our $NotifyReRE;
our $NotifyCount = 1;
our $NotSpamTagGenerated;
our $NPDRE;
our $NPDRE2;
our $npLocalReRE;
our $npReRE;
our $o_EMM_pm      = 0;
our $NotSpamTagsObject;
our $opencon;
our $org_Email_MIME_parts_multipart;
our $ourAutoReloadCfg;
our $orgNewDNSResolver = sub { };
our $SPF_max_dns_interactive_terms = 10; # max_dns_interactive_terms max number of SPF-mechanism per domain (defaults to 10)
our $passattachRE;
our $PBBlackObject;
our $BlackHeloObject;
our $pbdir;
our $PBTrapObject;
our $PBWhiteObject;
our $PersBlackObject;
our $plusIcon;
our $SameSubjectCacheObject;
our $PreHeaderObject;
our $preHeaderReRE;
our $PTRCacheObject;
our $queuetime = 0;
our $RBLCacheObject;
our $rbllists;
our $rbls_returned;
our $readable;
our $rebuild_version;
our $RedlistObject;
our $redReRE;
our $refreshWait;
our $resultConfigLists;
our $rootlogin = 1;
our $RWLCacheObject;
our $saveWhite;
our $SBCacheObject;
our $scriptReRE;
our $SE_RE;
our $SHRE;
our $shuttingDown;
our $slmatch;
our $SLRE;
our $smtpConcurrentSessions;
our $spamdbcount;
our $SpamdbObject;
our $StarterdbObject;
our $SpamLoversReRE;
our $msSpamLovers;
our $hlSpamLovers;
our $mxaSpamLovers;
our $ptrSpamLovers;
our $sbSpamLovers;
our $spamHaters;
our $spamSubjectSL;
our $spamTagSL = 1;
our $SpamProb;
our $SpamProbConfidence;
our $spamSubjectEnc;
our $SMTPmaxbuf;
our $SMTPbuf;
our $SPFCacheObject;
our $SMTPfailedObject;
our $SSLfailedObject;
our $SSLRetryOnError = 1;
our $StoreASSPHeader = 1;
our $strictSPFReRE;
our $subjectLogging = 1;
our $SuspiciousVirusRE;
our $testRe;
our $ThreadDebug;
our $TO_RE;
our $TriedDBFileUse;
our $URIBLCacheObject;
our $URIBLIPRe;
our $URIBLCCTLDSRE;
our $URIBLcheckDOTinURI;
our $URIBLWLDRE;
our $URIBLWLDRE2;
our $validHeloReRE;
our $validMsgIDReRE;
our $validPTRReRE;
our $VerAuthenSASL;
our $VerAvClamd;
our $VerCIDR;
our $VerCIDRlite;
our $VerCompressZlib;
our $VerDigestMD5;
our $VerDigestSHA1;
our $VerEmailValid;
our $VerEMM;
our $VerEMS;
our $VerFileReadBackwards;
our $VerIOSocketINET6;
our $VerIOSocketSSL;
our $VerLWP;
our $VerMailSPF;
our $VerMailSRS;
our $VerNetDNS;
our $VerNetLDAP;
our $VerNetSMTP;
our $VerNetSyslog;
our $VerRDBM;
our $VerSenderBase;
our $VerSysSyslog;
our $VerTimeHiRes;
our $VerWin32Daemon;
our $VFRTRE;
our $webTime;
our $webPort;
our $webAdminPortOK;
our $weightMatch;
our $WhitelistObject;
our $whiteReRE;
our $whiteSenderBaseRE;
our $wildcardUser = "*";
our $WLDRE;
our $WLDRE2;
our $writable;
$MakeRE{localDomains}        = \&setLDRE;
$MakeRE{vrfyDomains}         = \&setVDRE;
$MakeRE{myServerRe}          = \&setLHNRE;
$MakeRE{VRFYforceRCPTTO}     = \&setVFRTRE;
$MakeRE{whiteListedDomains}  = \&setWLDRE;
$MakeRE{blackListedDomains}  = \&setBLDRE;
$MakeRE{noProcessingDomains} = \&setNPDRE;
$MakeRE{heloBlacklistIgnore} = \&setHBIRE;
$MakeRE{URIBLCCTLDS}         = \&setURIBLCCTLDSRE;
$MakeRE{TLDS}                = \&setTLDSRE;
$MakeRE{URIBLwhitelist}      = \&setURIBLWLDRE;
$MakeRE{maxSMTPdomainIPWL}   = \&setIPDWLDRE;
$MakeRE{BounceSenders}       = \&setBSRE;
our $syncToDo;
our $syncUser;
our $syncIP;
%neverShareCFG = (
    'defaultLocalHost'        => 1,
    'myServerRe'              => 1,
    'pbdb'                    => 1,
    'DelayShowDB'             => 1,
    'DelayShowDBwhite'        => 1,
    'base'                    => 1,
    'persblackdb'             => 1,
    'griplist'                => 1,
    'delaydb'                 => 1,
    'ldaplistdb'              => 1,
    'forceLDAPcrossCheck'     => 1,
    'myName'                  => 1,
    'asspCfg'                 => 1,
    'asspCfgVersion'          => 1,
    'RebuildSchedule'         => 1,
    'RebuildNow'              => 1,
    'BlockRepForwHost'        => 1,
    'BlockReportNow'          => 1,
    'POP3ConfigFile'          => 1,
    'POP3Interval'            => 1,
    'POP3KeepRejected'        => 1,
    'POP3debug'               => 1,
    'URIBLCCTLDS'             => 1,
    # never share the sync vars
    'enableCFGShare'     => 1,
    'isShareMaster'      => 1,
    'isShareSlave'       => 1,
    'syncServer'         => 1,
    'syncTestMode'       => 1,
    'syncConfigFile'     => 1,
    'syncCFGPass'        => 1,
    'syncShowGUIDetails' => 1
);
%WeightedRe = (
    'SuspiciousVirus'      => 1,
    'weightedAddresses'    => 'blValencePB',
    'spamFriends'          => 'friendsValencePB',
    'bombRe'               => 'bombValencePB',
    'bombSenderRe'         => 'bombValencePB',
    'bombHeaderRe'         => 'bombValencePB',
    'bombSubjectRe'        => 'bombValencePB',
    'bombCharSets'         => 'bombValencePB',
    'bombCharSetsMIME'     => 'bombValencePB',
    'bombDataRe'           => 'bombValencePB',
    'bombSuspiciousRe'     => 'bombValencePB',
    'blackRe'              => 'blackValencePB',
    'scriptRe'             => 'bombValencePB',
    'CountryCodeBlockedRe' => 1,
    'CountryCodeRe'        => 1,
    'blackSenderBase'      => 1,
    'MyCountryCodeRe'      => 1,
    'whiteSenderBase'      => 1,
    'testRe'               => 1,
    'invalidHeloRe'        => 'ihValencePB',
    'invalidPTRRe'         => 'ptiValencePB',
);
%WeightedReOverwrite = (
    'bombRe'           => 0,
    'bombSenderRe'     => 0,
    'bombHeaderRe'     => 0,
    'bombSubjectRe'    => 0,
    'bombCharSets'     => 0,
    'bombDataRe'       => 0,
    'bombSuspiciousRe' => 0,
    'blackRe'          => 0,
    'scriptRe'         => 0,
    'invalidHeloRe'    => 0,
    'invalidPTRRe'     => 0,
);
### end global vars
$logfile    = $Config{logfile};      # set the log parms to preenable logging
$asspLog    = $Config{asspLog};
$sysLog     = $Config{sysLog};
$SysLogFac  = $Config{SysLogFac};
$sysLogPort = $Config{sysLogPort};
$sysLogIp   = $Config{sysLogIp};
&fixConfigSettings();
PrintConfigSettings() if ! SaveConfigSettings();
chmod 0666, "$base/assp.cfg";

sub niceConfigPos {
 my $counterT = -1;
 my $num = 0;
 foreach my $c (@ConfigArray) {
   if(@{$c} == 5) {
      $counterT++;
   } else {
      $ConfigPos{$c->[0]} = $counterT;
      $ConfigNum{$c->[0]} = ++$num;
   }
 }
}

sub niceConfig {
 %ConfigNice = ();
 %ConfigDefault = ();
 %ConfigListBox = ();
 foreach my $c (@ConfigArray) {
      my $value;
      next if(@{$c} == 5) ;
      $ConfigNice{$c->[0]} =  ($c->[10] && $WebIP{$ActWebSess}->{lng}->{$c->[10]})
                              ? encodeHTMLEntities($WebIP{$ActWebSess}->{lng}->{$c->[10]})
                              : encodeHTMLEntities($c->[1]);
      $ConfigNice{$c->[0]} =~ s/<a\s+href.*<\/a>//io;
      $ConfigNice{$c->[0]} =~ s/'|"|\n//go;
      $ConfigNice{$c->[0]} =~ s/\\/\\\\/go;
      $ConfigNice{$c->[0]} = '&nbsp;' unless $ConfigNice{$c->[0]};
      $ConfigDefault{$c->[0]} = encodeHTMLEntities($c->[4]);
      $ConfigDefault{$c->[0]} =~ s/'|"|\n//go;
      $ConfigDefault{$c->[0]} =~ s/\\/\\\\/go;

      $value = ($qs{theButton} || $qs{theButtonX}) ? $qs{$c->[0]} : $Config{$c->[0]} ;
      $value = $Config{$c->[0]} if $qs{theButtonRefresh};

      if ($c->[3] == \&listbox) {
          $ConfigDefault{$c->[0]} = 0 unless $ConfigDefault{$c->[0]};
          foreach my $opt ( split( /\|/o, $c->[2] ) ) {
                my ( $v, $d ) = split( /:/o, $opt, 2 );
                $ConfigDefault{$c->[0]} = $d if ( $ConfigDefault{$c->[0]} eq $v );
                $ConfigListBox{$c->[0]} = $d if ( $value eq $v );
                $ConfigListBoxAll{$c->[0]}{$v} = $d;
          }
      } elsif ($c->[3] == \&checkbox) {
                $ConfigDefault{$c->[0]} = $ConfigDefault{$c->[0]} ? 'On' : 'Off';
                $ConfigListBox{$c->[0]} = $value ? 'On' : 'Off';
      } else {
          $ConfigDefault{$c->[0]} = '&nbsp;' unless $ConfigDefault{$c->[0]};
          $ConfigListBox{$c->[0]} = $value;
      }
#      mlog( '',"c : $c->[0] : $ConfigDefault{$c->[0]}" );
 }
}

sub niceLink {
    my $c = shift;
    my $i = 0;
    my %v = ();
    while ($c =~ s/(\$[a-zA-Z0-9_{}\[\]\-\>\$]+)/\[\%\%\%\%\%\]/o) {
        my $var = $1;
        $v{$i} = eval($var);
        $v{$i} = $var unless defined $v{$i};
        $i++;
    }
    $i = 0;
    while ($c =~ s/\[\%\%\%\%\%\]/$v{$i}/o) {$i++}
    my $newline;
    foreach my $word (split(/ /o,$c)) {
         my $orgword = $word;
         $word =~ s/[^a-zA-Z0-9_]//go;
         if (exists $Config{$word} && ($rootlogin or ! $AdminUsersRight{"$WebIP{$ActWebSess}->{user}.user.hidDisabled"})) {
              my $alt = $ConfigNice{$word};
              my $value = encodeHTMLEntities($ConfigListBox{$word});
              $value =~ s/'|"|\n//go;
              $value =~ s/\\/\\\\/go;
              $value = '&nbsp;' unless $value;
              $value = 'ENCRYPTED' if exists $cryptConfigVars{$word};
              my $default = exists $cryptConfigVars{$word} && $word ne 'webAdminPassword'? 'ENCRYPTED' : $ConfigDefault{$word};
              my $subst = "<a href=\"./#$word\" style=\"color:#684f00\" onmousedown=\"showDisp('$ConfigPos{$word}');gotoAnchor('$word');return false;\" onmouseover=\"window.status='$alt'; showhint('<table BORDER CELLSPACING=0 CELLPADDING=4 WIDTH=\\'100%\\' bgcolor=lightyellow><tr><td>config var:</td><td>$word</td></tr><tr><td>description:</td><td>$alt</td></tr><tr><td>current value:</td><td>$value</td></tr><tr><td>default value:</td><td>$default</td></tr></table>', this, event, '450px', '1'); return true;\" onmouseout=\"window.status='';return true;\">$word</a>" ;
              $orgword =~ s/$word/$subst/;
         }
         $newline .= " $orgword";
    }
    return $newline;
}

SaveConfigSettings();$|=1;
chmod 0666, "$base/assp.cfg";

our $BayesCont = '-\$A-Za-z0-9\'\.!\xA0-\xFF';
our $TLDSRE;
# URI components - RFC3986, section 2, 'Characters'
our $URIContinuationRe   = '\=(?:\015?\012|\015)';
our $URIEncodedCharRe    = '[\=\%][a-f0-9]{2}|\&\#\d{1,3}\;?';
our $URIUnreservedCharRe = '[a-z0-9\-\_\.\~]';
our $URIGenDelimsCharRe  = '[\:\/\?\#\[\]\@]';
our $URISubDelimsCharRe =
  '[\!\$\&\'\(\)\*\+\,\;\=\%\^\`\{\}\|]';    # relaxed to a few other characters
our $URIReservedCharRe = $URIGenDelimsCharRe . '|' . $URISubDelimsCharRe;

# URI compounds
our $URICommonRe =
  $URIContinuationRe . '|' . $URIEncodedCharRe . '|' . $URIUnreservedCharRe;
our $URIHostRe = '(?:' . $URICommonRe . '|' . $URISubDelimsCharRe . ')+';
our $URIRe     = '(?:' . $URICommonRe . '|' . $URIReservedCharRe . ')+';

sub setMainLang {

$lngmsghint{'msg500011'} = '# main form buttom hint 1';
$lngmsg{'msg500011'} = 'The CIDR notation is allowed(182.82.10.0/24).';

$lngmsghint{'msg500012'} = '# main form buttom hint 2';
$lngmsg{'msg500012'} = '<br />Text after the range (and before a numbersign) will be accepted as comment which will be shown in a match (for example: 182.82.10.0/24 Yahoo Groups #comment not shown).' ;

$lngmsghint{'msg500013'} = '# main form buttom hint 3';
$lngmsg{'msg500013'} = 'CIDR notation is accepted (182.82.10.0/24).' ;

$lngmsghint{'msg500014'} = '# main form buttom hint 4';
$lngmsg{'msg500014'} = '<br />Text after the range (and before a numbersign) will be accepted as comment to be shown in a match. For example:<br />182.82.10.0/24 Yahoo #comment to be removed<br />The short notation like 182.82.10. is only allowed for IPv4 addresses, IPv6 addresses must be fully defined as for example 2201:1::1 or 2201:1::/96<br />' ;

$lngmsghint{'msg500015'} = '# main form buttom hint 5';
$lngmsg{'msg500015'} = 'If Net::CIDR::Lite is installed, hyphenated ranges can be used (182.82.10.0-182.82.10.255).';

$lngmsghint{'msg500016'} = '# main form buttom hint 6';
$lngmsg{'msg500016'} = 'Hyphenated ranges can be used (182.82.10.0-182.82.10.255).';

$lngmsghint{'msg500017'} = '# main form buttom hint 7';
$lngmsg{'msg500017'} = 'For defining any full filepathes, always use slashes ("/") not backslashes. For example: c:/assp/certs/server-key.pem !<br /><br />';

$lngmsghint{'msg500018'} = '# main form buttom hint 8';
$lngmsg{'msg500018'} = <<EOT;

Fields marked with at least one asterisk (*) accept a list separated by '|' (for example: abc|def|ghi) or a file designated as follows (path relative to the ASSP directory): 'file:files/filename.txt'.  Putting in the <i>file:</i> will prompt ASSP to put up a button to edit that file. <i>files</i> is the subdirectory for files. The file does not need to exist, you can create it by saving it from the editor within the UI. The file must have one entry per line; anything on a line following a numbersign or a semicolon ( # ;) is ignored (a comment).<br />
It is possible to include custom-designed files at any line of such a file, using the following directive<br />
<span class="positive"># include filename</span><br />
where filename is the relative path (from $base) to the included file like files/inc1.txt or inc1.txt (one file per line). The line will be internaly replaced by the contents of the included file!<br /><br />
Fields marked with two asterisk (**) contains regular expressions (regex) and accept a second weight value. Every weighted regex that contains at least one '|' has to begin and end with a '~' - inside such regexes it is not allowed to use a tilde '~', even it is escaped - for example:  ~abc<span class="negative"><b>\\~</b></span>|def~=>23 or ~abc<span class="negative"><b>~</b></span>|def~=>23 - instead use the octal (\\126) or hex (\\x7E) notation , for example <span class="positive">~abc\\126|def~=>23 or ~abc\\x7E|def~=>23</span> . Every weighted regex has to be followed by '=>' and the weight value. For example: Phishing\\.=>1.45|~Heuristics|Email~=>50  or  ~(Email|HTML|Sanesecurity)\\.(Phishing|Spear|(Spam|Scam)[a-z0-9]?)\\.~=>4.6|Spam=>1.1|~Spear|Scam~=>2.1 . The multiplication result of the weight and the penaltybox valence value will be used for scoring, if the absolute value of weight is less or equal 6. Otherwise the value of weight is used for scoring. It is possible to define negative values to reduce the resulting message score.<br />
For all "<span class="positive">bomb*</span>" regular expressions and "<span class="positive">invalidHeloRe</span>" and "<span class="positive">invalidPTRRe</span>" it is possible to define a third parameter (to overwrite the default options) after the weight like: Phishing\\.=>1.45|~Heuristics|Email~=>50<span class="positive">:>N[+-]W[+-]L[+-]I[+-]</span>. The characters and the optional to use + and - have the following functions:<br />
use this regex (+ = only)(- = never) for: N = noprocessing , W = whitelisted , L = local , I = ISP mails . So the line ~Heuristics|Email~=>50:>N-W-LI could be read as: take the regex with a weight of 50, never scan noprocessing mails, never scan whitelisted mails, scan local mails and mails from ISP's (and all others). The line ~Heuristics|Email~=>3.2:>N-W+I could be read as: take the regex with a weight of 3.2 as factor, never scan noprocessing mails, scan only whitelisted mails even if they are received from an ISP .<br />
If the third parameter is not set or any of the N,W,L,I is not set, the default configuration for the option will be used unless a default option string is defined anywhere in a single line in the file in the form !!!NWLI!!! (with + or - is possible).<br />
<span class="negative">If any parameter that allowes the usage of weighted regular expressions is set to "block", but the sum of the resulting weighted penalty value is less than the corresponding "Valence Value" (because of lower weights) - only scoring will be done!</span><br />
The literal 'MYNAME' will be replaced by the configuration value defined in 'myName' in every SMTP error reply.<br /><br />
If the internal name is shown in light blue like <span style="color:#8181F7">(uniqueIDPrefix)</span> , this indicates that the configured value differs from the defaut value. To show the default value, move the mouse over the internal name. An click on the internal name will reset the value to the default.<br /><br />
IP blocks are defined for example 182.82.10.
EOT

$lngmsghint{'msg500019'} = '# main form buttom hint 9';
$lngmsg{'msg500019'} = <<EOT;

EOT

$lngmsghint{'msg500020'} = '# manage users form hint';
$lngmsg{'msg500020'} = <<EOT;
Use the "Continue" button as long as you only want to see or to temporary change any parameter.
Use the "Apply Changes" button to apply all changes, that are currenty shown, to the user.
All user names that begins with a "~" are templates. The template "~DEFAULT" can't be deleted.
All permissions of a user can refer to a template, in this case the permission of the template
belongs to the user. If the template permission is changed, all user permissions
that refers to that template will also be changed. Template permissions can never refer to an
another user or template. It is possible to copy all permissions of a template or an user to
another user or template. If "use LDAP / LDAP host" is filled with an IP-address or hostname
the local password will only be used, if the LDAPhost is not available. If a LDAP login is
successful, the LDAP-password will be stored as local password. It is possible to configure
multiple LDAP hosts separated by "|". To navigate use the alpha-index on the left site.
EOT

$lngmsghint{'msg500031'} = '# White/Redlist/Tuplets';
$lngmsg{'msg500031'} = <<EOT;
Do you want to work with the:
EOT

$lngmsg{'msg500032'} = <<EOT;
Do you want to:
EOT

$lngmsg{'msg500033'} = <<EOT;
<p>Post less than 1 megabyte of data at a time.</p>
Note: The redlist is not a blacklist. The redlist is a list of addresses that cannot
contribute to the whitelist. For example, if someone goes on a vacation and turns on their
email's autoresponder, put them on the redlist until they return. Then as they reply
to every spam they receive they won't corrupt your non-spam collection or whitelist.
To add or remove global whitelist entries use emailaddress,* .
To add or remove domain whitelist entries use emailaddress,\@domain .
EOT

$lngmsg{'msg500034'} = <<EOT;
<p class="warning">Warning: If your whitelist or redlist is long, pushing these buttons
 is ill-advised. Use these for testing and while your whitelist is short.</p>
EOT

$lngmsghint{'msg500040'} = '# Recipient Replacement Test';
$lngmsg{'msg500040'} = '<p><a href="./#ReplaceRecpt">go to ReplaceRecpt to configure rules</a></p>';
$lngmsg{'msg500041'} = '<p><span class="negative"><a href="./#ReplaceRecpt">ReplaceRecpt</a> is not configured - please do this first!</span></p>';
$lngmsg{'msg500042'} = '<p>to modify the replacement rules, open the file by clicking edit ';

$lngmsg{'msg500043'} = '<p>the following replacement rules were processed</p><br />';

$lngmsghint{'msg500050'} = '# View Maillog Tail';
$lngmsg{'msg500050'} = <<EOT;
Refresh your browser or click [Search/Update] to update this screen. Newest entries are at the end. The search will stop, if the [search for] field is blank - and [tail bytes] is reached, or if the [search for] field is not blank - and [search in] or the number of [results] is reached. If you search for more than one word, all words must match. Words with a leading \\'-\\' will be negated. For example: a search pattern \\'user -root\\', will search all lines which contains the word \\'user\\' but not the word \\'root\\'!
EOT

$lngmsg{'msg500051'} = <<EOT;
Select [file lines only], if you want to reduce the shown number of lines to such (POST filter), which contains filenames.<br /><br /> Use the MaillogTail function carefully, while ASSP is processing any request, no new connections will be accepted by ASSP, and this could take some minutes, if you search in large or many maillogs! To start realtime maillog, click on [Auto], to stop realtime maillog, click on [Stop].
EOT

$lngmsg{'msg500052'} = <<EOT;
If [this file number(s)] is selected, you can define a single filenumber or a comma separated list of filenumbers here - like: <b>1,5,8,7,6 or 10,2...7,11,14-19,21,23...26</b>  A defined range 2...7 or 2-7 will include all numbers from 2 to 7. The resulting numbers will be internaly sorted ascending and the files will be used in that sorted order.
EOT

$lngmsg{'msg500053'} = <<EOT;
Enter the search string - for more help use the [help] link. If you want to start the realtime log [Auto], you can define the number of lines to show in the browser [1 - 33] here.
EOT

$lngmsghint{'msg500060'} = '# Mail Analyzer';
$lngmsg{'msg500060'} = <<EOT;
This page will show you how ASSP analyzes and pre-processes an email to come up with the assigned spam probability. Regular Expressions will always check the full message. To analyze/modify individual email addresses click <a href="javascript:void(0);" onclick="popAddressAction('example\@$myName');return false;">here</a>. To analyze/modify individual IP addresses click <a href="javascript:void(0);" onclick="popIPAction('1.1.1.1');return false;">here</a>.
EOT

$lngmsg{'msg500061'} = <<EOT;
Copy and paste the mail header and body here:
EOT

$lngmsg{'msg500062'} = <<EOT;
You may put here <b>helo=aaa.bbb.helo or ip=123.123.123.123</b>to look up the helo/ip information.
<br /><br /><b>Putting a textstring like "abc" into the analyze frame will start a lookup in the regular expression files for the "abc" matching regex.<br />
Put helo=domain.com and ip=123.123.123.123 in two lines, to lookup SPF results.</b>
<p>Note: Analysis is performed using the current spam database --
if yours was rebuilt since the time the mail was received you'll
receive a different result.</p>
EOT

$lngmsg{'msg500063'} = <<EOT;
<p>To use this form using <i>Outlook Express</i> do the following. Right-click on the message
of interest. Select <i>Properties</i>. Click the <i>Details</i> tab. Click the <i>message
source</i> button. Right-click on the message source and click <i>Select All</i>. Right-click
again and click <i>Copy</i>. Click on the text box above and paste (Ctrl-V perhaps). Click
the <i>Analyze</i> button.</p>
<p>The page will update to show you the following: if any of the email's addresses are in
the redlist or whitelist, the most and least spammy phrases together with their spaminess,
the resulting probabilities (probabilities may repeat one time), and the final spam probability
score.
EOT

$lngmsghint{'msg500070'} = '# Shutdown/Restart';
$lngmsg{'msg500070'} = <<EOT;
Note: It's possible to restart, if ASSP runs as a service or in a script that restarts it after it stops or it runs on WIN32 version Windows 2000(or higher) or it runs on linux,
otherwise this function can only shut ASSP down. <br />
The following command will be started in OS-shell, if ASSP runs not as a service or daemon:<br /><b><font color=green>$AutoRestartCmd</font></b>
EOT

$lngmsghint{'msg500080'} = '# EDIT files window/frame';
$lngmsg{'msg500080'} = <<EOT;
<span class="negative">Attention: This is the real database content!<br />
Incorrect editing hash lists could result in unexpected behavior or dieing ASSP!</span><br />
Use |::| as terminator between key and value, for example: 102.1.1.1|::|1234567890 !<br />
If a time is shown human readable, you can change the date or time,<br />
but leave the format as it is ([+]YYYY-MM-DD,hh:mm:ss) and leave a possible \'+\' in front.<br />
Use only one pair of key and value per line.<br />
Comments are not allowed!<br />
While the hash is saved, ASSP is unable to accept new connections!<br />
Be carefull saveing large hash here, this could take very long time. Better save the new contents of large hashes and lists to the Importfile, if this option is available. If possible, the DB-Import will be started immediately by the MaintThread!<br />
After saving the contents to the Importfile, you should close this windows and wait until the import has finished!
EOT

$lngmsg{'msg500081'} = 'File should have one entry per line; anything on a line following a numbersign ( #) is ignored (a comment). Whitespace at the beginning or end of the line is ignored.';
$lngmsg{'msg500082'} = 'First line specifies text that appears in the subject of report message. The remaining lines are the report message body.';
$lngmsg{'msg500083'} = 'Put here comments to your assp installation.';
$lngmsg{'msg500084'} = 'For removal of entries from BlackBox use <a onmousedown="showDisp(\'$ConfigPos{noPB}\')" target="main" href="./#noPB">noPB</a>.
For removal of entries from WhiteBox  use <a onmousedown="showDisp(\'$ConfigPos{noPBwhite}\')" target="main" href="./#noPBwhite">noPBwhite</a>. For  whitelisting IP\'s use <a onmousedown="showDisp(\'$ConfigPos{whiteListedIPs}\')" target="main" href="./#whiteListedIPs">Whitelisted IP\'s</a> or <a onmousedown="showDisp(\'$ConfigPos{noProcessingIPs}\')" target="main" href="./#noProcessingIPs">No Processing IP\'s</a>. For blacklisting use <a onmousedown="showDisp(\'$ConfigPos{denySMTPConnectionsFrom}\')" target="main" href="./#denySMTPConnectionsFrom">Deny SMTP Connections From these IP\'s</a> and <a onmousedown="showDisp(\'$ConfigPos{denySMTPConnectionsFromAlways}\')" target="main" href="./#denySMTPConnectionsFromAlways">Deny SMTP Connections From these IP\'s Strictly</a>.';

$lngmsg{'msg500086'} = 'CacheEntry: IP/Domain \'11\' CacheIntervalStart 1=fail/2=pass Result/Comment';

$lngmsg{'msg500090'} = 'To take an action, select the action and click "Do It!". To move a file to an other location, just copy and delete the file!';
$lngmsg{'msg500091'} = '<br /> For "resend file" action install Email::Send  modules!';

$lngmsg{'msg500092'} = 'IP ranges can be defined as: 182.82.10. ';

$lngmsghint{'msg500093'} = '# the following messages are in one line 0093.$records.0094';
$lngmsg{'msg500093'} = 'This hash/list seems to be too large (';
$lngmsg{'msg500094'} = 'records) to save it from GUI!';

$lngmsg{'msg500095'} = 'Please close this window, and wait until import has finished.';
$lngmsg{'msg500096'} = "This file was trunked to (MaxBytes) $MaxBytes byte. If you resend this file, the resulting view and/or attachments would be destroyed!";

$lngmsghint{'msg500100'} = '# SMTP-Connection - link - hintbox';
$lngmsg{'msg500100'} = 'Click here to open a SMTP-Connections-Window that never stops refreshing. Do not make any changes in the main window, while this SMTP-Connections-Window is still opened! A SMTP-Connections-Window which is started with the default (left beside) link, will stop refreshing if it is not in forground.';

}

sub renderConfigHTML {
  setMainLang();
  my $maillogEnd;
  if ($MaillogTailJump) {
    $maillogEnd = '#MlEnd';
  } else {
    $maillogEnd = '#MlTop';
  }
  $maillogJump = '<a href="javascript:void(0);" onclick="MlEndPos=document.getElementById(\'LogLines\').scrollTop; document.getElementById(\'LogLines\').scrollTop=0; return false;">Go to Top</a><a name="MlEnd"></a>';
  my $IndexPos = $hideAlphaIndex ? '451' : '440';
  my $IndexStart = $hideAlphaIndex ? '452' : '442';
  my $JavaScript;

  $plusIcon = 'get?file=images/plusIcon.png';
  $minusIcon = 'get?file=images/minusIcon.png';
  $noIcon = 'get?file=images/noIcon.png';
  $wikiinfo = 'get?file=images/info.png';
  $NavMenu = '

 <div class="menuLevel2">
    </div>';
      $JavaScript = "
<script type=\"text/javascript\">
<!--
var oldBrowser = false;
/*\@cc_on
   /*\@if (\@_jscript_version < 5.6)
      oldBrowser = true;
   /*\@end
\@*/

if (window.navigator.appName == \"Microsoft Internet Explorer\")
{
   var engine;
   if (document.documentMode) // IE8
      engine = document.documentMode;
   else // IE 5-7
   {
      engine = 5; // Assume quirks mode unless proven otherwise
      if (document.compatMode)
      {
         if (document.compatMode == \"CSS1Compat\")
            engine = 7; //standard mode
      }
   }
   if (engine < 8) {oldBrowser = true;}
}
var BrowserDetect = {
    init: function () {
        this.browser = this.searchString(this.dataBrowser) || \"An unknown browser\";
        this.version = this.searchVersion(navigator.userAgent)
            || this.searchVersion(navigator.appVersion)
            || \"an unknown version\";
        this.OS = this.searchString(this.dataOS) || \"an unknown OS\";
    },
    searchString: function (data) {
        for (var i=0;i<data.length;i++)    {
            var dataString = data[i].string;
            var dataProp = data[i].prop;
            this.versionSearchString = data[i].versionSearch || data[i].identity;
            if (dataString) {
                if (dataString.indexOf(data[i].subString) != -1)
                    return data[i].identity;
            }
            else if (dataProp)
                return data[i].identity;
        }
    },
    searchVersion: function (dataString) {
        var index = dataString.indexOf(this.versionSearchString);
        if (index == -1) return;
        return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
    },
    dataBrowser: [
        {
            string: navigator.userAgent,
            subString: \"Chrome\",
            identity: \"Chrome\"
        },
        {     string: navigator.userAgent,
            subString: \"OmniWeb\",
            versionSearch: \"OmniWeb/\",
            identity: \"OmniWeb\"
        },
        {
            string: navigator.vendor,
            subString: \"Apple\",
            identity: \"Safari\",
            versionSearch: \"Version\"
        },
        {
            prop: window.opera,
            identity: \"Opera\"
        },
        {
            string: navigator.vendor,
            subString: \"iCab\",
            identity: \"iCab\"
        },
        {
            string: navigator.vendor,
            subString: \"KDE\",
            identity: \"Konqueror\"
        },
        {
            string: navigator.userAgent,
            subString: \"Firefox\",
            identity: \"Firefox\"
        },
        {
            string: navigator.vendor,
            subString: \"Camino\",
            identity: \"Camino\"
        },
        {        // for newer Netscapes (6+)
            string: navigator.userAgent,
            subString: \"Netscape\",
            identity: \"Netscape\"
        },
        {
            string: navigator.userAgent,
            subString: \"MSIE\",
            identity: \"Explorer\",
            versionSearch: \"MSIE\"
        },
        {
            string: navigator.userAgent,
            subString: \"Gecko\",
            identity: \"Mozilla\",
            versionSearch: \"rv\"
        },
        {         // for older Netscapes (4-)
            string: navigator.userAgent,
            subString: \"Mozilla\",
            identity: \"Netscape\",
            versionSearch: \"Mozilla\"
        }
    ],
    dataOS : [
        {
            string: navigator.platform,
            subString: \"Win\",
            identity: \"Windows\"
        },
        {
            string: navigator.platform,
            subString: \"Mac\",
            identity: \"Mac\"
        },
        {
               string: navigator.userAgent,
               subString: \"iPhone\",
               identity: \"iPhone/iPod\"
        },
        {
            string: navigator.platform,
            subString: \"Linux\",
            identity: \"Linux\"
        }
    ]

};

BrowserDetect.init();
var detectedBrowser = 'ASSP-GUI is running in ' + BrowserDetect.browser + ' version ' + BrowserDetect.version + ' on ' + BrowserDetect.OS;
if (oldBrowser) {
    detectedBrowser = detectedBrowser + ' (old javascript engine and/or browser detected)';
}
// -->
</script>

<script type=\"text/javascript\">
<!--

var configPos = new Array();

";
 foreach my $c (@ConfigArray) {
   next if(@{$c} == 5);
   $JavaScript .= "configPos['$c->[0]']='$ConfigPos{$c->[0]}';";
 }

$JavaScript .= "
function quotemeta (qstr) {
    return qstr.replace( /([^A-Za-z0-9])/g , \"\\\\\$1\" );
}

function toggleDisp(nodeid)
{
  if (nodeid == null) return false;
  if(nodeid.substr(0,9) == 'setupItem')
    nodeid = nodeid.substr(9);
  layer = document.getElementById('treeElement' + nodeid);
  img = document.getElementById('treeIcon' + nodeid);
  if(layer.style.display == 'none')
  {
    layer.style.display = 'block';
    img.src = '$minusIcon';
    if(document.getElementById('setupItem' + nodeid))
      document.getElementById('setupItem' + nodeid).style.display = 'block';
  }
  else
  {
    layer.style.display = 'none';
    img.src = '$plusIcon';
    if(document.getElementById('setupItem' + nodeid))
      document.getElementById('setupItem' + nodeid).style.display = 'none';
  }
}
function showDisp(nodeid)
{
  if (nodeid == null) return false;
  if(nodeid.substr(0,9) == 'setupItem')
    nodeid = nodeid.substr(9);
  layer = document.getElementById('treeElement' + nodeid);
  img = document.getElementById('treeIcon' + nodeid);
  if(layer.style.display == 'none')
  {
    layer.style.display = 'block';
    img.src = '$minusIcon';
    if(document.getElementById('setupItem' + nodeid))
      document.getElementById('setupItem' + nodeid).style.display = 'block';
  }
}
function gotoAnchor(aname)
{
    window.location.href = \"#\" + aname;
    setAnchor(aname);
}
function expand(expand, force)
{
  counter = 0;
  while(document.getElementById('treeElement' + counter))
  {
    if(!expand)
    {
      //dont shrink if this element is the one passed in the URL
      arr = document.getElementById('treeElement' + counter).getElementsByTagName('a');
      txt = ''; found = 0;
      loc = new String(document.location);
      for(i=0; i < arr.length; i++)
      {
        txt = txt + arr.item(i).href;
        tmpHref = new String(arr.item(i).href);
        if(tmpHref.substr(tmpHref.indexOf('#')) == loc.substr(loc.indexOf('#')))
        {
          //give this tree node the right icon
          document.getElementById('treeIcon' + counter).src = '$minusIcon';
          found = 1;
        }
      }
      if(!found | force)
      {
        document.getElementById('treeIcon' + counter).src = '$plusIcon';
        document.getElementById('treeElement' + counter).style.display = 'none';
        if(document.getElementById('setupItem' + counter))
          document.getElementById('setupItem' + counter).style.display = 'none';
      }
    }
    else
    {
      document.getElementById('treeElement' + counter).style.display = 'block';
      document.getElementById('treeIcon' + counter).src = '$minusIcon';
      if(document.getElementById('setupItem' + counter))
        document.getElementById('setupItem' + counter).style.display = 'block';
    }
    counter++;
  }
}

//make the 'rel's work
function externalLinks()
{
  if (!document.getElementsByTagName)
    return;
  var anchors = document.getElementsByTagName(\"a\");
  for (var i=0; i<anchors.length; i++)
  {
    var anchor = anchors[i];
    if (anchor.getAttribute(\"href\")
      && anchor.getAttribute(\"rel\") == \"external\")
      anchor.target = \"_blank\";
  }
}

// handle cookies to remember something
function createCookie(name,value,days) {
    if (! navigator.cookieEnabled) {return null;}
    if (days) {
        var date = new Date();
        date.setTime(date.getTime()+(days*24*60*60*1000));
        var expires = \"; expires=\"+date.toGMTString();
    }
    else var expires = \"\";
    document.cookie = name+\"=\"+value+expires+\"; path=/\";
}

function readCookie(name) {
    return null;
    if (! navigator.cookieEnabled) {return null;}
    var nameEQ = name + \"=\";
    var ca = document.cookie.split(';');
    for(var i=0;i < ca.length;i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
    }
    return null;
}

function eraseCookie(name) {
    if (! navigator.cookieEnabled) {return null;}
    createCookie(name,\"\",-1);
}

function setAnchor(iname)
{
    if (navigator.cookieEnabled) {createCookie('lastAnchor',iname,1);}
}

function initAnchor(doIt)
{
    if (doIt != '1') {return null;}
    if (! navigator.cookieEnabled) {return null;}
    var iname = readCookie('lastAnchor');
    if (! iname || iname == '' || iname == 'delete') {return false;}
    if (window.location.pathname == '/' || window.location.pathname == '') {
        showDisp(configPos[iname]);
        gotoAnchor(iname);
    } else {
        return false;
    }
}
";
 if ($EnableFloatingMenu) {
  $JavaScript .= "
function docHeight()
{
  if (typeof document.height != 'undefined') {
    return document.height;
  } else if (document.compatMode && document.compatMode != 'BackCompat') {
    return document.documentElement.scrollHeight;
  } else if (document.body && typeof document.body.scrollHeight !='undefined') {
    return document.body.scrollHeight;
  }
}
//********************************************************
//* You may use this code for free on any web page provided that
//* these comment lines and the following credit remain in the code.
//* Floating Div from http://www.javascript-fx.com
//********************************************************
// Modified in May 2005 by Przemek Czerkas:
//  - added calls to docHeight()
//  - added bounding params tlx, tly, brx, bry
var ns = (navigator.appName.indexOf(\"Netscape\") != -1);
var d = document;
var px = document.layers ? \"\" : \"px\";
function JSFX_FloatDiv(id, sx, sy, tlx, tly, brx, bry)
{
  var el=d.getElementById?d.getElementById(id):d.all?d.all[id]:d.layers[id];
  window[id + \"_obj\"] = el;
  if(d.layers)el.style=el;
  el.cx = el.sx = sx;
  el.cy = el.sy = sy;
  el.tlx = tlx;
  el.tly = tly;
  el.brx = brx;
  el.bry = bry;
  el.sP=function(x,y){this.style.left=x+px;this.style.top=y+px;};
  el.flt=function()
  {
    var pX, pY;
    pX = ns ? pageXOffset : document.documentElement && document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft;
    pY = ns ? pageYOffset : document.documentElement && document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop;
    if(this.sy<0)
      pY += ns ? innerHeight : document.documentElement && document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight;
    this.cx += (pX + Math.max(this.sx-pX, this.tlx) - this.cx)/4;
    this.cy += (pY + Math.max(this.sy-pY, this.tly) - this.cy)/4;
    this.cx = Math.min(this.cx, this.brx);
    this.cy = Math.min(this.cy, this.bry);
    if (ns) {
      this.sP(
        Math.max(Math.min(this.cx+this.clientWidth,document.width)-this.clientWidth,this.sx),
        Math.max(Math.min(this.cy+this.clientHeight,document.height)-this.clientHeight,this.sy)
      );
    } else {
      var oldh, newh;
      oldh = docHeight();
      this.sP(this.cx, this.cy);
      newh = docHeight();
      if (newh>oldh) {
        this.sP(this.cx, this.cy-(newh-oldh));
      }
    }
    setTimeout(this.id + \"_obj.flt()\", 20);
  }
  return el;
}";
 }
 $JavaScript .= '
function popFileEditor(filename,note)
{
  var height = (note == 0) ? 500 : (note == \'m\') ? 580 : 550;
  newwindow=window.open(
    \'edit?file=\'+filename+\'&note=\'+note,
    \'FileEditor\',
    \'width=720,height=\'+height+\',overflow=scroll,toolbar=yes,menubar=yes,location=no,personalbar=yes,scrollbars=yes,status=no,directories=no,resizable=yes\'
  );
      // this puts focus on the popup window if we open a new popup without closing the old one.
      if (window.focus) {newwindow.focus()}
      return false;
}

function popAddressAction(address)
{
  var height = 500 ;
  var link = address ? \'?address=\'+address : \'\';
  newwindow=window.open(
    \'addraction\'+link,
    \'AddressAction\',
    \'width=720,height=\'+height+\',overflow=scroll,toolbar=yes,menubar=yes,location=no,personalbar=yes,scrollbars=yes,status=no,directories=no,resizable=yes\'
  );
      // this puts focus on the popup window if we open a new popup without closing the old one.
      if (window.focus) {newwindow.focus()}
      return false;
}

function popIPAction(ip)
{
  var height = 500 ;
  var link = ip ? \'?ip=\'+ip : \'\';
  newwindow=window.open(
    \'ipaction\'+link,
    \'IPAction\',
    \'width=720,height=\'+height+\',overflow=scroll,toolbar=yes,menubar=yes,location=no,personalbar=yes,scrollbars=yes,status=no,directories=no,resizable=yes\'
  );
      // this puts focus on the popup window if we open a new popup without closing the old one.
      if (window.focus) {newwindow.focus()}
      return false;
}

function popSyncEditor(cfgParm)
{
  setAnchor(cfgParm);
  var height = 400;
  newwindow=window.open(
    \'syncedit?cfgparm=\'+cfgParm,
    \'SyncEditor\',
    \'width=720,height=\'+height+\',overflow=scroll,toolbar=yes,menubar=yes,location=no,personalbar=yes,scrollbars=yes,status=no,directories=no,resizable=yes\'
  );
      // this puts focus on the popup window if we open a new popup without closing the old one.
      if (window.focus) {newwindow.focus()}
      return false;
}

function remember()
{
  var height =  580;
  newwindow=window.open(
    \'remember\',
    \'rememberMe\',
    \'width=720,height=\'+height+\',overflow=scroll,toolbar=no,menubar=yes,location=no,personalbar=yes,scrollbars=yes,status=no,directories=no,resizable=yes\'
  );
      // this puts focus on the popup window if we open a new popup without closing the old one.
      if (window.focus) {newwindow.focus()}
      return false;
}

window.onload = externalLinks;
// -->
</script>';

# JavaScript for alphabetic IndexMenu
 $JavaScript .= '
<style type="text/css" >
<!--
#smenu {background-color:#ffffff; text-align:left; font-size: 90%; border:1px solid #000099; z-Index:200; visibility:hidden; position:absolute; top:100px; left:-'.$IndexPos.'px; width:450px; height:700px;}
#sleftTop {width:420px; height:5%; float:left;font-size: 90%;color:#999999; font-family:arial, helvetica, sans-serif;overflow: hidden;}
#sleft {width:420px; height:94%; float:left;font-size: 90%;color:#999999; font-family:arial, helvetica, sans-serif;overflow-x: hidden;overflow-y: scroll;}
#sright {width:10px; height:99%; float:right;font-size: 90%;color:#999999; font-family:arial, helvetica, sans-serif;overflow: hidden;}
#sright a:link{text-decoration:none; color:#684f00; font-family:arial, helvetica, sans-serif;}
#sright a:visited{text-decoration:none; color:#684f00; font-family:arial, helvetica, sans-serif;}
#sright a:active{text-decoration:none; color:#684f00; font-family:arial, helvetica, sans-serif;}
#sright a:hover{text-decoration:underline; color:#999999; font-family:arial, helvetica, sans-serif;}
-->
</style>

<script type="text/javascript">
<!--
// Sliding Menu Script
// copyright Stephen Chapman, 6th July 2005
// you may copy this code but please keep the copyright notice as well
// ASSP implementation by Thomas Eckardt
var speed = 1;

function changeSlide() {
    var findText = xDOM(\'quickfind\').value;
    if (findText == \'**select**\') findText = \'\';
    var re;
    try {
        re = new RegExp(findText,"i");
        re.test(\'abc\');
    }
    catch(err) {
        alert(\'error in string (regex) : \'+err);
        return false;
    }
    var entries = xDOM(\'sleft\').getElementsByTagName(\'a\');
    for (var i=0; i<entries.length; i++) {
        var id=entries[i].id;
        if (! id) next;
        if (findText == \'\' || re.test(id.substr(3))) {
            setObjDisp(id,\'inline\');
        } else {
            setObjDisp(id,\'none\');
        }
    }
}

function ClientSize(HorW) {
  var myWidth = 0, myHeight = 0;
  if( typeof( window.innerWidth ) == \'number\' ) {
    //Non-IE
    myWidth = window.innerWidth;
    myHeight = window.innerHeight;
  } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
    //IE 6+ in \'standards compliant mode\'
    myWidth = document.documentElement.clientWidth;
    myHeight = document.documentElement.clientHeight;
  } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
    //IE 4 compatible
    myWidth = document.body.clientWidth;
    myHeight = document.body.clientHeight;
  }
  return  HorW == \'w\' ?  myWidth : myHeight;
}

var aDOM = 0, ieDOM = 0, nsDOM = 0; var stdDOM = document.getElementById;
if (stdDOM) aDOM = 1; else {ieDOM = document.all; if (ieDOM) aDOM = 1; else {
var nsDOM = ((navigator.appName.indexOf(\'Netscape\') != -1)
&& (parseInt(navigator.appVersion) ==4)); if (nsDOM) aDOM = 1;}}

function xDOM(objectId, wS) {
  if (stdDOM) return wS ? document.getElementById(objectId).style : document.getElementById(objectId);
  if (ieDOM) return wS ? document.all[objectId].style : document.all[objectId];
  if (nsDOM) return document.layers[objectId];
}
function objWidth(objectID) {var obj = xDOM(objectID,0); if(obj.offsetWidth) return obj.offsetWidth; if (obj.clip) return obj.clip.width; return 0;}
function objHeight(objectID) {var obj = xDOM(objectID,0); if(obj.offsetHeight) return obj.offsetHeight; if (obj.clip) return obj.clip.height; return 0;}
function setObjVis(objectID,vis) {var objs = xDOM(objectID,1); objs.visibility = vis;}
function setObjDisp(objectID,disp) {var objs = xDOM(objectID,1); objs.display = disp;}
function moveObjTo(objectID,x,y) {var objs = xDOM(objectID,1); objs.left = x; objs.top = y;}
function pageWidth() {return window.innerWidth != null? window.innerWidth: document.body != null? document.body.clientWidth:null;}
function pageHeight() {return window.innerHeight != null? window.innerHeight: document.body != null? document.body.clientHeight:null;}
function posLeft() {return typeof window.pageXOffset != \'undefined\' ? window.pageXOffset: document.documentElement.scrollLeft?
 document.documentElement.scrollLeft: document.body.scrollLeft? document.body.scrollLeft:0;}

function posTop() {return typeof window.pageYOffset != \'undefined\' ? window.pageYOffset: document.documentElement.scrollTop?
 document.documentElement.scrollTop: document.body.scrollTop? document.body.scrollTop:0;}

var xxx = 0; var yyy = 0; var dist = distX = distY = 0; var stepx = '.$IndexSlideSpeed.'; var stepy = 0; var mn = \'smenu\';

function disableSlide() {setObjVis(mn,\'hidden\');}
function enableSlide() {setObjVis(mn,\'visible\');}
function distance(s,e) {return Math.abs(s-e)}
function direction(s,e) {return s>e?-1:1}
function rate(a,b) {return a<b?a/b:1}
function setHeight() {var objs = xDOM(mn,1); var h = ClientSize(\'h\'); objs.height = h*0.95 +\'px\';}
function start() {setHeight(); xxx = -'.$IndexStart.'; yyy = 0; var eX = 0; var eY = 100; dist = distX = distance(xxx,eX); distY = distance(yyy,eY); stepx *=
-direction(xxx,eX) * rate(distX,distY); stepy *= direction(yyy,eY) * rate(distY,distX); moveit(); setObjVis(mn,\'visible\');}

function moveit() {var x = (posLeft()+xxx) + \'px\'; var y = posTop() + \'px\'; moveObjTo(mn,x,y);}
function mover() {if (dist > 0) {xxx += stepx; yyy += stepy; dist -= Math.abs(stepx);} moveit(); setTimeout(\'mover()\',speed);}
function slide() {dist = distX; stepx = -stepx; moveit(); setTimeout(\'mover()\',speed*2);return false;}

onload = start;
window.onscroll = moveit;
// -->
</script>
';
# END JavaScript for alphabetic IndexMenu

#start JavaScript for HintBox
$JavaScript .= <<EOT;
<style type="text/css">

#hintbox{ /*CSS for pop up hint box */
position:absolute;
top: 0;
background-color: lightyellow;
width: 150px; /*Default width of hint.*/
padding: 3px;
border:1px solid black;
font:normal 11px Verdana;
line-height:18px;
z-index:300;
border-right: 3px solid black;
border-bottom: 3px solid black;
visibility: hidden;

table { table-layout:fixed; word-wrap:break-word; }
}
</style>
EOT

$JavaScript .= '
<script type="text/javascript">

/***********************************************
* Show Hint script- (c) Dynamic Drive (www.dynamicdrive.com)
* This notice MUST stay intact for legal use
* Visit http://www.dynamicdrive.com/ for this script and 100s more.
*
* implemented in ASSP by Thomas Eckardt
***********************************************/

var horizontal_offset="0px" //horizontal offset of hint box from anchor link

/////No further editting needed

var vertical_offset="20px" //vertical offset of hint box from anchor link. No need to change.
var ie=document.all
var ns6=document.getElementById&&!document.all

function getposOffset(what, offsettype){
    var totaloffset=(offsettype=="left")? what.offsetLeft : what.offsetTop;
    var parentEl=what.offsetParent;
    while (parentEl!=null){
        totaloffset=(offsettype=="left")? totaloffset+parentEl.offsetLeft : totaloffset+parentEl.offsetTop;
        parentEl=parentEl.offsetParent;
    }
    return totaloffset;
}

function iecompattest(){
    return (document.compatMode && document.compatMode!="BackCompat")? document.documentElement : document.body
}

function clearbrowseredge(obj, whichedge, where){
    var edgeoffset=(whichedge=="rightedge")? (parseInt(horizontal_offset)-obj.offsetWidth*where/2)*-1 : parseInt(vertical_offset)*-1;
    if (whichedge=="rightedge"){
        var windowedge=ie && !window.opera? iecompattest().scrollLeft+iecompattest().clientWidth-90 : window.pageXOffset+window.innerWidth-100;
        dropmenuobj.contentmeasure=dropmenuobj.offsetWidth;
        if (windowedge-dropmenuobj.x < dropmenuobj.contentmeasure)
            edgeoffset=dropmenuobj.contentmeasure+obj.offsetWidth/(where+1)+parseInt(horizontal_offset);
    } else {
        var windowedge=ie && !window.opera? iecompattest().scrollTop+iecompattest().clientHeight-15 : window.pageYOffset+window.innerHeight-18
        dropmenuobj.contentmeasure=dropmenuobj.offsetHeight
        if (windowedge-dropmenuobj.y < dropmenuobj.contentmeasure)
            edgeoffset=dropmenuobj.contentmeasure-obj.offsetHeight+parseInt(vertical_offset)
    }
    return edgeoffset
}

function showhint(menucontents, obj, e, tipwidth, currLoc){
    if (document.getElementById("hintbox")){
        dropmenuobj=document.getElementById("hintbox")
        dropmenuobj.innerHTML=menucontents
        dropmenuobj.style.left=dropmenuobj.style.top=-500
        if (tipwidth!=""){
            dropmenuobj.widthobj=dropmenuobj.style
            dropmenuobj.widthobj.width=tipwidth
        }
        dropmenuobj.x=getposOffset(obj, "left")
        dropmenuobj.y=getposOffset(obj, "top");
        if (currLoc != "" && (ie||ns6)) {
            //var postop = ns6 ? 0 : posTop();
            var postop = 0;
            var objTop = yMousePos+postop+parseInt(vertical_offset);
            var Yedge=ie && !window.opera? iecompattest().scrollTop+iecompattest().clientHeight-15 : window.pageYOffset+window.innerHeight-18;
            if (dropmenuobj.offsetHeight + objTop > Yedge) {
                dropmenuobj.style.top=objTop-dropmenuobj.offsetHeight+"px";
            } else {
                dropmenuobj.style.top=objTop+"px";
            }
        } else {
            dropmenuobj.style.top=dropmenuobj.y-clearbrowseredge(obj, "bottomedge", 0)+"px";
        }
        if (currLoc != "") {
            dropmenuobj.style.left=dropmenuobj.x-clearbrowseredge(obj, "rightedge", 0)+obj.offsetWidth+"px";
        } else {
            dropmenuobj.style.left=dropmenuobj.x-clearbrowseredge(obj, "rightedge", 1)+obj.offsetWidth+"px";
        }
        //alert("x="+dropmenuobj.x+" , cb="+clearbrowseredge(obj, \'rightedge\')+" , offset="+obj.offsetWidth);
        //dropmenuobj.style.left=xMousePos+"px"
        dropmenuobj.style.visibility="visible"
        obj.onmouseout=hidetip
    }
}

function hidetip(e){
    dropmenuobj.style.visibility="hidden"
    dropmenuobj.style.left="-500px"
}

function createhintbox(){
    var divblock=document.createElement("div")
    divblock.setAttribute("id", "hintbox")
    document.body.appendChild(divblock)
}

if (window.addEventListener)
    window.addEventListener("load", createhintbox, false)
else if (window.attachEvent)
    window.attachEvent("onload", createhintbox)
else if (document.getElementById)
    window.onload=createhintbox

// Set Netscape up to run the "captureMousePosition" function whenever
// the mouse is moved. For Internet Explorer and Netscape 6, you can capture
// the movement a little easier.
if (document.layers) { // Netscape
    document.captureEvents(Event.MOUSEMOVE);
    document.onmousemove = captureMousePosition;
} else if (document.all) { // Internet Explorer
    document.onmousemove = captureMousePosition;
} else if (document.getElementById) { // Netcsape 6
    document.onmousemove = captureMousePosition;
}

// Global variables
xMousePos = 0; // Horizontal position of the mouse on the screen
yMousePos = 0; // Vertical position of the mouse on the screen
xMousePosMax = 0; // Width of the page
yMousePosMax = 0; // Height of the page

function captureMousePosition(e) {
    if (document.layers) {
        // When the page scrolls in Netscape, the event\'s mouse position
        // reflects the absolute position on the screen. innerHight/Width
        // is the position from the top/left of the screen that the user is
        // looking at. pageX/YOffset is the amount that the user has
        // scrolled into the page. So the values will be in relation to
        // each other as the total offsets into the page, no matter if
        // the user has scrolled or not.
        xMousePos = e.pageX;
        yMousePos = e.pageY;
        xMousePosMax = window.innerWidth+window.pageXOffset;
        yMousePosMax = window.innerHeight+window.pageYOffset;
    } else if (document.all) {
        // When the page scrolls in IE, the event\'s mouse position
        // reflects the position from the top/left of the screen the
        // user is looking at. scrollLeft/Top is the amount the user
        // has scrolled into the page. clientWidth/Height is the height/
        // width of the current page the user is looking at. So, to be
        // consistent with Netscape (above), add the scroll offsets to
        // both so we end up with an absolute value on the page, no
        // matter if the user has scrolled or not.

        if (window.event) {
            xMousePos = window.event.x+document.body.scrollLeft;
            yMousePos = window.event.y+document.body.scrollTop;
        } else {
            if (e) {};
        }
        xMousePosMax = document.body.clientWidth+document.body.scrollLeft;
        yMousePosMax = document.body.clientHeight+document.body.scrollTop;
    } else if (document.getElementById) {
        // Netscape 6 behaves the same as Netscape 4 in this regard
        xMousePos = e.pageX;
        yMousePos = e.pageY;
        xMousePosMax = window.innerWidth+window.pageXOffset;
        yMousePosMax = window.innerHeight+window.pageYOffset;
    }
}
function browserclose () {
    eraseCookie(\'lastAnchor\');
    confirm(\'please logout first ?\');
    return false;
}
if(window.addEventListener) {
    window.addEventListener("close", browserclose, false);
}

function changeTitle(title) {
    document.title = document.title.replace(/^\S+/ ,title);
}

function WaitDiv()
{
    document.getElementById(\'wait\').style.display = \'block\';
}

function WaitDivDel()
{
    document.getElementById(\'wait\').style.display = \'none\';
}
</script>
';
$JavaScript .= <<EOT;
<style type="text/css">
#wait {
    position: absolute;
    width: 350;
    heigth: 100;
    margin-left: 300;
    margin-top: 150;
    background-color: #FFF000;
    text-align: center;
    border: solid 1px #FFFFFF;
}
</style>
EOT
#end JavaScript for HintBox
  $headerHTTP = 'HTTP/1.1 200 OK
Content-type: text/html
Cache-control: no-cache
';
 $headerDTDStrict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
 $headerDTDTransitional = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
';
 $headers = "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">
<head>
  <meta http-equiv=\"content-type\" content=\"application/xhtml+xml; charset=utf-8\" />
  <title>Config ASSP ($myName) Host: $localhostname @ $localhostip</title>
  <link rel=\"stylesheet\" href=\"get?file=images/assp.css\" type=\"text/css\" />
  <link rel=\"shortcut icon\" href=\"get?file=images/favicon.ico\" />
$JavaScript
</head>
<body window.onunload=\"javascript:browserclose();\" window.onClose=\"javascript:browserclose();\"><a name=\"Top\"></a>
<div class=\"wait\" id=\"wait\" style=\"display: none;\">&nbsp;&nbsp; Please wait while loading... &nbsp;&nbsp;</div>
  <div id=\"smenu\"><div id=\"sleftTop\">&nbsp;
";

for ("A"..."Z") {
$headers .= "<a href=\"#$_\" onmousedown=\"gotoAnchor('$_');return false;\">$_&nbsp;</a>";
}
$headers .= "&nbsp;&nbsp;<input id=\"quickfind\" size=\"9\" value=\"**select**\" style=\"background:#eee none; color:#222; font-style: italic\" onfocus=\"if (this.value == '**select**') {this.value='';}\" onchange=\"changeSlide();\" >&nbsp;&nbsp;<img src=\"get?file=images/plusIcon.png\" onmouseover=\"showhint('<table BORDER CELLSPACING=0 CELLPADDING=4 WIDTH=\\'100%\\'><tr><td>Select the values to show. The string is searched anywhere in the value names. A regular expression could be used.</td></tr></table>', this, event, '450px', ''); return true;\">&nbsp;&nbsp;&nbsp;<a href=\"javascript:void();\" onclick=\"xDOM('quickfind').value='';changeSlide();return false;\" onmouseover=\"showhint('<table BORDER CELLSPACING=0 CELLPADDING=4 WIDTH=\\'100%\\'><tr><td>Click to reset view to default.</td></tr></table>', this, event, '450px', ''); return true;\"><img src=\"get?file=images/minusIcon.png\" ></a>\n<hr></div><div id=\"sleft\">\n";
my %Config1 = ();
niceConfig(); 
while (my ($k,$v) = each %Config) {
    $Config1{lc($k)} = $k;
}
my $firstChar = '';

foreach (sort keys %Config1) {
    my $k = $Config1{$_};
    my $name = uc($firstChar) ne uc(substr($k,0,1)) ? 'name="'.uc(substr($k,0,1)).'"' : '';
    $firstChar = uc(substr($k,0,1));

    my $value = $ConfigListBox{$k} ? $ConfigListBox{$k} : encodeHTMLEntities($Config{$k});
    $value =~ s/'|"|\n//go;
    $value =~ s/\\/\\\\/go;
    $value = '&nbsp;' unless $value;
    $value = 'ENCRYPTED' if exists $cryptConfigVars{$k} or $k eq 'webAdminPassword';
    my $default =  $ConfigDefault{$k};
#    mlog( '',"k : $k : $ConfigDefault{$k}" );

    $default = '' if $default eq undef;
    $headers .= "<a $name id=\"sl_$k\" href=\"./#$k\" onmousedown=\"expand(0, 1);showDisp('$ConfigPos{$k}');gotoAnchor('$k');slide();return false;\" onmouseover=\"window.status='$ConfigNice{$k}'; showhint('<table BORDER CELLSPACING=0 CELLPADDING=4 WIDTH=\\'100%\\'><tr><td>config var:</td><td>$k</td></tr><tr><td>description:</td><td>$ConfigNice{$k}</td></tr><tr><td>current value:</td><td>$value</td></tr><tr><td>default value:</td><td>$default</td></tr></table>', this, event, '500px', 'index'); return true;\" onmouseout=\"window.status='';return true;\">&nbsp;<img src=\"$noIcon\" alt=\"$ConfigNice{$k}\" />&nbsp;$k<br /></a>\n";
}

  $headers .= "<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;<br />&nbsp;</div><div id=\"sright\"><a href=\"#\" onclick=\"return slide();return false;\">";
  $headers .= "<img src=\"get?file=images/plusIcon.png\" alt=\"open and close alphabetical index\" /><br />&nbsp;<br \/>";
  $headers .= "<img src=\"get?file=images/minusIcon.png\" alt=\"open and close alphabetical index\" /><br />&nbsp;<br \/>";
  $headers .= "<img src=\"get?file=images/minusIcon.png\" alt=\"open and close alphabetical index\" /><br />&nbsp;<br \/>";
  $headers .= "<img src=\"get?file=images/plusIcon.png\" alt=\"open and close alphabetical index\" /><br />&nbsp;<br \/>";
# do not use spaces in $boardertext - instead use '#'
  my $boardertext = "sorted#config";
  $boardertext =~ s/([^#])/$1<br \/>/go;
  $boardertext =~ s/#/&nbsp;<br \/>/go;
  $headers .= "$boardertext<br />";
  $headers .= "<img src=\"get?file=images/plusIcon.png\" alt=\"open and close alphabetical index\" /><br />&nbsp;<br \/>";
  $headers .= "<img src=\"get?file=images/minusIcon.png\" alt=\"open and close alphabetical index\" /><br />&nbsp;<br \/>";
  $headers .= "<img src=\"get?file=images/minusIcon.png\" alt=\"open and close alphabetical index\" /><br />&nbsp;<br \/>";
  $headers .= "<img src=\"get?file=images/plusIcon.png\" alt=\"open and close alphabetical index\" /><br />&nbsp;<br \/>";
  $headers .= "</a></div></div><p>";
  $headers .= '<table id="TopMenu" class="contentFoot" style="margin:0; text-align:left;" CELLSPACING=0 CELLPADDING=4 WIDTH="100%">
  <tr><td rowspan="3" align="left">';
  if (-e "$base/images/logo.gif") {
      $headers .= "<a href=\"http://assp.sourceforge.net/\" target=\"_blank\"><img src=\"get?file=images/logo.gif\" alt=\"ASSP\" /></a>";
  } else {
      $headers .= "<a href=\"http://assp.sourceforge.net/\" target=\"_blank\"><img src=\"get?file=images/logo.jpg\" alt=\"ASSP\" /></a>";
  }
  $headers .= '</td>
  <td><a href="lists">&nbsp;</a></td>
  <td><a href="lists">&nbsp;</a></td>
  <td><a href="shutdown_list?nocache" target="_blank">&nbsp;</a></td>
  <td><a href="maillog' . $maillogEnd . '">&nbsp;</a></td>
  </tr><tr>';
  my $avv = "$availversion";
  my $stv = "$version$modversion";
  $headers .= 
  "<td>ASSP Version: $stv</td>";
  $avv =~ s/RC/\./gi;
  $stv =~ s/RC/\./gi;
  $avv =~ s/\s|\(|\)//gi;
  $stv =~ s/\s|\(|\)//gi;
  $avv =~ s/\.//gi;
  $stv =~ s/\.//gi;
  $avv =~ s/^19/109/gi; #Make 1.9.x Versions 1.09.x for new download comparison.
  $headers .= "<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href=\"$NewAsspURL\" target=\"_blank\" style=\"color:green;;font-size: 14px;font-family: 'Courier New',Courier,monospace;\">new available ASSP version $availversion</a>" if $avv gt $stv;
  my $webhost = $BlockReportHTTPName ? $BlockReportHTTPName : $localhostname ? $localhostname : 'please_define_BlockReportHTTPName';

  $webhost =~ s/localhost/127\.0\.0\.1/i ;  
  if ($AsASecondary && $webSecondaryPort) {

      $webAdminPort  =~ s/\|.*//go;
      $headers .= 
  "<td><a href='http://$webhost:$webAdminPort/lists' onmouseover=\"showhint('On Primary $webAdminPort', this, event, '200px', '');return false;\"><span class=positive>White/Redlist/Tuplets</span></a></td>
  <td><a href='http://$webhost:$webAdminPort/shutdown_list?nocache' target='connections' onmouseover=\"showhint('On Primary $webAdminPort', this, event, '200px', '');return false;\"><span class=positive>SMTP Connections</span></a></td>
  <td><a href='http://$webhost:$webSecondaryPort/maillog$maillogEnd ' onmouseover=\"showhint('On Secondary $webSecondaryPort', this, event, '200px', '');return false;\">View Maillog Tail</a></td>
  </tr><tr>";

      $headers .= 
  "<td><span class=positive>Started: $starttime</span></td>

  <td><a href=http://$webhost:$webAdminPort/analyze onmouseover=\"showhint('On Primary $webAdminPort', this, event, '200px', '');return false;\"><span class=positive>Mail Analyzer</span></a></td>";

      $headers .= "
          <td><a href=http://$webhost:$webAdminPort/infostats onmouseover=\"showhint('On Primary $webAdminPort', this, event, '200px', '');return false;\"><span class=positive>Info and Stats</span></a></td>";

    $headers .=
  "<td><a href=http://$webhost:$webAdminPort/shutdown onmouseover=\"showhint('On Primary $webAdminPort, this, event, '200px', '');return false;\"><span class=positive>Shutdown/Restart</span></a></td></tr>
  </table>";

  } else { 
    &readSecondaryPID();
      if ($SecondaryPid && $webSecondaryPort) {
        my $webPort = $webSecondaryPort;

          $headers .= 
          "<td><a href=lists>White/Redlist/Tuplets</a></td>
          <td><a href=shutdown_list?nocache target=_blank>SMTP Connections</a></td>
          <td><a href='http://$webhost:$webPort/maillog$maillogEnd ' onmouseover=\"showhint('On Secondary $webPort', this, event, '200px', '');return false;\"><span class=positive>View Maillog Tail</span></a></td>
          </tr><tr>";
    } else {
        $headers .= 
          '<td><a href="lists">White/Redlist/Tuplets</a></td>
          <td><a href="shutdown_list?nocache" target="_blank">SMTP Connections</a></td>
          <td><a href="maillog' . $maillogEnd . '">View Maillog Tail</a></td>
          </tr><tr>';
    }

    $headers .= 
"<td><span class=pass>Started: $starttime</span></td>
 <td><a href=analyze>Mail Analyzer</a></td>";

    $headers .= '
          <td><a href=/infostats>Info and Stats</a></td>';

    $headers .=
"<td><a href=shutdown>Shutdown/Restart</a></td></tr>
</table>";
  }

    $headers =~ s/http:/https:/go if $enableWebAdminSSL && $CanUseIOSocketSSL;

    $headers .= "
<div class=\"navMenu\"";
    $headers .= ' id="navMenu" style="position:absolute"' if $EnableFloatingMenu;

    $headers .= "><div><div style=\"text-align: center;\">
<div class=\"rightButton\" style=\"text-align: center;\">
<a href=\"javascript:void(0);\" onclick=\"remember();return false;\" onmouseover=\"showhint('open the remember me window', this, event, '200px', '');return false;\"><img height=12 width=12 src=\"$wikiinfo\" /></a>&nbsp;<input type=\"button\" value=\"Apply Changes\" onclick=\"document.forms['ASSPconfig'].theButtonX.value='Apply Changes';document.forms['ASSPconfig'].submit();return false;\" />
</div> 
  <hr />
  <a href=\"#\" onmousedown=\"expand(1, 1)\">Expand</a>&nbsp;
  <a href=\"#\" onmousedown=\"expand(0, 1)\">Collapse</a>&nbsp;
  <a href=\"#\" onmousedown=\"slide();return false;\">Index</a></div>
  <hr />
<div class=\"menuLevel1\"><a href=\"/\"><img src=\"$plusIcon\" alt=\"plusicon\" /> Main</a><br /></div>";
    my $counter = 0;
    foreach my $c (@ConfigArray) {
        if ( @{$c} == 5 ) {
            $headers .=
"</div>\n  <div class=\"menuLevel2\">\n  <a onmousedown=\"toggleDisp('$counter')\">"
              . "<img id=\"treeIcon$counter\" src=\"$plusIcon\" alt=\"plusicon\" /> "
              . "$c->[4]</a>\n</div>\n<div id=\"treeElement$counter\" style=\"padding-left: 3px; display: block\">";
            $counter++;
        } else {
            $headers .=
"\n    <div class=\"menuLevel3\"><a href=\"./#$c->[0]\">$c->[0]</a></div>";
        }
    }
    my $runas = $AsAService ? '  service ' : $IsDaemon ? '  daemon' : ' console mode ';
    $runas = ' as Secondary' if $AsASecondary;
    my $user;
    $user = "  root /" if $< == 0 && $^O ne "MSWin32";

    $user = "  " . (getpwuid($<))[0] . " /" if $< != 0 && $^O ne "MSWin32";
    $headers .= "</div>
<div class=\"menuLevel1\">$NavMenu</div>
<hr />
<div class=\"rightButton\" style=\"text-align: center;\">
  <a href=\"javascript:void(0);\" onclick=\"remember();return false;\" onmouseover=\"showhint('open the remember me window', this, event, '200px', '');return false;\"><img height=12 width=12 src=\"$wikiinfo\" /></a>&nbsp;<input type=\"button\" value=\"Apply Changes\" onclick=\"document.forms['ASSPconfig'].theButtonX.value='Apply Changes';document.forms['ASSPconfig'].submit();return false;\" />
</div>
<hr />
<div class=\"menuLevel2\">
    <a href=\"#\" onclick=\"popAddressAction();\"><img src=\"$noIcon\" alt=\"#\" /> work with addresses</a><br />
    <a href=\"#\" onclick=\"popIPAction();\"><img src=\"$noIcon\" alt=\"#\" /> work with IP\'s</a><br />
<hr />
    <a href=\"#\" onclick=\"return popFileEditor(\'/notes/confighistory.txt\',5); \"><img src=\"$noIcon\" alt=\"#\" /> Config History</a><br />
    <a href=\"#\" onclick=\"return popFileEditor(\'/notes/admininfo.txt\',5); \"><img src=\"$noIcon\" alt=\"#\" /> Email Interface</a><br />
    <a href=\"#\" onclick=\"return popFileEditor(\'/notes/configdefaults.txt\',8); \"><img src=\"$noIcon\" alt=\"#\" /> Non-Default Settings</a><br />
    <a href=\"#\" onclick=\"return popFileEditor(\'/docs/assp.cfg.description.txt\',8);\"><img src=\"$noIcon\" alt=\"#\" /> Config Description</a><br />
    <a href=\"#\" onclick=\"return popFileEditor(\'/docs/changelog.txt\',8);\"><img src=\"$noIcon\" alt=\"#\" /> ChangeLog</a>
    <hr />
    <span style=\"font-weight: bold;\">&nbsp;&nbsp;Current PID</span>: $mypid<br />
    &nbsp;&nbsp;$user$runas
</div>
<hr />

<div style=\"text-align: center;\">
  <span class=positive>Started: $starttime</span></div>
</div>
<script type=\"text/javascript\">
  <!--
  ";
    $headers .= 'JSFX_FloatDiv("navMenu",2,50,2,-2,2,99999).flt();'
      if $EnableFloatingMenu;

    if ($AutoUpdateASSPDev) {
        $ChangeLogURL =  $ChangeLogURLDev;
    } else {
        $ChangeLogURL =  $ChangeLogURLStable;
    } 

    $headers .= '
  expand(0,0);
  // -->
  </script>
  ';
    $footers = "
<div class=\"contentFoot\">
  <a href=\"http://www.enginsite.com/Library-Perl-Regular-Expressions-Tutorial.htm\"  rel=\"external\" target=\"_blank\">Regular Expressions</a> |
  <a href=\"http://sourceforge.net/p/assp/wiki/Main_Page/#quick-starts\" target=wiki>Getting Started</a> |
  <a href=\"http://assp.cvs.sourceforge.net/viewvc/assp/asspV1/files/\" rel=\"external\" target=\"_blank\">Option Files</a> |
  <a href=\"donations\" target=\"_blank\">Kudos</a> |
  <a href=\"http://sourceforge.net/projects/assp/files/ASSP%20Installation/ASSP%201.9.X/\" rel=\"external\" target=\"_blank\">Newest Installation</a> |
  <a href=\"http://sourceforge.net/projects/assp/files/ASSP%20Installation/AutoUpdate/ASSP1x/assp.pl.gz\" rel=\"external\" target=\"_blank\">Update</a> |
  <a href=\"http://assp.sourceforge.net/cgi-bin/assp_stats\" rel=\"external\" target=\"_blank\">Stats</a> |
  <a href=\"http://sourceforge.net/p/assp/mailman/\" rel=\"external\" target=\"_blank\">Maillists</a> |
  <a href=\"http://assp.sourceforge.net/forum/\" rel=\"external\" target=\"_blank\">Forums</a> |
  <a href=\"http://sourceforge.net/p/assp/wiki/Main_Page/\" rel=\"external\" target=\"_blank\">Wiki</a>
</div>";
    $kudos = '<div class="kudos">

</div>
';

}

# Notes on general operation & program structure
# We using IO::Select, so don't make any changes that block for long
# as new connections come we create a pair of entries in a hash %Con
# based on the hash of the filehandle, so $Con{$fh} has data for this
# connection. $Con{$fh}->{friend} is the partner socket for the smtp proxy.
# ->{ip} is the ip address of the connecting client
# ->{relayok} tells if we can relay mail for this client
# ->{getline} is a pointer to a function that should be called whan a line of input is received for this filehandle
# ->{mailfrom} is the envelope sender (MAIL FROM: <address>)
# ->{outgoing} is a buffer for outgoing socket traffic (see $writable & &sendque)
# ->{rcpt} are the addresses from RCPT TO: <address> (space separated)
# ->{header} is where the header (and eventually the first 10000 bytes) are stored
# ->{myheader} is where we store our header, we merge it with client's header later
# ->{maillog} if present stream logging is enabled
# ->{maillogbuf} buffer for storing unwritten stream log while waiting for isspam decision
# ->{maillogfh} is the filehandle for logging lines to the maillog
# ->{mailloglength} is the length logged so far (we stop after 10000 bytes)
# ->{spamfound} is a flag used to signal if an email is determined to be spam.
# ->{maillength} is the same as mailloglength but is not reset.
#
# After connection the {getline} field functions like a state machine
# redirecting input to subsequent handlers
#
# whitebody -> getline
#   getbody ->
#     error -> (disconnects)
#     getline -> getheader ->
#       whitebody -> getline
#         error -> (disconnects)
#
# getline looks for MAIL FROM, RCPT TO, RSET
# getheader looks for a blank line then tests for whitelist / spamaddresses
# getbody looks for the . and calls isspam, the Bayesian spam test
# whitebody waits for . and redirects client to server
# error waits for . ignoring data from client (and finishes the maillog)
#
# the server has states like this:
#
# skipok -> reply
#
# skipok traps the 250 ok response from the NOOP Connection from
# reply echos server messages to the client
# reply also looks for a 235 AUTH OK and sets {relayok}=1
sub serviceCheck { }
sub d            { 
$debugprint = $_[0];
return; }

-d "$base/debug"       or mkdir "$base/debug",       0777;
if ($debug && !$AsASecondary) {
    my $fn = localtime();
     $fn =~ s/^... (...) +(\d+) (\S+) ..(..)/$1-$2-$4-$3/;
     $fn =~ s/[\/:]/-/g;
    open( $DEBUG, '>' ,"$base/debug/" . $fn . ".dbg" );
    binmode($DEBUG);
    my $oldfh = select($DEBUG);
    $| = 1;
    select($oldfh);
  }

eval(
    q[sub d {
 my $time = &timestring();
 $time =~ s/[\/:]/-/g;
 $debugprint = $_[0];
 $debugprint =~ s/\n//;
 $debugprint =~ s/\r//;
 $debugprint =~ s/\s+$//;

 print DEBUG "$time <$debugprint>\n";
 w32dbg("(DEBUG) <$debugprint>");
 }]
) if $debug;
my $time = &timestring();
if ($AsASecondary) {    
    fork() && exit 0;
    close STDOUT;
    close STDERR;
    $0 = "secondary ASSP";
    $assp = $0;
    $silent = 1;
} elsif ($AsADaemon) {
    $IsDaemon = 1;
    print "\n$time starting as daemon\n" ;
    fork() && exit 0;
    close STDOUT;
    close STDERR;
    $silent = 1;
} elsif(  $AsAService) {
    close STDOUT;
    close STDERR;
    $silent=1;
}

# open the logfile
printLOG("open");
if ($pidfile && !$AsASecondary) { open(my $FH, ">","$base/$pidfile" ); print $FH $$; close $FH; }

my $logdir;
$logdir = $1 if $logfile =~ /(.*)\/.*/;

-d "$base/$logdir" or mkdir "$base/$logdir", 0755 if $logdir;

if (! $silent) {
      if ($ConsoleCharset) {
          binmode STDOUT, ":encoding($ConsoleCharset)";
          binmode STDERR, ":encoding($ConsoleCharset)";
      } else {
          binmode STDOUT;
          binmode STDERR;
      }
  }

&init();

$SIG{INT}    =    sub {mlog(0,"received 'SIG INT'"); &downASSP("terminated by 'SIG INT'"); exit 1;} if !$AsASecondary;
$SIG{INT}    =    sub {&downSecondary("terminated by 'SIG INT'"); } if $AsASecondary;

$SIG{TERM}   =    sub {mlog(0,"received 'KILL -TERM'"); &downASSP("terminated by 'KILL -TERM'"); exit 1;} if !$AsASecondary;
$SIG{TERM}   =    sub {&downSecondary("terminated by 'KILL -TERM'");} if $AsASecondary;
$SIG{QUIT}   =    \&ConfigRestart if !$AsASecondary;
$SIG{HUP}    =    \&reloadConfigFile;
$SIG{USR1}   =    \&saveSMTPconnections if !$AsASecondary;
$SIG{USR1}   =    "IGNORE" if $AsASecondary;
$SIG{USR2}   =    \&SaveWhitelist if !$AsASecondary;
$SIG{USR2}   =    "IGNORE" if $AsASecondary;
$SIG{PIPE}   =    \&renderConfigHTML if !$AsASecondary;;       
$SIG{SEGV}   =    \&SEGVRestart;
$SIG{'__WARN__'} = sub { warn $_[0] if (!($AsADaemon || $AsAService) && $_[0] !~ /uninitialized/oi)};      

&niceConfigPos();

&renderConfigHTML();

$lastTimeoutCheck = time;

eval {
    while (1)

    {
        &MainLoop;
    }
};
if ($@) {

    my $exmsg = "mainloop exception: $@\n";
    print $exmsg;

    printLOG("print",$exmsg) ;
    writeExceptionLog($exmsg);

    mlog( 0, "mainloop exception: $@", 1, 1 );
    downASSP("try restarting ASSP on exception: $@" );

    restartCMD(1); 
}
sub RemovePid {
    if ($pidfile && !$AsASecondary) {
          d('RemovePid');
          unlink("$base/$pidfile");
      }
    if ($pidfile && $AsASecondary) {
          d('RemovePid_Secondary');
          unlink("$base/$pidfile"."_Secondary");
     }
}

sub restartCMD {

    my ($exception) = @_;
    my $autorestart = 1;
    $autorestart = $AutoRestart if $exception;
    if ($AsAService) {
        mlog(0,"autorestart as a service: cmd.exe /C net stop ASSPSMTP & net start ASSPSMTP",1);
        exec('cmd.exe /C net stop ASSPSMTP & net start ASSPSMTP');
    } elsif ($IsDaemon) {
        if ($AutoRestartCmd && $autorestart) {
            mlog(0,"autorestart as a daemon: $AutoRestartCmd",1);
            exec($AutoRestartCmd);

            exit 1;
        } else {
            exit 1;
        }
    } else {
        if ($AutoRestartCmd && $autorestart) {
            mlog(0,"autorestart: $AutoRestartCmd",1);
            exec($AutoRestartCmd);
              }
        exit 1;
    }
    mlog(0,"autorestart not possible, AutoRestartCmd not configured",1) if !$AsAService;

}

sub checkPrimaryPID {

        if (!-e "$base/$pidfile") {

            return 0;
        }

        our $PID;
        open $PID, "<","$base/$pidfile";
        $PrimaryPid = <$PID>;
        $PrimaryPid =~ s/\r|\n|\s//go;
        close $PID;
        my $primary;
        $primary = kill 0, $PrimaryPid if $PrimaryPid;

        return $PrimaryPid if $primary;
        unlink("$base/$pidfile");
        return 0;
}
sub readSecondaryPID {

        if (!-e "$base/$pidfile". "_Secondary") {

            $SecondaryRunning = 0;
            $SecondaryPid = "";
            return 0;
        }
        my @s     = stat("$base/$pidfile". "_Secondary");
        my $mtime = $s[9];
        our $PID;
        open $PID, "<","$base/$pidfile". "_Secondary";
        my $Pid = <$PID>;
        close $PID;
        $Pid =~ s/\r|\n|\s//go;
        ($SecondaryPid,$webPort) = $Pid =~ /(.*)\:?(.*)?/;
        my $secondary;
        $secondary = kill 0, $SecondaryPid if $SecondaryPid;
        if ($secondary) {
            $SecondaryRunning = 1; 
            return $SecondaryPid;
        } else {
            unlink("$base/$pidfile"."_Secondary");
            $SecondaryRunning = 0;
            $SecondaryPid = 0;
            $webPort = "";
            return 0;
        }
}

sub startSecondary {
        return if $AsASecondary;
        return if !$webSecondaryPort;
        if (&readSecondaryPID())  {

            $SecondaryRunning = 1;
            return 1;
        }
        mlog( 0, "Info: starting Secondary" );

        my $cmd;
        my $assp = $0;
        my $perl = $^X;

        unlink("$base/$pidfile". "_Secondary");

        if ( $^O eq "MSWin32" ) {
            $assp = $base.'\\'.$assp if ($assp !~ /\Q$base\E/io);
            $assp =~ s/\//\\/go;
            my $asspbase = $base;
            $asspbase =~ s/\\/\//go;

            $cmd = "sleep 10;\"$perl\" \"$assp\" \"$asspbase\" --AsASecondary:=1";
        } else {
            $assp = $base.'/'.$assp if ($assp !~ /\Q$base\E/io);
            $cmd = "sleep 10;\"$^X\" \"$assp\" \"$base\"  --AsASecondary:=1";
        }
        d('Secondary - start');
        $cmd = $SecondaryCmd if $SecondaryCmd;

        mlog( 0, "Info: AutostartSecondary started '$cmd'" );

        system($cmd);

        $SecondaryRunning = 1;

        return 1;

}

sub downASSP {
    my $text = shift;

    return if $AsASecondary;
    return if $doShutdownForce;
    $doShutdownForce = 1;
    foreach (keys %SIG) {
       $SIG{$_} = {};
    }
    &closeAllSMTPListeners;
    &SaveStats;
    &SaveCache();
    &SavePB;
    &syncWriteConfig() if $enableCFGShare;

    &SaveWhitelist();
    &SaveRedlist();

    &closeAllWEBListeners;

    &RemovePid;
    mlog(0,"$text");

    return if !$AutostartSecondary;

    &readSecondaryPID();
    mlog(0,"terminating Secondary (PID: $SecondaryPid)") if $SecondaryPid;
    kill INT => $SecondaryPid if $SecondaryPid;

}
sub printSecondary {
    my $text = shift;
    my $time = &timestring();
#    print "$time Secondary($$): $text\n";
    $silent=0;
    mlog(0,"$assp($$): $text");
    $silent=1;
}

sub downSecondary {
    my $text = shift;

    return if !$AsASecondary;
    foreach my $WebSock (@WebSocket) {
            unpoll($WebSock,$readable);
            unpoll($WebSock,$writable);
            close($WebSock) || eval{$WebSock->close;} || eval{$WebSock->kill_socket();};
            delete $SocketCalls{$WebSock};         
    }

    printSecondary( "$text");
    unlink("$base/$pidfile"."_Secondary");
    exit 1;
}

sub closeAllSMTPListeners {

        mlog(0,"info: removing all SMTP listeners");
        foreach my $lsn (@lsn ) {
            eval{close($lsn);} if $lsn;
        }

        foreach my $lsn (@lsn2 ) {
            eval{close($lsn);} if $lsn;
        }

        foreach my $lsn (@lsnSSL ) {
            eval{close($lsn);} if $lsn;
        }

        foreach my $lsn (@lsnRelay ) {
            eval{close($lsn);} if $lsn;
        }

}

sub closeAllWEBListeners {
        my $lsn;
        mlog(0,"info: removing all WEB listeners");

        foreach my $StatSock (@StatSocket) {
            $readable->remove($StatSock);
            close($StatSock) || eval{$StatSock->close;} || eval{$StatSock->kill_socket();};
        }
        foreach my $WebSock (@WebSocket) {
            unpoll($WebSock,$readable);
            unpoll($WebSock,$writable);
            close($WebSock) || eval{$WebSock->close;} || eval{$WebSock->kill_socket();};
            delete $SocketCalls{$WebSock};         
        }
}

sub init {
    my $ver;

    my $perlver = $];

    mlog( 0, "$PROGRAM_NAME version $version$modversion (Perl $]) initializing " );
    mlog( 0, "Starting as root") if $< == 0 && $^O ne "MSWin32";
    mlog( 0, "Error: Starting not as root!!!") if $< != 0 && $^O ne "MSWin32";
    mlog( 0, "Perl>= 5.8 needed for Webinterface!" ) if $] <= "5.008";
    $ver = IO::Socket->VERSION;
      if (! $ver gt '1.30') {
      *{'IO::Socket::blocking'} = *{'main::assp_blocking'};   # MSWIN32 fix for nonblocking Sockets
      mlog(0,"IO::Socket version $ver is too less - recommended is 1.30_01 - hook ->blocking to internal procedure");
      }
     if ($localhostname) {
         mlog(0,"ASSP version $version$modversion (Perl $]) (on $^O)    / $localhostname ($localhostip)");
     } else {
         mlog(0,"ASSP version $version$modversion (Perl $]) (on $^O) running on server: localhost ($localhostip)") ;
     }
    if ($CanUseAvClamd) {
        if ($hConfig->{modifyClamAV}) {
            *{'File::Scan::ClamAV::ping'} = *{'main::ClamScanPing'};
            *{'File::Scan::ClamAV::streamscan'} = *{'main::ClamScanScan'};

        }
        my $clamavd = File::Scan::ClamAV->new(port => $AvClamdPort);

        if ( !$UseAvClamd ) {
            $AvailAvClamd = 1;
            $ver          = $clamavd->VERSION;
            $VerAvClamd   = $ver;
            $ver          = " version $ver" if $ver;
            mlog( 0, "File::Scan::ClamAV module$ver installed but disabled" );
            $CommentAvClamd = "<span class=negative>installed but disabled";

        } elsif ( $clamavd->ping() ) {
            $AvailAvClamd = 1;
            $ver          = $clamavd->VERSION;
            $VerAvClamd   = $ver;
            $ver          = " version $ver" if $ver;
            mlog( 0, "File::Scan::ClamAV module$ver installed and ready" );
            $CommentAvClamd = "<span class=positive>installed and ready";
            mlog( 0, "File::Scan::ClamAV modifyClamAV enabled" ) if $hConfig->{modifyClamAV} ;
            $CommentAvClamd .= "<br />modifyClamAV enabled" if $hConfig->{modifyClamAV};
        } else {
            $AvailAvClamd = 0;
            $ver          = $clamavd->VERSION;
            $VerAvClamd   = $ver;
            $ver          = " version $ver" if $ver;
            mlog( 0, "File::Scan::ClamAV module$ver installed but AvClamdPort not ready, error: ". $clamavd->errstr() );

            $CommentAvClamd =
              "<span class=negative>installed but AvClamdPort not ready, error:<br> " . $clamavd->errstr();
            $CommentAvClamd .= "<br />modifyClamAV enabled" if $hConfig->{modifyClamAV};
        }
    } else {
        $AvailAvClamd = 0;
        $VerAvClamd   = "";
        mlog( 0, "File::Scan::ClamAV module not installed" );
        $CommentAvClamd = "<span class=negative>not installed";
    }

    if ($CanUseLDAP) {
        $ver        = eval('Net::LDAP->VERSION');
        $VerNetLDAP = $ver;
        $ver        = " version $ver" if $ver;
        mlog( 0, "Net::LDAP module$ver installed and available" );
        $CommentNetLDAP = "<span class=positive>LDAP available";
    } else {
        mlog( 0, "Net::LDAP module not installed" );
        $CommentNetLDAP = "<span class=negative>LDAP not available";
    }
    if ($CanUseDNS) {
        $ver       = eval('Net::DNS->VERSION');
        $VerNetDNS = $ver;
        $ver       = " version $ver" if $ver;
        mlog( 0, "Net::DNS module$ver installed" );
        $CommentNetDNS = "<span class=positive>DNS Resolver available";
    } else {
        $CommentNetDNS = "<span class=negative>DNS Resolver not available";
        mlog( 0, "Net::DNS module not installed" );
    }
    if ($CanUseAddress) {
        $ver           = eval('Email::Valid->VERSION');
        $VerEmailValid = $ver;
        $ver           = " version $ver" if $ver;
        mlog( 0, "email::Valid module$ver installed and available" );
        $CommentEmailValid = "<span class=positive>RFC5322 checks available";
    } else {
        $CommentEmailValid = "<span class=negative>RFC5322 checks not available";
        mlog( 0, "email::Valid module not installed" );

    }
    if ($CanUseEMS) {
        $ver    = eval('Email::Send->VERSION');
        $VerEMS = $ver;
        $ver    = " version $ver" if $ver;
        mlog( 0,
            "Email::Send module$ver installed - notification, email-interface, blockreports and resend available" );
        $CommentEMS = "<span class=positive>notification, email-interface, blockreports and resend available";
    } elsif ( !$AvailEMS ) {
        $CommentEMS = "<span class=negative>notification, email-interface, blockreports and resend not available";
        mlog( 0,
"Email::Send module not installed - notification, email-interface, blockreports and resend is not available"
        );
    }

    if ($CanUseAuthenSASL) {
        $ver=eval('Authen::SASL->VERSION'); 
        $VerAuthenSASL=$ver; 
        $ver=" version $ver" if $ver;
        mlog(0,"Authen::SASL module$ver installed - SMTP AUTH is available");
        $CommentAuthenSASL= "<span class=positive>SMTP AUTH is available</span>";
      } elsif (!$AvailAuthenSASL)  {
        $CommentAuthenSASL= "<span class=negative>SMTP AUTH is not available</span>";
        mlog(0,"Authen::SASL module not installed - SMTP AUTH is not available");
      }

    if ($CanUseSPF) {
        $ver = eval('Mail::SPF->VERSION');
        $ver =~ s/^v//gio;    # strip leading 'v'
        $VerMailSPF = $ver;
        $ver = " version $ver" if $ver;
        if ( $VerMailSPF >= 2.001 ) {
            mlog( 0, "Mail::SPF module$ver installed and available" );
            $CommentMailSPF =
"<span class=positive>SPF installed";
        } else {
            mlog( 0, "Mail::SPF module$ver installed but must be >= 2.001" );
            mlog( 0, "Mail::SPF will not be used." );
            $CommentMailSPF = "<span class=negative>disabled, must be >= 2.001";
            $CanUseSPF      = 0;
        }
    } elsif ($AvailSPF) {
        $ver = eval('Mail::SPF->VERSION');
        $ver =~ s/^v//gio;    # strip leading 'v'
        $ver = " version $ver" if $ver;
        mlog( 0, "Mail::SPF module$ver installed but Net::DNS required" );
        $CommentMailSPF = "<span class=negative>will not be used, Net::DNS required";
    } else {
        mlog( 0, "Mail::SPF module not installed" );
        $CommentMailSPF = "<span class=negative>module not installed";
    }
    if ($CanUseSRS) {
        $ver        = eval('Mail::SRS->VERSION');
        $VerMailSRS = $ver;
        $ver        = " version $ver" if $ver;
        mlog( 0,
            "Mail::SRS module$ver installed - Sender Rewriting Scheme available"
        );
        $CommentMailSRS = "<span class=positive>SRS available";
    } elsif ( !$AvailSRS ) {
        mlog( 0,
            "Mail::SRS module not installed - Sender Rewriting Scheme disabled"
        ) if $EnableSRS;
        $CommentMailSRS = "<span class=negative>SRS not installed";
    }
    if ($CanUseHTTPCompression) {
        $ver                 = eval('Compress::Zlib->VERSION');
        $VerCompressZlib     = $ver;
        $ver                 = " version $ver" if $ver;
        $CommentCompressZlib = "<span class=positive>HTTP compression available";
        mlog( 0,
            "Compress::Zlib module$ver installed - HTTP compression available"
        );
    } elsif ( !$AvailZlib ) {
        mlog( 0,
            "Compress::Zlib module not installed - HTTP compression disabled" );
        $CommentCompressZlib = "<span class=negative>HTTP compression not available";
    }
    if ($CanUseMD5) {
        $ver          = eval('Digest::MD5->VERSION');
        $VerDigestMD5 = $ver;
        $ver          = " version $ver" if $ver;
        mlog( 0,
"Digest::MD5 module$ver installed - Greylisting/Delaying can use MD5 keys for hashes"
        );
        $CommentDigestMD5 = "<span class=positive>Greylisting/Delaying can use MD5 keys for hashes";
    } else {
        mlog( 0,
"Digest::MD5 module$ver not installed - Greylisting/Delaying can not use MD5 keys for hashes"
        );
        $CommentDigestMD5 = "<span class=negative>Greylisting/Delaying can not use MD5 keys for hashes</span>";
    }
    if ($CanUseSHA1) {
        $ver          = eval('Digest::SHA1->VERSION');
        $VerDigestSHA1 = $ver;
        $ver          = " version $ver" if $ver;
        mlog( 0,
"Digest::SHA1 module$ver installed - Message-ID tagging (FBMTV) available"
        );
        $CommentDigestSHA1 = "<span class=positive>Message-ID tagging (FBMTV) available</span>";
    } else {
        mlog( 0,
"Digest::SHA1 module$ver not installed - Message-ID tagging (FBMTV)  not available"
        );
        $CommentDigestSHA1 = "<span class=negative>Message-ID tagging (FBMTV) not available</span>";
    }
    if ($CanSearchLogs) {
        $ver                      = eval('File::ReadBackwards->VERSION');
        $VerFileReadBackwards     = $ver;
        $CommentFileReadBackwards = "<span class=negative>searching of log files not available";
        $ver                      = " version $ver" if $ver;
        mlog( 0,
"File::ReadBackwards module$ver installed - searching of log files enabled"
        );
        $CommentFileReadBackwards = "<span class=positive>searching of log files enabled";
    } elsif ( !$AvailReadBackwards ) {
        mlog( 0,
"File::ReadBackwards module not installed - searching of log files disabled"
        );
        $CommentFileReadBackwards = "<span class=negative>searching of log files disabled";
    }
    if ($CanStatCPU) {
        $ver          = eval('Time::HiRes->VERSION');
        $VerTimeHiRes = $ver;
        $ver          = " version $ver" if $ver;
        mlog( 0,
            "Time::HiRes module$ver installed - CPU usage statistics available"
        );
        $CommentTimeHiRes = "<span class=positive>CPU statistics available";
    } elsif ( !$AvailHiRes ) {
        $CommentTimeHiRes = "<span class=negative>CPU statistics disabled";
        mlog( 0,
            "Time::HiRes module not installed - CPU usage statistics disabled"
        );
    }
    if ($CanChroot) {
        $ver = eval('PerlIO::scalar->VERSION');
        $ver = " version $ver" if $ver;
        if ($ChangeRoot)  {
            mlog( 0, "PerlIO::scalar module$ver installed - chroot savy" );
            mlog(0,"error: ChangeRoot - /etc/protocols in $ChangeRoot not found!") unless -e "$ChangeRoot/etc/protocols";
        }
    }
    if ($CanUseSyslog) {
        $ver              = eval('Sys::Syslog->VERSION');
        $VerSysSyslog     = $ver;
        $ver              = " version $ver" if $ver;
        $CommentSysSyslog = "<span class=positive>Unix centralized logging installed";
        mlog( 0,
"Sys::Syslog module$ver installed - Unix centralized logging enabled"
        );
    } elsif ( !$AvailSyslog ) {
        $CommentSysSyslog = "<span class=negative>nix centralized logging disabled";
        mlog( 0, "Sys::Syslog module not installed." )
          if $sysLog && !$sysLogPort;
    }
    if( $^O eq "MSWin32") {
    if ($CanUseNetSyslog) {
        $ver              = eval('Net::Syslog->VERSION');
        $VerNetSyslog     = $ver;
        $ver              = " version $ver" if $ver;
        $CommentNetSyslog = "Network Syslog logging installed";
        mlog( 0,
            "Net::Syslog module$ver installed - network Syslog logging enabled"
        );
    } elsif ( !$AvailNetSyslog ) {
        $CommentNetSyslog = "<span class=negative>Network Syslog logging disabled";
        mlog( 0, "Net::Syslog module not installed." )
          if $sysLog && $sysLogPort;
    }
    }
    if( $^O eq "MSWin32") {
        if ($CanUseWin32Daemon) {
            $ver            = eval('Win32::Daemon->VERSION');
            $VerWin32Daemon = $ver;
            $ver            = " version $ver" if $ver;
            mlog( 0,
            "Win32::Daemon module$ver installed - can run as Win32 service" );
            $CommentWin32Daemon = "<span class=positive>can run as Win32 service";
        } else {

            $CommentWin32Daemon = "<span class=negative>module not installed";
        }
    }

    if ($CanUseTieRDBM) {
        $ver         = eval('Tie::RDBM->VERSION');
        $VerRDBM     = $ver;
        $ver         = " version $ver" if $ver;
        $CommentRDBM = "<span class=positive>mysql usage available";
        mlog( 0, "Tie::RDBM module$ver installed - mysql usage available" );
    } elsif ( !$AvailTieRDBM ) {
        $CommentRDBM = "<span class=negative>mysql usage not available";
        mlog( 0, "Tie::RDBM module not installed - mysql usage not available" );
    }

    if ($CanMatchCIDR) {
        $ver         = eval('Net::IP::Match::Regexp->VERSION');
        $VerCIDR     = $ver;
        $ver         = " version $ver" if $ver;
        $CommentCIDR = "<span class=positive>CIDR notation available";
        mlog( 0,
            "Net::IP::Match::Regexp module$ver installed - CIDR notation for IP range available"
        );
    } else {
        $CommentCIDR = "<span class=negative>CIDR notation not available";
        mlog( 0,
            "Net::IP::Match::Regexp module not installed - CIDR notation for IP range not available"
        );
    }
    if ($CanUseCIDRlite) {
        $ver             = eval('Net::CIDR::Lite->VERSION');
        $VerCIDRlite     = $ver;
        $ver             = " version $ver" if $ver;
        $CommentCIDRlite = "<span class=positive>Hyphenated IP address range available";
        mlog( 0,
"Net::CIDR::Lite module$ver installed - Hyphenated IP address range available"
        );
    } elsif ( !$AvailCIDRlite ) {
        $CommentCIDRlite = "<span class=negative>Hyphenated IP address range not available";
        mlog( 0,
"Net::CIDR::Lite module not installed - Hyphenated IP address range not available"
        );
    }
    if ($CanUseSenderBase) {
        $ver           = eval('Net::SenderBase->VERSION');
        $VerSenderBase = $ver;
        $ver           = " version $ver" if $ver;
        mlog( 0,
"Net::SenderBase module$ver installed - SenderBase Queries available"
        );
        $CommentSenderBase = "<span class=positive>SenderBase Queries available";
    } elsif ( !$AvailSenderBase ) {
        $CommentSenderBase = "<span class=negative>SenderBase Queries not available";
        mlog( 0,
"Net::SenderBase module not installed - SenderBase Queries not available"
        );
    }
    if ($CanUseLWP) {
        $ver        = eval('LWP::Simple->VERSION');
        $VerLWP     = $ver;
        $ver        = " version $ver" if $ver;
        $CommentLWP = "<span class=positive>Download griplist available";
        mlog( 0, "LWP::Simple module$ver installed - griplist available" );
    } elsif ( !$AvailLWP ) {
        $CommentLWP = "<span class=negative>Download griplist not available";
        mlog( 0, "LWP::Simple module not installed - griplist not available" );
    }

    if ($CanUseEMM) {
        $ver    = eval('Email::MIME->VERSION');
        $VerEMM = $ver;
        $ver    = " version $ver" if $ver;
        mlog( 0,
"Email::MIME module$ver installed - attachments detection available"
        );
        $CommentEMM = "<span class=positive>Attachments detection available";
                $org_Email_MIME_parts_multipart = *{'Email::MIME::parts_multipart'};
        *{'Email::MIME::parts_multipart'} = *{'main::parts_multipart'};
        *{'Email::MIME::ContentType::_extract_ct_attribute_value'} = *{'assp_extract_ct_attribute_value'};
        *{'Email::MIME::ContentType::_parse_attributes'} = *{'assp_parse_attributes'};

    } elsif ( !$AvailEMM ) {

        mlog( 0,
"Email::MIME module not installed - attachments detection not available"
        );
        $CommentEMM = "<span class=negative>Attachments detection & blockreport not available";
    }

    if ($CanUseNetSMTP) {
        $ver        = eval('Net::SMTP->VERSION');
        $VerNetSMTP = $ver;
        $ver        = " version $ver" if $ver;
        mlog( 0,
            "Net::SMTP module$ver installed - VRFY Recipients available" );
        $CommentNetSMTP = "<span class=positive>VRFY Recipients available";
    } elsif ( !$AvailNetSMTP ) {
        $CommentNetSMTP = "<span class=negative>VRFY Recipients not available";
        mlog( 0,
            "Net::SMTP module not installed - VRFY Recipients not available"
        );
    }

    if ($AvailIOSocketSSL) {
        $ver            = eval('IO::Socket::SSL->VERSION');
        $VerIOSocketSSL = $ver;
        if ($VerIOSocketSSL < 1.08) {
            $CommentIOSocketSSL = "<span class=negative>Version >= 1.08 required - SSL support not available";
            mlog( 0, "IO::Socket::SSL module$ver installed - Version >= 1.08 required, SSL support not available ");
            $AvailIOSocketSSL = 0;
         } else {
            $ver            = " version $ver" if $ver;
            $CommentIOSocketSSL = "<span class=positive>Secure SSL sockets installed";
            mlog( 0, "IO::Socket::SSL module$ver installed");
            $CanUseIOSocketSSL =
                $AvailIOSocketSSL
                && -f $SSLCertFile
                && -r $SSLCertFile
                && -f $SSLKeyFile
                && -r $SSLKeyFile;
            if ($CanUseIOSocketSSL) {
            $CommentIOSocketSSL = "<span class=positive>SSL support available";
            $CommentIOSocketSSL .= "<span class=negative>TLS not available (enableSSL=off)" if !$enableSSL;
            $CommentIOSocketSSL .= "<span class=negative>TLS available (enableSSL=on)" if $enableSSL;
            mlog(0,"TLS on listenports is switched off by enableSSL") if !$enableSSL;
            mlog(0,"TLS on listenports is switched on by enableSSL") if $enableSSL;
            } else {

                $CommentIOSocketSSL = "SSL support not ready";
                if ( !-f $SSLCertFile ) {
                    $CommentIOSocketSSL .= ", CertFile $SSLCertFile not found";
                    } elsif ( !-r $SSLCertFile ) {
                    $CommentIOSocketSSL .= ", CertFile $SSLCertFile not readable";
                }
                if ( !-f $SSLKeyFile ) {
                    $CommentIOSocketSSL .= ", KeyFile $SSLKeyFile not found";
                } elsif ( !-r $SSLKeyFile ) {
                    $CommentIOSocketSSL .= ", KeyFile $SSLKeyFile not readable";
                }
                mlog( 0, "$CommentIOSocketSSL" );
            }

        }

    } else {
        $CommentIOSocketSSL = "<span class=positive>Secure SSL sockets not installed";
        mlog( 0,
            "IO::Socket::SSL module not installed - SSL support not available"
        );
    }

    if (!$enableINET6) {
            $CommentIOSocketINET6 = "<span class=negative><span class=negative>IPv6 support is disabled in config (enableINET6)";
            mlog( 0,
            "IO::Socket::INET6 module not checked   - IPv6 support is disabled in config (enableINET6)");
            $CanUseIOSocketINET6 = 0;
    } elsif ($AvailIOSocketINET6) {

        $VerIOSocketINET6 = eval('IO::Socket::INET6->VERSION');

        if ($VerIOSocketINET6 < 2.56) {
            $CommentIOSocketINET6 = "<span class=negative>Version >= 2.56 required, IPv6 support not available";
            mlog( 0,
            "IO::Socket::INET6 module$ver installed - but Version >= 2.56 required, IPv6 support not available"
            );
            $CanUseIOSocketINET6 = 0;    
        } else {
            $ver            = " version $ver" if $ver;
            my $sys = ($SysIOSocketINET6 == 1) ? '' : ' - but IPv6 is not supported by your system';
            $CommentIOSocketINET6 = "<span class=positive>IPv6 installed and available$sys";
            mlog( 0,
                "IO::Socket::INET6 module$ver installed - IPv6 installed and available$sys"
            );
            $CanUseIOSocketINET6 = 1;
            $CanUseIOSocketINET6 = 0 if !$SysIOSocketINET6;
       }
    } else {
        $CommentIOSocketINET6 = "<span class=negative>IPv6 support not installed";
        mlog( 0, "IO::Socket::INET6 module not installed - IPv6 support not available" );

        $CanUseIOSocketINET6 = 0;
    }

    my $not;
    $readable = new IO::Select();
    $writable = new IO::Select();

    my $usessl = "HTTP";
    sleep 10;
    my $adminport = $webAdminPort; 
    $adminport = $webSecondaryPort if $AsASecondary && $webSecondaryPort;
    my @dummy;
    my ($WebSocket,$dummy);
    if ($CanUseIOSocketSSL && $enableWebAdminSSL) {
      ($WebSocket,$dummy)   = newListenSSL($adminport,\&NewWebConnection,1);
      @WebSocket = @$WebSocket;
      for (@$dummy) {s/:::/\[::\]:/o;}
      mlog(0,"listening for admin HTTPS connections on webAdminPort @$dummy") if @$dummy;
      $webAdminPortOK = 1 if @$dummy;
      mlog(0,"not listening for admin HTTPS connections on webAdminPort $adminport") if !@$dummy;
      $webAdminPortOK = 0 if !@$dummy;

      } else {
      ($WebSocket,$dummy)   = newListen($adminport,\&NewWebConnection,1);
      for (@$dummy) {s/:::/\[::\]:/o;}
      mlog(0,"listening for admin HTTP connections on webAdminPort @$dummy") if @$dummy;
      $webAdminPortOK = 1 if @$dummy;
      mlog(0,"not listening for admin HTTP connections on webAdminPort $adminport") if !@$dummy;
      mlog(0,"ASSP exiting (already running)!!!!!!!!!!!!!!!!!!") if !@$dummy;
      exit 1 if !@$dummy;
      $webAdminPortOK = 0 if !@$dummy;
      }
    if ($AsASecondary) {
        if (!@$dummy) {        
            my $Pid = &readSecondaryPID();        

            printSecondary( "already running as PID=$SecondaryPid") if $Pid;
            printSecondary( "not listening on webSecondaryPort $adminport") if !$Pid;

            $webAdminPortOK = 0;
            exit 1;

        } else {

            $webAdminPortOK = 1;
            printSecondary( "listening on webSecondaryPort @$dummy") ;

            my $F;
            if ($pidfile) { open( my $FH, ">","$base/$pidfile"."_Secondary" ); print $FH "$$"; close $FH; }

            my $pid = &checkPrimaryPID();
            kill PIPE => $pid if $pid;
        }
    }

    if (!$AsASecondary) {
    my ($lsn,$lsnI)        = newListen( $listenPort,   \&NewSMTPConnection );
    @lsn = @$lsn; @lsnI = @$lsnI;
    for (@$lsnI) {s/:::/\[::\]:/o;}
    $not = "NOT " if !$lsn[0];
    mlog( 0, "NOT listening for SMTP connections on listenPort $listenPort" ) if !$lsn[0];
    mlog(0,"listening for SMTP connections on listenPort @$lsnI") if $lsn[0];

    my ($StatSocket,$dummy); my @dummy;
    if ($CanUseIOSocketSSL && $enableWebStatSSL) {
        my ($StatSocket,$dummy) = newListenSSL( $webStatPort,  \&NewStatConnection );
        @StatSocket = @$StatSocket;
        for (@$dummy) {s/:::/\[::\]:/o;}
        mlog(0,"listening for statistics HTTPS connections on webStatPort @$dummy") if @$dummy;
        mlog(0,"not listening for statistics HTTPS connections on webStatPort $webStatPort") if !@$dummy;
    } else {
        my ($StatSocket,$dummy) = newListen( $webStatPort,  \&NewStatConnection );
        @StatSocket = @$StatSocket;
        for (@$dummy) {s/:::/\[::\]:/o;}
        mlog(0,"listening for statistics HTTP connections on webStatPort @$dummy") if @$dummy;
        mlog(0,"not listening for statistics HTTPS connections on webStatPort $webStatPort") if !@$dummy;
    }

    if ($listenPortSSL) {
        if ($CanUseIOSocketSSL) {
            my ($lsnSSL,$lsnSSLI) = newListenSSL($listenPortSSL, \&NewSMTPConnection );
            @lsnSSL = @$lsnSSL; @lsnSSLI = @$lsnSSLI;
              for (@$lsnSSLI) {s/:::/\[::\]:/o;}
            mlog( 0, "listening for SMTPS (SSL) connections on listenPortSSL @$lsnSSLI" )
              if $lsnSSL[0];
            mlog( 0,
                "NOT listening for SMTPS (SSL) connections on listenPortSSL $listenPortSSL" )
              if !$lsnSSL[0];
        } else {
            mlog( 0,
                "listening for SMTPS (SSL) connections on listenPortSSL $listenPortSSL not enabled" );
        }
    }

    if ($listenPort2) {
        my ($lsn2,$lsn2I) = newListen( $listenPort2, \&NewSMTPConnection );
        @lsn2 = @$lsn2; @lsn2I = @$lsn2I;
        for (@$lsn2I) {s/:::/\[::\]:/o;}
        mlog(0,"listening for additional SMTP connections on listenPort2 @$lsn2I") 
          if $lsn2[0];
        mlog( 0,
            "NOT listening for additional SMTP connections on listenPort2 $listenPort2" )
          if !$lsn2[0];
    }

    # handle the possible relayhost / smarthost option
    if ( $relayHost && $relayPort ) {

        my ($lsnRelay,$lsnRelayI) = newListen( $relayPort, \&NewSMTPConnection );
        @lsnRelay = @$lsnRelay; @lsnRelayI = @$lsnRelayI;
        for (@$lsnRelayI) {s/:::/\[::\]:/o;}
        mlog( 0, "listening for SMTP relay connections on relayPort @$lsnRelayI" )
          if $lsnRelay[0];
        mlog( 0, "NOT listening for SMTP relay connections on relayPort $relayPort" )
          if !$lsnRelay[0];
    }
    }

    $nextNoop           = time;

    $endtime            = $nextNoop + $AutoRestartInterval * 3600;

    $saveWhite          = $nextNoop + $UpdateWhitelist;
    $nextCleanDelayDB   = $nextNoop + $CleanDelayDBInterval;

    $check4queuetime    = $nextNoop + 60;
    $nextConCheck       = $nextNoop + 180;

    $nextDestinationCheck       = $nextNoop + 30;
    $nextResendMail     = $nextNoop + 60;
    $nextLDAPcrossCheck = $nextNoop + 60;
    $nextdetectHourJob     = int($nextNoop / 3600) * 3600 + 3600;
    $nextdetectHourJob += 15 unless $nextdetectHourJob % (24 * 3600);
    my $m = &getTimeDiff($nextdetectHourJob-$nextNoop);
      mlog(0,"info: hourly scheduler is starting in $m") if $MaintenanceLog >=2;
      mlog(0,"info: DEBUG (debug) is set") if $debug;
    $nextDNSCheck         = $nextNoop + 600;
    $nextSCANping         = $nextNoop + 300;

    $NextVersionFileDownload = $nextNoop + 600;
    $NextASSPFileDownload = $nextNoop + 900;
    $NextPOP3Collect     = $nextNoop + 300;
    $NextSyncConfig= $nextNoop + 60;

    $nextCleanIPDom     = $nextNoop + 300;
    $nextDebugClear     = $nextNoop + $DebugRollTime;
    my $assp = $0;
    $assp =~ s/\\/\//g;
    $assp = $base.'/'.$assp if ($assp !~ /\Q$base\E/io);
    if (-e $assp) {
            $FileUpdate{"$assp".'asspCode'} = ftime($assp);
            mlog(0,"info: watching the running script '$assp' for changes")
              if ($AutoRestartAfterCodeChange && ($AsAService || $AsADaemon || $AutoRestartCmd));
    } elsif ($AutoRestartAfterCodeChange) {
            mlog(0,"warning: unable to find running script '$assp' for 'AutoRestartAfterCodeChange'")
              if ($AsAService || $AsADaemon || $AutoRestartCmd);
    }

    my ($uid,$gid); 
    ($uid,$gid) = getUidGid($runAsUser,$runAsGroup) if ($runAsUser || $runAsGroup);
    if ($ChangeRoot) {
        my $chroot;
        eval('$chroot=chroot($ChangeRoot)');
        if ($@) {
            my $msg = "request to change root to '$ChangeRoot' failed: $@";
            mlog( '', $msg );
            die ucfirst($msg);
        } elsif ( !$chroot ) {
            my $msg =
              "request to change root to '$ChangeRoot' did not succeed: $!";
            mlog( '', $msg );
            die ucfirst($msg);
        } else {
            $chroot = $ChangeRoot;
            $chroot =~ s/(\W)/\\$1/g;
            $base   =~ s/^$chroot//i;
            chdir("/");
            mlog( '',
"successfully changed root to '$ChangeRoot' -- new base is '$base'"
            );
        }
    }

    switchUsers( $uid, $gid ) if ( $runAsUser || $runAsGroup );
    $mypid = $$;   
    &renderConfigHTML();
    if ($pidfile && !$AsASecondary) { open( my $FH, ">","$base/$pidfile" ); print $FH $$; close $FH; }

  # create folders if they're missing
    -d "$base/$spamlog"        or mkdir "$base/$spamlog",        0755;
    -d "$base/$notspamlog"     or mkdir "$base/$notspamlog",     0755;
    -d "$base/$incomingOkMail" or mkdir "$base/$incomingOkMail", 0755;
    -d "$base/$discarded"      or mkdir "$base/$discarded",      0755;
    -d "$base/$viruslog"       or mkdir "$base/$viruslog",       0755;
    -d "$FileScanDir"            or mkdir "$FileScanDir",             0755;
    -d "$base/tmp"                or mkdir "$base/tmp",             0777;
    -d "$base/stats"            or mkdir "$base/stats",             0777;

    my $dir = $correctedspam;
    $dir =~ s/\/.*?$//;
    -d "$base/$dir"              or mkdir "$base/$dir",              0755;
    -d "$base/$correctedspam"    or mkdir "$base/$correctedspam",    0755;
    -d "$base/$correctednotspam" or mkdir "$base/$correctednotspam", 0755;

  $pbdir = $1 if $pbdb=~/(.*)\/.*/;
  if ($pbdir) {
     -d  "$base/$pbdir" or mkdir "$base/$pbdir",0755;
     -d  "$base/$pbdir/global" or mkdir "$base/$pbdir/global",0755;
     -d  "$base/$pbdir/global/in" or mkdir "$base/$pbdir/global/in",0755;
     -d  "$base/$pbdir/global/out" or mkdir "$base/$pbdir/global/out",0755;
  }

    -d "$base/notes"       or mkdir "$base/notes",       0755;
    -d "$base/docs"        or mkdir "$base/docs",        0777;
    -d "$base/backup"      or mkdir "$base/backup",      0777;
    -d "$base/$resendmail" or mkdir "$base/$resendmail", 0777;
    -d "$base/files"       or mkdir "$base/files",       0755;
    -d "$base/logs"        or mkdir "$base/logs",        0755;
    -d "$base/starterdb"   or mkdir "$base/starterdb",   0777;
    -d "$base/cache"       or mkdir "$base/cache",       0777;
    -d "$base/reports"     or mkdir "$base/reports",     0755;

  foreach (glob("$base/tmp/*")) {
      unlink "$_";
      mlog(0,"info: delete temporary file $_") if $MaintenanceLog;
  }

  if ($^O ne 'MSWin32') {
      if($setFilePermOnStart) {

          &setPermission($base,oct('0777'),1,1) ;
          $Config{setFilePermOnStart} = '';
          $setFilePermOnStart = '';
          &SaveConfig();
      } elsif ($checkFilePermOnStart) {

          &checkPermission($base,oct('0600'),1,1) ;
      }
  } else {
      if($setFilePermOnStart) {

          $Config{setFilePermOnStart} = $setFilePermOnStart = '';
      } elsif ($checkFilePermOnStart) {

          $Config{checkFilePermOnStart} = $checkFilePermOnStart = '';
      }
  }

  mlog(0,"ASSP restart will be done with AutoRestartCmd: $AutoRestartCmd") if $MaintenanceLog;

    &writeRebuild;
    &downloadStarterDB if $spamdb && $BayesianStarterDB && $enableStarterDB;
    # put this after chroot so the paths don't change
    mlog(0,"warning: no filepath (spamdb) for Bayesian spam database!") if !$spamdb;
    if ($spamdb) {
        if (!-e "$base/$spamdb") {
            if (-e "$base/$spamdb.bak") {
                 copy("$base/$spamdb.bak","$base/$spamdb");
            }   
        } 
        if ( $CanUseTieRDBM && $spamdb =~ /mysql/ ) {
        eval {
            $SpamdbObject = tie %Spamdb, 'Tie::RDBM',
              "dbi:mysql:database=$mydb;host=$myhost",
              {
                user     => "$myuser",
                password => "$mypassword",
                table    => 'spamdb',
                create   => 1
              };
        };
        if ($@) {
            mlog( 0, "spamdb mysql error: $@" );
            $CanUseTieRDBM = 0;
            $spamdb   = "spamdb";
        }
    } else {
        $SpamdbObject = tie %Spamdb, 'orderedtie', "$base/$spamdb";
        $spamdbcount = scalar keys %Spamdb;
          $haveSpamdb = $spamdbcount;
          # check if there are at least 500 records in spamdb (~10KB)

          if ($spamdbcount < 500) {
              mlog(0,"warning: Bayesian spam database (spamdb) has only $spamdbcount records!") ;
            $asspWarnings .= "<span class=negative>warning: Bayesian spam database (spamdb) has only $spamdbcount records!</span><br />";
            $haveSpamdb = 0;
          } else {

              mlog(0,"info: Bayesian spamdb '$spamdb' with $haveSpamdb records") if -e "$base/$spamdb";
          };
    }

        $StarterdbObject = tie %Starterdb, 'orderedtie', "$base/$BayesianStarterDB" ;
        $spamdbcount = 0;

          $spamdbcount = scalar keys %Starterdb;
          $haveStarterdb = $spamdbcount;
          # check if there are at least 500 records in Starterdb (~10KB)
          if (-e "$base/$BayesianStarterDB" && $enableStarterDB){
              if ($spamdbcount < 500) {
                  mlog(0,"warning: Bayesian starter database (spamdb) has only $spamdbcount records!") ;
                $asspWarnings .= "<span class=negative>warning: Bayesian starter database (BayesianStarterDB) has only $spamdbcount records!</span><br />";
                $haveStarterdb = 0;
              } else {

                  mlog(0,"info: Bayesian starterdb '$BayesianStarterDB' with $haveStarterdb records") ;
          }}

          if ( $RebuildSchedule eq "*" ) {
        mlog(0,"info: RebuildSchedule for RebuildSpamdb.pl is every hour");
        } else {
              foreach my $shour ( split( /\|/, $RebuildSchedule ) ) {
            mlog(0,"info: RebuildSchedule for RebuildSpamdb.pl is $shour:00");
            }
          }

    }
    $HeloBlackObject = tie %HeloBlack, 'orderedtie', "$base/$spamdb.helo";

    if ($whitelistdb !~ /mysql/ && !-e "$base/$whitelistdb") {

        copy("$base/backup/$whitelistdb.yesterday.bak","$base/$whitelistdb");
        mlog( 0, "admininfo: '$base/backup/$whitelistdb.yesterday.bak' restored" );
    }
    if ( $CanUseTieRDBM && $whitelistdb =~ /mysql/ ) {
        eval {
            $WhitelistObject = tie %Whitelist, 'Tie::RDBM',
              "dbi:mysql:database=$mydb;host=$myhost",
              {
                user     => "$myuser",
                password => "$mypassword",
                table    => 'whitelist',
                create   => 1
              };
        };
        if ($@) {
            mlog( 0, "whitelist mysql error: $@" );
            $CanUseTieRDBM = 0;
            $whitelistdb   = "whitelist";
        }
    } else {
        $WhitelistObject = tie %Whitelist, 'orderedtie', "$base/$whitelistdb";
         # check if there are at least 50 records in whitelist (~1KB)
          my $i = 0;
          while (my ($k,$v) = each(%Whitelist)) {
            $i++;
            last if ($i > 50);
          }
          mlog(0,"warning: Whitelist (whitelistdb) has only $i records: (ignore if this is a new install)") if ($i < 50 );

          copy("$base/backup/$whitelistdb.yesterday.bak","$base/$whitelistdb") if ($i < 50 );
        mlog( 0, "admininfo: '$base/backup/$whitelistdb.yesterday.bak' restored" )if ($i < 50 );
    }

    mlog(0,"warning: option 'decodeMIME2UTF8' is set, but Email::MIME is not installed") if $decodeMIME2UTF8 && !$CanUseEMM;
    $asspWarnings .= '<span class="negative">\'decodeMIME2UTF8\' is set, but Email::MIME is not installed!</span><br />' if $decodeMIME2UTF8 && !$CanUseEMM;

    mlog(0,"warning: option 'nolocalDomains' is set, ASSP will not perform relay checks!") if $nolocalDomains;
    $asspWarnings .= '<span class="negative">\'nolocalDomains\' is set, ASSP will not perform relay checks!</span><br />' if $nolocalDomains;

    $asspWarnings .= '<span class="negative">warning: Email::Send not installed, email-interface and block-report not available</span><br />' if !$CanUseEMS;    

    if ( $CanUseTieRDBM && $redlistdb =~ /mysql/ ) {
        eval {
            $RedlistObject = tie %Redlist, 'Tie::RDBM',
              "dbi:mysql:database=$mydb;host=$myhost",
              {
                user     => "$myuser",
                password => "$mypassword",
                table    => 'redlist',
                create   => 1
              };
        };
        if ($@) {
            mlog( 0, "redlist mysql error: $@" );
            $CanUseTieRDBM = 0;
            $redlistdb     = "redlist";
        }
    } else {
        $RedlistObject = tie %Redlist,     'orderedtie', "$base/$redlistdb";
    }
    $GriplistObject    = tie %Griplist,    'orderedtie', "$base/$griplist"
      if $griplist;
    $SMTPfailedObject  = tie %SMTPfailed,  'orderedtie', "$base/$pbdb.smtptimeout.db";
    $SSLfailedObject   = tie %SSLfailed,   'orderedtie', "$base/$pbdb.ssl.db";
    $PBWhiteObject     = tie %PBWhite,     'orderedtie', "$base/$pbdb.white.db";
    $PBBlackObject     = tie %PBBlack,     'orderedtie', "$base/$pbdb.black.db";
    $PreHeaderObject   = tie %PreHeader,   'orderedtie', "$base/$pbdb.preheader.db";

    $SameSubjectCacheObject = tie %SameSubjectCache, 'orderedtie', "$base/$pbdb.samesubject.db";
    $RBLCacheObject     = tie %RBLCache,   'orderedtie', "$base/$pbdb.rbl.db";
    $URIBLCacheObject   = tie %URIBLCache, 'orderedtie', "$base/$pbdb.uribl.db";
    $PTRCacheObject     = tie %PTRCache,   'orderedtie', "$base/$pbdb.ptr.db";
    $MXACacheObject     = tie %MXACache,   'orderedtie', "$base/$pbdb.mxa.db";
    $RWLCacheObject     = tie %RWLCache,   'orderedtie', "$base/$pbdb.rwl.db";
    $SPFCacheObject     = tie %SPFCache,   'orderedtie', "$base/$pbdb.spf.db";
    $SBCacheObject      = tie %SBCache,    'orderedtie', "$base/$pbdb.sb.db";

    $NotSpamTagsObject  = tie %NotSpamTags,'orderedtie', "$base/notspamtagsdb";
    $PBTrapObject       = tie %PBTrap,     'orderedtie', "$base/$pbdb.trap.db";
    $PersBlackObject    = tie %PersBlack,  'orderedtie', "$base/$persblackdb";

    $LDAPlistObject     = tie %LDAPlist,   'orderedtie', "$base/$ldaplistdb";

    $LDAPNotFoundObject = tie %LDAPNotFound,        'orderedtie', "$base/$ldapnotfounddb";
    $FreqObject         = tie %localFrequencyCache, 'orderedtie', "$base/$pbdb.localfreq.db";

    if ( $CanUseTieRDBM && $delaydb =~ /mysql/ ) {
        eval {
            $DelayWhiteObject = tie %DelayWhite, 'Tie::RDBM',
              "dbi:mysql:database=$mydb;host=$myhost",
              {
                user     => "$myuser",
                password => "$mypassword",
                table    => 'delaywhitedb',
                create   => 1
              };
            $DelayObject = tie %Delay, 'Tie::RDBM',
              "dbi:mysql:database=$mydb;host=$myhost",
              {
                user     => "$myuser",
                password => "$mypassword",
                table    => 'delaydb',
                create   => 1
              };
        };
        if ($@) {
            mlog( 0, "delaydb mysql error: $@" );
            $CanUseTieRDBM = 0;
            $delaydb       = "delaydb";
        }

    } else {
        $DelayObject = tie %Delay, 'orderedtie', "$base/$delaydb";
        $DelayWhiteObject = tie %DelayWhite, 'orderedtie',
          "$base/$delaydb.white";
    }
    my $v;

    $v = (
"Whitelist,whitelistdb,WhitelistObject,$whitelistdb"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );

    $v = (
"Redlist,redlistdb,RedlistObject,$redlistdb"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );

    $v = (
"Delay,delaydb,DelayObject,$delaydb" 
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"DelayWhite,delaydb ,DelayWhiteObject,$delaydb.white"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );

    $v = (
"Spamdb,spamdb,SpamdbObject,$spamdb"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );

    $v = (
"Starterdb,BayesianStarterDB,StarterdbObject,$BayesianStarterDB"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"HeloBlack,spamdb,HeloBlackObject,$spamdb.helo"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );

    $v = (
"PBWhite,pbdb,PBWhiteObject,$pbdb.white.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"PBBlack,pbdb,PBBlackObject,$pbdb.black.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"PreHeader,pbdb,PreHeaderObject,$pbdb.preheader.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );

    $v = (
"SameSubjectCache,pbdb,SameSubjectCacheObject,$pbdb.samesubject.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"RBLCache,pbdb,RBLCacheObject,$pbdb.rbl.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"URIBLCache,pbdb,URIBLCacheObject,$pbdb.uribl.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"PTRCache,pbdb,PTRCacheObject,$pbdb.ptr.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"MXACache,pbdb,MXACacheObject,$pbdb.mxa.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"RWLCache,pbdb,RWLCacheObject,$pbdb.rwl.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"SPFCache,pbdb,SPFCacheObject,$pbdb.spf.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );

    $v = (
"SBCache,pbdb,SBCacheObject,$pbdb.sb.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"NotSpamTags,pbdb,NotSpamTagsObject,generatedtagsdb"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"PBTrap,pbdb,PBTrapObject,$pbdb.trap.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"localFrequencyCache,pbdb,FreqObject,$pbdb.localfreq.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"SMTPfailed,pbdb,SMTPfailedObject,$pbdb.smtptimeout.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
       $v = (
"SSLfailed,pbdb,SSLfailedObject,$pbdb.ssl.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"PersBlack,persblackdb,PersBlackObject,$persblackdb"); 
    $v=~s/\s*,/,/go;
    push(@dbGroup,$v);

    $v = (
"LDAPlist,ldaplistdb,LDAPlistObject,$ldaplistdb"
    );
    $v=~s/\s*,/,/go;
    push( @dbGroup, $v );
    $v = (
"LDAPNotFound,ldapnotfounddb,LDAPNotFoundObject,$ldapnotfounddb"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $shuttingDown = $doShutdown = 0;

    $NotSpamTagGenerated = &NotSpamTagGenerate;

    &WhiteOrgList();

    $smtpConcurrentSessions = 0;
    $Stats{starttime}       = time;
    $Stats{version}         = "$version$modversion";
    &ResetStats;
    my $runas = $AsAService ? 'as service' : $AsADaemon ? 'as daemon' : 'in console';
    my ($mv,$sv,$lv) = $] =~ /(\d)\.(\d{3})(\d{3})/o;
    $mv =~ s/^0+//o;$sv =~ s/^0+//o;$lv =~ s/^0+//o;
    mlog( 0, "Running in directory $base on host ". hostname());
    #-- check if running as root 
    mlog( 0, "Running as root") if $< == 0 && $^O ne "MSWin32";

    mlog( 0, "Running as user '" . (getpwuid($<))[0] . "'") if $< != 0 && $^O ne "MSWin32";
    mlog( 0, "using Perl $^X version $] ($mv.$sv.$lv)");
    mlog( 0, "ASSP $version$modversion starting (PID: $$) $runas" );

    &writeWatchdog if $EnableWatchdog;
    &startWatchdog if $EnableWatchdog;

}
sub getWebSocket {
    my $adminport = $webAdminPort; 
    $adminport = $webSecondaryPort if $AsASecondary && $webSecondaryPort;
    my @dummy;
    my ($WebSocket,$dummy);
    if ($CanUseIOSocketSSL && $enableWebAdminSSL) {
      ($WebSocket,$dummy)   = newListenSSL($adminport,\&NewWebConnection,1);
      @WebSocket = @$WebSocket;
      for (@$dummy) {s/:::/\[::\]:/o;}
      mlog(0,"listening for admin HTTPS connections on webAdminPort @$dummy") if @$dummy;
      $webAdminPortOK = 1 if @$dummy;
#      mlog(0,"not listening for admin HTTPS connections on webAdminPort $adminport") if !@$dummy;
      $webAdminPortOK = 0 if !@$dummy;

      } else {
      ($WebSocket,$dummy)   = newListen($adminport,\&NewWebConnection,1);
      for (@$dummy) {s/:::/\[::\]:/o;}
      mlog(0,"listening for admin HTTP connections on webAdminPort @$dummy") if @$dummy;
      $webAdminPortOK = 1 if @$dummy;
#      mlog(0,"not listening for admin HTTP connections on webAdminPort $adminport") if !@$dummy;
      $webAdminPortOK = 0 if !@$dummy;
      }
}

sub getDestSockDom {
    my $dest = shift;
    return unless $dest;
    my $orgdest = $dest;
    my ($ip4,$ip6,$ip,%Domain);
    $ip = $1 if $dest =~ /^\[?($IPRe)\]?/o;
    if (! $ip) {
        my ($port,@res);
        $dest =~ s/^\[//o;
        $dest =~ s/\]?:\d+$//o;
        if ($CanUseIOSocketINET6) {
            eval(<<EOT);
                @res = Socket6::getaddrinfo($dest,25,AF_INET6);
                ($ip6, $port) = getnameinfo($res[3], NI_NUMERICHOST | NI_NUMERICSERV) if $res[3];
EOT
            eval(<<EOT)  if $@ || !($ip6 =~ s/^\[?($IPv6Re)\]?$/$1/o);
                $ip6 = Socket6::inet_ntop( AF_INET6, scalar( Socket6::gethostbyname2($dest,AF_INET6) ) );
EOT
            $ip6 = undef if $@ || !($ip6 =~ s/^\[?($IPv6Re)\]?$/$1/o);
            mlog(0,"info: resolved IPv6 $ip6 for hostname $dest") if $ip6 && $ConnectionLog >= 2;
        }
        if (! $ip6) {
            eval{$ip4 = inet_ntoa( scalar( gethostbyname($dest) ) );};
            $ip4 = undef if ($ip4 !~ /^$IPv4Re$/o);
            mlog(0,"info: resolved IPv4 $ip4 for hostname $dest") if $ip4 && $ConnectionLog >= 2;
        }
    } else {
        $ip6 = $1 if $ip =~/^\[?($IPv6Re)\]?$/o;
        $ip4 = $1 if ! $ip6 && $ip =~/^($IPv4Re)$/o;
    }
    if ($ip6) {
        $Domain{Domain} = AF_INET6;
    } elsif ($ip4) {
        $Domain{Domain} = AF_INET;
    } else {
        $Domain{Domain} = AF_UNSPEC;
        mlog(0,"error: found unresolvable ($dest) - hostname or suspicious IP address definition in $orgdest");
    }
    return %Domain;
}

sub newListen {
    my($port,$handler)=@_;
    my $portA;
    my @s;
    my @sinfo;

    foreach my $portA (split(/\|/o, $port)) {
        if($portA !~ /$HostRe?:?$PortRe/o) {
            mlog(0,"wrong (host) + port definition in '$portA' -- entry will be ignored !");
            next;
        }
        my @stt;
        my ($interface,$p)=$portA=~/($HostRe):($PortRe)/o;

        my %parms = $interface
                    ? ('LocalPort' => $p, 'LocalAddr' => $interface)
                    : ('LocalPort' => $portA);
        $parms{Listen} = 10;
        $parms{Reuse} = 1;

        if ($CanUseIOSocketINET6) {
            my $isv4 = [&getDestSockDom($interface)]->[1] != AF_INET6;
            my ($s4,$s6);
            if (! $interface || $isv4) {
                $parms{Domain} = AF_INET;
                $s4 = IO::Socket::INET6->new(%parms);
                push @stt,$s4 if $s4;
            }
            if (! $interface || ! $isv4) {
                $parms{Domain} = AF_INET6;
                $s6 = IO::Socket::INET6->new(%parms);
                push @stt,$s6 if $s6;
            }
        } else {
            my $s4 = IO::Socket::INET->new(%parms);
            push @stt,$s4 if $s4;
        }

        if(! @stt) {
            mlog(0,"couldn't create server socket on port '$portA' -- maybe another service is running or I'm not root (uid=$>)? -- or a wrong IP address is specified? -- $! - $IO::Socket::SSL::SSL_ERROR") if !$AsASecondary;
             my $time = &timestring();

            next;
        }

        foreach my $s (@stt) {
            $s->blocking(0);
            $SocketCalls{$s}=$handler;

            $readable->add($s);    # add to select list
            push @s,$s;
            push @sinfo,$s->sockhost . ':' . $s->sockport;
        }
        last if $AsASecondary && $portA  =~ $webSecondaryPort;
    }
    return \@s,\@sinfo;
}

sub newListenSSL {
    my($port,$handler)=@_;
    my @s;
    my @sinfo;

    foreach my $portA (split(/\|/o, $port)) {
        if($portA !~ /$HostRe?:?$PortRe/o) {
            mlog(0,"wrong (host) + port definition in '$portA' -- entry will be ignored !");
            next;
        }
        my @stt;
        my ($interface,$p)=$portA=~/($HostRe):($PortRe)/o;

        my %parms = getSSLParms(1);
        if ($interface) {
            $parms{LocalPort} = $p;
            $parms{LocalAddr} = $interface;
        } else {
            $parms{LocalPort} = $portA;
        }
        $parms{Listen} = 10;
        $parms{Reuse} = 1;
        $parms{SSL_startHandshake} = 1;

        if ($SSLDEBUG > 1) {
            while(my($k,$v)=each(%parms)) {
                print "ssl-new-listener: $k = $v\n";
            }
        }

        if ($CanUseIOSocketINET6) {
            my $isv4 = [&getDestSockDom($interface)]->[1] != AF_INET6;
            my ($s4,$s6);
            if (! $interface || $isv4) {
                $parms{Domain} = AF_INET;
                $s4 = IO::Socket::SSL->new(%parms);
                push @stt,$s4 if $s4;
            }
            if (! $interface || ! $isv4) {
                $parms{Domain} = AF_INET6;
                $s6 = IO::Socket::SSL->new(%parms);
                push @stt,$s6 if $s6;
            }
        } else {
            $parms{Domain} = AF_INET;
            my $s4 = IO::Socket::SSL->new(%parms);
            push @stt,$s4 if $s4;
        }

        if(! @stt) {
            mlog(0,"couldn't create server SSL-socket on port '$portA' -- maybe another service is running or I'm not root (uid=$>)? - or a wrong IP address is specified? -- $! - $IO::Socket::SSL::SSL_ERROR");
            next;
        }
        foreach my $s (@stt) {
            $SocketCalls{$s}=$handler;

            $readable->add($s);    # add to select list
            push @s,$s;
            push @sinfo,$s->sockhost . ':' . $s->sockport;
        }
        last if $AsASecondary && $portA  =~ $webSecondaryPort;
    }
    $IO::Socket::SSL::DEBUG = $SSLDEBUG;
    return \@s,\@sinfo;
}

sub getUidGid {
    my ( $uname, $gname ) = @_;
    return if $AsAService;
    my $rname = "root";
    eval('getgrnam($rname);getpwnam($rname);');
    if ($@) {

        # windows pukes "unimplemented" for these -- just skip it
        mlog( '',
"warning:   uname and/or gname are set ($uname,$gname) but getgrnam / getpwnam give errors: $@"
        );
        return;
    }
    my $gid;
    if ($gname) {
        $gid = getgrnam($gname);
        if ( defined $gid ) {
        } else {
            my $msg =
"could not find gid for group '$gname' -- not switching effective gid ";
            mlog( '', $msg );
            return;
        }
    }
    my $uid;
    if ($uname) {
        $uid = getpwnam($uname);
        if ( defined $uid ) {
        } else {
            my $msg =
"could not find uid for user '$uname' -- not switching effective uid ";
            mlog( '', $msg );
            return;
        }
    }
    ( $uid, $gid );
}

sub switchUsers {
    my ( $uid, $gid ) = @_;
    return if $AsAService;
    my ( $uname, $gname ) = ( $runAsUser, $runAsGroup );
    $> = 0;
    if ( $> != 0 ) {
        my $msg =
"requested to switch to user/group '$uname/$gname' but cannot set effective uid to 0 --  uid is $>";
        mlog( '', $msg );
        return;
    }
    $< = 0;
    if ($gid) {
        $) = $gid;
        if ( $) + 0 == $gid ) {
            mlog( '', "switched effective gid to $gid ($gname)" );
        } else {
            my $msg =
"failed to switch effective gid to $gid ($gname) -- effective gid=$) ";
            mlog( '', $msg );
            return;
        }
        $( = $gid;
        if ( $( + 0 == $gid ) {
            mlog( '', "switched real gid to $gid ($gname)" );
        } else {
            mlog( '',
                "failed to switch real gid to $gid ($gname) -- real uid=$(" );
        }
    }
    if ($uid) {

        # do it both ways so linux and bsd are happy
        $< = $> = $uid;
        if ( $> == $uid ) {
            mlog( '', "switched effective uid to $uid ($uname)" );
        } else {
            my $msg =
"failed to switch effective uid to $uid ($uname) -- real uid=$<";
            mlog( '', $msg );
            return;
        }
        if ( $< == $uid ) {
            mlog( '', "switched real uid to $uid ($uname)" );
        } else {
            mlog( '',
                "failed to switch real uid to $uid ($uname) -- real uid=$<" );
        }
    }
}

sub MainLoop {
    my $timeout;
    my $entrytime = Time::HiRes::time();

    eval {
    local $SIG{ALRM} =
    sub { die "mainloop_timeout\n" };    # NB: \n required
    $timeout = 180;
    $timeout = $MainloopTimeout if $MainloopTimeout > 180;
    alarm $timeout;
    my $wait = 5; # keep it short enough for servicecheck to be called regularly
    my $stime =
      $CanStatCPU ? ( Time::HiRes::time() ) : time;    # loop cycle start time
    my ( $canread, $canwrite ) =
      IO::Select->select( $readable, $writable, undef, $wait );
    my $itime =
      $CanStatCPU ? ( Time::HiRes::time() ) : time;   # loop cycle idle end time
    my $ntime = $CanStatCPU ? 0.3 : 1;  
    $webTime   = 0;             # loop cycle web time interval, global var
    $nextLoop2 = $itime + 1; 

    # AZ: 2009-02-05 - signal service status
    if ( $SvcStopping != 0 ) {
      serviceCheck();
      return;
    }
    foreach my $fh (@$canwrite) {
        my $l = length( $Con{$fh}->{outgoing} );
        d("canwrite $fh $Con{$fh} l=$l paused=$Con{$fh}->{paused} ip=$Con{$fh}->{ip}");

        if ($l) {
            $fh->blocking(0) if $fh->blocking;
            my $written;
            eval {
                local $SIG{ALRM} =
                sub { die "syswrite_timeout\n" };    # 

                alarm 60;    
                $written =
                      syswrite( $fh, $Con{$fh}->{outgoing}, $OutgoingBufSizeNew );
                  alarm 0;
              };
            #exception check
            if ($@) {
                alarm 0;

                mlog( 0, "mainloop exception: $@", 1, 1 );

            }
            $Con{$fh}->{outgoing} = substr( $Con{$fh}->{outgoing}, $written );
            $l = length( $Con{$fh}->{outgoing} );

            # test for highwater mark
            if (   $written > 0
                && $l < $OutgoingBufSizeNew
                && $Con{$fh}->{paused} )
            {
                $Con{$fh}->{paused} = 0;
                $readable->add( $Con{$fh}->{friend} );
            }
            if ($Con{$fh}->{type} ne 'C' &&
                        $written > 0 &&
                        $Con{$fh}->{friend} &&
                        exists $Con{$Con{$fh}->{friend}} &&
                        $Con{$Con{$fh}->{friend}}->{lastcmd} =~ /^ *(?:DATA|BDAT)/io )
            {
                $Con{$Con{$fh}->{friend}}->{writtenDataToFriend} +=         $written;
            }
        }
        if ( length( $Con{$fh}->{outgoing} ) == 0 ) {
            $writable->remove($fh);
        }

        done2($fh) if $Con{$fh}->{closeafterwrite};
    }
    foreach my $fh (@$canread) {
        d("canread  $fh $Con{$fh}");
        if ( $fh && $SocketCalls{$fh} ) {
            if (
                $CanStatCPU
                && (   $SocketCalls{$fh} == \&WebTraffic
                    || $SocketCalls{$fh} == \&NewWebConnection
                    || $SocketCalls{$fh} == \&NewStatConnection )
              )
            {

                # calculate time spent serving web request
                $webTime -= Time::HiRes::time();
                $SocketCalls{$fh}->($fh);
                $webTime += Time::HiRes::time();
            } else {
                $SocketCalls{$fh}->($fh);
            }
        } else {
            my $ip;
            my $port;
            eval {
                $ip   = $fh->peerhost();
                $port = $fh->peerport();
            } if ( fileno($fh) );

            eval {
                delete $SocketCalls{$fh} if ( exists $SocketCalls{$fh} );
                delete $WebCon{$fh}      if ( exists $WebCon{$fh} );
                $readable->remove($fh);
                $writable->remove($fh);
                eval { $fh->close } if ( fileno($fh) );
            };
            delete $Con{$fh} if exists $Con{$fh};
        }
    }

    if ($smtpIdleTimeout > 0 || $smtpNOOPIdleTimeout > 0){
        if (scalar keys %Con > 0){
            my $tmpNow = time;
            # Check timeouts only every 15 seconds at least
            if ($tmpNow > ($lastTimeoutCheck + 15)){
                while (my ($tmpfh,$v) = each %Con){

                    delete $Con{$tmpfh}->{doNotTimeout} if ($tmpNow - $Con{$tmpfh}->{doNotTimeout} > $NpWlTimeOut);
                    if ($Con{$tmpfh}->{type} =~ /CC?/o &&
                        $Con{$tmpfh}->{timelast} > 0 &&
                        ! $Con{$tmpfh}->{movedtossl} &&
                        ! $Con{$tmpfh}->{doNotTimeout} &&
                        ! ($Con{$tmpfh}->{noprocessing} && $tmpNow - $Con{$tmpfh}->{timelast} < $NpWlTimeOut) &&   # 20 minutes for really large queued mails
                        (($smtpIdleTimeout && $tmpNow - $Con{$tmpfh}->{timelast} > $smtpIdleTimeout) ||
                          (uc($Con{$tmpfh}->{lastcmd}) =~ /NOOP/o &&
                          $smtpNOOPIdleTimeout &&
                          $tmpNow - $Con{$tmpfh}->{timelast} > $smtpNOOPIdleTimeout) ||
                          ($smtpNOOPIdleTimeout &&
                          $smtpNOOPIdleTimeoutCount &&
                          $Con{$tmpfh}->{NOOPcount} >= $smtpNOOPIdleTimeoutCount))
                        )
                    {
                        if ($ConTimeOutDebug) {
                           my $m = &timestring();
                           $Con{$tmpfh}->{contimeoutdebug} .= "$m client Timeout after $smtpIdleTimeout secs\r\n" if $ConTimeOutDebug;
                           my $check = "$m client was not readable\r\n";
                           my @handles = $readable->handles();
                           while (@handles) {
                              $_ = shift @handles;
                              $check = "$m client was readable\r\n" if ($tmpfh eq $_);
                           }
                           $Con{$tmpfh}->{contimeoutdebug} .= $check;
                           $check = "$m client was not writable\r\n";
                           @handles = $writable->handles();
                           while (@handles) {
                              $_ = shift @handles;
                              $check = "$m client was writable\r\n" if ($tmpfh eq $_);
                           }
                           $Con{$tmpfh}->{contimeoutdebug} .= $check;
                           $m=time;
                           my $f = "$base/debug/$m.txt";
                           my $CTOD;
                           open $CTOD,'>',"$f" or mlog(0,"error: unable to open connection timeout debug log [$f] : $!");
                           binmode $CTOD;
                           print $CTOD  $Con{$tmpfh}->{contimeoutdebug};
                           close $CTOD;
                        }
                        $Con{$tmpfh}->{prepend}="[Timeout]";
                        $Con{$tmpfh}->{timestart} = 0;
                        my $type;
                        my $addPB = 0;
                        if ($Con{$tmpfh}->{oldfh} && $Con{$tmpfh}->{ip}) {
                            setSSLfailed($Con{$tmpfh}->{ip});
                            $type = 'TLS-';
                            $Stats{smtpConnTLSIdleTimeout}++;
                        } elsif ("$tmpfh" =~/SSL/io && $Con{$tmpfh}->{ip}) {
                            $type = 'SSL-';
                            $Stats{smtpConnSSLIdleTimeout}++;
                        } else {
                            $addPB = 1;
                            $Stats{smtpConnIdleTimeout}++;
                        }
                        if ( ! $Con{$tmpfh}->{timedout} ) {
                            pbAdd( $tmpfh,$Con{$tmpfh}->{ip}, 'idleValencePB', "TimeOut",2 ) if $addPB;
                            mlog($tmpfh,$type."Connection idle for $smtpIdleTimeout secs - timeout") if $SessionLog;
                        } else {
                            done($Con{$tmpfh}->{client});
                            next;
                        }
                        $Con{$tmpfh}->{timedout} = 1;
                        if ($Con{$tmpfh}->{getline} != \&error) {
                            seterror($Con{$tmpfh}->{client},"451 Connection timeout, try later\r\n",1);
                        } else {
                            if (! $Con{$tmpfh}->{closeafterwrite}) {
                                sendque($Con{$tmpfh}->{client},"451 Connection timeout, try later\r\n");
                                $Con{$tmpfh}->{closeafterwrite} = 1;
                                unpoll($Con{$tmpfh}->{client}, $readable);
                            } else {
                                done($Con{$tmpfh}->{client});
                            }
                        }
                    }
                }
                $lastTimeoutCheck = $tmpNow;
            }
        }
    }

    d('mainloop before servicecheck');
    serviceCheck();    # for win32 services

    if ($syncToDo && !$AsASecondary ) {
      my $hassync;
      foreach (sort{&syncSortCFGRec()} glob("$base/configSync/*.cfg")) {
          next if -d $_;
          &syncConfigReceived($_);
          unlink "$_" if -e "$_";
          &syncWriteConfig();
          $hassync = 1;
          last;
      }
      $syncToDo = $hassync;
      }
    # timer related issues
    $opencon = ( keys %Con );

     if ( $UpdateWhitelist && $itime >= $saveWhite  && !$AsASecondary ) {

        &SaveWhitelist;

        $saveWhite = int($itime) + $UpdateWhitelist;
      }
    if ( $CleanDelayDBInterval && $itime >= $nextCleanDelayDB  && !$AsASecondary ) {

        &DoCleanDelayDB;

        $nextCleanDelayDB = int($itime) + $CleanDelayDBInterval;
      }

    if(time >= $nextCleanIPDom ) {
        $nextCleanIPDom = time + 600;
        d(' - CleanIP');

        &cleanCacheDelayIPPB();
        &cleanCacheLocalFrequency();
        &cleanCacheAUTHErrors();

    }

#      d('mainloop before restart check');

      if($AutoRestartInterval && $itime >= $endtime) {
# time to quit -- after endtime and we're bored.
        mlog(0,"info: restart time is reached - waiting until all connection are gone but max 5 minutes");
        while ($smtpConcurrentSessions && time < $endtime + 300) {
            my $tendtime = $endtime;
            $endtime = time + 10000;
            &MainLoop2();
            $endtime = $tendtime;
            Time::HiRes::sleep(0.5);
        }
        &downASSP("restarting");
        &restartCMD();
    }
    SaveStats() if ( $SaveStatsEvery && $itime >= $NextSaveStats  && !$AsASecondary );

    uploadStats() if ($totalizeSpamStats && $itime >= $Stats{nextUpload}  && !$AsASecondary );
    if ( $resendmail && $itime > $nextResendMail ) {
        &resend_mail();
        $nextResendMail = time + 300;
    }

    if ($enableCFGShare && $CanUseNetSMTP && $isShareMaster &&  time > $NextSyncConfig && !$AsASecondary ) {
        my $i = 0;
        my $wr = 0;

        my $stat;
        foreach my $c (@ConfigArray) {

            next if ( ! $c->[0] or @{$c} == 5);

            next if $ConfigSync{$c->[0]}->{sync_cfg} != 1;

            $stat = &syncGetStatus($c->[0]);

            next if($stat < 1 or $stat == 2);

            $wr += &syncConfigSend($c->[0]);
            $i++ > 10 and last;

        }
        $NextSyncConfig = time + ($wr ? 30 : 60);

    }

    if ($CanStatCPU) {

      #
      # cycleTime = cpuTime + webTime
      # cpuTime = cpuIdleTime + cpuBusyTime
      #
      my $ctime=Time::HiRes::time(); # loop cycle end time
      my $cycleTime=$ctime-$stime;
      my $cpuTime=$cycleTime-$webTime;
      my $cpuIdleTime=$itime-$stime;
      my $cpuBusyTime=$cpuTime-$cpuIdleTime;
      $Stats{cpuTime}+=$cpuTime;
      $Stats{cpuBusyTime}+=$cpuBusyTime;
# envelope following filter with instant rise and decay time of 1 second
      my $lusage=$cpuUsage*exp(-(0.693/1)*$cycleTime);
      my $usage=($cpuTime==0 ? 0 : $cpuBusyTime/$cpuTime);
      $cpuUsage=$usage>$lusage ? $usage : $lusage;
    }
    if ( $doShutdown && $itime >= $doShutdown && !$AsASecondary) {
        &downASSP("restarting");

        &restartCMD();
    }

    # run every day at midnight
    my $t = int(
        (
            time +
              Time::Local::timelocal( localtime() ) -
              Time::Local::timelocal( gmtime() )
        ) / ( 24 * 3600 )
    );
    if ( $blockRepLastT && $t != $blockRepLastT ) {
        cleanUpMailLog() if !$AsASecondary ;
    }
    $blockRepLastT = $t;

    if ( time > $nextdetectGhostCon  && !$AsASecondary ) {
        &detectGhostCon();
        $nextdetectGhostCon = time + 1800;
    }
      if ($DebugRollTime && $debug && time > $nextDebugClear  && !$AsASecondary ) {
        &DebugClear();
        $nextDebugClear = time + $DebugRollTime;
    }

    if($ReloadOptionFiles  && time - $lastOptionCheck > $ReloadOptionFiles ){
         d('ReloadOptionFiles');
         OptionCheck(); 
    }

    if(time > $nextDNSCheck && $DNSResponseLog  && !$AsASecondary ) {
        updateDNS( 'DNSServers', $Config{DNSServers}, $Config{DNSServers}, '' );
        $nextDNSCheck = time + 1800;
    }
    if(time > $nextSCANping && $UseAvClamd && $CanUseAvClamd && !$AvailAvClamd  && !$AsASecondary ) {
        pingScan();
        $nextSCANping = time + 180;
    }
    if ( time > $check4queuetime)  {
        &check4queue() ;

        if ($RunTaskNow{BlockReportNow}) {
                &BlockReportGen("1");

            $RunTaskNow{BlockReportNow} = '';

        }
        if ($RunTaskNow{RebuildNow}) {
                &Rebuild("25");
            $RunTaskNow{RebuildNow} = '';

        }
        if ($RunTaskNow{GriplistDownloadNow}) {
                $RunTaskNow{GriplistDownloadNow} = '';
                &downloadGrip(1);

        }

        if ($RunTaskNow{CleanWhitelistNow}) {
                &CleanWhitelist;
            $RunTaskNow{CleanWhitelistNow} = '';

        }

        if ($RunTaskNow{forceLDAPcrossCheck}) {
                &LDAPcrossCheck;
            $RunTaskNow{forceLDAPcrossCheck} = '';

        }
        if ($RunTaskNow{downloadStarterDBNow}) {
                &downloadStarterDB;
            $RunTaskNow{downloadStarterDBNow} = '';

        }
        if ($RunTaskNow{AutoUpdateNow}) {
            $NextASSPFileDownload = -1;
            $NextVersionFileDownload = -1;
            $RunTaskNow{AutoUpdateNow} = '';

        }

        $check4queuetime += 300;
    }

    if ( time > $nextDestinationCheck  && !$AsASecondary ) {

        $nextDestinationCheck = time + 180;
    }

    if ( time > $nextdetectHourJob  && !$AsASecondary ) {
        my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime(time);
        $nextdetectHourJob = int(time / 3600) * 3600 + 3600;
        $nextdetectHourJob += 15 unless $nextdetectHourJob % (24 * 3600);
        d("run HourJobs - scheduled - ($hour)");
        mlog(0,"$version$modversion info: hourly scheduler running after $hour:00");
  #      mlog(0,"$version$modversion info: next hourly scheduler will run after " . &timestring($nextdetectHourJob)) if $MaintenanceLog > 2;
        mlog(0,"info: DEBUG is set") if $debug;

        &Rebuild($hour) if $RebuildSchedule ;
        &HouseKeeping($hour) if $HouseKeepingSchedule  && !$AsASecondary ;
        &cleanCacheIPNumTries();
        &cleanCacheSMTPdomainIP();
         &cleanCacheSSLfailed();
        &BlockReportGen() if $hour == int($BlockReportSchedule);
        &BlockReportGen('USERQUEUE') if $hour == int($QueueSchedule);
    }

    if(!$AutoUpdateASSP  && !$AsASecondary && time > $NextVersionFileDownload) {
                &downloadVersionFile();

    }

    if ($AutoUpdateASSP && !$AsASecondary && time >= $NextASSPFileDownload) {

                &downloadASSPVersion();
                $NextCodeChangeCheck=time-1;

    }
    if ($AutoRestartAfterCodeChange && !$AsASecondary && time >= $NextCodeChangeCheck) {

                &codeChangeCheck();
                $NextCodeChangeCheck = time + 3600;
    }

    if ($POP3Interval && !$AsASecondary && time >= $NextPOP3Collect) {
            &POP3Collect();
            $NextPOP3Collect = $POP3Interval * 60 + time;
    }

    alarm 0;
    };

    #exception check
        if ($@) {

            alarm 0;
            if ($@ =~ /mainloop_timeout/) {

                if ($AutoRestartAfterTimeOut && $AutoRestartCmd) {
                    mlog( 0, "warning: restarting after MainloopTimeout of $MainloopTimeout seconds", 1 );
                    downASSP("AutoRestartAfterTimeOut caused restart after MainloopTimeout" );    
                    restartCMD(); 
                } else {
                    mlog( 0, "warning: continuing after MainloopTimeout of $MainloopTimeout seconds", 1 );
                }
            } else {    
                mlog( 0, "terminating ASSP: mainloop exception: $@ !!!!!!!", 1 );
                downASSP("terminating ASSP: mainloop exception: $@ !!!!!!!" );  
                exit 1;

            }
        }

}
d('Never reached...(we hope)');

# called during long operations to keep processing priority data
# alternates (1s/1s) between SMTP connections handling and the calling task
sub MainLoop2 {
    alarm 0;
    eval {
    local $SIG{ALRM} =
    sub { die "mainloop2_timeout\n" };    # NB: \n required
    my $timeout = 120;
    $timeout = $MainloopTimeout if $MainloopTimeout > 120;
    alarm $timeout;
    my $time = $AvailHiRes ? ( Time::HiRes::time() ) : time;

    # AZ: 2009-02-05 - signal service status
    if ( $SvcStopping != 0 ) {
      serviceCheck();
      return;
    }
    my $ntime = $CanStatCPU ? 0.3 : 1;
    if ( $time >= $nextLoop2 ) {
        $webTime += $time if $CanStatCPU;

        $nextLoop2 = $time + 1;    # 0.3s for SMTP stuff
        serviceCheck();            # for win32 services
        SaveStats() if ( $SaveStatsEvery && $itime >= $NextSaveStats );
        my ( $canread, $canwrite );
        do {
            ( $canread, $canwrite ) =
              IO::Select->select( $readable, $writable, undef, 0 );
            foreach my $fh (@$canwrite) {
                my $l = length( $Con{$fh}->{outgoing} );
                d("$fh $Con{$fh} l=$l");
                if ($l) {
                    my $written = syswrite( $fh, $Con{$fh}->{outgoing},
                        $OutgoingBufSizeNew );
                    if ($debug) {
                        if ( $written <= 200 ) {
                            d(      "wrote: ($written)<"
                                  . substr( $Con{$fh}->{outgoing}, 0, $written )
                                  . ">" );
                        } else {
                            d("wrote: ($written)<long text>");
                        }
                    }
                    $Con{$fh}->{outgoing} =
                      substr( $Con{$fh}->{outgoing}, $written );
                    $l = length( $Con{$fh}->{outgoing} );

                    # test for highwater mark
                    if (   $written > 0
                        && $l < $OutgoingBufSizeNew
                        && $Con{$fh}->{paused} )
                    {
                        $Con{$fh}->{paused} = 0;
                        $readable->add( $Con{$fh}->{friend} );
                    }
                    if ($Con{$fh}->{type} ne 'C' &&
                        $written > 0 &&
                        $Con{$fh}->{friend} &&
                        exists $Con{$Con{$fh}->{friend}} &&
                        $Con{$Con{$fh}->{friend}}->{lastcmd} =~ /^ *(?:DATA|BDAT)/io )
                    {
                        $Con{$Con{$fh}->{friend}}->{writtenDataToFriend} +=         $written;
                    }
                }
                if ( length( $Con{$fh}->{outgoing} ) == 0 ) {
                    $writable->remove($fh);
                }

            }
            foreach my $fh (@$canread) {
                if (
                    $fh
                    && (   $SocketCalls{$fh} == \&SMTPTraffic
                        || $SocketCalls{$fh} == \&NewSMTPConnection
                        || $SocketCalls{$fh} == \&WebTraffic
                        || $SocketCalls{$fh} == \&NewWebConnection
                        || $SocketCalls{$fh} == \&NewStatConnection )
                  )
                {
                    $SocketCalls{$fh}->($fh);
                }
            }
            $time = $AvailHiRes ? ( Time::HiRes::time() ) : time;
          } until ( ( @$canread == 0 && @$canwrite == 0 )
              || $time >= $nextLoop2 );
        my $ntime = $CanStatCPU ? 0.3 : 1;   
        $nextLoop2=$time+$ntime; # 0.3s for other tasks
        $webTime -= $time if $CanStatCPU;
        if ( $AutoRestartInterval && $itime >= $endtime ) {

            # time to quit -- after endtime and we're bored.
            mlog(0,"info: restart time is reached - waiting until all connection are gone but max 5 minutes");
            while ($smtpConcurrentSessions && time < $endtime + 300) {
                my $tendtime = $endtime;
                $endtime = time + 10000;
                &MainLoop2();
                $endtime = $tendtime;
                Time::HiRes::sleep(0.5);
            }
            &downASSP("restarting");

            &restartCMD();

        }

        if ( $doShutdown && $itime >= $doShutdown ) {
            &downASSP("restarting");

            &restartCMD();
        }
    }
    alarm 0;
    };

}

sub detectGhostCon {
    my $count = 0;
    foreach my $fh ( keys %Con ) {
        next if ( fileno($fh) );
        next if ( $Con{$fh}->{timestart} + 3600 > time );
        &printallCon($fh) if ( $MaintenanceLog > 1 );
        $count++;
        &done2($fh);
    }
    if ($count == 0) {
      $nextdetectGhostCon = time + 300;
    }
}

sub DebugClear {

if ($debug && !$AsASecondary) {

         close $DEBUG;
        my $fn = localtime();
         $fn =~ s/^... (...) +(\d+) (\S+) ..(..)/$1-$2-$4-$3/;
         $fn =~ s/[\/:]/\-/g;         
        $currentDEBUGfile= ">$base/debug/" . $fn . ".dbg";
        open( $DEBUG, '>',"$currentDEBUGfile" );
        binmode($DEBUG);
        my $oldfh = select($DEBUG);
        $| = 1;
        select($oldfh);

    }
}

sub SaveWhitelist {
    return if $AsASecondary;
    if ( $UpdateWhitelist && $whitelistdb !~ /mysql/ ) {
        mlog( 0, "saving whitelistdb" ) if $MaintenanceLog;

        $WhitelistObject->flush() if $WhitelistObject;
    }

}

sub SaveRedlist {
    return if $AsASecondary;

    if ( $UpdateWhitelist && $redlistdb !~ /mysql/ ) {
        mlog( 0, "saving redlistdb" ) if $MaintenanceLog;
        $RedlistObject->flush() if $RedlistObject;
    }

}
sub SaveLDAPlist {

    mlog( 0, "saving ldaplistdb" ) if $MaintenanceLog;
    $LDAPlistObject->flush()        if $LDAPlistObject;
    $LDAPNotFoundObject->flush()       if $LDAPNotFoundObject;

}

sub SavePersBlack {

    mlog( 0, "saving persblackdb" ) if $MaintenanceLog;
    $PersBlackObject->flush()        if $PersBlackObject;

}

sub SavePB {
    return if $AsASecondary;
    mlog( 0, "saving penaltydb (pbdb)" ) if $MaintenanceLog;
    $PBBlackObject->flush() if $PBBlackObject && $pbdb !~ /mysql/;
    $PBWhiteObject->flush() if $PBWhiteObject && $pbdb !~ /mysql/;

}

sub SaveCache {
    if ( $delaydb !~ /mysql/ ) {
        mlog( 0, "saving delaydb" ) if $MaintenanceLog;
        $DelayObject->flush()      if $DelayObject;
        $DelayWhiteObject->flush() if $DelayWhiteObject;
    }
    mlog( 0, "saving cache records" ) if $MaintenanceLog;
    $SameSubjectCacheObject->flush() if $SameSubjectCacheObject;
    $RBLCacheObject->flush()       if $RBLCacheObject;
    $URIBLCacheObject->flush()     if $URIBLCacheObject;
    $SPFCacheObject->flush()       if $SPFCacheObject;
    $PTRCacheObject->flush()       if $PTRCacheObject;
    $MXACacheObject->flush()       if $MXACacheObject;
    $SBCacheObject->flush()        if $SBCacheObject;
    $WhiteOrgObject->flush()    if $WhiteOrgObject;
    $RWLCacheObject->flush()       if $RWLCacheObject;
    $FreqObject->flush()            if $FreqObject;
    $SMTPfailedObject->flush()  if $SMTPfailedObject;

    $PBTrapObject->flush()      if $PBTrapObject;

    mlog( 0, "saving ldaplistdb" )     if $MaintenanceLog;
    $LDAPlistObject->flush()           if $LDAPlistObject;
    $LDAPNotFoundObject->flush()       if $LDAPNotFoundObject;
    mlog( 0, "saving persblackdb" ) if $MaintenanceLog;
    $PersBlackObject->flush()        if $PersBlackObject;

}

sub SaveDB {

    my ($CacheObject,$KeyName) = @_;

    mlog( 0, "saving cache records for $KeyName" ) if $MaintenanceLog;
    $$CacheObject->flush() if $$CacheObject;

}

sub SaveHash {
  my $HashName = shift;
    foreach my $dbGroupEntry (@dbGroup) {
            my ( $KeyName, $dbConfig, $CacheObject, $realFileName ) =
              split(/,/o,$dbGroupEntry);

            next unless $HashName eq $KeyName;

            next if $realFileName eq "mysql";
            mlog( 0, "saving cache records for $KeyName ($realFileName)") if $MaintenanceLog;
            $$CacheObject->flush() if $$CacheObject;

            last;

   }

}
sub ResetPB {
my $fil = shift;
foreach my $dbGroupEntry (@dbGroup) {
            my ( $KeyName, $dbConfig, $CacheObject, $realFileName ) =
              split(/,/o,$dbGroupEntry);

            next if !$CacheObject;
            next if $realFileName eq "mysql";
            next unless ( $fil =~ /$realFileName/ );
#            mlog( 0, "ResetPB $CacheObject,$realFileName/" ); 
            $$CacheObject->resetCache();
            last;

   }
}

# global and personal Whitelist handling
sub Whitelist {
    my($mf,$to,$action)=@_;
    d("Whitelist $mf,$to,$action");
    $mf = lc $mf;
    $to = undef;
    $action = lc $action;
    if (! $action) {                  # is there any Whitelist entry
        return exists $Whitelist{$mf};
    } elsif ($action eq 'add') {
        $Whitelist{$mf} = time;
        return;
    } elsif ($action eq 'delete') {
        delete $Whitelist{$mf};
    }
}
sub cleanTrashlist {
    my $TrashObject = tie %Trashlist, 'orderedtie', "$base/trashlist.db";
    my $files_before = my $files_deleted = 0;
    my $t = time;
    my $mcount;

    while ( my ( $k, $v ) = each(%Trashlist) ) {
        if ( !-e $k ) {
            delete $Trashlist{$k};
            $files_deleted++;
            next;
        }
        my $ct = $v;
        $files_before++;

        if ( !$MaxKeepDeleted or $t - $ct >= $MaxKeepDeleted * 3600 * 24 ) {
            delete $Trashlist{$k};
            unlink $k;
            $files_deleted++;
        }
    }
    mlog(
"Trashlist: cleaning finished; before=$files_before, deleted=$files_deleted\n"
    ) if $files_before > 0;

}
sub CleanWhitelist {
  d('CleanWhitelist');
  if (!-e "$base/$whitelistdb") {
        if (-e "$base/$whitelistdb.bak") {
             copy("$base/$whitelistdb.bak","$base/$whitelistdb");
        }   
  }
  mlog(0,"cleaning up whitelist database ...") if $MaintenanceLog;
  my $t=time;
  my $keys_before = my $keys_deleted = 0;
  my $maxwhite = $MaxWhitelistDays;
  $maxwhite = 360 if $MaxWhitelistDays < 360;
  my $maxtime = $maxwhite * 3600 * 24;
  my $delta;
  my $h;
  my $hat;
  if ($MaxWhitelistDays) {
      while (my ($k,$v)=each(%Whitelist)) {
        $keys_before++;

        $v = 0 unless $v;
        $k = batv_remove_tag(0,$k,'');
        $delta = $t-$v;
        if ($delta >= $maxtime) {
              delete $Whitelist{$k};
              $v -= 9999999999 if $v > 9999999999;
              mlog(0,"Admininfo: $k removed from whitelistdb - entry was older than MaxWhitelistDays (" . &timestring($v,'') . ')',1) if $MaintenanceLog >= 2;
              $keys_deleted++;

        }
        if ($k=~/,/o or $v > 9999999999) {
              delete $Whitelist{$k};
              mlog(0,"Admininfo: $k removed from whitelistdb - entry is outdated",1) if $MaintenanceLog >= 2;
              $keys_deleted++;

        }

        $h  = $1 if $k =~ /\@(.*)/;
        $hat = $1 if $k =~ /(\@.*)/;

        if (exists $LDAPlist{$hat} or ($localDomains && ( $h =~ $LDRE || $hat =~ $LDRE ))) {
            mlog(0,"Admininfo: $k should be removed from whitelistdb - entry was local address",1) if $MaintenanceLog;
        }
      }
      mlog(0,"cleaning whitelist database finished: keys before=$keys_before, deleted=$keys_deleted") if $keys_before && $MaintenanceLog;
  }
  &SaveWhitelist();
}
sub CleanCache {
    return if $AsASecondary;
    mlog( 0, "cleaning up cache records ..." ) if $MaintenanceLog;

    &cleanCacheSubject;
    $SameSubjectCacheObject->flush()   if $SameSubjectCacheObject;
    MainLoop2();

    &cleanCacheRBL if $RBLCacheInterval;
    $RBLCacheObject->flush()   if $RBLCacheObject;
    MainLoop2();

    &cleanCacheURI if $URIBLCacheInterval;
    $URIBLCacheObject->flush() if $URIBLCacheObject;
    MainLoop2();

    &cleanCacheRWL if $RWLCacheInterval;
    $RWLCacheObject->flush()   if $RWLCacheObject;
    MainLoop2();

    &cleanCachePTR if $PTRCacheInterval;
    $PTRCacheObject->flush()   if $PTRCacheObject;
    MainLoop2();

    &cleanCacheMXA if $MXACacheInterval;
    $MXACacheObject->flush()   if $MXACacheObject;
    MainLoop2();

    &cleanCacheSPF if $SPFCacheInterval;
    $SPFCacheObject->flush()   if $SPFCacheObject;
    MainLoop2();

    &cleanCacheSB if $SBCacheExp;
    $SBCacheObject->flush()    if $SBCacheObject;
    MainLoop2();

    &cleanTrapPB if $PBTrapObject;
    $PBTrapObject->flush()  if $PBTrapObject;
     MainLoop2();

}

sub DoCleanDelayDB {
    return if $AsASecondary;
    mlog( 0, "cleaning up delaying databases ..." ) if $MaintenanceLog;
    my $t = time;
    my $keys_before = $keys_deleted = 0;
    while ( my ( $k, $v ) = each(%Delay) ) {
        $keys_before++;
        if ( $t - $v >= $DelayEmbargoTime * 60 + $DelayWaitTime * 3600 ) {
            delete $Delay{$k};
            $keys_deleted++;
        }
        MainLoop2();
    }
    mlog( 0,
"cleaning delaying database (triplets) finished; keys before=$keys_before, deleted=$keys_deleted"
    ) if $MaintenanceLog;
    $keys_before = $keys_deleted = 0;
    while ( my ( $k, $v ) = each(%DelayWhite) ) {
        $keys_before++;
        if ( $t - $v >= $DelayExpiryTime * 24 * 3600 ) {
            delete $DelayWhite{$k};
            $keys_deleted++;
        }
        MainLoop2();
    }
    mlog( 0,
"cleaning delaying database (whitelisted tuplets) finished; keys before=$keys_before, deleted=$keys_deleted"
    ) if $MaintenanceLog;

    if ( $delaydb !~ /mysql/ ) {

        $DelayObject->flush()      if $DelayObject;
        $DelayWhiteObject->flush() if $DelayWhiteObject;
    }

}

sub mlogRe{
    my($fh,$subre,$regextype,$check,$logging)=@_;
    my $this = exists $Con{$fh} ? $Con{$fh} : {};
    $subre =~ s/\s+/ /go;
    $subre=substr($subre,0,$RegExLength);
    $this->{messagereason}="Regex:$regextype '$subre'";
     $this->{myheader}.="X-Assp-Re-$regextype: $subre\r\n" if $AddRegexHeader or $logging;
    my $m;
    $m = $check . ' ' if $check;
    $m .= $this->{messagereason};
    mlog( $fh, $m, 1, 1 ) if $regexLogging or $logging;
}

# win32 debug/trace output
sub w32dbg {
    if ($Win32Debug && $AvailWin32Debug) {
        my ($msg) = @_;
        OutputDebugString("(ASSP): $msg");
    }
}

sub printLOG {
my ( $action, $msg) = @_;
return if $silent && $AsASecondary;

return if !$logfile;
    if ($action eq "open" or ($action eq "print" && $LOGstatus!=1)) { 
    $LOGstatus=1;
    # open the logfile
          if(open($LOG,'>>',"$base/$logfile")) {
                if ($LogCharset) {
                      binmode $LOG, ":encoding($LogCharset)";

                  } else {
                      binmode $LOG if !$enableWORS;
                      binmode $LOG, ":crlf" if $enableWORS;
                  }
                  $LOG->autoflush;
          }
      }
      return if $action eq "open";

      if ($action eq "print") { 
        print $LOG $msg;
        return;
    }

      if ($action eq "close") {     
      # close the logfile
        $LOGstatus=2;
          close $LOG if $logfile;
                if ($! && fileno($LOG)) {
                print "error: unable to close $base/$logfile - $!\n";
                print $LOG "error: unable to close $base/$logfile - $!$WORS";
            }

    }

}

sub mlog {
    my ( $fh, $comment, $noprepend, $noipinfo ) = @_;

    return if $silent && $AsASecondary;
    return if $comment =~ /\[spam found\] --  --/;
    my $this = $fh ? $Con{$fh} : 0;
    my $mm;

    my $lccomment = lc $comment;
    my $noNotify  = $comment =~ s/^\*x\*//;

    my $logfile = $logfile;
    $logfile =~ s/\\/\//g;
    my $archivelogfile;

    PrintConfigHistory($lccomment) if $lccomment =~ /^adminupdate/i;
    PrintConfigHistory($lccomment) if $lccomment =~ /^configerror/i;

    PrintUpdateHistory($lccomment) if $lccomment =~ /autoupdate/i;
    PrintAdminInfo($lccomment)     if $lccomment =~ /^email-interface/i;
    PrintAdminInfo($lccomment)     if $lccomment =~ /^AdminUpdate/i;

    if ( $noLogLineRe && $comment =~ $noLogLineReRE ) { return 1; }
    if ( $this && $noLogLineRe && $this->{prepend} =~ $noLogLineReRE ) {
        return 1;
    }

    # cosmetic
    $comment =~ s/(.*)/$1;/ if $comment !~ /;$/;

    my $m = &timestring();
    my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime(time);

    if ( $LogRollDays > 0 ) {

        # roll log every $LogRollDays days, at midnight
        my $t = int(
            (
                time +
                  Time::Local::timelocal( localtime() ) -
                  Time::Local::timelocal( gmtime() )
            ) / ( $LogRollDays * 24 * 3600 )
        );

        if (   $logfile
            && !$AsASecondary 
            && $mlogLastT
            && $t != $mlogLastT
            && $logfile ne "maillog.log"
            && $asspLog )
        {

            # roll the log

            my $mm = &timestring( time - 7200, 'd', $LogNameDate );
            my ( $logdir, $logdirfile );
            ( $logdir, $logdirfile ) = ( $1, $2 )
              if $logfile =~ /^(.*)[\/\\](.*?)$/;
            if ( !$logdir ) {
                $archivelogfile = "$mm.$logfile";
            }
            else {
                -d "$base/$logdir" or mkdir "$base/$logdir", 0755;
                $archivelogfile = "$logdir/$mm.$logdirfile";
            }

            my $msg =
              "$m: Rolling log file -- archive saved as '$archivelogfile'$WORS";

            printLOG( "print", $msg );

            print $msg unless $silent;

            w32dbg(
                "$m: Rolling log file -- archive saved as '$archivelogfile'");
            printLOG("close");

            sleep 1;

            rename( "$base/$logfile", "$base/$archivelogfile" );
            if ( $! && !-e "$base/$archivelogfile" ) {
                print
"error: unable to rename file $base/$logfile to $base/$archivelogfile - $!\n";

            }

            # open the logfile
            printLOG("open");

            printLOG("print","$m new log file -- old log file renamed to '$archivelogfile'$WORS") ;
             w32dbg(
                "$m new log file -- old log file renamed to '$archivelogfile'");
        }
        $mlogLastT = $t;
    }
    my @m;
    my $header;
    if ($this) {
        return if $fh && $Con{$fh} && &matchIP( $Con{$fh}->{ip}, 'noLoggingIPs' );
        $header = substr( $this->{header}, 0, $MaxBytes )
          if ( $fh && $MaxBytes && !$this->{noLoggingIPs} && $noLoggingRe );
        if (
            $this->{noLoggingIPs}
            || (
                $noLoggingRe
                && (
                    (
                           $this->{mailfrom}
                        && $this->{mailfrom} =~ /($noLoggingReRE)/
                    )
                    || ( $header =~ /($noLoggingReRE)/ )
                )
            )
          )
        {
            $this->{noLoggingIPs} = 1 if ($fh);
            return 1;
        }

        $m .= " $this->{msgtime}" if $this->{msgtime};
        if ( "$fh" =~ /SSL/io or "$this->{friend}" =~ /SSL/io ) {
            $m .=
              ( "$fh" =~ /SSL/io && $this->{oldfh} ) ? ' [TLS-in]'
              : (    "$fh" =~ /SSL/io
                  && !$this->{oldfh}
                  && $this->{localport} == 25 ) ? ' [SSL-in]'
              : '';
            $m .=
              ( "$this->{friend}" =~ /SSL/io
                  && $Con{ $this->{friend} }->{oldfh} ) ? ' [TLS-out]'
              : ( "$this->{friend}" =~ /SSL/io
                  && !$Con{ $this->{friend} }->{oldfh} ) ? ' [SSL-out]'
              : '';
        }
        if ($tagLogging
           && $this->{prepend} 
           && $this->{prepends} !~ /\Q$this->{prepend}\E/
           && !$noprepend
           ) {
          #$this->{prepends} ensures prepend is added only once per type per message.
          $m .= " $this->{prepend}";
          $this->{prepends} .= "$this->{prepend}";
        }
        if (   $expandedLogging
            || $noipinfo >= 2
            || ( !$this->{loggedIpFromTo} && !$noipinfo ) )
        {
            $m .= " $this->{ip}"         if ( $this->{ip} );
            $m .= " [OIP: $this->{cip}]" if ( $this->{cip} );
            my $mf = &batv_remove_tag( 0, $this->{mailfrom}, '' );
            ( my $from ) =
              substr( $this->{header}, 0, $MaxBytes ) =~
              /\nfrom:[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio
              if !$mf;
            $m .= " FROM:<$from>" if $from && !$mf && $FromLogging;
            $m .= " <$mf>" if $mf;
            $this->{from} = $from if $from && !$mf;

            my $to;
            $to = $this->{orgrcpt} if $noipinfo == 3;
            ($to) = $this->{rcpt} =~ /(\S+)/o unless $to;
            my $mm = $m;
            if ($to) {
                $this->{loggedIpFromTo} = 1 if $noipinfo < 3;
                $m .= " to: $to";
            }
            if (   $noipinfo < 3
                && $comment =~ / \[(?:spam found|MessageOK)\] /oi )
            {
                my $c = $comment;
                $c =~ s/\r//go;
                $c =~ s/\n([^\n]+)/\n\t$1/go;
                $c .= "\n" if ( $c !~ /\n$/o );
                my %seen;
                for ( split( /\s+/o, $this->{rcpt} ) ) {
                    next unless $_;
                    next if $seen{ lc $_ };
                    $seen{ lc $_ } = 1;
                    push @m, "$mm to: $_ $c";
                }
            }
        }
        $m .= " $comment$WORS";
    } else {
        $m .= " " . ucfirst($comment) . "$WORS";
    }

    PrintWhitelistAdd($m) if $m =~ /whitelist addition/i;
    PrintWhitelistAdd($m) if $m =~ /whitelist deletion/i;

    if (   $canNotify
        && !$noNotify
        && scalar keys %NotifyRE
        && $m =~ /$NotifyReRE/
        && $m !~ /$NoNotifyReRE/ )
    {
        my $rcpt;
        my $sub;
        while ( my ( $k, $v ) = each %NotifyRE ) {
            if ( $m =~ /$k/i ) {
                $rcpt = $v;
                $sub  = $NotifySub{$k} . " from $myName"
                  if exists $NotifySub{$k};
                last;
            }
        }
        $sub ||= "ASSP event notification from $myName";
        &sendNotification( $EmailFrom, $rcpt, $sub,
            "log event on host $myName:\r\n\r\n$m\r\n" )
          if $rcpt;

    }

    $m =~ s/\r//go;
    $m =~ s/\n([^\n]+)/\n\t$1/go;
    $m .= "\n" if ( $m !~ /\n$/o );

    print DEBUG $m if $debug && !$AsASecondary;

    tosyslog( 'info', substr( $m, 18 ) )
      if ( $CanUseSyslog || $CanUseNetSyslog ) && $sysLog;
    my $sm = $m;
    $sm =~ s/\r//g;
    $sm =~ s/-\> $base\//-\> /;
    print $sm unless $silent;

    eval { $m = Encode::decode( 'Guess', $m ); } if $m;

    printLOG( "print", $m ) if $logfile && $asspLog;
    w32dbg("$m");
}

sub tosyslog {
    my ( $priority, $msg ) = @_;
    return 0 unless ( $priority =~ /info|err|debug/ );
    $msg =~ s/^\s+//;

    if ($AvailNetSyslog) {
        my $s = new Net::Syslog(
            Facility   => $SysLogFac,
            Priority   => 'Debug',
            SyslogPort => $sysLogPort,
            SyslogHost => $sysLogIp
        );
        $s->send( $msg, Priority => $priority );
    } else {
        setlogsock('unix');
        openlog( 'assp', 'pid,cons', $SysLogFac );
        syslog( $priority, $msg );
        closelog();
    }

    return 1;
}

sub tzStr {

    # calculate the time difference in minutes
    my $minoffset =
      ( Time::Local::timelocal( localtime() ) -
          Time::Local::timelocal( gmtime() ) ) / 60;

   # translate it to "hour-format", so that 90 will be 130, and -90 will be -130
    my $sign = $minoffset < 0 ? -1 : +1;
    $minoffset = abs($minoffset) + 0.5;
    my $tzoffset = 0;
    $tzoffset = $sign * ( int( $minoffset / 60 ) * 100 + ( $minoffset % 60 ) )
      if $minoffset;

    # apply final formatting, including +/- sign and 4 digits
    return sprintf( "%+05d", $tzoffset );
}

sub getTimeDiffAsString {

    my ( $tdiff, $seconds ) = @_;

    my $days  = int( $tdiff / 86400 );
    my $hours = int( ( $tdiff - ( $days * 86400 ) ) / 3600 );
    my $mins  = int( ( $tdiff - ( $days * 86400 ) - ( $hours * 3600 ) ) / 60 );
    my $secs =
      int( $tdiff - ( $days * 86400 ) - ( $hours * 3600 ) - ( $mins * 60 ) );

    my $ret;
    $ret = $days . " day" . ( $days == 1 ? " " : "s " );
    $ret .= $hours . " hour" . ( $hours == 1 ? " " : "s " );
    $ret .= $mins . " min" .   ( $mins == 1  ? " " : "s " );
    $ret .= $secs . " sec" .   ( $secs == 1  ? " " : "s " ) if $seconds;

    return $ret;
}

sub getTimeDiff {
    my ( $tdiff, $seconds ) = @_;
    my $m = getTimeDiffAsString( $tdiff, $seconds );
    if ( $m =~ s/^0 days // ) {
        if ( $m =~ s/^0 hours // ) {
        }
    }
    return $m;
}

#####################################################################################
#                Socket handlers

sub setSSLfailed {
    my $ip = shift;
    return unless $banFailedSSLIP;
    mlog(0, "SSLfailed $banFailedSSLIP $ip");
    if (exists $SSLfailed{$ip}) {   # ban if it failed before
        $SSLfailed{$ip} = time;
    } elsif (($banFailedSSLIP & 1) && (matchIP($ip,'acceptAllMail',0,1) or $ip =~ /$IPprivate/o)) {  # give privates one more chance
        $SSLfailed{$ip} = 0;
    } elsif ($banFailedSSLIP & 2) {
        $SSLfailed{$ip} = time;    # ban external IP if it failed before
    }
    return;
}

sub switchSSLClient {
    my $fh =shift;
    my $sslfh;
    my $try = 4;
    eval{$fh->blocking(1);};
    $sslfh = IO::Socket::SSL->start_SSL($fh,{
             SSL_startHandshake => 1,
             getSSLParms(1)
             });
    while ($try-- && "$sslfh" !~ /SSL/io && ($IO::Socket::SSL::SSL_ERROR == eval('SSL_WANT_READ') ? 1 : $IO::Socket::SSL::SSL_ERROR == eval('SSL_WANT_WRITE') ) && $SSLRetryOnError)
    {
         Time::HiRes::sleep(0.5);
         mlog($fh,"info: retry ($try) SSL negotiation - peer socket was not ready");

         $sslfh = IO::Socket::SSL->start_SSL($fh,{
             SSL_startHandshake => 1,
             getSSLParms(1)
             });
    }
    if ("$sslfh" =~ /SSL/io) {
        eval{$sslfh->blocking(0);};
    } else {
        eval{$fh->blocking(0);};
    }
    return $sslfh,$fh;
}
sub switchSSLServer {
    my $fh =shift;
    my $sslfh;
    my $try = 4;
    eval{$fh->blocking(1);};
    $sslfh = IO::Socket::SSL->start_SSL($fh,{
             SSL_startHandshake => 1,
             getSSLParms(0)
             });
    while ($try-- && "$sslfh" !~ /SSL/io && ($IO::Socket::SSL::SSL_ERROR == eval('SSL_WANT_READ') ? 1 : $IO::Socket::SSL::SSL_ERROR == eval('SSL_WANT_WRITE') ) && $SSLRetryOnError)
    {
         Time::HiRes::sleep(0.5);
         mlog($fh,"info: retry ($try) SSL negotiation - peer socket was not ready");

         $sslfh = IO::Socket::SSL->start_SSL($fh,{
             SSL_startHandshake => 1,
             getSSLParms(0)
             });
    }
    if ("$sslfh" =~ /SSL/io) {
        eval{$sslfh->blocking(0);};
    } else {
        eval{$fh->blocking(0);};
    }
    return $sslfh,$fh;
}

sub matchFH {
    my ($fh, @fhlist) = @_;
    return 0 unless @fhlist;
    my $sinfo = $fh->sockhost() . ':' . $fh->sockport();
    $sinfo =~ s/:::/\[::\]:/o;

    while (@fhlist) {
        my $lfh = shift @fhlist;
        if ($lfh =~ /^(?:0\.0\.0\.0|\[::\])(:\d+)$/o) {
            my $p = $1;
            return 1 if ($sinfo =~ /$p$/);
        }
        return 1 if ($sinfo eq $lfh);
    }
    return 0;
}

sub NewSMTPConnection {
    my $client = shift;

    my $isSSL;
    my ( $server, $destination, $relayok, $relayused );
    my $listenport;
    $destination = $smtpDestination;
    my $destinationport;
    $destinationport = "smtpDestination";
    if ( matchFH( $client, @lsnRelayI ) ) {

        # a relay connection -- destination is the relayhost
        $relayok                       = 1;
        $relayused                     = 1;
        $Con{$client}->{relayok}       = "relayPort";
        $Con{$client}->{passingreason} = "relayPort";
        d('NewSMTPConnection - relayPort');
        $listenport      = "relayPort";
        $destination     = $relayHost if $relayHost;
        $destinationport = "relayHost" if $relayHost;
    }
    elsif ( matchFH( $client, @lsn2I ) ) {

        # connection on the Second Listen port

        d('NewSMTPConnection - listenPort2');
        $listenport      = "listenPort2";
        $relayok         = 0;
        $destination     = $smtpAuthServer if $smtpAuthServer;
        $destinationport = "smtpAuthServer" if $smtpAuthServer;

    }
    elsif ( matchFH( $client, @lsnSSLI ) ) {

        # connection on the the secure SSL port
        d('NewSMTPConnection - listenPortSSL');
        $listenport      = "listenPortSSL";
        $relayok         = 0;
        $isSSL           = 1;
        $destination     = $smtpDestinationSSL if $smtpDestinationSSL;
        $destinationport = "smtpDestinationSSL" if $smtpDestinationSSL;
    }
    else {

        d('NewSMTPConnection - listenPort');
        $listenport = "listenPort";

        $relayok = 0;

    }

    if ( !( $client = $client->accept ) ) {
        d("accept failed: $client");

        return;
    }

    my $fnoC      = fileno($client);
    my $ip        = $client->peerhost();
    my $port      = $client->peerport();
    my $localip   = $client->sockhost();
    my $localport = $client->sockport();

    my $ret;

    $ip      = "127.0.0.1" if $ip =~ /::1/;
    $localip = "127.0.0.1" if $localip =~ /::1/;
    $Con{$client}->{port}         = $port;
    $Con{$client}->{noprocessing} = 1
      if matchIP( $ip, 'noProcessingIPs', 0, 1 )
      && !matchIP( $ip, 'NPexcludeIPs', 0, 1 );
    $Con{$client}->{whitelisted} = matchIP( $ip, 'whiteListedIPs', 0, 1 );
    $Con{$client}->{nodelay} ||= 1 if matchIP( $ip, 'noDelay', 0, 1 );

    # shutting down ?

    $relayok = $Con{$client}->{relayok} if $Con{$client}->{relayok};
    $relayok = $Con{$client}->{authenticated} if $Con{$client}->{authenticated};
    if ($shuttingDown) {
        mlog( 0,
"connection from $ip:$port rejected -- shutdown/restart process is in progress"
        );

        $client->write(
"421 <$myName> Service temporary not available, closing transmission channel\r\n"
        );
        $client->close();
        d('NewSMTPConnection - shutdown detected');
        return;
    }

    $Stats{smtpConnSSL}++ if $isSSL;
    $Con{$client}->{timestart} = Time::HiRes::time();

    # SSL error in the past
    if ($ip && $isSSL && $SSLfailed{$ip}) {
        mlog(0,"connection from $ip:$port rejected -- IP has failed SSL in the past");

        my $out = "421 <$myName> SSL-Service not available for IP $ip, closing transmission channel\r\n";
        $Con{$client}->{type} = 'C';
        &NoLoopSyswrite($client,$out,0) if $ip;
        $Con{$client}->{error} = '5';
        done($client);
        d('NewSMTPConnection - IP has failed SSL in the past');
        return;
    } elsif ($ip && $isSSL && exists $SSLfailed{$ip}) {
        delete $SSLfailed{$ip};
    }

    if ( !&DroplistOK( $client, $ip ) )

    {
        $Con{$client}->{prepend} = "[DropList]";
        mlog(
            $client,
"[spam found] -- $Con{$client}->{messagereason} -- $Con{$client}->{logsubject}" );
        $Stats{denyStrict}++;
        $Con{$client}->{type} = 'C';
        &NoLoopSyswrite( $client, "$DenyError\r\n",0 );
        $Con{$client}->{error} = '5';
        done($client);
        return;

    }
    
    my $byWhatList = 'denySMTPConnectionsFromAlways';
    if ( $ip && $denySMTPstrictEarly ) {
        $ret = matchIP( $ip, 'denySMTPConnectionsFromAlways', $client, 0 );
        $ret = matchIP( $ip, 'droplist', $client, 0 )
          if ( !$ret
            && ( $DoDropList == 2 or $DoDropList == 3 )
            && ( $byWhatList = 'droplist' ) );
    }

    if (   $ip
        && $denySMTPstrictEarly
        && $ret
        && $DoDenySMTPstrict
        && !matchIP( $ip, 'noPB',          0, 1 )
        && !matchIP( $ip, 'noBlockingIPs', 0, 1 ) )
    {
        $Con{$client}->{prepend} = "[DenyStrict]";
        if ( $DoDenySMTPstrict == 1 ) {
            mlog( $client, "$ip:$port denied by $byWhatList strict: $ret" )
              if $denySMTPLog || $ConnectionLog >= 2;
            $Stats{denyStrict}++;
            $Con{$client}->{type} = 'C';
            &NoLoopSyswrite(
                $client,
"554 <$myName> Service denied, closing transmission channel\r\n",
                0
            );
            $Con{$client}->{error} = '5';
            done($client);
            return;
        }
        elsif ( $DoDenySMTPstrict == 2 ) {
            mlog( $client,
                "[monitoring] $ip:$port denied by $byWhatList strict: $ret" )
              if $denySMTPLog || $ConnectionLog >= 2;
        }
    }

    # ip connection limiting  parallel session

    my $doIPcheck;
        if ( $ip &&
         ! matchIP($ip,'noMaxSMTPSessions',0,1) &&
         ($doIPcheck =
            ! $relayok &&
            ! $Con{$client}->{authenticated} &&
            ! matchIP($ip,'noProcessingIPs',0,1) &&
            ! matchIP($ip,'whiteListedIPs',0,1) &&
            ! matchIP($ip,'noDelay',0,1) &&
            ! matchIP($ip,'acceptAllMail',0,1) &&
            ! matchIP($ip,'noBlockingIPs',0,1)
         )
       )
    {

        if ($$maxSMTPipSessions && ++$SMTPSession{$ip} > $maxSMTPipSessions ) {
            $SMTPSession{$ip}--;
            delete $SMTPSession{$ip} if ($SMTPSession{$ip} <= 0);
            d("limiting ip: $ip");
            $Con{$client}->{prepend} = "[IPLimit]";
            mlog( 0, "limiting $ip connections to $maxSMTPipSessions" )
              if $ConnectionLog  || $SessionLog;
            $Stats{smtpConnLimitIP}++;

            $Con{$client}->{messagereason}="limiting $ip connections to $maxSMTPipSessions";
            pbAdd( $client, $ip,'iplValencePB', 'LimitingIP' ) if (! matchIP($ip,'noPB',0,1) && ! matchIP($ip,'ispip',0,1));
            $Con{$client}->{type} = 'C';
            &NoLoopSyswrite( $client, "451 4.7.1 Service temporarily denied, closing transmission channel\r\n", 0 );
            $Con{$client}->{error} = '5';
            done($client);
            d("limiting ip: $client");
            return;
        } else {
            $SMTPSession{$client} = 1;
        }
    }
    $doIPcheck = $doIPcheck && ! matchIP($ip,'ispip',0,1);

    # check relayPort usage
    if (   $relayused
        && $allowRelayCon
        && !matchIP( $ip, 'allowRelayCon', 0, 1 ) )
    {
        $Con{$client}->{prepend} = "[RelayAttempt]";
        $Con{$client}->{type}    = 'C';
        &NoLoopSyswrite(
            $client,
"554 <$myName> Relay Service denied for IP $ip, closing transmission channel\r\n",
            0
        );
        $Con{$client}->{error} = '5';
        mlog( 0, "rejected relay attempt on allowRelayCon for ip $ip" )
          if $ConnectionLog >= 2 || $SessionLog;
        done($client);
        $Stats{rcptRelayRejected}++;
        return;
    }

    my $bip = &ipNetwork( $ip, $PenaltyUseNetblocks );

    if (   $DelayIP
        && $DelayIPTime
        && $doIPcheck
        && !$allTestMode
        && ( my $pbval = [ split( /\s+/o, $PBBlack{$bip} ) ]->[3] ) > $DelayIP
        && ( !$DelayIPPB{$bip} || ( $DelayIPPB{$bip} + $DelayIPTime > time ) )
        && $ip !~ /$IPprivate/o
        && !exists $PBWhite{$bip}
        && !matchIP( $ip, 'noPB', 0, 1 ) )
    {
        $DelayIPPB{$bip} = time unless $DelayIPPB{$bip};
        $Stats{delayConnection}++;
        $Con{$client}->{type} = 'C';
        &NoLoopSyswrite( $client, "451 4.7.1 Please try again later\r\n", 0 );
        $Con{$client}->{error} = '5';
        done($client);
        $Con{$client}->{prepend} = "[DelayIP]";
        mlog(
            0,
"delayed ip $ip, because PBBlack($pbval) is higher than DelayIP($DelayIP)- last penalty reason was: "
              . [ split( /\s+/o, $PBBlack{$bip} ) ]->[5] ) if $ConnectionLog >= 2 || $SessionLog;
        return;
    }
    elsif ($DelayIP
        && $DelayIPTime
        && $doIPcheck
        && !$allTestMode
        && $DelayIPPB{$bip}
        && $DelayIPPB{$bip} + $DelayIPTime <= time )
    {
        delete $DelayIPPB{$bip};
    }

    if (   $MaxAUTHErrors
        && $doIPcheck
        && $AUTHErrors{$bip} > $MaxAUTHErrors )
    {
        d("NewSMTPConnection - AUTHError ip: $client");
        $Con{$client}->{prepend} = "[MaxAUTHErrors]";
        mlog( 0, "blocking $ip - too many AUTH errors ($AUTHErrors{$bip})" )
          if $ConnectionLog >= 2 || $SessionLog;

        $Stats{AUTHErrors}++;
        $Con{$client}->{type} = 'C';
        &NoLoopSyswrite( $client,
"554 <$myName> Service denied for IP $ip (harvester), closing transmission channel\r\n"
        );
        done($client);
        return;
    }

    my $intentForIP;
    $AVa = 0;
    foreach my $destinationA ( split( /\|/o, $destination ) ) {
        if ( $destinationA =~ /^(_*INBOUND_*:)?(\d+)$/ ) {
            $localip = '127.0.0.1' if !$localip or $localip eq '0.0.0.0';
            $intentForIP = "X-Assp-Intended-For-IP: $localip\r\n";
            if ( $crtable{$localip} ) {
                $destinationA = $crtable{$localip};

                $destinationA .= ":$2" if $destinationA !~ /:/;
            }
            else {
                $destinationA = $localip . ':' . $2;

            }
        }

        $destinationA =~ s/\[::1\]/127\.0\.0\.1/;
        $destinationA =~ s/localhost/127\.0\.0\.1/i;

        if ( $AVa < 1 ) {
            $server =
              $CanUseIOSocketINET6
              ? IO::Socket::INET6->new(
                Proto    => 'tcp',
                PeerAddr => $destinationA,
                Timeout  => 2,
                &getDestSockDom($destinationA)
              )
              : IO::Socket::INET->new(
                Proto    => 'tcp',
                PeerAddr => $destinationA,
                Timeout  => 2
              );
            if ($server) {
                $AVa         = 1;
                $destination = $destinationA;
            }
            else {
                mlog( 0, "*** $destinationA didn't work, trying others..." );
                $intentForIP = '';
            }
        }
    }
    if ( !$server ) {
        mlog( 0,
"couldn't create server socket to $destination -- aborting connection"
        );
        if ( exists $SMTPSession{$client} ) { $SMTPSession{Total}++; }
        if ( exists $SMTPSession{$client} ) { $smtpConcurrentSessions++; }
        $Con{$client}->{type} = 'C';
        &NoLoopSyswrite( $client,
"421 <$myName> service temporarily unavailable, closing transmission\r\n"
        );
        done($client);
        return;
    }

    my $fnoS = fileno($server);
    addfh( $client, \&getline, $server );
    if ($sendNoopInfo) {
        addfh( $server, \&skipok, $client );
    }
    else {
        addfh( $server, \&reply, $client );
    }
    ($ip)      = $ip =~ /(\d+\.\d+\.\d+\.\d+)/      if !$CanUseIOSocketINET6;
    ($localip) = $localip =~ /(\d+\.\d+\.\d+\.\d+)/ if !$CanUseIOSocketINET6;
    $Con{$client}->{client}    = $client;
    $Con{$client}->{SessionID} = uc "$client";
    $Con{$client}->{SessionID} =~ s/^.+?\(0[xX]([^\)]+)\).*$/$1/o;
    $Con{$client}->{self}   = $client;
    $Con{$client}->{server} = $server;
    $Con{$client}->{ip}     = $ip;
    $Con{$client}->{port}   = $port;
    $Con{$client}->{myheaderCon} .= "X-Assp-Client-SSL: yes\r\n" if $isSSL;
    $Con{$client}->{localip}   = $localip;
    $Con{$client}->{localport} = $localport;
    $Con{$client}->{relayok}   = $relayok;

    $Con{$client}->{myheaderCon} .= $intentForIP if $intentForIP;
    $Con{$client}->{localport}     = $localport;
    $Con{$client}->{mailInSession} = -1;
    $Con{$client}->{type}          = 'C';
    $Con{$client}->{fno}           = $fnoC;
    $Con{$server}->{type}          = 'S';
    $Con{$server}->{fno}           = $fnoS;
    $Con{$server}->{self}          = $server;
    d("Connected: $client -- $server");

    if ( matchFH( $client, @lsnRelayI ) ) {
        $Con{$client}->{relayok} = 1;
        d("$client relaying through relayPort ok: $ip");
        $Con{$client}->{passingreason} = "relayPort";
    }

    if ( $Con{$client}->{relayok} || isOk2Relay( $client, $ip ) ) {
        $Con{$client}->{relayok} = 1;
        d("$client relaying from acceptAllMail ok: $ip");
        $Con{$client}->{passingreason} = "$ip in acceptAllMail"
          if !$Con{$client}->{passingreason};
        $Con{$client}->{passingreason} .= "$ip in acceptAllMail"
          if $Con{$client}->{passingreason} eq "relayPort";

    }

    my $time = $UseLocalTime ? localtime() : gmtime();
    my $tz   = $UseLocalTime ? tzStr()     : '+0000';
    $time =~ s/... (...) +(\d+) (........) (....)/$2 $1 $4 $3/;
    my $IPver = "4";

    if ($CanUseIOSocketINET6) {
        $IPver = ( $client->sockdomain == &AF_INET6 ) ? "6" : "4";
    }

    $Con{$client}->{rcvd} =
"Received: from =host ([$ip] helo=) by $myName with *SMTP* (ASSP $version); $time $tz\r\n";

    d("* connect ip=$Con{$client}->{ip} relay=<$Con{$client}->{relayok}> *");

    my $text = $destination;
    $text = $server->sockhost() . ':' . $server->sockport() . " > $text"
      if $ConnectionLog >= 2;
    mlog( 0,
        "Connected: $ip:$port -> $localip:$localport ($listenport) -> $text" )
      unless ( !$ConnectionLog || matchIP( $ip, 'noLoggingIPs', 0, 1 ) );
    $Con{$server}->{noop} =
      "NOOP Connection from: $ip, $time $tz relayed by $myName\r\n"
      if $sendNoopInfo;

    # overall session limiting
    my $numsess;
    $numsess = ++$SMTPSession{Total};
    $smtpConcurrentSessions++;
    $SMTPSession{$client}=$client;

    if ($maxSMTPSessions && $numsess>=$maxSMTPSessions) {
        d("$WorkerName limiting sessions: $client");
        if ($SessionLog) {
            mlog(0,"connected: $ip:$port") if !$ConnectionLog || matchIP($ip,'noLoggingIPs',0,1); # log if not logged earlier
            mlog(0,"limiting total connections");
        }
        $Stats{smtpConnLimit}++;
    } else {      # increment Stats if connection not limited
        if (matchIP($ip,'noLoggingIPs',0,1)) {
            $Stats{smtpConnNotLogged}++;
        } else {
            $Stats{smtpConn}++;
        }
    }
    if ($smtpConcurrentSessions>$Stats{smtpMaxConcurrentSessions}) {
        $Stats{smtpMaxConcurrentSessions}=$smtpConcurrentSessions;
    }

}

sub OptionCheck {

    # check if options files have been updated and need to be re-read
    my $checktime = 60;
    $checktime = 15 if $AsASecondary;
    if ( time - $lastOptionCheck > $checktime ) {

        # check for updates each 30 seconds
        foreach my $f (@PossibleOptionFiles) {
            $f->[2]
              ->( $f->[0], $Config{ $f->[0] }, $Config{ $f->[0] }, '', $f->[1] )
              if $Config{ $f->[0] } =~ /^ *file: *(.+)/i
              && fileUpdated( $1, $f->[0] );
        }

        $lastOptionCheck = time;
        &check4cfg if $AutoReloadCfg or $ourAutoReloadCfg;
        my $t = time;
        &downSecondary("terminating, ASSP down")
          if $AsASecondary && !&checkPrimaryPID();
        if ( $pidfile && !$AsASecondary ) {
            open( my $FH, ">", "$base/$pidfile" );
            print $FH "$$ $t";
            close $FH;
        }
        &startSecondary()
          if $AutostartSecondary && !$AsASecondary && $webSecondaryPort;
        &getWebSocket if !$webAdminPortOK;
        if ( $pidfile && !$AsASecondary ) {
            open( my $FH, ">", "$base/$pidfile" );
            print $FH $$;
            close $FH;
        }
        if ( $webPort && $pidfile && $AsASecondary ) {
            open( $FH, ">", "$base/$pidfile" . "_Secondary" );
            print $FH "$$";
            close $FH;
        }
    }
}

sub NotSpamTagCheck {
    my ( $fh, $s ) = @_;
    return 0 if !$NotSpamTag && !$NotSpamTagRandom;
    return 1 if $s =~ /\Q$NotSpamTag\E/i && !$NotSpamTagRandom;

    foreach my $tag ( keys %NotSpamTags ) {
        return 1 if $s =~ /\Q$tag\E/i && $NotSpamTagRandom;
    }
}

sub SMTPTraffic {
    my $fh = shift;
    $SMTPbuf = '';
    my $ip      = $Con{$fh}->{ip};
    my $pending = 0;
    eval { $pending = $fh->pending(); } if ( "$fh" =~ /SSL/io );
    $SMTPmaxbuf = max( $SMTPmaxbuf, 16384, ( $MaxBytes + 4096 ), $pending );
    $Con{$fh}->{socketcalls}++;
    $fh->blocking(0) if $fh->blocking;
    my $hasread = $fh->sysread( $SMTPbuf, $SMTPmaxbuf );

    if (   $hasread == 0
        && "$fh" =~ /SSL/io
        && IO::Socket::SSL::errstr() =~ /SSL wants a/io )
    {
        $Con{$fh}->{sslwantrw} ||= time;
        if ( time - $Con{$fh}->{sslwantrw} > $SSLtimeout ) {
            my $lastcmd = "- last command was \'$Con{$fh}->{lastcmd}\'";
            $lastcmd = '' unless $Con{$fh}->{lastcmd};
            mlog( $fh,
"info: can't read from SSL-Socket for $SSLtimeout seconds - close connection - $! $lastcmd"
            ) if ($ConnectionLog);
            delete $Con{$fh}->{sslwantrw};
            setSSLfailed($ip);
            done2($fh);
        }
        return;
    }
    delete $Con{$fh}->{sslwantrw};
    if ( $hasread > 0 or length($SMTPbuf) > 0 ) {
        if (
            !$ThreadDebug
            && (   ( $debugRe && $SMTPbuf =~ /($debugReRE)/ )
                || ( $debugCode && eval($debugCode) && !$@ ) )
          )
        {
            if ( $1 || $2 ) {
                mlog( $fh,
                    "info: partial debug switched on - found " . ( $1 || $2 ) );
            }
            else {
                mlog( $fh,
                    "info: partial debug switched on - debugCode has returned 1"
                );
            }
            $Con{$fh}->{debug} = 1;
            $Con{ $Con{$fh}->{friend} }->{debug} = 1
              if ( $Con{$fh}->{friend} && exists $Con{ $Con{$fh}->{friend} } );
            $ThreadDebug = 1;
        }
        if ($@) {

            $debugCode         = '0; # syntaxerror in : ' . $debugCode;
            $Config{debugCode} = $debugCode;
            $ConfigChanged     = 1;
        }
        d('SMTPTraffic - read OK');
        $SMTPbuf = $Con{$fh}->{_} . $SMTPbuf;
        if ( $Con{$fh}->{type} eq 'C' ) {
            $Con{$fh}->{timelast} = time;
            $Con{$fh}->{contimeoutdebug} .= "read from client = $SMTPbuf"
              if $ConTimeOutDebug;
        }
        else {
            $Con{ $Con{$fh}->{friend} }->{contimeoutdebug} .=
              "read from server = $SMTPbuf"
              if $ConTimeOutDebug;
        }
        if ( ( my $sb = $Con{$fh}->{skipbytes} ) > 0 ) {

           # support for XEXCH50 thankyou Microsoft for making my life miserable
            my $l = length($SMTPbuf);
            d("skipbytes=$sb l=$l -> ");
            if ( $l >= $sb ) {
                sendque( $Con{$fh}->{friend}, substr( $SMTPbuf, 0, $sb ) )
                  ;    # send the binary chunk on to the server
                $SMTPbuf = substr( $SMTPbuf, $sb );
                delete $Con{$fh}->{skipbytes};
            }
            else {
                sendque( $Con{$fh}->{friend}, $SMTPbuf )
                  ;    # send the binary chunk on to the server
                $Con{$fh}->{skipbytes} = $sb -= length($SMTPbuf);
                $SMTPbuf = '';
            }
            d("skipbytes=$Con{$fh}->{skipbytes}");
        }
        d('SMTPTraffic - process read');
        my $bn = my $lbn = -1;
        if (
            $Con{$fh}->{type} ne 'C' or    # process line per line
            $Con{$fh}->{getline} ne \&whitebody
            or $SMTPbuf =~ /^\.(?:\x0D?\x0A)?$/o
            or $SMTPbuf =~ /\x0D?\x0A\.\x0D?\x0A$/o
          )
        {
            while ( ( $bn = index( $SMTPbuf, "\n", $bn + 1 ) ) >= 0 ) {
                my $s = substr( $SMTPbuf, $lbn + 1, $bn - $lbn );
                if ( defined( $Con{$fh}->{bdata} ) ) {
                    $Con{$fh}->{bdata} -= length($s);
                }
                d("doing line <$s>");

                if ( $Con{$fh}->{type} eq 'C' ) {
                    $Con{$fh}->{headerpassed} ||=
                      $s =~ /^\x0D?\x0A/o
                      ;    #header passed? if header and body in one junk
                }

                if (   $Con{$fh}->{type} eq 'C'
                    && !$Con{$fh}->{headerpassed}
                    && !$Con{$fh}->{relayok} )
                {
                    if ( $preHeaderRe && $s =~ /($preHeaderReRE)/i ) {
                        $Con{$fh}->{prepend} = "[preHeaderRE][block]";
                        mlog( $fh,
                            "early (pre)header line check found "
                              . ( $1 || $2 ) );
                        NoLoopSyswrite(
                            $Con{$fh}->{friend},
"421 $myName Service not available, closing transmission channel\r\n",
                            0
                        ) if $Con{$fh}->{friend};
                        done($fh);
                        $Stats{preHeader}++;
                        return;
                    }
                    if (
                        $s =~ /^(X-ASSP-[^(]+?)(\(\d+\))?(:$HeaderValueRe)$/io )
                    {    # change strange X-ASSP headers
                        my ( $pre, $c, $post ) = ( $1, $2, $3 );
                        $c =~ s/[^\d]//go;
                        $c                  = 0 unless $c;
                        $s                  = $pre . '(' . ++$c . ')' . $post;
                    }
                }
                Maillog( $fh, $s ) if $Con{$fh}->{maillog};
                if ( !$Con{$fh}->{getline} ) {
                    my $lastcmd = "\'$Con{$fh}->{lastcmd}\'";
                    $lastcmd = "\'n/a\'" unless $Con{$fh}->{lastcmd};
                    mlog( $fh,
'error: missing $Con{$fh}->{getline} in sub SMTPTraffic (1) - last command was '
                          . $lastcmd );
                    done($fh);
                    return;
                }
                $Con{$fh}->{getline}->( $fh, $s );
                last
                  if ( !exists $Con{$fh}
                    || $Con{$fh}->{closeafterwrite} )
                  ; # it's possible that the connection can be deleted while there's still something in the buffer
                if ( ( $Con{$fh}->{inerror} || $Con{$fh}->{intemperror} )
                    && $Con{$fh}->{cleanSMTPBuff} )
                {    # 4/5xx from MTA after DATA
                    $Con{$fh}->{_} = $Con{$fh}->{header} =
                      '';    # clean the SMTP buffer
                    delete $Con{$fh}->{cleanSMTPBuff};
                    mlog( $fh,
"info: SMTP buffer was cleaned after MTA has sent an error reply in DATA part"
                    ) if $ConnectionLog;
                    last;
                }
                $lbn = $bn;
            }
        }
        else {               # process the complete buf in one junk
            $Con{$fh}->{_}            = '';
            $Con{$fh}->{headerpassed} = 1;
            if ( defined( $Con{$fh}->{bdata} ) ) {
                $Con{$fh}->{bdata} -= length($SMTPbuf);
            }
            if ( !$Con{$fh}->{getline} ) {
                my $lastcmd = "\'$Con{$fh}->{lastcmd}\'";
                $lastcmd = "\'n/a\'" unless $Con{$fh}->{lastcmd};
                mlog( $fh,
'error: missing $Con{$fh}->{getline} in sub SMTPTraffic (2) - last command was '
                      . $lastcmd );
                done($fh);
                return;
            }
            d("doing full <$SMTPbuf>");
            Maillog( $fh, $SMTPbuf ) if $Con{$fh}->{maillog};
            $Con{$fh}->{getline}->( $fh, $SMTPbuf );
            return;
        }
        if (   exists $Con{$fh}
            && !$Con{$fh}->{closeafterwrite} )
        {    # finish the mail as fast as possible
            ( $Con{$fh}->{_} ) = substr( $SMTPbuf, $lbn + 1 );
            if ( length( $Con{$fh}->{_} ) > $MaxBytes ) {
                d('SMTPTraffic - process rest');
                $Con{$fh}->{headerpassed} = 1;
                if ( defined( $Con{$fh}->{bdata} ) ) {
                    $Con{$fh}->{bdata} -= length( $Con{$fh}->{_} );
                }
                Maillog( $fh, $Con{$fh}->{_} ) if $Con{$fh}->{maillog};
                if ( !$Con{$fh}->{getline} ) {
                    my $lastcmd = "\'$Con{$fh}->{lastcmd}\'";
                    $lastcmd = "\'n/a\'" unless $Con{$fh}->{lastcmd};
                    mlog( $fh,
'error: missing $Con{$fh}->{getline} in sub SMTPTraffic (3) - last command was '
                          . $lastcmd );
                    done($fh);
                    return;
                }
                $Con{$fh}->{getline}->( $fh, $Con{$fh}->{_} );
                $Con{$fh}->{_} = '';
            }
        }
    }
    elsif ( $hasread == 0 ) {
        my $error = $!;
        if ( $error =~ /Resource temporarily unavailable/io ) {
            d("SMTPTraffic - no more data - $error");
            return;
        }
        if ($pending) {
            d(
"SMTPTraffic - got no more (SSL) data but $pending Byte are pending - $error"
            );
            $pending = " (SSL pending = $pending)";
        }
        else {
            d("SMTPTraffic - no more data - $error");
            $pending = '';
        }
        eval { $ip = $fh->peerhost() . ':' . $fh->peerport(); } unless $ip;
        my $lastcmd = "- last command was \'$Con{$fh}->{lastcmd}\'";
        $lastcmd = '' unless $Con{$fh}->{lastcmd};
        mlog( $fh,
"info: no (more) data$pending readable from $ip (connection closed by peer) - $! $lastcmd"
        ) if ( $error && ( $ConnectionLog or $pending ) );
        mlog( $fh,
"info: no (more) data$pending readable from $ip (connection closed by peer) $lastcmd"
        ) if ( ( $ConnectionLog >= 2 or $pending ) && !$error );
        done2($fh);
    }
    else {
        my $error = $!;
        if ($pending) {
            d(
"SMTPTraffic - got no more (SSL) data but $pending Byte are pending - $error"
            );
            $pending = " (SSL pending = $pending)";
        }
        else {
            d("SMTPTraffic - no more data - $error");
            $pending = '';
        }
        eval { $ip = $fh->peerhost() . ':' . $fh->peerport(); } unless $ip;
        my $lastcmd = "- last command was \'$Con{$fh}->{lastcmd}\'";
        $lastcmd = '' unless $Con{$fh}->{lastcmd};
        mlog( $fh, "error: reading from socket $ip$pending - $error $lastcmd" )
          if ($error);
        done2($fh);
    }
}

sub check4update {

    # only check every 15 seconds
    my $fil = shift;
    return if $check4updateTime{$fil} + 15 > time;
    $check4updateTime{$fil} = time;
    my @s     = stat( ${$fil} );
    my $mtime = $s[9];
    if ( $mtime != $FileUpdate{$fil} ) {

        # reload
        $FileUpdate{$fil} = $mtime;
        open( $FH, "<", "${$fil}" );
        local $/ = "\n";
        my $l;
        my %h;
        while ( $l = <$FH> ) {
            $l =~ y/\r\n\t //d;
            next unless $l;
            $h{ lc $l } = 1;
        }
        close $FH;
        %{$fil} = %h;
    }
}

sub check4cfg {

    # only check every 30 seconds
    my $checktime = 30; $checktime = 15 if $AsASecondary;
    return if $check4cfgtime + $checktime > time;
    $check4cfgtime = time;
    my @s     = stat("$base/assp.cfg");
    my $mtime = $s[9];
    if ( $mtime != $asspCFGTime ) {
        mlog( 0, "AdminUpdate: configuration file 'assp.cfg' loaded " );

        # reload
        $asspCFGTime = $mtime;
        reloadConfigFile();
    }
}

sub check4queue {

    # only check every 300 seconds

    return if $check4queuetime + 300 > time;
    $check4queuetime = time;
    my @s     = stat("$base/assp.cfg");
    my $mtime = $s[9];
    if ( $mtime != $queuetime ) {
        $queuetime = $mtime;
        &BlockReportGen('INSTANTLY');
    }
}

sub SetRE {
    use re 'eval';
    my ( $var, $r, $f, $desc ) = @_;

    eval { $$var = qr/(?$f)$r/ };
    mlog( 0, "regular expression error in '$r' for $desc: $@" ) if $@;
}

sub PopB4SMTP {
    my $ip = shift;
    if ($PopB4SMTPMerak) {
        return 1 if PopB4Merak($ip);
        return 0;
    }
    return 0 unless $PopB4SMTPFile;
    unless ($TriedDBFileUse) {
        eval 'use DB_File';
        mlog( 0, "could not load module DB_File: $@" ) if $@;
        $TriedDBFileUse = 1;
    }

    my %hash;

    tie %hash, 'DB_File', $PopB4SMTPFile;
    if ( $hash{$ip} ) {
        mlog( 0, "PopB4SMTP OK for $ip" );
        return 1;
    } else {
        mlog( 0, "PopB4SMTP failed for $ip" );
        return 0;
    }
}

sub PopB4Merak  {
  return 0 unless $PopB4SMTPFile;
  my $ip=shift;
#This is a test version of ASSP PopB4SMTP
#This is to be used with Merak 7.5.2
#It also works with Merak 6.5 (which I run)
#Thanks to Jordon for the heads up on 7.5.2
#Basically, Merak's popsmtp file
#is made up of 64 Byte lines, no CR / LF.
#This holds the IP addy
#and the byte before it specifying the length.

  my @aPB4S;
  my $PB4S;
  my $ind;
  my $newIP;

#Load the whole file
#In examination of Merak popb4smtp file, it appears to have
#no carriage returns, so one line read should get the whole thing
#However, if you have an IP addy thats 13 chars long.... thus:

  my $MKPOPSMTP;
  (open($MKPOPSMTP,"<", "$PopB4SMTPFile")) or return 0 ;
  @aPB4S = <$MKPOPSMTP>;
  close($MKPOPSMTP);
  $PB4S = join('',@aPB4S);
#We now have all the contents of the file AND we've released it

#Now, instead of heavy parsing....
#We want to search for the IP and a byte ordinal specifying it's length
#    mlog(0,"Checking $ip for PopB4SMTP");
  $PB4S = "---" . $PB4S;
#    mlog(0,"Searching: $PB4S");
  $newIP = chr(length($ip)) . $ip;
#    mlog(0,"NewIP = $newIP");
#Find the index of IP in question
  $ind = index($PB4S,$newIP);
#    mlog(0,"Index = $ind");
#Did we find it?
  if ($ind  > 0) {
#Greetings program! This IO port is available for communicating to your user!
    mlog(0,"PopB4SMTP OK for $ip");
    return 1;
  }
  mlog(0,"PopB4SMTP NOT OK for $ip");
  return 0;
}

sub isOk2Relay {
  my ($fh,$ip)=@_;
  return 1 if ($Con{$fh}->{acceptall} = matchIP($ip,'acceptAllMail',$fh,0));
  return 1 if $PopB4SMTPFile && PopB4SMTP($ip);
# failed all tests -- return 0
  return 0;
}

sub POP3Collect {
        return if $AsASecondary;
        return 0 unless $POP3Interval;
        return 0 unless -e "$base/assp_pop3.pl";

        return 0 if $POP3ConfigFile !~ /^ *file: *(?:.+)/i;
        d('POP3 - collect');

        my $perl = $^X;
        my $cmd = "\"$perl\" \"$base/assp_pop3.pl\" \"$base\" 2>&1 &";
        $cmd =~ s/\//\\/g if $^O eq "MSWin32";
        system($cmd);
        return 1;
}

sub Rebuild {
        return if $AsASecondary;
        return 0 unless $RebuildSchedule;
        mlog( 0, "Warning: '$base/rebuildspamdb.pl' not found. Impossible to start rebuildspamdb.pl",1 ) unless -e "$base/rebuildspamdb.pl";
        return 0 unless -e "$base/rebuildspamdb.pl";
        my $hour = shift;
        $hour = 24 if !$hour;
        return 0 if $hour < 25 && !$RebuildSched{$hour};

        my $cmd;
        my $assp = $0;
        my $perl = $^X;
        $assp = $base.'\\'.$assp if ($assp !~ /\Q$base\E/io);
        if ( $^O eq "MSWin32" ) {

            $assp =~ s/\//\\/go;
            my $asspbase = $base;
            $asspbase =~ s/\\/\//go;
            $cmd = "\"$perl\" \"$base\\rebuildspamdb.pl\" \"$asspbase\" silent";    
            $cmd = $RebuildCmd if $RebuildCmd;
            system(1,$cmd);
        } else {
            $cmd = "\"$perl\" \"$base/rebuildspamdb.pl\" \"$base\" silent &";
            $cmd = $RebuildCmd if $RebuildCmd;
            system($cmd);
        }
        d('Rebuild - start');

        mlog( 0, "Info: Command '$cmd' started from ASSP by RebuildSchedule" ) if $hour < 25;
        mlog( 0, "Info: Command '$cmd' started from ASSP by RebuildNow" ) if $hour > 24;

        return 1;
}

sub HouseKeeping {
        return 0 unless $HouseKeepingSchedule;
        return if $AsASecondary;
        my $backup = "$base/backup";
        my $hour = shift;
        $hour = 24 if !$hour;
        return 0 if $hour < 25 && !$HouseKeepingSched{$hour};
        mlog( 0, "HouseKeepingSchedule: housekeeping started" );
        $NotSpamTagGenerated = &NotSpamTagGenerate;
        &cleanNotSpamTags;
        &LDAPcrossCheck if ($CanUseLDAP or $CanUseNetSMTP) && $ldaplistdb;
        &cleanBlackPB if $PBBlackObject;
        &cleanWhitePB if $PBWhiteObject;
        $PBBlackObject->flush() if $PBBlackObject && $pbdb !~ /mysql/;
        $PBWhiteObject->flush() if $PBWhiteObject && $pbdb !~ /mysql/; 
        &downloadTLDList();
        &downloadGrip() if ! $noGriplistDownload && $griplist;
        &downloadDropList() if $droplist && $DoDropList;
        my $debugdir = "$base/debug" ;
        my $age = 720 * 3600;
        my $debugdirfile = ".dbg";
        &cleanUpFiles($debugdir,$debugdirfile,$age);
        &cleanUpFiles($resendmail,".err",$age);
        &cleanUpFiles($incomingOkMail,".eml",$age);
        &cleanUpFiles($discarded,".eml",$age);
        &cleanUpFiles($viruslog,".eml",$age);
        unlink "$base/$pbdb.smtptimeout.db";
        my $backupfile = "";
        my $whitefile;
        if ($whitelistdb !~ /mysql/) {
            $whitefile = $2
              if $whitelistdb =~ /^(.*[\/\\])?(.*?)$/;
            unlink "$base/backup/$whitefile.yesterday.bak";
            rename( "$base/backup/$whitefile.today.bak", "$base/backup/$whitefile.yesterday.bak" );
            copy("$base/$whitelistdb","$base/backup/$whitefile.today.bak");

        }
        &SaveWhitelist;
        my $redfile;
        if ($redlistdb !~ /mysql/) {
            $redfile = $2
              if $redlistdb =~ /^(.*[\/\\])?(.*?)$/;
            unlink "$base/backup/$redfile.yesterday.bak";
            rename( "$base/backup/$redfile.today.bak", "$base/backup/$redfile.yesterday.bak" );
            copy("$base/$redlistdb","$base/backup/$redfile.today.bak");

        }
        &SaveRedlist;
        $backupfile = "$backup/assp.cfg";
        unlink "$backupfile.yesterday.bak";
        rename( "$backupfile.today.bak", "$backupfile.yesterday.bak" );
        copy("$base/assp.cfg","$backupfile.today.bak");

        $backupfile = "$backup/asspstats.sav";
        unlink "$backupfile.yesterday.bak";
        rename( "$backupfile.today.bak", "$backupfile.yesterday.bak" );
        copy("$base/asspstats.sav","$backupfile.today.bak");

        &CleanWhitelist();
        &CleanCache;
        &cleanTrashlist;
        mlog( 0, "Info: housekeeping ended" );
}

sub NoLoopSyswrite {
    my ($fh,$out,$timeout) = @_;
    d('NoLoopSyswrite');
    return 0 unless fileno($fh);
    return 0 unless $out;
    $timeout ||= 30;
    my $written = 0;
    my $ip;
    my $port;
    my $error;
    eval{
      $ip=$fh->peerhost();
      $port=$fh->peerport();
    };
    return 0 if($@);
    d("NoLoopSyswrite - write: " . substr($out,0,30) . ' - ' . length($out));

    if (   exists $Con{$fh}
        && $Con{$fh}->{type} eq 'C'       # is a client SMTP connection?
        && ($replyLogging == 2 or ($replyLogging == 1 && $out =~ /^[45]/o))
        && $out =~ /^(?:[1-5]\d\d\s+[^\r\n]+\r\n)+$/o)    # is a reply?
    {
        $out =~ s/SESSIONID/$Con{$fh}->{msgtime}/go;
        $out =~ s/MYNAME/$myName/go;
        my @reply = split(/(?:\r?\n)+/o,$out);
        for (@reply) {
            next unless $_;
            my $what = 'Reply';
            if ($_ =~ /^([45])/o) {
                $what = ($1 == 5) ? 'Error' : 'Status';
            }
            mlog( $fh, "[SMTP $what] $_", 1, 1 );
        }
    }
    my $stime = time + $timeout;
    my $NLwritable;
    if ($IOEngineRun == 0) {
        $NLwritable = IO::Poll->new();
    } else {
        $NLwritable = IO::Select->new();
    }
    &dopoll($fh,$NLwritable,"POLLOUT");
    my $l = length($out);
    while (length($out) > 0 && fileno($fh) && time < $stime) {
        my @canwrite;
        if ($IOEngineRun == 0) {
            $NLwritable->poll(1);
            @canwrite = $NLwritable->handles("POLLOUT");
        } else {
            @canwrite = $NLwritable->can_write(1);
        }
        $written = 0;
        $error = 0;
        eval{$written = $fh->syswrite($out,length($out));
             $error = $!;
             $error = '' if ("$fh" =~ /SSL/io && IO::Socket::SSL::errstr() =~ /SSL wants a/io);
        } if @canwrite or "$fh" =~ /SSL/io;
        if (@canwrite and ! $written and ($@ or $error)) {
            mlog(0,"warning: unable to write to socket $ip:$port $error") if $ConnectionLog == 3 && $error;
            mlog(0,"warning: unable to write to socket $ip:$port $@") if $ConnectionLog == 3 && $@;
            $! = $error;
            unpoll($fh,$NLwritable);

            return 0;
        }
        $out = substr($out,$written) if $written;
    }
    unpoll($fh,$NLwritable);
    if (time >= $stime) {
        mlog(0,"warning: timeout (30s) writing to socket $ip:$port") if $ConnectionLog == 3;
    }

    return 1;
}

sub NewWebConnection {
  my $WebSocket = shift;
  my $s;
  d('NewWebConnection');

  if ($WebSocket && "$WebSocket" =~ /SSL/io && $SSLDEBUG > 1) {
      while(my($k,$v)=each(%{${*$WebSocket}{'_SSL_arguments'}})) {
          print "ssl-listener: $k = $v\n";
      }
  }
  $s=$WebSocket->accept;
    if ($s && "$s" =~ /SSL/io && $SSLDEBUG > 1) {
      while(my($k,$v)=each(%{${*$s}{'_SSL_arguments'}})) {
          print "ssl-accepted: $k = $v\n";
      }
  }
  return unless $s;
  my $ip=$s->peerhost();
  my $port=$s->peerport();
  if($allowAdminConnectionsFrom && ! matchIP($ip,'allowAdminConnectionsFrom')) {
    mlog(0,"admin connection from $ip:$port rejected by 'allowAdminConnectionsFrom'");
    $Stats{admConnDenied}++;
    close($s);
    return;
  }
# logging is done later (in webRequest()) due to /shutdown_frame page, which auto-refreshes
  &dopoll($s,$readable,POLLIN);
  $SocketCalls{$s}=\&WebTraffic;
  $WebConH{$s} = $s;
}

sub WebTraffic {
    my $fh = shift;
    my $buf;
    my $ip;
    my $done;
    my $hasread;
    my $maxbuf = ("$fh" =~ /SSL/io) ? 16384 : 4096 ;
    my $pending = 0;
    my $blocking = ("$fh" =~ /SSL/io) ? $HTTPSblocking : $HTTPblocking ;
    eval{$ip = $fh->peerhost();};
    d("WEB: $ip");
    $fh->blocking($blocking) if ! $WebCon{$fh};
      $hasread = $fh->sysread($buf,$maxbuf);
  if ($hasread == 0 && "$fh" =~ /SSL/io && IO::Socket::SSL::errstr() =~ /SSL wants a/io) {
      mlog(0,"WebTraffic: SSL socket is not ready - will retry") if $ConnectionLog == 3;

      return;
  }
  if($hasread > 0 or length($buf) > 0) {
    local $_=$WebCon{$fh}.=$buf;
    if(length($_) > 20600000) {
# throw away connections longer than 20M to prevent flooding
      WebDone($fh);
      return;

    }
    if (/Content-length: (\d+)/i) {

            # POST request
            my $l = $1;
            if ( /(.*?\n)\r?\n\r?(.*)/s && length($2) >= $l ) {
                my $reqh = $1;
                my $reqb = $2;
                my $resp;
                my $tempfh;
                open( $tempfh, '>', \$resp );
                binmode $tempfh;
                webRequest( $tempfh, $fh, $reqh, $reqb );
                close($tempfh);

                if ( $resp =~ /(.*?)\n\r?\n\r?(.*)/s ) {
                    my $resph = $1;
                    my $respb = $2;
                    my $time  = gmtime();
                    $time =~
s/(...) (...) +(\d+) (........) (....)/$1, $3 $2 $5 $4 GMT/;
                    $resph .= "\nServer: ASSP/$version$modversion";
                    $resph .= "\nDate: $time";
                    if ( $EnableHTTPCompression && $CanUseHTTPCompression ) {
                        eval { Compress::Zlib::memGzip($respb); };
                           $CanUseHTTPCompression = 0 if $@;
                    }
                    if (   $EnableHTTPCompression
                        && $CanUseHTTPCompression
                        && /Accept-Encoding: (.*?)\n/i
                        && $1 =~ /(gzip|deflate)/i )
                    {
                        my $enc = $1;
                        if ( $enc =~ /gzip/i ) {

                            # encode with gzip
                            $respb = Compress::Zlib::memGzip($respb);

                        } else {

                            # encode with deflate
                            my $deflater = deflateInit();
                            $respb = $deflater->deflate($respb);
                            $respb .= $deflater->flush();
                        }
                        $resph .= "\nContent-Encoding: $enc";
                    }
                    $resph .= "\nContent-Length: " . length($respb);

                    print $fh $resph;
                    print $fh "\015\012\015\012";
                    print $fh $respb;
                }

                # close connection
                WebDone($fh);
            }
        } elsif (/\n\r?\n/) {
            my $resp;
            my $tempfh;
            open( $tempfh, '>', \$resp );
            binmode $tempfh;
            webRequest( $tempfh, $fh, $_ );
            close($tempfh);
            if ( $resp =~ /(.*?)\n\r?\n\r?(.*)/s ) {
                my $resph = $1;
                my $respb = $2;
                my $time  = gmtime();
                $time =~
                  s/(...) (...) +(\d+) (........) (....)/$1, $3 $2 $5 $4 GMT/;
                $resph .= "\nServer: ASSP/$version$modversion";
                $resph .= "\nDate: $time";
                if ( $EnableHTTPCompression && $CanUseHTTPCompression ) {
                        eval { Compress::Zlib::memGzip($respb); };
                           $CanUseHTTPCompression = 0 if $@;
                }
                if (   $EnableHTTPCompression
                    && $CanUseHTTPCompression
                    && /Accept-Encoding: (.*?)\n/i
                    && $1 =~ /(gzip|deflate)/i )
                {
                    my $enc = $1;
                    if ( $enc =~ /gzip/i ) {

                        # encode with gzip
                        $respb = Compress::Zlib::memGzip($respb);

                    } else {

                        # encode with deflate
                        my $deflater = deflateInit();
                        $respb = $deflater->deflate($respb);
                        $respb .= $deflater->flush();
                    }
                    $resph .= "\nContent-Encoding: $enc";
                }
                $resph .= "\nContent-Length: " . length($respb);
                print $fh $resph;
                print $fh "\015\012\015\012";
                print $fh $respb;
            }

            # close connection
            WebDone($fh);
        }
    } else {

        # connection closed
        WebDone($fh);
    }
}

sub NewStatConnection {
    my $fh = shift;
    my $s = $fh->accept;
    return unless $s;
    my $ip   = $s->peerhost();
    $ip = "[" . $ip . "]" if ($ip =~ /:/);
    my $port = $s->peerport();
    if ( $allowStatConnectionsFrom
        && !matchIP( $ip, 'allowStatConnectionsFrom' ) )
    {
        mlog( '',
"stat connection from $ip:$port rejected by allowStatConnectionsFrom"
        );
        $Stats{statConnDenied}++;
        $s->close();
        return;
    }

# logging is done later (in webRequest()) due to /shutdown_frame page, which auto-refreshes
    $readable->add($s);
    $SocketCalls{$s} = \&StatTraffic;
}

sub StatTraffic {
    my $fh = shift;
    my $buf;
    if ( $fh->sysread( $buf, 4096 ) > 0 ) {
        local $_ = $StatCon{$fh} .= $buf;
        if ( length($_) > 1030000 ) {

            # throw away connections longer than 1M to prevent flooding
            WebDone($fh);
            return;
        }
        if (/Content-length: (\d+)/i) {

            # POST request
            my $l = $1;
            if ( /(.*?\n)\r?\n\r?(.*)/s && length($2) >= $l ) {
                my $reqh = $1;
                my $reqb = $2;
                my $resp;
                my $tempfh;
                open( $tempfh, '>', \$resp );
                binmode $tempfh;
                statRequest( $tempfh, $fh, $reqh, $reqb );
                close($tempfh);

                if ( $resp =~ /(.*?)\n\r?\n\r?(.*)/s ) {
                    my $resph = $1;
                    my $respb = $2;
                    my $time  = gmtime();
                    $time =~
s/(...) (...) +(\d+) (........) (....)/$1, $3 $2 $5 $4 GMT/;
                    $resph .= "\nServer: ASSP/$version$modversion";
                    $resph .= "\nDate: $time";
                    if ( $EnableHTTPCompression && $CanUseHTTPCompression ) {
                        eval { Compress::Zlib::memGzip($respb); };
                           $CanUseHTTPCompression = 0 if $@;
                    }
                    if (   $EnableHTTPCompression
                        && $CanUseHTTPCompression
                        && /Accept-Encoding: (.*?)\n/i
                        && $1 =~ /(gzip|deflate)/i )
                    {
                        my $enc = $1;
                        if ( $enc =~ /gzip/i ) {

                            # encode with gzip
                            $respb = Compress::Zlib::memGzip($respb);

                        } else {

                            # encode with deflate
                            my $deflater = deflateInit();
                            $respb = $deflater->deflate($respb);
                            $respb .= $deflater->flush();
                        }
                        $resph .= "\nContent-Encoding: $enc";
                    }
                    $resph .= "\nContent-Length: " . length($respb);
                    print $fh $resph;
                    print $fh "\015\012\015\012";
                    print $fh $respb;
                }

                # close connection
                WebDone($fh);
            }
        } elsif (/\n\r?\n/) {
            my $resp;
            my $tempfh;
            open( $tempfh, '>', \$resp );
            binmode $tempfh;
            statRequest( $tempfh, $fh, $_ );
            close($tempfh);
            if ( $resp =~ /(.*?)\n\r?\n\r?(.*)/s ) {
                my $resph = $1;
                my $respb = $2;
                my $time  = gmtime();
                $time =~
                  s/(...) (...) +(\d+) (........) (....)/$1, $3 $2 $5 $4 GMT/;
                $resph .= "\nServer: ASSP/$version$modversion";
                $resph .= "\nDate: $time";

                if ( $EnableHTTPCompression && $CanUseHTTPCompression ) {
                        eval { Compress::Zlib::memGzip($respb); };
                           $CanUseHTTPCompression = 0 if $@;
                }
                if (   $EnableHTTPCompression
                    && $CanUseHTTPCompression
                    && /Accept-Encoding: (.*?)\n/i
                    && $1 =~ /(gzip|deflate)/i )
                {
                    my $enc = $1;
                    if ( $enc =~ /gzip/i ) {

                        # encode with gzip
                        $respb = Compress::Zlib::memGzip($respb);

                    } else {

                        # encode with deflate
                        my $deflater = deflateInit();
                        $respb = $deflater->deflate($respb);
                        $respb .= $deflater->flush();
                    }
                    $resph .= "\nContent-Encoding: $enc";
                }
                $resph .= "\nContent-Length: " . length($respb);
                print $fh $resph;
                print $fh "\015\012\015\012";
                print $fh $respb;
            }

            # close connection
            WebDone($fh);
        }
    } else {

        # connection closed
        WebDone($fh);
    }
}

sub WebDone {
    my $fh = shift;
    delete $SocketCalls{$fh};
    delete $WebCon{$fh};
    delete $StatCon{$fh};
    $readable->remove($fh);
    $writable->remove($fh);
    $fh->close;
}

# done with a file handle -- close him and his friend(s)
sub done {
  my $fh=shift;
  d('done');

  $Con{$Con{$fh}->{forwardSpam}}->{gotAllText} = 1 if $Con{$fh}->{forwardSpam} && exists $Con{$Con{$fh}->{forwardSpam}};
  $Con{$Con{$Con{$fh}->{friend}}->{forwardSpam}}->{gotAllText} = 1 if $Con{$Con{$fh}->{friend}}->{forwardSpam} && exists $Con{$Con{$Con{$fh}->{friend}}->{forwardSpam}};
  done2($Con{$fh}->{friend}) if $Con{$fh}->{friend};
  done2($fh);
}

# close a file handle & clean up associated records
sub done2 {
    my $fh = shift;
    d('done2');
    return unless $fh;
    my $ip=$Con{$fh}->{ip};
    $Con{$fh}->{prepend} = "[DONE]";
    if ($ip &&
            $ConnectionLog &&
            !(matchIP($ip,'noLoggingIPs',0,1)) &&
            (($Con{$fh}->{movedtossl} && "$fh" =~/SSL/io) or (!$Con{$fh}->{movedtossl})))
        {
            $Con{$fh}->{writtenDataToFriend} -= 6;
            $Con{$fh}->{writtenDataToFriend} = 0 if $Con{$fh}->{writtenDataToFriend} < 0;
            my $sz = max($Con{$fh}->{spambuf},$Con{$fh}->{mailloglength});
            $sz = $Con{$fh}->{maillength} unless $sz;
            mlog(0, 'finished message - received DATA size: ' . &formatNumDataSize($sz) . ' - sent DATA size: ' . &formatNumDataSize($Con{$fh}->{writtenDataToFriend}))
                if ($Con{$fh}->{maillength} > 3);
            my $tmpTimeNow = time();
            my $tmpDuration = $tmpTimeNow - $Con{$fh}->{timestart};
            mlog($fh, "disconnected ($tmpDuration seconds)" ) if $Con{$fh}->{timestart} ;
            mlog($fh, "disconnected " ) if !$Con{$fh}->{timestart} ;

            $SMTPSession{$ip}--   if $SMTPSession{$ip};

            delete $SMTPSession{$ip} if ($SMTPSession{$ip} <= 0);
    }
    d("closing $fh");

    # close the maillog if it's still open
    d('closing maillogfh');
    my $f = $Con{$fh}->{maillogfh};
    eval { close $f; } if $f;

    # remove from the select structure
    delete $SocketCalls{$fh};
    $readable->remove($fh);
    $writable->remove($fh);

    d("closing $fh $ip");
    # close it
    if ("$fh" =~ /SSL/io) {

        eval{close($fh);};
        if ($@) {
                mlog(0,"warning: unable to close $fh - $@");
                eval{IO::Socket::SSL::kill_socket($fh)};
                if ($@) {
                    mlog(0,"warning: unable to kill $fh - $@");
                }
        }

    } else {
        eval{close($fh) if fileno($fh);};
    }

    d('delete the Connection data');
    # delete the Connection data
    delete $Con{$fh};

    d('delete the Session data');
    # delete the Session data & re-add sockets.
    if ( exists $SMTPSession{$fh} ) {
        delete $SMTPSession{$fh};

        foreach my $lfh (@lsn)            { $readable->add($lfh) if !$readable->exists($lfh) };
        foreach my $lfh (@lsn2)           { $readable->add($lfh) if !$readable->exists($lfh) };
        foreach my $lfh (@lsnSSL)         { $readable->add($lfh) if !$readable->exists($lfh) };
        foreach my $lfh (@lsnRelay)       { $readable->add($lfh) if !$readable->exists($lfh) };
        $SMTPSession{Total}-- if $SMTPSession{Total} > 0;
        delete $SMTPSession{$ip} if ($SMTPSession{$ip} <= 0);

    }
    $SMTPSession{Total} = 0 if $SMTPSession{Total} < 0;
    $smtpConcurrentSessions = 0;
    map {$smtpConcurrentSessions++ if ($Con{$_}->{ip} || $Con{$_}->{type} eq 'C');} keys %Con;
    if ($smtpConcurrentSessions == 0) {
        %SMTPSession = ();
        $SMTPSession{Total} = 0;
    }
    d('finished closing connection');
}

# adding a socket to the Select structure and Con hash
sub addfh {
    my ( $fh, $getline, $friend ) = @_;
    d('addfh');
    $SocketCalls{$fh} = \&SMTPTraffic;
    $readable->add($fh);
    binmode($fh);
    $Con{$fh} = {};
    my $this = $Con{$fh};
    $this->{getline}   = $getline;
    $this->{friend}    = $friend;
    $this->{timestart} = time();
    $this->{timelast}  = time();
}

sub sayMessageOK {
    my ( $fh, $prepend ) = @_;
    $prepend |= "[MessageOK]";
    my $this = $Con{$fh};
    d('sayMessageOK');
    return if $this->{sayMessageOK} eq 'already';
    return if $this->{deleteMailLog};
    return unless $this->{sayMessageOK};
    &makeSubject($fh);
    ccMail($fh,$this->{mailfrom},$sendHamInbound,\$this->{header},$this->{rcpt}) if !$this->{spamfound};
    pbBlackDelete($this->{ip}) if !$this->{spamfound};    

    $this->{prepend} = "[MessageOK]" if !$this->{spamfound};
    $this->{prepend} = "[WhitelistedOK]" if $this->{sayMessageOK} =~ /whitelist/i;
    $this->{prepend} = "[NoprocessingOK]" if $this->{sayMessageOK} =~ /processing/i;
    $this->{prepend} = "[LocalOK]" if  $this->{relayok};

    mlog($fh,"$this->{sayMessageOK}", 0, 2 );

    $this->{sayMessageOK} = 'already';

}
# adding a SSL socket to the Select structure and Con hash
sub addsslfh {
  my ($oldfh,$sslfh,$friend) =@_;
  $SocketCalls{$sslfh}=$SocketCalls{$oldfh};
  $sslfh->blocking(0);
  binmode($sslfh);
  %{$Con{$sslfh}} = %{$Con{$oldfh}};
  $Con{$sslfh}->{friend} = $friend;
  $Con{$sslfh}->{self} = $sslfh;
  $Con{$sslfh}->{oldfh} = $oldfh;
  if ($Con{$sslfh}->{type} eq 'C') {
    $Con{$sslfh}->{client}   = $sslfh;
    $Con{$sslfh}->{server}   = $friend;
    $Con{$sslfh}->{myheaderCon} .= "X-Assp-Client-TLS: yes\r\n";
    $Stats{smtpConnTLS}++ unless $Con{$sslfh}->{relayok};
  } else {
    $Con{$friend}->{myheaderCon} .= "X-Assp-Server-TLS: yes\r\n";
  }
  &dopoll($sslfh,$readable,"POLLIN");
  &dopoll($sslfh,$writable,"POLLOUT");
  $Con{$oldfh}->{movedtossl} = 1;
  my $fno = $Con{$oldfh}->{fno} ;
  if (exists $ConFno{$fno}) {delete $ConFno{$fno};}
  delete $Fileno{$fno} if (exists $Fileno{$fno});
  $Con{$sslfh}->{fno} = fileno($sslfh);
  $Fileno{$Con{$sslfh}->{fno}} = $sslfh;
  d("info: switched connection from $oldfh to $sslfh");
}
# sendque enques a string for a socket
sub sendque {
    my ( $fh, $message ) = @_;
    my $outmessage = ref($message) ? $message : \$message;
    my $l=length($$outmessage);

    d("sendque: $fh $Con{$fh}->{ip} l=$l");
    return unless $fh && exists $Con{$fh};

    if (   $Con{$fh}->{type} eq 'C'       # is a client SMTP connection?
        && ($replyLogging == 2 or ($replyLogging == 1 && $$outmessage =~ /^[45]/o))
        && $$outmessage =~ /^[1-5]\d\d\s+[^\r\n]+\r\n$/o)    # is a reply?
    {
        my $what = 'Reply';
        $$outmessage =~ s/SESSIONID/$Con{$fh}->{msgtime}/go;
        $$outmessage =~ s/MYNAME/$myName/go;
        if ($$outmessage =~ /^([45])/o) {
            $what = ($1 == 5) ? 'Error' : 'Status';
        }
        my $reply = $$outmessage;
        $reply =~ s/\r?\n//o;
        mlog( $fh, "[SMTP $what] $reply", 1, 1 );
    }

    $writable->add($fh);
    $Con{$fh}->{outgoing} .= $$outmessage;
    if ( !$Con{$fh}->{paused}
        && length( $Con{$fh}->{outgoing} ) > $OutgoingBufSizeNew )
    {
        $Con{$fh}->{paused} = 1;
        d("pausing");
        $readable->remove( $Con{$fh}->{friend} );
    }
}
sub dopoll {
   my ($fh,$action,$mask) = @_ ;
   my $fno;
   $fh = $Con{$fh}->{self} if exists $Con{$fh} && $Con{$fh}->{self};
   $fh = $WebConH{$fh} if $WebConH{$fh};
   $fh = $StatConH{$fh} if $StatConH{$fh};
   if ($IOEngineRun == 0) {
       $fno = fileno($fh);
       eval{$action->mask($fh => $mask);};
       if ($@) {
           if (exists $WebConH{$fh} or exists $StatConH{$fh}) {
               &WebDone($fh);
           } else {
               done($fh);
           }
       } else {
           $action->[3]{$fh} = $fno if $fno;
       }
   } else {
       $action->add($fh);
   }
}

sub unpoll {
   my ($fh,$action) = @_ ;
   $fh = $Con{$fh}->{self} if $Con{$fh}->{self};
   if ($IOEngineRun == 0) {
       $fh = $Con{$fh}->{self} if $Con{$fh}->{self};
       $fh = $WebConH{$fh} if $WebConH{$fh};
       $fh = $StatConH{$fh} if $StatConH{$fh};

       eval{$action->mask($fh => 0);};

       if ($ConTimeOutDebug) {
           my $m = &timestring();
             my ($package, $file, $line) = caller;
           if ($Con{$fh}->{type} eq 'C'){
               $Con{$fh}->{contimeoutdebug} .= "$m client unpoll from $package $file $line\n" ;
           } else {
               $Con{$Con{$fh}->{friend}}->{contimeoutdebug} .= "$m server unpoll from $package $file $line\n" ;
           }
       }
       if (my $fno = $action->[3]{$fh}) {         # poll fd workaround
           delete $action->[3]{$fh};
           delete $action->[0]{$fno}{$fh};
           unless (%{$action->[0]{$fno}}) {
               delete $action->[0]{$fno};
               delete $action->[1]{$fno};
               delete $action->[2]{$fh};
           }
       }
   } else {
       if ($ConTimeOutDebug) {
           my $m = &timestring();
             my ($package, $file, $line) = caller;
           if ($Con{$fh}->{type} eq 'C'){
               $Con{$fh}->{contimeoutdebug} .= "$m client unselect from $package $file $line\n" ;
           } else {
               $Con{$Con{$fh}->{friend}}->{contimeoutdebug} .= "$m server ununselect from $package $file $line\n" ;
           }
       }
       $action->remove($fh);
   }
}

sub sigOK {
  my ($fh,$m,$done)=@_;
  my $this=$Con{$fh};
  my $server = $this->{friend};
  if (! $this->{addMSGIDsigDone} && $this->{relayok} && $DoMSGIDsig) { # add the MSGID Tag
    d('sigOK');
    if ($m =~ /(Message-ID\:[\r\n\s]*\<[^\r\n]+\>)/i) {       # if not already done
      my $msgid = $1;
      my $tag = MSGIDaddSig($fh,$msgid);
      if ($msgid ne $tag ) {
          $m =~ s/\Q$msgid\E/$tag/i;
          $this->{header} =~ s/\Q$msgid\E/$tag/i;
          $this->{maillength} = length($this->{header});
          $this->{addMSGIDsigDone};
      }
    }
  }
}

sub is_7bit_clean {
    return $_[0] !~ /[^\x20-\x7E\x0A\x0D]/os;
}

#####################################################################################
#                SMTP stuff

# compile the regular expression (RE) for the local domains list (LDRE)
sub setLDRE {
    SetRE( 'LDRE', "^($_[0])\$", "i", "Local Domains" );
}

# compile the regular expression (RE) for the vrfy domains list (VDRE)
sub setVDRE {
    SetRE( 'VDRE', "^($_[0])\$", "i", "VRFY Domains" );
}

# returns true if this address is local (any local domain)
sub localmail {
  my $h = shift;
  d("localmail - $h",1);
  return 0 unless $h;
  $h = $1 if $h=~/\@([^@]*)/o;
  return &localdomains($h);
}

# returns true if this address is in localdomains file or localDomains or LDAP
sub localdomains {
    my $h = shift;
    d("localdomains - $h");
    $h =~ tr/A-Z/a-z/;
    my $hat; $hat = $1 if $h =~ /(\@[^@]*)/o;
    $h = $1 if $h =~ /\@([^@]*)/o;

    return 1 if $h eq "assp.local";
    return 1 if $h eq "assp-nospam.org";

    my ($EBRD) = $EmailBlockReportDomain =~ /^\@*([^@]*)$/o;
    return 1 if ($EBRD && lc($h) eq lc($EBRD));

    return 1 if $localDomains && ( ($hat && $hat =~ /$LDRE/) || ($h && $h =~ /$LDRE/) );
    if ($localDomainsFile) {
        &check4update('localDomainsFile');
        return 1 if $localDomainsFile{$h};
    }

    return &localLDAPdomain($h);
}
# returns true if this address is in localdomains file or localDomains or LDAP

sub localLDAPdomain {
  my $h = shift;
  d("localLDAPdomain - $h");
  $h =~ tr/A-Z/a-z/;
  return 1 if &LDAPCacheFind('@'.$h,'LDAP',1);
  return 0 unless $CanUseLDAP;
  return 0 unless $ldLDAP;
  my $ldapflt = $ldLDAPFilter;
  $ldapflt =~ s/DOMAIN/$h/go;
  my $ldaproot = $ldLDAPRoot || $LDAPRoot;
  $ldaproot =~ s/DOMAIN/$h/go;
  return LDAPQuery($ldapflt, $ldaproot,$h);
}

sub localvrfy2MTA {
  my ($fh,$h) = @_;
  d("localvrfy2MTA - $h");
  return 0 unless $DoVRFY;
  my $this;
  $this = $Con{$fh} if $fh;
  my $smtp;
  my $vrfy;
  my $expn;
  my $domain;
  my $MTA;
  my $forceRCPTTO;
  my $canvrfy;
  my $canexpn;
  $this->{prepend} = "[VRFY]";

  return 1 if &LDAPCacheFind($h,'VRFY');
  if (my $nf = $LDAPNotFound{$h}) {
      return 0 if (time - $nf < 300);
      delete $LDAPNotFound{$h};
  }

  $domain = $1 if $h=~/\@([^@]*)/o;
  return 0 unless $domain;

  my $MTAList = &matchHashKey('DomainVRFYMTA',$domain);
  return 0 unless $MTAList;

  my $timeout = $VRFYQueryTimeOut ? $VRFYQueryTimeOut : 5;
    eval{
    for my $MTA (split(/,/,$MTAList)) {
      eval{
      $smtp = Net::SMTP->new($MTA,
                        Hello => $myName,
                        Timeout => $timeout);
      } or next;
      if ($smtp) {
          $forceRCPTTO = ($VRFYforceRCPTTO && $MTA =~ /$VFRTRE/) ? 1 : 0;
          if (! $forceRCPTTO) {
              $canvrfy = exists ${*$smtp}{'net_smtp_esmtp'}->{'VRFY'};   # was VRFY in EHLO Answer?
              $canexpn = exists ${*$smtp}{'net_smtp_esmtp'}->{'EXPN'};   # was EXPN in EHLO Answer?
              if (!$canvrfy && !$canexpn &&   # there was no VRFY or EXPN in the EHLO Answer, or HELO was used
                  (exists ${*$smtp}{'net_smtp_esmtp'}->{'HELP'} or    # we can use HELP      or
                   ! exists ${*$smtp}{'net_smtp_esmtp'}) )            # only HELO was used - try HELP
              {
                      my $help = $smtp->help();
                      $canvrfy = $help =~ /VRFY/io;
                      $canexpn = $help =~ /EXPN/io;
              }
              if ($canvrfy) {$vrfy = $smtp->verify($h) ? 1 : $smtp->verify("\"$h\"");}
              if ($canexpn && ! $vrfy) {$expn = scalar($smtp->expand($h)) ? 1 : scalar($smtp->expand("\"$h\""));}
          } else {
              mlog($fh,"info: using RCPT TO: (skipped VRFY) for $h") if ($VRFYLog >= 2);
          }
          if (!$canvrfy && !$canexpn) {    # VRFY and EXPN are both not supported or VRFYforceRCPTTO is set for this MTA
              mlog($fh,"info: host $MTA does not support VRFY and EXPN (tried EHLO and HELP) - now using RCPT TO to verify $h") if ($VRFYLog >= 2 && ! $forceRCPTTO);
              if ($smtp->mail('postmaster@'.$myName)) {
                  $vrfy = $smtp->to($h);
              } else {
                  mlog($fh,"info: host $MTA does not accept 'mail from:postmaster\@$myName'") if $VRFYLog;
              }
          }
          $smtp->quit;
      }
      last if ($vrfy || $expn);
    }
  };
  if ($@ or ! $smtp) {
     $vrfy = 0 ;
     $expn = 0 ;
     my $not =  $VRFYFail ? ' not' : '';
     if ($@){
         mlog($fh,"error: VRFY / RCPT TO failed on host $MTAList - address <$h>$not accepted - $@");
     } else {
         mlog($fh,"error: VRFY / RCPT TO failed on host $MTAList - address <$h>$not accepted");
     }

     $this->{userTempFail} = ! $VRFYFail if $this;

     return ! $VRFYFail;

  }

  if ($vrfy || $expn) {
     if ($ldaplistdb && $MaxLDAPlistDays) {
         $LDAPlist{$h}=time." 1";
         mlog($fh,"VRFY added $h to VRFY-/LDAPlist") if $VRFYLog ;
         d("VRFY added $h to VRFY-/LDAPcache");
     }
     delete $LDAPNotFound{$h};
     mlog($fh,"info: VRFY found $h") if $VRFYLog >= 2;
     return 1 ;
  } else {
     $LDAPNotFound{$h} = time if $MaxLDAPlistDays;
  }
  mlog($fh,"info: VRFY was unable to find $h") if $VRFYLog >= 2;
  return 0 ;

}

sub localmailaddress {
  my ($fh,$current_email) = @_;
  d("localmailaddress - $current_email");
  $current_email = &batv_remove_tag($fh,$current_email,'');
  $current_email =~ tr/A-Z/a-z/;
  my $at_position = index($current_email, '@');
  my $current_username = substr($current_email, 0, $at_position);
  my $current_domain = substr($current_email, $at_position + 1);
  my $ldapflt = $LDAPFilter;
  $ldapflt =~ s/EMAILADDRESS/$current_email/go;

  $ldapflt =~ s/USERNAME/$current_username/go;
  $ldapflt =~ s/DOMAIN/$current_domain/go;
  my $ldaproot = $LDAPRoot;
  $ldaproot =~ s/DOMAIN/$current_domain/go;
  if ( $LocalAddresses_Flat && $LocalAddresses_Flat_Domains
                && $current_email =~ /^([^@]*@)(.*)$/o
                && matchSL( $2, 'LocalAddresses_Flat' ) ) 
        { 

          return 1;
  }
  if ( $LocalAddresses_Flat && matchSL( $current_email, 'LocalAddresses_Flat' ) ) {
      return 1;
  }
  if (&LDAPCacheFind($current_email,'LDAP')) {
      $LDAPlist{'@'.$current_domain} = time if $ldaplistdb && $ldLDAPFilter;
      return 1;
  }
  if($DoLDAP && $CanUseLDAP && LDAPQuery($ldapflt, $ldaproot,$current_email)) {
      $LDAPlist{'@'.$current_domain} = time if (!$LDAPoffline && $ldaplistdb && $ldLDAPFilter);
      return 1;
  }
  if($DoVRFY && (&matchHashKey('FlatVRFYMTA',"\@$current_domain") or &matchHashKey('DomainVRFYMTA',$current_domain))
             && $CanUseNetSMTP
             && $current_email =~ /[^@]+\@[^@]+/o
             && localvrfy2MTA($fh,$current_email))
  {
      $LDAPlist{'@'.$current_domain} = time if (! ($fh && $Con{$fh}->{userTempFail}) && $ldaplistdb);
      return 1;
  }
  return 0;
}

sub LDAPCacheFind {
  my ($current_email,$how, $nolog) = @_;
  d("LDAPCacheFind - $current_email , $how");
  return 0 unless $ldaplistdb;
  return 0 unless $MaxLDAPlistDays;
  $current_email = lc $current_email;
  if (exists $LDAPlist{$current_email}) {
    mlog(0,"$how - found $current_email in LDAPlist") if (${$how.'Log'} >=2);
    d("$how - found $current_email in LDAP-cache");
    my ($vt,$vl) = split(/ /o,$LDAPlist{$current_email});
    if ($vl) {
      $LDAPlist{$current_email}=time." $vl";
    } else {
      $LDAPlist{$current_email}=time;
    }
    return 1;
  }
  d("$how - not found $current_email in LDAP-cache");
  mlog(0,"$how - $current_email not found in LDAPlist") if (${$how.'Log'} >= 2)  && !$nolog;
  return 0;
}

sub LDAPQuery {
    my ( $ldapflt, $ldaproot, $current_email ) = @_;
    my $retcode;
    my $retmsg;
    my $mesg;
    my $entry_count;

   d("LDAPQuery - $ldapflt, $ldaproot, $current_email");
   $current_email = &batv_remove_tag(0,lc($current_email),'');

   return 1 if &LDAPCacheFind($current_email,'LDAP');
   if (my $nf = $LDAPNotFound{$current_email}) {
      return 0 if (time - $nf < 300);
      delete $LDAPNotFound{$current_email};
   }

    d("doing LDAP lookup with $ldapflt in $ldaproot");

    my @ldaplist = split( /\|/, $LDAPHost );
    my $ldaplist = \@ldaplist;
    my $scheme = 'ldap';
    my $ldap;
    eval{
      $scheme = 'ldaps' if ($DoLDAPSSL == 1 && $AvailIOSocketSSL);
      $ldap = Net::LDAP->new( $ldaplist,
                          timeout => $LDAPtimeout,
                          scheme => $scheme,
                          inet4 =>  1,
                          inet6 =>  $CanUseIOSocketINET6
                        );
      $ldap->start_tls() if ($DoLDAPSSL == 2 && $AvailIOSocketSSL);
    };

    if ( !$ldap ) {
        $LDAPoffline=1;
        mlog( 0, "Couldn't contact LDAP server at $LDAPHost -- check ignored" );

        return !$LDAPFail;
    }

    # bind to a directory anonymous or with dn and password
    if ($LDAPLogin) {
        $mesg = $ldap->bind(
            $LDAPLogin,
            password => $LDAPPassword,
            version  => $LDAPVersion
        );
    } else {

        # mlog($fh,"LDAP anonymous bind");
        $mesg = $ldap->bind( version => $LDAPVersion );
    }
    $retcode = $mesg->code;

    my $rettext;
    if ($retcode) {

        $retmsg=$mesg->error_text();
        $rettext = "Invalid credentials" if $retcode eq "49";
        #    mlog($fh,"LDAP bind error: $retcode - Login Problem?");
        mlog( 0, "LDAP bind error: $retcode -- $retmsg -- check ignored", 1 );

        $ldap->unbind;
        $LDAPoffline=1;
        return !$LDAPFail;
    }

    # perform a search
    $mesg = $ldap->search(
        base      => $ldaproot,
        filter    => $ldapflt,
        attrs     => ['cn'],
        sizelimit => 1
    );
    $retcode = $mesg->code;

    # mlog($fh,"LDAP search: $retcode");
    if ( $retcode > 0 && $retcode != 4 ) {
        mlog( 0, "LDAP search error: $retcode -- '$ldapflt' check ignored", 1 );

        $ldap->unbind;
        $LDAPoffline=1;
        return !$LDAPFail;
    }
    $LDAPoffline = 0;
  $entry_count = $mesg->count;
  $retmsg = $mesg->entry(1);
  mlog(0,"LDAP Results $ldapflt: $entry_count : $retmsg") if $LDAPLog;
  d("got $entry_count result(s) from LDAP lookup");
  $mesg = $ldap->unbind;  # take down session
  if($entry_count) {
     if($ldaplistdb && $MaxLDAPlistDays) {
         $LDAPlist{$current_email}=time;
         mlog(0,"LDAP added $current_email to LDAPlist") if $LDAPLog;
         d("added $current_email to LDAP-cache");
     }
     delete $LDAPNotFound{$current_email};
  } else {
     $LDAPNotFound{$current_email} = time if $MaxLDAPlistDays;
  }

  return $entry_count;
}

sub LDAPcrossCheck {
  my $k;
  my $v;
  my $current_email;
  my $at_position;
  my $current_username;
  my $current_domain;
  my $ldapflt;
  my $ldaproot;
  my $retcode;
  my $retmsg;
  my @ldaplist;
  my $ldaplist;
  my $ldap;
  my $mesg;
  my $entry_count;
  my $t;
  my $timeout = $VRFYQueryTimeOut ? $VRFYQueryTimeOut : 5;
  my $forceRCPTTO;

  if(! $ldaplistdb) {
      mlog(0,"warning: unable to do crosscheck - ldaplistdb is not configured");
      return;
  }

  $t = time;

  mlog(0,"LDAP/VRFY-crosscheck started") if $MaintenanceLog;
  d("doing LDAP/VRFY-crosscheck");

  @ldaplist = split(/\|/o,$LDAPHost);
  $ldaplist = \@ldaplist;

  if ($CanUseLDAP && $DoLDAP && @ldaplist) {
      my $scheme = 'ldap';
      my $ldap;
      eval{
      $scheme = 'ldaps' if ($DoLDAPSSL == 1 && $AvailIOSocketSSL);
      $ldap = Net::LDAP->new( $ldaplist,
                          timeout => $LDAPtimeout,
                          scheme => $scheme,
                          inet4 =>  1,
                          inet6 =>  $CanUseIOSocketINET6
                        );
      $ldap->start_tls() if ($DoLDAPSSL == 2 && $AvailIOSocketSSL);
      };

      if(! $ldap) {
        mlog(0,"Couldn't contact LDAP server at $LDAPHost -- no LDAP-crosscheck is done") if $MaintenanceLog;
      } else {
          if ($LDAPLogin) {
            $mesg = $ldap->bind($LDAPLogin, password => $LDAPPassword, version => $LDAPVersion);
          } else {
            $mesg = $ldap->bind( version => $LDAPVersion );
          }
          $retcode = $mesg->code;
          if ($retcode) {
            mlog(0,"LDAP bind error: $retcode -- no LDAP-crosscheck is done") if $MaintenanceLog;
            undef $ldap;
          }
      }
  }

  my $expire_only;
  my $count;
  while (my ($k,$v)=each(%LDAPlist)) {
    $count++;

    $entry_count = 0;
    $expire_only = 0;
    $current_email = $k;
    my ($vt,$vl) = split(/ /o,$v);
    if($vl && $k !~ /^@/o) {  # do VRFY
        if ($DoVRFY && $CanUseNetSMTP) {
            mlog(0,"info: VRFY-crosscheck on $k") if $MaintenanceLog >= 2;
            my ($domain) = $k =~ /[^@]+\@([^@]+)/o;
            my $MTA = &matchHashKey('DomainVRFYMTA',lc $domain);
            $MTA = &matchHashKey('FlatVRFYMTA',lc "\@$domain") unless $MTA;
            $expire_only = 1;
            eval{
            $expire_only = 0;
            my $vrfy;
            my $expn;
            my $smtp = Net::SMTP->new($MTA,
                                 Hello => $myName,
                                 Timeout => $timeout);

            if ($smtp) {
                $forceRCPTTO = ($VRFYforceRCPTTO && $MTA =~ /$VFRTRE/) ? 1 : 0;
                if (! $forceRCPTTO) {
                    my $help = $smtp->help();
                    my $canvrfy = $help =~ /VRFY/io;
                    my $canexpn = $help =~ /EXPN/io;
                    if ($canvrfy) {$vrfy = $smtp->verify($k) ? 1 : $smtp->verify("\"$k\"");}
                    if ($canexpn && ! $vrfy) {$expn = scalar($smtp->expand($k)) ? 1 : scalar($smtp->expand("\"$k\""));}
                }
                if (!$expn && !$vrfy) {
                    if ($smtp->mail('postmaster@'.$myName)) {
                        $vrfy = $smtp->to($k);
                    }
                }
                $smtp->quit;
                $entry_count = $vrfy || $expn;
            }
            } if $MTA;
            if ($@) {
               mlog(0,"error: VRFY failed on host $MTA - $@");
               $expire_only = 1;
            }
        } else {
            $expire_only = 2;
        }
    } elsif ($ldap && $k !~ /^@/o) {   # do LDAP for addresses not for domains
        $expire_only = 0;
        mlog(0,"info: LDAP-crosscheck on $k") if $MaintenanceLog >= 2;
        $current_email =~ tr/A-Z/a-z/;
        $at_position = index($current_email, '@');
        $current_username = substr($current_email, 0, $at_position);
        $current_domain = substr($current_email, $at_position + 1);
        $ldapflt = $LDAPFilter;
        $ldapflt =~ s/EMAILADDRESS/$current_email/go;
        $ldapflt =~ s/USERNAME/$current_username/go;
        $ldapflt =~ s/DOMAIN/$current_domain/go;
        $ldaproot = $LDAPRoot;
        $ldaproot =~ s/DOMAIN/$current_domain/go;
# perform a search
        $mesg = $ldap->search(base   => $ldaproot,
                              filter => $ldapflt,
                              attrs => ['cn'],
                              sizelimit => 1
                              );
        $retcode = $mesg->code;
        if($retcode > 0 && $retcode != 4) {
          mlog(0,"LDAP search error: $retcode") if $MaintenanceLog;
          $expire_only = 1;
        }
        $entry_count = $expire_only ? 0 : $mesg->count;
    } else {
        $expire_only = 2;
    }

    if ($entry_count && exists $PBTrap{$k}) {
        pbTrapDelete($k);
        mlog(0,"info: TrapAddess $k removed") if $MaintenanceLog;
    }

    if (! $entry_count && ! $expire_only) { # entry was not found on LDAP/VRFY-server -> delete the cache entry
       delete($LDAPlist{$k});
       mlog(0,"LDAP/VRFY-crosscheck: $k not found and removed from LDAPlist") if $MaintenanceLog;
       d("LDAP/VRFY-crosscheck: $k removed from LDAPlist - Results $ldapflt: $entry_count : $retmsg");
    } elsif ($expire_only == 1 && $MaxLDAPlistDays && $vt + $MaxLDAPlistDays * 24 * 3600 < $t) { # entry is to old -> delete the cache entry
       delete($LDAPlist{$k});
       mlog(0,"LDAP/VRFY-crosscheck: $k removed from LDAPlist - entry is older than $MaxLDAPlistDays days") if $MaintenanceLog;
       d("LDAP/VRFY-crosscheck: $k removed from LDAPlist - entry is older than $MaxLDAPlistDays days");
    } elsif ($ldLDAPFilter && $expire_only == 2) {
       delete($LDAPlist{$k});
       mlog(0,"LDAP-crosscheck: $k domain entry removed from LDAPlist") if $MaintenanceLog;
       d("LDAP-crosscheck: $k domain removed from LDAPlist");
    }
  }
  $mesg = $ldap->unbind if $ldap;  # take down session
  mlog(0,"LDAP/VRFY-crosscheck finished") if $MaintenanceLog;
  &SaveLDAPlist();
}

sub serverIsSmtpDestination {
  my $server=shift;
  d('serverIsSmtpDestination');
  my $peeraddr=$server->peerhost().':'.$server->peerport();
  my $destination;
  foreach my $destinationA (split(/\|/o, $smtpDestination)) {
      if ($destinationA  =~ /^(_*INBOUND_*:)?(\d+)$/o){
          if ($crtable{$Con{$Con{$server}->{friend}}->{localip}}) {
              $destination=$crtable{$Con{$Con{$server}->{friend}}->{localip}};
          } else {
              $destination = $Con{$Con{$server}->{friend}}->{localip} .':'.$2;
          }
      } else {
          $destination = $destinationA;
      }
      return 1 if $peeraddr eq $destination || $peeraddr eq $destination.':25';
  }
  return 0;
}

sub sendNotification {
    my ($from,$to,$sub,$body,$file) = @_;
    my $text;
    if (! $from) {
        $from = 'ASSP <>';
        mlog(0,"*x*warning: 'EmailFrom' seems to be not configured - using '$from' as FROM: address");
    }
    if (! $to) {
        mlog(0,"*x*warning: TO: address not found for notification email - abort");
        return;
    }
    if (! $resendmail) {
        mlog(0,"*x*warning: 'resendmail' is not configured - abort notification");
        return;
    }
    my $date=$UseLocalTime ? localtime() : gmtime();
    my $tz=$UseLocalTime ? tzStr() : '+0000';
    $date=~s/(\w+) +(\w+) +(\d+) +(\S+) +(\d+)/$1, $3 $2 $5 $4/o;
    $text = "Date: $date $tz\r\n";
    $text .= "X-Assp-Notification: YES\r\n";
    $from =~ s/^\s+//o;
    $from =~ s/\s+$//o;
    if ($from !~ /\</o) {
        $text .= "From: <$from>\r\nTo:";
    } else {
        my ($t,$m) = split(/</o, $from);
        $m = '<' . $m;
        $t =~ s/^\s+//o;
        $t =~ s/\s+$//o;
        $t = encodeMimeWord($t,'Q','UTF-8') . ' ' if $t;
        $text .= "From: $t$m\r\nTo:";
    }
    foreach (split(/,|\|/o, $to)) {
        s/^\s+//o;
        s/\s+$//o;
        if ($_ !~ /\</o) {
            $text .= " <$_>,";
        } else {
            my ($t,$m) = split(/</o, $_);
            $m = '<' . $m;
            $t =~ s/^\s+//o;
            $t =~ s/\s+$//o;
            $t = encodeMimeWord($t,'B','UTF-8') . ' ' if $t;
            $text .= " $t$m,";
        }
    }
    chop $text;
    $text .= "\r\n";
    $sub = encodeMimeWord($sub,'B','UTF-8');
    $text .= "Subject: $sub\r\n";
    $text .= "MIME-Version: 1.0\r\n";
    $text .= "Content-Type: text/plain; charset=\"UTF-8\"\r\n";
    $text .= "Content-Transfer-Encoding: quoted-printable\r\n";
    my $msgid = sprintf("%06d",$NotifyCount++) . int(rand(100));
    $text .= "Message-ID: a$msgid\@$myName\r\n";
    $text = headerWrap($text);
    $text .= "\r\n";           # end header
    my $sendbody;
    foreach (split(/\r?\n/o,$body)) {
        $sendbody .= ( $_ ? assp_encode_Q(Encode::encode('UTF-8',$_)) : '') . "\r\n";
    }
    my $f;
    if ($file && -e $file && (open($f,"<",$file))) {
        while (<$f>) {
             s/\r?\n$//o;
             $sendbody .= ( $_ ? assp_encode_Q(Encode::encode('UTF-8',$_)) : '') . "\r\n";
        }
        close $f;
    }
    $text .= $sendbody;
    my $rfile = "$base/$resendmail/n$msgid$maillogExt";
    if (open($f,">",$rfile)) {
        binmode $f;
        print $f $text;
        close $f;
        mlog(0,"*x*info: notification message queued to sent to $to") if $MaintenanceLog;
        $nextResendMail = $nextResendMail < time + 3 ? $nextResendMail : time + 3;
    } else {
        mlog(0,"*x*error: unable to write notify message to file $f - $!");
    }
}

# resend the files in Directory $resendmail
# leading '*x*' for mlog is used to prevent notification loops
# '*x*' is removed in sub mlog
sub resend_mail {
  return unless($resendmail);
  return unless($CanUseEMS);
  opendir(my $DMAIL,"$base/$resendmail");
  my @filelist;
  my $result;
  my @list = readdir($DMAIL);
  close $DMAIL;
  while ( my $file = shift @list) {
      next if -d "$base/$resendmail/$file";
      next if ($file !~ /$maillogExt$/i);
      push(@filelist, "$base/$resendmail/$file");
  }
  return unless(@filelist);
  while ( my $file  = shift @filelist) {
      my $hostCFGname;
      my $message = "\r\n";
      mlog(0,"*x*(re)send - try to open: $file") if $MaintenanceLog >= 2;
      next unless(open my $FMAIL,'<',"$file");
      while (<$FMAIL>) {
          s/\r?\n//go;
          $message .= "$_\r\n";
      }
      close $FMAIL;
      $message =~ s/[\r?\n]\.[\r?\n]+$/\r\n/so;
      my $count = exists $ResendFile{$file} ? "(try $ResendFile{$file}" : "(first time)";
      mlog(0,"*x*(re)send - process: $file $count") if $MaintenanceLog >= 2;
      my ($howF, $mailfrom);
      ($howF, $mailfrom) = ($1,$2)
        if ($message =~ /\n(X-Assp-Envelope-From:)[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?\s*\r?\n/sio);
      ($howF, $mailfrom) = ($1,$2)
        if (! $mailfrom && $message =~ /\n(from:)[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?\s*\r?\n/sio);
      if (! $mailfrom) {
          ($howF, $mailfrom) = ($1,$2)
             if ($message =~ s/\n(from:)\s*(ASSP <>)\s*\r?\n/\n/sio);
          if (! $mailfrom) {
              mlog(0,"*x*(re)send - $file - From: and X-Assp-Envelope-From: headertag not found");
              $message = "# (re)send - $file - From: and X-Assp-Envelope-From: headertag not found\r\n".$message;
              &resendError($file,\$message);
              next;
          }
      }

      my ($howT, $to);
      ($howT, $to) = ($1,$2)
        if ($message =~ /\n(X-Assp-Intended-For:)[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio);
      ($howT, $to) = ($1,$2)
        if (! $to && $message =~ /\n(to:)[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio);
      if (! $to) {
          mlog(0,"*x*(re)send - $file - To: and X-Assp-Intended-For: headertag not found - skip file");
          $message = "# (re)send - $file - To: and X-Assp-Intended-For: headertag not found - skip file\r\n".$message;
          &resendError($file,\$message);
          next;
      }
      if (lc $howT eq lc "X-Assp-Intended-For:") {
          $message =~ s/\nto:[^\<]*?<?$EmailAdrRe\@$EmailDomainRe>?\s*\r?\n/\n/sio;
          $message =~ s/X-Assp-Intended-For:[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?\s*\r?\n/To: <$1>\r\n/sio;
      }

      my $islocal = localmail($to);
      if ($islocal && $ReplaceRecpt) {
            my ($mf) = $mailfrom =~ /($EmailAdrRe\@$EmailDomainRe)/o;
            my $newadr = RcptReplace($to,$mf,'RecRepRegex');
            if (lc $newadr ne lc $to) {
                $message =~ s/(\nto:[^\<]*?<?)$to(>?)/$1$newadr$2/is;
                mlog(0,"*x*(re)send - recipient $to replaced with $newadr");
            }
      }

      $message =~ s/^\r?\n//o;
      $message =~ s/(?:ReturnReceipt|Return-Receipt-To|Disposition-Notification-To):$HeaderValueRe//gios
            if ($removeDispositionNotification);

      mlog(0,"*x*(re)send - $file - $howF $mailfrom - $howT $to") if $MaintenanceLog >= 2;

      my $host = $smtpDestination;
      $hostCFGname = 'smtpDestination';
      if ($EmailReportDestination &&
          $islocal &&
          (($EmailFrom && $EmailFrom =~ /^$mailfrom$/i) || lc $mailfrom eq 'assp <>')
         )
      {
          mlog(0,"*x*(re)send - $file - using EmailReportDestination for local mail - From: $mailfrom - To: $to")
              if $MaintenanceLog >= 2;
          $host = $EmailReportDestination;
          $hostCFGname = 'EmailReportDestination';
      }

      if ($islocal && (my @bccRCPT = $message =~ /\nbcc:($HeaderValueRe)/igso)) {
          foreach my $bcc (@bccRCPT) {
              while ($bcc =~ /($EmailAdrRe\@$EmailDomainRe)/igos) {
                  my $addr = $1;
                  if ($ReplaceRecpt) {
                      my ($mf) = $mailfrom =~ /($EmailAdrRe\@$EmailDomainRe)/o;
                      my $newadr = RcptReplace($bcc,$mf,'RecRepRegex');
                      $newadr = '' if ! localmail($newadr);
                      if (lc $newadr ne lc $addr) {
                          $message =~ s/(\nbcc:(?:$HeaderValueRe)*?)$addr/$1$newadr/is;
                          mlog(0,"*x*(re)send - BCC - recipient $addr replaced with $newadr");
                      }
                  }
              }
          }
          $message =~ s/\nbcc:[\r\n\s]+($HeaderNameRe:)?/\n$1/iogs;
      }

      if (! $islocal && $relayHost) {
          mlog(0,"*x*(re)send - $file - using relayHost for not local mail - From: $mailfrom - To: $to")
              if $MaintenanceLog >= 2;
          $host = $relayHost;
          $hostCFGname = 'relayHost';
          my $t = time;
          $Con{$t} = {};
          $Con{$t}->{relayok} = 1;
          $Con{$t}->{mailfrom} = $mailfrom;
          $Con{$t}->{rcpt} = $to;
          $Con{$t}->{header} = $message;
          if ($DoMSGIDsig) {
              if ($message =~ /(Message-ID\:[\r\n\s]*\<[^\r\n]+\>)/io) {
                  my $msgid = $1;
                  my $tag = MSGIDaddSig($t,$msgid);
                  if ($msgid ne $tag ) {
                      $message =~ s/\Q$msgid\E/$tag/i;
                  }
              }
          }

          delete $Con{$t};
      }
      my $localip;
      if ( $islocal && $host eq $smtpDestination && $message =~ /X-Assp-Intended-For-IP: ([^\r\n]+)\r\n/o) {
          $localip = $1;
      }
      if (! $host) {
          mlog(0,"*x*(re)send - $file - no SMTP destination found in config - skip file - From: $mailfrom - To: $to");
          $message = "# (re)send - $file - no SMTP destination found in config - skip file - From: $mailfrom - To: $to\r\n".$message;
          &resendError($file,\$message);
          next;
      }
      my $AVa = 0;
      my $reason;
      foreach my $destinationA (split(/\|/o, $host)) {
          if ($destinationA =~ /^(_*INBOUND_*:)?(\d+)$/o){
              $localip = '127.0.0.1' if !$localip or $localip eq '0.0.0.0';
              if ($crtable{$localip}) {
                  $destinationA=$crtable{$localip};
              } else {
                  $destinationA = $localip .':'.$2;
              }
          }
          if ($AVa<1) {
              mlog(0,"*x*(re)send $file to host: $destinationA ($hostCFGname)") if $MaintenanceLog >= 2;
              eval {
                  my %auth = ($hostCFGname eq 'relayHost' && $relayAuthUser && $relayAuthPass) ? (username => $relayAuthUser, password => $relayAuthPass) : ();
                  my $sender = Email::Send->new({mailer => 'SMTP'});
                  $sender->mailer_args([Host => $destinationA, Hello => $myName, tls => ($hostCFGname eq 'relayHost' && $DoTLS == 2 && ! exists $localTLSfailed{$destinationA}), %auth]);
                  eval{$result = $sender->send($message);};
                  if ($@ && $DoTLS == 2 && $@ =~ /STARTTLS: *50\d/io) {
                      $localTLSfailed{$destinationA} = time;
                      $sender = Email::Send->new({mailer => 'SMTP'});
                      $sender->mailer_args([Host => $destinationA, Hello => $myName, %auth]);
                      $result = $sender->send($message);
                  } elsif ($@) {
                      die "$@\n";
                  }
              };
              if ($@ || !$result) {
                  mlog(0,"*x*error: unable to send file $file to $destinationA ($hostCFGname) - $@") if ($@ && $MaintenanceLog);
                  $@ =~ s/\r?\n/\r\n/go;
                  $@ =~ s/[\r\n]+$//o;
                  $reason .= "# error: unable to send file $file to $destinationA ($hostCFGname) - $@\r\n" if $@;
                  mlog(0,"*x*error: unable to send file $file to $destinationA ($hostCFGname) - $result") if ($result && $MaintenanceLog);
                  $result =~ s/\r?\n/\r\n/go;
                  $result =~ s/[\r\n]+$//o;
                  $reason .= "# error: unable to send file $file to $destinationA ($hostCFGname) - $result\r\n" if $result;
                  mlog(0,"*x**** send to $destinationA ($hostCFGname) didn't work, trying others...") ;
                  $reason .= "# send to $destinationA ($hostCFGname) didn't work, trying others\r\n";
              } else {
                  mlog(0,"*x*info: successful sent file $file to $destinationA ($hostCFGname) - $result") if $MaintenanceLog;
                  $AVa = 1;
                  mlog(0,"*x*warning: unable to delete $file - $!") unless (unlink("$file"));

                  if ( $autoAddResendToWhite > 1 && $islocal && $mailfrom && lc $mailfrom ne 'assp <>' && !&localmail($mailfrom)) {
                      &Whitelist($mailfrom,undef,'add');
                      mlog( 0, "info: whitelist addition on resend via GUI or copied file: $mailfrom" )
                        if $ReportLog || $MaintenanceLog;
                  }

              }
          }
      }
      $message = $reason . $message;
      &resendError($file,\$message);
  }
  return;
}

sub resendError {
     my ($file,$message) = @_;

     if ($eF->( $file)) {
          $ResendFile{$file} = 0 if (! exists $ResendFile{$file});
          if (++$ResendFile{$file} > 10) {
              mlog(0,"*x*error: send $file aborted after $ResendFile{$file} unsuccessful tries") if $MaintenanceLog;
              delete $ResendFile{$file};
              $file =~ s/\\/\//go;
              if ($eF->( $file.'.err')) {
                  mlog(0,"*x*warning: unable to delete $file.err - $!") unless ($unlink->($file.'.err')) ;
              }
              mlog(0,"*x*warning: unable to rename $file to $file.err - $!") unless ($rename->($file,$file.'.err'));
              if ($open->(my $MF,'>',$file.'.err.modified')) {
                 $MF->binmode;
                 $MF->print($$message);
                 $MF->close;
                 mlog(0,"*x*warning: the modified content of file $file was stored in to file $file.err.modified") if $MaintenanceLog;
              }
          }
      } else {
          delete $ResendFile{$file};
      }
}

# wrap too long bodys
sub bodyWrap {
    my $cont = shift;
    my $max = shift;
    d('bodyWrap');
    my $body = substr($$cont,0,$max);
    return \$body if $body =~ /[\x7F-\xFF]/o;  # binary data
    $body =~ s/\n+[^\n]+$/\n/o;              # remove last unterminated line
    return \$body;
}

# wrap long headers
sub headerWrap {
  my $header=shift;
  d('headerWrap');
  $header=~s/(?:([^\r\n]{60,75}?;)|([^\r\n]{60,75}) ) {0,5}(?=[^\r\n]{10,})/$1$2\r\n\t/g;

  return $header;
}

# unwrap long header (in place)
sub headerUnwrap {
  $_[0]=~s/\015\012[ \t]+//g;
}

sub headerFormat {
    my $text = shift;
    $text =~ s/(?:\r*\n)+/\r\n/gos;
    return headerWrap($text) if &is_7bit_clean($text);
    my $org = $text;

    eval{
         $text = join("\r\n", map{headerWrap(MIME::Words::encode_mimewords(&decodeMimeWords2UTF8($_),('Charset' => 'UTF-8')));} split(/\r?\n/o,$text));
         $text .= "\r\n" if $text !~ /\r\n$/o;
         $text =~ s/(?:\r?\n)+/\r\n/go;
    };

    if ($@) {
       my $hint; $hint = "- **** please install the Perl module MIME::Tools (includes MIME::Words) via 'cpan install MIME::Tools' (on nix/mac) or 'ppm install MIME-Tools' (on win32)"
           if $@ =~ /Undefined subroutine \&MIME::Words::encode_mimewords/io;
       mlog(0,"warning: MIME encoding for our ASSP header lines failed - $@ $hint") if ! $IgnoreMIMEErrors;
       eval{
           $text = join("\r\n", map{headerWrap(&encodeMimeWord(&decodeMimeWords2UTF8($_),'B','UTF-8'));} split(/\r?\n/o,$text));
           $text .= "\r\n" if $text !~ /\r\n$/o;
       };
       if ($@) {
           $org .= "\r\n" if $org;
           $org =~ s/(?:\r?\n)+/\r\n/go;
           return $org;
       }
    }
    $text =~ s/\=\?UTF\-8\?Q\?\=20\?\=/ /gio;    # revert unneeded MIME-encoding of a single space ????
    $text =~ s/\=\?UTF\-8\?Q\?\?\=//gio;    # revert unneeded MIME-encoding of an empty line ????
    $text .= "\r\n" if $text;
    $text =~ s/(?:\r?\n)+/\r\n/go;
    return $text;
}

# compile the regular expression for forcing the usage of RCPT TO
sub setLHNRE {
    my @h;
    foreach my $h ( split( /\|/, $_[0] ) ) {
        push( @h, $h );
    }
    my @s;
    push( @s, 'localhost' );             # 'localhost' alias
    push( @s, '127.0.0.1' );             # loopback interface address
    push( @s, join( '|', @h ) ) if @h;
    my $s = join( '|', @s );
    $s ||= '^(?!)';                      # regexp that never matches
    SetRE( 'LHNRE', "^($s)\$", 'i', 'Local Host Names' );
}
# compile the regular expression for the local host names
sub setVFRTRE {
    my @h;
    foreach my $h ( split( /\|/, $_[0] ) ) {
        push( @h, $h );
    }
    my @s;

    push( @s, join( '|', @h ) ) if @h;
    my $s = join( '|', @s );
    $s ||= '^(?!)';                      # regexp that never matches
    SetRE( 'VFRTRE', "^($s)\$", 'i', 'RCPT TO Names' );
}
# compile the regular expression for the bounce senders addresses
sub setBSRE {
    my ( @uad, @u, @d );
    foreach my $a ( split( /\|/, $_[0] ) ) {
        if ( $a =~ /\S\@\S/ ) {
            push( @uad, $a );
        } elsif ( $a =~ /^\@/ ) {
            push( @d, $a );
        } else {
            push( @u, $a );
        }
    }
    my @s;
    push( @s, '^\s*$' );                                   # null sender address
    push( @s, '^(' . join( '|', @uad ) . ')$' ) if @uad;
    push( @s, '^(' . join( '|', @u ) . ')@' ) if @u;
    push( @s, '(' . join( '|', @d ) . ')$' ) if @d;
    my $s = join( "|", @s );
    $s = '<not a valid list>' unless $s;
    SetRE( 'BSRE', $s, 'i', "Bounce Senders" );
}

sub stateReset {
    my $fh   = shift;
    my $this = $Con{$fh};
    my $friend;
    $friend = $Con{ $this->{friend} } if ( exists $Con{ $this->{friend} } );

    MaillogClose($fh) if ( $this->{maillogfh} || $this->{maillogfilename} );
    $this->{maillogbuf} = '';    # attention - allocmem
    delete $this->{mailloglength};
    delete $this->{maillogfilename};
    delete $this->{maillogfh};
    delete $this->{deleteMailLog};
    delete $this->{maillog};

    $this->{badhistory}             = '';   
    $this->{blackdone}              = '';
    $this->{helodone}               = '';
    $this->{nonoprocessing}         = '';
    $this->{PersonalBlackDomainOK}  = '';
    $this->{senderbasedone}         = '';
    $this->{sizeok}                 = '';
    $this->{acceptall}             &= 1;     # remove the cip acceptall
    $this->{addMSGIDsigDone}        = '';
    $this->{addressedToSpamBucket}  = '';
    $this->{alllog}                 = '';
    $this->{attachcomment}          = '';
    $this->{attachdone}             = '';
    $this->{averror}                = '';
    $this->{bombdone}               = '';
    $this->{bombheaderdone}         = '';
    $this->{bspams}                 = '';
    $this->{ccnever}                = '';
    $this->{charsetsdone}           = '';
    $this->{cip}                    = '';
    $this->{ciphelo}                = '';
    delete $this->{cleanSMTPBuff};
    $this->{clamscandone}           = '';
    $this->{contentonly}            = '';
    $this->{datastart}              = 0;
    $this->{delaydone}              = '';
    $this->{delayed}                = '';
    $this->{delayqueue}             = '';
    $this->{dlslre}                 = '';
    $this->{donotdelay}             = '';
    $this->{doNotTimeout}           = '';
    $this->{externalsenderok}       = '';
    delete $this->{forwardSpam};
    $this->{from}                   = '';
    $this->{hamcopydone}            = '';
    $this->{hasmaillogname}         = '';
    $this->{header}                 = '';
    $this->{headerlength}           = 0;
    $this->{headerpassed}           = '';
    $this->{inerror}                = '';
    $this->{intemperror}            = '';
    $this->{invalidHeloOK}          = '';
    $this->{invalidSRSBounce}       = '';
    $this->{isbounce}               = '';
    $this->{ispip}                  = '';
    $this->{ismaxsize}              = '';
    $this->{localmail}              = '';
    $this->{localsenderdone}        = '';
    $this->{localuser}              = '';
    $this->{loggedIpFromTo}         = '';
    $this->{logsubject}             = '';
    $this->{maximumuniqueuri}       = '';
    $this->{maximumuri}             = '';
    $this->{maxRealSize} = $this->{maxSize} = $this->{maxRealSizeExternal} =
    $this->{maxSizeExternal}  = undef;
    $this->{msgidsigdone}     = '';
    $this->{MSGIDsigRemoved}  = '';
    $this->{messagelow}       = '';
    $this->{messagereason}    = '';
    mlog( $fh, "info: message score is set to $this->{prescore}" )
      if $this->{prescore};
    $this->{messagescore}     = $this->{prescore};
    $this->{messagescore}   ||= 0;
    $this->{messagescoredone} = '';
    $this->{myheader}         = $this->{myheaderCon};
    $this->{myheaderdone}     = '';
    $this->{nobayesian}       = '';
    $this->{nocollect}        = '';
    $this->{nodelay}          = '';
    $this->{nohelo}           = '';
    $this->{nopb}             = '';
    $this->{nopbwhite}        = '';
    $this->{noprocessing}     = '';
    $this->{noscan}           = '';
    $this->{notspamtag}       = '';
    $this->{obfuscatedip}     = '';
    $this->{obfuscateduri}    = '';
    delete $this->{orgname};
    delete $this->{orgrcpt};
    $this->{pbblack}          = '';
    $this->{pbwhite}          = '';
    $this->{prepend}          = '';
    $this->{prepends}         = '';    
    $this->{rblcache}         = '';
    $this->{rblcachedone}     = '';
    $this->{rbldone}          = '';
    $this->{rblneutral}       = '';
    $this->{received}         = '';
    $this->{rcptnoprocessing} = '';
    $this->{rcpt}             = '';
    %{ $this->{rcptlist} } = ();
    undef %{ $this->{rcptlist} };
    delete $this->{rcptlist};
    $this->{redsl}        = '';
    $this->{red}          = '';
    $this->{reportaddress}= '';
    $this->{rwlok}        = 0;
    $this->{saveprepend2} = '';
    $this->{saveprepend}  = '';
    $this->{sayMessageOK} = '';
    $this->{senderok}     = '';
    @{ $this->{senders} } = ();
    undef @{ $this->{senders} };
    delete $this->{senders};
    $this->{serverErrors}        = 0;
    $this->{spambuf}             = 0;
    $this->{spamconf}            = 0;
    $this->{spamdone}            = '';
    $this->{spamfound}           = '';
    $this->{spamfriends}         = '';
    $this->{spamfriendsdone}     = '';
    $this->{spamlover}           = 0;
    $this->{spamloverall}        = '';
    $this->{spamloversre}        = '';
    $this->{spamprob}            = 0;
    $this->{spfok}               = '';
    delete $this->{spf_result};
    $this->{StatsmsgDelayed}     = '';
    $this->{subjectsl}           = '';
    delete $this->{testmode};
    $this->{test}                = '';
    $this->{uribldone}           = '';
    $this->{uri_listed_by}       = '';
    $this->{userTempFail}        = '';
    $this->{whitelisted}         = '';
    $this->{writtenDataToFriend} = 0;
    $this->{mailfrom}            = '';
    delete $this->{noMoreQueued};
    delete $friend->{noMoreQueued} if $friend;
    $this->{IPinHeloOK}    = '';
    $this->{BlackDomainOK} = '';
    %{ $this->{NoSpoofingOK} } = ();
    delete $this->{NoSpoofingOK};
    $this->{RWLokDone}        = '';
    $this->{SPFokDone}        = '';
    $this->{MXAOK}            = '';
    $this->{originalsubject}  = '';
    $this->{subject}          = '';
    $this->{subject2}         = '';
    $this->{subject3}         = '';
    undef %{ $this->{Xheaders} };
    delete $this->{Xheaders};
    $this->{allLoveSpam}      = 0;
    $this->{allLoveBaysSpam}  = 0;
    $this->{allLoveBlSpam}    = 0;
    $this->{allLoveBoSpam}    = 0;
    $this->{allLoveHiSpam}    = 0;
    $this->{allLoveSBSpam}    = 0;
    $this->{allLovePBSpam}    = 0;
    $this->{allLovePTRSpam}   = 0;
    $this->{allLoveHlSpam}    = 0;
    $this->{allLoveRBLSpam}   = 0;
    $this->{allLoveDLSpam}    = 0;
    $this->{allLoveMXASpam}   = 0;
    $this->{allLoveURIBLSpam} = 0;
    $this->{allLoveATSpam}    = 0;    
    $this->{allwhitelist}     = 0;
    delete $this->{TLSqueue};
    delete $this->{bdata};
    delete $this->{cipdone};
    delete $this->{gotAllText};
    delete $this->{gripdone};
    delete $this->{islocalmailaddress};
    delete $this->{maillength};
    delete $this->{mimehead};
    delete $this->{msgid};
    delete $this->{mycountry};
    delete $this->{noop};
    delete $this->{numrcpt};
    delete $this->{passingreason};
    delete $this->{rcptNonexistent};
    delete $this->{rcptValidated};
    delete $this->{received_spf};
    delete $this->{rwlstatus};
    delete $this->{skipbytes};
    delete $this->{storecompletemail};
    delete $this->{syncServer};
    delete $this->{uhnoprocessing};
    delete $this->{uriblforce};
    $this->{spamMaxScore} = undef;
    $this->{XCLIENT} = $this->{saveXCLIENT} if exists $this->{saveXCLIENT};
    $this->{XFORWARD} = $this->{saveXFORWARD} if exists $this->{saveXFORWARD};
    delete $this->{reportaddr};
    $this->{SIZE}     = 0;
    $this->{mfn}      = maillogNewFileName();
    $this->{rcvdTime} = time;
    $this->{reporttype} = -1;
    $this->{msgtime}  = '';
    $this->{msgtime}  = $uniqueIDPrefix if $uniqueIDPrefix;
    my $tstamp = substr( $this->{rcvdTime}, 5, 5 );
    $this->{msgtime} .= sprintf( "%s-%05d", $tstamp, $this->{mfn} );
    $this->{fn} = $this->{msgtime};
    $this->{mailInSession}++ if $this->{lastcmd} =~ /mail from/io;
}
# dropreply
# read from server, but ignore it

sub dropreply {
    my ($fh, $l) = @_;
    my $this = $Con{$fh};
    d("dropreply: $l");
    if ($l =~ /^250 .*/) {
        $this->{getline} = \&reply;
    }
}

# a line of input has been received from the smtp client
sub getline {
    my ( $fh, $l ) = @_;
    d('getline');
    my $this   = $Con{$fh};
    my $server = $this->{friend};
    my $friend = $Con{$server};
    my $ip     = $this->{ip};
    my $reply;
    d("gl: <$l>");
    if (   ! $this->{greetingSent}
        && ! $this->{relayok}
        && &matchFH($fh,@lsnI)
        && ! matchIP($this->{ip},'whiteListedIPs',$fh)
        && ! matchIP($this->{ip},'ispip',$fh)
        && ! matchIP($this->{ip},'noPB',$fh)
        && ! matchIP($this->{ip},'noDelay',$fh)
        && ! matchIP($this->{ip},'noBlockingIPs', $fh)
        && ! matchIP($this->{ip},'noProcessingIPs',$fh)
        && ! matchIP($this->{ip},'noHelo',$fh) )
    {
      $this->{prepend} = "[EarlyTalker]";
      pbAdd($fh, $this->{ip}, $etValencePB, "EarlyTalker");
      $this->{prescore} += $etValencePB;
      my $err = "554 5.7.1 Misbehaved SMTP session (EarlyTalker)";
      my $l1 = $l;
      $l1 =~ s/\r|\n//go;
      my $emergency;
      if ($l1 =~ /$NONPRINT/o) {
         $l1 = 'non printable hex data';
         $emergency = 1;
      }
      if ($l =~ /^([^\x00-\x1F\x7F-\xFF]+)/o) {
         $this->{lastcmd} = $1;
         push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
      }
      if ($etValencePB || $emergency) {
         mlog($fh, "got '$l1' from the client before the server greeting '220 ...' was sent - rejecting connection" ) if $SessionLog;
         if ($emergency) {
             NoLoopSyswrite($fh,$err."\r\n");
             done($fh);
             return;
         } else {
             seterror( $fh, $err, 1 );
             return;
         }
      } else {
         mlog($fh, "got '$l1' from the client before the server greeting '220 ...' was sent - this misbehave is currently ignored, because 'etValencePB' is set to zero" ) if $SessionLog >= 2 && ! $this->{relayok};
         $this->{greetingSent} = 1;
      }
    } elsif (! $this->{greetingSent}) {
       $this->{greetingSent} = 1;
       mlog($fh, "client has sent data before the server greeting '220 ...' was sent - this misbehave is currently ignored for this IP" ) if $SessionLog >= 2 && ! $this->{relayok};
       mlog($fh, "client has sent data before the server greeting '220 ...' was sent - this misbehave is currently ignored, because a relayed/local connection is in use" ) if $SessionLog >= 2 && $this->{relayok};
    } else {
       $this->{greetingSent} = 1;
    }

    if ($Con{$server}->{mtaSSLfailed}) {
      sendque($fh, "451 4.7.1 Local configuration error, please try again later\r\n");
      return;
    }
    
    my $ret  = &matchIP($ip,'noTLSIP',1);
    my ( $noTLSIPip, $iplimit ) = split( / /o, $ret, 2 );
    my $ct;
    my $count;
    
    if (exists $SSLfailed{$ip}) {
      my $data = $SSLfailed{$ip};
      ($ct, $count) = split( /:/o, $data, 2 );
    }

    if ((exists $SSLfailed{$this->{ip}} && !$count) 
        or (&matchIP($this->{ip},'noTLSIP',$fh,1) && !$iplimit)
        or matchFH($fh,@lsnNoTLSI)) {
      $this->{SSLnotOK} = $this->{ip};
    }

    if ( $l =~ /^ *(helo|ehlo) .*?([^<>,;\"\'\(\)\s]+)/i ) {
        $this->{greeting} = $1;
        $this->{lastcmd} = $1;
        my $helo  = $2;
        my $helo2 = $helo;
        $helo =~ s/\s//g;
        my $w = 60;
        $this->{prepend} = "[InvalidHELO]";
        $this->{messagereason} = "invalid HELO: '$helo'";
        if ( !$helo or $helo eq '' or $helo eq ' ' or $helo =~ /^\s*$/  ) { 
                pbAdd($fh,$ip,$w,"invalidHELO") ;
                $this->{prescore} += $w;
                pbWhiteDelete( $fh , $this->{ip} );
        }

        $this->{cliSSL}=0;
        $helo =~ s/(\W)/\\\$1/g;
        $this->{helo} = $helo2;
        my $ptr;

        if (! $this->{relayok}) {
            $ptr = $this->{PTR};
            if (! $ptr && $this->{ip} !~ /(?:127\.0\.0\.1|::1)$/io) {
                $this->{PTR} = $ptr = getRRData($this->{ip},'PTR');
            }
            $this->{PTR} = $ptr = $localhostname || 'localhost' if (! $ptr && $this->{ip} =~ /(?:127\.0\.0\.1|::1)$/io);
        } elsif ($HideIP or $HideHelo) {
            $helo2 = $HideHelo if $HideHelo;
            $this->{rcvd} =~ s/\[$IPRe\]/[$HideIP]/o if $HideIP;

        } elsif ($HideIPandHelo) {
            my %fake;
            $fake{$1} = $2 while (lc $HideIPandHelo =~ /(ip|helo)\s*=\s*(\S+)/iog);
            $helo2 = $fake{helo} if exists $fake{helo};
            $this->{rcvd} =~ s/\[$IPRe\]/[$fake{ip}]/o if exists $fake{ip};
        }
        $ptr =~ s/\.$//o;
        if ($ptr) {
            $this->{rcvd}=~s/=host/$ptr/o;
        } else {
            $this->{rcvd}=~s/=host/$helo2/o;
        }
        $this->{rcvd}=~s/=\)/=$helo2\)/o;

        my $prot = ("$fh" =~ /SSL/io) ? 'SMTPS' : 'SMTP';
        $prot = 'E' . $prot if lc($this->{greeting}) eq 'ehlo';
        $this->{rcvd} =~ s/\*SMTP\*/$prot/o;
        $this->{rcvd} = &headerWrap( $this->{rcvd} );    # wrap long lines
        $l = "$this->{greeting} $localhostname\r\n" if $myHelo == 2 && $localhostname;
        $l = "$this->{greeting} $myName\r\n" if $myHelo && ($myHelo == 1 or !$localhostname);
        $l = "$this->{greeting} $this->{ip}\r\n" if $myHelo == 3;
    } elsif ( $CanUseIOSocketSSL  && !$this->{SSLnotOK} && ($l =~ /STARTTLS/io  )) {

        # write directly to $fh, bypassing buffering
        $fh->write("220 2.0.0 Ready to start TLS\r\n");

        # the value of $fh changes when converted to SSL
        my $oldfh = "" . $fh;
        $IO::Socket::SSL::DEBUG = $SSLDEBUG;
        # stop watching old filehandle
        $readable->remove($fh);
        $writable->remove($fh);

        # convert to SSL
        my $try = 4;
        my $ssl;
        my $fail = 0;
        eval{$fh->blocking(1);};
        eval{eval{($ssl,$fh) = &switchSSLClient($fh);};
            if (!$ssl || $fh !~ /IO::Socket::SSL/) {
            my $error = IO::Socket::SSL::errstr();
            mlog($oldfh, "SSL negotiation with client $ip failed: $error") if $SSLLog;
            $fail = 1;
            setSSLfailed($ip);
            $readable->add($fh);
            $writable->remove($fh);
            }
        };
        return if $fail;
        if (!$Con{$server}->{mtaSSL} && "$ssl" =~ /SSL/i) {
            mlog($oldfh, "warning: SSL to client on port $this->{localport} but no SSL to our MTA") if $SSLLog>2;
        }

        # update Received: header to show SSL
        $this->{rcvd} =~ s/( with E?SMTP[0-9]+)/$1+SSL/;

        # copy data from old $fh
        $Con{$fh}           = $Con{$oldfh};
        $Con{$fh}->{client} = $fh;
        $SMTPSession{$fh}   = $SMTPSession{$oldfh};

        # clean up old $fh
        delete $Con{$oldfh};
        delete $SocketCalls{$oldfh};
        delete $SMTPSession{$oldfh};

        # set up new $fh
        $SocketCalls{$fh} = \&SMTPTraffic;
        $readable->add($fh);

        d("SSL: $fh $Con{$fh}");
        return;
       
    } elsif($l=~/^(\s*AUTH([^\r\n]*))\r?\n/io) {
        my $ffr = $1;
        my $authmeth = $2;

        if ( ! $this->{relayok} && $this->{DisableAUTH} )
        {
            $this->{lastcmd} = 'AUTH';
            push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
            $this->{prepend} = "[unsupported_$this->{lastcmd}]";
            mlog($fh,"$this->{lastcmd} not allowed");
            if($MaxErrors && ++$this->{serverErrors} > $MaxErrors) {
                MaxErrorsFailed($fh,
                "502 $this->{lastcmd} not supported\r\n421 <$myName> closing transmission\r\n",
                "max errors (MaxErrors=$MaxErrors) exceeded -- dropping connection after $this->{lastcmd}");
                return;
            }
            sendque($fh, "502 $this->{lastcmd} not supported\r\n");
            return;
        }

        my $ip = &ipNetwork( $this->{ip}, 1);
        $AUTHErrors{$ip} = $MaxAUTHErrors + 1 if matchIP( $this->{ip}, 'denySMTPConnectionsFromAlways', $fh );
        $AUTHErrors{$ip} = $MaxAUTHErrors + 1 if matchIP( $ip, 'denySMTPConnectionsFromAlways', $fh );
        if ($MaxAUTHErrors         
        && !$this->{relayok}
        && !$this->{nopb}

        && !$this->{ispip}
        && !$this->{noprocessing}
        && !$this->{whitelisted}
        && !$this->{acceptall} 
        && $AUTHErrors{$ip} > $MaxAUTHErrors) {
            $this->{prepend} = "[MaxAUTHErrors]";
            NoLoopSyswrite($fh,"521 $myName does not accept mail - closing transmission - too many previous AUTH errors from network $ip\r\n");
            mlog($fh,"too many ($AUTHErrors{$ip}) AUTH errors from network $ip") if $ConnectionLog;
            pbAdd( $fh, $this->{ip}, 'autValencePB', 'AUTHErrors' ) if ! matchIP($this->{ip},'noPB',0,1);
            $AUTHErrors{$ip}++;
            done($fh);
            return;
        }

        if ($CanUseIOSocketSSL &&
            ! $SSLfailed{$this->{ip}} &&
            ! $this->{TLSqueue} &&
            "$server" !~ /SSL/io &&
            ! &matchIP($this->{ip},'noTLSIP',$fh,1) &&
            ! &matchFH($fh,@lsnNoTLSI)
        ) {
            NoLoopSyswrite($server,"STARTTLS\r\n");
            $friend->{getline} = \&replyTLS;
            $this->{TLSqueue} = $ffr;
            mlog($fh,"info: injected STARTTLS request to " . $server->peerhost()) if $ConnectionLog;
            return;
        }
        $authmeth =~ s/^\s+//o;
        $authmeth =~ s/\s+$//o;

        $this->{prepend} = "[Authentication]";
        if ($authmeth =~ /(plain|login)\s*(.*)/io) {
            $authmeth = lc $1;
            my $authstr = base64decode($2);
            mlog($fh,"info: authentication - $authmeth is used") if $AUTHLogUser;
            if ($authmeth eq 'plain' and $authstr) {
                ($this->{userauth}{foruser},$this->{userauth}{user},$this->{userauth}{pass}) = split(/ |\0/so,$authstr);
                $this->{userauth}{stepcount} = 0;
                $this->{userauth}{authmeth} = 'plain';
                if ($AUTHLogUser) {
                    my $tolog = "info: authentication (PLAIN) realms - foruser:$this->{userauth}{foruser}, user:$this->{userauth}{user}";
                    $tolog .= ", pass:$this->{userauth}{pass}" if $AUTHLogPWD;
                    mlog($fh,$tolog);
                }
            } elsif ($authmeth eq 'plain' and ! $authstr) {
                $this->{userauth}{stepcount} = 1;
                $this->{userauth}{authmeth} = 'plain';
            } elsif ($authmeth eq 'login' and $authstr) {
                $this->{userauth}{user} = $authstr;
                $this->{userauth}{stepcount} = 1;
                $this->{userauth}{authmeth} = 'login';
            } else {
                $this->{userauth}{stepcount} = 2;
                $this->{userauth}{authmeth} = 'login';
            }
        }
        $this->{lastcmd} = 'AUTH';
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
        $this->{doneAuthToRelay} = 1;
        sendque($server,$l);
        return;

    } elsif ($this->{userauth}{stepcount}) {
        $this->{prepend} = "[Authentication]";
        if ($this->{userauth}{authmeth} eq 'plain') {
            $this->{userauth}{stepcount} = 0;
            $l =~ /([^\r\n]*)\r\n/o;
            my $authstr = base64decode($1);
            ($this->{userauth}{foruser},$this->{userauth}{user},$this->{userauth}{pass}) = split(/ |\0/o,$authstr);
            my $authuser = $this->{userauth}{user};
            return if $authuser && !SameAUTHuserOK ($fh,$authuser );
            if ($AUTHLogUser) {
                my $tolog = "info: authentication (PLAIN) realms - foruser:$this->{userauth}{foruser}, user:$this->{userauth}{user}";
                $tolog .= ", pass:$this->{userauth}{pass}" if $AUTHLogPWD;
                mlog($fh,$tolog);
            }
            sendque($server,$l);
            return;
        } elsif ($this->{userauth}{stepcount} == 2) {
            $this->{userauth}{stepcount} = 1;
            $l =~ /([^\r\n]*)\r\n/o;
            $this->{userauth}{user} = base64decode($1);
            sendque($server,$l);
            return;
        } else {
            $this->{userauth}{stepcount} = 0;
            $l =~ /([^\r\n]*)\r\n/o;
            $this->{userauth}{pass} = base64decode($1);
            if ($AUTHLogUser) {
                my $tolog = "info: authentication (LOGIN) realms - user:$this->{userauth}{user}";
                $tolog .= ", pass:$this->{userauth}{pass}" if $AUTHLogPWD;
                mlog($fh,$tolog);
            }
            sendque($server,$l);
            return;
        }
    } elsif(&syncCanSync() && $enableCFGShare && $isShareSlave && $l=~/^ *ASSPSYNCCONFIG\s*([^\r\n]+)\r\n/o ) {
        my $pass = $1;
        $this->{prepend} = "[ASSPSYNCCONFIG]";
        mlog(0,"info: got ASSPSYNCCONFIG request from $this->{ip}") if $ConnectionLog >=2;
        $this->{lastcmd} = 'ASSPSYNCCONFIG';
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
        my @tservers = split(/\|/o, $syncServer);
        my @servers;
        my %se;
        foreach (@tservers) {
            s/\s//go;
            s/\:\d+$//o;
            if ($_ =~ /^$IPRe$/o) {
                push(@servers, $_);
                $se{$_} = $_;
                next;
            }
            my $ip = eval{inet_ntoa( scalar( gethostbyname($_) ) );};
            if ($ip) {
                push(@servers, $ip);
                $se{$ip} = $_;
                next;
            } else {
                mlog(0,"syncCFG: error - unable to resolve ip for syncServer name $_ - $@");
            }
        }
        if (! @servers or ! (@servers = grep { $this->{ip} eq $_ } @servers )) {
            NoLoopSyswrite( $fh, "502 $this->{lastcmd} not implemented $this->{ip} - @servers\r\n" );
            mlog($fh,"syncCFG: error - got 'ASSPSYNCCONFIG' command from wrong ip $this->{ip}");
            done($fh);
            return;
        }
        if (Digest::MD5::md5_base64($syncCFGPass) ne $pass) {
            NoLoopSyswrite( $fh, "500 $this->{lastcmd} wrong authentication - check you configuration\r\n" );
            mlog($fh,"syncCFG: error - got wrong password in 'ASSPSYNCCONFIG' command from $this->{ip}");
            done($fh);
            return;
        }
        done2($server);
        my $ip = $this->{ip};
        $this->{syncServer} = $se{$ip};
        $this->{getline} = \&syncRCVData;
        NoLoopSyswrite($fh,"250 OK start the config sync\r\n");
        return;
    } elsif($l=~/^ *ASSPSYNCCONFIG\s*([^\r\n]+)?\r\n/o ) {
        my $pass = $1;
        $this->{prepend} = "[ASSPSYNCCONFIG]";
        mlog(0,"info: got ASSPSYNCCONFIG request from $this->{ip}") if $ConnectionLog >=2;
        $this->{lastcmd} = 'ASSPSYNCCONFIG';
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
        if (Digest::MD5::md5_base64($syncCFGPass) ne $pass) {
            NoLoopSyswrite( $fh, "502 $this->{lastcmd} not implemented\r\n" );
            mlog($fh,"syncCFG: error - got syncCFG request, but this is not an 'isShareSlave' and got wrong password in 'ASSPSYNCCONFIG' command from $this->{ip}");
            done($fh);
            return;
        }
        NoLoopSyswrite( $fh, "500 $this->{lastcmd} - sync peer $this->{ip} is not registered on $myName or this is not an isShareSlave\r\n" );
        mlog($fh,"syncCFG: error - got 'ASSPSYNCCONFIG' command from ip $this->{ip} - the request will be ignored - check your configuration");
        done($fh);
        return;

    } elsif ($l=~/^ *($notAllowedSMTP)/io) {
        $this->{lastcmd} = $1;
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;

    } elsif ( $l =~ /mail from:\s*<?($EmailAdrRe\@$EmailDomainRe|\s*)>?/io ) {

        my $RO_e = $1;

        $RO_e = "$RO_e" . "@" . "$defaultLocalHost" if $defaultLocalHost && $RO_e !~ /\@/i;
        my $fr   = $RO_e;

        stateReset($fh); # reset everything
        $this->{lastcmd} = 'MAIL FROM';
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;

        if ( ! $this->{relayok} && $this->{DisableAUTH} && $l =~ /\sAUTH=/io )
        {
            $this->{lastcmd} = 'AUTH';
            push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
            $this->{prepend} = "[unsupported_$this->{lastcmd}]";
            mlog($fh,"$this->{lastcmd} not allowed");
            if($MaxErrors && ++$this->{serverErrors} > $MaxErrors) {
                MaxErrorsFailed($fh,
                "502 $this->{lastcmd} not supported\r\n421 <$myName> closing transmission\r\n",
                "max errors (MaxErrors=$MaxErrors) exceeded -- dropping connection after $this->{lastcmd}");
                return;
            }
            sendque($fh, "502 $this->{lastcmd} not supported\r\n");
            return;
        }

        if($EnforceAuth && &matchFH($fh,@lsn2I) && ! $this->{authenticated} && ! $this->{DisableAUTH}) {
            NoLoopSyswrite($fh,"530 5.7.0 Authentication required\r\n",0);
            $this->{prepend} = "[Authentication]";
            mlog($fh,"$fr submitted without previous AUTH - 'EnforceAuth' is set to 'ON' for 'listenPort2'" );
            done($fh);
            return;
        }

# authentication on relayserver
        if ($CanUseAuthenSASL &&
            ! $this->{doneAuthToRelay} &&
            $this->{relayok} &&
            scalar keys %{$this->{authmethods}} &&
            $relayAuthUser &&
            $relayAuthPass
           )
        {
            $this->{doneAuthToRelay} = 1;
            $this->{doneAuthToRelay} = 1;
            $this->{sendAfterAuth} = $l;
            foreach ('PLAIN','LOGIN','CRAM-MD5','DIGEST-MD5') {
                $this->{AUTHmechanism} = $_ if exists $this->{authmethods}->{$_};
            }
            $this->{AUTHmechanism} = 'PLAIN' unless $this->{AUTHmechanism};
            $this->{prepend} = "[Authentication]";
            mlog($fh,"info: starting authentication - AUTH $this->{AUTHmechanism}") if $SessionLog >= 2;
            $this->{AUTHclient} =
                Authen::SASL->new(
                                    mechanism => $this->{AUTHmechanism},
                                    callback  => {
                                    user     => $relayAuthUser,
                                    pass     => $relayAuthPass,
                                    authname => $relayAuthUser
                                },
                                debug => $ThreadDebug
                )->client_new('smtp');
            @{$this->{AUTHclient} . 'AUTHclient'} = ();
            my $str = $this->{AUTHclient}->client_start;
            push (@{$this->{AUTHclient} . 'AUTHclient'}, MIME::Base64::encode_base64($str, ''))
                 if defined $str and length $str;

            NoLoopSyswrite($server,'AUTH ' . $this->{AUTHclient}->mechanism . "\r\n");
            $friend->{getline} = \&replyAUTH;

            return;
        }
# end authentication on relayserver

        #enforce valid email address pattern

        #enforce valid email address pattern

        if ( $RO_e && $CanUseAddress && $DoRFC522Sender && !$this->{relayok}) {
            if ($RO_e && $RO_e !~ /\.($TLDSRE|local)\b/i  && $RO_e !~/$defaultLocalHost$/i ) {
               # no valid TLD

                $this->{prepend} = "[MalformedAddress]";
                mlog( $fh, "malformed address: invalid TLD in '$RO_e'"  );
                $Stats{msgverify}++;

                delayWhiteExpire($fh);
                NoLoopSyswrite( $fh, "553 TLD invalid in '$RO_e'\r\n" );

                $this->{messagereason}="invalid TLD";
                pbAdd($fh,$this->{ip},'mxaValencePB',"invalidTLD");

                done($fh);
                return;

            } 
        } 
        my $valid;
        if ( $RO_e !~ /$defaultLocalHost/i && $RO_e && $CanUseAddress && $DoRFC522Sender && !$this->{relayok} && $RO_e !~/^SRS/) {

            eval { $valid = Email::Valid->address($RO_e); };
            if ( !$valid && !$@) {

                # couldn't understand sender

                $this->{prepend} = "[MalformedAddress]";
                mlog( $fh, "malformed address: '$RO_e' - failed $Email::Valid::Details check" );
                $Stats{msgverify}++;

                delayWhiteExpire($fh);
                NoLoopSyswrite( $fh, "553 Malformed address: $RO_e\r\n" );

                $this->{messagereason}="Malformed address";
                pbAdd($fh,$this->{ip},'mxaValencePB',"MalformedAddress");

                done($fh);
                return;

            }

        }    # reset everything

        $this->{mailfrom} = $fr;
        my $t    = time;
        my $mf   = lc $this->{mailfrom};
        $mf = batv_remove_tag($fh,$mf,'');
        $this->{mailfrom} = $mf;

        my $mfd;
        $mfd = $1 if $mf=~/\@(.*)/o;
        my $mfdd;
        $mfdd = $1 if $mf=~/(\@.*)/o;

        foreach my $adr ( split( " ", $this->{rcpt} ) ) {
             $this->{newrcpt} .= "$adr " if $adr =~ /$mfd/;
             last if $AddIntendedForHeader == 1;
        }

        $wildcardUser = lc $wildcardUser;
        my $alldd        = "$wildcardUser$mfdd";
        my $defaultalldd = "*$mfdd";

        if($l=~/SIZE=(\d*)\s/io) {
            my $size = $1;
            $this->{SIZE}=$size;
 #           mlog($fh,"info: found message size announcement: " . &formatNumDataSize($size)) if $SessionLog == 2;

            if ( ($this->{relayok} && $maxSize
                    && ( $size > $maxSize )) or (!$this->{relayok} && $maxSizeExternal
                    && ( $size > $maxSizeExternal )))
            {
                my $max = $this->{relayok} ? &formatNumDataSize($maxSize) : &formatNumDataSize($maxSizeExternal);
                my $err = "552 message exceeds MAXSIZE";
                $this->{prepend} = "[MAXSIZE]";
                mlog( $fh, "error: message exceeds maxSize $max!" );
                $err =~ s/MAXSIZE/$max/go;
                NoLoopSyswrite( $fh, "$err\r\n" );
                done($fh);

                return;
            }

            if (!$this->{relayok}) {

                if ($npSize && $size > $npSize) {
                    $this->{ismaxsize}=1 ;
                    $this->{noprocessing}=1;
                    $this->{prepend} = "[NPSIZE]";
                    $this->{passingreason} = "message size ($size) is above $npSize (npSize)";
                }
            }
        }
        $this->{doneAuthToRelay} = 1 if($l=~/ AUTH=.+/io);

########################################## !relayok ############

        $this->{externalsenderok} = 1 if !$this->{externalsenderok} && $EmailSenderOK && matchSL( $mf, 'EmailSenderOK' );
        $this->{externalsenderok} = 1 if !$this->{externalsenderok} && (   matchSL( $mf, 'EmailAdmins', 1 ));
        $this->{externalsenderok} = 1 if !$this->{externalsenderok} && ( $EmailSenderLocalAddress && &localmailaddress($fh,$mf) );
        $this->{externalsenderok} = 1 if !$this->{externalsenderok} && ( $EmailAdmins && matchSL( $mf, 'EmailAdmins'));
        $this->{externalsenderok} = 1 if !$this->{externalsenderok} && ( $EmailAdminReportsTo && $mf =~ /$EmailAdminReportsTo/i );

        $this->{senderok} = 2 if !$this->{externalsenderok} && ( $EmailSenderNotOK && matchSL( $mf, 'EmailSenderNotOK' ) ) ;
        $this->{senderok} = 3 if !$this->{externalsenderok} && ( $EmailSenderIgnore && matchSL( $mf, 'EmailSenderIgnore' ) ) ;

        if (   matchSL( $mf, 'EmailAdmins', 1 )
            or $mf eq lc($EmailAdminReportsTo) )
        {
            $this->{externalsenderok} = 1;
        }

        $this->{externalsenderok} = 1 if $this->{notspamtag};

        if (!$this->{relayok}) {
            eval {
              if ($allLogRe
                  && (   $mf =~ /$allLogReRE/
                      || $this->{ip}   =~ /$allLogReRE/
                      || $this->{helo} =~ /$allLogReRE/)
                ) {
                 $this->{alllog}=1;
              }
            };
            eval {
            if(!$this->{contentonly} && $contentOnlyRe && $this->{header}=~/($contentOnlyReRE)/) {
                mlogRe($fh,($1||$2),"Contentonly");
                pbBlackDelete($fh,$this->{ip});
                $this->{contentonly}=1;
                $this->{ispip}=1;
                $this->{noblockingips} = 1;
            }
            };
            if ($Con{$server}->{relayok} && $WhitelistAuth){
                $this->{whitelisted}="authenticated";
                $this->{relayok}="authenticated";
                $this->{passingreason} = "authenticated";
                # whitelist authenticated users
            }
            $this->{red} = $this->{redlist} = "$mf in RedList"
              if ( $Redlist{"$alldd"}
                || $Redlist{"$defaultalldd"}
                || $Redlist{"$mf"} );

            if (matchIP( $this->{ip}, 'noBlockingIPs', 0,1 ))
            {
                $this->{noblockingips} = 1;
                $this->{contentonly}=1;
            }
            my $ret = matchIP( $this->{ip}, 'noProcessingIPs', $fh ); 
            if ($noProcessingIPs
                && $ret
                &&  !matchIP( $this->{ip}, 'NPexcludeIPs', 0, 1 )
                && !$this->{nonoprocessing}
                 )
            {
                $this->{noprocessing}          = 1;
                $this->{passingreason}         = "noProcessingIPs '$ret'" if !$this->{passingreason};
            }
            if (  $noNoProcessing 
                && matchSL( $mf, 'noNoProcessing' ) )
            {
                $this->{nonoprocessing}  = 1;
            }
            if (   !$this->{noprocessing}
               && !$this->{nonoprocessing}
               && $noProcessingDomains
               && $mf =~ /($NPDRE)/ )
            {
                $this->{noprocessing}  = 1;
                mlogRe( $fh, ($1||$2), "noProcessingDomains" );
                $this->{passingreason} = "noProcessingDomains" if !$this->{passingreason};
            }
            $this->{localuser} = localmail($mf);
            if (!$this->{whitelisted} && $whiteReRE ) {
                    WhiteOk($fh) ;
            }
            if (   !$this->{noprocessing} ) {
                if (!$this->{noprocessing} && matchIP( $ip, 'noProcessingIPs', 0, 1 ) &&  !matchIP( $ip, 'NPexcludeIPs', 0, 1 ) )  {
                    $this->{noprocessing} = 1;
                    $this->{passingreason} = "$noProcessingIPs '$ip'" if !$this->{passingreason};
                } 
                if (! $this->{whitelisted} && $whiteListedDomains && $mf=~/($WLDRE)/) {
                    mlogRe($fh,($1||$2),"WhiteDomain") ;
                    $this->{whitedomain}= $1||$2;
                    $this->{passingreason} = "whiteListedDomains '$this->{whitedomain}'" if !$this->{passingreason};
                    $this->{whitelisted}=1;
                }
                my $mfdd;
                $mfdd = $1 if $mf=~/(\@.*)/o;
                my $alldd        = "*$mfdd";
                if (   !$this->{whitelisted}
                    && !localmail($mf)
                    &&  $Whitelist{$alldd} )
                {
                    $this->{whitelisted} = "$alldd";
                    $Whitelist{$alldd}        = $t if !$this->{red};
                    $this->{passingreason}    = "$alldd";
                }
                my $ret = matchIP( $this->{ip}, 'whiteListedIPs', $fh );
                if (  $whiteListedIPs && $ret )
                {
                    $this->{whitelisted}       = "whiteListedIPs '$ret'";
                    $this->{passingreason} = "whiteListedIPs '$ret'";
                }
                $this->{ispip} = 1 if ( matchIP( $this->{ip}, 'ispip', $fh ) );
                $this->{nopb}  = 1 if ( matchIP( $this->{ip}, 'noPB',  $fh ) );
                if ( matchIP( $this->{ip}, 'noPBwhite', $fh ) ) {
                    $this->{messagereason} = "noPBwhite";
                    $this->{nopbwhite} = 1;
                }
                if (pbWhiteFind( $this->{ip} ) && !$this->{nopbwhite}) {
                    $this->{pbwhite} = 1;
                    $this->{messagereason} = "PBwhite";

                }
                $this->{nohelo} = 1 if ( matchIP( $this->{ip}, 'noHelo', $fh ) );
                if ($this->{mailfrom}=~/$BSRE/) {
                    $this->{prepend} = "[isbounce]";
                    $this->{isbounce}=1;
                }
                $this->{nodelay} = 'noDelay' if matchIP( $this->{ip}, 'noDelay', $fh );
                $this->{nodelay} = 'noDelayAddresses' if matchSL($this->{mailfrom},'noDelayAddresses');
                $this->{acceptall} = 1
                  if matchIP( $this->{ip}, 'acceptAllMail', $fh );
                $this->{NPexcludeIPs} = 1
                  if matchIP( $this->{ip}, 'NPexcludeIPs', $fh );

                if ( $this->{whitelisted} ) {
                    pbBlackDelete( $fh, $this->{ip} );
                    pbWhiteAdd( $fh, $this->{ip}, "Whitelisted" );
                }
                if ( $this->{noprocessing} & !$this->{NPexcludeIPs} ) {
                    pbBlackDelete( $fh, $this->{ip} );
                    pbWhiteAdd( $fh, $this->{ip}, "NoProcessing" );
                }
                my $ip=$this->{ip};
                my $myip = &ipNetwork( $ip, $PenaltyUseNetblocks );
                my ( $ct, $ut, $level, $totalscore, $sip, $reason, $counter ) =
                  split( ' ', $PBBlack{$myip} );

                $this->{pbblack} = 1 if pbBlackFind( $this->{ip} );
                return if !$this->{relayok} && !&FrequencyIPOK($fh);

                if (   $DoDomainIP
                    && $this->{pbblack}
                    && !$this->{pbwhite}
                    && $maxSMTPdomainIP
                    && $mfd
                    && !$this->{nopb}
                    && !$this->{whitelisted}
                    && !$this->{rwlok}
                    && $this->{noprocessing} ne '1'
                    && !$this->{ispip}
                    && !$this->{acceptall}
                    && !$this->{nodelay}
                    && !$this->{contentonly}
                    && !$this->{noblockingips}
                    && (! $ValidateSPF || ($SPFCacheInterval && $SPFCacheObject && [&SPFCacheFind($this->{ip},$mfd)]->[1] ne 'pass'))
                    && (!$maxSMTPdomainIPWL || ($maxSMTPdomainIPWL &&  $mfd!~/($IPDWLDRE)/))
                   )
                {
                    my $myip=&ipNetwork($this->{ip}, $DelayUseNetblocks) . '.';
                    if ((time - $SMTPdomainIPTriesExpiration{$mfd}) > $maxSMTPdomainIPExpiration) {
                        $SMTPdomainIPTries{$mfd} = 1;
                        $SMTPdomainIPTriesExpiration{$mfd} = time;
                        $myip =~ s/\./\\\./go;
                        $SMTPdomainIP{$mfd} = $myip;
                    } elsif ($myip !~ /^(?:$SMTPdomainIP{$mfd})$/) {
                        $SMTPdomainIP{$mfd} .= '|' if $SMTPdomainIP{$mfd};
                        $myip =~ s/\./\\\./go;
                        $SMTPdomainIP{$mfd} .= $myip;
                        $SMTPdomainIPTriesExpiration{$mfd} = time if $SMTPdomainIPTries{$mfd}==1;
                        $SMTPdomainIPTries{$mfd}++;
                    }
                    my $tlit = &tlit($DoDomainIP);
                    $tlit = "[testmode]"   if $allTestMode && $DoDomainIP == 1 || $DoDomainIP == 4;
                    my $DoDomainIP = $DoDomainIP;
                    $DoDomainIP = 3 if $allTestMode && $DoDomainIP == 1 || $DoDomainIP == 4;
                    if ( exists $SMTPdomainIPTries{$mfd} && $SMTPdomainIPTries{$mfd} > $maxSMTPdomainIP) {

                        $this->{prepend} = "[IPperDomain]";
                        $this->{messagereason} = "'$mfdd' passed limit($maxSMTPdomainIP) of ips per domain";

                        mlog( $fh, "$tlit $this->{messagereason}")
                          if (  ($SessionLog && $SMTPdomainIPTries{$mfd} == $maxSMTPdomainIP + 1)
                              ||($SessionLog >= 2 && $SMTPdomainIPTries{$mfd} > $maxSMTPdomainIP + 1));

                        pbAdd( $fh, $this->{ip}, 'idValencePB', "LimitingIPDomain" ) if $DoDomainIP != 2;
                        if ( $DoDomainIP == 1 ) {
                            $Stats{smtpConnDomainIP}++;
                            seterror( $fh, "554 5.7.1 too many different IP's for domain '$mfdd'", 1 );
                            return;
                        }
                    }
                }

                if (&MessageScoreHigh($fh,25)) {
                        MessageScore( $fh, 1 );
                        return;
                }

            }
        }

############################################ ##############################

         if ($EnableSRS &&
            $CanUseSRS  &&
            $this->{relayok} &&
            ! localmail($this->{mailfrom}) &&
            $this->{mailfrom} !~ $BSRE &&
            ! ($SRSno && $this->{mailfrom} && matchSL($this->{mailfrom},'SRSno'))) 
            {

            # rewrite sender addresses when relaying through Relay Host
            my $tmpfrom;
            $this->{prepend} = "[SRS]";
            my $srs = new Mail::SRS(
                Secret        => $SRSSecretKey,
                MaxAge        => $SRSTimestampMaxAge,
                HashLength    => $SRSHashLength,
                AlwaysRewrite => 1
            );
            if (
                !eval { $tmpfrom = $srs->reverse( $this->{mailfrom} ) }
                && eval {
                    $tmpfrom =
                      $srs->forward( $this->{mailfrom}, $SRSAliasDomain );
                }
              )
            {
                mlog(
                    $fh,
                    "SRS rewriting sender '$this->{mailfrom}' into '$tmpfrom'",
                    1
                );
                $l =~ s/\Q$this->{mailfrom}\E/$tmpfrom/;
            } else {
                mlog( $fh, "SRS rewriting sender '$this->{mailfrom}' failed!",
                    1 );
            }
        }

    } elsif($l=~/^ *(VRFY|EXPN) *([^\r\n]*)/io) {
        $this->{lastcmd} = $1;
        my $e=$2;
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;

        if ( $DisableVRFY && !$this->{relayok} )
        {
            $this->{prepend} = "[unsupported_$this->{lastcmd}]";
            mlog($fh,"$this->{lastcmd} not allowed");
            if($MaxErrors && ++$this->{serverErrors} > $MaxErrors) {
                MaxErrorsFailed($fh,
                "502 $this->{lastcmd} not supported\r\n421 <$myName> closing transmission\r\n",
                "max errors (MaxErrors=$MaxErrors) exceeded -- dropping connection after $this->{lastcmd}");
                return;
            }
            sendque($fh, "502 $this->{lastcmd} not supported\r\n");
            return;
        }

        my ($u,$h);
        my ($str, $gen, $day, $hash, $orig_user) = ($e =~ /(prvs=(\d)(\d\d\d)(\w{6})=(.*))/o);
        $l =~ s/$str/$orig_user/ if ($orig_user);  # remove BATV-Tag from VRFY address

        # recipient replacment should be done next to here !
        if ($ReplaceRecpt) {
            if ($l=~/ *(?:VRFY|EXPN)\s*<*([^\r\n>]*).*/io) {
                my $midpart  = $1;
                my $orgmidpart = $midpart;
                if ($midpart) {
                  my $bpa = 0;
                  if($EnableBangPath && $midpart=~/([a-z\-_\.]+)!([a-z\-_\.]+)$/io) {
                      $midpart = "$2@$1";
                  }
                  my $mf = batv_remove_tag(0,lc $this->{mailfrom},'');
                  my $newmidpart = RcptReplace($midpart,$mf,'RecRepRegex');
                  if (lc $newmidpart ne lc $midpart) {
                      $l =~ s/$orgmidpart/$newmidpart/i;
                      mlog($fh,"info: $this->{lastcmd} recipient $orgmidpart replaced with $newmidpart");
                  }
                }
            }
        }
    } elsif ( $l =~ /rcpt to: *(.*)/i ) {
        my $e = $1;
        $e = batv_remove_tag(0,$e,'');
        my ( $u, $h );

        #enforce valid email address pattern

        if ( $l =~ /rcpt to:\s*<*([^\r\n>]*).*/i ) {
                my $RO_e = $1;
                if ( $RO_e =~ /($BlockLocalAddressesReRE)/ && $this->{relayok}) {

                    sendque( $fh, "553 Malformed address: $RO_e\r\n" );
                    $this->{prepend} = "[BlockedLocal]";
                    mlog( $fh, "address '$RO_e'  blocked by BlockLocalAddressesRe: '$1'" );
                    $Stats{rcptRelayRejected}++;
                    delayWhiteExpire($fh);
                    return;
                }
        }

        if ( $EnableSRS && $CanUseSRS ) {
            if ( $this->{isbounce} ) {

                # validate incoming bounces
                my $tmpto;
                my $srs = new Mail::SRS(
                    Secret        => $SRSSecretKey,
                    MaxAge        => $SRSTimestampMaxAge,
                    HashLength    => $SRSHashLength,
                    AlwaysRewrite => 1
                );
                if ( $e =~ /^<?(SRS0[=+-][^\r\n>]*).*/i ) {
                    if ( eval { $tmpto = $srs->reverse($1) } ) {
                        $l =~ s/\Q$1\E/$tmpto/;
                        $e = <$tmpto>;
                    } else {
                        $this->{invalidSRSBounce} = 1;
                    }
                } elsif ($e=~/^<?(SRS1[=+-][^\r\n>]*)/io) {
                    if (eval{$tmpto=$srs->reverse($1)}) {
                        if (eval{$_=$srs->reverse($tmpto)}) {
                            $l=~s/\Q$1\E/$_/;
                            $e=<$_>;
                        } else {
                            $this->{prepend} = "[RelayAttempt]";
                            $this->{messagereason} = "user not local; please try <$tmpto> directly";
                            mlog( $fh, $this->{messagereason} );
                            $Stats{rcptRelayRejected}++;
                            pbAdd($fh,$this->{ip},'rlValencePB','RelayAttempt',0);
                            if($MaxErrors && ++$this->{serverErrors} > $MaxErrors) {
                                NoLoopSyswrite( $fh, "551 5.7.1 User not local; please try <$tmpto> directly\r\n421 <$myName> closing transmission\r\n" );
                                $this->{prepend} = "[MaxErrors]";
                                $this->{messagereason}="max errors ($MaxErrors) exceeded";
                                mlog($fh,"max errors (MaxErrors=$MaxErrors) exceeded -- dropping connection - after SRS");
                                pbAdd($fh,$this->{ip},'meValencePB','MaxErrors',0);
                                $Stats{msgMaxErrors}++;
                                done($fh);
                                return;
                            }
                            sendque($fh,"551 5.7.1 User not local; please try <$tmpto> directly\r\n");
                            return;
                        }
                    } else {
                        $this->{invalidSRSBounce}=1;
                    }
                } else {
                    $this->{invalidSRSBounce} = 1;
                }
            } elsif ( &serverIsSmtpDestination($server) && $e=~/^<?(SRS[01][=+-][^\r\n>]*)/io) {
                $this->{prepend} = "[RelayAttempt]";
                $this->{messagereason} = "SRS only supported in DSN (Delivery Status Notification): $e";
                mlog( $fh, $this->{messagereason} );
                $Stats{rcptRelayRejected}++;
                pbAdd($fh,$this->{ip},'rlValencePB','RelayAttempt',0);
                if($MaxErrors && ++$this->{serverErrors} > $MaxErrors) {
                    MaxErrorsFailed($fh,
                    "554 5.7.6 SRS only supported in DSN\r\n421 <$myName> closing transmission\r\n" ,
                    "max errors (MaxErrors=$MaxErrors) exceeded -- dropping connection - after SRS-DSN");
                    return;
                }
                sendque($fh,"554 5.7.6 SRS only supported in DSN (Delivery Status Notification)\r\n");
                return;
            }
        }
        if ( $e !~ /ORCPT/ && $e =~ /[\!\@]\S*\@/ ) {

            # blatent attempt at relaying

            $this->{prepend} = "[RelayAttempt]";
            my $reply = $NoRelaying;            
            $reply =~ s/REASON/relay attempt: $e/g;
            $reply = replaceerror ($fh, $reply);

            $this->{messagereason} = "relay attempt blocked for (evil): $e";
            mlog( $fh, $this->{messagereason} );

            $Stats{rcptRelayRejected}++;
            delayWhiteExpire($fh);

            if ($NoRelayingStrict) {
                NoLoopSyswrite( $fh, $NoRelaying."\r\n421 <$myName> closing transmission\r\n" );
                done($fh);
                return;
            }

            if($MaxRelayingErrors  && ++$this->{serverErrors} >= $MaxRelayingErrors) {
                delayWhiteExpire($fh);
                NoLoopSyswrite( $fh, $reply."\r\n421 <$myName> closing transmission\r\n" );
                $this->{prepend} = "[MaxErrors]";
                $this->{messagereason}="max errors ($this->{serverErrors}) exceeded";
                mlog($fh,"max errors ($this->{serverErrors}) exceeded -- dropping connection - after ORCPT");
                pbAdd($fh,$this->{ip},'meValencePB','MaxErrors',0);
                $Stats{msgMaxErrors}++;
                done($fh);
                return;
            }

            sendque($fh, $reply."\r\n");

            return;
        } elsif ( $EnableBangPath && $e =~ /([a-z\-_\.]+)!([a-z\-_\.]+)$/i ) {
            # someone give me one good reason why I should support bang paths! grumble...
            $u = "$2@";
            $h = $1;
        } elsif ( $l =~ /rcpt to:.*?($EmailAdrRe\@)($EmailDomainRe)/io ) {
            ( $u, $h ) = ( $1, $2 );
#            mlog($fh,"2 $e u$u h=$h");
            CheckReportAddr($fh, "$u$h");
        } elsif ( $defaultLocalHost && $l =~ /rcpt to:.*?<($EmailAdrRe)>/io ) {
            ( $u, $h ) = ( $1, $defaultLocalHost );
            $u .= '@';

        } elsif($l=~/rcpt to:[^\r\n]*?(\"$EmailAdrRe\"\@)($EmailDomainRe)/io) {
            ($u,$h)=($1,$2);
            my $buh = batv_remove_tag(0,"$u$h",'');
            $buh =~ /($EmailAdrRe\@)($EmailDomainRe)/io;
            ($u,$h)=($1,$2);
            $u =~ s/\"//go;
            $this->{user} = $u;
        } else {

            # couldn't understand recipient

            $this->{prepend}       = "[RelayAttempt]";
            $this->{messagereason} = "relay attempt blocked for (parsing): $e";
            mlog( $fh, $this->{messagereason} ) if $RelayLog;

            $Stats{rcptRelayRejected}++;

            if ($NoRelayingStrict) {
                NoLoopSyswrite( $fh, $NoRelaying."\r\n421 <$myName> closing transmission\r\n" );
                done($fh);
                return;
            }
            sendque( $fh,"551 5.7.1 $this->{messagereason}\r\n");

            if($MaxRelayingErrors  && ++$this->{serverErrors} >= $MaxRelayingErrors && !$this->{noprocessing} && !$this->{ispip} && !$this->{relayok}) {
                $this->{prepend} = "[RelayAttempt]";
                delayWhiteExpire($fh);
                my $reply = $SpamError; 
                $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;           
                $reply =~ s/REASON/relay attempt: $e/g;
                $reply = replaceerror ($fh, $reply);

                NoLoopSyswrite( $fh, $reply."\r\n421 <$myName> closing transmission\r\n" );
                $this->{messagereason} = "max errors (MaxRelayingErrors=$MaxRelayingErrors) exceeded";
                mlog( $fh,
                    "max errors (MaxRelayingErrors=$MaxRelayingErrors) exceeded -- last relay attempt blocked for (parsing): $e" ) if $RelayLog > 1;
                pbAdd( $fh, $this->{ip}, 'meValencePB', "MaxErrors", 2 );
                $Stats{msgMaxErrors}++;
                done($fh);
            }

            return;
        }
        # recipient replacment should be done next to here !
        if ($ReplaceRecpt) {
            if ($l=~/rcpt to:\s*<*([^\r\n>]*)/io) {
                my $midpart  = $1;
                $midpart = batv_remove_tag(0,$midpart,'');
                my $orgmidpart = $midpart;
                if ($midpart) {
                  my $bpa = 0;
                  if($EnableBangPath && $midpart=~/([a-z\-_\.]+)!([a-z\-_\.]+)$/io) {
                      $midpart = "$2@$1";
                  }
                  my $mf = $this->{mailfrom};
                  $mf = batv_remove_tag(0,$mf,'');
                  my $newmidpart = RcptReplace($midpart,$mf,'RecRepRegex');
                  if (lc $newmidpart ne lc $midpart) {
                      $l =~ s/\Q$orgmidpart\E/$newmidpart/i;
                      mlog($fh,"info: recipient $orgmidpart replaced with $newmidpart");
                      $this->{myheader}.="X-Assp-Recipient: recipient $orgmidpart replaced with $newmidpart\r\n";
                      $this->{orgrcpt} = $orgmidpart;
                  }
                }
            }
            $l=~/rcpt to: *([^\r\n]*)/io;
            $e = batv_remove_tag(0,$1,'');
        }

        if ( matchSL( "$u$h", 'noCollecting' ) ) {
                $this->{nocollect} = 1;
        }
        if ( matchSL( "$u$h", 'noBayesian' ) ) {
                $this->{nobayesian} = 1;
        }

         #enforce valid email address pattern
        if ( $CanUseAddress && $DoRFC822 ) {

            if ($e=~/<*([^\r\n>]*)/io) {
                my $RO_e=$1;
                $RO_e = "$RO_e" . "@" . "$defaultLocalHost" if $defaultLocalHost && $RO_e !~ /\@/i;
                if ($RO_e !~/$defaultLocalHost/i && $RO_e !~ /RSBM_.*?x2DXx2DX\d+\Q$maillogExt\E\@/i && ! Email::Valid->address($RO_e)) {

                    # couldn't understand recipient

                    $this->{prepend} = "[MalformedAddress]";
                    mlog($fh,"malformed address: '$RO_e' - failed $Email::Valid::Details check");
                    $Stats{rcptRelayRejected}++;
                    if($MaxErrors && ++$this->{serverErrors} > $MaxErrors) {
                        delayWhiteExpire($fh);
                        NoLoopSyswrite( $fh, "553 Malformed address: $u$h\r\n421 <$myName> closing transmission\r\n" );
                        $this->{prepend} = "[MaxErrors]";
                        $this->{messagereason}="max errors ($MaxErrors) exceeded";
                        mlog($fh,"max errors (MaxErrors=$MaxErrors) exceeded -- dropping connection after Email::Valid") if $ValidateUserLog;
                        pbAdd($fh,$this->{ip},'meValencePB',"MaxErrors",0);
                        $Stats{msgMaxErrors}++;
                        done($fh);
                        return;
                    }
                    sendque( $fh, "553 Malformed address: $u$h\r\n" );
                    return;
                }
            }
        }
        my $rcptislocal = localmail($h);
        my ($mfd) = $this->{mailfrom} =~ /(\@.*)/;
        my $all = "*" . $mfd;
        my $istrapaddress = matchSL("$u$h",'spamtrapaddresses') && !matchSL("$u$h",'noPenaltyMakeTraps');
        if ($rcptislocal) {

            if ( lc $u eq "abuse\@" && $sendAllAbuse ) {

                # accept abuse catchall addresses
                if ($sendAllAbuse=~/$EmailAdrRe\@($EmailDomainRe)/io) {
                    $h=$1;
                    $l="RCPT TO:\<$sendAllAbuse\>\r\n";
                    $this->{noprocessing}=1 if $sendAllAbuseNP;
                }
            } elsif ( lc $u eq "postmaster\@" && $sendAllPostmaster ) {

                # accept postmaster catchall addresses
                if ($sendAllPostmaster=~/$EmailAdrRe\@($EmailDomainRe)/io) {
                    $h=$1;
                    $l="RCPT TO:\<$sendAllPostmaster\>\r\n";
                    $this->{noprocessing}=1 if $sendAllPostmasterNP;
                }
            } elsif ($AllowLocalAddressesRe && $AllowLocalAddressesReCount && "$u$h" !~ $AllowLocalAddressesReRE) {
                my $reply = $NoValidRecipient;
                $reply = "550 5.1.1 User unknown: $u$h\r\n" if !$NoValidRecipient;
                $reply = replaceerror ($fh, $reply, "$u$h" );
                $this->{prepend}       = "[AllowLocalRe]";
                $this->{messagereason} = "rejected by AllowLocalRe: $u$h";
                mlog( $fh, $this->{messagereason} )
                  if $this->{alllog} or $ValidateUserLog == 1 or $ValidateUserLog == 2;

                $Stats{rcptNonexistent}++;

                pbAdd( $fh, $this->{ip}, 'irValencePB', "UserUnknown" ) ;
                sendque( $fh, "$reply\r\n" );

                if(++$this->{serverErrors} >= $MaxErrors ) {
                    $this->{prepend}       = "[MaxErrors]";
                    $this->{messagereason} = "max errors (MaxErrors=$MaxErrors) exceeded";
                    mlog( $fh,
                    "max errors (MaxErrors=$MaxErrors) exceeded -- dropping connection after rejection by DoPenaltyMakeTraps" ) if $RelayLog > 1 or $ValidateUserLog;
                    pbAdd( $fh, $this->{ip}, 'meValencePB', "MaxErrors");
                    $Stats{msgMaxErrors}++;
                    done($fh);
                }

                return;
            } elsif ($DoPenaltyMakeTraps==3 && pbTrapExist($fh,"$u$h") ) {
                my $reply = $NoValidRecipient;
                $reply = "550 5.1.1 User unknown: $u$h\r\n" if !$NoValidRecipient;
                $reply = replaceerror ($fh, $reply, "$u$h" );
                $this->{prepend}       = "[DoPenaltyMT]";
                $this->{messagereason} = "rejected by DoPenaltyMakeTraps(3): $u$h";
                mlog( $fh, $this->{messagereason} )
                  if $this->{alllog} or $TrapLog;

                $Stats{rcptNonexistent}++;
                pbTrapAdd( $fh, "$u$h" );
                pbAdd( $fh, $this->{ip}, 'irValencePB', "UserUnknown" ) ;
                sendque( $fh, "$reply\r\n" );

                if(++$this->{serverErrors} >= $MaxErrors ) {
                    $this->{prepend}       = "[MaxErrors]";
                    $this->{messagereason} = "max errors (MaxErrors=$MaxErrors) exceeded";
                    mlog( $fh,
                    "max errors (MaxErrors=$MaxErrors) exceeded -- dropping connection after rejection by DoPenaltyMakeTraps" ) if $RelayLog > 1 or $ValidateUserLog;
                    pbAdd( $fh, $this->{ip}, 'meValencePB', "MaxErrors");
                    $Stats{msgMaxErrors}++;
                    done($fh);
                }

                return;
            } elsif ( matchSL( "$u$h", 'RejectTheseLocalAddresses' ) ) {
                my $reply = $NoValidRecipient;
                $reply = "550 5.1.1 User unknown: $u$h\r\n" if !$NoValidRecipient;
                $reply = replaceerror ($fh, $reply, "$u$h" );
                $Stats{rcptNonexistent}++;
                $this->{prepend} = "[RejectAddress]";
                mlog( $fh, "rejected by reject address list: $u$h" )
                  if $this->{alllog} or $ValidateUserLog ;
                delayWhiteExpire($fh);
                seterror( $fh, $reply, 1 );
                return;

            } elsif (!$this->{addressedToSpamBucket} 
                && ($spamaddresses && !$this->{nocollect} && matchSL( "$u$h", 'spamaddresses' ))
                or ($DoPenaltyMakeTraps==2 && &pbTrapFind("$u$h"))
                or ($UseTrapToCollect && $istrapaddress)) {
               $this->{addressedToSpamBucket}="$u$h";
               $this->{messagescore} = 99;
               $l="RCPT TO:\<$u$h\>\r\n";

             } elsif (!$this->{addressedToSpamBucket} 
                 && !matchSL( "$u$h",'noPenaltyMakeTraps' ) 
                 && ((($spamtrapaddresses && matchSL("$u$h",'spamtrapaddresses')) 
                 or (!$Whitelist{lc $this->{mailfrom} } && $DoPenaltyMakeTraps==1 && pbTrapFind($fh,"$u$h"))) 
                 && !$this->{relayok} && !$this->{nocollect} && !$this->{acceptall})) {
                $this->{prepend} = "[Trap]";
                pbWhiteDelete( $fh, $this->{ip} );
                $this->{messagereason} = "$u$h in spamtrapaddresses";
                mlog( $fh,"$this->{messagereason}") if $TrapLog >=2;
                pbAdd( $fh, $this->{ip}, 'stValencePB', "spamtrap:$u$h" );
                if ( $SpamTrap2NULL) {
                    $Stats{spambucket}++;
                    delayWhiteExpire($fh);
                    $this->{getline} = \&NullFromToData;
                    &NullFromToData( $fh, $l );
                    done($fh);
                    return;
                }
                if ($PenaltyTrapPolite) {
                    $reply = $PenaltyTrapPolite;
                    $reply =~ s/EMAILADDRESS/$u$h/go;
                    $reply =~ s/LOCALDOMAIN/$h/go;
                    $reply = replaceerror ($fh, $reply);
                    seterror( $fh, "$reply", 1 );
                }

                $Stats{spambucket}++;
                delayWhiteExpire($fh);
                done($fh);
                return;

                }

        } 

        if ($noProcessing) {

            $this->{rcptnoprocessing} = "";

            if ( matchSL( "$u$h", 'NoProcessing' ) ) {
                mlogRe( $fh, "$u$h", "NoProcessing" );
                $this->{uhnoprocessing}=1 if $LocalAddressesNP;
                $this->{delaydone}        = 1;
                $this->{rcptnoprocessing} = 1;
            }
        }
        if ($noProcessingTo && !$this->{rcptnoprocessing}) {
            if ( matchSL( "$u$h", 'noProcessingTo' ) ) {
                mlogRe( $fh, "$u$h", "noProcessingTo" );
                $this->{uhnoprocessing}=1 if $LocalAddressesNP;
                $this->{delaydone}        = 1;
                $this->{rcptnoprocessing} = 1;
            }
        }

        my $isEmailInterface =
                 (  (lc $u =~ /assp-/  or localmail($h) or lc $h eq lc $defaultLocalHost)
                    && (   lc $u eq lc "$EmailSpam\@"
                        || lc $u eq lc "$EmailHam\@"
                        || lc $u eq lc "$EmailWhitelistAdd\@"
                        || lc $u eq lc "$EmailWhitelistRemove\@"
                        || lc $u eq lc "$EmailRedlistAdd\@"
                        || lc $u eq lc "$EmailHelp\@"
                        || lc $u eq lc "$EmailAnalyze\@"
                        || lc $u eq lc "$EmailRedlistRemove\@"
                        || lc $u eq lc "$EmailSpamLoverAdd\@"
                        || lc $u eq lc "$EmailSpamLoverRemove\@"
                        || lc $u eq lc "$EmailNoProcessingAdd\@"
                        || lc $u eq lc "$EmailNoProcessingRemove\@"
                        || lc $u eq lc "$EmailBlackAdd\@"
                        || lc $u eq lc "$EmailBlackRemove\@"
                        || lc $u eq lc "$EmailPersBlackAdd\@"
                        || lc $u eq lc "$EmailPersBlackRemove\@"
                        || lc $u =~ /^RSBM.+?$maillogExt\@$/i
                        || lc $u eq lc "$EmailBlockReport\@"

                       )
                 );
        my $emailok;
        $emailok = 1
          if (   $EmailInterfaceOk
              && $this->{senderok} ne '2'
              && $this->{senderok} ne '3'
              && ( $this->{relayok} || $this->{externalsenderok}  )
              && $isEmailInterface
             );

        # skip check when RELAYOK or EMAIL-Interface
        if ($emailok)
        {
            emailInterface($fh,$u,$h,$l);
            return;
          }

        my $uh = "$u$h";
        $uh =~ /^(.*)(@.*)$/;
        my $hat = $2;
        $this->{alllog} = 1 if $allLogRe && $uh =~ ( '(' . $allLogReRE . ')' );
        my $t1 = "VRFY";
        $t1 = "LDAP" if $DoLDAP;

        my $reporterror;
        if (CheckReportAddr($fh,$uh)  && !$this->{relayok} && !$this->{externalsenderok}) {

                $this->{prepend} = "[ReportLog]";
                mlog( $fh, "email-interface warning: mail to '$uh' from $this->{mailfrom} contains no local sender and is not set in EmailSenderOK'");

        }

        if ($uh =~ /^(.*@)(.*)$/ 
                && ($2 =~ "assp.local" or $2 =~ "assp-notspam.org") 
                && !$emailok) {
            $uh =~ /^(.*)(@.*)$/;
            $this->{prepend} = "[ReportLog]";
            if ( !$EmailInterfaceOk ) {
                $reporterror = "EmailInterfaceOk disabled";
            }
            if ( !$this->{relayok} && !$this->{externalsenderok}) {
                $reporterror .= ", sender not local" if $reporterror;
                $reporterror = "sender not local" if !$reporterror;
            }
            if ( !$isEmailInterface) {
                $reporterror .= ", '$1' not set in email-interface" if $reporterror;
                $reporterror = "'$1' not set in email-interface" if !$reporterror;
            }    
            $this->{prepend}       = "[EmailInterface]";
            mlog( $fh, "email-interface reported '$reporterror'");
            seterror( $fh, "550 5.1.1 User unknown, email-interface reported '$reporterror'",1) ;

            return;

       }

        if (!$this->{uhnoprocessing} 
            && !$emailok         
            && !$this->{relayok}        
            && (   $LocalAddresses_Flat
                || $DoLDAP && $CanUseLDAP
                || &isvrfy( $fh, $uh)  

               )) {

            $this->{islocalmailaddress} = 0;

            if ($SepChar) {
                my $char = "\\$SepChar";
                if ( $u =~ "(.+?)$char" ) {
                    $uh = "$1\@$h";
                    $uh =~ s/"//;
                }
            }

            if ( $this->{addressedToSpamBucket} )            
                {
                $this->{islocalmailaddress} = 1;
                d("$uh validated by spamaddresses list\n");
                mlog( $fh, "$uh validated by spamaddresses list" ) if $ValidateUserLog == 3;

            } elsif (($DoLDAP || &isvrfy( $fh, $uh)) 
                    && !$LocalAddresses_Flat 
                    && &LDAPCacheFind($uh,$t1,1) )
                {
                $this->{islocalmailaddress} = 1;
                d("$uh validated by ldaplist\n");
                mlog( $fh, "$uh validated by ldap-cache" ) if $ValidateUserLog; 

            } elsif ( !$this->{islocalmailaddress}
                && $LocalAddresses_Flat
                && $uh =~ /^([^@]*)(@.*)$/o
                && matchSL( $2, 'LocalAddresses_Flat' ) )
                {
                    $this->{islocalmailaddress} = 1;
                    d("$2 validated by LocalAddresses_Flat\n");
                    mlog( $fh, "$2 validated by LocalAddresses_Flat" )
                          if $ValidateUserLog >= 2;

            } elsif ( !$this->{islocalmailaddress}
                && $LocalAddresses_Flat
                && $LocalAddresses_Flat_Domains
                && $uh =~ /^([^@]*@)(.*)$/o
                && matchSL( $2, 'LocalAddresses_Flat' ) )
                {
                    $this->{islocalmailaddress} = 1;
                    d("$2 validated by LocalAddresses_Flat\n");
                    mlog( $fh, "$2 validated by LocalAddresses_Flat" )
                          if $ValidateUserLog >= 2;

            } elsif (  !$this->{islocalmailaddress}
                && $LocalAddresses_Flat
                && matchSL( $uh, 'LocalAddresses_Flat' ))
                {
                $this->{islocalmailaddress} = 1;
                d("$uh validated by LocalAddresses_Flat\n");
                mlog( $fh, "$uh validated by LocalAddresses_Flat" ) if $ValidateUserLog >= 2;

            } elsif (  !$this->{islocalmailaddress}
                && $DoLDAP && $CanUseLDAP
                && &localmailaddress( $fh, $uh ))
                {
                $this->{islocalmailaddress} = 1;
                d("$uh validated by LDAP\n");
                mlog( $fh, "$uh validated by LDAP" ) if $ValidateUserLog >= 2;

            } elsif (  !$this->{islocalmailaddress}
                && $DoVRFY && $CanUseNetSMTP && $uh =~ /^([^@]*@)(.*)$/o
                && &matchHashKey('DomainVRFYMTA', lc $2 )
                && &localmailaddress( $fh, $uh ))
                {
                $this->{islocalmailaddress} = 1;
                d("$uh validated by VRFY\n");
                mlog( $fh, "$uh validated by VRFY" ) if $ValidateUserLog >= 2;

            }
            pbTrapDelete($uh) if $this->{islocalmailaddress};

        } else {
            $this->{islocalmailaddress}=localmail($h);
        }

        if (   !( $this->{relayok} )
            && !$this->{islocalmailaddress}
            && !$nolocalDomains
            && ( !$rcptislocal || ( $u . $h ) =~ /\%/ )
            || $u =~ /\@\w+/ )
        {

            $this->{prepend} = "[RelayAttempt]";
            $this->{messagereason} = "relay attempt blocked for: $u$h";
            mlog( $fh, $this->{messagereason} ) if $RelayLog;

            $Stats{rcptRelayRejected}++;
            delayWhiteExpire($fh);
            if ($NoRelayingStrict) {
                NoLoopSyswrite( $fh, $NoRelaying."\r\n421 <$myName> closing transmission\r\n" );
                done($fh);
                return;
            }

            if($MaxRelayingErrors  && ++$this->{serverErrors} >= $MaxRelayingErrors && !$this->{noprocessing}) {
                $this->{prepend}       = "[RelayAttempt]";
                $this->{messagereason} = "max errors (MaxRelayingErrors=$MaxRelayingErrors) exceeded";
                mlog( $fh,
                    " $this->{messagereason}" ) if $RelayLog > 1;
                pbAdd( $fh, $this->{ip}, 'meValencePB', "MaxRelayingErrors", 2 );
                $Stats{msgMaxErrors}++;
                my $reply = $NoRelaying;            
                $reply =~ s/REASON/dropping connection after relay attempt/g;
                $reply = replaceerror ($fh, $reply);
                seterror( $fh, $reply, 1 );
                NoLoopSyswrite( $fh, $reply."\r\n421 <$myName> closing transmission\r\n" );
                return;;
            }
            sendque($fh, $NoRelaying."\r\n");
            return;
        }

        if (   (matchSL($uh,'InternalAddresses')  &&  ! localmail($this->{mailfrom}))
            || (matchSL($uh,'$InternalAndWhiteAddresses') && ! ( localmail($this->{mailfrom}) || Whitelist($this->{mailfrom},$uh, undef)) )
           )
        {
            NoLoopSyswrite($fh, $NoRelaying."\r\n");
            $this->{prepend} = "[InternalAddress]";
            mlog($fh,"invalid remote sender for internal address: $uh");
            pbAdd($fh,$this->{ip},'iaValencePB',"internaladdress:$uh") ;
            $Stats{internaladdresses}++;
            delayWhiteExpire($fh);
            done($fh);
            return;
        }

        if ($noScan) {

            if ( matchSL( "$this->{nocollect}", 'noScan' ) ) {
                $this->{noscan} = 1;

            }
        }

        my $ret = matchIP( $this->{ip}, 'noProcessingIPs', $fh ); 
        if ($noProcessingIPs
                && $ret
                &&  !matchIP( $this->{ip}, 'NPexcludeIPs', 0, 1 )
                &&  !$this->{relayok}
                && !$this->{nonoprocessing}
                 )
        {
            $this->{noprocessing}          = 1;
            $this->{passingreason}         = "noProcessingIPs '$ret'" if !$this->{passingreason};
        }

       if (!$this->{relayok} && !$this->{addressedToSpamBucket}) {

        $this->{spamfriends} = "$u$h" if matchSL( "$u$h", 'spamFriends' );

        $this->{subjectsl} = matchSL( "$u$h", 'spamLoverSubjectSelected' );

        my $mSLRE     = matchSL($uh,'spamLovers')      and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'spamLovers'}},$uh));
        my $mBSLRE    = matchSL($uh,'baysSpamLovers')  and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'baysSpamLovers'}},$uh));
        my $mBLSLRE   = matchSL($uh,'blSpamLovers')    and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'blSpamLovers'}},$uh));
        my $mHLSLRE   = matchSL($uh,'hlSpamLovers')    and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'hlSpamLovers'}},$uh));
        my $mHISLRE   = matchSL($uh,'hiSpamLovers')    and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'hiSpamLovers'}},$uh));
        my $mBOSLRE   = matchSL($uh,'bombSpamLovers')  and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'bombSpamLovers'}},$uh));
        my $mPTRSLRE  = matchSL($uh,'ptrSpamLovers')   and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'ptrSpamLovers'}},$uh));
        my $mMXASLRE  = matchSL($uh,'mxaSpamLovers')   and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'mxaSpamLovers'}},$uh));

        my $mRBLSLRE  = matchSL($uh,'rblSpamLovers')   and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'rblSpamLovers'}},$uh));
        my $mURIBLSLRE= matchSL($uh,'uriblSpamLovers') and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'uriblSpamLovers'}},$uh));
        my $mSRSSLRE  = matchSL($uh,'srsSpamLovers')   and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'srsSpamLovers'}},$uh));
        my $mDLSLRE = $this->{dlslre} = matchSL( "$u$h", 'delaySpamLovers' );

        my $mPBSLRE   = matchSL($uh,'msSpamLovers')    and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'msSpamLovers'}},$uh));
        my $mSBSLRE   = matchSL($uh,'sbSpamLovers')    and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'sbSpamLovers'}},$uh));
        my $mATSLRE   = matchSL($uh,'atSpamLovers')    and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'atSpamLovers'}},$uh));

        $this->{spamMaxScore} = $MessageScoringUpperLimit if $this->{spamMaxScore} < $MessageScoringUpperLimit;
        $this->{spamMaxScore} = $slMaxScore if $slMaxScore > $this->{spamMaxScore};

        $this->{nodelay} = 'noDelayAddresses' if matchSL("$u$h",'noDelayAddresses');

        $this->{spamlover} = $this->{spamloverall} = 1 if $mSLRE;

        my $mWLORE = matchSL($uh,'WhitelistOnlyAddresses');

        if ( $rcptislocal && ( $mWLORE || $WhitelistOnly ) ) {
            $this->{allwhitelist} |= 1;
        } else {
            $this->{allwhitelist} |= 2;
        }

        if (
                   $mSLRE
                || $mBSLRE
                || $mBLSLRE
                || $mBOSLRE
                 || $mPTRSLRE
                || $mMXASLRE
                || $mHLSLRE
                || $mHISLRE

                || $mURIBLSLRE
                || $mRBLSLRE

                || $mPBSLRE
                || $mSBSLRE
                || $mATSLRE

          )
        {
            $this->{allLoveSpam} |= 1;
            $rcptislocal = 1;
        } else {
            $this->{allLoveSpam} = 2;
        }
        if ( $rcptislocal && ( $mBSLRE || $mSLRE ) ) {
            $this->{allLoveBaysSpam} |= 1;
        } else {
            $this->{allLoveBaysSpam} = 2;
        }
        if ( $rcptislocal && ( $mBLSLRE || $mSLRE ) ) {
            $this->{allLoveBlSpam} |= 1;
        } else {
            $this->{allLoveBlSpam} = 2;
        }
        if ( $rcptislocal && ( $mBOSLRE || $mSLRE ) ) {
            $this->{allLoveBoSpam} |= 1;
        } else {
            $this->{allLoveBoSpam} = 2;
        }

        if ( $rcptislocal && ( $mHLSLRE || $mSLRE ) ) {
            $this->{allLoveHlSpam} |= 1;
        } else {
            $this->{allLoveHlSpam} = 2;
        }
        if ( $rcptislocal && ( $mHISLRE || $mSLRE ) ) {
            $this->{allLoveHiSpam} |= 1;
        } else {
            $this->{allLoveHiSpam} = 2;
        }

        if ( $rcptislocal && ( $mRBLSLRE || $mSLRE ) ) {
            $this->{allLoveRBLSpam} |= 1;
        } else {
            $this->{allLoveRBLSpam} = 2;
        }
        if ( $rcptislocal && ($mATSLRE) ) { 
            $this->{allLoveATSpam} |= 1; }
        else  { 
            $this->{allLoveATSpam} = 2; }

        if ( $rcptislocal && ( $mURIBLSLRE || $mSLRE ) ) {
            $this->{allLoveURIBLSpam} |= 1;
        } else {
            $this->{allLoveURIBLSpam} = 2;
        }

        if ( $rcptislocal && ( $mDLSLRE ) ) {
            $this->{allLoveDLSpam} |= 1;
        } else {
            $this->{allLoveDLSpam} = 2;
        }
        if ( $rcptislocal && ( $mPBSLRE || $mSLRE ) ) {
            $this->{allLovePBSpam} |= 1;
        } else {
            $this->{allLovePBSpam} = 2;
        }
        if ( $rcptislocal && ( $mSBSLRE || $mSLRE ) ) {
            $this->{allLoveSBSpam} |= 1;
        } else {
            $this->{allLoveSBSpam} = 2;
        }

        }

        if (! $this->{whitelisted} && $whiteListedDomains && $this->{mailfrom} =~ /($WLDRE)/ ) {
            mlog( $fh, "whitelisted by whitedomainlist: '$1'", 1 );

            $this->{passingreason} = "whiteListedDomains '$1'" if !$this->{passingreason};
            $this->{whitelisted}=1;
        }        

        $this->{rcptValidated} = $this->{rcptNonexistent} = 0;

        if ( $this->{addressedToSpamBucket} ) {
            $this->{delayed} = "";
            # accept SpamBucket addresses in every case
            $this->{rcpt} .= "$u$h ";

        } elsif ( $LocalAddresses_Flat
        ||     $DoLDAP 
        || &isvrfy ($fh, "$u$h") )

        {
            if (   ( $this->{islocalmailaddress} )
                || ( $this->{relayok} ) && !$rcptislocal )
            {
                $this->{prepend} = "[LocalRelay]";
                if ( !$this->{relayok} ) {
                    if ( !Delayok( $fh, "$u$h" ) && $EnableDelaying ) {
                        $this->{delayqueue} .= "$u$h ";
                        $this->{rcpt}       .= "$u$h ";
                        $this->{delayed} = 1;
                        mlog( $fh, "recipient delaying queued: $u$h" )
                          if $DelayLog >= 2;
                        sendque( $server, $l );
                        return;
                    }
                }
                $this->{donotdelay} = 1;
                $this->{rcpt} .= "$u$h ";
                mlog( $fh, "recipient accepted: $u$h" )
                  if $this->{alllog} or $ValidateUserLog >= 2;
                $this->{rcptValidated} = 1;
            } elsif ( $calist{$h} ) {
                my $uhx = $calist{$h} . "@" . $h;
                $this->{prepend} = "[InvalidAddress]";
                mlog( $fh, "invalid address $uh replaced with $uhx" )
                  if $this->{alllog} or $ValidateUserLog >= 2;
                $this->{rcpt} .= "$uhx ";
                $this->{messagereason} = "invalid address $uh";
                pbTrapAdd( $fh, "$uh" );
                pbAdd( $fh, $this->{ip}, 'irValencePB', 'InvalidAddress' );
                $Stats{rcptNonexistent}++;
                $this->{rcptValidated} = 1;
                $l = "RCPT TO:\<$uhx\>\r\n";
                if (matchSL("$uhx",'NullAddresses')) {
                    $this->{getline} = \&NullFromToData;
                    &NullFromToData($fh,$l);
                        return;
                }
            } elsif ( $CatchallallISP2NULL && $this->{ispip} ) {
                $this->{prepend} = "[InvalidAddress]";
                mlog( $fh, "invalid address $u$h from ISP moved to NULL-connection" )
                  if $this->{alllog} or $ValidateUserLog >= 2;
                $this->{rcptValidated} = 1;
                $Stats{rcptNonexistent}++;
                $this->{getline} = \&NullFromToData;
                &NullFromToData( $fh, $l );
                return;
           } elsif ($CatchAllAll) {
                my $uhx = $CatchAllAll;
                $this->{prepend} = "[InvalidAddress]";
                mlog( $fh, "invalid address $uh replaced with $uhx" )
                  if $this->{alllog} or $ValidateUserLog >= 2;
                $this->{rcpt} .= "$uhx ";
                $this->{messagereason} = "invalid address $uhx";
                pbTrapAdd( $fh, "$uhx" );
                pbAdd( $fh, $this->{ip}, 'irValencePB', "UserUnknown" ) ;
                $Stats{rcptNonexistent}++;
                $this->{rcptValidated} = 1;
                $l = "RCPT TO:\<$uhx\>\r\n";
                if (matchSL("$uhx",'NullAddresses')) {
                    $this->{getline} = \&NullFromToData;
                    &NullFromToData($fh,$l);
                    return;
                }
           } else {
                $this->{prepend} = "[InvalidAddress]";
                $this->{messagereason}="invalid address $uh";
                mlog($fh,"User unknown: $uh") if $this->{alllog} or $ValidateUserLog == 1 or $ValidateUserLog == 2;
                pbTrapAdd($fh,"$uh");
                pbAdd($fh,$this->{ip},'irValencePB',"UserUnknown")  if !$this->{nonoprocessing};
                $Stats{rcptNonexistent}++;
                $this->{rcptNonexistent}=1;
                my $reply;
                if ($NoValidRecipient) {
                    $reply = $NoValidRecipient."\r\n";
                    $reply =~ s/EMAILADDRESS/$u$h/go;
                } else {
                    $reply = "550 5.1.1 User unknown\r\n";
                }
                if ($reply =~ /^5/) {
                    if ( ($this->{userTempFail} &&
                          $DoVRFY &&
                          $CanUseNetSMTP &&
                          (! ($DoLDAP && $CanUseLDAP) or
                             ($DoLDAP && $CanUseLDAP && $LDAPoffline)
                          )
                         ) or
                         ($DoLDAP && $CanUseLDAP && $LDAPoffline &&
                          (! ($DoVRFY && $CanUseNetSMTP) or
                             ($DoVRFY 
                              && $CanUseNetSMTP 
                              && !$this->{userTempFail} 
                              && $uh =~ /\@([^@]*)/o 
                              && (&matchHashKey('DomainVRFYMTA', lc $2 )
                            or &matchHashKey('FlatVRFYMTA', lc "\@$2" ) )
                             )
                          )
                         )
                       )
                    {
                        $reply =~ s/^\d{3}(?: \d+\.\d+\.\d+)?/450/;
                    }
                }

                sendque( $fh, $reply );

                # increment error and drop line if necessary
                if(++$this->{serverErrors} >= $MaxErrors && !$this->{noprocessing}) {
                    $this->{prepend}       = "[MaxErrors]";
                    $this->{messagereason} = "max errors (MaxErrors=$MaxErrors) exceeded";
                    mlog($fh,"max errors (MaxErrors=$MaxErrors) exceeded -- dropping connection - after invalid address") if $ValidateUserLog;
                    pbAdd( $fh, $this->{ip}, 'meValencePB', "MaxErrors", 2 ) ;

                    $Stats{msgMaxErrors}++;
                    delayWhiteExpire($fh);
                    done($fh);
                }
                return;
            }
        } elsif ( !$this->{relayok} ) {

            if ( $EnableDelaying && ! Delayok( $fh, "$u$h" ) ) {
                $this->{delayqueue} .= "$u$h ";
                $this->{rcpt}       .= "$u$h ";
                $this->{delayed} = 1;
                mlog( $fh, "recipient delaying queued: $u$h", 1 )
                  if $this->{alllog}
                      or $DelayLog >= 2;
                sendque( $server, $l );
                return;
            }
            $this->{rcpt} .= "$u$h ";
        } else {
            $this->{red} = $this->{redlist} = "$u$h in RedList"
              if ( $Redlist{"$u$h"}
                || $Redlist{"*@$h"}
                || $Redlist{"$wildcardUser@$h"} );
            $this->{rcpt} .= "$u$h ";
            mlog( $fh, "recipient accepted without delaying: $u$h", 1 )
              if $this->{alllog}
                  or $ValidateUserLog == 2;
            $this->{donotdelay}    = 1;
            $this->{rcptValidated} = 1;
        }

        # update Stats
        if ( $this->{rcptnoprocessing} == 1 ) {
            $Stats{rcptUnprocessed}++;
        } elsif ( $this->{addressedToSpamBucket} ) {
            $Stats{rcptSpamBucket}++;
        } elsif ( $this->{allLoveSpam} & 1 ) {
            $Stats{rcptSpamLover}++;
        } elsif ( $this->{rcptValidated} ) {
            $Stats{rcptValidated}++;
        } elsif ( $this->{rcptNonexistent} ) {
            $Stats{rcptNonexistent}++;
        } elsif ($rcptislocal) {
            $Stats{rcptUnchecked}++;
        } elsif ( $Whitelist{ lc "$u$h" } ) {
            pbWhiteAdd( $fh, $this->{ip}, "whitelisted:$u$h" );
            $Stats{rcptWhitelisted}++;
        } else {
            $Stats{rcptNotWhitelisted}++;
        }
        $this->{numrcpt} = 0;    # calculate the total number of rcpt
        foreach ( split( / /, $this->{rcpt} ) ) { $this->{numrcpt}++ }
        $this->{numrcpt} = 1 if ( $this->{numrcpt} == 0 );
    } elsif ( $l =~ /^ *XEXCH50 +(\d+)/i ) {
        $this->{skipbytes} = $1;
        d("XEXCH50 b=$1");
    } elsif ( $l =~ /^ *DATA/i || $l =~ /^ *BDAT (\d+)/i ) {
        $this->{lastcmd} = "DATA" if $l =~ /^ *DATA/i;
        if ($1) {
            $this->{bdata} = $1;
        } else {
            delete $this->{bdata};
        }
        $this->{rcpt} =~ s/\s$//;

        # drop line if no recipients left
        if ( $this->{rcpt} !~ /@/ ) {

            # possible workaround for GroupWise bug
            if ( $this->{delayed} ) {
                if ($DelayError) {
                    $reply = $DelayError;
                } else {
                    $reply = "451 4.7.1 Please try again later";
                }
                $this->{prepend} = "[Delay-Data]";
                mlog( $fh, "DATA phase delayed" ) if $DelayLog;
                $reply = replaceerror ($fh, $reply);
                seterror($fh, $reply,1);

                $Stats{msgDelayed}++ if ( !$this->{StatsmsgDelayed} );
                $this->{StatsmsgDelayed} = 1;
                return;
            }
            $this->{prepend} = "[NoRecipients]";
            mlog( $fh, "no recipients left -- dropping connection" )
              if $DelayLog || $ValidateUserLog == 2;
            $Stats{msgNoRcpt}++;
            delayWhiteExpire($fh);
            pbAdd( $fh, $this->{ip}, 'erValencePB', "NeedRecipient", 2 );
            seterror( $fh, "554 5.7.8 Need Recipient", 1 );
            done($fh);
            return;
        }
        if ( !$this->{noprocessing} && $noProcessingTo && !$this->{relayok} &&  allNoProcessingTo( $this->{rcpt}, $fh ) ) 
            { 
            $this->{noprocessing} = 1;
            $this->{passingreason} = "noProcessingTo '$this->{newrcpt}'" if !$this->{passingreason}
            }

        if (!$this->{noprocessing} && matchSL( $this->{mailfrom}, 'noProcessingFrom' ) ) 
            { 
            $this->{noprocessing} = 1;
            $this->{passingreason} = "noProcessingFrom '$this->{mailfrom}'" if  !$this->{passingreason};
            }

        if (!$this->{noprocessing} && matchSL( $this->{mailfrom}, 'noProcessing' ) ) 
            { 
            $this->{noprocessing} = 1;
            $this->{passingreason} = "noProcessing '$this->{mailfrom}'" if  !$this->{passingreason};
            }

        if ( $this->{isbounce} && $this->{delayed} ) {
            &NumRcptOK($fh,0) if $this->{relayok};
            $this->{prepend} = "[Delay-Bounce]";
            if ($DelayError) {
                $reply = $DelayError;
            } else {
                $reply = "451 4.7.1 Please try again later";
            }
            mlog( $fh, "bounce delayed" ) if $DelayLog;
            seterror($fh, $reply,1);
            $Stats{msgDelayed}++ if ( !$this->{StatsmsgDelayed} );
            $this->{StatsmsgDelayed} = 1;
            return;
        } elsif ( $this->{relayok} && (my $nextTry = &localFrequencyNotOK($fh)) ) {
            $nextTry = &timestring($nextTry);
            $reply = "452 too many recipients for $this->{mailfrom} in $LocalFrequencyInt seconds - please try again not before $nextTry or send a notification message to your postmaster\@LOCALDOMAIN or local administrators\r\n";
            my $mfd;
            $mfd = $1 if lc $this->{mailfrom} =~ /\@([^@]*)/o;
            $reply =~ s/LOCALDOMAIN/$mfd/go;
            seterror($fh, $reply,1);
            $this->{prepend}       = "[LocalFrequency]";
            mlog($fh,"warning: too many recipients (more than $LocalFrequencyNumRcpt in the last $LocalFrequencyInt seconds, $this->{numrcpt} in this mail) ($this->{ip}) for $this->{mailfrom} - possible local abuse" );
            $Stats{localFrequency}++;
            my $mfr = batv_remove_tag(0,lc $this->{mailfrom},'');
            if (! exists $localFrequencyNotify{$mfr} ||
                 $localFrequencyNotify{$mfr} < time)
            {
                $localFrequencyNotify{$mfr} = int((time + 86400) / 86400) * 86400;  # 24:00 today
                mlog($fh,"notification: too many recipients (more than $LocalFrequencyNumRcpt in the last $LocalFrequencyInt seconds, $this->{numrcpt} in this mail)($this->{ip}) for $mfr - possible local abuse" );
            }
            return;
        } else {
            if ( !$this->{donotdelay} ) {    # if there is a queued delay
                delete $this->{donotdelay};   # and the rcpt to: phase is passed
                if ( $this->{delayqueue} ) {  # and no valid recpt -> delay
                    if ( !$this->{isbounce} ) {
                        &NumRcptOK($fh,0) if $this->{relayok};
                        $this->{prepend} = "[Delay-Queue]";
                        if ($DelayError) {
                            $reply = $DelayError;
                        } else {
                            $reply = "451 4.7.1 Please try again later";
                        }

                        for ( split( ' ', $this->{delayqueue} ) ) {
                            mlog( $fh, "recipient delayed: $_" )
                              if $DelayLog;
                        }

                        seterror($fh, $reply,1);
                        delete $this->{delayqueue};
                        $Stats{msgDelayed}++ if ( !$this->{StatsmsgDelayed} );
                        $this->{StatsmsgDelayed} = 1;

                        return;
                    }
                }
            } else {
                if ( $this->{delayqueue} ) {
                    for ( split( ' ', $this->{delayqueue} ) ) {
                        mlog( $fh, "queued delay removed for recipient: $_", 1 )
                          if $DelayLog >= 2;
                        mlog( $fh, "recipient accepted: $_", 1 )
                          if $this->{alllog}
                              or $ValidateUserLog == 2;
                        $Stats{rcptDelayed}--;
                        $Stats{rcptValidated}++;
                    }
                    delete $this->{delayqueue};
                }
            }

            MaillogStart($fh);    # notify the stream logging to start logging
            $this->{getline} = \&getheader;
        }
    } elsif ( $l =~ /^ *RSET/i ) {
        stateReset($fh);          # reset everything
    }
    if (uc($this->{lastcmd}) =~ /NOOP/o) {
        $this->{NOOPcount}++;
    } else {
        $this->{NOOPcount} = 0;
    }
    sendque( $server, $l );
}

sub isvrfy {
        my ( $fh, $uh ) = @_;
        my $this = $Con{$fh};
        $uh =~ /^(.*@)(.*)$/;
        my $h = $2;
        return 1 if $DoVRFY && $CanUseNetSMTP && (&matchHashKey('DomainVRFYMTA', lc $2 ) or &matchHashKey('FlatVRFYMTA', lc "\@$2" ) );
}

# compile the helo-blacklist ignore regular expression
sub setHBIRE {
    SetRE( 'HBIRE', "^($_[0])\$", "i", "HELO Blacklisted Ignore" );
}

sub emailInterface {
    my ( $fh, $u,  $h, $l) = @_;
    my $this = $Con{$fh};
    my $uh = "$u$h";
    $this->{isadmin} = (matchSL( $this->{mailfrom}, 'EmailAdmins') or $this->{mailfrom} && lc $this->{mailfrom} eq lc $EmailAdminReportsTo);
    $this->{prepend} = "[EmailInterface]";
    if ( $EmailInterfaceOk && $this->{senderok} ne '2' && $this->{senderok} ne '3'
            && ( $this->{relayok} || $this->{externalsenderok}  )

           )
        {
           alarm 0;
            if(lc $u eq lc "$EmailSpam\@") {
                $this->{reporttype}=0;
                $this->{reportaddr} = 'EmailSpam';
                  $this->{getline}    =  \&SpamReport;
                mlog( $fh, "email: spam report" ) if !$EmailErrorsRemoveWhite;
                mlog( $fh, "email: combined spam report & whitelist deletion request") if $EmailErrorsRemoveWhite;
                $Stats{rcptReportSpam}++;
                sendque($fh,"250 OK\r\n");
                return;
            } elsif(lc $u eq lc "$EmailHam\@") {
                $this->{reporttype}=1;
                $this->{reportaddr} = 'EmailHam';
                $this->{getline}    =  \&SpamReport;
                mlog( $fh, "email: notspam report" ) if !$EmailErrorsModifyWhite;
                mlog( $fh, "email: combined notspam report & whitelist addition request" ) if $EmailErrorsModifyWhite == 1;
                $Stats{rcptReportHam}++;
                sendque($fh,"250 OK\r\n");
                return;
            } elsif(lc $u eq lc "$EmailWhitelistAdd\@") {
                $this->{reporttype}=2;
                $this->{reportaddr} = 'EmailWhitelistAdd';
                $this->{getline}=\&ListReport;
                mlog($fh,"email: whitelist addition request" );
                $Stats{rcptReportWhitelistAdd}++;
                foreach my $ad (split(/ /o,$this->{rcpt})) {ListReportExec($ad,$this)};
                sendque($fh,"250 OK\r\n");
                return;
            } elsif(lc $u eq lc "$EmailWhitelistRemove\@") {
                $this->{reporttype}=3;
                $this->{reportaddr} = 'EmailWhitelistRemove';
                $this->{getline}=\&ListReport;
                mlog($fh,"email: whitelist deletion request");
                $Stats{rcptReportWhitelistRemove}++;
                foreach my $ad (split(/ /o,$this->{rcpt})) {ListReportExec($ad,$this)};
                sendque($fh,"250 OK\r\n");
                return;
            } elsif(lc $u eq lc "$EmailRedlistAdd\@") {
                $this->{reporttype}=4;
                $this->{reportaddr} = 'EmailRedlistAdd';
                $this->{getline}=\&ListReport;
                mlog($fh,"email: redlist addition request");
                $Stats{rcptReportRedlistAdd}++;
                foreach my $ad (split(/ /o,$this->{rcpt})) {ListReportExec($ad,$this)};
                sendque($fh,"250 OK\r\n");
                return;
            } elsif(lc $u eq lc "$EmailRedlistRemove\@") {
                $this->{reporttype}=5;
                $this->{reportaddr} = 'EmailRedlistRemove';
                $this->{getline}=\&ListReport;
                mlog($fh,"email: redlist deletion request");
                $Stats{rcptReportRedlistRemove}++;
                foreach my $ad (split(/ /o,$this->{rcpt})) {ListReportExec($ad,$this)};
                sendque($fh,"250 OK\r\n");
                return;

            } elsif(lc $u eq lc "$EmailSpamLoverAdd\@") {
                $this->{reporttype}=10;
                $this->{reportaddr} = 'EmailSpamLoverAdd';
                $this->{getline}=\&ListReport;
                mlog($fh,"email spamlover addition report");
                foreach my $ad (split(/ /o,$this->{rcpt})) {ListReportExec($ad,$this)};
                sendque($fh,"250 OK\r\n");
                return;
            } elsif(lc $u eq lc "$EmailSpamLoverRemove\@") {
                $this->{reporttype}=11;
                $this->{reportaddr} = 'EmailSpamLoverRemove';
                $this->{getline}=\&ListReport;
                mlog($fh,"email spamlover deletion report");
                foreach my $ad (split(/ /o,$this->{rcpt})) {ListReportExec($ad,$this)};
                sendque($fh,"250 OK\r\n");
                return;
            } elsif(lc $u eq lc "$EmailNoProcessingAdd\@") {
                $this->{reporttype}=12;
                $this->{reportaddr} = 'EmailNoProcessingAdd';
                $this->{getline}=\&ListReport;
                mlog($fh,"email: noprocessing addition request");
                foreach my $ad (split(/ /o,$this->{rcpt})) {ListReportExec($ad,$this)};
                sendque($fh,"250 OK\r\n");
                return;
            } elsif(lc $u eq lc "$EmailNoProcessingRemove\@") {
                $this->{reporttype}=13;
                $this->{reportaddr} = 'EmailNoProcessingRemove';
                $this->{getline}=\&ListReport;
                mlog($fh,"email noprocessing deletion report");
                foreach my $ad (split(/ /o,$this->{rcpt})) {ListReportExec($ad,$this)};
                sendque($fh,"250 OK\r\n");
                return;
            } elsif ( lc $u eq lc "$EmailBlackAdd\@" ) {
                $this->{reporttype} = 14;
                $this->{reportaddr} = 'EmailBlackAdd';
                $this->{getline}    = \&ListReport;
                mlog( $fh, "email blacklist addition report" );
                sendque( $fh, "250 OK\r\n" );
                return;
            } elsif ( lc $u eq lc "$EmailBlackRemove\@" ) {
                $this->{reporttype} = 15;
                $this->{reportaddr} = 'EmailBlackRemove';
                $this->{getline}    = \&ListReport;
                mlog( $fh, "email blacklist deletion report" );
                sendque( $fh, "250 OK\r\n" );
                return;
            } elsif ( lc $u eq lc "$EmailPersBlackAdd\@" ) {
                $this->{reporttype} = 16;
                $this->{reportaddr} = 'EmailPersBlackAdd';
                $this->{getline}    = \&ListReport;
                mlog( $fh, "email personal blacklist addition report", 1 );
                sendque( $fh, "250 OK\r\n" );
                return;
            } elsif ( lc $u eq lc "$EmailPersBlackRemove\@" ) {
                $this->{reporttype} = 17;
                $this->{reportaddr} = 'EmailPersBlackRemove';
                $this->{getline}    = \&ListReport;
                mlog( $fh, "email personal blacklist deletion report", 1 );
                sendque( $fh, "250 OK\r\n" );
                return;
            }
        } 
        if (     $EmailInterfaceOk

                   && ( $this->{relayok} || $this->{externalsenderok}  )

                )
        {

             if ( lc $u eq lc "$EmailHelp\@" ) {
                $this->{reporttype} = 7;
                $this->{reportaddr} = 'EmailHelp';
                $this->{getline}    = \&HelpReport;
                mlog( $fh, "email: help-report request");
                $Stats{rcptReportHelp}++;
                sendque( $fh, "250 OK\r\n" );
                return;
            } elsif(lc $u eq lc "$EmailAnalyze\@") {
                $this->{reporttype}=8;
                $this->{reportaddr} = 'EmailAnalyze';
                $this->{getline}=\&AnalyzeReport;
                mlog( $fh, "email: analyze-report request");
                $Stats{rcptReportAnalyze}++;
                sendque($fh,"250 OK\r\n");
                return;
            } elsif(lc $u eq lc "$EmailBlockReport\@" or $u =~ /^RSBM_.+?$maillogExt\@$/i) {
                $this->{rcpt}="$u$h";
                $this->{reporttype}=9;
                $this->{reportaddr} = 'EmailBlockReport';
                $this->{getline}=\&BlockReport;
                mlog($fh,"email: request for blocked spam report" );
                sendque($fh,"250 OK\r\n");
                return;
            } 

            ReturnMail($fh,$this->{mailfrom},"$base/reports/denied.txt",'assp-error',"\n") if ($this->{senderok} eq '2');
            $this->{getline} = \&NullFromToData;
            &NullFromToData($fh,$l);
            mlog($fh,"denied connection to email interface ($uh) moved to NULL-connection" );
            return;

        }
}       
sub makeSubject {
        my $fh = shift;
        my $this = $Con{$fh};
        return if $Con{$fh}->{subject2};

        my $sub;
        $sub = $1 if (substr($Con{$fh}->{header},0,index($Con{$fh}->{header},"\015\012\015\012")) =~ /\015\012Subject: *($HeaderValueRe)/iso);
        if (!$sub && $Con{$fh}->{maillogbuf}) {
            $sub = $1 if (substr($Con{$fh}->{maillogbuf},0,index($Con{$fh}->{maillogbuf},"\015\012\015\012")) =~ /\015\012Subject: *($HeaderValueRe)/iso);
        }
        $sub =~ s/\r\n\s*//go;
        my $slength = length $sub;
        if ($slength > 2000) {
            delayWhiteExpire($fh);
            $Con{$fh}->{prepend} = "[SubjectBomb]";
            mlog( $fh, "Subject exploit attempt with $slength bytes");
            $sub = substr($sub,0,50);
            seterror( $fh, "554 5.7.1 Subject exploit attempt with $slength bytes", 1 );
            return;

        }
        headerUnwrap($sub);
        return unless $sub;
        $sub =~ s/\r|\n|\t//go;
        $Con{$fh}->{subject2}=$sub;
        $Con{$fh}->{subject2} =~ s/$NONPRINT//go;
        $sub=decodeMimeWords2UTF8($sub);
        $sub = d8($sub);
        $Con{$fh}->{subject3} = $sub;
        $Con{$fh}->{subject3} =~ s/_{2,}/_/go;
        $sub =~ s/[^a-zA-Z0-9]/_/go;
        $sub =~ s/_{2,}/_/go;

        $Con{$fh}->{originalsubject}=$sub;
        $Con{$fh}->{originalsubject} =~ tr/_/ /;
        $Con{$fh}->{originalsubject} =~ s/\s+$//o;
        $Con{$fh}->{originalsubject} =~ s/^\s+//o;

        $Con{$fh}->{originalsubject} = $Con{$fh}->{subject3} if $LogCharset;      
        $Con{$fh}->{originalsubject} = substr($Con{$fh}->{originalsubject},0,50) .
                                       '...' .
                                       substr($Con{$fh}->{originalsubject},length($Con{$fh}->{originalsubject})-50,50)
                     if length($Con{$fh}->{originalsubject}) > 100;
        $Con{$fh}->{subject}=substr($sub,0,50);
        $Con{$fh}->{subject} = e8($Con{$fh}->{subject});
        $Con{$fh}->{originalsubject} = e8($Con{$fh}->{originalsubject});
        $Con{$fh}->{subject3} = e8($Con{$fh}->{subject3});

        $Con{$fh}->{logsubject} =
            ( $subjectLogging ? "$subjectStart$Con{$fh}->{originalsubject}$subjectEnd" : "" );

}

# get the header length of the DATA.
sub getheaderLength {
    my $fh = shift;
    return 0 unless $fh;
    my $l = 0;
    if (ref($fh) && ref($fh) ne 'SCALAR' && exists $Con{$fh}) {
        return 0 unless $Con{$fh}->{headerpassed};
        $l = index($Con{$fh}->{header}, "\x0D\x0A\x0D\x0A");
        return ($l >= 0 ? $l + 4 : 0);
    }
    return 0 unless length(ref($fh)?$$fh:$fh);
    $l = index((ref($fh)?$$fh:$fh), "\x0D\x0A\x0D\x0A");
    return ($l >= 0 ? $l + 4 : 0);
}

# get the header part of the DATA.
sub getheader {
    my ( $fh, $l ) = @_;
    d('getheader');
    my $reply;
    my $done;
    my $fn;
    my $er;
    my $done2;
    my $this = $Con{$fh};

    if($this->{inerror} or $this->{intemperror}) {  # got 4/5xx from MTA - possibly next step after DATA
        if ($send250OK or ($this->{ispip} && $send250OKISP)) {
            $this->{prepend} = "[250OK]";
            mlog($fh,"info: connection is moved to NULL after MTA has sent an error reply in DATA part") if $ConnectionLog;
            $this->{getline}=\&NullData;
            NullData($fh,$l);
            return;
        }
        $this->{cleanSMTPBuff} = 1;         # delete the SMTPbuff
        $this->{header} = '';
        $this->{getline}=\&getline;
        getline($fh,$l);
        return;
    }

     my $ret = matchIP( $this->{ip}, 'noProcessingIPs', $fh );
     if (!$this->{noprocessing} && $noProcessingIPs
            && $ret
            &&  !matchIP( $this->{ip}, 'NPexcludeIPs', 0, 1 )
            && !$this->{nonoprocessing}
            )
            {
                $this->{noprocessing}          = 1;
                $this->{passingreason}         = "noProcessingIPs '$ret'" if !$this->{passingreason};
            }
    $this->{relayok} = 1 if matchIP($this->{ip}, 'acceptAllMail',   0, 1 ); 
    if($this->{spamblocked} or $this->{inerror} or $this->{inerror} or $this->{intemperror} ) {
        my $server = $this->{friend};
        $this->{getline} = \&getline;
        sendque( $server, $l );
        done($fh);
        return;
    }

    $this->{header} .= $l;
    my $headerlength=length($this->{header});
    my $maxheaderlength=$HeaderMaxLength;

    if($HeaderMaxLength && $headerlength>$maxheaderlength && $this->{prepend} !~ /OversizedHeader/) {
        delayWhiteExpire($fh);
        $this->{prepend} = "[OversizedHeader]";
        mlog($fh,"Possible Mailloop: Headerlength ($headerlength) > $maxheaderlength");
        seterror($fh,"554 5.7.1 possible mailloop - oversized header ($headerlength)",1);
        $Stats{msgverify}++;
        return;
    }

    if (   scalar keys %MEXH
        && ! $this->{relayok}
        && $this->{prepend} !~ /Max-Equal-X-Header/
        && ! $this->{noprocessing} 
        && ! $this->{whitelisted}
        && $l =~ /^X-(?!ASSP)/io
        && $l !~ /X-Notes-Item/i) 
    {
        my $line = $l;
        $line =~ s/\r?\n//go;
        my ($xh) = $line =~ /^($HeaderNameRe)\:/o;
        my $maxval;
        $maxval = matchHashKey(\%MEXH,$xh) if $xh;
        if ($xh && $maxval && ++$this->{Xheaders}{lc $xh} > $maxval) {
            delayWhiteExpire($fh);
            $this->{prepend} = "[Max-Equal-X-Header]";
            mlog($fh,"too many (MaxEqualXHeader = $maxval) equal X-header lines '$xh'");
            seterror($fh,"554 5.7.7 too many ($maxval) equal X-headers '$xh'",1);
            $Stats{msgverify}++;
            return;
        }
    }
    if (! $this->{relayok} && ! $this->{received}) {
        $this->{received} = $l =~ /^(?:Received:)|(?:Origin(?:at(?:ing|ed))?|Source)[\s\-_]?IP:/oi;
    }
    my $orgnp;
    if ( $l =~ /^\.?[\r\n]*$/ ) {
        $done2 = $l=~/^\.[\r\n]+$/o;
        $orgnp = $this->{noprocessing};
        $this->{noprocessing} = 0 if $this->{noprocessing} eq '2';  # noprocessing on message size
        $this->{headerpassed} = 1;
        $this->{maillength} = $this->{headerlength} = $headerlength;
        $this->{headerlength} -= 3 if $done2;
        $this->{headerlength} = 0 if $this->{headerlength} < 0;
        my $slok;
        $this->{localmail} = localmail($this->{mailfrom});
        if (! $this->{from} && $this->{header} =~ /(?:^|\n)from:($HeaderValueRe)/oi) {
            my $from = $1;
            headerUnwrap($from);
            $this->{from} = $1 if $from =~ /($EmailAdrRe\@$EmailDomainRe)/oi;
        }
        if ($this->{addressedToSpamBucket}){ 
            SpamBucketOK($fh, $done2);
            return 1;
        }
        if ( !$this->{noprocessing} && !$this->{whitelisted} &&  !$this->{relayok}){
            HistoryOK( $fh, $this->{ip} );
            }

        if(! &MailLoopOK($fh)) {
            $this->{prepend} = "[MailLoop]";
            mlog($fh,"warning: possible mailloop - found own received header more than $detectMailLoop times");
            sendque( $fh, "250 OK\r\n" );
            $Con{$fh}->{getline} = \&NullFromToData;
            $Stats{msgverify}++;
            return;
        }
        my $tip = $this->{ip};
        $tip = $this->{cip} if $this->{cip};
        if (   !$this->{relayok}
            && !$this->{contentonly}
            && pbWhiteFind($tip) )
        {
            $this->{contentonly} = "whitebox:$tip";

        }

        if (   !$this->{relayok}
            && !$this->{contentonly}
            && $contentOnlyRe
            && $contentOnlyReRE != ""
            && $this->{header} =~ ( '(' . $contentOnlyReRE . ')' ) )
        {
            $this->{contentonly} = $1;

            pbBlackDelete( $fh, $this->{ip} );           

        }

        if ( $allLogRe
             && ! $this->{alllog}
             && $this->{header} =~ /$allLogReRE/ )
        {
            $this->{alllog}=1;
        }

        if ( ! $this->{red}
            && $this->{header} =~ /(auto-submitted\:|subject\:.*?auto\:)/i )
            # RFC 3834
        {
            d('isred auto');
            $this->{red} = ($1||$2);
        }

        if ( ! $this->{red}
            && $redRe
            && $this->{header} =~ /($redReRE)/ ) {
            $this->{red} = ($1||$2);
            mlogRe( $fh, $this->{red}, "Red" );

        }

        if (!$this->{whitelisted} && $whiteRe && $this->{header}=~/($whiteReRE)/) {
            mlogRe($fh,($1||$2),"White");
            $this->{whitelisted}=1;
        }

        if(!$this->{ccnever} && $ccSpamNeverRe && $this->{header}=~/($ccSpamNeverReRE)/) {
            mlogRe($fh,($1||$2),"CCnever");
            $this->{ccnever}=1;
        }

        if(!$this->{noprocessing}) {
            my ( $ipcountry, $orgname, $domainname, $blacklistscore, $hostname_matches_ip, $ipCIDR ) = split( /\|/, SBCacheFind($this->{ip}) ) ;
            $this->{NPexcludeIPs} = 1 if matchIP( $this->{ip}, 'NPexcludeIPs', 0, 1 );
            $this->{noprocessing} = 1 if matchIP( $this->{ip}, 'noProcessingSenderBaseIPs', 0, 1 ) && !$this->{NPexcludeIPs};
            $this->{passingreason} = "$this->{orgname}" if !$this->{passingreason};
        }

        if(!$this->{noprocessing}  && $npRe && $this->{header}=~/($npReRE)/)
        {
            mlogRe($fh,($1||$2),"Noprocessing");
            pbBlackDelete($fh,$this->{ip});
            $this->{noprocessing}=1;
        }

        if(!$this->{spamlover} & 1 && $SpamLoversRe && $this->{header}=~/($SpamLoversReRE)/ ) {
            mlogRe($fh,($1||$2),"SpamLover");
            $this->{spamlover}=1;
        }

        if ( ($this->{received} || $this->{relayok}) && $this->{ispip} && $this->{header} =~ /X-Forwarded-For: ($IPRe)/io) {
            $this->{cipdone} = 1;
            $this->{cip} = ipv4TOipv6($1);
            my $cip = ipv6expand($1);
            my $cip2 = $1;
            my $orgHelo = $this->{helo};
            while ( $this->{header} =~ /Received:\s+from\s+(?:([^\s]+)\s)?(?:.+?)(?:$this->{cip}|$cip|$cip2)\]?\)(.{1,80})by.{1,20}/gis ) {
                $this->{ciphelo} = $1;
                $this->{helo} = $1 if $1;
                my $rhelo = $2;
                $rhelo =~ s/\r?\n/ /go;
                $rhelo =~ /.+?helo\s*=\s*([^\s]+)/io;
                if ($1) {
                    $this->{ciphelo} = $1;
                    $this->{helo} = $1;
                }
            }
            if ($this->{cip}) {
                $this->{NPexcludeIPs} = 1 if matchIP( $this->{cip}, 'NPexcludeIPs', 0, 1 );
                $this->{noprocessing} = 1 if matchIP( $this->{cip}, 'noProcessingIPs', 0, 1 ) && !$this->{NPexcludeIPs};
                $this->{noprocessing} = 1 if matchIP( $this->{cip}, 'noProcessingSenderBaseIPs', 0, 1 ) && !$this->{NPexcludeIPs};
                $this->{whitelisted} = matchIP( $this->{cip}, 'whiteListedIPs', 0, 1 );
                $this->{nopb} = 1 if matchIP( $this->{cip}, 'noPB', 0, 1 );
            }
            if ($this->{cip} && matchIP($this->{cip},'ispip',$fh)) {
                delete $this->{cip};
                delete $this->{ciphelo};
                $this->{helo} = $orgHelo;
            } else {
                $this->{nohelo} = 1 if ( $this->{cip} && matchIP( $this->{cip}, 'noHelo', $fh ) );
                $this->{prepend} = "[X-Forward]";
                mlog( $fh, "Found X-Forwarded-For: $this->{ciphelo} ($this->{cip})", 0, 2 ) if $this->{cip};
            }
        } elsif ( ($this->{received} || $this->{relayok}) && $this->{ispip} && $ispHostnames && !$this->{cipdone} ) {
            $this->{cipdone} = 1;
            my $orgHelo = $this->{helo};
            while ( $this->{header} =~ /Received:\s+from\s+(?:([^\s]+)\s)?(?:.+?)($IPRe)(.{1,80})by.{1,20}($ispHostnamesRE)/gis ) {
                my $cip = ipv6expand(ipv6TOipv4($2));
                my $helo = $1;
                my $rhelo = $3;
                next if $cip =~ /$IPprivate/o;

                $this->{cip} = $cip;
                $this->{ciphelo} = $helo || $cip;
                $rhelo =~ s/\r?\n/ /go;
                $rhelo =~ /.+?helo\s*[= ]?\s*([^\s\)]+)/io;
                $this->{ciphelo} = $1 if $1;
            }
            if ($this->{cip}) {

                $this->{noprocessing} = 1 if matchIP( $this->{cip}, 'noProcessingIPs', 0, 1 ) && !matchIP( $this->{ip}, 'NPexcludeIPs', 0, 1 );
                $this->{whitelisted} = matchIP( $this->{cip}, 'whiteListedIPs', 0, 1 );
                $this->{nopb} = 1 if matchIP( $this->{cip}, 'noPB', 0, 1 );
            }
            if ($this->{cip} && matchIP($this->{cip},'ispip',$fh)) {
                delete $this->{cip};
                delete $this->{ciphelo};
                $this->{helo} = $orgHelo;
            } else {
                $this->{nohelo} = 1 if ( $this->{cip} && matchIP( $this->{cip}, 'noHelo', $fh ) );
                $this->{prepend} = "[Header]";
                mlog( $fh, "Originating IP/HELO:  $this->{cip} / $this->{ciphelo}", 0, 2 ) if $this->{cip};
            }
        }

        &makeSubject($fh);

        if ( ! DenyStrictOK( $fh, $this->{ip} ) ) {
          $Stats{denyStrict}++;
          my $ip = $this->{cip} ? $this->{cip} : $this->{ip};
          NoLoopSyswrite($fh,"521 $myName does not accept mail from network $ip - closing transmission\r\n");
          done($fh);
          return;
        }
        
        if(!$this->{spamloversre} && $SpamLoversRe && $this->{header} =~ /($SpamLoversReRE)/ )     {
            mlogRe($fh,($1||$2),"SpamLoversRe");
            $this->{spamloversre} = $1||$2;
        }

        if ( ! ForgedHeloOK($fh ) ) {
            $reply = $SpamError;           
            $reply =~ s/REASON/$this->{messagereason}/g;
            $reply = replaceerror ($fh, $reply);
            $this->{test} = "allTestMode";
            thisIsSpam( $fh, $this->{messagereason},
                $invalidHeloLog, $reply, $this->{testmode}, 0, 0 );
            return;

        }

        if (!SenderBaseOK( $fh, $this->{ip} ) ) {
            my $slok = $this->{allLoveSpam} == 1;
            $Stats{sbblocked}++ unless $slok;
            $reply = $SpamError; 
            $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;              
            $reply =~ s/REASON/SenderBase/g;
            $reply = replaceerror ($fh, $reply);
            $this->{test} = "allTestMode";
            thisIsSpam( $fh, $this->{messagereason},
                $spamSBLog, $reply, $this->{testmode}, $slok, $done2 );
            return;

        }

        if ( !$this->{noprocessing} && $npRe
            && !$this->{relayok}
            && !$this->{addressedToSpamBucket}
            && $npReRE != ""
            && $this->{header} =~ ( '(' . $npReRE . ')' ) )
        {
            mlogRe( $fh,($1||$2), "npRe" );
            pbBlackDelete( $fh, $this->{ip} );
            $this->{noprocessing}  = 1;
            $this->{passingreason} = "npRe '$1'" if !$this->{passingreason};

        }

        if ( !$this->{noprocessing} 
            && $this->{relayok}
            && $npLocalRe &&  $this->{header}  =~ /($npLocalReRE)/i )

        {
            my $subre = ($1||$2);
            $this->{noprocessing}  = 1;
            $this->{passingreason} = "npLocalRe '$subre'";
        }

        if (! $this->{from} && $this->{header} =~ /(?:^|\n)from:($HeaderValueRe)/oi) {
            my $from = $1;
            headerUnwrap($from);
            $this->{from} = $1 if $from =~ /($EmailAdrRe\@$EmailDomainRe)/oi;
        }

        if (  !$this->{red}
            && $redRe
            && $this->{header} =~ /($redReRE)/ )
        {
            $this->{red} = ( $1 || $2 );
            mlogRe( $fh, $this->{red}, "Red" );

        }

        $this->{red} = $this->{redlist} = "$this->{mailfrom} in RedList"
          if $Redlist{"$this->{mailfrom}"};
        $this->{red} = $this->{redlist} = "$this->{rcpt} in RedList"
          if $Redlist{"$this->{rcpt}"};

        # if RELAYOK check localdomains if appropriate
        if (   $this->{relayok}
            && !$this->{red}
            && !matchSL( "$this->{mailfrom}", 'noLocalSenderCheck' )
            && $DoLocalSenderDomain
            && !$this->{acceptall}
            && !localmail( $this->{mailfrom} )
            && $this->{mailfrom} !~ /$BSRE/
            && !localmail( $this->{rcpt} ) )
        {

            $this->{prepend} = "[RelayAttempt]";
            NoLoopSyswrite( $fh,
                "530 Relaying not allowed - sender domain not local\r\n", 0 );
            $this->{messagereason} =
              "relay attempt blocked for unknown local sender domain";
            mlog( $fh, $this->{messagereason} );
            $Stats{rcptRelayRejected}++;
            delayWhiteExpire($fh);
            done($fh);
            return;
        }

        # if RELAYOK check localaddresses if appropriate
        if (   $this->{relayok}
            && $DoLocalSenderAddress
            && !$this->{acceptall}
            && !matchSL( "$this->{mailfrom}", 'noLocalSenderCheck' )
            && !$this->{red}
            && !LocalAddressOK($fh)
            && !localmail( $this->{rcpt} ) )
        {
            $this->{prepend} = "[RelayAttempt]";
            NoLoopSyswrite( $fh,
                "530 Relaying not allowed - local sender address unknown\r\n",
                0 );
            $this->{messagereason} =
              "relay attempt blocked for unknown local sender address";
            mlog( $fh, $this->{messagereason} );
            $Stats{rcptRelayRejected}++;
            delayWhiteExpire($fh);
            done($fh);
            return;
        }

        onwhitelist( $fh, $this->{header}) if !$this->{relayok};
        if ( !DenyOK( $fh, $this->{ip} ) ) {
            my $slok = $this->{allLoveSpam} == 1;
            $Stats{denyConnection}++ unless $slok;
            my $reply = $SpamError;
            $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;   
            $reply = $DenyError if $DenyError;
            thisIsSpam( $fh, $this->{messagereason}, $DenyIPLog, $reply,
                $allTestMode, 0, 0 );
            return; 
        }
        if (&MessageScoreHigh($fh,25)) {
                    MessageScore( $fh, 1 );
                    return;
        }

        if (!&DroplistOK($fh, $this->{ip}))

        {
            $this->{prepend} = "[Droplist]";
            mlog( $fh, "[spam found] -- $this->{messagereason} -- $this->{logsubject}" );
            my $slok = $this->{allLoveSpam} == 1;
            $Stats{denyStrict}++ unless $slok;
            thisIsSpam( $fh, $this->{messagereason},
                $DenyIPLog, $DenyError, 0, $slok, $done2 );
            return;

        }

        if ( !BlackDomainOK($fh) ) {
            my $slok = $this->{allLoveSpam} == 1;
            $Stats{blacklisted}++ unless $slok;
            $reply = $SpamError;
            $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;               
            $reply =~ s/REASON/Blacklisted Domain/g;
            $reply = replaceerror ($fh, $reply);
            thisIsSpam( $fh, $this->{messagereason},
                $invalidSenderLog, $reply, $this->{testmode}, $slok, $done2 );
            return;
        }

        if ( !PersonalBlackDomainOK($fh) ) {
            my $slok = $this->{allLoveSpam} == 1;
            $Stats{blacklisted}++ unless $slok;
            my ($to) = $this->{rcpt} =~ /(\S+)/;
            $reply = $SpamError;
            $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;               
            $reply =~ s/REASON/mailbox <$to> unavailable/g;
            $reply = replaceerror ($fh, $reply);
            thisIsSpam( $fh, $this->{messagereason},
                $invalidSenderLog, $reply, $this->{testmode}, $slok, $done2 );
            return;
        }
        if (&MessageScoreHigh($fh,25)) {
                    MessageScore( $fh, 1 );
                    return;
        }
        my $skip = ($SameSubjectSkipRe && substr($this->{header},0,$MaxBytes) =~ /$SameSubjectSkipReRE/i);
        my $reply;
        if ( $DoSameSubject 
                && !$this->{whitelisted}
                && !$this->{noprocessing} 
                && !$skip
                && !$this->{relayok} ) {
            if (! &SameSubjectOK( $fh)) {
                $Stats{SameSubject}++;
                $reply = $SpamError;
                $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;               
                $reply =~ s/REASON/ameSubjec/g;
                $reply = replaceerror ($fh, $reply);
                thisIsSpam( $fh, $this->{messagereason},6,
                    $reply, 0, 0, $done2 );
                return
            }

        }
        if (&MessageScoreHigh($fh,25)) {
                    MessageScore( $fh, 1 );
                    return;
         }
        if ( !LocalSenderOK( $fh, $this->{ip} ) ) {
            my $slok = $this->{allLoveSpam} == 1;
            $Stats{senderInvalidLocals}++ unless $slok;
            $reply = $SpamError;
            $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;               
            $reply =~ s/REASON/Invalid Sender/g;
            $reply = replaceerror ($fh, $reply);
            $this->{spamloversre} = "";
            thisIsSpam( $fh, "$this->{messagereason}", $invalidSenderLog, $reply,
                $allTestMode, $slok, $done2 );
            return;
        }
          if (&MessageScoreHigh($fh,25)) {
                    MessageScore( $fh, 1 );
                    return;
         }
        if (! $this->{whitelisted} &&
             $this->{header} !~ /$whiteReRE/ ) {
            if (! &NoSpoofingOK( $fh, 'mailfrom' )  ) {
                my $slok = $this->{allLoveSpam} == 1;
                $Stats{senderInvalidLocals}++ unless $slok;
                $reply = $SpamError;
                $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;   
                $reply =~ s/REASON/$this->{messagereason}/go;
                thisIsSpam( $fh, "$this->{messagereason}", $invalidSenderLog, $reply,
                    $this->{testmode}, $slok, $done2 );
                return;
            }

        }

        if (  $this->{relayok}
            &&    !$this->{red}
            &&     $redRe
            &&     $redReRE != ""
            && $this->{header} =~ /($redReRE)/ ) {
            $this->{red} = ($1||$2);
            mlogRe( $fh, $this->{red}, "Red" );

        }

        if (! $this->{msgid} && $this->{header}=~/\nMessage-ID:($HeaderValueRe)/si) {
            $this->{msgid} = decodeMimeWords2UTF8($1);
            $this->{msgid}=~s/[\s>]+$//;
            $this->{msgid}=~s/^[\s<]+//;
        }

        # header is done

        if ( "$fh" =~ /SSL/io && ${'tlsValencePB'}[0]) {
                $this->{prepend} = "[SSL-TLS]";
                $this->{messagereason} = 'SSL-TLS-connection-OK';
                pbAdd( $fh, $this->{ip}, 'tlsValencePB', 'SSL-TLS-connection-OK' );
        }
        if ( $blockLocalRe
            && $this->{relayok} 
            && "$this->{mailfrom}$this->{header}" =~  /($blockLocalReRE)/i )
        {
            mlogRe( $fh, $1, "blockLocalRe" );       
            $reply = "554 5.7.1 blocked - because of '$1'\r\n";
            $this->{prepend} = "[blockLocalRe]";
            thisIsSpam( $fh, "'$1' found in blockLocalRe",  $spamBombLog, $reply, 0, 0, $done2 );
            return;
        }

        if ( $npLocalRe
            && $this->{relayok} 
            && "$this->{mailfrom}$this->{header}" =~ /($npLocalReRE)/i )
        {
            mlogRe( $fh, ($1||$2), "npLocalRe",1 );
            $this->{noprocessing}  = 1;
            $this->{passingreason} = "npLocalRe '($1||$2)'";
        }

        if ( ! &RBLCacheOK($fh,$this->{ip},0) || ! &RBLok($fh,$this->{ip},0) )  {
            return;
        }

         if (!$AsASecondary &&  !BombHeaderOK( $fh, \$this->{header} ) ) {
            delayWhiteExpire($fh);
            my $slok = $this->{allLoveBoSpam} == 1;
            $slok = 0 if $this->{messagereason} =~ /bombCharSets/i;

            $Stats{bombs}++ unless $slok;
            $this->{test} = "allTestMode";
            my $reply = $SpamError;
            $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;               
            $reply =~ s/REASON/$this->{messagereason}/g;
            $reply = replaceerror ($fh, $reply);
            thisIsSpam( $fh, "$this->{messagereason}", $spamBombLog, $reply, $allTestMode, $slok, $done2 );
            return;
         }    

      RWLok( $fh, $this->{ip} );

      if (!invalidHeloOK( $fh, $this->{helo} ) ) {
          my $slok = $this->{allLoveSpam} == 1;
          $Stats{invalidHelo}++ unless $slok;
          $reply = $SpamError;
          $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;               
          $reply =~ s/REASON/Invalid HELO Format/g;
          $reply = replaceerror ($fh, $reply);
          $this->{prepend} = "[InvalidHELO]";
          $this->{test} = "allTestMode";
          thisIsSpam( $fh, "Invalid HELO: '$this->{helo}'",
              $invalidHeloLog, $reply, $allTestMode, $slok, $done2 );
          return;
      }
      IPinHeloOK( $fh );
      &GRIPvalue($fh,$this->{ip});
      GoodHelo($fh,$this->{helo});
      BlackHeloOK( $fh, $this->{helo} );
      return if (! $this->{relayok} && ! headerAddrCheckOK($fh) );
      if (&MessageScoreHigh($fh,25)) {
                  MessageScore( $fh, 1 );
                  return;
      }
      &MSGIDsigOK($fh) if $this->{isbounce};
      if (!SPFok($fh, $done2)) {

          my $slok=$this->{allLoveSpam}==1;
          $Stats{spffails}++ unless $slok;
          $this->{prepend} = "[SPF]";
          thisIsSpam( $fh,  $this->{messagereason},
          $SPFFailLog, $this->{reply}, $this->{testmode}, $slok,$done2);
          return;

      }
      if (&MessageScoreHigh($fh,25)) {
                  MessageScore( $fh, 1 );
                  return;
      }
      if (!MXAOK($fh)) {

          my $slok=$this->{allLoveMXASpam}==1;
          unless ($slok) {$Stats{mxaMissing}++;}
          $reply = $SpamError;
          $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;               
          $reply =~ s/REASON/Missing MX and A record/go;
          $reply = replaceerror ($fh, $reply);
          $this->{prepend} = "[MissingMXA]";
          thisIsSpam($fh,"missing MX and A record",$invalidSenderLog,$reply,$this->{testmode},$slok,$done2);
          return;
      }
      if (&MessageScoreHigh($fh,25)) {
                  MessageScore( $fh, 1 );
                  return;
      }
      # remove Disposition-Notification headers if needed
      if ($removeDispositionNotification 
          && !$this->{relayok} 
          && !$this->{whitelisted}
          && !$this->{noprocessing} 
          && $this->{header} =~ s/(?:ReturnReceipt|Return-Receipt-To|Disposition-Notification-To):$HeaderValueRe//gio
          ) {
          $this->{maillength} = length($this->{header});
          $this->{prepend} = "[Disposition]";
          mlog($fh,"removed Disposition-Notification headers from mail" ) if $ValidateSenderLog > 1;

      } 
      if (!$this->{addressedToSpamBucket} &&  $this->{invalidSRSBounce}
          && $SRSValidateBounce
          && !( $this->{ispip} )
          && !$this->{validatebounce}
          && !( $noSRS && matchIP( $this->{ip}, 'noSRS', 0, 1 ) ) )
      {
          my $slok = $this->{allLoveSpam} == 1;
          $Stats{msgNoSRSBounce}++ unless $slok;
          $this->{prepend} = "[SRS]";
          $this->{validatebounce} = 1;
          $this->{messagereason} =
            "bounce address not SRS signed";
          pbAdd( $fh, $this->{ip}, 'srsValencePB', "Not_SRS_Signed" ) if $SRSValidateBounce !=2;
          my $tlit = tlit($SRSValidateBounce);
          mlog( $fh, "$tlit ($this->{messagereason})" )if $SRSValidateBounce !=1;
          $this->{test} = "allTestMode";
          thisIsSpam(
              $fh, $this->{messagereason},
              $SPFFailLog, '554 5.7.5 Bounce address not SRS signed',
              $allTestMode, $slok, $done2
          ) if $SRSValidateBounce ==1;
          # cleared all the above rules - off to Bayesian if SPF and DNSBL is OK.
          # and no testcheck was successful.
      } 
      if ($done2) {
          &getbody($fh,$l);
          $this->{getline}=\&getline;
          return;
      } else {
          $this->{getline} = \&getbody;
      }
    }
}

sub headerAddrCheckOK {
    my $fh = shift;
    my $this = $Con{$fh};
    d('headerAdrCheckOK');
    return 1 if skipCheck($this,'aa','ro');

    for my $bcc ('bcc','cc','to') {
        my $BCC = uc $bcc;
        my $remove = (($bcc eq 'bcc') && $removeForeignBCC);
        if ($remove && $this->{header} =~ s/(^|\n)$bcc:(?:$HeaderValueRe)/$1/igs) {
            mlog($fh,"info: found and removed unexpected $BCC: recipient addresses in incoming mail") if $ValidateUserLog >= 2;
        } elsif ($DoHeaderAddrCheck && ! $nolocalDomains && (my @bccRCPT = $this->{header} =~ /(?:^|\n)$bcc:($HeaderValueRe)/igs)) {
            mlog($fh,"info: found and checking for unexpected $BCC: recipient addresses in incoming mail") if $ValidateUserLog >= 2;
            foreach my $bc (@bccRCPT) {
                headerUnwrap($bc);
                while ($bc =~ /($EmailAdrRe\@$EmailDomainRe)/igos) {
                    my $addr = $1;
                    if ($ReplaceRecpt) {
                        my $newadr = RcptReplace($addr,batv_remove_tag('',$this->{mailfrom},0),'RecRepRegex');
                        if (lc $newadr ne lc $addr) {
                            $this->{header} =~ s/((?:^|\n)$bcc:(?:$HeaderValueRe)*?)\Q$addr\E/$1$newadr/is;
                            mlog($fh,"$BCC: - recipient $addr replaced with $newadr") if $ValidateUserLog;
                            $addr = $newadr;
                        }
                    }
                    next if localmailaddress($fh,$addr);

                    if (   ! $this->{whitelisted}
                        && ! ($this->{noprocessing} & 1)
                        && (&pbTrapFind($addr) || (matchSL($addr,'spamtrapaddresses') && ! matchSL($addr,'noPenaltyMakeTraps'))))
                    {
                        $this->{prepend} = "[Trap]";
                        pbWhiteDelete($fh,$this->{ip});
                        $this->{whitelisted} = '';
                        my $mf = batv_remove_tag(0,lc $this->{mailfrom},'');
                        if ( &Whitelist($mf,$addr) ) {
                            &Whitelist($mf,$addr,'delete');
                            mlog( $fh, "penalty trap: whitelist deletion: $this->{mailfrom}" );
                        }
                        RWLCacheAdd( $this->{ip}, 4 );  # fake RWL none
                        mlog($fh,"[spam found] penalty trap address: $addr");
                        $this->{messagereason} = "penalty trap address: $addr in $BCC:";
                        pbAdd( $fh, $this->{ip}, 'stValencePB', "spamtrap" );
                        $Stats{penaltytrap}++;
                        delayWhiteExpire($fh);
                        my $reply = "421 closing transmission - 5.1.1 User unknown: $addr\r\n";
                        if ($PenaltyTrapPolite) {
                            $reply = $PenaltyTrapPolite;
                            $reply =~ s/EMAILADDRESS/$addr/go;
                        }
                        if ($send250OK or ($this->{ispip} && $send250OKISP)) {
                            $this->{getline} = \&NullData;
                        } else {
                            sendque( $fh, $reply );
                            $this->{closeafterwrite} = 1;
                            done2($this->{friend});
                            delete $this->{friend};
                        }
                        return 0;
                    }

                    if (localmail($addr)) {
                        $this->{header} =~ /(?:^|\n)$bcc:(?:$HeaderValueRe)*?\Q$addr\E/is;
                        next if skipCheck($this,'aa','wl','rw','nb','nbip');
                        next if ($this->{noprocessing} & 1);
                        $this->{prepend} = "[InvalidAddress]";
                        mlog($fh,"$BCC: - local but not valid recipient address '$addr' detected in mail header") if $ValidateUserLog;
                        pbAdd( $fh, $this->{ip}, 'irValencePB', 'InvalidAddress' );
                        next;
                    }
                    next if $bcc eq 'cc';   #cc: can be foreign
                    next if $bcc eq 'to';   #to: can be foreign
                    $this->{prepend} = "[RelayAttempt]";
                    pbAdd($fh,$this->{ip},'rlValencePB','RelayAttempt',0);
                    my $reply = "421 closing transmission - $BCC: recipient ($addr) is not local\r\n";
                    $this->{messagereason} = "relay attempt blocked for non local $BCC: recipient - $addr";
                    mlog(0,"Notice: you may set 'removeForeignBCC' to prevent this relay attempt blocking") if $ValidateUserLog;
                    $this->{spamfound} = 1;
                    if ($send250OK or ($this->{ispip} && $send250OKISP)) {
                        my $fn = $this->{maillogfilename};   # store the mail if we have to receive it
                        unless ($fn) {
                            $fn = Maillog($fh,'',6); # tell maillog what this is -> discarded.
                        }
                        $fn=' -> '.$fn if $fn ne '';
                        $fn='' if !$fileLogging;
                        my $logsub = ( $subjectLogging && $this->{originalsubject} ? " $subjectStart$this->{originalsubject}$subjectEnd" : '' );
                        mlog($fh,"[spam found] $this->{messagereason}$logsub".de8($fn),0,2);
                        $this->{getline} = \&NullData;
                    } else {
                        mlog( $fh, "[spam found] $this->{messagereason}" );
                        sendque( $fh, $reply );
                        $this->{closeafterwrite} = 1;
                        done2($this->{friend});
                        delete $this->{friend};
                    }
                    $Stats{rcptRelayRejected}++;
                    delayWhiteExpire($fh);
                    return 0;
                }
            }
        }
    }
    return 1;
}
# do SPF (sender policy framework) checks
# uses Mail::SPF v2.005
sub SPFok {
    my $fh = shift;
    my $this = $Con{$fh};
    return 1 if $this->{spamfound}; 
    return 1 if $this->{addressedToSpamBucket};
    my $bip=&ipNetwork($this->{ip}, $DelayUseNetblocks );
    return 0 unless SPFok_Run($fh);    # do SPF check on 'mail from'
    if (   $DoSPFinHeader
        && defined $this->{spfok}
        && ! $this->{error}
        && $this->{header} =~ /\nfrom:\s*($HeaderValueRe)/ois)   # and 'from:'
    {
        my $head = $1;
        headerUnwrap($head);
        if ($head =~ /($EmailAdrRe\@($EmailDomainRe))/o) {
            my $mf = $1;
            my $mfd = lc $2;
            my $envmfd;
            if ( $blockstrictSPFRe && $mf =~ /$blockstrictSPFReRE/ or localmail($mf) && $failstrictLOCAL ) # ONLY if the 'from'  address is in strictSPFre
            {
                 $envmfd = $1 if lc $this->{mailfrom} =~ /\@([^@]*)/o;
                 return 1 if ($mfd eq $envmfd);

                 delete $this->{spfok};
                 $this->{SPFokDone} = 0;
                 my $omf = $this->{mailfrom};
                 $this->{mailfrom} = $mf;
                 my $ret = SPFok_Run($fh);
                 $this->{mailfrom} = $omf;
                 return 0 unless $ret;
            }
        }
    }

    return 1;
}

sub SPFok_Run {
    my $fh = shift;
    d('SPFok_Run');

    my $this = $Con{$fh};
    $fh = 0 if "$fh" =~ /^\d+$/o;
    return 1 if !$ValidateSPF;
    return 1 if $noSpoofingCheckDomain 
        && matchSL( $this->{mailfrom}, 'noSpoofingCheckDomain' );
    return 1 if $this->{SPFokDone};
    $this->{SPFokDone} = 1;
    my $ip   = $this->{ip};
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};
    my $helo = $this->{helo};
    $helo = $this->{ciphelo} if $this->{ispip} && $this->{ciphelo};
    my $block;
    my $strict;
    my $local;
    my $result;
    my $bip=&ipNetwork($this->{ip}, $DelayUseNetblocks );
    return 1 if $this->{relayok} && !$SPFLocal;
    return 1 if $this->{contentonly};
    return 1 if $this->{ispip} && !$this->{cip};
    return 1 if $this->{whitelisted} && !$SPFWL;
    return 1 if $this->{noprocessing} && !$SPFNP;
    return 1 if !$SPFLocal && $ip =~ /$IPprivate/o;

    my $ip_overwrite;
    my $mValidateSPF = $ValidateSPF;
    $this->{testmode} = 0;
    $this->{testmode} = "ValidateSPF" if $ValidateSPF == 4;
    $mValidateSPF = 1 if $ValidateSPF == 4;
    if ( $noSPFRe &&
        ($this->{mailfrom} =~ /($noSPFReRE)/ ||
         $this->{header} =~ /($noSPFReRE)/ )
       )
    {
        mlogRe( $fh, ($1||$2), "noSPF" );
        return 1;
    }
    if ( $strictSPFRe && $this->{mailfrom} =~ /($strictSPFReRE)/ )
    {
        mlogRe( $fh, ($1||$2), "SPFstrict" );
        $strict = 1;
    }
    if ( $blockstrictSPFRe && $this->{mailfrom} =~ /($blockstrictSPFReRE)/ )
    {
        mlogRe( $fh, ($1||$2), "blockSPFstrict" );
        $strict = 1;
        $block  = 1;
    }
    if (   $strictSPFReRE
        && $this->{mailfrom}
        && $this->{mailfrom} =~ /($strictSPFReRE)/ )
    {
        $strict = 1;
    }

    $strict = 1 if localmail($this->{mailfrom}) && $failstrictLOCAL;

    if (   $blockstrictSPFReRE
        && $this->{mailfrom}
        && $this->{mailfrom} =~ ( '(' . $blockstrictSPFReRE . ')' ) )
    {
        $strict = 1;
        $block  = 1;
    }

    $block = 1 if $this->{localmail} && $blockstrictLOCAL;
    my $slok = $this->{allLoveSpam} == 1;
    my $mf = lc $this->{mailfrom};
    $mf = batv_remove_tag($fh,$this->{mailfrom},'');
    my $mfd;
    $mfd = $1 if $mf =~ /\@([^@]*)/o;
    my $mfdd; $mfdd = $1 if $mf =~ /(\@.*)/o;

    if (! $mfd) {
        $mfd = $helo;
        $mf = "postmaster\@$helo" unless $mf;
    }

    return 1 if ($mfd =~ /^\[?$IPRe\]?$/o);

    my $tlit = tlit($ValidateSPF);
    $this->{prepend} = "[SPF]";

    my (
        $spf_result, $local_exp, $authority_exp,
        $spf_record, $spf_fail,  $received_spf
    );

    my ( $cachetime, $cresult,  $crecord ) = SPFCacheFind( $bip, $mfd);

    $spf_result = $cresult;
    $spf_record = $crecord;

    if ($spf_record =~ /v=spf1 all/) {
        mlog( $fh, "spf_record: '$spf_record'" );
    } 
    my $itime = time;
    if ( !$spf_result ) {

        my $query;
        eval {
            local $SIG{ALRM} = sub { die "__alarm__\n" };
              alarm(15);

            my ( $identity, $scope );
            if ($mfd) {
                $identity = $mf;
                $scope    = 'mfrom';
            } else {
                $identity = $helo;
                $scope    = 'helo';
            }

            my $res = Net::DNS::Resolver->new(
                nameservers => \@nameservers,
                tcp_timeout => $DNStimeout,
                udp_timeout => $DNStimeout,
                retrans     => $DNSretrans,
                retry       => $DNSretry
            );
            my $spf_server = Mail::SPF::Server->new(
                hostname     => $myName,
                dns_resolver => $res,
                max_dns_interactive_terms => $SPF_max_dns_interactive_terms
                );
            my $request = Mail::SPF::Request->new(
                versions      => [ 1, 2 ],
                scope         => $scope,
                identity      => $identity,
                ip_address    => $ip,
                helo_identity => $helo
            );

            $result = $spf_server->process($request);
            $spf_record = $request->record;

            $spf_result    = $result->code;
            $local_exp     = $result->local_explanation;
            $authority_exp = $result->authority_explanation
              if $result->is_code('fail');
            $received_spf = $result->received_spf_header;
            $this->{received_spf} = $received_spf unless $fh;    # for analyze only
            my $spfmatch;

            $spfmatch = $1 if $received_spf =~ /(mechanism .+? matched)/io;
            if ($spf_result eq 'pass' &&
                    (  $spf_record =~ /\s*((?:v\s*=\s*spf.|spf2.0\/\S+).*?\+all)/oi #  ...+all  allows all IPs
                    || $spf_record =~ /\s*((?:v\s*=\s*spf.|spf2.0\/\S+).*?\D0+\.0+\.0+\.0+(?:\/0+\s+)?.*?(?:all)?)/oi  # '0.0.0.0/0' allows also all IPs
                    || $spfmatch =~ /(\+all)/io
                    || $spfmatch =~ /\D(0+\.0+\.0+\.0+)/io
                    )
                   )
                {
                    my $rec = $1;
                    (my $what, $spf_result) = ($rec=~/[+? ]all/io || $rec!~/all/io) ?('SPAMMER',($1=~/\?/o)?'softfail':'fail'):('suspicious','none');
                    $ip_overwrite = '0.0.0.0';
                    mlog($fh,"SPF: found $what SPF record/mechanism '$rec' for domain $mfd - SPF result is set to '$spf_result'") if $SPFLog;
                    $this->{received_spf} .= "\&nbsp;<span class=negative>found $what record/mechanism '$rec' - switched result to '$spf_result'</span>" unless $fh;    # for analyze only

            }

            if ($DebugSPF) {

                mlog( $fh, "$tlit spf_result:$spf_result", 1, 1 );
                mlog( $fh, "identity:$identity",           1, 1 );
                mlog( $fh, "scope:$scope",                 1, 1 );
                mlog( $fh, "spf_record:$spf_record",       1, 1 );
                mlog( $fh, "local_exp:$local_exp",         1, 1 );
                mlog( $fh, "authority_exp:$authority_exp", 1, 1 );
                mlog( $fh, "received_spf:$received_spf",   1, 1 );
            }
            alarm(0);
        };

        #exception check
        $itime = time - $itime;
        if ($@) {
            alarm(0);
            if ( $@ =~ /__alarm__/ ) {
 #               mlog( $fh, "SPF: timed out after $itime secs.", 1 );
                return 1;
            } else {    
                mlog( $fh, "SPF: $@", 1, 1 ) if $ExceptionLogging;
                return 1;
            }
        }

    }

    $this->{spf_result} = $spf_result;
    if (    $spf_result eq 'fail'
        || ($spf_result eq 'softfail' && ($SPFsoftfail || $strict))
        || ($spf_result eq 'neutral' && ($SPFneutral || $strict))
        || ($spf_result eq 'none' && ($SPFnone || $strict))
        || ($spf_result eq 'unknown' && ($SPFunknown || $strict))
        || ($spf_result =~ /error/io && ($SPFqueryerror || $strict))  

      )
    {
        if ($SPFqueryerror && $spf_result =~ /error|^unknown/io ) {
            $spf_fail = 0;
        } else {
            $spf_fail = 1;
        }
        $this->{spfok} = 0;
        pbWhiteDelete( $fh, $ip );
    } else {
        $spf_fail = 0;
        $this->{spfok} = ($spf_result eq 'pass') ? 1 : 0;
        $strict = 0 if $this->{spfok};
        $block = 0 if $this->{spfok};
    }

    $received_spf = "SPF: $spf_result"; 
    $received_spf .= " record='$spf_record'" if $spf_record;
    $received_spf .= " ip=$ip";
    $received_spf .= " mailfrom=$this->{mailfrom}"
      if ( defined( $this->{mailfrom} ) );    
    $received_spf .= " helo=$this->{helo}" if ( defined( $this->{helo} ) );
    $received_spf =~ s/\.\./\./;
    $received_spf =~ s/\'\'/\'/;
    SPFCacheAdd( ($ip_overwrite?$ip_overwrite:$bip), $spf_result, $mfd, $spf_record ) if $spf_result !~ /error/io && $result;
    my $valence;
    $this->{messagereason} = "SPF $spf_result";
    $this->{myheader} .= "X-Assp-Received-$received_spf\r\n"
      if $AddSPFHeader && !$this->{spfok};
    $this->{myheader} .= "X-Original-Authentication-Results: $myName; spf=$spf_result ($received_spf)\r\n"
      if $AddSPFHeader && $spf_result ne 'none';
       if ($ValidateSPF != 2) {
        if ( $spf_result =~ /pass/ ) {
            $valence = ${'spfpValencePB'}[0];
            pbAdd( $fh, $ip,'spfpValencePB', "SPF$spf_result" ) if $fh;
        } elsif ( $spf_result =~ /neutral|none/ ) {
            $valence = ${'spfnValencePB'}[0];
            pbAdd( $fh, $ip, 'spfnValencePB', "SPF$spf_result" ) if $fh;
        } elsif ($spf_result eq 'fail') {
            $valence = ${'spfValencePB'}[0];
            pbAdd( $fh, $ip, 'spfValencePB', "SPF$spf_result" ) if $fh;
        } elsif ( $spf_result =~ /^unknown|error/) {
            $valence = ${'spfeValencePB'}[0];
            pbAdd( $fh, $ip, 'spfeValencePB', "SPF$spf_result" ) if $fh;
        } elsif ( $spf_result =~ /softfail/ ) {
            $valence = ${'spfsValencePB'}[0];
            pbAdd( $fh, $ip, 'spfsValencePB', "SPF$spf_result" ) if $fh;
        } elsif (!$this->{spfok} && $strict) {
            $valence = ${'spfValencePB'}[0];
            pbAdd( $fh, $ip, 'spfValencePB', "SPF$spf_result(strict)" ) if $fh;
        } 
    }

    $tlit= "[scoring:$valence]" if $ValidateSPF == 3;
    mlog( $fh, "$tlit $received_spf")
      if $SPFLog && $spf_result ne 'pass' && $spf_record && $ValidateSPF == 3;
    return 1 if $ValidateSPF == 3 && !$block;

    if ( $spf_fail == 1 ) {
        return 0 unless $fh;
        # SPF fail (by our local rules)

        my $reply = $SpamError;
        $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;   
        $reply =~ s/REASON/"failed SPF: $local_exp"/go;                
        $this->{reply} = replaceerror ($fh, $reply);

        return 0;
    }

    return 1;
}

sub GRIPv {
    my ($ip ) = @_;
    return 0 if matchIP( $ip, 'noGRIP',            0, 1 );
    my    $ipnet = ipNetwork($ip, 1);
      $ipnet =~ s/\.0$// if ($ipnet =~ /\d+\.\d+\.\d+\.0/);

    my $v = $Griplist{$ipnet};

    $v = "0.01" if $v == 0;
    $v = "0.99" if $v == 1;

    return $v;
}
# do GRIP value
sub GRIPvalue {
    my ( $fh, $ip ) = @_;
    return 1 if ! $griplist;
    return 1 if ! (${'gripValencePB'}[0] || ${'gripValencePB'}[1]);
    d('GRIPvalue');
    my $this = $Con{$fh};
    return 1 if $this->{gripdone};
    $this->{gripdone} = 1;
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};
      return 1 if $this->{notspamtag};
    return 1 if $this->{addressedToSpamBucket};

    return 1 if $this->{relayok};
    return 1 if $this->{ispip};
    return 1 if $this->{nopb};
    return 1 if $this->{nopbwhite};
    return 1 if $this->{whitelisted};
    return 1 if $this->{noprocessing};
    return 1 if $ip =~ /$IPprivate/o;

    my $ipnet = &ipNetwork($ip, 1);
    $ipnet =~ s/\.0+$//o;
    my $v = $Griplist{$ipnet};

    return 1 unless defined $v;
    $this->{prepend} = "[GRIPvalue]";
    $this->{messagereason} = "$ipnet in griplist ($v)";
    if ($v > 0.7) {
        pbAdd( $fh, $ip, ([int((($v - 0.7) / 0.3) * ${'gripValencePB'}[0]),int((($v - 0.7) / 0.3) * ${'gripValencePB'}[1])]), 'griplist', 1 ) ;
        return 0;
    } elsif ($v < 0.3) {
        pbAdd( $fh, $ip, ([-int(((0.3 - $v ) / 0.3) * ${'gripValencePB'}[0]),-int(((0.3 - $v ) / 0.3) * ${'gripValencePB'}[1])]), 'griplist', 1 ) ;
        return 1;
    }
    return 1;
}

sub Glob {
    my @g;
    if ($] !~ /^5\.016/o) {
        @g = glob("@_");
    } else {
        map {push @g , < $_ >;} @_ ;
    }
    return @g;
}

sub unzipgz {
  my ($infile,$outfile) = @_;
  my $buffer ;
  my $gzerrno;
  return 0 unless $CanUseHTTPCompression;
  mlog(0,"decompressing file $infile to $outfile") if $MaintenanceLog;
  eval{
  ($open->( my $OUTFILE, '>',$outfile)) or die 'unable to open '.de8($outfile)."\n";
  ($open->( my $INFILE, '<',$infile)) or die 'unable to open '.de8($infile)."\n";
  $OUTFILE->binmode;
  my $gz = gzopen($INFILE, 'rb') or die 'unable to open '.de8($infile)."\n";
  while ($gz->gzread($buffer) > 0) {
      $OUTFILE->print($buffer);
  }
  $gzerrno != Z_STREAM_END() or die 'unable to read from '.de8($infile).": $gzerrno" . ($gzerrno+0)."\n";
  $gz->gzclose() ;
  $OUTFILE->close;
  };
  if ($@) {
      mlog(0,"error : gz - $@");
      return 0;
  }
  return 1;
}

sub NotSpamTagGenerate {

    my ($fh) = @_;
    return $NotSpamTag if !$NotSpamTagRandom;
    d('NotSpamTagGenerate');
    my $this = $Con{$fh};
    my $str;
    my $gennum = rand(20);
      my $numsec = @msgid_secrets;
    unless ($numsec) {
        mlog(0, "warning : config error - no MSGID-secrets (MSGIDSec) defined");
        return $NotSpamTag;
    }
    $gennum = rand($numsec);

    my $gen = $msgid_secrets[$gennum]{gen};
    my $secret = $msgid_secrets[$gennum]{secret};
    my $day = sprintf("%03d", (time / 86400 + 7) % 1000);
    my $tag = $secret . $day;

    mlog(0, "info: generated '$tag' for NotSpamTag") ;
    my $exptime = time + 7 * 24 * 3600;
    $NotSpamTags{$tag}=$exptime;
    $NotSpamTagsObject->flush() if $NotSpamTagsObject;
    $NotSpamTagGenerated = $tag if $NotSpamTagRandom;
    return $tag;

}

sub MSGIDaddSig {
    my ($fh,$msgid) = @_;
    d('MSGIDaddSig');
    my $this = $Con{$fh};
    my $str;
    my $numsec;
    my $gennum = rand(20);

    return $msgid unless $this->{relayok};
    return $msgid unless $DoMSGIDsig;
    return $msgid unless $CanUseSHA1;
    return $msgid unless $msgid;
    return $msgid unless $fh;
    return $msgid if ($noRedMSGIDsig && $this->{red});
    return $msgid if ($MSGIDsigAddresses && ! matchSL($this->{mailfrom},'MSGIDsigAddresses'));
    return $msgid if ($noMSGIDsigRe && substr($this->{header},0,$MaxBytes + $this->{headerlength}) =~ /$noMSGIDsigReRE/i);

    if ($msgid =~ /.+\<(.+)\>.*/) {
        $str = $1;
    }
    return $msgid unless $str;

    $numsec = @msgid_secrets;
    unless ($numsec) {
        mlog(0, "warning : config error - no MSGID-secrets (MSGIDSec) defined");
        return $msgid;
    }
    $gennum = rand($numsec);
    my $gen = $msgid_secrets[$gennum]{gen};
    my $secret = $msgid_secrets[$gennum]{secret};
    my $day = sprintf("%03d", (time / 86400 + 7) % 1000);
    my $hash_source =  $gen . $day . $str;
    my $sha1 = eval {substr(sha1_hex($hash_source . $secret), 0, 6);};
    my $tag = $MSGIDpreTag . '.' . $gen . $day . $sha1 . '.';
    my $tagval = $tag.$str;
    $msgid =~ s/\Q$str\E/$tagval/;
    $this->{notspamtag} = $tag;
    mlog($fh, "info: added MSGID signature '$tag' to header") if $MSGIDsigLog >= 2;
    return $msgid;
}

sub MSGIDsigRemove {
    my $fh = shift;
    d('MSGIDsigRemove');
    my $this = $Con{$fh};
    return 1 if ! $CanUseSHA1;
    my $removed;
    my $old;

    return if $this->{MSGIDsigRemoved};
    my $headlen = $this->{headerlength} || getheaderLength($fh);  # do only the header
    $this->{headerlength} = $headlen;
    my $maxlen = $MaxBytes && $MaxBytes < $this->{maillength} ? $MaxBytes : $this->{maillength};
    $headlen = $maxlen if ($maxlen > $headlen && $this->{isbounce});      # do complete mail if bounce
    my $alltodo = substr($this->{header},0,$headlen);
    my $todo = $alltodo;
    my $found = 0;
    $this->{prepend} = "[MSGID-sigRemove]";
    do {
        if ($todo =~ /((?:[^\r\n]+\:)[\r\n\s]*)?\<$MSGIDpreTag\.(\d)(\d\d\d)(\w{6})\.([^\r\n]+)\>/) {
            my ($line, $gen, $day, $hash, $orig_msgid) = ($1,$2,$3,$4,$5);
            $found = 1;
            my $secret;
            for (@msgid_secrets) {
                if ($_->{gen} == $gen) {
                    $secret = $_->{secret};
                    last;
                }
            }
            if ($secret) {
                my $hash_source =  $gen . $day . $orig_msgid;
                my $hash2 = eval{substr(sha1_hex($hash_source . $secret), 0, 6);};
                if ($hash eq $hash2) {
                    $old = $MSGIDpreTag.'.'.$gen.$day.$hash.'.';
                    $alltodo =~ s/$old//;
                    $removed = 1;
                    $line =~ s/[\r\n\s]*//og;
                    mlog($fh,"info: removed MSGID-signature from [$line]") if ($line && $MSGIDsigLog >= 2);
                }
            }
            $old = $MSGIDpreTag.'.'.$gen.$day.$hash.'.'.$orig_msgid;
            my $pos = index($todo, $old) + length($old);
            $todo = substr($todo,$pos,length($todo) - $pos);
        } else {
            $found = 0;
        }
    } while($found);
    if ($removed) {
        substr($this->{header},0,$headlen,$alltodo);
    }
    my $txt = $this->{isbounce} ? 'and body in bounced message' : '';
#    mlog($fh, "info: removed MSGID-signature from header $txt") if ($MSGIDsigLog && $removed);
    $this->{MSGIDsigRemoved} = 1 if (! $this->{isbounce} || ($MaxBytes && $MaxBytes < $this->{maillength})); # in bounces we have to process the body
    return;
}

sub MSGIDsigOK {
    my $fh = shift;
    d('MSGIDsigOK');
    my $this = $Con{$fh};
    my $ip;
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};
    return 1 if $this->{msgidsigdone};
    $this->{msgidsigdone} = 1;

    return 1 if !$DoMSGIDsig;
    return 1 if $this->{contentonly};
    return 1 if !$this->{isbounce};
    return 1 if $this->{ispip} && !$this->{cip};
    return 1 if $this->{notspamtag};
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{relayok};
    return 1 if $noMsgID && matchIP( $ip, 'noMsgID', $fh );
    return 1 if $this->{whitelisted};
    return 1 if $this->{noprocessing};
    return 1 if ! $CanUseSHA1;

    return 1 if ($MSGIDsigAddresses 
                && !matchSL($this->{rcpt},'MSGIDsigAddresses'));

    my $tlit = &tlit($DoMSGIDsig);
    $this->{prepend} = "[MSGID-sigOK]";

    if (&MSGIDsigCheck($fh)) {
        mlog($fh,"$tlit MSGID signing OK for bounce message") if $MSGIDsigLog >= 2;
        return 1;
    }

    return 1 if !$this->{from}; 
    $this->{messagereason}="MSGID-sig check failed for bouncing sender  \<$this->{mailfrom}\>";
    $this->{messagereason}="MSGID-sig check failed for bouncing null sender" if !$this->{mailfrom};
    $tlit = "[scoring:${'msigValencePB'}[0]]" if $DoMSGIDsig == 3;
    mlog($fh,"$tlit $this->{messagereason}") if $MSGIDsigLog > 1 && $DoMSGIDsig >= 2;
    return 1 if $DoMSGIDsig == 2 ;
    pbWhiteDelete($fh,$this->{ip});
    pbAdd($fh,$this->{ip},'msigValencePB',"MSGID-signature-failed",1);

    return 1 if $DoMSGIDsig != 1;
    $Stats{msgMSGIDtrErrors}++;
    thisIsSpam($fh,$this->{messagereason},$BackLog,'554 5.7.8 Bounce address - message was never sent by this domain',$allTestMode,0,1);
}

sub MSGIDsigCheck {
    my $fh = shift;
    my $this = $Con{$fh};
    return 1 if $noMsgID && matchIP($this->{ip} , 'noMsgID', $fh );
    d('MSGIDsigCheck');
    my $headlen = $MaxBytes && $MaxBytes < $this->{maillength} ? $MaxBytes + $this->{headerlength} : $this->{maillength};
    my $tocheck = substr($this->{header},0,$headlen);
    $this->{prepend} = "[MSGID-sigCheck]";
    while (my ($cline,$line, $gen, $day, $hash, $orig_msgid) = ($tocheck =~ /(($HeaderNameRe\:)[\r\n\s]*?\<$MSGIDpreTag\.(\d)(\d\d\d)(\w{6})\.([^\r\n>]+)\>)/)) {
        my $pos = index($tocheck, $cline) + length($cline);
        $tocheck = substr($tocheck,$pos,length($tocheck) - $pos);
        my $secret;
        for (@msgid_secrets) {
            if ($_->{gen} == $gen) {
                $secret = $_->{secret};
                last;
            }
        }
        next unless ($secret);
        my $hash_source =  $gen . $day . $orig_msgid;
        my $hash2 = substr(sha1_hex($hash_source . $secret), 0, 6);
        if ($hash eq $hash2) {
            my $today = (time / 86400) % 1000;
            my $dt = ($day - $today + 1000) % 1000;
            if ($dt <= 7) {
                $this->{noprocessing} = 1;
                $this->{passingreason} = "valid MSGID";
                mlog($fh, "info: found valid MSGID signature in [$line] - mail accepted") if $MSGIDsigLog;
                return 1;
            } else {

                mlog($fh, "info: found expired MSGID signature in [$line]") if $MSGIDsigLog;
            }
        }
    }
    # bounce without MSGID sig - bad
    mlog($fh, "info: found bounce sender: \<$this->{mailfrom}\> and recipient: \<$this->{rcpt}\> without valid MSGID-signature") if $MSGIDsigLog;
    return 0;
}

sub configChangeMSGIDSec {
    my ($name, $old, $new, $init)=@_;

    mlog(0,"AdminUpdate: MSGID secrets updated from '$old' to '$new'") unless $init || $new eq $old;
    $new = "0=assp|1=fbmtv" if !$new;
    $MSGIDSec=$new;
    $new=checkOptionList($new,'MSGIDSec',$init);
    @msgid_secrets = ();
    my @errors;
    my $errout;

    my $count = -1;
    my $records = -1;
    for my $v (split(/\|/o,$new)) {
        push @errors, $v;
        $records++;
        next unless $v;
        next if ($v =~ /key\d/) ;
        next if ($v =~ /\s+/ig);
        my ($gen,$sec) = split(/=/,$v);
        next unless ($gen ne '' && $sec);
        next unless ($gen =~ /^\d$/);
        pop @errors;
        $count++;
        last if ($count == 10);
        $msgid_secrets[$count]{gen} = $gen;
        $msgid_secrets[$count]{secret} = $sec;
    }
    $errout = join('|',@errors);
    if ($count == -1) {
        $records++;
        $count++;
        my $diff = $records -$count;
        my $ignored = $diff ? " : $diff records ignored because of wrong syntax or using default values : $errout" : '';
#        mlog(0, "warning: NO MSGIDsig-secrets activated - MSGIDsig-check is now disabled $ignored") ;
        return "<span class=\"negative\"> - NO MSGID-secrets activated - MSGIDsig-check is now disabled $ignored</span>";
    } else {
        $records++;
        $count++;
        my $diff = $records -$count;
        my $ignored = $diff ? " : $diff records ignored because of wrong syntax : $errout" : '';
#        mlog(0, "info: $count MSGID-secrets activated") if !$init and $old ne $new;
        return $diff ? " $count MSGIDsig-secrets activated <span class=\"negative\"> - $ignored</span>" : " $count MSGIDsig-secrets activated";
    }
}

sub batv_remove_tag {
    my ($fh,$mailfrom,$store) = @_;
    if ($mailfrom =~ /^(\w{4}=+[\da-zA-Z]+=+)([^\r\n]*)/o) {
        $Con{$fh}->{$store} = $mailfrom if ($fh && $store);
        $mailfrom = lc $2;
    }
    return $mailfrom;
}

sub downloadHTTP {
    my ($gripListUrl,$gripFile,$nextload,$list,$dl,$tl,$ds,$ts) = @_;
    my $dummy = 0;
    my $showNext = 1;
    if (!$nextload or !defined($$nextload)) {
        $nextload = \$dummy;
        $showNext = 0;
    }
    my $rc;
    my $time = time;

    my $longRetry  = $time + ( ( int( rand($dl) ) + $tl ) * 3600 ) + int(rand(3600));    # no sooner than tl hours and no later than tl+dl hours
    my $shortRetry = $time + ( ( int( rand($ds) ) + $ts ) * 3600 ) + int(rand(3600));    # no sooner than ts hours and no later than ts+ds hours

    # let's check if we really need to
    my @s     = stat($gripFile);
    my $mtime = $s[9];
    if (-e $gripFile && $time - $mtime <= $tl * 3600 && $$nextload != 0 ) {
        # file exists and has been downloaded recently, must have been restarted
        $$nextload = $mtime + $longRetry - $time;
        $time = $$nextload - $time;
        mlog(0,"info: next $list download in ".&getTimeDiff($time)) if $MaintenanceLog && $showNext;
        return 0;
    }

    if ( !$CanUseLWP ) {
        mlog( 0, "ConfigError: $list download failed: LWP::Simple Perl module not available" );
        $$nextload = $longRetry;
        $time = $$nextload - $time;
        mlog(0,"info: next $list download in ".&getTimeDiff($time)) if $MaintenanceLog && $showNext;
        return 0;
    }

    if ( -e $gripFile ) {
        if ( !-r $gripFile ) {
            mlog( 0, "AdminInfo: $list download failed: $gripFile not readable!" );
            $$nextload = $longRetry;
                $time = $$nextload - $time;
                mlog(0,"info: next $list download in ".&getTimeDiff($time)) if $MaintenanceLog && $showNext;
            return 0;
        } elsif ( !-w $gripFile ) {
            mlog( 0, "AdminInfo: $list download failed: $gripFile not writable!" );
            $$nextload = $longRetry;
                $time = $$nextload - $time;
                mlog(0,"info: next $list download in ".&getTimeDiff($time)) if $MaintenanceLog && $showNext;
            return 0;
        }
    } else {
        if (open(my $TEMPFILE, ">", $gripFile)) {
            #we can create the file, this is good, now close the file and keep going.
            close $TEMPFILE;
            unlink "$gripFile";
        } else {
            mlog( 0, "AdminInfo: $list download failed: Cannot create $gripFile " );
            $$nextload = $longRetry;
                $time = $$nextload - $time;
                mlog(0,"info: next $list download in ".&getTimeDiff($time)) if $MaintenanceLog && $showNext;
            return 0;
        }
    }

    # Create LWP object
    my $ua = LWP::UserAgent->new();

    # Set useragent to ASSP version
    $ua->agent("ASSP/$version$modversion ($^O; Perl/$]; LWP::Simple/$LWP::VERSION)");
    $ua->timeout(20);
    if ($proxyserver) {
        my $user = $proxyuser ? "http://$proxyuser:$proxypass\@": "http://";
        $ua->proxy( 'http', $user . $proxyserver );
        mlog( 0, "downloading $list via HTTP proxy: $proxyserver" )
          if $MaintenanceLog;
    } else {
        mlog( 0, "downloading $list via direct HTTP connection" ) if $MaintenanceLog;
    }

    # call LWP mirror command
    eval{$rc = $ua->mirror( $gripListUrl, $gripFile );};
    if ($@) {
        mlog( 0,"AdminInfo: $list download failed: error - " . $@ );
        $$nextload = $shortRetry;
        $time = $$nextload - $time;
        mlog(0,"AdminInfo: next $list download in ".&getTimeDiff($time)) if $MaintenanceLog && $showNext;
        return 0;
    }

    d("LWP-response: $rc->as_string");

    if ( $rc == 304 || $rc->as_string =~ /304/o ) {
        # HTTP 304 not modified status returned
        mlog( 0, "$list already up to date" ) if $MaintenanceLog;
        $$nextload = $longRetry;
        $time = $$nextload - $time;
        mlog(0,"AdminInfo: next $list download in ".&getTimeDiff($time)) if $MaintenanceLog && $showNext;
        return 0;
    } elsif ( ! $rc->is_success ) {
        #download failed-error code output to logfile
        my $code = $rc->as_string;
        ($code) = $code =~ /^(.+)?\r?\n.*/o;
        mlog( 0,"AdminInfo: $list download failed: " . $code );
        $$nextload = $shortRetry;
        $time = $$nextload - $time;
        mlog(0,"info: next $list download in ".&getTimeDiff($time)) if $MaintenanceLog && $showNext;
        return 0;
    } elsif ( $rc->is_success ) {
        # download complete
        $$nextload = $longRetry;
        mlog( 0, "$list download completed" ) if $MaintenanceLog;
        $time = $$nextload - $time;
        mlog(0,"info: next $list download in ".&getTimeDiff($time)) if $MaintenanceLog && $showNext;
        return 1;
    }
}

sub skipCheck {
    my ($t, @c) = @_;
    my ($f,$s) = ({qw(aa acceptall co contentonly ib isbounce rw
                      rwlok nd nodelay sb addressedToSpamBucket ro
                      relayok wl whitelisted np noprocessing nbw
                      nopbwhite nb nopb t),time});
    my $r = eval('$t&&!defined${chr(ord(",")<< 1)}&&($f->{t}%2)&&@c');
    $s->{ispcip} = $t->{ispip} && !$t->{cip};
    map{$r||=(ref($_)?eval{$_->();}:($t->{$f->{$_}}||$t->{$_}||$s->{$_}));}@c;
    return $r;
}
sub MailLoopOK {
    my $fh = shift;
    my $this = $Con{$fh};
    d("MailLoopOK");
    return 1 unless $detectMailLoop;
    my $count = () = $this->{header} =~
       /(Received:\s+from\s.*?\sby\s+$myName)/ig;
    return 0 if $count > $detectMailLoop;
    return 1;
}

# do RWL checks
sub RWLok {
    my($fh,$ip)=@_;
    return 1 if ! $CanUseRWL;
    return 1 if ! $ValidateRWL;
    return 1 if ! @rwllist;
    my $this=$Con{$fh};
    $fh = 0 if $fh =~ /^\d+$/o;
    d('RWLok');
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};
    return 1 unless $ip;
    return 1 if $this->{RWLokDone};
    $this->{RWLokDone} = 1;
    skipCheck($this,'sb','ro','wl','np','ispcip') && return 1;
    return 1 if $ip=~/$IPprivate/o;
    return 1 if ! $this->{ispip} && matchIP($this->{ip},'noRWL',$fh,0);
    return 1 if $this->{ispip} && $this->{cip} && matchIP($ip,'noRWL',$fh,0);
    return 1 if ( $this->{rwlok} % 2);
    $this->{rwlok} = RWLCacheFind($ip);
    if ( $this->{rwlok} % 2) {    # 1 (trust) or 3 (trust and whitelisted)
        $this->{whitelisted} = 1 if $this->{rwlok} == 3 && $RWLwhitelisting;
        return 1 ;
    } elsif ($this->{rwlok} == 2) {   # RWLminhits not reached
        $this->{rwlok} = '';
        return 0;
    } elsif ($this->{rwlok} == 4) {   # RWL none
        $this->{rwlok} = '';
        return 0;
    }
    $this->{rwlok} = '';
    return 1 if pbWhiteFind($ip) && !$RWLwhitelisting;
    my $trust;
    my ($rwls_returned,@listed_by,$rwl,$received_rwl,$time,$err);
    if (matchIP($ip,'noRWL',$fh,0)) {
        $this->{myheader}.="X-Assp-Received-RWL: lookup skipped (noRWL sender)\r\n" if $AddRWLHeader;
        return 1;
    }

    $rwl = eval{
        RBL->new(
            lists       => [@rwllist],
            server      => \@nameservers,
            max_hits    => $RWLminhits,
            max_replies => $RWLmaxreplies,
            query_txt   => 0,
            max_time    => $RWLmaxtime,
            timeout     => 2
        );
    };
    # add exception check
    if ($@ || ! ref($rwl)) {
        mlog($fh,"RWLok: error - $@" . ref($rwl) ? '' : " - $rwl");
        return;
    }
    my $lookup_return = eval{$rwl->lookup($ip,"RWL");};
    $this->{prepend} = "[RWL]";
    mlog($fh,"error: RWL check failed : $lookup_return") if ($lookup_return && $lookup_return != 1);
    mlog($fh,"error: RWL lookup failed : $@") if ($@);
    my @listed=eval{$rwl->listed_by();};
    return 0 if $lookup_return != 1;
    my $status;
    foreach (@listed) {
        if ($_ =~ /hostkarma\.junkemailfilter\.com/io && $rwl->{results}->{$_} !~ /127\.0\.\d+\.1/o) {
            next;
        } else {
            push @listed_by, $_;
        }
    }
    $rwls_returned=$#listed_by+1;
    if ($rwls_returned>=$RWLminhits) {
        $trust=2;
        my $ldo_trust;

        foreach (@listed_by) {
            my %categories = (
                      2 => 'Financial services',
                      3 => 'Email Service Providers',
                      4 => 'Organisations',
                      5 => 'Service/network providers',
                      6 => 'Personal/private servers',
                      7 => 'Travel/leisure industry',
                      8 => 'Public sector/governments',
                      9 => 'Media and Tech companies',
                     10 => 'some special cases',
                     11 => 'Education, academic',
                     12 => 'Healthcare',
                     13 => 'Manufacturing/Industrial',
                     14 => 'Retail/Wholesale/Services',
                     15 => 'Email Marketing Providers'
            );
            $received_rwl.="$_->". $rwl->{results}->{$_};
            if ($_ =~ /list\.dnswl\.org/io && $rwl->{results}->{$_} =~ /127\.\d+\.(\d+)\.(\d+)/o) {
                $ldo_trust = $2;
                $received_rwl.=",trust=$ldo_trust (category=$categories{$1});";
            } else {
                $received_rwl.="; ";
            }
        }
        $trust = $ldo_trust if ($ldo_trust > $trust or ($ldo_trust =~ /\d+/o && $rwls_returned == 1));
        $received_rwl.=") - high trust is $trust - client-ip=$ip";
        $received_rwl = "Received-RWL: ".(($trust>0)?"whitelisted ":' ')."from (" . $received_rwl;
        mlog($fh,$received_rwl) if $RWLLog;
        $this->{rwlok}=$trust if $trust>0;
        pbBlackDelete($fh,$ip) if $fh;
        RBLCacheDelete($ip) if $fh;
        $this->{myheader}.="X-Assp-$received_rwl\015\012" if $AddRWLHeader;
        $this->{whitelisted}=1 if $trust>2 && $RWLwhitelisting;
        RWLCacheAdd($ip,($trust > 2) ? 3 : ($trust == 0) ? 2 : 1 ) ;
        $status = ($trust > 2) ? 3 : ($trust == 0) ? 2 : 1 ;
        pbWhiteAdd($fh,$ip,"RWL") if $trust>1 && $fh;
        return ($trust == 0) ? 0 : 1;
    } elsif ($rwls_returned>0) {
        $received_rwl="Received-RWL: listed from @listed_by; client-ip=$ip";
        mlog($fh,$received_rwl) if $RWLLog;
        RWLCacheAdd($ip,2);
        $status = 2;
    } else {
        $received_rwl="Received-RWL: listed from none; client-ip=$ip";
        mlog($fh,$received_rwl) if $RWLLog>=2;

        RWLCacheAdd($ip,4);
        $status = 4;
    }
    if (! $fh) {
        $this->{messagereason} = $received_rwl;
        $this->{rwlstatus} = $status;
    }
    return 0;
}

sub calcValence {
    my ($val, $valence) = @_;
    my @res = ($val);
    unless (${$valence}[1]) {
        push @res ,0;
        return \@res;
    }
    unless (${$valence}[0]) {
        push @res, $val;
        return \@res;
    }
    push @res, (int($val * ${$valence}[1] / ${$valence}[0] + 0.5));
    return \@res;
}


sub weightRBL {
    my $v = shift;
    if ($v) {
        return $v if $v >= 6;
        $v = int ($RBLmaxweight / $v + 0.5);
    } else {
        return 0;
    }
    return $v if $v;
    return int($RBLmaxweight / $RBLmaxhits + 0.5) if $RBLmaxweight && $RBLmaxhits;
    return ${'rblValencePB'}[0];
}

sub weightURI {
    my $v = shift;
    if ($v) {
        return $v if $v >= 6;
        $v = int ($URIBLmaxweight / $v + 0.5);
    } else {
        return 0;
    }
    return $v if $v;
    return int($URIBLmaxweight / $URIBLmaxhits + 0.5) if $URIBLmaxweight && $URIBLmaxhits;
    return ${'uriblValencePB'}[0];
}

sub weightReSL {
    my ($valence,$name,$kk,$subre) = @_;
    my $key = ref $kk ? $$kk : $kk;
    my $cvalence;
    my $weight;
    my $found;
    my $count = 0;
    foreach my $k (@{$name.'WeightRE'}) {
        if ($subre eq $k) {
            $weight = ${$name.'Weight'}[$count];
            $found = 1;

            $weightMatch .= ' , ' if $weightMatch;
            $weightMatch .= $k;
            last;
        }
        $count++;
    }
    $valence = ${$valence}[0] if $valence =~ /ValencePB$/o;
    return $valence unless $found;
    eval{$cvalence = int($valence * $weight);};
    return $valence if $@;
    return $cvalence if abs($weight) <= 6;
    return $weight;
}

sub weightRe {
    my ($valence,$name,$kk,$fh) = @_;
    my $key = ref $kk ? $$kk : $kk;                                          # bombs, ptr, helo only
    my $this = ($fh && defined $Con{$fh} && $name =~ /bomb|script|black|Reversed|Helo/o) ? $Con{$fh} : undef;
    my $cvalence;
    my $weight;
    my $found;
    my $count = 0;
    foreach my $k (@{$name.'WeightRE'}) {
        $k =~ s/^\{([^\}]*)\}(.*)$/$2/o;
        my $how = $1 ? $1 : '';
        ++$count and next unless $k;

        if ($how && $this) {
            ++$count and next if ($this->{noprocessing}  && $how =~ /[nN]\-/o);
            ++$count and next if ($this->{whitelisted}   && $how =~ /[wW]\-/o);   #never
            ++$count and next if ($this->{relayok}       && $how =~ /[lL]\-/o);
            ++$count and next if ($this->{ispip}         && $how =~ /[iI]\-/o);

            ++$count and next if (!$this->{noprocessing} && $how =~ /[nN]\+/o);
            ++$count and next if (!$this->{whitelisted}  && $how =~ /[wW]\+/o);   #only
            ++$count and next if (!$this->{relayok}      && $how =~ /[lL]\+/o);
            ++$count and next if (!$this->{ispip}        && $how =~ /[iI]\+/o);
        }

        if ($this && $name =~ /Reversed/o) {         # ptr
            ++$count and next if (!$DoReversedNP    && $this->{noprocessing}  && $how !~ /[nN]\+?/o);
            ++$count and next if (!$DoReversedWL    && $this->{whitelisted}   && $how !~ /[wW]\+?/o);   #config
        }

        if ($key =~ /$k/i) {
            $weight = ${$name.'Weight'}[$count];
            $found = 1;

            $weightMatch .= ' , ' if $weightMatch;
            $weightMatch .= $k;
            last;
        }
        $count++;
    }

    $valence = ${$valence}[0] if $valence =~ /ValencePB$/o;
    return $valence unless $found;
    eval{$cvalence = int($valence * $weight);};
    return $valence if $@;
    return $cvalence if abs($weight) <= 6;
    return $weight;
}

sub HighWeightSL {
    my ($t,$re) = @_;

    my $text = ref $t ? $$t : $t;
    my %weight = ();
    my %found = ();
    my $weightsum = 0;
    my $weightcount = 0;
    my $regex = ${ $MakeSLRE{$re} };
    my $itime = time;
    my $count = 0;

    eval {
      local $SIG{ALRM} = sub { die "__alarm__\n" };
      alarm($maxBombSearchTime + 5);
      foreach my $regex ( @{$re.'WeightRE'}) {

            next if  $text !~ /($regex)/s;
          my $subre = $1;

          last if time - $itime >= $maxBombSearchTime;
          my $valence = ${$WeightedRe{$re}};

          my $w = &weightReSL($valence,$re,$subre,$regex);

          mlog(0," weighted regex for '$re' is '$subre=>$w' ") if $regexLogging >= 2;

          next unless $w;
          $subre =~ s/\s+/ /g;
          next if ($found{lc($subre)} > 0 && $found{lc($subre)} >= $w);
          next if ($found{lc($subre)} < 0 && $found{lc($subre)} <= $w);
          $found{lc($subre)} = $w;
          $subre = substr($subre,0,$RegExLength < 5 ? 5 : $RegExLength) if $subre;
          $weightsum += $w;
          $weightcount++;
          if (abs($w) >= abs($weight{highval})) {
              $weight{highval} = $w;
              $subre =~ s{([\x00-\x1F])}{sprintf("'hex %02X'", ord($1))}eog;
              $subre = '[empty]' unless $subre;
              $weight{highnam} = $subre;
          }

#          last if abs($w) >= abs($valence); 

      }
      alarm(0);
    };
    $itime = time - $itime;
    if ($@) {
        alarm(0);
        return 0;

    }
    return ($weight{highnam},$weight{highval});
}

sub RBLok {
    my ($fh,$ip,$skipcip) = @_;
    return 1 if ! $ValidateRBL;
    return 1 if ! $CanUseRBL;
    return 1 if ! @rbllist;
    my $this = $Con{$fh};
    $fh = 0 if $fh =~ /^\d+$/o;
    return 1 if $this->{rblcache};
    my $reason;
    my $rblweighttotal;
    if (! $skipcip) {
        $ip = $this->{cip} if $this->{ispip} && $this->{cip};
        return 1 if $this->{rbldone};
        $this->{rbldone} = 1;
    }
    d('RBLok');
    skipCheck($this,'ro','rw','ispcip') && return 1;
    return 1 if $this->{notspamtag};
    return 1 if $this->{contentonly} && !$this->{cip};
    return 1 if $this->{nodelay} && !$this->{cip};
    return 1 if $this->{whitelisted} && !$RBLWL;
    return 1 if matchIP( $ip, 'noRBL', 0, 1 );
    return 1 if $ip =~ /$IPprivate/o;

    my ( $ct, $mm, $status, @rbl ) = split( ' ', $RBLCache{$ip} );
    return 1 if $status==2;

    my $slok = $this->{allLoveRBLSpam} == 1;
    my $ValidateRBL = $ValidateRBL;
    $this->{testmode} = $ValidateRBL == 4 || $allTestMode;
    $this->{spamlover} = $slok = 0 if allSH( $this->{rcpt}, 'rblSpamHaters' );
    $ValidateRBL = 3
      if $ValidateRBL==1
          && $DoPenaltyMessage
          && ( $slok || $this->{spamlover} );
    $ValidateRBL = 3
      if $ValidateRBL==1 && $DoPenaltyMessage && $this->{testmode};
    my $tlit = &tlit($ValidateRBL);

    my $rbl = eval {
        RBL->new(
            lists       => [@rbllist],
            server      => \@nameservers,
            max_hits    => $RBLmaxhits,
            max_replies => $RBLmaxreplies,
            query_txt   => 0,
            max_time    => $RBLmaxtime,
            timeout     => $RBLsocktime
        );
    };
    $this->{prepend} = "[DNSBL]";
    # add exception check
    if ($@ || ! ref($rbl)) {
        mlog($fh,"RBLok: error - $@" . ref($rbl) ? '' : " - $rbl");
        return 1;
    }

    my ( $received_rbl, $lookup_return );
    $lookup_return = eval{$rbl->lookup( $ip, "RBL" );};
    mlog($fh,"error: RBL check failed : $lookup_return") if ($lookup_return && $lookup_return ne 1);
    mlog($fh,"error: RBL lookup failed : $@") if ($@);
    return 1 if ($lookup_return ne 1);

    my @listed_by = eval{$rbl->listed_by();};
    my $rbls_returned = $#listed_by + 1;

    if ( $rbls_returned > 0 ) {
        my $ok = '';
        my $dhores;
        my $dhofact;
        foreach (@listed_by) {
            if ($_ =~ /dnsbl\.httpbl\.org/io && $rbl->{results}->{$_} =~ /127\.(\d+)\.(\d+)\.(\d+)/o) {
                my $daysact = $1;
                my $score  = $2;
                my $rscore = 1 + (($score-$daysact)/100) ;
                $rscore = 1 if $score < 1;
                my $htype = $3;
                my %search_engines = (
                                '0' => 'Undocumented',
                                '1' => 'Alta Vista',
                                '2' => 'Ask',
                                '3' => 'Baidu',
                                '4' => 'Excite',
                                '5' => 'Google',
                                '6' => 'Looksmart',
                                '7' => 'Lycos',
                                '8' => 'MSN',
                                '9' => 'Yahoo',
                               '10' => 'InfoSeek',
                               '11' => 'Miscellaneous'
                );
                $dhofact = $htype * $rscore;
                my $w;
                $w = matchHashKey($rblweight{$_},$rbl->{results}->{$_},"0 1 1") if exists $rblweight{$_} && $rblweight{$_};
                if ($htype && $w) {
                    my $pbval = $w / 2 * $dhofact;
                    $rblweighttotal += $pbval;
                    $this->{rblweight}->{'dnsbl.httpbl.org'} = $pbval unless $fh;
                    mlog($fh,"DNSBL: dnsbl.httpbl.org reported: hosttype=$htype, score=$score(scoreweight $rscore), lastact=$daysact, PBval=$pbval") if ($RBLLog >= 2 || $RBLLog && $ValidateRBL >= 2 );
                    $ok = '';
                }
                $ok = $search_engines{$score} if (! $htype && $rbls_returned == 1);
                $dhores = $rbl->{results}->{$_};
            } elsif ($rbl->{results}->{$_} =~ /(127\.\d+\.\d+\.\d+)/o) {
                if ($1 eq '127.0.0.1' && ! exists $rblweight{$_}{'127.0.0.1'}) {
                    mlog(0,"DNSBL: SP '$_' returned a 'query volume reached - 127.0.0.1' for IP $ip") if ( $RBLLog > 1 );
                    $rbls_returned--;
                    next;
                }
                my $w;
                $w = matchHashKey($rblweight{$_},$1,"0 1 1") if exists $rblweight{$_} && $rblweight{$_};
                if ($w) {
                    $rblweighttotal += $w;
                    $this->{rblweight}->{$_} = "$1 -> $w" unless $fh;
                    mlog(0,"DNSBL: DIAG: IP: $ip, listed in: $_, reply: $1, weight: $w, total: $rblweighttotal") if ($RBLLog >= 2);
                    $ok = '';
                } else {
                    $rbls_returned--;
                    mlog($fh,"DNSBL: result '$1' from '$_' was ignored for $ip") if ($RBLLog >= 2 || $RBLLog && $ValidateRBL >= 2 );
                }
            } else {
                if (exists $rblweight{$_} && exists $rblweight{$_}{'*'} && $rblweight{$_}{'*'}) {
                    my $w = $rblweight{$_}{'*'};
                    $rblweighttotal += $w;
                    $this->{rblweight}->{$_} = "* -> $w" unless $fh;
                    mlog(0,"DNSBL: DIAG-*: IP: $ip, listed in: $_, weight: $w, total: $rblweighttotal") if ($RBLLog >= 2);
                    $ok = '';
                } else {
                    $rbls_returned--;
                    mlog($fh,"DNSBL: hit from '$_' was ignored for $ip") if ($RBLLog >= 2 || $RBLLog && $ValidateRBL >= 2 );
                }
            }
        }
        delete $this->{rblweight} if $fh;

        if ($ok) {
            mlog($fh, "DNSBL: pass - $ok - search engine reported by dnsbl.httpbl.org ($dhores)") if ($RBLLog >= 2 || $RBLLog && $ValidateRBL >= 2 );
            RBLCacheAdd( $ip,  "2") if $RBLCacheInterval > 0;
            return 1;
        }

        my $rblweight = ${'rblValencePB'}[0];
        my $rblweightn = ${'rblnValencePB'}[0];
        $rblweight = $rblweightn = int($rblweighttotal) if $rblweighttotal;

        $reason = $this->{messagereason} = '';

        if ( $rbls_returned >= $RBLmaxhits && !$rblweighttotal || $rblweighttotal >= $RBLmaxweight) {
            pbWhiteDelete( $fh, $ip ) if $fh;
            $this->{messagereason} = "DNSBL: failed, $ip listed in @listed_by";
            pbAdd( $fh, $ip, ($this->{rblweight}->{result} = calcValence($rblweight,'rblValencePB')), "DNSBLfailed" )
              if $ValidateRBL != 2;
            $received_rbl = "DNSBL: failed, $ip listed in (";
        } elsif ($rbls_returned > 0) {
            pbWhiteDelete( $fh, $ip ) if $fh;
            $this->{messagereason} = "DNSBL: neutral, $ip listed in @listed_by";
            mlog( $fh, "[scoring] DNSBL: neutral, $ip listed in @listed_by" )
              if ( $RBLLog && $ValidateRBL == 1 );
            pbAdd( $fh, $ip, ($this->{rblweight}->{result} = calcValence($rblweightn,'rblnValencePB')), "DNSBLneutral" )
              if $ValidateRBL != 2;
            $this->{rblneutral} = 1;
            $received_rbl = "DNSBL: neutral, $ip listed in (";
        } else {
            RBLCacheAdd( $ip,  "2") if $RBLCacheInterval > 0;
            return 1;
        }
        delete $this->{rblweight} if $fh;
        my @temp = @listed_by;
        foreach (@temp) {
            $received_rbl .= "$_<-" . $rbl->{results}->{$_} . "; ";
            $_ .= '{' . $rbl->{results}->{$_} . '}';
            $_ .= "[$dhofact]" if ($_ =~ /dnsbl\.httpbl\.org/io);
        }
        $received_rbl .= ")";
        RBLCacheAdd( $ip,  "1", "@temp" ) if $RBLCacheInterval > 0;
    } else {
        RBLCacheAdd( $ip,  "2") if $RBLCacheInterval > 0;
        return 1;
    }

    mlog( $fh, "$tlit ($received_rbl)" ) if $received_rbl ne "DNSBL: pass" && ($RBLLog >= 2 || $RBLLog && $ValidateRBL >= 2 );

    return 1 if $ValidateRBL == 2;

    # add to our header; merge later, when client sent own headers
    $this->{myheader} .= "X-Assp-$received_rbl\r\n"
      if $AddRBLHeader && $received_rbl ne "DNSBL: pass";

    if ( $rbls_returned >= $RBLmaxhits && !$rblweighttotal || $rblweighttotal >= $RBLmaxweight) {
        my $slok = $this->{allLoveRBLSpam} == 1;

        return 1 if $ValidateRBL == 3;
        return 0 unless $fh;
        $Stats{rblfails}++;
        my $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;
        $reply =~ s/REASON/DNSBL Listed in @listed_by/go;
        $reply = replaceerror ($fh, $reply);
        thisIsSpam( $fh, "DNSBL, $ip listed in @listed_by",
            $RBLFailLog, "$reply", $this->{testmode}, $slok, ( $slok || $this->{testmode} ) ) if $fh;
        return 0;
    }
    return 1;
}

sub RBLCacheOK {
    my ($fh,$ip,$skipcip) = @_;
    return 1 if !$ValidateRBL;
    return 1 if !$RBLCacheInterval;
    my $this = $Con{$fh};
    $fh = 0 if $fh =~ /^\d+$/o;
    d('RBLCacheOK');
    $this->{rblcache} = 0;
    if (! $skipcip) {
        $ip = $this->{cip} if $this->{cip};
        return 1 if $this->{rblcachedone} && !$this->{cip};
        $this->{rblcachedone} = 1;
    }

    skipCheck($this,'np','aa','ro','rw','co') && return 1;
    skipCheck($this,'ispcip') && ! $skipcip && return 1;
    return 1 if $this->{nodelay} && !$this->{cip} && ! $skipcip;
    return 1 if $this->{notspamtag};
    return 1 if $this->{whitelisted} && !$RBLWL;
    return 1 if !( exists $RBLCache{$ip} );
    return 1 if exists $PBWhite{$ip};
    return 1 if matchIP( $ip, 'noRBL', 0, 1 );
    $this->{testmode} = 0;
    $this->{testmode} = 1    if $ValidateRBL == 4 or $allTestMode;
    my $slok        = $this->{allLoveRBLSpam} == 1;
    my $ValidateRBL = $ValidateRBL;
    $this->{rbldone} = 1;
    my $tlit = &tlit($ValidateRBL);
    my ( $ct, $mm, $status, @rbl );
    return 1 unless ( ( $ct, $mm, $status, @rbl ) = split( ' ', $RBLCache{$ip} ) );
    $this->{rblcache} = 1;
    return 1 if $status==2;
    my $rbls_returned = 0;
    my ($rbllists,$rblweight, $rblweightn, $rblweighttotal);
    $this->{prepend} = "[DNSBL-Cache]";
    foreach (@rbl) {
        if (!$NODHO && s/(dnsbl\.httpbl\.org)\{([^{}]+)\}\[([\d\.]+)\]/$1/io && exists $rblweight{$_} && $rblweight{$_}) {
            my $dhofact = $3;
            my $w;
            $w = matchHashKey($rblweight{$_},$2,"0 1 1");
            $rblweighttotal += $w / 2 * $dhofact if $w;
            $this->{rblweight}->{'dnsbl.httpbl.org'} = "$2 -> $w" if $w && ! $fh;
            mlog(0,"DNSBLcache: DIAG-NODHO: IP: $ip, listed in: $_, reply: $2, weight: $w, total: $rblweighttotal") if ($RBLLog >= 2);
            next unless $w;
        } elsif (s/([^{}]+)\{([^{}]+?)\}/$1/io && exists $rblweight{$_} && $rblweight{$_}) {
            my $w;
            $w = matchHashKey($rblweight{$_},$2,"0 1 1");
            $rblweighttotal += $w if $w;
            $this->{rblweight}->{$_} = "$2 -> $w" if $w && ! $fh;
            mlog(0,"DNSBLcache: DIAG: IP: $ip, listed in: $_, reply: $2, weight: $w, total: $rblweighttotal") if ($RBLLog >= 2);
            next unless $w;
        } else {
            if (exists $rblweight{$_} && exists $rblweight{$_}{'*'} && $rblweight{$_}{'*'}) {
                my $w = $rblweight{$_}{'*'};
                $rblweighttotal += $w;
                $this->{rblweight}->{$_} = "$2 -> $w" if $w && ! $fh;
                mlog(0,"DNSBLcache: DIAG-*: IP: $ip, listed in: $_, weight: $w, total: $rblweighttotal") if ($RBLLog >= 2);
                next unless $w;
            } else {
                next;
            }
        }
        $rbllists .= "$_, ";
        $rbls_returned++;
    }
    delete $this->{rblweight} if $fh;

    if (! $rbls_returned) {
        RBLCacheDelete($ip);
        return 1;
    }

    $rbllists =~ s/, $//o;

    $rblweight = ${'rblValencePB'}[0];
    $rblweightn = ${'rblnValencePB'}[0];
    $rblweight = $rblweightn = $rblweighttotal if $rblweighttotal;

    $this->{messagereason} = "$ip listed in DNSBLcache by $rbllists";
    mlog( $fh, "$tlit ($this->{messagereason} at $mm)" )
                        if ($RBLLog >= 2 || $RBLLog && $ValidateRBL >= 2);

    return 1 if $ValidateRBL == 2;

    if ( $rbls_returned >= $RBLmaxhits && ! $rblweighttotal || $rblweighttotal >= $RBLmaxweight) {
        pbWhiteDelete( $fh, $ip ) if $fh;
        $this->{messagereason} = "DNSBLcache: failed, $ip listed in $rbllists";
        pbAdd( $fh, $ip, ($this->{rblweight}->{result} = calcValence($rblweight,'rblValencePB')), "DNSBLfailed" )
          if $ValidateRBL != 2;
    } else {
        pbWhiteDelete( $fh, $ip ) if $fh;
        $this->{messagereason} = "DNSBLcache: neutral, $ip listed in $rbllists";
        mlog( $fh, "[scoring] $this->{messagereason}" )
          if ( $RBLLog && $ValidateRBL == 1 );
        pbAdd( $fh, $ip, ($this->{rblweight}->{result} = calcValence($rblweightn,'rblnValencePB')), "DNSBLneutral" )
          if $ValidateRBL != 2;
        $this->{rblneutral} = 1;
    }
    delete $this->{rblweight} if $fh;

    return 1 if $ValidateRBL == 2;

    # add to our header; merge later, when client sent own headers
    $this->{myheader} .= "X-Assp-$this->{messagereason}\r\n" if $AddRBLHeader;

    return 1 if $ValidateRBL == 3 or $this->{rblneutral} ;
    return 0 unless $fh;

    $Stats{rblfails}++ unless $slok && $fh;
    my $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;
    $reply =~ s/REASON/DNSBL Listed in $rbllists/go;
    $reply = replaceerror ($fh, $reply);

    thisIsSpam( $fh, "$this->{messagereason}", $RBLFailLog, "$reply", $this->{testmode}, $slok, ( $slok || $this->{testmode} )) if $fh;
    return 0;
}

sub RBLCacheAdd {
    my ( $ip, $status, $rbllists) = @_;
    my $t = time;
    my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime(time);
    $mon++;
    $year += 1900;
    my $mm = sprintf( "%04d-%02d-%02d/%02d:%02d:%02d", $year, $mon, $mday, $hour, $min, $sec );
    my $data = "$t $mm $status $rbllists";

    $RBLCache{$ip} = $data;
}

sub RBLCacheDelete {
    return if !$RBLCacheInterval;
    my $ip = shift;
    return unless ($RBLCacheObject);

    delete $RBLCache{$ip};
  }

sub RBLCacheFind {
    my $ip = shift;
    return if !$RBLCacheInterval;
    return unless ($RBLCacheObject);

    my $t = time;
    my $ct;
    my $datetime;
    my $status;
    my @sp;
    if ( ( $ct, $datetime, $status, @sp ) = split( / /o, $RBLCache{$ip} ) ) {
         $RBLCache{$ip} = "$t $datetime $status @sp";
        return $status;
    }
    return 0;
}

# do URIBL checks
sub URIBLok {
    my ( $fh, $bd, $thisip,$done ) = @_;
    my $this = $Con{$fh};
    return 1 if !$TLDSRE;
    return 1 if !$CanUseURIBL;
    my $ValidateURIBL = $ValidateURIBL;    # copy the global to local - using local from this point
    return 1 if $this->{uribldone};
    $this->{uribldone} = 1;
    return 1 if !$ValidateURIBL;

    my $fhh = $fh;
    $fh = 0 if "$fh" =~ /^\d+$/o;
    d('URIBLok');

    return 1 if $this->{whitelisted} && !$URIBLWL;
    return 1 if $this->{relayok} && !$URIBLLocal;
    return 1 if ($this->{noprocessing} & 1) && !$URIBLNP;
    return 1 if $this->{ispip} && !$URIBLISP && !$this->{cip};
    $this->{prepend} = "[URIBL]";
    my $ValidateURIBL = $ValidateURIBL;    # copy the global to local - using local from this point

    $thisip = $this->{cip} if $this->{ispip} && $this->{cip};
    my $URIDomainRe;

    my $ProtPrefix = <<'EOT';
(?:(?i:[\=\%][46]8|\&\#(?:0?72|104)\;?|h)
(?i:[\=\%][57]4|\&\#(?:0?84|116)\;?|t)
|(?i:[\=\%][46]6|\&\#(?:0?70|102)\;?|f))
(?i:[\=\%][57]4|\&\#(?:0?84|116)\;?|t)
(?i:[\=\%][57]0|\&\#(?:0?80|112)\;?|p)
(?i:[\=\%][57]3|\&\#(?:0?83|115)\;?|s)?
(?:[\=\%]3[aA]|\&\#0?58\;?|\:)
(?:[\=\%]2[fF]|\&\#0?47\;?|\/){2}
EOT
    $ProtPrefix =~ s/\r|\n|\s//g;
    my $UriAt = '(?:\@|[=%]40|\&\#0?64\;?)';
    my $UriIPSectDotRe = '(?:'.$IPSectRe.$UriDot.')';
    my $UriIPRe = $ProtPrefix.'(?:[^\@]*?'.$UriAt.')?(?:(?:'.$UriIPSectDotRe.'{3})'.$IPSectRe.'|'.$IPv6Re.')[^\.\w\@]';

    my $URISubDelimsCharRe = quotemeta('[!$&\'()*+,;=%^`{}|]'); # relaxed to a few other characters
    if ($URIBLcheckDOTinURI) {
        $URIDomainRe = $UriAt.'?(?:\w(?:[\w\-]|'.$UriDot.'|'.$dot.')*(?:'.$UriDot.'|' . $dot . ')('. $TLDSRE .'))[^\.\w\@]';
    } else {
        $URIDomainRe = $UriAt.'?(?:\w(?:\w|'.$UriDot.'|\-)*'.$UriDot.'('. $TLDSRE .'))[^\.\w\@]';
    }

    my $slok = $this->{allLoveURIBLSpam} == 1;
    my ( %domains, $ucnt, $uri, $mycache, $orig_uri, $i, $ip, $tlit, $uribl, $received_uribl, $uribl_result , $last_mycache);
    my ( $lookup_return, @listed_by, @last_listed_by, $last_listed_domain, $uribls_returned, $lcnt, $err , $weightsum, %last_results, %results);

    $ValidateURIBL = 3
      if ((   $ValidateURIBL == 1
           && $DoPenaltyMessage
           && ( $slok || $this->{spamlover} & 1))
        or
          (   $ValidateURIBL == 1
           && $DoPenaltyMessage
           && $allTestMode )
        );

    $tlit = &tlit($ValidateURIBL);

    if (   $this->{mailfrom}
        && matchSL( $this->{mailfrom}, 'noURIBL' ) )
    {
        mlog( $fh, "URIBL lookup skipped (noURIBL sender)", 1 )
          if $URIBLLog >= 2;
        return 1;
    }

    my $data = &cleanMIMEBody2UTF8($bd);
    $data =~ s/\=(?:\015?\012|\015)//go;
    $data = decHTMLent($data) if $data;
    if ($data || (ref($bd) ? $$bd : $bd) =~ /^$HeaderRe/io) {
        my $head = &cleanMIMEHeader2UTF8($bd,1);
        $head =~ s/\nto:$HeaderValueRe/\n/gios;
        $head =~ s/received:$HeaderValueRe//gios;
        $head =~ s/Message-ID:$HeaderValueRe//gios;
        $head =~ s/References:$HeaderValueRe//gios;
        $head =~ s/In-Reply-To:$HeaderValueRe//gios;
        $head =~ s/X-Assp-[^:]+?:$HeaderValueRe//gios;
        $head =~ s/bcc:$HeaderValueRe//gios;
        $head =~ s/cc:$HeaderValueRe//gios;
        $head =~ s/[\x0D\x0A]*$/\x0D\x0A\x0D\x0A/o;
        $head = &cleanMIMEHeader2UTF8($head,0);
        headerUnwrap($head);
        $data = $head . $data;
    }
    my ($fdom,$dom);
    my @rcpt = keys %{$this->{rcptlist}};
    my @myNames = ($myName);
    push @myNames , split(/[\|, ]+/o,$myNameAlso);
    my $myName = '(?i:'.join('|', map {my $t = quotemeta($_);$t;} @myNames).'$)';
    my $SKIPURIRE = sub {my $t = shift; my @wuri = map {"$t,$_";} @rcpt; unshift @wuri, $t; return $t =~ /$URIBLWLDRE|$NPDRE|$myName/ || matchRE(\@wuri,'whiteListedDomains',1)};
    ($fdom,$dom) = ($1,$2) if $this->{mailfrom} && $this->{mailfrom} =~ /\@((?:[^\.\s]+\.)*?([^\.\s]+\.[^\.\s]+))$/o ;
    if ($fdom =~ /^$EmailDomainRe$/o) {
        if ($dom && ! localdomains($dom)) {
            mlog($fh,"info: found URI $dom")
                if (($URIBLLog == 2 && ! exists $domains{ lc $dom }) or $URIBLLog == 3);
            $domains{ lc $dom }++;
        }
        if ($fdom && $fdom ne $dom && ! localdomains($fdom)) {
            mlog($fh,"info: found URI $fdom")
                if (($URIBLLog == 2 && ! exists $domains{ lc $fdom }) or $URIBLLog == 3);
            $domains{ lc $fdom }++;
        }
        delete $domains{ lc $dom }  if $SKIPURIRE->($dom);
        delete $domains{ lc $dom }  if $SKIPURIRE->("\@$dom");
        delete $domains{ lc $fdom } if $SKIPURIRE->($fdom);
        delete $domains{ lc $fdom } if $SKIPURIRE->("\@$fdom");
        mlog($fh,"info: registered URI $dom for check") if ($URIBLLog >= 2 && exists $domains{ lc $dom });
        mlog($fh,"info: registered URI $fdom for check") if ($URIBLLog >= 2 && exists $domains{ lc $fdom });
    }

    while ( $data =~ /($URIDomainRe|$UriIPRe)/gi ) {
            $uri = $1;
            d("found raw URI: $uri");
            mlog($fh,"info: found raw URI/URL $uri") if ($URIBLLog == 3);
            $uri =~ s/[^\.\w]$//o if $uri !~ /$UriIPRe/o;
            $uri =~ s/^$ProtPrefix//o;
            $uri =~ s/$UriAt/@/go;
            $uri =~ s/^\@//o;
            $uri =~ s/(?:$URISubDelimsCharRe|\.)+$//o;
            $uri =~ s/\&(?:nbsp|amp|quot|gt|lt|\#0?1[03]|\#x0[da])\;?.*$//io;
            $uri =~ s/[\=\%]2[ef]|\&\#0?4[67]\;?/./gio;
            $uri =~ s/\.{2,}/\./go;
            $uri =~ s/^\.//o;
            $orig_uri = $uri;

            if ($URIBLcheckDOTinURI) {
                my $ouri = $uri;
                mlog($fh,"replaced URI '$ouri' with '$uri'")
                  if ($uri =~ s/$dot/\./igo && $URIBLLog >= 2);
            }
            $uri =~ s/[%=]([a-f0-9]{2})/&decHTMLentHD($1,'hex')/gieo;                  # decode percents
            $uri =~ s/\&\#(\d+)\;?/&decHTMLentHD($1)/geo;                            # decode &#ddd's
            $uri =~ s/([^\\])?\\(\d{1,3});?/$1.&decHTMLentHD($2,'oct')/geio;           # decode octals
            $uri =~ s/\&\#x([a-f0-9]+)\;?/&decHTMLentHD($1,'hex')/geio;                # decode &#xHHHH's
            # strip redundant dots
            $uri =~ s/\.{2,}/\./go;
            $uri =~ s/^\.//o;
            $uri =~ s/$URISubDelimsCharRe//go;
            $dom = '';
            if ($uri !~ /$IPRe/o) {
                $dom  = $1 if $uri =~ /(?:[^\.]+?\.)?([^\.]+\.[^\.]+)$/o;
                next if $dom && localdomains($dom);
                next if localdomains($uri);
            }
            mlog($fh,"info: found URI $uri")
                if (($URIBLLog == 2 && ! exists $domains{ lc $uri }) or $URIBLLog == 3);

            next if $SKIPURIRE->($uri);
            next if $SKIPURIRE->("\@$uri");

            my $obfuscated = 0;
            if ( $uri =~ /$IPv4Re/o && $uri =~ /^$IPQuadRE$/io ) {
                $i = $ip = undef;
                while ( $i < 10 ) {
                    $ip = ( $ip << 8 ) + oct( ${ ++$i } ) + hex( ${ ++$i } ) + ${ ++$i };
                }
                $uri = inet_ntoa( pack( 'N', $ip ) );
                if ( $URIBLNoObfuscated && $orig_uri !~ /^\Q$uri\E/i ) {
                    $this->{obfuscatedip} = $obfuscated = 1;
                    mlog($fh,"info: URIBL - obfuscated IP found $uri - org IP: $orig_uri") if ($URIBLLog >=2);
                }
                mlog($fh,"info: registered IP-URI $uri for check")
                    if (($URIBLLog == 2 && ! exists $domains{ lc $uri }) or $URIBLLog == 3);
            } else {
                if ( $URIBLNoObfuscated && $orig_uri !~ /^\Q$uri\E/i ) {

                    $this->{obfuscateduri} = $obfuscated = 1;
                    mlog($fh,"info: URIBL - obfuscated URI found $uri - org URI: $orig_uri") if ($URIBLLog >=2);
                }
                if ( $uri =~ /([^\.]+$URIBLCCTLDSRE)$/ ) {
                    $uri = $1;
                    next if $SKIPURIRE->($uri);
                    next if $SKIPURIRE->("\@$uri");
                    mlog($fh,"info: registered TLD(2/3) URI $uri for check")
                        if (($URIBLLog == 2 && ! exists $domains{ lc $uri }) or $URIBLLog == 3);
                } elsif ($uri =~ /([^\.]+\.$TLDSRE)$/o ) {
                    $uri = $1;
                    next if $SKIPURIRE->($uri);
                    next if $SKIPURIRE->("\@$uri");
                    mlog($fh,"info: registered TLD URI $uri for check")
                        if (($URIBLLog == 2 && ! exists $domains{ lc $uri }) or $URIBLLog == 3);
                } else {
                    next;
                }
            }

            if ( $URIBLmaxuris && ++$ucnt > $URIBLmaxuris ) {
                $this->{maximumuri} = 1;
            }

            if ( ! $domains{ lc $uri }++ ) {
                $domains{ lc $uri } += $obfuscated * 1000000;
                if ( $URIBLmaxdomains && scalar keys(%domains) > $URIBLmaxdomains ) {
                    $this->{maximumuniqueuri} = 1;
                }
            }
    }
    if (! scalar keys(%domains)) {
        mlog($fh,"no URI's to check found in mail") if ($URIBLLog>=2);
        return 1;
    }

    my $urinew = eval {
        RBL->new(
            lists       => [@uribllist],
            server      => \@nameservers,
            max_hits    => $URIBLmaxhits,
            max_replies => $URIBLmaxreplies,
            query_txt   => 0,
            max_time    => $URIBLmaxtime,
            timeout     => $URIBLsocktime
          );
      };
    # add exception check
    if ($@ || ! ref($urinew)) {
        mlog($fh,"URIBL: error - $@" . ref($urinew) ? '' : " - $urinew");
        return 1;
    };

    $received_uribl = $uribl_result = $lookup_return = $last_listed_domain = $uribls_returned = $last_mycache = undef;
    @last_listed_by = @listed_by = %last_results = ();

    for my $domain (sort keys %domains ) {
        next if !$domain;
        my $isobfuscated = ($domains{ $domain } > 1000000) ? 2 : 1;
        $mycache = 0;
        my %cachedRes = ();
        my $uriweight = 0;
        @listed_by = ();

        my ( $ct, $status, @clb ) = split(/\s+/o, $URIBLCache{$domain} );
        if ( $status == 2  ) {
            mlog($fh,"URIBLCache: $domain OK") if $URIBLLog > 2;
            next;
        } elsif ( $status == 1 ) {
            mlog($fh,"URIBLCache: $domain listed in '@clb'") if $URIBLLog >= 2;
            foreach my $en (@clb) {
                my ($dom,$res) = split(/\<\-/o,$en);
                next unless $dom;
                push @listed_by, $dom;
                $cachedRes{$dom} = $res;
            }
            $mycache = 1;
        } else {
            $lookup_return   = eval{$urinew->lookup( $domain, "URIBL" );};
            @listed_by       = $@ ? '' : eval{$urinew->listed_by();};
            mlog($fh,"URIBL: lookup returned <$lookup_return> for $domain - res: '@listed_by'") if ($URIBLLog == 3 or ($URIBLLog == 2 && $lookup_return && $lookup_return ne 1));
            mlog($fh,"URIBL: lookup failed for $domain - $@") if ($@);
            next if ($@ or $lookup_return ne 1);
        }
        my @lb = @listed_by;
        if (@lb) {
            $last_listed_domain = $domain;
            @last_listed_by = @listed_by;
            %last_results = $mycache ? %cachedRes : %{$urinew->{results}};
            $last_mycache = $mycache;
        }
        $lcnt = 0;
        foreach (@lb) {
            my $blhash = $_;
            mlog(0,"URIBL: DIAG-LB: processing $_ (Cache=$mycache)") if ( $URIBLLog > 2 );
            s/\Q$domain\E\.//g;
            mlog(0,"URIBL: DIAG-LR: processing $_ with $last_results{$blhash}") if ( $URIBLLog > 2 );

            if ($last_results{$blhash} =~ /(127\.\d+\.\d+\.\d+)/o) {
                if ($1 eq '127.0.0.1' && ! exists $URIBLweight{$_}{'127.0.0.1'}) {  # query volume reached or error
                    mlog(0,"URIBL: SP '$_' returned a 'query volume reached - 127.0.0.1' for $domain") if ( $URIBLLog > 1 );
                    next;
                }
                my $w;
                $w = matchHashKey($URIBLweight{$_},$1,"0 1 1") if exists $URIBLweight{$_} && $URIBLweight{$_};
                if ($w) {
                    $uriweight += $w * $isobfuscated;
                    mlog(0,"URIBL: DIAG-F: $domain, listed in $_, reply: $1, weight: $w, current uri score: $uriweight, is obfuscated: ".($isobfuscated-1)) if ( $URIBLLog > 2 );
                } else {
                    mlog(0,"URIBL: DIAG-N: $domain, listed in $_, reply: $1, weight: $w, current uri score: $uriweight, is obfuscated: ".($isobfuscated-1)) if ( $URIBLLog > 2 );
                    next;
                }
            } else {
                if (exists $URIBLweight{$_} && exists $URIBLweight{$_}{'*'} && $URIBLweight{$_}{'*'} ) {
                    my $w = $URIBLweight{$_}{'*'};
                    $uriweight += $w * $isobfuscated;
                    mlog(0,"URIBL: DIAG-F*: $domain, listed in $_, weight: $w, current uri score: $uriweight, is obfuscated: ".($isobfuscated-1)) if ( $URIBLLog > 2 );
                } else {
                    mlog(0,"URIBL: DIAG-N*: $domain, listed in $_, weight: 0, current uri score: $uriweight, is obfuscated: ".($isobfuscated-1)) if ( $URIBLLog > 2 );
                    next;
                }
            }
            $lcnt++;
        }
        $uribls_returned += $lcnt;
        $weightsum += $uriweight;

        if (! $mycache) {
            if ($lcnt == 0) {
                URIBLCacheAdd( $domain, "2" ) if (! @lb);
            } else {
                my $listed;
                foreach (@listed_by) {
                    $listed .= "$_<-" . $last_results{$_} . ' ' ;
                }
                $listed =~ s/\s$//o;
                $listed =~ s/^\s//o;
                $listed =~ s/\Q$domain\E\.//g;
                URIBLCacheAdd( $domain, "1", $listed );
            }
        } elsif ($mycache && $lcnt == 0) {
            delete $URIBLCache{$domain};
        }

        last if ( (!$URIBLmaxweight && $uribls_returned >= $URIBLmaxhits)
               or ($URIBLmaxweight && $weightsum >= $URIBLmaxweight));
    }

    @listed_by = @last_listed_by;
    %results = %last_results;
    $mycache = $last_mycache;
    my $listed = "@listed_by";
    $listed =~ s/\Q$last_listed_domain\E\.//g;
    $weightsum = $URIBLmaxweight if $URIBLmaxweight && $weightsum > $URIBLmaxweight;

    if ( $uribls_returned > 0) {
        foreach (@listed_by) {
            $received_uribl .= "$_<-" . $results{$_} . "; " ;
        }
        $received_uribl =~ s/\Q$last_listed_domain\E\.//g;
        $listed = $received_uribl if $URIBLLog >= 2;
        $this->{uri_listed_by} = $received_uribl if (! $fh);
        $mycache = $mycache ? 'URIBLcache' : 'URIBL' ;
        if ( (!$URIBLmaxweight && $uribls_returned >= $URIBLmaxhits) or ($URIBLmaxweight && $weightsum >= $URIBLmaxweight) ) {
            $this->{messagereason} = "$mycache: fail, $last_listed_domain listed in $listed";
        } else {
            $this->{messagereason} = "$mycache: neutral, $last_listed_domain listed in $listed";
            mlog( $fh, "$tlit ($this->{messagereason}" )
              if ( $URIBLLog && $ValidateURIBL >= 2 && $fh);
            pbWhiteDelete( $fh, $thisip ) if $fh;
            return 1 if $ValidateURIBL == 2;
            $weightsum = ${'uriblnValencePB'}[0] unless $URIBLmaxweight;
            pbAdd( $fh, $thisip, calcValence($weightsum,'uriblnValencePB'), "URIBLneutral" ) if $fh;
            $this->{myheader} .= "X-Assp-$this->{messagereason}\r\n" if $AddURIBLHeader;
            return 1 ;
        }
    } else {
        return 1;
    }

    mlog( $fh, "$tlit ($this->{messagereason}" )
      if ( $URIBLLog && $ValidateURIBL >= 2 && $fh);
    return 1 if $ValidateURIBL == 2 && $fh;

    pbWhiteDelete( $fh, $thisip ) if $fh;
    $weightsum = ${'uriblValencePB'}[0] if $weightsum < ${'uriblValencePB'}[0] && ! $URIBLmaxweight;
    pbAdd( $fh, $thisip, calcValence($weightsum,'uriblValencePB'), "URIBLfailed" ) if $fh;
    $this->{myheader} .= "X-Assp-$this->{messagereason}\r\n" if $AddURIBLHeader && $fh;
    $this->{uri_listed_by} = $received_uribl if (! $fh);
    return 1 if $ValidateURIBL == 3;
    my $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;
    $reply =~ s/REASON/URIBL Listed in $listed/go;
    $reply = replaceerror ($fh, $reply);
    my $testmode = $ValidateURIBL == 4;
    $testmode = 0 if $this->{uriblforce};
    $slok = 0 if $this->{uriblforce};
    if ($fh && ! $slok) {$Stats{uriblfails}++;}
    thisIsSpam($fh,$this->{messagereason},$URIBLFailLog,$reply,$testmode,$slok,$done) if ($fh);
    return 0;
}

sub ipNetwork {
    my ($ip,$netblock)=@_;
    if ($ip =~ /:[^:]*:/o) {
        return ipv6expand($ip) if (!$netblock);
        $netblock = 64 if $netblock == 1;
        return join ':', map{my $t = sprintf("%x", oct("0b$_"));$t;} unpack 'a16' x 8, ipv6binary($ip,$netblock) . '0' x (128 - $netblock);
    } else {
        return $ip if (!$netblock);
        $netblock = 24 if $netblock == 1;
        my $u32 = unpack 'N', pack 'CCCC', split /\./o, $ip;
        my $mask = unpack 'N', pack 'B*', '1' x $netblock . '0' x (32 - $netblock );
        return join '.', unpack 'CCCC', pack 'N', $u32 & $mask;
    }
}

# retrieve the trailing IPv4 address from a tunneled IPv6address
sub ipv6TOipv4 {
    my $ip = shift;
    $ip =~ s/^.*?($IPv4Re)$/$1/o;
    return $ip;
}

# converts IPv4 112.23.45.16 to 7017:2d10
sub ipv4TOipv6 {
    my $ip = shift;
    $ip =~ s/0?x?([A-F][A-F0-9]?|[A-F0-9]?[A-F])/hex($1)/goie;

    my ($h1,$h2,$h3,$h4) = split(/\./o,$ip);
    return sprintf("%x",256 * $h1 + $h2).':'.sprintf("%x",256 * $h3 + $h4);
}

# convert IPv6 2001:123:456::1 to 2001:123:456:0:0:0:0:1
# and convert trailing IPv4 to two IPv6 words
sub ipv6expand {
    my $ip = shift;
    return $ip if ($ip !~ /:/o);
    $ip =~ s/($IPv4Re)$/ipv4TOipv6($1)/eo;
    return $ip if ($ip !~ /::/o);
    my $col = $ip =~ tr/://;
    $col = 8 if $col > 8;
    $ip =~ s/^(.*)::(.*)$/($1||'0').':'.('0:'x(8-$col)).($2||'0')/oe;
    return $ip;
}

# convert IPv6 address to binary string
sub ipv6binary {
    my ($ip, $bits) = @_;
    return pack("a$bits", unpack 'B128', pack 'n8', map{my $t = hex($_);$t;} split(/:/o, ipv6expand($ip)));
}

# convert IPv6 2001:123:456::1 to 2001:0123:0456:0000:0000:0000:0000:0001
sub ipv6fullexp {
    return sprintf('%04s:'x(unpack("A1",${'X'})+5).'%04s',split(/:/o,ipv6expand(shift)));
}

# convert IPv6 to lower case reverse doted digits for RBL / RWL checks
# 2001:DB8:abc:123::42 to
# 2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.3.2.1.0.c.b.a.0.8.b.d.0.1.0.0.2
sub ipv6hexrev {
    local $_ = ipv6fullexp(shift);
    return join('.',split(//o, reverse $_)) unless(s z:zzg-((ord(":")*4+34)%($_[0]+1)));
    undef;
}

sub formatTimeInterval {
  my $interval=shift;
  my $res;
  $res.=$_.'d ' if local $_=int($interval/(24*3600)); $interval%=(24*3600);
  $res.=$_.'h ' if $_=int($interval/3600); $interval%=3600;
  $res.=$_.'m ' if $_=int($interval/60); $interval%=60;
  $res.=$interval.'s ' if ($interval || !defined $res);
  $res=~s/\s$//o;
  return $res;
}

sub getRRA {
    my $dom = shift;
    my @IP;
    my $type = 'A';
    eval {
        if (defined(${chr(ord($type)+23)}) && (my $res = queryDNS($dom ,$type))) {
            my @answer = map{$_->string} $res->answer;
            while (@answer) {
                push @IP, Net::DNS::RR->new(shift @answer)->rdatastr;
            }
        }
    };
    return @IP;
}
sub getRRData {
    my ($dom, $type) = @_;
    return getRRA($dom) if $type eq 'A';
    my $answer;
    my $RR;
    my $gotname;
    my $gottype;
    my $gotdata;
    eval {
      my $res = queryDNS($dom,$type);
      if ($res) {
          $answer = ($type ne 'PTR') ? join('', map{$_->string} $res->answer)
                                     : [$res->answer->string]->[0];
          $RR = Net::DNS::RR->new($answer);
          $gotname = $RR->name;
          $gottype = $RR->type;
          $gotdata = $RR->rdatastr;
      }
    };
    return if $@;
    return if $gotname ne $dom && $type ne 'PTR';
    return if $gottype ne $type;
    return unless $gotdata;
    return $gotdata;
}

sub queryDNS {
    my ($domain, $type) = @_;

    my $rslv = Net::DNS::Resolver->new(
        nameservers => \@nameservers,
        tcp_timeout => $DNStimeout,
        udp_timeout => $DNStimeout,
        retrans     => $DNSretrans,
        retry       => $DNSretry
    ) or return;
    getRes('force', $rslv);

    my $resp;
    eval
    {
            # set a timeout
            local $SIG{ALRM} = sub { die "DNS query timeout for $domain\n" };
            alarm $DNStimeout + 2;
            eval {$resp = $rslv->query($domain, $type);};
            my $E = $@;
            alarm 0;
            die $E if $E;
    };
    my $E = $@;
    alarm 0;
    return if $E;
    return $resp;
}
sub getRes {
    my $run = shift;
    eval(<<'EOT');
    $run.='_v'.(unpack("A1",${'X'})+2);
    $_[0]->$run(! $CanUseIOSocketINET6 || $forceDNSv4);
EOT
    return;
}

sub getDNSResolver {
    my $res;

    $res = $DNSresolver;
    if (! $res or $DNSresolverTime < time) {
        my $class = shift;
        $class ||= 'Net::DNS::Resolver';
        $res = $orgNewDNSResolver->($class,
            nameservers => \@nameservers,
            tcp_timeout => $DNStimeout,
            udp_timeout => $DNStimeout,
            retrans     => $DNSretrans,
            retry       => $DNSretry,
            @_
        );
        getRes('force', $res);
        $DNSresolver = $res;
    }
    $DNSresolverTime = time + 1800 if $DNSresolver;
    return $res;
}

sub ForgedHeloOK {
    my ( $fh, $rcpt ) = @_;
    my $this = $Con{$fh};
    my $ip = $this->{ip};
    my $helo = $this->{helo};

    return 1 if $AsASecondary;
    return 1 if $this->{addressedToSpamBucket};

    return 1 if $this->{ispip};
    return 1 if $this->{nohelo};
    return 1 if $this->{notspamtag};

    return 1 if $DoFakedWL && $this->{whitelisted}; 
    return 1 if $DoFakedNP && $this->{noprocessing}; 

    return 1 if $ip =~ /$IPprivate/ && $ip ne "127.0.0.1";

    return 1 if !$DoFakedLocalHelo;
    return 1 if $this->{relayok};
    return 1 if $this->{acceptall};

    return 1 if $heloBlacklistIgnore && $helo =~ $HBIRE;

    my $mDoFakedLocalHelo = $DoFakedLocalHelo;
    $this->{testmode} = 0;
    $this->{testmode} = 1    if $DoFakedLocalHelo == 4 or $allTestMode;
    $mDoFakedLocalHelo = 1         if $DoFakedLocalHelo == 4;
    my $tlit = tlit($DoFakedLocalHelo);

    ( my $literal ) =
      $helo =~ /\[?((?:\d{1,3}\.){3}\d{1,3})\]?/;    # domain literal

       if (   ( $localDomains && $helo =~ /$LDRE/)
        || ($localDomainsFile && $localDomainsFile{$helo})
        || $helo eq "friend"
        || $helo eq "localhost"        
        || $myServerRe && $helo =~ /$LHNRE/
        || $literal && $literal =~ /$LHNRE/
        || $literal && lc($literal) eq lc($localhostip)) {

        $this->{prepend} = "[ForgedHELO]";

        $this->{messagereason} = "forged Helo: '$helo'";
        $tlit= "[scoring:${'fhValencePB'}[0]]" if $mDoFakedLocalHelo == 3;

        mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" )
          if $mDoFakedLocalHelo >= 2;
        delayWhiteExpire($fh);
        pbWhiteDelete( $fh, $this->{ip} );
        return 1 if $mDoFakedLocalHelo == 2;

        pbAdd($fh,$ip,'fhValencePB','ForgedHELO');

        return 1 if $mDoFakedLocalHelo == 3;
        $Stats{invalidHelo}++;
        return 0;
    }
    return 1;
}
sub NoSpoofingOK {
    my ( $fh, $what ) = @_;
    my $this = $Con{$fh};
    d("NoSpoofingOK - $what");
    return 1 if $this->{NoSpoofingOK}{$what};
    $this->{NoSpoofingOK}{$what} = 1;
    return 1 if ! $DoNoSpoofing;
    skipCheck($this,'sb','np','ro','aa') && return 1;
    return 1 if ! $this->{$what};
    return 1 if $this->{$what} =~ /$BSRE/;
    return 1 if ! localmail( $this->{$what} ) || $LDAPoffline;
    return 1 if $onlySpoofingCheckIP && ! matchIP( $this->{ip}, 'onlySpoofingCheckIP', 0, 1);
    return 1 if matchIP( $this->{ip}, 'noSpoofingCheckIP', 0, 1 );
    return 1 if $onlySpoofingCheckDomain && ! matchSL( $this->{$what}, 'onlySpoofingCheckDomain' , 0, 1);
    return 1 if matchSL( $this->{$what}, 'noSpoofingCheckDomain' );

    my $tlit = tlit($DoNoSpoofing);
    my $toscore = 0;
    foreach (keys %{$this->{NoSpoofingOK}}) { $toscore += $this->{NoSpoofingOK}{$_}; }
    $this->{prepend}       = "[SpoofedSender]";
    $this->{messagereason} = "No Spoofing Allowed '$this->{$what}' in '$what'";
    mlog( $fh, "$tlit ($this->{messagereason})" )
           if $ValidateSenderLog && $DoNoSpoofing >= 2;

    return 1 if $DoNoSpoofing == 2 ;
    pbAdd( $fh, $this->{ip}, 'flValencePB', 'NoSpoofing' ) if $toscore < 10;
    $this->{NoSpoofingOK}{$what} = 10;
    return 1 if $DoNoSpoofing == 3 ;
    return 0;
}

# do forged local sender
sub LocalSenderOK {
    my ( $fh, $ip ) = @_;

    my $this = $Con{$fh};

    return 1 if $this->{notspamtag};
    my $mf = &batv_remove_tag($fh,$this->{mailfrom},'');
    return 1 if !$DoNoValidLocalSender;    
    return 1 if ! $LocalAddresses_Flat && ! $DoLDAP && (! $DoVRFY || (! scalar(keys %DomainVRFYMTA) && ! scalar(keys %FlatVRFYMTA)));
    d('LocalSenderOK');
    return 1 if $noSpoofingCheckDomain 
        && matchSL( $mf, 'noSpoofingCheckDomain' );
    return 1 if $noSpoofingCheckIP
        && matchIP( $ip, 'noSpoofingCheckIP', 0, 1 ) ;
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{localsenderdone};
    $this->{localsenderdone} = 1;

    return 1 if $this->{noprocessing};
    return 1 if $this->{passingreason} =~ /white/;

    return 1 if $this->{relayok};
    return 1 if $this->{acceptall};
    return 1 if $this->{ispip};
    return 1 if !localmail( $this->{mailfrom} );  

    my $mDoNoValidLocalSender = $DoNoValidLocalSender;
    $this->{testmode} = 0;
    $this->{testmode} = 1    if $DoNoValidLocalSender == 4 or $allTestMode;
    $mDoNoValidLocalSender = 1         if $DoNoValidLocalSender == 4;
    #enforce valid local mailfrom

    my $tlit = tlit($mDoNoValidLocalSender);
    $this->{prepend} = "[UnknownLocalAddress]";

    $this->{islocalmailaddress} = 0;

    if ( $LocalAddresses_Flat
        && matchSL( $mf, 'LocalAddresses_Flat' ) )
    {
        $this->{islocalmailaddress} = 1;
    } else {

      # Need another check?
      # check sender against LDAP or VRFY ?
      $this->{islocalmailaddress} = &localmailaddress($fh,$mf)
          if (($DoLDAP && $CanUseLDAP) or
              ($CanUseNetSMTP && $DoVRFY &&
               $mf =~ /^([^@]*@)([^@]*)$/o &&
               (&matchHashKey('DomainVRFYMTA',lc $2) or &matchHashKey('FlatVRFYMTA',lc "\@$2"))));
    }
    if ( !$this->{islocalmailaddress} ) {
        pbWhiteDelete( $fh, $this->{ip} );
        $this->{messagereason} = "Unknown address with local domain '$this->{mailfrom}'";
        mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" )
          if $ValidateSenderLog && $mDoNoValidLocalSender >= 2;
        return 1 if $mDoNoValidLocalSender == 2;
        pbAdd( $fh, $this->{ip}, 'flValencePB', "InvalidLocalSender");
        return 1 if $mDoNoValidLocalSender == 3;
        return 0;
    }
    return 1;
}

sub LocalAddressOK {
    my $fh = shift;
    my $this = $Con{$fh};
    d('LocalAddressOK');
    $this->{islocalmailaddress} = 0;

    if (($this->{relayok} and &batv_remove_tag(0,$this->{mailfrom},'') =~ /$BSRE/) or  # a bounce mail from a internal MTA
         &localmailaddress($fh,$this->{mailfrom})) {

        $this->{islocalmailaddress} = 1;
    }
    return $this->{islocalmailaddress};
}

sub AUTHErrorsOK {
    my $fh = shift;
    return 1 unless $MaxAUTHErrors;
    my $this = $Con{$fh};
    return 1 if ($this->{relayok});
    return 1 if ($this->{whitelisted});
    return 1 if ($this->{noprocessing} == 1);
    return 1 if ($this->{ispip});
    return 1 if matchIP($this->{ip},'noMaxAUTHErrorIPs',0,1);
    my $ip = $this->{ip};
    $ip = &ipNetwork( $ip, $PenaltyUseNetblocks);

    return 1 if $AUTHErrors{$ip}++ <= $MaxAUTHErrors;
    $this->{prepend} = "[AUTHError]";
    $this->{messagereason}="too many AUTH errors from $ip";
    pbAdd( $fh, $this->{ip}, 'autValencePB', 'AUTHErrors' ) if ! matchIP($ip,'noPB',0,1);
    $AUTHErrors{$ip}++;
    return 0;
}
sub SameSubjectOK {
    my $fh = shift;
    d('SameSubjectOK');
    my $this = $Con{$fh};
    $this->{prepend} = "[SameSubject]";
    &makeSubject($fh);
    my $sub;
     $sub = $Con{$fh}->{subject3};
     $this->{red} = 1 if ! $sub;
     return 1 if ! $sub;

    my $mf = &batv_remove_tag(0,$this->{mailfrom},'');

    my $myip = $this->{ip};
    if ($this->{ispip} && $this->{cip}) {
        $myip = $this->{cip};
    } elsif ($this->{ispip}) {
        return 1;
    }
    return 1 if ($SameSubjectOnly && ! &matchSL($mf,'SameSubjectOnly'));
    return 1 if (&matchSL($mf,'SameSubjectNoAddresses'));
    return 1 if (&matchIP($myip,'SameSubjectNoIP',$fh, 1));

    if (               !$this->{whitelisted}
                    && !$this->{relayok}
                    && $DoSameSubject
                    && ! matchIP( $myip, 'acceptAllMail',   0, 1 )

       ) {

                my $tlit = &tlit($DoSameSubject);
                $myip=&ipNetwork($myip, $DelayUseNetblocks );
                $myip .= '.' if $DelayUseNetblocks;

                if ( $SameSubjectCache{$sub}) {
                    $SameSubjectCache{$sub} = time();
                    $this->{messagereason} = "Same subject '$sub' limited to $SameSubjectNumber";
                    $tlit = "[scoring:${'isValencePB'}[0]]" ;   
                    mlog( $fh, "$tlit $this->{messagereason}");
                    pbAdd( $fh, $myip, 'isValencePB', 'LimitingSameSubject' );
                            pbWhiteDelete( $fh, $myip );                       
                    return 0 if $DoSameSubject == 1;
                    return 1;
                }

                if ((time() - $SameSubjectTriesExpiration{$sub}) > $SameSubjectInterval) {
                    $SameSubjectTries{$sub} = 1;
                    $SameSubjectTriesExpiration{$sub} = time();

                } else {

                    $SameSubjectTriesExpiration{$sub} = time() if $SameSubjectTries{$sub}==1;
                    $SameSubjectTries{$sub}++;
                }

                $tlit .= "[testmode]"   if ($allTestMode && $DoSameSubject) == 1 || $DoSameSubject == 4;
                my $mDoSameSubject = $DoSameSubject;
                $mDoSameSubject = 3 if ($allTestMode && $DoSameSubject == 1) || $DoSameSubject == 4;

                if ( $SameSubjectTries{$sub} > $SameSubjectNumber ) {
                    $this->{messagereason} = "number of same subjects '$sub' surpassed limit SameSubjectNumber ($SameSubjectNumber)";
                    pbWhiteDelete( $fh, $myip );
                    $tlit = "[scoring:${'isValencePB'}[0]]"  if $mDoSameSubject == 3;
                    mlog( $fh, "$tlit $this->{messagereason}")
                      if ($SessionLog or $denySMTPLog) && $mDoSameSubject != 1 && $SameSubjectTries{$sub} == $SameSubjectNumber + 1;
                    $SameSubjectCache{$sub} = time();
                    pbAdd( $fh, $myip, 'isValencePB', 'LimitingSameSubject' ) if $mDoSameSubject != 2;
                    if ( $mDoSameSubject == 1 && $sub ne "empty" ) {
                        return 0;
                    }
                }
    }
    return 1;
}

sub SameAUTHuserOK {
    my($fh,$sub)=@_;
    d('SameAUTHuserOK');
    my $this = $Con{$fh};

    return 1 if $this->{SameAUTHuserOK};
    $this->{SameAUTHuserOK} = 1;

    if ((time() - $SameAUTHuserDuration{$sub}) > $maxSameAUTHuserDuration) {
        $SameAUTHuserTries{$sub} = 1;
        $SameAUTHuserDuration{$sub} = time();

    } else {

        $SameAUTHuserDuration{$sub} = time() if $SameSubjectTries{$sub}==1;
        $SameAUTHuserTries{$sub}++;
    }
    my $tlit = &tlit($DoSameAUTHuser);
    $tlit .= "[testmode]"   if ($allTestMode && $DoSameAUTHuser) == 1 || $DoSameSubject == 4;
    my $mDoSameAUTHuser = $DoSameAUTHuser;
    $mDoSameAUTHuser = 3 if ($allTestMode && $DoSameAUTHuser == 1) || $DoSameAUTHuser == 4;

    if ( $SameAUTHuserTries{$sub} > $maxSameAUTHuser ) {
        $this->{prepend} = "[SameAUTHuser]";
        $this->{messagereason} = "number of same AUTH user '$sub' surpassed limit maxSameAUTHuser ($maxSameAUTHuser)";
        mlog( $fh, "$tlit $this->{messagereason}");
        if ( $mDoSameAUTHuser == 1 ) {
            return 0;
        }
    }

    return 1;
}

sub FrequencyIPOK {
    my $fh = shift;
    d('FrequencyIPOK');
    my $this = $Con{$fh};
    my $ConIp550 = $this->{ip};
    if ($this->{ispip} && $this->{cip}) {
        $ConIp550 = $this->{cip};
    } elsif ($this->{ispip}) {
        return 1;
    }

    return 1 if $this->{doneDoFrequencyIP} eq $ConIp550;
    $this->{doneDoFrequencyIP} = $ConIp550;

    if (               
                        ! $this->{relayok}
                    && !$this->{whitelisted}
                    && !$this->{noprocessing}
                    && !$this->{contentonly}
                    && $DoFrequencyIP
                    && $maxSMTPipConnects
                    && ! matchIP( $ConIp550, 'noPB',            $fh, 1 )
                    && ! matchIP( $ConIp550, 'noProcessingIPs', $fh, 1 )
                    && ! matchIP( $ConIp550, 'whiteListedIPs',  $fh, 1 )
                    && ! matchIP( $ConIp550, 'noDelay',         $fh, 1 )
                    && ! matchIP( $ConIp550, 'noBlockingIPs',   $fh, 1 )
                    && ! matchIP( $ConIp550, 'acceptAllMail',   $fh, 1 )

                    &&   pbBlackFind($ConIp550)
                    && ! pbWhiteFind($ConIp550)
       )
            # ip connection limiting per timeframe
    {

       # If the IP address has tried to connect previously, check it's frequency
                if ( $IPNumTries{$ConIp550} ) {
                    $IPNumTries{$ConIp550}++;

              # If the last connect time is past expiration, reset the counters.
              # If it has not expired, but is outside of frequency duration and
              # below the maximum session limit, reset the counters. If it is
              # within duration
                    if (((time() - $IPNumTriesExpiration{$ConIp550}) > $maxSMTPipExpiration)  || ((time() - $IPNumTriesDuration{$ConIp550}) > $maxSMTPipDuration) && ($IPNumTries{$ConIp550} < $maxSMTPipConnects)) {
                        $IPNumTries{$ConIp550} = 1;
                        $IPNumTriesDuration{$ConIp550} = time();
                        $IPNumTriesExpiration{$ConIp550} = time();
                    }
                } else {
                    $IPNumTries{$ConIp550} = 1;
                    $IPNumTriesDuration{$ConIp550} = time();
                    $IPNumTriesExpiration{$ConIp550} = time();

                }
                my $tlit = &tlit($DoFrequencyIP);
                $tlit = "[testmode]"   if $allTestMode && $DoFrequencyIP == 1 || $DoFrequencyIP == 4;

                my $mDoFrequencyIP = $DoFrequencyIP;
                $mDoFrequencyIP = 3 if $allTestMode && $DoFrequencyIP == 1 || $DoFrequencyIP == 4;

                if ( $IPNumTries{$ConIp550} > $maxSMTPipConnects ) {
                    $this->{prepend} = "[IPfrequency]";
                    $this->{messagereason} = "'$ConIp550' passed limit($maxSMTPipConnects) of ip connection frequency";
                    mlog( $fh, "$tlit $this->{messagereason}")
                      if $SessionLog >= 2
                          && $IPNumTries{$ConIp550} > $maxSMTPipConnects + 1;
                    mlog( $fh,"$tlit $this->{messagereason}")
                      if $SessionLog
                          && $IPNumTries{$ConIp550} == $maxSMTPipConnects + 1;
                    pbAdd( $fh, $this->{ip}, 'ifValencePB', "IPfrequency" ) if $mDoFrequencyIP!=2;
                    if ( $mDoFrequencyIP == 1 ) {
                        $Stats{smtpConnLimitFreq}++;
                        unless (($send250OKISP && $this->{ispip}) || $send250OK) {
                            seterror( $fh, "554 5.7.1 too frequent connections for '$ConIp550'", 1 );
                            return 0;
                        }
                    }
                }
    }
    return 1;
}

# returns 0 on success - else next possible try time
sub localFrequencyNotOK {
    my $fh = shift;
    return 0 unless $LocalFrequencyInt;
    return 0 unless $LocalFrequencyNumRcpt;
    my $this=$Con{$fh};
    d('localFrequencyNotOK');

    return 0 unless $this->{mailfrom};
    return 0 unless $this->{relayok};
    return 0 if $this->{noprocessing};
    my ($to) = $this->{rcpt} =~ /(\S+)/o;
    my $mf = batv_remove_tag(0,$this->{mailfrom},'');
    return 0 if matchSL( [$to,$mf], 'EmailAdmins' );
    return 0 if lc($to) eq lc($EmailFrom);
    return 0 if lc($mf) eq lc($EmailFrom);

    return 0 if ($LocalFrequencyOnly && ! &matchSL($mf,'LocalFrequencyOnly'));
    return 0 if ( matchSL($mf,'NoLocalFrequency'));
    return 0 if ( matchIP( $this->{ip}, 'NoLocalFrequencyIP', 0, 1 ));

    my $time = time;
    my $numrcpt;
    my $firsttime;
    my $data;
    my $hat; $hat = $1 if $mf =~ /(\@.*)/;

    my %F = split(/ /o,$localFrequencyCache{$hat});
    my $i;
    foreach (sort keys %F) {
        if ($_ + $LocalFrequencyInt  < $time) {
            delete $F{$_};
            next;
        } else {
            $numrcpt += $F{$_};
            $firsttime = $_ if $i < 1;
        }
        $i++;
    }
    foreach (sort keys %F) {
        $data .= "$_ $F{$_} ";
    }
    $firsttime = $time unless $firsttime;
    $localFrequencyCache{$hat} = $data . "$time $this->{numrcpt}";
    $numrcpt += $this->{numrcpt};
    return 0 if $numrcpt < $LocalFrequencyNumRcpt;
    return $firsttime + $LocalFrequencyInt;
}

sub NumRcptOK {
    my($fh,$block)=@_;
    return 1 unless $DoMaxDupRcpt;
    my $this=$Con{$fh};
    d('NumRcptOK');
    my $DoMaxDupRcpt = $DoMaxDupRcpt;
    $DoMaxDupRcpt = 3 if !$block  && $DoMaxDupRcpt == 1;
    return 1 unless $this->{numrcpt};
    return 1 unless (scalar keys %{$this->{rcptlist}});
    skipCheck($this,'aa','ro','wl') && return 1;
    return 1 if $this->{noprocessing} & 1;
    return 1 if $this->{spamlover} & 1;
    return 1 if $this->{allLoveSpam} & 1;
    return 1 if ((scalar keys %{$this->{rcptlist}}) + $MaxDupRcpt >= $this->{numrcpt});
    my $maxRcpt;
    my $maxNum = 0;
    while (my ($k,$v) = each %{$this->{rcptlist}}) {
        my $tt = needEs($v,' time','s');
        mlog($fh,"info: address $k used $tt") if $ValidateUserLog >= 2;
        if ($v > $maxNum) {
            $maxNum = $v;
            $maxRcpt = $k;
        }
    }
    my $tlit = &tlit($DoMaxDupRcpt);
    $this->{prepend} = "[MaxDuplicateRcpt]";
    $this->{messagereason} = "too many duplicate recipients ($maxRcpt , $maxNum)";
    mlog($fh,"$tlit $this->{messagereason}") if $ValidateUserLog;
    return 1 if $DoMaxDupRcpt == 2;
    my $reply = "550 5.5.3 $this->{messagereason}";
    pbAdd( $fh, $this->{ip}, 'mdrValencePB', 'MaxDuplicateRcpt' );
    return 1 if $DoMaxDupRcpt == 3;
    $Stats{rcptNonexistent}++;
    seterror($fh, $reply,1);
    return 0;
}

sub MessageSizeOK {
    my $fh = shift;
    my $this=$Con{$fh};
    return 1 if $this->{sizeok};

    d('MessageSizeOK');
    return 1 if $noMaxSize && matchSL( $this->{mailfrom}, 'noMaxSize' );
    my $maxRealSize = $this->{maxRealSize} || $maxRealSize || 0;
    my $maxSize = $this->{maxSize} || $maxSize || 0;
    if ($this->{relayok} && ! defined $this->{maxSize}) {
        $this->{maxRealSize} = $this->{maxSize} = 0;
        my @MSadr  = sort {$main::b <=> $main::a} map {matchHashKey('MSadr' ,$_)} split(' ',$this->{rcpt}),$this->{mailfrom},$this->{ip},$this->{cip},@{"$this.sip"};
        my @MRSadr = sort {$main::b <=> $main::a} map {matchHashKey('MRSadr',$_)} split(' ',$this->{rcpt}),$this->{mailfrom},$this->{ip},$this->{cip},@{"$this.sip"};
        $maxSize = $this->{maxSize} = $MSadr[0] if (defined $MSadr[0]);
        $maxSize = $this->{maxSize} = 0 if grep({$_ == 0} @MSadr);
        $maxRealSize = $this->{maxRealSize} = $MRSadr[0] if (defined $MRSadr[0]);
        $maxRealSize = $this->{maxRealSize} = 0 if grep({$_ == 0} @MRSadr);
    }

    my $maxRealSizeExternal = $this->{maxRealSizeExternal} || $maxRealSizeExternal || 0;
    my $maxSizeExternal = $this->{maxSizeExternal} || $maxSizeExternal || 0;
    if (! $this->{relayok} && ! defined $this->{maxSizeExternal}) {
        $this->{maxRealSizeExternal} = $this->{maxSizeExternal} = 0;
        my @MSEadr  = sort {$main::b <=> $main::a} map {matchHashKey('MSEadr' ,$_)} split(' ',$this->{rcpt}),$this->{mailfrom},$this->{ip},$this->{cip},@{"$this.sip"};
        my @MRSEadr = sort {$main::b <=> $main::a} map {matchHashKey('MRSEadr',$_)} split(' ',$this->{rcpt}),$this->{mailfrom},$this->{ip},$this->{cip},@{"$this.sip"};
        $maxSizeExternal = $this->{maxSizeExternal} = $MSEadr[0] if (defined $MSEadr[0]);
        $maxSizeExternal = $this->{maxSizeExternal} = 0 if grep({$_ == 0} @MSEadr);
        $maxRealSizeExternal = $this->{maxRealSizeExternal} = $MRSEadr[0] if (defined $MRSEadr[0]);
        $maxRealSizeExternal = $this->{maxRealSizeExternal} = 0 if grep({$_ == 0} @MRSEadr);
    }

    if ( ($this->{relayok} && $maxRealSize
            && ( ($this->{SIZE} > $this->{maillength} ? $this->{SIZE} : $this->{maillength}) * $this->{numrcpt} > $maxRealSize )) or
         (!$this->{relayok} && $maxRealSizeExternal
            && ( ($this->{SIZE} > $this->{maillength} ? $this->{SIZE} : $this->{maillength}) * $this->{numrcpt} > $maxRealSizeExternal ))
       )
    {
        &makeSubject($fh);
        my $max = $this->{relayok} ? &formatNumDataSize($maxRealSize) : &formatNumDataSize($maxRealSizeExternal);
        my $err = "552 message exceeds $max(size \* rcpt)";
        $this->{prepend} = "[MaxRealSize]";
        if ($this->{relayok}) {
            mlog( $fh, "error: message exceeds maxRealSize $max (size \* rcpt)!" );
        } else {
            my $logsub = ( $subjectLogging && $this->{originalsubject} ? " $subjectStart$this->{originalsubject}$subjectEnd" : '' );
            mlog( $fh, "error: message exceeds maxRealSizeExternal $max (size \* rcpt)!)$logsub;",0,2 );
        }

        $this->{sizeok} = 1;
        NoLoopSyswrite( $fh, "$err\r\n" );
        done($fh);
        return 0;
    }

    if ( (  $this->{relayok} && $maxSize         && $this->{maillength} > $maxSize         ) or
         (! $this->{relayok} && $maxSizeExternal && $this->{maillength} > $maxSizeExternal )
       )
    {
        &makeSubject($fh);
        my $max = $this->{relayok} ? &formatNumDataSize($maxSize) : &formatNumDataSize($maxSizeExternal);
        my $err = "552 message exceeds $max (size)";
        $this->{prepend} = "[MaxSize]";
        if ($this->{relayok}) {
            mlog( $fh, "error: message exceeds maxSize $max (size)!" );
        } else {
            my $logsub = ( $subjectLogging && $this->{originalsubject} ? " $subjectStart$this->{originalsubject}$subjectEnd" : '' );
            mlog( $fh, "error: message exceeds maxSizeExternal $max (size))$logsub;",0,2 );
        }
        $this->{sizeok} = 1;
        NoLoopSyswrite( $fh, "$err\r\n" );
        done($fh);
        return 0;
    }
    return 1;
}

#queries the SenderBase service
sub SenderBaseMyIP {
    my $ip = shift;
    d('SenderBaseMyIP');
    return if !$CanUseSenderBase;
    return $SenderBaseMyCountry if $SenderBaseMyCountry;
    $SenderBaseMyCountry = eval{
        Net::SenderBase::Query->new(
              Address => $ip,
              Host => 'query.senderbase.org',
              Timeout => 10
            )->results->ip_country;
        };
    return $SenderBaseMyCountry;
}

#queries the SenderBase service
sub SenderBaseOK {
    my ( $fh, $ip ) = @_;
    return 1 if !$CanUseSenderBase;
    my $this = $Con{$fh};
    $fh = 0 if "$fh" =~ /^\d+$/o;
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};
    return 1 if $this->{senderbasedone};
    $this->{senderbasedone}=1;
    d('SenderBaseOK');
    return 1 if $this->{notspamtag};

    my $results;
    my $query;
    my $cache;
    my $skip;
    my $tlit;

    my $mfd; $mfd  = $1 if $this->{mailfrom} =~ /\@(.*)/o;
    return 1 if $this->{notspamtag};
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{whitelisted};
    return 1 if $this->{noprocessing};
    return 1 if $AsASecondary;
    return 1 if !$DoOrgWhiting && !$DoOrgBlocking && !$DoCountryBlocking;
    return 1 if $this->{ispip} && !$this->{cip};
    return 1 if $this->{acceptall};

    return 1 if $this->{ip} =~ /$IPprivate/;
    return 1 if $this->{relayok};

    return 1 if $this->{contentonly};

    my $ipcountry;
    my $orgname;
    my $domainname;
    my $blacklistscore;
    my $hostname_matches_ip;
    my $fortune1000;
    my $ipbondedsender;
    my $domainrating;
    my $bondedsender;
    my $resultip;
    my $ipCIDR;
    my $slok = $this->{allLoveSpam} == 1;

    $this->{prepend} = "[SenderBase]";
    if ( ! &SBCacheFind($ip)  ) {

        eval {
            $query = Net::SenderBase::Query->new(
                Transport => 'dns',
                Address   => $ip,
                Host      => 'query.senderbase.org',
                Timeout   => 10,
              );
            $results = $query->results;
        };
        if ($@) {
#           mlog( $fh, "warning: SenderBase: $@", 1 ) if $SenderBaseLog >= 2;
            return 1;
        }

         if ($results) {

            eval{
            $bondedsender   = $results->{ip_in_bonded_sender};
            $blacklistscore = $results->{ip_blacklist_score};
            $hostname_matches_ip = $results->{hostname_matches_ip};
            $orgname        = $results->{org_name};
            $resultip       = $results->{ip};
            $fortune1000    = $results->{org_fortune_1000};
            $domainname     = $results->{domain_name};
            $domainrating   = $results->{domain_rating};
            $ipbondedsender = $results->{ip_in_bonded_sender};
            $ipcountry      = $results->{ip_country};
            $ipCIDR         = $results->{ip_cidr_range};
            if (! $fh) {
                $this->{sbstatus} = 0;
                $this->{sbdata} = "$ipcountry|$orgname|$domainname|$blacklistscore|$hostname_matches_ip|$ipCIDR";
            }
            };

            if ($@) {
#                 mlog( $fh, "warning: SenderBase: $@", 1 ) if $SenderBaseLog >= 2;
                 return 1;
            }
            SBCacheAdd( $ip, 0, "$ipcountry|$orgname|$domainname|$blacklistscore|$hostname_matches_ip|$ipCIDR" );
        } else {
            mlog( $fh, "info: SenderBase: got no results") if $SenderBaseLog >= 2;
            return 1;
        }

    } else {

        return 1 if !SBCacheFind($ip);

        ( $ipcountry, $orgname, $domainname, $blacklistscore, $hostname_matches_ip, $ipCIDR ) = split( /\|/, SBCacheFind($ip) ) ;
        $cache = 1;
        d("SenderBase: finished CACHE");
    }
    if ($ipcountry eq "noresult") {

       return 1;
       }

    $this->{myheader} .=
    "X-Assp-SenderBase: $ipcountry; $orgname; $domainname\r\n"
        if $AddSenderBaseHeader && ($ipcountry || $orgname || $domainname);
    mlog( $fh, "SenderBase info -- country:$ipcountry; organization:$orgname; domain:$domainname" )
        if $SenderBaseLog >= 2  && ($ipcountry || $orgname || $domainname);

    $orgname =~ s/\(/-/go;
    $orgname =~ s/\)/-/go;

    $this->{orgname}    = $orgname;

    $tlit = "[whiting]"    if $DoOrgWhiting == 1;
    $tlit = "[scoring]"    if $DoOrgWhiting == 3;
    $tlit = "[monitoring]" if $DoOrgWhiting == 2;
    my $mDoOrgBlocking = $DoOrgBlocking;
    $this->{testmode}  = 0;
    $this->{testmode}  = 1 if $DoOrgBlocking == 4 or $allTestMode;
    $mDoOrgBlocking    = 1 if $DoOrgBlocking == 4;

    if ($DoOrgWhiting) {

        $whiteSenderBaseRE =~ s/^\@//g;
        if (   $orgname =~ /($whiteSenderBaseRE)/
            || $domainname =~ /($whiteSenderBaseRE)/ )
        {

            my $wSB = $1;
            d("SenderBase: in DoOrgWhiting");
            SBCacheAdd( $ip, 2, "$ipcountry|$orgname|$domainname|$blacklistscore|$hostname_matches_ip|$ipCIDR" );
            d("SenderBase0: finished SBCacheAdd in DoOrgWhiting");
            $this->{sbstatus} = 2 if (! $fh);

            if ($DoOrgWhiting == 1) {
                $WhiteOrgList{$mfd} = $orgname if $mfd ne lc $domainname && $this->{spfok};
                $this->{passingreason} = "white-senderbase: $wSB";
                pbWhiteAdd( $fh, $ip, "WhiteSenderBase:$wSB" );
            }
            $this->{messagereason} = "whiteSenderBase '$wSB'";
            $this->{passingreason} = "whiteSenderBase '$wSB'"    if $DoOrgWhiting == 1;
            $this->{messagereason} .= " in cache " if $cache;

            my $w= &weightRe(${'sworgValencePB'}[0],'whiteSenderBase',$wSB,$fh);
            $this->{whitelisted} = 1 if abs ${'sworgValencePB'}[0] <= abs $w && $DoOrgWhiting == 1;
            pbAdd( $fh, $ip, $w, "WhiteSenderBase:$1" )
              if $DoOrgWhiting != 2;
            $tlit = "[scoring:$w]" ;
            mlog( $fh, "$tlit SenderBase -- $this->{messagereason}" )
              if $SenderBaseLog;
            return 1;
        }
     }

    if ($mDoOrgBlocking) {

      my $tlit = tlit($DoOrgBlocking);

      if (!$orgname && !$ipcountry ) {
          SBCacheAdd( $ip, 1, "$ipcountry|$orgname|$domainname|$blacklistscore|$hostname_matches_ip|$ipCIDR" );
          d("SenderBase1: finished SBCacheAdd in DoOrgBlocking");
          $this->{sbstatus} = 1 if (! $fh);
          $this->{messagereason} = "No CountryCode and No Organization";
          pbAdd( $fh, $ip, 'sbnValencePB', "NoCountryNoOrg" );
          mlog( $fh, "[scoring:${'sbnValencePB'}[0]] SenderBase -- $this->{messagereason}" )
            if $SenderBaseLog >= 2;
          return 1;

      } elsif (   $orgname =~ /($blackSenderBaseRE)/
          || $domainname =~ /($blackSenderBaseRE)/ )
      {
          pbWhiteDelete( $fh, $ip, "BlackOrg:$1" );
          $blacklistscore = pbBlackFind( $ip );
          SBCacheAdd( $ip, 1, "$ipcountry|$orgname|$domainname|$blacklistscore|$hostname_matches_ip|$ipCIDR" );
          d("SenderBase1: finished SBCacheAdd in DoOrgBlocking");
          $this->{sbstatus} = 1 if (! $fh);
          $this->{messagereason} = "blackSenderBase '$1'";
          my $w= &weightRe(${'sborgValencePB'}[0],'blackSenderBase',$1,$fh);
          pbAdd( $fh, $ip, $w, "BlackOrg:$1" )
              if $mDoOrgBlocking != 2;

          return 0 if $mDoOrgBlocking == 1;
          my $tlit; $tlit = "[scoring:$w]" if $DoOrgBlocking == 3;
          mlog( $fh, "$tlit SenderBase -- $this->{messagereason}" )
            if $SenderBaseLog >= 2;
      } else {
          SBCacheAdd( $ip, 0, "$ipcountry|$orgname|$domainname|$blacklistscore|$hostname_matches_ip|$ipCIDR" );
          d("SenderBase3: finished SBCacheAdd in DoOrgBlocking");
      }
  } else {
      SBCacheAdd( $ip, 0, "$ipcountry|$orgname|$domainname|$blacklistscore|$hostname_matches_ip|$ipCIDR" );
      d("SenderBase4: finished SBCacheAdd in DoOrgBlocking");
  }

  my $mDoCountryBlocking = $DoCountryBlocking;
  $this->{testmode} = 0;
  $this->{testmode} = 1    if $DoCountryBlocking == 4 or $allTestMode;
  $mDoCountryBlocking = 1 if $DoCountryBlocking == 4;

  $this->{mycountry} = 0;
  if (   $ipcountry
      && $MyCountryCodeReRE
      && $ipcountry =~ $MyCountryCodeReRE )
  {

      return 1 if ${'sbhccValencePB'}[0] >= 0;
      $this->{mycountry}     = 1;
      $this->{messagereason} = "Home Country $ipcountry";
      my $w= &weightRe(${'sbhccValencePB'}[0],'MyCountryCodeRe',$ipcountry,$fh);
      $tlit = "[scoring:$w]" if $DoSuspiciousCountry == 3;
      mlog( $fh, "$tlit -- $this->{messagereason} }" ) if $SenderBaseLog;
      pbAdd( $fh, $ip, $w, "HomeCountry-$ipcountry" )
        if $DoSuspiciousCountry != 2;

  }

  return 1 if !$DoCountryBlocking;
  return 1 if $ipcountry =~ $NoCountryCodeReRE;
  $this->{mycountry} = 0;

  if ($ipcountry &&
      (    $ipcountry =~ /$CountryCodeBlockedReRE/
       || (   $CountryCodeBlockedRe =~ /all/io
           && $ipcountry !~ /$MyCountryCodeReRE/
           && $ipcountry !~ /$CountryCodeReRE/
          )
      )
    )
  {
      $this->{messagereason} = "Blocked Country $ipcountry ($orgname)";
      my $tlit = tlit($mDoCountryBlocking);
      my $w= &weightRe(${'bccValencePB'}[0],'CountryCodeBlockedRe',$ipcountry,$fh);
      pbAdd( $fh, $ip, $w, "BlockedCountry-$ipcountry" )
        if $mDoCountryBlocking != 2;
      $blacklistscore = pbBlackFind( $ip );
      SBCacheAdd( $ip, 1, "$ipcountry|$orgname|$domainname|$blacklistscore|$hostname_matches_ip|$ipCIDR" );
      d("SenderBase2: finished SBCacheAdd in DoOrgBlocking");
      $tlit = "[scoring:$w]" if $mDoCountryBlocking == 3;
      mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" )
        if $mDoCountryBlocking == 2 || $mDoCountryBlocking == 3;
      return 0 if $mDoCountryBlocking == 1;

  }

  return 1 if !$DoSuspiciousCountry;

  $tlit = tlit($DoSuspiciousCountry);

  if (   ${'sbsccValencePB'}[0]
      && $ipcountry

      && $CountryCodeReRE 
      && $ipcountry =~ $CountryCodeReRE
    )
  {
      $this->{messagereason} = "Suspicious Country $ipcountry ($orgname)";

      my $w= &weightRe(${'sbsccValencePB'}[0],'CountryCodeRe',$ipcountry,$fh);
      pbAdd( $fh, $ip, $w, "CountryCode-$ipcountry", 1 ) 
        if $DoSuspiciousCountry != 2; 
      $tlit = "[scoring:$w]" if $DoSuspiciousCountry == 3;
      mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" ) if $SenderBaseLog;

  }
  if (   ${'sbfccValencePB'}[0]
      && $ipcountry
      && $ScoreForeignCountries
      && $ipcountry !~ $CountryCodeBlockedReRE
      && $ipcountry !~ $CountryCodeReRE
      && $ipcountry !~ $NoCountryCodeReRE
      && $ipcountry !~ $MyCountryCodeReRE

      )
  {
      $this->{messagereason} = "Foreign Country $ipcountry($orgname)";
      my $w= &weightRe(${'sbfccValencePB'}[0],'CountryCodeRe',$ipcountry,$fh);
      pbAdd( $fh, $ip, $w, "CountryCode-$ipcountry", 1 )
        if $DoSuspiciousCountry != 2;
      $tlit = "[scoring:$w]" if $DoSuspiciousCountry == 3;
      mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" ) if $SenderBaseLog >= 2;
      return 1;
  }
  return 1;
}

sub MXAOK {
    my $fh = shift;
    return 1 unless $CanUseDNS && $DoDomainCheck;
    my $hasPrivate;
    d('MXAOK');
    my $this = $Con{$fh};
    my $ip = $this->{ip};
    return 1 if $this->{MXAOK};

    $this->{MXAOK} = 1;
    return 1 if $ip =~ /$IPprivate/ && !$this->{cip};

    $ip = $this->{cip} if $this->{ispip} && $this->{cip};

    return 1 if $this->{relayok};
    return 1 if $this->{notspamtag};
    return 1 if $this->{contentonly};
    return 1 if $this->{noprocessing};
    return 1 if !$this->{mailfrom};
    return 1 if pbWhiteFind($ip);

    my $slok = $this->{allLoveSpam} == 1;

    my $mf   = lc $this->{mailfrom};
    my %mfd;
    $mfd{lc $1}->{mx} = $mfd{lc $1}->{a} = $mfd{lc $1}->{ctime} = undef if $mf =~ /\@($EmailDomainRe)$/o;

    while ($this->{header} =~ /($HeaderNameRe):($HeaderValueRe)/igos) {
        my $line = $2;
        next if $1 !~ /^(?:ReturnReceipt|Return-Receipt-To|Disposition-Notification-To|Return-Path|Reply-To|Sender|Errors-To|List-\w+)$/io;
        headerUnwrap($line);
        while ($line =~ /$EmailAdrRe\@($EmailDomainRe)/og) {
            $mfd{lc $1}->{mx} = $mfd{lc $1}->{a} = $mfd{lc $1}->{ctime} = undef;
        }
    }

    my $mDoDomainCheck = $DoDomainCheck;

    $this->{testmode} = 0;
    $this->{testmode} = 1    if $DoDomainCheck == 4 or $allTestMode;
    $mDoDomainCheck = 1     if $DoDomainCheck == 4;

    my $tlit;
    $tlit = &tlit($mDoDomainCheck);

    foreach my $mfd (keys %mfd) {
        my ( $cachetime, $mxexchange, $arecord ) = MXACacheFind($mfd);
        if ( ! $cachetime ) {
            my $res = getDNSResolver();
            my @queryMX = eval{ Net::DNS::mx( $res, $mfd ); };
            if (@queryMX) {
                foreach my $rr ( @queryMX ) {
                    my @MXip;
                    eval{$mxexchange = $rr->exchange;} or next;
                    my $res = queryDNS($mxexchange ,'A');
                    if (ref $res) {
                        my @answer = eval{map{$_->string} $res->answer;};
                        while (@answer) {
                            my $RR = Net::DNS::RR->new(shift @answer);
                            my $aip = eval{$RR->rdatastr};
                            if ($aip !~ /$IPprivate/o) {
                                push @MXip, $aip;
                                $hasPrivate = 0;
                            } elsif ($hasPrivate != 0) {
                                $hasPrivate = 1;
                            }
                        }
                    }
                    if (!@MXip && $mxexchange && $res) {
                        mlog( $fh,"MX $mxexchange has no or a private IP - this MX has failed" )
                            if $ValidateSenderLog;
                        $mfd{$mfd}->{mx} = $mfd{$mfd}->{a} = $mfd{$mfd}->{ctime} = undef;
                    } elsif ($mxexchange && $res) {
                        $mfd{$mfd}->{mx} = $mxexchange;
                        $mfd{$mfd}->{a} = $MXip[0];
                        $mfd{$mfd}->{ctime} = undef;
                        last;
                    }
                }
            } else {
                delete $mfd{$mfd};
            }
        } else {
            $mfd{$mfd}->{mx} = $mxexchange;
            $mfd{$mfd}->{a} = $arecord;
            $mfd{$mfd}->{ctime} = $cachetime;
        }
    }

    my $mfailed;
    my $afailed;
    my $failed;
    my $mpb;
    my $apb;
    foreach my $mfd (keys %mfd) {

        if ($mfd{$mfd}->{mx}) {

            #MX found
            $this->{prepend} = "[MX-Ok]";
            my $msg = "MX found";
            $msg .= " (cache)" if $mfd{$mfd}->{ctime};
            $msg .= ": $mfd -> ". $mfd{$mfd}->{mx};
            mlog( $fh, $msg, 0, 1 )
              if $ValidateSenderLog >= 2 ;

        } else {

            #MX not found
            $this->{prepend} = "[MX-Missing]";
            $this->{messagereason} = "MX missing";
            $this->{messagereason} .= " (cache)" if $mfd{$mfd}->{ctime};
            $this->{messagereason} .= ": $mfd";

            mlog( $fh,"[scoring] $this->{messagereason}")
              if $ValidateSenderLog && ${'mxValencePB'}[0];

            pbWhiteDelete( $fh, $ip ) if ! $mpb;
            pbAdd( $fh, $ip, 'mxValencePB', 'MissingMX' ) if $DoDomainCheck != 2 && !$mpb;
            pbAdd( $fh, $ip, 'mxValencePB', 'MissingMX' ) if $DoDomainCheck != 2 && !$mpb && $hasPrivate;
            if (! $mfd{$mfd}->{a} ) {
                my ($name, $aliases, $addrtype, $length, @addrs);
                eval{
                    ($name, $aliases, $addrtype, $length, @addrs) = gethostbyname($mfd);
                };
                foreach my $i (@addrs) {
                    my ($ad, $bd, $cd, $dd) = unpack('C4', $i);
                    my $arecord ="$ad.$bd.$cd.$dd";
                    if ( $MXACacheInterval > 0 && $arecord =~ /^$IPRe$/o && $arecord !~ /^$IPprivate$/o) {
                        $mfd{$mfd}->{a} = $arecord;
                        last;
                    }
                }
            }
            $mfailed = 1;
        }

        if ($mfd{$mfd}->{a}) {

            #A  found
            $this->{prepend} = "[MXA-Found]";
            my $msg = "A record found";
            $msg .= " (cache)" if $mfd{$mfd}->{ctime};
            $msg .= ": $mfd -> ".$mfd{$mfd}->{a};
            mlog( $fh, $msg, 0, 1 )    if $ValidateSenderLog >= 2 ;

        } else {

            #A not found
            $this->{prepend} = "[MXA-Missing]";
            $this->{messagereason} = "A record missing: $mfd";
            $this->{messagereason} .= " (cache)" if $mfd{$mfd}->{ctime};
            mlog( $fh,"[scoring] $this->{messagereason}")
              if $ValidateSenderLog && $DoDomainCheck >= 2;
            delayWhiteExpire($fh) if ! $apb;
            pbAdd( $fh, $ip, 'mxaValencePB', 'MissingMXA' ) if $DoDomainCheck != 2 && ! $apb;
            pbAdd( $fh, $ip, 'mxaValencePB', 'MissingMXA' ) if $DoDomainCheck != 2 && ! $apb && $hasPrivate;
            $afailed = 1;
        }
        if ( $MXACacheInterval > 0) {
            MXACacheAdd( $mfd, $mfd{$mfd}->{mx}, $mfd{$mfd}->{a} ) if ! $mfd{$mfd}->{ctime};
        }
        $failed = $mfailed && $afailed;
        $mf = $mfd if ($mfailed && $afailed);
        $apb |= $afailed;
        $mpb |= $mfailed;
        $mfailed = $afailed = undef;
    }

    if ($failed) {
        return 1 if $DoDomainCheck >= 2;
        $this->{prepend} = "[MissingMXA]";
        mlog($fh,"MX and A record missing at least for: $mf")
          if $ValidateSenderLog;
        return 0;
    } else {
        return 1;
    }
}

sub BombWeight {
    my ( $fh, $t, $re ) = @_;
    my %weight = ();
    mlog( 0,
"error: code error - missing valence value in 'WeightedRe' hash in sub BombWeight for $re"
    ) if ( !exists $WeightedRe{$re} );
    mlog( 0,
"warning: suspect valence value '0' in 'WeightedRe' hash for '$WeightedRe{$re}' in sub BombWeight for $re"
    ) if $BombLog >= 2 && ${ $WeightedRe{$re} }[0] == 0;
    return %weight unless ${$re};
    return %weight unless ${ $re . 'RE' };

    my $this = $Con{$fh};
    d("BombWeight - $re");
    my $text;
    my $rawtext = ref $t ? $$t : $t;
    my %weight      = ();
    my %found       = ();
    my $weightsum   = 0;
    my $weightcount = 0;
    my $maxhits     = $maxHits{ lc $re };

    $maxhits |= 3 if $re eq 'bombSubjectRe';
    $maxhits |= 3 if $re eq 'blackRe';
    $maxhits |= 3 if $re eq 'bombSuspiciousRe';
    $maxhits |= 3 if $re eq 'bombRe';
    $maxhits |= 1;
    $maxBombSearchTime = 10 unless $maxBombSearchTime;
    $weightMatch = '';
    my $regex = ${ $re . 'RE' };
    my $itime = time;
    $addCharsets = 1 if $re eq 'bombCharSets';
    if ( $re ne 'bombSubjectRe' ) {
        $rawtext =~ s/(<!--.+?)-->/$1/sgo;
        my $mimetext = cleanMIMEBody2UTF8( \$rawtext );

        if ($mimetext) {
            if ( $re ne 'bombDataRe' ) {
                $text = cleanMIMEHeader2UTF8( \$rawtext, 0 );
                $mimetext =~ s/\=(?:\015?\012|\015)//go;
                $mimetext = decHTMLent( \$mimetext );
            }
            $text .= $mimetext;
        }
        else {
            $text = decodeMimeWords2UTF8($rawtext);
        }

    }
    else {
        $text = $rawtext;
    }

    undef $rawtext;
    $addCharsets = 0;
    if ( $re eq 'bombSubjectRe' && $maxSubjectLength ) {
        my ( $submaxlength, $maxlengthweight ) =
          split( /\s*\=\>\s*/o, $maxSubjectLength );

        $maxlengthweight |= ${ $WeightedRe{$re} }[0];

        my $sublength = length($text);
        if ( $submaxlength && $sublength > $submaxlength ) {
            if ($maxlengthweight) {
                $weightsum += $maxlengthweight;
                $weightcount++;
                $weight{highval} = $maxlengthweight;
                $weight{highnam} =
                  "subject length($sublength) > max($submaxlength)";
                $found{ $weight{highnam} } = $maxlengthweight;
                $weight{matchlength} = '';
            }
            $text = substr( $text, 0, $submaxlength );
            mlog( $fh,
"info: Subject exceeds $maxSubjectLength byte - the checked subject is truncated to $submaxlength byte"
            ) if $BombLog && $fh;
        }
    }
    my $subre;

    eval {
        local $SIG{ALRM} = sub { die "__alarm__\n" };
        alarm( $maxBombSearchTime + 10 );
        while ( $text =~ /($regex)/gs ) {
            $subre = $1 || $2;
            my $matchlength = length($subre);

            last if time - $itime >= $maxBombSearchTime;
            my $w = &weightRe( $WeightedRe{$re}, $re, \$subre, $fh );
            next unless $w;
            $subre = substr( $subre, 0, $RegExLength < 5 ? 5 : $RegExLength )
              if $subre;
            $subre = '[!empty!]' unless $subre;

            $subre =~ s/\s+/ /go;
            next if ( $found{ lc($subre) } > 0 && $found{ lc($subre) } >= $w );
            next if ( $found{ lc($subre) } < 0 && $found{ lc($subre) } <= $w );
            $found{ lc($subre) } = $w;
            $weightsum += $w;
            $weightcount++;

            if ( abs($w) >= abs( $weight{highval} ) ) {
                $weight{highval} = $w;
                $subre =~ s{([\x00-\x1F])}{sprintf("'hex %02X'", ord($1))}eog;
                $weight{highnam} = $subre;
                $weight{matchlength} =
                  ( length($subre) != $matchlength )
                  ? "(matchlength:$matchlength) "
                  : '';
            }

            last if $fh && $maxBombValence && $weightsum >= $maxBombValence;
            last if $fh && !$maxhits;
            last
              if $fh
              && $maxhits
              && $weightcount >= $maxhits
              && !$maxBombValence;
            last
              if $fh
              && $weightsum >= $MessageScoringUpperLimit
              && !$maxBombValence;

        }
        alarm(0);
    };
    undef $text;
    $itime = time - $itime;
    if ($@) {
        alarm(0);
        if ( $@ =~ /__alarm__/ ) {
            mlog(
                $fh,
                "BombWeight: timed out in 'RE:$re ($subre)' after $itime secs.",
                1
            );
        }
        else {
            mlog( $fh, "BombWeight: failed in 'RE:$re': $@", 1 );
        }
    }
    if ( $itime > $maxBombSearchTime ) {
        mlog(
            $fh,
"info: $re canceled after $itime s > maxBombSearchTime $maxBombSearchTime s",
            1
        ) if $BombLog >= 2 && $fh;
    }

    return %weight if $weightcount == 0;
    if ( $maxBombValence > 0 ) {
        $weight{sum} =
          $weightsum > $maxBombValence ? $maxBombValence : $weightsum;
    }
    else {
        $weight{sum} = $weightsum;
    }

    $weight{count} = $weightcount;

#    mlogRe($fh,"PB $weight{sum}: for $weight{highnam}",ucfirst $re) if $BombLog && $fh;
    mlog(
        $fh,
"$weight{highnam} : $weight{highval} , count : $weightcount , sum : $weightsum , time : $itime s",
        1
    ) if $BombLog >= 2 && $fh;
    $weight{highnam} = join ' , ', map {
        my $t = "'"
          . substr( $_, 0, $RegExLength < 5 ? 5 : $RegExLength )
          . " ($found{$_})'"; $t;
    } ( sort { $found{$main::b} <=> $found{$main::a} } keys %found );
    $this->{match} = $weight{highnam};
    $weight{highnam} =~ s/ \(-?\d+\)//g if $weight{sum} == $bombMaxPenaltyVal;
    return %weight;
}

sub WhiteOk {

    my ( $fh, $msg) = @_;
    my $this=$Con{$fh};
      return 1 if $this->{relayok};
      return 1 if $this->{whitelisted};

      return 1 if $this->{addressedToSpamBucket};
      my $m;
      if ($msg) {
          $m = ref($msg) ? $$msg : $msg if $msg;
      } else {

          $m = "$this->{mailfrom} $this->{helo} $this->{ip} $this->{header}";
      }
    d('WhiteOk');

    my $ip = $this->{ip};
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};

    if ( $whiteReRE &&  $m =~ /($whiteReRE)/i) {
        my $subre = ($1||$2);
        $this->{prepend} = '[whitelisted]';
        $this->{whitelisted} = "whiteRe '$subre'";
        $this->{passingreason} = "whiteRe '$subre'";
        delete $PBBlack{$ip};
        pbWhiteAdd( $fh, $ip, "whiteRe: $subre" );
        $this->{prepend} = "[WhiteRe]";
        return 1;
    }

    return 0;

}

sub BombOK {
    my($fh,$dataref)=@_;
    my $this=$Con{$fh};
    return 1 if $this->{notspamtag};
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{bombdone} == 1;
    d('BombOK');

    $this->{bombdone}=1;
    my $ip = $this->{ip};
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};
    my $helo = $this->{helo};
    $helo = $this->{ciphelo} if $this->{ciphelo};
    my $tlit;
    my %Bombs = ();
    my $BombName;
    my $subre;    
    my $header = $dataref;

    my $datastart = $this->{datastart};
    my $maillength = length($$dataref);
    my $ofs = 0;
    my $data = " $this->{mailfrom} $helo $ip " . substr( $this->{header}, 0, 10000 );

    $this->{match}="";

    if ($noBombScript && $this->{mailfrom} && matchSL($this->{mailfrom},'noBombScript') ) {
        return 1;}

      return 1 if $this->{acceptall};
      return 1 if $this->{whitelisted};
      return 1 if $this->{noprocessing};
      return 1 if $this->{relayok};

    my $mDoBombRe = $DoBombRe;        
    $this->{testmode} = 0;
    $this->{testmode} = 1    if $DoBombRe == 4 or $allTestMode;
    $mDoBombRe = 1     if $DoBombRe == 4;

    $this->{match}="";
    if ($bombSuspiciousRe) {

        %Bombs = &BombWeight($fh,$data,'bombSuspiciousRe' );

        if ($Bombs{count}) {
            $subre = $Bombs{highnam};
            $this->{messagereason} = "bombSuspiciousRe: $subre";
            $this->{prepend} = "[Bomb-Blackish]" if $Bombs{sum} >= 0;
            $this->{prepend} = "[Bomb-Whitish]"  if $Bombs{sum} < 0;
            mlog( $fh, "[scoring:$Bombs{sum}] -- $this->{messagereason} " );
            pbAdd($fh,$ip,$Bombs{sum},'bombSuspicious'); 

        }
    }
      # bombCharSets in MIME parts
    if ($DoBombRe && $bombCharSetsMIME && !$this->{charsetsdone}) {

        $subre = $Bombs{highnam};
        %Bombs = &BombWeight($fh, $header,'bombCharSetsMIME' );
        if ($Bombs{count}) {
            $subre = $Bombs{highnam};

            $this->{messagereason}="bombCharSets in mime-header: $subre";
            $this->{prepend} = "[BombCharSets]";

            $tlit = ($mDoBombRe == 1 && $Bombs{sum} < ${'bombValencePB'}[0]) ? &tlit(3) : $tlit;
            $tlit = "[scoring:$Bombs{sum}]" if $DoBombRe==3;
            mlog($fh,"$tlit -- $this->{messagereason}") if $DoBombRe > 1;
            pbWhiteDelete($fh,$this->{ip});
            $this->{charsetsdone}=1;
            return 1 if $mDoBombRe==2;
            pbWhiteDelete($fh,$this->{ip});
            pbAdd($fh,$this->{ip},$Bombs{sum},"BombCharSetsMIME");
            $this->{charsetsdone}=1;
             if  ($this->{messagescore} > $this->{spamMaxScore}){
                return 0 if $mDoBombRe == 1 && $Bombs{sum}   >= ${'bombValencePB'}[0];
                return 0 if $mDoBombRe == 1 && $Bombs{count} >= $maxBombHits-1;
            }
        }
    }

    if ($DoBombRe) {
        my $slok=$this->{allLoveSpam}==1;

        my $mDoBombRe = $DoBombRe;        
        $this->{testmode} = 0;
        $this->{testmode} = 1    if $DoBombRe == 4 or $allTestMode;
        $mDoBombRe = 1     if $DoBombRe == 4;
        $this->{prepend} = "[BombData]";
        $tlit=&tlit($mDoBombRe);

        %Bombs = &BombWeight($fh,$header,'bombDataRe' );
        if ($Bombs{count}) {
          $subre = $Bombs{highnam};
          $this->{messagereason} = "bombDataRe: $subre";
          $tlit = ($mDoBombRe == 1 && $Bombs{sum} < ${'bombValencePB'}[0]) ? &tlit(3) : $tlit;
          $tlit = "[scoring:$Bombs{sum}]" if $mDoBombRe == 3;
          mlog( $fh, "$tlit -- $this->{messagereason}" ) if $mDoBombRe > 1;
          pbWhiteDelete($fh,$this->{ip});
          return 1 if $mDoBombRe==2;
          pbWhiteDelete($fh,$this->{ip});
          pbAdd($fh,$this->{ip},$Bombs{sum},"BombData");
          if  ($this->{messagescore} > $this->{spamMaxScore}){
              return 0 if $mDoBombRe == 1 && $Bombs{sum} >= ${'bombValencePB'}[0];
              return 0 if $mDoBombRe == 1 && $maxBombHits >= 1 && $Bombs{count} >= $maxBombHits-1;
          }
        }    
    }

    return 1 if !$DoBombRe;     
    return 1 if !$bombRe;
    return 1 if !${'bombValencePB'}[0];

    $this->{match}="";
    $this->{prepend} = "[BombRe]";
    %Bombs = &BombWeight($fh,\$data,'bombRe' );
    if ($Bombs{count}) {
        $subre = $Bombs{highnam};

        $this->{messagereason} = "bombRe: $subre";
        $tlit = ($mDoBombRe == 1 && $Bombs{sum} < ${'bombValencePB'}[0]) ? &tlit(3) : $tlit;
        $tlit = "[scoring:$Bombs{sum}]" if $mDoBombRe == 3;
        mlog( $fh, "$tlit -- $this->{messagereason}" ) if $mDoBombRe > 1;
        pbWhiteDelete($fh,$this->{ip});
        return 1 if $mDoBombRe==2;
        pbWhiteDelete($fh,$this->{ip});
        pbAdd($fh,$this->{ip},$Bombs{sum},"BombRe") if $mDoBombRe;
         if  ($this->{messagescore} > $this->{spamMaxScore}){
            return 0 if $mDoBombRe == 1 && $Bombs{sum} >= ${'bombValencePB'}[0];
            return 0 if $mDoBombRe == 1 && $maxBombHits >= 1 && $Bombs{count} >= $maxBombHits-1;
        }
    }

    return 1;
}

sub BombHeaderOK {
    my ($fh,$headerref) = @_;
    my $this=$Con{$fh};
    return 1 if $this->{notspamtag};
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{bombheaderdone};
    d('BombHeaderOK');
    $this->{bombheaderdone}=1;
    my $ip = $this->{ip};
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};
    my $helo = $this->{helo};
    $helo = $this->{ciphelo} if $this->{ispip} && $this->{ciphelo};
    my %Bombs;
    my $BombName;
    my $tlit;

      return 1 if $this->{notspamtag};
      return 1 if $this->{acceptall};
      return 1 if $this->{whitelisted};
      return 1 if $this->{noprocessing};
      return 1 if $this->{relayok};

    if ($noBombScript && $this->{mailfrom} && matchSL($this->{mailfrom},'noBombScript')) {
        return 1;}
    my $slok=$this->{allLoveSpam}==1;
    my $mDoBombHeaderRe = $DoBombHeaderRe;        
    $this->{testmode} = 0;
    $this->{testmode} = 1    if $DoBombHeaderRe == 4 or $allTestMode;
    $mDoBombHeaderRe = 1     if $DoBombHeaderRe == 4;

    my $subre;
    my $w;

    $tlit=&tlit($mDoBombHeaderRe);

    %Bombs = &BombWeight($fh,"$this->{mailfrom} $ip $helo",'bombSenderRe' ) if $DoBombHeaderRe &&  $bombSenderRe;
    if ($Bombs{count}) {
            $subre = $Bombs{highnam};
            $this->{prepend} = "[BombSender]";
            $this->{messagereason} = "bombSenderRe: $subre";
            pbWhiteDelete($fh,$ip);
            pbAdd($fh,$ip,$Bombs{sum},"BombSender") if $DoBombHeaderRe != 2;
            return 0 if $mDoBombHeaderRe == 1 && $Bombs{sum} >= ${'bombValencePB'}[0] && $this->{messagescore} > $this->{spamMaxScore};
            $tlit = "[scoring:$Bombs{sum}]" if $DoBombHeaderRe == 3;
            mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" ) ;

    }

    %Bombs = &BombWeight($fh, $headerref,'bombCharSets' ) if $DoBombHeaderRe && !$this->{charsetsdone};
    if ($Bombs{count} && $DoBombHeaderRe) {
        my $mDoBombHeaderRe = $DoBombHeaderRe;        
        $this->{testmode} = 0;
        $this->{testmode} = 1    if $DoBombHeaderRe == 4 or $allTestMode;
        $mDoBombHeaderRe = 1     if $DoBombHeaderRe == 4;
        $subre = $Bombs{highnam};
        $this->{messagereason}="bombCharSets: $subre";
        $this->{prepend} = "[BombCharSets]";
        $tlit = ($mDoBombHeaderRe == 1 && $Bombs{sum} < ${'bombValencePB'}[0]) ? &tlit(3) : $tlit;
        $tlit = "[scoring:$Bombs{sum}]" if  $DoBombHeaderRe == 3;
        pbWhiteDelete($fh,$this->{ip});
        $this->{charsetsdone}=1;
        pbWhiteDelete($fh,$this->{ip}) if $mDoBombHeaderRe !=2;
        pbAdd($fh,$this->{ip},$Bombs{sum},"BombCharSets") if $mDoBombHeaderRe && $mDoBombHeaderRe !=2;
        return 0 if $mDoBombHeaderRe !=2 && $mDoBombHeaderRe==1 && abs $Bombs{sum} >= abs ${'bombValencePB'}[0] && $this->{messagescore} > $this->{spamMaxScore};
        mlog($fh,"$tlit -- $this->{messagereason}") if $BombLog;
    }

;

    if ( $DoBombHeaderRe &&  $bombSubjectRe ) {
        $tlit=&tlit($DoBombHeaderRe);
        %Bombs = &BombWeight($fh,substr($this->{subject3},0,160),'bombSubjectRe' );
        if ($Bombs{count}) {
            $subre = $Bombs{highnam};
            $this->{prepend} = "[BombSubject]";
            $this->{messagereason} = "bombSubjectRe: $subre";
            pbWhiteDelete($fh,$ip);
            pbAdd($fh,$ip,$Bombs{sum},"BombSubject") if $DoBombHeaderRe != 2;
            return 0 if $mDoBombHeaderRe == 1 && $Bombs{sum} >= ${'bombValencePB'}[0] && $this->{messagescore} > $this->{spamMaxScore};
            $tlit = "[scoring:$Bombs{sum}]" if $DoBombHeaderRe == 3;
            mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" ) if $DoBombHeaderRe > 1;
        }

    }

    if ( $DoBombHeaderRe &&  $bombHeaderRe ) {
            %Bombs  = &BombWeight($fh,$headerref,'bombHeaderRe' );    
        if ($Bombs{count}) {
            $this->{prepend} = "[BombHeader]";
            $subre = $Bombs{highnam};
            $this->{messagereason} = "bombHeaderRe: $subre";
            pbWhiteDelete($fh,$ip) if $mDoBombHeaderRe!=2;
            pbAdd($fh,$ip,$Bombs{sum},"BombHeader") if $mDoBombHeaderRe!=2;
            return 0 if $mDoBombHeaderRe == 1 && $Bombs{sum} >= ${'bombValencePB'}[0] && $this->{messagescore} > $this->{spamMaxScore};
            $tlit = "[scoring:$Bombs{sum}]" if $mDoBombHeaderRe == 3;
            mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" );
            return 1;
            }
      }
    return 1;
}

sub BombBlackOK {
      my($fh,$headerref)=@_;
      my $this=$Con{$fh};

    return 1 if !$DoBlackRe;
    return 1 if !$blackRe;
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{notspamtag};
    return 1 if $this->{blackdone} == 1;
    d('BlackOK');
    $this->{blackdone}=1;
    my $ip = $this->{ip};
    $ip = $this->{cip} if  $this->{cip};
    my $helo = $this->{helo};
    $helo = $this->{ciphelo} if  $this->{ciphelo};
    my $data = " $this->{mailfrom} $helo $ip " . substr( $this->{header}, 0, 10000 );
    my $dataref = \$data;
    my $subre;
    my %Bombs;
    if (   $noBombScript
        && $this->{mailfrom}
        && matchSL( $this->{mailfrom}, 'noBombScript' ) )
    {
        return 1;
    }
    return 1 if $this->{whitelisted} && !$blackReWL;
    return 1 if $this->{noprocessing} && !$blackReNP;
    return 1 if $this->{relayok} && !$blackReLocal;
    return 1 if $this->{ispip} && !$blackReISPIP;
    my $slok       = $this->{allLoveSpam} == 1;
    my $mDoBlackRe = $DoBlackRe;        
    $this->{testmode} = 0;
    $this->{testmode} = 1    if $DoBlackRe == 4 or $allTestMode;
    $mDoBlackRe = 1     if $DoBlackRe == 4;
    my $tlit = tlit($mDoBlackRe);
    %Bombs = &BombWeight($fh,\$data,'blackRe' );
    if ($Bombs{count}) {
      $subre = $Bombs{highnam};
      $this->{messagereason} = "blackRe: $subre";
      $this->{prepend}       = "[BombBlack-Blackish]";
      $this->{messagereason} = "blackRe: $subre"   if $Bombs{sum}<0;
      $this->{prepend}       = "[BombBlack-Whitish]" if $Bombs{sum}<0;
      pbWhiteDelete($fh,$ip) if  $blackRe;
      pbAdd($fh,$ip,$Bombs{sum},"$this->{messagereason}") if  $mDoBlackRe !=2;
      return 0 if $mDoBlackRe == 1 && $Bombs{sum} >= ${'blackValencePB'}[0];
      return 0 if $mDoBlackRe == 1 && $maxBombHits > 1 && $Bombs{count} >= $maxBombHits;
      $tlit = "[scoring:$Bombs{sum}]" if  $mDoBlackRe !=2;
      mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" );
      return 1;
    }
    return 1;

}

# do invalid HELO check
sub invalidHeloOK {
    my ( $fh, $helo ) = @_;
    my $this = $Con{$fh};
    d('invalidHeloOK');
    return 1 if $this->{invalidHeloOK};
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{notspamtag};
    return 1 if $this->{ispip} && !$this->{cip};        
    my $ip = $this->{ip};
    $ip = $this->{cip} if $this->{cip};
    $helo = $this->{ciphelo} if $this->{ciphelo};
    return 1 if !$DoInvalidFormatHelo;
    return 1 if $this->{relayok};
    return 1 if $heloBlacklistIgnore && $helo =~ $HBIRE;
    return 1 if $this->{nohelo};
    return 1 if $this->{acceptall};
    return 1 if $this->{ispip} && ! $this->{cip};
    return 1 if (($this->{rwlok} && ! $this->{cip}) or ($this->{cip} && pbWhiteFind($this->{cip})));
    return 1 if $this->{whitelisted};
    return 1 if $this->{noprocessing};
    return 1 if $helo =~ /\[?$IPRe\]?/oi;
    my $slok    = $this->{allLoveSpam} == 1;

    my $mDoInvalidFormatHelo = $DoInvalidFormatHelo;
    my %HELOs = &BombWeight($fh,$helo,'invalidHeloRe' );
    if (   $DoInvalidFormatHelo
        && $invalidHeloRe
        && $HELOs{count} )
    {

        my $tlit = tlit($mDoInvalidFormatHelo);
        $this->{prepend} = "[InvalidHELO]";

        $this->{messagereason} = "invalid HELO: '$helo'";
        my $w = $HELOs{sum};
        $mDoInvalidFormatHelo = 3 if $mDoInvalidFormatHelo == 1 && $w < ${'ihValencePB'}[0];
        $tlit = "[scoring:$w]" if $mDoInvalidFormatHelo == 3;
        mlog( $fh, "$tlit -- $this->{messagereason}" )
          if $ValidateHeloLog && $mDoInvalidFormatHelo == 3
              || $mDoInvalidFormatHelo == 2;
        pbWhiteDelete( $ip );
        return 1 if $mDoInvalidFormatHelo == 2;

        pbAdd($fh,$ip,$w,"invalidHELO") ;
        $this->{helodone} = 1;
        $this->{invalidHeloOK} = 1;

        return 1 if $mDoInvalidFormatHelo == 3 or $w < ${'ihValencePB'}[0];
        return 1 if $validHeloReRE && $helo =~ /($validHeloReRE)/i;
        return 1 if $this->{messagescore} <= $this->{spamMaxScore};
        return 0;
    }
    return 1;
}
sub IPinHeloOK {
    my $fh = shift;
    return 1 if !$DoIPinHelo;
    my $this = $Con{$fh};
    $fh = 0 if "$fh" =~ /^\d+$/o;
    my $ip = $this->{ip};
    my $helo = $this->{helo};

    $ip = $this->{cip} if $this->{ispip} && $this->{cip};
    $helo = $this->{ciphelo} if $this->{ciphelo};
    d('IPinHeloOK');

    return 1 if $this->{IPinHeloOK};
    $this->{IPinHeloOK} = 1;
    return 1 if $helo eq $ip;
    skipCheck($this,'ro','aa','ispcip') && return 1;
    return 1 if $DoFakedWL && $this->{whitelisted}; 
    return 1 if $DoFakedNP && $this->{noprocessing}; 
    return 1 if ( matchIP( $ip, 'noHelo', $fh ) );

    return 1 if $ip =~ /$IPprivate/o;
    return 1 if $heloBlacklistIgnore && $helo =~ /$HBIRE/;

    my $tlit = tlit($DoIPinHelo);
    my @variants;

    if ( $helo =~ /\[?(?:(?:$IPSectRe(?:\.|\-)){3}$IPSectRe|(?:$IPSectHexRe(?:\.|\-)){3}$IPSectHexRe|$IPv6LikeRe)\]?/o ) {
        pos($helo) = 0;
        while ($helo =~ /\[?((?:$IPSectRe(?:\.|\-)){3}$IPSectRe|(?:$IPSectHexRe(?:\.|\-)){3}$IPSectHexRe|($IPv6LikeRe))\]?/og) {
            my $literal = $1;
            my $isV6 = $2;
            my $sep;
            # replace any - characters with a dot or :
            if ($isV6) {
                $literal =~ s/\-/\:/go;
                $literal = ipv6expand($literal);
                $sep = ':';
            } else {
                $literal =~ s/\-/\./go;
                $literal =~ s/0x([a-fA-F0-9]{1,2})/hex($1)/goe;
                $literal =~ s/([A-F][A-F0-9]?|[A-F0-9]?[A-F])/hex($1)/gioe;
                $sep = '.';
            }

            # remove leading zeros and put it into an array
            my @octets = map {
                if ( !m/^0$/io ) {my $t = $_; $t =~ s/^0*//o; $t }
                else             { 0 }    # properly handle a 0 in the IP
            } split( /\.|\:/o, $literal );

            #put the ip back together
            push @variants, (join $sep, @octets);
            push @variants, (join $sep, reverse(@octets));
        }

        return 1 unless scalar @variants;
        d("saw IP in HELO: @variants");

        my $mr = $this->{messagereason} = "Suspicious HELO - contains IP: '$helo'";
        $this->{prepend} = "[SuspiciousHelo]";

        pbAdd( $fh, $ip, 'fiphValencePB', 'IPinHELO' ) if $DoIPinHelo != 2;
        $tlit = "[scoring:$fiphValencePB]" if $DoIPinHelo != 2;
        mlog( $fh, "$tlit ($this->{messagereason})" ) if $ValidateSenderLog;
        if ( ! grep(/^\Q$ip\E$/i,@variants) ) {
            $this->{messagereason} = "IP in HELO '$helo' does not match IP in connection '$ip' ";
            $mr .= " - and IP in HELO '$helo' does not match IP in connection '$ip' ";
            pbAdd( $fh, $ip, 'fiphmValencePB', 'IPinHELOmismatch' ) if $DoIPinHelo != 2;
            $tlit = "[scoring:$fiphmValencePB]" if $DoIPinHelo != 2;
            mlog( $fh, "$tlit ($this->{messagereason})" ) if $ValidateSenderLog;
        }
        $this->{messagereason} = $mr unless $fh;
        return 1 if $this->{messagescore} <= $this->{spamMaxScore};
        return 0;
    }

    #the if didn't hit
    return 1;
}

sub GoodHelo {
      my($fh,$fhelo)=@_;
      my $this=$Con{$fh};
  return 1 if $this->{relayok};
  return 1 if $this->{whitelisted};
  return 1 if $this->{noprocessing};
  return 1 if $this->{nohelo};
  return 1 if $this->{helodone};
  return 1 unless $useHeloGoodlist;
  $this->{helodone}=1;    
  d('GoodHelo');

  $this->{prepend} = "[GoodHELO]";

  my $ip = $this->{ip};
  $ip = $this->{cip} if $this->{ispip} && $this->{cip};
  my $helo = lc($fhelo);
  $helo = lc($this->{ciphelo}) if $this->{ispip} && $this->{ciphelo};

  my $val;
  return 1 if !($HeloBlackObject && ($val = $HeloBlack{$helo}));
  return 1 if $heloBlacklistIgnore && $helo =~ /$HBIRE/;

  if ($HeloBlackObject && $val < 1) {
      $val *= -10;
      $this->{messagereason} = "good HELO '$helo'";
      mlog($fh,"[scoring:int($val * ${'hlValencePB'}[0])] found known good HELO '$helo' - weight is $val");
      if ($useHeloGoodlist == 1 or $useHeloGoodlist == 3) {
          pbAdd($fh,$ip,([int($val * ${'hlValencePB'}[0]),int($val * ${'hlValencePB'}[1])]),"KnownGoodHelo");
      }
      if ($useHeloGoodlist == 2 or $useHeloGoodlist == 3) {
          pbWhiteAdd($fh,$this->{ip},"KnownGoodHelo");
          $this->{whitelisted} = 1;
      }
  }
  return 1;
}

# do blacklisted HELO check
sub BlackHeloOK {
    my ( $fh, $fhelo ) = @_;
    my $this = $Con{$fh};
    d('BlackHeloOK');
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{helodone};
    $this->{helodone}=1;

    my $ip = $this->{ip};
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};
    my $helo = lc($fhelo);
    $helo = lc($this->{ciphelo}) if $this->{ispip} && $this->{ciphelo};

    return 1 if !$useHeloBlacklist;
    return 1 if $this->{relayok};
    return 1 if $this->{whitelisted};
    return 1 if $this->{noprocessing};
    return 1 if $this->{nohelo};
    return 1 if $this->{ispip} && ! $this->{cip};
    return 1 if $this->{rwlok} && ! $this->{cip};
    return 1 if $heloBlacklistIgnore && $helo =~ $HBIRE;

    my $museHeloBlacklist = $useHeloBlacklist;
    my $tlit = tlit($museHeloBlacklist);
    $this->{prepend} = "[BlackHELO]";
    $this->{messagereason} = "blacklisted HELO '$helo'";
    $tlit = "[scoring:${'hlbValencePB'}[0]]" if $museHeloBlacklist == 3;
    if ( $HeloBlackObject && $HeloBlack{ $helo } or $BlackHeloObject && $BlackHelo{ $helo }) {
        mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" )
        if $ValidateHeloLog && $museHeloBlacklist == 3
              || $museHeloBlacklist == 2;
        pbWhiteDelete( $fh, $ip );
        $HeloBlack{ $helo } = time if exists $HeloBlack{ $helo };
        return 1 if $museHeloBlacklist == 2;
        pbAdd( $fh, $ip, 'hlbValencePB', "BlacklistedHelo",1 );
        return 1 if $museHeloBlacklist == 3;

    }
    return 1;
}

# do blacklisted domains check
sub BlackDomainOK {

    my $fh = shift;
    my $this = $Con{$fh};
    my %Bombs;

    d('BlackDomainOK');
    return 1 if $this->{notspamtag};
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{BlackDomainOK};
    $this->{BlackDomainOK} = 1;
    return 1 if !$this->{mailfrom};
    return 1 if !$DoBlackDomain;
    return 1 if $this->{relayok};
    return 1 if $this->{whitelisted} && !$DoBlackDomainWL;
    return 1 if $this->{noprocessing} && !$DoBlackDomainNP;
    return 1 if $noBlackDomain 
        && matchSL( $this->{mailfrom}, 'noBlackDomain' );

    my $ip = $this->{ip};
      $ip = $this->{cip} if $this->{cip};

      my %senders;

       my $mDoBlackDomain = $DoBlackDomain;
    $this->{testmode} = 0;
    $this->{testmode} = 1    if $DoBlackDomain == 4 or $allTestMode;
    $mDoBlackDomain = 1         if $DoBlackDomain == 4;
      my $adr = lc $this->{mailfrom};
      $adr = batv_remove_tag(0,$this->{mailfrom},'');
      $senders{$adr} = 1;
    $senders{$adr} = 1;
    while ( $this->{header} =~ /\n(from|sender|reply-to|errors-to|list-\w+):.*?($EmailAdrRe\@$EmailDomainRe)/igo ) {
        my $s = $2;
        $s = batv_remove_tag(0,$s,'');
        $senders{lc $s}=1;
    } 
    $this->{senders} = join( ' ', keys %senders ) . " "; 

    my $slok           = $this->{allLoveSpam} == 1;

    my $subre;
    my $ret;
    my $tlit = tlit($mDoBlackDomain);
    my ($slmatch,$w);

    foreach my $adr ( split( " ", $this->{senders} ) ) {
        ($slmatch,$w) = &HighWeightSL($adr, 'weightedAddresses');

        last if $w ;
    }

    if ($w) {

        return 1 if $this->{noprocessing} && $w < ${'blValencePB'}[0];
        return 1 if $this->{whitelisted}  && $w < ${'blValencePB'}[0];
        my $bw; $bw = "black" if $w >= ${'blValencePB'}[0];
        $bw = "blackish" if $w >= 0 && $w < ${'blValencePB'}[0];
        $bw = "whitish" if $w < 0;
        $this->{messagereason} = $bw." address '$slmatch'";

        $this->{prepend} = "[weightedAddresses]";
        $tlit = "[scoring:$w]" if $mDoBlackDomain != 2;
        mlog( $fh, "$tlit -- $this->{messagereason}" );

        pbWhiteDelete( $fh, $ip );

        pbAdd($fh,$ip,$w,"$bw",1) if $mDoBlackDomain != 2;
        return 1 if $this->{messagescore} <= $this->{spamMaxScore};

        return 0 if $mDoBlackDomain == 1 && $w >= ${'blValencePB'}[0] ;

    } 
    if ($blackListedDomains && $this->{mailfrom}=~/($BLDRE)/ ) {
        $this->{messagereason}="blacklisted domain '$1'";
        $this->{prepend} = "[BlackDomain]";
        $tlit = "[scoring:${'blValencePB'}[0]]" if $mDoBlackDomain != 2;
        mlog($fh,"$tlit ($this->{messagereason})") if $ValidateSenderLog && $mDoBlackDomain==3 || $mDoBlackDomain==2;
        pbWhiteDelete($fh,$ip);
        return 1 if $mDoBlackDomain==2;
        pbAdd($fh,$ip,'blValencePB',"BlacklistedDomain") ;

        return 1 if $mDoBlackDomain==3;

        return 0 if $mDoBlackDomain==1;

    }
    if (!$NotGreedyBlackDomain && $this->{senders}) {
     foreach my $adr ( split( " ", $this->{senders} ) ) {

        if ($blackListedDomains && $adr =~/($BLDRE)/ ) {
            $this->{messagereason}="blacklisted domain '$1'";
            $this->{prepend} = "[BlackDomain]";
            $tlit = "[scoring:${'blValencePB'}[0]" if $mDoBlackDomain != 2;
            mlog($fh,"$tlit ($this->{messagereason})") if $ValidateSenderLog &&     $mDoBlackDomain==3 || $mDoBlackDomain==2;
            pbWhiteDelete($fh,$ip);
            return 1 if $mDoBlackDomain==2;
            pbAdd($fh,$ip,'blValencePB',"BlacklistedDomain") ;

            return 1 if $mDoBlackDomain==3;

            return 0 if $mDoBlackDomain==1;

        return 0;
        }

    }}

    return 1;
}

# do personal blacklisted domains check
sub PersonalBlackDomainOK {

    my $fh = shift;
    my $this = $Con{$fh};
    my %Bombs;

    d('PersonalBlackDomainOK');
    return 1 if $this->{notspamtag};
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{PersonalBlackDomainOK};
    $this->{PersonalBlackDomainOK} = 1;
    return 1 if !$this->{mailfrom};
    return 1 if $this->{whitelisted} && !$DoBlackDomainWL;
    return 1 if $this->{noprocessing} && !$DoBlackDomainNP;

    return 1 if $this->{relayok};

    return 1 if $noBlackDomain 
        && matchSL( $this->{mailfrom}, 'noBlackDomain' );

    my $ip = $this->{ip};
    $ip = $this->{cip} if $this->{cip};

    my %senders;
    my $adr = lc $this->{mailfrom};
    $adr = batv_remove_tag($fh,$this->{mailfrom},'');
    $senders{$adr} = 1;
    $senders{$adr} = 1;
    while ( $this->{header} =~ /\n(from|sender|reply-to|errors-to|list-\w+):.*?($EmailAdrRe\@$EmailDomainRe)/igo ) {
        my $s = $2;
        $s = batv_remove_tag(0,$s,'');
        $senders{lc $s}=1;
    } 
    $this->{senders} = join( ' ', keys %senders ) . " "; 

    my $slok           = $this->{allLoveBlSpam} == 1;

    my $mDoBlackDomain = $DoBlackDomain;
    $this->{testmode} = 0;
    $this->{testmode} = 1    if $DoBlackDomain == 4 or $allTestMode;
    $mDoBlackDomain = 1     if $DoBlackDomain == 4;
    my $subre;
    my $ret;
    my $tlit = tlit($mDoBlackDomain);
    my ($slmatch,$w);
    $this->{prepend} = "[PersonalBlack]";

    foreach my $adr ( split( " ", $this->{senders} ) ) {

        my ($mfd) = $adr =~ /\@(.*)/;
        my $all = "*@" . $mfd;

        my ($to) = $this->{rcpt} =~ /(\S+)/;
        my ($tod) = $this->{rcpt} =~ /\@(.*)/;
        my ($todd) = $this->{rcpt} =~ /(\@.*)/;
        $todd = "*$todd";

        if ( $PersBlack{ "*,$adr"}  ) {
            $PersBlack{lc "*,$adr"} = time;
            $this->{messagereason}="rejected by personal blacklist: '*,$adr'";
            pbAdd($fh,$ip,'blValencePB',"PersonalBlack",1) ;

            return 0;
        }

        if ( exists $PersBlack{lc "$to,$adr"} ) {
            $PersBlack{lc "$to,$adr"} = time;

            $this->{messagereason}="rejected by personal blacklist: '$to,$adr'";
            pbAdd($fh,$ip,'blValencePB',"PersonalBlack",1) ;

            return 0;
        }

    }
    return 1;
}

sub PTROK {

    my $fh = shift;
    my $this = $Con{$fh};
    return 1 if $this->{addressedToSpamBucket};

    d('PTROK');

    my $ip = $this->{ip};
    $ip = $this->{cip} if  $this->{cip};
    return 1 if $ip =~ /$IPprivate/;
    return 1 if !$DoReversed;
    return 1 if !$CanUseDNS;

    return 1 if $this->{ispip} && !$this->{cip};

    return 1 if $this->{contentonly} && !$this->{cip};
    return 1 if $this->{relayok} ;

    return 1 if $this->{whitelisted}  && !$DoReversedWL;
    return 1 if $this->{noprocessing} && !$DoReversedNP;

    return 1 if PTRCacheFind( $ip ) == 2 && !$whitePTRRe;

    my $slok = $this->{allLoveSpam} == 1;

    my $mDoReversed    = $DoReversed;        
    $this->{testmode}     = 0;
    $this->{testmode}         = 1    if $DoReversed == 4 or $allTestMode;
    $mDoReversed = 1     if $DoReversed == 4;

    my $tlit = tlit($mDoReversed);
    $this->{prepend} = "[PTRmissing]";

    if ( PTRCacheFind($ip) == 1 ) {
        $tlit = "[scoring:${'ptmValencePB'}[0]]" if $mDoReversed == 3;
        $this->{messagereason} = "PTR missing";
        mlog( $fh, "$tlit ($this->{messagereason})" )
          if $mDoReversed == 3 || $mDoReversed == 2;
        return 1 if $mDoReversed == 2;
        pbAdd( $fh, $ip, 'ptmValencePB', "PTRmissing" ) if $mDoReversed == 3;
        pbWhiteDelete( $fh, $ip );

        return 1 if $mDoReversed == 3;
        unless ($slok) {$Stats{ptrMissing}++;}
        pbAdd( $fh, $ip, 'ptmValencePB', "PTRmissing-blocked" ) if $mDoReversed == 1;
        return 1 if $this->{messagescore} <= $this->{spamMaxScore};

        return 0;
    }
    if ( PTRCacheFind($ip) == 2 ) {

        my ( $ct, $status, $ptrdsn) = split( " ", $PTRCache{$ip} );

        if (   $ptrdsn

            && $whitePTRRe
            && $whitePTRReRE != ""
            && $ptrdsn =~ $whitePTRReRE)
        {
            $this->{messagereason} = "PTR whitelisted '$ptrdsn'";
            $this->{prepend}       = "[PTRwhite]";

            mlog( $fh, "$this->{messagereason}" );

            pbWhiteAdd( $fh, $ip );
            $this->{noprocessing} = 1;
            $this->{passingreason} = "PTR $ptrdsn whitelisted";
            return 1;
        }
    }

    if ( $DoInvalidPTR && PTRCacheFind($ip) == 3 ) {

        my ( $ct, $status, $ptrdsn) = split( " ", $PTRCache{$ip} );

        if (   $ptrdsn
            && $DoInvalidPTR
            && $invalidPTRRe
            && $invalidPTRReRE != ""
            && $ptrdsn =~ $invalidPTRReRE
            && $ptrdsn !~ $validPTRReRE )
        {
            $this->{messagereason} = "PTR invalid '$ptrdsn'";
            $this->{prepend}       = "[PTRinvalid]";
            $tlit = "[scoring:${'ptiValencePB'}[0]]" if $mDoReversed == 3;
            mlog( $fh, "$tlit ($this->{messagereason})" )
              if $mDoReversed == 3 || $mDoReversed == 2;
            return 1 if $mDoReversed == 2;
            pbAdd( $fh, $ip, 'ptiValencePB', "PTRinvalid" ) if $mDoReversed == 3;
            pbWhiteDelete( $fh, $ip );
            return 1 if $mDoReversed == 3;
            pbAdd( $fh, $ip, 'ptiValencePB', "PTRinvalid-blocked") if $mDoReversed == 1; 
            unless ($slok) {$Stats{ptrInvalid}++;}
            return 1 if $this->{messagescore} <= $this->{spamMaxScore};

            return 0;
        }
    }

    my $res = Net::DNS::Resolver->new(
        nameservers => \@nameservers,
        tcp_timeout => $DNStimeout,
        udp_timeout => $DNStimeout,
        retrans     => $DNSretrans,
        retry       => $DNSretry
    );
    getRes('force', $res);
    my $ip_address = $ip;
    my $query;
    my $socket;
    if ($ip_address) {

        $query = eval { $res->search( $ip_address, 'PTR' ); };
        if ($@) {
            mlog( $fh, "error: $@" );
            pbAdd( $fh, $ip, 'ptiValencePB', "PTRerror:_$@" );
            PTRCacheAdd( $ip, 2 ); 
            return 1;
            }
        if ($query) {
            foreach my $rr ( $query->answer ) {
                next unless $rr->type eq "PTR";
                $this->{ptrdsn} = $rr->ptrdname;
                if (   $this->{ptrdsn}

                    && $whitePTRRe

                    && $this->{ptrdsn} =~ $whitePTRReRE)
                {
                    $this->{messagereason} = "PTR whitelisted '$this->{ptrdsn}'";
                    $this->{prepend}       = "[PTRwhite]";

                    mlog( $fh, "$this->{messagereason}" );

                    pbWhiteAdd( $fh, $ip );
                    $this->{noprocessing} = 1;
                    $this->{passingreason} = "PTR $this->{ptrdsn} whitelisted";
                    PTRCacheAdd( $ip, 2, $this->{ptrdsn} );
                    return 1;
                }
                return 1
                   if ( $heloBlacklistIgnore && $this->{ptrdsn} =~ $HBIRE );

                if (   $DoInvalidPTR
                    && $invalidPTRRe
                    && $invalidPTRReRE != ""
                    && $this->{ptrdsn} =~ $invalidPTRReRE
                    && $this->{ptrdsn} !~ $validPTRReRE )
                {
                    $this->{prepend} = "[PTRinvalid]";
                    $this->{messagereason} = "PTR invalid '$this->{ptrdsn}'";
                    $tlit = "[scoring:${'ptiValencePB'}[0]]" if $mDoReversed == 3;
                    mlog( $fh, "$tlit ($this->{messagereason})" )
                      if ( $mDoReversed == 3 || $mDoReversed == 2 );
                    PTRCacheAdd( $ip, 3, $this->{ptrdsn} );
                    return 1 if $mDoReversed == 2;
                    pbAdd( $fh, $ip, 'ptiValencePB', "PTRinvalid" ) if $mDoReversed == 3;
                    pbWhiteDelete( $fh, $ip );
                    return 1 if $mDoReversed == 3;
                    unless ($slok) {$Stats{ptrMissing}++;}
                    pbAdd( $fh, $ip, 'ptiValencePB', "PTRinvalid-blocked" ) if $mDoReversed == 1;;
                    return 1 if $this->{messagescore} <= $this->{spamMaxScore};

                    return 0;
                }
                PTRCacheAdd( $ip, 2, $this->{ptrdsn} );
                return 1;
            }
        } else {
            if ( $res->errorstring =~ "NXDOMAIN" ) {
                $this->{prepend} = "[PTRmissing]";
                $this->{messagereason} = "PTR missing";
                PTRCacheAdd( $ip, 1 );
                $tlit = "[scoring:${'ptmValencePB'}[0]]" if $mDoReversed == 3;
                mlog( $fh, "$tlit ($this->{messagereason})" )
                  if ( $mDoReversed == 3 || $mDoReversed == 2 );
                return 1 if $mDoReversed == 2;
                pbAdd( $fh, $ip, 'ptmValencePB', "PTRmissing" ) if $mDoReversed == 3;
                pbWhiteDelete( $fh, $ip );
                return 1 if $mDoReversed == 3;
                unless ($slok) {$Stats{ptrMissing}++;}
                pbAdd( $fh, $ip, 'ptmValencePB', "PTRmissing-blocked" ) if $mDoReversed == 1;
                return 1 if $this->{messagescore} <= $this->{spamMaxScore};
                return 0;
            }
        }
    }
    return 1;
}

sub DenyStrictOK {
    my ( $fh, $ip ) = @_;
    my $this = $Con{$fh};
    $ip = $this->{cip} if $this->{cip};
    my $ret;
    d('DenyStrictOK'); 
    my $byWhatList = 'denySMTPConnectionsFromAlways';

    $ret =  matchIP( $ip, 'denySMTPConnectionsFromAlways', $fh );
    if ($ret && !$this->{relayok}
        &&  !$this->{acceptall} 
        &&     !$this->{ispip}
        &&     !$this->{nopb}
        &&     !$this->{noblockingips} ) {

        $this->{prepend} = "[DenyIPStrict]";
        mlog( $fh, "[monitoring] ".$ip." blocked by $byWhatList: '$ret'" )
              if $DoDenySMTPstrict == 2 or $allTestMode;
        return 1 if $DoDenySMTPstrict == 2 or $allTestMode;
        mlog( $fh, $ip." blocked by $byWhatList: '$ret'" )
              if $DoDenySMTPstrict == 1;

        return 0 if $DoDenySMTPstrict == 1;
    }

    return 1;
}

sub DenyOK {
    my ( $fh, $myip ) = @_;
    my $this = $Con{$fh};
    $myip = $this->{cip} if $this->{cip};
    return 1 if $myip =~ /$IPprivate/;
    d('DenyOK');
    my $bip = &ipNetwork( $myip, $PenaltyUseNetblocks);
    return 1 if $this->{noprocessing};
    return 1 if $this->{ispip} && !$this->{cip};
    return 1 if $this->{noblockingips};
    return 1 if $this->{nopb};
    return 1 if $this->{acceptall};
    return 1 if $this->{relayok};
    my $t    = time;
    my $slok = $this->{allLoveSpam} == 1;
    my $tlit;

    return 1 if $this->{whitelisted};

    return 1 if $this->{addressedToSpamBucket};

    my $file;
    my $ret;
    $ret = matchIP( $myip, 'denySMTPConnectionsFrom', $fh );
    $this->{prepend} = "[DenyIP]";

    $this->{messagereason} = "denySMTPConnectionsFrom '$ret'";
    if ( $ret && $DoDenySMTP == 3 ) {
        mlog( $fh, "[scoring:${'dropValencePB'}[0]] -- $this->{messagereason} -- $this->{logsubject}" );
        pbAdd( $fh, $myip,'dropValencePB', "denySMTPConnectionsFrom");
        return 1;
    }

    if ( $ret && ( $DoDenySMTP == 2 or $allTestMode)) {
        mlog( $fh, "[monitoring] -- $this->{messagereason} -- $this->{logsubject}" );
        return 1;
    }
    if ( $ret && $DoDenySMTP == 1 ) {

        return 0;
    }

    return 1;
}

sub DroplistOK {
    my ( $fh, $ip ) = @_;
    my $this = $Con{$fh};

    d('DropOK');

    return 1 if $ip =~ /$IPprivate/;
    return 1 if $this->{ispip};
    return 1 if $this->{nopb};

    return 1 if $this->{acceptall};
    return 1 if $this->{relayok};
    return 1 if $this->{whitelisted};

    my $t    = time;

    return 1 if $this->{addressedToSpamBucket};
    my $ret = matchIP( $ip, 'droplist', $fh );
    return 1 if !$ret;
    my $mDoDropList = $DoDropList;
    $mDoDropList = 2 if $allTestMode;
    $mDoDropList = 3 if $this->{allLoveSpam} == 1;
    my $tlit = tlit($mDoDropList);
    $this->{prepend} = "[DropList]";    
    $this->{messagereason} = "found in DropList '$ret'";

    if ($mDoDropList == 2 ) {
        mlog( $fh, "[monitoring] -- $this->{messagereason} -- $this->{logsubject}" );
        return 1;
    }

    return 0 if $ret && $mDoDropList == 1;

    if ($mDoDropList == 3 ) {
        mlog( $fh, "[scoring:${'dropValencePB'}[0]] -- $this->{messagereason} -- $this->{logsubject}" );
        pbAdd( $fh, $ip,'dropValencePB', "Droplist");
        return 1;
    }

}
sub HistoryOK {
    my ( $fh, $myip ) = @_;
    my $this = $Con{$fh};
    return if $this->{addressedToSpamBucket};
    return if $this->{relayok};
    return 1 if $this->{notspamtag};
    return 1 if $myip =~ /$IPprivate/;
    return if $this->{badhistory};
    $myip = $this->{cip} if $this->{cip};
    d('HistoryOK');
    $this->{prepend} = "[History]";
    if ($spamFriends && $this->{spamfriends} && !$this->{spamfriendsdone}) {
        my ($slmatch,$w) = &HighWeightSL($this->{spamfriends}, 'spamFriends');
        $this->{messagereason} = "SpamFriends";
        $this->{spamfriendsdone} = 1;
        pbAdd( $fh, $myip, $w, "SpamFriends", 1 );
    }

    my $t    = time;
    my $ip   = ipNetwork( $myip, $PenaltyUseNetblocks );
    my $slok = $this->{allLoveSpam} == 1;
    my $tlit;   

    return if $this->{whitelisted};
    return if $this->{noprocessing};
    my $mf = lc $this->{mailfrom};
    $mf = batv_remove_tag($fh,$this->{mailfrom},'');
    my $mfd; $mfd = $1 if $mf =~ /\@(.*)/;

    return if $this->{ispip};
    return if $this->{contentonly};
    return if $this->{nopb};
    return if $this->{acceptall};

    return if $this->{relayok};
    return if $myip =~ /$IPprivate/ ;

    if (pbWhiteFind($myip)) {
        pbBlackDelete( $fh, $myip );
        return 1;
    }
    return 1 if !pbBlackFind( $myip );
    my $blackscore = pbBlackFind( $myip );

    if ( $blackscore >= $PenaltyLimit * 2 ) {
        mlog( $fh, "Very Bad Reputation of $blackscore for $myip") if $PenaltyLog >= 2;
        $this->{messagereason} = "Very Bad Reputation for $myip";
        pbAdd( $fh, $myip, 'vbrValencePB', "VeryBadReputation", 1 );
        $this->{badhistory} = 1;
        return 0;
    } 
    if (  $blackscore >= $PenaltyLimit) {
        mlog( $fh, "Bad Reputation of $blackscore for $myip") if $PenaltyLog >= 2;
        $this->{messagereason} = "Bad Reputation for $myip";
        pbAdd( $fh, $myip, 'brValencePB', "BadReputation", 1 );
        $this->{badhistory} = 1;
        return 0;
    }

} 

sub Delayok {
    my ( $fh, $rcpt ) = @_;
    my $this   = $Con{$fh};
    my $client = $this->{friend};
    d('Delayok');
    $this->{prepend} = "[DelayOK]";
    if ( $this->{delaydone} ) {
        $this->{delaydone} = '';
        return 1;
    }
    return 1 if !$EnableDelaying;
    return 1 if $this->{relayok};
    return 1 if $Con{$client}->{relayok};
    return 1 if $this->{ispip};
    return 1 if $this->{acceptall};
    return 1 if $this->{ip} =~ /$IPprivate/;
    my $helook = $noDelayHelosReRE;
    my $v = GRIPv( $fh, $this->{ip} );
    my $time = $UseLocalTime ? localtime() : gmtime();
    my $tz   = $UseLocalTime ? tzStr() : '+0000';
    $time =~ s/... (...) +(\d+) (........) (....)/$2 $1 $4 $3/;
    my $mf   = lc $this->{mailfrom};
    $mf = batv_remove_tag(0,$this->{mailfrom},'');
    my $mfd; $mfd  = $1 if $mf =~ /\@(.*)/;

    if ( !$DelayWL && $this->{whitelisted} ) {

       # add to our header; merge later, when client sent own headers  (per msg)
        $this->{myheader} .=
          "X-Assp-Delay: not delayed ($this->{passingreason}); $time $tz\r\n"
          if ( $DelayAddHeader
            && $this->{myheader} !~ /not delayed / );
        return 1;
    }

    if ( !$DelayNP && $this->{noprocessing} ) {

       # add to our header; merge later, when client sent own headers  (per msg)
        $this->{myheader} .=
          "X-Assp-Delay: $rcpt not delayed (noprocessing); $time $tz\r\n"
          if ( $DelayAddHeader
            && $this->{myheader} !~ /not delayed / );

        return 1;
    }
    if ( $this->{nodelay} ) {

       # add to our header; merge later, when client sent own headers  (per msg)
        $this->{myheader} .=
          "X-Assp-Delay: $rcpt not delayed ($this->{nodelay}); $time $tz\r\n"
          if ( $DelayAddHeader
            && $this->{myheader} !~ /not delayed / );
        return 1;
    }
    if ( !$DelayWL && pbWhiteFind( $this->{ip} ) ) {
        pbBlackDelete( $fh, $this->{ip} );

       # add to our header; merge later, when client sent own headers  (per msg)
        $this->{myheader} .=
"X-Assp-Delay: $rcpt not delayed (whitebox $this->{ip}); $time $tz\r\n"
          if ( $DelayAddHeader
            && $this->{myheader} !~ /not delayed / );

        return 1;
    }
    if ( !$DelayWL && SBCacheFind( $this->{ip},2 ) ) {

       # add to our header; merge later, when client sent own headers  (per msg)
        $this->{myheader} .=
"X-Assp-Delay: $rcpt not delayed (whiteorg $this->{ip}); $time $tz\r\n"
          if ( $DelayAddHeader
            && $this->{myheader} !~ /not delayed / );

        return 1;
    }
    if ( !$DelaySL && $this->{allLoveDLSpam} == 1 ) {

       # add to our header; merge later, when client sent own headers  (per msg)
        $this->{myheader} .=
          "X-Assp-Delay: $rcpt not delayed (spamlover); $time $tz\r\n"
          if ( $DelayAddHeader
            && $this->{myheader} !~ /not delayed / );
        return 1;
    }
    if ( $this->{dlslre} ) {

      # add to our header; merge later, when client sent own headers  (per rcpt)
        $this->{myheader} .=
          "X-Assp-Delay: $rcpt not delayed (delay-spamlover); $time $tz\r\n"
          if ( $DelayAddHeader
            && $this->{myheader} !~ /not delayed / );

        $this->{dlslre}="";    
        return 1;
    }
    my $mfwhite = $mf;
    $mfwhite =~ s/.*@//;

    my ( $cachetime, $cresult, $chelo ) = SPFCacheFind($this->{ip},$mfwhite);

    if (!$DelayWL && $DoOrgWhiting == 1 && ! &pbBlackFind($this->{ip})) {
        my ( $ipcountry, $orgname, $domainname ) = split( /\|/, SBCacheFind($this->{ip}) ) ;
        if (!$DelayWL && $domainname eq $mfwhite && exists $WhiteOrgList{$domainname}) {
          # add to our header; merge later, when client sent own headers  (per rcpt)
            $this->{myheader}.="X-Assp-Delay: not delayed (White-SenderBase-Cache-OK); $time $tz\r\n" if ($DelayAddHeader && $this->{myheader} !~ /not delayed \(White-SenderBase/o);
            return 1;
        }
    }
    if ($DelayNormalizeVERPs) {

        # strip extension
        $mf =~ s/\+.*(?=@)//;

        # replace numbers with '#'
        $mf =~ s/\b\d+\b(?=.*@)/#/g;
    }
    my $ip = ipNetwork( $this->{ip}, $DelayUseNetblocks );
    my $hash = "$ip $mf " . lc $rcpt;

    # get sender domain

    my $hashwhite = "$ip $mfwhite";
    if ( $CanUseMD5 && $DelayMD5 ) {
        $hash      = Digest::MD5::md5_hex($hash);
        $hashwhite = Digest::MD5::md5_hex($hashwhite);
    }
    my $t = time;
    my $delay_result;
    if ( !exists $DelayWhite{$hashwhite} ) {
        if ( !exists $Delay{$hash} ) {
            mlog( $fh, "adding new triplet: ($ip,$mf," . lc $rcpt . ")", 1 )
              if $DelayLog >= 2;
            $Stats{rcptDelayed}++;
            $Delay{$hash} = $t;
            $delay_result = 0;
        } else {
            my $interval          = $t - $Delay{$hash};
            my $intervalFormatted = formatTimeInterval($interval);
            if ( $interval < $DelayEmbargoTime * 60 ) {
                mlog(
                    $fh,
                    "embargoing triplet: ($ip,$a,"
                      . lc $rcpt
                      . ") waited: $intervalFormatted" ) if $DelayLog >= 2;
                $Stats{rcptEmbargoed}++;
                $delay_result = 0;
            } elsif (
                $interval < $DelayEmbargoTime * 60 + $DelayWaitTime * 3600 )
            {
                mlog(
                    $fh,
                    "accepting triplet: ($ip,$a,"
                      . lc $rcpt
                      . ") waited: $intervalFormatted" ) if $DelayLog >= 2;
                delete $Delay{$hash};
                $DelayWhite{$hashwhite} = $t;

                $delay_result = 1;

                # add to our header; merge later, when client sent own headers
                $this->{myheader} .=
"X-Assp-Delay: $rcpt was delayed for $intervalFormatted; $time $tz\r\n"
                  if $DelayAddHeader;
            } else {
                mlog(
                    $fh,
                    "late triplet encountered, deleting: ($ip,$a,"
                      . lc $rcpt
                      . ") waited: $intervalFormatted" ) if $DelayLog >= 2;
                $Stats{rcptDelayedLate}++;

                $Delay{$hash} = $t;
                $delay_result = 0;
            }
        }
    } else {
        my $interval          = $t - $DelayWhite{$hashwhite};
        my $intervalFormatted = formatTimeInterval($interval);
        if ( $interval < $DelayExpiryTime * 24 * 3600 ) {
            mlog( $fh,
                "renewing tuplet: ($ip,$mfwhite) age: " . $intervalFormatted )
              if $DelayLog >= 2;
            $DelayWhite{$hashwhite} = $t;

            # multiple rcpt's
            delete $Delay{$hash};
            $delay_result = 1;

            # add to our header; merge later, when client sent own headers
            $this->{myheader} .=
              "X-Assp-Delay: $rcpt not delayed (auto accepted); $time $tz\r\n"
              if $DelayAddHeader;
        } else {
            mlog(
                $fh,
                "deleting expired tuplet: ($ip,$mfwhite) age: "
                  . $intervalFormatted ) if $DelayLog >= 2;
            $Stats{rcptDelayedExpired}++;
            delete $DelayWhite{$hashwhite};
            $Delay{$hash} = $t;
            $delay_result = 0;
        }
    }
    return $delay_result;
}

sub allNoProcessingTo {
    my (  $rcpt, $fh) = @_;
     my $this   = $Con{$fh};
    my $c    = 0;
    for ( split( ' ', $rcpt ) ) {
        $this->{newrcpt} .= "$_ "; 
        return 0 unless matchSL( $_, 'noProcessing' ) or matchSL( $_, 'noProcessingTo' );
        $c++;
    }
    $c;
}

sub allSL {
    my ( $rcpt, $from, $re ) = @_;
    my $c = 0;
    return 1 if matchSL( $from, $re, 1 );
    for ( split( ' ', $rcpt ) ) {
        return 1 if matchSL( $_, $re, 1 );
        next;
    }
    return 0;
}

sub allSH {
   my($rcpt,$re)=@_;
   return 0 unless $rcpt;
   return 0 unless $re;
   return 0 unless $$re;
   my $ret = 1;
   for (split(/\s+/o,$rcpt)) {
      if (! matchSL($_,$re,1)) {
         $ret = 0 ;
         last;
      }
   }
   return $ret;
}
# the message is not spam -- route it to the server
sub isnotspam {
    my ( $fh, $done ) = @_;
    d('isnotspam');
    my $this   = $Con{$fh};
    my $server = $this->{friend};

    # it's time to merge our header with client's one

    if (   (! $this->{relayok} || ($this->{relayok} && ! $NoExternalSpamProb ) )
        && !$this->{myheaderdone}

       )
    {
    $this->{myheader} .= "X-Assp-ID: $myName ($this->{msgtime})\r\n" if $this->{myheader} !~ "X-Assp-ID";
    $this->{myheader} .= "X-Assp-Version: $version$modversion\r\n" if $this->{myheader} !~ "X-Assp-Version";
    my $myheader = $this->{myheader};
      $myheader = headerFormat($myheader);
      d('after headerWrap');
      $this->{header}=~s/^($HeaderRe*)/$1\r\n\n\n\r$myheader/o;
      d('after merge our header');
      $this->{header}=~s/\r?\n?\r\n\n\n\r/\r\n/;
      d("added header : $this->{myheader}");
      $this->{myheaderdone} = 1;

      }

     sigOK( $fh, $this->{header}, $done ) if $this->{relayok};

      if (
          ! $this->{MSGIDsigRemoved} 
          && ! $this->{relayok} 
          && $DoMSGIDsig 
          && !$this->{isbounce}) {   
              &MSGIDsigRemove($fh);  # remove the MSGID signatures from incoming emails
              $this->{maillength} = length($this->{header});
      }

    sendque( $server, $this->{header} );
    $this->{headerpassed} = 1;

    if ($done) {

    onwhitelist( $fh, $this->{header}) if $this->{relayok} && !$this->{spamfound};

        &sayMessageOK($fh) if !$this->{spamfound}; 
        $this->{getline} = \&getline;
    } else {
        $this->{getline} = \&whitebody;
    }
}

# the message is non spam -- just relay it to the server
sub whitebody {

    my ( $fh, $l ) = @_;
    my $this;
    $this=$Con{$fh} if exists $Con{$fh};
    my $friend;
    $friend=$Con{$fh} if exists $Con{$fh};
    d('whitebody');
    my $server = $this->{friend};
    my $mbytes;
    my $clamavbytes;
    my $maxbytes; 

    $this->{maillength}+=length($l);
    $this->{header} .= $l if(length($this->{header}) < 100000) or ($sendHamInbound && ! $this->{relayok}) or ($sendHamOutbound &&  $this->{relayok});

    return if ! MessageSizeOK($fh);

    my $done = $l =~ /^\.[\r\n]*$/
      || defined( $this->{bdata} ) && $this->{bdata} <= 0;
    $this->{headerlength} ||= getheaderLength($fh);
    $maxbytes = $MaxBytes > 10000 ? $MaxBytes + $this->{headerlength} : 10000 + $this->{headerlength};
    $clamavbytes = $ClamAVBytes ? $ClamAVBytes + $this->{headerlength} : 50000 + $this->{headerlength};
    $clamavbytes = 500000 if $ClamAVBytes > 500000;
    $mbytes = $maxbytes;
    $mbytes = $clamavbytes if $clamavbytes > $mbytes  && ($BlockExes || $CanUseAvClamd && $AvailAvClamd) ;

    $this->{headerpassed} = 1 if ($done || $this->{maillength} >= $mbytes );

    my $doneToError = $done || ($send250OK || ($send250OKISP && ($this->{ispip} or $this->{cip})));

    if (($done || $this->{maillength} >= $mbytes ) && haveToScan($fh) &&
         ! ClamScanOK($fh, bodyWrap(\$this->{header},$clamavbytes)))
    {

          if ( $this->{messagereason} =~ /UNOFFICIAL/i ){ 
              thisIsSpam($fh,$this->{messagereason}, $SpamLog,$this->{averror}, 0,0,$doneToError);
          } else {
              thisIsSpam($fh,$this->{messagereason}, $SpamVirusLog,$this->{averror}, 0,0,$doneToError);
          }
        return;
    }

    if (($done || $this->{maillength} >= $mbytes ) && haveToFileScan($fh) &&
         ! FileScanOK($fh, bodyWrap(\$this->{header},$clamavbytes)))
    {
        thisIsSpam($fh,$this->{messagereason}, $SpamVirusLog,$this->{averror},0,0,$doneToError);
        return;
    }

    sigOK( $fh, $l, $done );

    if (! $friend->{MSGIDsigRemoved} && ! $friend->{relayok} && $DoMSGIDsig && ! $this->{noMoreQueued}) {
      if ($friend->{isbounce}) {
          if ($done) {
              &MSGIDsigRemove($this->{friend});  # remove the MSGID signatures from incoming emails
              $friend->{maillength} = length($friend->{header});
          }
      } else {
          &MSGIDsigRemove($this->{friend});  # remove the MSGID signatures from incoming emails
          $friend->{maillength} = length($friend->{header});
      }
      }
    if ($this->{relayok} && !$this->{whitepassed}) {
        my %adr;
        my $t = time;
        $this->{whitepassed} = 1;
        foreach my $adr ( split( ' ', lc $this->{rcpt} ) ) {
            $adr{$adr} = 1;
          }
        foreach my $adr ( keys %adr ) {
            next if localmail($adr) || !$adr;
            next if $Redlist{$adr};             # don't add to whitelist if rcpt is redlisted
            next if $adr =~ s/^\'//;
            next if length($adr) > 127;
            $this->{whitepassed} = 1;
            mlog( $fh, "auto whitelist addition: $adr", 1 )
              unless $Whitelist{$adr} || $NoAutoWhite;
            $Whitelist{$adr} = $t unless !$Whitelist{$adr} && $NoAutoWhite;
        }
    }

    if($done) {

        $this->{getline}=\&getline;
        &sayMessageOK($fh) if !$this->{spamfound}; 
    }
    sendque( $server, $l);

}

# the message may or may not be spam -- get the body and test it.

sub getbody {
    my ( $fh, $l ) = @_;
    my $this = $Con{$fh};

    my ( $bomblt, $er );
    my $dataref;
    my $virusdataref;
    my $maxbytes;
    my $clamavbytes;
    my $mbytes;
    my $slok;
    $this->{datastart} = $this->{maillength} if (! $this->{datastart});
    $this->{maillength}+=length($l);
    $this->{header} .= $l;

    $this->{headerlength} ||= getheaderLength($fh);
    $this->{relayok} = 1 if matchIP($this->{ip}, 'acceptAllMail',   0, 1 ) && !$this->{relayok};

    $maxbytes = $MaxBytes > 10000 ? $MaxBytes + $this->{headerlength} : 10000 + $this->{headerlength};
    $clamavbytes = $ClamAVBytes ? $ClamAVBytes + $this->{headerlength} : 50000 + $this->{headerlength};
    $clamavbytes = 500000 if $ClamAVBytes > 500000;
    $mbytes = $maxbytes;
    $mbytes = $clamavbytes if $clamavbytes > $mbytes  && ($BlockExes || $CanUseAvClamd && $AvailAvClamd) ;

    my $done = $l =~ /^\.[\r\n]*$/o || defined( $this->{bdata} ) && $this->{bdata} <= 0;

    if ( $done || $this->{maillength} >= $mbytes) {
        my $doneToError = $done || ($send250OK || ($send250OKISP && ($this->{ispip} or $this->{cip})));
        $dataref = bodyWrap(\$this->{header},$maxbytes);
        $virusdataref = bodyWrap(\$this->{header},$clamavbytes);
        d( "getbody - done:$done maillength:$this->{maillength}" );
        if ( !$this->{red} && $redRe && $$dataref =~ /($redReRE)/ )    {
            $this->{red} = ($1||$2);
        }
        my $sub = $Con{$fh}->{subject3};
        if (&NotSpamTagCheck($fh,$sub) ) {
            $this->{prepend} = "[NotSpamTag]";
            $this->{notspamtag} = 1;
            $this->{noprocessing} = 1;
            $this->{whitelisted} = 1 if $NotSpamTagAutoWhite;
            $this->{passingreason} = "NotSpamTag";
            my $adr = lc $Con{$fh}->{mailfrom};
            $adr = batv_remove_tag($fh,$adr,'');
            if ($adr && length($adr) < 50 && !&localmailaddress($fh,$adr) &&  $adr !~ /^SRS/i && !$Con{$fh}->{red} && !$Redlist{$adr} && !$NoAutoWhite ) {
                mlog( $fh, "whitelist addition: $adr by NotSpamTag" )  if $NotSpamTagAutoWhite;
                $Whitelist{$adr} = time if $NotSpamTagAutoWhite;
            }
        }
        if (! $this->{relayok} && 
            ! $this->{msgidsigdone} &&
            $this->{isbounce} &&
            $DoMSGIDsig &&
            $CanUseSHA1 &&
            ! $this->{whitelisted} &&
            ! $this->{noprocessing} &&
            ! $this->{addressedToSpamBucket} &&
            $this->{header} =~ /([^\r\n]+\:)[\r\n\s]*\<$MSGIDpreTag\.(\d)(\d\d\d)(\w{6})\.([^\r\n]+)\>/ &&
            ! $this->{from} &&
            &MSGIDsigCheck($fh)
           )
        {
             $this->{msgidsigdone} = 1;
             $this->{noprocessing} = 1;
             $this->{prepend} = "[MSGID-Valid]";
             $this->{passingreason} = 'Valid MSGID signature';
             pbBlackDelete($fh,$this->{ip});
             pbWhiteAdd($fh,$this->{ip},"ValidMSGID");
        }    
        if ( !$this->{noprocessing} 
            && !$this->{whitelisted}
            && !$this->{addressedToSpamBucket}
            && $whiteRe) {
            WhiteOk($fh,$dataref);
        }
        if ( !$this->{noprocessing} && $npRe
            && !$this->{relayok} 
            && !$this->{whitelisted}
            && !$this->{addressedToSpamBucket}
            && $npReRE != ""
            && $this->{header} =~  /($npReRE)/i )
        {
            mlogRe( $fh, $1, "npRe" );
            pbBlackDelete( $fh, $this->{ip} );
            $this->{noprocessing}  = 1;
            $this->{passingreason} = "npRe '$1'";

        }
        if (!$this->{noprocessing} && $npLocalRe
            && $this->{relayok} 
            && $this->{header} =~ /($npLocalReRE)/i )
        {
            mlogRe( $fh,($1||$2), "npLocalRe" ,1);
            $this->{noprocessing}  = 1;
            $this->{passingreason} = "npLocalRe '($1||$2)'";
        }
        if (&MessageScoreHigh($fh,10)) {
                    MessageScore( $fh, $doneToError );
                    return;
        } 
        if ( !$this->{noprocessing} && !$this->{whitelisted} && $this->{allwhitelist} == 1 )
        {
            my $slok = $this->{allLoveSpam} == 1;
            $Stats{bspams}++ unless $slok;;
            delayWhiteExpire($fh);

            $this->{prepend} = "[WhitelistOnly]";
            my $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError; 
            $reply = replaceerror ($fh, $reply);  
            thisIsSpam( $fh, "Whitelist Only Allowed",
            $baysSpamLog, $reply, $allTestMode, $slok, $doneToError );

            return;
        }
        if (   $ccSpamNeverRe
            && !$this->{relayok}
            && $$dataref =~ ( '(' . $ccSpamNeverReRE . ')' ) ) {
            mlogRe( $fh, $1, "CCnever" );
            $this->{ccnever} = 1;
        }
        if ( $this->{spamfound} ) {

            return; 
        }   
        if ( haveToScan($fh) && !ClamScanOK( $fh, $virusdataref ) ) {
            my $slok = $this->{allLoveATSpam} == 1;
            if ( $this->{messagereason} =~ /UNOFFICIAL/i ){ 
                  thisIsSpam($fh,$this->{messagereason}, $SpamLog,$this->{averror}, 0,0,$doneToError);
              } else {
                  thisIsSpam($fh,$this->{messagereason}, $SpamVirusLog,$this->{averror}, 0,0,$doneToError);
              }
            return;
        } elsif ( haveToFileScan($fh) && !FileScanOK( $fh, $virusdataref ) ) {
            my $slok = $this->{allLoveATSpam} == 1;
            thisIsSpam( $fh, $this->{messagereason},
                $SpamVirusLog, $this->{averror}, 0,
                0, $doneToError );
            return;

        } 
        if (&MessageScoreHigh($fh,25)) {
                  MessageScore( $fh, 1 );
                  return;
        }
        if (!PTROK($fh)) {
            my $slok=$this->{allLovePTRSpam}==1;
            my $reply = $SpamError;
            $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;               
            $reply =~ s/REASON/$this->{messagereason}/go;
            $reply = replaceerror ($fh, $reply);

            thisIsSpam($fh,"$this->{messagereason}", $invalidSenderLog,$reply,$this->{testmode},$slok,$doneToError);
            $this->{messagereason}="";
            return;
        }

        if (&MessageScoreHigh($fh,25)) {
                  MessageScore( $fh, 1 );
                  return;
        }

        if ( !BombBlackOK( $fh, $dataref ) ){  
            delayWhiteExpire($fh);
            my $slok = $this->{allLoveBoSpam} == 1;
            $Stats{bombs}++ unless $slok;
            my $reply = $SpamError;
            $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;            
            $reply =~ s/REASON/$this->{messagereason}/g;
            $reply = replaceerror ($fh, $reply);
            $this->{test} = "allTestMode";
            thisIsSpam( $fh, $this->{messagereason},
                $spamBombLog, $reply, $this->{testmode}, $slok, $doneToError );
            return;
        }

        if ( !$AsASecondary && !BombOK($fh, $dataref) ) {

            $slok = $this->{allLoveBoSpam} == 1;
            $slok = 0 if $this->{messagereason} =~ /bombCharSets/i;
            $Stats{bombs}++ unless $slok;
            delayWhiteExpire($fh);            

            my $reply = $SpamError;
            $reply = replaceerror ($fh, $reply);            
            $reply =~ s/REASON/$this->{messagereason}/g;
            $reply = replaceerror ($fh, $reply);

            thisIsSpam( $fh, $this->{messagereason},
                $spamBombLog, $reply, $this->{testmode}, $slok, $doneToError );
            return;
        }

          if (&MessageScoreHigh($fh,25)) {
                    MessageScore( $fh, 1 );
                    return;
         }

        if ( $DoBlockExes
            && !CheckAttachments( $fh, $dataref, $AttachLog, $doneToError)){
            return;

        } 
        if ( !URIBLok( $fh, $dataref, $this->{ip}, $doneToError ) ) {
            delayWhiteExpire($fh);
            return;

        }

          if (&MessageScoreHigh($fh,25)) {
                    MessageScore( $fh, 1 );
                    return;
         }

        if ( !BayesOK( $fh, $dataref, $this->{ip} ) ) {
            $slok = $this->{allLoveBaysSpam} == 1;

            $this->{testmode} = $slok = 0 if allSH( $this->{rcpt}, 'baysSpamHaters' );
            $this->{messagereason} = 'Bayesian';
            my $reply = $SpamError;
            $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;            
            $reply =~ s/REASON/$this->{messagereason}/g;
            $reply = replaceerror ($fh, $reply);

            if ( !$slok ) { $Stats{bspams}++; }
            $this->{test} = "bayesTestMode";
            $this->{prepend} = "[Bayesian]";
            thisIsSpam( $fh, 'Bayesian', $baysSpamLog, $reply,
                $this->{testmode}, $slok, $doneToError );
            return;

        } 
        if ($DoPenaltyMessage){
            if ($MessageScoringUpperLimit
                && !$this->{whitelisted}
                && $this->{messagescore} >= ($MessageScoringUpperLimit ) ) {

                  MessageScore( $fh, $done);
                  return; 

            } elsif ($MessageScoringLowerLimit
                    && !$this->{whitelisted}
                    && $this->{messagescore} > $MessageScoringLowerLimit
                    && $MessageScoringUpperLimit
                    && $this->{messagescore} < $MessageScoringUpperLimit ) {

                  $this->{messagelow} = 1;
                  $this->{messagereason} = "MessageScore in warning range($this->{messagescore})";                 
                  my $reply = $SpamError; 
                  $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;                   
                  $reply =~ s/REASON/MessageScore/go;
                  $reply = replaceerror ($fh, $reply);
                  $this->{prepend} = "[MessageScore]";
                  thisIsSpam( $fh, $this->{messagereason},  $spamMSLog, $reply,1 , 0, $done );
                  return;
            }
        }    

        my $Spamlog;
        my $prepend;
        if ($this->{spamfound}) {
            $this->{prepend} = "[SpamLover]";
            $prepend = "spam passing";
            $Spamlog = $SpamLog;
        } elsif ($this->{relayok}) {
            $this->{prepend} = "[LocalOK]";
            $prepend = "local";
            $Stats{locals}++;
            $Spamlog = $NonSpamLog;
            $Spamlog = "" if $this->{attachcomment};
        } elsif ($this->{noprocessing}) {
            $this->{prepend} = "[NoProcessingOK]";
            $prepend = "noprocessing";
            $Stats{noprocessing}++ if !$this->{relayok};
            $Spamlog = $noProcessingLog;
        } elsif ($this->{whitelisted}) {
            $this->{prepend} = "[WhitelistedOK]";
            $prepend = "whitelisted";
            $Stats{whites}++;
            $Spamlog = $NonSpamLog;

        } else { 
            $Spamlog = $baysNonSpamLog;
            $this->{prepend} = "[MessageOK]";
            $prepend = "message ok";
            $Stats{bhams}++
        }

        addSpamProb( $fh) if !$this->{spamfound}; 
        $Spamlog = "" if $this->{spamfound};
        $Spamlog = "" if $this->{attachcomment};
        my $fn; $fn = Maillog( $fh, '', $Spamlog ) if $Spamlog;

        $fn = ' -> ' . $fn if !$fn == "";
        $fn = ""           if !$fileLogging && !$inclResendLink;

        &makeSubject($fh);
        my $logsub = ( $subjectLogging ? " $subjectStart$this->{subject3}$subjectEnd" : '' );
        my $pr = $this->{passingreason} ? " - $this->{passingreason} -" : '' ;
        my $ac = $this->{attachcomment} ? " - $this->{attachcomment} " : '' ;
        $this->{sayMessageOK} = "$prepend$pr$logsub$ac$fn";
        mlog( $fh, "$this->{sayMessageOK}" ) if $this->{spamfound};

        isnotspam( $fh, $done );

    }
}

# checks for blocked attachments
sub CheckAttachments
{
    my ( $fh, $bd, $attachlog, $done ) = @_;
    my $this = $Con{$fh};
    my @name;

    return 1 unless $CanUseEMM;
    return 1 unless $DoBlockExes;
    return 1 if $this->{attachdone};

    my $msg = ref $bd ? $$bd : $bd;
    $this->{prepend} = "[Attachment]";

    eval {
        $Email::MIME::ContentType::STRICT_PARAMS=0;      # no output about invalid CT
        my $email=Email::MIME->new($msg);
        foreach my $part ( $email->parts ) {
            my $dis = $part->header("Content-Type") || '';
            my $attrs = $dis =~ s/^[^;]*;//o ? Email::MIME::ContentType::_parse_attributes($dis) : {};
            my $name = $attrs->{name} || $part->{ct}{attributes}{name};
            my $filename = $attrs->{filename} || $part->{ct}{attributes}{filename};
            eval{$filename ||= $part->filename;};
            if (! $name || ! $filename) {
              eval{
                $dis = $part->header("Content-Disposition") || '';
                $attrs = $dis =~ s/^[^;]*;//o ? Email::MIME::ContentType::_parse_attributes($dis) : {};
                $name ||= $attrs->{name} || $part->{ct}{attributes}{name};
                $filename ||= $attrs->{filename} || $part->{ct}{attributes}{filename};
              };
            }
            if (($name||$filename) && $part->header("Content-Disposition")=~ /attachment|inline/io ) {
                my $attname = $filename || $name;
                $this->{attachcomment} = "attachment '$attname'";
                mlog($fh,"info:  found attachment '$attname'") if ($AttachmentLog >= 2);
                push(@name,$attname);
            }
        }
    };
    if ($@) {
        mlog($fh,"error: unable to parse message for attachments - $@") unless $IgnoreMIMEErrors;
        d("error: unable to parse message for attachments - $@") ;
    }
    my $numatt = @name;
    my $s; $s = 's' if ($numatt > 1);
    mlog($fh,"info: $numatt attachment$s") if ($AttachmentLog && $numatt > 1);
    $this->{attachcomment} = "$numatt attachment$s" if $numatt > 1;
    my $tlit = tlit($DoBlockExes);
    my $block = $BlockExes;

    if ($this->{noprocessing}) {
        $block = $BlockNPExes;
    } elsif ($this->{relayok} ) {
        $block = $BlockLCExes;
    } elsif ($this->{whitelisted} ) {
        $block = $BlockWLExes;
    }

    return 1 if !$block;
    mlog($fh,"info:  checking attachments using Level-$block") if ($AttachmentLog >= 2);
    my $bRE = $badattachRE[$block];
    foreach my $name (@name) {
        my $ext;
        eval{use bytes;($ext) = $1 if $name =~ /(\.[^\.]+)$/o;};
        if ( ( $block >= 1 && $block <= 3 && $ext =~ /$bRE/ ) ||
             ( $GoodAttach && $block == 4 && $ext !~ /$goodattachRE/  ) )
        {
            $this->{attachdone} = 1;

            if ($DoBlockExes == 1) {$Stats{viri}++;}
            delayWhiteExpire($fh) if $DoBlockExes == 1;

            eval{$this->{messagereason} = "bad attachment '$name'";};
            $this->{attachcomment} = $this->{messagereason};
            $tlit = "[scoring:${'baValencePB'}[0]]" if $DoBlockExes != 2;
            mlog( $fh, "$tlit $this->{messagereason}" ) if ($DoBlockExes > 1 && $AttachmentLog);
            return 1 if $DoBlockExes == 2;

            pbAdd( $fh, $this->{ip}, 'baValencePB', "BadAttachment" ) if $DoBlockExes != 2;

            return 1 if $DoBlockExes == 3;

            my $reply = $AttachmentError;
            eval{$name = encodeMimeWord($name,'B','UTF-8') unless is_7bit_clean($name);
                 $reply =~ s/FILENAME/$name/go;
            };
            my $slok = $this->{allLoveATSpam} == 1;
            # Send attachment report to recipient if set
            &sendNotification ($this->{rcpt}, $this->{rcpt},"blocked bad attachment $name send from $this->{mailfrom}",$this->{messagereason})
            if $AttachmentReportToRCPT && !$this->{relayok};
            thisIsSpam( $fh, $this->{messagereason}, $attachlog, $reply, $allTestMode, $slok, $done );

            return 0;
        }
    }
    return 1;
}

# This is spam, lets see if its Testmode or spamlover.
sub replaceerror {
    my ( $fh, $error, $email) = @_;
    my $this = $Con{$fh};
    my ($to) = $this->{rcpt} =~ /(\S+)/;
    my $mfd; $mfd = $1 if $to =~ /\@(.*)/;
    $mfd = $1 if $DefaultDomain =~ /\@(.*)/ && !$mfd;
    $error = $SpamError if !$error;

    $error =~ s/500/550/g;
    $error =~ s/LOCALDOMAIN/$mfd/g if $mfd;
    $error =~ s/LOCALDOMAIN/$defaultLocalHost/g if !$mfd;

    $error =~ s/SESSIONID/$this->{msgtime}/g;
    $error =~ s/MYNAME/$myName/g;

    $error =~ s/REASON/$this->{messagereason}/g;
    $error =~ s/NOTSPAMTAG/$NotSpamTag/g;
    $error =~ s/EMAILADDRESS/$email/g if $email;

    return $error
}

sub makeMyheader {
    my ($fh,$slok,$testmode,$reason) = @_;
    my $this = $Con{$fh};
    d('makeMyheader');
    # add to our header; merge later, when client sent own headers
    $this->{myheader}="X-Assp-Version: $version$modversion on $myName\r\n" . $this->{myheader}
        if $this->{myheader} !~ /X-Assp-Version:.+? on $myName/;
    $this->{myheader}.= "X-Assp-ID: $myName $this->{msgtime}\r\n"
        if $this->{myheader} !~ /X-Assp-ID: $myName/;
    $this->{myheader}.="X-Assp-Redlisted: Yes ($this->{red})\015\012"
        if $this->{red} && $this->{myheader} !~ /X-Assp-Redlisted/o;
    $this->{myheader}.= "X-Assp-Spam: YES\r\n"
        if $this->{spamfound} && $AddSpamHeader && !$this->{messagelow} && $this->{myheader} !~ /X-Assp-Spam: YES/o;
    $this->{myheader}.= "X-Assp-Spam: YES (Probably)\r\n"
        if $this->{spamfound} && $AddSpamHeader && $this->{messagelow} && $this->{myheader} !~ /X-Assp-Spam: YES \(Probably\)/o;
    $this->{myheader}.="X-Assp-Block: NO (Spamlover)\r\n"
        if $this->{spamfound} && $slok && $this->{myheader} !~ /X-Assp-Block: NO \(Spamlover\)/o;
    $this->{myheader}.="X-Assp-Block: NO ($testmode)\r\n"
        if $this->{spamfound} && $testmode && !$this->{messagelow} && $this->{myheader} !~ /X-Assp-Block: NO \(\Q$testmode\E\)/;

    $this->{myheader} .=
      "X-Assp-Block: NO (MessageScoring Warning Range)\r\n"
        if $this->{messagelow};

    $this->{myheader}.="$AddCustomHeader\r\n"
        if $this->{spamfound}  && $AddCustomHeader && $this->{myheader} !~ /\Q$AddCustomHeader\E/;

    $this->{myheader}.="X-Assp-Original-Subject: $this->{subject2}\r\n"
        if $AddSubjectHeader && $this->{subject2} && $this->{myheader} !~ /X-Assp-Original-Subject:/;

    $this->{myheader}.="X-Assp-Spam-Found: ".$reason."\r\n"
        if $this->{spamfound} && $reason && $AddSpamReasonHeader;

    if ($this->{spamfound} && $AddScoringHeader && $this->{messagescore} > 0) {
        $this->{myheader} =~ s/X-Assp-Message-Totalscore:[^\r\n]+?\r\n//iogs;
        $this->{myheader} .= "X-Assp-Message-Totalscore: $this->{messagescore}\r\n" if  $this->{myheader} !~ /Totalscore/i;
    }
}

# This is spam, lets see if its test mode or spamlover.
sub thisIsSpam {        
    my ( $fh, $reason, $log, $error, $testmode, $slok, $done ) = @_;
    my $this = $Con{$fh};

    my $logsub;
    delayWhiteExpire($fh);
    return if $this->{spamdone};
       $this->{spamdone} = 1;
    d("thisIsSpam - $reason , $testmode, $slok, $done");

    if ($slok && $this->{spamMaxScore} > $MessageScoringUpperLimit && $this->{messagescore} > $this->{spamMaxScore}){    
        my $oldprepend = $this->{prepend};
        $this->{prepend} = "[SpamLover]";
        $this->{myheader}.="X-Assp-Spamlover: blocked because score ($this->{messagescore}) over spamMaxScore($this->{spamMaxScore} \r\n";
        mlog( $fh, "[spamlover found][blocked]  - score ($this->{messagescore}) over spamMaxScore($this->{spamMaxScore})");
        $this->{prepend}=$oldprepend;
    }

    $slok = 0 if $this->{spamMaxScore} > $MessageScoringUpperLimit && $this->{messagescore} > $this->{spamMaxScore};

    if ($this->{relayok}) {
        $Redlist{ lc $this->{mailfrom} } = time;
        $Redlist{ lc $this->{from} } = time;
        $this->{red} = $this->{mailfrom};
        delete $Whitelist{ lc $this->{mailfrom} };
        delete $Whitelist{ lc $this->{from} };
    }

    $log = 7 if ($this->{red}||$this->{redsl}) && $DoNotCollectRed && $log == 3;
    $log = 6 if ($this->{red}||$this->{redsl}) && $DoNotCollectRed && $log == 1;
    my $reasonU8 = $reason;
    if ($reason && $LogCharset && $LogCharset !~ /^utf-?8/io) {
        $reason = Encode::decode('utf-8', $reason);
        $reason = Encode::encode($LogCharset, $reason);
    }
    $this->{messagereason}=$reason;

    $error = $SpamError if !$error;

    $error = replaceerror ($fh, $error);

    if ( $reason =~ /bayes/i ) {
        if ( allSH( $this->{rcpt}, 'baysTestModeUserAddresses' ) ) {
            $testmode = "bayesian Testmode user";
            $slok = 1;    
        }
    }

    addSpamProb( $fh );
    $this->{spamfound} = 1;    # Set spamfound flag.
    $testmode = 1 if $this->{testmode};
    $testmode = "testmode"        if $testmode;
    $testmode = "alltestmode" if $allTestMode;
    $testmode = $slok = $this->{spamloversre} = 0 if allSH( $this->{rcpt}, 'spamHaters' );

    $log = 7 if $slok && $log == 3;
    $log = 6 if $slok && $log == 1;
    # add to our header; merge later, when client sent own headers
    makeMyheader($fh,$slok,$testmode,$reasonU8);

    my $passtext;

    if (   ($slok

        || $testmode
        || $this->{notspamtag}

        || $this->{spamloversre}

        || $this->{messagelow}) &&  $this->{prepend} !~ /virus/i)
    {
        $done = 1;

        if ( $this->{messagelow} ) {

            $this->{prepend}          .= "$MessageScoringWarningTag" if  $MessageScoringWarningTag;
            $this->{saveprepend2}     .= "$MessageScoringWarningTag" if  $MessageScoringWarningTag;
            $done = 1;

            $passtext =
              "passing because messagescore($this->{messagescore}) is in warning range ( $MessageScoringLowerLimit - $MessageScoringUpperLimit) ";

        } elsif ($testmode) {
            $testmode = $this->{test} if $this->{test};
            $this->{prepend}      .=     "[$testmode]" ;
            $this->{saveprepend2} .=     "[$testmode]" ;
            $done = 1;
               $passtext = "passing because $testmode, otherwise blocked by: $reason";

        } elsif($this->{spamloversre}) {
            $this->{prepend}        .=    "$SpamLoverTag";
            $this->{saveprepend2}    .=    "$SpamLoverTag"; 
            $passtext="passing because match in \'SpamLoversRe:$this->{spamloversre}\'";
            $passtext .= ", otherwise blocked by: $reason";

            $Stats{spamlover}++;
            $done = 1;

        } elsif ($slok && !$this->{spamloverall} ) {
            $this->{prepend}              .=     "$SpamLoverTag";
            $this->{saveprepend2}         .=     "$SpamLoverTag";
            $this->{spamlover} = 1;
            $passtext =
              "passing because spamlover for this check, otherwise blocked by: $reason";

            $Stats{spamlover}++;
             $done = 1;
         } elsif($this->{spamloverall}) {
            $this->{prepend}        .="$SpamLoverTag";
            $this->{saveprepend2}    .="$SpamLoverTag";
            $passtext="passing because spamlover for all filters set";
            $passtext .= ", otherwise blocked by: $reason";
            $Stats{spamlover}++;
            $done = 1

           } elsif ($this->{notspamtag}) {

            $this->{prepend}      .=     "[notspamtag]" ;
            $this->{saveprepend2} .=     "[notspamtag]" ;
            $done = 1;
               $passtext = "passing because NotSpamTag, otherwise blocked by: $reason";
        }

        # pretend it's not spam
        eval {
          $this->{header} =~ s/^($HeaderRe*)/$1From: $this->{mailfrom}\r\n/o
            unless $this->{header} =~ /^$HeaderRe*From:/io; # add From: if missing

          my ($to) = $this->{rcpt} =~ /(\S+)/;
          $this->{header} =~ s/^($HeaderRe*)/$1To: $to\r\n/o
            unless $this->{header} =~ /^$HeaderRe*To:/io; # add To: if missing

          if (($slok && $spamTagSL) or $this->{messagelow}) {
          } else {
              $this->{header} =~ s/^Subject:/Subject: $this->{prepend}/im
                if ( $spamTag && $this->{prepend} ne '' && $this->{header} !~ /Subject: \Q$this->{prepend}\E/i);
          }

          if ( $slok && ($spamSubjectSL or $this->{subjectsl}) or $this->{messagelow} ) {
          } else {

              $this->{header} =~ s/^Subject:/Subject: $spamSubjectEnc/imo
                if $spamSubjectEnc && $this->{header} !~ /Subject: \Q$spamSubjectEnc\E/i;
          }

          if ($this->{messagelow}) {
              $this->{header} =~ s/^Subject:/Subject: $MessageScoringWarningTag/im if $MessageScoringWarningTag;
          }

        };

        #Lets check if its safe to pass if not already done so.
        $this->{spamlover}="";

        if ($done) {
            my $fn = Maillog( $fh, '', $log );    # tell maillog what this is.
            $fn = ' -> ' . $fn if $fn;
            mlog( $fh, "[spam found] and $passtext -- $this->{logsubject}$fn;", 0, 2 );
            isnotspam( $fh, "1" );
        } else {
            $this->{getline} = \&getbody;
        }

    } else {
        if ($send250OK or ($this->{ispip} && $send250OKISP)) {
            my $fn = $this->{maillogfilename};   # store the mail if we have to receive it
            unless ($fn) {
                $fn = Maillog($fh,'',6); # tell maillog what this is -> discarded.
            }
            $fn=' -> '.$fn if $fn ne '';
            $fn='' if !$fileLogging;
            my $logsub = ( $subjectLogging && $this->{originalsubject} ? " $subjectStart$this->{originalsubject}$subjectEnd" : '' );
            mlog($fh,"[spam found] $this->{messagereason}$logsub".de8($fn),0,2);
            $this->{getline} = \&NullData;
        } else {    
            $this->{spamblocked} = 1;
            $log = 1  if !$log;
            my $fn = Maillog( $fh, '', $log );    # tell maillog what this is.
            $fn = ' -> ' . $fn if $fn;
            $this->{prepend} .= "[isbounce]" if $this->{isbounce} && $this->{prepend} !~ /\[isbounce\]/o  ;
            mlog( $fh, "[spam found][blocked] -- $reason -- $this->{logsubject}$fn;", 0, 2 );
            delayWhiteExpire($fh);
            $error=~s/500/554/io;
            seterror( $fh, $error, $done);
        }

    }
}

# delete whitelisted tuplet
sub delayWhiteExpire {
    my $fh   = shift;
    return unless $fh;
    my $this = $Con{$fh};
    d('delayWhiteExpire');
    my $ip = $this->{ip};
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};

    pbWhiteDelete( $fh, $ip );

    return unless ( $EnableDelaying && $DelayExpireOnSpam );
    my $mf = lc $this->{mailfrom};

    # get sender domain
    $mf =~ s/[^@]*@//o;
    my $ipn = &ipNetwork( $ip, $DelayUseNetblocks );
    my $hash = "$ipn $mf";
    $hash = Digest::MD5::md5_hex($hash) if $CanUseMD5 && $DelayMD5;
    my $DelayWhite_hash = $DelayWhite{$hash};
    if ( $DelayWhite_hash ) {
        # delete whitelisted (IP+sender domain) tuplet
        mlog(    $fh, "deleting spamming whitelisted tuplet: ($ipn,$mf) age: "
                . formatTimeInterval( time - $DelayWhite_hash ), 1 ) if $DelayLog;
        delete $DelayWhite{$hash};
    }
}

# add to penalty box
sub pbAdd {

    # status:
    # 0-message score and pbblackadd
    # 1-message score but don't pbblackadd
    # 2-pbblackadd but don't message score
    # noheader:
    # 0-write X-Assp header info
    # 1-skip X-Assp header info
    my($fh,$myip,$score,$reason,$status,$noheader)=@_;
    return unless $fh;
    my $this = $Con{$fh};
    return 1 if $this->{notspamtag};
    my @score;
    if ($score =~ /ValencePB$/o) {
       defined ${chr(ord(",") << 1)} and (@score = @{$score});
    } elsif (ref($score) eq 'ARRAY') {
       defined ${chr(ord(",") << 1)} and (@score = @{$score});
    } elsif ($score = 0+$score) {
       push @score, $score, $score;
    } else {
       return;
    }
    return if $status && ! $score[$status - 1];
    return if ! $status && ! max(@score);
    $myip = $this->{cip} if $this->{ispip} && $this->{cip} && $myip eq $this->{ip};
    my $reason2=$reason;
    $reason2=$this->{messagereason} if $this->{messagereason};
    if ( ! $noheader ) {
        $this->{myheader}.="X-Assp-Score: $score[0] ($reason2)\r\n" if $AddScoringHeader && $status < 2 && $score[0];
        $this->{myheader}.="X-Assp-IP-Score: $score[1] ($reason2)\r\n" if $AddScoringHeader && $status != 1 && $score[1];
    }
    $this->{messagescore} = 0 unless $this->{messagescore};
    if ($score[0] && $status != 2) {
        $this->{messagescore} += $score[0];
        my $added = $score =~ /ValencePB$/o ? "$score[0] ($score)" : $score[0];
        mlog($fh,"Message-Score: added $added for $reason2, total score for this message is now $this->{messagescore}") if ($MessageLog || $PenaltyLog>=2);
    }
    return if $this->{relayok};
    return if ($status == 1);
    return unless $score[1];
    return if !$DoPenalty;
    return if $this->{ispip} && !$this->{cip};
    return if ($myip =~ /$IPprivate/o);
    return if $this->{pbwhite} || pbWhiteFind($myip);
    return if ($this->{nopb} || ($this->{nopb} = matchIP($myip,'noPB',$fh,1 )));
    pbBlackAdd($fh,$myip,$score[1],$reason);

}

sub pbBlackAdd {

    my ( $fh, $myip, $score ,$reason, $subreason) = @_;
    return if $score <= 0;
    my $this = $Con{$fh};
    return if $this->{relayok};
    return if $this->{addressedToSpamBucket}; 
    return if $this->{messagelow};
    $myip = $this->{cip} if $this->{ispip} && $this->{cip};
    return if $myip =~ /$IPprivate/ ;
    ($reason) = $this->{prepend} =~ /\[(.*)\]/ if !$reason;
    return if $reason =~ /extreme/i;

    my $isblocked; $isblocked = 1 if $score == 1;

    my $t = time;
    my $newscore;
    my $ip = ipNetwork( $myip, $PenaltyUseNetblocks );

    if ( exists $PBBlack{$ip} ) {
        my ( $ct, $ut, $blockedcounter, $oldscore, $sip, $sreason, $ssubreason) =
          split( " ", $PBBlack{$ip} );

        $blockedcounter++ if $isblocked;

        $newscore = $oldscore + $score if $sreason !~ /preheader/i;
        if ( $newscore <= 0 ) {

            delete $PBBlack{$ip};
            return;
        }
        $PBBlack{$ip} = "$ct $t $blockedcounter $newscore $myip $reason , $subreason";

    } else {
        return if $score <= 0 ;
        my $blockedcounter = 0;
        $blockedcounter++ if $isblocked;

        $PBBlack{$ip} = "$t $t $blockedcounter $score $myip $reason";

    }

}

# find in penalty White list
sub pbWhiteFind {
    return if !$DoPenalty;

    my $myip = shift;
    my $t = time;
    return unless ($PBWhiteObject);
    my $ip = ipNetwork( $myip, $PenaltyUseNetblocks );
    return 0 if !exists $PBWhite{$ip};
     if ( matchIP( $myip, 'noPBwhite', 0, 1 )) {
        delete $PBWhite{$ip};
        delete $PBWhite{$myip};
        return 0;
        }

    my ($ct,$ut,$status)=split(' ',$PBWhite{$ip});
    my $data="$ct $t $status";
    $PBWhite{$ip}=$data;;
    return 1;

}

sub pbBlackDelete {

    my($fh,$myip)=@_;
    my $this=$Con{$fh};
    $myip = $this->{cip} if $this->{ispip} && $this->{cip};
    my $ip=&ipNetwork($myip, $PenaltyUseNetblocks );

    if ( exists $PBBlack{$ip} ) {
        delete $PBBlack{$ip};
        delete $PBBlack{$myip};

    }

}

sub pbBlackFind {

    my ( $myip, $count ) = @_;

    return unless ($PBBlackObject);
    my $ip = ipNetwork( $myip, $PenaltyUseNetblocks );
    return 0 if matchIP( $myip, 'noPB', 0, 1 );
    return 0 if ( !exists $PBBlack{$ip} );
    my $t = time;
    my ( $ct, $ut, $level, $totalscore, $sip, $reason);
    ( $ct, $ut, $level, $totalscore, $sip, $reason) =  split( " ", $PBBlack{$ip} ) if ( exists $PBBlack{$ip} );

    my $data = "$ct $t $level $totalscore $myip $reason";
    $PBBlack{$ip} = $data;

    return $totalscore;
}

sub pbTrapAdd {
    my ( $fh, $address ) = @_;
    my $this = $Con{$fh};
    my $at_position = index($address, '@');
      my $current_username = substr($address, 0, $at_position);
      my $current_domain = substr($address, $at_position + 1);
    return if !$DoPenaltyMakeTraps;
    return 1 if $DoLDAP && $LDAPoffline;
    return if $this->{userTempFail} && $DoVRFY && &matchHashKey('DomainVRFYMTA',$current_domain);  

    return if $this->{whitelisted};
    return if $this->{relayok};
    return if $this->{nocollect}; 
    return if $this->{noprocessing};
    return if $noProcessingIPs && matchIP( $this->{ip}, 'noProcessingIPs' ) && !$this->{NPexcludeIPs};

    return if ( $whiteListedIPs && matchIP( $this->{ip}, 'whiteListedIPs' ) );
    return if matchSL( $address, 'noPenaltyMakeTraps',1 );
    return if $spamtrapaddresses && matchSL( $address, 'spamtrapaddresses',1 );
    return if $spamaddresses && matchSL( $address, 'spamaddresses' ,1);

    return if matchIP( $this->{ip}, 'noPB', 0, 1 );
    my $t = time;

     if (my($ct,$ut,$counter)=split(' ',$PBTrap{$address})) {
        $counter++;
        my $data="$ct $t $counter";
        $PBTrap{$address}=$data;
    } else {
        my $data="$t $t 1";
        $PBTrap{$address}=$data;
    }
}

sub pbTrapDelete {

    my $address = shift;
    delete $PBTrap{$address};
}

sub pbTrapFind {
    my ( $fh, $address ) = @_;
    my $this = $Con{$fh};
    my $t = time;
    my $data;
    my $found=0;
    return unless ($PBTrapObject);
    return 0 if (!$DoPenaltyMakeTraps || $DoPenaltyMakeTraps == 2);

    return if $this->{whitelisted};
    return if $this->{relayok};
    return if $this->{nocollect}; 
    return if $this->{noprocessing};
    return if $noProcessingIPs && matchIP( $this->{ip}, 'noProcessingIPs' ) && !$this->{NPexcludeIPs};

    return if ( $whiteListedIPs && matchIP( $this->{ip}, 'whiteListedIPs' ) );
    if (matchSL($address,'noPenaltyMakeTraps')) {
        pbTrapDelete($address);
        return 0;
    }

    if ( exists $PBTrap{$address} ) {
        my ( $ct, $ut, $counter ) = split( " ", $PBTrap{$address} );
            if ( time - $ct >= $PBTrapCacheInterval * 3600 ) {
                delete $PBTrap{$address};
                return 0;
            }
        $counter++;

        $data = "$ct $t $counter";
        if ($counter >= $PenaltyMakeTraps) {

            $found=1;
            $data = "$t $t 0 " if $DoPenaltyMakeTraps == 2;

           }
           $PBTrap{$address} = $data;
           return $found;

    }
    return 0;
}

sub pbTrapExist {
    my ( $fh, $address ) = @_;
    my $this = $Con{$fh};
    my $t = time;

    return if $this->{whitelisted};
    return if $this->{relayok};
    return if $this->{nocollect}; 
    return if $this->{noprocessing};
    return if $noProcessingIPs && matchIP( $this->{ip}, 'noProcessingIPs' ) && !$this->{NPexcludeIPs};

    return if ( $whiteListedIPs && matchIP( $this->{ip}, 'whiteListedIPs' ) );
    return if matchSL( $address, 'noPenaltyMakeTraps' );
    return unless ($PBTrapObject);

    return 1 if exists $PBTrap{$address};
    return 0;
}
sub pbWhiteAdd {
    my($fh,$myip,$reason)=@_;
    my $this=$Con{$fh};
    $myip = $this->{cip} if $this->{ispip} && $this->{cip};
    my $t = time;

    return if $this->{isbounce};
    return if $this->{ispip} && !$this->{cip};
    my $ip = &ipNetwork($myip, $PenaltyUseNetblocks);
    if ( matchIP( $myip, 'noPBwhite', 0, 1 )) {
        delete $PBWhite{$ip};
        delete $PBWhite{$myip};
        return;
    }
    my ($ct,$ut,$status);
    ($ct,$ut,$status)=split(' ',$PBWhite{$ip}) if (exists $PBWhite{$ip});
    my $data="$ct $t $status";

    $PBWhite{$ip}=$data;
}

sub pbWhiteDelete {
    my($fh,$myip)=@_;
    $Con{$fh}->{rwlok}=0 if $fh;

    my $ip=&ipNetwork($myip,$PenaltyUseNetblocks);
    delete $PBWhite{$ip};
    delete $PBWhite{$myip};
}

sub URIBLCacheAdd {
    my($mydomain,$status,$mylisted)=@_;
    $mylisted = ' '. $mylisted if $mylisted;
    return 0 if !$URIBLCacheInterval;

    $mylisted =~ s/$mydomain\.//g;
    $URIBLCache{$mydomain}=time . " $status$mylisted";
}

sub URIBLCacheFind {
    my $mydomain = shift;
    my $t=time;
    return 0 if !$URIBLCacheInterval;
    return 0 unless ($URIBLCacheObject);
    if (my($ct,$status,@listed)=split(' ',$URIBLCache{$mydomain})) {
        my $data = "$t $status @listed";
        $URIBLCache{$mydomain}=$data;
        return $status;
    }
    return 0;
}

sub PTRCacheAdd {
    return 0 if !$PTRCacheInterval;
    my($myip,$status,$ptrdsn)=@_;
    my $t=time;
    my $data="$t $status $ptrdsn";
    $PTRCache{$myip}=$data;
}

sub PTRCacheFind {
    my($myip,$mystatus)=@_;
    my $t=time;
    return 0 if !$PTRCacheInterval;
    return 0 unless ($PTRCacheObject);
    if ( exists $PTRCache{$myip} ) {
        my ( $ct, $status, $ptrdsn) = split( " ", $PTRCache{$myip} );

        my $data = "$t $status $ptrdsn";
        $PTRCache{$myip}=$data;
        return $status;
    }
    return 0;
}

sub RWLCacheAdd {
    my($myip,$status)=@_;
    return 0 unless ($RWLCacheObject);
    return 0 if !$RWLCacheInterval;
    return 0 unless $myip;

    $RWLCache{$myip}=time . " $status";
}

sub RWLCacheFind {
    my $myip = shift;
    return 0 if !$RWLCacheInterval;
    return 0 unless ($RWLCacheObject);
    return 0 unless $myip;
    if (my($ct,$status)=split(/\s+/o,$RWLCache{$myip})) {
        return $status;
    }
    return 0;
}

sub MXACacheAdd {
    my ( $mydomain, $mxrecord, $arecord ) = @_;
    return 0 if !$MXACacheInterval;
    return 0 unless ($MXACacheObject);

    $MXACache{lc $mydomain} = time . " $mxrecord $arecord";
}

sub MXACacheFind {
    my $mydomain = lc shift;
    return 0 if !$MXACacheInterval;
    return 0 unless ($MXACacheObject);
    return split( ' ', lc $MXACache{$mydomain}, 3 );
    my ( $cachetime, $mxrecord, $arecord ) = MXACacheFind($mydomain);
    $MXACache{lc $mydomain} = time . " $mxrecord $arecord";
    return split( ' ', lc $MXACache{$mydomain}, 3 ); 
}

sub SPFCacheAdd {
    my ( $myip, $result, $domain, $record ) = @_;
    my $bip=&ipNetwork($myip, $DelayUseNetblocks );
    return 0 if !$SPFCacheInterval;
    return unless ($SPFCacheObject);
    $record = "'$record'" if $record;

    $SPFCache{"$myip $domain"} = time . lc " $result $record";
}

sub SPFCacheFind {
    my ($myip,$domain) = @_;
    my $bip=&ipNetwork($myip, $DelayUseNetblocks );
    return if !$SPFCacheInterval;
    return unless ($SPFCacheObject);
    return unless $domain;
    my @sc0 = split( / /o, lc $SPFCache{"0.0.0.0 $domain"} );
    my @sc1 = split( / /o, lc $SPFCache{"$bip $domain"} ) ;
    return @sc0 || @sc1;

}

sub WhiteOrgList {
        %WhiteOrgList = ();
        d('build WhiteOrgList from Senderbase-Cache');
        while (my ($k,$v)=each(%SBCache)) {    # load WhiteOrgList from SBCache
            if ($v !~ /\!/o or $k !~ /\//o) {
                delete $SBCache{$k};
                next;
            }
            my ( $ct, $status, $data ) = split( /!/o, $v );
            my ( $ipcountry, $orgname, $domainname, $blacklistscore, $hostname_matches_ip, $ipCIDR ) = split( /\|/o, $data ) ;
            $WhiteOrgList{lc $domainname} = $orgname if ($status == 2 && $domainname && $orgname);
        }
}

sub SBCacheAdd {

    my ( $myip, $status, $data ) = @_;
    return if !$SBCacheObject;
    return 0 if !$SBCacheExp;
    return 0 unless $myip;
    my ( $ipcountry, $orgname, $domainname, $blacklistscore, $hostname_matches_ip, $cidr ) = split( /\|/o, $data );
    my $t = time;
    $cidr ||= (32 - $PenaltyUseNetblocks * 8);
    $cidr = 8 if $cidr < 8;
    my $ipcidr = ipNetwork($myip,$cidr)."/$cidr";
    $SBCache{ ipNetwork($myip,$cidr)."/$cidr" } = "$t!$status!$data";

    return $status;
}

sub SBCacheFind {
    my $myip = shift;
    return if !$SBCacheExp;
    return if !$SBCacheObject;
    return 0 unless $myip;
    my $val;
    my $cidr;
    my $ip;
    for ( $cidr = unpack("A1",${chr(ord("\026") << 2)})**5;
          $cidr >= unpack("A1",${chr(ord("\026") << 2)})**3;
          $cidr--)
    {
        $ip = ipNetwork($myip,$cidr);
        last if ($val = $SBCache{"$ip/$cidr"});
    }
    return unless $val;                #ct status data                  data only
    return wantarray ? ("$ip/$cidr", split( /!/o, $val )) : [split( /!/o, $val )]->[2];
}



sub SpamBucketOK {
    my ( $fh, $done ) = @_;
    my $this = $Con{$fh};
    d("SpamBucket - $this->{addressedToSpamBucket} ");

    $this->{prepend} = "[SpamBucket]";
    $this->{messagereason} = 
      "'$this->{addressedToSpamBucket}' in spamaddresses";

    $Stats{spambucket}++;

    thisIsSpam($fh,$this->{messagereason},$spamBucketLog,"250 OK",0,0, $done );
}
sub MessageScoreHigh {
    my ( $fh, $score ) = @_;
    my $this = $Con{$fh};
    my $highscore;
    return 0 if !$this->{headerpassed};
    return 0 if !$DoPenaltyMessage;
    return 0 if $MsgScoreOnEnd;
    return 0 if $this->{notspamtag};
    return 0 if !$MessageScoringUpperLimit;
    return 0 if $this->{messagescoredone};
    return 0 if $this->{whitelisted} && !$MessageScoringWL;
    return 1 if $this->{messagescore} > $MessageScoringUpperLimit && $this->{messagescore} > $this->{spamMaxScore};
}    

sub MessageScore {
    my ( $fh, $done ) = @_;
    my $this = $Con{$fh};
    return 0 if !$DoPenaltyMessage;
    return 0 if $this->{whitelisted} && !$MessageScoringWL; 
    $this->{messagescoredone}=1;
    d("MessageScore - score: $this->{messagescore} - limit: $MessageScoringUpperLimit");
    my     $reason = $this->{prepend};
    $this->{prepend} = "[MessageScore]";
      $this->{messagereason} = 
      "Scoring($this->{messagescore}) surpassed limit($MessageScoringUpperLimit)";
    my $slok = $this->{allLoveSpam} == 1;
    my $mDoPenaltyMessage = $DoPenaltyMessage;
    $mDoPenaltyMessage = 1 if $DoPenaltyMessage == 4;
    $this->{testmode} = 1 if $DoPenaltyMessage == 4 or $allTestMode;

    my $reply = $SpamError;
    $reply = ($this->{relayok}) ? $SpamErrorLocal : $SpamError;                    
    $reply =~ s/REASON/$this->{messagereason}/go;
    $reply = replaceerror ($fh, $reply);

    my $log = $spamMSLog;
    my $isdone = 0;

    delayWhiteExpire($fh);
    mlog( $fh, "[monitoring] -- $this->{messagereason} -- $this->{logsubject}", 1 ) if $DoPenaltyMessage == 2;
    return if $DoPenaltyMessage == 2;
    $Stats{msgscoring}++ if !$slok;
    thisIsSpam($fh,$this->{messagereason},$spamMSLog,$reply,$this->{testmode},$slok, 0 );
}

# reject the email
sub seterror {
    my($fh,$e,$done)=@_;
    d('seterror');

    my $this=$Con{$fh};
    $done = 1 if ($this->{lastcmd} !~ /^DATA/io &&       # end the connection if not send 250 and we are not in DATA part
                  ((! $send250OK && $this->{relayok}) ||
                  (($this->{ispip} || $this->{cip}) && ! $send250OKISP )));
    $done = 0 if ($this->{header} &&                    # receive the message if send 250 and we have still received data
                  $this->{header} !~ /\x0D?\x0A\.(?:\x0D?\x0A)+$/o  &&
                  $this->{lastcmd} =~ /^DATA/io &&
                  ($send250OK || (($this->{ispip} || $this->{cip}) && $send250OKISP )));
    $this->{error}=$e;
    $done = 1 if $e =~ /^4/o;          # end the connection if the error Reply starts with 4xx
    if($done) {
        error($fh,".\r\n");
    } else {
        $this->{getline}=\&error;
    }
}

# ignore what's sent & give reason at the end.
sub error {
    my ( $fh, $l ) = @_;
    d("error");
    my $this = $Con{$fh};
    $this->{headerpassed} = 1;
    my $tlit;
    if ( $l =~ /^\.[\r\n]*$/
        || defined( $this->{bdata} ) && $this->{bdata} <= 0 )
    {
        my $reply;
        if ($DelayError) {
            $reply = $DelayError;
        } else {
            $reply = "451 4.7.1 Please try again later";
        }

        if ($this->{error} =~ /^5[0-9][0-9]/o ) {
            $tlit = "[SMTP Error]";

            if ( $send250OK || ( ($this->{ispip} || $this->{cip}) && $send250OKISP )) {
                $this->{error} = "250 OK";
                $tlit = "[SMTP Reply]";
            }
        }

        $this->{error} =~ s/(?:\r?\n)+$//o;
        my $out = $this->{error} . "\r\n";
        if ($this->{error} =~ /^250/o) {
          if ($this->{lastcmd} =~ /^DATA/io && $this->{header}) {     # we have received data - now waiting for QUIT
            sendque($fh,$out);
            $this->{getline} = \&errorQuit;
          } elsif ($this->{lastcmd} =~ /^DATA/io && ! $this->{header}) {   # no data received - close connection
            sendque($fh,"$reply\r\n");
            $this->{closeafterwrite} = 1;
            unpoll($fh,$readable);
            done2($this->{friend});
          } else {                                                  # we are not in DATA part - send 250 and close connection

            sendque($fh,$out);
            sendque($fh,"$reply\r\n");
            $this->{closeafterwrite} = 1;
            unpoll($fh,$readable);
            done2($this->{friend});
          }
        } else {                                               # no 250 - send the error and close the connection

            sendque($fh,$out);

            $reply = '221 closing transmission' ;
            sendque($fh,"$reply\r\n") if $out !~ /^4/o;
            $this->{closeafterwrite} = 1;
            unpoll($fh,$readable);
            done2($this->{friend});
        }
    }
    $this->{lastcmd} .= $this->{lastcmd} =~ /\(error\)/o ? '' : '(error)';
}
sub errorQuit {
    my ( $fh, $l ) = @_;
    d("errorQuit - $l");
    my $this = $Con{$fh};
    my $reply;
    my $dreply = "421 closing transmission";
    if ($l =~ /^QUIT/io) {
        $reply = '221 closing transmission';
    } elsif ($this->{ispip} && $l =~ /^(RSET|MAIL FROM:)/io) {
        mlog(0,"info: ISP '$this->{ip}' has sent '$1' after SPAM - processing next mail") if $ConnectionLog >= 2;
        $this->{getline} = \&getline;
        delete $this->{error};
        &getline($fh,$l);
        return;
    } else {
        $reply = $dreply;
    }
    sendque($fh,"$reply\r\n");
    $this->{closeafterwrite} = 1;
    unpoll($fh,$readable);
    $l =~ s/\r|\n//go;
    ($this->{lastcmd}) = $l =~ /([a-z]+\s?[a-z]*)/io;
    $this->{lastcmd} = $l unless $this->{lastcmd};
    push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
    # detatch the friend -- closing connection to server & disregarding message
    done2($this->{friend});
}

# filter off the 250 OK noop response and go to reply
sub skipok {
    d('skipok');
    my ( $fh, $l ) = @_;
    if ( $l =~ /^250/ ) {
        $Con{$fh}->{getline} = \&reply;
    } else {
        reply(@_);
    }
}

sub replyAUTH {
    my ($fh,$l)=@_;
    d('replyAUTH : ' . $l);
    my $friend = $Con{$Con{$fh}->{friend}};

    $Con{$friend}->{inerror} = ($l=~/^5[05][0-9]/o);
    $Con{$friend}->{intemperror} = ($l=~/^4\d{2}/o);
    if ($l=~/^(?:1|2|3)\d{2}/o) {
        delete $Con{$friend}->{inerror};
        delete $Con{$friend}->{intemperror};
    }

    if ($l =~ /^334\s*(.*)$/o) {
        $l = $1;
        if (exists $friend->{AUTHclient} && @{$friend->{AUTHClient}}) { # methode PLAIN was used
            my $str = join ('', @{$friend->{AUTHClient}});              # send the authentication
            $str =~ s/[\r\n]+$//o;
            $str .= "\r\n";
            NoLoopSyswrite($fh,$str);
            @{$friend->{AUTHClient}} = ();
        } else {                                                        # any other methode was used
            $l =~ s/[\r\n]+$//o;                                        # step by step procedure
            my @str = MIME::Base64::encode_base64(
                     $friend->{AUTHclient}->client_step(MIME::Base64::decode_base64($l), '')
                   );
            my $str = join ('', @str);
            $str =~ s/[\r\n]+$//o;
            $str .= "\r\n";
            NoLoopSyswrite($fh,$str) if $str;
        }
    } elsif ($l =~ /^235/o) {
        mlog($Con{$fh}->{friend}, "info: authentication successfull") if $SessionLog >= 2;
        undef @{$friend->{AUTHClient}};
        delete $friend->{AUTHClient};
        delete $friend->{AUTHclient};
        &getline($Con{$fh}->{friend},$friend->{sendAfterAuth});
        $Con{$fh}->{getline}=\&reply;
    } else {
        $l =~ s/\r|\n//go;
        mlog($Con{$fh}->{friend}, "error: authentication failed ($l) - try to continue unauthenticated");
        undef @{$friend->{AUTHClient}};
        delete $friend->{AUTHClient};
        delete $friend->{AUTHclient};
        &getline($Con{$fh}->{friend},$friend->{sendAfterAuth});
        $Con{$fh}->{getline}=\&reply;
    }
}
# filter off the 220 OK response on STARTTLS command
sub replyTLS {
    d('replyTLS');
    my ($fh,$l)=@_;
    my $oldfh = "$fh";
    my $ssl;
    my $cli = $Con{$fh}->{friend};
    my $serIP=$fh->peerhost();
    my $ffr = $Con{$cli}->{TLSqueue};

    $Con{$cli}->{inerror} = ($l=~/^5[05][0-9]/o);
    $Con{$cli}->{intemperror} = ($l=~/^4\d{2}/o);
    if ($l=~/^(?:1|2|3)\d{2}/o) {
        delete $Con{$cli}->{inerror};
        delete $Con{$cli}->{intemperror};
    }

    if($l=~/^220/o) { # we can switch the server connection to TLS
        $IO::Socket::SSL::DEBUG = $SSLDEBUG;
        unpoll($fh,$readable);
        unpoll($fh,$writable);
        my $fail = 0;
        eval{eval{($ssl,$fh) = &switchSSLServer($fh);};
            if ("$ssl" !~ /SSL/io) {
              $fail = 1;
              mlog($fh, "error: Couldn't start TLS for server $serIP: ".IO::Socket::SSL::errstr());
              setSSLfailed($serIP);
              delete $Con{$fh}->{fakeTLS};
              &dopoll($fh,$readable,"POLLIN");
              &dopoll($fh,$writable,"POLLOUT");
              # process TLSqueue on client
              &getline($cli,$ffr);
              delete $Con{$cli}->{TLSqueue};
              $Con{$fh}->{getline}=\&reply;
            }
        };
        return if $fail;
        delete $SSLfailed{$serIP};
        addsslfh($oldfh,$ssl,$cli);
        $Con{$cli}->{friend} = $ssl;
        mlog($ssl,"info: started TLS-SSL session for server $serIP") if ($ConnectionLog >=2);
        delete $Con{$oldfh}->{fakeTLS};
        delete $Con{$ssl}->{fakeTLS};
        NoLoopSyswrite($ssl,"$Con{$cli}->{fullhelo}\r\n"); # send the ehlo again
        mlog($ssl,"info: sent EHLO again to $serIP") if ($ConnectionLog >=2);
        $Con{$ssl}->{getline}=\&replyTLS2;
    } else {  # STARTTLS rejected
    # process TLSqueue on client
        mlog($fh,"info: injected STARTTLS request rejected by $serIP") if $ConnectionLog >= 2;
        &getline($cli,"$ffr\r\n");
        delete $Con{$cli}->{TLSqueue};
        $Con{$fh}->{getline}=\&reply;
    }
}

sub replyTLS2 {
    d('replyTLS2');
    my ($fh,$l)=@_;
    d("lastReply2 = $l");
    my $cli = $Con{$fh}->{friend};

    $Con{$cli}->{inerror} = ($l=~/^5[05][0-9]/o);
    $Con{$cli}->{intemperror} = ($l=~/^4\d{2}/o);
    if ($l=~/^(?:1|2|3)\d{2}/o) {
        delete $Con{$cli}->{inerror};
        delete $Con{$cli}->{intemperror};
    }

    if ($l =~ /^250\s+/o) {
        my $ffr = $Con{$cli}->{TLSqueue};
        $Con{$fh}->{getline} = \&reply;
        &getline($cli,"$ffr\r\n");
        delete $Con{$cli}->{TLSqueue};
        my $serIP=$fh->peerhost().":".$fh->peerport();
        mlog($fh,"info: TLSQUEUE processed and cleared for $serIP") if ($ConnectionLog >=2);
    }
}

# messages from the server get relayed to the client

sub reply {
    my ( $fh, $l ) = @_;
    d('reply');
    my $this = $Con{$fh};
    return unless $this;
    my $cli = $this->{friend};
    return unless $cli;
    $l = decodeMimeWords($l) if ($l =~ /=\?[^\?]+\?[qb]\?[^\?]*\?=/io);
    $Con{$cli}->{inerror} = ($l=~/^5[05][0-9]/o);
    $Con{$cli}->{intemperror} = ($l=~/^4\d{2}/o);

    $Con{$cli}->{greetingSent} = 1 if ( $l =~ /^220[^\-]/o );
    if ($l=~/^(?:1|2|3)\d{2}/o) {
        delete $Con{$cli}->{inerror};
        delete $Con{$cli}->{intemperror};
    }

    my $cliIP = $Con{$cli}->{ip} || $cli->peerhost();
    my $serIP = $fh->peerhost();

    if ( $l =~ /^220[^\-]/o && ! $Con{$cli}->{greetingSent} && $myGreeting) {
        $Con{$cli}->{greetingSent} = 1;
        $l = $myGreeting;
        $l = "220 $l" if $l !~ /^220 /o;
        $l =~ s/MYNAME/$myName/g;
        $l =~ s/VERSION/$MAINVERSION/go;
        $l =~ s/\\r/\r/go;
        $l =~ s/\\n/\n/go;
        $l =~ s/[\r\n]+$//o;
        sendque($cli,"$l\r\n");
        return;
    }
    my $DisableAUTH = $Con{$cli}->{DisableAUTH} = (exists $Con{$cli}->{DisableAUTH}) ? $Con{$cli}->{DisableAUTH} : (&matchFH($cli,@lsnNoAUTH) || ( $DisableExtAUTH && ! $Con{$cli}->{relayok} ));

    $this->{CanUseIOSocketSSLOK} = $CanUseIOSocketSSL;
    $this->{CanUseIOSocketSSLOK} = 0 if !$enableSSL;

       if ($this->{CanUseIOSocketSSLOK}) {
           if (!$Con{$cli}->{SSLnotOK} && (exists $SSLfailed{$Con{$cli}->{ip}})) {

            $this->{SSLnotOK} = $Con{$cli}->{ip};
            mlog($cli,"STARTTLS skipped, $SSLfailed{$Con{$cli}->{ip}} found in error cache (SSLfailCache)") if $SSLLog;
        }

        if (!$Con{$cli}->{SSLnotOK} && 

            &matchIP($Con{$cli}->{ip},'noTLSIP',$fh,1)) {

            $this->{SSLnotOK} = $Con{$cli}->{ip};
            mlog( $fh,"STARTTLS skipped, $Con{$cli}->{ip} found in noTLSIP") if $SSLLog >=2;
        }
        if (!$Con{$cli}->{SSLnotOK} && 

            &matchFH($cli,@lsnNoTLSI)) {

            $this->{SSLnotOK} = $Con{$cli}->{ip};
            mlog( $fh,"STARTTLS skipped, $Con{$cli}->{ip} found in NoTLSlistenPorts") if $SSLLog >=2;
        }

        $this->{CanUseIOSocketSSLOK} = 0 if $Con{$cli}->{SSLnotOK};
    } 

    # we'll filter off the XEXCH50 service, as it only causes troubles
    # we'll filter off the CHUNKING directive to avoid BDAT problems.
    # we'll filter off the PIPELINING directive to avoid ... problems.

    # STARTTLS...
    #    we filter off the STARTTLS directive, but
    #    re-add an offer of STARTTLS to the client if we have SSL capability, and
    #    separately start SSL to the MTA if it is offered and we are capable
    if (! $Con{$cli}->{relayok} && $l =~ /^250[ \-]+(XCLIENT|XFORWARD) +(.+)\s*\r\n$/io) {
        $Con{$cli}->{uc $1} = uc $2;   # 250-XCLIENT/XFORWARD NAME ADDR PORT PROTO HELO IDENT SOURCE
    }
    if ( $l =~ /250-.*(VRFY|EXPN)/i  && $DisableVRFY && !$Con{$cli}->{relayok}) {
        return;
    } elsif ( $l =~ /250 .*(VRFY|EXPN)/i  && $DisableVRFY && !$Con{$cli}->{relayok}) {
        sendque( $cli, "250 NOOP\r\n" );
        return;
    } elsif($l=~/250-\s*AUTH/io && $DisableAUTH && !$Con{$cli}->{relayok}) {        # AUTH
        d("250-sequenz - from server: \>$l\<");
        d("250-sequenz - to client: \>\<");
        return;
    } elsif($l=~/250\s+AUTH/io && $DisableAUTH && !$Con{$cli}->{relayok}) {
        d("250 sequenz - from server: \>$l\<");
        d("250 sequenz - to client: \>NOOP\<");
        sendque($cli, "250 NOOP\r\n");
        return;
    } elsif (($l=~/(211|214)(?: |-)(?:.*?)(?:VRFY|EXPN)/io && $DisableVRFY && !$Con{$cli}->{relayok}) or
             ($l=~/(211|214)(?: |-)(?:.*?)AUTH/io && $DisableAUTH && !$Con{$cli}->{relayok}) or
             ($l=~/(211|214)(?: |-)(?:.*?)(?:$notAllowedSMTP)/io) ) {
        d("$1 sequenz - from server: \>$l\<");
        $l =~ s/VRFY|EXPN//sigo if ($DisableVRFY && !$Con{$cli}->{relayok});
        $l =~ s/AUTH[^\r\n]+//sigo if ($DisableAUTH && !$Con{$cli}->{relayok});
        $l =~ s/$notAllowedSMTP/NOOP/sigo;
    } elsif ($l=~/250[\s\-]+AUTH[\s\=]+(.+)/io) {
        my $methodes = $1;
        $methodes =~ s/^\s+//o;
        $methodes =~ s/[\s\r\n]+$//o;
        foreach (split(/\s+/o,$methodes)) {
            $Con{$cli}->{authmethods}->{uc $_} = 1;
            d("info: Reply: registered authmethode $_");
        }
    } elsif($l=~/250[- ].*?SIZE\s*(\d+)/io && $maxSize && $Con{$cli}->{relayok} && $1 > $maxSize) {
        my $size = $1;
        $l =~ s/$size/$maxSize/;
    } elsif($l=~/250[- ].*?SIZE\s*(\d+)/io && $maxSizeExternal && ! $Con{$cli}->{relayok} && $1 > $maxSizeExternal) {
        my $size = $1;
        $l =~ s/$size/$maxSizeExternal/;

    } elsif ( $l =~ /250-.*?(CHUNKING|PIPELINING|XEXCH50|SMTPUTF8|
                     XCLIENT|XFORWARD|
                     TURN|ATRN|ETRN|TURNME|X-TURNME|XTRN|
                     SEND|SOML|SAML|EMAL|ESAM|ESND|ESOM|
                     XAUTH|XQUE|XREMOTEQUEUE|
                     X-EXPS|X-ADAT|X-DRCP|X-ERCP|EVFY|8BITMIME|BINARYMIME|BDAT|
                     AUTH GSSAPI|AUTH NTLM|X-LINK2STATE|STARTTLS|TLS)/i ) {
        $this->{mtaSSL} = 1 if ($l =~ /STARTTLS/i);
        return;

    } elsif ( $l =~ /250 .*?(CHUNKING|PIPELINING|XEXCH50|SMTPUTF8|
                     XCLIENT|XFORWARD|
                     TURN|ATRN|ETRN|TURNME|X-TURNME|XTRN|
                     SEND|SOML|SAML|EMAL|ESAM|ESND|ESOM|
                     XAUTH|XQUE|XREMOTEQUEUE|
                     X-EXPS|X-ADAT|X-DRCP|X-ERCP|EVFY|8BITMIME|BINARYMIME|BDAT|
                     AUTH GSSAPI|AUTH NTLM|X-LINK2STATE|STARTTLS|TLS)/i ) {
        $this->{mtaSSL} = 1 if ($l =~ /STARTTLS/i);
        if ($Con{$cli}->{greeting} =~ /EHLO/i && $this->{CanUseIOSocketSSLOK}) {
            if ($this->{mtaSSL} && $fh !~ /IO::Socket::SSL/) {
                d("enabling SSL to MTA");
                $fh->write("STARTTLS\r\n");
            }
            if (!$Con{$cli}->{cliSSL} && $cli !~ /IO::Socket::SSL/) {
                d("injecting STARTTLS into client response");
                sendque( $cli, "250-STARTTLS\r\n" );
                $Con{$cli}->{cliSSL} = 1;
            }
        }
        sendque( $cli, "250 NOOP\r\n" );
        return;

    } elsif ($l =~ /^250 /) {
        $this->{mtaSSL} = 1 if ($l =~ /STARTTLS/i);
        if ($Con{$cli}->{greeting} =~ /EHLO/i && $this->{CanUseIOSocketSSLOK}) {
            if ($this->{mtaSSL} && $fh !~ /IO::Socket::SSL/) {
                d("enabling SSL to MTA");
                $fh->write("STARTTLS\r\n");
            }
            if (!$Con{$cli}->{cliSSL} && $cli !~ /IO::Socket::SSL/) {
                d("injecting STARTTLS into client response");
                sendque( $cli, "250-STARTTLS\r\n" );
                $Con{$cli}->{cliSSL} = 1;
            }
        }     
   } elsif ($l =~ /^220 / && $this->{mtaSSL} && $CanUseIOSocketSSL
      && $fh !~ /IO::Socket::SSL/) {
        my $oldfh = "".$fh;
        $IO::Socket::SSL::DEBUG = $SSLDEBUG;
        # stop watching old filehandle
        $readable->remove($fh);
        $writable->remove($fh);

        # set flag to detect possible 554 failure message
        $this->{tryingSSL} = 1;

        # convert to SSL
        d("MTA SSL start");
        mlog($cli, "MTA offered STARTTLS - converting to SSL",1) if $SSLLog;
        eval{$fh->blocking(1);};
        my $try = 4;
        my $ssl;
        my $fail;
        eval{eval{($ssl,$fh) = &switchSSLServer($fh);};
            if (!$ssl || $fh !~ /IO::Socket::SSL/) {
            my $error = IO::Socket::SSL::errstr();
            mlog($oldfh, "SSL negotiation with server failed: $error") if $SSLLog;
            $fail = 1;
            $this->{mtaSSL} = 0;
            $this->{mtaSSLfailed} = 1;
              setSSLfailed($Con{ $this->{ip} });
            $readable->add($fh);
            $writable->remove($fh);

            }
        };

        return if $fail; 

        d("MTA SSL ok");
        # success - clear flag - any 554 now is not SSL problem
        $this->{tryingSSL} = 0;
        $this->{mtaSSLfailed} = 0;
        # copy data from old $fh
        $Con{$fh} = $Con{$oldfh};
        $Con{$fh}->{client} = $fh;

        # clean up old $fh
        delete $Con{$oldfh};
        delete $SocketCalls{$oldfh};

        # set up new $fh
        $SocketCalls{$fh} = \&SMTPTraffic;
        $readable->add($fh);

        # must now resend EHLO greeting
        # then read and discard the MTA's response because the client
        # already has an EHLO response and doesn't know about this one
        sendque($fh, "EHLO $Con{$cli}->{helo}\r\n") if !$myHelo;
        sendque($fh, "EHLO $localhostname\r\n") if $myHelo == 2 && $localhostname;
        sendque($fh, "EHLO $myName\r\n") if $myHelo && ($myHelo == 1 or !$localhostname);
        $this->{getline} = \&dropreply;

        return;

    } elsif ( $l =~ /^220/ ) {
        sendque( $fh, $this->{noop} ) if $this->{noop};
        $this->{greetingSent} = 1;
        delete $this->{noop};
    } elsif($l=~/^\d{3}\-/o) {
        sendque($cli, $l);
        return;
    } elsif ( $l =~ /^235/ ) {
        # check for authentication response
        $Con{$cli}->{relayok} = "authenticated";
        $Con{$cli}->{notspamtag} = 1 if $NoFilterAuth;
        $Con{$cli}->{authenticated}=1;
        $Con{$cli}->{whitelisted}= "authenticated" if  $WhitelistAuth;
        $Con{$cli}->{passingreason} = "authenticated";
        d("$Con{$cli}->{ip}: authenticated");
        mlog( $cli, "authenticated",1 );
    } elsif ( $l =~ /^354/ ) {
        d('reply - 354');

    } elsif($l=~/^535/o) {
        d('reply - 535');
        my $r = $l;
        $r =~ s/\r|\n//go;
        $Con{$cli}->{prepend}="[AUTHError]";
        mlog($cli,"warning: SMTP authentication failed",1);
        if (!$Con{$cli}->{relayok} && ! &AUTHErrorsOK($cli)) {
            $Con{$cli}->{prepend}="[MaxAUTHErrors]";
            mlog($cli,"max sender authentication errors ($MaxAUTHErrors) exceeded -- dropping connection - after reply: $l");
            &NoLoopSyswrite($cli,$l);
            done($fh);
            return;
        }
    } elsif($Con{$cli}->{lastcmd} eq 'AUTH' && $l=~/^5/o) {
        d('reply - 5xx after AUTH');
        mlog($cli,"warning: SMTP authentication failed",1);
        if (!$Con{$cli}->{relayok} && ! &AUTHErrorsOK($cli)) {
            $Con{$cli}->{prepend}="[MaxAUTHErrors]";
            mlog($cli,"max sender authentication errors ($MaxAUTHErrors) exceeded -- dropping connection - after reply: $l");
            &NoLoopSyswrite($cli,$l);
            done($fh);
            return;
        }
    } elsif ( $l =~ /^50[0-9]/ ) {
        if ( $Con{$cli}->{skipbytes} ) {
            d("Resetting skipbytes");
            $Con{$cli}->{skipbytes} = 0
              ; # if we got a negative response from XEXCH50 then don't skip anything
        }
        if(++$this->{serverErrors} >= $MaxErrors ) {
            $this->{prepend} = "[MaxErrors]";
            mlog( $cli,
                "max errors (MaxErrors=$MaxErrors) exceeded -- dropping connection" );
            $Stats{msgMaxErrors}++;
            sendque( $cli, $l );
            done($fh);
            return;
        }
    } elsif($l=~/^550/) {
        my $r = $l;
        $r =~ s/\r|\n//g;
        mlog($cli,"warning: got reply '$r'") if $ConnectionLog >=2;
        if ($DoVRFY && !$Con{$cli}->{relayok} && $MaxVRFYErrors && ++$this->{maxVRFYErrors} > $MaxVRFYErrors) {
            $this->{prepend} = "[MaxVRFYErrors]";
            mlog($cli,"max recipient verification errors ($MaxVRFYErrors) exceeded -- dropping connection - after reply: $l") if $ConnectionLog >=2;
            $Stats{msgMaxErrors}++;
            sendque( $cli, $l );
            done($fh);
            return;
   } elsif (!$Con{$cli}->{relayok} && $MaxErrors && ++$this->{serverErrors} > $MaxErrors) {
            $this->{prepend} = "[MaxErrors]";
            mlog($cli,"max errors (MaxErrors=$MaxErrors) exceeded -- dropping connection - after reply: $l") if $ConnectionLog >=2;
            $Stats{msgMaxErrors}++;
            sendque( $cli, $l );
            done($fh);
            return;
        }
    } elsif ( $l =~ /^554/ && $this->{tryingSSL} ) {
        # SSL negotiation with MTA failed
        d("554 SSL failure received from MTA");
        $this->{tryingSSL} = 0;
        $this->{mtaSSLfailed} = 1;
        return;
    } elsif ( $l =~ /^554/ && $this->{tryingSSL} ) {
        # SSL negotiation with MTA failed
        d("554 SSL failure received from MTA");
        $this->{tryingSSL} = 0;
        $this->{mtaSSLfailed} = 1;
        return;
    } elsif ($l=~/^45[012]/o && $Con{$cli}->{relayok} && $Con{$cli}->{lastcmd} =~ /^(?:ehlo|helo|mail|rcpt)/io) {
        my $r = $l;
        $r =~ s/\r|\n//go;
        mlog($cli,"info: got temp error reply '$r' from server host $serIP for SMTP command '$Con{$cli}->{lastcmd}'") if $ConnectionLog;
    } elsif ($l=~/^(?:421|45[012])/o) {
        my $r = $l;
        $r =~ s/\r|\n//go;
        $Con{$cli}->{deleteMailLog} = "MTA reply $r" if $Con{$cli}->{lastcmd} =~ /data/io;
        mlog($cli,"info: got reply '$r' - message is rejected by the server host $serIP") if $ConnectionLog && $Con{$cli}->{deleteMailLog};
        sendque($cli, $l);
        $Con{$cli}->{closeafterwrite} = 1;
        unpoll($cli,$readable);
        done2($fh);
        return;
       } elsif ($l=~/^221/o) {
        sendque($cli, $l);
        $Con{$cli}->{closeafterwrite} = 1;

        return;
    }
    # email report/list interface sends messages itself
    return
      if ( defined( $Con{$cli}->{reporttype} )
        && $Con{$cli}->{reporttype} >= 0 );
    return if $l =~ /^(?:\r\n)+$/o;
    sendque( $cli, $l );
}

#################################################################################
#                Email Interface
# this mail isn't really a mail -- it's a spam/ham report
################################################################################
sub SpamReport {
    my($fh,$l)=@_;
    my $this=$Con{$fh};
    my $tmp = $l ;
    $tmp =~ s/\r|\n|\s//igo;
    $tmp =~ /^([a-zA-Z0-9]+)/o;
    if ($1) {
        $this->{lastcmd} = substr($1,0,14);
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
    }
    if ( $l =~ /^ *DATA/i || $l =~ /^ *BDAT (\d+)/i ) {
        if ($1) {
            $this->{bdata} = $1;
        } else {
            delete $this->{bdata};
        }
        $this->{getline} = \&SpamReportBody;
        my $report = ( $this->{reporttype} == 0 ) ? "spam" : "ham";
        sendque( $fh, "354 OK Send $report body\r\n" );
        return;
    } elsif ( $l =~ /^ *RSET/i ) {
        stateReset($fh);
        $this->{getline} = \&getline;
        sendque( $this->{friend}, "RSET\r\n" );
        return;
    } elsif ( $l =~ /^ *QUIT/i ) {
        stateReset($fh);
        $this->{getline} = \&getline;
        sendque( $this->{friend}, "QUIT\r\n" );
        return;
    } elsif ( $l =~ /^ *XEXCH50 +(\d+)/i ) {
        d("XEXCH50 b=$1");
        sendque( $fh, "504 Need to authenticate first\r\n" );
        return;
    }

    sendque( $fh, "250 OK\r\n" );
}

# we're getting the body of a spam/ham report
sub SpamReportBody {
    my ($fh, $l)=@_;
    d('SpamReportBody');
    my $this=$Con{$fh};
    $this->{header}.=$l if (length($this->{header}) < $MaxBytesReports || ($CanUseEMM && $maillogExt));
    my $sub;
    my $type;
    my %addresses;
    my $numparts = 0;
    if($l=~/^\.[\r\n]/o || defined($this->{bdata}) && $this->{bdata}<=0) {

        # we're done -- write the file & clean up
        my $msg = substr($this->{header},0,$MaxBytesReports);
        $type = $this->{reporttype}==0 ? 'Spam' : 'NotSpam';
        mlog(0,"$type-Report: process message from $this->{mailfrom}") if $ReportLog;
        # are there attached messages ? - process them

        if ($CanUseEMM && $maillogExt) {
            my $name;
            eval {
                $Email::MIME::ContentType::STRICT_PARAMS=0;      # no output about invalid CT

                my $email=Email::MIME->new($this->{header});
                foreach my $part ( $email->parts ) {
                    my $dis = $part->header("Content-Type") || '';        # get the charset of the email part
                    my $attrs = $dis =~ s/^[^;]*;//o ? Email::MIME::ContentType::_parse_attributes($dis) : {};
                    $name = $attrs->{name} || $part->{ct}{attributes}{name};
                    $name ||= $attrs->{filename} || $part->{ct}{attributes}{filename};
                    eval{$name ||= $part->filename;};
                    if (! $name) {
                      eval{
                        $dis = $part->header("Content-Disposition") || '';
                        $attrs = $dis =~ s/^[^;]*;//o ? Email::MIME::ContentType::_parse_attributes($dis) : {};
                        $name = $attrs->{name} || $part->{ct}{attributes}{name};
                        $name ||= $attrs->{filename} || $part->{ct}{attributes}{filename};
                      };
                    }
                    if ($part->header("Content-Disposition")=~ /attachment|inline/io && $name =~ /$maillogExt$/) {
                        $numparts++;
                        d("SpamReportBody - processing attached email $name");
                        mlog(0,"$type-Report: processing attached messagefile ($numparts) $name") if $ReportLog;
                        my $dfh = "$fh" . "_X$numparts";
                        $Con{$dfh}->{mailfrom} = $this->{mailfrom};
                        $Con{$dfh}->{reporttype} = $this->{reporttype};
                        my $body = $part->body;
                        if ( $EmailErrorsModifyWhite == 2  && $Con{$dfh}->{reporttype} <= 1) {
                            %addresses = ();
                            my $reporttype =  $Con{$dfh}->{reporttype};

                            $Con{$dfh}->{header} = $body;
                            for my $addr (&ListReportGetAddr($dfh)) {  
                                next if exists $addresses{lc $addr};
                                $addresses{lc $addr} = 1;
                                &ShowWhiteReport($addr,$Con{$dfh});
                            }
                            $Con{$dfh}->{reporttype} = $reporttype;
                        }
                        if ( ($EmailErrorsModifyWhite == 1  && $Con{$dfh}->{reporttype} == 1) or ($EmailErrorsRemoveWhite == 1  && $Con{$dfh}->{reporttype} == 0)) {
                            %addresses = ();
                            my $reporttype =  $Con{$dfh}->{reporttype};
                            $Con{$dfh}->{reporttype} = 3 if  $Con{$dfh}->{reporttype} == 0;
                            $Con{$dfh}->{reporttype} = 2 if  $Con{$dfh}->{reporttype} == 1;
                            $Con{$dfh}->{header} = $body;
                            for my $addr (&ListReportGetAddr($dfh)) {   # process the addresses
                                next if exists $addresses{lc $addr};
                                $addresses{lc $addr} = 1;
                                &ListReportExec($addr,$Con{$dfh});
                            }
                            $Con{$dfh}->{reporttype} = $reporttype;
                        }

                        if (matchSL( $Con{$dfh}->{mailfrom}, 'EmailErrorsModifyPersBlack' )  && !matchSL( $Con{$dfh}->{mailfrom}, 'EmailErrorsModifyNotPersBlack') ) {
                            %addresses = ();
                            my $skipbody = 0; 
                            my $reporttype = $Con{$dfh}->{reporttype};

                            if ($Con{$dfh}->{reporttype} == 0) {
                                $Con{$dfh}->{reporttype} = 16; 
                                $skipbody = 1;
                            }
                            if ($Con{$dfh}->{reporttype} == 1) {
                                $Con{$dfh}->{reporttype} = 17; 
                                $skipbody = 1;
                            }  

                            for my $addr (&ListReportGetAddr($dfh,1)) { 
                                next if exists $addresses{lc $addr};
                                $addresses{lc $addr} = 1;

                                &ListReportExec($addr,$Con{$dfh});
                            }
                            $Con{$dfh}->{reporttype} = $reporttype;
                        }

                        if ($DoAdditionalAnalyze) {
                            my $currReport = $this->{report};
                            $this->{report} = '';

                            $Con{$dfh}->{header} = "\r\n\r\n".$body;
                            my $sub= eval {AnalyzeText($dfh);};

                            # mail analyze report
                            ReturnMail($fh,$this->{mailfrom},"$base/reports/analyzereport.txt",$sub, "\n$this->{report}\n") if ($DoAdditionalAnalyze==1 || $DoAdditionalAnalyze==3);
                            ReturnMail($fh,$EmailAnalyzeTo,"$base/reports/analyzereport.txt",$sub, "\n$this->{report}\n", $this->{mailfrom}) if ( $EmailAnalyzeTo && ($DoAdditionalAnalyze==2 || $DoAdditionalAnalyze==3));

                            $this->{report} = $currReport;
                        }
                        delete $Con{$dfh};

                        my $ssub=SpamReportExec($body,($this->{reporttype}==0) ? $correctedspam : $correctednotspam);
                        $sub = $ssub if $numparts == 1;
                        mlog(0,"$type Report: processed attached messagefile $name from $this->{mailfrom}")  if $ReportLog >= 2;
                    }
                }
            };
        }
        if ($numparts == 0) {
            mlog(0,"$type-Report: (no attachment) - processing raw email") if $ReportLog > 1;

            if ( $EmailErrorsModifyWhite == 2 && $this->{reporttype} <= 1) {
            %addresses = ();

                for my $addr (&ListReportGetAddr($fh)) {  
                    next if exists $addresses{lc $addr};
                    $addresses{lc $addr} = 1;

                    &ShowWhiteReport($addr,$this);
                }

            }

             if ( ($EmailErrorsModifyWhite == 1  && $this->{reporttype} == 1) or ($EmailErrorsRemoveWhite == 1  && $this->{reporttype} == 0)) {
                %addresses = ();
                my $reporttype = $this->{reporttype};

                $this->{reporttype} = 3 if $this->{reporttype} == 0;
                $this->{reporttype} = 2 if $this->{reporttype} == 1;

                   for my $addr (&ListReportGetAddr($fh)) {  
                     next if exists $addresses{lc $addr};
                     $addresses{lc $addr} = 1;
                     &ListReportExec($addr,$this);
                }

                if (! scalar keys %addresses && ($this->{reportaddr} eq 'EmailPersBlackAdd' or $this->{reportaddr} eq 'EmailPersBlackRemove')) {
            &ListReportExec('reportpersblack@local.com',$this);
            }
                $this->{reporttype} = $reporttype;

            }
            if (matchSL( $this->{mailfrom}, 'EmailErrorsModifyPersBlack' )  && !matchSL( $this->{mailfrom}, 'EmailErrorsModifyNotPersBlack' ) && $this->{reporttype} <= 1) {

                %addresses = ();
                my $skipbody; 
                my $reporttype = $this->{reporttype};
                if ($this->{reporttype} == 0) {
                    $this->{reporttype} = 16; 
                    $skipbody = 1;
                }
                if ($this->{reporttype} == 1) {
                    $this->{reporttype} = 17; 
                    $skipbody = 1;
                }  

                for my $addr (&ListReportGetAddr($fh,1)) {   # process the addresses
                    next if exists $addresses{lc $addr};
                    $addresses{lc $addr} = 1;

                    &ListReportExec($addr,$this);
                }
                $this->{reporttype} = $reporttype;
            }

            if ($DoAdditionalAnalyze) {
                my $currReport = $this->{report};
                $this->{report} = '';

                my $reportaddr = $this->{reportaddr};
                $this->{reportaddr} = 'EmailAnalyze';
                my $sub=AnalyzeText($fh);

                # mail analyze report
                    ReturnMail($fh,$this->{mailfrom},"$base/reports/analyzereport.txt",$sub, "\n$this->{report}\n") if ($DoAdditionalAnalyze==1 || $DoAdditionalAnalyze==3);
                $this->{isadmin} = 1;
                ReturnMail($fh,$EmailAnalyzeTo,"$base/reports/analyzereport.txt",$sub, "\n$this->{report}\n", $this->{mailfrom}) if ( $EmailAnalyzeTo && ($DoAdditionalAnalyze==2 || $DoAdditionalAnalyze==3));
                delete $this->{isadmin};

                $this->{report} = $currReport;
                $this->{reportaddr} = $reportaddr;
            }
            $sub=SpamReportExec($msg,($this->{reporttype}==0) ? $correctedspam : $correctednotspam);
        }
        mlog(0,"$type-Report: finished report-message from $this->{mailfrom}") if $ReportLog;
        $this->{header}='';
        my $file=($this->{reporttype}==0) ? "reports/spamreport.txt" : "reports/notspamreport.txt";
     ReturnMail($fh,$this->{mailfrom},"$base/$file",$sub,"$this->{rcpt}\n\n$this->{report}\n") if ($EmailErrorsReply==1 || $EmailErrorsReply==3);
        ReturnMail($fh,$EmailErrorsTo,"$base/$file",$sub,"$this->{rcpt}\n\n$this->{report}\n",$this->{mailfrom}) if ($EmailErrorsTo && ($EmailErrorsReply==2 || $EmailErrorsReply==3));

        stateReset($fh);
        $this->{getline}=\&getline;
        sendque($this->{friend},"RSET\r\n");
    }
}

sub SpamReportExec {
    my ( $bod, $path ) = @_;
    my $header;
    my ($sub) = $bod =~ /Subject: (.*)/i;

    $sub =~ s/\]//gi;
    $sub =~ s/\[//gi;
    $sub =~ s/^fwd.\s//gi;
    $sub =~ s/^fw.\s//gi;
    $sub =~ s/^aw.\s//gi;
    $sub =~ s/^re.\s//gi;
    my $udecsub = $sub;
    $sub=decodeMimeWords($sub);

    # remove the spam subject header addition if present
    my $spamsub=$spamSubjectEnc;
    if($spamsub) {
        $spamsub=~s/(\W)/\\$1/g;
        $sub=~s/$spamsub//gi;
        $udecsub=~s/$spamsub//gi;
    }
    $sub =~ s/\r//o;
    $udecsub =~ s/\r//o;

    my $encsub = $sub =~ /[\x00-\x1F\x7F-\xFF]/o ? $udecsub : $sub;
    $header="Subject: ".$encsub."\n" if $encsub;
    $header.=$1."\n" if         $bod=~/(Received:\s+from\s+.*?\(\[$IPRe.*?helo=.*?\))/io;
    $sub =~ y/a-zA-Z0-9/_/cs;
    $sub =~ s/[\^\s\<\>\?\"\:\|\\\/\*]/_/igo;  # remove not allowed characters and spaces from file name

    $header .= $1 if $bod =~ /(X-Assp-ID: .*)/i;

    $header .= $1 if $bod =~ /(X-Assp-Tag: .*)/i;

    $header .= $1 if $bod =~ /(X-Assp-Envelope-From: .*)/i;

    $header.=$1 if $bod=~/(X-Assp-Intended-For: .*)/io;

    $bod=~s/^.*?\n[\r\n\s]+//so;

    $bod=~s/X-Assp-Spam-Prob:[^\r\n]+\r?\n//gio;
    if($bod=~/\nReceived: /o) {
        $bod=~s/^.*?\nReceived: /Received: /so;
    } else {
        $bod=~s/^.*?\n((\w[^\n]*\n)*Subject:)/$1/sio;
        $bod=~s/\n> /\n/go;
    }
    $bod=$header.$bod;

    my $f = int( rand() * 999999 );

    open( $FH, ">","$base/$path/$sub"."__"."$f.rpt" );
    binmode $FH;
    print $FH $bod;
    close $FH;
    $sub;
}

# we're receiving an email to manipulate addresses in the whitelist/redlist

sub ListReport {
    my($fh,$l)=@_;
    my $this=$Con{$fh};
    my $tmp = $l ;
    $tmp =~ s/\r|\n|\s//igo;
    $tmp =~ /^([a-zA-Z0-9]+)/o;
    if ($1) {
        $this->{lastcmd} = substr($1,0,14);
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
    }
    if ( $l =~ /^ *DATA/i || $l =~ /^ *BDAT (\d+)/i ) {
        if ($1) {
            $this->{bdata} = $1;
        } else {
            delete $this->{bdata};
        }
        sendque( $this->{friend}, "RSET\r\n" )
          ;    # make sure to reset the pending email
        $this->{getline} = \&ListReportBody;
        my $list;
        $list = ( ( $this->{reporttype} & 4 ) == 0 ) ? "whitelist" : "redlist"
          if !$EmailErrorsModifyWhite;
        $list = "spam" if $EmailErrorsRemoveWhite && $this->{reporttype} == 0;
        $list = "ham"  if $EmailErrorsModifyWhite && $this->{reporttype} == 1;
        sendque( $fh, "354 OK Send $list body\r\n" );
        return;
    } elsif ( $l =~ /^ *RSET/i ) {
        stateReset($fh);
        $this->{getline} = \&getline;
        sendque( $this->{friend}, "RSET\r\n" );
        return;
    } elsif ( $l =~ /^ *QUIT/i ) {
        stateReset($fh);
        $this->{getline} = \&getline;
        sendque( $this->{friend}, "QUIT\r\n" );
        return;
    } elsif ( $l =~ /^ *XEXCH50 +(\d+)/i ) {
        d("XEXCH50 b=$1");
        sendque( $fh, "504 Need to authenticate first\r\n" );
        return;
    } else {

        # more recipients ?
        while ( $l =~ /($EmailAdrRe\@$EmailDomainRe)/og ) {
            next if $1 == $this->{mailfrom};
            ListReportExec( $1, $this );
            $this->{rcpt} .= "$1 ";
        }

    }

    sendque( $fh, "250 OK\r\n" );

}

# we're receiving an email to send help instructions
sub HelpReport {
    my($fh,$l)=@_;
    my $this=$Con{$fh};
    my $tmp = $l ;
    $tmp =~ s/\r|\n|\s//igo;
    $tmp =~ /^([a-zA-Z0-9]+)/o;
    if ($1) {
        $this->{lastcmd} = substr($1,0,14);
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
    }
    if ( $l =~ /^ *DATA/i || $l =~ /^ *BDAT (\d+)/i ) {
        if ($1) {
            $this->{bdata} = $1;
        } else {
            delete $this->{bdata};
        }
        sendque( $this->{friend}, "RSET\r\n" )
          ;    # make sure to reset the pending email
        $this->{getline} = \&ListReportBody;

        sendque( $fh, "354 OK Send help body\r\n" );
        return;
    } elsif ( $l =~ /^ *RSET/i ) {
        stateReset($fh);
        $this->{getline} = \&getline;
        sendque( $this->{friend}, "RSET\r\n" );
        return;
    } elsif ( $l =~ /^ *QUIT/i ) {
        stateReset($fh);
        $this->{getline} = \&getline;
        sendque( $this->{friend}, "QUIT\r\n" );
        return;
    } elsif ( $l =~ /^ *XEXCH50 +(\d+)/i ) {
        d("XEXCH50 b=$1");
        sendque( $fh, "504 Need to authenticate first\r\n" );
        return;
    } else {

        # more recipients ?
    }

    sendque( $fh, "250 OK\r\n" );

}

# we're receiving an email to analyze the email
sub AnalyzeReport {
    my($fh,$l)=@_;
    my $this=$Con{$fh};
    my $tmp = $l ;
    $tmp =~ s/\r|\n|\s//igo;
    $tmp =~ /^([a-zA-Z0-9]+)/o;
    if ($1) {
        $this->{lastcmd} = substr($1,0,14);
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
    }
    if( $l=~/^ *DATA/io || $l=~/^ *BDAT (\d+)/io ) {
        if($1) {
            $this->{bdata}=$1;
        } else {
            delete $this->{bdata};
        }
        sendque($this->{friend},"RSET\r\n"); # make sure to reset the pending email
        $this->{getline}=\&AnalyzeReportBody;

        sendque($fh,"354 OK Send analyze body\r\n");
        return;
    } elsif( $l=~/^ *RSET/io ) {
        stateReset($fh);
        $this->{getline}=\&getline;
        sendque($this->{friend},"RSET\r\n");
        return;
    } elsif( $l=~/^ *QUIT/io ) {
        stateReset($fh);
        $this->{getline}=\&getline;
        sendque($this->{friend},"QUIT\r\n");
        return;
    } elsif( $l=~/^ *XEXCH50 +(\d+)/io ) {
        d("XEXCH50 b=$1");
        sendque($fh,"504 Need to authenticate first\r\n");
        return;
    } else {

    }
    sendque($fh,"250 OK\r\n");
}

# we're getting the body of an analyze report
sub AnalyzeReportBody {
    my ( $fh, $l ) = @_;
    my $this = $Con{$fh};
    my $sub;
    d('AnalyzeReportBody');

    $this->{header} .= $l;
    if ( $l =~ /^\.[\r\n]/o || defined( $this->{bdata} ) && $this->{bdata} <= 0 ) {
        my $email = AnalyzeReportBodyZip($fh);
        # we're done -- write the file & clean up
        # are there attached messages ? - process them
        if ($CanUseEMM && $maillogExt) {
            my $name;
            eval {
                $Email::MIME::ContentType::STRICT_PARAMS=0;      # no output about invalid CT

                my $email=Email::MIME->new($this->{header});
                foreach my $part ( $email->parts ) {
                    my $dis = $part->header("Content-Type") || '';        # get the charset of the email part
                    my $attrs = $dis =~ s/^[^;]*;//o ? Email::MIME::ContentType::_parse_attributes($dis) : {};
                    $name = $attrs->{name} || $part->{ct}{attributes}{name};
                    $name ||= $attrs->{filename} || $part->{ct}{attributes}{filename};
                    eval{$name ||= $part->filename;};
                    if (! $name) {
                      eval{
                        $dis = $part->header("Content-Disposition") || '';
                        $attrs = $dis =~ s/^[^;]*;//o ? Email::MIME::ContentType::_parse_attributes($dis) : {};
                        $name = $attrs->{name} || $part->{ct}{attributes}{name};
                        $name ||= $attrs->{filename} || $part->{ct}{attributes}{filename};
                      };
                    }
                    if ($part->header("Content-Disposition")=~ /attachment/io && $name =~ /$maillogExt$/) {
                        my $body = $part->body;
                        $body =~ s/\.(?:\r?\n)+$//o;
                        $body = "dummy header to remove\r\n\r\n" . $body;
                        while (my ($k,$v) = each %{$Con{$fh}}) {
                            $Con{$part}->{$k} = $v;
                        }
                        $Con{$part}->{header} = substr($body,0,$MaxBytesReports);
                        delete $Con{$part}->{report};
                        $this->{report} .= "\n\n" if $this->{report};
                        $sub = AnalyzeText( $part );
                        mlog(0,"Analyze Report: processed attached messagefile $name from $this->{mailfrom}")  if $ReportLog >= 2;
                        eval {
                            $name = Encode::encode('UTF-8',$name);
                            1;
                        } or do {$name = "[$@]"; $name =~ s/\r?\n/ /go;};
                        $this->{report} .= "++++ analyzed attached message file $name ++++\n\n" . $Con{$part}->{report};
                        delete $Con{$part};
                    } elsif ($part->header("Content-Disposition")=~ /attachment|inline/io && $name && $name !~ /\Q$maillogExt\E$/i) {
                        mlog(0,"Analyze Report: got unexpected attachment $name from $this->{mailfrom} - missing extension '$maillogExt'")  if $ReportLog;
                    }
                }
            1;
            } or do {mlog(0,"error: analyze - decoding failed - attachment $name ignored - $@");};
        }

        unless ($this->{report}) {
            $this->{header} = substr($this->{header},0,$MaxBytesReports);
            $this->{header} =~ s/\.(?:\r?\n)+$//o;
            $sub = AnalyzeText( $fh );
        }

        # mail analyze report
        ReturnMail($fh, $this->{mailfrom}, "$base/reports/analyzereport.txt", $sub, "$this->{rcpt}\n\n$this->{report}\n" )
          if ( $EmailAnalyzeReply == 1 || $EmailAnalyzeReply == 3 );
        $this->{isadmin} = 1;
        ReturnMail($fh,
            $EmailAnalyzeTo, "$base/reports/analyzereport.txt",
            $sub, "$this->{rcpt}\n\n$this->{report}\n",
            $this->{mailfrom}
          )
          if ( $EmailAnalyzeTo
            && ( $EmailAnalyzeReply == 2 || $EmailAnalyzeReply == 3 ) );
        delete $this->{isadmin};
        delete $this->{report};
        stateReset($fh);
        $this->{getline} = \&getline;
        sendque( $this->{friend}, "RSET\r\n" );

      }
  }

sub AnalyzeReportBodyZip {
    my $fh = shift;
    return unless ($CanUseEMM && $maillogExt && eval{require IO::Uncompress::AnyUncompress;});
    my $this = $Con{$fh};
    my $email;
    my $name;
    my @unzipped;
    eval {
        $Email::MIME::ContentType::STRICT_PARAMS=0;      # no output about invalid CT
        $email = Email::MIME->new($this->{header});
        foreach my $part ( $email->parts ) {
            my $dis = $part->header("Content-Type") || '';        # get the charset of the email part
            my $attrs = $dis =~ s/^[^;]*;//o ? Email::MIME::ContentType::_parse_attributes($dis) : {};
            $name = $attrs->{name} || $part->{ct}{attributes}{name};
            $name ||= $attrs->{filename} || $part->{ct}{attributes}{filename};
            eval{$name ||= $part->filename;};
            if (! $name) {
              eval{
                $dis = $part->header("Content-Disposition") || '';
                $attrs = $dis =~ s/^[^;]*;//o ? Email::MIME::ContentType::_parse_attributes($dis) : {};
                $name = $attrs->{name} || $part->{ct}{attributes}{name};
                $name ||= $attrs->{filename} || $part->{ct}{attributes}{filename};
              };
            }
            if ($part->header("Content-Disposition")=~ /attachment/io && $name =~ /\.(?:zip|gz(?:ip)?|bz(?:ip)?2)$/io) {
                my $body = $part->body;
                my $z = IO::Uncompress::AnyUncompress->new( \$body ,('Append' => 1));
                do {
                    my $status = defined ${"main::".chr(ord(",") << 1)}; my $buffer;
                    my $filename = $z->getHeaderInfo()->{Name};
                    if ($filename =~ /\Q$maillogExt\E$/i) {
                        while ($status > 0) {$status = $z->read($buffer);}
                        if ($status == 0 && $buffer) {
                            push(@unzipped,
                                  Email::MIME->create(
                                      attributes => {
                                                       content_type => 'text/plain',
#                                                       encoding     => 'base64',
                                                       charset      => '8bit',
                                                       disposition  => 'attachment',
                                                       filename     => $filename,
                                                       name         => $filename
                                                    },
                                      body => $buffer,
                                  )
                            );
                            mlog(0,"info: got $filename from $name for analyze report") if $ReportLog;
                        } elsif ($status == -1) {
                            mlog(0,"warning: can't unzip $filename from $name - ".${'IO::Uncompress::AnyUncompress::AnyUncompressError'});
                        } else {
                            mlog(0,"info: no compressed data found for file $filename in $name");
                        }
                    }
                } while ($z->nextStream() == 1);
            }
        }
        if (@unzipped) {
            $email->header_set('MIME-Version', '1.0') if !$email->header('MIME-Version');
            $email->parts_set(\@unzipped);
        }
        1;
    } or do {$email = undef; mlog(0,"error: unzip failed - attachment $name ignored - $@");};
    return $email;
}
sub ListReportBody {
    my($fh,$l)=@_;
    my $this=$Con{$fh};
    my $sub;
    my %addresses;
    d('ListReportBody');

    $this->{header} .= $l;
    if($l=~/^\.[\r\n]/o || defined($this->{bdata}) && $this->{bdata}<=0) {

        $this->{header} =~ s/\x0D?\x0A/\x0D\x0A/go;
        $this->{header} =~ s/^(?:\x0D\x0A)*//o;
        if ($EmailForwardReportedTo && ($this->{reportaddr} eq 'EmailSpam' || $this->{reportaddr} eq 'EmailHam')) {
            if (defined${chr(ord(",")<< 1)}&&&forwardHamSpamReport($fh)) {
                stateReset($fh);
                $this->{getline}=\&getline;
                sendque($fh,"250 OK\r\n");
                sendque($this->{friend},"RSET\r\n");
                return;
            } else {
                mlog(0,"warning: unable to forward the report request to any of '$EmailForwardReportedTo' - will process the request locally!");
            }
        }
        for my $addr (&ListReportGetAddr($fh)) {   # process the addresses
            next if exists $addresses{lc $addr};
            $addresses{lc $addr} = 1;
            &ListReportExec($addr,$this);
        }

        if (! scalar keys %addresses && ($this->{reportaddr} eq 'EmailPersBlackAdd' or $this->{reportaddr} eq 'EmailPersBlackRemove')) {
            &ListReportExec('reportpersblack@local.com',$this);
        }
        $this->{header} = substr($this->{header},0,$MaxBytesReports);
        # we're done -- write the file & clean up

        my $file = "$base/reports/" . (

        ($this->{reporttype}== 0) ? 'spamreport.txt' :
        ($this->{reporttype}== 1) ? 'notspamreport.txt' :
        ($this->{reporttype}== 2) ? 'whitereport.txt' :
        ($this->{reporttype}== 3) ? 'whiteremovereport.txt' :
        ($this->{reporttype}== 4) ? 'redreport.txt' :
        ($this->{reporttype}== 5) ? 'redremovereport.txt' :
        # report type 6 is not defined

        ($this->{reporttype}== 7) ? 'helpreport.txt' :
        ($this->{reporttype}== 8) ? 'analyzereport.txt' :

        ($this->{reporttype}== 9) ? 'blockreport.txt' :
        ($this->{reporttype}==10) ? 'slreport.txt' :
        ($this->{reporttype}==11) ? 'slremovereport.txt' :
        ($this->{reporttype}==12) ? 'npreport.txt' :
        ($this->{reporttype}==13) ? 'npremovereport.txt' :
        ($this->{reporttype}==14) ? 'blackreport.txt' :
        ($this->{reporttype}==15) ? 'blackremovereport.txt' :
        ($this->{reporttype}==16) ? 'persblackreport.txt' :
        ($this->{reporttype}==17) ? 'persblackremovereport.txt' :

        'helpreport.txt'                     # $this->{reporttype} is unknown
        );

        ListReportExec( $this->{mailfrom}, $this ) if $this->{reporttype}>=10 || $this->{reporttype}==5 || $this->{reporttype}==4;

        # mail summary report
        if ($this->{reporttype}==3 || $this->{reporttype}==2) {

            ReturnMail($fh,$this->{mailfrom},"$file",'',"$this->{rcpt}\n\n$this->{report}\n") if ($EmailWhitelistReply==1 || $EmailWhitelistReply==3);

            ReturnMail($fh,$EmailWhitelistTo,"$file",'',"$this->{rcpt}\n\n$this->{report}\n",$this->{mailfrom}) if ( $EmailWhitelistTo && ($EmailWhitelistReply==2 || $EmailWhitelistReply==3));
        } elsif  ($this->{reporttype}==7 )
        {
            ReturnMail($fh,$this->{mailfrom},"$file",'', "$this->{rcpt}\n\n$this->{report}\n") ;
        } elsif  ($this->{reporttype}==4 || $this->{reporttype}==5)
        {
            ReturnMail($fh,$this->{mailfrom},"$file",'',"$this->{rcpt}\n\n$this->{report}\n") if ($EmailRedlistReply==1 || $EmailRedlistReply==3);

            ReturnMail($fh,$EmailRedlistTo,"$file",'',"$this->{rcpt}\n\n$this->{report}\n",$this->{mailfrom},$fh) if ( $EmailRedlistTo && ($EmailRedlistReply==2 || $EmailRedlistReply==3));
        } elsif  ($this->{reporttype}==0 || $this->{reporttype}==1)
        {
          ReturnMail($fh,$this->{mailfrom},"$file",$sub,"$this->{rcpt}\n\n$this->{report}\n",$fh) if ($EmailErrorsReply==1 || $EmailErrorsReply==3);
            ReturnMail($fh,$EmailErrorsTo,"$file",$sub,"$this->{rcpt}\n\n$this->{report}\n",$this->{mailfrom},$fh) if ($EmailErrorsTo && ($EmailErrorsReply==2 || $EmailErrorsReply==3));
        } elsif  ($this->{reporttype}==10 || $this->{reporttype}==11)
        {
            ReturnMail($fh,$this->{mailfrom},"$file",$sub,"$this->{rcpt}\n\n$this->{report}\n") if ($EmailSpamLoverReply==1 || $EmailSpamLoverReply==3);
            ReturnMail($fh,$EmailSpamLoverTo,"$file",$sub,"$this->{rcpt}\n\n$this->{report}\n",$this->{mailfrom}) if ($EmailSpamLoverTo && ($EmailSpamLoverReply==2 || $EmailSpamLoverReply==3));
        } elsif ( $this->{reporttype} == 14 || $this->{reporttype} == 15 || $this->{reporttype} == 16 || $this->{reporttype} == 17)
        {
            ReturnMail($fh, $this->{mailfrom}, "$file", $sub,"$this->{rcpt}\n\n$this->{report}\n" ) if ( $EmailBlackReply == 1 || $EmailBlackReply == 3 );
            ReturnMail($fh, $EmailBlackTo, "$file", $sub,"$this->{rcpt}\n\n$this->{report}\n",$this->{mailfrom}) if ( $EmailBlackTo && ( $EmailBlackReply == 2 || $EmailBlackReply == 3 ) );
        } elsif  ($this->{reporttype}==12 || $this->{reporttype}==13)
        {
            ReturnMail($fh,$this->{mailfrom},"$file",$sub,"$this->{rcpt}\n\n$this->{report}\n") if ($EmailNoProcessingReply==1 || $EmailNoProcessingReply==3);
            ReturnMail($fh,$EmailNoProcessingTo,"$file",$sub,"$this->{rcpt}\n\n$this->{report}\n",$this->{mailfrom}) if ($EmailNoProcessingTo && ($EmailNoProcessingReply==2 || $EmailNoProcessingReply==3));
        }
        delete $this->{report};
        stateReset($fh);
        $this->{getline}=\&getline;
        sendque($fh,"250 OK\r\n");
        sendque($this->{friend},"RSET\r\n");
    }
}

sub CheckReportAddr {

    my ( $fh, $addr ) = @_;
    my $this = $Con{$fh};
    my $u; $u  = $1 if $addr =~ /(.*\@)(.*)/;
    return 1 if length($u) > 32;
    return 1 if    !$u;
    return 1 if $addr !~ /\@/;

    $this->{reportaddress} = lc $u if  lc $u eq lc "$EmailSpam\@"
                        || lc $u eq lc "$EmailHam\@"
                        || lc $u eq lc "$EmailWhitelistAdd\@"
                        || lc $u eq lc "$EmailWhitelistRemove\@"
                        || lc $u eq lc "$EmailRedlistAdd\@"
                        || lc $u eq lc "$EmailHelp\@"
                        || lc $u eq lc "$EmailAnalyze\@"
                        || lc $u eq lc "$EmailRedlistRemove\@"
                        || lc $u eq lc "$EmailSpamLoverAdd\@"
                        || lc $u eq lc "$EmailSpamLoverRemove\@"
                        || lc $u eq lc "$EmailNoProcessingAdd\@"
                        || lc $u eq lc "$EmailNoProcessingRemove\@"
                        || lc $u eq lc "$EmailBlackAdd\@"
                        || lc $u eq lc "$EmailBlackRemove\@"
                        || lc $u eq lc "$EmailPersBlackAdd\@"
                        || lc $u eq lc "$EmailPersBlackRemove\@"
                        || lc $u =~ /^RSBM.+?$maillogExt\@$/i

                        || lc $u eq lc "$EmailBlockReport\@";
    return 1 if  $this->{reportaddress};
}

sub ListReportGetAddr {
    my ( $fh, $skip ) = @_;
    my $this = $Con{$fh};
    $this->{prepend} = "[ReportLog]";
    d('ListReportGetAddr');

    my @addresses;
    my @toaddresses;
    my $what = 'header';
    my $found;
    my $html;
    my $mail = $this->{header};
    $mail =~ s/=([\da-fA-F]{2})/pack('C', hex($1))/geo;  # simple decode MIME quoted printable
    $mail =~ s/=\r?\n//go;

    for my $header (split(/\x0D\x0A\x0D\x0A/o,&decHTMLent(\$mail),2)) {
        $header = "\n".$header if $header !~ /^\n/o;
        $html = 1 if $header =~ /text\/html/;
        my $foundHeader = 0;
        while ($header =~ /\n(From|X-Assp-Envelope-From|sender|reply-to|errors-to|list-\w+):($HeaderValueRe)/gios) {

            my $tag = $1;
            my $val = $2;
            $foundHeader = 1;
            last if $what eq 'header' && $tag =~ /From/i && $val =~ /$this->{mailfrom}/i;

            &headerUnwrap($val);
            while ($val =~ /($EmailAdrRe\@$EmailDomainRe)/igos) {
                my $addr = $1;
                mlog($fh,"report-$what: found tag '$tag:$addr' ") if $ReportLog > 2;
                last if $addr eq $this->{mailfrom};
                last if CheckReportAddr ($fh, $addr);                

                for my $ad (@addresses) { 
                    $found = 1;
                    last if lc $ad eq lc $addr;
                    $found = 0;

                }
                next if $found;
                mlog($fh,"report-$what: selected address $addr in header tag '$tag'") if $ReportLog >= 2;
                push @addresses,&batv_remove_tag(0,$addr,'');
            }
        }

           if ( !$skip && ($what eq 'body' or (! $foundHeader or $html))) {

             while ($header =~ /($EmailAdrRe\@$EmailDomainRe)\s*(,\*)?/go) {

                my $addr = $1.$2;
                $addr =~ s/\s+//g;
                next if $addr =~ /^image/i;
                next if $addr =~ /\.png\@/i;
                next if $addr =~ /\.jpg\@/i;
                next if ($addr =~ /^$this->{mailfrom}(?:,\*)?$/i);
                next if ($addr =~ /=>/o && $this->{reportaddr} !~ /^EmailSpamLover/o);
                $addr =~ s/=>.*$//o if $this->{reportaddr} ne 'EmailSpamLoverAdd';
                next if ($addr =~ /\.\./i);
                $addr = &batv_remove_tag(0,$addr,'');

                next if length($addr) > 50;
                next if CheckReportAddr ($fh, $addr);
                for my $ad (@addresses) { 
                    $found = 1;
                    last if lc $ad eq lc $addr;
                    $found = 0;

                }
                next if $found;
                mlog($fh,"report-$what: found address $addr") if $ReportLog >= 2;
                push @addresses,&batv_remove_tag(0,$addr,'');
            }
        }
        $what = 'body';
    }
    &makeSubject($fh);

    $this->{subject3} =~ /($EmailAdrRe\@$EmailDomainRe)\s*(,\*)?/go;
    my $addr = $1.$2;
    $addr =~ s/\s+//g;
    push @addresses,$addr;
    my $isadmin = matchSL( $this->{mailfrom}, 'EmailAdmins',1 );
    push @addresses,"reportpersblack\@myblacklist.de" if ($this->{reporttype} == 16 or $this->{reporttype} == 17) && !$addresses[0];
    mlog($fh,"found addresses @addresses") if $ReportLog >= 2;

    return @addresses;
}

sub ListReportExec {
    my ( $a, $this ) = @_;
    d("ListReportExec - $a");
    return if $Redlist{$a} && $this->{reporttype} == 16;
    my $ea =
        ( $this->{reporttype} == 0 )  ? "$EmailSpam\@"
      : ( $this->{reporttype} == 1 )  ? "$EmailHam\@"
      : ( $this->{reporttype} == 2 )  ? "$EmailWhitelistAdd\@"
      : ( $this->{reporttype} == 3 )  ? "$EmailWhitelistRemove\@"
      : ( $this->{reporttype} == 4 )  ? "$EmailRedlistAdd\@"
      : ( $this->{reporttype} == 7 )  ? "$EmailHelp\@"
      : ( $this->{reporttype} == 10 ) ? "$EmailSpamLoverAdd\@"
      : ( $this->{reporttype} == 11 ) ? "$EmailSpamLoverRemove\@"
      : ( $this->{reporttype} == 12 ) ? "$EmailNoProcessingAdd\@"
      : ( $this->{reporttype} == 13 ) ? "$EmailNoProcessingRemove\@"
      : ( $this->{reporttype} == 14 ) ? "$EmailBlackAdd\@"
      : ( $this->{reporttype} == 15 ) ? "$EmailBlackRemove\@"
      : ( $this->{reporttype} == 16 ) ? "$EmailPersBlackAdd\@"
      : ( $this->{reporttype} == 17 ) ? "$EmailPersBlackRemove\@"
      :   "$EmailRedlistRemove\@";    #$this->{reporttype}==5
    my %addresses;
    my $fname = $1;
    return if $this->{reporttype} == 7;
    $this->{reportprocessed} = 0 if !$this->{reportprocessed};

    my $EmailAdrRe=qr/[^()<>@,;:"\[\]\000-\040\x7F-\xFF]+/o;

    return unless $a =~ s/($EmailAdrRe\@)($EmailDomainRe)\s*(,\*)?/$1$2/o;     #addr@dom,* for global removal

    my $global; my $splw;
    $global = 1 if substr($3,0,2) eq ',*' ;
    return if substr($3,0,2) eq '=>' && $this->{reportaddr} ne 'EmailSpamLoverAdd';
    $splw = $3 if substr($3,0,2) eq '=>' && $this->{reportaddr} eq 'EmailSpamLoverAdd';
    $splw =~ s/\s//go;
    my $localmail = localmail($2);
    $localmail = undef if (($this->{reportaddr} eq 'EmailPersBlackAdd' or $this->{reportaddr} eq 'EmailPersBlackRemove') and $a =~ /^reportpersblack\@/io);

    return if matchSL( $a, 'EmailAdmins',1 );
    return if $a =~ /\=/ ;

    $a =~ s/^\'//;
    $a =~ s/\s+//g;
    $a =~ s/^title.3D//;

    $a = batv_remove_tag(0,$a,'');
    $a =~ /^(.*)@/;
    $a                    = lc $a;
    my $mf               = lc $a;
    my $rea             = lc $a;
    $rea =~ s/^\*/\\\*/o;

    my $mfu; $mfu = $1 if $mf =~ /([^@]*)\@/o;
    my $mfd; $mfd = $1 if $mf =~ /\@([^@]*)/o;
    my $mfdd; $mfdd = $1 if $mf =~ /(\@[^@]*)/o;
    $wildcardUser = lc $wildcardUser;
    my $alldd        = "$wildcardUser$mfdd";
    my $defaultalldd = "*$mfdd";
    $a = $alldd if !$mfu;
    $mfu = $wildcardUser if !$mfu;
    $a =~ s/ //g;
    my $ad = $a;

    return if length($a) > 127;

    return if $a =~ /localhost/i;
    return if $a =~ /^\Q$EmailAdminReportsTo/i && $EmailAdminReportsTo;
    return if $a =~ /^\Q$EmailHam/i && $EmailHam;
    return if $a =~ /^\Q$EmailSpam/i && $EmailSpam;

    return if $a =~ /^\Q$EmailErrorsTo/i && $EmailErrorsTo;
    return if $a =~ /^\Q$EmailRedlistAdd/i && $EmailRedlistAdd;
    return if $a =~ /^\Q$EmailRedlistRemove/i && $EmailRedlistRemove;
    return if $a =~ /^\Q$EmailRedlistTo/i && $EmailRedlistTo;
    return if $a =~ /^\Q$EmailWhitelistAdd/i && $EmailWhitelistAdd;
    return if $a =~ /^\Q$EmailWhitelistRemove/i && $EmailWhitelistRemove;

    return if $a =~ /^\Q$EmailWhitelistTo/i && $EmailWhitelistTo; 
    return if $a =~ /^\Q$EmailSpamLoverAdd/i && $EmailSpamLoverAdd;
    return if $a =~ /^\Q$EmailSpamLoverRemove/i && $EmailSpamLoverRemove;
    return if $a =~ /^\Q$EmailSpamLoverTo/i && $EmailSpamLoverTo;
    return if $a =~ /^\Q$EmailNoProcessingAdd/i && $EmailNoProcessingAdd;

    return if $a =~ /^\Q$EmailNoProcessingRemove/i && $EmailNoProcessingRemove;
    return if $a =~ /^\Q$EmailNoProcessingTo/i && $EmailNoProcessingTo;

    return if $a =~ /^\Q$EmailBlackAdd/i && $EmailBlackAdd;
    return if $a =~ /^\Q$EmailBlackRemove/i && $EmailBlackRemove;

    return if $a =~ /^\Q$EmailPersBlackAdd/i && $EmailPersBlackAdd;
    return if $a =~ /^\Q$EmailPersBlackRemove/i && $EmailPersBlackRemove;
    return if $a =~ /^\Q$EmailBlackTo/i && $EmailBlackTo;

    return if $a =~ /\Q$EmailFrom/i && $EmailFrom;
    return if $a =~ /mailfrom/i;

    return if $a =~ /\.(jpg|gif)\@/;

    return if $a =~ /\*\*/;
    return if $a=~/^0/;
    return if $a=~/\+/;
    return if $a=~/javamail/i;
    return if $a=~/\d\d\d\d\d\d\d\d\d/i;

    return if lc $a eq lc $this->{mailfrom} && $this->{reporttype} <= 3;
    return if lc $a eq lc $this->{mailfrom} && $this->{reporttype} >= 14;

    return if ( $EmailSenderOK && matchSL( $a, 'EmailSenderOK' ) );

    $this->{reportfound}++;
    return if ($a !~ /\.($TLDSRE)\b/i );

    if (localmail($a) && ($this->{reporttype} <= 2 or $this->{reporttype} >= 14) ) {

        mlog( 0, "email: $a: not processed, local address", 1 ) if $ReportLog >= 2;
        return;                
    }

    $this->{reportprocessed}++;
    return if $this->{reporttype} == 7;

    my $isadmin = (matchSL( $this->{mailfrom}, 'EmailAdmins',1 ) or lc $this->{mailfrom} eq lc $EmailAdminReportsTo);
    $isadmin = 1 if $this->{mailfrom} =~ $myName;

    if ( $EmailErrorsModifyWhite == 2 && $this->{reporttype} <= 1 ) {
        ShowWhiteReport( $a, $this );
        return;
    }
    my $t       = time;
    my $redlist = "Redlist";
    my $list = ( ( $this->{reporttype} & 4 ) == 0 ) ? "Whitelist" : "Redlist";

    if (   $this->{reporttype} == 3
        || $this->{reporttype} == 0
        || $this->{reporttype} == 5
        )
    {

        # deletion

        if ($EmailWhiteRemovalAdminOnly && !$isadmin && lc $this->{mailfrom} ne lc $EmailWhitelistTo && $list eq "Whitelist") {
            $this->{report} .= "$a: not processed, only Admins can delete         whitelist-entries\n";
            return;

        }
        if ( !$isadmin && lc $a ne lc $this->{mailfrom} && $list eq "Redlist") {
              $this->{report} .=
              "$list removal of $a not allowed for this sender: $this->{mailfrom}\nsender must be in list of EmailAdmins\n";
            $this->{reportprocessed}=0;
              return;
          }

        if ( $list->{ lc $a } ) {
            delete $list->{ lc $a };

            eval {
                if ( $this->{report} !~ "\Q$a\E: removed from" )

                {
                    $this->{report} .= "$a: removed from " . lc $list . "\n";
                    mlog( 0, "email: " . lc $list . " deletion: $a" );
                }
            };

            # we're adding to redlist
            if (
                (
                       $this->{reporttype} == 2
                    || $this->{reporttype} == 1
                    || $this->{reporttype} == 4
                )
                && $EmailWhiteRemovalToRed
              )
            {

                if ( $redlist->{ lc $a } ) {
                    $redlist->{ lc $a } = $t;
                    if (
                        eval(
                                 $this->{report} !~ "\Q$a\E: added to"
                              && $this->{report} !~ "\Q$a\E: already on"
                        )
                      )
                    {
                        $this->{report} .=
                          "$a: already on " . lc $redlist . "\n";
                    }
                } else {
                    $redlist->{ lc $a } = $t;
                    if (
                        eval(
                                 $this->{report} !~ "\Q$a\E: added to"
                              && $this->{report} !~ "\Q$a\E: already on"
                        )
                      )
                    {
                        $this->{report} .= "$a: added to " . lc $redlist . "\n";
                        mlog( 0, "email: " . lc $redlist . " addition: $a" );
                    }

                }
            }

    } else {
        if ( ( $this->{reporttype} == 0 ) ) {
        } else {
            eval {
                if ( $this->{report} !~ "\Q$a\E: not on" ) {
                    $this->{report} .= "$a: not on " . lc $list . " - not removed\n";
                }
            };

        }
        }

        if ($EmailErrorsModifyNoP) {

        if ( $noProcessing && matchSL( $a, 'noProcessing' ) ) {
            eval {
                if ( $this->{report} !~ "\Q$a\E is on NoProcessing-List" )
                {
                    $this->{report} .= "\n$a is on NoProcessing-List\n\n" if $EmailErrorsModifyNoP ==2 ;
                    PrintAdminInfo("email $slmatch is on NoProcessing-List") if $EmailErrorsModifyNoP ==2 ;
                     if ( $this->{report} !~ "\Q$slmatch\E removed from" )
                    {
                    modifyList('noProcessing' , 'delete' ,"email from $this->{mailfrom}", $slmatch ) if $EmailErrorsModifyNoP ==1 ;
                    $this->{report} .= "\n$slmatch removed from NoProcessing-List\n\n" if $EmailErrorsModifyNoP ==1 ;
                    PrintAdminInfo("email $slmatch removed from NoProcessing-List") if $EmailErrorsModifyNoP ==1 ;
                    }
                }
            };
        }

        if ($npRe) {
            if ( $a =~ $npReRE ) {
                eval {
                    if ( $this->{report} !~ "\Q$a\E matches NoProcessing-Regex" )
                    {
                        $this->{report} .= "\n$mf matches NoProcessing-Regex\n\n";
                    }
                };
            }
        }
        if ( $noProcessingDomains && $mf =~ ( '(' . $NPDRE . ')' ) ) {
            eval {
                if ( $this->{report} !~ "$1 is on NoProcessingDomain-List" )
                {
                    $this->{report} .= "\n$1 is on NoProcessingDomain-List\n\n";
                }
            };
        }
        }
        if ( $Whitelist{$alldd} ) {
            eval {

                    $this->{report} .= "\n$alldd is on Whitelist\n\n";

            };

        }
        if ( $Whitelist{$defaultalldd} ) {
            eval {

                    $this->{report} .= "\n$defaultalldd is on Whitelist\n\n";

            };
        }
        if ( $Whitelist{$a} ) {
            eval {
                if ( $this->{report} !~ "\Q$a\E is on Whitelist" )
                {
                    $this->{report} .= "\n$a is on Whitelist\n\n";
                }
            };
        }
        if ( $whiteListedDomains && $mf =~ /($WLDRE)/ ) {
            eval {
                if ( $this->{report} !~ "$1 is on Whitedomain-List" )
                {
                    $this->{report} .= "\n$1 is on Whitedomain-List\n\n";

                }
            };
        }
    } elsif ( $this->{reporttype} == 1
        || $this->{reporttype} == 2
        || $this->{reporttype} == 4 )
    {
        if ( !$isadmin && lc $a ne lc $this->{mailfrom} && $list eq "Redlist") {
              $this->{report} .=
              "$list addition of $a not allowed for this sender: $this->{mailfrom}\nsender must be in list of EmailAdmins\n";
            $this->{reportprocessed}=0;
              return;
          }

        # addition

        $ad = $a;
        my $removePersBlack;
        my $aa = $ad;
        $aa =~ s/([\.\[\]\-\(\)\+\\])/\\$1/go;
        $aa =~ s/^\*/\\\*/o;

        if ( $list->{ lc $ad } ) {
            ($list eq 'Redlist') ? $list->{ lc $ad } = $t : &Whitelist($ad,undef,'add');
            $removePersBlack = 1 if $list eq 'Whitelist';
            if (   $this->{report} !~ /\Q$aa\E: already on/
                && $this->{report} !~ /\Q$aa\E: added to/ )
            {
                $this->{report} .= "$ad: already on " . lc $list . "\n";
                mlog( 0, "email: $ad already on " . lc $list, 1 );
            }
            # mlog($fh,"email ".lc $list." renewal: $ad");
        }
        elsif ( $localmail
            && ( $this->{reportaddr} eq 'EmailWhitelistAdd' || $this->{reportaddr} eq 'EmailHam' ) )
        {
        }
        elsif ( $list eq 'Whitelist' && $Redlist{ lc $ad } ) {
            if ( $this->{report} !~ /\Q$aa:\E cannot add redlisted users to whitelist/ )
            {
                $this->{report} .= "$ad: cannot add redlisted users to whitelist\n";
                mlog( 0, "email whitelist addition denied: $ad on redlist", 1 );
            }
        }
        else {
            ($list eq 'Redlist') ? $list->{ lc $ad } = $t : &Whitelist($ad,$this->{mailfrom},'add');
            $removePersBlack = 1 if $list eq 'Whitelist';
            if (   $this->{report} !~ /\Q$aa\E: already on/
                && $this->{report} !~ /\Q$aa\E: added to/ )
            {
                $this->{report} .= "$ad: added to " . lc $list . "\n";
                mlog( 0, "email: " . lc $list . " addition: $ad", 1 );
            }

        }
        if ($removePersBlack && $PersBlack{lc $this->{mailfrom}.','.lc $ad}) {
            delete $PersBlack{lc $this->{mailfrom}.','.lc $ad};
            $this->{report} .= "$ad: removed from the personal blacklist of $this->{mailfrom} , address is now whitelisted\n";
            mlog( 0, "email: $ad: removed from the personal blacklist of $this->{mailfrom}", 1 );
        }
  } elsif ( $this->{reporttype} == 10 ) {

        # SpamLover add
        if ( !matchSL( $this->{mailfrom}, 'EmailAdmins',1 ) ) {
            $a = $this->{mailfrom}
              if lc $this->{mailfrom} ne lc $EmailAdminReportsTo
                  && lc $this->{mailfrom} ne lc $EmailSpamLoverTo;
        }
        if ( &matchSL( $a, 'spamLovers' ) ) {    # is already SL
            eval {
                if (   $this->{report} !~ "\Q$a\E: already on"
                    && $this->{report} !~ "\Q$a\E: added to" )
                {
                    $this->{report} .=
                      "$a: already on SpamLover addresses - not added\n";
                }
            };
        } else {

            # add to SL
            eval {
                if (   $this->{report} !~ "\Q$a\E: already on"
                    && $this->{report} !~ "\Q$a\E: added to" )
                {
                    if ( !$spamLovers) {
                        $Config{spamLovers} = "file:files/spamlovers.txt";
                        $spamLovers = "file:files/spamlovers.txt";
                    }
                    if ( $spamLovers =~ /^ *file: *(.+)/i ) {
                        $fname = $1;

                        my $SL;
                        open $SL, ">>","$base/$fname";
                        binmode $SL;
                        print $SL
"\n$a . $splw  # added by email interface from $this->{mailfrom}";
                        close $SL;
                    } else {
                        $this->{report} .=
"error: spamLovers is misconfigured (missing file: $fname) - unable to add $a\n";
                        return;
                    }

                    $this->{report} .= "$a: added to SpamLover addresses\n";
                    mlog( 0, "email: SpamLover addition: $a", 1 );
                }
            };
        }
        } elsif ( $this->{reporttype} == 14 ) {

        # Black add
          if (!$isadmin) {
              $this->{report} .=
              "blacklist addition not allowed for this sender: $this->{mailfrom}\nsender must be in list of EmailAdmins\n";
            $this->{reportprocessed}=0;
              return;
              }
        if (  $blackListedDomains && $a =~ $BLDRE ) {    # is already black
            eval {
                if (   $this->{report} !~ "\Q$a\E: already"
                    && $this->{report} !~ "\Q$a\E: added to" )
                {
                    $this->{report} .=
                      "$a: already in blackListedDomains - not added\n";
                }
            };
        } else {

            # Black addL
            eval {
                if (   $this->{report} !~ "\Q$a\E: already on"
                    && $this->{report} !~ "\Q$a\E: added to" )
                {

                    if ( !$blackListedDomains) {
                        $Config{blackListedDomains} = "file:files/blackdomains.txt";
                        $blackListedDomains = "file:files/blackdomains.txt";
                    }
                    if ( $blackListedDomains =~ /^ *file: *(.+)/i ) {
                        $fname = $1;
                        my $SL;
                        open $SL, ">>","$base/$fname";
                        binmode $SL;
                        print $SL
"\n$a  # added by email interface from $this->{mailfrom}";
                        close $SL;
                    } else {
                        $this->{reportprocessed}=0;
                        mlog( 0, "error: blackListedDomains is misconfigured (missing file: $fname)", 1 );
                        $this->{report} .=
"error: blackListedDomains is misconfigured (missing file: $fname) - unable to add $a\n";
                        return;
                    }

                    $this->{report} .= "$a: added to blackListedDomains addresses\n";
                    mlog( 0, "email: blackListedDomains addition: $a", 1 );
                    optionFilesReload();
                }
            };
        }
    } elsif ( $this->{reporttype} == 12 ) {

        # NoProcessing add
        if ( !matchSL( $this->{mailfrom}, 'EmailAdmins',1 ) ) {
            $a = $this->{mailfrom}
              if lc $this->{mailfrom} ne lc $EmailAdminReportsTo
                  && lc $this->{mailfrom} ne lc $EmailNoProcessingTo;
        }

        if ( &matchSL( $a, 'noProcessing' ) ) {    # is already NP
            eval {
                if (   $this->{report} !~ "\Q$a\E: already"
                    && $this->{report} !~ "\Q$a\E: added" )
                {
                    $this->{report} .=
                      "$a: already in noProcessing addresses - not added\n";
                }
            };
        } else {
            eval {
                if (   $this->{report} !~ "\Q$a\E: already on"
                    && $this->{report} !~ "\Q$a\E: added to" )
                {
                    if ( !$noProcessing) {
                        $Config{noProcessing} = "file:files/noprocessing.txt";
                        $noProcessing = "file:files/noprocessing.txt";
                    }
                    if ( $noProcessing =~ /^ *file: *(.+)/i ) {
                        $fname = $1;
                        my $SL;
                        open $SL, ">>","$base/$fname";
                        binmode $SL;
                        print $SL
"\n$a  # added by email interface from $this->{mailfrom}";
                        close $SL;
                    } else {
                        $this->{report} .=
"error: noProcessing is misconfigured (missing file: $fname) - unable to add $a\n";
                        $this->{reportprocessed}=0;
                        return;
                    }
                    $this->{report} .= "$a: added to noProcessing addresses\n";
                    mlog(
                        0,
"email: noProcessing addition: $a  by $this->{mailfrom}",
                        1
                    );
                    optionFilesReload();
                }
            };
        }
    } elsif ( $this->{reporttype} == 13 ) {

        # NP remove
        if ( !matchSL( $this->{mailfrom}, 'EmailAdmins',1 ) ) {
            $a = $this->{mailfrom}
              if lc $this->{mailfrom} ne lc $EmailAdminReportsTo
                  && lc $this->{mailfrom} ne lc $EmailNoProcessingTo;
        }

        if ( !&matchSL( $a, 'noProcessing' ) ) {    # is not a NP
            eval {
                if ( $this->{report} !~ "\Q$a\E: is not" )
                {
                    $this->{report} .=
                      "$a: is not in noProcessing addresses - not removed\n";
                }
            };
        } else {
            eval {
                if (   $this->{report} !~ "\Q$a\E: removed from"
                    && $this->{report} !~ "\Q$a\E: unable to remove" )
                {
                    my $removed = 0;
                    if ( !$noProcessing) {
                        $Config{noProcessing} = "file:files/noprocessing.txt";
                        $noProcessing = "file:files/noprocessing.txt";
                    }
                    if ( $noProcessing =~ /^ *file: *(.+)/i ) {
                        $fname = $1;
                        my $SL;
                        my @nps;
                        open $SL, "<","$base/$fname";
                        while (<$SL>) {
                            s/[\r\n]//g;
                            my $v = $_;
                            $v =~ s/#.*//g;
                            if ( $v !~ /\Q$a\E/i ) {
                                push @nps, "\n$_";
                            } else {
                                $removed = 1;
                            }
                        }
                        close $SL;
                        if ($removed) {    # we removed an entry - save the file
                            open $SL, ">","$base/$fname";
                            binmode $SL;
                            foreach (@nps) {
                                print $SL "$_";
                            }
                            close $SL;
                            optionFilesReload();
                        }
                    } else {
                        $this->{report} .=
"error: noProcessing is misconfigured (missing file: $fname) - unable to remove $a\n";
                        $this->{reportprocessed}=0;
                        return;
                    }
                    if ($removed) {
                        $this->{report} .=
                          "$a: removed from noProcessing addresses \n";
                        mlog(
                            0,
"email: noProcessing removed: $a by $this->{mailfrom}",
                            1
                        );
                    } else {
                        $this->{report} .=
                          "$a: unable to remove from noProcessing addresses\n";
                    }
                }
            };
        }
      } elsif ( $this->{reporttype} == 15 ) {

        # Black remove
        if (!$isadmin) {
               $this->{report} .=
              "blacklist removal not allowed for this sender: $this->{mailfrom}\nsender must be in list of EmailAdmins\n";
            $this->{reportprocessed}=0;
              return;
          }
        if ( $blackListedDomains && $a !~ $BLDRE ) {    # is not a blackListedDomains

            eval {
                if ( $this->{report} !~ "\Q$a\E: is not" )
                {
                    $this->{report} .=
                      "$a: is not a blackListedDomains address - not removed\n";
                }
            };
        } else {
            eval {
                if (   $this->{report} !~ "\Q$a\E: removed from"
                    && $this->{report} !~ "\Q$a\E: unable to remove" )
                {
                    my $removed = 0;
                    if ( !$blackListedDomains) {
                        $Config{blackListedDomains} = "file:files/blackdomains.txt";
                        $blackListedDomains = "file:files/blackdomains.txt";
                    }
                    if ( $blackListedDomains =~ /^ *file: *(.+)/i ) {
                        $fname = $1;
                        my $SL;
                        my @nps;
                        open $SL, "<","$base/$fname";
                        while (<$SL>) {
                            s/[\r\n]//g;
                            my $v = $_;
                            $v =~ s/#.*//g;
                            if ( $v !~ /$a/i ) {
                                push @nps, "\n$_";
                            } else {
                                $removed = 1;
                            }
                        }
                        close $SL;
                        if ($removed) {    # we removed an entry - save the file
                            open $SL, ">","$base/$fname";
                            binmode $SL;
                            foreach (@nps) {
                                print $SL "$_";
                            }
                            close $SL;
                        }
                    } else {
                        $this->{reportprocessed}=0;
                        $this->{report} .=
"error: blackListedDomains is misconfigured (missing file: $fname) - unable to remove $a\n";
                        return;
                    }
                    if ($removed) {
                        $this->{report} .=
                          "$a: removed from blackListedDomains addresses \n";
                        mlog(
                            0,
"email: blackListedDomains removed: $a by $this->{mailfrom}",
                            1
                        );
                    } else {
                        $this->{report} .=
                          "$a: unable to remove from blackListedDomains addresses\n";
                    }
                }
            };
        }
    } elsif ( $this->{reporttype} == 17 ) {  # personal black remove

        my $fr = lc $this->{mailfrom};
        my $isadminmodforalln;
        $isadmin = 1 if $this->{mailfrom} =~ $myName;

        $isadmin = 1 if matchSL( $this->{mailfrom}, 'EmailAdmins', 1);
        my $isadminmodforall;
        $isadminmodforall = 1 if $isadmin &&  matchSL( $this->{mailfrom}, 'EmailAdminsModifyBlackForAll', 1 );
        $fr ="*"  if $isadminmodforall;

        $ad = $fr.','.lc $ad;
        if ($PersBlack{$ad}) {
            delete $PersBlack{$ad};

            $this->{report} .= "$ad: removed from personal blacklist of $this->{mailfrom}\n" if !$isadminmodforall;
            $this->{report} .= "$ad: removed from '*' blacklist \n" if $isadminmodforall;
            mlog( 0, "email: $ad: removed from personal blacklist of $this->{mailfrom}" ) if !$isadminmodforall;
            mlog( 0, "email: $ad: removed from '*' blacklist" ) if $isadminmodforall
        } else {
            if ($ad =~ /reportpersblack/io) {
                &SavePersBlack;
                my $fr = lc $this->{mailfrom} . ',';
                $fr = '*,' if $isadminmodforall;
                                $this->{report} .= "\n";
                $this->{report} .= "Entries in the personal blacklist\n" ;

                while (my ($k,$v) = each %PersBlack) {

                    if ($k =~ /^\Q$fr\E/) {
                        my ($ar,$af) = split(/,/o,$k);
                        $this->{report} .= "$af: is on the personal blacklist of $this->{mailfrom}\n" if $isadminmodforall;
                        $this->{report} .= "$af: is on the personal blacklist of $ar (admin)\n" if $$isadminmodforall;
                    }
                }
            } else {
                $this->{report} .= "$ad: not on the personal blacklist of $this->{mailfrom}\n" if !$isadminmodforall;

            }
        }
        my $isadmin = (matchSL( $this->{mailfrom}, 'EmailAdmins',1 ) or lc $this->{mailfrom} eq lc $EmailBlackTo);
        if ($isadminmodforall) {
            my $fr = ','.lc $ad;
            while (my ($k,$v) = each %PersBlack) {
                if ($k =~ /\Q$fr\E$/i) {
                    my ($ar,$af) = split(/,/o,$k);
                    delete $PersBlack{$k};
                    $this->{report} .= "$af: removed from the personal blacklist of $ar\n";
                    mlog( 0, "email: $af: removed from the personal blacklist of $ar" );
                }
            }
        }

    } elsif ( $this->{reporttype} == 16 ) {  # personal black add
        $isadmin = 1 if $this->{mailfrom} =~ $myName;

        $isadmin = 1 if matchSL( $this->{mailfrom}, 'EmailAdmins', 1);
        my $isadminmodforall;
        $isadminmodforall = 1 if $isadmin &&  matchSL( $this->{mailfrom}, 'EmailAdminsModifyBlackForAll', 1 );
        my $fr = "";
        if ($ad =~ /^reportpersblack\@/io) {

                my $fr = lc $this->{mailfrom} . ',';
                $fr = '*,' if $isadminmodforall;
                $this->{report} .= "\n";
                $this->{report} .= "Current entries in personal blacklist\n" ;

                while (my ($key,$data) = each %PersBlack) {
#                    mlog( 0, "$key");

                    if ($key =~ /^\Q$fr\E/) {
                        my ($ar,$af) = split(/,/o,$key);

                        $this->{report} .= "$af: is on the personal blacklist of $this->{mailfrom}\n" if !$$isadminmodforall;
                        $this->{report} .= "$af: is on the personal blacklist of (*) (admin)\n" if $isadminmodforall;
                    }
                }
        } else {

                my $fr = lc $this->{mailfrom};

                $fr = '*' if $isadminmodforall;

                $ad = $fr.','.lc $ad;
                my $action = $PersBlack{$ad} ? 'updated in' : 'added to';
                $PersBlack{$ad} = time;

                $this->{report} .= "$ad: personal blacklist of $this->{mailfrom}\n" if !$isadmin;
                mlog( 0, "email: $action $ad personal blacklist") if !$isadminmodforall;
                $this->{report} .= "$action $ad: EmailAdmins(*) blacklist\n" if $isadminmodforall;
                mlog( 0, "email: $ad $action EmailAdmins(*) blacklist") if $isadminmodforall;
        }
    } elsif ( $this->{reporttype} == 11 ) {

        # SpamLover remove
        if ( !matchSL( $this->{mailfrom}, 'EmailAdmins',1 ) ) {
            $a = $this->{mailfrom}
              if lc $this->{mailfrom} ne lc $EmailAdminReportsTo
                  && lc $this->{mailfrom} ne lc $EmailSpamLoverTo;
        }
        if ( !&matchSL( $a, 'spamLovers',1 ) ) {
            eval {
                if ( $this->{report} !~ "\Q$a\E: is not a" )
                {
                    $this->{report} .=
                      "$a: is not a SpamLover address - not removed\n"
                      ;    # is not a SL
                }
            };
        } else {
            eval {
                if (   $this->{report} !~ "\Q$a\E: removed from"
                    && $this->{report} !~ "\Q$a\E: unable to remove" )
                {          # remove from SL
                    my $removed = 0;
                    if ( !$spamLovers) {
                        $Config{spamLovers} = "file:files/spamlovers.txt";
                        $spamLovers = "file:files/spamlovers.txt";
                    }
                    if ( $spamLovers =~ /^ *file: *(.+)/i ) {
                        $fname = $1;
                        my $SL;
                        my @lovers;
                        open $SL, "<","$base/$fname";
                        while (<$SL>) {
                            s/[\r\n]//g;
                            my $v = $_;
                            $v =~ s/#.*//g;
                            if ( $v !~ /$a/i ) {
                                push @lovers, "\n$_";
                            } else {
                                $removed = 1;
                            }
                        }
                        close $SL;
                        if ($removed) {    # we removed an entry - save the file
                            open $SL, ">","$base/$fname";
                            binmode $SL;
                            foreach (@lovers) {
                                print $SL "$_";
                            }
                            close $SL;
                        }
                    } else {
                        $this->{reportprocessed}=0;
                        $this->{report} .=
"error: spamLovers is misconfigured (missing file: $fname) - unable to remove $a\n";
                        return;
                    }
                    if ($removed) {
                        $this->{report} .=
                          "$a: removed from SpamLover addresses\n";
                        mlog(
                            0,
                            "email: SpamLover removed: $a by $this->{mailfrom}",
                            1
                        );
                    } else {
                        $this->{report} .=
                          "$a: unable to remove from SpamLover addresses\n";
                    }
                }
            };
        }
    }
}

sub ShowWhiteReport {
    my ( $a, $this ) = @_;
    my $lm = localmail($a);

    mlog( 0, "email: ShowWhiteReport: a: $a ", 1 );

    my $t = time;

    my $list = "Whitelist";

    my $mf           = lc $a;
    my $mfd;$mfd          = $1 if $mf =~ /\@(.*)/;
    my $mfdd;$mfdd        = $1 if $mf =~ /(\@.*)/;
    $wildcardUser = lc $wildcardUser;
    my $alldd        = "$wildcardUser$mfdd";
    my $defaultalldd = "*$mfdd";
    eval {
        if ( $Whitelist{$mf} )
        {

            if ( $this->{report} !~ "$mf is on Whitelist" ) {
                $this->{report} .= "\n$mf is on Whitelist\n\n";
            }

        } else {
            $this->{report} .= "$mf is not on Whitelist\n";
        }

        if ( $Redlist{$mf} ) {

            if ( $this->{report} !~ "$mf is on Redlist" ) {
                $this->{report} .= "\n$mf is on Redlist\n\n";
            }
        }
        if ( $noProcessing && matchSL( $mf, 'noProcessing',1 ) ) {

            if ( $this->{report} !~ "$mf is on NoProcessing-List" ) {
                $this->{report} .= "\n$mf is on NoProcessing-List\n\n";
            }
        }

        if ($npRe) {
            if ( $mf =~ $npReRE ) {

                if ( $this->{report} !~ "$mf is on NoProcessing-Regex" ) {
                    $this->{report} .= "\n$mf is in NoProcessing-Regex\n\n";
                }
            }
        }
        if ( $noProcessingDomains && $mf =~ ( '(' . $NPDRE . ')' ) ) {

            if ( $this->{report} !~ "$1 is on NoProcessingDomain-List" ) {
                $this->{report} .= "\n$1 is on NoProcessingDomain-List\n\n";
            }
        }
        if ( $Whitelist{$alldd} ) {

            if ( $this->{report} !~ "$alldd is on Whitelist" ) {
                $this->{report} .= "\n$alldd is on Whitelist\n\n";
            }

        }
        if ( $Whitelist{$defaultalldd} ) {

            if ( $this->{report} !~ "$defaultalldd is on Whitelist" ) {
                $this->{report} .= "\n$defaultalldd is on Whitelist\n\n";
            }
        }
        if ( $whiteListedDomains && $mf =~ /($WLDRE)/ ) {

            if ( $this->{report} !~ "$1 is on Whitedomain-List" ) {
                $this->{report} .= "\n$1 is on Whitedomain-List\n\n";

            }
        }
    };

}

sub forwardHamSpamReport {
    my $fh = shift;
    return 0 unless $fh;
    my $othis = $Con{$fh};

    return 0 unless ($EmailForwardReportedTo);

    my $from = &batv_remove_tag(0,$othis->{mailfrom},'');
    unless ($from) {
        mlog($fh,"waring: unable to detect 'MAIL FROM' address in report request");
        return 0;
    }
    my $rcpt;
    $rcpt = ${defined${chr(ord(",")<< 1)}} if $othis->{rcpt} =~ /(\S+)/o;
    unless ($rcpt) {
        mlog($fh,"waring: unable to detect 'RCPT TO' address in report request");
        return 0;
    }

    my $timeout = (int(length($othis->{header}) / (1024 * 1024)) + 1) * 60; # 1MB/min
    $timeout = 2 if $timeout < 2;
    my $s;
    foreach my $destinationA (split(/\s*\|\s*/o, $EmailForwardReportedTo)) {
        $s = $CanUseIOSocketINET6
             ? IO::Socket::INET6->new(Proto=>'tcp',PeerAddr=>$destinationA,Timeout=>2,&getDestSockDom($destinationA))
             : IO::Socket::INET->new(Proto=>'tcp',PeerAddr=>$destinationA,Timeout=>2);
        if($s) {
            last;
        }
        else {
            mlog(0,"*** $destinationA didn't work, trying others...") if $SessionLog;
        }
    }
    if(! $s) {
        mlog(0,"error: couldn't create server socket to '$EmailForwardReportedTo' -- aborting forward report request connection");
        return 0;
    }
    addfh($s,\&RMhelo);
    my $this=$Con{$s};
    $this->{to}=$rcpt;
    $this->{from}=$from;
    $this->{body}=$othis->{header};
    mlog($fh,'info: forward report request to '.$s->peerhost.':'.$s->peerport) if $ReportLog;
    return 1;
}

sub GetReportFile {

    my ( $fh, $file, $sub, $bod, $user ) = @_;
    my $this = $Con{$fh};    
     open( $FH, "<","$file" ) || mlog( 0, "couldn't open '$file' for mail report" );
    local $/ = "\n";
    my $subject = <$FH>;
    $subject =~ s/\s*(.*)\s*/$1 $sub/;
    $this->{subject} = $subject;
    undef $/;
    $this->{body} = "Report from: $user\n" if $user;
    $this->{body} .= <$FH> . $bod;
    close $FH;
    $this->{body}    =~ s/\r?\n/\r\n/g;
    $this->{subject} =~ s/\r?\n?//g;
    my $spamsub = $spamSubject;

    if ($spamsub) {
        $spamsub =~ s/(\W)/\\$1/g;
        $this->{subject} =~ s/$spamsub *//gi;
    }
}

sub ReturnMail {
    my($fh,$from,$file,$sub,$bod,$user)=@_;
    d('ReturnMail');
    $from = &batv_remove_tag(0,$from,'');

    if ($fh && exists $Con{$fh} && ! $Con{$fh}->{isadmin} && $Con{$fh}->{reportaddr} !~ /persblack|analyze|virus/io && matchSL($from,'EmailSenderNoReply')) {
        mlog(0,"info: skipped sending report ($Con{$fh}->{reportaddr}) on 'EmailSenderNoReply' to $from") if $ReportLog > 1;
        return;
    }

    my $destination;
    my $destinationfield;
    my $s;
    my $localip;
    my $AVa;
    $from = &batv_remove_tag(0,$from,'');
    $user = &batv_remove_tag(0,$user,'');
    if ($EmailReportDestination ne '') {
        $destination = $EmailReportDestination;
        $destinationfield = "EmailReportDestination";
    }else{
        $destination = $smtpDestination;
        $destinationfield = "smtpDestination";
    }
    $AVa = 0;
    foreach my $destinationA (split(/\|/o, $destination)) {
        if ($destinationA =~ /^(_*INBOUND_*:)?(\d+)$/o){
            $localip = '127.0.0.1';
            $destinationA = $localip .':'.$2;
        }
        if ($AVa<1) {
            $s=new IO::Socket::INET(Proto=>'tcp',PeerAddr=>$destinationA,Timeout=>2);
            if($s) {
                $AVa=1;
                $destination=$destinationA;
            }
            else {
                mlog(0,"*** $destinationfield $destinationA didn't work, trying others...") if $SessionLog;
            }
        }
    }
    if(! $s) {
        mlog(0,"couldn't create server socket to $destination -- aborting ReturnMail connection");
        return;
    }
    addfh($s,\&RMhelo);
    my $this=$Con{$s};
    $this->{to}=$from;
    $this->{from}=$EmailFrom;
    my $RM;
    (open($RM,"<","$file")) || mlog(0,"couldn't open '$file' for mail report");
    local $/="\n";
    my $subject;$subject=<$RM> if fileno($RM);
    $subject=~s/\s*(.*)\s*/$1 $sub/o;
    $this->{subject}=$subject;
    undef $/;
    $this->{body}="Report from: $user\r\n" if $user;
    $this->{body}.=<$RM> if fileno($RM);
    $this->{body}.= ref $bod ? $$bod : $bod;
    close $RM if fileno($RM);
    $this->{body}=~s/\r?\n/\r\n/go;
    $this->{body}=~s/[\r\n\.]+$//o;
    $this->{subject}=~s/\r?\n?//go;
    my $spamsub=$spamSubject;
    if($spamsub) {
        $spamsub=~s/(\W)/\\$1/go;
        $this->{subject}=~s/$spamsub *//gi;
    }

}

sub ReportIncludes {
    my $file = shift;
    $file = "$base/$file";
    open (my $F ,'<', $file) or return;
    my @ret;
    while (<$F>) {
        s/^$UTF8BOMRE//o;
        next unless /\s*#\s*include\s+([^\r\n]+)\r?\n/io;
        my $ifile = $1;
        $ifile =~ s/([^\\\/])[#;].*/$1/go;
        $ifile =~ s/[\"\']//go;
        push @ret , $ifile;
        my @inc = ReportIncludes($ifile);
        push @ret, @inc if @inc;
    }
    close $F;
    return @ret;
}

sub AdminReportMail {

    my($sub,$bod,$to)=@_;
    return if !$to;
    $to = &batv_remove_tag(0,$to,'');

    my $s;
    my $AVa;
    my $destination = $smtpDestination;
    $destination = $EmailReportDestination if $EmailReportDestination;
    $destination |= $sendAllDestination if $sendAllDestination;
    mlog(0,"error: destination for reports is not set, please configure EmailReportDestination...",1) if !$destination;
    return if !$destination;

    $AVa = 0;
    foreach my $destinationA (split(/\|/o, $destination)) {
        if ($destinationA =~ /^(_*INBOUND_*:)?(\d+)$/o){

            if ( $crtable{$localip} ) {
                $destinationA = $crtable{$localip};
                $destinationA .=  ":$2" if $destinationA !~ /:/;
            } else {
                $localip = '127.0.0.1';
                $destinationA = $localip . ':' . $2;
                   mlog(0,"warning: destination for reports is $destinationA, please configure EmailReportDestination...",1);

            }

        }

        $destinationA=~ s/\[::1\]/127\.0\.0\.1/ ;
        $destinationA=~ s/localhost/127\.0\.0\.1/i ;

        if ($AVa<1) {
            $s = $CanUseIOSocketINET6
                 ? IO::Socket::INET6->new(Proto=>'tcp',PeerAddr=>$destinationA,Timeout=>2,&getDestSockDom($destinationA))
                 : IO::Socket::INET->new(Proto=>'tcp',PeerAddr=>$destinationA,Timeout=>2);
            if($s) {
                $AVa=1;
                $destination=$destinationA;
            }
            else {
                mlog(0,"*** $destinationA didn't work, trying others...") if $SessionLog;
            }
        }

    }
    if(! $s) {
        mlog(0,"couldn't create server socket to '$destination' -- aborting AdminReport connection ");

        return;
    }
    eval {addfh($s,\&RMhelo);};
    my $this=$Con{$s};
    $this->{to}=$to;
    $this->{from}=$EmailFrom;

    local $/="\n";

    $this->{subject}=$sub;
    $this->{subject}=~s/\r?\n?//go;
    undef $/;

    $this->{body} = ref $bod ? $$bod : $bod;
    $this->{body} =~ s/[\r\n\.]+$//o;

}

sub RMhelo { my ($fh,$l)=@_;
    if($l=~/^ *220 /o) {
        sendque($fh,"HELO $myName\r\n");
        $Con{$fh}->{getline}=\&RMfrom;
    } elsif ($l=~/^ *220-/o) {
    } else {
        RMabort($fh,"helo Expected 220, got: $l (from:$Con{$fh}->{from} to:$Con{$fh}->{to})");
    }
}
sub RMfrom { my ($fh,$l)=@_;
    if($l=~/^ *250 /o) {
        sendque($fh,"MAIL FROM: ".($Con{$fh}->{from}=~/(<[^<>]+>)/o ? $1 : $Con{$fh}->{from})."\r\n");
        $Con{$fh}->{getline}=\&RMrcpt;
    } elsif ($l=~/^ *250-/o) {
    } else {
        RMabort($fh,"from Expected 250, got: $l (from:$Con{$fh}->{from} to:$Con{$fh}->{to})");
    }
}
sub RMrcpt { my ($fh,$l)=@_;
    if($l!~/^ *250/o) {
        RMabort($fh,"rcpt Expected 250, got: $l (from:$Con{$fh}->{from} to:$Con{$fh}->{to})");
    } else {
        sendque($fh,"RCPT TO: <$Con{$fh}->{to}>\r\n");
        $Con{$fh}->{getline}=\&RMdata;
    }
}
sub RMdata { my ($fh,$l)=@_;
    if($l!~/^ *250/o) {
        RMabort($fh,"data Expected 250, got: $l (from:$Con{$fh}->{from} to:$Con{$fh}->{to})");
    } else {
        sendque($fh,"DATA\r\n");
        $Con{$fh}->{getline}=\&RMdata2;
    }
}
sub RMdata2 { my ($fh,$l)=@_;
    if($l!~/^ *354/o) {
        RMabort($fh,"data2 Expected 354, got: $l");
    } else {
        my $date=$UseLocalTime ? localtime() : gmtime();
        my $tz=$UseLocalTime ? tzStr() : '+0000';
        $date=~s/(\w+) +(\w+) +(\d+) +(\S+) +(\d+)/$1, $3 $2 $5 $4/o;
        my $this=$Con{$fh};
        my $msgid = int(rand(1000000));

        sendque($fh,<<EOT);
From: $this->{from}\r
To: $this->{to}\r
Subject: $this->{subject}\r
X-Assp-Report: YES\r
Date: $date $tz\r
Message-ID: a$msgid\@$myName\r
\r
$this->{body}\r
.\r
EOT
        $Con{$fh}->{getline}=\&RMquit;
    }
}
sub RMquit { my ($fh,$l)=@_;
    if($l!~/^ *250/o) {
        RMabort($fh,"quit Expected 250, got: $l");
    } else {
        sendque($fh,"QUIT\r\n");
        $Con{$fh}->{getline}=\&RMdone;
        $Con{$fh}->{type} = 'C';          # start timeout watching for case 221/421 will not be send
        $Con{$fh}->{timelast} = time();
        $Con{$fh}->{nodelay} = 1;
    }
}
sub RMdone { my ($fh,$l)=@_;
    if($l!~/^ *[24]21/o) {
        RMabort($fh,"done Expected 221 or 421, got: $l");
    } else {
        mlog(0,"info: report successfully sent to ".$Con{$fh}->{to}) if $ReportLog;
        done2($fh); # close and delete
    }
}

sub RMabort {mlog(0,"RMabort: $_[1] - report to ". $Con{$_[0]}->{to}); done2($_[0]);}

########################################### #  Null-Device emulation ###################

sub NullFromToData { my ($fh,$l)=@_;
    d('NullFromToData');
    ($Con{$fh}->{lastcmd}) = $l =~ /^([^\s]+)/o;
    push(@{$Con{$fh}->{cmdlist}},$Con{$fh}->{lastcmd}) if $ConnectionLog >= 2;
    if($l=~/^DATA/io) {
        $Con{$fh}->{getline}=\&NullData;
        sendque($fh,"354 send data\r\n");
    } elsif ($l=~/^HELO|EHLO/io){
        sendque($fh,"220 OK - $myName ready\r\n");
    } elsif ($l=~/^RSET/io){
        &stateReset($fh);
        sendque($Con{$fh}->{friend},"RSET\r\n");
        $Con{$fh}->{getline}=\&getline;
    } elsif ($l=~/^MAIL FROM:/io){
        $Con{$fh}->{getline}=\&getline;
        &getline($fh,$l);
    } elsif ($l=~/^QUIT/io){
        sendque($fh,"221 <$myName> closing transmission\r\n");
        $Con{$fh}->{closeafterwrite} = 1;
        done2($Con{$fh}->{friend}); # close and delete
    } elsif ($l=~/^(?:NOOP|HELP|RCPT TO:)/io) {
        sendque($fh,"250 OK\r\n");
    } else {
        sendque($fh,"502 command not implemented\r\n");
    }
}

sub NullData { my ($fh,$l)=@_;
    d('NullData');
    $Con{$fh}->{headerpassed} = 1;
    if ($Con{$fh}->{header} ne 'NULL') {
        $Con{$fh}->{header} .= $l;
        $Con{$fh}->{maillength} += length($l);
    }
    if ( $l =~ /^\.[\r\n]/ || defined( $Con{$fh}->{bdata} ) && $Con{$fh}->{bdata} <= 0 ) {
        sendque($fh,"250 OK\r\n");
        $Con{$fh}->{getline}=\&NullFromToData;
    }
}

############################################ blockreport ###################

sub BlockReportSend {
    my ( $fh, $to, $for, $subject, $bod ) = @_;
    my ( $sfh, $sto, $sfor, $ssubject, $sbod ) = @_;

    my $RM;
    my $this     = $Con{$fh};
    my $mailfrom = $this->{mailfrom};

    $mailfrom = $EmailFrom if ( lc $mailfrom eq lc $EmailAdminReportsTo );

    if (! $CanUseNetSMTP) {
        mlog(0,"error: Perl module Net::SMTP is not installed or disabled in configuration - assp is unable to send the BlockReport");
        return;
    }

    $bod     =~ s/\r?\n/\r\n/go;
    $subject =~ s/\r?\n?//go;

    my $destination;
    my $local = 1;
    if ( $EmailReportDestination ne '' ) {
        $destination = $EmailReportDestination;
    } else {
        $destination = $smtpDestination;
        if (! localmail($to) && $relayHost) {
            $destination = $relayHost;
            $local = 0;
        }
    }

    my $brmsgid = 'assp_bl_'.time.'_'.rand(1000).'@'.$myName;
    my $smtp;
    my $SMTPMOD;
    foreach my $MTA ( split( /\|/o, $destination ) ) {
        if ( $MTA =~ /^(_*INBOUND_*:)?(\d+)$/o ) {
            $MTA     = '127.0.0.1:' . $2;
        }

        my $TLS = 0;
        my ($mtaIP) = $MTA =~ /^($IPRe)/o;
        if (   $CanUseNetSMTPTLS
            && $DoTLS == 2
            && ! exists $localTLSfailed{$MTA}
            && ! $this->{blNoTLS}
            && ! matchIP($mtaIP,'noTLSIP',1)
           )
        {
            $SMTPMOD = 'Net::SMTP::TLS';
            mlog(0,"BlockReport-send: will try to use TLS connection to $MTA") if $ConnectionLog >= 2;
            $TLS = 1;
        } else {
            $SMTPMOD = 'Net::SMTP';
        }

        eval {
            $smtp = $SMTPMOD->new(
                $MTA,
                Debug => ($TLS ? $SSLDEBUG : $debug),
                Hello   => $myName,
                Timeout => ($TLS ? $SSLtimeout: 120),   # 120 is the default in Net::SMTP
                NoTLS => 1
            );
            if ($smtp) {
                my $fh = $TLS ? $smtp->{sock} : $smtp;
                $TLS && exists $smtp->{features}->{STARTTLS} && eval{$smtp->starttls();};
                $localTLSfailed{$MTA} = time if ($@);
                if ($TLS && ! $local && $relayAuthUser && $relayAuthPass ) {
                    $smtp->{User} = $relayAuthUser;
                    $smtp->{Password} = $relayAuthPass;
                    $smtp->login();
                }
                my $timeout = (int(length($bod) / (1024 * 1024)) + 1) * 60; # 1MB/min
                $smtp->auth($relayAuthUser,$relayAuthPass) if(! $TLS && ! $local && $relayAuthUser && $relayAuthPass);
                $smtp->mail($mailfrom);
                $smtp->to($to);
                $smtp->data();
                my $blocking = $fh->blocking(0);
                NoLoopSyswrite($fh,"To: $to\r\n") or die "$!\n";
                NoLoopSyswrite($fh,"From: $mailfrom\r\n") or die "$!\n";
                NoLoopSyswrite($fh,"Subject: $subject\r\n") or die "$!\n";
                NoLoopSyswrite($fh,"Message-ID: $brmsgid\r\n") or die "$!\n";
                NoLoopSyswrite($fh,$bod . "\r\n",$timeout) or die "$!\n";
                $fh->blocking($blocking);
                $smtp->dataend();
                $smtp->quit;
            }
        };
        if ( $smtp && !$@ ) {
            mlog( 0, "info: sent block report for $for to $to at $MTA" )
              if $ReportLog >= 2;
            last;
        }
    }
    if ( !$smtp or $@ ) {
        mlog( 0, "error: couldn't send block report for $for to $to at $destination using $SMTPMOD - $@",1) if $ReportLog;
        if ($SMTPMOD eq 'Net::SMTP::TLS') {
            mlog( 0, "try to use Net::SMTP to send block report") if $ReportLog;
            $this->{blNoTLS} = 1;
            BlockReportSend( $sfh, $sto, $sfor, $ssubject, $sbod );
        }
    }
}

sub BlockedMailResend {
    my ( $fh, $filename , $special) = @_;
    my $this = $Con{$fh};
    my $infile;
    my $outfile;
    my $pastheader;
    my $sender;
    d("BlockedMailResend - $filename");

    return unless ($resendmail);
    return unless ($CanUseEMS);

    $special =~ s/[(\[][^(\[)\]]*[)\]]//io;
    my ($resfile) = $filename =~ /([^\\|\/]+\Q$maillogExt\E)$/i;
    my $fname = $resfile;
    my $corrNotSpamFile = "$base/$correctednotspam/$resfile";
    $resfile = "$base/$resendmail/$resfile";
    if ( $filename !~ /[\\|\/]+$spamlog[\\|\/]+/ ) {
        $corrNotSpamFile = '';
    }
    unless ($open->($outfile,'>' ,$resfile)) {
        mlog( 0, "error: unable to open output file ".de8($resfile)." - $!" ) if $ReportLog;
        return;
    }
    my $foundDir;
    if (!($open->($infile,'<',$filename)) && !$doMove2Num) {    # if the original file is not found, try to find it anywhere
        foreach ($spamlog,$discarded,$notspamlog,$incomingOkMail,$viruslog,$correctedspam,$correctednotspam,
                 "rebuild_error/$spamlog","rebuild_error/$notspamlog","rebuild_error/$correctedspam","rebuild_error/$correctednotspam") {
            next unless $_;
            ($open->($infile,'<',"$base/$_/$fname")) and ($foundDir = $_) and last;
        }
    }
    unless ( $infile->fileno ) {
        mlog( 0, "error: can't open requested file ".de8($fname)." in any collection folder" ) if $ReportLog;
        local $/ = "\r\n";
        $filename =~ s/^.*?\/?([^\/]*\/?[^\/]+)$/$1/o;
        $outfile->print( <<EOT );
From: $EmailFrom
To: $this->{mailfrom}
Subject: failed - request ASSP to resend blocked mail

The requested email-file $filename no longer exists on ASSP-host $myName.
Please contact your email adminstrator, if you need more information.

.
EOT
        $outfile->close;
        undef local $/;
        $nextResendMail =
          $nextResendMail < time + 3 ? $nextResendMail : time + 3;
        return;
    }

    my $foundRecpt;
    my $requester;
    $foundRecpt = 1
      if ( matchSL( $this->{mailfrom}, 'EmailAdmins', 1 )
        or lc( $this->{mailfrom} ) eq lc($EmailAdminReportsTo)
        or lc( $this->{mailfrom} ) eq lc($EmailBlockTo)
        or ($requester = matchSL( $this->{mailfrom}, 'EmailResendRequester', 1 ))
        );

    $foundDir = $viruslog if (! $foundDir && $viruslog && $filename =~ /^$base\/$viruslog\//);
    if (!$foundRecpt && $viruslog && $foundDir eq $viruslog) {
        mlog( 0, "warning: resend for file $filename denied - found it in viruslog folder $viruslog" ) if $ReportLog;
        local $/ = "\r\n";
        $filename =~ s/^.*?\/?([^\/]*\/?[^\/]+)$/$1/o;
        $outfile->print( <<EOT );
From: $EmailFrom
To: $this->{mailfrom}
Subject: denied - request ASSP to resend blocked mail

The requested email-file $filename on ASSP-host $myName possibly contains a virus!
Please contact your email adminstrator, if you need more information.

.
EOT
        $outfile->close;
        undef local $/;
        $nextResendMail =
          $nextResendMail < time + 3 ? $nextResendMail : time + 3;
        return;
    }
    $outfile->binmode;
    my $lastline;
    my $Skip; $Skip = 1 if $foundRecpt;
    while ( my $line = (<$infile>)) {
        my $text;
        my $adr;
        $line =~ s/\r|\n//go;
        next if !$Skip && $line =~ /X-Assp-Intended-For/io;
        if ( !$pastheader ) {
            if ( $line =~ /([^:]+)(:).*?($EmailAdrRe\@$EmailDomainRe)/o ) {
                $text = $1 . $2;
                $adr  = $3;
                $sender = lc($adr) if ( $text =~ /^from:/io );
                next if (!$Skip && ( $text =~ /^cc:/io or $text =~ /^bcc:/io ) );
                next if (!$Skip && ( $text =~ /^to:/io
                        && lc($adr) ne lc( $this->{mailfrom} ) ));
                next if ($text =~ /^to:/io && ! &localmail($adr));
                $foundRecpt = 2 if ( $text =~ /^to:/io
                                     && lc($adr) eq lc( $this->{mailfrom} ) );
                $foundRecpt = 2 if ( $text =~ /^to:/io && $Skip );
            }
        }
        if ( $line eq '' && !$pastheader ) {
            $pastheader = 1;
            if ( $foundRecpt < 2 ) {
                $outfile->print( "To: <$this->{mailfrom}>\r\n");
                $foundRecpt = 2;
            }
            $outfile->print("X-Assp-Resend-Blocked: $myName\r\n");
        }
        $outfile->print("$line\r\n");
        $lastline = 1 if ( $line eq '.' );
    }
    $outfile->print("\r\n.\r\n") unless $lastline;
    $infile->close;
    $outfile->close;

    if ( $autoAddResendToWhite && $sender && !&localmail($sender)) {
        if (   matchSL( $this->{mailfrom}, 'EmailAdmins', 1 )
            or lc( $this->{mailfrom} ) eq lc($EmailAdminReportsTo)
            or lc( $this->{mailfrom} ) eq lc($EmailBlockTo) )
        {
            if ( $autoAddResendToWhite > 1 && $special !~ /(?:don'?t|no)[^,]*whit/io ) {
                &Whitelist($sender,undef,'add');
                mlog( 0, "info: whitelist addition on resend: $sender" )
                  if $ReportLog;
            }
        } elsif ( $autoAddResendToWhite != 2 && $special !~ /(?:don'?t|no)[^,]*whit/io ) {
            &Whitelist($sender,undef,'add');
            mlog( 0, "info: whitelist addition on resend: $sender" )
              if $ReportLog;
        }
    }

    if ( $corrNotSpamFile && $DelResendSpam && $special !~ /(?:don'?t|no)[^,]*(?:del|rem|move)/io) {
        $filename =~ s/\\/\//go;
        $corrNotSpamFile =~ s/\\/\//go;
        if (   matchSL( $this->{mailfrom}, 'EmailAdmins', 1 )
            or lc( $this->{mailfrom} ) eq lc($EmailAdminReportsTo)
            or lc( $this->{mailfrom} ) eq lc($EmailBlockTo) )
        {
            $move->( $filename, $corrNotSpamFile ) and $ReportLog or
            mlog(0,"error: unable to move $filename to $corrNotSpamFile - $!" );
        } else {
            $unlink->($filename) and $ReportLog or
            mlog(0,"error: unable to delete $filename - $!" );
        }
    }
    $nextResendMail = $nextResendMail < time + 3 ? $nextResendMail : time + 3;
}

sub BlockReportGen {
    my ( $now, $brfile ) = @_;
    srand(time);
    my $fh = int( rand(time) );    # a dummy $fh for a dummy $Con{$fh}
    my $filename;
    my $number;
    my @lines;
    my $userq;
    d('BlockReportGen');
    return unless $CanUseNetSMTP;
    ($filename) = $BlockReportFile =~ /file:(.+)/io if $BlockReportFile;
    if ( $now eq 'USERQUEUE' ) {
        $now      = '';
        $userq    = 1;
        $filename = "files/UserBlockReportQueue.txt";
    }
    if ( $now eq 'INSTANTLY' ) {
        $now      = '';
        $userq    = 1;
        $filename = "files/UserBlockReportInstantQueue.txt";
    }

    $filename =
      $brfile
      ? "email block report list request from " . $Con{$brfile}->{mailfrom}
      : "$base/$filename";
    if ( ! $brfile ) {
        return if ! -e "$filename" or -d "$filename" or ! (open $brfile,'<' ,"$filename");
    }
   # mlog( 0, "info: generating block reports from $filename" );

    while (<$brfile>) {
        s/\r|\n//go;
        my $cline = $_;
        my $comment; $comment = $1 if s/\s*#(.*)//go;

        if ( !$_ ) {
            push( @lines, $cline );
            next;
        }

        my $entrytime;
        if ( $comment =~ /^\s*next\srun\s*\:\s*(\d+)[\-|\.](\d+)[\-|\.](\d+)/o )
        {
            my $year = $1;
            $year += $year < 100 ? 2000 : 0;
            eval { $entrytime = timelocal( 0, 0, 0, $3, $2 - 1, $1 - 1900 ); };
            if ($@) {
                mlog( 0,"error: wrong syntax in next-run-date (yyyy-mm-dd) at line <$cline> in $filename - $@")
                 if $ReportLog;
                $entrytime = 0;
            }
            if ( time < $entrytime && !$now ) {
                push( @lines, $cline );
                next;
            }
        }
        my ( $addr, $to, $numdays, $exceptRe, $sched) = split( /\=\>/o, $_ );
        if ( $addr =~ /^\s*\#/o ) {
            push( @lines, $cline );
            next;
        }
        $to = '' if ( $to =~ /\s*\*\s*/o );
        if ( $to && $to !~ /\s*($EmailAdrRe\@$EmailDomainRe)\s*/go ) {
            mlog( 0,"error: syntax error in send to address in $filename in entry $_" )
             if $ReportLog;
            push( @lines, $cline );
            next;
        }
        $to = $1 if $to =~ /\s*($EmailAdrRe\@$EmailDomainRe)\s*/go;
        ($numdays) = $numdays =~ /\s*(\d+)\s*/o;
        $numdays = 1 unless $numdays;
        if ( $addr !~ /.*?(\[?$EmailAdrRe|\*)\@($EmailDomainRe\]?|\*)/go ) {
            mlog( 0,"error: syntax error in report address '$addr' in BlockReportFile ");
            push( @lines, $cline );
            next;
        }

        if ( !$now ) {
            if ( !$entrytime ) {
                my $time = time;
                my $dayoffset = $time % ( 24 * 3600 );
                $entrytime = $time - $dayoffset;
            }
            $entrytime = $numdays * 24 * 3600 + $entrytime;
            my (
                $second,    $minute,    $hour,
                $day,       $month,     $yearOffset,
                $dayOfWeek, $dayOfYear, $daylightSavings
            ) = localtime($entrytime);
            my $year = 1900 + $yearOffset;
            $month++;
            if ($userq) {
                if (
                    $comment =~ /^\s*next\srun\s*\:\s*\d+[\-|\.]\d+[\-|\.]\d+/o )
                {
                    push( @lines, "$_ # next run: $year-$month-$day" );
                }
            } else {
                push( @lines, "$_ # next run: $year-$month-$day" );
            }
        } else {
            push( @lines, $cline );
        }
        if ($sched && ! $RunTaskNow{BlockReportNow}) {
            next;
        }
        my $mto;
        $mto = "to send it to $to" if $to;
        my $mfor = $addr;
        $mfor = "Group $addr" if $addr =~ /\[/o;
        mlog( 0, "info: generating block reports ($numdays) for $mfor $mto" )
          if $ReportLog >= 2;
        $Con{$fh}->{mailfrom} = $EmailAdminReportsTo;    # set to get all lines
        $Con{$fh}->{header} = "$addr=>$to=>$numdays=>$exceptRe\r\n";
        my $isGroup = $addr =~ s/\[(.+)\]/$1/o;

        my %user;
        &BlockReasonsGet( $fh, $numdays , \%user, $exceptRe);
        my @textreasons;
        my @htmlreasons;
        my $count;

        push( @textreasons, $user{sum}{textparthead} );
        push( @htmlreasons, $user{sum}{htmlparthead} );
        push( @htmlreasons, $user{sum}{htmlhead} );
        foreach  my $ad ( sort keys %user ) {
            next if ( $ad eq 'sum' );
            $number = scalar @{ $user{$ad}{text} } + $user{$ad}{correct};
            $number = 0 if $number < 0;
            $count += $number;
            $number = 'no' unless $number;
            my $rcpt = $to;
            if ( $addr !~ /\*/o or ( $addr =~ /\*/o and !$to ) ) {
                $rcpt = $to ? $to : $addr;
                $rcpt = $rcpt =~ /\*/o ? $ad : $rcpt;
            }
            push( @textreasons,
                &BlockReportText( 'text', $ad, $numdays, $number, $rcpt ) );
            my $userhtml =
              &BlockReportText( 'html', $ad, $numdays, $number, $rcpt );
            push( @htmlreasons,  BlockReportHTMLTextWrap(<<"EOT"));
<table id="report">
 <col /><col /><col />
 <tr>
  <th colspan="3" id="header">
   <img src=cid:1001 alt="powered by ASSP on $myName">
   $userhtml
  </th>
 </tr>
EOT
            while ( @{ $user{$ad}{text} } ) { push( @textreasons, shift @{ $user{$ad}{text} } ); }
            while ( @{ $user{$ad}{html} } ) { push( @htmlreasons, BlockReportHTMLTextWrap(shift @{ $user{$ad}{html} })); }
            if ( ($addr !~ /\*/o && ! $isGroup) or ( $addr =~ /\*/o and !$to ) ) {
                push( @textreasons, $user{sum}{text} );
                push( @htmlreasons, $user{sum}{html} );
                @textreasons = () if ( $BlockReportFormat == 2 );
                @htmlreasons = () if ( $BlockReportFormat == 1 );
                BlockReportSend(
                    $fh,
                    $rcpt,
                    $ad,
                    &BlockReportText( 'sub', $ad, $numdays, $number, $rcpt ),
                    $BlModify->($user{sum}{mimehead}
                      . join( '', @textreasons )
                      . join( '', @htmlreasons )
                      . $user{sum}{mimebot})
                ) if $count;
                @textreasons = ();
                @htmlreasons = ();

                push( @textreasons, $user{sum}{textparthead} );
                push( @htmlreasons, $user{sum}{htmlparthead} );
                push( @htmlreasons, $user{sum}{htmlhead} );
                $count = 0;
                next;
            }
        }
        if ($count) {
            push( @textreasons, $user{sum}{text} );
            push( @htmlreasons, $user{sum}{html} );
            @textreasons = () if ( $BlockReportFormat == 2 );
            @htmlreasons = () if ( $BlockReportFormat == 1 );
            BlockReportSend(
                $fh,
                $to,
                $addr,
                &BlockReportText( 'sub', $addr, $numdays, $count, $to ),
                $BlModify->($user{sum}{mimehead}
                  . join( '', @textreasons )
                  . join( '', @htmlreasons )
                  . $user{sum}{mimebot})
            );
        } else {
            if ( $addr =~ /\*/o and $to ) {
                my $for = $addr;
                $addr =~ s/\*\@//o;
                push( @textreasons,
"---------------------------------- $addr -----------------------------------\n\n"
                );
                push( @htmlreasons,BlockReportHTMLTextWrap(
"---------------------------------- $addr -----------------------------------<br />\n<br />\n")
                );
                push( @textreasons,
"\nno blocked email found for domain $addr in the last $numdays day(s)\n\n"
                );
                push( @htmlreasons,
"<br />\nno blocked email found for domain $addr in the last $numdays day(s)<br />\n<br />\n"
                );
                push( @textreasons, $user{sum}{text} );
                push( @htmlreasons, $user{sum}{html} );
                @textreasons = () if ( $BlockReportFormat == 2 );
                @htmlreasons = () if ( $BlockReportFormat == 1 );
                BlockReportSend(
                    $fh,
                    $to,
                    $for,
                    &BlockReportText( 'sub', $for, $numdays, $number, $to ),
                    $BlModify->($user{sum}{mimehead}
                      . join( '', @textreasons )
                      . join( '', @htmlreasons )
                      . $user{sum}{mimebot})
                );
            }
        }
        mlog( 0,
            "info: finished generating block reports ($numdays) for $addr $mto"
        ) if $ReportLog >= 2;

        @textreasons = ();
        @htmlreasons = ();
        %user        = ();
        delete $Con{$fh};
    }
    close $brfile;
    delete $Con{$fh};
    $filename="$base/$filename" if $filename!~/^\Q$base\E/io;
    if ( !$now && (open $brfile,'>' ,"$filename")) {
        binmode $brfile;
        print $brfile join("\n",@lines);
        print $brfile "\n";
        close $brfile;
    } elsif (! $now && $!) {
        mlog(0,"warning: error writing file $base/$filename - $!");
    }

}

sub BlockReasonsGet {
    my ( $fh, $numdays , $buser, $exceptRe) = @_;
    my $this = $Con{$fh};
    d("BlockReasonsGet - numdays: $numdays - exceptRe: $exceptRe",1);
    my $isadmin = 0;
    my @to;
    my @from;
    my $toRe;
    my $fromRe;
    my %exceptRe;
    my $webAdminPort = [split(/\s*\|\s*/o,$webAdminPort)]->[0];
    $webAdminPort =~ s/\s//go;
    $webAdminPort = $1 if $webAdminPort =~ /^$HostPortRe\s*:\s*(\d+)/o;
    my $prot =  $enableWebAdminSSL && $CanUseIOSocketSSL? 'https' : 'http';
    my $host = $BlockReportHTTPName ? $BlockReportHTTPName : $localhostname ? $localhostname : 'please_define_BlockReportHTTPName';
    my $BRF = ($BlockReportFilter) ? $BlockReportFilterRE : '';
    $exceptRe =~ s/\$BRF/$BRF/ig;
    $exceptRe =~ s/BRF/$BRF/g;
    $exceptRe =~ s/\|\|+/\|/go;
    $exceptRe =~ s/^\|//o;
    $exceptRe =~ s/\|$//o;
    my $mimetime=$UseLocalTime ? localtime() : gmtime();
    my $tz=$UseLocalTime ? tzStr() : '+0000';
    $mimetime=~s/... (...) +(\d+) (........) (....)/$2 $1 $4 $3/o;
    $EmailBlockReportDomain = '@' . $EmailBlockReportDomain
      if $EmailBlockReportDomain !~ /^\@/o;
    my $relboundary = '=======_00_ASSP_1298347655_======';
    my $boundary    = '=======_01_ASSP_1298347655_======';
    my $mimehead    = <<"EOT";
Date: $mimetime $tz
MIME-Version: 1.0
EOT
    $mimehead .= <<"EOT" if ( $BlockReportFormat != 1 );
Content-Type: multipart/related;
    boundary=\"$relboundary\"

--$relboundary
EOT
    $mimehead .= <<"EOT";
Content-Type: multipart/alternative;
    boundary=\"$boundary\"

EOT
    my $mimebot = "\r\n--$boundary--\r\n";
    $mimebot .= <<"EOT" . &BlockReportGetImage('blockreport.gif') . "\r\n" if ( $BlockReportFormat != 1 );

--$relboundary
Content-Type: image/gif
Content-ID: <1001>
Content-Transfer-Encoding: base64

EOT

    $mimebot .= <<"EOT" . &BlockReportGetImage('blockreporticon.gif') . <<"EOT2" if ( $BlockReportFormat != 1 );

--$relboundary
Content-Type: image/gif
Content-ID: <1000>
Content-Transfer-Encoding: base64

EOT
--$relboundary--

EOT2

    my $textparthead = <<"EOT";

--$boundary
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: Quoted-Printable

EOT

    if ( $BlockReportFormat == 0 ) {
        $textparthead .= <<"EOT";
For a better view of this email - please enable html in your client!

EOT
    }

    my $htmlparthead = <<"EOT";

--$boundary
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: Quoted-Printable

EOT
    my $htmlhead = &BlockReportHTMLTextWrap(<<'EOT' . <<"EOT1" . &BlockReportGetCSS()) . <<'EOT2'; eval(<<'WHITCHWORKER');

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
EOT
<title>Spam filtering block report from $myName</title>
EOT1

<script type=3D"text/javascript">
var show =3D 'inline';
function changeview(value) {
    var ht1 =3D new Array();
    ht1 =3D document.getElementsByName("ht1id");
    for (var i =3D 0; i < ht1.length; ++i) {
        ht1[i].style.display =3D value;
    }
}
</script>
</head>
<body>
<input type="button" name="toggle" value="toggle view" onclick="show=((show=='none')?'inline':'none');changeview(show);return false;"
 title="click the button to simplify or to extend the BlockReport view - requires javascript to be enabled in your mail clients HTML view">
<br />
EOT2
my $rt;($rt = 1) and $htmlhead =~ s/(\x68)(\164)(\d+)/${$rt+1}\157${$rt}/go;
WHITCHWORKER
    if (   matchSL( $this->{mailfrom}, 'EmailAdmins', 1 )
        or lc( $this->{mailfrom} ) eq lc($EmailAdminReportsTo)
        or lc( $this->{mailfrom} ) eq lc($EmailBlockTo) )
    {
        $isadmin = 1;
        my %hfrom = ();
        my %hto = ();
        foreach (split( "\r\n", $this->{header} )) {
            if (/^(.*?)((?:\[?$EmailAdrRe|\*)\@(?:$EmailDomainRe\]?|\*))(.*)$/o) {
                my $text = $1;
                my $addr = $2;
                my $how  = $3;
                next if $text =~ /:/o;
                next if $text =~ /^\s*#/o;
                next if $text =~ /=>/o;
                my @adr;
                if ($addr =~ s/^\[(.+)\]$/$1/o) {
                    @adr = map {my $t = $_; $t =~ s/^\s+//o; $t =~ s/\s+$//o;$t;} split(/\|/o,$GroupRE{lc $addr});
                } else {
                    push @adr, $addr;
                }
                while (@adr) {
                    $addr = shift @adr;
                    if ( $addr !~ /\@\*/o && ! &localmail($addr) ) {
                        if ( $how =~ /^\s*=>\s*$EmailAdrRe\@$EmailDomainRe/o ) {
                            push( @from , lc($addr) ) unless $hfrom{ lc($addr) };
                            $hfrom{ lc($addr) } = 1;
                        } else {
                            mlog( 0,"warning: ignoring entry: $_ for report - no recipient defined")
                              if $ReportLog;
                        }
                    } else {
                        push (@to , lc($addr) ) unless $hto{ lc($addr) };
                        $hto{ lc($addr) } = 1;
                        $isadmin = 0
                          if ( $how =~ /^\s*=>\s*($EmailAdrRe\@$EmailDomainRe)/o &&
                             ! ( lc( $1 ) eq lc($EmailAdminReportsTo) or
                                 lc( $1 ) eq lc($EmailBlockTo) or
                                 matchSL( $1, 'EmailAdmins', 1 )
                               )
                             );
                        $isadmin = 'user'
                          if ( $how !~ /^\s*=>\s*$EmailAdrRe\@$EmailDomainRe/o );

                        $addr = lc $addr;
                        $addr =~ s/\*\@/$EmailAdrRe\@/go;
                        $addr =~ s/\@\*/\@$EmailDomainRe/go;
                        if ( $how =~ /^\s*=>.*?=>.*?=>\s*(.*?)\s*$/o && $1) {
                            my $ere = $1;
                            $ere =~ s/\$BRF/$BRF/ig;
                            $ere =~ s/BRF/$BRF/g;
                            $ere =~ s/\|\|+/\|/go;
                            $ere =~ s/^\|//o;
                            $ere =~ s/\|$//o;
                            $exceptRe{$addr} = $ere if $ere;
                            $exceptRe{$addr} .=  '|' . $exceptRe if ($exceptRe && $exceptRe ne $ere);
                        } else {
                            $exceptRe{$addr} = $exceptRe if ($exceptRe);
                        }
                    } # end else
                } # end while
            } # end record
        } # end forech
        $toRe  =  BlockReportFormatAddr(@to);
        $fromRe = BlockReportFormatAddr(@from);
        if ( !($toRe or $fromRe) && $this->{mailfrom}) {
            if( exists $GroupRE{lc $this->{mailfrom}} ) {
                @to = map {my $t = $_; $t =~ s/^\s+//o; $t =~ s/\s+$//o;$t;} split(/\|/o,$GroupRE{lc $this->{mailfrom}});
                $toRe = BlockReportFormatAddr(@to);
                foreach (@to) {
                    $exceptRe{lc $_} = $exceptRe if ($exceptRe);
                }
            } else {
                $toRe = quotemeta( $this->{mailfrom} );
                @to = ($this->{mailfrom});
                $exceptRe{lc $this->{mailfrom}} = $exceptRe if ($exceptRe);
            }
        }
    } elsif ($this->{mailfrom}) {
        if( exists $GroupRE{lc $this->{mailfrom}} ) {
            @to = map {my $t = $_; $t =~ s/^\s+//o; $t =~ s/\s+$//o;$t;} split(/\|/o,$GroupRE{lc $this->{mailfrom}});
            $toRe = BlockReportFormatAddr(@to);
            foreach (@to) {
                $exceptRe{lc $_} = $exceptRe if ($exceptRe);
            }
        } else {
            $toRe = quotemeta( $this->{mailfrom} );
            @to = ($this->{mailfrom});
            $exceptRe{lc $this->{mailfrom}} = $exceptRe if ($exceptRe);
        }
    }
    if ( !$toRe && !$fromRe ) {
        mlog( 0, "error: BlockReport is unable to parse for a valid report address" );
        return;
    }
    local $/ = "\n";
    my ( $date, $day, $gooddays, $address, $faddress );

    my ( $logdir, $logdirfile ) = $logfile =~ /^(.*[\/\\])?(.*?)$/o;
    my @logfiles;
    @logfiles = sort( Glob("$base/$logdir*b$logdirfile")) if ($ExtraBlockReportLog && ! $fromRe);
    unless (@logfiles) {
        my @logfiles1 = sort( Glob("$base/$logdir*$logdirfile"));
        while (@logfiles1) {
            my $k = shift @logfiles1;
            push(@logfiles, $k) if $k !~ /b$logdirfile/;
        }
    }

    my $time = Time::HiRes::time();
    my $dayoffset = $time % ( 24 * 3600 );
    my $sdate;
    for ( my $i = 0 ; $i < $numdays + 1 ; $i++ ) {
        $gooddays .= '|' if ( $i > 0 );
        $day = &timestring( $time - $i * 24 * 3600 , 'd');
        $sdate .= "'$day', ";
        $gooddays .= quotemeta($day);
    }
    my $timeformat = $LogDateFormat;
    my $dateformat = $LogDateFormat;
    $dateformat =~ s/[^YMD]*(?:hh|mm|ss)[^YMD]*//go;
    $timeformat =~ s/$dateformat//go;
    $timeformat =~ s/h|m|s/\\d/go;

    chop $sdate; chop $sdate;
    mlog( 0, "info: search dates are: $sdate" ) if $MaintenanceLog >= 2 or $ReportLog >= 2;
    undef $day;
    my $lines;
    my $numfiles;
    my $FLogFile;
    my $bytes;
    my %ignoreAddr;
    my $runtime = time;
    &matchSL(\@to,'BlockResendLinkLeft',1);
    &matchSL(\@to,'BlockResendLinkRight',1);

    if ($ReportLog > 2) {
        mlog(0,"info: BlockReport global filter: $exceptRe");
        while (my ($k,$v) = each %exceptRe) {
            mlog(0,"info: BlockReport filter list: '$k' = '$v'");
        }
    }

    while (my $File  = shift @logfiles) {
        my $ftime = ftime($File) || time;
        next if ( ( $ftime + $numdays * 24 * 3600 ) <= ( $time - $dayoffset ) );
        if ( !(open( $FLogFile, '<', "$File" )) ) {
            sleep 2;
            if ( !(open( $FLogFile, '<', "$File" )) ) {
                mlog( 0,
"warning: report is possibly incomplete, because ASSP is unable to open logfile $File"
                ) if $ReportLog;
                $buser->{sum}{html} .=
"<br />\nwarning: report is possibly incomplete, because ASSP is unable to open logfile $File";
                $buser->{sum}{text} .=
"\r\nwarning: report is possibly incomplete, because ASSP is unable to open logfile $File";
                next;
            }
        }
        mlog( 0, "info: searching in logfile $File" ) if $MaintenanceLog >= 2 or $ReportLog >= 2;
        $numfiles++;
        my $fl;
        my $start = time;
        while ( $fl = <$FLogFile> ) {
            if ($BlockMaxSearchTime && time - $start > $BlockMaxSearchTime) {
                mlog(0,"warning: blockreport search in file $File has taken more than 3 minutes - skip the file") if $ReportLog;;
                $buser->{sum}{html} .=
"<br />\nwarning: report is possibly incomplete, because ASSP was skipping some parts of logfile $File";
                $buser->{sum}{text} .=
"\r\nwarning: report is possibly incomplete, because ASSP was skipping some parts of logfile $File";
                last;
            }
            $bytes += length($fl);
            $fl =~ s/\r*\n//go;
            $lines++;
            $address  = '';
            $faddress = '';
            unless (   $toRe
                    && ( ( $date, $address ) = $fl =~ /^($gooddays) .*?\s$IPRe[ \]].*?\sto:\s($toRe)\s\[\s*spam\sfound\s*\]/i)
                   )
            {
                next unless (   $fromRe
                             && ( ( $date, $faddress ) =  $fl =~ /^($gooddays) .*?\s$IPRe[\]]?\s<($fromRe)>/i)
                            );
            }
            if ($address) {
                next if ( $fl =~ m/local\sor\swhitelisted|message\sok/io )

                     || ( $fl =~ m/\[testmode\]/io && ! $allTestMode)
                     || ( $fl =~ m/\[local\]/io )
                     || ( $fl =~ m/\[attachment\]/io )
                     || ( $fl =~ m/\[whitelisted\]/io )
                     || ( $fl =~ m/\[noprocessing\]/io )
                     || ( $fl =~ m/\[lowconfidence\]/io )
                     || ( $fl =~ m/\[tagmode\]/io )
                     || ( $fl =~ m/\[trap\]/io )
                     || ( $fl =~ m/\[collect\]/io )
                     || ( $fl =~ m/\[sl\]/io )
                     || ( $fl =~ m/\[spamlover\]/io )
                     || ( $fl =~ m/\[lowlimit\]/io )
                     || ( $fl =~ m/\[warning\]/io );
                my $match = 0;
                foreach my $re (keys %exceptRe) {
                    if (eval{$address =~ /$re/i;}) {
                        $match = $re;
                        last;
                    }
                }
                if ($match) {
                    if ($fl =~ m/$exceptRe{$match}/i) {
                        my $s = (++$buser->{lc($address)}{filtercount} > 1) ? 's' : '';
                        $buser->{lc($address)}{filter} = $buser->{lc($address)}{filtercount}." line$s skipped on defined filter regex '$exceptRe{$match}'";
                        next;
                    }
                } else {
                    my @res;
                    if ($BlockReportFilter && ((@res) = $fl =~ /($BlockReportFilterRE)/g)) {
                        my $nres = $res[0];
                        unless (scalar @res == 1
                                && $address =~ /\Q$nres\E/i
                                && ! grep(/\*/o,@to)
                               )
                        {
                            my $s = (++$buser->{lc($address)}{filtercount2} > 1) ? 's' : '';
                            $buser->{lc($address)}{filter2} = $buser->{lc($address)}{filtercount2}." line$s skipped on global defined filter regex 'BlockReportFilter'";
                            next;
                        }
                    }
                }
                $fl =~ s/\sto:\s(?:$toRe)//i;
            } else {    # $faddress is OK
                $address = $faddress;
            }

            my $is_admin = 0;
            $is_admin = 1 if $isadmin == 1;
            $is_admin = 1
              if ($isadmin eq 'user' &&
                  (matchSL( $address, 'EmailAdmins', 1 )
                   or lc( $address ) eq lc($EmailAdminReportsTo)
                   or lc( $address ) eq lc($EmailBlockTo)
                  )
                 );
            if (! $is_admin && ! $faddress && ! &localmail($address)) {
                mlog(0,"info: BlockReport ignoring $address - address is not a valid local mail address") if $ReportLog >= 2 && ! $ignoreAddr{ lc($address) };
                $ignoreAddr{ lc($address) } = 1;
                next;
            }
            my $addWhiteHint = (   ($autoAddResendToWhite > 1 && $isadmin)
                                or ($autoAddResendToWhite && $autoAddResendToWhite != 2 && ! $isadmin)
                               ) ? '%5Bdo%20not%5D%20autoadd%20sender%20to%20whitelist' : '';

            my $filename;
            $filename = $1 if $fl =~ s/\-\>\s*([^\r\n]+\Q$maillogExt\E)//i;
            $filename =~ s/\\/\//go;

            my $addFileHint = (   $correctednotspam
                               && $DelResendSpam
                               && $isadmin
                               && $filename =~ /\/$spamlog\//
                              ) ? '%5Bdo%20not%5D%20move%20file%20to%20'.$correctednotspam : '';
            $addFileHint = '%2C' . $addFileHint if $addFileHint && $addWhiteHint;

            my $abase = $base;
            $abase    =~ s/\\/\//go;
            $filename =~ s/^$abase[\\|\/]*//o;
            $fl       =~ s/\s+\[worker_\d+\]//io;
            $fl       =~ s/\s*;\s*$//o;
            $fl =~ s/(\d\d:\d\d:\d\d)\s$uniqueIDPrefix*\-*\d{5}\-\d{5}/$1/i
              unless $faddress;

            my $rawline = $fl;
            my $line;
            $line = &encodeHTMLEntities($fl);

            $fl =~ s{([\x80-\xFF])}{sprintf("=%02X", ord($1))}eog;

            if ( !exists $buser->{ lc($address) }{bgcolor} ) {
                $buser->{ lc($address) }{bgcolor} = '';
            }
            $buser->{ lc($address) }{bgcolor} =
              $buser->{ lc($address) }{bgcolor} eq ' class="odd"'
              ? ''
              : ' class="odd"';
            my $bgcolor = $buser->{ lc($address) }{bgcolor};

            if ( $filename && $eF->( "$base/$filename" )) {
                if (! $faddress && ! $NotGreedyWhitelist) {
                    my ($rs,$foundbody) = &BlockReportGetFrom("$base/$filename",\$rawline);
                    $line .= '<span name="tohid" class="addr">&nbsp;<br /></span>' . $rs if ($rs) ;
                    $filename = '' unless $foundbody;
                }
            }
            if ( $filename && $eF->( "$base/$filename" )) {
                my ($ofilename) = $filename =~ /^(.+)\Q$maillogExt\E$/i;
                $ofilename =~ s{([^0-9a-zA-Z])}{sprintf("x%02XX", ord($1))}eog;
                $ofilename = 'RSBM_' . $ofilename . $maillogExt;
                $filename =~ normHTML($filename);
                if ( $inclResendLink == 1 or $inclResendLink == 3 ) {
                    push( @{ $buser->{ lc($address) }{text} },
"\r\n$fl\r\nTo get this email, send an email to - mailto:$ofilename$EmailBlockReportDomain\r\n" .
($is_admin ? "to open the mail use :   $prot:\/\/$host:$webAdminPort\/edit?file=$filename\&note=m\&showlogout=1\r\n" : '')
                    );
                } else {
                    push( @{ $buser->{ lc($address) }{text} }, "\r\n$fl\r\n" );
                }
                if ( $inclResendLink == 2 or $inclResendLink == 3 ) {
                    $line =~
s/($gooddays)($timeformat)/<span class="date"><a href="$prot:\/\/$host:$webAdminPort\/edit?file=$filename&note=m&showlogout=1" target="_blank" title="open this mail in the assp fileeditor">$1$2<\/a><\/span>/ if $is_admin;
                    $line =~
s/(\[OIP: )?($IPRe)(\])?/my($p1,$e,$p2)=($1,$2,$3);($e!~$IPprivate)?"<span name=\"tohid\" class=\"ip\"><a href=\"$prot:\/\/$host:$webAdminPort\/ipaction?ip=$e\&showlogout=1\" target=\"_blank\" title=\"take an action via web on ip $e\">$p1$e$p2<\/a><\/span>":"<span name=\"tohid\">$p1$e$p2<\/span>";/goe if $is_admin;
                    $line =~
s/($EmailAdrRe\@$EmailDomainRe)/<a href="mailto:$EmailWhitelistAdd$EmailBlockReportDomain\?subject=add\%20to\%20whitelist&body=$1\%0D\%0A" title="add this email address to whitelist" target="_blank">$1<\/a>/go
                      if (! $faddress && ! $is_admin);
                    $line =~
s/($EmailAdrRe\@$EmailDomainRe)/<a href="mailto:$EmailWhitelistAdd$EmailBlockReportDomain\?subject=add\%20to\%20whitelist&body=$1\%0D\%0A" title="add this email address to whitelist" target="_blank">$1<\/a>&nbsp;<a href="$prot:\/\/$host:$webAdminPort\/addraction?address=$1&showlogout=1" target="_blank" title="take an action via web on address $1">\@<\/a>/go
                      if (! $faddress && $is_admin);
                    $line =~ s/\[spam found\](\s*\(.*?\))( \Q$subjectStart\E)/<span name="tohid"><br \/><span class="spam">spam reason: <\/span>$1<\/span>$2/;
                    $line =~ s/($SpamTagRE|\[(?:TLS-(?:in|out)|SSL-(?:in|out)|PersonalBlack)\])/<span name="tohid">$1<\/span>/gio;
                    my $leftbut = '<a href="mailto:'.$EmailBlockReport.$EmailBlockReportDomain.'?subject=request%20ASSP%20to%20resend%20blocked%20mail%20from%20ASSP-host%20'.$myName.'&body=%23%23%23'.$filename.'%23%23%23'.$addWhiteHint.$addFileHint.'%0D%0A" class="reqlink" target="_blank" title="request ASSP on '.$myName.' to resend this blocked email"><img src=cid:1000 alt="request ASSP on '.$myName.' to resend this blocked email"> Resend </a>';
                    my $rightbut = '<a href="mailto:'.$ofilename.$EmailBlockReportDomain.'?&subject=request%20ASSP%20to%20resend%20blocked%20mail%20from%20ASSP-host%20'.$myName.'" class="reqlink" target="_blank" title="request ASSP on '.$myName.' to resend this blocked email"><img src=cid:1000 alt="request ASSP on '.$myName.' to resend this blocked email"> Resend </a>';
                    $rightbut = '' if (&matchSL(\@to,'BlockResendLinkLeft') or
                                             ($BlockResendLink == 1 && ! matchSL(\@to,'BlockResendLinkRight')));
                    $leftbut = '' if (&matchSL(\@to,'BlockResendLinkRight') or
                                             ($BlockResendLink == 2 && ! matchSL(\@to,'BlockResendLinkLeft')));
                    $line =~ s/^(.+\)\s*)(\Q$subjectStart\E.+?\Q$subjectEnd\E.*)$/$1<br\/><strong>$2<\/strong>/ unless $faddress;
                    $line =~ s/(.*)/\n<tr$bgcolor>\n<td class="leftlink">$leftbut\n<\/td>\n<td class="inner">$1\n<\/td>\n<td class="rightlink">$rightbut\n<\/td>\n<\/tr>/o;
                    push( @{ $buser->{ lc($address) }{html} }, $line);
                } else {
                    $line =~ s/\[spam found\](\s*\(.*?\))( \Q$subjectStart\E)/<span name="tohid"><br \/><span class="spam">spam reason: <\/span>$1<\/span>$2/;
                    $line =~ s/($SpamTagRE|\[(?:TLS-(?:in|out)|SSL-(?:in|out)|PersonalBlack)\])/<span name="tohid">$1<\/span>/gio;
                    $line =~
s/(\[OIP: )?($IPRe)(\])?/my($p1,$e,$p2)=($1,$2,$3);($e!~$IPprivate)?"<span name=\"tohid\" class=\"ip\"><a href=\"$prot:\/\/$host:$webAdminPort\/ipaction?ip=$e\&showlogout=1\" target=\"_blank\" title=\"take an action via web on ip $e\">$p1$e$p2<\/a><\/span>":"<span name=\"tohid\">$p1$e$p2<\/span>";/goe if $is_admin;
                    $line =~
s/($EmailAdrRe\@$EmailDomainRe)/<a href="mailto:$EmailWhitelistAdd$EmailBlockReportDomain\?subject=add\%20to\%20whitelist&body=$1\%0D\%0A" title="add this email address to whitelist" target="_blank">$1<\/a>/go
                      if (! $faddress && ! $is_admin);
                    $line =~
s/($EmailAdrRe\@$EmailDomainRe)/<a href="mailto:$EmailWhitelistAdd$EmailBlockReportDomain\?subject=add\%20to\%20whitelist&body=$1\%0D\%0A" title="add this email address to whitelist" target="_blank">$1<\/a>&nbsp;<a href="$prot:\/\/$host:$webAdminPort\/addraction?address=$1&showlogout=1" target="_blank" title="take an action via web on address $1">\@<\/a>/go
                      if (! $faddress && $is_admin);
                    $line =~ s/^(.+\)\s*)(\Q$subjectStart\E.+?\Q$subjectEnd\E.*)$/$1<br\/><strong>$2<\/strong>/ unless $faddress;
                    $line =~ s/(.*)/\n<tr$bgcolor>\n<td class="leftlink">&nbsp;\n<\/td>\n<td class="inner">$1\n<\/td>\n<td class="rightlink">&nbsp;\n<\/td>\n<\/tr>/o;
                    push( @{ $buser->{ lc($address) }{html} }, $line );
                }
            } else {
                push( @{ $buser->{ lc($address) }{text} }, "\r\n$fl\r\n");
                $line =~ s/\[spam found\](\s*\(.*?\))( \Q$subjectStart\E)/<span name="tohid"><br \/><span class="spam">spam reason: <\/span>$1<\/span>$2/;
                $line =~ s/($SpamTagRE|\[(?:TLS-(?:in|out)|SSL-(?:in|out)|PersonalBlack)\])/<span name="tohid">$1<\/span>/gio;
                $line =~
s/(\[OIP: )?($IPRe)(\])?/my($p1,$e,$p2)=($1,$2,$3);($e!~$IPprivate)?"<span name=\"tohid\" class=\"ip\"><a href=\"$prot:\/\/$host:$webAdminPort\/ipaction?ip=$e\&showlogout=1\" target=\"_blank\" title=\"take an action via web on ip $e\">$p1$e$p2<\/a><\/span>":"<span name=\"tohid\">$p1$e$p2<\/span>";/goe if $is_admin;
                $line =~
s/($EmailAdrRe\@$EmailDomainRe)/<a href="mailto:$EmailWhitelistAdd$EmailBlockReportDomain\?subject=add\%20to\%20whitelist&body=$1\%0D\%0A" title="add this email address to whitelist" target="_blank">$1<\/a>/go
                  if (! $faddress && ! $is_admin);
                $line =~
s/($EmailAdrRe\@$EmailDomainRe)/<a href="mailto:$EmailWhitelistAdd$EmailBlockReportDomain\?subject=add\%20to\%20whitelist&body=$1\%0D\%0A" title="add this email address to whitelist" target="_blank">$1<\/a>&nbsp;<a href="$prot:\/\/$host:$webAdminPort\/addraction?address=$1&showlogout=1" target="_blank" title="take an action via web on address $1">\@<\/a>/go
                  if (! $faddress && $is_admin);
                $line =~ s/^(.+\)\s*)(\Q$subjectStart\E.+?\Q$subjectEnd\E.*)$/$1<br\/><strong>$2<\/strong>/ unless $faddress;
                $line =~ s/(.*)/\n<tr$bgcolor>\n<td class="leftlink">&nbsp;\n<\/td>\n<td class="inner">$1\n<\/td>\n<td class="rightlink">&nbsp;\n<\/td>\n<\/tr>/o;
                push( @{ $buser->{ lc($address) }{html} }, $line );
            }
        }
        close $FLogFile;
    }
    while ( my ($ad,$v) = each %$buser ) {
        next if ( $ad eq 'sum' );
        push( @{ $buser->{$ad}{html} }, "\n</table>\n<br />\n");
        delete $buser->{$ad}{bgcolor};
        if (exists $buser->{$ad}{filtercount}) {
            push( @{ $buser->{$ad}{html} },"<br />\n".$buser->{$ad}{filter}."<br />\n");
            push( @{ $buser->{$ad}{text} },"\r\n\r\n".$buser->{$ad}{filter}."\r\n");
            $buser->{$ad}{correct}--;
        }
        if (exists $buser->{$ad}{filtercount2}) {
            push( @{ $buser->{$ad}{html} },"<br />\n") unless exists $buser->{$ad}{filtercount};
            push( @{ $buser->{$ad}{html} },$buser->{$ad}{filter2}."<br />\n");
            push( @{ $buser->{$ad}{text} },"\r\n\r\n") && $buser->{$ad}{correct}-- unless exists $buser->{$ad}{filtercount};
            push( @{ $buser->{$ad}{text} },$buser->{$ad}{filter2}."\r\n");
            $buser->{$ad}{correct}--;
        }
        delete $buser->{$ad}{filter};
        delete $buser->{$ad}{filtercount};
        delete $buser->{$ad}{filter2};
        delete $buser->{$ad}{filtercount2};
    }
    $bytes                    = formatDataSize( $bytes, 1 );
    $runtime                  = time - $runtime;
    $buser->{sum}{mimehead}     = $mimehead;
    $buser->{sum}{mimebot}      = $mimebot;
    $buser->{sum}{textparthead} = $textparthead;
    $buser->{sum}{htmlparthead} = $htmlparthead;
    $buser->{sum}{htmlhead}     = $htmlhead;

    $buser->{sum}{html} .= "\n".<<'EOT';
<input type="button" name="toggle" value="toggle view" onclick="show=((show=='none')?'inline':'none');changeview(show);return false;"
 title="click the button to simplify or to extend the BlockReport view - requires javascript to be enabled in your mail clients HTML view">
<br />
EOT
    my ($t10html,$t10text);
    if ($DoT10Stat && $isadmin == 1) {
        ($t10html,$t10text) = T10StatOut();
        my $ire = qr/^(?:$IPRe|[\d\.]+)$/o;
        $t10html =~ s/((?:$EmailAdrRe\@)?$EmailDomainRe)/my$e=$1;($e!~$ire)?"<a href=\"$prot:\/\/$host:$webAdminPort\/addraction?address=$e\&showlogout=1\" target=\"_blank\" title=\"take an action via web on address $e\">$e<\/a>":$e/goe;
        $t10html =~ s/($IPRe)/my$e=$1;($e!~$IPprivate)?"<a href=\"$prot:\/\/$host:$webAdminPort\/ipaction?ip=$e\&showlogout=1\" target=\"_blank\" title=\"take an action via web on ip $e\">$e<\/a>":$e;/goe;
    }
    if ( matchSL( $this->{mailfrom}, 'EmailAdmins', 1 )
        or lc( $this->{mailfrom} ) eq lc($EmailAdminReportsTo)
        or lc( $this->{mailfrom} ) eq lc($EmailBlockTo) )
    {
        $buser->{sum}{html} .= $t10html . "<br />\n<div name=\"tohid\">" . &needEs($lines, ' line','s') . " with $bytes analysed in " .
            &needEs($numfiles,' logfile','s') . " on host $myName in $runtime seconds - running ASSP version $MAINVERSION<br /></div>\n";
        $buser->{sum}{text} .= $t10text . "\r\n\r\n" . &needEs($lines, ' line','s') . " with $bytes analysed in " .
            &needEs($numfiles,' logfile','s') . " on host $myName in $runtime seconds - running ASSP version $MAINVERSION\r\n";
    } else {
        $buser->{sum}{html} .= "\n".<<'EOT';
<script type="text/javascript">
<!--
show = "none";
changeview(show);
// -->
</script>
EOT
    }
    $buser->{sum}{html} .= "</body>\n</html>\n";
    return;
}

sub BlockReportFormatAddr {
    return join('|', map {my $t = $_;
                          $t =~ s/([^*]+)\@/quotemeta($1).'@'/oe;
                          $t =~ s/\@([^*]+)/'@'.quotemeta($1)/oe;
                          $t =~ s/\@/\\@/;
                          $t =~ s/\*(\\\@)/$EmailAdrRe$1/o;
                          $t =~ s/\@\*/\@$EmailDomainRe/o;
                          $t;} @_);
}
sub BlockReportGetFrom {
    my ($fn,$fl) = @_;
    my $res;
    my $foundbody;
    my $headerseen;
    return unless (open my $F,'<' ,"$fn");
    while (<$F>) {
        s/\r|\n//go;
        $headerseen = 1 if (! $_);  # header only
        if ($headerseen && $_) {
            $foundbody = 1;
            last;
        }
        my ($tag,$adr);
        ($tag,$adr) = ($1,$2) if /^(from|sender|reply-to|errors-to|list-\w+:)[^\r\n]*?($EmailAdrRe\@$EmailDomainRe)/io;
        next unless ($tag && $adr);
        next if $$fl =~ /\Q$adr\E/i;
        $tag = &encHTMLent(\$tag);
        $adr = &encHTMLent(\$adr);
        $res .= '<br /><span class="addr">'. $tag . '&nbsp;&nbsp;' . $adr . '</span>';
    }
    close $F;
    $res .= '<br /><small>no message body received</small>' unless $foundbody;
    return ($res,$foundbody);
}

# wrap long html lines in BlockReport
sub BlockReportHTMLTextWrap {
    my $line=shift;
    d('BlockReportHTMLTextWrap');
    return unless $line;

    $line =~ s/\r//go;
    $line =~ s/ +/ /go;
    $line = MIME::QuotedPrint::encode_qp($line);
    $line =~ s/(^|\n)\./$1=2E/gos;
    return $line;
}

sub BlockReport {
    my ( $fh, $l ) = @_;
    my $this = $Con{$fh};
    if ( $l =~ /^ *DATA/io || $l =~ /^ *BDAT (\d+)/io ) {
        if ($1) {
            $this->{bdata} = $1;
        } else {
            delete $this->{bdata};
        }
        $this->{getline} = \&BlockReportBody2Q;
        my $report = 'blocked email report';
        sendque( $fh, "354 OK Send $report body\r\n" );
        $this->{lastcmd} = 'DATA';
        push( @{ $this->{cmdlist} }, $this->{lastcmd} ) if $ConnectionLog >= 2;
        return;
    } elsif ( $l =~ /^ *RSET/io ) {
        stateReset($fh);
        $this->{getline} = \&getline;
        sendque( $this->{friend}, "RSET\r\n" );
        $this->{lastcmd} = 'RSET';
        push( @{ $this->{cmdlist} }, $this->{lastcmd} ) if $ConnectionLog >= 2;
        return;
    } elsif ( $l =~ /^ *QUIT/io ) {
        stateReset($fh);
        $this->{getline} = \&getline;
        sendque( $this->{friend}, "QUIT\r\n" );
        $this->{lastcmd} = 'QUIT';
        push( @{ $this->{cmdlist} }, $this->{lastcmd} ) if $ConnectionLog >= 2;
        return;
    } elsif ( $l =~ /^ *XEXCH50 +(\d+)/io ) {
        d("XEXCH50 b=$1");
        sendque( $fh, "504 Need to authenticate first\r\n" );
        $this->{lastcmd} = 'XEXCH50';
        push( @{ $this->{cmdlist} }, $this->{lastcmd} ) if $ConnectionLog >= 2;
        return;
    }
    sendque( $fh, "250 OK\r\n" );
}

sub BlockReportForwardRequest {
    my ($fh, $host) = @_;
    my $this = $Con{$fh};
    d("BlockReportForwardRequest - $host");

    if ( $BlockRepForwHost && ! $CanUseNetSMTP ) {
        mlog(0,"error: unable to forward blocked mail request - module Net::SMTP is not installed and/or enabled") if $ReportLog;
        return;
    }

    if ( $BlockRepForwHost && $CanUseNetSMTP ) {
        my $smtp;
        my $MTAip;
        my $port;
        my $ip;
        my $hostip;
        my $fwhost = $BlockRepForwHost;

        $host =~ s/\s//go;
        if ($host && $host !~ /$IPRe/o ) {
            eval {
                my $pip = gethostbyname($host);
                if ( defined $pip ) {
                    $hostip = inet_ntoa($pip);
                }
            };
            mlog( 0,"info: forwarding blocked mail request - resolved ip $hostip for host $host") if $ReportLog >= 2;
        }

        if ( ($hostip && $BlockRepForwHost =~ /\s*(\Q$hostip\E)\s*:\s*(\d+)\s*/i) or
             ($host && $BlockRepForwHost =~ /\s*(\Q$host\E)\s*:\s*(\d+)\s*/i) ) {
                $fwhost = "$1:$2";
                mlog( 0,"info: got forwarding blocked mail request from $this->{mailfrom} to host $fwhost") if $ReportLog >= 2;
        }

        foreach my $MTA ( split( /\|/o, $fwhost ) ) {
            $MTA =~ s/\s//go;
            ( $MTAip, $port ) = split( /\:/o, $MTA );
            if ( $MTAip !~ /$IPRe/o ) {
                eval {
                    my $pip = gethostbyname($MTAip);
                    $ip = inet_ntoa($pip) if ( defined $pip );
                };
            }
            $MTAip = $ip ? $ip : $MTAip;
            if ( $this->{ip} eq $MTAip or $this->{cip} eq $MTAip ) {
                mlog( 0,"info: skip forwarding blocked mail request from $this->{mailfrom} to host $MTA - request comes from this host")
                  if $ReportLog >= 2;
                next;
            }
            eval {
                $smtp = Net::SMTP->new(
                    $MTA,
                    Hello   => $myName,
                    Timeout => 120 # 120 is the default in Net::SMTP
                );
                if ($smtp) {
                    $smtp->mail( $this->{mailfrom} );
                    $smtp->to( $this->{rcpt} );
                    $smtp->data();
                    my $timeout = (int(length($this->{header}) / (1024 * 1024)) + 1) * 60; # 1MB/min
                    my $blocking = $smtp->blocking(0);
                    my $data = $this->{header};
                    $data =~ s/\.[\r\n]+$//o;
                    NoLoopSyswrite($smtp, $data, $timeout);
                    $smtp->blocking($blocking);
                    $smtp->dataend();
                    $smtp->quit;
                }
            };
            if ( $smtp && !$@ ) {
                mlog( 0,"info: forwarded blocked mail request from $this->{mailfrom} to host $MTA") if $ReportLog >= 2;
            } else {
                mlog( 0,"error: unable to forward blocked mail request from $this->{mailfrom} to host $MTA - $@") if $ReportLog;
            }
        }
    }
}

sub BlockReportBody2Q {
    my ( $fh, $l ) = @_;
    my $this = $Con{$fh};
    my $host;
    d('BlockReportBody2Q');

    $this->{header} .= $l;
    if ( $l =~ /^\.[\r\n]/o || defined( $this->{bdata} ) && $this->{bdata} <= 0 )
    {
        if ( !$CanUseEMM ) {
            mlog( 0,"info: module Email::MIME is not installed and/or enabled - local blockreport is impossible") if $ReportLog;
            BlockReportForwardRequest($fh,$host);
            stateReset($fh);
            $this->{getline} = \&getline;
            sendque( $this->{friend}, "RSET\r\n" );
            return;
        }
        my $parm = "$this->{mailfrom}\x00$this->{rcpt}\x00$this->{ip}\x00$this->{cip}\x00$this->{header}";
         mlog( 0,"info: send blocked mail request from $Con{$fh}->{mailfrom}")
          if $ReportLog >= 2 or $MaintenanceLog;
        &BlockReportFromQ($parm );

        $Email::MIME::ContentType::STRICT_PARAMS = 0;    # no output about invalid CT
        my $email = Email::MIME->new($this->{header});
        my $sub = $email->header("Subject") || '';    # get the subject of the email
        $sub =~ s/\r?\n//go;

        ($host) = $sub =~ /ASSP\-host\s+(.*)/io;
        $host =~ s/\s//go;

        BlockReportForwardRequest($fh,$host) if ( lc($myName) ne lc($host) );

        stateReset($fh);
        $this->{getline} = \&getline;
        sendque( $this->{friend}, "RSET\r\n" );
    }
}

sub BlockReportFromQ {
    my $parm = shift;
    my $fh = Time::HiRes::time();    # a dummy $fh for a dummy $Con{$fh}
    $Con{$fh} = {};

    (   $Con{$fh}->{mailfrom},
        $Con{$fh}->{rcpt},
        $Con{$fh}->{ip},
        $Con{$fh}->{cip},
        $Con{$fh}->{header}
    ) = split( /\x00/o, $parm );
    $Con{$fh}->{blqueued} = 1;
    mlog( 0,"info: processing  blocked mail request from $Con{$fh}->{mailfrom}")
      if $ReportLog >= 2 or $MaintenanceLog;
    &BlockReportBody( $fh, ".\r\n" );
    delete $Con{$fh};
}

sub BlockReportBody {
    my ( $fh, $l ) = @_;
    my $this = $Con{$fh};
    my $sub;
    my $host;
    my %resendfile = ();
    my $forcelist;    # $this->{blqueued} is set, if V2 has queued
                      # the report to MaintThread

    d('BlockReportBody');

    $EmailBlockReportDomain = '@' . $EmailBlockReportDomain
      if $EmailBlockReportDomain !~ /^\@/o;
    eval {
        $this->{header} .= $l unless $this->{blqueued};
        if ( $l =~ /^\.[\r\n]/o
            || defined( $this->{bdata} ) && $this->{bdata} <= 0 )
        {

            if ( !$CanUseEMM ) {
                mlog( 0,"info: module Email::MIME is not installed and/or enabled - local blockreport is impossible") if $ReportLog;
                BlockReportForwardRequest($fh,$host);
                stateReset($fh);
                $this->{getline} = \&getline;
                sendque( $this->{friend}, "RSET\r\n" );
                return;
            }
            matchSL( $this->{mailfrom}, 'EmailAdmins' );
            $Email::MIME::ContentType::STRICT_PARAMS =  0;    # no output about invalid CT
            my $email = Email::MIME->new($this->{header});
            $sub = $email->header("Subject") || '';    # get the subject of the email
            $sub =~ s/\r?\n//go;
            $sub =~ s/\s+/ /go;

            #        mlog(0,"subject: $sub");
            ($host) = $sub =~ /ASSP\-host\s+(.*)/io;
            $host =~ s/\s//go;

            #       mlog(0,"host: $host");
            foreach my $part ( $email->parts ) {
                my $body = $part->body;

                #           mlog(0,"BODY:\n$body\n");
                my $preline;
                foreach my $line ( split( /\n/o, $body ) ) {
                    $line =~ s/\r?\n//go;
                    $line      = decodeHTMLEntities($line);
                    $forcelist = 1
                      if (
                        $line =~ /^\s*(\[?$EmailAdrRe|\*)\@($EmailDomainRe\]?|\*)\s*\=\>/o
                        && ( matchSL( $this->{mailfrom}, 'EmailAdmins', 1 )
                            or lc( $this->{mailfrom} ) eq lc($EmailAdminReportsTo)
                            or lc( $this->{mailfrom} ) eq lc($EmailBlockTo) )
                      );

                    if ( ( $line =~ /###/o or $preline ) && $line !~ /###/o )
                    {
                        $preline .= $line;
                        next;
                    }
                    if ($preline) {
                        $line    = $preline . $line;
                        $preline = '';
                    }
                    my ($fname,$special) = $line =~ /###(.*)?###(.*)$/o;
                    if ($fname) {
                        $fname =~ s/\r?\n//go;
                        $fname = "$base/$fname";
                        $special =~ s/\r?\n//go;
                        $special ||= 0;
                        $resendfile{$fname} = $special if ! $resendfile{$fname};
                    }
                }
            }
            if ( $this->{rcpt} =~
                /^RSBM_(.+?)\Q$maillogExt\E$EmailBlockReportDomain\s*$/i )
            {
                my $rfile = $1;
                $rfile =~ s/x([0-9a-fA-F][0-9a-fA-F])X/pack('C',hex($1))/geo;
                $rfile = "$base/$rfile$maillogExt";
                $resendfile{$rfile} = 0;
                $sub .= ' resend ' if $sub !~ /resend/io;
            }
            if ( $sub =~ /\sresend\s/io or scalar( keys %resendfile ) ) {
                foreach my $rfile ( keys %resendfile ) {

#               mlog(0,"info: resend filename - $rfile on host - $host to $this->{mailfrom}");
                    if ( (!$host or ( lc($myName) eq lc($host) )) && $resendmail && $CanUseEMS) {
                        mlog( 0,"info: got resend blocked mail request from $this->{mailfrom} for $rfile")
                          if $ReportLog >= 2;
                        &BlockedMailResend( $fh, $rfile , $resendfile{$rfile});
                    }
                }
                mlog( 0,"error: got resend blocked mail request from $this->{mailfrom} without valid filename")
                  if ( !scalar( keys %resendfile ) && $ReportLog );
                if ( ! $forcelist ) {
                    BlockReportForwardRequest($fh,$host) if ( ! $this->{blqueued} && lc($myName) ne lc($host) );
                    stateReset($fh);
                    $this->{getline} = \&getline;
                    sendque( $this->{friend}, "RSET\r\n" );

                    return;
                }
            }

            if ($forcelist) {
                my $body;
                my %lines = ();
                mlog( 0,"info: got blocked mail report for a user list from $this->{mailfrom}")
                  if $ReportLog >= 2;
                foreach my $part ( $email->parts ) {
                    my $mbody = decodeHTMLEntities( $part->body );
                    while ( $mbody =~
                        /(.*?)((\[?$EmailAdrRe|\*)\@($EmailDomainRe\]?|\*).*)/go )
                    {
                        my $line = $2;
                        $line =~ s/\r?\n//go;
                        $line =~ s/<[^\>]*>//go;
                        my ( $ad, $bd, $cd, $dd) = split( /\=\>/o, $line );
                        $ad =~ s/\s//go;
                        $bd =~ s/\s//go;
                        $cd =~ s/\s*(\d+).*/$1/o;
                        $dd =~ s/^\s*(.*?)\s*$/$1/o;
                        if ( $ad !~ /^(\[?$EmailAdrRe|\*)\@($EmailDomainRe\]?|\*)$/o ) {
                            mlog( 0,"warning: syntax error in $ad, entry was ignored")
                              if $ReportLog;
                            next;
                        }
                        if ( $bd && $bd !~ /^($EmailAdrRe\@$EmailDomainRe|\*)$/o )
                        {
                            mlog( 0,"warning: syntax error in =>$bd, entry was ignored")
                              if $ReportLog;
                            next;
                        }
                        eval{'a' =~ /$dd/i} if $dd;
                        if ( $@ )
                        {
                            mlog( 0,"warning: syntax error in =>$dd, entry was ignored - regex error $@")
                              if $ReportLog;
                            next;
                        }

                        $ad    = lc $ad;
                        $bd    = lc $bd;
                        ($cd)  = $sub =~ /^\s*(\d+)/o  unless $cd;
                        $cd    = 1 unless $cd;
                        $line = "$ad=>$bd=>$cd=>$dd";
                        $body .= "$line\r\n" if ( !exists $lines{$line} );
                        $lines{$line} = 1;
                    }
                }
                if (%lines) {
                    open( my $tmpfh, '<', \$body );
                    $Con{$tmpfh}->{mailfrom} = $this->{mailfrom};
                    BlockReportGen( '1', $tmpfh );
                    delete $Con{$tmpfh};
                }
                if ( !$this->{blqueued} ) {
                    BlockReportForwardRequest($fh,$host) if lc($myName) ne lc($host);
                    stateReset($fh);
                    $this->{getline} = \&getline;
                    sendque( $this->{friend}, "RSET\r\n" );
                }

                return;
            }

            if ( $sub =~ /^\s*[\-|\+]/o or $QueueUserBlockReports > 0 ) {
                &BlockReportStoreUserRequest( $this->{mailfrom}, $sub, $QueueUserBlockReports );
                if ( !$this->{blqueued} ) {
                    BlockReportForwardRequest($fh,$host) if lc($myName) ne lc($host);
                    stateReset($fh);
                    $this->{getline} = \&getline;
                    sendque( $this->{friend}, "RSET\r\n" );
                }

                return;
            }

            my ($numdays, $exceptRe) = $sub =~ /^\s*(\d+)\s*(.*)$/o;
            if ($exceptRe) {
                eval{'a' =~ /$exceptRe/i};
                if ($@) {
                    mlog(0,"error: regular expression error in blockreport request - $sub - $@");
                    $exceptRe = '';
                }
            }
            $numdays = 5 unless $numdays;
            my %user;
            &BlockReasonsGet( $fh, $numdays , \%user, $exceptRe);
            my @textreasons;
            my @htmlreasons;

            push( @textreasons, $user{sum}{textparthead} );
            push( @htmlreasons, $user{sum}{htmlparthead} );
            push( @htmlreasons, $user{sum}{htmlhead} );
            foreach  my $ad ( sort keys %user ) {
                next if ( $ad eq 'sum' );
                my $number = scalar @{ $user{$ad}{text} } + $user{$ad}{correct};
                $number = 0 if $number < 0;
                $number = 'no' unless $number;
                push(
                    @textreasons,
                    &BlockReportText('text', $ad, $numdays, $number, $this->{mailfrom})
                  );
                my $userhtml =
                  &BlockReportText( 'html', $ad, $numdays, $number,
                    $this->{mailfrom} );
                push( @htmlreasons,  BlockReportHTMLTextWrap(<<"EOT"));
<table id="report">
 <col /><col /><col />
 <tr>
  <th colspan="3" id="header">
   <img src=cid:1001 alt="powered by ASSP on $myName">
   $userhtml
  </th>
 </tr>
EOT
                while ( @{ $user{$ad}{text} } ) { push( @textreasons, shift @{ $user{$ad}{text} } ); }
                while ( @{ $user{$ad}{html} } ) { push( @htmlreasons, BlockReportHTMLTextWrap(shift @{ $user{$ad}{html} } )); }
            }
            if ( scalar( keys %user ) < 2 ) {
                push( @textreasons,"\nno blocked email found in the last $numdays day(s)\n\n");
                push( @htmlreasons,"\nno blocked email found in the last $numdays day(s)\n\n");
            }
            push( @textreasons, $user{sum}{text} );
            push( @htmlreasons, $user{sum}{html} );

            @textreasons = () if ( $BlockReportFormat == 2 );
            @htmlreasons = () if ( $BlockReportFormat == 1 );

            BlockReportSend(
                $fh,
                $this->{mailfrom},
                $this->{mailfrom},
                &BlockReportText(
                    'sub',    $this->{mailfrom},
                    $numdays, 'n/a',
                    $this->{mailfrom}
                  ),
                $BlModify->($user{sum}{mimehead}
                  . join( '', @textreasons )
                  . join( '', @htmlreasons )
                  . $user{sum}{mimebot})
              ) if ( $EmailBlockReply == 1 || $EmailBlockReply == 3 );

            BlockReportSend(
                $fh,
                $EmailBlockTo,
                $this->{mailfrom},
                &BlockReportText(
                    'sub',    $this->{mailfrom},
                    $numdays, 'n/a',
                    $EmailBlockTo
                  ),
                $BlModify->($user{sum}{mimehead}
                  . join( '', @textreasons )
                  . join( '', @htmlreasons )
                  . $user{sum}{mimebot})
              )
              if ( $EmailBlockTo
                && ( $EmailBlockReply == 2 || $EmailBlockReply == 3 ) );

            if ( !$this->{blqueued} ) {
                BlockReportForwardRequest($fh,$host) if lc($myName) ne lc($host);
                stateReset($fh);
                $this->{getline} = \&getline;
                sendque( $this->{friend}, "RSET\r\n" );
            }
        }
      };    # end eval
      if ($@) {
          mlog( 0,"error: unable to process blockreport - $@") if $ReportLog;
          BlockReportForwardRequest($fh,$host) if ( ! $this->{blqueued} && lc($myName) ne lc($host) );
          stateReset($fh);
          $this->{getline} = \&getline;
          sendque( $this->{friend}, "RSET\r\n" );

          return;
      }

}

sub BlockReportStoreUserRequest {
    my ( $from, $sub, $oldrequest ) = @_;
    my $request=$oldrequest;
    my $file = "$base/files/UserBlockReportQueue.txt";
    $file = "$base/files/UserBlockReportInstantQueue.txt" if $oldrequest>=3;

    $request=1 if $oldrequest>=3;
    my %lines = ();
    my ( $user, $to, $numdays, $nextrun, $comment, $exceptRe );
    my $reply;

    open my $f, '<',"$file";
    while (<$f>) {
        s/\r?\n//igo;
        s/\s*#(.*)//go;
        $comment = $1;
        next unless $_;
        ( $user, $to, $numdays , $exceptRe ) = split( /\=\>/o, $_ );
        next unless $user;
        $comment =~ /^\s*(next\srun\s*\:\s*\d+[\-|\.]\d+[\-|\.]\d+)/o;
        $nextrun               = $1 ? "# $1" : '';
        $user                  = lc($user);
        $numdays               = 5 unless $numdays;
        $lines{$user}{numdays} = $numdays;
        $lines{$user}{nextrun} = $nextrun;
        $lines{$user}{exceptRe} = $exceptRe;
    }
    close $f;
    $from = lc($from);
    $sub =~ /^\s*([\-|\+])*\s*(\d)\s*(.*)/o;
    my $how = $1;
    $numdays = $2 ? $2 : 5;
    $exceptRe = $3;
    if ( $how eq '-' ) {
        if (delete $lines{$from}) {
            mlog( 0, "info: removed entry for $from from block report queue" )
              if $ReportLog >= 2;
            $reply = "your entry $from was removed from the block report queue!\n";
        } else {
            $reply = "an entry $from was not found in the block report queue!\n";
        }
    } else {
        my $time = time;
        my $dayoffset = $time % ( 24 * 3600 );
        $nextrun = $time - $dayoffset + ( 24 * 3600 );
        my (
            $second,    $minute,    $hour,
            $day,       $month,     $yearOffset,
            $dayOfWeek, $dayOfYear, $daylightSavings
        ) = localtime($nextrun);
        my $year = 1900 + $yearOffset;
        $month++;
        $nextrun = "# next run: $year-$month-$day";
        $nextrun = '' if ( $request < 2 && $how ne '+' );

        if ($exceptRe) {
            eval{'a' =~ /$exceptRe/i};
            if ($@) {
                mlog(0,"error: regex error in blockreport request from $from - $sub - $@") if $ReportLog;
                $reply = "Your entry $from was not processed - bad regex found - $@ !\n";

                my $fh = int( rand(time) );    # a dummy $fh for a dummy $Con{$fh}
                $Con{$fh}->{mailfrom} = $from;
                BlockReportSend(
                    $fh,
                    $from,
                    $from,
                    &BlockReportText( 'sub', $from, $numdays, 'n/a', $from )
                      . " - Block Report Queue ",
                    $reply
                );
                delete $Con{$fh};
                return;
            }
        }

           if ( exists $lines{$from} ) {
            $reply = "Your entry $from was updated in the block report queue!\n";
            mlog( 0, "info: updated entry for $from in block report queue" )
              if $oldrequest <3 && $ReportLog >= 2;
            mlog( 0, "info: updated entry for $from in block report instant queue" )
              if $oldrequest =3 && $ReportLog >= 2;
        } else {
            $reply = "Your entry $from was added to the block report queue!\n";
            mlog( 0, "info: added entry for $from to block report queue" )
              if $oldrequest <3 && $ReportLog >= 2;
            mlog( 0, "info: added entry for $from to block report instant queue" )
              if $oldrequest =3 && $ReportLog >= 2;
        }
        $lines{$from}{numdays} = $numdays;
        $lines{$from}{nextrun} = $nextrun;
        $lines{$from}{exceptRe} = $exceptRe;
    }
    my $time = time;
    open $f, '>',"$file";
    while ( !($f->opened) && time - $time < 10 ) { sleep 1; open $f, '>',"$file"; }
    if ($f->opened) {
        binmode $f;
        foreach my $line ( sort keys %lines ) {
            $lines{$line}{exceptRe} =~ s/^\s*(.*?)\s*$/$1/o;
            print $f $line . '=>'
              . $line . '=>'
              . $lines{$line}{numdays} . '=>'
              . $lines{$line}{exceptRe} . ' '
              . $lines{$line}{nextrun} . "\n";
        }
        close $f;
    } else {
        $reply =~ s/ was / was not /o;
        $reply .= " Internal write error, please contact your email admin!";
        mlog( 0,"error: unable to open $file for write within 10 seconds - entry for $from not updated" )
          if $ReportLog;
    }
    my $fh = int( rand(time) );    # a dummy $fh for a dummy $Con{$fh}
    $Con{$fh}->{mailfrom} = $from;
    BlockReportSend(
        $fh,
        $from,
        $from,
        &BlockReportText( 'sub', $from, $numdays, 'n/a', $from )
          . " - Block Report Queue ",
        $reply
    ) if $oldrequest < 3;
    delete $Con{$fh};
}

sub BlockReportText {
    my ( $what, $for, $numdays, $number, $from ) = @_;
    my $file = "$base/reports/blockreport_$what.txt";
    my $text;
    my %slines = ();
    my $f;
    my $section;
    $for  = lc($for);
    $from = lc($from);
    my ($domain) = $for =~ /$EmailAdrRe\@($EmailDomainRe)/o;

    return "report text file $file not found" unless ( open $f, '<',"$file" );
    while (<$f>) {
        next if /^\s*#/o;
        if (/^\s*<([^\/]+)>/o && !$section) {
            $section = lc($1);
        } elsif ( $section && /^\s*<\/$section>/i ) {
            $section = '';
        } elsif ($section) {
            s/REPORTDAYS/$numdays/go;
            s/ASSPNAME/$myName/go;
            s/EMAILADDRESS/$for/go;
            s/NUMBER/$number/go;
            $slines{$section} .= $_;
        }
    }
    close $f;

    $text .= $slines{'all'} if $slines{'all'};
    if (   matchSL( $from, 'EmailAdmins', 1 )
        or lc($from) eq lc($EmailAdminReportsTo)
        or lc($from) eq lc($EmailBlockTo) )
    {
        $text .= $slines{'admins'} if $slines{'admins'};
    } else {
        $text .= $slines{'users'} if $slines{'users'};
    }

    if ( $slines{$for} ) {
        $text .= $slines{$for};
    } elsif ( $slines{$domain} or $slines{ '@' . $domain } ) {
        $text .= $slines{$domain};
        $text .= $slines{ '@' . $domain };
    }

    return $text;
}

sub BlockReportGetCSS {
    if (open my $F , '<', "$base/images/blockreport.css") {
        binmode $F;
        my @css = <$F>;
        close $F;
        @css = map {my $t = $_; $t =~ s/\/\*.*?\*\///so; $t =~ s/^\s*\r?\n//o; $t;} @css;
        return '<style type="text/css">' . "\n" . join('',@css). "\n" . '</style>';
    } else {
        mlog(0,"warning: BlockReport - unable to open file '$base/images/blockreport.css' - using internal css");
        my $ret = <<'EOF';
<style type="text/css">
/* the general layout of the Block Report */
a {color:#06c;}
a:hover {text-decoration:none;}
#report {font-family:Arial, Helvetica, sans-serif; font-size:12px; color:#333;}
#report table {width:700px; border:0; border-spacing:0; padding:0; table-layout:fixed;}

/* the layout of the header with the image and the text from blockreport_html.txt */
#header {
 background:#4398c6;
 color:#fff;
 font-weight:normal;
 text-align:left;
 border-bottom:1px;
 solid #369;
 white-space: pre-wrap; /* css-3 */
 white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
 white-space: -pre-wrap; /* Opera 4-6 */
 white-space: -o-pre-wrap; /* Opera 7 */
 word-wrap: break-word; /* Internet Explorer 5.5+ */
}
/* #header table {width:"100%"; border:0; border-spacing:0; padding:0; table-layout:fixed;} */
/* #header th {background:#4398c6; font-weight:normal; text-shadow:0 1px 0 #0C6FA5;} */
#header strong.title {font-size:16px;}
#header img {width:200px; height:75px; border:0; float:left;}

/* the general column definition */
#report td {
 padding:7px;
 background:#f9f9f9;
 border-top:1px solid #fff;
 border-bottom:1px solid #eee;
 line-height:18px;
}

/* the odd column definition (other color) */
#report tr.odd td {
 background:#e0ebf7;
 border-top:1px solid #fff;
 border-bottom:1px solid #c6dcf2;
}

/* the left resend link column */
#report td.leftlink {width: 30px;}

/* the middle column */
#report td.inner {
 width: 630px;
 white-space: pre-wrap; /* css-3 */
 white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
 white-space: -pre-wrap; /* Opera 4-6 */
 white-space: -o-pre-wrap; /* Opera 7 */
 word-wrap: break-word; /* Internet Explorer 5.5+ */
}

/* the right resend link column */
#report td.rightlink {width: 30px;}

/* the title view on hover */
#report td.title {padding:5px; line-height:16px;}
#report td.title strong {font-size:15px;text-shadow:0 1px 0 #0C6FA5;}

/* the date link to open the mail in the browser */
span.date {background:#ddd; padding:1px 2px; color:#555;}
span.date a {color:#333;text-decoration:none;}
span.date a:hover {color:#06c; text-decoration:underline;}

/* the IP link to open the mail in the browser */
span.ip {background:#ddd; padding:1px 2px; color:#555;}
span.ip a {color:#333;text-decoration:none;}
span.ip a:hover {color:#06c; text-decoration:underline;}

/* the 'spam reason'*/
span.spam {color:#b00;}

/* the from and reply to lines*/
span.addr {font-size:10px;text-shadow:0 1px 0 #0C6FA5;}

/* the 'add to whitelist' link */
a.reqlink {color:#06c; font-size:11px;}
a.reqlink img {float:left; margin-right:3px; width:16px; height:16px; border:0;}
</style>
EOF

        $ret =~ s/\/\*.*?\*\///sgo;
        $ret =~ s/(?:\s*\r?\n)/\n/sgo;
        return $ret;
    }
}

sub BlockReportGetImage {
    my $file = shift;
    if ($file =~ /icon/io) {
        -r "$base/images/$file" or
        (mlog(0," BlockReport - unable to open file '$base/images/$file' - using internal image") and return <<'EOT');
R0lGODlhEAAQAPedAEShzke540jB6kfB6ki950SdykWv20a030jB63TR8Uav20e96EecyX+52EWr
1kWl0ky24Eaiz0Odytno8qvQ5d/t9cTo9t/s9EfB68/t+EidyUqcyE/F7kfA6tjp8+Hs9E2l0E7G
7azb78nl8kqw3NXm8Vu02mm63aPW7J7L4kecyL/b64DR7aPY7oTM6YC52ePt9XPR8eXu9fD0+bri
87XV5+Xy+bjg8bjX6brg8ZTD3Uaw27Lf8XLR8Nzq81ulzbHh81aiy3LF5abO5FPC6J/Z77vk9Lbl
9nm42ZHJ43rF5Fylzdjo8VTI7lK54Y7Z88zi73LF5nnE5Ee443TR8Nro8nK32VWx2Xm93l2q0a/S
5kqk0N7r803F7VPF60q/6IXP62q225/d8nnB4fP2+dvp8kOayNvv+Onw9mSp0KLR53jK6vX3+ke+
53/K6NLm8bHj9n3N6lSx2UabyaDM47je78Dc7Euk0HrL6lqkza7a7YW72bni8m2/4cbk8rzY6WvG
6Emw3Pf4+sTe7XzU8nLM7Ei+53TS8HG32dvr86PT6aLP5cPe7X2310u75LDe8XnI59/r86rR5mqu
06HP5U6gy7Te8Ee953TS8UWq1kSl0kOYxk/G7vn5+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJ0ALAAAAAAQABAA
AAjyADMQ6nEoAaaDCBNQiYEJzpEmXThJnCiRA6cQnJ5Y8IIBwQABHz8K6OARgRgbRlgQabOAgEtD
lxZ8KQTkTBFAPPiAcRRgSoCfax7RiIOnxYEDQkTkcOEEQhRLN9xAOAAJhYEdBkj00TPCTx0lgRQY
UCBFUSYHmdI6uGIik5wTaTONUaPpQV1NePGGqZAEL5ZFAAIHjrAFxB1EiTxQimAlhQQJBSLTYTRo
xRsoWexIQoKj0hwGKhiUGNLgRYNJZjRQuMCGC5MJE8qgSbOptu1NNTrp3j2jUZ4fS4LU3iNj925B
MHxE+vBngw4yxqPvrqJld0AAOw==
EOT
    } else {
        -r "$base/images/$file" or $file = 'logo.gif';
    }
    if (open my $F , '<', "$base/images/$file") {
        binmode $F;
        my @img = <$F>;
        close $F;
        return MIME::Base64::encode_base64( join('',@img) );
    } else {
        mlog(0,"warning: BlockReport - unable to open file '$base/images/$file' - no image available");
        return;
    }
}

################################################################################

sub ccMail {
    my($fh,$from,$to,$bod,$sub,$rcpt)=@_;
    my $this=$Con{$fh};
    my $msgtime = $this->{msgtime};
    return if $this->{hamcopydone};
    $this->{hamcopydone} = 1;
    return if !$sendHamInbound && !$sendHamOutbound;
    my $s;
    my $AVa;
    $from = batv_remove_tag(0,$from,'');

    if ($sendHamInbound && localmail($to) && (!$ccHamFilter || allSL($rcpt,$from,'ccHamFilter'))  && ! allSL($rcpt,$from,'ccnHamFilter')) {
        $to=$sendHamInbound;
    } elsif ($sendHamOutbound  && (!$ccHamFilter || allSL($rcpt,$from,'ccHamFilter'))  && ! allSL($rcpt,$from,'ccnHamFilter')) {
        $to=$sendHamOutbound;

    } else {
        return;
    }

    $rcpt =~/($EmailAdrRe)\@($EmailDomainRe)/o;
    my ($current_username,$current_domain) = ($1,$2);
    my $cchamlt = $to;
    $cchamlt =~ s/USERNAME/$current_username/go;
    $cchamlt =~ s/DOMAIN/$current_domain/go;

    if ($ccMailReplaceRecpt && $ReplaceRecpt) {
          my $newcchamlt = RcptReplace($cchamlt,$from,'RecRepRegex');
          if (lc $newcchamlt ne lc $cchamlt) {
              $cchamlt = $newcchamlt;
              mlog($fh,"info: ccMail recipient $cchamlt replaced with $newcchamlt");
          }
    }

    my $destination;
    if ($sendAllHamDestination ne '') {
        $destination = $sendAllHamDestination;
    } elsif ($sendAllDestination ne '') {
        $destination = $sendAllDestination;
    } else {
        $destination = $smtpDestination;
    }
    $AVa = 0;
    foreach my $destinationA (split(/\|/o, $destination)) {
        if ($destinationA =~ /^(_*INBOUND_*:)?(\d+)$/o){
            $destinationA = '127.0.0.1:'.$2;
        }

        $destinationA=~ s/\[::1\]/127\.0\.0\.1/ ;
        $destinationA=~ s/localhost/127\.0\.0\.1/i ;

        if ($AVa<1) {
            $s = $CanUseIOSocketINET6
                 ? IO::Socket::INET6->new(Proto=>'tcp',PeerAddr=>$destinationA,Timeout=>2,&getDestSockDom($destinationA))
                 : IO::Socket::INET->new(Proto=>'tcp',PeerAddr=>$destinationA,Timeout=>2);
            if($s) {
                $AVa=1;
                $destination=$destinationA;
            }
            else {
                mlog(0,"*** $destinationA didn't work, trying others...") if $SessionLog;
            }
        }

    }
    if(! $s) {
        mlog(0,"couldn't create server socket to $destination -- aborting  connection ccmail");
        return;
    }
    addfh($s,\&CChelo);
    $this=$Con{$s};
    $this->{to}=$cchamlt;
    $this->{from}=$from;
    $this->{msgtime} = $msgtime;
    local $/="\n";

    $this->{subject}= ref $sub ? $$sub : $sub;
    $this->{subject}=~s/\r?\n?//go;
    undef $/;

    $this->{body} = ref $bod ? $$bod : $bod;
    $this->{body} =~ s/\r?\n/\r\n/gos;
    $this->{body} =~ s/[\r\n\.]$//o;
}

sub CChelo { my ($fh,$l)=@_;
    if($l=~/^ *220 /o) {
        sendque($fh,"HELO $myName\r\n");
        $Con{$fh}->{getline}=\&CCfrom;
    } elsif ($l=~/^ *220-/o){
    } else {
        CCabort($fh,"helo Expected 220, got: $l (from:$Con{$fh}->{from} to:$Con{$fh}->{to})");
    }
}
sub CCfrom { my ($fh,$l)=@_;
    if($l=~/^ *250 /o) {
        sendque($fh,"MAIL FROM: ".($Con{$fh}->{from}=~/(<[^<>]+>)/o ?$1:"<$Con{$fh}->{from}>")."\r\n");
        $Con{$fh}->{getline}=\&CCrcpt;
    } elsif ($l=~/^ *250-/o) {
    } else {
        CCabort($fh,"HELO send, Expected 250, got: $l (from:$Con{$fh}->{from} to:$Con{$fh}->{to})");
    }
}
sub CCrcpt { my ($fh,$l)=@_;
    if($l!~/^ *250/o) {
        CCabort($fh,"MAIL FROM send, Expected 250, got: $l (from:$Con{$fh}->{from} to:$Con{$fh}->{to})");
    } else {
        sendque($fh,"RCPT TO: <$Con{$fh}->{to}>\r\n");
        $Con{$fh}->{getline}=\&CCdata;
    }
}
sub CCdata { my ($fh,$l)=@_;
    if($l!~/^ *250/o) {
        CCabort($fh,"RCPT TO send, Expected 250, got: $l (from:$Con{$fh}->{from} to:$Con{$fh}->{to})");
    } else {
        sendque($fh,"DATA\r\n");
        $Con{$fh}->{getline}=\&CCdata2;
    }
}
sub CCdata2 { my ($fh,$l)=@_;
    my $this=$Con{$fh};
    if($l!~/^ *354/o) {
        CCabort($fh,"DATA send, Expected 354, got: $l");
    } else {
        $this->{body} =~ s/(?:ReturnReceipt|Return-Receipt-To|Disposition-Notification-To):$HeaderValueRe//gios
            if ($removeDispositionNotification);
        sendque($fh,$this->{body}) if $this->{body};
        sendque($fh,"\r\n.\r\n");
        mlog($fh,"info: message copied to $this->{to}") if $ConnectionLog >= 2;
        $Con{$fh}->{getline}=\&CCquit;
    }
}
sub CCquit { my ($fh,$l)=@_;
    if($l!~/^ *250/o) {
        CCabort($fh,"\\r\\n.\\r\\n send, Expected 250, got: $l");
    } else {
        sendque($fh,"QUIT\r\n");
        $Con{$fh}->{getline}=\&CCdone;
        $Con{$fh}->{type} = 'C';          # start timeout watching for case 221/421 will not be send
        $Con{$fh}->{timelast} = time;
        $Con{$fh}->{nodelay} = 1;
    }
}
sub CCdone { my ($fh,$l)=@_;
    if($l!~/^ *[24]21/o) {
        CCabort($fh,"QUIT send, Expected 221 or 421, got: $l");
    } else {
        done2($fh); # close and delete
    }
}
sub CCabort {mlog(0,"Copy Spam/Ham:CCabort: $_[1]"); done2($_[0]);}

################################################################################
#                SPAM Detection
# check if the message is spam, based on Bayesian factors in $Spamdb
################################################################################
sub BayesOK {
    my ( $fh, $msg, $ip ) = @_;
    my $this = $Con{$fh};
    return 1 if $this->{messagescore} < - 20;
    return 1 if $this->{spamdone};
    return 1 if !$DoBayesian;
    return 1 if !$spamdb;
    return 1 if $this->{notspamtag};

    if (!-e "$base/$spamdb") {
        if (-e "$base/$spamdb.bak") {
             copy("$base/$spamdb.bak","$base/$spamdb");
        }   
    } 

    my ($bd,$ok);
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};
    my $m = ref($msg) ? $$msg : $msg;
    return 1 if $this->{whitelisted} && !$BayesWL;
    return 1 if $this->{noprocessing} && !$BayesNP;
    return 1 if $this->{relayok} && !$BayesLocal;
    my $stime = time;
    my $itime;
    $this->{prepend} = "[Bayesian]";

    if (! $bd) {
        eval {
          local $SIG{ALRM} = sub { die "__alarm__\n" };
          alarm($BayesMaxProcessTime + 60);
          ($bd,$ok) = &clean($msg);
          alarm(0);
        };
        if ($@) {
            alarm(0);
            if ( $@ =~ /__alarm__/o ) {
                my $itime = time - $stime;
                mlog( $fh, "BayesOK: timed out after $itime secs.");
            } else {
                mlog( $fh, "BayesOK: failed: $@");
            }
        }
        unless ($ok) {
            mlog($fh,"info: Bayesian-Process-Timeout ($BayesMaxProcessTime s) is reached - Bayesian Check will only be done on mail header") if ($BayesianLog && time-$stime > $BayesMaxProcessTime);
            my $itime=time-$stime;
            mlog($fh,"info: Bayesian-Check-Conversion has taken $itime seconds") if $BayesianLog >= 2;
            return 1;
        }
    }

    my $mDoBayesian = $DoBayesian;
    $this->{testmode} = 0;
    $this->{testmode} = 1    if $DoBayesian == 4 or $allTestMode;
    $mDoBayesian = 1         if $DoBayesian == 4;
    my $yeslocalbayesian;
    $yeslocalbayesian = $this->{relayok} &&     matchSL($this->{mailfrom},'yesBayesian_local');

    if ($this->{relayok} && !$yeslocalbayesian && $noBayesian_local && matchSL($this->{mailfrom},'noBayesian_local')) {
        mlog($fh,"Bayesian Check skipped for local sender") if $BayesianLog>=2;
        $this->{spamprob}=0;
        return 1;
    }
    if ( $this->{nobayesian} ){
        mlog( $fh, "Bayesian Check skipped for recipient $this->{rcpt} " )
            if $BayesianLog >= 2;
        return 1;
    }
    if (   $noBayesian && matchSL( $this->{mailfrom}, 'noBayesian' ) ){
        mlog( $fh, "Bayesian Check skipped for $this->{mailfrom} " )
          if $BayesianLog >= 2;
        return 1;
    }

    my $tlit = tlit($mDoBayesian);
    my $myip = ipNetwork( $ip, $PenaltyUseNetblocks );
    d('BayesOK');
    my $ipnet = ipNetwork($ip, 1);
    my ( $v, $lt, $t, %seen );
    my @t;my @t2;
    my $count = 0;
    while ( $bd =~ /([-\$A-Za-z0-9\'\.!\240-\377]+)/g ) {
        next if length($1) > 20;
        next if length($1) < 2;
        $lt = $t;
        $t  = BayesWordClean($1);

        my $j = "$lt $t";
        next if $seen{$j}++ > 1;    # first two occurances are significant
        $count++;
            if ($BayesianStarterDB && $enableStarterDB) {
                if  ($v = $Spamdb{$j}) {
                    push( @t, $v );
                } else {
                    push( @t, $v ) if $v = $Starterdb{$j};
                }
            } else {
                push( @t, $v ) if $v = $Spamdb{$j};
            }
    }

    @t=sort {abs($main::b-.5)<=>abs($main::a-.5)} @t;
    @t=@t[0..($maxBayesValues - 1)];
    (my $p1, my $p2, my $c1, $this->{spamprob}, $this->{spamconf}) = BayesProb(\@t);

    my $valence;
    if ($this->{spamprob}>=$baysProbability)  {
        $valence = int (${'baysValencePB'}[0] * $this->{spamprob} + 1);
        $valence = $MessageScoringUpperLimit + 1 if !$this->{messagescore};
    } 

    mlog($fh,"Bayesian Array Totals: @t, derived from $count values") if $BayesianLog >= 2;

    $tlit = "[scoring:$valence]" if $mDoBayesian != 2 && $valence;
    if ($baysConf>0 && $this->{spamprob}>= $baysProbability) {
        mlog($fh, sprintf("Bayesian Check $tlit - Prob: %.5f / Confidence: %.5f => %s.%s", $this->{spamprob}, $this->{spamconf}, $this->{spamconf}<$baysConf?"doubtful":"confident", ($this->{spamprob}<$baysProbability)?"ham":"spam")) if $BayesianLog || $DoBayesian>=2;
    } else {
        mlog($fh, sprintf("Bayesian Check $tlit - Prob: %.5f => %s", $this->{spamprob}, ($this->{spamprob}<$baysProbability)?"ham":"spam")) if $BayesianLog || $mDoBayesian>=2;
    }

    return 1 if $mDoBayesian==2;
    $this->{messagereason}=sprintf("Bayesian Probability: %.5f", $this->{spamprob});

    pbAdd($fh,$this->{ip},$valence,"BayesianProbability") if $this->{spamprob}  >= $baysProbability && $fh;
    $this->{messagereason}=sprintf("Bayesian Confidence: %.5f", $this->{spamconf}) if $this->{spamconf}>=$baysConf && $fh;
    pbAdd($fh,$this->{ip},$valence,"BayesianConfidence") if $this->{spamprob}  < $baysProbability && $this->{spamconf}>=$baysConf && $fh;
    pbAdd($fh,$this->{ip},$baysconfidenceValencePB,"BayesianConfidence") if $baysConf && $this->{spamconf}>=$baysConf && $this->{spamprob}  >= $baysProbability && $fh;
    $this->{myheader}=~s/X-Assp-Bayes-Prob:$HeaderValueRe//gios; # clear out existing X-Assp-Spam-Prob headers
    $this->{myheader}=~s/X-Assp-Bayes-Confidence:$HeaderValueRe//gios; # clear out existing X-Assp-Bayes-Confidence headers
    $this->{myheader} .= sprintf( "X-Assp-Bayes-Probability: %.4f\r\n", $this->{spamprob} )
    if  $AddSpamProbHeader && $this->{myheader} !~ /Probability/;

    $this->{myheader} .= sprintf( "X-Assp-Bayes-Confidence: %.4f (confident)\r\n", $this->{spamconf} )
    if $AddSpamProbHeader && $this->{spamprob} > 0.9 && $baysConf
        && $this->{spamconf}>=$baysConf && $this->{myheader} !~ /Confidence/o &&  $this->{myheader} =~ /Probability/;    
    return  1 if $mDoBayesian == 3;
    return 0 if $this->{spamprob} >= $baysProbability && $mDoBayesian == 1;
    return  1;
}

sub readNorm {
    open (my $F, '<', "$base/normfile") or return 1;
    binmode $F;
    my @t = split(/ /,join('',<$F>));
    close $F;
    $t[0] ||= 1;
    $bayesnorm = $t[0];
}
sub BayesConfNorm {
    my $c = abs(1 - $bayesnorm);
    my $exp = int($c * 10.0001);
    $exp = 4 if $exp > 4;
    return 1 / (($c + 1) ** $exp);
}

sub BayesProb {
    my $t = shift;
    my $p1 = 1;
    my $p2 = 1;
    my $p1c = 1;
    my $p2c = 1;
    my $cc = 0;
    my $c1;
    my $norm = BayesConfNorm();
    foreach my $p (@$t) {
        if ($p) {
            $p1 *= $p;
            $p2 *= ( 1 - $p );
            $c1++;
            if ($p < 0.01) {           # eliminate and count positive extreme ham values for confidence
                $cc++;
                next;
            }
            if ((1 - $p) < 0.01) {     # eliminate and count negative extreme spam values for confidence
                $cc--;
                next;
            }
            $p1c*=$p;                  # use the not extreme values for confidence calculation
            $p2c*=(1-$p);
        }
    }
    my $ps = $p1 + $p2;
    my $SpamProb = $ps ? ($p1 / $ps) : 1;       # default Bayesian math

    #  ignore    ham extremes if spam      and   spam extremes if ham for confidence calculation
    $cc = 0 if ($cc < 0 && $SpamProb > 0.5) or ($cc > 0 && $SpamProb <= 0.5);
    # use the spam/ham extremes left, to set a factor to reduce confidence
    $cc = 0.01 ** abs($cc);

    # found only extreme or no value -> set confidence to 1
    $p1c = 0 if ($p1c == 1 && $p2c == 1);

    # weight the confidence down, if not enough values are available ($c1/$maxBayesValues)**2
    my $SpamProbConfidence = abs( $p1c - $p2c ) * $cc * $norm * ($c1/$maxBayesValues) ** 2;
    $SpamProbConfidence = 1 if $SpamProbConfidence > 1;   # this should never happen -> but be save

    # return spampropval, hampropval, valcount, combined SpamProb, Confidence of combined SpamProb
    return ($p1,$p2,$c1,$SpamProb,$SpamProbConfidence);

}

sub BayesWordClean {
    my $word = lc(shift);
    no warnings qw(utf8);
    Encode::_utf8_on($word);
    $word = substr($word,0,length($word));
    return unless $word;
    eval{
    $word =~ s/#(?:[a-f0-9]{2})+/randcolor/go;
    $word =~ s/^#\d+/randdecnum/go;
    $word =~ s/[_\[\]\~\@\%\$\&\{\}<>#(),.'";:=!?*+\/\\\-]+$//o;
    $word =~ s/^[_\[\]\~\@\%\$\&\{\}<>#(),.'";:=!?*+\/\\\-]+//o;
    $word =~ s/!!!+/!!/go;
    $word =~ s/\*\*+/**/go;
    $word =~ s/--+/-/go;
    $word =~ s/__+/_/go;
    $word =~ s/[\d,.]{2,}/randnumber/go;
    $word =~ s/^[\d:\.\-+();:<>,!"'\/%]+(?:[ap]m)?$/randwildnum/o;    # ignore numbers , dates, times, versions ...
    };
    my $l;
    eval{$l = length($word);};
    return if ($l > 20 or $l < 2);
    Encode::_utf8_off($word);
    return $word;
}

# attach a header line to the message if the config option is set
sub addSpamProb {
    my $fh = shift;
    my $this           = $Con{$fh};
    my $spamprobheader = "";
    my $mscore;
    return if $this->{spamprobheaderdone};
    $this->{spamprobheaderdone} = 1;
    return if $NoExternalSpamProb && $this->{relayok};

    if ($this->{passingreason}) {
        $spamprobheader .= "X-Assp-Passing: $this->{passingreason}\r\n";
        $this->{myheader}=~s/X-Assp-Passing:$HeaderValueRe//gios; 
        # clear out existing X-Assp-Passing headers

    }

    $this->{myheader}.="X-Assp-Redlisted: Yes ($this->{red})\015\012"
        if $this->{red} && $this->{myheader} !~ /X-Assp-Redlisted/o;
    $this->{myheader} =~ s/^X-Assp-Spam-Level:$HeaderValueRe//gios;        
    # clear out existing X-Assp-Spam-Level headers

    $this->{saveprepend} = $this->{prepend};

    my $counter = 0;
    my $stars   = "";
    $mscore = $this->{messagescore};
    if ($this->{spamfound} && $AddScoringHeader && $this->{messagescore} > 0 && $this->{myheader} !~ /totalscore/) {
        $this->{myheader} =~ s/X-Assp-Message-Totalscore:[^\r\n]+?\r\n//iogs;
        $this->{myheader} .= "X-Assp-Message-Totalscore: $this->{messagescore}\r\n" if  $this->{myheader} !~ /Totalscore/i;
    }

    if ((($this->{relayok} && ! $NoExternalSpamProb) || ! $this->{relayok}) && $this->{messagescore} && $AddLevelHeader) {
        my $counter=0;
        my $stars='';
        my $mscore=$this->{messagescore};
        $mscore = 100 if $mscore > 100;
        while ($counter<int($mscore/5)) {
            $counter++;
            $stars.= "*";
        }
        $this->{myheader}=~s/^X-Assp-Spam-Level:$HeaderValueRe//gios; # clear out existing X-Assp-Spam-Level headers
        $this->{myheader}.="X-Assp-Spam-Level: $stars\r\n" if $counter && $this->{spamfound};
    }

    if ( defined( $this->{mailfrom} ) ) {
        $spamprobheader .= "X-Assp-Envelope-From: $this->{mailfrom}\r\n"
            if $spamprobheader !~ /X-Assp-Envelope-From/;

        $this->{myheader} =~ s/^X-Assp-Envelope-From:$HeaderValueRe//gios;
        # clear out existing X-Assp-Envelope-From headers
    }
    my ($to) = $this->{rcpt} =~ /(\S+)/;
    my ($mfd) = $to =~ /\@(.*)/;
    $this->{newrcpt}="";
    foreach my $adr ( split( " ", $this->{rcpt} ) ) {
         $this->{newrcpt} .= "$adr " if $adr =~ /$mfd/;
         last if $AddIntendedForHeader == 1;
    }

    $spamprobheader .= "X-Assp-Intended-For: $this->{newrcpt}\r\n"
            if $spamprobheader !~ /X-Assp-Intended-For/;
    $this->{newrcpt}="";
    $this->{myheader} =~ s/^X-Assp-Intended-For:$HeaderValueRe//gios;

    # add to our header; merge later, when client sent own headers
    $this->{myheader} .= $spamprobheader;

}
# compile the nonprocessing domains regular expression
sub setNPDRE {
    my $new = shift;
    $new ||= '^(?!)';    # regexp that never matches
    $new =~ s/\*/\.\*/g;
    SetRE( 'NPDRE', "($new)\$", 'i', 'NoProcessing Domains' );
    SetRE( 'NPDRE2', "($new)\$", 'i', 'NoProcessing Domains' );
    $NPDRE2 =~ s/\@//g;
}

# compile the whitelisted domains regular expression
sub setWLDRE {
    my $new = shift;
    $new ||= '^(?!)';    # regexp that never matches
    $new =~ s/\*/\.\*/g;
    SetRE( 'WLDRE', "($new)\$", 'i', 'Whitelisted Domains' );
    SetRE( 'WLDRE2', "($new)\$", 'i', 'Whitelisted Domains' );
    $WLDRE2 =~ s/\@//g;

}
# compile the blacklisted domains regular expression
sub setBLDRE {
    my $new=shift;
    $new||='^(?!)'; # regexp that never matches
    $new=~s/\*/\.\*/g;
    SetRE('BLDRE',"(?:$new)\$",

          'i',
          'Blacklisted Domains',$_[0]);
}

# compile the regular expression for the list of two&three-level TLDs
sub setURIBLCCTLDSRE {
    my $s = join( '|', @_ );
    $s ||= '^(?!)';      # regexp that never matches
    SetRE( 'URIBLCCTLDSRE', "([^\\.]+\\.($s))\$", 'i', 'Country Code TLDs' );

}
# compile the regular expression for the list of top-level TLDs
sub setTLDSRE {

$TLDSRE = shift;

}
# compile the URIBL whitelist regular expression
sub setURIBLWLDRE {
    my $new = shift;
    $new ||= '^(?!)';    # regexp that never matches
    $new =~ s/\*/\.\*/g;
    SetRE( 'URIBLWLDRE', "^($new)\$", 'i', 'Whitelisted URIBL Domains' );
    SetRE( 'URIBLWLDRE2', "^($new)\$", 'i', 'Whitelisted URIBL Domains' );
    $URIBLWLDRE2 =~ s/\@//g;
}

# compile the Max IP/Domain whitelist regular expression
sub setIPDWLDRE {
    my $new = shift;
    $new ||= '^(?!)';    # regexp that never matches
    $new =~ s/\*/\.\*/g;
    SetRE( 'IPDWLDRE', "^($new)", 'i', 'Whitelisted IP/Domain Domains' );
}

# see if the address in the mailfrom is on the whitelist meanwhile update the whitelist if that seems appropriate

sub onwhitelist {
    my ( $fh, $header) = @_;
    d('onwhitelist');
    my $this = $Con{$fh};
    my $adr = lc $this->{mailfrom};

    my $whitelisted = $this->{relayok};
    $Stats{locals}++ if $whitelisted;
    return $whitelisted
      unless $adr;  
      # don't add to the whitelist unless there's a valid envelope 
      #- prevent bounced mail from adding to the whitelist
    if (  !$this->{red}
        && $redRe
        && $redReRE != ""
        && $this->{header} =~ /($redReRE)/ ) {
        $this->{red} = ($1||$2);
        mlogRe( $fh, $this->{red}, "Red" ,1);
      }
    mlogRe( $fh, $adr, "Redlist" ,1) if !$this->{red} && $Redlist{$adr};
    $this->{red} = "$adr in Redlist" if $Redlist{$adr};
    $this->{prepend} = "[WhitelistedOK]";
    my %senders;
    unless ($whitelisted) {
        $senders{$adr} = 1;
        if ( !$NotGreedyWhitelist ) {
            while ( $header =~ /\n(from|sender|reply-to|errors-to|list-\w+):.*?($EmailAdrRe\@$EmailDomainRe)/igo ) {
                my $s = $2;

                $senders{lc $s}=1;
              }
          }
        foreach my $adr ( keys %senders ) {

            next if localmail($adr) || $adr eq '';
            if ( $whiteListedDomains &&  $adr=~/($WLDRE)/) {
                d("wld");
                $whitelisted = 1;
                mlog( $fh, "Whitelisted Domain: $1" );
                $this->{passingreason} = "Whitelisted Domain: $1" if !$this->{passingreason};
                pbWhiteAdd( $fh, $this->{ip}, "WhiteDomains" );
                $this->{whitelisted}="Whitelisted Domain: $1";
                last;
              } elsif ( $Whitelist{$adr} ) {
                d("on whitelist");
                $this->{passingreason} = "$adr on whitelistdb" if !$this->{passingreason};
                $whitelisted = 1;
                $this->{whitelisted} = "$adr on whitelistdb";
                pbWhiteAdd( $fh, $this->{ip}, "Whitelist" );
                last;
              }
          }
        $this->{senders} = join( ' ', keys %senders ) . " ";    # used for finding blacklisted domains
        if ($whitelisted || $this->{whitelisted}) {
            $Stats{whites}++;
          }

      }

    # don't add to whitelist if sender is redlisted

    return $whitelisted
      if $this->{red}
          || $WhitelistLocalOnly && !$this->{relayok}
          || $WhitelistLocalFromOnly && !localmail( $this->{mailfrom} );
    if ($whitelisted) {
        $this->{doNotTimeout} = time;
        # keep the whitelist up-to-date
        my %adr = %senders;
        my $t = time;
        $adr{$adr} = 1;
        $header =~ s/\n\s+/ /g;
        while ( $header =~ /\n(to|cc): (.*)/ig ) {
            my $adr = $2;
            while ( $adr =~ /($EmailAdrRe\@$EmailDomainRe)/go ) {
                $adr{ lc $1 } = 1;
              }
        }
        foreach my $adr ( split( ' ', lc $this->{rcpt} ) ) {
            $adr{$adr} = 1;
        }
        foreach my $adr ( keys %adr ) {

            next if localmail($adr) || !$adr;
            next if $Redlist{$adr};             # don't add to whitelist if rcpt is redlisted

            next if $adr =~ s/^\'//;
            next if length($adr) > 127;

           mlog( $fh, "auto whitelist addition: $adr" )
              unless $Whitelist{$adr} || $NoAutoWhite;
          $Whitelist{$adr} = $t unless !$Whitelist{$adr} && $NoAutoWhite;
          $this->{whitepassed} = 1;

        }
        return 1;
      }
      return 0;
}

#Email::MIME substitution for mixed alternative multipart messages
sub parts_multipart {
  my $self     = shift;

  #use the original code, if don't need the hack
  return $org_Email_MIME_parts_multipart->($self) if $o_EMM_pm;

  my $boundary = $self->{ct}->{attributes}->{boundary};

  return $self->parts_single_part
    unless $boundary and $self->body_raw =~ /^--\Q$boundary\E\s*$/sm;

  $self->{body_raw} = $self->body_raw;

  # rfc1521 7.2.1
  my ($body, $epilogue) = split /^--\Q$boundary\E--\s*$/sm, $self->body_raw, 2;

  my @bits = split /^--[^\n\r]+\s*$/sm, ($body || '');

  $self->{body} = undef;
  $self->{body} = (\shift @bits) if ($bits[0] || '') !~ /.*:.*/;

  my $bits = @bits;

  my @parts;
  for my $bit (@bits) {
    $bit =~ s/\A[\n\r]+//smg;
    my $email = (ref $self)->new($bit);
    push @parts, $email;
  }

  $self->{parts} = \@parts;

  return @{ $self->{parts} };
}

sub cleanMIMEHeader2UTF8 {
    my ($m , $noconvert) = @_;
    my $msg = ref($m) ? $$m : $m;
    $msg =~ s/([^\x0D])\x0A/$1\x0D\x0A/go;
    my $hl = index($msg,"\x0D\x0A\x0D\x0A");
    if ($hl > 0) {
        $msg = substr($msg,0,$hl);
        $msg = decodeMimeWords2UTF8($msg) if ! $noconvert;
        $msg .= "\x0D\x0A\x0D\x0A";
        return $msg;
    } elsif ($hl == 0) {
        return "\x0D\x0A\x0D\x0A";
    }
    return;
}

sub cleanMIMEBody2UTF8 {
    my $m = shift;
    my $msg = ref($m) ? $$m : $m;
    $msg =~ s/([^\x0D])\x0A/$1\x0D\x0A/go;
    my $body;
    my %cs;

    eval {
        local $SIG{ALRM} = sub { die "__alarm__\n"; };
        alarm(15);
        $Email::MIME::ContentType::STRICT_PARAMS=0;      # no output about invalid CT
        my $email = Email::MIME->new($msg);
        foreach my $part ( $email->parts ) {
            my $cs;
            my $dis = $part->header("Content-Type") || '';
            next if $part->header("Content-ID") && $dis !~ /text/o;    # no inline images / app's
            my $attrs = $dis =~ s/^.*?;//o ? Email::MIME::ContentType::_parse_attributes($dis) : {};
            my $name =    $attrs->{name}
                       || $part->{ct}{attributes}{name}
                       || $attrs->{filename}
                       || $part->{ct}{attributes}{filename}
                       || $part->filename;
            $cs = $attrs->{charset} || $part->{ct}{attributes}{charset};
            $cs{uc $cs} = "charset=$cs" if $cs;
            if (! $name && ! $addCharsets) {
                $dis = $part->header("Content-Disposition") || '';
                $attrs = $dis =~ s/^.*?;//o ? Email::MIME::ContentType::_parse_attributes($dis) : {};
                $name =    $attrs->{name}
                        || $part->{ct}{attributes}{name}
                        || $attrs->{filename}
                        || $part->{ct}{attributes}{filename};
            }

            my $bd;
            if (! $addCharsets) {
                $bd = $name ? "\r\nattachment:$name\r\n" : $part->body;
            }
            if ($bd && $cs && ! $addCharsets) {
                $bd = Encode::decode($cs, $bd);
                $bd = Encode::encode('UTF-8', $bd);
            }
            $body .= $bd;
        }
        if ($addCharsets) {
            my @mime_coded;
            eval {@mime_coded = $msg =~ /=\?([a-zA-Z0-9\-]{2,20})\?[bq]\?/iog;
                  map {$cs{uc $_} = "charset=$_" if $_;} @mime_coded;
                 };
        }
        if (! $body) {
            my $dis = $email->header("Content-Type") || '';
            my $attrs = $dis =~ s/^.*?;//o ? Email::MIME::ContentType::_parse_attributes($dis) : {};
            my $cs = $attrs->{charset} || $email->{ct}{attributes}{charset};
            $cs{uc $cs} = "charset=$cs" if $cs;
            $body = $email->body unless $addCharsets;
            if ($body && $cs && ! $addCharsets) {
                $body = Encode::decode($cs, $body);
                $body = Encode::encode('UTF-8', $body);
            }
        }
        $body = join("\n", values %cs)."\n" if scalar keys %cs && $addCharsets;
        alarm 0;
        1;
    } and alarm 0 if $CanUseEMM;

    return $body;
}

sub cleanMIME {
    my $msg = shift;
    my $body;

    eval {
        $Email::MIME::ContentType::STRICT_PARAMS=0;      # no output about invalid CT
        my $email=Email::MIME->new($msg);
        foreach my $part ( $email->parts ) {
            my $dis = $part->header("Content-Type") || '';
            my $attrs = $dis =~ s/^.*?;// ? Email::MIME::ContentType::_parse_attributes($dis) : {};
            my $name = $attrs->{name} || $part->{ct}{attributes}{name};
            $name ||= $attrs->{filename} || $part->{ct}{attributes}{filename};
            eval{$name ||= $part->filename;};
            if (! $name) {
              eval{
                $dis = $part->header("Content-Disposition") || '';
                $attrs = $dis =~ s/^.*?;// ? Email::MIME::ContentType::_parse_attributes($dis) : {};
                $name = $attrs->{name} || $part->{ct}{attributes}{name};
                $name ||= $attrs->{filename} || $part->{ct}{attributes}{filename};
              };
            }
            next if $name;   # skip attachments
            $body .= eval{&decHTMLent($part->body);};
        }
        if (! $body) {
            $body = eval{&decHTMLent($email->body);};
        }
    } if $CanUseEMM;
    return $body;
}

# clean up source email

sub clean {
    my $m = shift;

    my $msg = ref($m) ? $$m : $m;
    my $t = time + 60;     # max 60 seconds for this cleaning
    my $body;
    my $header;
    my $undec = 1;

    $body = cleanMIMEBody2UTF8(\$msg);

    if ($body) {
        $header = cleanMIMEHeader2UTF8(\$msg,0);
        headerUnwrap($header);
        $undec = 0;
    }

        local $_= "\n". (($header) ? $header : $msg);
    my ($helo,$rcpt);
    if ($header) {
        ($helo)=/helo=([^)]+)\)/io;
        $helo = substr($helo,0,19); # if the helo string is long, break it up
        my (@sender,@receipt,$sub);
        while (/($HeaderNameRe):($HeaderValueRe)/igos) {
            my($head,$val) = ($1,$2);
            next if $head =~ /^(?:x-assp|(?:DKIM|DomainKey)-Signature)|X-Original-Authentication-Results/oi;
            if ($head =~ /^(to|cc|bcc)$/io) {
                push @receipt, $1 while ($val =~ /($EmailAdrRe\@$EmailDomainRe)/gio);
            }
            if ($head =~ /^(?:from|ReturnReceipt|Return-Receipt-To|Disposition-Notification-To|Return-Path|Reply-To|Sender|Errors-To|List-\w+)/io) {
                push @sender, $1 while ($val =~ /($EmailAdrRe\@$EmailDomainRe)/gio);
            }
            if ($head =~ /^(subject)$/io) {
                $sub = fixsub($val);
            }
        }
        $rcpt = ' rcpt ' . join(' rcpt ',@receipt) if scalar @receipt;
        $rcpt .= ' sender ' . join(' sender ',@sender) if scalar @sender;
        # mark the subject
        $rcpt .= "\n".$sub if $sub;
        return "helo: $helo\n$rcpt\n",0 if (time > $t);
    }

    # from now only do the body if possible
    local $_ = $body if $body;

    # replace HTML encoding
    s/&amp;?/and/gio;
    $_ = decHTMLent($_);
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    if ($undec) {
      # replace base64 encoding
      s/\n([a-zA-Z0-9+\/=]{40,}\r?\n[a-zA-Z0-9+\/=\r\n]+)/base64decode($1)/gseo;

      # clean up quoted-printable references
      s/(Subject: .*)=\r?\n/$1\n/o;
      s/=\r?\n//go;
      # strip out mime continuation
      s/.*---=_NextPart_.*\n//go;
      return "helo: $helo\n$rcpt\n",0 if (time > $t);
    }

    # clean up MIME quoted-printable line breakings
    s/=\r?\n//gos;

    # clean up &nbsp; and &amp;
    s/(\d),(\d)/$1$2/go;
    s/\r//go; s/ *\n/\n/go;
    s/\n\n\n\n\n+/\nblines blines\n/go;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    # clean up html stuff
    s/<\s*(head)\s*>.*?<\/\s*\1\s*>//igos;
    s/<\s*(title|h\d)\s*>(.*?)<\/\s*\1\s*>/fixsub($2)/igse;
    s/<\s*((?:no)?script)[^>]+>.*?<\s*\/\s*\1\s*>/ jscripttag /igs;
    s/<\s*(?:no)?script[^>]+>/ jscripttag /igos;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);
    # remove style sheets
    s/<\s*(style|select)[^>]*>(.*?)<\s*\/\s*\1\s*>/$2/igs;
    # remove comments
    s/(?:<!--.*?-->|<([^>\s]+)[^>]*\s+style=['"]?[^>'"]*(?:display:\s*none|visibility:\s*hidden)[^>'"]*['"]?[^>]*>.*?<\/\1\s*>)//igs;
    s/<\s*!\s*[A-Za-z].*?>//igso;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    s/<\s*(?:[biu]|strong)\s*>/ boldifytext /gio;
    # remove some tags that are not informative
    s/<\s*\/?\s*(?:p|br|div|t[drh]|li|dd|[duo]l|center)[^>]*>/\n/gios;
    s/<\s*\/?\s*(?:[biuo]|strong)\s*>//gio;
    s/<\s*\/?\s*(?:html|meta|head|body|span|table|font|col|map)[^>]*>//igos;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    # look for linked images
    s/(<\s*a[^>]*>[^<]*<\s*img)/ linkedimage $1/giso;
    s/<[^>]*href\s*=\s*("[^"]*"|\S*)/fixhref($1)/isgeo;
    s/(<\s*a\s[^>]*>)(.*?)(<\s*\/a\s*>)/$1.fixlinktext($2)/igseo;

    s/(?:ht|f)tps?:\/\/(\S*)/fixhref($1)/isgeo;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    s/(\S+\@\S+\.\w{2,5})\b/fixhref($1)/geo;
    s/<?\s*img .{0,50}src\s*=\s*['"]([^'"]*)['"][^>]+>/$1/gois;
    s/["']\s*\/?s*>|target\s*=\s*['"]_blank['"]|<\s*\/|:\/\/ //go;
    s/ \d{2,} / 1234 /go;
    $_ = &decHTMLent($_);

    return ("helo: $helo\n$rcpt\n$_",1);

}

sub fixhref { my $t = shift; $t =~ s/(\w+)/ href $1 /g; $t; }

sub fixlinktext { my $t = shift; $t =~ s/(\w+)/atxt $1/g; $t; }

sub fixsub {
    my $s = shift;
    $s =~ s/ {3,}/ lotsaspaces /g;
    $s =~ s/(\S+)/ssub $1/g;
    "\n$s ssub";
}

sub base64decode {
    my $str = shift;
    my $res;
    $str =~ tr|A-Za-z0-9+/||cd;
    $str =~ tr|A-Za-z0-9+/| -_|;
    while ( $str =~ /(.{1,60})/gs ) {
        my $len = chr( 32 + length($1) * 3 / 4 );
        $res .= unpack( "u", $len . $1 );
    }
    $res;
}

sub formatMethod {
    my $res;
    if ( $_[2] == 0 ) {
        $res = int( $_[0] / $_[1] );
        $_[0] -= $res * $_[1];    # modulus on floats
    } elsif ( $_[2] == 1 ) {
        if ( $_[0] >= $_[1] ) {
            $res = sprintf( "%.1f", $_[0] / $_[1] );
            $_[0] = 0;
        }
    }
    return $res;
}

sub formatNumDataSize {
    my $size = shift;
    my $res;
    if ($size >= 1099511627776) {
        $res = sprintf("%.2f TByte", $size / 1099511627776);
    } elsif ($size >= 1073741824) {
        $res = sprintf("%.2f GBbyte", $size / 1073741824);
    } elsif ($size >= 1048576) {
        $res = sprintf("%.2f MByte", $size / 1048576);
    } elsif ($size >= 1024) {
        $res = sprintf("%.2f kByte", $size / 1024);
    } else {
        $res = $size . ' Byte';
    }
    return $res;
}

sub formatDataSize {
    my ( $size, $method ) = @_;
    my ( $res, $s );
    $res .= $s . 'TB ' if $s = formatMethod( $size, 1099511627776, $method );
    $res .= $s . 'GB ' if $s = formatMethod( $size, 1073741824,    $method );
    $res .= $s . 'MB ' if $s = formatMethod( $size, 1048576,       $method );
    $res .= $s . 'kB ' if $s = formatMethod( $size, 1024,          $method );
    if ( $size || !defined $res ) {

        if ( $method == 0 ) {
            $res .= $size . 'B ';
        } elsif ( $method == 1 ) {
            $res .= sprintf( "%.1fB ", $size );
        }
    }
    $res =~ s/\s$//;
    return $res;
}

sub decodeMimeWord {
    my ($fulltext,$charset,$encoding,$text)=@_;
    my $ret;

    eval {$charset = Encode::resolve_alias(uc($charset));} if $charset;

    if (!$@ && $CanUseEMM && $charset && $decodeMIME2UTF8 ) {
        eval{$ret = MIME::Words::decode_mimewords($fulltext)} if $fulltext;
        return $ret unless $@;
    }

    if (lc $encoding eq 'b') {
        $text=base64decode($text);
    } elsif (lc $encoding eq 'q') {
        $text=~s/_/\x20/g; # RFC 1522, Q rule 2
        $text=~s/=([\da-fA-F]{2})/pack('C', hex($1))/ge; # RFC 1522, Q rule 1
    };
    return $text;
}

sub decodeMimeWords {
    my $s = shift;
    headerUnwrap($s);
    $s =~ s/(=\?([^?]+)\?(b|q)\?([^?]*)\?=)/decodeMimeWord($1,$2,$3,$4)/gieo;
    return $s;
}

sub dedecodeMimeWord {
    my ($fulltext,$charset,$encoding,$text)=@_;
    return decodeMimeWord($fulltext,$charset,$encoding,$text) unless $LogCharset;
    my $ret;

    eval {$charset = Encode::resolve_alias(uc($charset));} if $charset;

    if (! $@ && $CanUseEMM && $charset && $decodeMIME2UTF8 ) {
        eval{$ret = MIME::Words::decode_mimewords($fulltext)} if $fulltext;
        if ($LogCharset && $ret) {
            eval{
                 $ret = Encode::decode($charset, $ret);
                 $ret = Encode::encode($LogCharset, $ret);
                 Encode::_utf8_on($ret) if $LogCharset =~ /^utf-?8/io;
            };
        }
        return $ret unless $@;
        $@ = undef;
    }

    if (lc $encoding eq 'b') {
        $text=base64decode($text);
    } elsif (lc $encoding eq 'q') {
        $text=~s/_/\x20/go; # RFC 1522, Q rule 2
        $text=~s/=([\da-fA-F]{2})/pack('C', hex($1))/geo; # RFC 1522, Q rule 1
    }
    if (! $@ && $charset && $LogCharset && $text) {
        eval{
             $text = Encode::decode($charset, $text);
             $text = Encode::encode($LogCharset, $text);
             Encode::_utf8_on($text) if $LogCharset =~ /^utf-?8/io;
        };
    }
    return $text;
}

sub dedecodeMimeWords {
    my $s = shift;
    return decodeMimeWords($s) unless $LogCharset;
    headerUnwrap($s);
    $s =~ s/(=\?([^?]*)\?(b|q)\?([^?]+)\?=)/dedecodeMimeWord($1,$2,$3,$4)/gieo;
    return $s;
}

sub assp_encode_Q {
    my $str = shift;
    my $out;
    eval {$out = MIME::QuotedPrint::encode($str,'');}
      or do {mlog(0,"info: unable to encode string to quoted-printable, will try base64 - $@");};
    return $out;
}

sub assp_encode_B {
    my $str = shift;
    my $out;
    eval {$out = MIME::Base64::encode_base64($str, '');1;}
      or do {mlog(0,"warning: unable to encode string to base64 - $@");};
    return $out;
}

sub encodeMimeWord {
    my $word = shift;
    return '' unless $word;
    my $encoding = uc(shift || 'Q');
    my $charset  = uc(shift || 'UTF-8');
    use bytes;
    my $encfunc  = (($encoding eq 'Q') ? \&assp_encode_Q : \&assp_encode_B);
    my $encword = &$encfunc($word);
    if ($word && ! $encword && $encoding eq 'Q') {
        $encword = &assp_encode_B($word);
        $encoding = 'B';
    }
    return "=?$charset?$encoding?" . $encword . "?=";
}

sub downloadGripConf {
    return if $AsASecondary;
    d('downloadGripConf-start');

    my $ret;
    my $file = "$base/griplist.conf";
    $ret = downloadHTTP("http://downloads.sourceforge.net/project/assp/griplist/griplist.conf",
                 $file,
                 0,
                 "griplist.conf",5,9,2,1);
    mlog(0,"info: updated GRIPLIST upload and download URL's in $file") if $ret;
    $ret = 0;
    open my $GC , '<', $file or return 0;
    binmode $GC;
    while (<$GC>) {
        s/\r|\n//o;
        if (/^\s*(gripList(?:DownUrl|UpUrl|UpHost))\s*:\s*(.+)$/) {
            ${$1} = $2;
            $ret++;
        }
    }
    close
    mlog(0,"info: loaded GRIPLIST upload and download URL's from $file") if $ret;
    mlog(0,"info: GRIPLIST config $file is possibly incomplete") if $ret < 3;
    $gripListDownUrl =~ s/\*HOST\*/$gripListUpHost/o;
    $gripListUpUrl  =~ s/\*HOST\*/$gripListUpHost/o;
    return $ret;
}
sub downloadGrip {
    my $noskip = shift;
    return if $AsASecondary;
    d('griplistdownload-start');
    no warnings 'once';
    &mlog(0,"Griplist file not configured")  if (!$griplist);
    return if (!$griplist);

    my $rc;
    &downloadGripConf();  # reload the griplist.conf
    my $gripListUrl = $gripListDownUrl;
    my $gripFile    = "$base/$griplist";
    my $gripListDownUrlAdd;
    my $dltime = time;
    ## let's check if we really need to
    if (-e $gripFile && !$noskip) {
        my @s     = stat($gripFile);
        my $mtime = $s[9];
        my $random = int(rand(48-24)+24)+1; 
        if (time - $mtime < $random*60*60) {
            return;
        }
    }

    # check for previous download timestamp, so we can do delta now
    my %lastdownload;
    $lastdownload{full} = 0;
    $lastdownload{fullUTC} = 0;
    $lastdownload{delta} = 0;
    $lastdownload{deltaUTC} = 0;
    my $delta = "";
    if (open(my $UTC, "<","$gripFile.utc")) {
        local $/;
        my $buf = <UTC>;
        close($UTC);
        chop($buf);
        if ($buf =~ /full/ && $buf =~ /delta/) {
            %lastdownload = split(/\s+|\n/, $buf);
        } else {
            $lastdownload{delta} = $buf;
        }
        if (! ($DoFullGripDownload && time - $lastdownload{fullUTC} > $DoFullGripDownload*24*60*60)) {
            my $lasttime;
            $lasttime = $lastdownload{full};
            $lasttime = $lastdownload{delta} if ($lastdownload{delta} > $lastdownload{full});
            $gripListDownUrlAdd = "&delta=$lasttime";
            $delta = " (delta)";
        }
    }

    if (open(my $TEMPFILE, ">", "$gripFile.tmp")) {
        #we can create the file, this is good, now close the file and keep going.
        close $TEMPFILE;
        unlink("$gripFile.tmp");
    } else {
        &mlog(0,"Griplist download failed: Cannot create $gripFile.tmp");
        return;
    }

    my $gripListDownUrlL = $gripListDownUrl . $gripListDownUrlAdd;
    my $ret = downloadHTTP($gripListDownUrlL,
        "$gripFile.tmp",
        \$NextGriplistDownload,
        "Griplist$delta",5,9,2,1);

    # download complete
    my $filesize = -s "$gripFile.tmp";
    &mlog(0,"Griplist download complete: binary download $filesize bytes");

    # enough data?
    if ($filesize < 12) {
        &mlog(0,"Griplist download error: grip data too small");
        unlink("$gripFile.tmp");
        return;
    }

    # record download time so we can do delta next time
    unlink("$gripFile.utc");
    if (open(my $UTC, ">","$gripFile.utc")) {
        my ($sec, $min, $hour, $day, $mon, $year, $wday, $yday, $isdst) = gmtime($dltime);
        $year += 1900;
        $mon += 1;
        if (! $delta) {
            $lastdownload{full} = sprintf "%04d%02d%02d%02d%02d%02d", $year, $mon, $day, $hour, $min, $sec;
            $lastdownload{fullUTC} = $dltime;
        } else {
            $lastdownload{delta} = sprintf "%04d%02d%02d%02d%02d%02d", $year, $mon, $day, $hour, $min, $sec;
            $lastdownload{deltaUTC} = $dltime;
        }
        printf $UTC "full\t%s\n", $lastdownload{full};
        printf $UTC "fullUTC\t%s\n", $lastdownload{fullUTC};
        printf $UTC "delta\t%s\n", $lastdownload{delta};
        printf $UTC "deltaUTC\t%s\n", $lastdownload{deltaUTC};
        close($UTC);
    }

    # if we did a delta download, read in previous data so we can merge
    my @binFiles;
    push(@binFiles, "$gripFile.bin") if ($gripListUrl =~ /delta=/);
    push(@binFiles, "$gripFile.tmp");

    # convert binary download form to text form used by ASSP
    my $buf;
    my %grip;
    my $action = "read";
    foreach my $binF (@binFiles) {
        my $binSize = -s $binF;
        open(my $BIN,"<", $binF);   
        binmode($BIN);
        read($BIN, $buf, $binSize);
        close($BIN);

    # IPv6 count
        my ($n6h, $n6l) = unpack("N2", $buf);
        my $n6 = $n6h * 2**32 + $n6l;

    # IPv4 count
        my $n4;
        eval { $n4 = unpack("x[N2] N", $buf); };

    # decode IPv6 data
        my $x6 = 0;
        eval {
        for (my $i = 0; $i < $n6; $i++) {
        my ($bip, $grey) = unpack("x[N2] x[N] x$x6 a8 C", $buf);
        my $ip = join(":", unpack("H4H4H4H4", $bip)) . ":";
        $ip =~ s/:0+([0-9a-f])/:$1/gio;
        $ip =~ s/:0:$/::/o;
        $x6 += 9;
        }
        };

    # decode IPv4 data
        my $x4 = 0;
        for (my $i = 0; $i < $n4; $i++) {
        my ($bip, $grey) = unpack("x[N2] x[N] x$x6 x$x4 a3 C", $buf);
        my $ip = join(".", unpack("C3", $bip));
        $grip{$ip} = $grey / 255;

        $x4 += 4;
    }
        &mlog(0,"Griplist binary $action OK: $binF, $n6 IPv6 addresses, $n4 IPv4 addresses");
        $action = "merge";
    }

    # remove download file
    unlink("$gripFile.tmp");

    # output binary version, so we can do a delta next time
    &mlog(0,"Writing merged Griplist binary...");

    my $n6 = 0;
    my $n4 = 0;
    my ($buf6, $buf4);
    foreach my $ip (keys %grip) {
        if ($ip =~ /:/) {
            my $ip2 = $ip;
            $ip2 =~ s/([0-9a-f]*):/0000$1:/gi;
            $ip2 =~ s/0*([0-9a-f]{4}):/$1:/gi;
            $buf6 .= pack("H4H4H4H4", split(/:/, $ip2));
            $buf6 .= pack("C", int($grip{$ip} * 255));
            $n6++;
        } else {
            $buf4 .= pack("C3C", split(/\./, $ip), int($grip{$ip} * 255));
            $n4++;
        }
    }
    $buf = pack("N2", $n6/2**32, $n6);
    $buf .= pack("N", $n4);
    $buf .= $buf6 . $buf4;
    unlink("$gripFile.bin");
    open (my $BIN, ">", "$gripFile.bin");
    binmode($BIN);
    print $BIN $buf;
    close($BIN);
    chmod 0644, "$gripFile.bin";

    # output text version
    &mlog(0,"Writing merged Griplist text...");
    unlink("$gripFile");
    open (my $TEXT, ">","$gripFile");
    binmode($TEXT);
    print $TEXT "\n";
    foreach my $ip (sort keys %grip) {

        printf $TEXT "$ip\002%.2f\n", $grip{$ip};
    }
    close($TEXT);
    chmod 0644, "$gripFile";

    &mlog(0,"Griplist writing complete: $n6 IPv6 addresses, $n4 IPv4 addresses");
}

sub downloadDropList {
    d('droplistdownload-start');
    my $ret;

    my ($file) = $droplist =~ /^ *file: *(.+)/io;
    $ret = downloadHTTP("http://www.spamhaus.org/drop/drop.lasso",
                 "$base/$file.tmp",
                 \$NextDroplistDownload,
                 "Droplist",5,9,2,1) if $file;
    if ($ret) {
        open (my $F, '<' , "$base/$file.tmp") or return;
        my $firstline = <$F>;
        close $F;
        if ($firstline =~ /^\s*;\s*Spamhaus\s+DROP\s+List/io ) {
            unlink "$base/$file";
            copy("$base/$file.tmp","$base/$file");
        } else {
            mlog(0,"warning: the file $droplist was downloaded but contains no usable data - ignoring the download");
            return;
        }
        $ConfigChanged = 1;         # tell all to reload Config
    }
    return $ret;
}

sub downloadStarterDB {
    d('downloadStarterDB-start');
    my $file = "$base/starterdb/spamdb.gz";
    mlog(0,"info: starting download $file");

        my $ret = downloadHTTP("http://sourceforge.net/projects/assp/files/ASSP%20Installation/Spam%20Collection/spamdb.gz",
                 "$file",
                 \$NextDroplistDownload,
                 "spamdb.gz",5,9,2,1);
        return 0 unless $ret;
        unlink ("$base/starterdb/spamdb");
        if (unzipgz("$base/starterdb/spamdb.gz", "$base/starterdb/spamdb")) {
            mlog(0,"info:  current starterdb '$base/starterdb/spamdb' available ") if $MaintenanceLog;
        } else {
            mlog(0,"warning:  unable to unzipgz '$base/starterdb/spamdb.gz' to '$base/starterdb/spamdb'");
        return 0;
        }

    return $ret;
}

sub downloadTLDList {
    d('TLDlistdownload-start');
    my $ret;
    my $ret2;
    my $ret3;
    my $n1;
    my $n2;
    my $n3;
    $NextTLDlistDownload = time + 720000;

    my ($file) = $TLDS =~ /^ *file: *(.+)/io;
    $ret = downloadHTTP(
                 $tlds_alpha_URL,
                 "$base/$file",
                 \$n1,
                 "TLDlist",24,48,2,1) if $file;

    ($file) = $URIBLCCTLDS =~ /^ *file: *(.+)/io;
    $ret2 = downloadHTTP(
                 $tlds2_URL,
                 "$base/files/URIBLCCTLDS-L2.txt",
                 \$n2,
                 "level-2-TLDlist",24,48,2,1) if $file;
    $ret3 = downloadHTTP(
                 $tlds3_URL,
                 "$base/files/URIBLCCTLDS-L3.txt",
                 \$n3,
                 "level-3-TLDlist",24,48,2,1) if $file;

    if (! $file) {
        if ($n1) {
            $NextTLDlistDownload = $n1;
            return $ret;
        }
    }

    $NextTLDlistDownload  =  ($n1 && $n1 < $n2) ? $n1 : ($n2 > 0) ? $n2 : $NextTLDlistDownload;
    $NextTLDlistDownload  =  $n3 if $n3 &&  $NextTLDlistDownload > $n3;

    if ($file &&
        -s "$base/files/URIBLCCTLDS-L2.txt" > 0 &&
        -s "$base/files/URIBLCCTLDS-L3.txt" > 0 &&
        ($ret2 || $ret3 || ! -e "$base/$file" || -s "$base/$file" == 0))
    {
        if (((open my $f1 ,'<' ,"$base/files/URIBLCCTLDS-L2.txt") || mlog(0,"error: unable to open $base/files/URIBLCCTLDS-L2.txt")&0) &&
            ((open my $f2 ,'<' ,"$base/files/URIBLCCTLDS-L3.txt") || mlog(0,"error: unable to open $base/files/URIBLCCTLDS-L3.txt")&0) &&
            ((open my $f3 ,'>' ,"$base/$file") || mlog(0,"error: unable to open $base/$file")&0))
        {
            binmode $f3;
            print $f3 "# two level TLDs\n\n";
            while (<$f1>) {
                s/\r?\n//o;
                next unless $_;
                print $f3 "$_\n";
            }
            mlog(0,"info: merged file $base/files/URIBLCCTLDS-L2.txt in to $base/$file for URIBLCCTLDS") if $MaintenanceLog >= 2;
            print $f3 "\n\n";
            print $f3 "# three level TLDs\n\n";
            while (<$f2>) {
                s/\r?\n//o;
                next unless $_;
                print $f3 "$_\n";
            }
            mlog(0,"info: merged file $base/files/URIBLCCTLDS-L3.txt in to $base/$file for URIBLCCTLDS") if $MaintenanceLog >= 2;
            close $f3;
            close $f2;
            close $f1;
            mlog(0,"info: file $base/$file updated for URIBLCCTLDS") if $MaintenanceLog;
            $ret2 = 1;
        } else {
            mlog(0,"error: unable to read or write one of the URIBLCCTLDS files - $!");
        }
    }

    $ConfigChanged = 1 if $ret || $ret2 || $ret3;         # tell all to reload Config
    return $ret || $ret2 || $ret3;
}

sub UpdateDownloadURLs {
    if (open my $UVS ,"<", "$base/version.txt") {
        while (<$UVS>) {
            s/\n|\r//g;
            s/^\s+//;
            s/\s+$//;
            next if /^\s*[#;]/o;
            if (/^\s*versionURL\s*:\s*(http(?:s)?:\/\/.+)$/i) {
                my $old = $versionURL;
                $versionURL = $1;
                mlog(0,"autoupdate: version.txt file download URL changed from $old to $versionURL") if $versionURL ne $old;
                next;
            } 
            if (/^\s*NewAsspURL\s*:\s*(http(?:s)?:\/\/.+)$/io) {
                my $old = $NewAsspURL;
                $NewAsspURL = $1;
                mlog(0,"autoupdate: ASSP file download URL changed from $old to $NewAsspURL") if $NewAsspURL ne $old;
                next;
            }
            if (/^\s*ChangeLogURL\s*:\s*(http(?:s)?:\/\/.+)$/io) {
                my $old = $ChangeLogURL;
                $ChangeLogURL = $1;
                mlog(0,"autoupdate: ASSP changelog download URL changed from $old to $ChangeLogURL") if $ChangeLogURL ne $old;
                next;
            }
            if (/^\s*(\w+)\s*:\s*(.+)$/io) {
                my ($var,$val) = ($1,$2);
                next unless defined ${$var};
                $val =~ s/\s+$//o;
                my $old = ${$var};
                ${$var} = $val;
                if (exists $Config{$var}) {
                    $Config{$var} = $val;
                    $ConfigChanged = 1;
                    mlog(0,"autoupdate: version file changed $var from $old to $val") if $val ne $old;
                }
                next;
            }
        }
        close $UVS;
    }
}

sub downloadVersionFile {
    d('downloadVersionFile-start');
       my $force;
    $force = 1 if ($NextASSPFileDownload == -1 or $NextVersionFileDownload == -1);
    &UpdateDownloadURLs();
    $NewAsspURL = $NewAsspURLStable;
    $versionURL = $versionURLStable;
    $ChangeLogURL =  $ChangeLogURLStable;

    return 0 unless $versionURL;
    my $ret;
    my $file = "$base/version.txt";
    $ret = downloadHTTP("$versionURL",
                 $file,
                 \$NextVersionFileDownload,
                 "assp version check",16,12,4,4) if $file;

    if ( !-e "$base/docs/changelog.txt" or $ret) {
        &UpdateDownloadURLs();
        downloadHTTP("$ChangeLogURL",
                     "$base/docs/changelog.txt",
                     0,
                     "assp change log",16,12,4,4);
    }

    if (open my $VS , "<","$file") {
        while (<$VS>) {
            s/\n|\r//g;
            s/^\s+//;
            s/\s+$//;
            next if /^#|;/;
            s/#.*//;
            s/;.*//;
            if (/^\s*(\d+\.\d+\.\d+.+)$/) {
                $availversion = $1;
                my $avv = "$availversion";
                my $stv = "$version$modversion";

                $avv =~ s/RC/\./gi;
                $stv =~ s/RC/\./gi;
                $avv =~ s/\s|\(|\)//gi;
                $stv =~ s/\s|\(|\)//gi;
                $avv =~ s/\.//gi;
                $stv =~ s/\.//gi;
                $avv =~ s/^19/109/gi; #Make 1.9.x Versions 1.09.x for new download comparison.

                if ($avv gt $stv) {
                    mlog(0,"autoupdate: new assp version $availversion is available for download at $NewAsspURL");
                    $ret = 1;
                } else {
                    $ret = 0;
                }
            } elsif (/^\s*versionURL\s*:\s*(http(?:s)?:\/\/.+)$/i) {
                $versionURL = $1;
            } elsif (/^\s*NewAsspURL\s*:\s*(http(?:s)?:\/\/.+)$/i) {
                $NewAsspURL = $1;

            }
        }
        close $VS;
    } else {
        $ret = 0;
    }
    return $ret || $force;
}
sub codeChangeCheck {
    return 0 unless $AutoUpdateASSP == 2;
    d('codeChangeCheck');
    my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime(time);
    if ((lc $AutoRestartAfterCodeChange eq 'immed' ||
        ( $AutoRestartAfterCodeChange && $codeChanged && $hour == $AutoRestartAfterCodeChange)) &&
        ($AsAService || $AsADaemon || $AutoRestartCmd ) &&
        $NextCodeChangeCheck < time) {
        $assp = $0;
        $assp =~ s/\\/\//go;
        $assp = $base.'/'.$assp if ($assp !~ /\//);
    }
    if ((lc $AutoRestartAfterCodeChange eq 'immed' ||
        ( $AutoRestartAfterCodeChange && $codeChanged && $hour == $AutoRestartAfterCodeChange)) &&
        ($AsAService || $AsADaemon || $AutoRestartCmd ) &&
        $NextCodeChangeCheck < time &&
        -e "$assp" &&
        fileUpdated($assp,'asspCode')
       )
    {
        my @s     = stat($assp);
        my $mtime = $s[9];
        $FileUpdate{"$assp".'asspCode'} = $mtime;
        mlog(0,"autoupdate: info:  new '$assp' script detected - performing syntax check on new script");
        my $cmd;
        if ($^O eq "MSWin32") {
            $cmd = '"' . $^X . '"' . " -c \"$assp\" 2>&1";
        } else {
            $cmd = '\'' . $^X . '\'' . " -c \'$assp\' 2>&1";
        }
        my $res = qx($cmd);
        if ($res =~ /syntax\s+OK/ig) {
            mlog(0,"autoupdate: info:  new '$assp' script detected - syntax check returned OK - initialize automatic restart for ASSP in 15 seconds");
            $doShutdown = time + 15;
        } else {
            mlog(0,"autoupdate: error:  new '$assp' script detected - syntax error in new script - skipping automatic restart - syntax error is: $res");
        }
        $NextCodeChangeCheck = time + 60;
        $codeChanged = '';
    } else {
        $NextCodeChangeCheck = time + 60 if $NextCodeChangeCheck < time;
    }

}    
sub downloadASSPVersion {
    d('downloadASSPVersion-start');

    return 0 unless $AutoUpdateASSP;
    &UpdateDownloadURLs();
    if ($AutoUpdateASSPDev) {
    $NewAsspURL = $NewAsspURLDev;

    $versionURL = $versionURLDev;
    $ChangeLogURL =  $ChangeLogURLDev;
    } else {
    $NewAsspURL = $NewAsspURLStable;

    $versionURL = $versionURLStable;
    $ChangeLogURL =  $ChangeLogURLStable;
    }       
    return 0 unless $NewAsspURL;
    return 0 unless $versionURL;

    my $assp = $PROGRAM_NAME;
    $assp =~ s/\\/\//g;
    $assp =~ s/\/\//\//g;
    $assp = $base.'/'.$PROGRAM_NAME if ($assp !~ /\Q$base\E/i);
    if (-e "$base/download/assp.pl" && ! -w "$base/download/assp.pl") {
        mlog(0,"autoupdate: warning:  unable to write to $base/download/assp.pl - skip update - please check the file permission");
        $NextASSPFileDownload = time + 3600;
        return 0;
    }
    if (-e "$base/download/assp.pl.gz" && ! -w "$base/download/assp.pl.gz") {
        mlog(0,"autoupdate: warning:  unable to write to $base/download/assp.pl.gz - skip update - please check the file permission");
        $NextASSPFileDownload = time + 3600;
        return 0;
    }
    if (! -w "$assp") {
        mlog(0,"autoupdate: warning:  unable to write to $assp - skip update - please check the file permission");
        $NextASSPFileDownload = time + 3600;
        return 0;
    }
    -d "$base/download" or mkdir "$base/download", 0777;
    if (! -e "$base/download/assp.pl" && ! copy("$assp","$base/download/assp.pl")) {
        mlog(0,"autoupdate: warning:  unable to copy current script '$assp' to '$base/download/assp.pl' - skip update - $!");
        $NextASSPFileDownload = time + 3600;
        return 0;
    }
    unless (&downloadVersionFile()){
        $NextASSPFileDownload = $NextVersionFileDownload;
        return 0;
    }
    my $ret;
    $NextASSPFileDownload = 0;
    mlog(0,"autoupdate: info:  performing assp.pl.gz download to $base/download/assp.pl.gz") if $MaintenanceLog;
    $ret = downloadHTTP("$NewAsspURL",
                 "$base/download/assp.pl.gz",
                 \$NextASSPFileDownload,
                 "assp.pl.gz",16,12,4,4);
    return 0 unless $ret;
    mlog(0,"autoupdate: info:  new assp.pl.gz downloaded to $base/download/assp.pl.gz") if $MaintenanceLog;
    if (unzipgz("$base/download/assp.pl.gz", "$base/download/assp.pl")) {
        mlog(0,"autoupdate: info:  new assp version '$base/download/assp.pl' available - version $availversion") if $MaintenanceLog;
    } else {
        mlog(0,"autoupdate: warning:  unable to unzip '$base/download/assp.pl.gz' to '$base/download/assp.pl' - skip update");
        return 0;
    }

    mlog(0,"autoupdate: info:  saving current script '$assp' to 'assp_$version$modversion.pl'") if $MaintenanceLog;
    if (! copy("$assp","$base/download/assp_$version$modversion.pl")) {
        mlog(0,"autoupdate: warning:  unable to save current script '$assp' to '$base/download/assp_$version$modversion.pl' - skip update - $!");
        return 0;
    }
    my $cmd;
    if ($^O eq "MSWin32") {
        $cmd = '"' . $^X . '"' . " -c \"$base/download/assp.pl\" \"$base\" 2>&1";
    } else {
        $cmd = '\'' . $^X . '\'' . " -c \'$base/download/assp.pl\' \'$base\' 2>&1";
    }
    my $res = qx($cmd);
    if ($res =~ /syntax\s+OK/ig) {
        mlog(0,"autoupdate: info:  syntax check for '$base/download/assp.pl' returned OK");
    } else {
        mlog(0,"autoupdate: warning:  syntax error in '$base/download/assp.pl' - skip assp.pl update - syntax error is: $res");
        return 0;
    }
    if ($res =~ /assp\s+(.+)?is starting/i) {
        my $v = $1;
        $v =~ s/RC/\./gi;
        $v =~ s/\s|\(|\)//gi;
        $v =~ s/\.//gi;
        my $stv = "$version$modversion";
        $stv =~ s/RC/\./gi;
        $stv =~ s/\s|\(|\)//gi;               
        $stv =~ s/\.//gi;
        $v =~ s/^19/109/gi; #Make 1.9.x Versions 1.09.x for new download comparison.
        if ($stv ge $v) {
            mlog(0,"autoupdate: warning:  version of downloaded '$base/download/assp.pl' ($v) is less or equal to the running version of assp ($stv) - skip assp.pl update");
            return 0;
        }
    }
    return 0 if $AutoUpdateASSP == 1;
    if (copy("$base/download/assp.pl", "$assp")) {
        mlog(0,"autoupdate: info:  new version assp installed - '$assp' - version $availversion");
    } else {
        mlog(0,"autoupdate: warning:  unable to replace current script '$assp' - skip update - $!");
        return 0;
    }

    return 1 if (lc $AutoRestartAfterCodeChange eq 'immed' &&
        ($AsAService || $AsADaemon || $AutoRestartCmd ));
    $codeChanged = 1 if $AutoRestartAfterCodeChange;
    return 1;
}

sub uploadStats {

    my ( $peeraddress, $connect );
    if ($proxyserver) {
        mlog( 0, "uploading stats via proxy:$proxyserver" ) if $MaintenanceLog;
        $peeraddress = $proxyserver;
        $connect =
          "POST http://assp.sourceforge.net/cgi-bin/assp_stats HTTP/1.0";
    } else {
        mlog( 0, "uploading stats via direct connection" ) if $MaintenanceLog;
        $peeraddress = "assp.sourceforge.net:80";
        $connect     = "POST /cgi-bin/assp_stats HTTP/1.1
Host: assp.sourceforge.net";
    }
    my $s;
    if ($CanUseIOSocketINET6) {
    $s = new IO::Socket::INET6(
        Proto    => 'tcp',
        PeerAddr => $peeraddress,
        Timeout  => 2
    );
    } else {
    $s = new IO::Socket::INET(
        Proto    => 'tcp',
        PeerAddr => $peeraddress,
        Timeout  => 2
    );
    }
    if ($s) {
        my %UploadStats = ();
        my %tots = statsTotals();
        my $buf;

    # spam filtering counters
    foreach (keys %Stats) {$UploadStats{$_} = $Stats{$_};}

    # stats upload version
        $UploadStats{upproto_version}      = 2;

    # ASSP version
        $UploadStats{version}              = $Stats{version};

        # message totals
        $UploadStats{upproto_version}      = 2;
        $UploadStats{timenow}              = time;
        $UploadStats{connects}             = $tots{smtpConnTotal};
        $UploadStats{messages}             = $tots{msgTotal};
        $UploadStats{spams}                = $tots{msgRejectedTotal} - $Stats{bspams};
        delete $UploadStats{nextUpload};

        my $content = join( "\001", %UploadStats );
        my $len = length($content);
        $connect .= "
Content-Type: application/x-www-form-urlencoded
Content-Length: $len

$content";
        print $s $connect;
        $s->sysread($buf, 4096);
        $s->close;
    } else {
        mlog( 0, "unable to connect to stats server" );
    }
    $Stats{nextUpload} = time + 3600 * 12;
}

sub ResetStats {
    $Stats{nextUpload}                = time + 3600 * 12;
    $Stats{cpuTime}                   = 0;
    $Stats{cpuBusyTime}               = 0;
    $Stats{sbblocked}                 = 0;
    $Stats{smtpConn}                  = 0;
    $Stats{smtpConnNotLogged}         = 0;
    $Stats{smtpConnLimit}             = 0;
    $Stats{smtpConnLimitIP}           = 0;
    $Stats{smtpConnDomainIP}          = 0;
    $Stats{SameSubject}               = 0;
    $Stats{smtpConnLimitFreq}         = 0;
    $Stats{smtpConnDenied}            = 0;
    $Stats{smtpConnIdleTimeout}       = 0;
    $Stats{smtpConnSSLIdleTimeout}    = 0;
    $Stats{smtpConnTLSIdleTimeout}    = 0;
    $Stats{smtpConcurrentSessions}    = 0;
    $Stats{smtpMaxConcurrentSessions} = 0;
    $Stats{admConn}                   = 0;
    $Stats{admConnDenied}             = 0;
    $Stats{statConn}                  = 0;
    $Stats{statConnDenied}            = 0;
    $Stats{rcptValidated}             = 0;
    $Stats{rcptUnchecked}             = 0;
    $Stats{rcptSpamLover}             = 0;
    $Stats{rcptWhitelisted}           = 0;
    $Stats{rcptNotWhitelisted}        = 0;
    $Stats{rcptUnprocessed}           = 0;
    $Stats{rcptReportSpam}            = 0;
    $Stats{rcptReportHam}             = 0;
    $Stats{rcptReportWhitelistAdd}    = 0;
    $Stats{rcptReportWhitelistRemove} = 0;
    $Stats{rcptReportRedlistAdd}      = 0;
    $Stats{rcptReportRedlistRemove}   = 0;
    $Stats{rcptReportAnalyze}         = 0;
    $Stats{rcptReportHelp}            = 0;
    $Stats{rcptNonexistent}           = 0;
    $Stats{rcptDelayed}               = 0;
    $Stats{rcptDelayedLate}           = 0;
    $Stats{rcptDelayedExpired}        = 0;
    $Stats{rcptEmbargoed}             = 0;
    $Stats{rcptSpamBucket}            = 0;
    $Stats{rcptRelayRejected}         = 0;
    $Stats{senderInvalidLocals}       = 0;
    $Stats{pbdenied}                  = 0;
    $Stats{pbextreme}                 = 0;
    $Stats{delayConnection}           = 0;
    $Stats{denyConnection}            = 0;
    $Stats{denyStrict}                = 0;
    $Stats{msgscoring}                = 0;
    $Stats{msgMaxErrors}              = 0;
    $Stats{msgDelayed}                = 0;
    $Stats{msgNoRcpt}                 = 0;
    $Stats{msgNoSRSBounce}            = 0;
    $Stats{bhams}                     = 0;
    $Stats{whites}                    = 0;
    $Stats{locals}                    = 0;
    $Stats{localFrequency}            = 0;
    $Stats{noprocessing}              = 0;
    $Stats{spamlover}                 = 0;
    $Stats{bspams}                    = 0;
    $Stats{blacklisted}               = 0;
    $Stats{invalidHelo}               = 0;
    $Stats{forgedHelo}                = 0;
    $Stats{mxaMissing}                = 0;
    $Stats{ptrMissing}                = 0;
    $Stats{ptrInvalid}                = 0;
    $Stats{helolisted}                = 0;
    $Stats{spambucket}                = 0;
    $Stats{penaltytrap}               = 0;
    $Stats{viri}                      = 0;
    $Stats{viridetected}              = 0;
    $Stats{bombs}                     = 0;
    $Stats{bombs}                     = 0;
    $Stats{msgverify}                 = 0;
    $Stats{scripts}                   = 0;
    $Stats{internaladdresses}         = 0;
    $Stats{spffails}                  = 0;
    $Stats{rblfails}                  = 0;
    $Stats{uriblfails}                = 0;
    $Stats{msgMaxErrors}              = 0;
    $Stats{msgMSGIDtrErrors}          = 0;
    $Stats{msgBackscatterErrors}      = 0;
    $Stats{AUTHErrors}                = 0;
    $Stats{msgMSGIDtrErrors}          = 0;
    $Stats{preHeader}                 = 0;

    open( $FH, "<","$base/asspstats.sav" );
    (%OldStats) = split( /\001/, <$FH> );
    close $FH;

    # conversion from previous versions
    if ( exists $OldStats{messages} ) {
        $OldStats{smtpConn}        = $OldStats{connects};
        $OldStats{smtpConnLimit}   = $OldStats{maxSMTP};
        $OldStats{smtpConnLimitIP} = $OldStats{maxSMTPip};
        $OldStats{viri} -= $OldStats{viridetected};    # fix double counting
        $OldStats{rcptRelayRejected} = $OldStats{norelays};

        # remove unused entries
        delete $OldStats{connects};
        delete $OldStats{maxSMTP};
        delete $OldStats{maxSMTPip};
        delete $OldStats{messages};
        delete $OldStats{spams};
        delete $OldStats{hams};
        delete $OldStats{norelays};
        delete $OldStats{testmode};
        SaveStats();
    }
}

sub SaveStats {
    $Stats{smtpConcurrentSessions} = $smtpConcurrentSessions;
    $NextSaveStats = time + ( $SaveStatsEvery * 60 );
       %AllStats = %OldStats;

    foreach ( keys %Stats ) {
        if ( $_ eq 'version' ) {

            # just copy
            $AllStats{$_} = $Stats{$_};
        } elsif ( $_ eq 'smtpMaxConcurrentSessions' ) {

            # pick greater value
            $AllStats{$_} = $Stats{$_} if $Stats{$_} > $AllStats{$_};
        } else {
            $AllStats{$_} += $Stats{$_};
        }
    }
    $AllStats{starttime} = $OldStats{starttime} || $Stats{starttime};
    unlink("$base/asspstats.sav.bak");
    rename("$base/asspstats.sav","$base/asspstats.sav.bak");

     my $SS;
     if (open($SS,">","$base/asspstats.sav")) {
         print $SS join("\001",%AllStats);
         close $SS;
     } else {
         mlog(0,"warning: unable to save STATS to $base/asspstats.sav - $!");
     }
}

#####################################################################################
#                Maillog functions
# find an appropriate name for a maillog file
sub maillogFilename {
    my ($fh,$isspam)  = @_;
    my $this=$Con{$fh};
    my @dirs    = ( $notspamlog, $spamlog, $incomingOkMail, $viruslog, $discarded, $discarded );
    my $maillog = $dirs[$isspam];
    return if !$maillog;
    d('maillogFilename');
   my $sub;
    $sub = $1
      if ($this->{maillogbuf}=~/\015\012Subject: *($HeaderValueRe)/iso or
          $this->{header}=~/\015\012Subject: *($HeaderValueRe)/iso);
    $sub =~ s/\r\n\s*//go;

    eval('no bytes;');
    $sub =~ s/\r?\n$//o;
    $sub =~ s/$NONPRINT//go;
    $sub = dedecodeMimeWords($sub);
    if ($LogCharset =~ /125[02]$/) {
        $sub =~ s/\x80/(EUR)/go;
    }

    my $sub2 = $sub;
    $sub =~ y/a-zA-Z0-9/_/cs ;
    $sub =~ s/[\s\<\>\?\"\'\:\|\\\/\*\&\.]/_/ig;  # remove not allowed characters and spaces from file name
    $sub =~s/\.{2,}/\./g;
    $sub =~s/_{2,}/_/g;
    $sub =~s/[_\.]+$//;
    $sub =~s/^[_\.]+//;
    $this->{subject} = substr( $sub, 0, 50 ) unless $this->{subject};
    $sub = substr($sub,0,($MaxFileNameLength ? $MaxFileNameLength : 50));

    eval('use bytes;');
    if ( $UseSubjectsAsMaillogNames && $isspam == 1 && $MaxAllowedDups && $sub2 !~ /$AllowedDupSubjectReRE/) {
          my $md5sub = $sub ? Digest::MD5::md5($sub) : Digest::MD5::md5(' ') ;
          if ($Spamfiles{$md5sub} >= $MaxAllowedDups) {
              $maillog = $discarded if $discarded;
              mlog($fh,"MaxAllowedDups reached for this subject - store and discard mail in $discarded") if $SessionLog;
          } else {
              $Spamfiles{$md5sub}++;
          }
    }
    if ( $UseSubjectsAsMaillogNames || $isspam == 2 || $isspam == 3 ) {
        $Stats{Counter}++ unless $this->{hasmaillogname};
        $Stats{Counter} = 1 if $Stats{Counter} > 99999999;
        $sub .= "__" . ( $Stats{Counter} );
        $this->{hasmaillogname} = 1;
        return "$base/$maillog/$sub$maillogExt";
    } else {
        my $fn = $this->{fn};
        $this->{hasmaillogname} = 1;
        return "$base/$maillog/$fn$maillogExt";
    }
}
sub maillogNewFileName {

    return  $Counter++ % 99999;
}

# integrated mail collection subroutine
sub MaillogStart {
    my $fh = shift;
    d('MaillogStart');
    $Con{$fh}->{maillog} = 1 unless $NoMaillog ;
    if ("$fh" =~ /SSL/o) {
        my $ciffer = eval{$fh->get_cipher();};
        $ciffer = '' unless $ciffer;
        $ciffer and $ciffer = '('.$ciffer.')';
        $Con{$fh}->{rcvd} =~ s/(\sE?SMTP)S?/$1S$ciffer/os;
    }
    $Con{$fh}->{maillogbuf}=$Con{$fh}->{header}=$Con{$fh}->{rcvd};
}

sub Maillog {
    my ( $fh, $text, $parm ) = @_;
    my $fln;
    my $isnotcc;
    d('Maillog');
    return unless $fh;

    if ( $parm == 1 ) {
        $parm    = 3;
        $isnotcc = 1;
    }

# 0 -- no collection, 1 -- is spam, parm = 2 -- not spam,
# 3 -- is spam && cc to spamaccount, 4 -- mail ok, 5 -- virii
# 6 -- discard folder, 7 -- discard folder && cc to spamaccount

    $parm = 7
      if  $Con{$fh}->{redlist}  &&  $parm==3 && $DoNotCollectRed;
    $parm = 7
      if  $Con{$fh}->{red}  &&  $parm==3 && $DoNotCollectRed;
    $parm = 7
      if  $Con{$fh}->{redsl}  &&  $parm==3 && $DoNotCollectRed;
    $parm = 6
      if  $Con{$fh}->{redlist}  &&  $parm==1 && $DoNotCollectRed;
    $parm = 6
      if  $Con{$fh}->{red}  &&  $parm==1 && $DoNotCollectRed;
    $parm = 6
      if  $Con{$fh}->{redsl}  &&  $parm==1 && $DoNotCollectRed;
   $parm = 4
      if  $Con{$fh}->{redlist}  &&  $parm==2 && $DoNotCollectRed;
    $parm = 4
      if  $Con{$fh}->{red}  &&  $parm==2 && $DoNotCollectRed;
    $parm = 4
      if  $Con{$fh}->{redsl}  &&  $parm==2 && $DoNotCollectRed;

    $parm = 7
      if $Con{$fh}->{isbounce} &&  $parm == 3 && $DoNotCollectBounces;
    $parm = 6
      if $Con{$fh}->{isbounce} &&  $parm == 1 && $DoNotCollectBounces;

    $parm = 7
      if $Con{$fh}->{messagelow} &&  $parm == 3;
    $parm = 7
      if $Con{$fh}->{messagelow} &&  $parm == 1;

    $parm = 6
      if $Con{$fh}->{ccnever} &&  $parm == 3;
    $parm = 6
      if $Con{$fh}->{ccnever} &&  $parm == 1;

    $parm = 7 if $sendAllDiscard &&  $parm == 6;
    return unless ( $Con{$fh}->{maillog} );
    return if ( $Con{$fh}->{nocollect} );

    return if $Con{$fh}->{noprocessing} && ( $parm == 2 || $parm == 4 ) && ! $noProcessingLog;
    if ( $noCollecting && matchSL( $Con{$fh}->{mailfrom}, 'noCollecting' ) ) {
        $Con{$fh}->{nocollect} = 1;
        return;
      }
    $parm = 1
      if $ccMaxScore && ( $parm == 3 && $Con{$fh}->{messagescore} > $ccMaxScore );
    $parm = 6
      if $ccMaxScore && ( $parm == 7 && $Con{$fh}->{messagescore} > $ccMaxScore );

    $parm = 7
      if ( $parm == 6
        && $ccSpamAlways
        && allSL( $Con{$fh}->{rcpt}, $Con{$fh}->{mailfrom}, 'ccSpamAlways' ) );
    $parm = 7
      if ( $parm == 6
        && $ccSpamFilter
        && $sendAllSpam
        && allSL( $Con{$fh}->{rcpt}, $Con{$fh}->{mailfrom}, 'ccSpamFilter' ) );

    my $skipLog = 0;

    $Con{$fh}->{storecompletemail} = $StoreCompleteMail;
    $Con{$fh}->{storecompletemail} = "9999999" if !$StoreCompleteMail && $Con{$fh}->{alllog};
    $Con{$fh}->{storecompletemail} = "9999999" if $ccSpamAlways
        && allSL( $Con{$fh}->{rcpt}, $Con{$fh}->{mailfrom}, 'ccSpamAlways' );

    $parm = 4 if $parm == 6 && !$Con{$fh}->{spamfound};
    if (   $parm == 4 && !$incomingOkMail
        || $parm == 1 && (!$spamlog or !$SpamLog)
        || $parm == 2 && (!$notspamlog or !$NonSpamLog)
        || $parm == 5 && !$viruslog
        || $parm == 6 && !$discarded
        || $parm == 7 && !$discarded
        || $skipLog ) {
        d('Maillog - no log - missing folder');
        delete $Con{$fh}->{maillogbuf};
        delete $Con{$fh}->{maillog};
        close $Con{$fh}->{maillogfh} if $Con{$fh}->{maillogfh};
        delete $Con{$fh}->{maillogfh};
        delete $Con{$fh}->{mailloglength};
    } elsif ( $parm > 1 ) {

        d('Maillog - log');

        # we now know if it is spam or not -- open the file
        $text = $Con{$fh}->{maillogbuf} . $text;
        if ( $parm < 8 ) {

            my $fn = maillogFilename( $fh, $parm - 2 );

            $fln = $fn;
            $Con{$fh}->{maillogfilename} = $fn;
            if (!$fn) {
                $fln = '';
            } else {
                if ( open( my $FH , '>',"$fn" ) ) {
                    binmode $FH;
                    $Con{$fh}->{maillogfh} = $FH;
                    $Con{$fh}->{mailloglength} = 0;
                    if ($StoreASSPHeader) {
                        my $myheader = $Con{$fh}->{myheader};

                        $myheader = "X-Assp-Version: $version$modversion on $myName\r\n" . $myheader
                            if $myheader !~ /X-Assp-Version:.+? on \Q$myName\E/;
                        $myheader .= "X-Assp-ID: $myName $Con{$fh}->{msgtime}\r\n"
                            if $myheader !~ /X-Assp-ID: \Q$myName\E/;

                        $myheader =~ s/X-Assp-Spam:$HeaderValueRe//gios;
                        $myheader =~ s/X-Assp-Spam-Level:$HeaderValueRe//gios;
                        $myheader =~ s/[\r\n]+$/\r\n/o;
                        $myheader = headerFormat($myheader);

                        print $FH $myheader;
                        $Con{$fh}->{mailloglength} = length($myheader);
                    }
                } else {
                    mlog( $fh, "error opening maillog '$fn': $!" );
                }
            }
        }

        # start sending the message to sendAllSpam if appropriate

        my $current_email = $Con{$fh}->{rcpt};
        $current_email =~/($EmailAdrRe)\@($EmailDomainRe)/o;
        my ($current_username,$current_domain) = ($1,$2);

        if(($sendAllSpam or scalar keys %ccdlist) && !$Con{$fh}->{whitelist}) {
            my $ccspamlt = $sendAllSpam;
            if ($ccspamlt) {
                $ccspamlt =~ s/USERNAME/$current_username/go;
                $ccspamlt =~ s/DOMAIN/$current_domain/go;
            }
            if ( $ccdlist{lc $current_domain} ) {
                $ccspamlt .= ' ' if $ccspamlt;
                $ccspamlt .= $ccdlist{lc $current_domain} . '@' . $current_domain;
            } elsif ($ccdlist{'*'}) {
                $ccspamlt .= ' ' if $ccspamlt;
                $ccspamlt .= $ccdlist{"*"}.'@'.$current_domain;
            }

            $Con{$fh}->{forwardSpam}=forwardSpam($Con{$fh}->{mailfrom},$ccspamlt,$fh) if ($ccspamlt && $isnotcc!=1 && ($parm==3 || $parm==7) && (!$ccSpamFilter  || $ccSpamFilter && allSL($Con{$fh}->{rcpt},$Con{$fh}->{mailfrom},'ccSpamFilter')));
        }
    }
    my $gotAllText;
    if(my $h = $Con{$fh}->{maillogfh}) {
        if (! $Con{$fh}->{spambuf}) {
            $h->print(substr($text,0,max($Con{$fh}->{storecompletemail},$MaxBytes)));
            $Con{$fh}->{mailloglength} = $Con{$fh}->{spambuf} = length($text);
            $Con{$fh}->{maillogbuf} = $text;
        } else {
            if ( $Con{$fh}->{spambuf} < $Con{$fh}->{storecompletemail}) {
                $h->print(substr($text,0,$Con{$fh}->{storecompletemail} - $Con{$fh}->{spambuf}));
            } else {
                $h->print(substr($text,0,$MaxBytes + $Con{$fh}->{headerlength})) if $Con{$fh}->{spambuf}<$MaxBytes + $Con{$fh}->{headerlength} ;
            }
            $Con{$fh}->{maillogbuf}.=$text;
            $Con{$fh}->{spambuf} += length($text);
            $Con{$fh}->{mailloglength} = length($Con{$fh}->{maillogbuf});
        }
        if(  (   $ccMaxBytes
              && $Con{$fh}->{mailloglength} > $MaxBytes + $Con{$fh}->{headerlength}
              && $Con{$fh}->{mailloglength} > $Con{$fh}->{storecompletemail})
           || $text=~/(^|[\r\n])\.[\r\n]/o)
        {
            d('Maillog - no cc');
            $gotAllText = 1;
            $h->close;
            delete $Con{$fh}->{maillog} unless $Con{$fh}->{forwardSpam};
            delete $Con{$fh}->{maillogfh};
            delete $Con{$fh}->{mailloglength};
        }
    } elsif(! $ccMaxBytes || $Con{$fh}->{mailloglength} < $MaxBytes + $Con{$fh}->{headerlength} || $Con{$fh}->{mailloglength} < $Con{$fh}->{storecompletemail}) {
        eval{$Con{$fh}->{maillogbuf}.=$text; };
        $Con{$fh}->{mailloglength} = length($Con{$fh}->{maillogbuf});
    }
    if($Con{$fh}->{forwardSpam} && exists $Con{$Con{$fh}->{forwardSpam}} && exists $Con{$Con{$fh}->{forwardSpam}}->{body}) {
        $Con{$Con{$fh}->{forwardSpam}}->{body} .= $text;
        $Con{$Con{$fh}->{forwardSpam}}->{gotAllText} = $gotAllText;
    }
    return $fln;
}

sub MaillogClose {
    my $fh = shift;
    d('MaillogClose');
    return unless $fh;
    my $f=$Con{$fh}->{maillogfh};
    eval{close $f if $f;};
    return if $Con{$fh}->{type} ne 'C';
    return unless $Con{$fh}->{maillogfilename};
    if ($Con{$fh}->{deleteMailLog}) {
        unlink "$Con{$fh}->{maillogfilename}";
        mlog($fh,"info: file $Con{$fh}->{maillogfilename} was deleted");
        delete $Con{$fh}->{maillogfilename};
    } elsif ($noCollectRe) {
        my ($mfh,$buf);
        my $bytes = ($MaxBytes + $Con{$fh}->{headerlength} > 100000) ? 100000 : $MaxBytes + $Con{$fh}->{headerlength};
        $bytes = 100000 unless $bytes;
        if (open $mfh, '<',"$Con{$fh}->{maillogfilename}") {
            binmode $mfh;
            my $hasread = 1;
            while ($hasread > 0 and length($buf) < $bytes) {
                my $read;
                $hasread = $mfh->sysread($read,$bytes);
                $buf .= $read;
            }
            close $mfh;
            if ($buf && $buf =~ /$noCollectReRE/is) {
              unlink "$Con{$fh}->{maillogfilename}";
              mlog($fh,"info: file $Con{$fh}->{maillogfilename} was deleted - matched noCollectRe");
              delete $Con{$fh}->{maillogfilename};
            }
        }
    }

}

sub forwardSpam {
    my ($from,$to,$oldfh)=@_;
    my $s;
    my $AVa;
    my $this=$Con{$oldfh};
    my $msgtime = $this->{msgtime};
    my $headerlength = $this->{headerlength};
    my $destination;
    if ($sendAllDestination ne '') {
        $destination = $sendAllDestination;
    }else{
        $destination = $smtpDestination;
    }

    $AVa = 0;
    foreach my $destinationA (split(/\|/o, $destination)) {
        if ($destinationA =~ /^(_*INBOUND_*:)?(\d+)$/o){
            $destinationA = '127.0.0.1:'.$2;
        }

        $destinationA=~ s/\[::1\]/127\.0\.0\.1/ ;
        $destinationA=~ s/localhost/127\.0\.0\.1/i ;

        if ($AVa<1) {
            $s = $CanUseIOSocketINET6
                 ? IO::Socket::INET6->new(Proto=>'tcp',PeerAddr=>$destinationA,Timeout=>2,&getDestSockDom($destinationA))
                 : IO::Socket::INET->new(Proto=>'tcp',PeerAddr=>$destinationA,Timeout=>2);
            if($s) {
                $AVa=1;
                $destination=$destinationA;
            }
            else {
                mlog(0,"*** $destinationA didn't work, trying others...") if $SessionLog;
            }
        }
    }
    if(! $s) {

        mlog(0,"couldn't create server socket to $destination -- aborting sendAllSpam connection") if $SessionLog;
        return;
    }
    addfh($s,\&FShelo);
    $this=$Con{$s};
    $this->{to_as} = $to;
    @{$this->{to}}=split(/\s*,\s*|\s+/o,$to);
    $this->{msgtime} = $msgtime;
    $this->{from}=$from;
    $this->{headerlength}=$headerlength;
    $this->{fromIP}=$Con{$oldfh}->{ip};
    $this->{clamscandone}=$Con{$oldfh}->{clamscandone};
    $this->{rcpt}=$Con{$oldfh}->{rcpt};
    $this->{myheader}=$Con{$oldfh}->{myheader};
    $this->{prepend}=$Con{$oldfh}->{prepend};
    $this->{saveprepend}=$Con{$oldfh}->{saveprepend};
    $this->{saveprepend2}=$Con{$oldfh}->{saveprepend2};
    $this->{body} = '';
    $this->{FSnoopCount} = 0;
    $this->{self} = $s;
    return $s;
}
sub FShelo { my ($fh,$l)=@_;
    if($l=~/^ *[54]/o) {
        FSabort($fh,"helo Expected 220, got: $l");
    } elsif($l=~/^ *220 /o) {
        sendque($fh,"HELO $myName\r\n");
        $Con{$fh}->{getline}=\&FSfrom;
    }
}
sub FSfrom { my ($fh,$l)=@_;
    if($l=~/^ *[54]/o) {
        FSabort($fh,"send HELO($myName), expected 250, got: $l");
    } elsif($l=~/^ *250 /o) {
        $Con{$fh}->{FSlastCMD} = "MAIL FROM: <$Con{$fh}->{from}>";
        sendque($fh,"$Con{$fh}->{FSlastCMD}\r\n");
        $Con{$fh}->{getline}=\&FSrcpt;
    }
}
sub FSrcpt { my ($fh,$l)=@_;
    if($l=~/^ *[54]/o) {
        FSabort($fh,"send $Con{$fh}->{FSlastCMD}, expected 250, got: $l");
    } elsif($l=~/^ *250 /o) {
        $Con{$fh}->{FSlastCMD} = "RCPT TO: <" . shift(@{$Con{$fh}->{to}}) . ">";
        sendque($fh,"$Con{$fh}->{FSlastCMD}\r\n");
        $Con{$fh}->{getline} = \&FSdata;
    }
}
sub FSnoop { my ($fh,$l)=@_;
    if ($Con{$fh}->{gotAllText}) {
        &FSdata($fh,$l);
        return;
    }
    if($l=~/^ *[54]/o) {
        FSabort($fh,"send $Con{$fh}->{FSlastCMD}, expected 250, got: $l");
    } elsif($l=~/^ *250 /o) {
        sendque($fh,"NOOP\r\n");
        $Con{$fh}->{FSnoopCount}++ if $Con{$fh}->{FSnoopCount} < 5;
        $Con{$fh}->{sendTime} = time + $Con{$fh}->{FSnoopCount};
        $Con{$fh}->{FSlastCMD} = 'NOOP';
    }
}
sub FSdata { my ($fh,$l)=@_;
    delete $Con{$fh}->{sendTime};
    if($l=~/^ *[54]/o) {
        FSabort($fh,"send $Con{$fh}->{FSlastCMD}, expected 250, got: $l");
    } elsif($l=~/^ *250 /o) {
        sendque($fh,"DATA\r\n");
        $Con{$fh}->{getline}=\&FSdata2;
    }
}
sub FSdata2 { my ($fh,$l)=@_;
    my $this=$Con{$fh};
    if($l=~/^ *[54]/o) {
        FSabort($fh,"FSdata2 Expected 354, got: $l");
    } elsif($l=~/^ *354 /o) {

        $this->{myheader}=~s/X-Assp-Intended-For:$HeaderValueRe//giso; # clear out existing X-Assp-Envelope-From headers
        $this->{body}=~s/^($HeaderRe*)/$1From: sender not supplied\r\n/o unless $this->{body}=~/^$HeaderRe*From:/io; # add From: if missing
        $this->{body}=~s/^($HeaderRe*)/$1Subject:\r\n/o unless $this->{body}=~/^$HeaderRe*Subject:/io; # add Subject: if missing

        $this->{saveprepend}.=$this->{saveprepend2};
        $this->{body}=~s/^Subject:/Subject: $this->{saveprepend}/gim if ($spamTagCC && $this->{saveprepend} );

        $this->{body}=~s/^Subject:/Subject: $spamSubjectEnc/gimo if $spamSubjectCC && $spamSubjectEnc;

# remove Disposition-Notification headers if needed

        $this->{body} =~ s/(?:ReturnReceipt|Return-Receipt-To|Disposition-Notification-To):$HeaderValueRe//gios
            if ($removeDispositionNotification);

        # merge our header, add X-Intended-For header
        $this->{myheader} = headerFormat($this->{myheader});
        my ($to) = $this->{rcpt} =~ /(\S+)/;
        my ($mfd) = $to =~ /\@(.*)/;
        $this->{newrcpt}="";
        foreach my $adr ( split( " ", $this->{rcpt} ) ) {
             $this->{newrcpt} .= "$adr " if $adr =~ /$mfd/;
             last if $AddIntendedForHeader == 1;
        }
        $this->{body}=~s/^($HeaderRe*)/$1\r\n\n\n\r$this->{myheader}X-Assp-Intended-For: $this->{newrcpt}\r\nX-Assp-Copy-Spam: yes\r\n/o;
        $this->{body}=~s/\r?\n?\r\n\n\n\r/\r\n/o;
        my $maxbytes = $MaxBytes > 10000 ? $MaxBytes + $this->{headerlength} : 10000 + $this->{headerlength};
        $this->{body} = substr($this->{body},0,$maxbytes) if $ccMaxBytes && $MaxBytes;

        my $clamavbytes = $ClamAVBytes ? $ClamAVBytes : 50000;
        $clamavbytes = 100000 if $ClamAVBytes>100000;
        $this->{mailfrom} = $this->{from};
        $this->{ip} = $this->{fromIP};
        if ($ScanCC &&
                   $this->{body}  &&
                   ((haveToScan($fh) && ! ClamScanOK($fh,\substr($this->{body},0,$clamavbytes))) or
                    (haveToFileScan($fh) && ! FileScanOK($fh,\substr($this->{body},0,$clamavbytes)))
                   )
           ) {
           mlog($fh,"info: skip forwarding message to $this->{to_as} - virus found") if $ConnectionLog;
           @{$Con{$fh}->{to}} = (); undef @{$Con{$fh}->{to}};
           done2($fh);
           return;
        }

        $this->{body} =~ s/\r?\n/\r\n/gos;
        $this->{body} =~ s/[\r\n\.]+$//os;

        sendque($fh,$this->{body}) if $this->{body};
        sendque($fh,"\r\n.\r\n");
        delete $this->{body};
        mlog($fh,"info: message forwarded to $this->{to_as}") if $ConnectionLog;
        delete $this->{mailfrom};
        delete $this->{ip};
        $Con{$fh}->{getline}=\&FSdone;
    }
}

sub FSdone { my ($fh,$l)=@_;

    if($l=~/^ *[54]/o) {
        FSabort($fh,"done Expected 250, got: $l");
    } elsif($l=~/^ *250 /o) {
        NoLoopSyswrite($fh,"QUIT\r\n");
        @{$Con{$fh}->{to}} = (); undef @{$Con{$fh}->{to}};
        done2($fh); # close and delete
    }
}
sub FSabort {mlog(0,"FSabort: $_[1]"); @{$Con{$_[0]}->{to}} = (); undef @{$Con{$_[0]}->{to}};done2($_[0]);}

sub haveToFileScan {
    my $fh=shift;
    return 0 unless $fh;
    my $this=$Con{$fh};

    my $DoFileScan = $DoFileScan;    # copy the global to local - using local from this point

    return 0 if !$DoFileScan;
    return 0 if $this->{noscan};
    return 0 if $this->{whitelisted} && $ScanWL!=1;
    return 0 if $this->{noprocessing} && $ScanNP!=1;
    return 0 if $this->{relayok} && $ScanLocal!=1;
    if ($noScan && matchSL($this->{mailfrom},'noScan')) {
        $this->{noscan} = 1;
        return 0;
    }

    if (($noScanIP && matchIP($this->{ip},'noScanIP',$fh)) ||
        ($NoScanRe  && $this->{ip}=~/$NoScanReRE/) ||
        ($NoScanRe  && $this->{helo}=~/$NoScanReRE/) ||
        ($NoScanRe  && $this->{mailfrom}=~/$NoScanReRE/))
    {
        $this->{noscan} = 1;
        return 0;
    }
    return 1;
}

sub FileScanOK {
    my ($fh,$b)=@_;
    return 1 if (! haveToFileScan($fh));
    return 1 unless $FileScanCMD;
    my $this = $Con{$fh};
    my $failed;
    my $cmd;
    my $res;
    my $virusname;

    my $msg = $$b;
    my $lb = length($msg);
    if ($NoScanRe && $NoScanReRE ne '' && $msg=~('('.$NoScanReRE.')')) {
        mlogRe($1,"NoVirusscan");
        return 1;
    }

    my $mtype = '';
    $mtype = "whitelisted"   if $this->{whitelisted};
    $mtype = "noprocessing"  if $this->{noprocessing};
    $mtype = "local"         if $this->{relayok};

    my $file = $FileScanDir . "/" . int(rand(100000)) . "$maillogExt";

    my $SF;
    eval {
        open $SF, ">","$file";
        binmode $SF;
        print $SF substr($msg,0,$lb);
        close $SF;
      };
    my $wait;
    $wait = $1 if ($FileScanCMD =~ /^\s*NORUN\s*\-\s*(\d+)/i);
    Time::HiRes::sleep($wait / 1000) if $wait;
    $this->{prepend} = "[FileScan]";
    if (-r $file) {
        if ($FileScanCMD !~ /^\s*NORUN/i) {
            my $runfile = $file;
            my $rundir = $FileScanDir;
            if ( $^O eq "MSWin32" ) {
                $runfile =~ s/\//\\/g;
                $runfile = '"' . $runfile .'"' if $runfile =~ / /;
                $rundir =~ s/\//\\/g;
                $rundir = '"' . $rundir .'"' if $rundir =~ / /;
            } else {
                $runfile = "'" . $runfile ."'" if $runfile =~ / /;
                $rundir = "'" . $rundir ."'" if $rundir =~ / /;
            }

            $cmd = "$FileScanCMD 2>&1";
            $cmd =~ s/FILENAME/$runfile/ig;
            $cmd =~ s/NUMBER/0/ig;
            $cmd =~ s/FILESCANDIR/$rundir/ig;
            d("filescan: running - $cmd");
            mlog($fh,"diagnostic: FileScan will run command - $cmd") if $ScanLog == 3;

            $res = qx($cmd);

            $res =~ s/\r//g;
            $res =~ s/\n/ /g;
            $res =~ s/\t/ /g;
            mlog($fh,"diagnostic: FileScan returned $res") if $ScanLog == 3;

            $failed = 1 if ($FileScanBad && $FileScanBadRE ne ''  && $res =~ ('('.$FileScanBadRE.')'));
            $failed = 1 if ($FileScanGood && $FileScanGoodRE ne ''  && $res !~ ('('.$FileScanGoodRE.')'));
        }
        eval{unlink $file;};

        mlog($fh,"FileScan: scanned $lb bytes in $mtype message")
            if(($failed && $ScanLog ) || $ScanLog >= 2);
        return 1 unless $failed;
    } else {
         mlog($fh,"FileScan: is unable find temporary $file - possibly removed by the file system scanner") if $ScanLog >= 2;
        $res = 'unable to find file to scan';
        $failed = 1;
    }

        if($failed) {
        ($virusname) = $res =~ /($FileScanRespReRE)/;

        if($virusname && $SuspiciousVirus && $virusname=~/($SuspiciousVirusRE)/){
            my $susp = $1;
            $this->{messagereason}="SuspiciousVirus: $virusname '$susp'";
            pbAdd($fh,$this->{ip},&weightRe(${'vsValencePB'}[0],'SuspiciousVirus',\$susp,$fh),"SuspiciousVirus-FileScan:$virusname",1);
            $this->{prepend} = "[VIRUS][scoring]";
            mlog($fh,"'$virusname' passing the virus check because of only suspicious virus '$susp'");
            return 1;
        }

        $this->{prepend} = "[VIRUS]";
        if ($DoFileScan == 2) {
            $this->{prepend} = "[VIRUS][monitor]";
            mlog($fh,"message is infected but pass - $res");
            return 1;
        }
        $virusname = 'a virus' unless $virusname;
        $this->{averror}=$AvError;
        $this->{averror}=~s/INFECTION/$virusname/gi;
        my $reportheader;
        $reportheader="Full Header:\r\n$this->{header}\r\n" if $EmailVirusReportsHeader;
        my $sub="virus detected: 'FileScan'";

        my $bod="Message ID: $this->{msgtime}\r\n";
        $bod.="Remote IP: $this->{ip}\r\n";
        $bod.="Subject: $this->{subject2}\r\n";
        $bod.="Sender: $this->{mailfrom}\r\n";
        $bod.="Recipients(s): $this->{rcpt}\r\n";
        $bod.="Virus Detected: 'FileScan' - $res\r\n";

        my $rfile="reports/virusreport.txt";
        GetReportFile($fh, $rfile, $sub );
        &sendNotification ($EmailFrom, $EmailVirusReportsTo,$this->{subject},"$this->{body}\n\n$bod$reportheader")
            if $EmailVirusReportsTo ;
        # Send virus report to recipient if set
        &sendNotification ($EmailFrom, $this->{rcpt},$this->{subject},"$this->{body}\n\n$bod")
            if $EmailVirusReportsToRCPT && !$this->{relayok};

        $Stats{viridetected}++;
        delayWhiteExpire($fh);
        $this->{messagereason}="virus detected: 'FileScan' - $res";
        pbAdd($fh,$this->{ip},'vdValencePB',"virus-FileScan") if ${'vdValencePB'}[0]>0;

        return 0;
    } else {
        mlog($fh,"info: FileScan - message is not infected") if $ScanLog >= 2;
        return 1;
    }
}

sub haveToScan {
    my $fh   = shift;
    my $this = $Con{$fh};
    return 0 if !$AvailAvClamd;
    return 0 if !$UseAvClamd;
    return 0 if !$CanUseAvClamd;
    return 0
      if ( $this->{noscan}
        || $noScan && matchSL( $this->{mailfrom}, 'noScan' ) );
    return 0 if $this->{clamscandone};

    return 0 if $this->{whitelisted} && !$ScanWL;
    return 0 if $this->{noprocessing}==1 && !$ScanNP;
    return 0 if $this->{noprocessing}==2;
    return 0 if $this->{relayok} && !$ScanLocal;
    return 0 if $noScanIP && matchIP($this->{ip},'noScanIP');
    return 0 if $NoScanRe  && $this->{ip}=~('('.$NoScanReRE.')');
    return 0 if $NoScanRe  && $this->{helo}=~('('.$NoScanReRE.')');
    return 0 if $NoScanRe  && $this->{mailfrom}=~('('.$NoScanReRE.')'); 

    if ( $NoScanRe && $NoScanReRE != "" && $b =~ ( '(' . $NoScanReRE . ')' ) ) {
        mlogRe( $1, "NoVirusscan" );
        return 0;
    }
    return 1;
}

sub pingScan {

    my $av = new File::Scan::ClamAV( port => $AvClamdPort );

        if ( $av->ping() ) {

            $VerAvClamd         = $av->VERSION;
#            mlog( 0, 'ClamAv Up Again' ) if $ScanLog && $AvailAvClamd == 0;
            $AvailAvClamd = 1;
            $CommentAvClamd = "installed and ClamAv up";
        }
        undef $av;
    }
# substitutes File::Scan::ClamAV::ping
sub ClamScanPing {
 my ($self) = @_;
 my $response;
 my $timeout = $ClamAVtimeout / 2;
 $timeout = 5 if $timeout < 5;
 d('ClamScanPing - maxwait ' . $timeout * 2 . ' seconds');

 my $conn = $self->_get_connection || return;
 my $select = IO::Select->new();
 $select->add($conn);

 my @canwrite = $select->can_write(int($timeout));
 if (@canwrite) {
     $self->_send($conn, "PING\n");

     my @canread = $select->can_read(int($timeout));

     if (@canread) {
         chomp($response = $conn->getline);

     # Run out the buffer?
         1 while (<$conn>);
     } else {
         $response = 'unable to read from Socket';
     }
 } else {
     $response = 'unable to write to Socket';
 }
 $select->remove($conn);
 $conn->close;

 return ($response eq 'PONG' ? 1 : $self->_seterrstr("Unknown reponse from ClamAV service: $response"));
}

# substitutes File::Scan::ClamAV::streamscan
sub ClamScanScan {
 my ($self) = shift;
 my $response;
 my $timeout = $ClamAVtimeout / 2;
 $timeout = 2 if $timeout < 2;
 d('ClamScanScan - maxwait ' . $timeout + $ClamAVtimeout . ' seconds');

 my $data = join '', @_;

 $self->_seterrstr;

 my $conn = $self->_get_connection || return;
 my $select = IO::Select->new();
 $select->add($conn);

 my @canwrite = $select->can_write(int($timeout));
 if (@canwrite) {
     $self->_send($conn, "STREAM\n");
     chomp($response = $conn->getline);
 }

 my @return;
 if($response =~ /^PORT (\d+)/){
    if((my $c = $self->_get_tcp_connection($1))){
                my $stream = IO::Select->new();
                $stream->add($c);
                my @cwrite = $stream->can_write(int($timeout));
                if (@cwrite) {
            $self->_send($c, $data);
                    $stream->remove($c);
            $c->close;

                    my @canread = $select->can_read(int($ClamAVtimeout));
                    if (@canread) {
                chomp(my $r = $conn->getline);
                if($r =~ /stream: (.+) FOUND/i){
                   @return = ('FOUND', $1);
                } else {
                @return = ('OK');
                }
                    }
                }
    } else {
                $select->remove($conn);
                $conn->close;
        return;
    }
 }
 $select->remove($conn);
 $conn->close;
 return @return;
 }

sub ClamScanOK {
    my ($fh,$bd)=@_;
    return 1 if (! haveToScan($fh));
    d('ClamAV');
    my $av;
    my $errstr;
    my $this = $Con{$fh};

    $this->{clamscandone} = 1 ;

    my $mtype = '';
    $mtype = "whitelisted"   if $this->{whitelisted};
    $mtype = "noprocessing"  if $this->{noprocessing};
    $mtype = "local"         if $this->{relayok};

    my $lb = length($$bd);
    my $timeout = $ClamAVtimeout;
    my ( $code, $virus );
    $this->{prepend} = "[ClamAV]";
    eval {
       local $SIG{ALRM} = sub { die "__alarm__\n" };
         alarm($timeout) if $timeout;
        $av = new File::Scan::ClamAV( port => $AvClamdPort );
        if ( $av->ping() ) {
            mlog(0, 'ClamAv Up') if $ScanLog && $AvailAvClamd==0 ;
            $CommentAvClamd = "<span class=positive>installed and ready</span>";
            $AvailAvClamd = 1;
            ( $code, $virus ) = $av->streamscan($$bd);
        } else {
            mlog(0, 'ClamAv Down') if $ScanLog && $AvailAvClamd==1 ;
            $CommentAvClamd = "<span class=negative>installed but down</span>";

            $AvailAvClamd = 0;
        }

        $errstr = $av->errstr();
        alarm(0);
    };
    alarm(0);
    if ($@) {
        if ( $@ =~ /__alarm__/o ) {
            mlog( $fh, "ClamAV: streamscan timed out after $timeout secs." );
        } else {
            mlog( $fh, "ClamAV: streamscan failed: $@" );
        }
        undef $av;

        return 1;
    }
    unless ($AvailAvClamd) {

        return 1;
    }
    undef $av;
    $this->{prepend} = "[VIRUS]";
    mlog($fh,"ClamAV: scanned $lb bytes in $mtype message - $code $virus")
        if((!( $virus eq '') || !($code eq 'OK')) && $ScanLog ) || $ScanLog >= 2;

    if($code eq 'OK'){
        return 1;
    } elsif ($SuspiciousVirus && $virus=~/($SuspiciousVirusRE)/) {
        my $SV = $1;
        $this->{messagereason}="SuspiciousVirus: $virus '$SV'";
        my $w = &weightRe(${'vsValencePB'}[0],'SuspiciousVirus', \$SV,$fh);
        pbAdd($fh,$this->{ip},$w,"SuspiciousVirus-ClamAV:$virus",1);
        $this->{prepend} = "[VIRUS][scoring]";
        mlog($fh,"[scoring:$w] '$virus' not blocked but scored because '$SV' is in SuspiciousVirus");
        return 1;
    } elsif($code eq 'FOUND'){

        $this->{averror}=$AvError;
        $this->{averror}=~s/INFECTION/$virus/go;

        #mlog($fh,"virus detected '$virus'");
        my $reportheader;$reportheader="Full Header:\r\n$this->{header}\r\n" if $EmailVirusReportsHeader;
        my $sub="virus detected: '$virus'";

        my $bod="Message ID: $this->{msgtime}\r\n";
        $bod.="Remote IP: $this->{ip}\r\n";
        $bod.="Subject: $this->{subject2}\r\n";
        $bod.="Sender: $this->{mailfrom}\r\n";
        $bod.="Recipients(s): $this->{rcpt}\r\n";
        $bod.="Virus Detected: '$virus'\r\n";
        $reportheader = $bod.$reportheader;

        my $file="reports/virusreport.txt";

        # Send virus report to administrator if set
        AdminReportMail($sub,\$reportheader,$EmailVirusReportsTo) if $EmailVirusReportsTo;

        # Send virus report to recipient if set
        $this->{reportaddr} = 'virus';
        ReturnMail($fh,$this->{rcpt},"$base/$file",$sub,\$bod,'') if $EmailVirusReportsToRCPT;

        delete $this->{reportaddr};
        $Stats{viridetected}++;
        delayWhiteExpire($fh);
        $this->{messagereason}="virus detected: '$virus'";
        pbAdd($fh,$this->{ip},'vdValencePB',"$virus") if $vdValencePB>0;

        return 0;
    }

    $AvailAvClamd = 0;
    $CommentAvClamd = "<span class=negative>installed but temporary off</span>";
    $CommentAvClamd = "<span class=negative>installed but temporary off: $errstr</span>" if $errstr;
    mlog(0, "ClamAv Temporary Off : $errstr") if $ScanLog && $errstr;
    return 1;
}

#####################################################################################
#                Web Configuration functions
# add multiple tooltips span tags

sub statRequest {
    my ( $tempfh, $fh, $head, $data ) = @_;
    my $v;
    %statRequests = (
        '/'    => \&ConfigStatsRaw,
        '/raw' => \&ConfigStatsRaw,
        '/xml' => \&ConfigStatsXml
    );
    my $i = 0;

    # %head -- public hash
    (%head) = map { ++$i % 2 ? lc $_ : $_ } map /^([^ :]*)[: ]{0,2}(.*)/,
      split( /\r\n/, $head );
    my ( $page, $qs ) =
      ( $head{get} || $head{head} || $head{post} ) =~ /^([^\? ]+)(?:\?(\S*))?/;
    if ( defined $data ) {    # GET, POST order
        $qs .= '&' if ( $qs ne '' );
        $qs .= $data;
    }
    $qs =~ y/+/ /;
    $i = 0;

    # parse query string, get rid of google autofill
    # %qs -- public hash
    (%qs) = map {my $t = $_; $t =~ s/(e)_(mail)/$1$2/gi if ++$i % 2; $t } split( /[=&]/, $qs );
    foreach my $k ( keys %qs ) {
        $qs{$k} =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack('C',hex($1))/ge;
    }
    my $ip   = $fh->peerhost();
    $ip = "[" . $ip . "]" if ($ip =~ /:/);
    my $port = $fh->peerport();
    mlog( '', "stat connection from $ip:$port;" );

    $Stats{statConn}++;

    if ( defined( $v = $statRequests{$page} ) ) {
        print $tempfh $v->( $head, $qs );
    }
}

sub webRequest {
    my ( $tempfh, $fh, $head, $data ) = @_;
    my $v;
    my $k; 
    %webRequests = (
        '/lists'             => \&ConfigLists,
        '/recprepl'         => \&CheckRcptRepl,
        '/maillog'            => \&ConfigMaillog,
        '/analyze'            => \&ConfigAnalyze,
        '/infostats'          => \&ConfigStats,
        '/edit'               => \&ConfigEdit,
        '/shutdown'                       => \&Shutdown,

        '/shutdown_frame'                 => \&ShutdownFrame,

        '/shutdown_list'                  => \&ShutdownList,
        '/connections_list'                  => \&ConnectionsList,
        '/remember'           => \&remember,
        '/donations'         => \&Donations,
        '/get'               => \&GetFile,
        '/syncedit'         => \&syncedit,
        '/addraction'         => \&ConfigAddrAction,
        '/ipaction'         => \&ConfigIPAction
    );
    my $i = 0;

    # %head -- public hash
    (%head) = map { ++$i % 2 ? lc $_ : $_ } map /^([^ :]*)[: ]{0,2}(.*)/,
      split( /\r\n/, $head );
    my ( $page, $qs ) =
      ( $head{get} || $head{head} || $head{post} ) =~ /^([^\? ]+)(?:\?(\S*))?/;
       $currentPage = $page;
    $currentPage =~ s/^\/+//;
    $currentPage = 'Config' unless $currentPage;
    $currentPage = ucfirst($currentPage);
    $headers =~ s/<title>\S+ ASSP/<title>$currentPage ASSP/ if $page ne '/get' && exists $webRequests{$page};
    if ( defined $data ) {    # GET, POST order
        $qs .= '&' if ( $qs ne '' );
        $qs .= $data;
    }
    $qs =~ y/+/ /;
    $i = 0;

    # parse query string, get rid of google autofill
    # %qs -- public hash
    (%qs)=map{my$t=$_;$t=~s/(e)_(mail)/$1$2/gio if ++$i % 2; $t} split(/[=&]/o,$qs);
    while (($k,$v) =  each %qs) {$qs{$k}=~s/%([0-9a-fA-F][0-9a-fA-F])/pack('C',hex($1))/geo}
    my ($auth)=$head{authorization}=~/Basic (\S+)/io;
    my ($user,$pass)=split(':',base64decode($auth));
    my $ip   = $fh->peerhost();
    $ip = "[" . $ip . "]" if ($ip =~ /:/);
    my $port = $fh->peerport();

    if ( substr( $Config{webAdminPassword}, 0, 2 ) eq "45" ) {
        $pass = crypt( $pass, "45" );
    }

    if ( $pass eq $webAdminPassword || !$webAdminPassword ) {
        if ( $page !~ /shutdown_frame|shutdown_list|favicon.ico|get/i ) {

            # only count requests for pages without meta refresh tag
            # dont count requests for favicon.ico file
            # dont count requests for 'get' page
            my $args;
            if ( $page =~ /edit/i ) {
                if ( defined( $qs{contents} ) ) {
                    if ( $qs{B1} =~ /delete/i ) {
                        $args = "deleting file '$qs{file}'";
                    } else {
                        $args = "writing file '$qs{file}'";
                    }
                } else {
                    $args = "reading file '$qs{file}'";
                    my $fil = $qs{file};
                     foreach my $dbGroupEntry (@dbGroup) {
                            my ( $KeyName, $dbConfig, $CacheObject, $realFileName ) =
                          split(/,/o,$dbGroupEntry);
                        next if $realFileName eq "mysql";
                        next if $realFileName eq "";
                        next if !$CacheObject;
                        next unless ( $fil =~ /$realFileName/ );

                        SaveDB($CacheObject,$KeyName);

                        last;

                       }

                }
            }
            if ($args) {
                mlog( '',"admin connection from $ip:$port; page:$page; $args" );
                optionFilesReload() if $args =~ /writing/i ;
                optionFilesReload() if $args =~ /deleting/i ;
                ResetPB($qs{file}) if $args =~ /writing/i;
                ResetPB($qs{file}) if $args =~ /deleting/i;

            } else {
                mlog( '', "admin connection from $ip:$port; page:$page" );
            }

            $Stats{admConn}++;
        }
        if ( $page =~ /quit/i ) {
            ConfigQuit($tempfh);
        }

        if ( $page =~ /terminateprimary/i ) {
            ConfigTerminatePrimary($tempfh);
        }

        if ( $page =~ /autorestart/i ) {
            ConfigRestart($tempfh);
            &downASSP("Restarted");
             &restartCMD();

        }

        if ( $page =~ /restartsecondary/i ) {
            ConfigRestartSecondary($tempfh);

        }
        if ( $page =~ /restartprimary/i ) {
            ConfigRestartPrimary($tempfh);
        }
        if ( $page =~ /reload/i ) {
            reloadConfigFile();
        }
        if ( $page =~ /save/i ) {

            SaveConfig();

        }
        if ( $page =~ /syncedit/i ) {

        }
        if ( $page =~ /favicon.ico/i ) {
            print $tempfh "HTTP/1.1 404 Not Found
Content-type: text/html

<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\"><body><h1>Not found</h1>
</body></html>\n";
        } else {
            print $tempfh (
                ( defined( $v = $webRequests{$page} ) )
                ? $v->( $head, $qs )
                : webConfig( $head, $qs )
            );
        }
    } else {

        print $tempfh "HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm=\"Anti-Spam SMTP Proxy (ASSP) Configuration\"
Content-type: text/html

<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\"><body><h1>Unauthorized</h1>
</body></html>\n";
    }
}

sub webBlock {
    my $tempfh = shift;
    print $tempfh &webBlockText();
    return 1;
}

sub webBlockText {
    return "HTTP/1.1 200 OK
Content-type: text/html

<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\"><body><h3>permission denied - you are not allowed to start this action<br /><br />use the back button</h3>
</body></html>\n";
}

sub ConfigQuit {
 my $fh=shift;
 mlog(0,"quit requested from admin interface",1);
 print $fh "HTTP/1.1 200 OK
Content-type: text/html

<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\"><body><h3>ASSP Terminated.</h3>
</body></html>
";
 &downASSP("Terminated");

exit 2;

}
sub ConfigRestart {
    mlog( 0, "restart requested from admin interface",1 );
    my $fh = shift;
    print $fh "HTTP/1.1 200 OK
Content-type: text/html

<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\"><body><h3>ASSP Restarted.</h3>
</body></html>
";

}
sub ConfigRestartSecondary {
 my $fh=shift;
 my $time = &timestring();
 return if !$AutostartSecondary;

 my $pid = &readSecondaryPID();
 mlog( 0, "restart Secondary requested from admin interface",1 );
 unlink("$base/$pidfile"."_Secondary");
 mlog( 0, "stopping Secondary($pid) ",1 );
 kill TERM => $pid if $pid;
 &startSecondary() if  $AutostartSecondary && !$AsASecondary && $webSecondaryPort;
}

sub ConfigRestartPrimary {
 my $fh=shift;
 my $time = &timestring();
 my $pid = &checkPrimaryPID();
 print "\n$time Secondary (PID: $$): restarting primary ($pid)\n";
 print $fh "HTTP/1.1 200 OK
Content-type: text/html

<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\"><body><h1>ASSP Primary Restarted.</h1>
</body></html>
" if $pid;
 print $fh "HTTP/1.1 200 OK
Content-type: text/html

<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\"><body><h1>ASSP Primary Not Running.</h1>
</body></html>
" if !$pid;

 kill QUIT => $pid if $pid;
}

sub ConfigTerminatePrimary {
 my $fh=shift;
 my $time = &timestring();
 my $pid = &checkPrimaryPID();
 print "\n$time Secondary (PID: $$): terminating primary ($pid)\n";
 print $fh "HTTP/1.1 200 OK
Content-type: text/html

<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\"><body><h1>ASSP Primary Terminated.</h1>
</body></html>
" if $pid;
 print $fh "HTTP/1.1 200 OK
Content-type: text/html

<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\"><body><h1>ASSP Primary Not Running.</h1>
</body></html>
" if !$pid;

 kill INT => $pid if $pid;
}
# total current and previous stats
sub ComputeStatTotals {

    my %totStats = %PrevStats;
    foreach my $k ( keys %Stats ) {
        if ( $k eq 'version' ) {

            # just copy
            $totStats{$k} = $Stats{$k};
        } elsif ( $k eq 'smtpMaxConcurrentSessions' ) {

            # pick greater value
            $totStats{$k} = $Stats{$k} if $Stats{$k} > $PrevStats{$k};
        } elsif ( $k eq 'starttime' ) {

            # initialize if needed
            $totStats{$k} = $Stats{$k} unless $PrevStats{$k};
        } else {

            #sum
            $totStats{$k} += $Stats{$k};
        }
    }

    return %totStats;
}

# compute various totals
sub statsTotals {
    my %s;
    $s{smtpConnIdleTimeout}       =    $Stats{smtpConnIdleTimeout};
    $s{smtpConnIdleTimeout2}      =    $AllStats{smtpConnIdleTimeout};
    $s{smtpConnSSLIdleTimeout}    =    $Stats{smtpConnSSLIdleTimeout};
    $s{smtpConnSSLIdleTimeout2}   =    $AllStats{smtpConnSSLIdleTimeout};
    $s{smtpConnTLSIdleTimeout}    =    $Stats{smtpConnTLSIdleTimeout};
    $s{smtpConnTLSIdleTimeout2}   =    $AllStats{smtpConnTLSIdleTimeout};

    $s{smtpConnAcceptedTotal}     =     $Stats{smtpConn} + $Stats{smtpConnNotLogged};
    $s{smtpConnAcceptedTotal2}    =
      $AllStats{smtpConn} + $AllStats{smtpConnNotLogged};
    $s{smtpConnLimit}=$Stats{smtpConnLimit}+$Stats{smtpConnDomainIP}+$Stats{SameSubject}+$Stats{smtpConnLimitIP}+$Stats{smtpConnLimitFreq}+$Stats{AUTHErrors}+$Stats{delayConnection};
    $s{smtpConnLimit2}=$AllStats{smtpConnLimit}+$AllStats{smtpConnDomainIP}+$AllStats{smtpConnLimitIP}+$AllStats{smtpConnLimitFreq}+$AllStats{AUTHErrors}+$AllStats{delayConnection};

    $s{smtpConnRejectedTotal}=$s{smtpConnLimit}+$Stats{smtpConnDenied};
    $s{smtpConnRejectedTotal2}=$s{smtpConnLimit2}+$AllStats{smtpConnDenied};

    $s{smtpConnTotal}  = $s{smtpConnAcceptedTotal} + $s{smtpConnRejectedTotal};
    $s{smtpConnTotal2} =
      $s{smtpConnAcceptedTotal2} + $s{smtpConnRejectedTotal2};
    $s{admConnTotal}   = $Stats{admConn} + $Stats{admConnDenied};
    $s{admConnTotal2}  = $AllStats{admConn} + $AllStats{admConnDenied};
    $s{statConnTotal}  = $Stats{statConn} + $Stats{statConnDenied};
    $s{statConnTotal2} = $AllStats{statConn} + $AllStats{statConnDenied};
    $s{rcptAcceptedLocal} =
      $Stats{rcptValidated} + $Stats{rcptUnchecked} + $Stats{rcptSpamLover};
    $s{rcptAcceptedLocal2} =
      $AllStats{rcptValidated} +
      $AllStats{rcptUnchecked} +
      $AllStats{rcptSpamLover};
    $s{rcptAcceptedRemote} =
      $Stats{rcptWhitelisted} + $Stats{rcptNotWhitelisted};
    $s{rcptAcceptedRemote2} =
      $AllStats{rcptWhitelisted} + $AllStats{rcptNotWhitelisted};
    $s{rcptUnprocessed}  = $Stats{rcptUnprocessed};
    $s{rcptUnprocessed2} = $AllStats{rcptUnprocessed};
    $s{rcptReport} =
      $Stats{rcptReportSpam} +
      $Stats{rcptReportHam} +
      $Stats{rcptReportWhitelistAdd} +
      $Stats{rcptReportWhitelistRemove} +
      $Stats{rcptReportRedlistAdd} +
      $Stats{rcptReportRedlistRemove};
    $s{rcptReport2} =
      $AllStats{rcptReportSpam} +
      $AllStats{rcptReportHam} +
      $AllStats{rcptReportWhitelistAdd} +
      $AllStats{rcptReportWhitelistRemove} +
      $AllStats{rcptReportRedlistAdd} +
      $AllStats{rcptReportRedlistRemove};
    $s{rcptAcceptedTotal} =
      $s{rcptAcceptedLocal} +
      $s{rcptAcceptedRemote} +
      $s{rcptUnprocessed} +
      $s{rcptReport};
    $s{rcptAcceptedTotal2} =
      $s{rcptAcceptedLocal2} +
      $s{rcptAcceptedRemote2} +
      $s{rcptUnprocessed2} +
      $s{rcptReport2};
    $s{rcptRejectedLocal} =
      $Stats{rcptNonexistent} +
      $Stats{rcptDelayed} +
      $Stats{rcptDelayedLate} +
      $Stats{rcptDelayedExpired} +
      $Stats{rcptEmbargoed} +
      $Stats{rcptSpamBucket};
    $s{rcptRejectedLocal2} =
      $AllStats{rcptNonexistent} +
      $AllStats{rcptDelayed} +
      $AllStats{rcptDelayedLate} +
      $AllStats{rcptDelayedExpired} +
      $AllStats{rcptEmbargoed} +
      $AllStats{rcptSpamBucket};
    $s{rcptRejectedRemote}  = $Stats{rcptRelayRejected};
    $s{rcptRejectedRemote2} = $AllStats{rcptRelayRejected};
    $s{rcptRejectedTotal}   = $s{rcptRejectedLocal} + $s{rcptRejectedRemote};
    $s{rcptRejectedTotal2}  = $s{rcptRejectedLocal2} + $s{rcptRejectedRemote2};
    $s{rcptTotal}           = $s{rcptAcceptedTotal} + $s{rcptRejectedTotal};
    $s{rcptTotal2}          = $s{rcptAcceptedTotal2} + $s{rcptRejectedTotal2};
    $s{msgAcceptedTotal} =
      $Stats{bhams} +
      $Stats{whites} +
      $Stats{locals} +
      $Stats{noprocessing} +
      $Stats{spamlover};
    $s{msgAcceptedTotal2} =
      $AllStats{bhams} +
      $AllStats{whites} +
      $AllStats{locals} +
      $AllStats{noprocessing} +
      $AllStats{spamlover};
    $s{msgRejectedTotal} =
      $Stats{preHeader} +
      $Stats{localFrequency} +
      $Stats{bspams} +
      $Stats{blacklisted} +
      $Stats{helolisted} +
      $Stats{spambucket} +
      $Stats{penaltytrap} +
      $Stats{viri} +
      $Stats{internaladdresses} +
      $Stats{smtpConnDenied} +
      $Stats{smtpConnDomainIP} +
      $Stats{SameSubject} +
      $Stats{smtpConnLimitFreq} +
      $Stats{viridetected} +
      $Stats{msgverify} +
      $Stats{bombHeader} +
      $Stats{bombs} +
      $Stats{ptrMissing} +
      $Stats{ptrInvalid} +
      $Stats{mxaMissing} +
      $Stats{forgedHelo} +
      $Stats{invalidHelo} +
      $Stats{pbdenied} +
      $Stats{pbextreme} +
      $Stats{denyConnection} +
      $Stats{denyStrict} +
      $Stats{sbblocked} +
      $Stats{msgscoring} +
      $Stats{senderInvalidLocals} +
      $Stats{scripts} +
      $Stats{spffails} +
      $Stats{rblfails} +
      $Stats{uriblfails} +
      $Stats{msgMSGIDtrErrors} +
      $Stats{msgBackscatterErrors} +
      $Stats{msgMaxErrors} +
      $Stats{msgNoSRSBounce};
    $s{msgRejectedTotal2} =
      $AllStats{preHeader} +
      $AllStats{localFrequency} +
      $AllStats{bspams} +
      $AllStats{blacklisted} +
      $AllStats{helolisted} +
      $AllStats{spambucket} +
      $AllStats{penaltytrap} +
      $AllStats{viri} +
      $AllStats{internaladdresses} +
      $AllStats{smtpConnDenied} +
      $AllStats{smtpConnDomainIP} +
      $AllStats{smtpConnLimitFreq} +
      $AllStats{viridetected} +
      $AllStats{bombs} +
      $AllStats{msgverify} +
      $AllStats{bombHeader} +
      $AllStats{bombBlack} +
      $AllStats{ptrMissing} +
      $AllStats{ptrInvalid} +
      $AllStats{mxaMissing} +
      $AllStats{forgedHelo} +
      $AllStats{invalidHelo} +
      $AllStats{pbdenied} +
      $AllStats{pbextreme} +
      $AllStats{denyConnection} +
      $AllStats{denyStrict} +
      $AllStats{sbblocked} +
      $AllStats{msgscoring} +
      $AllStats{senderInvalidLocals} +
      $AllStats{scripts} +
      $AllStats{spffails} +
      $AllStats{rblfails} +
      $AllStats{uriblfails} +
      $AllStats{msgMSGIDtrErrors} +
      $AllStats{msgBackscatterErrors} +
      $AllStats{msgMaxErrors} +
      $AllStats{msgNoSRSBounce};
    $s{msgTotal}  = $s{msgAcceptedTotal} + $s{msgRejectedTotal};
    $s{msgTotal2} = $s{msgAcceptedTotal2} + $s{msgRejectedTotal2};
    %s;
}

sub ConfigStats {
    if ($qs{ResetAllStats}) {
         %OldStats = ();
         %AllStats = ();
         $AllStats{starttime} = time;

         rename("$base/asspstats.sav","$base/stats/asspstats-".timestring('','','YYYY-MM-DD-hh-mm-ss').'.sav');
         ResetStats();
     } elsif ($qs{ResetStats}) {
         ResetStats();
     }
    SaveStats();
    my %tots = statsTotals();
    delete $qs{ResetAllStats};
     delete $qs{ResetStats};
    my $upt  = ( time - $Stats{starttime} ) / ( 24 * 3600 );
    my $upt2 = ( time - $AllStats{starttime} ) / ( 24 * 3600 );

    my $uptime    = getTimeDiffAsString( time - $Stats{starttime}, 1 );
    my $resettime = localtime( $AllStats{starttime} );
    my $uptime2   = getTimeDiffAsString( time - $AllStats{starttime} );
    my $mpd       = sprintf( "%.1f", $upt == 0 ? 0 : $tots{msgTotal} / $upt );
    my $mpd2 = sprintf( "%.1f", $upt2 == 0 ? 0 : $tots{msgTotal2} / $upt2 );
    my $pct  = sprintf( "%.1f",
        $tots{msgTotal} - $Stats{locals} == 0
        ? 0
        : 100 * $tots{msgRejectedTotal} / ( $tots{msgTotal} - $Stats{locals} )
    );
    my $pct2;
    $pct2 = sprintf( "%.1f",
        $tots{msgTotal2} - $AllStats{locals} == 0
        ? 0
        : 100 *
          $tots{msgRejectedTotal2} /
          ( $tots{msgTotal2} - $AllStats{locals} ) );
    my $cpu=$CanStatCPU ? sprintf("%.2f%%",100*$cpuUsage) : 'n/a';
     my $cpuAvg;$cpuAvg=sprintf(" (%.2f%% avg)",$Stats{cpuTime}==0 ? 0 : 100*$Stats{cpuBusyTime}/$Stats{cpuTime}) if $CanStatCPU;
     my $cpuAvg2=$CanStatCPU ? sprintf("%.2f%% avg",$AllStats{cpuTime}==0 ? 0 : 100*$AllStats{cpuBusyTime}/$AllStats{cpuTime}) : 'n/a';

    my $LocalDNSStatus;

 if ($UseLocalDNS) {
     $LocalDNSStatus = "Local <a href=\"/#UseLocalDNS\">DNS Servers</a> in use";
 } else {
     $LocalDNSStatus = "Custom <a href=\"/#DNSServers\">DNS servers</a> in use";
 }
 my $reset = 'reset';
 my $restart = 'reset or restart';
 my $fil = "asspstats.sav";
 my $currentCL = (-e "$base/docs/changelog.txt") ? "docs/changelog.txt" : '';
 my $currentCLtext = $currentCL ? '<a href="javascript:void(0);" onclick="javascript:popFileEditor(\'docs/changelog.txt\',8);">show changelog</a>' : '&nbsp;';
 $uptime2 = "<a href=\"javascript:void(0);\" title=\"click to reset all stats to zero\" onclick=\"if (confirm('reset all STATS ?')) {WaitDiv();window.location.href='/infostats?ResetAllStats=1';}\">$uptime2</a>";
 $reset = "<a href=\"javascript:void(0);\" title=\"click to reset all stats to zero\" onclick=\"if (confirm('reset all STATS ?')) {WaitDiv();window.location.href='/infostats?ResetAllStats=1';}\">reset</a>";
 $uptime = "<a href=\"javascript:void(0);\" title=\"click to reset all stats since last start to zero\" onclick=\"if (confirm('reset current STATS ?')) {WaitDiv();window.location.href='/infostats?ResetStats=1';}\">$uptime</a>";
 $restart = "<a href=\"javascript:void(0);\" title=\"click to reset all stats since last start to zero\" onclick=\"if (confirm('reset current STATS ?')) {WaitDiv();window.location.href='/infostats?ResetStats=1';}\">reset</a> or restart";

    <<EOT;
$headerHTTP
$headerDTDTransitional
$headers
$footers
<script type=\"text/javascript\">
<!--
  function toggleTbody(id) {
    if (document.getElementById) {
      var tbod = document.getElementById(id);
      if (tbod && typeof tbod.className == 'string') {
        if (tbod.className == 'off') {
          tbod.className = 'on';
        } else {
          tbod.className = 'off';
        }
      }
    }
    return false;
  }
//-->
</script>
   <div class="content">
      <h2>
        ASSP Information and Statistics
      </h2><br />
      <table class="statBox">
      <thead>
          <tr>
            <td colspan="5" class="sectionHeader" onmousedown=
            "toggleTbody('StatItem0')">
              Server Information
            </td>
          </tr>
        </thead>

        <tbody id="StatItem0" class="off">
          <tr>
            <td class="statsOptionTitle">
              Server Name:
            </td>
            <td class="statsOptionValue" colspan="2">
              $localhostname
            </td>
            <td class="statsOptionValue" colspan="2">
              &nbsp;
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Server OS:
            </td>
            <td class="statsOptionValue" colspan="2">
              $^O
            </td>
            <td class="statsOptionValue" colspan="2">
              &nbsp;
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Server IP:
            </td>
            <td class="statsOptionValue" colspan="2">
              $localhostip
            </td>
            <td class="statsOptionValue" colspan="2">
              &nbsp;
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              DNS Servers:
            </td>
            <td class="statsOptionValue" colspan="2">
              $nameserversrt
            </td>
            <td class="statsOptionValue" colspan="2">
                $LocalDNSStatus
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Perl Version:
            </td>
            <td class="statsOptionValue" colspan="2">
              $]
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://www.perl.org/get.html" rel=
              "external">Perl.org</a>
            </td>
          </tr>
          <tr>
            <td  class="statsOptionTitle">
              ASSP Version:
            </td>

            <td class="statsOptionValue" colspan="2">
              <table>
               <tr>
                <td rowspan="2">
                 $version$modversion
                </td>
                <td class="statsOptionValueC">
                 $currentCLtext
                </td>
               </tr>
               <tr>
                <td class="statsOptionValueC">
                 <a href="$ChangeLogURL" rel="external" target="_blank">show changelog</a>
                </td>
               </tr>
              </table>
            </td>
            <td class="statsOptionValueC">
              <a href=
              "http://downloads.sourceforge.net/project/assp/ASSP%20Installation/AutoUpdate/ASSP1x/assp.pl.gz"
              rel="external">stable release</a>
            </td>
            <td class="statsOptionValueC">
              <a href= http://downloads.sourceforge.net/project/assp/ASSP%20Installation/AutoUpdate/ASSP1dev/assp.pl.gz rel="external">development release</a>
            </td>
          </tr>
          <tr>
            <td  class="statsOptionTitle">
              ASSP Warnings:
            </td>

            <td class="statsOptionValue" colspan="4">
              $asspWarnings
            </td>

          </tr>
          <tr>
            <td class="statsOptionValue" style="background-color: #FFFFFF">
              &nbsp;
            </td>
            <td class="statsOptionValue" style="background-color: #FFFFFF"
            colspan="2">
              &nbsp;
            </td>
            <td class="statsOptionValueC" style="background-color: #FFFFFF"
            colspan="2">
              <font size="1" color="#C0C0C0"><em>downloads</em></font>
            </td>
          </tr>
        </tbody>
        <tbody>
          <tr>
            <td class="sectionHeader" onmousedown="toggleTbody('StatItem2')"
            colspan="5">
              Perl Modules
            </td>
          </tr>
        </tbody>
        <tbody id="StatItem2" class="off">
          <tr>
            <td class="statsOptionTitle">
              Compress::Zlib
            </td>
            <td class="statsOptionValue">
              $VerCompressZlib
            </td>
            <td class="statsOptionValue">
              $CommentCompressZlib
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Compress::Zlib" rel=
              "external">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Digest::MD5
            </td>
            <td class="statsOptionValue" >
              $VerDigestMD5
            </td>
             <td class="statsOptionValue" >
              $CommentDigestMD5
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Digest::MD5" rel=
              "external">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Digest::SHA1
            </td>
            <td class="statsOptionValue" >
              $VerDigestSHA1
            </td>
             <td class="statsOptionValue" >
              $CommentDigestSHA1
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Digest::SHA1" rel=
              "external">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Email::Valid
            </td>
            <td class="statsOptionValue" >
              $VerEmailValid
            </td>
             <td class="statsOptionValue" >
              $CommentEmailValid
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Email::Valid" rel=
              "external">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Email::Send
            </td>
            <td class="statsOptionValue" >
              $VerEMS
            </td>
            <td class="statsOptionValue" >
              $CommentEMS
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Email::Send" rel=
              "external">CPAN</a>
            </td>
          </tr>
           <tr>
            <td class="statsOptionTitle">
              Email::MIME
            </td>
            <td class="statsOptionValue" >
              $VerEMM
            </td>
            <td class="statsOptionValue" >
              $CommentEMM
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Email::MIME" rel=
              "external">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              File::ReadBackwards
            </td>
            <td class="statsOptionValue" >
              $VerFileReadBackwards
            </td>
            </td>
            <td class="statsOptionValue" >
              $CommentFileReadBackwards
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=File::ReadBackwards"
              rel="external">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              File::Scan::ClamAV
            </td>
            <td class="statsOptionValue" >
              $VerAvClamd
            </td>
             <td class="statsOptionValue">
              $CommentAvClamd
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=File::Scan::ClamAV"
              rel="external">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              IO::Socket::INET6
            </td>
            <td class="statsOptionValue" >
              $VerIOSocketINET6
            </td>
            <td class="statsOptionValue" >
              $CommentIOSocketINET6
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=IO::Socket::INET6" rel=
              "external">CPAN</a>
            </td>
          </tr> 
          <tr>
            <td class="statsOptionTitle">
              IO::Socket::SSL
            </td>
            <td class="statsOptionValue" >
              $VerIOSocketSSL
            </td>
            <td class="statsOptionValue" >
              $CommentIOSocketSSL
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=IO::Socket::SSL" rel=
              "external">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              LWP::Simple
            </td>
            <td class="statsOptionValue">
              $VerLWP
            </td>
            <td class="statsOptionValue">
              $CommentLWP
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a rel="external" href=
              "http://search.cpan.org/search?query=LWP::Simple">CPAN</a>
            </td>
          </tr>

            <tr>
            <td class="statsOptionTitle">
              Authen::SASL
            </td>
            <td class="statsOptionValue">
              $VerAuthenSASL
            </td>
            <td class="statsOptionValue">
              $CommentAuthenSASL
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Authen::SASL"
              rel="external">CPAN</a>
            </td>
          </tr>
           <tr>
            <td class="statsOptionTitle">
              Mail::SPF
            </td>
            <td class="statsOptionValue">
              $VerMailSPF
            </td>
            <td class="statsOptionValue">
              $CommentMailSPF
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Mail::SPF"
              rel="external">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Mail::SRS
            </td>
            <td class="statsOptionValue" >
              $VerMailSRS
            </td>
            <td class="statsOptionValue" >
              $CommentMailSRS
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Mail::SRS" rel=
              "external">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Net::CIDR::Lite
            </td>
            <td class="statsOptionValue" >
              $VerCIDRlite
            </td>
            <td class="statsOptionValue" >
              $CommentCIDRlite
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a rel="external" href=
              "http://search.cpan.org/search?query=Net::CIDR::Lite">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Net::DNS
            </td>
            <td class="statsOptionValue" >
              $VerNetDNS
            </td>
            <td class="statsOptionValue">
              $CommentNetDNS
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Net::DNS" rel=
              "external">CPAN</a>
            </td>
          </tr>

          <tr>
            <td class="statsOptionTitle">
              Net::SMTP
            </td>
            <td class="statsOptionValue" >
              $VerNetSMTP
            </td>
            <td class="statsOptionValue" >
              $CommentNetSMTP
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Net::SMTP" rel=
              "external">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Net::IP::Match::Regexp
            </td>
            <td class="statsOptionValue" >
              $VerCIDR
            </td>
            <td class="statsOptionValue" >
              $CommentCIDR
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a rel="external" href=
              "http://search.cpan.org/search?query=Net::IP::Match::Regexp">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Net::LDAP
            </td>
            <td class="statsOptionValue" >
              $VerNetLDAP
            </td>
            <td class="statsOptionValue" >
              $CommentNetLDAP
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Net::LDAP" rel=
              "external">CPAN</a>
            </td>
          </tr>
            <tr>
            <td class="statsOptionTitle">
              Net::SenderBase
            </td>
            <td class="statsOptionValue" >
              $VerSenderBase
            </td>
            <td class="statsOptionValue" >
              $CommentSenderBase
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a rel="external" href=
              "http://search.cpan.org/search?query=Net::SenderBase">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Sys::Syslog
            </td>
            <td class="statsOptionValue" >
              $VerSysSyslog
            </td>
            <td class="statsOptionValue" >
              $CommentSysSyslog
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Sys::Syslog" rel=
              "external">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Net::Syslog
            </td>
            <td class="statsOptionValue" >
              $VerNetSyslog
            </td>
            <td class="statsOptionValue" >
              $CommentNetSyslog
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Net::Syslog" rel=
              "external">CPAN</a>
            </td>
          </tr>

          <tr>
            <td class="statsOptionTitle">
              Tie::RDBM
            </td>
            <td class="statsOptionValue" >
              $VerRDBM
            </td>
             <td class="statsOptionValue" >
              $CommentRDBM
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a rel="external" href=
              "http://search.cpan.org/search?query=Tie::RDBM">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Time::HiRes
            </td>
            <td class="statsOptionValue" >
              $VerTimeHiRes
            </td>
             <td class="statsOptionValue" >
              $CommentTimeHiRes
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a href="http://search.cpan.org/search?query=Time::HiRes" rel=
              "external">CPAN</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Win32::Daemon
            </td>
            <td class="statsOptionValue" >
              $VerWin32Daemon
            </td>
            <td class="statsOptionValue" >
              $CommentWin32Daemon
            </td>
            <td class="statsOptionValueC" colspan="2">
              <a rel="external" href=
              "http://www.roth.net/perl/Daemon/">roth.net</a>
            </td>
          </tr>
          <tr>
            <td class="statsOptionValue" style="background-color: #FFFFFF">
              &nbsp;
            </td>
            <td class="statsOptionValue" style="background-color: #FFFFFF"
            colspan="2">
              &nbsp;
            </td>
            <td class="statsOptionValueC" style="background-color: #FFFFFF"
            colspan="2">
              <font size="1" color="#C0C0C0"><em>downloads</em></font>
            </td>
          </tr>
        </tbody>

        <tbody>
          <tr>
            <td class="sectionHeader" onmousedown="toggleTbody('StatItem3')"
            colspan="5">
              General Runtime Information
            </td>
          </tr>
        </tbody>
        <tbody id="StatItem3" class="on">
          <tr>
            <td class="statsOptionTitle">
              ASSP Proxy Uptime:
            </td>
            <td class="statsOptionValue" colspan="2">
              $uptime 
            </td>
            <td class="statsOptionValue" colspan="2">
              $uptime2
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Messages Processed:
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{msgTotal} ($mpd per day)
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{msgTotal2} ($mpd2 per day)
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Non-Local Mail Blocked:
            </td>
            <td class="statsOptionValue" colspan="2">
              $pct%
            </td>
            <td class="statsOptionValue" colspan="2">
              $pct2%
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              CPU Usage:
            </td>
            <td class="statsOptionValue" colspan="2">
              $cpu$cpuAvg
            </td>
            <td class="statsOptionValue" colspan="2">
              $cpuAvg2
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Concurrent SMTP Sessions:
            </td>
            <td class="statsOptionValue" colspan="2">
              $smtpConcurrentSessions ($Stats{smtpMaxConcurrentSessions} high / $maxSMTPSessions max)
            </td>
            <td class="statsOptionValue" colspan="2">
             $AllStats{smtpMaxConcurrentSessions} high
            </td>
          </tr>
          <tr>
            <td class="statsOptionValue" style="background-color: #FFFFFF">
              &nbsp;
            </td>
            <td class="statsOptionValue" style="background-color: #FFFFFF"
            colspan="2">
              <font size="1" color="#C0C0C0"><em>since $restart at $starttime</em></font>
            </td>
            <td class="statsOptionValue" style="background-color: #FFFFFF"
            colspan="2">
              <font size="1" color="#C0C0C0"><em>since $reset at $resettime</em></font>
            </td>
          </tr>
        </tbody>
        <tbody>
          <tr>
            <td colspan="5" class="sectionHeader" onmousedown=
            "toggleTbody('StatItem4')">
              Totaled Statistics
            </td>
          </tr>
        </tbody>
        <tbody id="StatItem4" class="off">
          <tr>
            <td class="statsOptionTitle">
              SMTP Connections Received:
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{smtpConnTotal}
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{smtpConnTotal2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;SMTP Connections Accepted:
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{smtpConnAcceptedTotal}
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{smtpConnAcceptedTotal2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;SMTP Connections Rejected:
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{smtpConnRejectedTotal}
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{smtpConnRejectedTotal2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Envelope Recipients Processed:
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{rcptTotal}
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{rcptTotal2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Envelope Recipients Accepted:
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{rcptAcceptedTotal}
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{rcptAcceptedTotal2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Envelope Recipients Rejected:
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{rcptRejectedTotal}
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{rcptRejectedTotal2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Messages Processed:
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{msgTotal}
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{msgTotal2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Messages Passed:
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{msgAcceptedTotal}
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{msgAcceptedTotal2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Messages Rejected:
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{msgRejectedTotal}
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{msgRejectedTotal2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Admin Connections Received:
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{admConnTotal}
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{admConnTotal2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Admin Connections Accepted:
            </td>
            <td class="statsOptionValue" colspan="2">
              $Stats{admConn}
            </td>
            <td class="statsOptionValue" colspan="2">
              $AllStats{admConn}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Admin Connections Rejected:
            </td>
            <td class="statsOptionValue" colspan="2">
              $Stats{admConnDenied}
            </td>
            <td class="statsOptionValue" colspan="2">
              $AllStats{admConnDenied}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Stat Connections Received:
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{statConnTotal}
            </td>
            <td class="statsOptionValue" colspan="2">
              $tots{statConnTotal2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Stat Connections Accepted:
            </td>
            <td class="statsOptionValue" colspan="2">
              $Stats{statConn}
            </td>
            <td class="statsOptionValue" colspan="2">
              $AllStats{statConn}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Stat Connections Rejected:
            </td>
            <td class="statsOptionValue" colspan="2">
              $Stats{statConnDenied}
            </td>
            <td class="statsOptionValue" colspan="2">
              $AllStats{statConnDenied}
            </td>
          </tr>
          <tr>
            <td class="statsOptionValue" style="background-color: #FFFFFF">
              &nbsp;
            </td>
            <td class="statsOptionValue" style="background-color: #FFFFFF"
            colspan="2">
              <font size="1" color="#C0C0C0"><em>since $restart at $starttime</em></font>
            </td>
            <td class="statsOptionValue" style="background-color: #FFFFFF"
            colspan="2">
              <font size="1" color="#C0C0C0"><em>since $reset at $resettime</em></font>
            </td>
          </tr>
        </tbody>
        <tbody>
          <tr>
            <td colspan="5" class="sectionHeader" onmousedown=
            "toggleTbody('StatItem5')">
              SMTP Connection Statistics
            </td>
          </tr>
        </tbody>
        <tbody id="StatItem5" class="off">
          <tr>
            <td class="statsOptionTitle">
              Accepted Logged SMTP Connections:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{smtpConn}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{smtpConn}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Not Logged SMTP Connections:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{smtpConnNotLogged}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{smtpConnNotLogged}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              SMTP Connection Limits:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $tots{smtpConnLimit}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $tots{smtpConnLimit2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Overall Limits:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{smtpConnLimit}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{smtpConnLimit}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;By IP Limits:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{smtpConnLimitIP}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{smtpConnLimitIP}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;By Simple IP Delay:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{delayConnection}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{delayConnection}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;By AUTH Errors Count:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{AUTHErrors}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{AUTHErrors}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;By IP Frequency Limits:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{smtpConnLimitFreq}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{smtpConnLimitFreq}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;By Domain IP Limits:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{smtpConnDomainIP}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{smtpConnDomainIP}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;By Same Subject Limits:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{SameSubject}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{smtpConnDomainIP}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              SMTP Connections Timeout:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $tots{smtpConnIdleTimeout}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $tots{smtpConnIdleTimeout2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              SMTP SSL-Connections Timeout:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $tots{smtpConnSSLIdleTimeout}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $tots{smtpConnSSLIdleTimeout2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              SMTP TLS-Connections Timeout:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $tots{smtpConnTLSIdleTimeout}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $tots{smtpConnTLSIdleTimeout2}
            </td>
          </tr>

          <tr>
            <td class="statsOptionValue" style="background-color: #FFFFFF">
              &nbsp;
            </td>
            <td class="statsOptionValue" style="background-color: #FFFFFF"
            colspan="2">
              <font size="1" color="#C0C0C0"><em>since $restart at $starttime</em></font>
            </td>
            <td class="statsOptionValue" style="background-color: #FFFFFF"
            colspan="2">
              <font size="1" color="#C0C0C0"><em>since $reset at $resettime</em></font>
            </td>
          </tr>
        </tbody>
        <tbody>
          <tr>
            <td colspan="5" class="sectionHeader" onmousedown=
            "toggleTbody('StatItem6')">
              Envelope Recipient Statistics
            </td>
          </tr>
        </tbody>
        <tbody id="StatItem6" class="off">
          <tr>
            <td class="statsOptionTitle">
              Local Recipients Accepted:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $tots{rcptAcceptedLocal}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $tots{rcptAcceptedLocal2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Validated Recipients:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{rcptValidated}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{rcptValidated}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Unchecked Recipients:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{rcptUnchecked}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{rcptUnchecked}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;SpamLover Recipients:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{rcptSpamLover}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{rcptSpamLover}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Remote Recipients Accepted:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $tots{rcptAcceptedRemote}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $tots{rcptAcceptedRemote2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Whitelisted Recipients:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{rcptWhitelisted}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{rcptWhitelisted}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Not Whitelisted Recipients:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{rcptNotWhitelisted}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{rcptNotWhitelisted}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Noprocessed Recipients:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{rcptUnprocessed}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{rcptUnprocessed}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Email Reports:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $tots{rcptReport}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $tots{rcptReport2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Spam Reports:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{rcptReportSpam}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{rcptReportSpam}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Ham Reports:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{rcptReportHam}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{rcptReportHam}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Whitelist Additions:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{rcptReportWhitelistAdd}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{rcptReportWhitelistAdd}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Whitelist Deletions:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{rcptReportWhitelistRemove}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{rcptReportWhitelistRemove}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Redlist Additions:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{rcptReportRedlistAdd}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{rcptReportRedlistAdd}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Redlist Deletions:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{rcptReportRedlistRemove}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{rcptReportRedlistRemove}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Analyze Reports:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{rcptReportAnalyze}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{rcptReportAnalyze}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Help Reports:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{rcptReportHelp}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{rcptReportHelp}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Local Recipients Rejected:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $tots{rcptRejectedLocal}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $tots{rcptRejectedLocal2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Nonexistent Recipients:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{rcptNonexistent}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{rcptNonexistent}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Delayed Recipients:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{rcptDelayed}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{rcptDelayed}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Delayed (Late) Recipients:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{rcptDelayedLate}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{rcptDelayedLate}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Delayed (Expired) Recipients:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{rcptDelayedExpired}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{rcptDelayedExpired}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Embargoed Recipients:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{rcptEmbargoed}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{rcptEmbargoed}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Spam Bucketed Recipients:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{rcptSpamBucket}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{rcptSpamBucket}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Remote Recipients Rejected:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $tots{rcptRejectedRemote}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $tots{rcptRejectedRemote2}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              &nbsp;&nbsp;&nbsp;&nbsp;Relay Attempts Rejected:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{rcptRelayRejected}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{rcptRelayRejected}
            </td>
          </tr>
          <tr>
            <td class="statsOptionValue" style="background-color: #FFFFFF">
              &nbsp;
            </td>
            <td class="statsOptionValue" style="background-color: #FFFFFF"
            colspan="2">
              <font size="1" color="#C0C0C0"><em>since $restart at $starttime</em></font>
            </td>
            <td class="statsOptionValue" style="background-color: #FFFFFF"
            colspan="2">
              <font size="1" color="#C0C0C0"><em>since $reset at $resettime</em></font>
            </td>
          </tr>
        </tbody>
        <tbody>
          <tr>
            <td colspan="5" class="sectionHeader" onmousedown=
            "toggleTbody('StatItem7')">
              Message Statistics
            </td>
          </tr>
        </tbody>
        <tbody id="StatItem7" class="on">
          <tr>
            <td class="statsOptionTitle">
              Message OK:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{bhams}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{bhams}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Whitelisted:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{whites}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{whites}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Local:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{locals}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{locals}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Passing without Processing:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{noprocessing}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{noprocessing}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Spamlover Spams Passed:
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $Stats{spamlover}
            </td>
            <td class="statsOptionValue positive" colspan="2">
              $AllStats{spamlover}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Bayesian Spams:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{bspams}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{bspams}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Blacklisted Domains:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{blacklisted}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{blacklisted}
            </td>
          </tr>

          <tr>
            <td class="statsOptionTitle">
              Invalid HELO:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{invalidHelo}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{invalidHelo}
            </td>
          </tr>
         <tr>
            <td class="statsOptionTitle">
              Missing MX and A Record:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{mxaMissing}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{mxaMissing}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Missing PTR Record:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{ptrMissing}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{ptrMissing}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Invalid PTR Record:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{ptrInvalid}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{ptrInvalid}
            </td>
          </tr>

          <tr>
            <td class="statsOptionTitle">
              Collect/Trap Messages:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{spambucket}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{spambucket}
            </td>
          </tr>

          <tr>
            <td class="statsOptionTitle">
              Bad Attachments:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{viri}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{viri}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Viruses Detected:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{viridetected}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{viridetected}
            </td>
          </tr>

          <tr>
            <td class="statsOptionTitle">
              Black/Bomb Regex:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{bombs}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{bombs}
            </td>
          </tr>

          <tr>
            <td class="statsOptionTitle">
             IPs blocked:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{denyConnection}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{denyConnection}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
             IPs blocked (strict):
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{denyStrict}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{denyStrict}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              CountryCode blocked:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{sbblocked}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{sbblocked}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Message Scoring:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{msgscoring}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{msgscoring}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Invalid Local Sender:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{senderInvalidLocals}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{senderInvalidLocals}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Invalid Internal Mail:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{internaladdresses}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{internaladdresses}
            </td>
          </tr>

          <tr>
            <td class="statsOptionTitle">
              RBL Failures:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{rblfails}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{rblfails}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              URIBL Failures:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{uriblfails}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{uriblfails}
            </td>
          </tr>

          <tr>
            <td class="statsOptionTitle">
              Max Errors Exceeded:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{msgMaxErrors}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{msgMaxErrors}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              MSGverify :
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{msgverify}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{msgverify}
            </td>
          </tr>

          <tr>
            <td class="statsOptionTitle">
              Local Frequency:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{localFrequency}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{localFrequency}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Early (Pre)Header:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{preHeader}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{preHeader}
            </td>
          </tr>

          <tr>
            <td class="statsOptionValue" style="background-color: #FFFFFF">
              &nbsp;
            </td>
            <td class="statsOptionValue" style="background-color: #FFFFFF"
            colspan="2">
              <font size="1" color="#C0C0C0"><em>since $restart at $starttime</em></font>
            </td>
            <td class="statsOptionValue" style="background-color: #FFFFFF"
            colspan="2">
              <font size="1" color="#C0C0C0"><em>since $reset at $resettime</em></font>
            </td>
          </tr>
        </tbody>

      </table><br />

      $kudos
        <br />
        </div>
         $footers

</body></html>
EOT
}

sub ConfigStatsRaw {
    SaveStats();
    my %tots    = statsTotals();
    my $upt     = ( time - $Stats{starttime} ) / ( 24 * 3600 );
    my $upt2    = ( time - $AllStats{starttime} ) / ( 24 * 3600 );
    my $uptime  = sprintf( "%.3f", $upt );
    my $uptime2 = sprintf( "%.3f", $upt2 );
    my $mpd     = sprintf( "%.1f", $upt == 0 ? 0 : $tots{msgTotal} / $upt );
    my $mpd2    = sprintf( "%.1f", $upt2 == 0 ? 0 : $tots{msgTotal2} / $upt2 );
    my $pct     = sprintf( "%.1f",
        $tots{msgTotal} - $Stats{locals} == 0
        ? 0
        : 100 * $tots{msgRejectedTotal} / ( $tots{msgTotal} - $Stats{locals} )
    );
    my $pct2;
    $pct2 = sprintf( "%.1f",
        $tots{msgTotal2} - $AllStats{locals} == 0
        ? 0
        : 100 *
          $tots{msgRejectedTotal2} /
          ( $tots{msgTotal2} - $AllStats{locals} ) );
    my $cpu = $CanStatCPU ? sprintf( "%.2f\%", 100 * $cpuUsage ) : 'n/a';
    my $cpuAvg; $cpuAvg = sprintf( " (%.2f\% avg)",
        $Stats{cpuTime} == 0 ? 0 : 100 * $Stats{cpuBusyTime} / $Stats{cpuTime} )
      if $CanStatCPU;
    my $cpuAvg2 =
      $CanStatCPU
      ? sprintf( "%.2f\% avg",
        $AllStats{cpuTime} == 0
        ? 0
        : 100 * $AllStats{cpuBusyTime} / $AllStats{cpuTime} )
      : 'n/a';
    <<EOT;
$headerHTTP
ASSP Proxy Uptime | $uptime days| $uptime2 days 
Messages Processed | $tots{msgTotal} ($mpd per day) | $tots{msgTotal2} ($mpd2 per day)
Non-Local Mail Blocked | $pct% | $pct2%
CPU Usage | $cpu$cpuAvg | $cpuAvg2
Concurrent SMTP Sessions | $smtpConcurrentSessions ($Stats{smtpMaxConcurrentSessions} highest) | $AllStats{smtpMaxConcurrentSessions} max

SMTP Connections Received | $tots{smtpConnTotal} | $tots{smtpConnTotal2}
SMTP Connections Accepted | $tots{smtpConnAcceptedTotal} | $tots{smtpConnAcceptedTotal2}
SMTP Connections Rejected | $tots{smtpConnRejectedTotal} | $tots{smtpConnRejectedTotal2}
Envelope Recipients Processed | $tots{rcptTotal} | $tots{rcptTotal2}
Envelope Recipients Accepted | $tots{rcptAcceptedTotal} | $tots{rcptAcceptedTotal2}
Envelope Recipients Rejected | $tots{rcptRejectedTotal} | $tots{rcptRejectedTotal2}
Messages Processed | $tots{msgTotal} | $tots{msgTotal2}
Messages Passed | $tots{msgAcceptedTotal} | $tots{msgAcceptedTotal2}
Messages Rejected | $tots{msgRejectedTotal} | $tots{msgRejectedTotal2}
Admin Connections Received | $tots{admConnTotal} | $tots{admConnTotal2}
Admin Connections Accepted | $Stats{admConn} | $AllStats{admConn}
Admin Connections Rejected | $Stats{admConnDenied} | $AllStats{admConnDenied}
Stat Connections Received | $tots{statConnTotal} | $tots{statConnTotal2}
Stat Connections Accepted | $Stats{statConn} | $AllStats{statConn}
Stat Connections Rejected | $Stats{statConnDenied} | $AllStats{statConnDenied}

Accepted Logged SMTP Connections | $Stats{smtpConn} | $AllStats{smtpConn}
Not Logged SMTP Connections | $Stats{smtpConnNotLogged} | $AllStats{smtpConnNotLogged}
SMTP Connection Limits | $tots{smtpConnLimit} | $tots{smtpConnLimit2}
Overall Limits | $Stats{smtpConnLimit} | $AllStats{smtpConnLimit}
By IP Limits | $Stats{smtpConnLimitIP} | $AllStats{smtpConnLimitIP}
By Delay on PB | $Stats{delayConnection} | $AllStats{delayConnection}
BY IP By AUTH Errors Count | $Stats{AUTHErrors} | $AllStats{AUTHErrors}
By IP Frequency Limits | $Stats{smtpConnLimitFreq} | $AllStats{smtpConnLimitFreq}
By Domain IP Limits | $Stats{smtpConnDomainIP} | $AllStats{smtpConnDomainIP}
SMTP Connections Timeout | $tots{smtpConnIdleTimeout} | $tots{smtpConnIdleTimeout2}
SMTP SSL-Connections Timeout | $tots{smtpConnSSLIdleTimeout} | $tots{smtpConnSSLIdleTimeout2}
SMTP TLS-Connections Timeout | $tots{smtpConnTLSIdleTimeout} | $tots{smtpConnTLSIdleTimeout2}
Denied SMTP Connections | $Stats{smtpConnDenied} | $AllStats{smtpConnDenied}

Local Recipients Accepted | $tots{rcptAcceptedLocal} | $tots{rcptAcceptedLocal2}
Validated Recipients | $Stats{rcptValidated} | $AllStats{rcptValidated}
Unchecked Recipients | $Stats{rcptUnchecked} | $AllStats{rcptUnchecked}
SpamLover Recipients | $Stats{rcptSpamLover} | $AllStats{rcptSpamLover}
Remote Recipients Accepted | $tots{rcptAcceptedRemote} | $tots{rcptAcceptedRemote2}
Whitelisted Recipients | $Stats{rcptWhitelisted} | $AllStats{rcptWhitelisted}
Not Whitelisted Recipients | $Stats{rcptNotWhitelisted} | $AllStats{rcptNotWhitelisted}
Noprocessed Recipients | $Stats{rcptUnprocessed} | $AllStats{rcptUnprocessed}
Email Reports | $tots{rcptReport} | $tots{rcptReport2}
Spam Reports | $Stats{rcptReportSpam} | $AllStats{rcptReportSpam}
Ham Reports | $Stats{rcptReportHam} | $AllStats{rcptReportHam}
Whitelist Additions | $Stats{rcptReportWhitelistAdd} | $AllStats{rcptReportWhitelistAdd}
Whitelist Deletions | $Stats{rcptReportWhitelistRemove} | $AllStats{rcptReportWhitelistRemove}
Redlist Additions | $Stats{rcptReportRedlistAdd} | $AllStats{rcptReportRedlistAdd}
Redlist Deletions | $Stats{rcptReportRedlistRemove} | $AllStats{rcptReportRedlistRemove}
Local Recipients Rejected | $tots{rcptRejectedLocal} | $tots{rcptRejectedLocal2}
Nonexistent Recipients | $Stats{rcptNonexistent} | $AllStats{rcptNonexistent}
Delayed Recipients | $Stats{rcptDelayed} | $AllStats{rcptDelayed}
Delayed (Late) Recipients | $Stats{rcptDelayedLate} | $AllStats{rcptDelayedLate}
Delayed (Expired) Recipients | $Stats{rcptDelayedExpired} | $AllStats{rcptDelayedExpired}
Embargoed Recipients | $Stats{rcptEmbargoed} | $AllStats{rcptEmbargoed}
Spam Bucketed Recipients | $Stats{rcptSpamBucket} | $AllStats{rcptSpamBucket}
Remote Recipients Rejected | $tots{rcptRejectedRemote} | $tots{rcptRejectedRemote2}
Relay Attempts Rejected | $Stats{rcptRelayRejected} | $AllStats{rcptRelayRejected}

Hams | $Stats{bhams} | $AllStats{bhams}
Whitelisted | $Stats{whites} | $AllStats{whites}
Local | $Stats{locals} | $AllStats{locals}
Noprocessing | $Stats{noprocessing} | $AllStats{noprocessing}
Spamlover Spams Passed | $Stats{spamlover} | $AllStats{spamlover}
Bayesian Spams | $Stats{bspams} | $AllStats{bspams}
Domains Blacklisted | $Stats{blacklisted} | $AllStats{blacklisted}

HELO Invalid | $Stats{invalidHelo} | $AllStats{invalidHelo}

Missing MX | $Stats{mxaMissing} | $AllStats{mxaMissing}
Missing PTR | $Stats{ptrMissing} | $AllStats{ptrMissing}
Invalid PTR | $Stats{ptrInvalid} | $AllStats{ptrInvalid}
Spam Collect Messages | $Stats{spambucket} | $AllStats{spambucket}
Penalty Trap Messages | $Stats{penaltytrap} | $AllStats{penaltytrap}
Bad Attachments | $Stats{viri} | $AllStats{viri}
Viruses Detected | $Stats{viridetected} | $AllStats{viridetected}
Bomb Regex | $Stats{bombs} | $AllStats{bombs}
Black Regex | $Stats{bombs} | $AllStats{bombBlack}

PenaltyBox | $Stats{pbdenied} | $AllStats{pbdenied}
Message Scoring | $Stats{msgscoring} | $AllStats{msgscoring}
Invalid Local Sender | $Stats{senderInvalidLocals} | $AllStats{senderInvalidLocals}
Invalid Internal Mail | $Stats{internaladdresses} | $AllStats{internaladdresses}
Scripts | $Stats{scripts} | $AllStats{scripts}
SPF Failures | $Stats{spffails} | $AllStats{spffails}
RBL Failures | $Stats{rblfails} | $AllStats{rblfails}
URIBL Failures | $Stats{uriblfails} | $AllStats{uriblfails}
Max Errors Exceeded | $Stats{msgMaxErrors} | $AllStats{msgMaxErrors}
Delayed | $Stats{msgDelayed} | $AllStats{msgDelayed}
Empty Recipient | $Stats{msgNoRcpt} | $AllStats{msgNoRcpt}
Not SRS Signed Bounces | $Stats{msgNoSRSBounce} | $AllStats{msgNoSRSBounce}
MSGID Signature | $Stats{msgMSGIDtrErrors} | $AllStats{msgMSGIDtrErrors}

EOT
}

sub ConfigStatsXml {

    # must pass by ref
    my ( $href, $qsref ) = @_;
    my %head = %$href;
    my %qs   = %$qsref;

    my %totStats = &ComputeStatTotals;
    my %tots     = statsTotals(%totStats);

    my $statstart  = localtime( $Stats{starttime} );
    my $statstart2 = localtime( $totStats{starttime} );

    my $tstatstime = ( time - $totStats{starttime} ) / ( 24 * 3600 );
    my $cstatstime = ( time - $Stats{starttime} ) /    ( 24 * 3600 );

    my $uptime = getTimeDiffAsString( time - $starttime, 1 );
    my $uptime2 = getTimeDiffAsString( time - $totStats{starttime} );

    my $mpd =
      sprintf( "%.1f", $cstatstime == 0 ? 0 : $tots{msgTotal} / $cstatstime );
    my $mpd2 =
      sprintf( "%.1f", $tstatstime == 0 ? 0 : $tots{msgTotal2} / $tstatstime );
    my $pct = sprintf( "%.1f",
        $tots{msgTotal} - $Stats{locals} == 0
        ? 0
        : 100 * $tots{msgRejectedTotal} / ( $tots{msgTotal} - $Stats{locals} )
    );
    my $pct2 = sprintf( "%.1f",
        $tots{msgTotal2} - $totStats{locals} == 0
        ? 0
        : 100 *
          $tots{msgRejectedTotal2} /
          ( $tots{msgTotal2} - $totStats{locals} ) );
    my $cpu = $CanStatCPU ? sprintf( "%.2f\%", 100 * $cpuUsage ) : 'na';
    my $cpuAvg =
      $CanStatCPU
      ? sprintf( "%.2f\%",
        $Stats{cpuTime} == 0 ? 0 : 100 * $Stats{cpuBusyTime} / $Stats{cpuTime} )
      : 'na';
    my $cpuAvg2 =
      $CanStatCPU
      ? sprintf( "%.2f\%",
        $totStats{cpuTime} == 0
        ? 0
        : 100 * $totStats{cpuBusyTime} / $totStats{cpuTime} )
      : 'na';

    my $r = '';
    foreach my $k ( keys %tots ) {
        next unless $k;

        my $s = $k;
        if ( $s =~ tr/2//d ) {
            $r .= "<stat name='$s' type='cumulativetotal'>$tots{$k}</stat>";
        } else {
            $r .= "<stat name='$s' type='currenttotal'>$tots{$k}</stat>";
        }
    }
    foreach my $k ( keys %Stats ) {
        next unless $k;
        $r .= "<stat name='$k' type='currentstat'>$Stats{$k}</stat>";
    }
    foreach my $k ( keys %totStats ) {
        next unless $k;
        $r .= "<stat name='$k' type='cumulativestat'>$totStats{$k}</stat>";
    }

    <<EOT;
$headerHTTP

<?xml version='1.0' encoding='UTF-8'?>
<stats>
<stat name='statstart' type='currentstat'>$statstart</stat>
<stat name='statstart' type='cumulativestat'>$statstart2</stat>
<stat name='uptime' type='currentstat'>$uptime</stat>
<stat name='uptime' type='cumulativestat'>$uptime2</stat>
<stat name='msgPerDay' type='currentstat'>$mpd</stat>
<stat name='msgPerDay' type='cumulativestat'>$mpd2</stat>
<stat name='pctBlocked' type='currentstat'>$pct</stat>
<stat name='pctBlocked' type='cumulativestat'>$pct2</stat>
<stat name='cpuAvg' type='currentstat'>$cpuAvg</stat>
<stat name='cpuAvg' type='cumulativestat'>$cpuAvg2</stat>
<stat name='smtpConcurrentSessions' type='currentstat'>$smtpConcurrentSessions</stat>
$r
</stats>
EOT

}

sub ConfigLists {
    my $s;
    my $a;
    my $act = $qs{action};
    if ($act) {
        if ( $qs{list} eq 'tuplets' ) {
            my $ip;
            my $hash;
            my $t;
            my $interval;
            my $intervalFormatted;
            while ($qs{addresses}=~/($IPRe)\s*,?\s*<?(?:$EmailAdrRe\@)?($EmailDomainRe|)>?/go) {
                $ip = ipNetwork( $1, $DelayUseNetblocks );
                $a = lc $2;
                if ($DelayNormalizeVERPs) {

                    # strip extension
                    $a =~ s/\+.*(?=@)//;

                    # replace numbers with '#'
                    $a =~ s/\b\d+\b(?=.*@)/#/g;
                }

                # get sender domain
                $a =~ s/.*@//;
                $hash = "$ip $a";
                $hash = Digest::MD5::md5_hex($hash)
                  if $CanUseMD5 && $DelayMD5;
                $t = time;
                $s .= "<div class=\"text\">($ip,$a) ";
                if ( $act eq 'v' ) {

                    if ( !exists $DelayWhite{$hash} ) {
                        $s .=
"<span class=\"negative\">tuplet NOT whitelisted</span>";
                    } else {
                        $interval          = $t - $DelayWhite{$hash};
                        $intervalFormatted = formatTimeInterval($interval);
                        if ( $interval < $DelayExpiryTime * 24 * 3600 ) {
                            $s .= "tuplet whitelisted, age: $intervalFormatted";
                        } else {
                            $s .= "tuplet expired, age: $intervalFormatted";
                        }
                    }
                } elsif ( $act eq 'a' ) {
                    if (
                        !exists $DelayWhite{$hash}
                        || ( $t - $DelayWhite{$hash} >=
                            $DelayExpiryTime * 24 * 3600 )
                      )
                    {
                        if ( localmail( '@' . $a ) ) {
                            $s .=
"<span class=\"negative\">local addresses not allowed on whitelisted tuplets</span>";
                        } else {
                            $s .= "tuplet added";
                            $DelayWhite{$hash} = $t;
                            mlog( 0,
"AdminInfo: whitelisted tuplets addition: ($ip,$a) (admin)"
                            );
                        }
                    } else {
                        $s .=
"<span class=\"positive\">tuplet already whitelisted</span>";
                    }
                } elsif ( $act eq 'r' ) {
                    if ( !exists $DelayWhite{$hash} ) {
                        $s .=
"<span class=\"negative\">tuplet NOT whitelisted</span>";
                    } else {
                        $interval          = $t - $DelayWhite{$hash};
                        $intervalFormatted = formatTimeInterval($interval);
                        if ( $interval < $DelayExpiryTime * 24 * 3600 ) {
                            $s .= "tuplet removed, age: $intervalFormatted";
                        } else {
                            $s .=
                              "expired tuplet removed, age: $intervalFormatted";
                        }
                        delete $DelayWhite{$hash};
                        mlog( 0,
"AdminInfo: whitelisted tuplets deletion: ($ip,$a) (admin)"
                        );
                    }
                }
                $s .= "</div>\n";
            }
        } else {
            my $color = $qs{list} eq 'red' ? 'Red' : 'White';
            my $list = $color . "list";
            while            ($qs{addresses}=~/($EmailAdrRe\@$EmailDomainRe'?)(?:(,(?:$EmailAdrRe\@$EmailDomainRe'?)|\*))?/go) {
                $a = $1;
                $s .= "<div class=\"text\">$a ";
                $a = lc $a;
                if ( $act eq 'v' ) {
                    if ( $list->{$a} ) {
                        $s .= "${color}listed";
                    } else {
                        $s .=
                          "<span class=\"negative\">NOT $qs{list}listed</span>";
                    }
                } elsif ( $act eq 'a' ) {
                    if ( $list->{$a} ) {
                        $s .=
"<span class=\"positive\">already $qs{list}listed</span>";
                    } else {

                        if($color eq 'White' && localmail($a)) {
                            $s.="<span class=\"negative\">local addresses not allowed on whitelist</span>";

                         } else {
                        $s .= "added";
                        $list->{$a} = time;
                        mlog( 0,
                            "AdminInfo: $qs{list}list addition: $a (admin)" );

                        }
                    }
                } elsif ( $act eq 'r' ) {
                    if ( $list->{$a} ) {
                        $s .= "removed";
                        delete $list->{$a};
                        mlog( 0,
                            "AdminInfo: $qs{list}list deletion: $a (admin)" );
                    } else {
                        $s .= "not $qs{list}listed";
                    }
                }
                $s .= "</div>\n";
            }
        }
    }
    if ( $qs{B1} =~ /^Show (.)/i ) {
        local $/ = "\n";
        if ( $1 eq 'R' ) {
            $qs{list} = "red";    # update radios
            $RedlistObject->flush() if $RedlistObject && $redlistdb !~ /mysql/;
            open( $FH, "<","$base/$redlistdb" ) if $redlistdb !~ /mysql/;
            $s .= '<div class="textbox"><b>Redlist</b></div>';
            if ( $redlistdb =~ /mysql/ ) {
                $s .= '<div class="textbox"><b>mysql</b></div>';
                while ( my ($v) = each(%Redlist) ) {
                    my ( $a, $time ) = split( "\002", $v );
                    $s .= "<div class=\"textbox\">$a</div>";
                }
            }
        } else {
            $qs{list} = "white";    # update radios
            $WhitelistObject->flush()
              if $WhitelistObject && $whitelistdb !~ /mysql/;
            open( $FH, "<","$base/$whitelistdb" ) if $whitelistdb !~ /mysql/;
            $s .= '<div class="textbox"><b>Whitelist</b></div>';
            if ( $whitelistdb =~ /mysql/ ) {
                $s .= '<div class="textbox"><b>mysql</b></div>';
                while ( my ($v) = each(%Whitelist) ) {
                    my ( $a, $time ) = split( "\002", $v );
                    $s .= "<div class=\"textbox\">$a</div>";

                }
            }
        }
        if ( $whitelistdb !~ /mysql/ ) {
            my $l;
            while ( $l = <$FH> ) {
                my ($a) = $l =~ /([^\002]*)/;
                $s .= "<div class=\"textbox\">$a</div>";
            }
            close $FH;
        }
    }
    $resultConfigLists = $s;
    <<EOT;
$headerHTTP
$headerDTDTransitional
$headers

<div class="content">
<h2>Update or Verify the Whitelist/Redlist</h2>
$s
<form method="post" action=\"\">
    <table class="textBox" style="width: 99%;">
        <tr>
            <td class="noBorder">Do you want to work with the:
            </td>
            <td class="noBorder">
            <input type="radio" name="list" value="white"${\((!$qs{list} || $qs{list} eq 'white') ? ' checked="checked" ' : ' ')} /> Whitelist or<br />
            <input type="radio" name="list" value="red"${\($qs{list} eq 'red' ? ' checked="checked" ' : ' ')} /> Redlist or<br />
            <input type="radio" name="list" value="tuplets"${\($qs{list} eq 'tuplets' ? ' checked="checked" ' : ' ')} /> Tuplets
            </td>
        </tr>
        <tr>
            <td class="noBorder">Do you want to: </td>
            <td class="noBorder"><input type="radio" name="action" value="a" />add<br />
            <input type="radio" name="action" value="r" />remove<br />
            <input type="radio" checked="checked" name="action" value="v" />or verify</td>
            <td class="noBorder">
                List the addresses in this box:<br />
                (for tuplets put: ip-address,domain-name)<br />
                <p><textarea name="addresses" rows="5" cols="40" wrap="off">$qs{addresses}</textarea></p>
            </td>
        </tr>
        <tr>
            <td class="noBorder">&nbsp;</td>
            <td class="noBorder"><input type="submit" name="B1" value="  Submit  " /></td>
            <td class="noBorder">&nbsp;</td>
        </tr>
    </table>
</form>
<div class="textBox">
<p>Post less than 1 megabyte of data at a time.</p>
Note: The redlist is not a blacklist. The redlist is a list of addresses that cannot
contribute to the whitelist. For example, if someone goes on a vacation and turns on their
email's autoresponder, put them on the redlist until they return. Then as they reply
to every spam they receive they won't corrupt your non-spam collection or whitelist.

  <form action="" method="post">
  <table style="width: 90%; margin-left: 5%;">
    <tr>
      <td align="center" class="noBorder"><input type="submit" name="B1" value="Show Whitelist" /></td>
      <td align="center" class="noBorder"><input type="submit" name="B1" value="Show Redlist" /></td>
    </tr>
  </table>
  </form>
      <p class="warning">warning:   If your whitelist or redlist is long, pushing these buttons
      is ill-advised.</p>
</div>
</div>
$footers
</body></html>
EOT

}

sub SearchBombW {
    my ($name, $srch)=@_;

    $incFound = '';
    $weightMatch = '';
    my %Bombs = &BombWeight(0,$srch,$name );
    if ($Bombs{count}) {
        my $match = &SearchBomb($name, $$srch);
        $weightMatch = $match if (! $weightMatch);
        return 'highest match: "' . "$Bombs{matchlength}$Bombs{highnam}" . '" with valence: ' . $Bombs{highval} . ' - PB value = ' . $Bombs{sum};
    }
    return;
}

sub SearchBomb {
    my ($name, $srch, $nolog)=@_;
    my $extLog = $AnalyzeLogRegex && ! $silent && [caller(1)]->[3] =~ /analyze/io;
    $incFound = '';
    my $fil=$Config{"$name"};
    return 0 unless $fil;
    $addCharsets = 1 if $name eq 'bombCharSets';
    my $text;
    if ($name ne 'bombSubjectRe') {
       my $mimetext = cleanMIMEBody2UTF8(\$srch);

       if ($mimetext) {
           $text =  cleanMIMEHeader2UTF8(\$srch,0);
           $mimetext =~ s/\=(?:\015?\012|\015)//go;
           $mimetext = decHTMLent(\$mimetext);
           $text .= $mimetext;
       } else {
           $text = decodeMimeWords2UTF8($srch)
       }
    } elsif (! $LogCharset) {
       eval{$text = ( $srch ? Encode::encode('utf-8',$srch) : '');};
    } else {
       $text = $srch;
    }
    $srch = $text;

    undef $text;
    $addCharsets = 0;
    my @complex;
    if($fil=~/^\s*file:\s*(.+)\s*$/io) {
        $fil=$1;
        open (my $BOMBFILE, "<","$base/$fil");
        my $counter=0;
        my $complexStartLine;
        while (my $i = <$BOMBFILE>)  {
            $counter++;
            $i =~ s/\<\<\<(.*?)\>\>\>/$1/o;
            $i =~ s/!!!(.*?)!!!//o;
            $i =~ s/a(?:ssp)?-do?-n(?:ot)?-o(?:ptimize)?-r(?:egex)?//iso;
            if ($i =~ /(^\s*#\s*include\s+)(.+)/io) {
                my $fn = $2;
                $i = $1;
                $fn =~ s/([^\\\/])[#;].*/$1/go;
                $i .= $fn;
            } else {
                $i =~ s/^#.*//go;
                $i =~ s/([^\\])#.*/$1/go;
            }
            $i =~ s/^;.*//go;
            $i =~ s/([^\\]);.*/$1/go;
            $i =~ s/\r//go;
            $i =~ s/\s*\n+\s*//go;
             $i =~ s/\s+$//o;
            next if !$i;

            if (($i =~ /^\~?\Q$complexREStart\E\s*$/o || @complex) && $i !~ /^\Q$complexREEnd\E\d+\}\)(?:\s*\=\>\s*(?:-{0,1}\d+\.*\d*)?\s*(?:\s*\:\>\s*(?:[nNwWlLiI\+\-\s]+)?)?)?$/o) {
                $complexStartLine = $counter if !$complexStartLine && $i =~ /^\~?\Q$complexREStart\E\s*$/o;
                if ($i !~ /^\s*#\s*include\s+.+/io) {
                    push @complex, $i;
                    next;
                }
            } elsif ($i =~ /^\Q$complexREEnd\E\d+\}\)(?:\s*\=\>\s*([+\-]?(?:0?\.\d+|\d+\.\d+|\d+))?\s*(?:\s*\:\>\s*(?:[nNwWlLiI\+\-\s]+)?)?)?$/o) {
                push @complex, $i;
                $i = join('|', @complex);
                @complex = ();
            }
            my $isave=$i; 
            $i =~ s/(\~([^\~]+)?\~|([^\|]+)?)\s*\=\>\s*([+\-]?(?:0?\.\d+|\d+\.\d+|\d+))?\s*(?:\s*\:\>\s*(?:[nNwWlLiI\+\-\s]+)?)?/$2/o;

            next if !$i;
#            $i =~ s/\~//go;

            my $line;
            my $reg = $i;
            my $file = $fil;
            my $found;
            my $INCFILE;
            if ($i =~ /^\s*#\s*include\s+(.+)\s*/io && (open $INCFILE, "<","$base/$1")) {
                $line = 0;
                $file = $1;
                my @complexInc;
                my $complexIncStart;
                while (my $ii = <$INCFILE>) {
                    $line++;

                    $ii =~ s/\<\<\<(.*?)\>\>\>/$1/o;
                    $ii =~ s/!!!(.*?)!!!/$1/o;
                    $ii =~ s/a(?:ssp)?-do?-n(?:ot)?-o(?:ptimize)?-r(?:egex)?//iso;
                    $ii =~ s/^[#;].*//go;
                    $ii =~ s/^;.*//go;
                    $ii =~ s/([^\\])#.*/$1/go;
                    $ii =~ s/([^\\]);.*/$1/go;
                    $ii =~ s/\r//go;
                    $ii =~ s/\s*\n+\s*//go;
                    $ii =~ s/\s+$//o;
                    next if !$ii;

                    if (@complex)  {                   # complex regex started in upper file
                        push @complex, $ii;
                        next;
                    }
# complex regex started in include file    

                    if (($ii =~ /^\~?\Q$complexREStart\E\s*$/o || @complexInc) && $ii !~ /^\Q$complexREEnd\E\d+\}\)(?:\s*\=\>\s*(?:-{0,1}\d*\.?\d*)?\s*(?:\s*\:\>\s*(?:[nNwWlLiI\+\-\s]+)?)?)?$/o) {
                        $complexIncStart = $line if !$complexIncStart && $ii =~ /^\~?\Q$complexREStart\E\s*$/o;
                        push @complexInc, $ii;
                        next;
                    } elsif ($ii =~ /^\Q$complexREEnd\E\d+\}\)(?:\s*\=\>\s*(?:-{0,1}\d*\.?\d*)?\s*(?:\s*\:\>\s*(?:[nNwWlLiI\+\-\s]+)?)?)?$/o) {
                        push @complexInc, $ii;
                        $ii = join('|', @complexInc);
                        @complexInc = ();
                    }
                    my $iisave=$ii;
                    $ii =~ s/(\~([^\~]+)?\~|([^\|]+)?)\s*\=\>\s*([+\-]?(?:0?\.\d+|\d+\.\d+|\d+))?\s*(?:\s*\:\>\s*(?:[nNwWlLiI\+\-\s]+)?)?/$2/o;

                    next if !$ii;

                    $found = '';
                    eval{$found = $1 || $2 if $srch =~ m/($ii)/i;};
                    if ($@) {
                        mlog(0,"ConfigError: '$name' regular expression error in line $counter of file $fil - line $line of include '$file' for '$iisave': $@");
                        &disableRegex ($name, $file, $ii, $@);
                        next;
                    }
                    if ($found)
                    {

                        mlog(0,"Info: '$found' matched in regular expression '$name' - in line $counter of file $fil - line ".($complexIncStart?"$complexIncStart-$line":$line)." of file '$file' with '$iisave'") if ($BombLog > 2 or $extLog) && !$nolog;
                        close ($INCFILE);
                        $incFound = "<a href=\"javascript:void(0);\" onclick=\"javascript:popFileEditor('$fil',1);\" onmouseover=\"showhint('edit file $fil', this, event, '250px', '1'); return true;\">$Config{$name}\[line $counter\]</a>|incl:<a href=\"javascript:void(0);\" onclick=\"javascript:popFileEditor('$file',1);\" onmouseover=\"showhint('edit file $file', this, event, '250px', '1'); return true;\">$file\[line ".($complexIncStart?"$complexIncStart-$line":$line)."\]</a>";

                        close ($BOMBFILE);

                        return $iisave;
                    }
                    $complexIncStart = 0;
                }
                close $INCFILE;
                next;
            } elsif ($i =~ /^\s*#\s*include\s+(.+)s*/io) {
                mlog(0,"ConfigError: '$name' unable to open include file $1 in line $counter of '$file'");
                next;
            } else {
                $found = '';

                eval{$found = $1 || $2 if $srch =~ m/($i)/i;};
                if ($@) {
                    mlog(0,"ConfigError: '$name' regular expression error in line $counter of '$file' for '$isave': $@");
                    &disableRegex ($name, $file, $i, $@);
                    next;
                }
            }
            if ($found)

            {

                mlog(0,"Info: '$found' matched in regular expression '$name' - in line $counter of file $fil - line" .($complexStartLine?"$complexStartLine-$counter":$counter). " of file '$file' with '$isave'") if ($BombLog > 2 or $extLog) && !$nolog;
                close ($BOMBFILE);
                $incFound = "<a href=\"javascript:void(0);\" onclick=\"javascript:popFileEditor('$file',1);\" onmouseover=\"showhint('edit file $file', this, event, '250px', '1'); return true;\">$Config{$name}\[line ".($complexStartLine?"$complexStartLine-$counter":$counter)."\]</a>";
                return $isave;
            }
            $complexStartLine = 0;
        }
        close ($BOMBFILE);
    } else {
        my $regex;
        $fil =~ s/(\~([^\~]+)?\~|([^\|]+)?)\s*\=\>\s*([+\-]?(?:0?\.\d+|\d+\.\d+|\d+))?\s*(?:\s*\:\>\s*(?:[nNwWlLiI\+\-\s]+)?)?/$2/o; # skip weighted regexes
        my @reg;
        my $bd=0;
        my $sk;
        my $t;
        foreach my $s (split('',$fil)) {
            if ($s eq '\\') {
                $sk = 1;
                $t .= $s;
                next;
            } elsif ($sk == 1) {
                $sk = 0;
                $t .= $s;
                next;
            }
            if ($s eq '(' or $s eq '[' or $s eq '{') {
                $bd++;
                $t .= $s;
                next;
            } elsif ($s eq ')' or $s eq ']' or $s eq '}') {
                $bd--;
                $t .= $s;
                next;
            }
            if ($bd > 0) {
                $t .= $s;
                next;
            } elsif ($s eq '|') {
                push @reg, $t;
                $t = '';
                $sk = 0;
                next;
            } else {
                $t .= $s;
            }
        }
        push @reg,$t if $t;

        while (@reg) {
            $regex = shift @reg;
            if (my ($i) = eval{$srch =~ m/($regex)/i}) {
                mlog(0,"Info: '$name' regular expression '$regex' match with '$i' ") if ($BombLog > 2 or $extLog) && !$nolog;
                $incFound = $i;
                return $i;
            }
        }
        if ($@) {
            mlog(0,"ConfigError: '$name' regular expression error of '$fil' for '$name': $@");
        }
    }
    return 0;
}

sub disableRegex {
    my ($name, $file, $regex, $error) = @_;
    mlog(0,"Info: regular expression '$regex' in '$file' disabled");
    my $BOMBFILE;
    my $fil = "$base/$file";
    open( $BOMBFILE, "<","$fil" );
    my @lines = <$BOMBFILE>;
    close($BOMBFILE);
    unlink "$fil.bak";
    rename( "$fil", "$fil.bak" );
    open( $BOMBFILE, ">","$fil" );
    my $time = timestring();
    foreach my $k (@lines) {
            $k =~ s/(\Q$regex\E)/## $time disabled ##: $1 $error/i;        
            print $BOMBFILE "$k";
      }
    close($BOMBFILE);
}
sub ConfigAnalyze {
    my ( $ba, $st, $fm, %fm, %to, %wl, $ip, $helo, $text, $ip3, $received , $emfd);
    my $checkRegex = ! $silent && $AnalyzeLogRegex;
    my $mail = $qs{mail};
    $mail =~ s/\r?\n/\r\n/gos;
    my $hl = getheaderLength(\$mail);
    my $mBytes = $MaxBytes ? $MaxBytes + $hl : 10000 + $hl;
    $mail = substr( $qs{mail}, 0, $mBytes );
    $fm = "analyze is restricted to a maximum length of $mBytes bytes<br />\n" if length($qs{mail}) > length($mail);
    if ($mail =~ /X-Assp-ID: (.+)/io) {
        $fm .= "ASSP-ID: $1<br />";
    }
    if ($mail =~ s/X-Assp-Envelope-From:\s*($HeaderValueRe)//ios) {
        my $s = $1;
        &headerUnwrap($s);
        if ($s =~ /($EmailAdrRe\@$EmailDomainRe)/io) {
            $s = batv_remove_tag(0,lc $1,'');
            $fm{$s}=1;
            ($emfd) = $s =~ /\@(.*)/o;
        }
    }
    if ($mail =~ s/X-Assp-Recipient:\s*($HeaderValueRe)//ios) {
        my $s = $1;
        &headerUnwrap($s);
        if ($s =~ /($EmailAdrRe\@$EmailDomainRe)/o) {
            $s = batv_remove_tag(0,lc $1,'');
            $to{$s}=1;
        }
    }
    if (! scalar keys %to && $mail =~ s/X-Assp-Envelope-For:\s*($HeaderValueRe)//ios) {
        my $s = $1;
        &headerUnwrap($s);
        if ($s =~ /($EmailAdrRe\@$EmailDomainRe)/o) {
            $s = batv_remove_tag(0,$1,'');
            $to{lc $s}=1;
        }
    }
    $fm .= "removed all local X-ASSP- header lines for analysis<br />\n"
        if ($mail =~ s/x-assp-[^()]+?:\s*$HeaderValueRe//gios);
    my $mystatus;
    my @t;
    my $ret;
    my $bombsrch;
    my $orgmail;
    my @sips;
    my $foundReceived;
    my $sub = undef;
    my $wildcardUser = lc $wildcardUser;
    my $headerLen = index($mail,"\015\012\015\012");

        if ($mail) {
        $orgmail = $mail;
        my $name = $myName;
        $name =~ s/(\W)/\\$1/go;
        if ($headerLen > -1) {
            my $fhh;
            do {
               $fhh = rand(1000000);
            } while exists $Con{$fhh};
            $Con{$fhh}->{header} = $mail;
            $Con{$fhh}->{headerpassed} = 1;
            &makeSubject($fhh);
            $sub = $Con{$fhh}->{subject3} if defined $Con{$fhh}->{subject3};
            headerUnwrap($sub) if (defined $sub);
            delete $Con{$fhh};
        }

        my @myNames = ($myName);
        push @myNames , split(/[\|, ]+/o,$myNameAlso);
        my $myName = join('|', map {my $t = quotemeta($_);$t;} @myNames);
        while ( $mail =~ /Received:\s+from\s+.*?\(\[($IPRe).*?helo=(.{0,64})\)(?:\s+by\s+(?:$myName))?\s+with/isg ) {
            $ip = ipv6expand(ipv6TOipv4($1));
            $helo = $2;
            $foundReceived = -1;
        }

        if (! $ip && $mail =~ /(?:^[\s\r\n]*|\r?\n)\s*ip\s*=\s*($IPRe)/ios ) {
            $ip = ipv6expand(ipv6TOipv4($1));
            $mystatus="ip";
        }

        $fm .= "Connecting IP: '$ip'<br />\n" if $ip;
        my $conIP = $ip;
        $ip3 = ipNetwork($ip,1);
        if (!$helo && $mail =~ /(?:^[\s\r\n]*|\r?\n)\s*helo\s*=\s*([^\r\n]+)/ios ) {
            $helo = $1;
            $helo =~ s/\)$//o;
            $mystatus="helo";
        }
        $fm .= "Connecting HELO: $helo<br />\n" if $helo;
        if ( $foundReceived != -1 && $mail =~ /(?:^[\s\r\n]*|\r?\n)\s*(text|test|regex)\s*=\s*(.+)/ios ) {
            $text = $2;
            $mystatus="text";
            $fm .= "found $1='$2' - lookup regular expressions in $1<br />\n";
        } else {
            $text = $mail;
        }

$text =~ s/(?:\r?\n)+/\r\n/gos if $mystatus;
        $fm = "<div class=\"textBox\"><b><font size=\"3\" color=\"#003366\">General Hints:</font></b><br /><br />\n$fm</div>\n" if $fm;
        $fm .= "<div class=\"textBox\"><br />";
        if ($ispHostnames && (my($header) = $mail =~ /($HeaderRe+)/o)) {
            my $ispHost;
            while ( $header =~ /Received:($HeaderValueRe)/gis ) {
                my $val = $1;
                if ( $val =~ /(\s+from\s+(?:([^\s]+)\s)?(?:.+?)($IPRe)(?:.{1,80})by.{1,20}($ispHostnamesRE))/gis ) {
                    $helo = $2;
                    $received = 'Received:'.$1;
                    $ispHost = $4;
                    $ip = ipv6expand(ipv6TOipv4($3));
                    $ip3 = ipNetwork($ip,1);
                    $foundReceived = 1;
                }
            }
            if ($received) {
                $fm =~ s/(Connecting IP: '[^']+')/$1 is an <a href='.\/ispip'>ISPIP<\/a>/o;
                $fm =~ s/(Connecting HELO: [^<]+)/$1 is HELO from ISP-host: <a href='.\/ispHostnames'>$ispHost<\/a>/o;
                $fm .= "<b><font color='orange'>&bull;</font>ISP/Secondary Header:</b>'$received'<br />\n";
                $fm .= "<b><font color='orange'>&bull;</font>Switched to ISP/Secondary IP:</b> '$ip'<br /><br />\n";
            }
        }

        if ($foundReceived <= 0 && !$mystatus) {
            $foundReceived += () = $mail =~ /(Received:\s*from\s*)/isgo;
            $fm .= "<b><font color='yellow'>&bull;</font>no foreign received header line found</b><br /><br />\n"
              if ($foundReceived <= 0) ;
        }

        $fm .= "<b><font color=\"#003366\">sender and reply addresses:</font></b><br />";
        my $mailfrom;
        foreach (keys %fm) {
            $fm .=  "MAIL FROM: $_ ,";
            $mailfrom = $_;
        }
        while ($mail =~ /($HeaderNameRe):($HeaderValueRe)/igos) {
            my $who = $1;
            my $s = $2;

            next if $who !~ /^(from|sender|reply-to|errors-to|list-\w+|ReturnReceipt|Return-Receipt-To|Disposition-Notification-To)$/io;
            $mailfrom = lc($1) if (! $mailfrom && lc($1) eq 'from');
            &headerUnwrap($s);
            while ($s =~ /($EmailAdrRe\@$EmailDomainRe)/go) {
                my $ss = batv_remove_tag(0,$1,'');
                $mailfrom = $ss if $mailfrom eq 'from';
                $fm{lc $ss}=1;
                $fm .=  " $who: $ss <br />  ";
            }
        }
        $fm =~ s/  $/<br \/><br \/>/o;

        $fm .= "<b><font color=\"#003366\">recipient addresses:</font></b><br />";
        foreach (keys %to) {
            $fm .=  "RCPT TO: $_ ,";
            my $newadr = RcptReplace($_,$mailfrom,'RecRepRegex');
            $fm =~ s/,$/(replaced with $newadr),/o if lc($newadr) ne lc $_;
        }
        while ($mail =~ /(?:^|\n)(to|cc|bcc):($HeaderValueRe)/igos) {
            my $who = $1;
            my $s = $2;
            &headerUnwrap($s);
            while ($s =~ /($EmailAdrRe\@$EmailDomainRe)/go) {
                my $ss = batv_remove_tag(0,$1,'');
                $to{lc $ss}=1;
                $fm .=  " $who: $ss ,";
            }
        }
        $fm =~ s/,$/<br \/><br \/>/o;

        $fm .= "<b><font size=\"3\" color=\"#003366\">Feature Matching:</font></b><br /><br />";

        my $mfd;
        my $mfdd;
        while ( $mail =~ /($EmailAdrRe\@$EmailDomainRe)/go ) {
            my $ad    = lc $1;
            next if $ad =~ /\=/;
            my $mf = batv_remove_tag(0,$ad,'');
            $mfd  = $1 if $mf =~ /\@(.*)/;
            $mfdd = $1 if $mf =~ /(\@.*)/;

            next if $fm{$ad};
            $fm{$ad}=1;

            if (matchSL( $mf, 'noProcessing' )) {
                $fm .=
"<b><font color='orange'>&bull;</font> <a href='./#noProcessing'>noProcessing</a></b>: '$slmatch' ($ad)<br />\n";
              }
            if ( $noProcessingDomains && $mf =~ /($NPDRE)/ ) {
                $fm .=
"<b><font color='orange'>&bull;</font> <a href='./#noProcessingDomains'>noProcessingDomains</a></b>: '$1' ($ad)<br />\n";
              }
            if ( matchSL( $mf, 'noProcessingFrom' ) ) {
                $fm .=
"<b><font color='orange'>&bull;</font> <a href='./#noProcessingFrom'>noProcessingFrom</a></b>: '$slmatch' ($ad)<br />\n";
              }
            if ($blackListedDomains && $mf =~ /($BLDRE)/ ) {
                $fm .=
"<b><font color='red'>&bull;</font> <a href='./#blackListedDomains'>blackListedDomains</a></b>: '$1' ($ad)<br />\n";
              }
            if ($weightedAddresses) {
                my ($slmatch,$w) = &HighWeightSL($mf, 'weightedAddresses');
                if ($w) {
                    $fm .=
"<b><font color=red>&bull;</font> <a href='./#weightedAddresses'>weightedAddresses </a></b>: '$slmatch' ($ad) with valence: ${'blValencePB'}[0] - PB value = $w<br />\n";
                  }
             }
            if ($whiteListedDomains && $mf =~ /($WLDRE)/ ) {
                $fm .=
"<b><font color=#66CC66>&bull;</font> <a href='./#whiteListedDomains'>whiteListedDomains</a></b>: '$1' ($ad)<br />\n";
              }

            $fm .= "<b><font color='orange'>&bull;</font> <a href='./lists'>Redlist</a></b>: '$ad'<br />\n"
              if $Redlist{$ad};
            $fm .=
"<b><font color=#66CC66>&bull;</font> <a href='./lists'>Redlisted Domain/ Wildcard</a></b>: '$wildcardUser$mfdd'<br />\n"
              if $Redlist{"$wildcardUser$mfdd"};
            $fm .=
"<b><font color=#66CC66>&bull;</font> <a href='./lists'>Whitelisted WildcardDomain</a></b>: '$wildcardUser$mfdd'<br />\n"
              if &Whitelist("$wildcardUser$mfdd");

            if (&Whitelist($ad)) {
                $fm .= "<b><font color=#66CC66>&bull;</font> <a href='./lists'>Whitelist</a></b>: '$ad'<br />\n";
                foreach my $t (sort keys %to) {
                    if (! &Whitelist($ad,$t)) {
                        $fm .= "<b><font color='red'>&bull;</font> <a href='./lists'>Whitelist removed for $t </a></b>: '$ad'<br />\n";
                    }
                }
            }

            foreach my $t (sort keys %to) {
                $fm .=
"<b><font color='red'>&bull;</font> <a href='./#persblackdb'>on personal Blacklist for $t </a></b>: '$ad'<br />\n"
                    if exists $PersBlack{lc "$t,$ad"};
            }

            $fm .=
"<b><font color=#66CC66>&bull;</font> <a href='./#noURIBL'>No URIBL sender</a></b>: '$mf'<br />\n"
              if matchSL( $mf, 'noURIBL' );
        }

        if ( $preHeaderRe && $text =~ /($preHeaderReRE)/ ) {
            $fm .= "<b><font color='red'>&bull;</font> <a href='./#preHeaderRe'>preHeaderRe</a></b>: '".($1||$2)."'<br />\n";
            $bombsrch = SearchBomb( "preHeaderRe", ($1||$2) );
            $fm .= "<font color='red'>&bull;</font> matching preHeaderRe($incFound): '$bombsrch'<br />\n"
              if $bombsrch;
          }
        if ( $noSPFRe && $text =~ /($noSPFReRE)/ ) {
            $fm .= "<b><font color='green'>&bull;</font> <a href='./#noSPFRe'>No SPF RE</a></b>: '".($1||$2)."'<br />\n";
            $bombsrch = SearchBomb( "noSPFRe", ($1||$2) );
            $fm .= "<font color='green'>&bull;</font> matching noSPFRe($incFound): '$bombsrch'<br />\n"
              if $bombsrch;
          }
        if ( $strictSPFRe && $text =~ /($strictSPFReRE)/ ) {
            $fm .= "<b><font color='green'>&bull;</font> <a href='./#strictSPFRe'>Strict SPF RE</a></b>: '".($1||$2)."'<br />\n";
            $bombsrch = SearchBomb( "strictSPFRe", ($1||$2) );
            $fm .= "<font color='green'>&bull;</font> matching strictSPFRe($incFound): '$bombsrch'<br />\n"
              if $bombsrch;
          }
        if ( $blockstrictSPFRe && $text =~ /($blockstrictSPFReRE)/ ) {
            $fm .= "<b><font color='green'>&bull;</font> <a href='./#blockstrictSPFRe'>Block Strict SPF RE</a></b>: '".($1||$2)."'<br />\n";
            $bombsrch = SearchBomb( "blockstrictSPFRe", ($1||$2) );
            $fm .= "<font color='green'>&bull;</font> matching blockstrictSPFRe($incFound): '$bombsrch'<br />\n"
              if $bombsrch;
          }
        if ( exists $SPFCache{"$ip $emfd"} ) {
            my ( $ct, $status, $result ) = split( ' ', $SPFCache{"$ip $emfd"} );
            my $color = (($status eq 'pass') ? 'green' : 'orange');
            $fm .= "<b><font color='$color'>&bull;</font> $ip is in SPFCache</b>: status=$status with helo=$result<br />\n";
          }
           if ($ip && ($mailfrom || $helo)) {
            my $tmpfh = time;
            $Con{$tmpfh} = {};
            $Con{$tmpfh}->{ip} = $ip;
            $Con{$tmpfh}->{mailfrom} = $mailfrom;
            $Con{$tmpfh}->{helo} = $helo;
            if (SPFok($tmpfh)) {
                $fm .= "<b><font color='green'>&bull;</font> SPF-check returned OK</b> for $ip -&gt; $mailfrom, $helo<br />\n";
            } else {
                $fm .= "<b><font color='red'>&bull;</font> SPF-check returned FAILED</b> for $ip -&gt; $mailfrom, $helo<br />\n";
            }
            delete $Con{$tmpfh};
        }
        if ( $whiteRe && $text =~ /($whiteReRE)/ ) {
            $fm .= "<b><font color='green'>&bull;</font> <a href='./#whiteRe'>White RE</a></b>: '".($1||$2)."'<br />\n";
            $bombsrch = SearchBomb( "whiteRe", ($1||$2) );
            $fm .= "<font color='green'>&bull;</font> matching whiteRe($incFound): '$bombsrch'<br />\n"
              if $bombsrch;
          }
        if ( $redRe && $text =~ /($redReRE)/ ) {
            $fm .= "<b><font color='yellow'>&bull;</font> <a href='./#redRe'>Red RE</a></b>: '".($1||$2)."'<br />\n";
            $bombsrch = SearchBomb( "redRe", ($1||$2) );
            $fm .= "<font color='yellow'>&bull;</font> matching redRe($incFound): '$bombsrch'<br />\n"
              if $bombsrch;
          }
        if ( $npRe && $text =~ /($npReRE)/ ) {
            $fm .= "<b><font color='green'>&bull;</font> <a href='./#npRe'>No Processing RE</a></b>: '".($1||$2)."'<br />\n";
            $bombsrch = SearchBomb( "npRe", ($1||$2) );
            $fm .= "<font color='green'>&bull;</font> matching npRe($incFound): '$bombsrch'<br />\n"
              if $bombsrch;
          }
        if ( $baysSpamLoversRe && $text =~ /($baysSpamLoversReRE)/ ) {
            $fm .=
"<b><font color='green'>&bull;</font> <a href='./#baysSpamLoversRe'>Bayes Spamlover RE</a></b>: '".($1||$2)."'<br />\n";
            $bombsrch = SearchBomb( "baysSpamLoversRe", ($1||$2) );
            $fm .=
"<font color='green'>&bull;</font> matching baysSpamLoversRe($incFound): '$bombsrch'<br />\n"
              if $bombsrch;
          }
        if ( $SpamLoversRe && $text =~ /($SpamLoversReRE)/ ) {
            $fm .= "<b><font color='green'>&bull;</font> <a href='./#SpamLoversRe'>Spamlover RE</a></b>: '".($1||$2)."'<br />\n";
            $bombsrch = SearchBomb( "SpamLoversRe", ($1||$2) );
            $fm .=
              "<font color='green'>&bull;</font> matching SpamLoversRe($incFound): '$bombsrch'<br />\n"
              if $bombsrch;
          }

        if ( $contentOnlyRe && $text =~ /($contentOnlyReRE)/ ) {
            $fm .=
"<b><font color='yellow'>&bull;</font> <a href='./#contentOnlyRe'>Restrict to Content Only RE</a></b>: '".($1||$2)."'<br />\n";
            $bombsrch = SearchBomb( "contentOnlyRe", ($1||$2) );
            $fm .=
              "<font color='yellow'>&bull;</font> matching contentOnlyRe($incFound): '$bombsrch'<br />\n"
              if $bombsrch;
          }
                my $textheader;
        if ($headerLen > -1 ) {
             $textheader = substr($text,0,$headerLen);
        } else {
            $textheader = $text;
        }

        if (  $bombRe && ($bombsrch = SearchBombW( "bombRe", \$text ))) {
            if ( !$DoBombRe ) {
                $fm .=
"<i><font color='red'>&bull;</font> <a href='./#DoBombRe'>bombRe</a> is <b>disabled because DoBombRe is disabled</b></i><br />\n";
              }
            $fm .= "<b><font color='red'>&bull;</font> <a href='./#bombRe'>bombRe</a></b>: '$bombsrch'<br />\n";
            $fm .= "<font color='red'>&bull;</font> matching bombRe($incFound): '$weightMatch'<br />\n";
          }

        if (  $bombDataRe && ($bombsrch = SearchBombW( "bombDataRe", \$text ))) {
            if ( !$DoBombRe ) {
                $fm .=
"<i><font color='red'>&bull;</font> <a href='./#DoBombRe'>bombDataRe</a> is <b>disabled because DoBombRe is disabled</b></i><br />\n";
              }
            $fm .= "<b><font color='red'>&bull;</font> <a href='./#bombDataRe'>bombDataRe</a></b>: '$bombsrch'<br />\n";
            $fm .= "<font color='red'>&bull;</font> matching bombDataRe($incFound): '$weightMatch'<br />\n";
          }

        if (  $bombHeaderRe && ($bombsrch = SearchBombW( "bombHeaderRe", \$textheader ))) {
            if ( !$DoBombHeaderRe ) {
                $fm .=
"<i><font color='red'>&bull;</font> <a href='./#DoBombHeaderRe'>bombHeaderRe</a> is <b>disabled</b></i><br />\n";
              }
            $fm .= "<b><font color='red'>&bull;</font> <a href='./#bombHeaderRe'>bombHeaderRe</a></b>: '$bombsrch'<br />\n";
            $fm .= "<font color='red'>&bull;</font> matching bombHeaderRe($incFound): '$weightMatch'<br />\n";
          }

        if (  $bombSubjectRe && defined $sub && ($bombsrch = SearchBombW( "bombSubjectRe", \$sub))) {
            if ( !$DoBombHeaderRe ) {
                $fm .=
"<i><font color='red'>&bull;</font> <a href='./#DoBombHeaderRe'>bombSubjectRe</a> is <b>disabled</b> because DoBombHeaderRe is disabled</i><br />\n";
              }
            $fm .= "<b><font color='red'>&bull;</font> <a href='./#bombSubjectRe'>bombSubjectRe</a></b>: '$bombsrch'<br />\n";
            $fm .=
              "<font color='red'>&bull;</font> matching bombSubjectRe($incFound): '$weightMatch'<br />\n";

         } elsif (  $bombSubjectRe && $mystatus eq "text" && ($bombsrch = SearchBombW( "bombSubjectRe", \$text))) {
            if ( !$DoBombHeaderRe ) {
                $fm .=
"<i><font color='red'>&bull;</font> <a href='./#DoBombHeaderRe'>bombSubjectRe</a> is <b>disabled</b> because DoBombHeaderRe is disabled</i><br />\n";
              }
            $fm .= "<b><font color='red'>&bull;</font> <a href='./#bombSubjectRe'>bombSubjectRe</a></b>: '$bombsrch'<br />\n";
            $fm .=
              "<font color='red'>&bull;</font> matching bombSubjectRe($incFound): '$weightMatch'<br />\n";
        }

        if (  $bombCharSets && ($bombsrch = SearchBombW( "bombCharSets", \$textheader ))) {
            if ( !$DoBombHeaderRe ) {
                $fm .=
"<i><font color='red'>&bull;</font> <a href='./#DoBombHeaderRe'>bombCharSets</a> is <b>disabled</b> because DoBombHeaderRe is disabled</i><br />\n";
              }
            $fm .=
              "<b><font color='red'>&bull;</font> <a href='./#bombCharSetsRe'>bombCharSetsRe</a></b>: '$bombsrch'<br />\n";
            $fm .= "<font color='red'>&bull;</font> matching bombCharSets($incFound): '$weightMatch'<br />\n";
          }

        if (  $bombSuspiciousRe && ($bombsrch = SearchBombW( "bombSuspiciousRe", \$text ))) {
            $fm .=
"<b><font color='red'>&bull;</font> <a href='./#bombSuspiciousRe'>bombSuspiciousRe</a></b>: '$bombsrch'<br />\n";
            $fm .=
"<font color='red'>&bull;</font> matching bombSuspiciousRe($incFound): '$weightMatch'<br />\n";
          }

        if (  $blackRe && ($bombsrch = SearchBombW( "blackRe", \$text ))) {
            if ( !$DoBlackRe ) {
                $fm .=
"<i><font color='red'>&bull;</font> <a href='./#DoBlackRe'>blackRe</a> is  <b>disabled</b></i><br />\n";
              }
            $fm .= "<b><font color='red'>&bull;</font> <a href='./#blackRe'>blackRe</a></b>: '$bombsrch'<br />\n";
            $fm .= "<font color='red'>&bull;</font> matching blackRe($incFound): '$weightMatch'<br />\n";
          }

        if (  $bombSenderRe && ($bombsrch = SearchBombW( "bombSenderRe", \$textheader )))
        {
            $fm .= "<b><font color='red'>&bull;</font> <a href='./#bombSenderRe'>bombSenderRe</a></b>: '$bombsrch'<br />\n";
            $fm .= "<font color='red'>&bull;</font> matching bombSenderRe($incFound): '$weightMatch'<br />\n";
        }

        my $obfuscatedip;
        my $obfuscateduri;
        my $maximumuniqueuri;
        my $maximumuri;
        if ( !$ValidateURIBL )
        {
            $fm .=
"<i><font color='red'>&bull;</font> <a href='./#ValidateURIBL'>URIBL check</a> is <b>disabled because ValidateURIBL is disabled</b></i><br />\n";
        } else {
            my $tmpfh = time;
            $Con{$tmpfh} = {};
            $Con{$tmpfh}->{mailfrom} = $mailfrom;
            my $color = 'green';
            my $failed = 'OK';
            my $res = &URIBLok($tmpfh,\$text,$ip,'');
            if (! $res) {
                $color = 'red';
                $failed = 'failed';
            }
            $fm .=
"<b><font color='$color'>&bull;</font> <a href='./#ValidateURIBL'>URIBL check</a></b>: '$failed'<br />\n";
            $fm .=
"<font color='$color'>&nbsp;&bull;</font> URIBL result: '$Con{$tmpfh}->{messagereason}'<br />" if $Con{$tmpfh}->{messagereason};

            $obfuscatedip = $Con{$tmpfh}->{obfuscatedip};
            $obfuscateduri= $Con{$tmpfh}->{obfuscateduri};
            $maximumuniqueuri = $Con{$tmpfh}->{maximumuniqueuri};
            $maximumuri = $Con{$tmpfh}->{maximumuri};
            delete $Con{$tmpfh};
        }

        my $cOK;
        ($mail,$cOK) = &clean( substr( $mail, 0, $mBytes ) );
        $mail =~ s/^helo:\s*\r?\nrcpt\s*\r?\n//o;

        if ($helo) {
            $fm .= "<b><font color='red'>&bull;</font> HELO Blacklist</b>: '$helo'</b><br />\n"
              if ( $HeloBlack{ lc $helo } );
            $fm .=
"<b><font color='#66CC66'>&bull;</font> <a href='./#heloBlacklistIgnore'>HELO Blacklist Ignore</a></b>: '$helo'</b><br />\n"
              if ( $heloBlacklistIgnore && $helo =~ /$HBIRE/ );
            if ( !$DoInvalidFormatHelo ) {
                $fm .= "<b><font color='orange'>&bull;</font>invalidHeloRe not activated</b><br />\n";
              }

            if ( $invalidHeloRe && $helo !~ /$validHeloReRE/ && ($bombsrch = SearchBombW( "invalidHeloRe", \$helo )))
            {
                $fm .= "<b><font color='red'>&bull;</font> <a href='./#invalidHeloRe'>invalidHeloRe</a></b>: '$bombsrch'<br />\n";
                $fm .= "<font color='red'>&bull;</font> matching invalidHeloRe($incFound): '$weightMatch'<br />\n";
            }
        }

        if ( exists $PBBlack{$ip} ) {
            my ( $ct, $ut, $pbstatus, $value, $sip, $reason ) = split( ' ', $PBBlack{$ip} );
            push( @t, 0.97 );

            $fm .=
"<b><font color='red'>&bull;</font> $ip is in <a href='./#pbdb'>PB Black</a></b>: score:$value, last event - $reason<br />\n";
          }
        if ( exists $PBWhite{$ip} ) {
            my ( $ct, $ut, $pbstatus, $sip, $reason ) = split( ' ', $PBWhite{$ip} );

            $fm .= "<b><font color=#66CC66>&bull;</font> $ip is in <a href='./#pbdb'>PB White</a></b><br />\n";
          }

        if ( $ret = matchIP( $ip, 'noProcessingIPs', 0, 1 ) ) {
            $fm .=
"<b><font color='green'>&bull;</font> IP $ip is in <a href='./#noProcessingIPs'>noProcessingIPs</a> ($ret)</b><br />\n";
          }
        if ( $ret = matchIP( $ip, 'whiteListedIPs', 0, 1 ) ) {
            $fm .=
"<b><font color='green'>&bull;</font> IP $ip is in <a href='./#whiteListedIPs'>whiteListedIPs</a> ($ret)</b><br />\n";
          }
        if ( $ret = matchIP( $ip, 'noPB', 0, 1 ) ) {
            $fm .=
              "<b><font color='green'>&bull;</font> IP $ip is in <a href='./#noPB'>noPB IPs</a> ($ret)</b><br />\n";
          }

        if ( exists $RBLCache{$ip} ) {
                my ( $ct, $mm, $status, @rbllists ) = split( ' ', $RBLCache{$ip} );
                $mm = '20'.$mm.':00';
                $mm =~ s/\// /o;
                $status = ( $status == 2 ? 'as ok at '.$mm : "as not ok at $mm , listed by @rbllists" );
                $fm .=
                  "<b><font color='red'>&bull;</font> $ip is in RBLCache</b>: inserted $status<br />\n";
        }

        if ( exists $MXACache{$emfd} ) {
            my ($ct,$status) = split(' ',$MXACache{$emfd},2);
            $fm .= "<b><font color='green'>&bull;</font> domain $emfd has valid MXA record</b>: $status<br />\n";
        }
        if ( exists $PTRCache{$ip} ) {
            my ( $ct, $status, $dns ) = split( ' ', $PTRCache{$ip} );
            my %statList = (
                1 => 'no PTR',
                2 => "PTR OK - $dns",
                3 => "PTR NOTOK - $dns"
            );
            my $color = ($status == 2 ? 'green' : 'red');
            $status = $statList{$status};
            $fm .= "<b><font color='$color'>&bull;</font> $ip is in PTRCache</b>: status=$status<br />\n";
          }
        if ( exists $RWLCache{$ip} ) {
            my ( $ct, $status ) = split( ' ', $RWLCache{$ip} );
            my %statList = (
                1 => 'tusted',
                2 => 'trusted but RWLminHits not reached',
                3 => 'trusted and whitelisted',
                4 => 'not listed'
            );
            my $color = ($status == 4 ? 'orange' : 'green');
            $status = $statList{$status};
            $fm .= "<b><font color='$color'>&bull;</font> $ip is in RWLCache</b>: status=$status<br />\n";
          }

        if ( $ret = matchIP( $ip, 'acceptAllMail', 0, 1 ) ) {
            $fm .=
"<b><font color='green'>&bull;</font> IP $ip is in <a href='./#acceptAllMail'>Accept All Mail</a> ($ret)</b><br />\n";
          }
        if ( $ret = matchIP( $ip, 'noBlockingIPs', 0, 1 ) ) {
                $fm .=
"<b><font color='green'>&bull;</font> IP $ip is in <a href='./#noBlockingIPs'>noBlockingIPs</a> ($ret)</b><br />\n";
        }
        if ( $ret = matchIP( $ip, 'ispip', 0, 1 ) ) {
            $fm .=
"<b><font color='green'>&bull;</font> IP $ip is in <a href='./#ispip'>ISP/Secondary MX Servers</a> ($ret)</b><br />\n";
          }

        if ( $ret = matchIP( $ip, 'denySMTPConnectionsFrom', 0, 1 ) ) {
                $fm .=
"<b><font color='red'>&bull;</font> IP $ip is in <a href='./#denySMTPConnectionsFrom'>denySMTPConnectionsFrom</a> ($ret)</b><br />\n";
         }

        if ( $ret = matchIP( $ip, 'denySMTPConnectionsFromAlways', 0, 1 ) ) {
                $fm .=
"<b><font color='red'>&bull;</font> IP $ip is in <a href='./#denySMTPConnectionsFromAlways'>denySMTPConnectionsFromAlways</a>($ret)</b><br />\n";
        }

        if ( my ( $cidr , $ct, $status, $data ) = SBCacheFind($ip) ) {
            my %statList = (
                0 => 'not classified',
                1 => 'black country',
                2 => 'white SenderBase',
                3 => 'changed to black country'
            );
            my $color = 'orange';
            $color = 'red' if $status % 2;
            $color = 'green' if $status == 2;
            $status = $statList{$status};
            $data =~ s/\|/, /og;
            $fm .= "<b><font color='$color'>&bull;</font> $ip is in CountryCache</b>: status=$status, data=$data<br />\n";
        } else {
            my $tmpfh = time;
            $Con{$tmpfh} = {};
            $Con{$tmpfh}->{ip} = $ip;
            $Con{$tmpfh}->{mailfrom} = $mailfrom;
            my %statList = (
                0 => 'not classified',
                1 => 'black country',
                2 => 'white SenderBase',
                3 => 'changed to black country'
            );
            my $res = SenderBaseOK($tmpfh, $ip);
            my $data = $Con{$tmpfh}->{sbdata};
            my $status = $Con{$tmpfh}->{sbstatus};
            my $color = 'orange';
            $color = 'red' if $status % 2;
            $color = 'green' if $status == 2;
            $status = $statList{$status};
            $data =~ s/\|/, /og;
            $fm .= "<b><font color='$color'>&bull;</font> $ip SenderBase</b>: status=$status, data=$data<br />\n" if $data;
            delete $Con{$tmpfh};
        }
        my @t;
        my %got = ();
        my ( $v, $lt, $t, %seen );
        $v = GRIPv( $ip3 ); 
        $fm .= "<b><font color='gray'>&bull;</font> $ip3 has a Griplist value of $v</b><br />\n" if $v;      

            push(@t,0.97) if $foundReceived <= 0;

        $fm =~ s/($IPRe)/my$e=$1;($e!~$IPprivate)?"<a href=\"javascript:void(0);\" title=\"take an action on that IP\" onclick=\"popIPAction('$1');return false;\">$1<\/a>":$e;/goe;
        $fm =~ s/(')?($EmailAdrRe?\@$EmailDomainRe)(')?/"<a href=\"javascript:void(0);\" title=\"take an action on that address\" onclick=\"popAddressAction('".&encHTMLent($2)."');return false;\">".&encHTMLent($1.$2.$3)."<\/a>";/goe;

        $fm .= "<br /><hr><br />";

        while ( $mail =~ /([-\$A-Za-z0-9\'\.!\240-\377]+)/g ) {
            next if length($1) > 20;
            next if length($1) < 2;
            $lt = $t;
            $t  = BayesWordClean($1);
            my $j = "$lt $t";
          next if $seen{$j}++ > 1;
              if ($BayesianStarterDB && $enableStarterDB) {
                  if  ($v = $Spamdb{$j}) {
                      push( @t, $v );
                  } else {
                      push( @t, $v ) if $v = $Starterdb{$j};
                  }
              } else {
                  push( @t, $v ) if $v = $Spamdb{$j};
              }
#            mlog(0,"j = $j, v = $v");
            $got{$j} = $v if $v;
        }
        my $cnt       = 0;
        my $bayestext;
        if (!$mystatus) {
        $bayestext = "<font color='red'>&bull; Bayesian Check is disabled</font>"
          if !$DoBayesian;
        $ba .=
"<b><font size='3' color='#003366'>Bayesian Analysis: $bayestext</font></b><br /><br /><table cellspacing='0' cellpadding='0'>";
        $ba .= "<tr>
  <td style=\"padding-left:5px; padding-right:5px; padding-top:5; padding-bottom:5; text-align:right; font-size:small;\"><b>Bad Words</b></td>
  <td style=\"padding-left:5px; padding-right:5px; padding-top:5; padding-bottom:5; text-align:left; font-size:small; background-color:#F4F4F4\"><b>Bad Prob&nbsp;</b></td>
  <td style=\"padding-left:20px; padding-right:5px; padding-top:5; padding-bottom:5; text-align:right; font-size:small;\"><b>Good Words</b></td>
  <td style=\"padding-left:5px; padding-right:5px; padding-top:5; padding-bottom:5; text-align:left; font-size:small; background-color:#F4F4F4\"><b>Good Prob</b></td>
  </tr>\n";
        foreach (
            sort { abs( $got{$b} - .5 ) <=> abs( $got{$a} - .5 ) }
            keys %got
          ) {
            my $g = sprintf( "%.4f", $got{$_} );
            if ( $g < 0.5 ) {
                $ba .= "<tr>
    <td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; text-align:right; font-size:small;\"></td>
    <td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; text-align:left; font-size:small; background-color:#F4F4F4\"></td>
    <td style=\"padding-left:20px; padding-right:5px; padding-top:0; padding-bottom:0; text-align:right; font-size:small;\">$_</td>
    <td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; text-align:left; font-size:small; background-color:#F4F4F4\">$g</td>
    </tr>\n";
              } else {
                $ba .= "<tr>
    <td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; text-align:right; font-size:small;\">$_</td>
    <td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; text-align:left; font-size:small; background-color:#F4F4F4\">$g</td>
    <td style=\"padding-left:20px; padding-right:5px; padding-top:0; padding-bottom:0; text-align:right; font-size:small;\"></td>
    <td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; text-align:left; font-size:small; background-color:#F4F4F4\"></td>
    </tr>\n";
              }
            last if $cnt++ > 20;
          }
        $ba .= "</td></tr></table>\n";
        @t  = sort { abs( $b - .5 ) <=> abs( $a - .5 ) } @t;
        @t=@t[0..($maxBayesValues - 1)];
        $st = "<br />Totals: ";
        foreach (@t) { $st .= sprintf( "%.4f ", $_ ) if $_; }
        $st .= "\n";
        (my $p1, my $p2, my $c1, $SpamProb, $SpamProbConfidence) = BayesProb(\@t);
        $SpamProbConfidence = 0 if @t < 2 && $t[0] eq '';
        $SpamProb = 0.8 if @t < 2 && $t[0] eq '';
        $st .=
"<br /><hr><br /><b><font size=\"3\" color=\"#003366\">Spam/Ham Probabilities:</font></b><br /><br />\n<table cellspacing=\"0\" cellpadding=\"0\">"
          if $baysConf;
        $st .=
"<br /><hr><br /><b><font size=\"3\" color=\"#003366\">Spam Probability:</font></b><br /><br />\n<table cellspacing=\"0\" cellpadding=\"0\">"
          if !$baysConf;
        $st .= sprintf(
" <tr><td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; text-align:right; font-size:small;\"><b>spamprobability</b>:</td><td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; font-size:small;\">%.4f</td></tr>\n",
            $p1 )
          if $baysConf;
        $st .= sprintf(
" <tr><td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; text-align:right; font-size:small;\"><b>hamprobability</b>:</td><td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; font-size:small;\">%.4f</td></tr>\n",
            $p2 )
          if $baysConf;
        $st .= sprintf(
" <tr><td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; text-align:right; font-size:small;\"><b>combined probability</b>:</td><td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; font-size:small;\">%.4f</td></tr>\n",
            $SpamProb )
          if $baysConf;
        $st .= sprintf(
" <tr><td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; text-align:right; font-size:small;\"><b>probability</b>:</td><td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; font-size:small;\">%.4f</td></tr>\n",
            $SpamProb )
          if !$baysConf;
        $st .= sprintf(
" <tr><td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; text-align:right; font-size:small;\"><b>bayesian confidence</b>:</td><td style=\"padding-left:5px; padding-right:5px; padding-top:0; padding-bottom:0; font-size:small;\">%.4f</td></tr>\n",
            $SpamProbConfidence )
          if $baysConf;
         }
        $st .= " </table><br /></div><br />";
        $mail =~ s/([^\n]{70,84}[^\w\n<\@])/$1\n/g;
        $mail =~ s/\s*\n+/\n/g;
        $mail =~ s/<\/textarea>/\/textarea/ig;

      }

      $mail = $orgmail if $mystatus;
      $mail =~ s/\r//gos;

      my $h1 = $WebIP{$ActWebSess}->{lng}->{'msg500060'} || $lngmsg{'msg500060'};
      my $h2 = $WebIP{$ActWebSess}->{lng}->{'msg500061'} || $lngmsg{'msg500061'};
      my $h3 = $WebIP{$ActWebSess}->{lng}->{'msg500062'} || $lngmsg{'msg500062'};
      my $h4 = $WebIP{$ActWebSess}->{lng}->{'msg500063'} || $lngmsg{'msg500063'};

    <<EOT;
$headerHTTP
$headerDTDTransitional
$headers
<script type="text/javascript">
//<![CDATA[
function getInput() { return document.getElementById("mail").value; }
function setOutput(string) {document.getElementById("mail").value=string; }

function replaceIt() { try {
var findText = document.getElementById("find").value;
var replaceText = document.getElementById("replace").value;
setOutput(getInput().replace(eval("/"+findText+"/ig"), replaceText));
} catch(e){}}

//-->
//]]>
</script>
<div class="content">
<h2>ASSP Mail Analyzer</h2>
<div class="note"><small>$h1</small>
</div>

<p class="note" ><small>$h3
</small></p>
<br />
$fm$ba$st
<form action="" method="post">
    <table class="textBox">
        <tr>
            <td >
             <span style="float: left">Replace: <input type="text" id="find" size="20" /> with <input type="text" id="replace" size="20" /> <input type="button" value="Replace" onclick="replaceIt();" /></span>
            </td >
        </tr>
        <tr>
            <td class="noBorder" align="center">$h2<br />
            <textarea id="mail" name="mail" rows="10" cols="60" wrap="off">$mail</textarea>
            </td>
        </tr>
        <tr>
            <td class="noBorder" align="center"><input type="submit" name="B1" value=" Analyze " /></td>
        </tr>
    </table>
</form>
<br />
<p class="note" ><small>
<div class="textbox">
$h4</small></p>

</div>
</div>

$footers
<form name="ASSPconfig" id="ASSPconfig" action="" method="post">
  <input name="theButtonLogout" type="hidden" value="" />
</form>
</body></html>
EOT
}

sub cleanincFound {
    my $i = shift;
    $i =~ s/\<[^\>]+\>//go;
    return $i;
}

sub AnalyzeText {
    my $fh = shift;
    my $this = $Con{$fh};
    my @t;
    my $bombsrch;
    my @sips;

    my ( $ba, $st, $fm, %fm, %to, %wl, $ip, $ipnet, $helo, $text, $header, $received, $emfd );
    my $foundReceived = 0;
    my $mail = $this->{header};
    $mail =~ s/^.*?\n[\r\n\s]+//so;
    $mail =~ s/\r?\n/\r\n/gos;
    my $hl = getheaderLength(\$mail);
    my $mBytes = $MaxBytes ? $MaxBytes + $hl : 10000 + $hl;
    my $lm = length($mail);
    $mail = substr( $mail, 0, $mBytes );
    $fm = "analyze is restricted to a maximum length of $mBytes bytes\n" if $lm > $mBytes;
    if ($mail =~ /X-Assp-ID: (.+)/io) {
        $fm .= "ASSP-ID: $1\n";
    }
    if ($mail =~ s/X-Assp-Envelope-From:\s*($HeaderValueRe)//ios) {
        my $s = $1;
        &headerUnwrap($s);
        if ($s =~ /($EmailAdrRe\@$EmailDomainRe)/io) {
            $s = batv_remove_tag(0,lc $1,'');
            $fm{$s}=1;
            ($emfd) = $s =~ /\@([^@]*)/o;
        }
    }
    if ($mail =~ s/X-Assp-Recipient:\s*($HeaderValueRe)//ios) {
        my $s = $1;
        &headerUnwrap($s);
        if ($s =~ /($EmailAdrRe\@$EmailDomainRe)/io) {
            $s = batv_remove_tag(0,lc $1,'');
            $to{$s}=1;
        }
    }
    if (! scalar keys %to && $mail =~ s/X-Assp-Envelope-For:\s*($HeaderValueRe)//ios) {
        my $s = $1;
        &headerUnwrap($s);
        if ($s =~ /($EmailAdrRe\@$EmailDomainRe)/io) {
            $s = batv_remove_tag(0,$1,'');
            $to{lc $s}=1;
        }
    }
    my $bod  = $mail;
    my $sub = undef;
    my $wildcardUser = lc $wildcardUser;
    my $headerLen = index($mail,"\015\012\015\012");
    if ($headerLen > -1) {
        my $fhh;
        do {
           $fhh = rand(1000000);
        } while exists $Con{$fhh};
        $Con{$fhh}->{header} = $mail;
        $Con{$fhh}->{headerpassed} = 1;
        &makeSubject($fhh);
        $sub = $Con{$fhh}->{subject3} if defined $Con{$fhh}->{subject3};

        if (defined $sub) {
            headerUnwrap($sub);
            $sub =~ s/^fwd.\s//gio;
            $sub =~ s/^fw.\s//gio;
            $sub =~ s/^aw.\s//gio;
            $sub =~ s/^re.\s//gio;
            # remove the spam subject header addition if present
            my $spamsub = $spamSubject;
            if ($spamsub) {
                $spamsub =~ s/(\W)/\\$1/go;
                $sub     =~ s/$spamsub//gi;
            }
            $sub =~ s/\r//o;
        }
        delete $Con{$fhh};

    }
    if ($mail =~ /\nsubject: *($HeaderValueRe)/iso) {
            $sub = substr($1,0,$maxSubjectLength);
            headerUnwrap($sub);
            $sub =~ s/^fwd.\s//gio;
            $sub =~ s/^fw.\s//gio;
            $sub =~ s/^aw.\s//gio;
            $sub =~ s/^re.\s//gio;
            # remove the spam subject header addition if present
            my $spamsub = $spamSubject;
            if ($spamsub) {
                $spamsub =~ s/(\W)/\\$1/go;
                $sub     =~ s/$spamsub//gi;
            }
        }
    $header = "Subject: " . $sub . "\n" if $sub;

    $header .= $1 if $bod =~ /(X-Assp-ID: .*)/io;

    $header .= $1 if $bod =~ /(X-Assp-Tag: .*)/io;

    $header .= $1 if $bod =~ /(X-Assp-Envelope-From: .*)/io;

    $header .= $1 if $bod =~ /(X-Assp-Intended-For: .*)/io;

    $bod =~ s/X-Assp-Spam-Prob: .*\n//gio;
    if ( $bod =~ /\nReceived: /o ) {
        $bod =~ s/^.*?\nReceived: /Received: /so;
    } else {
        $bod =~ s/^.*?\n((\w[^\n]*\n)*Subject:)/$1/sio;
        $bod =~ s/\n> /\n/go;
    }
    $fm .= "removed all local X-ASSP- header lines for analysis\n"
        if ($mail =~ s/x-assp-[^()]+?:\s*$HeaderValueRe//gios);
    if ($mail) {
        my $name = $myName;
        $name =~ s/(\W)/\\$1/go;
        if ( $mail =~ /(?:^[\s\r\n]*|\r?\n)\s*ip\s*=\s*(\d+\.\d+\.\d+\.\d+|[0-9a-f:]+)/ios ) {
            $ip = $1;
        } else {

            while ( $mail =~ /Received: from.*?\(\[([0-9\.]+|[0-9a-f:]+).*?helo=(.{0,64})\)(?:\s+by\s+$myName)?\s+with/isgo ) {
                $ip = ipv6expand(ipv6TOipv4($1));
                $helo = $2;
                $foundReceived = -1;
            }
        }

        $fm .= "Connecting IP: $ip\n" if $ip;
        my $conIP = $ip;
        $ipnet = &ipNetwork($ip, 1);
        if ( $mail =~ /(?:^[\s\r\n]*|\r?\n)\s*helo\s*=\s*([^\r\n]+)/ios ) {
            $helo = $1;
        }
        $fm .= "Connecting HELO: $helo\n\n" if $helo;
        if ( $mail =~ /(?:^[\s\r\n]*|\r?\n)\s*text\s*=\s*(.+)/ios ) {
            $text = $1;
        } else {
            $text = $mail;
        }
                if ($ispHostnames) {
            my $ispHost;
            while ( $mail =~ /(Received:\s+from\s+(?:([^\s]+)\s)?(?:.+?)($IPRe)(.{1,80})by.{1,20}($ispHostnamesRE))/gis)
            {
                $helo     = $2;
                $received = $1;
                $ispHost = $4;
                $ip = ipv6expand(ipv6TOipv4($3));
                $ipnet = &ipNetwork($ip, 1);
                $foundReceived = 1;

            }
            $fm =~ s/(Connecting IP: [^\n]+)/$1 is an ISPIP/o;
            $fm =~ s/(Connecting HELO: [^\n]+)/$1 is HELO from ISP-host: $ispHost/o;
            $fm .= "\nISP/Secondary Header:'$received'\n"    if $received;
            $fm .= "Switched to ISP/Secondary IP: '$ip'\n\n" if $received;
        }
        if ($foundReceived <= 0) {
            $foundReceived += () = $mail =~ /(Received: from )/isgo;
            $fm .= "no foreign received header line found\n\n"
              if ($foundReceived <= 0) ;
        }

        $fm .= "general hints:\n\n$fm\n\n" if $fm;
        $fm .= "sender and reply addresses:\n";
        my $mailfrom;
        foreach (keys %fm) {
            $fm .=  "MAIL FROM: $_ ,";
            $mailfrom = $_;
        }
        while ($mail =~ /(?:^|\n)(from|sender|reply-to|errors-to|list-\w+|ReturnReceipt|Return-Receipt-To|Disposition-Notification-To):($HeaderValueRe)/igos) {
            my $who = $1;
            my $s = $2;
            &headerUnwrap($s);
            while ($s =~ /($EmailAdrRe\@$EmailDomainRe)/go) {
                my $ss = batv_remove_tag(0,$1,'');
                $fm{lc $ss}=1;
                $fm .=  " $who: $ss ,";
            }
        }
        $fm =~ s/,$/\n\n/o;

        $fm .= "recipient addresses:\n";
        foreach (keys %to) {
            $fm .=  "RCPT TO: $_ ,";
            my $newadr = RcptReplace($_,$mailfrom,'RecRepRegex');
            $fm =~ s/,$/(replaced with $newadr),/o if lc($newadr) ne lc $_;
        }
        while ($mail =~ /(?:^|\n)(to|cc|bcc):($HeaderValueRe)/igos) {
            my $who = $1;
            my $s = $2;
            &headerUnwrap($s);
            while ($s =~ /($EmailAdrRe\@$EmailDomainRe)/go) {
                my $ss = batv_remove_tag(0,$1,'');
                $to{lc $ss}=1;
                $fm .=  " $who: $ss ,";
            }
        }
        $fm =~ s/,$/\n\n/o;

        $fm .= "Feature Matching:\n\n";
        my $mfd;
        my $mfdd;
        while ( $mail =~ /($EmailAdrRe\@$EmailDomainRe)/go ) {
            my $ad    = lc $1;
            my $mf = batv_remove_tag(0,$ad,'');
            $mfd  = $1 if $mf =~ /\@(.*)/;
            $mfdd = $1 if $mf =~ /(\@.*)/;
            next if $fm{$ad}++;

            if (matchSL( $mf, 'noProcessing' )) {
                $fm .= "noProcessing: '$slmatch'\n";
              }
            if ( $noProcessingDomains && $mf =~ /($NPDRE)/ ) {
                $fm .= "noProcessingDomains: '$1'\n";
              }
            if ( matchSL( $mf, 'noProcessingFrom' ) ) {
                $fm .= "noProcessingFrom Address: '$slmatch'\n";
              }
            if ($blackListedDomains && $mf =~ /($BLDRE)/ ) {
                $fm .= "blackListedDomains: '$1'\n";
              }
            if ($whiteListedDomains && $mf =~ /($WLDRE)/ ) {
                $fm .= "whiteListedDomains: '$1'\n";
              }

            $fm .= "Redlist: '$ad'\n"
              if $Redlist{$ad};
            $fm .= "Redlisted Domain/ Wildcard: '$wildcardUser$mfdd'\n"
              if $Redlist{"$wildcardUser$mfdd"};
            $fm .= "Whitelisted WildcardDomain: '$wildcardUser$mfdd'\n"
              if &Whitelist("$wildcardUser$mfdd");

            if (&Whitelist($ad)) {
                $fm .= "Whitelist: '$ad'\n";
                foreach my $t (sort keys %to) {
                    if (! &Whitelist($ad,$t)) {
                        $fm .= "Whitelist removed for $t: '$ad'\n";
                    }
                }
            }

            foreach my $t (sort keys %to) {
                $fm .= "on personal Blacklist for $t: '$ad'\n"
                    if exists $PersBlack{lc "$t,$ad"};
            }

            $fm .= "No URIBL sender: '$mf'\n"
              if matchSL( $mf, 'noURIBL' );
        }
    }

    if ( $preHeaderRe && $text =~ /($preHeaderReRE)/ ) {

        $fm .= "preHeaderRe: '".($1||$2)."\n";
        $bombsrch = SearchBomb( "preHeaderRe", ($1||$2) );
        $fm .= " matching preHeaderRe(" . &cleanincFound($incFound) . "): '$bombsrch'\n"
          if $bombsrch;
    }
    if ( $noSPFRe && $text =~ /($noSPFReRE)/ ) {

        $fm .= "No SPF RE: '".($1||$2)."'\n";
        $bombsrch = SearchBomb( "noSPFRe", ($1||$2) );
        $fm .= " matching noSPFRe(" . &cleanincFound($incFound) . "): '$bombsrch'\n"
          if $bombsrch;
    }
    if ( $strictSPFRe && $text =~ /($strictSPFReRE)/ ) {

        $fm .= "Strict SPF RE: '".($1||$2)."'\n";
        $bombsrch = SearchBomb( "strictSPFRe", ($1||$2) );
        $fm .= " matching strictSPFRe(" . &cleanincFound($incFound) . "): '$bombsrch'\n"
          if $bombsrch;
    }
    if ( $blockstrictSPFRe && $text =~ /($blockstrictSPFReRE)/ ) {

        $fm .= "Block Strict SPF RE: '".($1||$2)."'\n";
        $bombsrch = SearchBomb( "blockstrictSPFRe", ($1||$2) );
        $fm .= " matching blockstrictSPFRe(" . &cleanincFound($incFound) . "): '$bombsrch'\n"
          if $bombsrch;
    }
    if ( $whiteRe && $text =~ /($whiteReRE)/ ) {

        $fm .= "White RE: '".($1||$2)."'\n";
        $bombsrch = SearchBomb( "whiteRe", ($1||$2) );
        $fm .= " matching whiteRe(" . &cleanincFound($incFound) . "): '$bombsrch'\n"
          if $bombsrch;
    }
    if ( $redRe && $text =~ /($redReRE)/ ) {

        $fm .= "Red RE: '".($1||$2)."'\n";
        $bombsrch = SearchBomb( "redRe", ($1||$2) );
        $fm .= " matching redRe(" . &cleanincFound($incFound) . "): '$bombsrch'\n" if $bombsrch;
    }
    if ( $npRe && $text =~ /($npReRE)/ ) {

        $fm .= "No Processing RE: '".($1||$2)."'\n";
        $bombsrch = SearchBomb( "npRe", ($1||$2) );
        $fm .= " matching npRe(" . &cleanincFound($incFound) . "): '$bombsrch'\n" if $bombsrch;
    }
    if (   $baysSpamLoversRe
        && $text =~ /($baysSpamLoversReRE)/ )
    {

        $fm .= "Bayes Spamlover RE: '".($1||$2)."'\n";
        $bombsrch = SearchBomb( "baysSpamLoversRe", ($1||$2) );

        $fm .= " matching baysSpamLoversRe(" . &cleanincFound($incFound) . "): '$bombsrch'\n"
          if $bombsrch;
    }
    if ( $SpamLoversRe && $text =~ /($SpamLoversReRE)/ ) {

        $fm .= "SpamLoversRe: '".($1||$2)."'\n";
        $bombsrch = SearchBomb( "SpamLoversRe", ($1||$2) );
        $fm .= " matching SpamLoversRe(" . &cleanincFound($incFound) . "): '$bombsrch'\n"
          if $bombsrch;
    }

    if (   $contentOnlyRe
        && $text =~ /($contentOnlyReRE)/ )
    {

        $fm .= "Restrict to Content Only RE<: '".($1||$2)."'\n";
        $bombsrch = SearchBomb( "contentOnlyRe", ($1||$2) );
        $fm .= " matching contentOnlyRe(" . &cleanincFound($incFound) . "): '$bombsrch'\n"
          if $bombsrch;
    }

    my $textheader;
    if ($headerLen > -1 ) {
        $textheader = substr($text,0,$headerLen);
    } else {
        $textheader = $text;
    }

    if ( $bombRe && ($bombsrch = SearchBombW( "bombRe", \$text ))) {
        if ( !$DoBombRe ) {
            $fm .= "BombRe is disabled because DoBombRe is disabled\n";
        }
        $fm .= "BombRe: '$bombsrch'\n";
        $fm .= " matching bombRe(" . &cleanincFound($incFound) . "): '$weightMatch'\n";
    }
    if (   $bombDataRe
        && ($bombsrch = SearchBombW( "bombDataRe", \$text )) )
    {
        if ( !$DoBombRe ) {
            $fm .= "BombDataRE is disabled because DoBombRe is disabled\n";
        }
        $fm .= "BombData RE: '$bombsrch'\n";
        $fm .= " matching bombDataRe(" . &cleanincFound($incFound) . "): '$weightMatch'\n";
    }
    if (   $bombHeaderRe
        && ($bombsrch = SearchBombW( "bombHeaderRe", \$textheader )) )
    {
        if ( !$DoBombHeaderRe ) {
            $fm .= "BombHeaderRE is disabled\n";
        }
        $fm .= "BombHeader RE: '$bombsrch'\n";
        $fm .= " matching bombHeaderRe(" . &cleanincFound($incFound) . "): '$weightMatch'\n";
    }
    if (   $bombSubjectRe
        && defined $sub
        && ($bombsrch = SearchBombW( "bombSubjectRe", \$sub )))
    {
        if ( !$DoBombHeaderRe ) {
            $fm .= "BombSubjectRE is disabled because DoBombHeaderRe is disabled\n";
        }
        $fm .= "BombSubject RE: '$bombsrch'\n";
        $fm .= " matching bombSubjectRe(" . &cleanincFound($incFound) . "): '$weightMatch'\n";
    }
    if (   $bombCharSets
        && ($bombsrch = SearchBombW( "bombCharSets", \$textheader )))
    {
        if ( !$DoBombHeaderRe ) {
            $fm .= "BombCharsets is disabled because DoBombHeaderRe is disabled\n";
        }
        $fm .= "BombCharsets: '$bombsrch'\n";
        $fm .= " matching bombCharSets(" . &cleanincFound($incFound) . "): '$weightMatch'\n";
    }
    if (   $bombSuspiciousRe
        && ($bombsrch = SearchBombW( "bombSuspiciousRe", \$text )))
    {
        $fm .= "BombSuspiciousRe RE: '$bombsrch'\n";
        $fm .= " matching bombSuspiciousRe(" . &cleanincFound($incFound) . "): '$weightMatch'\n";
    }

    if ( $blackRe && ($bombsrch = SearchBombW( "blackRe", \$text ))) {
        if ( !$DoBlackRe ) {
            $fm .= "BlackRE is disabled\n";
        }
        $fm .= "Black RE: '$bombsrch'\n";
        $fm .= " matching blackRe(" . &cleanincFound($incFound) . "): '$weightMatch'\n";
    }

    if (   $bombSenderRe
        && ($bombsrch = SearchBombW( "bombSenderRe", \$textheader )))
    {
        $fm .= "BombSender RE: '$bombsrch'\n";
        $fm .= " matching bombSenderRe(" . &cleanincFound($incFound) . "): '$weightMatch'\n";
    }
    my $cOK;
    ($mail,$cOK) = &clean( $mail );
    $mail =~ s/^helo:\s*\nrcpt\s*\n//o;

    if ($helo) {
        $fm .= "HELO Blacklist: '$helo'\n" if ( $HeloBlack{ lc $helo } );
        $fm .= "HELO Blacklist Ignore: '$helo'\n"
          if ( $heloBlacklistIgnore && $helo =~ /$HBIRE/ );

            if (   $invalidHeloRe && $helo !~ /$validHeloReRE/
            && ($bombsrch = SearchBombW( "invalidHeloRe", \$helo )))
        {
            $fm .= "Invalid Format of HELO: '$bombsrch'\n";
            $fm .= " matching invalidHeloRe(" . &cleanincFound($incFound) . "): '$weightMatch'\n";
        }
    }

    if ( exists $PBBlack{$ip} ) {
        my ( $ct, $ut, $pbstatus, $value, $sip, $reason ) =
          split( ' ', $PBBlack{$ip} );
        push( @t, 0.97 );

        $fm .= "IP $ip is in PB Black: score:$value, last event - $reason\n";
    }
    if ( exists $PBWhite{$ip} ) {
        my ( $ct, $ut, $pbstatus, $sip, $reason ) = split( ' ', $PBWhite{$ip} );
        push( @t, 0.03 );

        $fm .= "IP $ip is in PB White\n";
    }
    my $ret;
    if ( $ret = matchIP( $ip, 'noProcessingIPs', 0, 1 ) ) {
        $fm .= "IP $ip is in noProcessing IPs ($ret)\n";
    }
    if ( $ret = matchIP( $ip, 'whiteListedIPs', 0, 1 ) ) {
        $fm .= "IP $ip is in whiteListed IPs ($ret)\n";
    }
    if ( $ret = matchIP( $ip, 'noPB', 0, 1 ) ) {
        $fm .= "IP $ip is in noPB IPs ($ret)\n";
    }
    foreach my $iip (@sips) {
        if ( exists $RBLCache{$ip} ) {
            my ( $ct, $mm, $status, @rbllists ) = split( ' ', $RBLCache{$iip} );
            $mm = '20'.$mm.':00';
            $mm =~ s/\// /o;
            $status = ( $status == 2 ? 'as ok at '.$mm : "as not ok at $mm , listed by @rbllists" );
            $fm .= "IP $iip is in DNSBLCache: inserted $status\n";
        }
    }
    if ( exists $SPFCache{"$ip $emfd"} ) {
        my ( $ct, $result, $domain ) = split( ' ', $SPFCache{"$ip emfd"} );
        $fm .= "IP $ip is in SPFCache: $result, $domain\n";
    }
    if ( exists $MXACache{$emfd} ) {
        my ($ct,$status) = split(' ',$MXACache{$emfd},2);
        $fm .= "domain $emfd has valid MXA record: $status\n";
    }

    if ( exists $PTRCache{$ip} ) {
        my ( $ct, $status, $dns ) = split( ' ', $PTRCache{$ip} );
        my %statList = (
            1 => 'no PTR',
            2 => "PTR OK - $dns",
            3 => "PTR NOTOK - $dns"
        );
        $status = $statList{$status};
        $fm .= "IP $ip is in PTRCache: status=$status\n";
    }
    if ( exists $RWLCache{$ip} ) {
        my ( $ct, $status) = split( ' ', $RWLCache{$ip} );
        my %statList = (
            1 => 'tusted',
            2 => 'trusted but RWLminHits not reached',
            3 => 'trusted and whitelisted',
            4 => 'not listed'
        );
        $status = $statList{$status};
        $fm .= "IP $ip is in RWLCache: $status \n";
    }
    if ( SBCacheFind($ip) ) {
        my ( $ct, $status, $data  ) = split( '!', $SBCache{$ip} );
;
        my %statList = (
            0 => 'not classified',
            1 => 'SenderBase',
            2 => 'white SenderBase',

        );
        $status = $statList{$status};
        $data =~ s/\|/,/og;
        $fm .= "IP $ip is in CountryCache: status=$status, data=$data\n" if $data;
    }
    if ( $ret = matchIP( $ip, 'acceptAllMail', 0, 1 ) ) {
        $fm .= "IP $ip is in acceptAllMail ($ret)\n";
    }
    if ( $ret = matchIP( $ip, 'noBlockingIPs', 0, 1 ) ) {
        $fm .= "IP $ip is in noBlockingIPs ($ret)\n";
                $fm .=
"<b><font color='green'>&bull;</font> IP $ip is in <a href='./#noBlockingIPs'>noBlockingIPs</a> ($ret)</b><br />\n";
        }
    if ( $ret = matchIP( $ip, 'ispip', 0, 1 ) ) {
        $fm .= "IP $ip is in ISP/Secondary MX Servers ($ret)\n";
    }
    foreach my $iip (@sips) {
        if ( $ret = matchIP( $iip, 'denySMTPConnectionsFrom', 0, 1 ) ) {
            $fm .= "IP $iip is in denySMTPConnectionsFrom ($ret)\n";
        }
    }
    foreach my $iip (@sips) {
        if ( $ret = matchIP( $iip, 'denySMTPConnectionsFromAlways', 0, 1 ) ) {
            $fm .= "IP $iip is in denySMTPConnectionsFromAlways ($ret)\n";
        }
    }
    @t = ();
    my %got = ();
    my ( $v, $lt, $t, %seen );

       $ipnet = &ipNetwork($ip, 1);
      $ipnet =~ s/\.0$//o;
    $v = GRIPv( $ipnet );    

    $fm .= "$ipnet has a Griplist value of $v: \n" if $v;      
    $fm .= "\n";

    while ( $mail =~ /([$BayesCont]{2,})/go) {
            next if length($1) > 20;
            next if length($1) < 2;
            $lt = $t;
            $t  = BayesWordClean($1);
            my $j = "$lt $t";
          next if $seen{$j}++ > 1;
              if ($BayesianStarterDB && $enableStarterDB) {
                  if  ($v = $Spamdb{$j}) {
                      push( @t, $v );
                  } else {
                      push( @t, $v ) if $v = $Starterdb{$j};
                  }
              } else {
                  push( @t, $v ) if $v = $Spamdb{$j};
              }
          $got{$j} = $v if $v;
    }
    my $cnt = 0;
    my $bayestext; $bayestext = "Bayesian Check is disabled" if !$DoBayesian;
    $ba .= "Bayesian Analysis: $bayestext\n";
    $ba .= "Bad Words:Bad Prob\t\t\tGood Words:Good Prob\n";
    foreach ( sort { abs( $got{$main::b} - .5 ) <=> abs( $got{$main::a} - .5 ) } keys %got )
    {
        my $g = sprintf( "%.4f", $got{$_} );
        if ( $g < 0.5 ) {

            $ba .= "\t\t\t\t\t\t$_:$g\n";
        } else {
            $ba .= "$_:$g\n";
        }
        last if $cnt++ > 20;
    }

    $ba .= "\n";
    @t  = sort { abs( $main::b - .5 ) <=> abs( $main::a - .5 ) } @t;
    @t=@t[0..($maxBayesValues - 1)];
    $st = "Totals: ";
    foreach (@t) { $st .= sprintf( "%.4f ", $_ ) if $_; }
    $st .= "\n";
    my $p1 = 1;
    my $p2 = 1;
    foreach my $p (@t) {
        if ($p) { $p1 *= $p; $p2 *= ( 1 - $p ); }
    }
    $SpamProb = $p1 / ( $p1 + $p2 );
    $this->{spamconf} = abs( $p1 - $p2 );
    $st .= "\n\nSpam/Ham Probabilities:\n";
    $st .= "\n\nSpam Probability:\n" if !$baysConf;
    $st .= sprintf( " spamprobability: %.8f\n", $p1 ) if $baysConf;
    $st .= sprintf( " hamprobability: %.8f\n", $p2 ) if $baysConf;
    $st .= sprintf( " combined probability %.8f\n", $SpamProb ) if $baysConf;
    $st .= sprintf( " probability %.4f\n", $SpamProb ) if !$baysConf;
    $st .= sprintf( " bayesian confidence %.8f\n", $this->{spamconf} )
      if $baysConf;

    $st .= " \n";
    $this->{report} .= "$header$fm$ba$st$bod";
    $this->{report} =~ s{([\x7F-\xFF])}{sprintf("=%02X", ord($1))}eog;
    return $sub;
}

sub needEs {
    my ( $count, $text, $es ) = @_;
    return $count . $text . ( $count == 1 ? '' : $es );
}

sub encodeHTMLEntities {
 my $s=shift;
 $s=~s/\&/\&amp;/gso;
 $s=~s/\</\&lt;/gso;
 $s=~s/\>/\&gt;/gso;
 $s=~s/\"/\&quot;/gso;
 return $s;
}

sub decodeHTMLEntities {
 my $s=shift;
 $s=~s/\&quot;?/\"/giso;
 $s=~s/\&gt;?/\>/giso;
 $s=~s/\&lt;?/\</giso;
 $s=~s/\&amp;?/\&/giso;
 return $s;
}

sub encHTMLent {
    my $sh = shift;
    my $s = ref $sh ? $$sh : $sh;
    my $ret;
    eval{$ret = ($s ? &HTML::Entities::encode($s) : '');};
    if ($@) { # do what we can if HTML::Entities failes
         mlog(0,"warning: an error occured in encoding HTML-Entities - $@");
         $ret = encodeHTMLEntities($s);
    }
    return $ret ? $ret : $$s;
}

sub decHTMLent {
    my $sh = shift;
    my $s = ref $sh ? $$sh : $sh;
    my $ret;

    $s =~ s/(?:\&nbsp|[%=]a0);?/ /gosi;  # decode &nbsp; to space not to \160
    $s =~ s/(?:\&shy|[%=]ad);?/-/gosi;   # decode &shy; to '-' not to \173

    $s =~ s/\&\#(\d+);?/decHTMLentHD($1)/geo;
    $s =~ s/\&\#x([a-f0-9]+);?/decHTMLentHD($1,'h')/geio;
    $s =~ s/([^\\])?\\(\d{1,3});?/$1.decHTMLentHD($2,'o')/geio;
    $s =~ s/([^\\])?[%=]([a-f0-9]{2});?/$1.chr(hex($2))/gieo;

    local $@ = undef;
    eval{$ret = &HTML::Entities::decode($s);} if $s;
    if ($@) { # do what we can if HTML::Entities fails
         mlog(0,"warning: an error occured in decoding HTML-Entities - $@");
         $ret = decodeHTMLEntities($s);
    }
    return $ret ? $ret : $s;
}

sub decHTMLentHD {
    my ($s, $how) = @_;
    $s = hex($s) if $how eq 'h';
    $s = oct($s) if $how eq 'o';
    $s = chr($s);
    use bytes;
    $s =~ s/^(?:\xA1\x43|\xA1\x44|\xA1\x4F|\xE3\x80\x82|\xEF\xBC\x8E|\xEF\xB9\x92|\xDB\x94)$/./go;  #Big5 Chinese language character set (.)
    $s =~ s/^\xA0$/ /gosi;  # decode to space not to \160
    $s =~ s/^\xAD$/-/gosi;  # decode to '-' not to \173
    no bytes;
    return $s;
}

sub normHTML {
    my $s = shift;
    $s =~ s{([^a-zA-Z0-9])}{sprintf("%%%02X", ord($1))}eog;
    return $s;
}
sub ConfigMaillog {
 my $stime = time;
 my $maxsearchtime = time + 60;
 my $pat=$qs{search};
 my $matches=0;
 my $currWrap;
 alarm 0;
 if (exists $qs{wrap}) {
    $currWrap = $qs{wrap};

 } elsif ( ! $currWrap) {
     $currWrap = 0 if !$MaillogTailWrap;
    $currWrap = 2 if $MaillogTailWrap;
 }

 $AdminUsersRight{"$WebIP{$ActWebSess}->{user}.user.wrap"} = $currWrap if $WebIP{$ActWebSess}->{user} ne 'root';
 &niceConfig();

 my $colorLines;
 $colorLines = 1 if $MaillogTailColorLine;
 if (exists $qs{color}) {
    $colorLines = $qs{color};

 } elsif ( ! $colorLines) {
    $colorLines = 0;
 }
 $colorLines = 1 unless $colorLines;
 $AdminUsersRight{"$WebIP{$ActWebSess}->{user}.user.color"} = $colorLines if $WebIP{$ActWebSess}->{user} ne 'root';

 my $order;
 if (exists $qs{order}) {
    $order = $qs{order};
 } elsif ($MaillogTailOrder) {
    $order = $MaillogTailOrder;
 } elsif ( ! $order) {
    $order = 0;
 }
 $order = 0 unless $order;
 $AdminUsersRight{"$WebIP{$ActWebSess}->{user}.user.order"} = $order if $WebIP{$ActWebSess}->{user} ne 'root';

 my $savTailByte = $MaillogTailBytes;
 my $currTailByte;
 ($currTailByte) = $1 if $qs{tailbyte}=~/(\d+)/;
 $currTailByte = $MaillogTailBytes if ($MaillogTailBytes>0 && (! $currTailByte || $currTailByte<160));
 $currTailByte = 2000 unless $currTailByte;
 $MaillogTailBytes = $currTailByte;
 $AdminUsersRight{"$WebIP{$ActWebSess}->{user}.user.TailByte"} = $currTailByte if $WebIP{$ActWebSess}->{user} ne 'root';

 my $orgpat = $pat;
 my $filesonly=$qs{filesonly};
 my $autoJS = '';
 my $autoButton = 'Auto';
 my $CMheaders = \$headers;
 my $content = 'class="content"';
 my $logstyle = '';
 my $display = '';
 $pat = $qs{search} = '' if $qs{autorefresh} eq 'Stop';
 if ($qs{autorefresh} eq 'Auto') {
     $pat = '';
     $qs{filesonly}= $filesonly = '';
     $qs{nohighlight} = 1;
     $autoButton = 'Stop';
     $CMheaders = '';
     $display = 'style="display:none"';
     $content = 'class="content" style="margin: 0 0 0 0;"';
     $logstyle = 'style="border-width: 4px 4px 4px 4px; border-color: #6699cc; border-style: solid;"';

     $autoJS = '
<script type="text/javascript">
 Timer=setTimeout("newTimer();",'. $refreshWait .'000);
 var Run = 1;
 function noop () {}
 function tStart () {
    Run = 1;
 }
 function tStop () {
    Run = 0;
    Timer=setTimeout("noop();", 1000);
 }
 function newTimer() {
   if (Run == 1) {location.reload(true)};
   Timer=setTimeout("newTimer();",'. $refreshWait .'000);
 }
</script>
';
  $CMheaders = "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">
<head>
  <meta http-equiv=\"content-type\" content=\"application/xhtml+xml; charset=utf-8\" />
  <meta http-equiv=\"refresh\" content=\"$refreshWait;url=/maillog?search=\&wrap=$qs{wrap}\&color=$colorLines\&autorefresh=Auto\&files=$qs{files}\&limit=$qs{limit}\&nohighlight=$qs{nohighlight}\&nocontext=$qs{nocontext}\&tailbyte=$qs{tailbyte}\&size=$qs{size}\&order=$qs{order}\" />
  <title>$currentPage ASSP ($myName) Host: $localhostname @ $localhostip</title>
  <link rel=\"stylesheet\" href=\"get?file=images/assp.css\" type=\"text/css\" />
  <link rel=\"shortcut icon\" href=\"get?file=images/favicon.ico\" />
$autoJS
</head>
<body onfocus=\"tStart();\" onblur=\"tStop();\"><a name=\"MlTop\"></a>
";
 }
 my $s='';
 my $res='';
 my $base = $base;
 $base =~ s/([^\\])\\([^\\])/$1\\\\$2/go;
 # calculate indent
 my $m = &timestring().' ';
 my $resetpat;
 my $reportExt = $maillogExt;
 if(!$pat && $filesonly) {
     $resetpat = 1;
     $pat = $maillogExt;
 }
 if(!$pat) {
  my $TailBytes = ($qs{autorefresh} eq 'Auto' && $MaillogTailBytes > 2000) ? 2000 : $MaillogTailBytes;
  if ($qs{autorefresh} eq 'Auto') {
      my $sl; $sl = $1 if $qs{search} =~ /(\d+)/o;
      $sl = $AdminUsersRight{"$WebIP{$ActWebSess}->{user}.user.autolines"} if $sl == '' && $WebIP{$ActWebSess}->{user} ne 'root';
      my $al = $sl ? 33 - $sl : $currWrap ? 10 : 0;
      $al = 0 if $al < 0;
      $al = 32 if $al > 32;
      $AdminUsersRight{"$WebIP{$ActWebSess}->{user}.user.autolines"} = $sl if $WebIP{$ActWebSess}->{user} ne 'root';
      for (my $i = $al; $i < 33; $i++) {
          $s .= $RealTimeLog[$i];
      }
  } else {
      open(my $CML,'<',"$base/$logfile");
      seek($CML,-$TailBytes,2) || seek($CML,0,0);
      local $/;
      $s=<$CML>;
      close $CML;
  }
  if ($s && $LogCharset && $LogCharset !~ /^utf-?8/io) {
      $s = Encode::decode($LogCharset, $s);
      $s = Encode::encode('UTF-8', $s);
  }
  $s=encodeHTMLEntities($s) if $s;
  $s=~s/([^\\])?\\([^\\])?/$1\\\\$2/gso;
   my @sary=map{$_."\n" if $_;} split(/\r?\n|\r/o,$s);
   shift @sary if ($qs{autorefresh} ne 'Auto');
   my @rary;
   $matches=0;
   while (@sary) {
    $_ = shift @sary;
    @sary = () if time > $maxsearchtime;
    s/\\x\{\d+\}//g;
    if ($qs{autorefresh} ne 'Auto') {
     if (/(.*)?($base\/(($spamlog|$discarded|$notspamlog|$incomingOkMail|$viruslog|$correctedspam|$correctednotspam|$resendmail)\/[^\s]+(?:$maillogExt|$reportExt)))(.*)/)
     {
         my $text = $1;
         my $file = $2;
         my $hfile = $3;
         my $dname = $4;
         my $text2 = $5;
         my $span = ($dname =~ /^(?:$spamlog|$discarded|$viruslog|$correctedspam)$/) ? 'negative' : 'positive';
         $span = 'spampassed' if /\[spam passed\]/gio;
         $span = 'spampassed' if /and passing/gio;
         $text =~ s/([^ ]+) +/<span style="white-space:nowrap;">$1<\/span> /go;
         $text2 =~ s/([^ ]+) +/<span style="white-space:nowrap;">$1<\/span> /go;
         if (&MaillogExistFile($file)) {

            $hfile =~s/\Q$spamlog\E\//$maillogNewFile\// if $maillogNewFile;
            $span = ($maillogNewFile =~ /^(?:$spamlog|$discarded|$viruslog|$correctedspam)$/) ? 'negative' : 'positive' if $maillogNewFile;
            $hfile = "<span style=\"white-space:nowrap;\" onclick=\"popFileEditor('" . &normHTML($hfile) . "','m');\" class=\"" . $span . "\" onmouseover=\"fileBG=this.style.backgroundColor; this.style.backgroundColor='#BBBBFF';\" onmouseout=\"this.style.backgroundColor=fileBG;\"><b>" . $hfile . "<\/b><\/span>";
         } else {
            $hfile =~ s/([^ ]+) +/<span style="white-space:nowrap;">$1<\/span> /go;
         }
         $text .= $hfile . $text2;
         push(@rary,'<div id="ll' . $matches .'" class="assplogline'. ($currWrap + ($matches % 2 && $colorLines)) .'">' . $text . "\n</div>");
         $matches++;
         next;
     } elsif (! $filesonly) {
         my @links;
         my @addr;
         my @ips;
         $_ = niceLink($_);
         while ($_ =~ s/(\<a href.*?<\/a\>)/XXXIIIXXX/o) {
             my $link = $1;
             $link =~ s/WIDTH=[^\d]*(\d+\%)[^ ]*/WIDTH=$1/io;
             push @links,$link;
         }
         if (&canUserDo($WebIP{$ActWebSess}->{user},'action','addraction')) {
             while ($_ =~ s/((?<!Message-ID found: ))($EmailAdrRe\@$EmailDomainRe)/$1XXXAIIIDXXX/o) {
                 push @addr ,
                    "<span style=\"white-space:nowrap;\" onclick=\"popAddressAction('"
                    . &normHTML($2)
                    . "');\" class=\"menuLevel2\" onmouseover=\"fileBG=this.style.backgroundColor; this.style.backgroundColor='#BBBBFF';\" onmouseout=\"this.style.backgroundColor=fileBG;\"><b>"
                    . $2
                    . "<\/b><\/span>";
             }
         }
         if (&canUserDo($WebIP{$ActWebSess}->{user},'action','ipaction')) {
             while ($_ =~ s/($IPRe)([^:\d\/])/XXXiIIIpXXX$2/o) {
                 my  $ip = $1;
                 if (   $ip !~ /$IPprivate/o
                     && $ip ne $localhostip
                     && $ip ne $version
                     && $ip !~ /$LHNRE/)
                 {
                     push @ips,
                        "<span style=\"white-space:nowrap;\" onclick=\"popIPAction('"
                        . &normHTML($ip)
                        . "');\" class=\"menuLevel2\" onmouseover=\"fileBG=this.style.backgroundColor; this.style.backgroundColor='#BBBBFF';\" onmouseout=\"this.style.backgroundColor=fileBG;\"><b>"
                        . $ip
                        . "<\/b><\/span>";
                 } else {
                     push @ips, $ip;
                 }
             }
         }
         s/([^ ]+) +/<span style="white-space:nowrap;">$1<\/span> /go;
         if (@links) {
             s/XXXIIIXXX/shift(@links)/geo;
         }
         if (@addr) {
             s/XXXAIIIDXXX/shift(@addr)/geo;
         }
         if (@ips) {
             s/XXXiIIIpXXX/shift(@ips)/geo;
         }
     }
     if ($filesonly) {
         next;
     }
    }
    push(@rary,'<div id="ll' . $matches .'" class="assplogline'. ($currWrap + ($matches % 2 && $colorLines)) .'">' . $_ . "\n</div>");
    $matches++;
   }
   $s = join('',@rary);
   $s =~ s/"/\\"/go;
   $s =~ s/\n+<\/div>/<\/div>XXXIIIXXX/go;
   $s =~ s/\r|\n//go;
   $s =~ s/XXXIIIXXX$//o;
 } elsif ($CanSearchLogs) {
  my @sary;
  $matches=0;
  my $lines=0;
  my $files=0;
  my ($logdir, $logdirfile) = $logfile=~/^(.*[\/\\])?(.*?)$/o;
  my @logfiles1=reverse sort glob("$base/$logdir*$logdirfile");
  my @logfiles;
  while (@logfiles1) {
      my $k = shift @logfiles1;
      push(@logfiles, $k) if $k !~ /b$logdirfile/;
  }
  my $maxmatches =
                $qs{limit} eq '2000' ? 2000
              : $qs{limit} eq '1000' ? 1000
              : $qs{limit} eq '100'  ? 100
              : $qs{limit} eq '10'   ? 10
              : $qs{limit} eq '1'    ? 1
              :                        0;
  my $maxlines;
  my $maxfiles;
  if ($qs{files} eq 'lines') {
      ($maxlines) = $qs{size} =~ /(\d+)/o;
      $maxlines = 10000 unless $maxlines;
      $maxfiles = 0;
  } elsif ($qs{files} eq 'files') {
      ($maxfiles) = $qs{size} =~ /(\d+)/o;
      $maxfiles = 2 unless $maxfiles;
      $maxlines = 0;
  } elsif ($qs{files} eq 'ago') {
      $maxfiles = $qs{size};
      $maxfiles =~ s/\s//go;
      $maxfiles =~ s/-/.../go;
      my @num = sort {$main::a <=> $main::b} map(eval($_),split(',', $maxfiles));
      @num = (1) unless $maxfiles or @num;
      my @lof = @logfiles;
      @logfiles = ();
      foreach (@num) {
          push(@logfiles , $lof[$_ - 1]) if $_ > 0 && $lof[$_ - 1];
      }
      push(@logfiles,$lof[0]) unless @logfiles;
      $maxlines = 0;
  } else {
      $maxlines = 0;
      $maxfiles = 0;
  }
  my $logf=File::ReadBackwards->new(shift(@logfiles),'(?:\r?\n|\r)',1); # line terminator regex
  if ($logf) {
   $files++;
   # normalize and strip redundant minuses
   $pat = &HTML::Entities::decode($pat,'"\'><&');
   $pat=~s/(?<!(?:-|\w))(-(?:\s+|\z))+/-/go;
   $pat=~s/\s+-$//o;
   my $l;
   $l=$logf->readline();
   if ($l && $LogCharset && $LogCharset !~ /^utf-?8/io) {
       $l = Encode::decode($LogCharset, $l);
       $l = Encode::encode('UTF-8', $l);
   }
   $l =~ s/\\x\{\d+\}//go;
   # make line terminators uniform
   $l=~s/(.*?)(?:\r?\n|\r)/$1\n/o;
   $l=encodeHTMLEntities($l) if $l;
   $l=~s/([^\\])?\\([^\\])?/$1\\\\$2/gso;
   my @ary;
   push(@ary,$l);
   my $infinity=10000;
   my $precontext=my $postcontext=$qs{nocontext} ? 0 : 6;
   my $notmatched=0;
   my $currentpre=0;
   my $seq=0;
   my $lastoutput=$infinity;
   my $cur=$ary[0];
   my $i=0;
   my @words=map/^\d+\_(.*)/o, sort values %{{map{lc $_ => sprintf("%02d",$i++).'_'.$_} split(' ',$pat)}};
   $pat=join(' ', @words);
   my @highlights=('<span%%20%%style="color:black;%%20%%background-color:#ffff66">',
                   '<span%%20%%style="color:black;%%20%%background-color:#A0FFFF">',
                   '<span%%20%%style="color:black;%%20%%background-color:#99ff99">',
                   '<span%%20%%style="color:black;%%20%%background-color:#ff9999">',
                   '<span%%20%%style="color:black;%%20%%background-color:#ff66ff">',
                   '<span%%20%%style="color:white;%%20%%background-color:#880000">',
                   '<span%%20%%style="color:white;%%20%%background-color:#00aa00">',
                   '<span%%20%%style="color:white;%%20%%background-color:#886800">',
                   '<span%%20%%style="color:white;%%20%%background-color:#004699">',
                   '<span%%20%%style="color:white;%%20%%background-color:#990099">');
   my $findExpr=join(' && ',((map{'$cur=~/'.quotemeta($_).'/io'} map/^([^-].*)/o, split(' ',$pat)),
                             (map{'$cur!~/'.quotemeta($_).'/io'} map/^-(.*)/o, split(' ',$pat))));
   my %replace = ();
   my $j=0;
   my $highlightExpr='=~s/(';
   foreach (map/^([^-].*)/o, split(' ',$pat)) {
    $replace{lc $_}=$highlights[$j % @highlights]; # pick highlight style
    $highlightExpr.=quotemeta($_).'|';
    $j++;
   }
   $highlightExpr=~s/\|$//o;
   $highlightExpr.=')/$replace{lc $1}$1<\/span>/gio';
   my $loop=<<'LOOP';
   while (time < $maxsearchtime && $cur && !($maxmatches && $matches>=$maxmatches && $notmatched>$postcontext) && !($maxlines && $lines>=$maxlines)) {
LOOP
    $loop.='
    if (!($maxmatches && $matches>=$maxmatches) && '.$findExpr.') {'. <<'LOOP';
     $matches++;
LOOP
     $loop.='$cur'.$highlightExpr.' unless $qs{nohighlight};'. <<'LOOP';
     if ($lastoutput<=$postcontext) {
      push(@sary,$cur);
     } else {
      push(@sary,"\r\n") if ($seq++ && ($precontext+$postcontext>0));
      for ($i=0; $i<@ary; $i++) {
       if ($i<$precontext && $currentpre==$precontext || $i<$currentpre) {
        $ary[$i]=~s/^(.*?)(\r?\n)$/<span\%\%20\%\%style="color:#999999">$1<\/span>$2/so;
       } else {
LOOP
        $loop.='$ary[$i]'.$highlightExpr.' unless $qs{nohighlight};'. <<'LOOP';
       }
       push(@sary,$ary[$i]);
      }
     }
     $lastoutput=0;
     $notmatched=0;
    } elsif ($logf->eof) {
     for (; $currentpre>=0; $currentpre--) {
      shift(@ary);
     }
     $logf->close if exists $logf->{'handle'};
     if (!($maxfiles && $files>=$maxfiles)) {
      $logf=File::ReadBackwards->new(shift(@logfiles),'(?:\r?\n|\r)',1);
      $files++ if $logf;
     }
     $lastoutput=$infinity;
    } elsif ($lastoutput<=$postcontext) {
     $cur=~s/^(.*?)(\r?\n)$/<span\%\%20\%\%style="color:#999999">$1<\/span>$2/so;
     push(@sary,$cur);
    }
    $lastoutput++;
    $notmatched++;
    if ($l) {
     $l=$logf->readline();
     if ($l && $LogCharset && $LogCharset !~ /^utf-?8/io) {
         $l = Encode::decode($LogCharset, $l);
         $l = Encode::encode('UTF-8', $l);
     }
     # make line terminators uniform
     $l=~s/(.*?)(?:\r?\n|\r)/$1\n/o;
     $l =~ s/\\x\{\d+\}//go;

     my $fname;
     if ($l=~ s/($base\/.+?\/.+?$maillogExt)/aAaAaAaAaAbBbBbBbBbB$maillogExt/) {
       $fname = $1;
     }

     $l=encodeHTMLEntities($l) if $l;
     $l=~s/([^\\])?\\([^\\])?/$1\\\\$2/gso;

     $l =~ s/aAaAaAaAaAbBbBbBbBbB$maillogExt/$fname/o;
     $fname = '';

     $lines++;
    }
    push(@ary,$l);
    if ($currentpre<$precontext) {
     $currentpre++;
    } else {
     shift(@ary);
    }
    $cur=$ary[$currentpre];
   }
LOOP
   eval $loop;
   $logf->close if exists $logf->{'handle'};
  }
  my $orgmatches = $matches;
  if ($matches>0) {
   $matches = 0;
   my @rary;
   my $line = $_;
   $maxsearchtime = time + 60;
   while (@sary) {
    $_ = shift @sary;
    @sary = () if time > $maxsearchtime;
    my @sp;
    my @words;
    my $pretag;
    my $posttag;
    $line = $_;
    if ($_ =~ /<\/span>/o ) {
     if (!$qs{nocontext} && $_ =~ s/^(<span\%\%20\%\%style="color:#999999">)//o) {
        $pretag = $1;
        $posttag = $1 if ($_ =~ s/(<\/span>[\r\n]*)$//o);
     }
     if ($_ =~ /<\/span>/o ) {
      my $iline = '';
      @words = split(/(<span[^>]+>|<\/span>)/o);
      my $i = 0;
      while (@words) {
        $sp[$i][0] = shift @words;
        $sp[$i][1] = shift @words;
        $sp[$i][2] = shift @words;
        $sp[$i][3] = shift @words;
        $iline .=  $sp[$i][0] . $sp[$i][2];
        $i++;
      }
      if ($iline =~ /$base\/(?:$spamlog|$discarded|$notspamlog|$incomingOkMail|$viruslog|$correctedspam|$correctednotspam|$resendmail)\/[^\s]+(?:$maillogExt|$reportExt)/) {
          $line = $iline ;
      } else {
         @sp = ();
      }
     }
    }
    $_ = $line;
    if (/^(<[^<>]+>)*(.*)?($base\/(($spamlog|$discarded|$notspamlog|$incomingOkMail|$viruslog|$correctedspam|$correctednotspam|$resendmail)\/[^\s]+(?:$maillogExt|$reportExt)))(.*)$/)
    {
        my $sp = $1;
        my $text = $2;
        my $file = my $hfile = $3;
        my $hlfile = $4;
        my $dname = $5;
        my $text2 = $6;
        my $span = ($dname =~ /^(?:$spamlog|$discarded|$viruslog|$correctedspam)$/) ? 'negative' : 'positive';
        $span = 'spampassed' if /\[spam passed\]/gio;
        $span = 'spampassed' if /and passing/gio;

        if (@sp) {
            my $i = 0;
            my $j = scalar @sp;
            my $fpos = 0;
            my $tpos = 0;
            my $t2pos = 0;
            while ($j > $i) {
              my ($s0,$s1,$s2,$s3) = ($sp[$i][0],$sp[$i][1],$sp[$i][2],$sp[$i][3]);
              if ($s1) {
                  pos($text) = $tpos;
                  $text =~ s/\Q$s0$s2\E/$s0$s1$s2$s3/;
                  $tpos = pos($text);
                  if ($tpos) {
                      $tpos += length($s1 . $s3);
                  } else {
                      $tpos = 0;
                  }

                  pos($text2) = $t2pos;
                  $text2 =~ s/\Q$s0$s2\E/$s0$s1$s2$s3/;
                  $t2pos = pos($text2);
                  if ($t2pos) {
                      $t2pos += length($s1 . $s3);
                  } else {
                      $t2pos = 0;
                  }

                  pos($hfile) = $fpos;
                  $hfile =~ s/\Q$s0$s2\E/$s0$s1$s2$s3/;
                  $fpos = pos($hfile);
                  if ($fpos) {
                      $fpos += length($s1 . $s3);
                  } else {
                      $fpos = 0;
                  }
              }
              $i++;
            }
        }
        $hfile =~ s/\Q$base\E\///o;

        if (&MaillogExistFile($file)) {
            $hlfile =~s/\Q$spamlog\E\//$maillogNewFile\// if $maillogNewFile;
            $hfile =~s/\Q$spamlog\E\//$maillogNewFile\// if $maillogNewFile;
            $span = 'positive' if $maillogNewFile eq $correctednotspam;
              $hfile = "<span\%\%20\%\%style=\"white-space:nowrap;\"\%\%20\%\%onclick=\"popFileEditor('" . &normHTML($hlfile) . "','m');\"\%\%20\%\%class=\"" . $span . "\"\%\%20\%\%onmouseover=\"fileBG=this.style.backgroundColor;\%\%20\%\%this.style.backgroundColor='#BBBBFF';\"\%\%20\%\%onmouseout=\"this.style.backgroundColor=fileBG;\"><b>" . $hfile . "<\/b><\/span>";
        } else {
          $hfile =~ s/([^ ]+)( +)?/<span style="white-space:nowrap;">$1<\/span>$2/go;
        }

        $text = $sp . $text .$hfile . $text2;
        my $out = '<div%%20%%id="ll' . $matches .'"%%20%%class="assplogline'. ($currWrap + ($matches % 2 && $colorLines)) .'">' . $text . "\n</div>";
        $out =~ s/\%\%20\%\%/ /go;
        push(@rary,$pretag . $out . $posttag);
        $matches++;
        next;
    } elsif (! $filesonly) {
        s/\%\%20\%\%/ /go;
        $_ = niceLink($_);
        my @links;
        my @addr;
        my @ips;
        while ($_ =~ s/(\<a href.*?<\/a\>)/XXXIIIXXX/o) {
            my $link = $1;
            $link =~ s/WIDTH=[^\d]*(\d+\%)[^ ]*/WIDTH=$1/io;
            push @links,$link;
        }
        if (&canUserDo($WebIP{$ActWebSess}->{user},'action','addraction')) {
            while ($_ =~ s/((?<!Message-ID found: ))($EmailAdrRe\@$EmailDomainRe)/$1XXXAIIIDXXX/o) {
                push @addr ,
                   "<span style=\"white-space:nowrap;\" onclick=\"popAddressAction('"
                   . &normHTML($2)
                   . "');\" class=\"menuLevel2\" onmouseover=\"fileBG=this.style.backgroundColor; this.style.backgroundColor='#BBBBFF';\" onmouseout=\"this.style.backgroundColor=fileBG;\"><b>"
                   . $2
                   . "<\/b><\/span>";
            }
        }
        if (&canUserDo($WebIP{$ActWebSess}->{user},'action','ipaction')) {
            while ($_ =~ s/($IPRe)([^:\d\/])/XXXiIIIpXXX$2/o) {
                my  $ip = $1;
                if (   $ip !~ /$IPprivate/o
                    && $ip ne $localhostip
                    && $ip ne $version
                    && $ip !~ /$LHNRE/)
                {
                    push @ips,
                       "<span style=\"white-space:nowrap;\" onclick=\"popIPAction('"
                       . &normHTML($ip)
                       . "');\" class=\"menuLevel2\" onmouseover=\"fileBG=this.style.backgroundColor; this.style.backgroundColor='#BBBBFF';\" onmouseout=\"this.style.backgroundColor=fileBG;\"><b>"
                       . $ip
                       . "<\/b><\/span>";
                } else {
                    push @ips, $ip;
                }
            }
        }
        if (@links) {
            s/XXXIIIXXX/shift(@links)/geo;
        }
        if (@addr) {
            s/XXXAIIIDXXX/shift(@addr)/geo;
        }
        if (@ips) {
            s/XXXiIIIpXXX/shift(@ips)/geo;
        }
    }
    if ($filesonly) {
        next;
    }
    my $out =  '<div id="ll' . $matches .'" class="assplogline'. ($currWrap + ($matches % 2 && $colorLines)) .'">' . $_ . "\n</div>";
    push(@rary, $pretag . $out . $posttag);
    $matches++;
   }
   $s = join('', reverse @rary);
   $s =~ s/"/\\"/go;
   $s =~ s/\n+<\/div>/<\/div>XXXIIIXXX/go;
   $s =~ s/\r|\n//go;
   $s =~ s/XXXIIIXXX$//o;
   my $ftext = $filesonly ? ' with ' . needEs($matches,' line','s') . ' that contains filesnames' : '';
   $res='found '. needEs($orgmatches,' matching line','s') . $ftext . ', searched in '. needEs($files,' log file','s') .' ('. needEs($lines,' line','s'). ')';
  } else {
   $res='no results found, searched in '. needEs($files,' log file','s') .' ('. needEs($lines,' line','s'). ')';
  }
 } else {
  $s='<p class="warning">Please install required module <a href="http://search.cpan.org/~uri/File-ReadBackwards-1.03/" rel="external">File::ReadBackwards</a>.</p>';
 }
 $MaillogTailBytes = $savTailByte;
 my $size = $qs{size} ? $qs{size} : 10000;
 my $files = $qs{files} || 'lines';
 my $limit = $qs{limit} || 10;
 $pat = ($resetpat) ? '' : &HTML::Entities::encode($orgpat,'"\'><&');
 my $h1 = $WebIP{$ActWebSess}->{lng}->{'msg500050'} || $lngmsg{'msg500050'};
 my $h2 = $WebIP{$ActWebSess}->{lng}->{'msg500051'} || $lngmsg{'msg500051'};
 my $h4 = $WebIP{$ActWebSess}->{lng}->{'msg500052'} || $lngmsg{'msg500052'};
 my $h5 = $WebIP{$ActWebSess}->{lng}->{'msg500053'} || $lngmsg{'msg500053'};
 $h1 =~ s/\r|\n//go;
 $h2 =~ s/\r|\n//go;
 $h4 =~ s/\r|\n//go;
 $h5 =~ s/\r|\n//go;

 my $dir = $base;
 $dir .= "/$1" if $logfile =~ /^([^\/]+)\//o;
 my ($lf) = $logfile =~/([^\/]+)$/o;
 my $h3 = '<center><table BORDER CELLSPACING=2 CELLPADDING=4><tr><th></th><th>filename</th><th>size</th><th></th><th>filename</th><th>size</th></tr>';
 $h3 .= '<tr><td>01</td><td>' . $lf . '</td><td>' . formatDataSize( -s "$dir/$lf", 1 ) . '</td></tr>';
 opendir(my $DIR,"$dir");
 my @filelist = readdir($DIR);
 close $DIR;
 my $i = 0;
 foreach my $file (reverse sort @filelist) {
     next if $file !~ /\.$lf$/;
     $h3 .= '<tr>' unless $i % 2;
     $h3 .= '<td>' . sprintf("%02d",($i + 2)) . '</td><td>' . $file . '</td><td>' . formatDataSize( -s "$dir/$file", 1 ) . '</td>';
     $h3 .= '</tr>' if $i % 2;
     $i++;
 }
 $h3 .= '</tr>' if $h3 !~ /tr\>$/;
 $h3 .= '</table></center>';
 $stime = time - $stime;
 $res .= ', ' if ($res &&  $qs{autorefresh} ne 'Auto');
 $res .= "searchtime $stime seconds (max 60)" if ($qs{autorefresh} ne 'Auto');
 my $headline = ($qs{autorefresh} eq 'Auto') ? '' : '<h2>ASSP Maillog Tail</h2>' ;
 $headline = ($qs{autorefresh} eq 'Auto') ? '' : '<h2>Secondary GUI Maillog Tail  Viewer</h2>' if $AsASecondary;

<<EOT;
$headerHTTP
$headerDTDTransitional
$$CMheaders
<style type="text/css">
.spampassed { color: #FFA500; }
</style>
<div id="headline" $content>
$headline
<a name="MlTop" style="font-weight: normal;"></a>
<div class="log" ><pre><a id="dummy" name="dummy" style="font-weight: normal;">$m</a></pre></div>
<script type="text/javascript">
var fileBG;
var MlEndPos;

var intend = document.getElementById('dummy').offsetWidth;
document.getElementById('dummy').style.display='none';

document.write("<style id=\\"aloli0\\" type=\\"text/css\\">\\n.assplogline0\\n {\\nwhite-space:nowrap;\\n padding-left:" + intend + "px;\\n text-indent:-" + intend + "px;\\n background-color:#FFFFFF;\\n}\\n</style>\\n");
document.write("<style id=\\"aloli1\\" type=\\"text/css\\">\\n.assplogline1\\n {\\nwhite-space:nowrap;\\n padding-left:" + intend + "px;\\n text-indent:-" + intend + "px;\\n background-color:#F0F0F0;\\n}\\n</style>\\n");
document.write("<style id=\\"aloli2\\" type=\\"text/css\\">\\n.assplogline2\\n {\\nwhite-space:normal;\\n padding-left:" + intend + "px;\\n text-indent:-" + intend + "px;\\n background-color:#FFFFFF;\\n}\\n</style>\\n");
document.write("<style id=\\"aloli3\\" type=\\"text/css\\">\\n.assplogline3\\n {\\nwhite-space:normal;\\n padding-left:" + intend + "px;\\n text-indent:-" + intend + "px;\\n background-color:#F0F0F0;\\n}\\n</style>\\n");

function changeSpan(change) {
  var iswrap = document.MTform.wrap[1].checked ? 2 : 0;
  var iscolor = document.MTform.color[1].checked ? 1 : 0;
  var dowrap = change - 2;
  for(i=0; i < $matches; i++) {
    if (change == 0 || change == 1) {
      if (change == 0) {
          document.getElementById('ll' + i).className = 'assplogline' + iswrap;
      } else {
          document.getElementById('ll' + i).className = 'assplogline' + ((i % 2) + iswrap);
      }
    } else {
      if (iscolor == 0) {
          document.getElementById('ll' + i).className = 'assplogline' + dowrap;
      } else {
          document.getElementById('ll' + i).className = 'assplogline' + ((i % 2) + dowrap);
      }
    }
  }
}
</script>
<form name="MTform" action="" method="get">
  <table class="textBox" style="width: 100%;">
    <tr>
      <td rowspan="2" align="left" $display>
        <label>wrap lines: </label>
        <input type="radio" name="wrap" ${\(! $currWrap ? ' checked="checked" ' : ' ')} value='0' onclick="javascript:changeSpan('2');" />no
        <input type="radio" name="wrap" ${\(  $currWrap ? ' checked="checked" ' : ' ')} value='2' onclick="javascript:changeSpan('4');" />yes<br />
        <label>color lines: </label>
        <input type="radio" name="color" ${\(! $colorLines ? ' checked="checked" ' : ' ')} value='0' onclick="javascript:changeSpan('0');" />no
        <input type="radio" name="color" ${\(  $colorLines ? ' checked="checked" ' : ' ')} value='1' onclick="javascript:changeSpan('1');" />yes<br />
        <label>tail bytes:</label>
        <input type="text" name="tailbyte" value='$currTailByte' size="7"/>
      </td>
      <td align="left" $display>
        <label>search for: </label>
        <a href="javascript:void(0);" onmouseover="showhint('$h5', this, event, '450px', '1');return false;"><img height=12 width=12 src="$wikiinfo" /></a>
        <input type="text" name="search" value='$pat' size="30"/>
      </td>
      <td align="left">
        <input type="submit" value="Submit/Update" $display />

        <input type="hidden" name="order" value='$order'/>
      </td>
      <td rowspan="2" $display>
        <input type="checkbox" name="nocontext"${\($qs{nocontext} ? ' checked="checked" ' : ' ')}value='1' />hide&nbsp;context&nbsp;lines<br />
        <input type="checkbox" name="nohighlight"${\($qs{nohighlight} ? ' checked="checked" ' : ' ')}value='1' />no&nbsp;highlighting<br />
        <input type="checkbox" name="filesonly"${\($qs{filesonly} ? ' checked="checked" ' : ' ')}value='1' />file&nbsp;lines&nbsp;only
      </td>
    </tr>
    <tr $display>
      <td align="left">
        <label>search in:</label>
        <a href="javascript:void(0);" onmouseover="showhint('$h4', this, event, '450px', '1');return false;"><img height=12 width=12 src="$wikiinfo" /></a>
        <input type="text" name="size" value='$size' size="7" />
        <select size="1" name="files" value="$qs{files}" />
          <option value="lines">last lines</option>
          <option value="files">last log files</option>
          <option value="all">all log files</option>
          <option value="ago">this file number(s)</option>
        </select>
        <a href="javascript:void(0);" onmouseover="showhint('$h3', this, event, '450px', '1');return false;"><img height=12 width=12 src="$wikiinfo" /></a>
      </td>
      <td align="left">
        <label>show </label>
        <select size="1" name="limit" value="$qs{limit}">
          <option value="1">1</option>
          <option value="10">10</option>
          <option value="100">100</option>
          <option value="1000">1000</option>
          <option value="2000">2000</option>
        </select> Results
      </td>
    </tr>
  </table>
</form>
<script type="text/javascript">
document.MTform.files.value='$files';
document.MTform.limit.value='$limit';
function resetForm() {
  document.MTform.search.value='';
  document.MTform.nocontext.checked=false;
  document.MTform.nohighlight.checked=false;
  document.MTform.filesonly.checked=false;
  document.MTform.tailbyte.value='$MaillogTailBytes';
  document.MTform.size.value='10000';
  document.MTform.files.value='lines';
  document.MTform.limit.value='10';
  document.MTform.order.value='0';
}
</script>
<div class="log $logstyle" $display>
<a href="javascript:void(0);" onclick="document.getElementById(\'LogLines\').scrollTop=MlEndPos; return false;" >Go to End</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="javascript:void(0);" onclick="document.getElementById(\'LogLines\').scrollTop=0;return false;">Go to Top</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="javascript:void(0);" onmouseover="showhint('$h3', this, event, '450px', '1');return false;">show filelist</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="javascript:void(0);" onmouseover="showhint('$h1<br /><br />$h2', this, event, ie ? document.body.offsetWidth / 2.1 + 'px' : window.innerWidth / 2.1 + 'px' , '');return false;">help</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="javascript:void(0);" onclick="resetForm();" onmouseover="showhint('click to reset the form to system defaults', this, event, '300px', '');return false;">reset form</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="javascript:void(0);" onclick="switchMTOrder();" onmouseover="showhint('click to switch the time order of lines', this, event, '300px', '');return false;">switch order</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="/" onmouseover="showhint('click to return to config dialog', this, event, '300px', '');return false;">back to config</a><br />
<script type="text/javascript">
if ('$qs{autorefresh}' != 'Auto') {
//var LogWidth = objWidth('headline') + 'px';
var LogHeight = ClientSize('h') - document.getElementById('headline').offsetHeight + 'px';
}
</script>
$res
<hr>
</div>
<div id="LogLines" class="log" style="display:block;height:100%;width=100%;overflow:auto;">
<div class="log $logstyle" width=100%>
<pre id="allLogLines" style="font-size: 1.4em;">
</pre>

<script type="text/javascript">

if ('$qs{autorefresh}' != 'Auto') {
//document.getElementById('LogLines').style.width = LogWidth;
document.getElementById('LogLines').style.height = LogHeight;
}

var order = $order;
var allLines = "$s".split("XXXIIIXXX");
var allLinesF = allLines.join('');
var allLinesR = allLines.reverse().join('');
allLines = ('');
function switchMTOrder() {
  order = order ? 0 : 1 ;
  document.MTform.order.value=order;
  var logdiv = document.getElementById('allLogLines');
  logdiv.innerHTML = '';
  if (order == 1) {
      logdiv.innerHTML = allLinesR;
  } else {
      logdiv.innerHTML = allLinesF;
  }
}
order = order ? 0 : 1 ;
switchMTOrder();
if ('$qs{autorefresh}' != 'Auto') {
MlEndPos = document.getElementById('allLogLines').scrollHeight;
window.location.href = '#MlTop';
${\($MaillogTailJump && $qs{autorefresh} ne 'Auto' ? 'document.getElementById(\'LogLines\').scrollTop=MlEndPos;' : 'order = order;') }
}
</script>
</div>
<div $display >
$maillogJump
</div>
</div>
</div>
<div $display >
$footers
</div>
<form name="ASSPconfig" id="ASSPconfig" action="" method="post">
  <input name="theButtonLogout" type="hidden" value="" />
</form>
</body></html>
EOT
}

sub MaillogExistFile {
    my $file = shift;

    return 0 unless $file;
    $maillogNewFile="";
    if ($LogCharset) {
     $file = Encode::encode($LogCharset, Encode::decode('UTF-8',$file));
    }
    my $newfile = $file;
    if ( !-e "$file" && $file =~ /\Q$spamlog\E\//i) {

        if ($discarded) {
            $newfile =~ s/\Q$spamlog\E\//$discarded\// ;
            $maillogNewFile = $discarded if -e "$newfile";
            return 1 if -e "$newfile" ;
        }

        $newfile = $file;
        $newfile =~ s/\Q$spamlog\E\//$correctednotspam\// ;
        $maillogNewFile = $correctednotspam if -e "$newfile";
        return 1 if -e "$newfile" ;

    }
    if ( !-e "$file" && $file =~ /\Q$notspamlog\E\//i) {

        if ($discarded) {
            $newfile =~ s/\Q$notspamlog\E\//$discarded\// ;
            $maillogNewFile = $discarded if -e "$newfile";
            return 1 if -e "$newfile" ;
        }

        $newfile = $file;
        $newfile =~ s/\Q$notspamlog\E\//$correctedspam\// ;
        $maillogNewFile = $correctedspam if -e "$newfile";
        return 1 if -e "$newfile" ;

    }
    if ( !-e "$file" && $file =~ /\Q$incomingOkMail\E\//i) {

        if ($spamlog) {
            $newfile =~ s/\Q$incomingOkMail\E\//$spamlog\// ;
            $maillogNewFile = $spamlog if -e "$newfile";
            return 1 if -e "$newfile" ;
        }

        $newfile = $file;
        $newfile =~ s/\Q$incomingOkMail\E\//$correctedspam\// ;
        $maillogNewFile = $correctedspam if -e "$newfile";
        return 1 if -e "$newfile" ;

    }    
    return -e "$file" ;
}

sub d8 {
    local $@;
    my $ret = eval{Encode::decode('UTF-8',$_[0]);};
    return ($ret && defined ${chr(ord("\026") << 2)}) ? $ret : $_[0];
}

sub e8 {
    local $@;
    my $ret = eval{Encode::encode('UTF-8',$_[0]);};
    return ($ret && defined ${chr(ord("\026") << 2)}) ? $ret : $_[0];
}

sub de8 {
    local $@;
    my $ret = eval{require Encode::Guess; e8(Encode::decode('GUESS',$_[0]));};
    return ($ret && defined ${chr(ord("\026") << 2)}) ? $ret : $_[0];
}
sub decodeMimeWord2UTF8 {
    my ($fulltext,$charset,$encoding,$text)=@_;
    my $ret;

    eval {$charset = Encode::resolve_alias(uc($charset));} if $charset;

    if (!$@ && $CanUseEMM && $charset ) {
        eval{$ret = MIME::Words::decode_mimewords($fulltext)} if $fulltext;
        eval{
            $ret = Encode::decode($charset, $ret);
            $ret = e8($ret) if $ret;
        } if $ret;
        return $ret unless $@;
    }

    if (lc $encoding eq 'b') {
        $text=base64decode($text);
    } elsif (lc $encoding eq 'q') {
        $text=~s/_/\x20/go; # RFC 1522, Q rule 2
        $text=~s/=([\da-fA-F]{2})/pack('C', hex($1))/geo; # RFC 1522, Q rule 1
    };
    eval{
        $text = Encode::decode($charset, $text);
        $text = e8($text) if $text;
    } if $text;
    return $text;
}

sub decodeMimeWords2UTF8 {
    my $s = shift;
    headerUnwrap($s);
    $s =~ s/(=\?([^?]*)\?(b|q)\?([^?]+)\?=)/decodeMimeWord2UTF8($1,$2,$3,$4)/gieo;
    return $s;
}

sub canUserDo {
    my ($user,$what,$item) = @_;

    return 1;
}
sub ConfigAddrAction {
    my $addr = lc($qs{address});
    $addr =~ s/^\s+//o;
    $addr =~ s/\s+$//o;
    my $local;
    my $isnameonly;
    $local = localmail($addr) if $addr;
    my $action = $qs{action};
    my $slo;
    $slo = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="button"  name="showlogout" value="  logout " onclick="window.location.href=\'./logout\';return false;"/></span>' if exists $qs{showlogout};
    my $s = $qs{reloaded} eq 'reloaded' ? '<span class="positive">(page was auto reloaded)</span><br /><br />' : '';

    my $mfd;my $wrongaddr;
    if ($addr =~ /^(?:$EmailAdrRe)?(\@$EmailDomainRe)$/io) {
        $mfd = $1;
    } elsif ($addr =~ /^($EmailDomainRe)$/io) {
        $mfd = $1;
    } elsif ($addr =~ /^$EmailAdrRe$/io) {
        $isnameonly = '<br />This is interpreted as the userpart of an email address!<br />';
    } else {
        $wrongaddr = '<br /><span class="negative">This is not a valid email address or domain!</span><br />';
    }

    if ($addr && $action && $qs{Submit} && !$wrongaddr) {
        my %lqs = %qs;
        if ($mfd && $action eq '1' && &canUserDo($WebIP{$ActWebSess}->{user},'action','lists')) {
            %qs = ('action' => 'a', 'list' => 'white', 'addresses' => $addr);
            $s = &ConfigLists();
            $s =~ s/^.+?<\/h2>(.+?)<form.+$/$1/ois;
        } elsif ($mfd && $action eq '2' && &canUserDo($WebIP{$ActWebSess}->{user},'action','lists')) {
            %qs = ('action' => 'r', 'list' => 'white', 'addresses' => $addr);
            $s = &ConfigLists();
            $s =~ s/^.+?<\/h2>(.+?)<form.+$/$1/ois;
        } elsif ($mfd && $action eq '3' && &canUserDo($WebIP{$ActWebSess}->{user},'action','lists')) {
            %qs = ('action' => 'a', 'list' => 'red', 'addresses' => $addr);
            $s = &ConfigLists();
            $s =~ s/^.+?<\/h2>(.+?)<form.+$/$1/ois;
        } elsif ($mfd && $action eq '4' && &canUserDo($WebIP{$ActWebSess}->{user},'action','lists')) {
            %qs = ('action' => 'r', 'list' => 'red', 'addresses' => $addr);
            $s = &ConfigLists();
            $s =~ s/^.+?<\/h2>(.+?)<form.+$/$1/ois;
        } elsif ($action eq '5' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingTo')) {
            my $r = $GPBmodTestList->('GUI','noProcessing','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to noProcessingTo" : "$addr not added to noProcessingTo";
        } elsif ($action eq '6' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingTo')) {
            my $r = $GPBmodTestList->('GUI','noProcessing','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from noProcessingTo" : "$addr not removed from noProcessingTo";
        } elsif ($action eq '7' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingFrom')) {
            my $r = $GPBmodTestList->('GUI','noProcessingFrom','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to noProcessingFrom" : "$addr not added to noProcessingFrom";
        } elsif ($action eq '8' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingFrom')) {
            my $r = $GPBmodTestList->('GUI','noProcessingFrom','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from noProcessingFrom" : "$addr not removed from noProcessingFrom";
        } elsif ($mfd && $action eq '9' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','whiteListedDomains')) {
            my $r = $GPBmodTestList->('GUI','whiteListedDomains','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to whiteListedDomains" : "$addr not added to whiteListedDomains";
        } elsif ($mfd && $action eq 'A' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','whiteListedDomains')) {
            my $r = $GPBmodTestList->('GUI','whiteListedDomains','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from whiteListedDomains" : "$addr not removed from whiteListedDomains";
        } elsif ($mfd && $action eq 'B' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','blackListedDomains')) {
            my $r = $GPBmodTestList->('GUI','blackListedDomains','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to blackListedDomains" : "$addr not added to blackListedDomains";
        } elsif ($mfd && $action eq 'C' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','blackListedDomains')) {
            my $r = $GPBmodTestList->('GUI','blackListedDomains','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from blackListedDomains" : "$addr not removed from blackListedDomains";
        } elsif ($action eq 'D' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','spamLovers')) {
            my $r = $GPBmodTestList->('GUI','spamLovers','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to spamLovers (All Spam-Lover)" : "$addr not added to spamLovers (All Spam-Lover)";
        } elsif ($action eq 'E' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','spamLovers')) {
            my $r = $GPBmodTestList->('GUI','spamLovers','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from spamLovers (All Spam-Lover)" : "$addr not removed from spamLovers (All Spam-Lover)";
        } elsif ($action eq 'F' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','spamHaters')) {
            my $r = $GPBmodTestList->('GUI','spamHaters','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to spamHaters (All Spam-Haters)" : "$addr not added to spamHaters (All Spam-Haters)";
        } elsif ($action eq 'G' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','spamHaters')) {
            my $r = $GPBmodTestList->('GUI','spamHaters','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from spamHaters (All Spam-Haters)" : "$addr not removed from spamHaters (All Spam-Haters)";
        } elsif ($mfd && $action eq 'H' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingDomains')) {
            my $r = $GPBmodTestList->('GUI','noProcessingDomains','add',' - via MaillogTail',$mfd,0);
            $s = ($r > 0) ? "$mfd added to noProcessing Domains" : "$mfd not added to noProcessing Domains";
        } elsif ($mfd && $action eq 'I' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingDomains')) {
            my $r = $GPBmodTestList->('GUI','noProcessingDomains','delete',' - via MaillogTail',$mfd,0);
            $s = ($r > 0) ? "$mfd removed from noProcessing Domains" : "$mfd not removed from noProcessing Domains";
        } elsif ($action eq 'J' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','preHeaderRe')) {
            my $addrRe = quotemeta($addr);
            my $r = $GPBmodTestList->('GUI','preHeaderRe','add',' - via MaillogTail',$addrRe,0);
            $s = ($r > 0) ? "$addr added as regex ($addrRe) to preHeaderRe" : "$addr not added to preHeaderRe";
        } elsif ($action eq 'K' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','preHeaderRe')) {
            my $addrRe = quotemeta($addr);
            my $r = $GPBmodTestList->('GUI','preHeaderRe','delete',' - via MaillogTail',$addrRe,0);
            $s = ($r > 0) ? "$addr removed as regex ($addrRe) from preHeaderRe" : "$addr not removed from preHeaderRe";
        } elsif ($action eq 'L' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noScan')) {
            my $r = $GPBmodTestList->('GUI','noScan','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to sDo Not Virus-Scan Messages from/to these Addresses(noScan)" : "$addr not added to Do Not Scan Messages from/to these Addresses(noScan)";
        } elsif ($action eq 'M' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noScan')) {
            my $r = $GPBmodTestList->('GUI','noScan','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from Do Not Virus-Scan Messages from/to these Addresses(noScan)" : "$addr not Do Not Scan Messages from/to these Addresses(noScan)";
        } elsif ($action) {
            $s = "<span class=\"negative\">access denied for the selected action</span>";
        }
        %qs = %lqs;
    }
    $s = 'no action selected - or no result available' if (! $s && $qs{Submit});
    if ($s !~ /not|negative/ && $qs{Submit}) {
        $ConfigChanged = 1;
        &tellThreadsReReadConfig();   # reread the config
    }

    my $option  = "<option value=\"0\">select action</option>";
    if ($addr && ! $wrongaddr) {
        $option .= "<option value=\"1\">add to WhiteList</option>"
         if ($mfd && ! $local && ! Whitelist($addr,'','') && &canUserDo($WebIP{$ActWebSess}->{user},'action','lists'));
        $option .= "<option value=\"2\">remove from WhiteList</option>"
         if ($mfd && ! $local &&  Whitelist($addr,'','') && &canUserDo($WebIP{$ActWebSess}->{user},'action','lists'));
        $option .= "<option value=\"3\">add to RedList</option>"
         if ($mfd && ! exists $Redlist{$addr} && &canUserDo($WebIP{$ActWebSess}->{user},'action','lists'));
        $option .= "<option value=\"4\">remove from RedList</option>"
         if ($mfd && exists $Redlist{$addr} && &canUserDo($WebIP{$ActWebSess}->{user},'action','lists'));
        $option .= "<option value=\"5\">add to noProcessingTo addresses</option>"
         if ($noProcessingTo=~/\s*file\s*:\s*.+/o && ! $local && ! matchSL( $addr, 'noProcessingTo' ,1) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessing'));
        $option .= "<option value=\"6\">remove from noProcessingTo addresses</option>"
         if ($noProcessingTo=~/\s*file\s*:\s*.+/o && ! $local &&  matchSL( $addr, 'noProcessing' ,1) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingTo'));
        $option .= "<option value=\"7\">add to noProcessingFrom addresses</option>"
         if ($noProcessingFrom=~/\s*file\s*:\s*.+/o && ! $local && ! matchSL( $addr, 'noProcessingFrom' ,1) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingFrom'));
        $option .= "<option value=\"8\">remove from noProcessingFrom addresses</option>"
         if ($noProcessingFrom=~/\s*file\s*:\s*.+/o && ! $local && matchSL( $addr, 'noProcessingFrom' ,1) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingFrom'));
        $option .= "<option value=\"9\">add to whitelisted domains/addresses</option>"
         if ($mfd && $whiteListedDomains=~/\s*file\s*:\s*.+/o && ! $local && $addr !~ /$WLDRE/ && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','whiteListedDomains'));
        $option .= "<option value=\"A\">remove from whitelisted domains/addresses</option>"
         if ($mfd && $whiteListedDomains=~/\s*file\s*:\s*.+/o && ! $local && $addr =~ /$WLDRE/ && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','whiteListedDomains'));
        $option .= "<option value=\"B\">add to blacklisted domains/addresses</option>"
         if ($mfd && $blackListedDomains=~/\s*file\s*:\s*.+/o && ! $local && $addr !~ /$BLDRE/ && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','blackListedDomains'));
        $option .= "<option value=\"C\">remove from blacklisted domains/addresses</option>"
         if ($mfd && $blackListedDomains=~/\s*file\s*:\s*.+/o && ! $local && $addr =~ /$BLDRE/ && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','blackListedDomains'));
        $option .= "<option value=\"D\">add to All Spam-Lover</option>"
         if ($spamLovers=~/\s*file\s*:\s*.+/o && $local && $addr !~ /$SLRE/ && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','spamLovers'));
        $option .= "<option value=\"E\">remove from All Spam-Lover</option>"
         if ($spamLovers=~/\s*file\s*:\s*.+/o && $local && $addr =~ /$SLRE/ && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','spamLovers'));
        $option .= "<option value=\"F\">add to All Spam-Haters</option>"
         if ($spamHaters=~/\s*file\s*:\s*.+/o && $local && $addr !~ /$SHRE/ && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','spamHaters'));
        $option .= "<option value=\"G\">remove from All Spam-Haters</option>"
         if ($spamHaters=~/\s*file\s*:\s*.+/o && $local && $addr =~ /$SHRE/ && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','spamHaters'));
        $option .= "<option value=\"H\">add $mfd to noProcessing domains</option>"
         if ($mfd && $noProcessingDomains=~/\s*file\s*:\s*.+/o && ! $local && $mfd !~ /$NPDRE/ && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingDomains'));
        $option .= "<option value=\"I\">remove $mfd from noProcessing domains</option>"
         if ($mfd && $noProcessingDomains=~/\s*file\s*:\s*.+/o  && ! $local && $mfd =~ /$NPDRE/ && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingDomains'));

#experimental for preHeaderRe
        my $addrRe = quotemeta($addr);
        $option .= "<option value=\"J\">add to preHeaderRe as regex</option>"
         if ($preHeaderRe=~/\s*file\s*:\s*.+/o && ! $local && $GPBmodTestList->('GUI','preHeaderRe','check','',$addrRe,0) != 2 && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','preHeaderRe'));
        $option .= "<option value=\"K\">remove regex from preHeaderRe</option>"
         if ($preHeaderRe=~/\s*file\s*:\s*.+/o && ! $local && $GPBmodTestList->('GUI','preHeaderRe','check','',$addrRe,0) == 2 && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','preHeaderRe'));
#end experimental for preHeaderRe

        $option .= "<option value=\"L\">add to no Virus-Scan addresses</option>"
         if ($noScan=~/\s*file\s*:\s*.+/o && ! $local && ! matchSL( $addr, 'noScan' ,1) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noScan'));
        $option .= "<option value=\"M\">remove from no Virus-Scan addresses</option>"
         if ($noScan=~/\s*file\s*:\s*.+/o && ! $local &&  matchSL( $addr, 'noScan' ,1) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noScan'));
    }

    return <<EOT;
$headerHTTP

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" />
  <title>$currentPage ASSP address action ($myName)</title>
  <link rel=\"stylesheet\" href=\"get?file=images/editor.css\" type=\"text/css\" />
</head>
<body onmouseover="this.focus();" ondblclick="this.select();">
<h2>add/remove addresses from lists</h2><hr>
    <div class="content">
      <form name="edit" id="edit" action="" method="post" autocomplete="off">
        <h3>address to work with</h3>
        <input name="address" size="100" autocomplete="off" value="$addr" onchange="document.forms['edit'].action.value='0';document.forms['edit'].reloaded.value='reloaded';document.forms['edit'].submit();return false;"/>
        $wrongaddr$isnameonly
        <br /><hr>
        <div style="align: left">
         <div class="shadow">
          <div class="option">
           <div class="optionValue">
            <select size="1" name="action">
             $option
            </select>
           </div>
          </div>
         </div>
        </div>
        <hr>
        <input type="submit" name="Submit" value="Submit" />&nbsp;&nbsp;&nbsp;&nbsp;
        <input type="hidden" name="reloaded" value="" />
        <input type="button" value="Close" onclick="javascript:window.close();"/>
        $slo
        <hr>
      </form>
      <br />Only configured (file:...), possible and authorized option are shown.
      <hr>
      <div class="note" id="notebox">
        <h3>results for action</h3><hr>
        $s
      </div>
    </div>
</body>
</html>

EOT
}

sub ConfigIPAction {
    my $addr = lc($qs{ip});
    $addr =~ s/^\s+//o;
    $addr =~ s/\s+$//o;
    my $wrongaddr;
    if ($addr !~ /^$IPRe$/o) {
        $wrongaddr = '<br /><span class="negative">This is not a valid IP address or a resolvable hostname!</span><br />' ;
    }
    if ($wrongaddr && $addr =~ /^$HostRe$/o) {
        my $ta = $addr;
        $addr = join(' ' ,&getRRA($ta));
        if ($addr =~ /($IPv4Re)/o) {
            $addr = $1;
        } elsif ($addr =~ /($IPv6Re)/o) {
            $addr = $1;
        } else {
            $addr = undef;
        }
        eval {$addr = inet_ntoa( scalar( gethostbyname($ta) ) );} unless $addr;
        if ($addr =~ /^$IPRe$/o ) {
            $wrongaddr = undef;
        } else {
            $addr = $ta;
        }
    }
    my $local = $addr =~ /^$IPprivate$/o || $addr eq $localhostip || $addr =~ /$LHNRE/;
    my $action = $qs{action};
    my $slo;
    $slo = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="button"  name="showlogout" value="  logout " onclick="window.location.href=\'./logout\';return false;"/></span>' if exists $qs{showlogout};
    my $s = $qs{reloaded} eq 'reloaded' ? '<span class="positive">(page was auto reloaded)</span><br /><br />' : '';

    if ($addr && $action && $qs{Submit} && ! $wrongaddr) {
        if ($action eq '1' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingIPs')) {
            my $r = $GPBmodTestList->('GUI','noProcessingIPs','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to noProcessingIPs" : "$addr not added to noProcessingIPs";
        } elsif ($action eq '2' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingIPs')) {
            my $r = $GPBmodTestList->('GUI','noProcessingIPs','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from noProcessingIPs" : "$addr not removed from noProcessingIPs";
        } elsif ($action eq '3' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','whiteListedIPs')) {
            my $r = $GPBmodTestList->('GUI','whiteListedIPs','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to whiteListedIPs" : "$addr not added to whiteListedIPs";
        } elsif ($action eq '4' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','whiteListedIPs')) {
            my $r = $GPBmodTestList->('GUI','whiteListedIPs','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from whiteListedIPs" : "$addr not removed from whiteListedIPs";
        } elsif ($action eq '5' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noDelay')) {
            my $r = $GPBmodTestList->('GUI','noDelay','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to noDelay" : "$addr not added to noDelay";
        } elsif ($action eq '6' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noDelay')) {
            my $r = $GPBmodTestList->('GUI','noDelay','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from noDelay" : "$addr not removed from noDelay";
        } elsif ($action eq '7' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','denySMTPConnectionsFrom')) {
            my $r = $GPBmodTestList->('GUI','denySMTPConnectionsFrom','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to denySMTPConnectionsFrom" : "$addr not added to denySMTPConnectionsFrom";
        } elsif ($action eq '8' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','denySMTPConnectionsFrom')) {
            my $r = $GPBmodTestList->('GUI','denySMTPConnectionsFrom','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from denySMTPConnectionsFrom" : "$addr not removed from denySMTPConnectionsFrom";
        } elsif ($action eq '9' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noBlockingIPs')) {
            my $r = $GPBmodTestList->('GUI','noBlockingIPs','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to noBlockingIPs" : "$addr not added to noBlockingIPs";
        } elsif ($action eq 'A' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noBlockingIPs')) {
            my $r = $GPBmodTestList->('GUI','noBlockingIPs','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from noBlockingIPs" : "$addr not removed from noBlockingIPs";
        } elsif ($action eq 'S' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','NPexcludeIPs')) {
            my $r = $GPBmodTestList->('GUI','NPexcludeIPs','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to NPexcludeIPs" : "$addr not added to NPexcludeIPs";
        } elsif ($action eq 'T' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','NPexcludeIPs')) {
            my $r = $GPBmodTestList->('GUI','NPexcludeIPs','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from NPexcludeIPs" : "$addr not removed from NPexcludeIPs";
        } elsif ($action eq 'B' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','denySMTPConnectionsFromAlways')) {
            my $r = $GPBmodTestList->('GUI','denySMTPConnectionsFromAlways','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to denySMTPConnectionsFromAlways" : "$addr not added to denySMTPConnectionsFromAlways";
        } elsif ($action eq 'C' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','denySMTPConnectionsFromAlways')) {
            my $r = $GPBmodTestList->('GUI','denySMTPConnectionsFromAlways','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from denySMTPConnectionsFromAlways" : "$addr not removed from denySMTPConnectionsFromAlways";
        } elsif ($action eq 'D' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb')) {
            my $t = time;
            my $data="$t $t 2";
            my $ip=&ipNetwork($addr,1);
            $PBWhite{$ip}=$data;
            $PBWhite{$addr}=$data;
            $s = "$addr added to PenaltyBox white" ;
        } elsif ($action eq 'E' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb')) {
            &pbWhiteDelete(0,$addr);
            $s = "$addr removed from PenaltyBox white" ;
        } elsif ($action eq 'F' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb')) {
            my $ip=&ipNetwork($addr, $PenaltyUseNetblocks );
            delete $PBBlack{$ip};
            delete $PBBlack{$addr};
            $s = "$addr removed from PenaltyBox black";
        } elsif ($action eq 'G' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb')) {
            delete $PTRCache{$addr};
            $s = "$addr removed from PTR Cache";
        } elsif ($action eq 'H' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb')) {
            delete $URIBLCache{$addr};
            $s = "$addr removed from URIBL Cache";
        } elsif ($action eq 'I' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb')) {
            my @record = SBCacheFind($addr);
            my $domain = [split( /\|/o, $record[2])]->[2];
            delete $WhiteOrgList{lc $domain} if $domain;
            delete $SBCache{$record[0]};
            $s = "$record[0] removed from SenderBase Cache";
        } elsif ($action eq 'J' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb')) {
            delete $RBLCache{$addr};
            $s = "$addr removed from RBL Cache";
        } elsif ($action eq 'K' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb')) {
            delete $MXACache{$addr};
            $s = "$addr removed from MXA Cache";
# L is free to be used
        } elsif ($action eq 'M' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb')) {
            delete $RWLCache{$addr};
            $s = "$addr removed from RWL Cache";
        } elsif ($action eq 'N' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noScanIP')) {
            my $r = $GPBmodTestList->('GUI','noScanIP','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to Virus-noScanIP" : "$addr not added to Virus-noScanIP";
        } elsif ($action eq 'O' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noScanIP')) {
            my $r = $GPBmodTestList->('GUI','noScanIP','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from Virus-noScanIP" : "$addr not removed from Virus-noScanIP";
        } elsif ($action) {
            $s = "<span class=\"negative\">access denied for the selected action</span>";
        }
    }
    $s = 'no action selected - or no result available' if (! $s && $qs{Submit});

    if ($s =~ /\Q$addr\E (?:added to|removed from)/ && $qs{Submit}) {
        $ConfigChanged = 1;
        &tellThreadsReReadConfig();   # reread the config
    }

    my $option  = "<option value=\"0\">select action</option>";
    if ($addr && ! $wrongaddr) {
        $option .= "<option value=\"1\">add to noProcessing IP's</option>"
         if (! $local && $noProcessingIPs=~/\s*file\s*:\s*.+/o && ! matchIP( $addr, 'noProcessingIPs',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingIPs'));
        $option .= "<option value=\"2\">remove from noProcessing IP's</option>"
         if (! $local && $noProcessingIPs=~/\s*file\s*:\s*.+/o &&  matchIP( $addr, 'noProcessingIPs',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessingIPs'));
        $option .= "<option value=\"3\">add to whitelisted IP's</option>"
         if (! $local && $whiteListedIPs=~/\s*file\s*:\s*.+/o && ! matchIP( $addr, 'whiteListedIPs',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','whiteListedIPs'));
        $option .= "<option value=\"4\">remove from whitelisted IP's</option>"
         if (! $local && $whiteListedIPs=~/\s*file\s*:\s*.+/o &&  matchIP( $addr, 'whiteListedIPs',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','whiteListedIPs'));
        $option .= "<option value=\"5\">add to noDelay IP's</option>"
         if (! $local && $noDelay=~/\s*file\s*:\s*.+/o && ! matchIP( $addr, 'noDelay',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noDelay'));
        $option .= "<option value=\"6\">remove from noDelay IP's</option>"
         if (! $local && $noDelay=~/\s*file\s*:\s*.+/o &&  matchIP( $addr, 'noDelay',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noDelay'));
        $option .= "<option value=\"7\">add to Deny Connections from these IP's</option>"
         if (! $local && $denySMTPConnectionsFrom=~/\s*file\s*:\s*.+/o && ! matchIP( $addr, 'denySMTPConnectionsFrom',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','denySMTPConnectionsFrom'));
        $option .= "<option value=\"8\">remove from Deny Connections from these IP's</option>"
         if (! $local && $denySMTPConnectionsFrom=~/\s*file\s*:\s*.+/o &&  matchIP( $addr, 'denySMTPConnectionsFrom',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','denySMTPConnectionsFrom'));
        $option .= "<option value=\"9\">add to Do not block Connections from these IP's</option>"
         if (! $local && $noBlockingIPs=~/\s*file\s*:\s*.+/o && ! matchIP( $addr, 'noBlockingIPs',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noBlockingIPs'));
        $option .= "<option value=\"A\">remove from Do not block Connections from these IP's</option>"
         if (! $local && $noBlockingIPs=~/\s*file\s*:\s*.+/o &&  matchIP( $addr, 'noBlockingIPs',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noBlockingIPs'));
        $option .= "<option value=\"S\">add to Exclude these IPs</option>"
         if (! $local && $NPexcludeIPs=~/\s*file\s*:\s*.+/o && ! matchIP( $addr, 'NPexcludeIPs',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','NPexcludeIPs'));
        $option .= "<option value=\"T\">remove from Exclude these IP's</option>"
         if (! $local && $NPexcludeIPs=~/\s*file\s*:\s*.+/o &&  matchIP( $addr, 'NPexcludeIPs',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','NPexcludeIPs'));
        $option .= "<option value=\"B\">add to Deny Connections from these IP's Strictly</option>"
         if (! $local && $denySMTPConnectionsFromAlways=~/\s*file\s*:\s*.+/o && ! matchIP( $addr, 'denySMTPConnectionsFromAlways',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','denySMTPConnectionsFromAlways'));
        $option .= "<option value=\"C\">remove from Deny Connections from these IP's Strictly</option>"
         if (! $local && $denySMTPConnectionsFromAlways=~/\s*file\s*:\s*.+/o &&  matchIP( $addr, 'denySMTPConnectionsFromAlways',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','denySMTPConnectionsFromAlways'));
        $option .= "<option value=\"D\">add to PenaltyBox white</option>"
         if (! &pbWhiteFind($addr) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb'));
        $option .= "<option value=\"E\">remove from PenaltyBox white</option>"
         if (&pbWhiteFind($addr) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb'));
        $option .= "<option value=\"F\">remove from PenaltyBox black</option>"
         if (&pbBlackFind($addr) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb'));
        $option .= "<option value=\"G\">remove from PTR Cache</option>"
         if (&PTRCacheFind($addr) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb'));
        $option .= "<option value=\"H\">remove from URIBL Cache</option>"
         if (&URIBLCacheFind($addr) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb'));
        $option .= "<option value=\"I\">remove from SenderBase Cache</option>"
         if (&SBCacheFind($addr) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb'));
        $option .= "<option value=\"J\">remove from RBL Cache</option>"
         if (&RBLCacheFind($addr) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb'));
        $option .= "<option value=\"K\">remove from MXA Cache</option>"
         if (&MXACacheFind($addr) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb'));
# L is free to be used
        $option .= "<option value=\"M\">remove from RWL Cache</option>"
         if (&RWLCacheFind($addr) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb'));
        $option .= "<option value=\"N\">add to Do Not Scan Messages from these IP\'s</option>"
         if ($noScanIP=~/\s*file\s*:\s*.+/o && ! matchIP( $addr, 'noScanIP',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noScanIP'));
        $option .= "<option value=\"O\">remove from Do Not Scan Messages from these IP\'s</option>"
         if ($noScanIP=~/\s*file\s*:\s*.+/o &&  matchIP( $addr, 'noScanIP',0,1 ) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noScanIP'));
    }

    if ($addr && ! $wrongaddr) {
        $s .= "<br /><br /><b>general IP-matches for $addr :</b><br /><br />\n";
        foreach (sort {lc($main::a) cmp lc($main::b)} keys %MakeIPRE) {
            next unless &canUserDo($WebIP{$ActWebSess}->{user},'cfg',$_);
            my $res = matchIP( $addr, $_,0,1 );
            $s .= "matches in<b> $_ </b>with <b>$res</b><br />" if $res;
        }
    }

    return <<EOT;
$headerHTTP

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" />
  <title>$currentPage ASSP IP action ($myName)</title>
  <link rel=\"stylesheet\" href=\"get?file=images/editor.css\" type=\"text/css\" />
</head>
<body onmouseover="this.focus();" ondblclick="this.select();">
<h2>add/remove IP addresses from lists</h2><hr>
    <div class="content">
      <form name="edit" id="edit" action="" method="post" autocomplete="off">
        <h3>IP-address or hostname to work with</h3>
        <input name="ip" size="20" autocomplete="off" value="$addr" onchange="document.forms['edit'].action.value='0';document.forms['edit'].reloaded.value='reloaded';document.forms['edit'].submit();return false;"/>
        $wrongaddr
        <br /><hr>
        <div style="align: left">
         <div class="shadow">
          <div class="option">
           <div class="optionValue">
            <select size="1" name="action">
             $option
            </select>
           </div>
          </div>
         </div>
        </div>
        <hr>
        <input type="submit" name="Submit" value="Submit" />&nbsp;&nbsp;&nbsp;&nbsp;
        <input type="hidden" name="reloaded" value="" />
        <input type="button" value="Close" onclick="javascript:window.close();"/>
        $slo
        <hr>
      </form>
      <br />Only configured, possible and authorized option are shown.
      <hr>
      <div class="note" id="notebox">
        <h3>results for action</h3><hr>
        $s
      </div>
    </div>
</body>
</html>

EOT
}

sub tellThreadsReReadConfig {
    if ($Config{inclResendLink}) {
        $Config{fileLogging} = 1;
        $fileLogging = 1;
    }
    &SaveConfig() if ($ConfigChanged < 2);
    &optionFilesReload();

    %LDAPNotFound = ();

    &readNorm();

    $ConfigChanged = 0;
}

sub ConfigEdit {
 my $fil  = $qs{file};
 $qs{note} = lc $qs{note};
 my $htmlfil;
 my $note = q{};
 my ($cidr,$regexp1,$regexp2);
 my ($s1,$s2,$editButtons,$option);
 my $noLineNum = '';
 $cidr=$regexp1=$regexp2=q{};

 if ($qs{note} eq '1'){
  $note = '<div class="note" id="notebox">File should have one entry per line; anything on a line following a numbersign ( #) is ignored (a comment). <a href=http://assp.cvs.sourceforge.net/viewvc/assp/asspV1/files/ target=files >newest option files are archived here</a>.';
 }
  elsif($qs{note} eq '9'){
  $note = '<div class="note" id="notebox">This matches the end of the address, so if you don\'t want to match subdomains then include the @. File should have one entry per line; anything on a line following a numbersign ( #) is ignored (a comment). Whitespace at the beginning or end of the line is ignored. </div>';
 }
 elsif($qs{note} eq '2'){
  $note = '<div class="note" id="notebox">First line specifies text that appears in the subject of report message. The remaining lines are the report message body. </div>';
 }
 elsif($qs{note} eq '3'){
  $note = '<div class="note" id="notebox">Put here comments to your assp installation.</div>';
 }
 elsif($qs{note} eq '4'){
  $note = '<div class="note" id="notebox">For removal of entries from BlackBox  use <a onmousedown="showDisp(\'8\')" target="main" href="./#noPB">noPB</a>.
For removal of entries from WhiteBox  use <a onmousedown="showDisp(\'8\')" target="main" href="./#noPBwhite">noPBwhite</a>. For  whitelisting IP addresses use <a onmousedown="showDisp(\'5\')" target="main" href="./#whiteListedIPs">Whitelisted IPs</a> or <a onmousedown="showDisp(\'4\')" target="main" href="./#noProcessingIPs">NoProcessing IPs</a>. For blacklisting use <a onmousedown="showDisp(\'2\')" target="main" href="./#denySMTPConnectionsFrom">Deny SMTP Connections From these IPs</a> and <a onmousedown="showDisp(\'2\')" target="main" href="./#denySMTPConnectionsFromAlways">Deny SMTP Connections From these IP addresses Strictly</a>.</div>';
 }
  elsif($qs{note} eq '5'){
  $note = '<div class="note" id="notebox"></div>';
 }
  elsif($qs{note} eq '6'){
  $note = '<div class="note" id="notebox">CacheEntry: <IP/Domain> <Separator(12)> <CreationDate> </div\>';
 }
  elsif($qs{note} eq '8' ){
  $note = '<div class="note" id="notebox"></div>';
 }
 elsif ($qs{note} eq 'm'){
        $fil="$base/$fil" if $fil!~/^\Q$base\E/i;
        $option  = "<option value=\"0\">select action</option>";
        $option .= "<option value=\"1\">resend mail</option>" if($CanUseEMS && $resendmail && $fil !~/\/$resendmail\//);
        $option .= "<option value=\"2\">save file</option>";
        $option .= "<option value=\"3\">copy file to notspamlog</option>" if ($fil !~/\/$notspamlog\//);
        $option .= "<option value=\"4\">copy file to spamlog</option>" if ($fil !~/\/$spamlog\//);
        $option .= "<option value=\"5\">copy file to incomingOkMail</option>" if ($fil !~/\/$incomingOkMail\//);
        $option .= "<option value=\"6\">copy file to viruslog</option>" if ($fil !~/\/$viruslog\//);
        $option .= "<option value=\"7\">copy file to correctedspam</option>" if ($fil !~/\/$correctedspam\//);
        $option .= "<option value=\"8\">copy file to correctednotspam</option>" if ($fil !~/\/$correctednotspam\//);
        $option .= "<option value=\"9\">copy file to discarded</option>" if ($fil !~/\/$discarded\//);

        $note = '<div class="note" id="notebox">To take an action, select the action and click "Do It!". To move a file to an other location, just copy and delete the file!';
$note .= '<br /> For "resend file" action install Email::Send  modules!' if !($CanUseEMS && $resendmail && $fil !~/\/$resendmail\//);
}

$regexp1 = " <span class=negative>CIDR notation like 182.82.10.0/24 cannot be used because Net::IP::Match::Regexp is not installed.</span>" if !$CanMatchCIDR;

$regexp1 = " <span class=positive>CIDR notation like 182.82.10.0/24 can be used.</span>" if $CanMatchCIDR;
$regexp2 = " Text after the IP range but before a numbersign will be used as comment to be shown in a match. For example: <br />182.82.10.0/24 Yahoo # this comment not shown" if $CanMatchCIDR;
my $replaceit;$replaceit = '<span style="align: left">Replace: <input type="text" id="find" size="20" /> with <input type="text" id="replace" size="20" /> <input type="button" value="Replace" onclick="replaceIt();" /></span>' if $qs{note} ne '8';

$cidr = " <span class=negative>Hyphenated ranges like 182.82.10.0-182.82.10.255 cannot be used because Net::CIDR::Lite is not installed.</span>  " if !$CanUseCIDRlite;
$cidr = " <span class=positive>Hyphenated ranges like 182.82.10.0-182.82.10.255 can be used.</span>" if $CanUseCIDRlite;
  if ($qs{note} eq '7'){
  $note = "<div class='note' id='notebox'>IP ranges can be defined as: 182.82.10. $regexp1 $cidr $regexp2</div>";
 }

 $s2 = '';
 if ($fil =~ /\.\./){
  $s2.='<div class="text"><span class="negative">File path includes \'..\' -- access denied</span></div>';
  mlog(0,"file path not allowed while editing file '$fil'");
 }

 else {

  $fil="$base/$fil" if $fil!~/^\Q$base\E/i;
  $fil =~ s/\/\//\//;
  $fil =~ s/\\\\/\\/;
  if ($qs{B1}=~/delete/i) {
   unlink($fil);
  }
  else {
   if (defined($qs{contents})) {
    $s1=$qs{contents};
    $s1=decodeHTMLEntities($s1);
    $s1 =~ s/\n$//; # prevents ASSP from appending a newline to the file each time it is saved.
    $s1 =~ s/\r$//;
    $s1 =~ s/\s+$//;
   # make line terminators uniform
    if ($qs{note} ne 'm') {
        $s1 =~ s/\r?\n/\n/g;
        open(my $CE,">",$fil);
        binmode $CE;
        print $CE $s1;
        close $CE;
        $s2='<span class="positive">File saved successfully</span>';
        &optionFilesReload();
    } else {      # to take actions on a mailfile
         $s1 =~ s/([^\r])\n/$1\r\n/go;
         $s1 .= "\r\n";
         my $action = $qs{fileaction};
         if ($action eq '1') {    # resend
             $s1 = "\r\n" . $s1;
             my $rfil = $fil;
             $rfil =~ s/^(\Q$base\E\/).+(\/.+$maillogExt)$/$1$resendmail$2/i;
             my ($to) = $s1 =~ /\nX-Assp-Intended-For:[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio;
             ($to) = $s1 =~ /\nto:[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio unless $to;
             my ($from) = $s1 =~ /\nX-Assp-Envelope-From:[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio;
             ($from) = $s1 =~ /\nfrom:[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio unless $from;
             $s1 =~ s/^\r\n//o;
             $s2='';
             if (! $to ) {
                 $s2 .= '<br />' if $s2;
                 $s2 .= '<span class="negative">!!! no addresses found in X-Assp-Intended-For: or TO: header line - please check !!!</span>';
             }
             if (! $from ) {
                 $s2 .= '<br />' if $s2;
                 $s2 .= '<span class="negative">!!! no addresses found in X-Assp-Envelope-From: or FROM: header line - please check !!!</span>';
             }
             if ((! $nolocalDomains && ! (localmail($to) or localmail($from)))) {
                 $s2 .= '<br />' if $s2;
                 $s2 .= '<span class="negative">!!! no local addresses found in X-Assp-Intended-For: or TO: header line - please check !!!</span>'
                     unless localmail($to);
                 $s2 .= '<br />' if $s2 =~ /span>$/o;
                 $s2 .= '<span class="negative">!!! no local addresses found in X-Assp-Envelope-From: or FROM: header line - please check !!!</span>'
                     unless localmail($from);
             }
             if (! $s2) {
                 if (open(my $CE,">",$rfil)) {
                     binmode $CE;
                     print $CE $s1;
                     close $CE;
                     $s2 .= '<span class="positive">File copied to resendmail folder</span>';
                     mlog(0,"info: request to create file: $rfil");
                     $nextResendMail = $nextResendMail < time + 3 ? $nextResendMail: time + 3;
                 } else {
                     $s2 .= '<span class="negative">unable to create file in resendmail folder - $!</span>';
                     mlog(0,"error: unable to create file in resendmail folder - $!");
                 }
             }
         } elsif ($action eq '2') {    # save
             if (open($CE,">",$fil)) {
                 binmode $CE;
                 print $CE $s1;
                 close $CE;
                 $s2='<span class="positive">File saved successfully</span>';
             } else {
                 $s2='<span class="negative">unable to save file - $!</span>';
                 mlog(0,"error: unable to save file - $!");
             }
         } elsif ($action eq '3') {    # copy to notspam
             my $rfil = $fil;
             $rfil =~s/^(\Q$base\E\/).+(\/.+$maillogExt)$/$1$notspamlog$2/i;
             if (open($CE,">",$rfil)) {
                 binmode $CE;
                 print $CE $s1;
                 close $CE;
                 $s2='<span class="positive">File copied to notspamlog folder</span>';
                 mlog(0,"info: request to create file: $rfil");
             } else {
                 $s2='<span class="negative">unable to create file in notspamlog folder - $!</span>';
                 mlog(0,"error: unable to create file in notspamlog folder - $!");
             }
         } elsif ($action eq '4') {    # copy to spam
             my $rfil = $fil;
             $rfil =~s/^(\Q$base\E\/).+(\/.+$maillogExt)$/$1$spamlog$2/i;
             if (open($CE,">",$rfil)) {
                 binmode $CE;
                 print $CE $s1;
                 close $CE;
                 $s2='<span class="positive">File copied to spamlog folder</span>';
                 mlog(0,"info: request to create file: $rfil");
             } else {
                 $s2='<span class="negative">unable to create file in spamlog folder - $!</span>';
                 mlog(0,"error: unable to create file in spamlog folder - $!");
             }
         } elsif ($action eq '5') {    # incomingOkMail
             my $rfil = $fil;
             $rfil =~s/^(\Q$base\E\/).+(\/.+$maillogExt)$/$1$incomingOkMail$2/i;
             if (open($CE,">",$rfil)) {
                 binmode $CE;
                 print $CE $s1;
                 close $CE;
                 $s2='<span class="positive">File copied to incomingOkMail folder</span>';
                 mlog(0,"info: request to create file: $rfil");
             } else {
                 $s2='<span class="negative">unable to create file in incomingOkMail folder - $!</span>';
                 mlog(0,"error: unable to create file in incomingOkMail folder - $!");
             }
         } elsif ($action eq '6') {    # viruslog
             my $rfil = $fil;
             $rfil =~s/^(\Q$base\E\/).+(\/.+$maillogExt)$/$1$viruslog$2/i;
             if (open($CE,">",$rfil)) {
                 binmode $CE;
                 print $CE $s1;
                 close $CE;
                 $s2='<span class="positive">File copied to viruslog folder</span>';
                 mlog(0,"info: request to create file: $rfil");
             } else {
                 $s2='<span class="negative">unable to create file in viruslog folder - $!</span>';
                 mlog(0,"error: unable to create file in viruslog folder - $!");
             }
         } elsif ($action eq '7') {    # correctedspam
             my $rfil = $fil;
             $rfil =~s/^(\Q$base\E\/).+(\/.+$maillogExt)$/$1$correctedspam$2/i;
             if (open($CE,">",$rfil)) {
                 binmode $CE;
                 print $CE $s1;
                 close $CE;
                 $s2='<span class="positive">File copied to correctedspam folder</span>';
                 mlog(0,"info: request to create file: $rfil");

                 my ($to) = $s1 =~ /\nX-Assp-Intended-For:[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio;
                 ($to) = $s1 =~ /\nto:[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio unless $to;
                 my ($from) = $s1 =~ /\nX-Assp-Envelope-From:[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio;
                 ($from) = $s1 =~ /\nfrom:[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio unless $from;
                 if (   ($EmailErrorsModifyWhite == 1 )
                     && $to
                     && &localmail($to)
                     && $from && lc $from ne 'assp <>'
                     && !&localmail($from)

                    )
                 {
                     my $dfh = rand(1000);
                     $Con{$dfh}->{reporttype} = 0;
                     $Con{$dfh}->{mailfrom} = $EmailAdminReportsTo || $WebIP{$ActWebSess}->{user}.'@'.$myName;
                     $Con{$dfh}->{header} = $s1;
                     for my $addr (&ListReportGetAddr($dfh)) {   # process the addresses
                         &ListReportExec($addr,$Con{$dfh});
                     }
                     $Con{$dfh}->{report} =~ s/^(?:\s|\r|\n)+//;
                     $Con{$dfh}->{report} =~ s/\r?\n/<br \/>/gos;
                     $s2 .= '<br />'.$Con{$dfh}->{report};
                     delete $Con{$dfh};

                 }
                 if ( $EmailErrorsModifyPersBlack
                     && $to
                     && &localmail($to)
                     && $from && lc $from ne 'assp <>'
                     && !&localmail($from)

                    )
                 {
                     my $dfh = rand(1000);
                     $Con{$dfh}->{reporttype} = 16;
                     $Con{$dfh}->{mailfrom} = $EmailAdminReportsTo || $WebIP{$ActWebSess}->{user}.'@'.$myName;
                     $Con{$dfh}->{header} = $s1;
                     for my $addr (&ListReportGetAddr($dfh)) {                       
                         next if $addr =~ /reportpersblack/;
                         &ListReportExec($addr,$Con{$dfh});
                     }
                     $Con{$dfh}->{report} =~ s/^(?:\s|\r|\n)+//;
                     $Con{$dfh}->{report} =~ s/\r?\n/<br \/>/gos;
                     $s2 .= '<br />'.$Con{$dfh}->{report};
                     delete $Con{$dfh};

                 }
             } else {
                 $s2='<span class="negative">unable to create file in correctedspam folder - $!</span>';
                 mlog(0,"error: unable to create file in correctedspam folder - $!");
             }
        } elsif ($action eq '9') {    # discarded
             my $rfil = $fil;
             $rfil =~s/^(\Q$base\E\/).+(\/.+$maillogExt)$/$1$discarded$2/i;
             if (open($CE,">",$rfil)) {
                 binmode $CE;
                 print $CE $s1;
                 close $CE;
                 $s2='<span class="positive">File copied to discarded folder</span>';
                 mlog(0,"info: request to create file: $rfil");
             } else {
                 $s2='<span class="negative">unable to create file in discarded folder - $!</span>';
                 mlog(0,"error: unable to create file in discarded folder - $!");
             }

         } elsif ($action eq '8') {    # correctednotspam
             my $rfil = $fil;
             $rfil =~s/^(\Q$base\E\/).+(\/.+$maillogExt)$/$1$correctednotspam$2/i;
             if (open($CE,">",$rfil)) {
                 binmode $CE;
                 print $CE $s1;
                 close $CE;
                 $s2='<span class="positive">File copied to correctednotspam folder</span>';
                 mlog(0,"info: request to create file: $rfil");

                 my ($to) = $s1 =~ /\nX-Assp-Intended-For:[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio;
                 ($to) = $s1 =~ /\nto:[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio unless $to;
                 my ($from) = $s1 =~ /\nX-Assp-Envelope-From:[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio;
                 ($from) = $s1 =~ /\nfrom:[^\<]*?<?($EmailAdrRe\@$EmailDomainRe)>?/sio unless $from;
                 if (   ($EmailErrorsModifyWhite == 1 || $EmailErrorsModifyNoP == 1)
                     && $to
                     && &localmail($to)
                     && $from
                     && lc $from ne 'assp <>'
                     && !&localmail($from)

                    )
                 {
                     my $dfh = rand(1000);
                     $Con{$dfh}->{reporttype} = 1;
                     $Con{$dfh}->{mailfrom} = $EmailAdminReportsTo || $WebIP{$ActWebSess}->{user}.'@'.$myName;
                     $Con{$dfh}->{header} = $s1;
                     for my $addr (&ListReportGetAddr($dfh)) {   # process the addresses
                         &ListReportExec($addr,$Con{$dfh});
                     }
                     $Con{$dfh}->{report} =~ s/^(?:\s|\r|\n)+//;
                     $Con{$dfh}->{report} =~ s/\r?\n/<br \/>/gos;
                     $s2 .= '<br />'.$Con{$dfh}->{report};
                     delete $Con{$dfh};
                     mlog( 0, "info: possible noprocessing and/or whitelist entrys added on File copied to correctednotspam folder" )
                       if $MaintenanceLog;
                 }
                 if ( $EmailErrorsModifyPersBlack
                     && $to
                     && &localmail($to)
                     && $from && lc $from ne 'assp <>'
                     && !&localmail($from)

                    )
                 {
                     my $dfh = rand(1000);
                     $Con{$dfh}->{reporttype} = 17;
                     $Con{$dfh}->{mailfrom} = $EmailAdminReportsTo || $WebIP{$ActWebSess}->{user}.'@'.$myName;
                     $Con{$dfh}->{header} = $s1;
                     for my $addr (&ListReportGetAddr($dfh)) {
                         next if $addr =~ /reportpersblack/;
                         &ListReportExec($addr,$Con{$dfh});
                     }
                     $Con{$dfh}->{report} =~ s/^(?:\s|\r|\n)+//;
                     $Con{$dfh}->{report} =~ s/\r?\n/<br \/>/gos;
                     $s2 .= '<br />'.$Con{$dfh}->{report};
                     delete $Con{$dfh};

                 }
             } else {
                 $s2='<span class="negative">unable to create file in correctednotspam folder - $!</span>';
                 mlog(0,"error: unable to create file in correctednotspam folder - $!");
             }
         }
         $qs{fileaction} = '0';
    }
   }
  }

  if(open($CE,"<","$fil")) {
   local $/;
   $s1=<$CE>;
   # make line terminators uniform
   $s1=~s/(?:\r?\n|\r)/\n/g;
   $s1=encodeHTMLEntities($s1);
   close $CE;
  }
  else {
   $s2='<span class="negative">'.ucfirst($!).'</span>';
  }
  $htmlfil = ($fil && $LogCharset && $LogCharset !~ /^utf-?8/i) ? Encode::encode('UTF-8', Encode::decode($LogCharset,$fil)) : $fil;
  if (-e $fil) {
      if($qs{note} eq '8') {
          $editButtons='<div><input type="button" value="Close" onclick="javascript:window.close();"/></div>';
      } elsif ($qs{note} eq 'm') {
          if ($s1 !~ /\n\.\n+$/) {
              $note .= "";
          }
          $note .= '</div>';

          $editButtons="
 <div style=\"align: left\">
  <div class=\"shadow\">
   <div class=\"option\">
    <div class=\"optionValue\">
     <select size=\"1\" name=\"fileaction\">" .
      $option . "
     </select>
    </div>
   </div>
  </div>
 </div>
 &nbsp;&nbsp;";

          $editButtons .='<input type="submit" name="B1" value="Do It!" />&nbsp;&nbsp;<input type="submit" name="B1" value="Delete file" onclick="return confirmDelete(\''.$fil.'\');"/>';

          my $nf = $fil;
          $nf =~ s{([^a-zA-Z0-9])}{sprintf("%%%02X", ord($1))}eog;

          $editButtons .='&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Close" onclick="javascript:window.close();"/>';
      } else {
          $editButtons='<div><input type="submit" name="B1" value="Save changes" />&nbsp;<input type="submit" name="B1" value="Delete file" onclick="return confirmDelete(\''.$fil.'\');"/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Close" onclick="javascript:window.close();"/></div>';
      }
  }
  else {
   $s2='<div class="text"><span class="positive">File deleted</span></div>' if $qs{B1}=~/delete/i;
   $editButtons='<div><input type="submit" name="B1" value="Save changes" />&nbsp;<input type="submit" name="B1" value="Delete file" disabled="disabled" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="button" value="Close" onclick="javascript:window.close();"/></div>';

  }        
 }
 return <<EOT;
$headerHTTP

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" />
  <title>$currentPage ASSP File Editor ($myName $fil)</title>
  <link rel=\"stylesheet\" href=\"get?file=images/editor.css\" type=\"text/css\" />
    <script type="text/javascript">
//<![CDATA[
    // Javascript code and layout adapted from TinyMCE
    // http://tinymce.moxiecode.com/
    <!--
        var wHeight=0, wWidth=0, owHeight=0, owWidth=0;

        function resizeInputs() {
        var contents = document.getElementById('contents');
        var notebox = document.getElementById('notebox');
        //alert(el2.offsetHeight);

        if (!isIE()) {
             //alert(navigator.userAgent);
             wHeight = self.innerHeight - (notebox.offsetHeight+150);
             wWidth = self.innerWidth - 50;
        } else {
             //alert(navigator.userAgent);
             wHeight = document.body.clientHeight - (notebox.offsetHeight+150);
             wWidth = document.body.clientWidth - 50;
        }

        contents.style.height = Math.abs(wHeight) + 'px';
        contents.style.width  = Math.abs(wWidth) + 'px';
        container.style.height = Math.abs(wHeight - 18) + 'px';
        }

    function isIE () {
        var check,agent;
        check=/MSIE/i;
        agent=navigator.userAgent;
        if(check.test(agent)) {
            return true;
        } 
        else {
            return false;
        }
    }

    function confirmDelete(FileName)
    {
        var strmsg ="Are you sure you wish to delete: \\n" + FileName  + "\\n This action cannot be undone";
        var agree=confirm( strmsg );
        if (agree)
            return true;
        else
            return false;
    }

function popFileEditor(filename,note)
{
  var height = (note == 0) ? 500 : (note == \'m\') ? 580 : 550;
  newwindow=window.open(
    \'edit?file=\'+filename+\'&note=\'+note,
    \'FileEditorM\',
    \'width=720,height=\'+height+\',overflow=scroll,toolbar=yes,menubar=yes,location=no,personalbar=yes,scrollbars=yes,status=no,directories=no,resizable=yes\'
  );
      // this puts focus on the popup window if we open a new popup without closing the old one.
      if (window.focus) {newwindow.focus()}
      return false;
}
function popSyncEditor(cfgParm)

{
  setAnchor(cfgParm);
  var height = 400;
  newwindow=window.open(
    \'syncedit?cfgparm=\'+cfgParm,
    \'SyncEditor\',
    \'width=720,height=\'+height+\',overflow=scroll,toolbar=yes,menubar=yes,location=no,personalbar=yes,scrollbars=yes,status=no,directories=no,resizable=yes\'
  );
      // this puts focus on the popup window if we open a new popup without closing the old one.
      if (window.focus) {newwindow.focus()}
      return false;
}

function remember()
{
  var height =  580;
  newwindow=window.open(
    \'remember\',
    \'rememberMe\',
    \'width=720,height=\'+height+\',overflow=scroll,toolbar=yes,menubar=yes,location=no,personalbar=yes,scrollbars=yes,status=no,directories=no,resizable=yes\'
  );
      // this puts focus on the popup window if we open a new popup without closing the old one.
      if (window.focus) {newwindow.focus()}
      return false;
}
function getInput() { return document.getElementById("contents").value; }
function setOutput(string) {document.getElementById("contents").value=string; }

function replaceIt() { try {
var findText = document.getElementById("find").value;
var replaceText = document.getElementById("replace").value;
setOutput(getInput().replace(eval("/"+findText+"/ig"), replaceText));
} catch(e){}}

      //-->
    //]]>
    </script>
<style type="text/css">
#container
{
    width: 40px;
    color: Gray;
    font-family: Courier New;
    font-size: 14px;
    float: left;clear: left;
    overflow: hidden;
        position: relative;
        top: 2px;
}
#divlines
{
    position: absolute;
}
</style>
</head>
<body onresize="resizeInputs();" onload="resizeInputs();" style="overflow:hidden;" onmouseover="this.focus();" ondblclick="this.select();">
    <div class="content">
      <form action="" method="post">
        <span style="float: left;"><a href="javascript:void(0);" onclick="remember();return false;"><img height=12 width=12 src="$wikiinfo" alt="open the remember me window"/></a>&nbsp; Contents of $htmlfil</span><br/><hr /><br />
        <div id="message" style="float: right">$s2</div>
        <br style="clear: both;" />
        <span style="align: left">Replace: <input type="text" id="find" size="20" /> with <input type="text" id="replace" size="20" /> <input type="button" value="Replace" onclick="replaceIt();" /></span>
<div>
<div id="container">
<div id="divlines">
</div>
</div>
        <textarea id="contents" name="contents" rows="18" style="max-height:75%;width:100%;overflow:scroll;align: right;font-size: 14px; font-family: 'Courier New',Courier,monospace; " wrap="off">$s1
        </textarea>
<script type="text/javascript">
var lines = document.getElementById("divlines");
var txtArea = document.getElementById("contents");
var nLines;
window.onload = function() {
    $noLineNum
    resizeInputs();
    refreshlines();
    txtArea.onscroll = function () {
        lines.style.top = -(txtArea.scrollTop) + "px";
        return true;
    }
    txtArea.onkeyup = function () {

      var keycode;
      if (window.event) keycode = window.event.keyCode;
      else if (e) keycode = e.which;
      else return true;

      if (keycode == 13)
         {
         nLines++;
         lines.innerHTML = lines.innerHTML + nLines + "." + "<br />";
         return false;
         }
      else
         {
         return true;
         }
    }
}

function refreshlines() {
    $noLineNum
    nLines = txtArea.value.split("\\n").length;
    var innerlines = "";
    for (i=1; i<=nLines; i++) {
        innerlines = innerlines + i + "." + "<br />";
    }
    lines.innerHTML = innerlines;
    lines.style.top = -(txtArea.scrollTop) + "px";
}
</script>

        $editButtons
      </form>
    <br />$note
</div>
    </div>
<script type="text/javascript">
if (!isIE()) {
    resizeInputs();
    refreshlines();
}
</script>
  </body>
</html>

EOT

}

sub remember {
 return <<EOT;
$headerHTTP

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" />
  <title>$currentPage ASSP remember me ($myName)</title>
  <link rel=\"stylesheet\" href=\"get?file=images/editor.css\" type=\"text/css\" />
</head>
<body onmouseover="this.focus();" ondblclick="this.select();">
    <div class="content">
      <form action="" method="post">
        <textarea  rows="10" style="max-height:25%;width:100%;overflow:scroll;align: right;font-size: 14px; font-family: 'Courier New',Courier,monospace; " wrap="on">
        </textarea>
        <textarea  rows="10" style="max-height:25%;width:100%;overflow:scroll;align: right;font-size: 14px; font-family: 'Courier New',Courier,monospace; " wrap="on">
        </textarea>
        <textarea  rows="10" style="max-height:25%;width:100%;overflow:scroll;align: right;font-size: 14px; font-family: 'Courier New',Courier,monospace; " wrap="on">
        </textarea>
        <textarea  rows="10" style="max-height:25%;width:100%;overflow:scroll;align: right;font-size: 14px; font-family: 'Courier New',Courier,monospace; " wrap="on">
        </textarea>
      </form>
    </div>
</body>
</html>

EOT
}

sub webConfig{
    my $r = '';
    my @tmp;
    &niceConfig();
    $ConfigChanged = 0;

    # don't post partial data if somebody's browser's busted
    undef %qs unless $qs{theButton} || $qs{theButtonX};
    undef %qs if $qs{theButtonRefresh};
    my $counter = 0;
    foreach my $c (@ConfigArray) {
        if ( @{$c} == 5 ) {

            # Is a header
            @tmp = @{$c};
            push( @tmp, "setupItem$counter" );
            $r .= $c->[3]->(@tmp);
            $counter++;
        } else {

            # Is a variable
            $r .= $c->[3]->( @{$c} );
        }
    }
    if ($ConfigChanged) {
        SaveConfig();
        renderConfigHTML();
        PrintConfigDefaults();
    }
    my $regexp1 = "";
    my $regexp2 = "";
my $reload;

$reload  = '<table class="textBox" style="width: 99%;">
 <tr><td class="noBorder" align="right">Panic Button:</td></tr>
<tr><td class="noBorder" align="right"><form action="quit" method="post"><input type="submit" value="Terminate Now!" /></form></td></tr>
</table>';
$reload  = '<table class="textBox" style="width: 99%;">
 <tr><td class="noBorder" align="center">Restart Button:</td><td class="noBorder" align="right">Panic Button:</td></tr>
<tr>  <td class="noBorder" align="center"><form action="autorestart" method="post"><input type="submit" value="Restart Now!" /></form></td><form action="quit" method="post"><td class="noBorder" align="right"><input type="submit" value="Terminate Now!" /></td></form></tr>
</table>' if $AutoRestartCmd;

    $regexp1 =
"If Net::IP::Match::Regexp is installed  CIDR notation is allowed(182.82.10.0/24)."
      if !$CanMatchCIDR;
    $regexp2 =
"<br />If Net::IP::Match::Regexp is installed, Text after the range (and before a numbersign) will be accepted as comment which will be shown in a match (for example: 182.82.10.0/24 Yahoo Groups #comment not shown)."
      if !$CanMatchCIDR;
    $regexp1 = "CIDR notation is accepted (182.82.10.0/24)." if $CanMatchCIDR;
    $regexp2 =
"<br />Text after the range (and before a numbersign) will be accepted as comment to be shown in a match. For example:<br />182.82.10.0/24 Yahoo #comment to be removed"
      if $CanMatchCIDR;
    my $cidr = "";

    $cidr =
"If Net::CIDR::Lite is installed, hyphenated ranges can be used (182.82.10.0-182.82.10.255)."
      if !$CanUseCIDRlite;
    $cidr = "Hyphenated ranges can be used (182.82.10.0-182.82.10.255)."
      if $CanUseCIDRlite;
 my $quit;

$quit  = '<table class="textBox" style="width: 99%;">
 <tr><td class="noBorder" align="left">Restart Button:</td><td class="noBorder" align="right">Panic Button:</td></tr>
<tr><td class="noBorder" align="left"><form action="autorestart" method="post"><input type="submit" value="Restart Now!" /></form></td><td class="noBorder" align="right"><form action="quit" method="post"><input type="submit" value="Terminate Now!" /></form></td></tr>
</table>';

my $runas =  $AsAService ? ' (as service)' : $AsADaemon ? ' (as daemon)' : ' (console mode)';
$runas = ' (as secondary)' if $AsASecondary;
my $secondaryrunning;$secondaryrunning = " 'kill -PIPE $SecondaryPid' will terminate Secondary" if $SecondaryPid;
my $pathhint = $^O eq 'MSWin32' ? 'For defining any full filepathes, always use slashes ("/") not backslashes. For example: c:/assp/certs/server-key.pem !<br /><br />' : '';
    <<EOT;
$headerHTTP
$headerDTDTransitional
$headers
$footers
<div class="content">

<form name="ASSPconfig" id="ASSPconfig" action="" method="post">
<div>
$r
</div>
<div class="rightButton">
  <input name="theButton" type="submit" value="Apply Changes" />
  <input name="theButtonRefresh" type="hidden" value="" />
  <input name="theButtonX" type="hidden" value="" />
</div>
<div class="textBox">
$asspWarnings<br />
</div>
<div class="textBox">
$pathhint
$lngmsg{'msg500018'}
IP ranges are defined as for example \'182.82.10.\'. $regexp1 $cidr $regexp2.
$lngmsg{'msg500019'}

</div>
</form>
$reload

<br />
$kudos
<br />
</div>
$footers
<script type="text/javascript">
<!--
  expand(0, 0);
  string = new String(document.location);
  string = string.substr(string.indexOf('#')+1);
  if(document.forms[0].length) {
    for(i = 0; i < document.forms[0].elements.length; i++) {
      if(string == document.forms[0].elements[i].name) {
        document.forms[0].elements[i].focus();
      }
    }
  }
  initAnchor('0');
// -->
</script>
</body></html>
EOT
}

sub Donations {
    <<EOT;
$headerHTTP
$headerDTDTransitional
$headers

<div class="content">
<h2>ASSP Kudos</h2>
<div class="note">
ASSP is here thanks to the following people:

<br />
<table style="width: 99%;" class="textBox">
<tr>
<td class="note">John Hanna the founder and developer of ASSP's first versions.</td>
<td class="underline"></td>
</tr>

<tr>
<td class="note">Fritz Borgstedt(&dagger;&nbsp; 5/2014)/Thomas Eckardt :  1.1.0 - 1.9.9 </td>
<td class="underline"><a href="https://www.paypal.com/xclick/business=Thomas.Eckardt%40thockar.com&amp;item_name=Support+ASSP&amp;item_number=assp&amp;no_note=1&amp;tax=0&amp;currency_code=USD" rel="external">Donate</a></td>
</tr>
<tr>
<td class="note">Thomas Eckardt : ASSP V2 Pro since 2.0.0 </td>
<td class="underline"><a href="https://www.paypal.com/xclick/business=Thomas.Eckardt%40thockar.com&amp;item_name=Support+ASSP&amp;item_number=assp&amp;no_note=1&amp;tax=0&amp;currency_code=USD" rel="external">Donate</a></td>
</tr>
<tr>
<td class="underline">bitcoins are also welcome</td>
<td class="underline">15ekjW9grtT7WTUFMcfbmokwoomZCYeMKr</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td colspan="2">
<div class="underline">Special thanks to......<br /><br />
&nbsp;&nbsp;  John Calvi : ASSP  version 1.0.12<br />
&nbsp;&nbsp;  Przemek Czerkas : SRS, Greylisting, Maillog Search.
&nbsp;&nbsp;  Robert Orso : LDAP functions.<br />
&nbsp;&nbsp;  AJ : web interface &amp; site.<br />
&nbsp;&nbsp;  Nigel Barling : SPF &amp; RBL.<br />
&nbsp;&nbsp;  Mark Pizzolato : SMTP Session Limits.<br />
&nbsp;&nbsp;  Craig Schmitt : SPF2.<br />
&nbsp;&nbsp;  J.R. Oldroyd : SSL support, IPv6 support, griplist/stats upload/download.<br /><br />

&nbsp;&nbsp;  Wim Borghs, Doug Traylor, Lars Troen, Marco Tomasi,
&nbsp;&nbsp;  Andrew Macpherson, Marco Michelino, Matti Haack, Dave Emory 
<br />

</div>
</td>
</tr>
</table>
<br />
$kudos
<br />
</div>
$footers
</body></html>
EOT
}

sub HTTPStrToTime {
    my $str = shift;
    if ( $str =~
/[SMTWF][a-z][a-z], (\d\d) ([JFMAJSOND][a-z][a-z]) (\d\d\d\d) (\d\d):(\d\d):(\d\d) GMT/
      )
    {
        my %MoY =
          qw(Jan 1 Feb 2 Mar 3 Apr 4 May 5 Jun 6 Jul 7 Aug 8 Sep 9 Oct 10 Nov 11 Dec 12);
        return eval {
            my $t =
              Time::Local::timegm( $6, $5, $4, $1, $MoY{$2} - 1, $3 - 1900 );
            $t < 0 ? undef : $t;
        };
    } else {
        return;
    }
}

sub GetFile {
    my $fil = $qs{file};
    my %mimeTypes;
    if ( $fil =~ /\.\./ ) {
        mlog( 0, "file path not allowed while getting file '$fil'" );
        return <<EOT;
HTTP/1.1 403 Forbidden
Content-type: text/html

<html><body><h1>Forbidden</h1>
</body></html>
EOT
    }

    if ( $fil !~ /^\Q$base\E/i ) {
        $fil = "$base/$fil";
    }
    if ( -e $fil ) {
        my $mtime;
        if ( defined( $mtime = $head{'if-modified-since'} ) ) {
            if ( defined( $mtime = HTTPStrToTime($mtime) ) ) {
                if ( $mtime >= [ stat($fil) ]->[9] ) {
                    return "HTTP/1.1 304 Not Modified\n\r\n\r";
                }
            }
        }
        if ( open( $FH, "<","$fil" ) ) {
            binmode $FH;
            local $/;
            my $s = <$FH>;
            close $FH;
            %mimeTypes = (
                'log|txt|pl' => 'text/plain',
                'htm|html'   => 'text/html',
                'css'        => 'text/css',
                'bmp'        => 'image/bmp',
                'gif'        => 'image/gif',
                'jpg|jpeg'   => 'image/jpeg',
                'png'        => 'image/png',
                'zip'        => 'application/zip',
                'sh'         => 'application/x-sh',
                'gz|gzip'    => 'application/x-gzip',
                'exe'        => 'application/octet-stream',
                'js'         => 'application/x-javascript'
            );
            my $ct = 'text/plain';    # default content-type
            foreach my $key ( keys %mimeTypes ) {
                $ct = $mimeTypes{$key} if $fil =~ /\.($key)$/i;
            }
            $mtime = [ stat($fil) ]->[9];
            $mtime = gmtime($mtime);
            $mtime =~
              s/(...) (...) +(\d+) (........) (....)/$1, $3 $2 $5 $4 GMT/;
            return <<EOT;
HTTP/1.1 200 OK
Content-type: $ct
Last-Modified: $mtime

$s
EOT
        }
    }
    return <<EOT;
HTTP/1.1 404 Not Found
Content-type: text/html

<html><body><h1>Not found</h1>
</body></html>
EOT

}

sub Shutdown {
my $title = "ASSP Shutdown/Restart";
$title = "Primary & Secondary Shutdown/Restart" if $AsASecondary;
my $restart;$restart = '<tr>
<td style="background-color: white; padding: 0px;"><form action="restartsecondary" method="post">
<table style="background-color: white; border-width: 0px; width: 600px">
  <tr><td style="background-color: white; padding: 0px;" align="center">Secondary:</td></tr>
  <tr><td style="background-color: white; padding: 0px;" align="center"><input type="submit" name="action" value="Restart Secondary ASSP Now!" /></td></tr>
</table>
</form>
</td>
</tr>' if $AutostartSecondary;

    <<EOT;
$headerHTTP
$headerDTDTransitional
$headers

<div class="content">
<h2>$title</h2>
<div class="note">
Note: It's possible to restart, if ASSP runs as a service or in a script that restarts it after it stops or AutoRestartCmd is configured<br />
The following AutoRestartCmd will be started in OS-shell, if ASSP does not run as a service:<br /><b><font color=red>'$AutoRestartCmd'</font></b>
</div>

<br />
<table style="background-color: white; border-width: 0px; width: 600px">
<tr>
<td style="background-color: white; padding: 0px;">
<iframe src="/shutdown_frame" width="100%" height="300" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</td>
</tr>
$restart
</table>

</div>

$footers
</body></html>
EOT
}

sub ShutdownFrame {
    my $action = $qs{action};

    my ( $s1, $s2, $editButtons, $query, $refresh );
    my $shutdownDelay = 2;

    my $timerJS = '
<script type="text/javascript">
  var ns=(navigator.appName.indexOf("Netscape")!=-1);
  var timerVal=parseInt(ns ? document.getElementById("myTimer1").childNodes[0].nodeValue : myTimer1.innerText);
  function countDown() {
    if (isNaN(timerVal)==0 && timerVal>=0) {
      if (ns) {
        document.getElementById("myTimer1").childNodes[0].nodeValue=timerVal--;
      } else {
        myTimer1.innerText=timerVal--;
      }
      setTimeout("countDown()",1000);
    }
  }
  countDown();
</script>';
    if ( $action =~ /abort/i ) {
        $shuttingDown = 0;
        $refresh      = 3;
        $s1           = 'Shutdown request aborted';
        $editButtons =
'<input type="submit" name="action" value=" Proceed " disabled="disabled" />&nbsp;
<input type="submit" name="action" value=" Abort " disabled="disabled" />';
        $doShutdown = 0;
        $query      = '?nocache';
        mlog( 0,
"shutdown/restart process aborted per admin request; SMTP session count:$smtpConcurrentSessions"
        );
    } elsif ( $action =~ /proceed/i || $shuttingDown ) {
        $shuttingDown = 1;
        $refresh = $smtpConcurrentSessions > 0 ? 2 : 60;
        $s1 =
          $smtpConcurrentSessions > 0
          ? 'Waiting for '
          . needEs( $smtpConcurrentSessions, ' SMTP session', 's' )
          . ' to finish ...'
          : "Shutdown in progress, please wait: <span id=\"myTimer1\">$refresh</span>s$timerJS";
        $editButtons =
'<input type="submit" name="action" value=" Proceed " disabled="disabled" />&nbsp;
<input type="submit" name="action" value=" Abort "'
          . ( $smtpConcurrentSessions > 0 ? '' : ' disabled="disabled"' ) . ' />
'
          . (
            $refresh > 1
            ? ''
            : '&nbsp;<input type="button" name="action" value=" View " onclick="javascript:window.open(\'shutdown_list?\',\'SMTP_Connections\',\'width=600,height=600,toolbar=no,menubar=no,location=no,personalbar=no,scrollbars=yes,status=no,directories=no,resizable=yes\')" />'
          ) . '';

        $doShutdown = $smtpConcurrentSessions > 0 ? 0 : time + $shutdownDelay;
        $query = $smtpConcurrentSessions > 0 ? '?nocache' : '?action=Success';
        mlog( 0,
"shutdown/restart process initiated per admin request; SMTP session count:$smtpConcurrentSessions"
        ) if $action =~ /proceed/i;
    } elsif ( $action =~ /success/i ) {
        $refresh = 3;
        $s1      = 'ASSP restarted successfully.';
        $editButtons =
'<input type="submit" name="action" value=" Proceed Shutdown " disabled="disabled" />&nbsp;
<input type="submit" name="action" value=" Abort " disabled="disabled" />' if  !$AutoRestartCmd;
        $editButtons =
'<input type="submit" name="action" value=" Proceed Restart " disabled="disabled" />&nbsp;
<input type="submit" name="action" value=" Abort " disabled="disabled" />' if  $AutoRestartCmd;
        $doShutdown = 0;
        $query      = '?nocache';
    } else {
        $refresh = 1;

        $s1 =
          $smtpConcurrentSessions > 0
          ? ( $smtpConcurrentSessions > 1 ? 'There are ' : 'There is ' )
          . needEs( $smtpConcurrentSessions, ' SMTP session', 's' )
          . ' active'
          : 'There are no active SMTP sessions';
        $editButtons =
          '<input type="submit" name="action" value=" Proceed Shutdown " />&nbsp;
<input type="submit" name="action" value=" Abort " disabled="disabled" />&nbsp;
<input type="button" name="action" value=" View " onclick="javascript:window.open(\'shutdown_list?\',\'SMTP_Connections\',\'width=800,height=600,toolbar=no,menubar=no,location=no,personalbar=no,scrollbars=yes,status=no,directories=no,resizable=yes\')" />' if  !$AutoRestartCmd;
        $editButtons =
          '<input type="submit" name="action" value=" Proceed Restart " />&nbsp;
<input type="submit" name="action" value=" Abort " disabled="disabled" />&nbsp;
<input type="button" name="action" value=" View " onclick="javascript:window.open(\'shutdown_list?\',\'SMTP_Connections\',\'width=800,height=600,toolbar=no,menubar=no,location=no,personalbar=no,scrollbars=yes,status=no,directories=no,resizable=yes\')" />' if  $AutoRestartCmd;

        $s1 = $editButtons = '' if $AsASecondary;
        $doShutdown = 0;
        $query      = '?nocache';
    }

my $quit;
$quit  = '<form action="quit" method="post">
<table class="textBox" style="width: 99%;">
  <tr><td class="noBorder" align="center">Panic Button:</td></tr>
  <tr><td class="noBorder" align="center"><input type="submit" name="action" value="Terminate ASSP Now!" /></td></tr>
</table>
</form>' unless $AsAService;
$quit  = '<form action="autorestart" method="post">
<table class="textBox" style="width: 99%;">
  <tr><td class="noBorder" align="center">Panic Button:</td></tr>
  <tr><td class="noBorder" align="center"><input name="action" type="submit" value="Restart ASSP Now!" /></td></tr>
</table>
</form>' if $AutoRestartCmd;

my $bod = $action=~/success/i ? '<body onload="top.location.href=\'/#\'">' : '<body>' ;
    <<EOT;
$headerHTTP
$headerDTDTransitional
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" />
  <meta http-equiv="refresh" content="$refresh;url=/shutdown_frame$query" />
  <title>ASSP ($myName)</title>
  <link rel=\"stylesheet\" href=\"get?file=images/shutdown.css\" type=\"text/css\" />
</head>
$bod
<div class="content">
<form action="" method="get">
  <table class="textBox">
    <tr>
      <td class="noBorder" nowrap>
        $editButtons&nbsp;&nbsp;&nbsp;$s1
      </td>
    </tr>

  </table>
</form>
</div>
<div class="content">

$quit
</div>
</body></html>
EOT

}

sub ShutdownList {

    my $action = $qs{action};
    my ( $s1, $s2, $editButtons, $query, $refresh );
    my $nocache = $qs{nocache};
    my $showcolor = $qs{showcolor} ? 1 : 0;
    my $forceRefresh = $qs{forceRefresh};
    my $rowclass;
    my $shutdownDelay = 2;
    my %conperwo = ();
    my $key;

 $query  = '?nocache='.time;
 $query .= '&forceRefresh=1' if $forceRefresh;
 my $focusJS = '
<script type="text/javascript">
 Timer=setTimeout("newTimer();", 5000);
 ';
 $focusJS .= $forceRefresh ? '
 var Run = 1;
 function tStop () {
    Run = 1;
 }
 '
 :
 '
 var Run = 1;
 function tStop () {
    Run = 0;
    Timer=setTimeout("noop();", 3000);
 }
 ';

 $focusJS .= '
 function noop () {}
 var Run2 = 1;
 var linkBG;
 var showcolor = '.$showcolor.';
//noprint
 setcolorbutton();
 function startstop() {
     Run2 = (Run2 == 1) ? 0 : 1;
     document.getElementById(\'stasto\').value = (Run2 == 1) ? "Stop" : "Start";
 }
 function tStart () {
    Run = 1;
 }
 function newTimer() {
   if (Run == 1 && Run2 == 1) {window.location.reload();}
   Timer=setTimeout("newTimer();", 5000);
 }
//endnoprint
function popAddressAction(address)
{
  var height = 500 ;
  var link = address ? \'?address=\'+address : \'\';
  newwindow=window.open(
    \'addraction\'+link,
    \'AddressAction\',
    \'width=720,height=\'+height+\',overflow=scroll,toolbar=yes,menubar=yes,location=no,personalbar=yes,scrollbars=yes,status=no,directories=no,resizable=yes\'
  );
      // this puts focus on the popup window if we open a new popup without closing the old one.
      if (window.focus) {newwindow.focus()}
      return false;
}

function popIPAction(ip)
{
  var height = 500 ;
  var link = ip ? \'?ip=\'+ip : \'\';
  newwindow=window.open(
    \'ipaction\'+link,
    \'IPAction\',
    \'width=720,height=\'+height+\',overflow=scroll,toolbar=yes,menubar=yes,location=no,personalbar=yes,scrollbars=yes,status=no,directories=no,resizable=yes\'
  );
      // this puts focus on the popup window if we open a new popup without closing the old one.
      if (window.focus) {newwindow.focus()}
      return false;
}
//noprint
function switchcolor () {
    showcolor = (showcolor == 1) ? 0 : 1;
    setcolorbutton();
    setcolor();
}

function setcolor () {
    if (showcolor == 1) {
        window.location.href=\'./shutdown_list?nocache='.$nocache.'&forceRefresh='.$forceRefresh.'&showcolor=1\';
    } else {
        window.location.href=\'./shutdown_list?nocache='.$nocache.'&forceRefresh='.$forceRefresh.'&showcolor=0\';
    }
}

function setcolorbutton () {
    if (showcolor == 1) {
        document.getElementById(\'colorbutton\').value = "color-off";
    } else {
        document.getElementById(\'colorbutton\').value = "color-on";
    }
}
//endnoprint
</script>
';
    $refresh = 1;
    $query   = '?nocache';
    $s1      = '<div style="float: left;">';
    $s1 .=
      $smtpConcurrentSessions > 0
      ? ( $smtpConcurrentSessions > 1 ? 'There are ' : 'There is ' )
      . needEs( $smtpConcurrentSessions, ' SMTP session', 's' )
      . ' active.</div>'
      : 'There are no active SMTP sessions.' . '</div>';
    $s1 .= '<div style="float: right;">'  . '</div><br />';

    $s2 =
            "<tr><td class=\"conTabletitle\"># TLS</td><td class=\"conTabletitle\">Remote IP</td><td class=\"conTabletitle\">HELO</td><td class=\"conTabletitle\">From</td><td class=\"conTabletitle\">Rcpt</td><td class=\"conTabletitle\">CMD</td><td class=\"conTabletitle\">RY/NP/WL</td><td class=\"conTabletitle\">SPAM</td><td class=\"conTabletitle\">Bytes</td><td class=\"conTabletitle\">Duration</td><td class=\"conTabletitle\">Idle</td></tr>";

    my $tmpTimeNow = time();

    my @tmpConKeys = keys(%Con);
    my @tmpConSortedKeys =
      sort { $Con{$a}->{timestart} <=> $Con{$b}->{timestart} } @tmpConKeys;
    my $tmpCount = 0;
    foreach my $key (@tmpConSortedKeys) {
        if ( $Con{$key}->{ip} ) {
            $tmpCount++;
            $Con{$key}->{messagescore} ||= 0;
            my $tmpScore;$tmpScore = ' / ' . $Con{$key}->{messagescore} if $Con{$key}->{messagescore};
            $Con{$key}->{spamfound} ||= $Con{$key}->{lastcmd} =~ /error/io;
            my $tmpDuration = $tmpTimeNow - $Con{$key}->{timestart};
            my $tmpInactive = $tmpTimeNow - $Con{$key}->{timelast};
            my $relay = $Con{$key}->{relayok} ? 'OUT' : 'IN';
            $relay .= '/NP' if $Con{$key}->{noprocessing};
            $relay .= '/WL' if $Con{$key}->{whitelisted};

            my $bgcolor;
            $bgcolor = ' style="background-color:#7CFC7F;"' if $Con{$key}->{whitelisted};
            $bgcolor = ' style="background-color:#7CFC00;"' if $Con{$key}->{noprocessing};
            $bgcolor = ' style="background-color:#7CFCFF;"' if $Con{$key}->{relayok};
            my $cc = 255;
            $cc -= int($Con{$key}->{messagescore} * 127 / $MessageScoringUpperLimit) if $MessageScoringUpperLimit;
            $cc = 63 if $MessageScoringLowerLimit && $Con{$key}->{messagescore} >= $MessageScoringLowerLimit;
            $cc = 0 if $cc < 0 or ($MessageScoringUpperLimit && $Con{$key}->{messagescore} >= $MessageScoringUpperLimit);
            $cc = sprintf("%02X",$cc);
            $bgcolor = ' style="background-color:#FF'.$cc.'00;"' if $Con{$key}->{messagescore} > 0;
            $bgcolor = ' style="background-color:#FF0000;"' if $Con{$key}->{spamfound};

            if ($tmpCount%2==1) {
                    $rowclass = "\n<tr$bgcolor>";
            } else {
                    $rowclass = "\n<tr class=\"even\"$bgcolor>";
            }

            $s2 .= $rowclass
                  . "<td $bgcolor><b>"
                  . ( $tmpCount ) . ' ' .$Con{$key}->{ssl}.$Con{$key}->{friendssl}
                  . "</b></td><td $bgcolor>" .

                  (
                  (! $Con{$key}->{relayok} )
                  ? (
                        "<span onclick=\"popIPAction('"
                      . &normHTML($Con{$key}->{ip})
                      . "');\" onmouseover=\"linkBG=this.style.backgroundColor; this.style.backgroundColor='#BBBBFF';\" onmouseout=\"this.style.backgroundColor=linkBG;\"><b>"
                      . $Con{$key}->{ip}
                      . "<\/b><\/span>"
                    )
                  :
                      $Con{$key}->{ip}
                  )

                  . "</td><td $bgcolor>"
                  . substr( $Con{$key}->{helo}, 0, 25 )
                  . "</td><td $bgcolor>" .

                  (
                  (! $Con{$key}->{relayok} )
                  ? (
                        "<span onclick=\"popAddressAction('"
                      . &encHTMLent($Con{$key}->{mailfrom})
                      . "');\" onmouseover=\"linkBG=this.style.backgroundColor; this.style.backgroundColor='#BBBBFF';\" onmouseout=\"this.style.backgroundColor=linkBG;\"><b>"
                      . substr( $Con{$key}->{mailfrom}, 0, 25 )
                      . "<\/b><\/span>"
                    )
                  :
                      substr( $Con{$key}->{mailfrom}, 0, 25 )
                  )

                  . "</td><td $bgcolor>"
                  . substr( $Con{$key}->{rcpt}, 0, 25 )
                  . "</td><td $bgcolor>"
                  . $Con{$key}->{lastcmd}
                  . "</td><td $bgcolor>"
                  . $relay
                  . "</td><td $bgcolor>"
                  . (($Con{$key}->{spamfound}) ? 'Y' : 'N') .  $tmpScore
                  . "</td><td $bgcolor>"
                  . $Con{$key}->{maillength}
                  . "</td><td $bgcolor>"
                  . $tmpDuration
                  . "</td><td $bgcolor>"
                  . $tmpInactive
                  . '</td></tr>';
        }
    }
    $showcolor = $showcolor ? 'color-off' : 'color-on';
    my $ctime = localtime();
    <<EOT;
$headerHTTP
$headerDTDTransitional
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" />
  $focusJS
  <title>$currentPage ASSP ($myName) this monitor will slow down ASSP dramaticly - use it careful</title>
  <link rel=\"stylesheet\" href=\"get?file=images/assp.css\" type=\"text/css\" />
</head>
<body onfocus="tStart();" onblur="tStop();">
<div >
<div style="float: right">$ctime\&nbsp;\&nbsp;
\&nbsp;\&nbsp;

<input id="stasto" type="button" value="Stop" onclick="javascript:startstop();"/>
\&nbsp;\&nbsp;
<input type="button" value="Close" onclick="javascript:window.close();"/></div>
<h2>SMTP Connections List</h2>
$s1
<table cellspacing="0" id="conTable">
$s2
</table>
<br />
</div>
</body></html>
EOT
}

sub SaveConfig {
 mlog( 0, "saving config" ,1);
 if ($AsASecondary && $PrimaryPid) {        
        kill HUP => $PrimaryPid;
}
 my $content;
 my $SC;
 local $/ = undef;
 open($SC,'>',\$content);

 my $enc = ASSP::CRYPT->new($Config{webAdminPassword},0);

 foreach my $c (@ConfigArray) {
   next if $c->[0] eq "0";
   if (exists $cryptConfigVars{$c->[0]}) {
       my $var = $enc->ENCRYPT($Config{$c->[0]});
       print $SC "$c->[0]:=$var\n";
   } else {
       print $SC "$c->[0]:=$Config{$c->[0]}\n";
   }
 }
 foreach my $c (sort keys %ConfigAdd) {
   if (exists $cryptConfigVars{$c}) {
       my $var = $enc->ENCRYPT($ConfigAdd{$c});
       print $SC "$c:=$var\n";
   } else {
       print $SC "$c:=$ConfigAdd{$c}\n";
   }
 }
 print $SC "ConfigSavedOK:=1\n";
 close $SC;

 if (open($SC, '<', "$base/assp.cfg")) {
     my $current = (<$SC>);
     close $SC;
     if ($current eq $content) {
         mlog(0,"info: no configuration changes detected - nothing to save - file $base/assp.cfg is unchanged");
         return;
     }
 } else {
     mlog(0,"warning: unable to read the current config in $base/assp.cfg");
 }

 unlink("$base/assp.cfg.bak.bak.bak") or mlog(0,"error: unable to delete file $base/assp.cfg.bak.bak.bak - $!");
 rename("$base/assp.cfg.bak.bak","$base/assp.cfg.bak.bak.bak") or mlog(0,"error: unable to rename file $base/assp.cfg.bak.bak to $base/assp.cfg.bak.bak.bak - $!");
 rename("$base/assp.cfg.bak","$base/assp.cfg.bak.bak") or mlog(0,"error: unable to rename file $base/assp.cfg.bak to $base/assp.cfg.bak.bak - $!");
 $FileUpdate{"$base/assp.cfgasspCfg"} = 0;

 open($SC,'>',"$base/assp.cfg.tmp");
 print $SC $content;
 close $SC;
 mlog(0,"info: saved config to $base/assp.cfg.tmp - which is now renamed to $base/assp.cfg");

 rename("$base/assp.cfg","$base/assp.cfg.bak") or mlog(0,"error: unable to rename file $base/assp.cfg to $base/assp.cfg.bak - $!");
 rename("$base/assp.cfg.tmp","$base/assp.cfg") or mlog(0,"error: unable to rename file $base/assp.cfg.tmp to $base/assp.cfg - $!");
 $asspCFGTime = $FileUpdate{"$base/assp.cfgasspCfg"} = ftime("$base/assp.cfg");
 mlog( 0, "finished saving config" ,1);
}

sub textinput {my ($name,$nicename,$size,$func,$default,$valid,$onchange,$description,$cssoption,$note,$lngNice,$lngDesc)=@_;
  my $Error = checkUpdate( $name, $valid, $onchange, $nicename);
  my $value = encodeHTMLEntities( $Config{$name} );
  my $hdefault = encodeHTMLEntities($default);

  my $showdefault;
  if (exists $WebIP{$ActWebSess}->{lng}->{$lngNice}) {
     $nicename = $WebIP{$ActWebSess}->{lng}->{$lngNice};
  }
  if (exists $WebIP{$ActWebSess}->{lng}->{$lngDesc}) {
     $description = $WebIP{$ActWebSess}->{lng}->{$lngDesc};
  }
  $description = &niceLink($description);
  $hdefault =~ s/'|"|\n//g;
  $hdefault =~ s/\\/\\\\/g;
  $showdefault = $hdefault ? $hdefault : '&nbsp;';
  my $shortdefault = $showdefault;
  $shortdefault = substr( $showdefault, 0, 40 ) . "..." if length ($showdefault) > 40;
  my $color = ($value eq $hdefault) ? '' : 'style="color:#8181F7;"';

  $default = (!$hdefault or $value eq $hdefault) ? '' : ", default=$shortdefault";
  my $cfgname = $EnableInternalNamesInDesc?"<a href=\"javascript:void(0);\"$color onmousedown=\"document.forms['ASSPconfig'].$name.value='$hdefault';return false;\" onmouseover=\"showhint('<table BORDER CELLSPACING=0 CELLPADDING=4 WIDTH=\\'100%\\'><tr><td>click to reset<br />to default value</td><td>$showdefault</td></tr></table>', this, event, '450px', ''); return true;\" onmouseout=\"window.status='';return true;\"><i> ($name$default)</i></a>":'';
  $cfgname .= syncShowGUI($name);
  my $edit  =  '';
  my $display = '';
  $note=1 if !$note;
  if ($name !~ /^adminuser/io &&
     $name ne 'pbdb' &&
     ($value=~/^\s*file:\s*(.+)\s*/io or
      $value=~/^\s*(DB):\s*/io or
      $name eq 'griplist' or
      exists $ReportFiles{$name})
    )
  {
      # the optionlist is actually saved in a file or is a DB.
      my $fil;
      $fil = $1 if ($value=~/^\s*file:\s*(.+)\s*/io or $value=~/^\s*(DB):\s*/io);
      my $what = 'file';
      if ($fil eq 'DB') {
          $what = 'list';
          $fil .= "-$name";
      }
      if ($name eq 'griplist') {
          $fil = $griplist;
          $note = 8;
      }
      my $act = $note == 8 ? 'Show' : 'Edit' ;
      my $ifil = $fil;
      if ($fil) {
          $fil  =~ s/([^\w\-.!~*\'() ])/sprintf("%%%02X",ord($1))/ego;
          $fil  =~ s/ /+/go;
          $edit = "<input type=\"button\" value=\" $act $what \" onclick=\"javascript:popFileEditor(\'$fil\',$note);setAnchor('$name');\" /><br />";
      }

      my @reportIncludes;
      if (exists $ReportFiles{$name}) {
        my $what = "report file: $ReportFiles{$name}";
        my $note = 2;
        @reportIncludes = ReportIncludes($ReportFiles{$name});
        my $fil = $ReportFiles{$name};
        $fil  =~ s/([^\w\-.!~*\'() ])/sprintf("%%%02X",ord($1))/ego;
        $fil  =~ s/ /+/go;
        $edit .= "<input type=\"button\" value=\" $act $what \" onclick=\"javascript:popFileEditor(\'$fil\',$note);setAnchor('$name');\" /><br />";
      }
      foreach my $f (keys %{$FileIncUpdate{"$base/$ifil$name"}}) {
         my $fi = $f;
         $f  =~ s/([^\w\-.!~*\'() ])/sprintf("%%%02X",ord($1))/eg;
         $f  =~ s/ /+/g;
         $fi  =~ s/$base/\./;
         $edit .= "<input type=\"button\" value=\" $act included file $fi \" onclick=\"javascript:popFileEditor(\'$f\',$note);\" /><br />";
      }
  }

  # get rid of google autofill
  #$name=~s/(e)(mail)/$1_$2/gi;
  return "<a name=\"$name\"></a>
  <div class=\"shadow\">
    <div class=\"option\">
     <div class=\"optionTitle$cssoption\">$nicename $cfgname</div>
     <div class=\"optionValue\">
      <input name=\"$name\" size=\"$size\" value=\"$value\" />
      $edit<br />
      $Error
      $description\n
     </div>
    </div>
    &nbsp;
  </div>";

}

sub textnoinput {
    my (
        $name,  $nicename, $size,        $func,      $default,
        $valid, $onchange, $description, $cssoption, $note
    ) = @_;
    my $Error = checkUpdate( $name, $valid, $onchange, $nicename);
    my $value = encodeHTMLEntities( $Config{$name} );

    my $hdefault = encodeHTMLEntities($default);
    $description = &niceLink($description);
    $hdefault =~ s/'|"|\n//g;
    $hdefault =~ s/\\/\\\\/g;
    my $showdefault = $hdefault ? $hdefault : '&nbsp;';
    my $shortdefault = $showdefault;
     $shortdefault = substr( $showdefault, 0, 40 ) . "..." if length ($showdefault) > 40;
    my $color = ($value eq $hdefault) ? '' : 'style="color:#8181F7;"';
    $default = (!$shortdefault or $value eq $hdefault) ? '' : ", default=$shortdefault";
    my $cfgname =
      $EnableInternalNamesInDesc
      ? "<a href=\"javascript:void(0);\"$color onmouseover=\"showhint('<table BORDER CELLSPACING=0 CELLPADDING=4 WIDTH=\\'100%\\'><tr><td>default value:</td><td>$showdefault</td></tr></table>', this, event, '450px', ''); return true;\" onmouseout=\"window.status='';return true;\"><i>($name$default)</i></a>"
      : "";
    $cfgname .= syncShowGUI($name);
    my $edit = '';
    $note = 1 if !$note;

    if ( $value =~ /^\s*file:\s*(.+)\s*/i ) {

        # the optionlist is actually saved in a file.
        my $fil = $1;

        # escape query string
        $fil =~ s/([^\w\-.!~*\'() ])/sprintf("%%%02X",ord($1))/eg;
        $fil =~ s/ /+/g;
        $edit =
"<input type=\"button\" value=\" Edit file \" onclick=\"javascript:popFileEditor(\'$fil\',$note);\" />";
    }

    # get rid of google autofill
    #$name=~s/(e)(mail)/$1_$2/gi;
    return "<a name=\"$name\"></a>
 <div class=\"shadow\">
  <div class=\"option\">
   <div class=\"optionTitle$cssoption\">$nicename $cfgname</div>
   <div class=\"optionValue\">
    <input name=\"$name\" readonly style=\"background:#eee none; color:#222; font-style: italic\" size=\"$size\" value=\"$value\" />
    $edit<br />
    $Error
    $description\n
   </div>
  </div>
  &nbsp;
 </div>";

}

# everybody wants this, but I hate it -- use it if you care.
sub passinput {
    my (
        $name,  $nicename, $size,        $func, $default,
        $valid, $onchange, $description, $cssoption
    ) = @_;
    my $Error = checkUpdate( $name, $valid, $onchange, $nicename);
    my $value = encodeHTMLEntities( $Config{$name} );
    $description = &niceLink($description);
    my $hdefault = encodeHTMLEntities($default);
    $hdefault =~ s/'|"|\n//g;
    $hdefault =~ s/\\/\\\\/g;
    my $showdefault = $hdefault ? $hdefault : '&nbsp;';
    my $cfgname =
      $EnableInternalNamesInDesc
      ? "<a href=\"#$name\" onmouseover=\"showhint('<table BORDER CELLSPACING=0 CELLPADDING=4 WIDTH=\\'100%\\'><tr><td>default value:</td><td>$showdefault</td></tr></table>', this, event, '450px', ''); return true;\" onmouseout=\"window.status='';return true;\"><i>($name)</i></a>"
      : "";
    $cfgname .= syncShowGUI($name);
    "<a name=\"$name\"></a>
 <div class=\"shadow\">
 <div class=\"option\">
  <div class=\"optionTitle$cssoption\">$nicename $cfgname</div>
  <div class=\"optionValue\"><input type=\"password\" name=\"$name\" size=\"$size\" value=\"$value\" /><br />\n$Error$description
  </div>
 </div>
 &nbsp;
 </div>";
}

sub listbox {

    my (
        $name,  $nicename, $values,      $func, $default,
        $valid, $onchange, $description, $cssoption
    ) = @_;
    my $Error = checkUpdate( $name, $valid, $onchange, $nicename);
    $description = &niceLink($description);
    my $options;
    my $hdefault;
    foreach my $opt ( split( /\|/o, $values ) ) {
        my ( $v, $d ) = split( /:/o, $opt, 2 );
        $d = $v unless $d;
        Encode::from_to($d,'ISO-8859-1','UTF-8') if ($d && ! Encode::is_utf8($d));
        if ( $Config{$name} eq $v ) {
            $options .= "<option selected=\"selected\" value=\"$v\">$d</option>";
        } else {
            $options .= "<option value=\"$v\">$d</option>";
        }
        $hdefault = $d if ( $default eq $v );
    }
    my $color = ($Config{$name} eq $default) ? '' : 'style="color:#8181F7;"';
    $default = ($Config{$name} eq $default) ? '' : ", default=$hdefault";

    my $cfgname = $EnableInternalNamesInDesc?"<a href=\"javascript:void(0);\"$color onmousedown=\"document.forms['ASSPconfig'].$name.value='$default';return false;\" onmouseover=\"showhint('<table BORDER CELLSPACING=0 CELLPADDING=4 WIDTH=\\'100%\\'><tr><td>click to reset<br />to default value</td><td>$hdefault</td></tr></table>', this, event, '450px', ''); return true;\" onmouseout=\"window.status='';return true;\"><i>($name$default)</i></a>":'';
    $cfgname .= syncShowGUI($name);
    "<a name=\"$name\"></a>
     <div class=\"shadow\">
     <div class=\"option\">
  <div class=\"optionTitle$cssoption\">$nicename $cfgname</div>
  <div class=\"optionValue\">
  <span style=\"z-Index:100;\"><select size=\"1\" name=\"$name\">
    $options
    </select></span>
  <br />\n$Error$description
  </div>
 </div>
 &nbsp;
 </div>";
}

sub checkbox {
    my (
        $name,  $nicename, $size,        $func, $default,
        $valid, $onchange, $description, $cssoption
    ) = @_;
    my $Error = checkUpdate( $name, $valid, $onchange, $nicename);
    my $checked  = $Config{$name} ? 'checked="checked"' : '';
    my $disabled = "";
    my $isrun    = "";
    my $script   = "";
    $description = &niceLink($description);
    if (
        ( $name =~ /forceLDAPcrossCheck/ )
        && (   $RunTaskNow{forceLDAPcrossCheck}
            or ( !$CanUseLDAP && !$CanUseNetSMTP )
            or !$ldaplistdb )
      )
    {
        $disabled = "disabled";
        $isrun =
          'LDAPlist (ldaplistdb) is not configured - not available!<br />'
          if ( !$ldaplistdb );
        $isrun .= 'module Net::LDAP is not available!<br />'
          if ( !$CanUseLDAP );
        $isrun .= 'module Net::SMTP is not available!<br />'
          if ( !$CanUseNetSMTP );
    }
    if ( exists $RunTaskNow{$name} && $RunTaskNow{$name} && $qs{$name} ) {
        ${$name} = '';
        $Config{$name} = '';
        $qs{$name}     = '';
        $disabled      = "disabled";
        $isrun .=
"task $name (or related task) is just running - not available now!<br />Refreshing your browser will possibly restart $name, instead use the 'Refresh Browser' button to refresh the browser!<br />";
    }

    my $hdefault = $default ? 'on'   : 'off';
    my $cdefault = $default ? 'true' : 'false';
    my $color = ($Config{$name} eq $default) ? '' : 'style="color:#8181F7;"';
    $default = ($Config{$name} eq $default) ? '' : ", default=$hdefault";

    my $cfgname = $EnableInternalNamesInDesc?"<a href=\"javascript:void(0);\"$color onmousedown=\"document.forms['ASSPconfig'].$name.checked=$cdefault;return false;\" onmouseover=\"showhint('<table BORDER CELLSPACING=0 CELLPADDING=4 WIDTH=\\'100%\\'><tr><td>click to reset<br />to default value</td><td>$hdefault</td></tr></table>', this, event, '450px', ''); return true;\" onmouseout=\"window.status='';return true;\"><i>($name$default)</i></a>":'';
    $cfgname .= syncShowGUI($name);
    "<a name=\"$name\"></a>
 <div class=\"shadow\">
 <div class=\"option\">
  <div class=\"optionTitle$cssoption\">
   <input type=\"checkbox\" $disabled name=\"$name\" value=\"1\" $checked /><span style=\"color:red\">$isrun</span>$nicename $cfgname<br /></div>
  <div class=\"optionValue\">\n$Error$description
  </div>
 </div>
 &nbsp;
 </div>$script";
}

sub heading {
    my ( $description, $nodeId ) = @_[ 4, 5, 6 ];

    "</div>
<div onmousedown=\"toggleDisp('$nodeId')\" class=\"contentHead\">
 $description
</div>
<div id=\"$nodeId\">\n";
}
sub checkUpdate {
    my ($name,$valid,$onchange,$desc)=@_;
    return '' unless %qs;
    unless (exists $Config{$name}) {
        mlog(0,"warning: config parm $name requested but $name is not defined") if $name !~ /^AD/o;
        return '';
    }
    if (exists $qs{'AD'.$name}) {
#        mlog(0,"info: QS AD$name found");  # access denied and/or hidden
        return '';
    }
    if($qs{$name} ne $Config{$name}) {
        if($qs{$name}=~/$valid/i && $qs{$name} eq $1) {
            my $new=$1; my $info;
            my $old=$Config{$name};
            $Config{$name}=$new;
            if($onchange) {
                $info=$onchange->($name,$old,$new,'',$desc);
            } else {
                my $dold = $old;
                my $dnew = $new;
                if (exists  $ConfigListBox{$name}) {
                    if (exists $ConfigListBoxAll{$name}) {
                        $dold = decHTMLent($ConfigListBoxAll{$name}{$old}." ($old)");
                        $dnew = decHTMLent($ConfigListBoxAll{$name}{$new}." ($new)");
                    } elsif ($ConfigListBox{$name} =~ /^O(?:n|ff)$/o) {
                        $dold = $old ? 'On' : 'Off';
                        $dnew = $new ? 'On' : 'Off';
                    }
                }
                my $text = exists $cryptConfigVars{$name} ? '' : "from '$dold' to '$dnew'";
                mlog(0,"AdminUpdate: $name changed $text") unless $new eq $old;
                ${$name}=$new;
            # -- this sets the variable name with the same name as the config key to the new value
            # -- for example $Config{myName}="ASSP-nospam" -> $myName="ASSP-nospam";
            }
            $ConfigChanged=1 unless exists $RunTaskNow{$name};
            if ($info !~ /span class.+?negative/o) {
                if ($new ne $old) {
                    my $ret = "<span class=\"positive\"><b>*** Updated $info</b></span><br />" ;
                    if ($Config{$name} ne ${$name} && ! exists $RunTaskNow{$name}) {

                        ${$name}=$Config{$name};
                    }
                    &syncConfigDetect($name);
                    return $ret;
                }
            } else {
                return "<span class=\"negative\"><b>*** Incorrect: '$qs{$name}' $info</b></span><br />
                <script type=\"text/javascript\">alert(\"Incorrect '$name' - Unchanged. Note:Valid='$valid'\");</script>";
            }
        } else {
            my $text; $text = "(check returned '$1')" if $qs{$name}=~/$valid/i;
            return "<span class=\"negative\"><b>*** Invalid: '$qs{$name}' $text</b></span><br />
            <script type=\"text/javascript\">alert(\"Invalid '$name' - Unchanged. Note:Valid='$valid'\");</script>";
        }
    }
}

sub PrintConfigDefaults {
    my $desc;
    open( $FH, ">","$base/notes/configdefaults.txt" );
    my $counterT = -1;

    my %ConfigNice;
    my %ConfigDefault;
    my %ConfigNow;

    foreach my $c (@ConfigArray) {
        if ( @{$c} == 5 ) {
            $counterT++;
        } else {
            $ConfigPos{ $c->[0] }  = $counterT;
            $ConfigNice{ $c->[0] } = $c->[1] ;
            $ConfigNice{ $c->[0] } =~ s/\&lt\;a href.*\/&gt;&lt\;\/a\&gt\;//i;
            $ConfigNice{ $c->[0] } =~ s/<a\s+href.*<\/a>//i;
            $ConfigNice{ $c->[0] } =~ s/'|"|\n//g;
            $ConfigNice{ $c->[0] } =~ s/\\/\\\\/g;
            $ConfigNice{ $c->[0] } = '&nbsp;' unless $ConfigNice{ $c->[0] };
            $ConfigDefault{ $c->[0] } = $c->[4] ;
            $ConfigDefault{ $c->[0] } =~ s/'|"|\n//g;
            $ConfigDefault{ $c->[0] } =~ s/\\/\\\\/g;
            $ConfigNow{ $c->[0] } =  $Config{$c->[0]};
            $ConfigNow{ $c->[0] } =~ s/'|"|\n//g;
            $ConfigNow{ $c->[0] } =~ s/\\/\\\\/g;

            if ( $c->[3] == \&listbox ) {
                foreach my $opt ( split( /\|/o, $c->[2] ) ) {
                    my ( $v, $d ) = split( /:/o, $opt, 2 );
                    $ConfigDefault{ $c->[0] } = $d
                      if ( $ConfigDefault{ $c->[0] } eq $v );
                    $ConfigNow{ $c->[0] } = $d
                      if ( $ConfigNow{ $c->[0] } eq $v );

                }
            } elsif ( $c->[3] == \&checkbox ) {
                $ConfigDefault{ $c->[0] } =
                  $ConfigDefault{ $c->[0] } ? 'On' : 'Off';
                $ConfigNow{ $c->[0] } =
                  $ConfigNow{ $c->[0] } ? 'On' : 'Off';

            } else {
                $ConfigDefault{ $c->[0] } = ' '
                  unless $ConfigDefault{ $c->[0] };
                $ConfigNow{ $c->[0] } = ' '
                  unless $ConfigNow{ $c->[0] };
            }
        }
    }

    foreach my $c (@ConfigArray) {
        $desc = $c->[4]  if $c->[0] eq "0";
        $desc =~ s/\<.*\>//g;
        $desc =~ s/\<.*\>//g;

        print $FH "# $desc #\n" if $c->[0] eq "0";
        next if $c->[0] eq "webAdminPassword";

        my $c0 = uc $c->[0];

        if ( $ConfigDefault{ $c->[0] } ne $ConfigNow{ $c->[0] } ) {

            print $FH "$c->[0] -- $ConfigNice{ $c->[0] }: $ConfigNow{ $c->[0] } (Default: $ConfigDefault{ $c->[0] }) \n";
        } else {

            #print F "$c->[0] -- $desc: $Config{$c->[0]}  \n";
        }
    }
    close $FH;

}

sub PrintConfigSettings {
    my ($desc, $F);
    open( $F, '>',"$base/notes/configdefaults.txt" );
    my %ConfigNice = ();
    my %ConfigDefault = ();
    my %ConfigNow = ();

    foreach my $c (@ConfigArray) {
            next if ( @{$c} == 5 );
            $ConfigNice{ $c->[0] } = encodeHTMLEntities( $c->[1] );
            $ConfigNice{ $c->[0] } =~ s/<a\s+href.*?<\/a>//io;
            $ConfigNice{ $c->[0] } =~ s/'|"|\n//go;
            $ConfigNice{ $c->[0] } =~ s/\\/\\\\/go;
            $ConfigNice{ $c->[0] } = '&nbsp;' unless $ConfigNice{ $c->[0] };
            $ConfigDefault{ $c->[0] } = encodeHTMLEntities( $c->[4] );
            $ConfigDefault{ $c->[0] } =~ s/'|"|\n//go;
            $ConfigDefault{ $c->[0] } =~ s/\\/\\\\/go;
            $ConfigNow{ $c->[0] } = encodeHTMLEntities( $Config{$c->[0]} );
            $ConfigNow{ $c->[0] } =~ s/'|"|\n//go;
            $ConfigNow{ $c->[0] } =~ s/\\/\\\\/go;

            if ( $c->[3] == \&listbox ) {
                $ConfigDefault{ $c->[0] } = 0 unless $ConfigDefault{ $c->[0] };
                $ConfigNow{ $c->[0] } = 0 unless $ConfigNow{ $c->[0] };
                foreach my $opt ( split( /\|/o, $c->[2] ) ) {
                    my ( $v, $d ) = split( /:/o, $opt, 2 );
                    $ConfigDefault{ $c->[0] } = $d
                      if ( $ConfigDefault{ $c->[0] } eq $v );
                    $ConfigNow{ $c->[0] } = $d
                      if ( $ConfigNow{ $c->[0] } eq $v );
                }
            } elsif ( $c->[3] == \&checkbox ) {
                $ConfigDefault{ $c->[0] } =
                  $ConfigDefault{ $c->[0] } ? 'On' : 'Off';
                $ConfigNow{ $c->[0] } =
                  $ConfigNow{ $c->[0] } ? 'On' : 'Off';

            } else {
                $ConfigDefault{ $c->[0] } = ' '
                  unless $ConfigDefault{ $c->[0] };
                $ConfigNow{ $c->[0] } = ' '
                  unless $ConfigNow{ $c->[0] };
            }
    }

    foreach my $c (@ConfigArray) {
        $desc = $c->[4] if $c->[0] eq "0";
        $desc =~ s/\<[^<>]*\>//go;
        print $F "# $desc #\n" if $c->[0] eq "0";
        next if $c->[0] eq "0";

        my $c0 = uc $c->[0];

        if ( $c->[4] ne $Config{ $c->[0] } ) {

            print $F "$c->[0] -- $ConfigNice{ $c->[0] }: $ConfigNow{ $c->[0] } (Default: $ConfigDefault{ $c->[0] }) \n";
        } else {

            #print F "$c->[0] -- $desc: $Config{$c->[0]}  \n";
        }
    }
    close $F;

    open( $F, '>',"$base/notes/config.txt" );
    foreach my $c (@ConfigArray) {
        $desc = $c->[7];
        if ($desc) {
          $desc =~ s/\<b\>//go;
          $desc =~ s/\<i\>//go;
          $desc =~ s/\<p\>//go;
          $desc =~ s/\<small\>//go;
          $desc =~ s/\<br \/\>//go;
          $desc =~ s/\<\/i\>//go;
          $desc =~ s/\<\/b\>//go;
          $desc =~ s/\<\/p\>//go;
          $desc =~ s/\<\/small\>//go;
          $desc =~ s/\<[^<>]*\>//go;
        }

        my $c0  = uc $c->[0];
        my $act; $act = "actual: $Config{$c->[0]}" if $Config{ $c->[0] };
        my $def = $c->[4] ? "default: $c->[4]" : '' ;
        print $F "$c->[0]: $c->[1] -- $desc $def \n"
          if $Config{ $c->[0] } eq $c->[4] && $c->[0] ne "0";
        print $F "$c->[0]: $c->[1] -- $desc $def  \n"
          if $Config{ $c->[0] } ne $c->[4] && $c->[0] ne "0";
        $desc = $c->[4] if $c->[0] eq "0";
        $desc = '' unless $desc;
        $desc =~ s/\<[^<>]*\>//go;
        print $F "# $desc #\n" if $c->[0] eq "0";

    }
    close $F;

    open( $F, '>',"$base/assp.cfg.defaults" );
    foreach my $c (@ConfigArray) {
        next if $c->[0] eq "0";
        print $F "$c->[0]:=$c->[4]\n";
    }
    close $F;
    chmod 0664, "$base/assp.cfg.defaults";

}

sub SaveConfigSettings {
    return 0 if $Config{asspCfgVersion} eq "$version$modversion";
    $Config{asspCfgVersion}="$version$modversion";
    $asspCfgVersion = "$version$modversion";
    SaveConfigSettingsForce();
    return 1;
}

sub SaveConfigSettingsForce {
    SaveConfig();
    PrintConfigSettings();
}

sub PrintConfigHistory {
    my ($text) = @_;
    my $lt = localtime(time);
    $text =~ s/^AdminUpdate://i;
    open( $FH, ">>","$base/notes/confighistory.txt" );
    print $FH "$lt:  $text\n";
    close $FH;
}
sub PrintWhitelistAdd {
    my ($text) = @_;
    my $lt = localtime(time);

    open( $FH, ">>","$base/notes/whitelistadd.txt" );
    print $FH "$text";
    close $FH;
}

sub PrintUpdateHistory {
    my ($text) = @_;
    my $lt = localtime(time);

    open( $FH, ">>","$base/notes/updatehistory.txt" );
    print $FH "$lt:  $text\n";
    close $FH;
}
sub PrintAdminInfo {
    my ($text) = @_;
    my $lt = localtime(time);
    $text =~ s/^AdminUpdate://i;
    open( $FH, ">>","$base/notes/admininfo.txt" );
    print $FH "$lt:  $text\n";
    close $FH;
}

# This function is called on startup to clean up some settings
# Primarily these are settings that might be absent from assp.cfg
# or settings that are not needed anymore after an upgrade
sub fixConfigSettings {
    $Config{LogNameDate} = "MM-DD" if $Config{LogNameMMDD};

    $Config{DoSameSubject} = 3 if $Config{DoSameSubject} == 1;

    $Config{DoBombHeaderRe} = 3 if $Config{DoBombHeaderRe} == 1;

    if (exists $Config{enableSSL}) {
        $Config{DoTLS} = 2 if $Config{enableSSL};

    }
    $Config{noMsgID} = '127.0.0.|192.168.|10.' if $Config{noMsgID} =~ /127.0.0.|192.168.|10./;   
    $Config{maxBayesValues} = 40 if $Config{maxBayesValues} < 40;
    $Config{MaxAUTHErrors} = 5 if $Config{MaxAUTHErrors} eq "";
    $Config{base} = $base;
    $Config{TLDS} = 'file:files/tlds-alpha-by-domain.txt';

    $Config{URIBLServiceProvider} = 'file:files/uriblserviceprovider.txt' if $Config{URIBLServiceProvider}  =~ /black.uribl/;

    $Config{webSecondaryPort} = '22222';
    $Config{webAdminPort} = '55555' if $Config{webAdminPort} eq '55555|55556';

    $Config{ HouseKeepingSchedule } = 3 if !$Config{ HouseKeepingSchedule };
    if ($Config{BayesMaxProcessTime} > 60) {
        $BayesMaxProcessTime = $Config{BayesMaxProcessTime} = 60;
    }
    $Config{ noGriplistUpload } = 1 if $Config{ noGriplist };
    $Config{ noGriplistDownload } = 1 if $Config{ noGriplist };

    $Config{AutoRestartAfterCodeChange} = 'immed' if $Config{AutoRestartAfterCodeChange} eq "1";

    $Config{MaxEqualXHeader} = '*=>'.$Config{MaxEqualXHeader} if $Config{MaxEqualXHeader} =~ /^\d+$/;

    $Config{smtpDestination} =~ s/localhost/127.0.0.1/;
    if ( $Config{webAdminPassword} ) {
        $Config{webAdminPassword} = crypt( $Config{webAdminPassword}, "45" )
          if substr( $Config{webAdminPassword}, 0, 2 ) ne "45";
    }

    if ($Config{allowAdminConnectionsFromName}) {
        my $host = $Config{allowAdminConnectionsFrom};
        $host .= '|' if $host;
        $host .= $Config{allowAdminConnectionsFromName};
        $Config{allowAdminConnectionsFrom} = $host;
        delete $Config{allowAdminConnectionsFromName};
    }

    if ($Config{EmailFrom} =~ /ASSP <>/io) {
        mlog(0,"warning: invalid value '$Config{EmailFrom}' in EmailFrom was set to ''");
        $Config{EmailFrom} = '';
        $EmailFrom = '';
    } 

    $Config{maxBombSearchTime} = 10 if !$Config{maxBombSearchTime};

    $Config{wildcardUser} = lc $Config{wildcardUser};  
    my $host = $defaultLocalHost;
    $host ||= $EmailBlockReportDomain;

    $host ||= "assp-notspam.org";
    if ($Config{EmailFrom} eq '') {

        $EmailFrom = "\<postmaster\@$host\>";
        $Config{EmailFrom} = $EmailFrom;
        mlog(0,"info: empty value '' in EmailFrom was set to '$EmailFrom'");
    }
    my  $oldEmailFrom = $Config{EmailFrom}; 
    if ($Config{EmailFrom}  =~ /\<\>/) {
        $EmailFrom = $Config{EmailFrom};
           $EmailFrom =~ s/\<\>/\<postmaster$host\>/;

        $Config{EmailFrom} = $EmailFrom;
        mlog(0,"info: invalid value $oldEmailFrom in EmailFrom was set to '$EmailFrom'");
    }
    $Config{maillogExt} = ".eml" if !$Config{maillogExt};

    my $mydelaydb = $Config{delaydb};
    if ($mydelaydb !~ /sql/i) {
    $Config{DelayShowDBwhite} = "file:$mydelaydb.white";
    $Config{DelayShowDB}      = "file:$mydelaydb";
    } else {
    $Config{DelayShowDBwhite} = "";
    $Config{DelayShowDB}      = "";
    }
    $Config{fileLogging} = 1;
    $fileLogging = 1;

# -- this sets the variable name with the same name as the config key to the new value
# -- for example $Config{myName}="ASSP-nospam" -> $myName="ASSP-nospam";
    foreach ( keys %Config ) { 
    mlog(0,"info: '$_' => '$Config{$_}' ") if ($_ =~ /^[+-]?\d+(\.\d+)?$/); 
    ${$_} = $Config{$_}  if ($_ !~ /^[+-]?\d+(\.\d+)?$/); 
    }

    # set the date/time for assp.cfg
    my @s     = stat("$base/assp.cfg");
    my $mtime = $s[9];
    $FileUpdate{"$base/assp.cfgasspCfg"} = $mtime;
    $asspCFGTime = $mtime;

    my $savecfg = 0;
    my $savesync = 0;
    foreach (sort keys %newConfig) {
        mlog(0,"info: new config parameter $_ was set to '${$_}'");
        if (&syncCanSync() && ! exists $neverShareCFG{$_}) {
            $ConfigSync{$_} = {};

            $ConfigSync{$_}->{sync_cfg} = 0;

            $ConfigSyncServer{$_} = {};

            $savesync = 1;
        }
        $savecfg = 1;
    }
    &SaveConfig() if $savecfg;
    &syncWriteConfig() if $savesync;
    %newConfig = ();

    # turn settings into regular expressions
    &CompileAllRE(1);
    eval {
    foreach my $c (@ConfigArray) {
        if ($c->[0] && ${$c->[0]} !~ /$c->[5]/) {

            ${$c->[0]} = $c->[4];
            $Config{$c->[0]} = $c->[4];
        }
    }
    }

}

sub CompileAllRE {
    my $init = shift;
    @PossibleOptionFiles=();
    foreach my $c (@ConfigArray) {
        next if @{$c}==1; # skip headings

        next if (! $c->[6]);
        if (   $c->[6] eq 'ConfigMakeRe' 

            || $c->[6] eq 'ConfigMakeSLRe' 
            || $c->[6] eq 'ConfigMakeSLReSL'
            || $c->[6] eq 'ConfigMakeIPRe' 
            || $c->[6] eq 'configUpdateRBLSP' 
            || $c->[6] eq 'configUpdateRWLSP' 
            || $c->[6] eq 'configUpdateURIBLSP' 
            || $c->[6] eq 'configUpdateCCD' 
            || $c->[6] eq 'configUpdateASSPCfg' 
            || $c->[6] eq 'updateDNS' 
            || $c->[6] eq 'configUpdateCA' 
            || $c->[6] eq 'configUpdateSPFOF' 
            || $c->[6] eq 'ConfigCompileNotifyRe'
            || $c->[6] eq 'configChangeRcptRepl'
            || $c->[6] eq 'ConfigCompileRe'  
            || $c->[6] eq 'configChangeMSGIDSec'
            || $c->[6] eq 'configChangeRT'
            || $c->[6] eq 'configUpdateMaxSize'
            || $c->[6] eq 'configUpdateStringToNum'
            || $c->[6] eq 'configUpdateBACKSctrSP'
            || $c->[6] eq 'ConfigMakeEmailAdmDomRe'
            || $c->[6] eq 'ConfigChangeSyncFile'

           )
        {
            push(@PossibleOptionFiles,[$c->[0],$c->[1],$c->[6]]);
            mlog(0,"ERROR: possible code or language file error in config for $c->[0] - '*' not found at the end of the small description") if ($c->[1] !~ /\*\s*$/ );
            mlog(0,"ERROR: possible code or language file error in config for $c->[0] - '**' not found at the end of the small description for weighted RE") if (exists $WeightedRe{$c->[0]} && $c->[1] !~ /\*\*\s*$/ );
        } elsif ($c->[0] ne 'POP3ConfigFile') {
            mlog(0,"ERROR: possible code error in sub 'CompileAllRE' for $c->[0] - $c->[6] - option file is not checked") if $c->[1] =~ /\*$/;

        }
        if ($c->[0] =~ /ValencePB$/o && defined $c->[6]) {
            $c->[6]->($c->[0],$Config{$c->[0]},$Config{$c->[0]},$init);
        }
    }

    foreach my $f (@PossibleOptionFiles) {
        next if ($f->[0] eq 'asspCfg');
        if ($init) {
            $f->[2]->($f->[0],'',$Config{$f->[0]},'Initializing',$f->[1]);
        } else {
            if (($Config{$f->[0]} =~ /^ *file: *(.+)/io && fileUpdated($1,$f->[0])) or
                $Config{$f->[0]} !~ /^ *file: *(.+)/io)
            {
               $f->[2]->($f->[0],$Config{$f->[0]},$Config{$f->[0]},'',$f->[1]);
            }
        }
    }

    updateBadAttachL1('BadAttachL1','',$Config{BadAttachL1},'Initializing');
    updateGoodAttach('GoodAttach','',$Config{GoodAttach},'Initializing');
    updatePassAttach('PassAttach','',$Config{PassAttach},'Initializing');
    configChangeRT('smtpDestinationRT','',$Config{smtpDestinationRT},'Initializing');
    configUpdateRBL('ValidateRBL','',$Config{ValidateRBL},'Initializing');
    configUpdateRWL('ValidateRWL','',$Config{ValidateRWL},'Initializing');

    configChangeRBSched('RebuildSchedule','',$Config{RebuildSchedule},'Initializing');
    configChangeHKSched('HouseKeepingSchedule','',$Config{HouseKeepingSchedule},'Initializing');
    configUpdateURIBL('ValidateURIBL','',$Config{ValidateURIBL},'Initializing');
    configUpdateCA('CatchAll','',$Config{CatchAll},'Initializing');
    configUpdateCCD('ccSpamInDomain','',$Config{ccSpamInDomain},'Initializing');
    updateSRS('EnableSRS','',$Config{EnableSRS},'Initializing');
    freqNonSpam('freqNonSpam','',$Config{freqNonSpam},'Initializing');
    freqSpam('freqSpam','',$Config{freqSpam},'Initializing');
    ConfigChangeNoAUTHPorts('NoAUTHlistenPorts','',$Config{NoAUTHlistenPorts},'NoAUTHlistenPorts');
    configUpdateMaxSize('MaxSizeExternalAdr','',$Config{MaxSizeExternalAdr},'Initializing');
    configUpdateMaxSize('MaxSizeAdr','',$Config{MaxSizeAdr},'Initializing');   configUpdateMaxSize('MaxRealSizeExternalAdr','',$Config{MaxRealSizeExternalAdr},'Initializing');
    configUpdateMaxSize('MaxRealSizeAdr','',$Config{MaxRealSizeAdr},'Initializing');    ConfigChangeTLSPorts('NoTLSlistenPorts','',$Config{NoTLSlistenPorts},'Initializing');
    ConfigChangeMaxAllowedDups('MaxAllowedDups','',$Config{MaxAllowedDups},'Initializing');
    ConfigChangePOP3File('POP3ConfigFile','',$Config{POP3ConfigFile},'Initializing');
    configChangeMSGIDSec('MSGIDSec','',$Config{MSGIDSec},'Initializing');
    $spamSubjectEnc = is_7bit_clean($spamSubject) ? $spamSubject : encodeMimeWord($spamSubject,'Q','UTF-8');
}

sub optionFilesReload {
 # check if options files have been updated and need to be re-read
    foreach my $f (@PossibleOptionFiles) {
        if($f->[0] ne 'asspCfg' or ($f->[0] eq 'asspCfg' && $AutoReloadCfg)) {
            if ($Config{$f->[0]}=~/^ *file: *(.+)/io && fileUpdated($1,$f->[0]) ) {
                $f->[2]->($f->[0],$Config{$f->[0]},$Config{$f->[0]},'',$f->[1]);
                &syncConfigDetect($f->[0]);
            }
        }
    }
}

sub ConfigMakeRe {
 my ($name, $old, $new, $init, $desc)=@_;
 my $note = "AdminUpdate: $name changed from '$old' to '$new'";
 $note = "AdminUpdate: $name changed" if exists $cryptConfigVars{$name};
 mlog(0,$note) unless $init || $new eq $old;
 $new=checkOptionList($new,$name,$init);
 $new =~ s/([\\]\|)*\|+/$1\|/go;
 my $mHostPortRe = $HostRe . '(?:\:' . $PortRe . ')?' . '(?:|' . $HostRe . '(?:\:' . $PortRe . ')?)*';

 my %toChangeMTA;
 $new = join('|', sort split(/\|/o,$new)) if $new;
 if ($name eq "vrfyDomains" ) {
        %DomainVRFYMTA = ();

        my @entry = split(/\|/o,$new);
        $new = '';
        my $ld;
        my $lds;
        my $mta;
        my $defaultMTA;

        while (@entry) {
           my $ad = shift @entry;
           $ad =~ s/\s//go;
           ($ld,$mta) = split(/\s*\=\>\s*/o,$ad,2);
           if ($ld =~ /^(all)$/io && $mta) {
               my $e = $1;
               if ($mta !~ /$mHostPortRe/o) {
                   mlog(0,"warning: $name - VRFY entry '$ad' contains a not valid MTA definition");
                   next;
               }

           }
           if ($mta && $mta =~ /$mHostPortRe/o) {
               $DomainVRFYMTA{lc $ld} = $mta;

           } elsif ($mta && $mta !~ /$mHostPortRe/o) {
               mlog(0,"warning: found entry '$ad' with wrong syntax in $name file");
               next;
           }
           $toChangeMTA{lc $ld} = 1;

        }

        if ($defaultMTA) {
            while (my ($k,$v) = each %toChangeMTA) {
                $DomainVRFYMTA{$k} = $defaultMTA;
            }
        }

        foreach my $k (reverse sort keys %DomainVRFYMTA) {
            my $v = $DomainVRFYMTA{$k};
            $DomainVRFYMTA{$k} = $defaultMTA if !$v && $defaultMTA;
            $v = $DomainVRFYMTA{$k};

            mlog(0," $k => $v",1) if $name eq "vrfyDomains" && $v && $DoVRFY && $VRFYLog>=2;

        }    
        return '';
 }

 if ($name eq "maxBombHits") {
        my @entry = split(/\|/o,$new);
        $new = '';
        my $ld;
        my $lds;
        my $mta;

        while (@entry) {
           my $ad = shift @entry;
           ($ld,$mta) = split(/\=\>/,$ad);
           if ($mta) {
               $maxHits{lc $ld} = $mta;

           }

        }

        return '';
 }
 $new=~s/([\.\[\]\-\(\)\+\\])/\\$1/g;
 $new=~s/\*/\.\{0,64\}/g;
 $new=~s/\?/\.\?/go;
#    mlog(0,"$new") if $name eq "blackListedDomains";
 if ($name eq 'TLDS') {

        $TLDSRE = $new;

 } else {

     $new||='^(?!)'; # regexp that never matches
 }
 mlog(0,"ERROR: !!!!!!!!!! missing MakeRE{$name} in code !!!!!!!!!!") unless exists $MakeRE{$name};
 $MakeRE{$name}->($new);
 '';
}

# make spamlover address like RE
sub ConfigMakeSLRe {
    my ( $name, $old, $new, $init, $desc ) = @_;

    my $ld;
    my $re;
    my $we;
    my $mta;
    my $note = "AdminUpdate: $name changed from '$old' to '$new'";
    %FlatVRFYMTA = () if $name eq "LocalAddresses_Flat";
    %FlatDomains = () if $name eq "LocalAddresses_Flat";
    mlog( 0, $note )
      unless $init || $new eq $old;
    ${$name} = $new;
    $new = checkOptionList( $new, $name, $init );
    $new =~ s/\./\\\./go;
     $new =~ s/\\\\/\\/go;
     $new =~ s/\\\\/\\/go;
    $new =~ s/\*/\.\*\?/go;
    $new =~ s/\?/\.\?/go;
    my ( @uad, @u, @d );
    if (exists $WeightedRe{$name}) {

        my @Weight = @{$name.'Weight'};
        my @WeightRE = @{$name.'WeightRE'};
        @{$name.'Weight'} = ();
        @{$name.'WeightRE'} = ();

        my $newnew;
        foreach my $rex ( split( /\|/o, $new ) ) {
            next if $rex =~ /\=\>/;
             push (@{$name.'WeightRE'},$rex);
            push (@{$name.'Weight'},"1");
            $newnew .= "$rex|";

        }
        foreach my $rex ( split( /\|/o, $new ) ) {
            next if $rex !~ /\=\>/;

            ($re, $we) = $rex =~ /(.*)\=\>(.*)/;

            $we =~ s/\\//;
            $we += 0;
            $re .= '$' if $re =~ /^@/;
            $newnew .= "$re|";

            eval{$note =~ /$re/};
            if ($@) {
                mlog(0,"error: weighted regex for $name is invalid '$re=>$we' - $@") ;
                mlog(0,"warning: value for $name was not changed - all changes are ignored") ;

                 @{$name.'Weight'} = @Weight;
                @{$name.'WeightRE'} = @WeightRE;
                $new = $old;
                return "<span class=\"negative\"> - weighted regex for $name is invalid '$re=>$we'!</span>";
            }

            push (@{$name.'WeightRE'},$re);
            push (@{$name.'Weight'},$we);

        }

        my $count = 0;
        foreach my $k (@{$name.'Weight'}) {
            my $reg = ${$name.'WeightRE'}[$count];
            $count++;
        }

        $new = $newnew;
        $new =~ s/\|$//;

    }

    foreach my $a ( split( /\|/, $new ) ) {
        if ( $a =~ /\S\@\S/ ) {
            if ( $name eq "LocalAddresses_Flat" ) {
                $a =~ /^(.*@)(.*)$/;
                my $h = $2;
                $h =~ s/\\//;
                $FlatDomains{ lc $h } = 1 if $h;
                }
            push( @uad, $a );
        } elsif ( $a =~ /^\@/ ) {

            if ( $name eq "LocalAddresses_Flat" ) {
                ( $ld, $mta ) = split( /\=\>/, $a );
                $FlatVRFYMTA{ lc $ld } = $mta if $mta;
                mlog(0,"warning: LocalAddresses_Flat VRFY entry $ld also exists in localDomains")
                   if exists $DomainVRFYMTA{ lc $ld };
                $a = $ld if $mta;
                $a =~ /^\@(.*)/;
                my $h = $1;
                $h =~ s/\\//;
                $FlatDomains{ lc $h } = 1 if $h;
            }

            push( @d, $a );
        } elsif ( $name eq "LocalAddresses_Flat"
            && $LocalAddresses_Flat_Domains )
        {    
            my $h = $a;
            $h =~ s/\\//;
            $h =~ s/\@//;
            $FlatDomains{ lc $h } = 1 if $h;
            ( $ld, $mta ) = split( /\=\>/, $a );
            $ld = '@' . $ld;
            $a  = '@' . $a;

            $FlatVRFYMTA{ lc $ld } = $mta if $mta;
            mlog(0,"warning: LocalAddresses_Flat VRFY entry $ld also exists in localDomains")
               if exists $DomainVRFYMTA{ lc $ld };
            $a = $ld if $mta;

            push( @d, $a );
        } else {
            if ( $name eq "LocalAddresses_Flat" ) {
                ( $ld, $mta ) = split( /\=\>/, $a );
                if ($mta) {
                    $ld                    = '@' . $ld;
                    $a                     = '@' . $a;
                    $FlatVRFYMTA{ lc $ld } = $mta;
                    mlog(0,"warning: LocalAddresses_Flat VRFY entry $ld also exists in localDomains")
                       if exists $DomainVRFYMTA{ lc $ld };
                    $a                     = $ld;
                    push( @d, $a );
                } else {
                    $a =~ /^(.*@)(.*)/;
                    my $h = $2;
                    $h =~ s/\\//;
                    $h =~ s/\@//;
                    $FlatDomains{ lc $h } = 1 if $h;
                    push( @u, $a );
                }
            } else {
                push( @u, $a );
            }
        }
    }

    my @s;
    push( @s, '^(' . join( '|', @uad ) . ')$' ) if @uad;
    push( @s, '^(' . join( '|', @u ) . ')@' )   if @u;
    push( @s, '(' . join( '|', @d ) . ')$' ) if @d;
    my $s = join( '|', @s );
    $s ||= '^(?!)';    # regexp that never matches
    mlog( 0, "ERROR: !!!!!!!!!! missing MakeSLRE{$name} in code !!!!!!!!!!" )
      unless exists $MakeSLRE{$name};

    SetRE( $MakeSLRE{$name}, $s, 'i', $desc );
    '';

}

# make spamlover RE for SpamLovers
sub ConfigMakeSLReSL {
    my ( $name, $old, $new, $init, $desc ) = @_;
    my $ld;
    my $mta;
    $SLscore{$name} = ();
    mlog( 0, "adminupdate: $name changed from '$old' to '$new'" )
      unless $init || $new eq $old;
    ${$name} = $new;
    $new = checkOptionList( $new, $name, $init );
    my $ret;

    $new =~ s/([\\]\|)*\|+/$1\|/go;
    $new =~ s/\./\\\./go;
    $new =~ s/\*/\.\{0,64\}/go;

    my ( @uad, @u, @d , %entry_uad, %entry_u, %entry_d);

    foreach ( split( /\|/o, $new ) ) {

        my ($ad, $score) = split /=>/o;
        $ad =~ s/\s//go;

        if ($score && ! ($score =~ s/^\s*(\d+(?:\.\d+)?)\s*$/$1/o)) {
            $score = undef;
            $ret .= ConfigShowError( 0, "warning: spamlover max score for $name in definition '$_' is not a numbered value - the score value is ignored" );
        }
        $SLscore{$name}->{unescape(lc $ad)} = max($SLscore{$name}->{unescape(lc $ad)},$score) if defined $score;

        if ( $ad =~ /\S\@\S/o ) {
            push( @uad, $ad ) unless exists $entry_uad{lc $ad};
            $entry_uad{lc $ad} = 1;
        } elsif ( $ad =~ s/^\@//o ) {
            push( @d, $ad ) unless exists $entry_d{lc $ad};
            $entry_d{lc $ad} = 1;
        } else {
            push( @u, $ad ) unless exists $entry_u{lc $ad};
            $entry_u{lc $ad} = 1;
        }
    }

    my @s;
    push( @s, '(?:' . join( '|', sort @u ) . ')@.*' )   if @u;
    push( @s, '(?:' . join( '|', sort @uad ) . ')' ) if @uad;
    push( @s, '.*?@(?:' . join( '|', sort @d ) . ')' ) if @d;
    my $s;
    $s = '(?:^' . join( '|', @s ) . '$)' if @s;
    $s =~ s/\@/\\\@/go;
    $s ||= '^(?!)';    # regexp that never matches
    $ret .= ConfigShowError( 1, "ERROR: !!!!!!!!!! missing MakeSLRE{$name} in code !!!!!!!!!!" )
      if ! exists $MakeSLRE{$name};

       SetRE( $MakeSLRE{$name}, $s, 'i', $desc );
}
# make EmailAdmins -> Domain match RE
sub ConfigMakeEmailAdmDomRe {
    my ( $name, $old, $new, $init, $desc ) = @_;
    %EmailAdminDomains = ();
    mlog( 0, "adminupdate: $name changed from '$old' to '$new'" )
      unless $init || $new eq $old;
    ${$name} = $new;
    $new = checkOptionList( $new, $name, $init );
    my $ret = &ConfigRegisterGroupWatch(\$new,$name,$desc);

    $new =~ s/([\\]\|)*\|+/$1\|/go;

    foreach ( split( /\|/o, $new ) ) {
        my ($ad, $domain) = split /=>/o;
        $ad =~ s/\s//go;
        $ad = lc $ad;
        $domain =~ s/\s//go;
        $domain =~ s/[, ]+/\|/go;
        $domain =~ s/\|+/\|/go;
        $domain =~ s/^\|//o;
        $domain =~ s/\|$//o;
        $domain = '*@'.$domain if $domain !~ /\@/o;
        $domain =~ s/\./\\\./go;
        $domain =~ s/\*/\.\{0,64\}/go;
        $domain =~ s/\@/\\@/go;
        $domain = lc $domain;
        eval{$EmailAdminDomains{$ad} = qr/$domain/;};
        if ($@) {
            $ret .= ConfigShowError( 1, "ERROR: $name contains wrong definition ($_) - $@");
        }
    }
    return $ret;
}
sub ConfigRegisterGroupWatch {

}

# inplace replace a hostname with all available IP's
# in a ConfigMakeIPRe value and return errors
sub replaceHostByIP {
    my ($new,$name,$desc) = @_;

    return unless $$new;
    return if $name eq "droplist";

    my @nnew;
    my $ret;
    my $minTTL = 999999999;
    foreach my $l (split(/\|/o,$$new)) {
        $l =~ s/^\s+//o;
        $l =~ s/\s$//o;
        if ($l =~ m/^$IPv6Re(?:\/\d{1,3})?/io) {  # is a IPv6 address
            push @nnew, $l;
            next;
        }
        if ($l =~ m/^(?:\d{1,3}\.){1,3}(?:\d{1,3})?(?:\/\d{1,2})?/o) { # is a IPv4 fragment or address
            push @nnew, $l;
            next;
        }
        # found a hostname - replace it with all available IP's

        my ($sl,$sep,$desc) = split(/(\s+)/o,$l,2);

        if ($sl !~ /$EmailDomainRe|\w\w+/o) {      # not a valid hostname
            $ret .= ConfigShowError(1, "AdminInfo: '$sl' is not a valid hostname in $name - ignore entry") if $sl;
            mlog(0,"4 '$sl' is not a valid hostname in $name");
            next;
        }
        $desc = $sep.$desc if $desc;
        my $res = queryDNS($sl ,'A');
        ref $res or next;
        if ($res) {
            my @answer = map{$_->string} $res->answer;
            my $w = 1;
            while (@answer) {
                my $RR = Net::DNS::RR->new(shift @answer);
                my $ttl = $RR->ttl;

                my $data = $RR->rdatastr;
                push @nnew, "$data/32$desc" if $data =~ /^$IPv4Re$/o;
                push @nnew, "$data/128$desc" if $data =~ /^$IPv6Re$/o;
                d("replaceHostByIP record: $sl -> $data , TTL -> $ttl");
                mlog(0,"added IP '$data/32' for hostname '$sl' to $name") if $MaintenanceLog > 2;
                $w = 0 if $ttl;
                $minTTL = $ttl if $ttl < $minTTL;
            }
        } else {
            $ret .= ConfigShowError(1, "AdminInfo: error - unable to resolve hostname '$sl' for configuration of '$name'");
        }
    }

    $$new = join('|',@nnew);
    return $ret;
}
sub ConfigShowError {
    eval {
    my ($red, $msg, $noprepend, $noipinfo , $noS) = @_;
    return unless $msg;
    mlog(0, $msg, $noprepend, $noipinfo , $noS);
    my ($prsp,$posp);
    if ($red) {
        $prsp = '<span class="negative">';
        $prsp = '</span>';
    }
    $msg =~ s/[^:]+:\s*//o;
    return "$prsp$msg$prsp<br />\n";
    };
}

sub unescape {
    my $string = shift;
    $string =~ s/\\//go;
    return $string;
}

# make IP address RE
# allow for CIDR notation if Net::IP::Match::Regexp available
sub ConfigMakeIPRe {

    my ($name, $old, $new, $init, $desc)=@_;
    my $newexpanded;
    my $cips;
    use re 'eval';

    mlog(0,"AdminUpdate: $name changed from '$old' to '$new'") unless $init || $new eq $old;
    ${$name} = $new;
    $new=~s/\s*\-\s*/\-/go;
    $new=checkOptionList($new,$name,$init) ;
    my $ret = &ConfigRegisterGroupWatch(\$new,$name,$desc);

    if ($CanMatchCIDR) {
        foreach my $l (split(/\|/o,$new)) {

            $l=~s/\.\./\./go;
            $l=~s/--+/*/go;
            $l=~s/#.*//o;
            $l=~s/;.*//o;
            $l=~s/\[([0-9a-f:]+)\]/$1/ig;

            $l=~s/^($IPRe)\s+($IPRe)/$1-$2/go;

            if ($CanUseCIDRlite && $l=~/^$IPRe-$IPRe/o ) {

                $l=~s/($IPRe)-($IPRe)(.*)/ipv6expand($1).'-'.ipv6expand($2)/oe;
                $desc=$3;

                my $cidr = Net::CIDR::Lite->new;
                eval{$cidr->add_any($l);};
                if ($@) {
                    $@=~/^(.+?)\sat\s/o ;
                    $ret .= ConfigShowError(1,"AdminInfo: $name: $1 ($l)") ;
                    next;
                }
                my @cidr_list = $cidr->list;
                my $cidr_join = join("$desc|",@cidr_list);
                $newexpanded.=$cidr_join."$desc|";
                next;
            }
            $newexpanded.=$l."|";
        }
        $newexpanded=~s/\|$//o if $newexpanded;
        $new=$newexpanded;

    }
    if ($new) {
        $ret .= replaceHostByIP(\$new,$name,$desc);
        $new =~ s/\|\|/\|/go;
    }
    if ($new && $CanMatchCIDR) {
        my %ips = ();
        my $new6;
        my $new4;
        foreach my $l (split(/\|/o,$new)) {
            my $hasIPv6;
            if ($l =~ /:[^:]*:/o) {
                $l =~ s/^\[([0-9a-f:\.]+)\]/$1/io;
                $hasIPv6 = 1;
                my $ip;
                my $bits;
                my $ll = $l;
                ($l, $desc) = ($l =~ m/^([0-9a-f:.]+(?:\/[0-9]{1,3})?)\s*(.*)\s*$/io);
                $desc = " $desc" if ($desc);
                ($ip, $bits) = split(/\//, $l);
                if ($l =~ /\//) {
                    if (!$bits || $bits > 128) {
                        $ret .= ConfigShowError(1, "AdminInfo: invalid IPv6 address $l in $name");
                        next;
                    }
                    $ip = ipv6expand($ip);
                } else {
                    $ip = ipv6expand($ip);
                    $ip =~ s/(?::0)+$//o;
                    my @pre = split /:/o, $ip;
                    $bits = ($#pre+1)*16;
                    if ($bits > 128) {
                        $ret .= ConfigShowError(1, "AdminInfo: invalid IPv6 address $l in $name");
                        next;
                    }
                }
                my $ip6b = ipv6binary($ip, $bits);

                $new6 .= $ip6b . "(?{'" . $l . $desc . "'})" . "|";

                $cips++;
                $l = $ll;
            }
            if (my @matches=$l=~/(\d{1,3}\.)(\d{1,3}\.?)?(\d{1,3}\.?)?(\d{1,3})?(\/)?(\d{1,3})?\s*(.*)\s*$/io)   {
                my $description=$7;
                my $ip=$1.($2?$2:'').($3?$3:'').($4?$4:'');
                my $bits=($5?$5:'').($6?$6:'');

                foreach (@matches) {
                    $_ = 0 unless $_;
                    s/\.$//go;
                }
                if  ($matches[0]>255 || $matches[1]>255 || $matches[2]>255 || $matches[3]>255) {
                    $ret .= ConfigShowError(1,"AdminInfo: $name, error in line $l, Dotted Quad Number > 255 ");
                    next;
                }

                $ip=~s/\.$//go;

                if ($hasIPv6) {
                    $bits -= 96;
                    $bits = 32 if $bits < 0;
                }
                $bits='' if !$bits;

                my $dcnt=($ip=~tr/\.//);
                if ($dcnt>=3) {
                    $bits||='/32';
                } elsif ($dcnt>=2) {
                    $ip.='.0';
                    $bits||='/24';
                } elsif ($dcnt>=1) {
                    $ip.='.0'x2;
                    $bits||='/16';
                } else {
                    $ip.='.0'x3;
                    $bits||='/8';
                }

                $desc= $description ? "$ip$bits $description" : "$ip$bits";

                $desc=~s/'/\\'/go;
                $desc||=1;
                if  ("$ip$bits" !~ /$IPv4Re\/\d{1,2}/o) {
                    $ret .= ConfigShowError(1,"AdminInfo: $name error in line $l, IP notation: $ip$bits");
                    next;
                }
                $ips{"$ip$bits"}=$desc;
                $cips++;
            }
        }

        if (scalar keys %ips) {
            eval{$new4=Net::IP::Match::Regexp::create_iprange_regexp(\%ips);};
            $ret .= ConfigShowError(1,"AdminInfo:$name $@") if $@;
        }

        if ($new6) {
            $new6 =~ s/\|$//o;
            if ($CanUseRegexOptimizer && eval{Regex::Optimizer->VERSION >= 1.11} && (! exists $noOptRe{$name} || $noOptRe{$name} > 0)) {
                my $optimSX = $noOptRe{$name} == 2;
                my $how = $optimSX ? 'strong ' : 'simple ';
                my $lenBefore = length($new6) + 19;      # (?-xims:(?$f:......))
                if ($WorkerName eq 'startup' && $MaintenanceLog >= 2) {
                   print $how . "optimizing IPv6 regex for $name";
                   print ' ' x (30 - length($name) - length($how));
                }
                my $on6 = $new6;
                my $o = Regex::Optimizer->new;
                $o->set(optim_sx => $optimSX);
                eval{$new6 = $o->optimize(qr/$new6/)};
                if ($@) {
                    $ret .= ConfigShowError(0,"AdminInfo: regular expression optimzation error in '$name' for IPv6 addresses: $@");
                    $new6 = $on6;
                    if ($WorkerName eq 'startup' && $MaintenanceLog >= 2) {
                        print "[FAILED]\n";
                    }
                } else {
                    if ($WorkerName eq 'startup' && $MaintenanceLog >= 2) {
                        print "[OK]\n";
                    }
                }
            }
        }
        if ($new6 && $new4) {
            $new4 =~ s/^.*\^(.*)\)/$1/o;
            $new = "(?msx-i:^(6(?:$new6)|$new4))";
        } elsif ($new6) {
            $new6 = '(?msx-i:^6('.$new6.'))';
            $new = $new6;
        } elsif ($new4) {
            $new = $new4;
        } else {
            $new = qr/^(?!)/;    # regexp that never matches
        }

        $ret .= ConfigShowError(1,"ERROR: !!!!!!!!!! missing MakeIPRE{$name} in code !!!!!!!!!!") if ! exists $MakeIPRE{$name};
        eval{${$MakeIPRE{$name}}=qr/$new/;};
        $ret .= ConfigShowError(1,"AdminInfo: regular expression error in '$name:$new': $@") if $@;
    } else {
        my %ips = ();
        if ($new) {
            foreach my $l (split(/\|/o,$new)) {
                if ($l =~ /:[^:]*:/o) {
                    $l =~ s/^\[([0-9a-f:\.]+)\]/$1/io;
                    if ($l =~ /([0-9a-f:\.]+)\s*(.*)\s*$/io )
                    {
                        my $description = $2;
                        my $ip = ipv6expand($1);

                        $desc = $description ? "$ip $description" : $ip ;
                        $ips{$ip} = $desc;
                        $cips++;
                    }
                }
                if ($l =~ /(\d{1,3}\.)(\d{1,3}\.?)?(\d{1,3}\.?)?(\d{1,3})?(\/)?(\d{1,3})?\s*(.*)\s*$/io)   {
                    my $description=$7;
                    my $ip=$1.$2.$3.$4;

                    $desc = $description ? "$ip $description" : $ip ;
                    $ips{$ip}=$desc;
                    $cips++;
                }
            }
        }
        my @ips;
        while (my ($ip,$desc)=each(%ips)) {
            $ip=~s/([\.\[\]\-\(\)\*\+\\])/\\$1/go;
            next unless $ip;
            $desc=~s/'/\\'/go;
            push(@ips,"$ip(?{'$desc'})");
        }
        $new=join('|',@ips);
        $new ||='^(?!)'; # regexp that never matches
        $ret .= ConfigShowError(1,"ERROR: !!!!!!!!!! missing MakeIPRE{$name} in code !!!!!!!!!!") if ! exists $MakeIPRE{$name};
        eval{${$MakeIPRE{$name}}=qr/^(?:$new)/};
        $ret .= ConfigShowError(1,"AdminInfo: regular expression error in '$name:$new' for $desc: $@") if $@;
    }

    return $ret;
}

sub NoTLS {

    return;
}
# check if IP address matches RE

sub matchIP {
    my ( $ip, $re, $fhh, $donotmlog ) = @_;
    my $reRE = ${ $MakeIPRE{$re} };
    return 0 unless $ip && $reRE;
    return if $reRE =~ /^$neverMatchRE$/o;

    $fhh = 0 if ! $fhh || ! exists $Con{$fhh};
    $ip =~ s/\r|\n//go;
    my $ret;
    local $^R = undef;
    my $this = $Con{$fhh};
    use re 'eval';
    if ($CanMatchCIDR) {
        if ($ip =~ /:[^:]*:/o) {
            $ip =~ s/^\[([0-9a-f:]+)\].*/$1/io;
            $ip = ipv6expand($ip);
            my $ip6b = ipv6binary($ip, 128);
            $ret = $^R if (( '6' . $ip6b ) =~ /$reRE/xms);
        }
        if (!$ret && $ip =~ /($IPv4Re)$/o) {
            $ret = $^R if ('4'.unpack 'B32', pack 'C4', split /\./xms, $1)=~/$reRE/xms;
        }
    } else {
        ($ret) = $ip=~/($reRE)/xms;
    }
$ret = 0 unless $ret;
    d("matchIP: ip=$ip re=$re") if $ret && ! $donotmlog;
    return $ret if $re eq 'noLoggingIPs';
    if ( $ret && !$donotmlog) {
        $this->{myheader} .= "X-Assp-$re: $ip '$ret'\r\n" if $AddIPHeader && $this->{myheader} !~ $re;
        mlog( $fhh, "IP $ip" . ( $ret == 1 ? '' : " ($ret)" ) . " matches $re", 1 ) if $ipmatchLogging && $fhh;
    }
    return $ret;
  }

# check if email address matches RE
sub matchSL {
    my ($ad,$re,$nolog)=@_;
    $ad = join(' ',@$ad) if ref($ad);
    d("matchSL - $ad - $re",1);
    return 0 unless ${$re};
    my $reRE = ${$MakeSLRE{$re}};
    my $alllog;
    $alllog = 1 if $allLogRe && $ad =~ /$allLogReRE/ ;
    my $ret;
    $ret = $1 if $ad =~ /($reRE)/;
    if ($alllog or ($slmatchLogging && !$nolog && $ret) ) {
        my $matches = $ret ? "matches $ret": 'does not match';
        mlog( 0, "$ad $matches in $re", 1 );
    }
    return $ret ? 1 : 0;
}
# check if a regex matches
sub matchRE {
    my ($ad,$re,$nolog)=@_;
    my @ad = (ref($ad)) ? @$ad : ($ad);
    $lastREmatch = '';
    d("matchRE - @ad - $re",1);
    return 0 unless $re;
    return 0 unless ${$re.'RE'};
    my $reRE = ${$re.'RE'};
    return 0 if $reRE =~ /$neverMatchRE/o;
    my $alllog;
    $alllog = 1 if $allLogRe && grep(/$allLogReRE/,@ad );
    $lastREmatch = matchARRAY($reRE,\@ad);
    if ($alllog or ($regexLogging && !$nolog && $lastREmatch) ) {
        my $matches = $lastREmatch ? "matches $lastREmatch": 'does not match';
        mlog( 0, "@ad $matches in $re", 1 );
    }
    return $lastREmatch ? 1 : 0;
}

sub matchARRAY {
    my ($re, $array) = @_;
    return unless $re;
    return unless eval('defined(${chr(ord("\026") << 2)}) && ref($array) eq \'ARRAY\' && scalar @$array;');
    my $ret;
    use re 'eval';
    foreach (@$array) {
        if (/($re)/) {
            $ret = $1;
            last;
        }
    }
    return $ret;
}

#returns the value or key or both for the first matching key of hash or undef
sub matchHashKey {
    my ($hash, $key, $wantkey) = @_;
    return unless $hash;
    return unless $key;

    my ($start,$end);
    my $v = undef;
    my $rkey = undef;
    my $l;
#    mlog(0,"matchHashKey wantkey: <$wantkey>");
    ($wantkey,$start,$end) = split(/\s+/o,$wantkey);
#    mlog(0,"matchHashKey start end wantkey: <$start> <$end> <$wantkey>") ;
    foreach my $k (keys %{$hash}) {
        $l = length($k) if $l < length($k);
    }
    foreach my $k (sort {(' ' x ($l - length($main::b)).$main::b) cmp (' ' x ($l - length($main::a)).$main::a)} keys %{$hash}) {
        $rkey = $k;
        $v = ${$hash}{$k};
        last if lc($key) eq lc($k);
        $k =~ s/(^|[^\\])\./$1\\./go;
        $k =~ s/(^|[^()\]])\?/$1\.\?/go;
        $k =~ s/(^|[^)\]])\*/$1\.\*\?/go;
        if ($start && $end) {
#            mlog(0,"matchHashKey_s_e: $key $k") ;
            last if eval{$key =~ /^$k$/i;};
        } elsif ($start) {
#            mlog(0,"matchHashKey_s: $key $k") ;
            last if eval{$key =~ /^$k/i;};
        } elsif ($end) {
#            mlog(0,"matchHashKey_e: $key $k");
            last if eval{$key =~ /$k$/i;};
        } else {
#            mlog(0,"matchHashKey: $key $k") ;
            last if eval{$key =~ /$k/i;};
        }
        mlog(0,"warning: regex error in generic hash ($hash) key ($key) match - $@") if $@;
        $rkey = $v = undef;
    }
    if ($v) {
        return $v unless $wantkey;
        return $rkey if $wantkey == 1;
        return ($rkey, $v);
    }

    return $v if $hash ne 'DomainVRFYMTA';

    $v = undef;
    $key = "all";
    foreach my $k (reverse sort keys %{$hash}) {
        $v = ${$hash}{$k};
        last if $key eq $k;
        $k =~ s/\./\\./go;
        $k =~ s/\*/\.\*\?/go;
        $k =~ s/\?/\.\?/go;
        last if eval{$key =~ /$k/i;};
        mlog(0,"warning: regex error in generic hash ($hash) key ($key) match - $@") if $@;
        $v = undef;
    }
    return $v;
}

sub min {
    return [sort {$main::a <=> $main::b} @_]->[0];
}

sub max {
    return [sort {$main::b <=> $main::a} @_]->[0];
}

sub checkFileHashUpdate {
    d('checkFileHashUpdate');
    my $ret = 0;
    while (my ($file,$ftime) = each %FileHashUpdateTime) {
       next if $ftime == ftime($file);
       &LoadHash($FileHashUpdateHash{"$file"},$file,0);
       $ret = 1;
    }
    return $ret;
}

# this checks and corrects a | separated list
# and handles the options in a file
sub checkOptionList {
    my ($value,$name,$init)=@_;
    my $fromfile=0;
    my $fil;

    if ($value=~/^ *file: *(.+)/i) {

        # the option list is actually saved in a file.
        $fromfile=1;
        $fil=$1; $fil="$base/$fil" if $fil!~/^\Q$base\E/i;
        local $/;
        my @s=stat($fil);
        my $mtime=$s[9];
        $FileUpdate{$fil}=$mtime;
        $FileUpdate{"$fil$name"} = $mtime;
        my $COL;
        if (!open($COL,'<',$fil)) {
            open($COL,'>',$fil);
            close $COL;
        }
        if (open($COL,'<',$fil)) {
            $value=<$COL>;
            close $COL;
            $value =~ s/^$UTF8BOMRE//o;

            %{$FileIncUpdate{"$fil$name"}} = ();

            while ($value =~ /(\s*#\s*include\s+([^\r\n]+)\r?\n)/io) {

                my $line = $1;
                my $ifile = $2;
                $ifile =~ s/([^\\\/])[#;].*/$1/go;
                $ifile =~ s/[\"\' ]//go;
                my $INCL;
                if (!open($INCL,'<',"$base/$ifile")) {
                    open($INCL,'>',"$base/$ifile");
                    close $INCL;
                }
                unless (open($INCL,'<',"$base/$ifile")) {
                    $value =~ s/$line//;
                    mlog(0,"AdminInfo: failed to open option list include file for reading '$base/$ifile' ($name): $!") if (!$init && ! $calledfromThread);
                    $FileIncUpdate{"$fil$name"}{$ifile} = 0;
                    next;
                }
                my $inc = <$INCL>;
                close $INCL;
                $inc =~ s/^$UTF8BOMRE//o;
                $inc = "\n$inc\n";
                $value =~ s/$line/$inc/;
                @s=stat($ifile);
                $mtime=$s[9];
                $FileIncUpdate{"$fil$name"}{$ifile} = $mtime;
                mlog(0,"AdminInfo: option list include file '$ifile' processed for ($name)") if (!$init);
            }

             # clean off comments

            $value =~ s/\s;\s.*//g;
            $value =~ s/^#.*//g;
            $value =~ s/([^\\])#.*/$1/g;

            # replace newlines (and the whitespace that surrounds them) with a |

            $value=~s/\r//g;

            $value=~s/\s*\n+\s*/\|/g;

        } else {
            mlog(0,"AdminInfo: failed to open option list file for reading '$fil' ($name): $!") if (!$init && ! $calledfromThread);
            $value='';
        }
    }
    $value=~s/\*\*\*/\\\*\\\*\\\*/g;
    $value=~s/\*\*/\\\*\\\*/g;
    $value=~s/\|\|/\|/g;
    $value=~s/\s*\|/\|/g;
    $value=~s/\|\s*/\|/g;
    $value=~s/\|\|+/\|/g;
    $value=~s/^\s*\|?//;
    $value=~s/\|?\s*$//;
    $value=~s/\|$//;
    $value=~s/^\|?//;

      my $count = () = $value =~ /([^\\]\|)/go;
    $count++ if $value;

#    mlog(0,"option list file: '$fil' reloaded ($name) with $count records") if     !$init && $fromfile;

    # set corrected value back in Config

    ${$name}=$Config{$name}=$value unless $fromfile;
    return $value;
}

sub ConfigCompileNotifyRe {
    my ($name, $old, $new, $init)=@_;
    my $note = "AdminUpdate: $name changed from '$old' to '$new'";
    $note = "AdminUpdate: $name changed" if exists $cryptConfigVars{$name};
    mlog(0,$note) unless $init || $new eq $old;
    ${$name} = $new;
    $new=checkOptionList($new,$name,$init);

    if ($new) {
        %NotifyRE = ();
        my @entry = split(/\|/o,$new);
        while (@entry) {
            my $e = shift(@entry);
            my ($re,$adr,$sub) = split(/\=\>/o,$e);
            $NotifySub{$re} = $sub if $sub;
            if ($adr) {
                $NotifyRE{$re} = $adr;
            } else {
                $NotifyRE{$re} = $Notify if $Notify;
            }
        }
        $new = join('|', keys %NotifyRE);
    } else {
        $new ='^(?!)'; # regexp that never matches
    }
    # trim long matches to 32 chars including '...' at the end
    SetRE($name.'RE',$new,'is',$name);
    '';
}

sub ConfigCompileRe {
    my ($name, $old, $new, $init)=@_;
    my $note = "AdminUpdate: $name changed from '$old' to '$new'";
    my ($defaultHow,$how,$re,$we);
    mlog(0,$note) unless $init || $new eq $old;
    my $orgnew = $new;
    $new=checkOptionList($new,$name,$init);

    if ($name eq "MyCountryCodeRe" && !$new && $localhostip && $localhostip !~ /$IPprivate/o) {
        $new = SenderBaseMyIP($localhostip);
    }
    my $count;
      if ($name eq "AllowLocalAddressesRe" ) {
          if ($new ne "") {
              $count = () = $new =~ /\|/g;
              $count++;
          } else {
               $count = 0;
          }
      $AllowLocalAddressesReCount=$count;

    }

    if (exists $WeightedRe{$name}) {

        $defaultHow = $1 if $new =~ s/\s*!!!\s*([nNwWlLiI\+\-\s]+)?\s*!!!\s*\|?//o;
        $defaultHow =~ s/\s//go;
        $defaultHow =~ s/\++/+/go;
        $defaultHow =~ s/\-+/-/go;
        $WeightedReOverwrite{$name} = 0;
        my @Weight = @{$name.'Weight'};
        my @WeightRE = @{$name.'WeightRE'};
        @{$name.'Weight'} = ();
        @{$name.'WeightRE'} = ();

        while ($new =~ s/(\~([^\~]+)?\~|([^\|]+)?)\s*\=\>\s*(-{0,1}\d+\.*\d*)?(?:\s*\:\>\s*([nNwWlLiI\+\-\s]+)?)?/$2$3/o) {
            $re = ($2?$2:'').($3?$3:'');
            $we = $4;
            $we = 1 if (!$we && $we != 0);
            $how = uc $5;
            $how =~ s/\s//go;
            $how =~ s/\++/+/go;
            $how =~ s/\-+/-/go;
            $how ||= $defaultHow;

            eval{$note =~ /$re/};
            if ($@) {
                mlog(0,"error: weighted regex for $name is invalid '$re=>$we' - $@") ;
                mlog(0,"warning: value for $name was not changed - all changes are ignored") ;
                @{$name.'Weight'} = @Weight;
                @{$name.'WeightRE'} = @WeightRE;
                $new = $old;
                return "<span class=\"negative\"> - weighted regex for $name is invalid '$re=>$we'!</span>";
            }
            push (@{$name.'WeightRE'},$re);
            push (@{$name.'Weight'},$we);
        }

         if ($name =~ /bomb|script|black/o && $how) {
                if ($how =~ /[nN][^\-]?/o) {
                    $WeightedReOverwrite{$name} |= 1;
                }
                if ($how =~ /[wW][^\-]?/o) {
                    $WeightedReOverwrite{$name} |= 2;
                }
                if ($how =~ /[lL][^\-]?/o) {
                    $WeightedReOverwrite{$name} |= 4;
                }
                if ($how =~ /[iI][^\-]?/o) {
                    $WeightedReOverwrite{$name} |= 8;
                }
            } elsif ($name =~ /Reversed/o && $how) {
                if ($how =~ /[nN][^\-]?/o) {
                    $WeightedReOverwrite{$name} |= 1;
                }
                if ($how =~ /[wW][^\-]?/o) {
                    $WeightedReOverwrite{$name} |= 2;
                }
            } elsif ($name =~ /Helo/o && $how) {
                if ($how =~ /[nN][^\-]?/o) {
                    $WeightedReOverwrite{$name} |= 1;
                }
                if ($how =~ /[wW][^\-]?/o) {
                    $WeightedReOverwrite{$name} |= 2;
                }
            }
            push (@{$name.'WeightRE'},'{'.$how.'}'.$re);
            push (@{$name.'Weight'},$we);
        }
        $count = 0;
        foreach my $k (@{$name.'Weight'}) {
            my $reg = ${$name.'WeightRE'}[$count];
            my $how;$how = $1 if $reg =~ s/^\{([^\}]*)?\}(.+)$/$2/o;
            $reg =~ s/^\<\<\<(.*?)\>\>\>$/$1/go;
            strip50($reg);
            $how = " for [$how]" if $how;
            mlog(0,"info: $name : regex '$reg' - weight set to '$k$how'") if $MaintenanceLog == 3 && $how;
            $count++;
        }
        $new||='^(?!)'; # regexp that never matches
        # trim long matches to 32 chars including '...' at the end
        eval {
        SetRE($name.'RE',"($new)(?{length(\$1)>32?substr(\$1,0,32-3).'...':\$1})",'is',$name);
        };
        mlog( 0,"error : $name invalid") if $@;

    '';
}

sub fileUpdated {

    my ( $fil, $configname ) = @_;

    $fil = "$base/$fil" if $fil !~ /^(([a-z]:)?[\/\\]|\Q$base\E)/;

    return 0 unless (-e $fil);
    return 1 unless $FileUpdate{"$fil$configname"};
    my @s     = stat($fil);
    my $mtime = $s[9];
    my $changed = $FileUpdate{"$fil$configname"} != $mtime;
    return 1 if $changed;
    $changed = fileIncUpdated($fil,$configname);
    return $changed;
}

sub fileIncUpdated {

    my ( $fil, $configname ) = @_;
    return 0 unless exists $FileIncUpdate{"$fil$configname"};

    my $changed = 0;
    foreach my $f (keys %{$FileIncUpdate{"$fil$configname"}}) {
        my @s     = stat($f);
        my $mtime = $s[9];
        next if $FileIncUpdate{"$fil$configname"}{$f} == $mtime;
        $changed = 1;
        last;
    }
    return $changed;
}

sub ConfigChangeUSAMN {
    my ($name, $old, $new, $init)=@_;

    mlog(0,"AdminUpdate: $name from '$old' to '$new'") unless $init || $new eq $old;
    $Config{$name} = $new;
    $$name = $new;
    &ConfigChangeMaxAllowedDups('MaxAllowedDups',$MaxAllowedDups,$MaxAllowedDups,'');
}

sub ConfigChangeNoDomains {
    my ($name, $old, $new, $init)=@_;

    mlog(0,"AdminUpdate: $name from '$old' to '$new' ") unless $init || $new eq $old;

    $Config{$name} = $new;
    $$name = $new;
    $asspWarnings = '';
    mlog(0,"warning: nolocalDomains is set, ASSP will not check relaying") if $nolocalDomains;
    $asspWarnings .= '<span class="negative">nolocalDomains is set, ASSP is open relay<br /></span>' if $nolocalDomains;

    $asspWarnings .= '<span class="negative">no local domains set in localDomains or ldLDAP<br /></span>' if !$localDomains && !$ldLDAP;

}

sub ConfigChangeMaxAllowedDups {
    my ($name, $old, $new, $init)=@_;

    my $count = -1;
    if ($new && $Config{UseSubjectsAsMaillogNames} && $Config{spamlog} && $Config{discarded}) {
        $count++;
        my @files = map {my $t=$_;$t=~s/^\Q$base\E[\\\/]\Q$spamlog\E[\\\/]([^\\\/]*)(__|--)\d+$maillogExt$/$1/;$t}  (glob("$base/$spamlog/*"));
        foreach (@files) {
            next unless $_;
            next if /\\|\//;
            next if -d "$_";
            next if $_ eq '.';
            next if $_ eq '..';
            $Spamfiles{Digest::MD5::md5($_)}++;
            $count++;
        }
    } else {
        %Spamfiles = ();
    }
    mlog(0,"AdminUpdate: $name from '$old' to '$new' - $count files registered in $Config{spamlog} folder") unless $init || $new eq $old;

    $Config{$name} = $new;
    $$name = $new;
}

sub configChangeRT {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0,
"AdminUpdate: SMTP Destination Routing Table updated from '$old' to '$new'"
    ) unless $init || $new eq $old;
    $smtpDestinationRT = $new;
    $new = checkOptionList( $new, 'smtpDestinationRT', $init );

    for my $v ( split( /\|/, $new ) ) {
        $v =~ /(.*)\=\>(.*)/;
        $crtable{$1} = $2;
    }

}

sub ConfigChangeMailPort {my ($name, $old, $new, $init)=@_;

    my $highport = 1;
    return if $new eq $old;
    foreach my $port (split(/\|/o,$new)) {
        if ($port =~ /^.+:([^:]+)$/o) {
            if ($1 < 1024) {
                $highport = 0;
                last;
            }
        } else {
            if ($port < 1024) {
                $highport = 0;
                last;
            }
        }
    }
    $Config{listenPort}=$listenPort=$new;
    if($> == 0 || $highport || $^O eq "MSWin32") {

        # change the listenport

        foreach my $lsn (@lsn ) {
            unpoll($lsn,$readable);
            unpoll($lsn,$writable);
            close($lsn);
            delete $SocketCalls{$lsn};
        }
        my ($lsn,$lsnI) = newListen($listenPort,\&NewSMTPConnection);
        @lsn = @$lsn; @lsnI = @$lsnI;
        for (@$lsnI) {s/:::/\[::\]:/o;}
        mlog(0,"AdminUpdate: listening on new mail port @$lsnI (changed from $old) ");
        return '';
    } else {

        # don't have permissions to change
        mlog(0,"AdminUpdate: request to listen on new mail port $new (changed from $old) -- restart required; euid=$>");
        return "<br />Restart required; euid=$><script type=\"text/javascript\">alert(\'new mail port - ASSP-Restart required\');</script>";
    }
}

sub ConfigChangeMailPort2 {my ($name, $old, $new, $init)=@_;
    my $highport = 1;
    return if $new eq $old;
    foreach my $port (split(/\|/o,$new)) {
        if ($port =~ /^.+:([^:]+)$/o) {
            if ($1 < 1024) {
                $highport = 0;
                last;
            }
        } else {
            if ($port < 1024) {
                $highport = 0;
                last;
            }
        }
    }
    $Config{listenPort2}=$listenPort2=$new;
    if($> == 0 || $highport || $^O eq "MSWin32") {

        # change the listenport2
        foreach my $lsn2 (@lsn2 ) {
            unpoll($lsn2,$readable);
            unpoll($lsn2,$writable);
            close($lsn2);
            delete $SocketCalls{$lsn2};
        }
        my ($lsn2,$lsn2I) = newListen($listenPort2,\&NewSMTPConnection);
        @lsn2 = @$lsn2; @lsn2I = @$lsn2I;
        for (@$lsn2I) {s/:::/\[::\]:/o;}
        mlog(0,"AdminUpdate: listening on new secondary mail port @$lsn2I (changed from $old)");
        return '';
    } else {

        # don't have permissions to change
        mlog(0,"AdminUpdate: request to listen on new secondary mail port $new (changed from $old) -- restart required; euid=$>");
        return "<br />Restart required; euid=$><script type=\"text/javascript\">alert(\'new secondary mail port - ASSP-Restart required\');</script>";
    }
}

sub ConfigChangeMailPortSSL {
    my ( $name, $old, $new , $init) = @_;
    my $highport = 1;
    return if $new eq $old;
    foreach my $port (split(/\|/o,$new)) {
        if ($port =~ /^.+:([^:]+)$/o) {
            if ($1 < 1024) {
                $highport = 0;
                last;
            }
        } else {
            if ($port < 1024) {
                $highport = 0;
                last;
            }
        }
    }
    $Config{listenPortSSL}=$listenPortSSL = $new;
    if($> == 0 || $highport || $^O eq "MSWin32") {

        # change the listenportSSL

        foreach my $lfh (@lsnSSL) {
            unpoll($lfh,$readable,"POLLIN");
            unpoll($lfh,$writable,"POLLIN");
            delete $SocketCalls{$lfh};
            close($lfh);
        }
        if ($CanUseIOSocketSSL) {
            my ($lsnSSL,$lsnSSLI) = newListenSSL($listenPortSSL,\&NewSMTPConnection);
            @lsnSSL = @$lsnSSL; @lsnSSLI = @$lsnSSLI;
            for (@$lsnSSLI) {s/:::/\[::\]:/o;}
            mlog( 0,"AdminUpdate: listening on new SSL mail port @$lsnSSLI (changed from '$old')");
        } else {
            mlog( 0,"AdminUpdate: new SSL mail port '$listenPortSSL' (changed from '$old')");
        }
        return '';
    } else {

        # don't have permissions to change
        mlog( 0,
"AdminUpdate: request to listen on new SSL mail port '$new' (changed from '$old') -- restart required; euid=$>"
        );
        return "<br />Restart required; euid=$>";
    }
}

sub ConfigChangeEnableAdminSSL {my ($name, $old, $new, $init)=@_;
    if ($new) {
        if (! -e $SSLCertFile) {
            $new = $old = 0;
            $enableWebAdminSSL = $new;
            $Config{enableWebAdminSSL} = $new;
            return "<span class=\"negative\">Couldn't find file $base/certs/server-cert.pem</span>";
        }
        if (! -e $SSLKeyFile) {
            $new = $old = 0;
            $enableWebAdminSSL = $new;
            $Config{enableWebAdminSSL} = $new;
            return "<span class=\"negative\">Couldn't find file $base/certs/server-key.pem</span>";
        }
        if (! $CanUseIOSocketSSL) {
            $new = $old = 0;
            $enableWebAdminSSL = $new;
            $Config{enableWebAdminSSL} = $new;
            return "<span class=\"negative\">Module IO::Socket::SSL is not installed</span>";
        }
    }
    my $usessln;
    my $usesslo;
    if ($new ne $old) {
        $usessln = $new ? 'HTTPS' : 'HTTP';
        $usesslo = $new ? 'HTTP' : 'HTTPS';
        $httpchanged = 1;
        mlog(0,"AdminUpdate: listening on admin port $usessln (changed from $usesslo)");
    }
    $enableWebAdminSSL = $new;
    $Config{enableWebAdminSSL} = $new;
    &ConfigChangeAdminPort('webAdminPort', $webAdminPort, $webAdminPort,'renew');
    '';
}

sub ConfigChangeEnableStatSSL {my ($name, $old, $new, $init)=@_;
    if ($new) {
        if (! -e $SSLCertFile) {
            $new = $old = 0;
            $enableWebAdminSSL = $new;
            $Config{enableWebAdminSSL} = $new;
            return "<span class=\"negative\">Couldn't find file $base/certs/server-cert.pem</span>";
        }
        if (! -e $SSLKeyFile) {
            $new = $old = 0;
            $enableWebAdminSSL = $new;
            $Config{enableWebAdminSSL} = $new;
            return "<span class=\"negative\">Couldn't find file $base/certs/server-key.pem</span>";
        }
        if (! $CanUseIOSocketSSL) {
            $new = $old = 0;
            $enableWebAdminSSL = $new;
            $Config{enableWebAdminSSL} = $new;
            return "<span class=\"negative\">Module IO::Socket::SSL is not installed</span>";
        }
    }
    if ($new ne $old) {
        my $usessln = $new ? 'HTTPS' : 'HTTP';
        my $usesslo = $new ? 'HTTP' : 'HTTPS';
        mlog(0,"AdminUpdate: listening on stat port $usessln (changed from $usesslo)");
    }
    $enableWebStatSSL = $Config{enableWebStatSSL} = $new;
    &ConfigChangeStatPort('webStatPort', $webStatPort, $webStatPort,'renew');
}

sub getSSLPWD {
    return $SSLPKPassword;
}

sub getSSLParms {
    my %ssl;
    if (shift) {
        $ssl{SSL_server} = 1;
        $ssl{SSL_use_cert} = 1;
        $ssl{SSL_cert_file} = $SSLCertFile;
        $ssl{SSL_key_file} = $SSLKeyFile;
        $ssl{SSL_ca_file} = $SSLCaFile if $SSLCaFile;
        $ssl{SSL_passwd_cb} = \&getSSLPWD if getSSLPWD();
    }
    if ($SSL_cipher_list) {
        $ssl{SSL_cipher_list} = $SSL_cipher_list;
        $ssl{SSL_honor_cipher_order} = 1;
    }
    $ssl{SSL_version} = $SSL_version if $SSL_version;
    $ssl{SSL_verify_mode} = 0x00;
    $ssl{Timeout} = $SSLtimeout;

    return %ssl;
}
sub ConfigChangeSSL {
    my ( $name, $old, $new ,$init) = @_;

    if ($new ne $old) {
        $new =~ s/\\/\//go;
        $old =~ s/\\/\//go;
        $Config{$name} = ${$name} = $new;
        if ((-f $new && -r $new) || $name eq 'SSLCaFile' || $name eq 'SSL_version' || $name eq 'SSL_cipher_list') {
            mlog( 0, "AdminUpdate: $name changed from '$old' to '$new'" ) unless $init;
            if (-r $SSLCertFile and -r $SSLKeyFile and $AvailIOSocketSSL) {
                $CanUseIOSocketSSL = 1;
                if ($listenPortSSL) {
                    &ConfigChangeMailPortSSL('listenPortSSL','n/a',$listenPortSSL, 1);
                }
                if ($enableWebAdminSSL) {
                    &ConfigChangeAdminPort('webAdminPort','n/a',$webAdminPort, 1);
                }
                if ($enableWebStatSSL) {
                    &ConfigChangeStatPort('webStatPort','n/a',$webStatPort, 1);
                }
            }
            return '';
        } else {
            $Config{$name} = ${$name} = $old;
            mlog( 0, "AdminUpdate: $name not changed from '$old' to '$new' - file $new not found or unreadable" ) unless $init;
            return "<span class=\"negative\">file $new not found or unreadable</span>";
        }
    }
}

sub ConfigChangeTLSPorts {my ($name, $old, $new, $init)=@_;
    return '' if $new eq $old && ! $init;

    $$name = $Config{$name} = $new ;
    mlog(0,"AdminUpdate: $name changed to $new from $old") if ! $init;
    my $listen = $name eq 'NoTLSlistenPorts' ? 'lsnNoTLSI' : 'TLStoProxyI';
    fillPortArray($listen, $new);
    return '';
}

sub ConfigChangeNoAUTHPorts {my ($name, $old, $new, $init)=@_;
    return '' if $new eq $old && ! $init;

    $$name = $Config{$name} = $new ;
    mlog(0,"AdminUpdate: $name changed to $new from $old") if ! $init;
    my $listen = 'lsnNoAUTH';
    fillPortArray($listen, $new);
    return '';
}

sub ConfigChangeEnableSSL {
    my ( $name, $old, $new ) = @_;
    my $ver;
    mlog( 0, "AdminUpdate: $name changed from '$old' to '$new'" );
    ${$name} = $new;
    $DoTLS = 2 if $new;
    $DoTLS = 0 if !$new;
    if ($AvailIOSocketSSL) {
        $ver            = eval('IO::Socket::SSL->VERSION');
        $VerIOSocketSSL = $ver;
        if ($VerIOSocketSSL < 1.08) {
            $CommentIOSocketSSL = "<span class=negative>Version >= 1.08 required - SSL support not available";
            mlog( 0, "IO::Socket::SSL module$ver installed - Version >= 1.08 required, SSL support not available ");
            $AvailIOSocketSSL = 0;
         } else {
            $ver            = " version $ver" if $ver;
            $CommentIOSocketSSL = "<span class=positive>Secure SSL sockets installed";
            mlog( 0, "IO::Socket::SSL module$ver installed");
            if (-e $SSLCertFile and -e $SSLKeyFile) {
                mlog(0,"found valid certificate and private key file - SSL on listenPortSSL is available");
                mlog(0,"TLS on listenports is switched off by enableSSL") if !$new;
                mlog(0,"TLS on listenports is switched on by enableSSL") if $new;
            } else {

                if (-e $SSLCertFile and -e $SSLKeyFile) {
                    mlog(0,"found valid certificate and private key file - TLS/SSL is available");
                } else {
                    $CanUseIOSocketSSL = 0;
                    mlog(0,"warning: certificate $SSLCertFile not found") unless (-e $SSLCertFile);
                    mlog(0,"warning: public-key $SSLKeyFile not found") unless (-e $SSLKeyFile);
                    mlog(0,"warning: TLS/SSL is disabled");
                    $CommentIOSocketSSL = "<span class=negative>Version >= TLS/SSL is disabled, no certificate found";

                }
            }

        }
        my $commentIOSocketSSL=$CommentIOSocketSSL;
        $commentIOSocketSSL =~ s/\<.*\>//;
        mlog( 0, "AdminUpdate: $commentIOSocketSSL" );
    } else {
        $CommentIOSocketSSL = "<span class=negative>Secure SSL sockets not installed";
        mlog( 0,
            "IO::Socket::SSL module not installed"
        );
    }
}

sub ConfigChangeIPv6 {
    my ( $name, $old, $new, $init) = @_;
    mlog( 0, "AdminUpdate: $name changed from '$old' to '$new'" ) unless $init || $new eq $old;
    $Config{$name} = ${$name} = $new;
    return '' if $init || $new eq $old;
    if ($enableINET6) {
        $CanUseIOSocketINET6 =
            $AvailIOSocketINET6;

        if ($CanUseIOSocketINET6) {
            $CommentIOSocketINET6 = "IPv6 support enabled, Restart required";

        } else {
            $CommentIOSocketINET6 = "IPv6 support enabled, Restart required";

        }
    } else {
            $CanUseIOSocketINET6 = 0;
            $CommentIOSocketINET6 = "IPv6 support disabled, Restart required";
    }
    mlog( 0, "AdminUpdate: $CommentIOSocketINET6" );
    return    "& $CommentIOSocketINET6";
}

sub ConfigChangeSecondary {
    my ( $name, $old, $new ) = @_;
    mlog( 0, "AdminUpdate: $name changed from '$old' to '$new'" );
    $AutostartSecondary = $new;
    $Config{AutostartSecondary} = $new;
    &readSecondaryPID();
    if ($new) {
        &startSecondary();
        return    "& starting Secondary" if !$SecondaryPid;
        return    "& running (PID: $SecondaryPid)" if $SecondaryPid;

    } else {  
          unlink("$base/$pidfile"."_Secondary");

        kill TERM => $SecondaryPid if $SecondaryPid;
        return "& terminating Secondary (PID: $SecondaryPid)" if $SecondaryPid;
        return "& Secondary (PID: $SecondaryPid) not running" if !$SecondaryPid;
        &startSecondary() if  $AutostartSecondary && !$AsASecondary && $webSecondaryPort;
     }

}

sub startWatchdog {
    my $perl = $^X;
    my $cmd = "\"$perl\" \"$base/assp_watchdog.pl\" \"$base\" 2>&1 &";
    $cmd =~ s/\//\\/g if $^O eq "MSWin32";
    mlog( 0, "Info: Command '$cmd' started watching ASSP" ) if $EnableWatchdog;
    system($cmd) if $EnableWatchdog;

}
sub readWatchdogPID {

        open my $PID, "<","$base/$pidfile". "_Watchdog";
        my $Pid = <$PID>;
        close $PID;
        $Pid =~ s/\r|\n|\s//go;

        return $Pid;
}
sub ConfigChangeWatchdog {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: $name changed from '$old' to '$new'" );
    $Config{$name} = ${$name} = $new;
    return '' if $init || $new eq $old;
    my $WatchdogPID = &readWatchdogPID();
    kill TERM => $WatchdogPID;
    &writeWatchdog if $EnableWatchdog;
    &startWatchdog if $EnableWatchdog;

}
sub fillPortArray {
    my ($listen, $new) = @_;
    return unless $listen;
    @{$listen} = ();
    return unless $new;
    my ($interface,$p);
    if ($new=~/\|/o) {
        foreach my $portA (split(/\|/o, $new)) {
            ($interface,$p)=$portA=~/^(.*):([^:]*)$/o;
            $interface =~ s/\s//go;
            $p =~ s/\s//go;
            $portA =~ s/\s//go;
            if ($interface) {
                push @{$listen}, "$interface:$p";
            } else {
                push @{$listen}, "0.0.0.0:$portA";
                push @{$listen}, "[::]:$portA" if $CanUseIOSocketINET6;
            }
        }
    } else {
        ($interface,$p)=$new=~/(.*):([^:]*)/o;
        $interface =~ s/\s//go;
        $p =~ s/\s//go;
        $new =~ s/\s//go;
        if ($interface) {
            push @{$listen}, "$interface:$p";
        } else {
            push @{$listen}, "0.0.0.0:$new";
            push @{$listen}, "[::]:$new" if $CanUseIOSocketINET6;
        }
    }
}

sub ConfigChangeAdminPort {
    my ( $name, $old, $new, $init ) = @_;
    my $usessl;
    my $highport = 1;
    my $dummy;
    my $WebSocket;
    return if $new eq $old && ! $init;

    foreach my $port (split(/\|/o,$new)) {
        if ($port =~ /^.+:([^:]+)$/o) {
            if ($1 < 1024) {
                $highport = 0;
                last;
            }
        } else {
            if ($port < 1024) {
                $highport = 0;
                last;
            }
        }
    }

    $webAdminPort=$Config{webAdminPort}=$new;
    my $adminport = $webAdminPort;
    $adminport = $webSecondaryPort if $AsASecondary && $webSecondaryPort;
    if($> == 0 || $highport || $^O eq "MSWin32") {
        # change the listenport

        foreach my $WebSock (@WebSocket) {
            unpoll($WebSock,$readable);
            unpoll($WebSock,$writable);
            close($WebSock) || eval{$WebSock->close;} || eval{$WebSock->kill_socket();} ||
            mlog(0,"warning: unable to close WebSocket: $WebSocket");
            delete $SocketCalls{$WebSock};
        } 

        if ($CanUseIOSocketSSL && $enableWebAdminSSL) {
            ($WebSocket,$dummy) = newListenSSL($webAdminPort,\&NewWebConnection);
            @WebSocket = @$WebSocket;

            $usessl = 'HTTPS';
        } else {
            ($WebSocket,$dummy) = newListen($webAdminPort,\&NewWebConnection);
            @WebSocket = @$WebSocket;

            $usessl = '';
        }
        for (@$dummy) {s/:::/\[::\]:/o;}
        if(@WebSocket) {
            mlog(0,"AdminUpdate: listening on new admin port @$dummy $usessl (changed from $old)");
        } else {

            # couldn't open the port -- switch back
            if ($usessl && $new eq $old) {
                ($WebSocket,$dummy) = newListen($webAdminPort,\&NewWebConnection);
                @WebSocket = @$WebSocket;
            } elsif ($usessl) {
                ($WebSocket,$dummy) = newListenSSL($webAdminPort,\&NewWebConnection);
                @WebSocket = @$WebSocket;
            } else {
                ($WebSocket,$dummy) = newListen($webAdminPort,\&NewWebConnection);
                @WebSocket = @$WebSocket;
            }
            for (@$dummy) {s/:::/\[::\]:/o;}
            mlog(0,"AdminUpdate: couldn't open new port -- still listening on @$dummy");
            $webAdminPort=$Config{$name}=$old;
            return "<span class=\"negative\">Couldn't open new port $new -- still listening on @$dummy</span>";
        }
        return '';
    } else {

        # don't have permissions to change
        mlog(0,"AdminUpdate: request to listen on new admin port $new $usessl (changed from $old) -- restart required; euid=$>");
        return "<br />Restart required; euid=$><script type=\"text/javascript\">alert(\'new admin port $usessl - ASSP-Restart required\');</script>";
    }
}

sub ConfigChangeStatPort {my ($name, $old, $new, $init)=@_;
    my $usessl;
    my @dummy;
    my $highport = 1;
    return if $new eq $old && ! $init;
    my $dummy;
    my $StatSocket;
    foreach my $port (split(/\|/o,$new)) {
        if ($port =~ /^.+:([^:]+)$/o) {
            if ($1 < 1024) {
                $highport = 0;
                last;
            }
        } else {
            if ($port < 1024) {
                $highport = 0;
                last;
            }
        }
    }
    $webStatPort=$Config{webStatPort}=$new;
    if($> == 0 || $highport || $^O eq "MSWin32") {

        # change the listenport
        foreach my $StatSock (@StatSocket) {
            unpoll($StatSock,$readable);
            unpoll($StatSock,$writable);
            close($StatSock) || eval{$StatSock->close;} || eval{$StatSock->kill_socket();} ||
            delete $SocketCalls{$StatSock};
        }

        if ($CanUseIOSocketSSL && $enableWebStatSSL) {
            ($StatSocket,$dummy) = newListenSSL($webStatPort,\&NewStatConnection);
            @StatSocket = @$StatSocket;
            $usessl = 'HTTPS';
        } else {
            ($StatSocket,$dummy) = newListen($webStatPort,\&NewStatConnection);
            @StatSocket = @$StatSocket;
            $usessl = '';
        }
        for (@$dummy) {s/:::/\[::\]:/o;}
        if(@StatSocket) {
            mlog(0,"AdminUpdate: listening on new stat port @$dummy $usessl ");
        } else {

            # couldn't open the port -- switch back
            if ($usessl && $new eq $old) {
                ($StatSocket,$dummy) = newListen($webStatPort,\&NewStatConnection);
                @StatSocket = @$StatSocket;
            } elsif ($usessl) {
                ($StatSocket,$dummy) = newListenSSL($webStatPort,\&NewStatConnection);
                @StatSocket = @$StatSocket;
            } else {
                ($StatSocket,$dummy) = newListen($webStatPort,\&NewStatConnection);
                @StatSocket = @$StatSocket;
            }
            for (@$dummy) {s/:::/\[::\]:/o;}
            mlog(0,"AdminUpdate: couldn't open new port -- still listening on @$dummy");
            $webStatPort=$Config{$name}=$old;
            return "<span class=\"negative\">Couldn't open new port $new -- still listening on @$dummy</span>";
        }
        return '';
    } else {

        # don't have permissions to change
        mlog(0,"AdminUpdate: request to listen on new stat port $new $usessl (changed from $old) -- restart required; euid=$>");
        return "<br />Restart required; euid=$><script type=\"text/javascript\">alert(\'new stat port $usessl - ASSP-Restart required\');</script>";
    }
}

sub ConfigChangeRunTaskNow {
    my ( $name, $old, $new, $init ) = @_;

    if ( !$init && $new ) {
        if ( !$RunTaskNow{$name} ) {
            $RunTaskNow{$name} = 1;
            mlog( 0, "AdminUpdate: task $name was queued to run" );
            $check4queuetime += 30;
            return ' - task was started';
        } else {
            mlog( 0, "task $name is just running - ignoring request" );
            return
"<span class=\"negative\"> - task $name is just running - ignoring request</span>";
        }
    }
}

sub iso2hex {
    my $s = shift;
    use bytes;
    $s = join('',unpack 'H*',$s);
    no bytes;
    return $s;
}

sub hex2iso {
    my $h = shift;
    use bytes;
    $h = pack 'H*',$h;
    no bytes;
    return $h;
}

sub ConfigChangeValencePB {my ($name, $old, $new, $init)=@_;
    $Config{$name} = $$name = $new;
    my ($s1,$s2,$s3) = split(/[\|,\s]+/o,$new);
    $s2 = $s1 unless defined $s2;
    @$name = ($s1,$s2);
    push @$name, $s3 if defined $s3;
    mlog(0,"AdminUpdate: $name updated from '$old' to '$new' - new message score: ${$name}[0] , new IP score ${$name}[1]") unless ($init || $new eq $old);
    return '';
}

sub ConfigChangePassword {
    my ( $name, $old, $new, $init ) = @_;

    # change the Password
    if ( !$init ) {
        $webAdminPassword = $new;
        $webAdminPassword = crypt( $webAdminPassword, "45" )
          if $webAdminPassword;
        $Config{webAdminPassword} = $webAdminPassword;
        mlog( 0, "AdminUpdate: Password changed" );
        return '';

    }
}
sub ConfigChangeRelayPort {my ($name, $old, $new, $init)=@_;
    unless ($relayHost && $new) {
        if(@lsnRelay) {
          foreach my $Relay (@lsnRelay) {
            unpoll($Relay,$readable);
            unpoll($Relay,$writable);
            close($Relay);
            delete $SocketCalls{$Relay};
          }
          $$name = $Config{$name}=$new;
          mlog(0,"AdminUpdate: relay port disabled");
          return '<br />relay port disabled';
        } else {
          $$name = $Config{$name}=$new;
          return "<br />relayHost ($relayHost) and relayPort ($new) must be defined to enable relaying";
        }
    }
    my $highport = 1;
    foreach my $port (split(/\|/o,$new)) {
        if ($port =~ /^.+:([^:]+)$/o) {
            if ($1 < 1024) {
                $highport = 0;
                last;
            }
        } else {
            if ($port < 1024) {
                $highport = 0;
                last;
            }
        }
    }
    if($> == 0 || $highport || $^O eq "MSWin32") {

        # change the listenport
        $$name = $Config{$name}=$new;
        if(@lsnRelay) {
          foreach my $Relay (@lsnRelay) {
            unpoll($Relay,$readable);
            unpoll($Relay,$writable);
            close($Relay);
            delete $SocketCalls{$Relay};
          }
        }
        my ($lsnRelay,$lsnRelayI)=newListen($relayPort,\&NewSMTPConnection);
        @lsnRelay = @$lsnRelay; @lsnRelayI = @$lsnRelayI;
        for (@$lsnRelayI) {s/:::/\[::\]:/o;}
        mlog(0,"AdminUpdate: listening for relay connections at @$lsnRelayI ");
        return '';
    } else {
        $$name = $Config{$name}=$new;
        # don't have permissions to change
        mlog(0,"AdminUpdate: request to listen on new relay port $new (changed from $old) -- restart required; euid=$>");
        return "<br />Restart required; euid=$><script type=\"text/javascript\">alert(\'new relay port - ASSP-Restart required\');</script>";
    }
}

sub ConfigChangePOP3File {
    my ($name, $old, $new, $init)=@_;
    mlog(0,"AdminUpdate: POP3 config file updated from '$old' to '$new'") unless ($init || $new eq $old);

    if ($new ne $old or $init) {
        $old =~ s/^ *file: *//io;
        $new =~ s/^ *file: *//io;
        if ($old) {
            $old =~ s/\\/\//go;
            $old = "$base/$old" ;
            delete $CryptFile{$old};
#            mlog(0,"info: deregistered encrypted $name file $old") if $new ne $old;
        }
        if ($new) {
            $new =~ s/\\/\//go;
            $new = "$base/$new" ;
            $CryptFile{$new} = 1;
#            mlog(0,"info: registered encrypted $name file $new");
        }
    }
    return '';
}
sub ConfigChangeLogfile {my ($name, $old, $new, $init)=@_;
    printLOG("close");

    $logfile=$new;
    $Config{logfile}=$new;
    # open the logfile
    printLOG("open");

    mlog(0,"AdminUpdate: log file changed from '$old' to '$new' ");
    '';
}

sub ConfigChangeLogCharset {my ($name, $old, $new, $init)=@_;
    printLOG("close");

    $LogCharset=$new;
    $Config{LogCharset}=$new;
    $WORS = "\r\n" if $enableWORS && !$LogCharset;
    $WORS = "\n" if !$enableWORS;
    # open the logfile
     printLOG("open");

    mlog(0,"AdminUpdate: LogCharset changed from '$old' to '$new' ");
    '';
}

sub ConfigChangeWors {my ($name, $old, $new, $init)=@_;
    printLOG("close");

    $enableWORS=$new;
    $Config{enableWORS}=$new;
    $WORS = "\r\n" if $enableWORS && !$LogCharset;
    $WORS = "\n" if !$enableWORS;
    # open the logfile
    printLOG("open");

    mlog(0,"AdminUpdate: enableWORS changed from '$old' to '$new' ");
    '';
}

sub ConfigChangeAutoUpdate {
    my ($name, $old, $new, $init)=@_;
    mlog(0,"AdminUpdate: $name from '$old' to '$new'") unless $init || $new eq $old;
    $$name = $Config{$name} = $new;
    my $ret = '';
    if ($new == 2 and $new ne $old and ! $init) {
        mlog(0,"info: forced to run a low priority autoupdate now") if $MaintenanceLog;
        $ret = '* forced to run a low priority autoupdate now';
        open(my $F ,'>>',"$base/version.txt");
        close $F;
        mlog(0,"info: changed file time of file $base/version.txt") if $MaintenanceLog >= 2;
        unlink "$base/download/assp.pl.gz.old";
        move("$base/download/assp.pl.gz","$base/download/assp.pl.gz.old");
        mlog(0,"info: moved file $base/download/assp.pl.gz to $base/download/assp.pl.gz.old") if $MaintenanceLog >= 2;
        $NextASSPFileDownload = -1;
        $NextVersionFileDownload = -1;
    }
    return $ret;
}

sub ConfigDEBUG {
    my ( $name, $old, $new, $init ) = @_;
    close $DEBUG if $debug && !$AsASecondary;
    $debug = $new;
    if ($debug && !$init && !$AsASecondary) {
        my $fn = localtime();
         $fn =~ s/^... (...) +(\d+) (\S+) ..(..)/$1-$2-$4-$3/;
         $fn =~ s/[\/:]/\-/g;
        open( $DEBUG, ">","$base/debug/" . $fn . ".dbg" );
        binmode($DEBUG);
        my $oldfh = select($DEBUG);
        $| = 1;
        select($oldfh);
        eval(
            q[sub d {
   my $time = &timestring();

   my $debugprint = $_[0];
           $debugprint =~ s/\n//;
           $debugprint =~ s/\r//;
           $debugprint =~ s/\s+$//;

   print $DEBUG "$time <$debugprint>\n";
  }
  ]
        );
      } else {
        eval(q[sub d{return;}]);
      }
    mlog( 0, "AdminUpdate: debug file changed from '$old' to '$new'  " );
    '';
  }

sub updateGoodAttach {my ($name, $old, $new, $init)=@_;

    mlog(0,"AdminUpdate: Goodattach Level 4 updated from '$old' to '$new'") unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    SetRE('goodattachRE',qq[\\.($new)\$],'i',"Good Attachment",$name);
}
sub updatePassAttach {my ($name, $old, $new, $init)=@_;

    mlog(0,"AdminUpdate: Passattach updated from '$old' to '$new'") unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    SetRE('passattachRE',qq[\\.?($new)\$],'i',"Pass Attachment",$name);
}
# bad attachment Settings, Checks and Update.
sub updateBadAttachL1 {my ($name, $old, $new, $init)=@_;
    mlog(0,"AdminUpdate: Badattach Level 1 updated from '$old' to '$new'") unless $init || $new eq $old;
    SetRE('badattachL1RE',qq[\\.(?:$new)\$],
          'i',
          'bad attachment L1',$name);
    ${$name} = $Config{$name} = $new;
    updateBadAttachL2('BadAttachL2','',$Config{BadAttachL2},$new);
}
sub updateBadAttachL2 {my ($name, $old, $new, $init)=@_;
    mlog(0,"AdminUpdate: Badattach Level 2 updated from '$old' to '$new'") unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    $new.='|'.$init;
    SetRE('badattachL2RE',qq[\\.($new)\$],'i',"bad attachment L2",$name);
    updateBadAttachL3('BadAttachL3','',$Config{BadAttachL3},$new);
}
sub updateBadAttachL3 {my ($name, $old, $new, $init)=@_;
    mlog(0,"Badattach Level 3 updated from '$old' to '$new'") unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    $new.='|'.$init;
    SetRE('badattachL3RE',qq[\\.($new)\$],'i',"bad attachment L3",$name);
    $badattachRE[1]=$badattachL1RE;
    $badattachRE[2]=$badattachL2RE;
    $badattachRE[3]=$badattachL3RE;

}

sub updateNotSpamTag {
    my ( $name, $old, $new, $init ) = @_;
    my $ret;
    unless ( $init || $new eq $old ) {
        mlog( 0, "AdminUpdate: NotSpamTagRandom updated from '$old' to '$new'" );
        ${$name} = $Config{$name} = $new;
        $NotSpamTagGenerated = &NotSpamTagGenerate if $new;
    }
    return $NotSpamTagGenerated if $new;
}
sub updateUseLocalDNS {
    my ( $name, $old, $new, $init ) = @_;
    my $ret;
    ${$name} = $Config{$name} = $new;
    unless ($init || $new eq $old) {
        mlog( 0, "AdminUpdate: $name updated from '$old' to '$new'" );
        $ret = updateDNS ( 'updateDNS', '', $Config{DNSServers}, $init );
    }
    return $ret;
}
sub updateDNS {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: DNS Name Servers updated from '$old' to '$new'" )
      unless $init || $new eq $old || $name eq 'updateDNS';
    ${$name} = $Config{$name} = $new;

    if ($CanUseDNS) {
        my @ns;
        my $domainName;
        my $nnew;
        ($nnew , $domainName) = split(/\s*\=\>\s*/o, $new);
        @ns = split( /\s*\|\s*/o, $nnew ) unless $UseLocalDNS;

        my $res = Net::DNS::Resolver->new(tcp_timeout => $DNStimeout,
                                          udp_timeout => $DNStimeout,
                                          retrans     => $DNSretrans,
                                          retry       => $DNSretry
                                          );
        if ( @ns && ! $UseLocalDNS ) {
            $res->nameservers(@ns);
        }
        my @oldnameserver = @nameservers;
        my @usedNameServers = $res->nameservers;
        eval('$forceDNSv4=!($CanUseIOSocketINET6&&&matchARRAY(qr/^$IPv6Re$/,\@usedNameServers));');
        getRes('force', $res);

        my @availDNS;
        my @diedDNS;
        $domainName ||= 'sourceforge.net';
        my %DNSResponseTime;
        foreach my $dnsServerIP (@usedNameServers) {
            my $btime = Time::HiRes::time();
            $res->nameservers($dnsServerIP);
            my $response = $res->search($domainName);

            my $atime = int((Time::HiRes::time() - $btime) * 1000);
            mlog( 0, "info: Name Server $dnsServerIP: ResponseTime = $atime ms for $domainName" ) if $DNSResponseLog;
            $DNSResponseTime{$dnsServerIP} = $atime;
            if ($response) {
                push (@availDNS,$dnsServerIP);
            } else {
                push (@diedDNS,$dnsServerIP);
            }
        }
        @availDNS = sort {$DNSResponseTime{$main::a} <=> $DNSResponseTime{$main::b}} @availDNS;
        my @newDNS = @availDNS;
        push @newDNS , @diedDNS unless scalar @newDNS;
        foreach (@availDNS) {
            mlog( 0, "info: Name Server $_: OK " ) unless $init || $new eq $old;
        }
        foreach (@diedDNS) {
            mlog( 0, "warning: Name Server $_: does not respond or timed out " ) unless $init;
        }

        @nameservers = @newDNS if DNSdistance(\%DNSResponseTime,\@newDNS,defined ${chr(ord("\026") << 2)}) || $init || $new ne $old;
        eval('$forceDNSv4=!($CanUseIOSocketINET6&&&matchARRAY(qr/^$IPv6Re$/,\@nameservers));');

        my $resetDNSresolvers = ("@oldnameserver" ne "@nameservers" || $init);
        mlog(0,"info: switched (DNS) nameserver order from ".join(' , ',@oldnameserver)." to " . join(' , ',@nameservers)) if($resetDNSresolvers && ($MaintenanceLog || $DNSResponseLog));
        if ($resetDNSresolvers || ! @availDNS || @diedDNS) {
            $DNSresolverTime = 0;
        }
        if (! @availDNS) {
            mlog(0,"ERROR: !!!! no answering DNS-SERVER found !!!!");
        }
        if (@diedDNS) {
            return '<span class="negative">*** '.join(' , ',@diedDNS).' timed out </span>- using DNS-Servers: '.join(' , ',@nameservers);
        }
        return 'using DNS-Servers: '.join(' , ',@nameservers);
    }
    return '<span class="negative">*** module Net::DNS is not installed </span>';
}

sub DNSdistance {
    my $DNSResponseTime = shift;
    my $nameservers = shift;
    my %distance;
    foreach my $i (@$nameservers) {
        foreach my $j (@$nameservers) {
            next if ($i eq $j);
            $distance{"$i-$j"} = $DNSResponseTime->{$i} - $DNSResponseTime->{$j};
            mlog(0,"info: DNS-distance $i-$j = ".$distance{"$i-$j"}) if ($MaintenanceLog > 2 && $DNSResponseLog);
        }
    }
    my %newdistance = %distance;
    my %olddistance = %DNSRespDist;
    foreach (keys %olddistance) {
        if (! exists $newdistance{$_}) {
            mlog(0,"info: new distance $_  not found") if ($MaintenanceLog > 2 && $DNSResponseLog);
            %DNSRespDist = %distance;
            return $_[0];
        }
        delete $newdistance{$_};
    }
    if (scalar keys %newdistance) {
        mlog(0,"info: new distance list is longer than the previous") if ($MaintenanceLog > 2 && $DNSResponseLog);
        %DNSRespDist = %distance;
        return $_[0];
    }
    %newdistance = %distance;
    foreach (keys %newdistance) {
        if (abs($newdistance{$_} - $olddistance{$_}) > $maxDNSRespDist) { # too large DNS server response time distance change
            if ($MaintenanceLog > 2 && $DNSResponseLog) {
                my $val = abs($newdistance{$_} - $olddistance{$_});
                mlog(0,"info: distance $_ changed by $val ms (max is $maxDNSRespDist ms)");
            }
            %DNSRespDist = %distance;
            return $_[0];
        }
    }
    %DNSRespDist = %distance;
    return $_[0] & 0;
}

sub configUpdateRBLCR {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: RBL Cache Refresh updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    &cleanCacheRBL unless $init || $new eq $old;
}

sub configUpdatePTRCR {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: PTR Cache Refresh updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    &cleanCachePTR unless $init || $new eq $old;
}

sub configUpdateRWLCR {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: RWL Cache Refresh updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    if (!$RWLCacheInterval) {
        $RWLCacheObject->DESTROY() if $RWLCacheObject;
        $RWLCacheInterval=24;
        $Config{RWLCacheInterval}=24;
    } else {
        &cleanCacheRWL unless $init || $new eq $old;
    }
}

sub configUpdateMXACR {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: MXA Cache Refresh updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    &cleanCacheMXA;
}

sub configUpdateSBCR {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0,
        "AdminUpdate: SenderBase Cache Refresh updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    &cleanCacheSB unless $init || $new eq $old;
}

sub configUpdateTrapCR {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0,
        "AdminUpdate: Invalid Addresses Refresh updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    &cleanTrapPB unless $init || $new eq $old;
}

sub configUpdateSPFCR {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: SPF Cache Refresh updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    &cleanCacheSPF unless $init || $new eq $old;
}

sub configUpdateURIBLCR {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: $name updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    &cleanCacheURI unless $init || $new eq $old;
}

# URIBL Settings Checks, and Update.
sub configUpdateURIBL {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: URIBL-Enable updated from '$old' to '$new'" )
      unless $init || $new eq $old;

    $ValidateURIBL = $Config{ValidateURIBL} = $new;
    unless ($CanUseURIBL) {
        mlog( 0,
"AdminUpdate:error URIBL-Enable updated from '1' to '0', Net::DNS not installed"
        ) if $Config{ValidateURIBL};
        ( $ValidateURIBL, $Config{ValidateURIBL} ) = 0;
        return
'<span class="negative">*** Net::DNS must be installed before enabling URIBL.</span>';
    } else {
        configUpdateURIBLMH( 'URIBLmaxhits', '', $Config{URIBLmaxhits},
            'Cascading' );
    }
}

# RWL Settings Checks, and Update.
sub configUpdateRWL {
    my ( $name, $old, $new, $init ) = @_;

    mlog( 0, "AdminUpdate: RWL-Enable updated from '$old' to '$new'" )
      unless $init || $new eq $old || !$new and !$old;
    ${$name} = $Config{$name} = $new;
    unless ($CanUseRWL) {
        mlog( 0,
"AdminUpdate:error RWL-Enable updated from '$new' to '', Net::DNS not installed"
        ) if $Config{ValidateRWL};
        ( $ValidateRWL, $Config{ValidateRWL} ) = ();
        return
'<span class="negative">*** Net::DNS must be installed before enabling RWL.</span>';
    } else {
        configUpdateRWLMH( 'RWLminhits', '', $Config{RWLminhits}, 'Cascading' );
    }
}

sub configUpdateRWLMH {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: RWL Minimum Hits updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    if ( $new <= 0 ) {
        mlog( 0,
"AdminUpdate:error RWL-Enable updated from '1' to '', RWLminhits must be defined and positive"
        ) if $Config{ValidateRWL};
        ( $ValidateRWL, $Config{ValidateRWL} ) = ();
        return
'<span class="negative">*** RWLminhits must be defined and positive before enabling RWL.</span>';
    } else {
        configUpdateRWLMR( 'RWLmaxreplies', '', $Config{RWLmaxreplies},
            'Cascading' );
    }
}

sub configUpdateRWLMR {
    my ( $name, $old, $new, $init ) = @_;

    mlog( 0, "AdminUpdate: RWL Maximum Replies updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    if ( $new < $RWLminhits ) {
        mlog( 0,
"AdminUpdate:error RWL-Enable updated from '1' to '', RWLmaxreplies not >= RWLminhits"
        ) if $Config{ValidateRWL};
        ( $ValidateRWL, $Config{ValidateRWL} ) = ();
        return
'<span class="negative">*** RWLmaxreplies must be more than or equal to RWLminhits before enabling RWL.</span>';
    } else {
        configUpdateRWLSP( 'RWLServiceProvider', '',
            $Config{RWLServiceProvider}, 'Cascading' );
    }
}

sub configUpdateRWLSP {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0,
        "AdminUpdate: RWL Service Providers updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    $new = checkOptionList( $new, 'RWLServiceProvider', $init );
    my $domains = ( $new =~ s/\|/|/g ) + 1;

    if ( $domains < $RWLmaxreplies ) {
        mlog( 0,
"AdminUpdate:error RWL-Enable updated from '1' to '',RWLServiceProvider not >= RWLmaxreplies "
        ) if $Config{ValidateRWL};
        ( $ValidateRWL, $Config{ValidateRWL} ) = ();
        return
'<span class="negative">*** RWLServiceProvider must contain more than or equal to RWLmaxreplies  before enabling RWL. RWL deactivated. </span>';
    } elsif ($CanUseRWL) {
            my @templist = split( /\|/, $new );
            @rwllist   = ();
            %rwlweight = ();
            foreach my $c (@templist) {

                if ( $c =~ /(.*)\=\>(.*)/ ) {
                    push( @rwllist, $1 );
                    $rwlweight{$1} = $2;
                } else {

                    push( @rwllist, $c );
                }
            }

        if ($ValidateRWL) {
            return ' & RWL activated';
        } else {
            return 'RWL deactivated';
        }
    }
}

# DNSBL Settings Checks, and Update.
sub configUpdateRBL {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: ValidateRBL updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    $ValidateRBL = $Config{ValidateRBL} = $new;
    unless ($CanUseRBL) {
        mlog( 0, "AdminUpdate:error DNSBL disabled, Net::DNS not installed " )
          if $Config{ValidateRBL};
        ( $ValidateRBL, $Config{ValidateRBL} ) = 0;
        return '<span class="negative">*** Net::DNS must be installed before enabling DNSBL.</span>';
    } else {
        configUpdateRBLMH( 'RBLmaxhits', '', $Config{RBLmaxhits}, 'Cascading' );
    }
}

sub configUpdateRBLMH {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: RBLmaxhits updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    if ( $new <= 0 ) {
        mlog( 0,
"AdminUpdate:error DNSBL disabled', RBLmaxhits must be > 0 before enabling DNSBL.</span>';"
        ) if $Config{ValidateRBL};
        ( $ValidateRBL, $Config{ValidateRBL} ) = 0;
        return '<span class="negative">*** RBLmaxhits must be > 0 before enabling DNSBL.</span>';
    } else {
        configUpdateRBLMR( 'RBLmaxreplies', '', $Config{RBLmaxreplies}, 'Cascading' );
    }
}

sub configUpdateRBLMR {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: RBLmaxreplies updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    configUpdateRBLSP( 'RBLServiceProvider', '', $Config{RBLServiceProvider},
        'Cascading' );
}

sub configUpdateRBLSP {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: RBLServiceProvider updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    $new = checkOptionList( $new, 'RBLServiceProvider', $init );
    if ($new =~ s/^\x00\xff //o) {
        ${$name} = $Config{$name} = $old;
        return ConfigShowError(1,$new);
    }
    my $domains = ( $new =~ s/\|/|/go ) + 1;
    $RBLmaxreplies = $domains if $RBLmaxreplies > $domains;
    if ( $domains < $RBLmaxreplies ) {
        mlog( 0, "AdminUpdate:error DNSBL disabled, RBLServiceProvider is not >= RBLmaxreplies")
            if $Config{ValidateRBL};
        $ValidateRBL = $Config{ValidateRBL} = 0;
        return
'<span class="negative">*** RBLServiceProvider must be >= RBLmaxreplies before enabling DNSBL.</span>';
    }
    elsif ($CanUseRBL) {
        my @templist = split( /\|/o, $new );

        @rbllist   = ();
        %rblweight = ();
        while (@templist) {
            my $c = shift @templist;
            if ($NODHO && $c =~ /dnsbl\.httpbl\.org/io) {
                mlog(0,"RBLSP:warning - dnsbl.httpbl.org is not supported as RBL-Service-Provider by ASSP and will be ignored - remove the entry");
                next;
            }
            if ( $c =~ /^(.*?)=>(.*?)=>(.*)$/o ) {
                my ($sp,$res,$w) = ($1,$2,$3);
                next unless $sp;
                $res ||= '*';
                push( @rbllist, $sp ) unless grep(/\Q$sp\E/, @rbllist);
                $sp =~ s/^.*?\$DATA\$\.?//io;
                if ($res =~ /(?:^|\.)M(?:[1248]|16|32|64|128)(?:\.|$)/io) {
                    $rblweight{$sp} = {} unless exists $rblweight{$sp};
                    setSPBitMask($rblweight{$sp},$res, weightRBL($w),"'$name' for '$sp'");
                } elsif ($res =~ /(?:^|\.)M/io) {
                    mlog(0,"error: invalid bitmask definition '$res' found in $name for $sp") ;
                    next;
                } else {
                    $rblweight{$sp}{$res} = weightRBL($w);
                }
            } elsif ( $c =~ /^(.*?)\=\>(.*)$/o ) {
                my ($sp,$w) = ($1,$2);
                next unless $sp;
                push( @rbllist, $sp ) unless grep(/\Q$sp\E/, @rbllist);
                $sp =~ s/^.*?\$DATA\$\.?//io;
                $rblweight{$sp}{'*'} = weightRBL($w);
            } else {
                $c =~ s/^.*?\$DATA\$\.?//io;
                next unless $c;
                push( @rbllist, $c ) unless grep(/\Q$c\E/, @rbllist);
                $rblweight{$c}{'*'} = ${'rblValencePB'}[0];
            }
        }
        if ( $MaintenanceLog > 1 || $RBLLog > 1 ) {
            foreach my $sp (sort keys %rblweight) {
                my $num = scalar keys %{$rblweight{$sp}};
                if ($num > 1) {
                    my $tag = $num < 1025 ? 'info' : $num < 2049 ? 'warning' : 'error';
                    mlog(0,"$tag: $name: registered $num reply weights for $sp");
                }
            }
        }
        &cleanCacheRBL() unless $init || $new eq $old;

        if ($ValidateRBL) {
            return ' & DNSBL activated';
        }
        else {
            return 'DNSBL deactivated';
        }
    }
}

sub setSPBitMask {
    my ($whash,$res,$w,$name) = @_;

    my (@b0,@b1,@b2,@b3);
    my @r = split(/\./o,$res);
    @b0 = setSPBitMaskNum($r[0],$name);
    @b1 = setSPBitMaskNum($r[1],$name);
    @b2 = setSPBitMaskNum($r[2],$name);
    @b3 = setSPBitMaskNum($r[3],$name);
    if (@b0 && @b1 && @b2 && @b3) {
        for my $b0 (@b0) { for my $b1 (@b1) { for my $b2 (@b2) { for my $b3 (@b3) { $whash->{"$b0.$b1.$b2.$b3"} += $w; }}}}
    }
}

sub setSPBitMaskNum {
    my ($r,$name) = @_;
    my $m = $r =~ s/^M//io;
    my $v = $r =~ /^(?:128|64|32|16|[1248])$/o;
    if ($m && !$v) {
        mlog(0,"error: invalid bitmask definition 'M$r' found in $name");
        return;
    }
    return ($r) unless ($m && $v);
    my @addr;
    for ($r...(unpack("A1",${chr(ord("\026") << 2)})**8-1)) {
       push @addr,$_ if $_ & $r;
    }
    return @addr;
}

sub configUpdateMaxSize {
    my ( $name, $old, $new, $init , $desc) = @_;
    mlog( 0, "AdminUpdate: $name updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    my %hash = (
                 'MaxRealSizeAdr' => 'MRSadr',
                 'MaxSizeAdr' => 'MSadr',
                 'MaxRealSizeExternalAdr' => 'MRSEadr',
                 'MaxSizeExternalAdr' => 'MSEadr'
               );
    my $hash = $hash{$name};
    $new = checkOptionList( $new, $name, $init );
    my $ret;

    my @templist = split( /\|/o, $new );

    my %tmp = ();
    while (@templist) {
        my $c = shift @templist;
        $c =~ s/\s//go;
        my ($adr,$val) = $c =~ /^(.+?)\=\>(\d+)$/o;
        next unless $adr;
        next unless defined $val;
        if ($adr =~ /^\@.+$/o) {                         # a domain
            $adr = '[^@]+'.$adr;
            $adr = '^(?i:'.$adr.')$';
        } elsif ($adr =~ /^[^@]+\@$/o) {                 # a user name with @
            $adr = $adr.'[^@]+';
            $adr = '^(?i:'.$adr.')$';
        } elsif ($adr =~ /^(?:\d{1,3}\.[\d\.\*\?]+|[a-f0-9:\?\*]+)$/io) {    # an IP address
            $adr = '^(?i:'.$adr.')';
        } elsif ($adr !~ /\@/o) {                        # a simple user name
            $adr = $adr.'@[^@]+';
            $adr = '^(?i:'.$adr.')$';
        } elsif ($adr =~ /^[^@]+\@[^@]+$/) {             # an email address
            $adr = '^(?i:'.$adr.')$';
        } else {
            next;
        }
        $adr =~ s/([^\\]?)\@/$1\\@/go;
        $tmp{$adr} = $val;
    }
    %{$hash} = %tmp;
    return $ret;
}

sub configUpdateStringToNum {
    my ( $name, $old, $new, $init , $desc) = @_;
    mlog( 0, "AdminUpdate: $name updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    $new = checkOptionList( $new, $name, $init );
    my %hash = (
                 'MaxEqualXHeader' => 'MEXH'
               );
    my $hash = $hash{$name};
    my $ret;

    my @templist = split( /\|/o, $new );

    my %tmp = ();
    while (@templist) {
        my $c = shift @templist;
        $c =~ s/^\s+//o;
        $c =~ s/\s+$//o;
        my ($tag,$val) = $c =~ /^(.+?)\s*\=\>\s*(\d+)$/o;
        next unless $tag;
        next unless $val;
        $tmp{$tag} = $val;
    }
    %{$hash} = %tmp;
    return $ret;
}

sub configUpdateURIBLMH {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: URIBL Maximum Hits updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    if ( $new <= 0 ) {
        mlog( 0, "AdminUpdate:error URIBL-Enable updated from '1' to '0', URIBLmaxhits not > 0" )
          if $Config{ValidateURIBL};
        ( $ValidateURIBL, $Config{ValidateURIBL} ) = 0;
        return '<span class="negative">*** URIBLmaxhits must be defined and positive before enabling URIBL.</span>';
    } else {
        configUpdateURIBLMR( 'URIBLmaxreplies', '', $Config{URIBLmaxreplies}, 'Cascading' );
    }
}

sub configUpdateURIBLMR {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: URIBL Maximum Replies updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    if ( $new < $URIBLmaxhits ) {
        mlog( 0, "AdminUpdate:error URIBL-Enable updated from '1' to '0': URIBLmaxreplies not >=  URIBLmaxhits" )
          if $Config{ValidateURIBL};
        ( $ValidateURIBL, $Config{ValidateURIBL} ) = 0;
        return
          '<span class="negative">*** URIBLmaxreplies must be more than or equal to URIBLmaxhits before enabling URIBL.</span>';
    } else {
        configUpdateURIBLSP( 'URIBLServiceProvider', '', $Config{URIBLServiceProvider}, 'Cascading' );
    }
}

sub configUpdateURIBLSP {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: URIBL Service Providers updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new ;
    $new = checkOptionList( $new, 'URIBLServiceProvider', $init );
    if ($new =~ s/^\x00\xff //o) {
        ${$name} = $Config{$name} = $old;
        return ConfigShowError(1,$new);
    }
    my $domains = ( $new =~ s/\|/|/go ) + 1;
    if ( $domains < $URIBLmaxreplies ) {
        mlog( 0, "AdminUpdate: warning count of URIBLServiceProvider not >= URIBLmaxreplies - possibly ok if weights are used" )
          if $Config{ValidateURIBL};
    }
    if ($CanUseURIBL) {
        my @templist = split( /\|/o, $new );
        @uribllist = ();
        %URIBLweight = ();
        while (@templist) {
            my $c = shift @templist;

            if ( $c =~ /^(.*?)=>(.*?)=>(.*)/o ) {
                my ($sp,$res,$w) = ($1,$2,$3);
                next unless $sp;
                $res ||= '*';
                push( @uribllist, $sp ) unless grep(/\Q$sp\E/, @uribllist);
                $sp =~ s/^.*?\$DATA\$\.?//io;
                if ($res =~ /(?:^|\.)M(?:[1248]|16|32|64|128)(?:\.|$)/io) {
                    $URIBLweight{$sp} = {} unless exists $URIBLweight{$sp};
                    setSPBitMask($URIBLweight{$sp},$res, weightURI($w),"'$name' for '$sp'");
                } elsif ($res =~ /(?:^|\.)M/io) {
                    mlog(0,"error: invalid bitmask definition '$res' found in $name for $sp");
                    next;
                } else {
                    $URIBLweight{$sp}{$res} = weightURI($w);
                }
            } elsif ( $c =~ /^(.*?)\=\>(.*)$/o ) {
                my ($sp,$w) = ($1,$2);
                next unless $sp;
                push( @uribllist, $sp ) unless grep(/\Q$sp\E/, @uribllist);
                $sp =~ s/^.*?\$DATA\$\.?//io;
                $URIBLweight{$sp}{'*'} = weightURI($w);
            } else {
                $c =~ s/^.*?\$DATA\$\.?//io;
                next unless $c;
                push( @uribllist, $c ) unless grep(/\Q$c\E/, @uribllist);
                $URIBLweight{$c}{'*'} = ${'uriblValencePB'}[0];
            }
        }
        if ( $MaintenanceLog > 1 || $URIBLLog > 1 ) {
            foreach my $sp (sort keys %URIBLweight) {
                my $num = scalar keys %{$URIBLweight{$sp}};
                if ($num > 1) {
                    my $tag = $num < 1025 ? 'info' : $num < 2049 ? 'warning' : 'error';
                    mlog(0,"$tag: $name: registered $num reply weights for $sp");
                }
            }
        }
        &cleanCacheURI() unless $init || $new eq $old;
        if ($ValidateURIBL) {
            return ' & URIBL activated';
        } else {
            return 'URIBL deactivated';
        }
    }
}

sub updateLDAPHost {my ($name, $old, $new, $init)=@_;
    my $ldap;
    my $ldaplist;
    my @ldaplist;
    mlog(0,"AdminUpdate: LDAP Hosts updated from '$old' to '$new'") unless $init || $new eq $old;
    $LDAPHost=$new;
    $Config{$name} = $new;
    if($CanUseLDAP && $DoLDAP) {
        @ldaplist = split(/\|/o,$LDAPHost);
        $ldaplist = \@ldaplist;
        mlog(0,"checking LDAP server at $LDAPHost -- ");
        my $scheme = 'ldap';
        eval{
        $scheme = 'ldaps' if ($DoLDAPSSL == 1 && $AvailIOSocketSSL);
        $ldap = Net::LDAP->new( $ldaplist,
                                timeout => $LDAPtimeout,
                                scheme => $scheme,
                                inet4 =>  1,
                                inet6 =>  $CanUseIOSocketINET6
                              );
        $ldap->start_tls() if ($DoLDAPSSL == 2 && $AvailIOSocketSSL);
        };

        if(! $ldap || $@) {
            mlog(0,"AdminUpdate: error couldn't contact LDAP server at $LDAPHost -- $@");
            if (!$init) {
                return ' & LDAP not activated';
            } else {
                return '';
            }
        } else {
            mlog(0,"AdminUpdate: LDAP server at $LDAPHost contacted -- ");
            if (!$init) {
                return ' & LDAP activated';
            } else {
                return '';
            }
        }
    }
}

sub configUpdateCA {
    my ( $name, $old, $new, $init ) = @_;

    %calist = ();
    mlog( 0, "AdminUpdate: $name updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;
    $new = checkOptionList( $new, 'CatchAll', $init );
    for my $an ( split( /\|/, $new ) ) {

        if ( $an =~ /(\S*)\@(\S*)/ ) {

            $calist{$2} = "$1";
        }
    }
}

sub configUpdateCCD {
    my ( $name, $old, $new, $init ) = @_;
    %ccdlist = ();
    mlog( 0, "AdminUpdate: $name updated from '$old' to '$new'" ) unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;

    $new = checkOptionList( $new, 'ccSpamInDomain', $init );

    for my $ad ( split( /\|/, $new ) ) {
        if($ad=~/(\S*)\@(\S*)/) {

            $ccdlist{lc $2} = "$1";

        }
    }

}

sub configUpdateASSPCfg {
    my ($name, $old, $new, $init)=@_;
    if (fileUpdated("assp.cfg",$name)){
        mlog(0,"AdminUpdate: assp.cfg was externaly changed - reload the configuration");
        &reloadConfigFile();
        $ConfigChanged = 0;
        $asspCFGTime = $FileUpdate{"$base/assp.cfg$name"} = ftime("$base/assp.cfg");
    }
    return;
}

sub updateBlackExpiration {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: $name updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    &cleanBlackPB  unless $init || $new eq $old;
    return "";
}
sub configChangeRBSched {
    my ($name, $old, $new, $init)=@_;
    my $shour = 0;
    my $ihour = 0;
    my $n = 0;
    mlog(0,"AdminUpdate: $name updated from '$old' to '$new'") unless $init || $new eq $old;
    %RebuildSched = ();
    if ($new =~ /^\*/) {

        $ihour = $1 if $1;
        while ($shour + $ihour  <=24) {

            $shour += $ihour;
            mlog(0,"info: RebuildSchedule for RebuildSpamdb.pl is $shour:00");
            next if $shour >=1 and $shour <=5;
            $RebuildSched{$shour} = 1;

        }
    } else {
        foreach my $shour ( split( /\|/, $new ) ) {
            $RebuildSched{$shour} = 1 if $shour > 0 && $shour <= 24;
            $n++;
            last if $n > 6;
        }
    }
    $Config{$name} = $new;
    ${$name} = $new;
    return '';
}

sub configChangeHKSched {
    my ($name, $old, $new, $init)=@_;
    my $shour;
    mlog(0,"AdminUpdate: $name updated from '$old' to '$new'") unless $init || $new eq $old;
    %HouseKeepingSched = ();
    if ($new eq "*") {
        $shour = 1;
        while ($shour <=24) {
            $HouseKeepingSched{$shour} = 1;

            $shour++;
        }
    } else {
        foreach my $shour ( split( /\|/, $new ) ) {
            $HouseKeepingSched{$shour} = 1;
        }
    }
    $Config{$name} = $new;
    ${$name} = $new;
    return '';
}
sub configChangeUpdateWhitelist {
    my ($name, $old, $new, $init)=@_;
    my $shour;
    mlog(0,"AdminUpdate: $name updated from '$old' to '$new'") unless $init || $new eq $old;

    $Config{$name} = $new;
    ${$name} = $new;
    $saveWhite = time - 1;
    return '';
}
sub cleanBlackPB {
    return if !$PBBlackObject;
    if ( $BlackExpiration == 0 ) {

        while ( my ( $k, $v ) = each(%PBBlack) ) {
            delete $PBBlack{$k};
        }

        return;
    }

    my $ips_before = my $ips_deleted = 0;
    my $t = time;
    my $tdif;
    my $tdifut;
    my $newscore = 0;
    my $mcount;
    my ( $ct, $ut, $pbstatus, $score, $sip, $reason );

    delete $PBBlack{"0.0.0.0"};
    delete $PBBlack{""};

    while ( my ( $k, $v ) = each(%PBBlack) ) {

        ( $ct, $ut, $pbstatus, $score, $sip, $reason ) = split( " ", $v );

        $tdif   = $t - $ct;
        $tdifut = $t - $ut;
        $ips_before++;
        next if $reason  =~ /preHeader/i;
        next if $reason  =~ /DNSBLfailed/i;
        next if $reason  =~ /LimitingSameSubject/i;
        next if $reason  =~ /SPFfail/i;
        next if $reason  =~ /DNSBLneutral/i;

        if ( $k =~ /$IPprivate/ ) {
            delete $PBBlack{$k};
            $ips_deleted++;
            next;
        }

        if ( $tdif > $BlackExpiration * 60 && $score < $PenaltyLimit ) {
            delete $PBBlack{$k};
            $ips_deleted++;
            next;
        }

        if ($tdifut > 7 * 24 * 3600 ) {
            delete $PBBlack{$k};
            $ips_deleted++;
            next;
        }

        if (
               exists $PBWhite{$k}
            || ( $ispip && matchIP( $k, 'ispip', 0, 1 ) )
            || matchIP( $k, 'noProcessingIPs', 0, 1 )
            || matchIP( $k, 'noProcessingSenderBaseIPs', 0, 1 )
            || matchIP( $k, 'whiteListedIPs',  0, 1 )
            || ( $noDelay && matchIP( $k, 'noDelay', 0, 1 ) )
            || ( $noPB    && matchIP( $k, 'noPB',    0, 1 ) )
            || (   $contentOnlyRe
                && $contentOnlyReRE != ""
                && $k =~ ( '(' . $contentOnlyReRE . ')' ) )
          )
        {
            delete $PBBlack{$k};
            $ips_deleted++;
            next;
        }

    }

    mlog( 0,
"PenaltyBox: cleaning BlackBox finished; before=$ips_before, deleted=$ips_deleted"
    ) if $MaintenanceLog && $ips_before>0;

}

sub cleanWhitePB {
    my $ips_before = my $ips_deleted = 0;
    my $t          = time;
    my $newscore   = 0;
    my $mcount;
    delete $PBWhite{"0.0.0.0"};
    delete $PBWhite{""};
    my $maxtime1;
    my $maxtime2 = $WhiteExpiration*24*3600;

    while ( my ( $k, $v ) = each(%PBWhite) ) {
        $mcount++;
        if ( $mcount == 100 ) {
            $mcount = 0;
            &MainLoop2();
        }
        my ( $ct, $ut, $pbstatus, $reason ) = split( " ", $v );
        $ips_before++;

        if ($pbstatus == 3) {           # an entry from global PB
            if ($t-$ut>=$maxtime1) {
                delete $PBWhite{$k};
                $ips_deleted++;
            }
            next;
        }

        if ( matchIP( $k, 'denySMTPConnectionsFromAlways', 0, 1 ) ) {
            delete $PBWhite{$k};
            $ips_deleted++;
            next;
        }
        if ( matchIP( $k, 'noPBwhite', 0, 1 ) ) {
            delete $PBWhite{$k};
            $ips_deleted++;
            next;
        }
        if ( $k =~ /$IPprivate/ ) {
            delete $PBWhite{$k};
            $ips_deleted++;
            next;
        }
        if ( $t - $ut >= $maxtime2 ) {
            delete $PBWhite{$k};
            $ips_deleted++;
        }

    }

      mlog( 0,
"PenaltyBox: cleaning WhiteBox finished; before=$ips_before, deleted=$ips_deleted"
        ) if $MaintenanceLog && $ips_before>0;

}

sub cleanCacheRBL {
    d('cleanCacheRBL');
    my $ips_before = my $ips_deleted = 0;
    my $t = time;
    my $ct;
    my $mm;
    my $status;
    my @sp;
    my $maxtime = $RBLCacheInterval * 24 * 3600;
    while (my ($k,$v)=each(%RBLCache)) {

        ( $ct, $mm, $status, @sp ) = split( ' ', $v );

            $maxtime = $RBLCacheInterval * 8 * 3600 if $status == 2;
            $maxtime = $RBLCacheInterval * 24 * 3600 if $status == 1;
        $ips_before++;
        if ( $t - $ct >= $maxtime  ) {
            delete $RBLCache{$k};
            $ips_deleted++;
            next;
        }

        my $spstr = join(' ',@sp);
        foreach my $sp (@sp) {
            my $tsp = $sp;
            $tsp =~ s/([^\{]+).*/$1/o;
            next if grep(/\Q$tsp\E/i,@rbllist);
            $spstr =~ s/ ?\Q$sp\E//ig;
        }
        if ($spstr) {
            $RBLCache{$k} = "$ct $mm $status $spstr";
        } else {
            delete $RBLCache{$k};
            $ips_deleted++;
        }
    }    

    mlog( 0, "DNSBLCache: cleaning cache finished: IP\'s before=$ips_before, deleted=$ips_deleted" ) if $MaintenanceLog && $ips_before != 0;
    if ( $ips_before == 0) {
        %RBLCache=();
        &SaveHash('RBLCache');

    }
}

sub cleanCacheSubject {
    my $subs_before; 
    my $subs_deleted = 0;
    my $t = time;

    while ( my ( $k, $ct ) = each(%SameSubjectCache) ) {

        $subs_before++;
        if ( $t - $ct >= $SameSubjectExpiration * 24 * 3600 ) {
            delete $SameSubjectCache{$k};
            $subs_deleted++;
        }
    }
    mlog( 0,
"SameSubjectCache: cleaning up cache finished; before=$subs_before, deleted=$subs_deleted"
    ) if $MaintenanceLog && $subs_before>0;

}

sub cleanCachePTR {
    my $ips_before = my $ips_deleted = 0;
    my $t = time;
    my $ct;
    my $status;
    my $mcount;
    while ( my ( $k, $v ) = each(%PTRCache) ) {
        $mcount++;
        if ( $mcount == 100 ) {
            $mcount = 0;
            &MainLoop2();
        }
        ( $ct, $status ) = split( " ", $v );

        $ips_before++;
        if ( $t - $ct >= $PTRCacheInterval * 24 * 3600 ) {
            delete $PTRCache{$k};
            $ips_deleted++;
        }
    }
    mlog( 0,
"PTRCache: cleaning up cache finished; before=$ips_before, deleted=$ips_deleted"
    ) if $MaintenanceLog && $ips_before>0;

}

sub cleanCacheRWL {
    return;
    my $ips_before = my $ips_deleted = 0;
    my $t = time;
    my $ct;
    my $status;
    my $mcount;
    while ( my ( $k, $v ) = each(%RWLCache) ) {
        ( $ct, $status ) = split( " ", $v );
        $mcount++;
        if ( $mcount == 100 ) {
            $mcount = 0;
            &MainLoop2();
        }

        $ips_before++;
        if ( $t - $ct >= $RWLCacheInterval * 3600 * 24 ) {
            delete $RWLCache{$k};
            $ips_deleted++;
        }
    }
    mlog( 0,
"RWLCache: cleaning up cache finished; before=$ips_before, deleted=$ips_deleted"
    ) if $MaintenanceLog && $ips_before>0;

}

sub cleanCacheMXA {

    my $ips_before = my $ips_deleted = 0;
    my $ct;
    my $status;
    my $t = time;
    my $mcount;
    my $maxtime = $MXACacheInterval*3600 * 24;
    while (my ($k,$v)=each(%MXACache)) {

        ($ct,$status)=split(" ",$v);

        $ips_before++;

        if ($t-$ct>=$maxtime) {
            delete $MXACache{$k};
            $ips_deleted++;
        }
    }
    mlog( 0,
"MXACache: cleaning up cache finished; before=$ips_before, deleted=$ips_deleted"
    ) if $MaintenanceLog && $ips_before>0;

}

sub cleanCacheSB {
    d('cleanCacheSB');
    my $ips_before= my $ips_deleted=0;
    my $t=time;
    my %delWOL;
    while (my ($k,$v)=each(%SBCache)) {
        my $mSBCacheExp = $SBCacheExp;
        my ( $ct, $status, $data ) = split( "!", $v );
        my ( $ipcountry,  $orgname,  $domainname , @res) = split( /\|/o, $data ) ;

        $ips_before++;
        $mSBCacheExp = 10 * $SBCacheExp if ($status == 2);
        if ($t-$ct>=$mSBCacheExp*3600*24) {
            delete $SBCache{$k};
            delete $WhiteOrgList{lc $domainname};
            $delWOL{$orgname} = 1;
            $ips_deleted++;
        }
    }
    while (my ($k,$v)=each(%WhiteOrgList)) {
        delete $WhiteOrgList{$k} if exists $delWOL{$v};
    }

    mlog(0,"SenderBaseCache: cleaning cache finished: IP\'s before=$ips_before, deleted=$ips_deleted") if  $MaintenanceLog && $ips_before != 0;
    if ($ips_before==0) {
        %SBCache=();
        if ($pbdb =~ /DB:/o && ! $failedTable{SBCache}) {
        } else {
            &SaveHash('SBCache');
        }
    }
}

sub cleanCacheSPF {
    d('cleanCacheSPF');

    my $ips_before= my $ips_deleted=0;
    my $t=time;
    my $mcount;
    my $maxtime = $SPFCacheInterval*24*3600;
    while (my ($k,$v)=each(%SPFCache)) {
        my ($ct, $result, $helo)=split(' ',$v);
        $mcount++;
        if ( $mcount == 1000 ) {
            $mcount = 0;
            &MainLoop2();
        }
        $ips_before++;
        if ($t-$ct>=$maxtime or $k !~ /\s/o) {
            delete $SPFCache{$k};
            $ips_deleted++;
        }
    }
    mlog(0,"SPFCache: cleaning up cache finished: IP\'s before=$ips_before, deleted=$ips_deleted") if  $MaintenanceLog && $ips_before != 0;

}

sub cleanCacheURI {
    d('cleanCacheURI');
    my $domains_before= my $domains_deleted=0;
    my $t=time;
    my $ct;my $status;my @sp;my $maxtime1;my $maxtime2;
    $maxtime1 = $maxtime2 = $URIBLCacheInterval*24*3600;

    while (my ($k,$v)=each(%URIBLCache)) {
        &MainLoop2() if  ! $domains_before % 100;
        ( $ct, $status, @sp ) = split( ' ', $v );

        $domains_before++;
        if (!$URIBLCacheInterval) {
            delete $URIBLCache{$k};
            $domains_deleted++;
            next;
        }
        if ($status==2 && $t-$ct>=$maxtime1) {
            delete $URIBLCache{$k};
            $domains_deleted++;
            next;
        }
        if ($t-$ct>=$maxtime2) {
            delete $URIBLCache{$k};
            $domains_deleted++;
        }
        next if $status == 2;
        my $spstr = join(' ',@sp);
        foreach my $sp (@sp) {
            my $tsp = $sp;
            $tsp =~ s/([^\<]+).*/$1/;
            next if grep(/$tsp/i,@uribllist);
            $spstr =~ s/ ?$sp//ig;
        }
        if ($spstr) {
            $URIBLCache{$k} = "$ct $status $spstr";
        } else {
            delete $URIBLCache{$k};
            $domains_deleted++;
        }
    }
    mlog(0,"URIBLCache: cleaning cache finished: Domains before=$domains_before, deleted=$domains_deleted") if  $MaintenanceLog && $domains_before != 0;
    if ($domains_before==0) {
        %URIBLCache=();
        &SaveHash('URIBLCache');
    }
}

sub cleanTrapPB {
    my $addresses_before = my $addresses_deleted = 0;
    my $t = time;
    my $mcount;

    while ( my ( $k, $v ) = each(%PBTrap) ) {
        $mcount++;
        if ( $mcount == 1000 ) {
            $mcount = 0;
            &MainLoop2();
        }
        $addresses_before++;

        my ( $ct, $ut, $counter ) = split( " ", $v );

        if ( $t - $ut >= $PBTrapCacheInterval  * 3600
        or matchSL( $k, 'noPenaltyMakeTraps',1 ))
        {
            delete $PBTrap{$k};
            $addresses_deleted++;
            next;
        }
        if ($t - $ct >= $PBTrapCacheInterval  * 3600 && $counter < $PenaltyMakeTraps) {
               my $data = "$t $t 0 ";
            $PBTrap{$k} = $data;
            next;
        }

    }
    mlog( 0,
"PBTrap: cleaning finished; before=$addresses_before, deleted=$addresses_deleted"
    ) if $MaintenanceLog && $addresses_before>0 ;

}

sub cleanCacheAUTHErrors {
    d('cleanCacheAUTHErrors');

    my $i = 0;
    while (my ($k,$v)=each(%AUTHErrors)) {
        if (--$AUTHErrors{$k} <= 0) {
            delete $AUTHErrors{$k};
        }
        $i++;
    }

}
sub cleanCacheDelayIPPB {
    d('cleanCacheDelayIPPB');
    my $ips_deleted = 0;
    my $ips_before = 0;
    my $t = time - 24 * 3600 ;
    while (my ($k,$v)=each(%DelayIPPB)) {

        $ips_before++;
        if ($DelayIPPB{$k} <= $t) {
            delete $DelayIPPB{$k};
            $ips_deleted++;
        }
    }

}
sub cleanNotSpamTags {
    d('cleanNotSpamTags');
    my $ips_deleted = 0;
    my $ips_before = 0;
    my $t = time - 24 * 3600 ;
    while (my ($k,$v)=each(%NotSpamTags)) {

        $ips_before++;
        if ($NotSpamTags{$k} <= $t) {
            delete $NotSpamTags{$k};
            $ips_deleted++;
        }
    }

}
sub cleanCacheSSLfailed {
    d('cleanCacheSSLfailed');
    my $ips_before= my $ips_deleted=0;
    my $ct;
    my $t=time;
    while (my ($k,$v)=each(%SSLfailed)) {

        $ips_before++;

        if ($t-$v>=43200) {   # 3600*12
            delete $SSLfailed{$k};
            $ips_deleted++;
        }
    }
    mlog(0,"SSLfailedCache: cleaning cache finished: IP\'s before=$ips_before, deleted=$ips_deleted") if  $MaintenanceLog && $ips_before > 0;
    if ($ips_before==0) {
        %SSLfailed=();
    }
}

sub cleanCacheSMTPdomainIP {
    d('cleanCacheSMTPdomainIP');
    my $ips_before= my $ips_deleted=0;
    my $ct;
    my $t=time;
    while (my ($k,$v)=each(%SMTPdomainIP)) {
        $ips_before++;

        if ($t-$v>=$SMTPdomainIPTriesExpiration{$k}) {
            $ips_deleted++;

            delete $SMTPdomainIP{$k};
            delete $SMTPdomainIPTries{$k};
            delete $SMTPdomainIPTriesExpiration{$k};
        }
    }
    mlog(0,"SMTPdomainIP: cleaning up cache finished: before=$ips_before, deleted=$ips_deleted") if  $MaintenanceLog > 1 && $ips_before > 0;
}

sub cleanCacheIPNumTries {
    d('cleanCacheIPNumTries');
    my $ips_before= my $ips_deleted=0;
    my $ct;
    my $t=time;
    while (my ($k,$v)=each(%IPNumTries)) {
        $ips_before++;

        if ($t-$v>=$IPNumTriesExpiration{$k}) {
            $ips_deleted++;

            delete $IPNumTries{$k};
            delete $IPNumTriesDuration{$k};
            delete $IPNumTriesExpiration{$k};
        }
    }
    mlog(0,"IPNumTries: cleaning up cache finished: before=$ips_before, deleted=$ips_deleted") if  $MaintenanceLog > 1 && $ips_before > 0;
}

sub cleanCacheLocalFrequency {
    d('cleanCacheLocalFrequency');
    unless ($LocalFrequencyInt) {%localFrequencyCache = (); return;}
    unless ($LocalFrequencyNumRcpt) {%localFrequencyCache = (); return;}

    my $adr_before= my $adr_deleted=0;
    my $t=time;
    while (my ($k,$v)=each(%localFrequencyCache)) {

        my %F = split(/ /o,$v);
        foreach (sort keys %F) {
            delete $F{$_} if ($_ + $LocalFrequencyInt  < $t);
        }
        if (! scalar keys %F) {
            delete $localFrequencyCache{$k};
            $adr_deleted++;
        }
    }
    mlog(0,"localFrequency: cleaning up cache finished: addresses\'s before=$adr_before, deleted=$adr_deleted") if  $MaintenanceLog >= 2 && $adr_before > 0;

    while (my ($k,$v)=each(%localFrequencyNotify)) {
        delete $localFrequencyNotify{$k} if $v < time;
    }

}

sub saveSMTPconnections {
    mlog( 0, "sig USR1 -- saving concurrent session stats" );
    open( my $SMTP, ">","$base/smtp.txt" );
    print $SMTP "$smtpConcurrentSessions\n";
    close($SMTP);
}

sub cleanUpFiles {
    my ($folder, $filter, $filetime) = @_;
    d('cleanUpFiles - '."$folder, $filter, $filetime");
    my $textfilter;$textfilter = " (*$filter)" if $filter;
    my @files;

    my $count;
    my $dir = ($folder !~ /\Q$base\E/i) ? "$base/$folder" : $folder ;
    $dir =~ s/\\/\//g;
    return unless -e $dir;
    mlog(0,"info: starting cleanup old files$textfilter for folder $dir") if $MaintenanceLog >= 2;
    opendir(my $DIR,"$dir");
    @files = readdir($DIR);
    close $DIR;
    foreach my $file (@files) {
        next if $file eq '.';
        next if $file eq '..';
        next if ($filter && $file !~ /$filter$/i);
        next if ($filter && $file =~ /^$filter$/i);
        $file = "$dir/$file";
        next if -d $file;
        next unless -w $file;
        my $dtime=(stat($file))[9]-time;
        if (($dtime < $filetime * -1) or ($dtime > 0 && $dtime < 60 - $filetime)) {
            unlink $file;
            $count++;

        }
    }
    mlog(0,"info: deleted $count old$textfilter files from folder $dir") if $MaintenanceLog && $count;
}

sub cleanUpMailLog {
    d('cleanUpMailLog');
    return unless $MaxLogAge;
    return unless $logfile;
    return if $logfile =~ /\/?maillog\.log$/i;
    my $age = $MaxLogAge * 3600 * 24;
    my ($logdir, $logdirfile) = $logfile=~/^(.*[\/\\])?(.*?)$/;
    $logdir = $base unless $logdir;
    return unless $logdirfile;

    &cleanUpFiles($logdir,$logdirfile,$age);
}

sub modifyList {
    $GPBmodTestList->('email',@_);
}

# SRS Settin Checks, and Update.
sub updateSRS {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: SRS-Enable updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    $EnableSRS = $Config{EnableSRS} = $new;
    if ( !$CanUseSRS ) {
        mlog( 0,
"AdminUpdate: SRS-Enable updated from '1' to '', Mail::SRS not installed"
        ) if $Config{EnableSRS};
        $EnableSRS = $Config{EnableSRS} = undef;
        return
'<span class="negative">*** Mail::SRS must be installed before enabling SRS.</span>';
    } else {
        updateSRSAD( 'updateSRSAD', '', $Config{SRSAliasDomain}, 'Cascading' );
    }
}

sub updateSRSAD {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: SRS Alias Domain updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    $SRSAliasDomain = $new;
    if ( $new eq '' ) {
        mlog( 0,
"AdminUpdate: SRS-Enable updated from '1' to '', SRSAliasDomain not defined "
        ) if $Config{EnableSRS};
        $EnableSRS = $Config{EnableSRS} = undef;
        return
'<span class="negative">*** SRSAliasDomain must be defined before enabling SRS.</span>';
    } else {
        updateSRSSK( 'updateSRSSK', '', $Config{SRSSecretKey}, 'Cascading' );
    }
}

sub updateSRSSK {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: SRS Secret Key updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    $SRSSecretKey = $new;
    if ( length($new) < 5 ) {
        mlog( 0,
"AdminUpdate: SRS-Enable updated from '1' to '', SRSSecretKey not at least 5 characters long "
        ) if $Config{EnableSRS};
        $EnableSRS = $Config{EnableSRS} = undef;
        return
'<span class="negative">*** SRSSecretKey must be at least 5 characters long before enabling SRS.</span>';
    } elsif ($CanUseSRS) {
        if ( $init && $EnableSRS ) {
            return ' & SRS activated';
        } else {
            return '';
        }
    }
}

# Database File Logging Frequency Setup.
sub freqNonSpam {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0,
        "AdminUpdate: Non Spam Logging Frequency updated from '$old' to '$new'"
    ) unless $init || $new eq $old;
    $logFreq[2] = $new;

}

sub freqSpam {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0,
        "AdminUpdate: Spam Logging Frequency updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    $logFreq[3] = $new;
    return '';
}

sub syncCanSync {
    return ($syncConfigFile && $syncCFGPass && $syncServer && ($enableCFGShare or $isShareMaster or $isShareSlave)) ? 1 : 0;
}

sub ConfigChangeSyncServer {my ($name, $old, $new, $init)=@_;
    return '' if $new eq $old && ! $init;
    if (! $new or $init) {
        $Config{$name} = $new;
        ${$name} = $new;
        return &ConfigChangeEnableCFGSync('enableCFGShare', $enableCFGShare, '', '');
    }
    $Config{$name} = $new;
    ${$name} = $new;
    mlog(0,"AdminUpdate: $name changed from '$old' to '$new'")  unless $init || $new eq $old;
    if (&syncLoadConfigFile()) {
        return '';
    } else {
        return "<span class=\"positive\">updated - but sync-config-file was still not loaded - sync config is still incomplete</span>";
    }
}

sub ConfigChangeSyncFile {my ($name, $old, $new, $init)=@_;
    my $tnew;$tnew=checkOptionList($new,'syncConfigFile',$init) ;
    $Config{$name} = $new;
    ${$name} = $new;
    mlog(0,"AdminUpdate: $name changed from $old to $new")  unless $init || $new eq $old;
    $NextSyncConfig = time - 1;
    if (&syncLoadConfigFile()) {
        return '';
    } else {
        return "<span class=\"positive\">updated - but sync-config-file was still not loaded - sync config is still incomplete</span>";
    }
}

sub ConfigChangeSync {my ($name, $old, $new, $init)=@_;
    return '' if $new eq $old && ! $init;
    $Config{$name} = $new;
    ${$name} = $new;
    return '' if $init;
    my $text = ($name eq 'syncCFGPass') ? '' : "from '$old' to '$new'";
    mlog(0,"AdminUpdate: $name changed $text") unless $init || $new eq $old;
    if (&syncLoadConfigFile()) {
        return '';
    } else {
        return "<span class=\"positive\">updated - but sync-config-file was still not loaded - sync config is still incomplete</span>";
    }
}

sub ConfigChangeEnableCFGSync {my ($name, $old, $new, $init)=@_;
    return if $AsASecondary;
    my $failed;
    if ($new) {
        unless ($isShareMaster or $isShareSlave) {
            $new = $old = '';
            $enableCFGShare = $new;
            $Config{enableCFGShare} = $new;
            $failed .= "<span class=\"negative\">any of isShareMaster or isShareSlave must be selected first</span><br />";
        }
        unless ($syncConfigFile) {
            $new = $old = '';
            $enableCFGShare = $new;
            $Config{enableCFGShare} = $new;
            $failed .= "<span class=\"negative\">syncConfigFile must be configured first</span><br />";
        }
        unless ($syncServer) {
            $new = $old = '';
            $enableCFGShare = $new;
            $Config{enableCFGShare} = $new;
            $failed .= "<span class=\"negative\">at least one default syncServer must be configured first</span><br />";
        }
        unless ($syncCFGPass) {
            $new = $old = '';
            $enableCFGShare = $new;
            $Config{enableCFGShare} = $new;
            $failed .= "<span class=\"negative\">a password in syncCFGPass must be configured first</span><br />";
        }
        return $failed if $failed;
    }
    mlog(0,"AdminUpdate: $name changed from '$old' to '$new'") unless $init || $new eq $old;

    $enableCFGShare = $new;
    $Config{enableCFGShare} = $new;

    return '<span class="positive">config synchronization is now activated</span>' if $new;
    return '<span class="positive">config synchronization is now deactivated</span>';
}

sub syncGetStatus {
    my $name = shift;

    return $ConfigSync{$name}->{sync_cfg} if ($ConfigSync{$name}->{sync_cfg} < 1);
    my $syncservers = $ConfigSyncServer{$name};
    my $res = 0;
    foreach my $syncserver ( split( ",", $syncservers ) ) {

        my ($k,$v) = split(/\s*\=\s*/o,$syncserver);

        if ($v == 1) {
            $res = $v;
            last;
        } elsif ($v >= 2) {
            $v = 2;
        }
        $res |= $v;
    }
    return $res;
}

sub syncedit {
    my $name = $qs{cfgparm};

    return 'incomplete request' unless $name;
    return 'synchronization not allowed for ' . $name if exists $neverShareCFG{$name};
    my %sync_server;
    my @syncServer = (split(/\|/o,$syncServer));
    my %syncMode = (0 => 'no sync', 1 => 'out of sync', 2 => 'in sync', 3 => 'as slave', '' => 'remove');
    my $msg;
    my ($fn) = $syncConfigFile =~ /^ *file:(.+)$/io;
    while (my ($k,$v) = each %qs) {
        next if $k !~ /^sync_server(\d+)/o;
        $v =~ s/\s//go;
        next unless $v;
        $sync_server{$v} = $qs{'val'.$1} if $qs{'val'.$1} ne '';
    }
    my $enable_sync = $qs{enable} ? 1 : 0;
    if ($qs{theButton}){
        $ConfigSync{$name}->{sync_cfg} = $enable_sync;
        $ConfigSyncServer{$name} = {};
        my $i = 0;
        while (my ($k,$v) = each %sync_server) {
            $ConfigSyncServer{$name}="$k=$v"  if $i == 0;
            $ConfigSyncServer{$name}=",$k=$v" if $i > 0;
            $i++;
        }

        unless ($i) {
            $ConfigSync{$name}->{sync_cfg} = 0;
            $msg .= "<hr>no sync peer defined for $name - synchronization is now disabled for $name<hr>\n";
        }
        if (&syncWriteConfig()) {
            $msg .= "<hr><span class=\"positive\">successfully saved changes to file $fn</span><hr>\n";
            $NextSyncConfig = time - 1;
        }
    }
    my $syncservers = $ConfigSyncServer{$name};

    my $checked = $ConfigSync{$name}->{sync_cfg} ? 'checked="checked"' : '';
    $msg .= "<hr>resulting line in file $fn:<br /><br />$name:=$ConfigSync{$name}->{sync_cfg}";
    foreach my $syncserver ( split( ",", $syncservers ) ) {
        my ($k,$v) = split(/\s*\=\s*/o,$syncserver);
        $msg .= ",$k=$v";
    }
    $msg .= '<br /><hr>';

    my $s = '<table BORDER CELLSPACING=0 CELLPADDING=4 WIDTH="100%" >';
    $s .= '<tr><td>enable/disable synchronization for '.$name.' : ';
    $s .= "<input type=\"checkbox\" name=\"enable\" value=\"1\" $checked /></td></tr></table><hr>\n";
    $s .= '<table BORDER CELLSPACING=0 CELLPADDING=4 WIDTH="100%" >'."\n";
    my $i = 0;
    foreach my $k (@syncServer) {
        $i++;

        $s .= "<tr><td>&nbsp;&nbsp;peer : ";
        $s .= "<span style=\"z-Index:100;\"><select size=\"1\" name=\"sync_server$i\">\n";
        my $sel = '';
        $sel = "selected=\"selected\"" if $syncservers =~ /$k/;
        $s .= "<option $sel value=\"$k\">$k</option>";
        $s .= "</select></span></td>\n";

        $s .= "<td>&nbsp;&nbsp;mode/status : ";
        $s .= "<span style=\"z-Index:100;\"><select size=\"1\" name=\"val$i\">\n";
        my ($kserver,$vstatus);
        foreach my $syncserver ( split( ",", $syncservers ) ) {
            ($kserver,$vstatus) = split(/\s*\=\s*/o,$syncserver);
            last if $kserver eq $k;
        }

        for (0..3,'') {
            my $sel = '';
            $sel = "selected=\"selected\"" if $vstatus == $_;
            my $s1 = ($_ ne '') ? "($_)" : '';
            $s .= "<option $sel value=\"$_\">$syncMode{$_} $s1</option>\n";
        }
        $s .= "</select></span></td></tr>";
    }
    $s .= '</table>'."\n<hr>\n";
    $s .= '<input type="hidden" name="cfgparm" value="'.$name.'" />';
    $s .= '<input type="submit" name="theButton" value="Save Changes" />&nbsp;&nbsp;';
    $s .= '<input type="button" value="Close" onclick="javascript:window.close();"/>';
    $s .= $msg;
return <<EOT;
$headerHTTP

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" />
  <title>$currentPage ASSP SyncConfig ($myName - $name)</title>
  <link rel=\"stylesheet\" href=\"get?file=images/editor.css\" type=\"text/css\" />
</head>
<body onmouseover="this.focus();" >
    <div class="content">
      <form action="" method="post">
        $s
      </form>
    </div>
</body>
</html>

EOT
}

sub syncShowGUI {
    my $name = shift;
    return '' unless &syncCanSync();
#    d('syncShowGUI');

    if ($ConfigSync{$name}->{sync_cfg} == -1) {
        return '';
    } elsif ($ConfigSyncServer{$name} == 0) {

        return $syncShowGUIDetails
          ? '&nbsp;&nbsp;<a href="javascript:void(0);" onclick="javascript:popSyncEditor(\''.$name.'\');" >(shareable)</a>'
          : '<a href="javascript:void(0);" onclick="javascript:popSyncEditor(\''.$name.'\');" onmouseover="showhint(\'<table BORDER CELLSPACING=0 CELLPADDING=4 WIDTH=\\\'100%\\\'><tr><td>&nbsp;&nbsp;shareable</td></tr></table>\', this, event, \'90px\', \'1\'); return true;"><b><font color=\'black\'>&nbsp;&nbsp;&bull;</font></b></a>';
    } else {
        my $stat = &syncGetStatus($name);
        return '' if $stat == -1;
        return ($syncShowGUIDetails
          ? '&nbsp;&nbsp;<a href="javascript:void(0);" onclick="javascript:popSyncEditor(\''.$name.'\');" >(shareable)</a>'
          : '<a href="javascript:void(0);" onclick="javascript:popSyncEditor(\''.$name.'\');" onmouseover="showhint(\'<table BORDER CELLSPACING=0 CELLPADDING=4 WIDTH=\\\'100%\\\'><tr><td>&nbsp;&nbsp;shareable</td></tr></table>\', this, event, \'90px\', \'1\'); return true;"><b><font color=\'black\'>&nbsp;&nbsp;&bull;</font></b></a>') if $stat == 0;
        my $ret = '&nbsp;&nbsp;(<span class="negative">shared: </span>';
        $ret = '&nbsp;&nbsp;(<span class="positive">shared: </span>' if ($stat == 2);
        my $shared = 0;
        my $syncservers = $ConfigSyncServer{$name};
        foreach my $syncserver ( split( ",", $syncservers ) ) {
            my ($k,$v) = split(/\s*\=\s*/o,$syncserver);

            $k =~ s/:.+$//o;
            if ($v == 0) {
                $ret .= "$k not shared, ";
            } elsif ($v == 1) {
                $ret .= "<span class=\"negative\">$k out of sync, </span>";
                $shared = 1;
            } elsif ($v == 2 or $v == 4) {
                $ret .= "<span class=\"positive\">$k in sync, </span>";
                $shared = 1;
            } elsif ($v == 3) {
                $ret .= "<span class=\"positive\">$k local slave mode, </span>";
                $shared = 1;
            }
        }
        $ret .= ')';
        $ret =~ s/(sync|mode), ([^\)]+?\))$/$1$2/o;
        if ($syncShowGUIDetails) {
            return '<a href="javascript:void(0);" onclick="javascript:popSyncEditor(\''.$name.'\');">'.$ret.'</a>';
        }
        my $color = ($ret =~ /negative/o) ? 'red' : 'green';
        $color = 'black' unless $shared;
        $ret =~ s/"/\\'/go;
        $ret =~ s/\(|\)//go;
        $ret =~ s/, /\<br \/\>/go;
        $ret =~ s/: /:\<hr\>/go;
        return '<a href="javascript:void(0);" onclick="javascript:popSyncEditor(\''.$name.'\');" onmouseover="showhint(\'<table BORDER CELLSPACING=0 CELLPADDING=4 WIDTH=\\\'100%\\\'><tr><td>'.$ret.'</td></tr></table>\', this, event, \'220px\', \'1\'); return true;"><b><font color=\''. $color .'\'>&nbsp;&nbsp;&bull;</font></b></a>';
    }
}

sub syncLoadConfigFile {
    my $log = shift;
    my $RCF;

    %ConfigSync = ();
    %ConfigSyncServer = ();

    while (my ($k,$v) = each %Config) {
        $ConfigSync{$k} = {};

        $ConfigSync{$k}->{sync_cfg} = -1;
        $ConfigSyncServer{$k} = {};

    }
    return 0 unless &syncCanSync();
    my ($fn) = $syncConfigFile =~ /^ *file:(.+)$/io;
    return 0 unless $fn;
    open($RCF,"<","$base/$fn") or return 0;
    d('syncLoadConfigFile');
    mlog(0,"loading config synchronization configuration file '$fn'") if $MaintenanceLog == 2 && $log;
    while (<$RCF>) {
        s/\r|\n//go;
        s/[#;].*//o;
        my ($k,$v) = split(/:=/o,$_,2);
        next unless $k;
        next if exists $neverShareCFG{$k};
        next unless exists $Config{$k};
        my @scfg = split(/\s*,\s*/o,$v);
        $ConfigSync{$k}->{sync_cfg} = shift @scfg || 0;
        if (! @scfg) {
            foreach my $se (split(/\|/o,$syncServer)) {
                push @scfg , "$se=1" if $ConfigSync{$k}->{sync_cfg};
            }
        }
        my $x=0;
        my $t;
        while (my $se = shift @scfg) {
            my ($server,$status) = split(/\s*\=\s*/o,$se);
            next unless $server;

            $status = 3 if (! $isShareMaster && $isShareSlave);

            $t = "$server=$status"         if $x==0;
            $t .= ",$server=$status"     if $x>0;
            $x++;
        }
        $ConfigSyncServer{$k} = "$t";

    }
    close $RCF;
    &syncWriteConfig();
    return 1;
}

sub syncWriteConfig {
    my $new;
    my $newST;
    return 0 unless &syncCanSync();
    my ($fn) = $syncConfigFile =~ /^ *file:(.+)$/io;
    return 0 unless $fn;
    open(my $RCF,">","$base/$fn.new") or return 0;
    open(my $RCFST,">","$base/files/sync_failed.txt");
    d('syncWriteConfig');
    binmode $RCF;
    binmode $RCFST;
    foreach my $c (@ConfigArray) {
        next if (! $c->[0] or @$c == 5);
        next if $ConfigSync{$c->[0]}->{sync_cfg} == -1;
        next if exists $neverShareCFG{$c->[0]};
        my $st;
        my $data = $c->[0] . ':=' . $ConfigSync{$c->[0]}->{sync_cfg};

        if ( $ConfigSync{$c->[0]}->{sync_cfg} > 0 ) {
            my $syncservers = $ConfigSyncServer{$c->[0]};
                foreach my $syncserver ( split( ",", $syncservers ) ) {
                my ($k,$v) = split(/\s*\=\s*/o,$syncserver);
                $data .= ",$k=$v";
                $st = 1 if $v == 1;
             }
        }

        $new .= "$data\n";
        $newST .= "$data\n" if $st;
    }
    print $RCF $new;
    if ($newST) {
        print $RCFST '# ' , &timestring() , ' The following configuration values are still out of sync:',"\n\n";
        print $RCFST $newST;
    } else {
        print $RCFST '# ' , &timestring() , ' All configuration values are still synchronized.';
    }
    close $RCF;
    close $RCFST;
    if (open $RCF,"<","$base/$fn.bak") {
        binmode $RCF;
        my $bak = join('',<$RCF>);
        close $RCF;
        $new =~ s/\r|\n//go;
        $bak =~ s/\r|\n//go;
        if ($new eq $bak) {
            unlink "$base/$fn.new";
            return 1;
        }
    }
    unlink "$base/$fn.bak.bak.bak";
    rename "$base/$fn.bak.bak","$base/$fn.bak.bak.bak";
    rename "$base/$fn.bak","$base/$fn.bak.bak";
    rename "$base/$fn","$base/$fn.bak";
    rename "$base/$fn.new","$base/$fn";
    mlog(0,"syncCFG: saved sync configuration to $base/$fn") if $MaintenanceLog >= 2;
    $FileUpdate{"$base/$fn".'syncConfigFile'} = [stat("$base/$fn")]->[9];
    return 1;
}

sub syncConfigDetect {
    my $name = shift;

    return if $syncUser eq 'sync';
    return unless (&syncCanSync() && $enableCFGShare && $isShareMaster && $CanUseNetSMTP);
    return if exists $neverShareCFG{$name};
    return unless exists $Config{$name};
    return if $ConfigSync{$name}->{sync_cfg} < 1;
    my $stat = &syncGetStatus($name);
    return if $stat < 1;
    d("syncConfigDetect $name");
    my $syncservers = $ConfigSyncServer{$name};

    my $r = 0;
    foreach my $syncserver ( split( ",", $syncservers ) ) {
        my ($k,$v) = split(/\s*\=\s*/o,$syncserver);
        next if $v < 1;
        next if $v == 3;
        $r |= $v;
    }
    return unless $r;
    if ($r == 4) {
        foreach my $syncserver ( split( ",", $syncservers ) ) {
            my ($k,$v) = split(/\s*\=\s*/o,$syncserver);
            $syncservers=~s/$k=4/$k=2/g;

        }
        $ConfigSyncServer{$name} = $syncservers;
        &syncWriteConfig();
        return;
    }
    mlog(0,"syncCFG: start synchronization of $name") if $MaintenanceLog;
    &syncConfigSend($name);
}

sub syncConfigSend {
    my $name = shift;
# ConfigName.sprintf("%.3f",(Time::HiRes::time())).ip|host.cfg
# first line plain var name\r\n  rest Base64 \r\n.\r\n
# varname:=value\r\n
# file start (.+)$
# file eof\s*$

    return 0 unless (&syncCanSync() && $enableCFGShare && $CanUseNetSMTP);
    return 0 unless $isShareMaster;
    return 0 if exists $neverShareCFG{$name};
    return 0 unless exists $Config{$name};
    return 0 if $ConfigSync{$name}->{sync_cfg} < 1;
    my $syncservers = $ConfigSyncServer{$name};
    my ($k,$v);
    my $r = 0;
    foreach my $syncserver ( split( ",", $syncservers ) ) {
        my ($k,$v) = split(/\s*\=\s*/o,$syncserver);
        next if $v < 1;
        next if $v == 3;
        $r |= $v;
    }
    return 0 unless $r;
    if ($r == 4) {
        foreach my $syncserver ( split( ",", $syncservers ) ) {
            my ($k,$v) = split(/\s*\=\s*/o,$syncserver);

            $syncservers=~s/$k=4/$k=2/g; 
        }
        $ConfigSyncServer{$name} = $syncservers;
        &syncWriteConfig();
        return 0;
    }
    d("syncConfigSend $name");
    mlog(0,"syncCFG: request to synchronize $name") if $MaintenanceLog;
    my $body = "$name\r\n";
    $body .= MIME::Base64::encode_base64("$name:=" . ${$name},'') . "\r\n";
    my $fil;
    foreach my $f (@PossibleOptionFiles) {
        if ($name eq $f->[0] && $Config{$f->[0]} =~ /^ *file: *(.+)/io) {
           my $ffil = $fil = $1;
           $ffil="$base/$ffil" if $ffil!~/^\Q$base\E/io;
           if (defined ${$name.'RE'} && ${$name.'RE'} =~ /^$neverMatchRE$/o && -s $ffil) {
              mlog(0,"syncCFG: warning - the file '$fil' is not empty, but the running regex for $name is a never matching regex (used for empty files) - the sync request will be ignored, because it seems that the file contains an invalid regex");
              my $syncservers = $ConfigSyncServer{$name};
              foreach my $syncserver ( split( ",", $syncservers ) ) {
                    my ($k,$v) = split(/\s*\=\s*/o,$syncserver);
                  next if $v < 1;
                  next if $v == 3;
                  if ($v == 4) {
                      $syncservers=~s/$k=4/$k=2/g;

                      next;
                  }

                  if ($v == 1) {
                      $syncservers=~s/$k=1/$k=2/g;
                      next;
                  }

              }
              $ConfigSyncServer{$name}=$syncservers;
              return 0;
           }
           if (($body .= &syncGetFile($fil)) && scalar keys %{$FileIncUpdate{"$ffil$name"}}) {
               foreach (keys %{$FileIncUpdate{"$ffil$name"}}) {
                   $body .= &syncGetFile($_);
               }
           }
           last;
        }
    }
    # send to  %{$syncserver}

    my $failed = 1;
    foreach my $syncserver ( split( ",", $syncservers ) ) {
        my ($MTA,$v) = split(/\s*\=\s*/o,$syncserver);

        next if $v < 1;
        next if $v == 3;
        if ($v == 4) {

            $syncservers=~s/$MTA=4/$MTA=2/g;
            $ConfigSyncServer{$name}=$syncservers;
            next;
        }
        my $smtp;
      eval {
        $smtp = Net::SMTP->new(
            $MTA,
            Hello   => $myName,
            Debug => $debug,
            Timeout => 5
        );
        if ($smtp &&
            $smtp->command('ASSPSYNCCONFIG ' , ' ' . Digest::MD5::md5_base64($syncCFGPass))->response() == 2 &&
            $smtp->data() &&
            $smtp->rawdatasend( $body ) &&
            $smtp->dataend() &&
            $smtp->quit
            )
        {
            mlog(0,"syncCFG: successfully sent config for $name to $MTA") if $MaintenanceLog;

            $syncservers=~s/$MTA=1/$MTA=2/g;
            $failed = 0;
        } else {
            my $text;
            eval{$text = $smtp ? ' - ' . $smtp->message() : '';};
            mlog(0,"syncCFG: unable to send config for $name to $MTA$text");

        }
      } unless $syncTestMode; # end eval
        if ($@) {
            mlog(0,"syncCFG: error - unable to send config for $name to $MTA - $@");

        }
        if ($syncTestMode) {
            mlog(0,"syncCFG: [testmode] successfully sent config for $name to $MTA") if $MaintenanceLog;
            $syncservers=~s/$MTA=1/$MTA=2/g;
            $failed = 0;
        }
    }
    $ConfigSyncServer{$name}=$syncservers;
    &syncWriteConfig();
    return $failed;
}

sub syncGetFile {
    d('syncGetFile');
    my $file = shift;
    my $ffil = $file;
    $ffil="$base/$ffil" if $ffil!~/^\Q$base\E/o;
    my $body;

    if (open my $FH, '<',$ffil) {
        binmode $FH;
        $body  = MIME::Base64::encode_base64("# file start $file\r\n",'')."\r\n";
        $body .= MIME::Base64::encode_base64(join('',<$FH>),'') . "\r\n";
        close $FH;
        $body .= MIME::Base64::encode_base64("# file eof\r\n",'')."\r\n";
    }
    return $body;
}

sub syncSortCFGRec {
   my ($ga) = $main::a =~ /\Q$base\E\/configSync\/([^\.]+)/o;
   my ($gb) = $main::b =~ /\Q$base\E\/configSync\/([^\.]+)/o;
   if ($ConfigNum{$ga} < $ConfigNum{$gb}) { -1; }
   elsif ($ConfigNum{$ga} == $ConfigNum{$gb}) { 0; }
   else { 1; }
}

sub syncConfigReceived {
    my $file = shift;
    $file =~ s/\\/\//go;
    my ($name,$ip) = $file =~ /\/([^\/\.]+?)\.\d{10}\.\d{3}\.($HostRe)\.cfg$/o;

    unless ($name) {unlink $file; return;}
    unless ($ip) {unlink $file; return;}
    unless (&syncCanSync()) {unlink $file; return;}
    unless ($enableCFGShare) {unlink $file; return;}
    unless ($isShareSlave) {unlink $file; return;}
    if (exists $neverShareCFG{$name}) {unlink $file; return;}
    unless (exists $Config{$name}) {unlink $file; return;}
    my $FH;
    (-w $file && open $FH, "<","$file")  or return;
    d("syncConfigReceived $file $name $ip");

    my ($line,$var,$val,@cfg,$File,$FileCont);
    my $FileWritten = 0;

    while ($line = (<$FH>)) {
        $line =~ s/\r|\n//go;
        next unless $line;
        push @cfg , MIME::Base64::decode_base64($line);
    }
    close $FH;
    while (@cfg) {
        $line = shift @cfg;
        $line =~ s/(\r?\n)$//o;
        next if (! $line && ! $File);
        if ($line =~ /^\s*([a-zA-Z0-9_-]+)\:\=(.*)$/o) {
            ($var,$val) = ($1,$2);
            if ($var ne $name) {
                last;
            }
            next;
        }
        next unless $var;
        if ($line =~ /^\s*# file start (.+)$/o) {   # file start
            $File = "$base/$1";
            $File .= '.synctest' if $syncTestMode;
            next;
        }
        if ($File && $line =~ /^\s*# file eof\s*$/o) {   # file eof
            my $currFileCont;
            if (-e $File) {
                if (open my $FileH , "<","$File") {
                    binmode $FileH;
                    $currFileCont = join('',<$FileH>);
                    close $FileH;
                }
            }
            if ($currFileCont ne $FileCont && open my $FileH , ">","$File") {
                binmode $FileH;
                print $FileH $FileCont;
                close $FileH;
                my $text = $syncTestMode ? '[testmode] ' : '' ;
                mlog(0,"syncCFG: $text" . "wrote file $File for $name") if $MaintenanceLog;
                $FileWritten = 1;
            }
            $File = '';
            $FileCont = '';
            next;
        }
        $FileCont .= $line if $File;
    }
    if ($var ne $name) {
        mlog(0,"syncCFG: wrong variable $var found - expected $name - ignore the sync-file");
        unlink $file;
        return;
    }

    if (${$var} ne $val or $FileWritten) {
        my $ovar = ${$var};
        foreach my $c (@ConfigArray) {
            next if (! $c->[0] or @$c == 5 or $c->[0] ne $var);
            my $oqs = $qs{$var};
            $qs{$var} = $val;
            $syncUser = 'sync';
            $syncIP = $ip;
            my $Error;$Error = checkUpdate($var,$c->[5],$c->[6],$c->[1]) unless $syncTestMode;
            mlog(0,"syncCFG: [testmode] changed $name from '$Config{$name}' to '$val'") if $syncTestMode;
            $qs{$var} = $oqs;
            $syncUser = '';
            $syncIP = '';
            delete $qs{$var} unless defined $oqs;
            if ($Error =~ /span class.+?negative/o) {
                 mlog(0,"syncCFG: wrong value ($val) for $var found in sync file from $ip") if $MaintenanceLog;
                 unlink $file;
                 return;
            }
            if (! $ConfigChanged and $FileWritten) {
                $syncUser = 'sync';
                $syncIP = $ip;
                &optionFilesReload();
                $ConfigChanged = 1 if ($val eq $ovar);
                $syncUser = '';
                $syncIP = '';
            }
        }
    }

    my $syncserver = $ConfigSyncServer{$name};
    my ($k,$v,$ns);
    $ns = 0;
    while ( ($k,$v) = each %{$ConfigSyncServer{$name}})  {$ns++ if $isShareMaster && ($v == 1 or $v == 2 or $v == 4);}
    while ( ($k,$v) = each %{$syncserver}) {
        my $isM = $isShareMaster && ($v == 1 or $v == 2 or $v == 4);
        my $s = $k;
        $s =~ s/\:\d+$//o;
        if ($s eq $ip && $isM) {
            $syncserver->{$k} = ($ns == 1) ? 2 : 4;
        } elsif ($isM) {
            $syncserver->{$k} = 1;
        } else {
            $syncserver->{$k} = 3;
        }
    }

    unlink $file;
}

sub syncRCVData {
    my($fh,$l)=@_;
    d('syncRCVData');
    my $this=$Con{$fh};
    if($l=~/^DATA/io) {
        $this->{lastcmd} = 'DATA';
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
        $Con{$fh}->{getline}=\&syncRCVData2;
        sendque($fh,"354 send data\r\n");
    } else {
        ($this->{lastcmd}) = $l =~ /^\s*(\S+)[\s\r\n]+/o;
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
        mlog($fh,"syncCFG: error - syncRCVData expected 'DATA' got $l");
        NoLoopSyswrite($fh,"500 sequence error - DATA expected\r\n");
        done($fh);
    }
}

sub syncRCVData2 {
    my($fh,$l)=@_;
    d('syncRCVData2');
    my $this=$Con{$fh};
    $this->{header} .= $l;
    if($this->{header} =~ /\r\n\.\r\n$/os) {
        $Con{$fh}->{getline}=\&syncRCVQuit;
        sendque($fh,"250 OK got all SYNC data\r\n");
    }
}

sub syncRCVQuit {
    my($fh,$l)=@_;
    d('syncRCVQuit');
    my $this=$Con{$fh};
    if($l=~/^QUIT/io) {
        $this->{lastcmd} = 'QUIT';
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
        my $time = sprintf("%.3f",(Time::HiRes::time()));
        my $var; ($var) = $1 if $this->{header} =~ s/^([^\r\n]+)\r\n//os;
        &NoLoopSyswrite($fh,"221 closing transmission for SYNC $var\r\n");
        unless (defined ${$var}) {
            mlog(0,"warning: $var is no valid Configuration Parameter - ignore request");
            done($fh);
            return;
        }
        -d "$base/configSync/" or mkdir "$base/configSync", 0755;
        my $file = "$base/configSync/" . $var . '.' . $time  . '.' . $this->{syncServer} . '.cfg';
        if (open my $FH, ">","$file") {
            binmode $FH;
            $this->{header} =~ s/\.[\r\n]+$//;
            print $FH $this->{header};
            close $FH;
            $syncToDo = 1;
        } else {
            mlog(0,"syncCFG: error - unable to write file $file - $!");
        }
    } else {
        ($this->{lastcmd}) = $l =~ /^\s*(\S+)[\s\r\n]+/o;
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;
        mlog($fh,"syncCFG: error - syncRCVQuit expected 'QUIT' got $l");
        NoLoopSyswrite($fh,"500 sequence error after DATA - Quit expected\r\n");
    }
    done($fh);
}

sub reloadConfigFile {

    # called on SIG HUP
    d('reloadConfigFile');
    my %newConfig = ();
    mlog(0,"called on SIG HUP :reloading config");
    my $RCF;
    open($RCF,"<","$base/assp.cfg");
    while (<$RCF>) {
        s/\r|\n//go;
        my ($k,$v) = split(/:=/o,$_,2);
        next unless $k;
        $newConfig{$k} = $v;
    }
    close $RCF;

    my $dec = ASSP::CRYPT->new($Config{webAdminPassword},0);

    foreach my $c (@ConfigArray) {
        my ($name,$nicename,$size,$func,$default,$valid,$onchange,$description)=@$c;
        if($Config{$name} ne $newConfig{$name}) {
            if($newConfig{$name}=~/$valid/i) {
                my $new=$1; my $info;
                if($onchange) {
                    $info=$onchange->($name,$Config{$name},$new);
                } else {
                    my $app;$app = "from '$Config{$name}' to '$new'" unless (exists $cryptConfigVars{$name});
                    mlog(0,"AdminUpdate: reload config - $name changed $app");
                    ${$name}=$new;

# -- this sets the variable name with the same name as the config key to the new value
# -- for example $Config{myName}="ASSP-nospam" -> $myName="ASSP-nospam";
                }
                if (exists $cryptConfigVars{$name} &&
                    $new =~ /^[a-fA-F0-9]+$/o &&
                    defined $dec->DECRYPT($new)) {

                    $Config{$name} = $dec->DECRYPT($new);
                    ${$name}=$Config{$name};
                } else {
                    $Config{$name}=$new;
                }

            } else {
                mlog(0,"AdminUpdate:error: invalid '$newConfig{$name}' -- not changed");
            }
        }
    }

    renderConfigHTML();
    $ConfigChanged = 1;
}

sub GPBSetup {
    $GPBmodTestList = sub {my ($how,$parm,$whattodo,$text,$value,$skipbackup)=@_;
    d("GPBmodTestList - $parm - $whattodo");
    my $file;
    my $GPBFILE;
    my @cont;
    my $case = (exists $preMakeRE{$parm}) ? '' : 'i';
    $case = 'i' if $parm eq 'preHeaderRe';
    if(${$parm} =~ /^\s*file:\s*(.+)\s*$/io) {
        $file=$1;
    } else {
        mlog(0,"warning: config parameter '$parm' is not configured to use a file (file:...) - unable to $whattodo entry") if $parm ne 'noProcessingSenderBaseIPs';
        return 0;
    }
    $file="$base/$file" if $file!~/^(([a-z]:)?[\/\\]|\Q$base\E)/io;
    return if ( !-e "$file");
    (open ($GPBFILE, '<',$file)) or (mlog(0,"error: unable to read from file $file for '$parm' to '$whattodo' entry") and return 0);
    @cont = <$GPBFILE>;
    close ($GPBFILE);
    if ($whattodo eq 'delete' && grep(/(?$case:^\s*[^#]?\s*\Q$value\E)/,@cont)) {
        if (!$skipbackup) {
            unlink "$file.bak";
            rename("$file","$file.bak");
        }
        (open ($GPBFILE, '>',"$file")) or (mlog(0,"error: unable to write to file $file for '$parm' to '$whattodo' entry") and return 0);
        binmode $GPBFILE;
        while (@cont) {
            my $line = shift @cont;
            $line =~ s/\r?\n$//o;
            if ($line =~ /(?$case:^\Q$value\E)/) {
                mlog(0,"$how: $value removed from $parm - $text");
                next;
            }
            print $GPBFILE "$line\n";
        }
        close ($GPBFILE);
        $ConfigChanged = 1;
        $lastOptionCheck = time - 35;
        optionFilesReload();
        return 1;
    } elsif ($whattodo eq 'add' && ! grep(/(?$case:^\Q$value\E)/,@cont)) {
        if (!$skipbackup) {
            unlink "$file.bak";
            copy("$file","$file.bak");
        }
        (open ($GPBFILE, '>>',"$file")) or (mlog(0,"error: unable to write to file $file for '$parm' to '$whattodo' entry") and return 0);
        binmode $GPBFILE;
        print $GPBFILE "\n$value";
        close ($GPBFILE);
        mlog(0,"$how: $value added to $parm - $text");
        $ConfigChanged = 1;
        $lastOptionCheck = time - 35;
        optionFilesReload();
        return 1;
    } elsif ($whattodo eq 'check') {
        grep(/(?$case:^\s*#\s*\Q$value\E)/,@cont) and return 1;
        grep(/(?$case:^\Q$value\E)/,@cont) and return 2;
        return -1;
    }
    return 0;};

    $GPBCompLibVer = sub {my($f1,$f2) = @_;
    return unless($f1 && $f2);
    return unless(-e $f1 && -e $f2);
    my $cmdf1;
    my $cmdf2;
    my ($mod) = $f1 =~ /^\Q$base\E\/(?:(?:download|lib|Plugins)\/)?(.+)\.p[ml]$/oi;
    $mod =~ s/\//::/go;
    my $perl = $^X;
    $perl =~ s/\"\'//go;
    if ($^O eq "MSWin32") {
        my $inc = join(' ', map {'-I "'.$_.'"'} @INC);
        $cmdf1 = '"' . $perl . '"' . " $inc -e \"require '$f1';print \$$mod"."::VERSION;\"";
        $cmdf2 = '"' . $perl . '"' . " $inc -e \"require '$f2';print \$$mod"."::VERSION;\"";
    } else {
        my $inc = join(' ', map {'-I \''.$_.'\''} @INC);
        $cmdf1 = '\'' . $perl . '\'' . " $inc -e \"require '$f1';print \$$mod"."::VERSION;\"";
        $cmdf2 = '\'' . $perl . '\'' . " $inc -e \"require '$f2';print \$$mod"."::VERSION;\"";
    }
    mlog(0,"info: version f1 command: $cmdf1") if $MaintenanceLog > 2;
    mlog(0,"info: version f2 command: $cmdf2") if $MaintenanceLog > 2;
    my $resf1 = qx($cmdf1);
    my $resf2 = qx($cmdf2);
    $resf1 =~ s/\r|\n//go;
    $resf2 =~ s/\r|\n//go;
    $resf1 = undef if $resf1 !~ /^\d+(?:\.\d+)?$/o;
    $resf2 = undef if $resf2 !~ /^\d+(?:\.\d+)?$/o;
    mlog(0,"info: found file versions: $f1 ($resf1) , $f2 ($resf2)") if $MaintenanceLog >= 2;
    return unless $resf2;
    return $resf2 if $resf2 gt $resf1;
    return;};

    $GPBinstallLib = sub {my ($url,$file) = @_;
    return 0 unless $url && $file;
    return 0 unless $GPBautoLibUpdate;
    my ($name) = $file =~ /\/?([^\/]+)$/io;
    $file="$base/$file" if $file!~/^\Q$base\E/io;
    copy("$base/download/$name","$base/tmp/$name.bak") if -e "$base/download/$name";
    if (! downloadHTTP($url,"$base/download/$name",0,$name,24,24,2,1)) {
        unlink("$base/tmp/$name.bak");
        return 0;
    }
    if (-e $file) {
        use File::Compare;
        my $ret = File::Compare::compare("$base/download/$name",$file);
        if ($ret == 0) { # files are equal - nothing to do
            mlog(0,"info: the most recent version of $name is still installed") if $MaintenanceLog;
            unlink("$base/tmp/$name.bak");
            return 0;
        } elsif (-e $file && $ret == -1) { # an error while compare
            mlog(0,"warning: unable to compare $base/download/$name and $file");
            unlink("$base/tmp/$name.bak");
            return 0;
        }
    }
    File::Copy::move("$base/tmp/$name.bak","$base/download/$name.bak") if -e "$base/tmp/$name.bak";
    my $cmd;
    my $perl = $^X;
    $perl =~ s/\"\'//go;
    if ($^O eq "MSWin32") {
        my $inc = join(' ', map {'-I "'.$_.'"'} @INC);
        $cmd = '"' . $perl . '"' . " $inc -c \"$base/download/$name\" 2>&1";
    } else {
        my $inc = join(' ', map {'-I \''.$_.'\''} @INC);
        $cmd = '\'' . $perl . '\'' . " $inc -c \'$base/download/$name\' 2>&1";
    }
    my $res = qx($cmd);
    if ($res =~ /syntax\s+OK/igos) {
        mlog(0,"info: GPB-autoupdate: syntax check for '$file' returned OK");
    } else {
        mlog(0,"warning: GPB-autoupdate: syntax error in '$file' - skip $file update - syntax error is: $res");
        return 0;
    }
    my $newVer = $GPBCompLibVer->($file,"$base/download/$name");
    unless ($newVer) {
        mlog(0,"info: the installed version of file $name is equal to, or newer than the downloaded version") if $MaintenanceLog;
        return 0;
    }
    mlog(0,"info: GPB-autoaupdate: successful downloaded version ($newVer) of $file in $base/download/$name");
    return 1 if ($GPBautoLibUpdate == 1 or ! -e $file);
    File::Copy::move($file,"$file.bak");
    copy("$base/download/$name",$file);
    mlog(0,"info: GPB-autoupdate: new version ($newVer) of $file was installed - restart required");
    return 1;};
}

sub printallCon {
    my $fh   = shift;
    my $this = $Con{$fh};
    return unless $this;
    return unless scalar( keys %$this );
    my $friend = $Con{ $this->{friend} };
    -d "$base/debug" or mkdir "$base/debug", 0777;
    my $c = 1;
    while ( -s "$base/debug/con$c.txt" ) { $c++ }
    my $file = "$base/debug/con$c.txt";
    my $OUT;
    open $OUT, ">","$file";
    binmode $OUT;
    print $OUT "this --------------------------------------\n";

    foreach ( keys %$this ) {
        print $OUT "this->$_ = $this->{$_}\n";
    }
    if ($friend) {
        print $OUT "\nfriend --------------------------------------\n";
        foreach ( keys %$friend ) {
            print $OUT "friend->$_ = $friend->{$_}\n";
        }
    }
    close $OUT;
}

sub tlit {
    my $mode = shift;

    return "[monitoring]" if $mode == 2;
    return "[scoring]"    if $mode == 3;
    return "[testmode]"   if $mode == 4;
}

sub setPermission {
    my ($dir,$perm,$subdirs,$print) = @_;
    $dir =~ s/\\/\//go;
    my @files;
    my $file;
    my $has;
    my $type;
    if ($dF->( $dir )) {
        @files = $unicodeDH->($dir);
    } else {
        push @files,$dir;
    }
    $has = $chmod->( $perm, $dir);
    print "unable to set permission for directory $dir\n" if(! $has && $print);
    mlog(0, "unable to set permission for directory $dir") if(! $has && $print);
    return unless ($dF->( $dir ));
    while (@files) {
        $file = shift @files;
        next if $file eq '.';
        next if $file eq '..';
        $file = "$dir/$file";
        $type = $dF->( $file ) ? 'directory' : 'file' ;
        $has = $chmod->( $perm,$file ) if ($eF->( $file ));
        print "unable to set permission for $type $file\n" if(! $has && $print);
        mlog(0, "unable to set permission for $type $file") if(! $has && $print);
        &setPermission($file,$perm,$subdirs,$print) if ($dF->( $file ) && $subdirs);
    }
}

sub checkPermission {
    my ($dir,$perm,$subdirs,$print) = @_;
    $dir =~ s/\\/\//go;
    my @files;
    my $file;
    my $has;
    my $type;
    if ($dF->( $dir )) {
        @files = $unicodeDH->($dir);
    } else {
        push @files,$dir;
    }
    $has = [$stat->($dir)]->[2];
    $has=sprintf("0%o", $has & oct('07777'));
    print "permission for directory $dir is $has - should be at least $perm\n" if($has < $perm && $print);
    mlog(0, "permission for directory $dir is $has - should be at least $perm") if($has < $perm && $print);
    return unless ($dF->( $dir ));
    while (@files) {
        $file = shift @files;
        next if $file eq '.';
        next if $file eq '..';
        $file = "$dir/$file";
        $type = $dF->( $file ) ? 'directory' : 'file' ;
        $has = [$stat->($file)]->[2];
        $has=sprintf("0%o", $has & oct('07777'));
        print "permission for $type $file is $has - should be at least $perm\n" if($has < $perm && $print);
        mlog(0, "permission for $type $file is $has - should be at least $perm") if($has < $perm && $print);
        print "$type $file is not writeable with this job - it has a wrong permission, or is still opened by another process!\n" if($type eq 'file' && ! -w $file && $print);
        mlog(0, "$type $file is not writeable with this job - it has a wrong permission, or is still opened by another process!") if($type eq 'file' && ! -w $file && $print);
        &checkPermission($file,$perm,$subdirs,$print) if ($dF->( $file ) && $subdirs);
    }
}
sub RcptReplace {
  my ($recpt,$sender,$RecRepRegex) = @_;
  my $new = $recpt;
  my @new;
  my @ret;
  $ret[0] = "result";
  my $k;
  my $v;
  my $ad;
  my $bd;
  my $jmptarget;
  my $sendertext;

  if ($sender) {
    $sendertext = "for sender $sender";
  } else {
    $sendertext = "for all senders";
  }

  push(@ret,"try replace $recpt $sendertext with rules in configuration");

  foreach (sort(keys(%$RecRepRegex))) {
    $k = $_;
    if ($jmptarget && $k ne $jmptarget) {
       next;
    } else {
       $jmptarget = '';
    }
    $v = $$RecRepRegex{$k};
    my ($type,$toregex,$replregex,$sendregex,$nextrule,$jump) = split('<=>',$v);
    $sendregex = '*' if ($sendregex eq '' && ($type eq 'S' || $type eq ''));
    $sendregex = '.*' if ($sendregex eq '*' && $type eq 'R');
    $type = uc($type);
    if ($type eq 'S' || $type eq '') {
      $toregex   = RcptRegexMake($toregex,1);
      $replregex = RcptRegexMake($replregex,0);
      $sendregex = RcptRegexMake($sendregex,1);
    }
    next if($type ne 'S' && $type ne '' && $type ne 'R');
    @new = RecRep($toregex,$replregex,$sendregex,$recpt,$sender,$k);
    $new = shift(@new);
    push (@ret, "$k $v");
    if ($type eq 'S' || $type eq '') {push (@ret,"$k  :R\<=\>$toregex\<=\>$replregex\<=\>$sendregex\<=\>$nextrule\<=\>$jump : regex $k");}
    foreach (@new) {push(@ret,$_);}
    $ad = @new - 1;
    $bd = $ad;
    $ad = $new[$ad];
    $new[$bd] = '';

    if ($ad eq '1' && $nextrule == 1) {       # match and action if
      if ($jump) {
        if (! exists $$RecRepRegex{$jump}) {
          if ($jump eq 'END') {
             push (@ret, "$k jumptarget: rule $jump - found in rule $k - end processing");
          } else {
             push (@ret, "$k jumptarget: rule $jump - not found in rule $k - end processing");
          }
          last;
        }
        if ($jump eq $k) {
          push (@ret, "$k jumptarget: jump to the same rule $jump is not permitted - end processing");
          last;
        }
        if ($jump lt $k) {
          push (@ret, "$k jumptarget: jump backward from rule $k to rule $jump is not permitted - end processing");
          last;
        }
        $jmptarget = $jump;
        push (@ret, "$k jump: to rule $jump");
        next;
      }
      last;
    }

    if ($ad eq '0' && $nextrule == 2) {     # no match and action if
      if ($jump) {
        $recpt = $new;
        if (! exists $$RecRepRegex{$jump}) {
          if ($jump eq 'END') {
             push (@ret, "$k jumptarget: rule $jump - found in rule $k - end processing");
          } else {
             push (@ret, "$k jumptarget: rule $jump - not found in rule $k - end processing");
          }
          last;
        }
        if ($jump eq $k) {
          push (@ret, "$k jumptarget: jump to the same rule $jump is not permitted - end processing");
          last;
        }
        if ($jump lt $k) {
          push (@ret, "$k jumptarget: jump backward from rule $k to rule $jump is not permitted - end processing");
          last;
        }
        $jmptarget = $jump;
        push (@ret, "$k jump: to rule $jump");
        next;
      }
      last;
    }

    if ($nextrule == 0 && $jump) {
       $jmptarget = $jump;
       push (@ret, "$k jump: to rule $jump");
    }

    $recpt = $new;
  }
  if ($k) {
    push (@ret, "returns: $new after rule $k in configuration");
  } else {
    push (@ret, "returns: $new - no rule found in configuration");
  }
  if (wantarray) {
    $ret[0] = $new;
    return @ret;
  } else {
    return $new;
  }
}

sub RecRep {
  my ($toregex,$replregex,$sendregex,$recpt,$sender,$rnum) = @_;
  my @retval;
  my $cmpl_error;
  $retval[0] = "result";

  $cmpl_error = RecRepSetRE('TO_RE',$toregex);
  push (@retval, $cmpl_error) if ($cmpl_error);
  $cmpl_error = RecRepSetRE('RP_RE',$replregex);
  push (@retval, $cmpl_error) if ($cmpl_error);
  $cmpl_error = RecRepSetRE('SE_RE',$sendregex);
  push (@retval, $cmpl_error) if ($cmpl_error);

  if ($sender =~ /$SE_RE/i && $recpt =~ /$TO_RE/i) {

    push (@retval, "$rnum  |\$1=$1|\$2=$2|\$3=$3|\$4=$4|\$5=$5|\$6=$6|\$7=$7|\$8=$8|\$9=$9|");
    my $d1 = $1;my $d2 = $2;my $d3 = $3;
    my $d4 = $4;my $d5 = $5;my $d6 = $6;
    my $d7 = $7;my $d8 = $8;my $d9 = $9;

    $replregex =~ s/\$1/$d1/;
    $replregex =~ s/\$2/$d2/;
    $replregex =~ s/\$3/$d3/;
    $replregex =~ s/\$4/$d4/;
    $replregex =~ s/\$5/$d5/;
    $replregex =~ s/\$6/$d6/;
    $replregex =~ s/\$7/$d7/;
    $replregex =~ s/\$8/$d8/;
    $replregex =~ s/\$9/$d9/;
    if (wantarray){
      $retval[0] = $replregex;
      push(@retval,'1');
      return @retval;
    } else {
      return $replregex;
    }
  } else {
    if (wantarray){
      $retval[0] = $recpt;
      push(@retval,'0');
      return @retval;
    } else {
      return $recpt;
    }
  }
}

sub RecRepSetRE {
 use re 'eval';
 my ($var,$r)=@_;
 eval{$$var=qr/(?i)$r/};
 return $@;
}

sub RcptRegexMake {
  my ($string,$how) = @_;
  if ($how) {
    $string =~ s/\./\\\./go;
    $string =~ s/\*/\(\.\*\)/go;
    $string =~ s/\+/\(\.\+\)/go;      # hidden option
    $string =~ s/\?/\(\.\?\)/go;      # hidden option
    $string =~ s/\;/\(\.\)/go;        # hidden option
    $string = "^".$string."\$";
  } else {
    my $i = 1;
    while ($string =~ /\*/o) {
       $string =~ s/\*/\$$i/o ;
       $i++;
    }
  }
  return $string;
}

sub configChangeRcptRepl {
 my ($name, $old, $new, $init)=@_;
 mlog(0,"AdminUpdate: recipient replacement updated from '$old' to '$new'") unless $init || $new eq $old;

 $ReplaceRecpt=$Config{ReplaceRecpt}=$new;
 $new=checkOptionList($new,'ReplaceRecpt',$init);
 my $k;
 my $i = 0;
 my $j = 0;
 my $t1;
 my $t2;
 my %rules;
 my $ret;

 for my $v (split(/\|/o,$new)) {
     $v=~/(.*?)\<\=\>(.*?\<\=\>.*?\<\=\>.*?\<\=\>.*?\<\=\>.*?\<\=\>.*)/o;
     $t1 = $1;
     $t2 = $2;
     if ($t1 eq '') { # rule is disabled
       $j++;
       next;
     }
     if (! $t1 && ! $t2) {
       $ret .= ConfigShowError(1,"ERROR: syntax error in recipient replacement rule $v");
       $j++;
       next;
     }
     if (! $t2) {
       $ret .= ConfigShowError(1,"ERROR: syntax error in recipient replacement rule $v");
       $j++;
       next;
     }
     if ($t1 =~ /END/o) {
       $ret .= ConfigShowError(1,"ERROR: rule number END is not permitted - in recipient replacement rule $v");
       $j++;
       next;
     }
     if (exists $rules{$t1}) {
       $ret .= ConfigShowError(1,"ERROR: rule number $t1 is already defined with $rules{$t1} - ignore entry $v");
       $j++;
       next;
     }
     $i++;
     $rules{$t1}=$t2;
 }
 %RecRepRegex = %rules;
 my $tlit = $init ? 'info: ' : 'AdminUpdate: ';

 return $ret;
}

sub CheckRcptRepl {
 my $RecReprecipient = $qs{RecReprecipient};
 my $RecRepsender = $qs{RecRepsender};
 my $RecRepresult = '';
 my @RecRepresult;
 my $RecRepdspres = '';
 my $RecRepbutton;
 my $disabled = '';
 my $link_to_RecRep_config = $WebIP{$ActWebSess}->{lng}->{'msg500040'} || $lngmsg{'msg500040'};

 my $updres;
 my $file;
 my @s;
 my $mtime;

 if ($ReplaceRecpt =~ /^ *file: *(.+)/io) {
    $file=$1; $file="$base/$file" if $file!~/^\Q$base\E/io;
    @s=stat($file);
    $mtime=$s[9];
    if ( $FileUpdate{$file} != $mtime ) {
      $updres = configChangeRcptRepl('ReplaceRecpt',$ReplaceRecpt,$ReplaceRecpt,0);
    }
 }

 if ($ReplaceRecpt) {
   if ($qs{B1} =~ /Check/o){
       @RecRepresult = RcptReplace($RecReprecipient,$RecRepsender,'RecRepRegex');
       if ($updres) {
          $RecRepresult = $RecRepresult[0];
          $RecRepresult[0] = $updres;
       } else {
          $RecRepresult = shift(@RecRepresult);
       }
   }
   $RecRepbutton ='
    <tr>
        <td class="noBorder">&nbsp;</td>
        <td class="noBorder"><input type="submit" name="B1" value="  Check  " /></td>
        <td class="noBorder">&nbsp;</td>
    </tr>';
   foreach (@RecRepresult) {
     next if ($_ eq '1' || $_ eq '0');
     s/configuration$/ file $file/ if ($file);
     $RecRepdspres .= "$_\<br /\>";
   }
 } else {
   @RecRepresult = ();
   push (@RecRepresult, $WebIP{$ActWebSess}->{lng}->{'msg500041'} || $lngmsg{'msg500041'});
   $disabled = "disabled";
 }

 if ($ReplaceRecpt =~ /^ *file: *(.+)/io) {
  $file = $1;
  if ($file) {
    $link_to_RecRep_config = $WebIP{$ActWebSess}->{lng}->{'msg500042'} || $lngmsg{'msg500042'};
    $link_to_RecRep_config .= $file.' &nbsp;<input type="button" value="Edit" onclick="javascript:popFileEditor(\''.$file.'\',3);" /></p>';
  }
 }
 my $h1 = $WebIP{$ActWebSess}->{lng}->{'msg500043'} || $lngmsg{'msg500043'};

<<EOT;
$headerHTTP
$headerDTDTransitional
$headers
<div class="content">
<h2>Recipient Replacement Test</h2>
<div class="textBox">
$link_to_RecRep_config
</div>
<form method="post" action=\"\">
    <table class="textBox" style="width: 99%;">
        <tr>
            <td class="noBorder">recipient : </td>
            <td class="noBorder">
            <input type="text" $disabled size="30" name="RecReprecipient" value="$RecReprecipient"</td>
        </tr>
        <tr>
            <td class="noBorder">sender    : </td>
            <td class="noBorder">
            <input type="text" $disabled size="30"  name="RecRepsender" value="$RecRepsender"</td>
        </tr>
        <tr><td class="noBorder">  </td></tr>
        <tr>
            <td class="noBorder">result    : </td>
            <td class="noBorder">
            <p>$RecRepresult</p></td>
        </tr>
        $RecRepbutton
    </table>
</form>
<div class="textBox">
$h1
$RecRepdspres
</form>
<form name="ASSPconfig" id="ASSPconfig" action="" method="post">
  <input name="theButtonLogout" type="hidden" value="" />
</form>
</div>
</div>
$footers
</body></html>
EOT

}

################################################################################
#              write rebuildspamdb.pl
################################################################################
sub writeRebuild {
my $rebuild_version = '14241';
my $curr_version;
if (open my $ADV, '<',"$base/rebuildspamdb.pl") {
    while (<$ADV>) {
        if (/^\s*our \$modversion.+?\((.*)\)/o) {
            $curr_version = $1;

            last;
        }

    }
    close $ADV;

    if ($curr_version eq $rebuild_version) {
        mlog(0,"info: $base/rebuildspamdb.pl $curr_version is current ");        
        return 0;
    } 

}

mlog(0,"info: installing new $base/rebuildspamdb.pl version $rebuild_version");
(open my $ADV, '>',"$base/rebuildspamdb.pl") or return 0;

print $ADV <<'RBEOT' or return 0;
#!/usr/bin/perl
# rebuilds bayesian spam database
our $VERSION = "RB-1.10.0";
our $modversion = '(14241)'; 
# (c) John Hanna 2003 under the terms of the GPL
# Updated July 2004 for simple proxy support.
# (c) Fritz Borgstedt 2006 under the terms of the GPL
# Updated Feb 2008 refactoring and rewrites
# (c) Kevin 2008 under the terms of the GPL
use bytes;    # get rid of anoying 'Malformed UTF-8' messages
use Digest::MD5 qw(md5_hex);
use English '-no_match_vars';
use File::Copy;
use IO::Handle;
use IO::Socket;
use Time::Local;
use Time::HiRes;
use Cwd;
use strict qw(vars subs);
our $AvailLWP  = eval('use LWP::Simple; 1');    # LWP::Simple module installed
our $CanUseLWP = $AvailLWP;
our    $noGriplistUpload;
our    $noGriplistDownload;
our $gripListDownUrl = 'http://*HOST*/cgi-bin/assp_griplist?binary';
our $gripListUpUrl = 'http://*HOST*/cgi-bin/assp_griplist?binary';
our $gripListUpHost = 'assp.sourceforge.net';
$gripListDownUrl =~ s/\*HOST\*/$gripListUpHost/o;
$gripListUpUrl  =~ s/\*HOST\*/$gripListUpHost/o;

our %Config;

our $HeaderValueRe=qr/[ \t]*[^\r\n]*(?:\r?\n[ \t]+\S[^\r\n]*)*(?:\r?\n)?/o;
# IP address representations
our $IPprivate;
our $IPQuadSectRE;
our $IPQuadSectDotRE;
our $IPQuadRE;
our $IPStrictQuadRE;

# Host
our $IPSectRe;
our $IPSectHexRe;
our $IPSectDotRe;
our $IPSectHexDotRe;
our $IPRe;
our $IPv4Re;
our $IPv6Re;
our $IPv6LikeRe;
# IP address representations
my $sep;
my $v6Re = '[0-9A-Fa-f]{1,4}';
$IPSectRe = '(?:25[0-5]|2[0-4]\d|1\d\d|0?\d?\d)';
$IPSectHexRe = '(?:(?:0x)?(?:[A-Fa-f][A-Fa-f0-9]?|[A-Fa-f0-9]?[A-Fa-f]))';

$IPprivate  = '^(?:0{1,3}\.0{1,3}\.0{1,3}\.0{1,3}|127(?:\.'.$IPSectRe.'){3}|169\.254(?:\.'.$IPSectRe.'){2}|0?10(?:\.'.$IPSectRe.'){3}|192\.168(?:\.'.$IPSectRe.'){2}|172\.0?1[6-9](?:\.'.$IPSectRe.'){2}|172\.0?2[0-9](?:\.'.$IPSectRe.'){2}|172\.0?3[01](?:\.'.$IPSectRe.'){2})$';   #RFC 1918 decimal
$IPprivate .= '|^(?:(?:0x)?0{1,2}\.(?:0x)?0{1,2}\.(?:0x)?0{1,2}\.(?:0x)?0{1,2}|(?:0x)?7[Ff](?:\.'.$IPSectHexRe.'){3}|(?:0x)?[aA]9\.(?:0x)?[Ff][Ee](?:\.'.$IPSectHexRe.'){2}|(?:0x)?0[aA](?:\.'.$IPSectHexRe.'){3}|(?:0x)?[Cc]0\.(?:0x)?[Aa]8(?:\.'.$IPSectHexRe.'){2}|(?:0x)[Aa][Cc]\.(?:0x)1[0-9a-fA-F](?:\.'.$IPSectHexRe.'){2})$';   #RFC 1918 Hex
$IPprivate .= '|^(?:0{0,4}:){2,6}'.$IPprivate.'$';  # privat IPv4 in IPv6
$IPprivate .= '|^(?:0{0,4}:){2,7}[1:]?$';  # IPv6 loopback and universal

$IPQuadSectRE='(?:0([0-7]+)|0x([0-9a-fA-F]+)|(\d+))';
$IPQuadSectDotRE='(?:'.$IPQuadSectRE.'\.)';
$IPQuadRE=qr/$IPQuadSectDotRE?$IPQuadSectDotRE?$IPQuadSectDotRE?$IPQuadSectRE/o;

$IPSectDotRe = '(?:'.$IPSectRe.'\.)';
$IPSectHexDotRe = '(?:'.$IPSectHexRe.'\.)';
$IPv4Re = qr/(?:
(?:$IPSectDotRe){3}$IPSectRe
|
(?:$IPSectHexDotRe){3}$IPSectHexRe
)/xo;

# private IPv6 addresses
$IPprivate .= <<EOT;
|^(?i:FE[89A-F][0-9A-F]):
(?:
(?:(?:$v6Re:){6}(?:                                $v6Re      |:))|
(?:(?:$v6Re:){5}(?:                   $IPv4Re |   :$v6Re      |:))|
(?:(?:$v6Re:){4}(?:                  :$IPv4Re |(?::$v6Re){1,2}|:))|
(?:(?:$v6Re:){3}(?:(?:(?::$v6Re)?    :$IPv4Re)|(?::$v6Re){1,3}|:))|
(?:(?:$v6Re:){2}(?:(?:(?::$v6Re){0,2}:$IPv4Re)|(?::$v6Re){1,4}|:))|
(?:(?:$v6Re:)   (?:(?:(?::$v6Re){0,3}:$IPv4Re)|(?::$v6Re){1,5}|:))|
                (?:(?:(?::$v6Re){0,4}:$IPv4Re)|(?::$v6Re){1,6}|:)
)\$
EOT
$IPprivate = qr/$IPprivate/xo;

# RFC4291, section 2.2, "Text Representation of addresses"
$sep = '[:-]';
$IPv6Re = $IPv6LikeRe = <<EOT;
(?:
(?:(?:$v6Re$sep){7}(?:                                         $v6Re      |$sep))|
(?:(?:$v6Re$sep){6}(?:                         $IPv4Re |   $sep$v6Re      |$sep))|
(?:(?:$v6Re$sep){5}(?:                     $sep$IPv4Re |(?:$sep$v6Re){1,2}|$sep))|
(?:(?:$v6Re$sep){4}(?:(?:(?:$sep$v6Re)?    $sep$IPv4Re)|(?:$sep$v6Re){1,3}|$sep))|
(?:(?:$v6Re$sep){3}(?:(?:(?:$sep$v6Re){0,2}$sep$IPv4Re)|(?:$sep$v6Re){1,4}|$sep))|
(?:(?:$v6Re$sep){2}(?:(?:(?:$sep$v6Re){0,3}$sep$IPv4Re)|(?:$sep$v6Re){1,5}|$sep))|
(?:(?:$v6Re$sep)   (?:(?:(?:$sep$v6Re){0,4}$sep$IPv4Re)|(?:$sep$v6Re){1,6}|$sep))|
(?:        $sep    (?:(?:(?:$sep$v6Re){0,5}$sep$IPv4Re)|(?:$sep$v6Re){1,7}|$sep))
)
EOT

$IPv6Re =~ s/\Q$sep\E/:/go;
$IPv6Re = qr/$IPv6Re/xo;
$IPv6LikeRe = qr/$IPv6LikeRe/xo;

$IPRe = qr/(?:
$IPv4Re
|
$IPv6Re
)/xo;
our $EmailDomainRe;
$EmailDomainRe=qr/(?:\w[\w\.\-]*\.\w\w+|\[[\d\.]*\.\d+\])/o;

our %m;
our %GpOK;
if ($CanUseLWP) {
#        my $ver        = eval('LWP::Simple->VERSION');
#        print "LWP::Simple $ver installed - download griplist available\n" ;
    } elsif ( !$AvailLWP ) {
        print "LWP::Simple module not installed - download griplist not available\n";
    }

#no output buffering to screen
*STDOUT->autoflush();

#holy predeclarations Batman!
use vars qw(
    $autoCorrectCorpus $base $DropList $correctednotspam $correctednotspamcount $correctedspam
    $correctedspamcount $discarded $DoDropList $DoNotCollectRed $EmailAdrRe $EmailDomainRe $EmailFrom $EmailAdminReportsTo $griplist $HamWordCount
    $KeepWhitelistedSpam $lowernorm $logfile $Log $LogDateFormat $maillogExt $MaxBytes $MaxCorrectedDays $MaxBayesFileAge $MaxNoBayesFileAge  $MaintBayesCollection $MaxFiles $MaxKeepDeleted $MaxWhitelistDays
    $MaxWhitelistLength  $maintbayescollection $minimumfiles $minimumdays $mydb $myhost $mypassword $myuser $myName $notspamlog $processTime
    $notspamlogcount $npRe $incomingOkMail $OrderedTieHashSize $pbdbfile $proxyserver $proxyuser $proxypass $noGriplist        
    $RebuildLog $rebuildrun $redlistdb $redRe $redReRE $resendmail $setFilePermOnStart $silent $spamdb $spamdberror $spamdbFile $RegExLength
    $spam $spamdbFname $spamdberrorFname $spamlog $spamlogcount $SpamWordCount $starttime
    $usesubject $whitelistdb $WhitelistObject $RebuildNotify $RedlistObject $viruslog $whiteRe $whiteReRE $wildcardUser
    %HamHash %Helo %Redlist %spam %SpamHash %Whitelist $asspLog $DoNotCollectRedList $DoNotCollectRedRe
    $DoFullGripDownload $UseLocalTime $TrashObject %Trashlist
    $runAsUser $runAsGroup
);
# load from command line if specified
if($ARGV[0]) { 
 $base=$ARGV[0]; 
} else { 
 # the last one is the one used if all else fails 
 $base = cwd(); 
 unless (-e "$base/assp.cfg") { 
   foreach ('.','/usr/local/assp','/home/assp','/etc/assp','/usr/assp','/applications/assp','/assp','.') {
    if (-e "$_/assp.cfg") {
      $base=$_; 
      last ; 
    } 
   } 
 } 
 $base = cwd() if $base eq '.'; 
}
unless (chdir $base) {
print
"Usage:
  perl rebuildspamdb.pl  c:\\assp  -- runs the programm in basedirectory c:\\assp\n
";
 die "Abort: unable to change to basedirectory $base";
}
$silent = 1 if (lc $ARGV[1] =~ /silent/i ||  lc $ARGV[0] =~ /silent/i);

#load configuration options from assp.cfg file
&loadconfig();

fork() && exit;

# open log file
if ( -e "$rebuildrun.bak" ) {
    unlink("$rebuildrun.bak") or die "unable to remove file: $!";
}
if ( -e $rebuildrun ) {
    copy( $rebuildrun, "$rebuildrun.bak" ) or die "unable to copy file for: $!";
}
open( $RebuildLog, '>', "$rebuildrun" ) or die "unable to open file for logging: $!";

our $silentlog;
$starttime = time;
&rb_printlog("\n");
for ( my $c = 10; $c >= 1; $c-- ) { &rb_printlog(q{*}); }
my $savesilent=$silent;
$silent=0;
rb_printlog (&timestring(time) . " RebuildSpamDB $VERSION $modversion is starting;\n") ;
$silent=$savesilent;

&rb_printlog( "\nRunning in $myName basedirectory '$base'\n");
#-- check if running as root 
&rb_printlog( "Running as root!!\n") if $< == 0 && $^O ne "MSWin32";

#-- print username
&rb_printlog( "Running as user '" . (getpwuid($<))[0] . "'\n") if $< != 0 && $^O ne "MSWin32";

&rb_printlog("\n--- ASSP $myName Settings ---\n");

my $AvailTieRDBM  = eval "use Tie::RDBM; 1";    # Is the required module installed?
my $CanUseTieRDBM = $AvailTieRDBM;              # this looks wierd but it's the only way it works
undef $AvailTieRDBM;
$EmailAdrRe    = "[^()<>@,;:\\\"\\[\\]\000-\040]+";
$EmailDomainRe = '(?:\w[\w\.\-]*\.\w+|\[[\d\.]*\.\d+\])';

# set counts
$HamWordCount          = $SpamWordCount = $correctedspamcount = 0;
$correctednotspamcount = $spamlogcount  = $notspamlogcount    = 0;

# read old norm
our $Normfile;
our ($oldnorm, $oldcorrectedspamcount, $oldcorrectednotspamcount, $oldspamlogcount, $oldnotspamlogcount);
open( $Normfile, '<', "$base/normfile" ) || warn "unable to open $base/normfile: $!\n";
if ($Normfile) {
    while (<$Normfile>) {
        ($oldnorm, $oldcorrectedspamcount, $oldcorrectednotspamcount, $oldspamlogcount, $oldnotspamlogcount) = split(" ",$_);
    }
    close $Normfile;
}

if ($DoNotCollectRedList) {
    &rb_printlog(
        "Do Not Collect Messages with redlisted address: Enabled\n**Messages with redlisted addresses will be removed from the corpus!**\n\n"
    );
}

&rb_printlog("Maxbytes: $MaxBytes \n");
&rb_printlog("Maxfiles: $MaxFiles \n");

#rebuild various cache files and lists
&repair();

# Let's rb_clean the old deleted entries

 &rb_cleanTrashlist();

# Let's rb_clean the non bayesian folder of old entries 
# Let's rb_clean the bayesian folder of old entries  
# Let's rb_clean the corrected spam/notspam folder of old entries

 &rb_cleanUpCollection();

# name, contents, refrence to "compiled" object
#&compileregex( "whiteRe", $whiteRe, \$whiteReRE );
&compileregex( "redRe",   $redRe,   \$redReRE );

# redlist,whitelist
&createlistobjects();

# isspam?, path, filter, weight, processing sub
$correctedspamcount    = &processfolder( 1, $correctedspam,    "*",      2, \&dospamhash );
$correctednotspamcount = &processfolder( 0, $correctednotspam, "*",      4, \&dohamhash );
$spamlogcount          = &processfolder( 1, $spamlog,          "*", 1, \&checkspam );
$notspamlogcount       = &processfolder( 0, $notspamlog,       "*", 1, \&checkham );
our $norm = $HamWordCount ? ( $SpamWordCount / $HamWordCount ) : 1;
$norm = sprintf("%.4f",$norm);
open( my $normFile, '>', "$base/normfile" ) || warn "unable to open $base/normfile: $!\n";
if ($normFile) {
    print { $normFile } "$norm $correctedspamcount $correctednotspamcount $spamlogcount $notspamlogcount";
    close $normFile;
}

# Create Bayesian DB
&generatescores();
our %HeloBlack;
our $HeloBlackObject = tie %HeloBlack, 'orderedtie', "$base/$spamdb.helo";
# Create HELo blacklist
&createheloblacklist();
&rb_printlog(
    "\nSpam Weight:\t   " . commify($SpamWordCount) . "\nNot-Spam Weight:   " . commify($HamWordCount) . "\n\n" );
if ( !($norm) ) {    #invalid norm
    &rb_printlog("Warning: Corpus insufficent to calculate normality!\n");
}
else {               #norm exists, print it

        my $normdesc = '';

        &rb_printlog( "Wanted Corpus norm:\t%.4f %s $normdesc \n", $autoCorrectCorpus  );
        &rb_printlog( "Reached Corpus norm:\t%.4f %s $normdesc \n", $norm  );

}

$lowernorm = 0.5 if $lowernorm && ($lowernorm > 1 or $lowernorm < 0.5);

if   ( time - $starttime != 0 ) { $processTime = time - $starttime; }
else                            { $processTime = 1; }
&rb_printlog( "\nTotal processing time: %d second(s)\n\n", $processTime );

&downloadGripConf();
&uploadgriplist() if ! $noGriplistUpload && !$noGriplist;
&downloadgriplist() if ! $noGriplistDownload && !$noGriplist;

&downloaddroplist();

$savesilent=$silent;
$silent=0;
&rb_printlog( "\n");
&rb_printlog( &timestring(time) . " RebuildSpamDB $VERSION $modversion ended;\n");
$silent=$savesilent;
&rb_printlog( "Sending Notify to $RebuildNotify\n") if $RebuildNotify;
&rb_printlog( "Sending Notify not possible, address in RebuildNotify missing\n") if !$RebuildNotify;
close $RebuildLog;
if ($RebuildNotify) {
        &sendNotification(
          $EmailFrom,
          $RebuildNotify,
          'RebuildSpamDB - report',
          "File rebuildrun.txt follows:\r\n\r\n",
          "$base/rebuildrun.txt");
    }

################################################################################
#              end rebuildspamdb mainloop
################################################################################

sub createlistobjects {

    if ( $CanUseTieRDBM && $whitelistdb =~ /mysql/ && !$KeepWhitelistedSpam ) {
        eval {
            $WhitelistObject = tie %Whitelist, 'Tie::RDBM', "dbi:mysql:database=$mydb;host=$myhost",
                { user => "$myuser", password => "$mypassword", table => 'whitelist', create => 0 };
        };
        if ($EVAL_ERROR) {
            &rb_printlog("whitelist mysql error: $@");
            $CanUseTieRDBM = 0;
            $whitelistdb   = "whitelist";
        }
    }
    elsif ( !$KeepWhitelistedSpam ) {
        if ( -e $whitelistdb ) { $WhitelistObject = tie( %Whitelist, 'orderedtie', "$whitelistdb" ); }
    }
    if ( $CanUseTieRDBM && $redlistdb =~ /mysql/ && ( $DoNotCollectRed || $DoNotCollectRedList ) ) {
        eval {
            $RedlistObject = tie %Redlist, 'Tie::RDBM', "dbi:mysql:database=$mydb;host=$myhost",
                { user => "$myuser", password => "$mypassword", table => 'redlist', create => 0 };
        };
        if ($EVAL_ERROR) {
            &rb_printlog("redlist mysql error: $@");
            $CanUseTieRDBM = 0;
            $redlistdb     = "redlist";
        }
    }
    elsif ($DoNotCollectRed) {
        if ( -e $redlistdb ) { $RedlistObject = tie( %Redlist, 'orderedtie', "$redlistdb" ); }
    }
    return;
} ## end sub createlistobjects

sub generatescores {
    my ( $t, $s, @result, $pair, $v );
    &rb_printlog("\nGenerating weighted Bayesian tuplets...");
    open( $spamdbFile, '>', "$spamdb.tmp" ) || die "unable to open $spamdb.tmp: $!\n";
    binmode $spamdbFile;
    print { $spamdbFile } "\n";
    while ( ( $pair, $v ) = each(%spam) ) {
        ( $s, $t ) = split( q{ }, $v );
        $t = ( $t - $s ) * $norm + $s;    # normalize t
        if ( $t < 5 ) {
            next;
        }

        # if token represents all spam or all ham then square its value
        if ( $s == $t || $s == 0 ) {
            $s = $s * $s;
            $t = $t * $t;
        }
        $v = ( 1 + $s ) / ( $t + 2 );
        $v = sprintf( "%.7f", $v );
        $v = '0.9999999' if $v >= 1;
        $v = '0.0000001' if $v <= 0;
        push( @result, "$pair\002$v\n" ) if abs( $v - .5 ) > .09;
    }
    &rb_printlog("done\n");
    undef %spam;    # free some memory
    &rb_printlog("\nSaving rebuilt SPAM database...");
    for ( sort @result ) { print { $spamdbFile } $_; }
    close $spamdbFile;
    if ( -e "$spamdb.bak" ) { unlink("$spamdb.bak") || &rb_printlog("unable to remove '$spamdb.bak' $!\n") }
    if ( -e $spamdb ) {
        rename( $spamdb, "$spamdb.bak" ) || &rb_printlog("unable to rename '$spamdb' to '$spamdb.bak' $!\n");
    }
    rename( "$spamdb.tmp", $spamdb ) || &rb_printlog("unable to rename '$spamdb.tmp' to '$spamdb' $!\n");
    &rb_printlog("done\n");
    my $filesize = -s "$spamdb";
    &rb_printlog( "\nResulting file '$spamdbFname' is " . commify($filesize) . " bytes\n" );
    my $pairs = scalar @result;
    &rb_printlog( "Bayesian Pairs: " . commify($pairs) . "\n" );
    return;
} ## end sub generatescores

sub createheloblacklist {
    my (@Helo);
    open( my $FheloBlack, '>', "$spamdb.helo.tmp" ) || &rb_printlog("unable to open '$spamdb.helo.tmp' $!\n");
    binmode $FheloBlack;
    print { $FheloBlack } "\n";
    while ( my ( $helostr, $weights ) = each(%Helo) ) {

        if ( $weights->[1] / ( $weights->[0] + $weights->[1] + .1 ) > .98 ) {     push( @Helo, "$helostr\0021\n" ); } 
        elsif     ( $weights->[1] / ( $weights->[0] + $weights->[1] + .1 ) < .2 ) { push( @Helo, "$helostr\0020.01\n" ); } 

    }
    print { $FheloBlack } sort @Helo;
    eval{close $FheloBlack;};
    &rb_printlog( "\nHELO Blacklist: " . scalar(@Helo) . " HELOs\n" );
    if ( -e "$spamdb.helo.bak" ) {
        unlink("$spamdb.helo.bak") || &rb_printlog("unable to remove '$spamdb.helo.bak' $!\n");
    }
    if ( -e "$spamdb.helo" ) {
        rename( "$spamdb.helo", "$spamdb.helo.bak" )
            || &rb_printlog("unable to rename '$spamdb.helo' to '$spamdb.helo.bak' $!\n");
    }
    rename( "$spamdb.helo.tmp", "$spamdb.helo" )
        || &rb_printlog("unable to rename '$spamdb.helo.tmp' to '$spamdb.helo' $!\n");
    return;
}

sub loadconfig {

    my $RCF;
    open($RCF,"<$base/assp.cfg");
    while (<$RCF>) {
        s/\r|\n//go;
        my ($k,$v) = split(/:=/o,$_,2);
        next unless $k;
        $Config{$k} = $v;
    }
    close $RCF;
    $TrashObject           = tie %Trashlist,   'orderedtie', "$base/trashlist.db";

    $DoDropList            = $Config{ DoDropList };
    $runAsUser             = $Config{ runAsUser };
    $runAsGroup            = $Config{ runAsGroup };
    $correctednotspam      = $Config{ correctednotspam } && "$Config{base}/$Config{correctednotspam}" || 'errors/notspam';
    $correctedspam         = $Config{ correctedspam } && "$Config{base}/$Config{correctedspam}" || 'errors/spam';
    $incomingOkMail        = $Config{ incomingOkMail } && "$Config{base}/$Config{incomingOkMail}" || 'okmail';
    $DoNotCollectRed       = $Config{ DoNotCollectRed };
    $DoNotCollectRedRe     = $Config{ DoNotCollectRedRe };
    $DoNotCollectRedList   = $Config{ DoNotCollectRedList };
    $KeepWhitelistedSpam   = $Config{ KeepWhitelistedSpam };
    $logfile               = $Config{ logfile } && "$Config{base}/$Config{logfile}" || 'maillog.txt';
    $Log                   = $Config{ logfile } && "$Config{base}/$Config{logfile}" || 'maillog.txt';
    $maillogExt            = $Config{ maillogExt };
    $MaxBytes              = $Config{ MaxBytes } || 10000;
    $MaxFiles              = $Config{ MaxFiles } || 14000;

    $MaxWhitelistDays      = $Config{ MaxWhitelistDays } || 90;
    $MaxCorrectedDays      = $Config{ MaxCorrectedDays } || 1000;
    $MaxNoBayesFileAge     = $Config{ MaxNoBayesFileAge } || 30; 
    $MaxBayesFileAge       = $Config{ MaxBayesFileAge };
    $MaintBayesCollection  = $Config{ MaintBayesCollection } || 1;
    $maintbayescollection  = $MaintBayesCollection;
    $MaxWhitelistLength    = $Config{ MaxWhitelistLength } || 60;
    $MaxKeepDeleted        = $Config{ MaxKeepDeleted } || 0;
    $notspamlog            = $Config{ notspamlog } && "$Config{base}/$Config{notspamlog}" || 'notspam';
    $npRe                  = $Config{ npRe };
    $OrderedTieHashSize    = $Config{ OrderedTieHashSize } || 10_000;
    $pbdbfile              = $Config{ pbdb };

    $proxyserver           = $Config{ proxyserver };
    $proxyuser             = $Config{ proxyuser };    
    $proxypass             = $Config{ proxypass };
    $resendmail            = $Config{ resendmail };
    $redlistdb             = $Config{ redlistdb } && "$Config{base}/$Config{redlistdb}" || 'redlist';
    $redRe                 = $Config{ redRe };
    $myName                = $Config{ myName };
    $setFilePermOnStart    = $Config{ setFilePermOnStart };
    $spamdb                = $Config{ spamdb } && "$Config{base}/$Config{spamdb}" || 'spamdb';
    $spamdbFname           = $Config{ spamdb } || 'spamdb';
    $spamdberrorFname      = $spamdbFname.'error';
    $spamdberror           = $spamdb.'error';
    $spamlog               = $Config{ spamlog } && "$Config{base}/$Config{spamlog}" || 'spam';
    $discarded             = $Config{ discarded } && "$Config{base}/$Config{discarded}" || 'discarded';
    $viruslog              = $Config{ viruslog } && "$Config{base}/$Config{viruslog}" || 'viruslog';
    $usesubject            = $Config{ UseSubjectsAsMaillogNames };
    $whitelistdb           = $Config{ whitelistdb } && "$Config{base}/$Config{whitelistdb}" || 'whitelist';
    $griplist              = $Config{ griplist };
    $DropList              = $Config{ DropList } || 'file:files/droplist.txt';
    ($DropList)            = $DropList =~ /^ *file: *(.+)/i if $DropList =~ /^ *file:/;
    $noGriplist            = $Config{ noGriplist };
    $noGriplistUpload      = $Config{ noGriplistUpload };
    $noGriplistDownload    = $Config{ noGriplistDownload };
    $asspLog               = $Config{ asspLog };
    $whiteRe               = $Config{ whiteRe };
    $wildcardUser          = $Config{ wildcardUser };
    $mydb                  = $Config{ mydb };
    $myhost                = $Config{ myhost };
    $myuser                = $Config{ myuser };
    $mypassword            = $Config{ mypassword };
    $rebuildrun            = &fixPath($base) . "/rebuildrun.txt";
    $EmailAdminReportsTo   = $Config{ EmailAdminReportsTo };
    $RebuildNotify         = $Config{ RebuildNotify };

    $autoCorrectCorpus     = $Config{ autoCorrectCorpus } || "1";

    $minimumfiles = 10000 if !$minimumfiles;
    $minimumfiles = 5000 if  $minimumfiles < 5000;
    $minimumdays  = 14 if !$minimumdays;
    $minimumdays  = 7 if  $minimumdays < 7;

    $EmailFrom             = $Config{ EmailFrom };
    $RegExLength           = $Config{ RegExLength };
    $UseLocalTime          = $Config{ UseLocalTime };
    $LogDateFormat         = $Config{ LogDateFormat } || "MMM-DD-YY hh:mm:ss";

    $DoFullGripDownload    = $Config{ DoFullGripDownload };

    return;
} ## end sub loadconfig

sub processfolder {
    my ( $fldrType, $fldrpath, $filter, $weight, $sub ) = @_;
    my ( $count, $processFolderTime, $folderStartTime, $fileCount, @files );
    our ( $WhiteCount, $RedCount );
    $folderStartTime = time;
    $fldrpath        = &fixPath($fldrpath);
    &rb_printlog( "\n" . $fldrpath . "\n" );
    $fldrpath .= "/*";
    $fileCount = &countfiles($fldrpath);
    &rb_printlog( "File Count:\t" . commify($fileCount) );
    &rb_printlog("\nProcessing...");
    $count = $WhiteCount = 0;
    @files = glob($fldrpath);
    my $importmaxfiles = $MaxFiles;

    my $percent;
    my $filenum;

    foreach my $file (@files) {

        &add( $fldrType, $file, $weight, $sub );
        last if  ($spamlogcount && $autoCorrectCorpus && !$fldrType && $SpamWordCount && ($HamWordCount * $autoCorrectCorpus) > $SpamWordCount);
        $count++;
        last if $count >= $importmaxfiles;    #too many files
    }
    if   ( time - $folderStartTime != 0 ) { $processFolderTime = time - $folderStartTime; }
    else                                  { $processFolderTime = 1; }
    $count = $count - $WhiteCount ;

    if ($WhiteCount) {
        &rb_printlog( "\nRemoved White:\t" . commify($WhiteCount) );
    }

    &rb_printlog( "\nImported Files:\t" . commify($count) );
    &rb_printlog( "\nImported SpamWordCount(total):\t" . commify($SpamWordCount) );
    &rb_printlog( "\nImported HamWordCount(total):\t" . commify($HamWordCount) );

    if ( $count  > $MaxFiles ) {
        $maintbayescollection = 1;
    }

    #&rb_printlog( "\n " . commify($SpamWordCount) . " spam weight \n " . commify($HamWordCount) . " non-spam weight." );
    &rb_printlog("\nFinished in $processFolderTime second(s)\n");

    return $count;
} ## end sub processfolder

sub countfiles {
    my ($fldrpath) = @_;
    my @fileCount = glob("$fldrpath");
    return scalar(@fileCount);
}

sub commify {
    local $_ = shift;
    1 while s/^([-+]?\d+)(\d{3})/$1,$2/;
    return $_;
}

sub hash {
    my ($msgText) = @_;

    my ( $head, $body );

    # creates a md5 hash of $msg body
    if ( $msgText =~ /^(.*?)\n\r?\n(.*)/s ) {

        $head = $1;
        $body = $2;

        return md5_hex($body);
    }
    else {

        #There is no split, the message has no valid body
        return md5_hex($msgText);
    }

    return;
}

sub dospamhash {
    my ( $FileName, $msgText ) = @_;
    $SpamHash{ &hash($msgText) } = '1';
    return;
}

sub dohamhash {
    my ( $FileName, $msgText ) = @_;
    $HamHash{ &hash($msgText) } = q{};
    return;
}

sub checkspam {
    my ( $FileName, $msgText ) = @_;
    our $HamHash;
    $msgText = &hash($msgText);
    my ( $return, $reason );
    if ( defined( $HamHash{ $msgText } ) ) {

        # we've found a message in the spam database that is the same as one in the corrected Ham group
        my $fn = shift;
        &deletefile( $fn, "found in $correctednotspam" );
        return 1;
    } elsif ( $reason = &redlisted( $_[1] ) ) {
        my $fn = shift;
        &deletefile( $fn, $reason );
        return 1;

    } elsif ( $reason = &whitelisted( $_[1] ) ) {
        my $fn = shift;
        &deletefile( $fn, $reason );
        return 1;
    }
    return 0;
}

sub checkham {
    my ( $FileName, $msgText ) = @_;
    our $SpamHash;
    my ( $return, $reason );
    $msgText = &hash($msgText);
    if ( defined( $SpamHash{ $msgText } ) ) {

        # we've found a message in the ham database that is the same as one in the corrected spam group
        my $fn = shift;
        &deletefile( $fn, "found in $correctedspam" );
        return 1;

    }
    return 0;
}

sub getrecontent {
    my ( $value, $name ) = @_;
    my $fromfile = 0;
    if ( $value =~ /^ *file: *(.+)/i ) {

        # the option list is actually saved in a file.
        $fromfile = 1;
        my $fil = $1;
        $fil = "$base/$fil" if $fil !~ /^\Q$base\E/i;
        local $/;
        if ( open( my $File, '<', $fil ) ) {
            $value = <$File>;

            # rb_clean off comments
            $value =~ s/#.*//g;

            # replace newlines (and the whitespace that surrounds them) with a |(pipe character)
            $value =~ s/\s*\n\s*/|/g;
            close $File;
        }
        else { $value = q{}; }
    }
    $value =~ s/\|\|/\|/g;
    $value =~ s/\s*\|/\|/g;
    $value =~ s/\|\s*/\|/g;
    $value =~ s/\|\|+/\|/g;
    $value =~ s/^\s*\|?//;
    $value =~ s/\|?\s*$//;
    $value =~ s/\|$//;
    return $value;
} ## end sub getrecontent

sub batv_remove_tag {
    my $mailfrom = shift;
    if ($mailfrom =~ /^(\w{4}=+[\da-zA-Z]+=+)([^\r\n]*)/o) {
        $mailfrom = $2;
    }
    return $mailfrom;
}

sub whitelisted {
    return 0 if $KeepWhitelistedSpam;
    my $m = shift;
    my $curaddr;
    my %seen;

    $m =~ s/\n\r?\n.*//s;    # remove body
    while ( $m =~     /(?:from|sender|reply-to|errors-to|envelope-from|list-\w+):($HeaderValueRe)/igo) {
        my $s = $1;

        if ($s !~ /($EmailAdrRe\@$EmailDomainRe)/io) {
            next;
        } else {
            $curaddr = batv_remove_tag($1);

           }

        if ( exists $seen{ $curaddr } ) {

            next;                #we already checked this address
        } else { 
            $seen{ $curaddr } = 1; 
        }

        if ( $Whitelist{ $curaddr } ) {
            my $reason = $curaddr;
            $reason =~ s/\s+$/ /g;
            $reason =~ s/[\r\n]/ /g;
            our $WhiteCount++;
            return ( " -- '$reason' is in Whitelist");
        }
        if ($wildcardUser) {
            my ( $mfdd, $alldd, $reason );
            $mfdd = $1 if $curaddr =~ /(\@.*)/;
            $alldd = "$wildcardUser$mfdd";
            if ( $Whitelist{ lc $alldd } ) {
                $reason = $curaddr;
                $reason =~ s/\s+$/ /g;
                $reason =~ s/[\r\n]/ /g;
                our $WhiteCount++;
                return ( " -- '$reason' is in Whitelist ($wildcardUser)");
            }
        }
    } ## end while ( $m =~ /($EmailAdrRe\@$EmailDomainRe)/igo)
    return 0;
} ## end sub whitelisted

sub redlisted {
    my $m = shift;
    my (%seen);

    if ( $DoNotCollectRedList) {    
        $m =~ s/\n\r?\n.*//s;                            # remove body
        while ( $m =~ /($EmailAdrRe\@$EmailDomainRe)/igo ) {
            my $curaddr = lc($1);
            if ( exists $seen{ $curaddr } ) {
                next;                                    #we already checked this address
            }
            else { $seen{ $curaddr } = 1; }
            if ( $Redlist{ $curaddr } ) {
                my $reason = $curaddr;
                $reason =~ s/\s+$/ /g;
                $reason =~ s/[\r\n]/ /g;
                our $RedCount++;
                return ( " -- '$reason' is in Redlist");
            }
        }
    }
    return 0;
} ## end sub redlisted

sub deletefile {
    my ( $fn, $reason, $nolog, $notrashlist ) = @_;

    if ( -e $fn ) {
            if ( -w $fn || -W $fn ) {
                &rb_printlog( "\nremoving " . $fn . q{ } . $reason );
                if ($MaxKeepDeleted  && !$notrashlist ) {
                    $Trashlist{$fn}=time;
                } else {

                    unlink($fn);

                }
            } else { 
                rb_printlog( "\ncannot delete " . $reason . " message " . $fn . ": file is not writable: $!" ) ; 
            }
    } else { 
            rb_printlog( "\ncannot delete " . $reason . " message " . $fn . ": $!" ) if !$nolog; 
    }

}

sub getfile {
    my ( $fn, $sub ) = @_;
    my $message;
    my $count;
    my $numreadchars;
    return if exists $Trashlist{$fn};
    open( my $file, '<', "$fn" ) || return;
    # Maxbytes or 10000, whichever is less
    $numreadchars = $MaxBytes <= 10_000 ? $MaxBytes : 10_000;
    $count = read( $file, $message, $numreadchars );    # read characters into memory
    close $file;
    return if $sub->( $fn, $message );                  # have i read this before?

    return $message;
}

sub add {
    my ( $isspam, $fn, $factor, $sub ) = @_;

    return if -d $fn;
    my ( $curHelo, $CurWord, $PrevWord, $sfac, $tfac );
    $PrevWord = $CurWord = q{};
    my $content = &getfile( $fn, $sub );
    return unless $content;
    if ( $content =~ /helo=(.*?)\)/i ) {
        $curHelo = lc($1);
        if ( $Helo{ $curHelo } ) { $Helo{ $curHelo }->[$isspam] += $factor; }
        else {    #it doesn't seem to exist. create it.
            $Helo{ $curHelo }->[$isspam] = $factor;
        }
    }
    my $OK;
    ($content,$OK) = &rb_clean($content);
    while ( $content =~ /([-\$A-Za-z0-9\'\.!\240-\377]{2,})/g ) {
        if  ($spamlogcount && $autoCorrectCorpus && !$isspam && $SpamWordCount && ($HamWordCount * $autoCorrectCorpus) > $SpamWordCount) {

        return; 
        }
        if ( length($1) > 20 || length($1) < 2 ) { next }
        $PrevWord = $CurWord;

        $CurWord  = BayesWordrb_clean($1);

        if ( !$PrevWord ) { next }  # We only want word pairs
        if ( length($CurWord) < 2 || length($PrevWord) < 2 ) { next }    # too short after rb_cleaning

        # increment global weights, they are not really word counts
        if   ($isspam) { $SpamWordCount += $factor; }
        else           { $HamWordCount  += $factor; }
        if ( exists( $spam{ "$PrevWord $CurWord" } ) ) {
            ( $sfac, $tfac ) = split( q{ }, $spam{ "$PrevWord $CurWord" } );
        }
        else {

            # the pair does not exist, create it
            $spam{ "$PrevWord $CurWord" } = "0 0";
            ( $sfac, $tfac ) = split( q{ }, $spam{ "$PrevWord $CurWord" } );
        }
        $sfac += $isspam ? $factor : 0;
        $tfac += $factor;
        $spam{ "$PrevWord $CurWord" } = "$sfac $tfac";
    } ## end while ( $content =~ /([-\$A-Za-z0-9\'\.!\240-\377]{2,})/g)
    return;
} ## end sub add

sub BayesWordrb_clean {
    my $word = lc(shift);
    $word =~ s/#(?:[a-f0-9]{2})+/randcolor/go;
    $word =~ s/^#\d+/randdecnum/go;

    $word =~ s/[_\[\]\~\@\%\$\&\{\}<>#(),.'";:=!?*+\/\\\-]+$//o;
    $word =~s/^[_\[\]\~\@\%\$\&\{\}<>#(),.'";:=!?*+\/\\\-]+//o;
    $word =~ s/!!!+/!!/go;
    $word =~ s/\*\*+/**/go;
    $word =~ s/--+/-/go;
    $word =~ s/__+/_/go;
    $word =~ s/[\d,.]{2,}/randnumber/go;
    $word =~ s/^[\d:\.\-+();:<>,!"'\/%]+(?:[ap]m)?$/randwildnum/o;    # ignore numbers , dates, times, versions ...

    return if length($word) > 20 or length($word) < 2;
    return $word;
}
# rb_clean up source email
sub rb_clean {
    my $m = shift;

    my $msg = ref($m) ? $$m : $m;
    my $t = time + 15;     # max 15 seconds for this rb_cleaning
    my $body;
    my $header;
    my $undec = 1;

    local $_= "\n". (($header) ? $header : $msg);
    my ($helo,$rcpt);
    if ($header) {
        ($helo)=/helo=([^)]+)\)/io;
        $helo=~s/(\w+)/ hlo $1 /go if length($helo) > 19; # if the helo string is long, break it up
        $rcpt="rcpt ".join(" rcpt ",/($EmailAdrRe\@$EmailDomainRe)/go);
        return "helo: $helo\n$rcpt\n",0 if (time > $t);
        # mark the subject
        $rcpt .= "\n".fixsub($1) if /\nsubject: (.*)/io;
        return "helo: $helo\n$rcpt\n",0 if (time > $t);
    }

    # from now only do the body if possible
    local $_ = $body if $body;

    # replace &#ddd encoding
    s/\&\#(\d{1,5})\;?/chr($1)/geo;
    s/\&\#x(\d{1,4})\;?/chr(hex($1))/geo;
    s/([^\\])?[%=]([0-9A-F]{2})/$1.chr(hex($2))/gieo; # replace url encoding
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    if ($undec) {
      # replace base64 encoding
      s/\n([a-zA-Z0-9+\/=]{40,}\r?\n[a-zA-Z0-9+\/=\r\n]+)/base64decode($1)/gseo;
      return "helo: $helo\n$rcpt\n",0 if (time > $t);

      # rb_clean up quoted-printable references
      s/(Subject: .*)=\r?\n/$1\n/o;
      return "helo: $helo\n$rcpt\n",0 if (time > $t);
      s/=\r?\n//go;
      return "helo: $helo\n$rcpt\n",0 if (time > $t);
      # strip out mime continuation
      s/.*---=_NextPart_.*\n//go;
      return "helo: $helo\n$rcpt\n",0 if (time > $t);
    }

    # rb_clean up MIME quoted-printable line breakings
    s/=\r?\n//gos;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    # rb_clean up &nbsp; and &amp;
    s/&nbsp;?/ /gio; s/&amp;?/and/gio;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);
    s/(\d),(\d)/$1$2/go;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);
    s/\r//go; s/ *\n/\n/go;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);
    s/\n\n\n\n\n+/\nblines blines\n/go;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    # rb_clean up html stuff
    s/<\s*script[^>]+>.*?<\s*\/\s*script\s*>/ jscripttag /igos;
    s/<\s*script[^>]+>/ jscripttag /igos;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);
    # remove style sheets
    s/<\s*style[^>]*>.*?<\s*\/\s*style\s*>//igso;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

#    while (s/(\w+)(<[^>]*>)((?:<[^>]*>)*\w+)/$2$1$3/go){return "helo: $helo\n$rcpt\n",0 if (time > $t);} # move html out of words
    s/<\s*(?:[biu]|strong)\s*>/ boldifytext /gio;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    # remove some tags that are not informative
    s/<\s*\/?\s*(?:p|br|div|t[drh]|li|[uo]l|center)[^>]*>/\n/gios;
    s/<\s*\/?\s*(?:[biuo]|strong)\s*>//gio;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);
    s/<\s*\/?\s*(?:html|meta|head|body|span|table|font|col|map)[^>]*>//igos;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);
    s/(<\s*a\s[^>]*>)(.*?)(<\s*\/a\s*>)/$1.fixlinktext($2)/igseo;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    # treat titles like subjects
    s/<\s*title[^>]*>(.*?)<\s*\/\s*title\s*>/fixsub($1)/igeos;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    # remove html comments
    s/<\s*!.*?-->//gso; s/<\s*![^>]*>//go;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    # look for random words
    s/[ a-z0-9][ghjklmnpqrstvwxz_]{2}[bcdfghjklmnpqrstvwxz_0-9]{3}\S*/ randword /gio;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    # remove mime separators
    s/\n--.*?randword.*//go;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    # look for linked images
    s/(<\s*a[^>]*>[^<]*<\s*img)/ linkedimage $1/giso;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);
    s/<[^>]*href\s*=\s*("[^"]*"|\S*)/fixhref($1)/isgeo;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);
    s/(?:ht|f)tps?:\/\/(\S*)/fixhref($1)/isgeo;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);
    s/(\S+\@\S+\.\w{2,5})\b/fixhref($1)/geo;
    s/<?\s*img .{0,50}src\s*=\s*['"]([^'"]*)['"][^>]+>/$1/gois;
    s/["']\s*\/?s*>|target\s*=\s*['"]_blank['"]|<\s*\/|:\/\/ //go;
    s/ \d{2,} / 1234 /go;

    return ("helo: $helo\n$rcpt\n$_",1);

}

sub fixhref     { my $t = shift; $t =~ s/(\w+)/ href $1 /g; return $t; }
sub fixlinktext { my $t = shift; $t =~ s/(\w+)/atxt $1/g;   return $t; }

sub fixsub {
    my $s = shift;

    #print "$s=>";
    $s =~ s/ {3,}/ lotsaspaces /g;
    $s =~ s/(\S+)/ssub $1/g;

    #print "$s\n";
    return "\n$s ssub";
}

sub base64decode {
    my $str = shift;
    my $res = "\n\n";
    $str =~ tr|A-Za-z0-9+/||cd;
    $str =~ tr|A-Za-z0-9+/| -_|;
    while ( $str =~ /(.{1,60})/gs ) {
        my $len = chr( 32 + length($1) * 3 / 4 );
        $res .= unpack( "u", $len . $1 );
    }
    return $res;
}

sub rb_printlog {
    return if $silentlog;
    my ( $text, $format ) = @_;
    if ( !$format ) {
        print "$text" unless $silent;
        print $RebuildLog "$text";
    }
    if ($format) {
        printf "$text", $format unless $silent;
        printf $RebuildLog "$text", $format;
    }
    return;
}
sub timestring {
    my ($time,$what) = @_;
    my @m = $time ? localtime($time) : localtime();
    my $tstr = $time ? scalar(localtime($time)) : scalar(localtime());
    my ($day,$month) = $tstr =~ /(...) (...)/;
    my $format = $LogDateFormat;
    if (lc $what eq 'd') {   # date only - remove time part from format
        $format =~ s/[^YMD]*(?:hh|mm|ss)[^YMD]*//g;
    } elsif (lc $what eq 't') { # time only - remove date part from format
        $format =~ s/[^hms]*(?:Y{2,4}|M{2,3}|D{2,3})[^hms]*//g;
    }
    $format =~ s/^[^YMDhms]//;
    $format =~ s/[^YMDhms]$//;
    $format =~ s/YYYY/sprintf("%04d",$m[5]+1900)/e;
    $format =~ s/YY/sprintf("%02d",$m[5]-100)/e;
    $format =~ s/MMM/$month/;
    $format =~ s/MM/sprintf("%02d",$m[4]+1)/e;
    $format =~ s/DDD/$day/e;
    $format =~ s/DD/sprintf("%02d",$m[3])/e;
    $format =~ s/hh/sprintf("%02d",$m[2])/e;
    $format =~ s/mm/sprintf("%02d",$m[1])/e;
    $format =~ s/ss/sprintf("%02d",$m[0])/e;

    return $format;
}

sub downloadGripConf {

    my $rc;

    my $griplistUrl = "http://downloads.sourceforge.net/project/assp/griplist/griplist.conf";
    my $file     = "$base/griplist.conf";

    $file         = &fixPath($file);

    if ( !$CanUseLWP ) {
        &rb_printlog("$file download failed: LWP::Simple Perl module not available\n");
        return;
    }

    if ( -e $file  ) {
        if ( !-r $file  ) {
            &rb_printlog( "$file download failed: $file  not readable!\n" );
            return;
        } elsif ( !-w $file  ) {
            &rb_printlog( "$file download failed: $file  not writable!\n" );
            return;
        }
    }
    else {
        if ( open( TEMPFILE, ">", $file ) ) {
            #we can create the file, this is good, now close the file and keep going.
            close TEMPFILE;
        unlink($file);
        } else {
            &rb_printlog("$file download failed: Cannot create $file - $!\n" );
            return;
        }
    }

    # Create LWP object
    use LWP::Simple qw(mirror is_success status_message $ua);

    # Set useragent to Rebuild version
    $ua->agent(
        "rebuildspamdb/$VERSION ($^O; Perl/$]; LWP::Simple/$LWP::VERSION)");
    $ua->timeout(20);
    if ($proxyserver) {
        $ua->proxy( 'http', "http://" . $proxyserver );

    } else {

    }

    # call LWP mirror command
    $rc = mirror( $griplistUrl, $file );

    if ( $rc == 304 ) {
        # HTTP 304 not modified status returned
        return;
    } elsif ( !is_success($rc) ) {
        #download failed-error code output to logfile
        &rb_printlog("$file download failed: $rc " . status_message($rc). "\n" );
        return;
    } elsif ( is_success($rc) ) {
        # download complete
        my $filesize = -s "$file";
        &rb_printlog("$file download complete: $filesize bytes\n" );
        chmod 0644, "$file";
    }
    my $ret = $rc;
#    &rb_printlog("info: updated GRIPLIST upload and download URL's in $file\n") if $ret;
    $ret = 0;
    open my $GC , '<', $file or return 0;
    binmode $GC;
    while (<$GC>) {
        s/\r|\n//o;
        if (/^\s*(gripList(?:DownUrl|UpUrl|UpHost))\s*:\s*(.+)$/) {
            ${$1} = $2;
            $ret++;
        }
    }
    close
#    &rb_printlog("info: loaded GRIPLIST upload and download URL's from $file\n") if $ret;
    &rb_printlog("info: GRIPLIST config $file is possibly incomplete\n") if $ret < 3;
    $gripListDownUrl =~ s/\*HOST\*/$gripListUpHost/o;
    $gripListUpUrl  =~ s/\*HOST\*/$gripListUpHost/o;
    return $ret;
}

sub uploadgriplist {
    local $/ = "\n";

    #&rb_printlog("Start building Griplist \n");
    open( my $FLogFile, '<', "$Log" ) || &rb_printlog("Unable to create Griplist.\n unable to open logfile '$Log': $!\n");
    my ( $date, $ip, $ipnet, %m, %ok, %locals, $match, $peeraddress, $hostaddress, $connect, $day, $gooddays, $st );
    my $buf;
    #build list of the last 2 days
    my $time = Time::HiRes::time();
    my $dayoffset = $time % ( 24 * 3600 );

    for ( my $i = 0 ; $i < 2 ; $i++ ) {
        $gooddays .= '|' if ( $i > 0 );
        $day = localtime( $time - $i * 24 * 3600 );
        $day =~ s/^... (...) +(\d+) (\S+) ..(..)/$1-$2-$4/;
        $gooddays .= $day;
    }
    if ($LogDateFormat !~ /MMM-DD-YY/)  {
    $gooddays .= '|';
    for ( my $i = 0 ; $i < 2 ; $i++ ) {
        $gooddays .= '|' if ( $i > 0 );
        $day = &timestring( $time - $i * 24 * 3600 , 'd');
        $gooddays .= $day;
    }}

    undef $day;
    %locals = ( '127', 1, '10', 1, '192.168', 1, '169.254', 1, '::1', 1, 'fe80:', 1 );    #RFC 1918, IPv6
    for ( 16 .. 31 ) { $locals{ "172.$_" } = 1 }                                          #RFC 1918

    while (<$FLogFile>) {
        next unless ( $date, $ip, $match ) = /($gooddays) .*\s([0-9a-f\.:]+) .* to: \S+ (.*)/io;
    $ipnet = $ip;
    if ($ipnet =~ /:.*:/) {
        $ipnet =~ s/:.*/:/ if ($ipnet !~ /^:/);
            next if $locals{ $ipnet };        # ignore local IP ranges
    }
    else {
        $ipnet =~ s/^(\d+\.\d+)\..*/$1/;
            next if $locals{ $ipnet };        # ignore local IP ranges
        $ipnet =~ s/^(\d+)\..*/$1/;
            next if $locals{ $ipnet };        # ignore local IP ranges
    }
    $ipnet = $ip;
    if ($ipnet =~ /:.*:/) {
        $ipnet =~ s/^([0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]*:).*/$1/i;    # yes: "+++*" so as to allow "2001:123:456::"
    }
    else {
        $ipnet =~ s/(\d+)\.(\d+)\.(\d+)\.(\d+)/$1.$2.$3/;
    }

        if (m/(\[Local]|\[MessageOK]|\[RWL]|\[whitelisted])|\[NoProcessing]/i) {

            #Good IP
            $m{ $ipnet }  += 1;
            $ok{ $ipnet } += 1;
            next;
        }
        if (m/(Connection idle for|\[Backscatter]|\[Bayesian]|\[BlackDomain]|\[BlackHELO]|\[BombBlack]|\[BombData]|\[BombHeader]|\[BombRaw]|\[BombScript]|\[BombSender]|\[Collect]|\[Connection]|\[DNSBL]|\[DenyIP]|\[DenyStrict]|\[Extreme]|\[ForgedHELO]|\[ForgedLocalSender]|\[FromMissing]|\[IPfrequency]|\[IPperDomain]|\[InvalidHELO]|\[Malformedaddress]|\[MaxErrors]|\[MessageScore]|\[MissingMX]|\[MsgID]|\[OversizedHeader]|\[PTRinvalid]|\[PTRmissing]|\[PenaltyBox]|\[Penalty]|\[RelayAttempt]|\[SpoofedSender]|\[Trap]|\[URIBL]|\[VIRUS]|\[ValidHELO]|spam found|\[blocked\])/i) {

            #Bad IP
            $m{ $ipnet }  += 1;
            $ok{ $ipnet } += 0;
            next;
        }
    }
    close $FLogFile;
    if ( !%m ) {
        &rb_printlog( "Skipping Griplist upload. Not enough messages processed.\n");
        return;
    }
    &rb_printlog("Preparing binary Griplist upload...");
    my $n6 = 0;
    my $n4 = 0;
    my ($buf6, $buf4);
    foreach (keys %m) {
        next if (!$m{$_});
        if ($_ =~ /:/) {
            my $ip = $_;
            $ip =~ s/([0-9a-f]*):/0000$1:/gi;
            $ip =~ s/0*([0-9a-f]{4}):/$1:/gi;
            $buf6 .= pack("H4H4H4H4", split(/:/, $ip));
            $buf6 .= pack("C", (1 - $ok{$_} / $m{$_}) * 255);
            $n6++;
        } else {
            $buf4 .= pack("C3C", split(/\./, $_), (1 - $ok{$_} / $m{$_}) * 255);
            $n4++;
        }
    }
    $st = pack("N2", $n6 / 2**32, $n6);
    $st .= pack("N", $n4);
    $st .= $buf6 . $buf4;
    &rb_printlog(" done\n");
    if ($proxyserver) {
        &rb_printlog("Uploading Griplist via Proxy: $proxyserver\n");

        my $user = $proxyuser ? "$proxyuser:$proxypass\@": '';
        $peeraddress = $user . $proxyserver;
        $hostaddress = $proxyserver;
        $connect     = "POST $gripListUpUrl HTTP/1.0";
    } else {
        &rb_printlog("Uploading Griplist via Direct Connection\n");
        $peeraddress = $gripListUpHost . ':80';
        $hostaddress = $gripListUpHost;
        my ($url) = $gripListUpUrl =~ /http:\/\/[^\/](\/.+)/oi;
        $connect     = <<"EOF";
POST $url HTTP/1.1
User-Agent: rebuildspamdb/$VERSION ($^O; Perl/$];)
Host: $gripListUpHost
EOF
    }
    my $socket = new IO::Socket::INET( Proto => 'tcp', Peeraddr => $peeraddress, Timeout => 2 );
    if ( defined $socket ) {
        my $len = length($st);
        $connect .= <<"EOF";
Content-Type: application/x-www-form-urlencoded
Content-Length: $len

$st
EOF
        print { $socket } $connect;
        $socket->sysread($buf, 4096);
        $socket->close;
        &rb_printlog("Submitted $len bytes: $n6 IPv6 addresses, $n4 IPv4 addresses\n");
    }
    else {
        &rb_printlog("unable to connect to assp.sourceforge.net to upload griplist\n");
        return;
    }
    return;
} ## end sub uploadgriplist

sub downloadgriplist {
    &rb_printlog("Griplist download disabled\n")  if $noGriplist;
    return if $noGriplist;
    &rb_printlog("Griplist file not configured\n")  if (!$griplist);
    return if (!$griplist);

    my $rc;

    my $gripListUrl = "http://assp.sourceforge.net/cgi-bin/assp_griplist?binary";
    my $gripFile    = "$base/$griplist";

    ## let's check if we really need to
    if (-e $gripFile) {
        my @s     = stat($gripFile);
        my $mtime = $s[9];
        if (time - $mtime < 8*60*60) {
            &rb_printlog("Griplist download failed: last download too recent\n");
            return;
        }
    }

    # check for previous download timestamp, so we can do delta now
    my %lastdownload;
    $lastdownload{full} = 0;
    $lastdownload{fullUTC} = 0;
    $lastdownload{delta} = 0;
    $lastdownload{deltaUTC} = 0;
    my $delta = "";
    if (open(UTC, "$gripFile.utc")) {
        local $/;
        my $buf = <UTC>;
        close(UTC);
        chop($buf);
        if ($buf =~ /full/ && $buf =~ /delta/) {
            %lastdownload = split(/\s+|\n/, $buf);
        } else {
            $lastdownload{delta} = $buf;
        }
        if (! ($DoFullGripDownload && time - $lastdownload{fullUTC} > $DoFullGripDownload*24*60*60)) {
            my $lasttime;
            $lasttime = $lastdownload{full};
            $lasttime = $lastdownload{delta} if ($lastdownload{delta} > $lastdownload{full});
            $gripListUrl .= "&delta=$lasttime";
            $delta = " (delta)";
        }
    }

    if (!$CanUseLWP) {
        &rb_printlog("Griplist download failed: LWP::Simple Perl module not available\n");
        return;
    }

    if (open(TEMPFILE, ">", "$gripFile.tmp")) {
        #we can create the file, this is good, now close the file and keep going.
        close TEMPFILE;
        unlink("$gripFile.tmp");
    } else {
        &rb_printlog("Griplist download failed: Cannot create $gripFile.tmp\n");
        return;
    }

    # Create LWP ogject
    use LWP::Simple qw(mirror is_success status_message $ua);

    # Set useragent to Rebuild version
    $ua->agent("rebuildspamdb/$VERSION ($^O; Perl/$]; LWP::Simple/$LWP::VERSION)");
    $ua->timeout(20);
    if ($proxyserver) {
        $ua->proxy('http', "http://" . $proxyserver);
        &rb_printlog("Downloading Griplist$delta via HTTP proxy: $proxyserver\n");
    } else {
        &rb_printlog("Downloading Griplist$delta via direct HTTP connection\n");
    }

    # call LWP mirror command
    my $dltime = time;
    $rc = mirror($gripListDownUrl, "$gripFile.tmp");

    if ($rc == 304) {
        # HTTP 304 not modified status returned
        # can't happen - we ALWAYS get new data
        unlink("$gripFile.tmp");
        return;
    } elsif (!is_success($rc)) {
        # download failed-error code output to logfile
        # &rb_printlog("Griplist download failed: $rc " . status_message($rc). "\n");
        unlink("$gripFile.tmp");
        return;
    }

    # download complete
    my $filesize = -s "$gripFile.tmp";
    &rb_printlog("Griplist download complete: binary download $filesize bytes\n");

    # enough data?
    if ($filesize < 12) {
        &rb_printlog("Griplist download error: grip data too small\n");
        unlink("$gripFile.tmp");
        return;
    }

    # record download time so we can do delta next time
    unlink("$gripFile.utc");
    if (open(UTC, ">$gripFile.utc")) {
        my ($sec, $min, $hour, $day, $mon, $year, $wday, $yday, $isdst) = gmtime($dltime);
        $year += 1900;
        $mon += 1;
        if (! $delta) {
            $lastdownload{full} = sprintf "%04d%02d%02d%02d%02d%02d", $year, $mon, $day, $hour, $min, $sec;
            $lastdownload{fullUTC} = $dltime;
        } else {
            $lastdownload{delta} = sprintf "%04d%02d%02d%02d%02d%02d", $year, $mon, $day, $hour, $min, $sec;
            $lastdownload{deltaUTC} = $dltime;
        }
        printf UTC "full\t%s\n", $lastdownload{full};
        printf UTC "fullUTC\t%s\n", $lastdownload{fullUTC};
        printf UTC "delta\t%s\n", $lastdownload{delta};
        printf UTC "deltaUTC\t%s\n", $lastdownload{deltaUTC};
        close(UTC);
    }

    # if we did a delta download, read in previous data so we can merge
    my @binFiles;
    push(@binFiles, "$gripFile.bin") if ($gripListUrl =~ /delta=/);
    push(@binFiles, "$gripFile.tmp");

    # convert binary download form to text form used by ASSP
    my $buf;
    my %grip;
    my $action = "read";
    foreach my $binF (@binFiles) {
        my $binSize = -s $binF;
        open(BIN, $binF);   
        binmode(BIN);
        read(BIN, $buf, $binSize);
        close(BIN);

    # IPv6 count
        my ($n6h, $n6l) = unpack("N2", $buf);
        my $n6 = $n6h * 2**32 + $n6l;

    # IPv4 count
        my $n4;
        eval { $n4 = unpack("x[N2] N", $buf); };

    # decode IPv6 data
        my $x6 = 0;
        eval {
        for (my $i = 0; $i < $n6; $i++) {
        my ($bip, $grey) = unpack("x[N2] x[N] x$x6 a8 C", $buf);
        my $ip = join(":", unpack("H4H4H4H4", $bip)) . ":";
        $ip =~ s/:0+([0-9a-f])/:$1/gio;
        $ip =~ s/:0:$/::/o;
        $x6 += 9;
        }
        };

    # decode IPv4 data
        my $x4 = 0;
        for (my $i = 0; $i < $n4; $i++) {
        my ($bip, $grey) = unpack("x[N2] x[N] x$x6 x$x4 a3 C", $buf);
        my $ip = join(".", unpack("C3", $bip));
        $grip{$ip} = $grey / 255;

        $x4 += 4;
    }
        &rb_printlog("Griplist binary $action OK: $binF, $n6 IPv6 addresses, $n4 IPv4 addresses\n");
        $action = "merge";
    }

    # remove download file
    unlink("$gripFile.tmp");

    # output binary version, so we can do a delta next time
    &rb_printlog("Writing merged Griplist binary...");
    my $buf;
    my $n6 = 0;
    my $n4 = 0;
    my ($buf6, $buf4);
    foreach my $ip (keys %grip) {
        if ($ip =~ /:/) {
            my $ip2 = $ip;
            $ip2 =~ s/([0-9a-f]*):/0000$1:/gi;
            $ip2 =~ s/0*([0-9a-f]{4}):/$1:/gi;
            $buf6 .= pack("H4H4H4H4", split(/:/, $ip2));
            $buf6 .= pack("C", int($grip{$ip} * 255));
            $n6++;
        } else {
            $buf4 .= pack("C3C", split(/\./, $ip), int($grip{$ip} * 255));
            $n4++;
        }
    }
    $buf = pack("N2", $n6/2**32, $n6);
    $buf .= pack("N", $n4);
    $buf .= $buf6 . $buf4;
    unlink("$gripFile.bin");
    open (BIN, ">$gripFile.bin");
    binmode(BIN);
    print BIN $buf;
    close(BIN);
    chmod 0644, "$gripFile.bin";
    &rb_printlog(" done\n");

    # output text version
    &rb_printlog("Writing merged Griplist text...");
    unlink("$gripFile");
    open (TEXT, ">$gripFile");
    binmode(TEXT);
    print TEXT "\n";
    foreach my $ip (sort keys %grip) {

        printf TEXT "$ip\002%.2f\n", $grip{$ip};
    }
    close(TEXT);
    chmod 0644, "$gripFile";
    &rb_printlog(" done\n");

    &rb_printlog("Griplist writing complete: $n6 IPv6 addresses, $n4 IPv4 addresses\n\n");
}

sub downloaddroplist {

    &rb_printlog("Droplist download disabled\n\n")  if !$DoDropList;
    return if !$DoDropList;
    my $rc;

    my $droplistUrl = "http://www.spamhaus.org/drop/drop.lasso";
    my $dropFile     = "$base/$DropList";

    $dropFile         = &fixPath($dropFile);

    # let's check if we really need to
    if (-e $dropFile) {
        my @s     = stat($dropFile);
        my $mtime = $s[9];
        my $random = int(rand(144-72)+72)+1; 
        if (time - $mtime < $random*60*60) {

            &rb_printlog("Droplist download skipped: last download too recent\n");
            return;
        }
    }

    if ( !$CanUseLWP ) {
        &rb_printlog("Droplist download failed: LWP::Simple Perl module not available\n");
        return;
    }

    if ( -e $dropFile  ) {
        if ( !-r $dropFile  ) {
            &rb_printlog( "Droplist download failed: $dropFile  not readable!\n" );
            return;
        } elsif ( !-w $dropFile  ) {
            &rb_printlog( "Droplist download failed: $dropFile  not writable!\n" );
            return;
        }
    }
    else {
        if ( open( TEMPFILE, ">", $dropFile ) ) {
            #we can create the file, this is good, now close the file and keep going.
            close TEMPFILE;
        unlink($dropFile);
        } else {
            &rb_printlog("Droplist download failed: Cannot create $dropFile \n" );
            return;
        }
    }

    # Create LWP ogject
    use LWP::Simple qw(mirror is_success status_message $ua);

    # Set useragent to Rebuild version
    $ua->agent(
        "rebuildspamdb/$VERSION ($^O; Perl/$]; LWP::Simple/$LWP::VERSION)");
    $ua->timeout(20);
    if ($proxyserver) {
        $ua->proxy( 'http', "http://" . $proxyserver );
        &rb_printlog("Downloading $dropFile via HTTP proxy: $proxyserver\n" );
    } else {
        &rb_printlog("Downloading $dropFile via direct HTTP connection\n" );
    }

    # call LWP mirror command
    $rc = mirror( $droplistUrl, $dropFile );

    if ( $rc == 304 ) {
        # HTTP 304 not modified status returned
        return;
    } elsif ( !is_success($rc) ) {
        #download failed-error code output to logfile
        &rb_printlog("$dropFile download failed: $rc " . status_message($rc). "\n" );
        return;
    } elsif ( is_success($rc) ) {
        # download complete
        my $filesize = -s "$dropFile";
        &rb_printlog("$dropFile download complete: $filesize bytes\n" );
        chmod 0644, "$dropFile";
    }
}

sub compileregex {
    use re 'eval';
    my ( $name, $contents, $REname ) = @_;
    $contents = getrecontent( $contents, $name );
    $contents ||= '^(?!)';    # regexp that never matches

    # trim long matches to 32 chars including '...' at the end
    eval { $$REname = qr/(?si)$contents/ };
    if ($EVAL_ERROR) { print "regular expression error in '$contents' for $name: $@\n"; }
    return q{};
}

sub fixPath {
    my ($path) = @_;
    my $len = length($path);
    if   ( !substr( $path, ( $len - 1 ), 1 ) eq q{/} ) { return $path . q{/}; }
    else                                               { return $path; }
    return;
}

sub repair {
    $/ = "\n";

    # mxa ptr rbl spf uribl white black
    my $pbdb = "$base/$pbdbfile";
    my ( @files, %w );
    my ( $k,     $v );
    if ( !( $pbdbfile =~ /mysql/ ) ) {
        foreach ( glob("$pbdb.*.db") ) { push( @files, $_ ); }
    }
    if ( !( $whitelistdb =~ /mysql/ ) ) { push( @files, $whitelistdb ); }
    if ( !( $redlistdb   =~ /mysql/ ) ) { push( @files, $redlistdb ); }
    foreach my $f (@files) {
        if ( !-e $f ) { next }
        open( my $curfile, "<", $f );

        #<$curfile>;
        while (<$curfile>) {
            ( $k, $v ) = split( /[\001\002\n]/, $_ );
            if ( $k eq q{} || $v eq q{} ) { next }
            #print "$k=$v\n";
            $w{ $k } = $v;
        }
        close $curfile;
        open( my $newfile, ">", "$f.new" );
        binmode $newfile;
        print { $newfile } "\n";
        for ( sort keys %w ) { print { $newfile } "$_\002$w{$_}\n"; }
        close $newfile;
        rename( $f, "$f.bak" );
        rename( "$f.new", $f );
        undef %w;
    }
    return;
} ## end sub repair

sub sendNotification {
    my ($from,$to,$sub,$body,$file) = @_;
    my $text;
    return unless $to;
    return unless $resendmail;    
    my $date=$UseLocalTime ? localtime() : gmtime();
    my $tz=$UseLocalTime ? tzStr() : '+0000';
    $date=~s/(\w+) +(\w+) +(\d+) +(\S+) +(\d+)/$1, $3 $2 $5 $4/;
    $text = "Date: $date $tz\r\n";
    $text .= "X-Assp-Notification: YES\r\n";
      $text .= "From: <$from>\r\nTo:" if $from !~ /\</;
    $text .= "From: $from\r\nTo:" if $from =~ /\</;

    foreach (split(/\|/, $to)) {
        $text .= " <$_>,";
    }
    chop $text;
    $text .= "\r\n";
    $text .= "Subject: $sub\r\n";
    $text .= "Content-Type: text/plain;    charset=\"ISO-8859-1\"\r\n";
    $text .= "Content-Transfer-Encoding: 7bit\r\n";
    my $msgid = int(rand(1000000));
    $text .= "Message-ID: a$msgid\@$myName\r\n";
    $text = &headerWrap($text);
    $text .= "\r\n";           # end header
    foreach (split(/\r?\n/,$body)) {
        $text .= "$_\r\n";
    }

    my $f;
    if ($file && -e $file && open($f,"<",$file)) {

        while (<$f>) {                 
             s/\r?\n//g;
             $text .= "$_\r\n";

        }
        close $f;
    }

    $text .= ".\r\n";
    $text =~ tr/\x80-\xFF/_/;   # 7bit only
    my $rfile = "$base/$resendmail/n$msgid$maillogExt";
    -d "$base/$resendmail" or mkdir "$base/$resendmail", 0777;
    if (open($f,">",$rfile)) {
        binmode $f;
        print $f $text;
        close $f;
        &rb_printlog( "write notify message to $rfile\n" );        
    } else {

        &rb_printlog( "error: unable to write notify message to $rfile - $!\n" );
    }

}
sub tzStr {

    # calculate the time difference in minutes
    my $minoffset =
      ( Time::Local::timelocal( localtime() ) -
          Time::Local::timelocal( gmtime() ) ) / 60;

   # translate it to "hour-format", so that 90 will be 130, and -90 will be -130
    my $sign = $minoffset < 0 ? -1 : +1;
    $minoffset = abs($minoffset) + 0.5;
    my $tzoffset = 0;
    $tzoffset = $sign * ( int( $minoffset / 60 ) * 100 + ( $minoffset % 60 ) )
      if $minoffset;

    # apply final formatting, including +/- sign and 4 digits
    return sprintf( "%+05d", $tzoffset );
}
# wrap long headers
sub headerWrap {
    my $header = shift;
    $header =~
s/(?:([^\r\n]{60,75}?;)|([^\r\n]{60,75}) ) {0,5}(?=[^\r\n]{10,})/$1$2\r\n\t/g;

    return $header;
}
sub rb_cleanUpFiles {
    my ($folder, $filter, $filetime) = @_;

    my $textfilter = " (*$filter)" if $filter;
    my @files;
    my $file;
    my $count;
    my $dir = &fixPath($folder); 
    $dir =~ s/\\/\//g;
    return unless -e $dir;
    &rb_printlog( "starting rb_cleanup old files$textfilter for folder $dir\n" );

    opendir(my $DIR,"$dir");
    @files = readdir($DIR);
    close $DIR;
    my $fldrpath        = $dir . "/*"; 

    my $filecount = &countfiles($fldrpath);
    foreach $file (@files) {
        next if $file eq '.';
        next if $file eq '..';
        next unless $file =~ /$maillogExt$/i or $file =~ /\.rpt$/i;
        next if ($filter && $file !~ /$filter$/i);
        next if ($filter && $file =~ /^$filter$/i);
        $file = "$dir/$file";
        next if -d $file;
        next unless -w $file;
        my $dtime=(stat($file))[9]-time;
        if (($dtime < $filetime * -1) or ($dtime > 0 && $dtime < $MaxKeepDeleted - $filetime)) {
            $count++;

        }
    }

    my $filecountafter = &countfiles($fldrpath);
    &rb_printlog( "folder $dir before: $filecount\n" ) ;
    &rb_printlog( "folder $dir deleted: $count\n" ) if $count;
    &rb_printlog( "folder $dir after: $filecountafter\n\n" ) ;
    $correctedspamcount = $filecountafter if $dir =~ 'errors\/spam';
    $correctednotspamcount = $filecountafter if $dir =~ 'errors\/notspam';

}

sub rb_cleanUpMaxFiles {
    my $folder = shift;
    my $percent = shift;

    my @files;
    my $file;
    my $count;
    my $info;
    my $dir = ($folder !~ /\Q$base\E/i) ? "$base/$folder" : $folder ;
    my $maxfiles = $MaxFiles;
    my $importmaxfiles = $MaxFiles;

    my $adiff = abs ($correctedspamcount - $correctednotspamcount);
    $maxfiles = $MaxFiles + $adiff if $dir =~ /assp\/spam/ && $correctednotspamcount > $correctedspamcount;
    $maxfiles = $MaxFiles + $adiff if $dir =~ /assp\/notspam/ && $correctedspamcount > $correctednotspamcount;

    $dir =~ s/\\/\//g;
    return unless -e $dir;

    opendir(my $DIR,"$dir");
    @files = readdir($DIR);
    close $DIR;
    my $filecount = @files - 2;

    rb_printlog("rb_cleaning $dir skipped - filecount: $filecount < maxfiles: $maxfiles\n") if $filecount <= $maxfiles;
    return $info if  $filecount <= $maxfiles;

    my %filelist = ();
    while (@files ) {
        $file = shift @files;
        next if $file eq '.';
        next if $file eq '..';
        $file = "$dir/$file";
        if (-d $file) {
            $filecount--;
            next;
        }
        my $ft = (stat($file))[9];
        $ft = $ft - (60 * 24 * 3600) if $ft > time;
        while (exists $filelist{$ft}) {
            $ft++;
        }
        $filelist{$ft} = $file;
        $count++;

    }
    return $info if $filecount <= $maxfiles;

    rb_printlog("\nstarting rb_cleaning $dir - delete files from $dir - old filecount: $filecount: \n");
    my $toFilenumber;
    my $filenum;
    my $time = time - ($minimumdays * 24 * 3600);   # two weeks ago

    $filenum = $maxfiles - $filecount;
    $toFilenumber = $maxfiles;

    $count = 0;

    foreach my $filetime (sort keys %filelist) {
        last if $filecount-- < $toFilenumber;

        unlink "$filelist{$filetime}";
        $count++;

    }

    opendir(my $DIR,"$dir");
    @files = readdir($DIR);
    close $DIR;
    my $newfilecount = @files - 2;

    rb_printlog("finished rb_cleaning $dir - new filecount: $newfilecount\n") ;

    return $info;
}

sub rb_cleanUpCollection {

    my $age = $MaxNoBayesFileAge * 3600 * 24;
    my @dirs = ('incomingOkMail','discarded','viruslog');
    my $dir;

        &rb_printlog( "\n--- rb_cleaning NoBayesian folders ---\n" );
        &rb_printlog( "entries older than $MaxNoBayesFileAge days will be removed\n" ) if $MaxNoBayesFileAge;

    foreach my $dir (@dirs) {
        if ($age) {
            &rb_cleanUpFiles(${$dir},'',$age) if ${$dir};
        } 
    }

    $age = $MaxCorrectedDays * 3600 * 24;
    @dirs = ('correctedspam','correctednotspam');

    &rb_printlog( "\n--- rb_cleaning corrected (errors) spam/notspam folders ---\n" );
    &rb_printlog( "entries older than $MaxCorrectedDays days will be removed\n" ) if $MaxCorrectedDays;

    foreach my $dir (@dirs) {
        if ($age) {
            &rb_cleanUpFiles(${$dir},'',$age) if ${$dir};
        } else {
            &rb_cleanUpMaxFiles(${$dir}) if ${$dir};
        }
    }

    return unless $maintbayescollection;
    $age = 0;
    @dirs = ('spamlog','notspamlog');

    &rb_printlog( "\n--- rb_cleaning Bayesian folders ---\n" );
    &rb_printlog( "entries older than $MaxBayesFileAge days will be removed\n" ) if $MaxBayesFileAge;

    foreach my $dir (@dirs) {
        if ($age) {
            &rb_cleanUpFiles(${$dir},'',$age) if ${$dir};
        } else {
            &rb_cleanUpMaxFiles(${$dir}) if ${$dir};
        }
    }
}

sub rb_cleanTrashlist {
    my $files_before = my $files_deleted = 0;
    my $t = time;
    my $mcount;

    while ( my ( $k, $v ) = each(%Trashlist) ) {
        if (!-e $k) {
            delete $Trashlist{$k};
            $files_deleted++;
            next;
        }
        my $ct = $v;
        $files_before++;

        if (!$MaxKeepDeleted or ( $t - $ct >= $MaxKeepDeleted * 3600 * 24)
            )
        {
            unlink $k;
            delete $Trashlist{$k};
            $files_deleted++;
        }
    }
    &rb_printlog(
"\nTrashlist: rb_cleaning finished; before=$files_before, deleted=$files_deleted\n"
    );

}

sub getUidGid {
    my ( $uname, $gname ) = @_;

    my $rname = "root";
    eval('getgrnam($rname);getpwnam($rname);');
    if ($@) {

        # windows pukes "unimplemented" for these -- just skip it
        rb_printlog(
"warning:   uname and/or gname are set ($uname,$gname) but getgrnam / getpwnam give errors: $@"
        );
        return;
    }
    my $gid;
    if ($gname) {
        $gid = getgrnam($gname);
        if ( defined $gid ) {
        } else {
            my $msg =
"could not find gid for group '$gname' -- not switching effective gid ";
            rb_printlog( $msg );
            return;
        }
    }
    my $uid;
    if ($uname) {
        $uid = getpwnam($uname);
        if ( defined $uid ) {
        } else {
            my $msg =
"could not find uid for user '$uname' -- not switching effective uid ";
            rb_printlog( $msg );
            return;
        }
    }
    ( $uid, $gid );
}

sub mlog {
}

################################################################################
{
package orderedtie;
################################################################################
# This is a tied value that caches lookups from a sorted file; \n separates records,
# \002 separates the key from the value. After main::OrderedTieHashTableSize lookups the cache is
# cleared. This give us most of the speed of the hash without the huge memory overhead of storing
# the entire hash and should be totally portable. Picking the best value for n requires some
# tuning. A \n is required to start the file.

# if you're updating entries it behoves you to call flush every so often to make sure that your
# changes are saved. This also frees the memory used to remember updated values.

    # for my purposes a value of undef and a nonexistant key are the same

# Obviously if your keys or values contain \n or \002 it will totally goof things up.

sub TIEHASH {
    my ( $c, $fn ) = @_;
    my $self = {
        fn      => $fn,
        age     => mtime($fn),
        cnt     => 0,
        cache   => {},
        updated => {},
        ptr     => 1,
    };
    bless $self, $c;
    return $self;
}
sub DESTROY { $_[0]->flush(); }

sub mtime { my @s = stat( $_[0] ); $s[9]; }

sub flush {
 my $this=shift;
 return unless %{$this->{updated}};
 my $f=$this->{fn};
 open(my $O,'>',"$f.tmp") or return;
 binmode $O;
 open(my $I,'<',$f) || print $O "\n";
 binmode $I;
 local $/="\n";
 my @l=(sort keys %{$this->{updated}});
 my ($k,$d,$r,$v);
 while ($r=<$I>) {
  ($k,$d)=split("\002",$r);
  while (@l && $l[0] lt $k) {
   $v=$this->{updated}{$l[0]};
   print $O "$l[0]\002$v\n" if $v;
   shift @l;
  }
  if ($l[0] eq $k) {
   $v=$this->{updated}{$l[0]};
   print $O "$l[0]\002$v\n" if $v;
   shift @l;
  } else {
   print $O $r;
  }
 }
 while (@l) {
  $v=$this->{updated}{$l[0]};
  print $O "$l[0]\002$v\n" if $v;
  shift @l;
 }
 close $I;
 close $O;
 unlink($f);
 rename("$f.tmp", $f);
 $this->{updated}={};
}

sub STORE {
    my ( $this, $key, $value ) = @_;
    $this->{cache}{$key} = $this->{updated}{$key} = $value;
}

sub FETCH {
    my ( $this, $key ) = @_;
    return $this->{cache}{$key} if exists $this->{cache}{$key};
    $this->resetCache()
      if ( $this->{cnt}++ > $main::OrderedTieHashTableSize
        || ( $this->{cnt} & 0x1f ) == 0
        && mtime( $this->{fn} ) != $this->{age} );

    return $this->{cache}{$key} = binsearch( $this->{fn}, $key );
}

sub resetCache {
    my $this = shift;

    $this->{cnt}   = 0;
    $this->{age}   = mtime( $this->{fn} );
    $this->{cache} = { %{ $this->{updated} } };

}

sub binsearch {
    my ( $f, $k ) = @_;
    open( my $F, "<","$f" ) || return;
    binmode($F);
    my $count = 0;
    my $siz = my $h = -s $f;
    $siz -= 1024;
    my $l  = 0;
    my $k0 = $k;
    $k =~ s/([\[\]\(\)\*\^\!\|\+\.\\\/\?\`\$\@\{\}])/\\$1/g
      ;    # make sure there's no re chars unqutoed in the key

    while (1) {
        my $m = ( ( $l + $h ) >> 1 ) - 1024;
        $m = 0 if $m < 0;
        seek( $F, $m, 0 );
        my $d;
        my $read = read( $F, $d, 2048 );
        if ( $d =~ /\n$k\002([^\n]*)\n/ ) {
            close $F;
            return $1;
        }
        my ( $pre, $first, $last, $post ) =
          $d =~ /^(.*?)\n(.*?)\002.*\n(.*?)\002.*?\n(.*?)$/s;
        last unless defined $first;
        if ( $k0 gt $first && $k0 lt $last ) {
            last;
        }
        if ( $k0 lt $first ) {
            last if $m == 0;
            $h = $m - 1024 + length($pre);
            $h = 0 if $h < 0;
        }
        if ( $k0 gt $last ) {
            last if $m >= $siz;
            $l = $m + $read - length($post);
        }
        if ( $count++ > 100 ) {

            #main::mlog(0,"warning:   $this->{fn} must be repaired ($k0)");
            last;
        }
    }
    close $F;
    return;
}

sub FIRSTKEY {
    my $this = shift;
    $this->flush();
    $this->{ptr} = 1;
    $this->NEXTKEY();
}

sub NEXTKEY {
    my ( $this, $lastkey ) = @_;
    local $/ = "\n";
    open( my $F, "<","$this->{fn}" ) || return;
    binmode($F);
    seek( $F, $this->{ptr}, 0 );
    my $r = <$F>;
    return unless $r;
    $this->{ptr} = tell $F;
    close $F;
    my ( $k, $v ) = $r =~ /(.*?)\002(.*?)\n/s;

    if ( !exists( $this->{cache}{$k} )
        && $this->{cnt}++ > $main::OrderedTieHashTableSize )
    {
        $this->{cnt}   = 0;
        $this->{cache} = { %{ $this->{updated} } };
    }
    $this->{cache}{$k} = $v;
    $k;
}

sub EXISTS {
    my ( $this, $key ) = @_;
    return FETCH( $this, $key );
}

sub DELETE {
    my ( $this, $key ) = @_;
    $this->{cache}{$key} = $this->{updated}{$key} = undef;
}

sub CLEAR {
    my ($this) = @_;
    open(my $F, ">","$this->{fn}" );
    binmode($F);
    print $F "\n";
    close $F;
    $this->{cache}   = {};
    $this->{updated} = {};
    $this->{cnt}     = 0;
}
}
#              end orderedtie
################################################################################
RBEOT

}

sub writeWatchdog {
my $watchdog_version = '1.02';
my $curr_version;
if (open my $ADV, '<',"$base/assp_watchdog.pl") {
    while (<$ADV>) {
        if (/^\s*our \$VERSION.+?(\d\.\d+)/o) {
            $curr_version = $1;

            last;
        }

    }
    close $ADV;
    mlog(0,"info: found module $base/assp_watchdog.pl version $curr_version");
}
return 0 if $curr_version eq $watchdog_version;
(open my $ADV, '>',"$base/assp_watchdog.pl") or return 0;

print $ADV "#!/usr/local/bin/perl\n\n";
print $ADV 'our $VERSION = ',"'$watchdog_version';\n\n";
print $ADV <<'RBEOT' or return 0;

use strict;
use IO::Socket;

use Time::Local;
use Time::HiRes;
use Cwd;

 our $base;
 our $pid; 
if($ARGV[0]) { 
 $base=$ARGV[0]; 
} else { 
 # the last one is the one used if all else fails 
 $base = cwd(); 
 unless (-e "$base/assp.cfg") { 
   foreach ('.','/usr/local/assp','/home/assp','/etc/assp','/usr/assp','/applications/assp','/assp','.') {
    if (-e "$_/assp.cfg") {
      $base=$_; 
      last ; 
    } 
   } 
 } 
 $base = cwd() if $base eq '.'; 
}

open( my $confFile, '<', "$base/assp.cfg" ) || die "cannot open \"$base/assp.cfg\": $!";
local $/;
my %Config = split( /:=|\n/, <$confFile> );
close $confFile or die "unable to close: $!";
our $EnableWatchdog = $Config{ EnableWatchdog };
our $WatchdogHeartBeat = $Config{WatchdogHeartBeat };
our $WatchdogRestart = $Config{ WatchdogRestart };
our $AutoRestartCmd = $Config{ AutoRestartCmd };
our $AsAService = $Config{ AsAService };

our $pidfile = $Config{ pidfile };
fork() && exit;
my $watchdogPID = $pidfile . "_watchdog";
open(my $FH, ">$base/$watchdogPID" ); print $FH $$; close $FH;
$pidfile = "$base/$pidfile";

open my $FH, "<$pidfile";
$pid = <$FH>;
$pid =~ s/\r|\n|\s//go;
close $FH;
print "assp_watchdog.pl watching ASSP(pid=$pid), HeartBeat = $WatchdogHeartBeat, Restart = $WatchdogRestart\n" ;
my $count;

eval {
    while (1)

    {
        sleep 30;
        if (!-e "$pidfile") {
            exit 1;
            }
        open my $FH, "<$pidfile";
        $pid = <$FH>;
        $pid =~ s/\r|\n|\s//go;
        close $FH;
        my @s     = stat("$pidfile");
        my $mtime = $s[9];
        my $heartbeats = time - $mtime;
        if ( $heartbeats > 100) {
            kill HUP =>  => $pid if $pid;
            }
        if ( $heartbeats > $WatchdogHeartBeat) {
            print "assp_watchdog.pl: ASSP($pid) heartbeats ($heartbeats) reached limit ($WatchdogHeartBeat)\n" ;

            kill TERM => $pid if $pid;
            sleep 10;
            if ($WatchdogRestart) {
                print "assp_watchdog.pl: trying to restart ASSP\n" if $AutoRestartCmd;
                print "assp_watchdog.pl: ASSP restart not possible, AutoRestartCmd not configured\n" if !$AutoRestartCmd;
                if ($AutoRestartCmd) {
                    if ($AsAService) {

                        exec('cmd.exe /C net stop ASSPSMTP & net start ASSPSMTP') ;
                        exit 1;
                    } else {

                        print "assp_watchdog.pl: restarting with '$AutoRestartCmd'\n";
                        print "assp_watchdog.pl for ASSP(pid=$pid) ended\n";
                        exec($AutoRestartCmd);

                        exit 1;

                    }

                }

            }
            print "assp_watchdog.pl watching ASSP(pid=$pid) ended\n" ;
            exit 1;
        }
    }
};
RBEOT

}
#              end write watchdog
################################################################################


################################################################################
{
package orderedtie;
################################################################################
# This is a tied value that caches lookups from a sorted file; \n separates records,
# \002 separates the key from the value. After main::OrderedTieHashTableSize lookups the cache is
# cleared. This give us most of the speed of the hash without the huge memory overhead of storing
# the entire hash and should be totally portable. Picking the best value for n requires some
# tuning. A \n is required to start the file.

# if you're updating entries it behoves you to call flush every so often to make sure that your
# changes are saved. This also frees the memory used to remember updated values.

    # for my purposes a value of undef and a nonexistant key are the same

# Obviously if your keys or values contain \n or \002 it will totally goof things up.

sub TIEHASH {
    my ( $c, $fn ) = @_;
    my $self = {
        fn      => $fn,
        age     => mtime($fn),
        cnt     => 0,
        cache   => {},
        updated => {},
        ptr     => 1,
    };
    bless $self, $c;
    return $self;
}
sub DESTROY { $_[0]->flush(); }

sub mtime { my @s = stat( $_[0] ); $s[9]; }

sub flush {
 my $this=shift;
 return unless %{$this->{updated}};
 my $f=$this->{fn};
 open(my $O,'>',"$f.tmp") or return;
 binmode $O;
 open(my $I,'<',$f) || print $O "\n";
 binmode $I;
 local $/="\n";
 my @l=(sort keys %{$this->{updated}});
 my ($k,$d,$r,$v);
 while ($r=<$I>) {
  ($k,$d)=split("\002",$r);
  while (@l && $l[0] lt $k) {
   $v=$this->{updated}{$l[0]};
   print $O "$l[0]\002$v\n" if $v;
   shift @l;
  }
  if ($l[0] eq $k) {
   $v=$this->{updated}{$l[0]};
   print $O "$l[0]\002$v\n" if $v;
   shift @l;
  } else {
   print $O $r;
  }
 }
 while (@l) {
  $v=$this->{updated}{$l[0]};
  print $O "$l[0]\002$v\n" if $v;
  shift @l;
 }
 close $I;
 close $O;
 unlink($f);
 rename("$f.tmp", $f);
 $this->{updated}={};
}

sub STORE {
    my ( $this, $key, $value ) = @_;
    $this->{cache}{$key} = $this->{updated}{$key} = $value;
}

sub FETCH {
    my ( $this, $key ) = @_;
    return $this->{cache}{$key} if exists $this->{cache}{$key};
    $this->resetCache()
      if ( $this->{cnt}++ > $main::OrderedTieHashTableSize
        || ( $this->{cnt} & 0x1f ) == 0
        && mtime( $this->{fn} ) != $this->{age} );

    return $this->{cache}{$key} = binsearch( $this->{fn}, $key );
}

sub resetCache {
    my $this = shift;

    $this->{cnt}   = 0;
    $this->{age}   = mtime( $this->{fn} );
    $this->{cache} = { %{ $this->{updated} } };

}

sub binsearch {
    my ( $f, $k ) = @_;
    open( my $F, "<","$f" ) || return;
    binmode($F);
    my $count = 0;
    my $siz = my $h = -s $f;
    $siz -= 1024;
    my $l  = 0;
    my $k0 = $k;
    $k =~ s/([\[\]\(\)\*\^\!\|\+\.\\\/\?\`\$\@\{\}])/\\$1/g
      ;    # make sure there's no re chars unqutoed in the key

    while (1) {
        my $m = ( ( $l + $h ) >> 1 ) - 1024;
        $m = 0 if $m < 0;
        seek( $F, $m, 0 );
        my $d;
        my $read = read( $F, $d, 2048 );
        if ( $d =~ /\n$k\002([^\n]*)\n/ ) {
            close $F;
            return $1;
        }
        my ( $pre, $first, $last, $post ) =
          $d =~ /^(.*?)\n(.*?)\002.*\n(.*?)\002.*?\n(.*?)$/s;
        last unless defined $first;
        if ( $k0 gt $first && $k0 lt $last ) {
            last;
        }
        if ( $k0 lt $first ) {
            last if $m == 0;
            $h = $m - 1024 + length($pre);
            $h = 0 if $h < 0;
        }
        if ( $k0 gt $last ) {
            last if $m >= $siz;
            $l = $m + $read - length($post);
        }
        if ( $count++ > 100 ) {

            #main::mlog(0,"warning:   $this->{fn} must be repaired ($k0)");
            last;
        }
    }
    close $F;
    return;
}

sub FIRSTKEY {
    my $this = shift;
    $this->flush();
    $this->{ptr} = 1;
    $this->NEXTKEY();
}

sub NEXTKEY {
    my ( $this, $lastkey ) = @_;
    local $/ = "\n";
    open( my $F, "<","$this->{fn}" ) || return;
    binmode($F);
    seek( $F, $this->{ptr}, 0 );
    my $r = <$F>;
    return unless $r;
    $this->{ptr} = tell $F;
    close $F;
    my ( $k, $v ) = $r =~ /(.*?)\002(.*?)\n/s;

    if ( !exists( $this->{cache}{$k} )
        && $this->{cnt}++ > $main::OrderedTieHashTableSize )
    {
        $this->{cnt}   = 0;
        $this->{cache} = { %{ $this->{updated} } };
    }
    $this->{cache}{$k} = $v;
    $k;
}

sub EXISTS {
    my ( $this, $key ) = @_;
    return FETCH( $this, $key );
}

sub DELETE {
    my ( $this, $key ) = @_;
    $this->{cache}{$key} = $this->{updated}{$key} = undef;
}

sub CLEAR {
    my ($this) = @_;
    open(my $F, ">","$this->{fn}" );
    binmode($F);
    print $F "\n";
    close $F;
    $this->{cache}   = {};
    $this->{updated} = {};
    $this->{cnt}     = 0;
}
}
#              end orderedtie
################################################################################


################################################################################
package ASSP::CRYPT;
################################################################################
# based on GOST 28147-89  (Vipul Ved Prakash, 1997)
#
# GOST 28147-89 is a 64-bit symmetric block cipher
# with a 256-bit key developed in the former Soviet Union (KGB).
#
# redesigned and improved by Thomas Eckardt (2009)
################################################################################
use strict qw(vars subs);

sub new {
        my ($argument,$pass,$bin) = @_;
    my $class = ref ($argument) || $argument;
    my $self = {};
    $self->{KEY} = [];
    $self->{SBOX} = [];
    $self->{BIN} = $bin;
    $self->{PASS} = $pass;
        _generate_sbox($self,$pass) if $pass;
        _generate_keys($self,$pass) if $pass;
    bless $self, $class;
    return $self;
}

sub _generate_sbox {
    my $self = shift;
    my $passphrase = shift;
    if (ref ($passphrase)) {
        @{$self->{SBOX}} = @$passphrase;
    } else {
        my ($i, $x, $y, $random, @tmp) = 0;
        my @temp = (0..15);
        for ($i=0; $i <= (length $passphrase); $i+=4)
        { $random = $random ^ (unpack 'L', pack 'a4', substr ($passphrase, $i, $i+4)) };
        srand $random;
        for ($i=0; $i < 8; $i++) {
                @tmp = @temp;
                       grep { $x = _rand (15); $y = $tmp[$x]; $tmp[$x] = $tmp[$_]; $tmp[$_] = $y; } (0..15);
                    grep {$self->{SBOX}->[$i][$_] = $tmp[$_] } (0..15);
        }
    }
}

sub _generate_keys {
    my ($self, $passphrase) = @_;
    if (ref ($passphrase)) {
        @{$self->{KEY}} = @$passphrase;
    } else {
        my ($i, $random) = 0;
        for ($i=0; $i <= (length $passphrase); $i+=4)
        { $random = $random ^ (unpack 'L', pack 'a4', substr ($passphrase, $i, $i+4))};
        srand $random; grep { $self->{KEY}[$_] = _rand (2**32) } (0..7);
    }
}

sub _crypt {
    my ($self, $data, $decrypt, $bin) = @_;
        return $data unless $self->{PASS};
    $bin = $bin || $self->{BIN};
        my $l;
        my $check;
        my $cl = $bin ? 3 : 6;
        my $ll = $bin ? 2 : 4;
        if ($decrypt) {
            $check = substr($data,length($data)-$cl,$cl);
            $data = substr($data,0,length($data)-$cl);
            $l = int(hex(_IH(substr($data,length($data)-$ll,$ll),$bin)));
            $data = substr($data,0,length($data)-$ll);
        $data = _HI($data,! $bin);
    } else {
            $check = _XOR_SYSV($data,$bin);
            $l = length($data);
            my $s = $l % 8;
            $l = _HI(sprintf("%04x",$l),$bin);
            $data .= "\x5A" x (8-$s) if $s;
    }
    my ($i, $j, $d1, $d2) = 0;
    my $return = '';
    for ($i=0; $i < length $data; $i += 8) {
        $d1 = unpack 'L', pack 'a4', substr ($data, $i, $i + 4);
        $d2 = unpack 'L', pack 'a4', substr ($data, $i + 4, $i + 8);
        $j = 0;
        grep {
            $j = ($_ % 8) - 1; $j = 7 if $j == -1;
            $decrypt ? ($_ >= 9) && ($j = (32 - $_) % 8) : ($_ >= 25) && ($j = 32 - $_);
            ($_ % 2) == 1 ? ($d2 ^= $self->_substitute ($d1 + $self->{KEY}[$j])) :
                    ($d1 ^= $self->_substitute ($d2 + $self->{KEY}[$j])) ;
        } (1..32);
        $return = $return . (pack 'L', $d2) . (pack 'L', $d1);
    }
        return _IH($return,! $bin).$l.$check unless ($decrypt);
        $return = substr($return,0,$l);
        return if _XOR_SYSV($return,$bin) ne $check;
        return $return;
}

sub ENCRYPT    {_crypt(shift,shift,0,0);}

sub DECRYPT    {_crypt(shift,shift,1,0);}

sub ENCRYPTHEX {_crypt(shift,shift,0,1);}

sub DECRYPTHEX {_crypt(shift,shift,1,1);}

sub _substitute {
    my ($self, $d) = @_;
    my $return = 0;
    grep { $return = $return | $self->{SBOX}->[$_][$d >> ($_ * 4) & 15] << ($_ * 4) } reverse (0..7);
    return $return << 11 | $return >> 21;
}

sub _rand {
    return int (((shift) / 100) * ((rand) * 100));
}

sub _XOR_SYSV {
    my ($d,$bin) = @_;
    my $xor = 0x03 ^ 0x0d;
    for ( split(//o, $d) ) { $xor ^= ord($_); };
    return _HI(sprintf ("%02x", $xor),$bin) . _HI(sprintf("%04x",unpack("%32W*",$d) % 65535),$bin) if ( $]>="5.010" );
    return _HI(sprintf ("%02x", $xor),$bin) . _HI(sprintf("%04x", _SYSV($d)),$bin);
}

sub _SYSV {
    my $d = shift;
    my $checksum = 0;
    foreach (split(//o,$d)) { $checksum += unpack("%16C*", $_) }
    $checksum %= 65535;
    return $checksum;
}

sub _IH {
    my ($s,$do) = @_;
        return $s unless $do;
        return join('',unpack 'H*',$s);
}

sub _HI {
    my ($h,$do) = @_;
        return $h unless $do;
        return pack 'H*',$h;
}

1;
#              end assp::crypt
################################################################################


################################################################################
# this package implements realtime blacklisting
# it is based on Net::RBLClient by Asher Blum <asher@wildspark.com>
# CREDITS Martin H. Sluka <martin@sluka.de>
# Copyright (C) 2002 Asher Blum.  All rights reserved.
# This code is free software; you can redistribute it and/or modify it under
# the same terms as Perl itself.
# Modified for integration with ASSP by John Calvi.
package RBL;
################################################################################
use strict qw(vars subs);
use IO::Socket;
use IO::Select;

sub new {
    # This avoids compile time errors if Net::DNS is not installed.
    # The error will be returned on the lookup function call.
    if ($main::CanUseDNS) {
     require Net::DNS::Packet;
    }
    my($class, %args) = @_;
    my $self = {
        lists       => [ lists() ],
        query_txt   => 1,
        max_time    => 10,
        timeout     => 1,
        max_hits    => 3,
        max_replies => 6,
        udp_maxlen  => 4000,
        server      => '127.0.0.1',
    };
    bless $self, $class;
    foreach my $key(keys %args) {
        defined($self->{ $key })
            or return "Invalid key: $key";
        $self->{ $key } = $args{ $key };
    }

    $self;
}

sub lookup {
    return "Net::DNS package required" unless $main::CanUseDNS;
    my($self, $target, $type) = @_;
    my $start_time = time;
    my $qtarget;
    my $dur;
    my @ok;
    my @failed;
    my $isip = 0;
    $target =~ s/[^\w\-\.:].*$//o if $type ne 'URIBL';
    if ($target=~/^$main::IPv4Re$/o) {
        $qtarget = join ('.', reverse(split /\./o, $target));
        $isip = 1;
    } elsif ($target=~/^$main::IPv6Re$/o) {
        $qtarget = &main::ipv6hexrev($target,36) or return "IPv6 addresses are not supported";
        $isip = 2;
    } else {
        $qtarget=$target;
    }
    my $deadline = time + $self->{ max_time };
    my @sock;

    for (@{$self->{server}}) {
        my $sock = $main::CanUseIOSocketINET6
                   ? IO::Socket::INET6->new(Proto=>'udp',PeerAddr=>$_,PeerPort=>53,&main::getDestSockDom($_))
                   : IO::Socket::INET->new(Proto=>'udp',PeerAddr=>$_,PeerPort=>53);
        push @sock, $sock if $sock;
    }
    if (! @sock) {
        return "Failed to create any UDP client for DNS queries";
    }
    for (@sock) {
        $_->blocking(0) if $_->blocking;
    }
    my $sn = 0;
    my @availsock;
    my %regsock;
    if ( $self->{ query_txt } ) {
      foreach my $list(@{ $self->{ lists } }) {
        if (length($qtarget.$list) > 62 && $type ne 'URIBL' && $isip != 2) {
          eval{close($_) if $_;} for (@sock);
          return "domain name too long";
        }
        if ($list && !($type eq 'URIBL' && lc $list eq 'dbl.spamhaus.org' && $isip)) {
            my($msg_a, $msg_t) = mk_packet($qtarget, $list);
            $list =~ s/.*?\$DATA\$\.?//io;
            foreach ($msg_a, $msg_t) {
                my $redo;
                if ($sock[$sn]->send($_)) {
                    if (! exists $regsock{$sock[$sn]} ) {
                        push @availsock , $sock[$sn];
                        $regsock{$sock[$sn]} = $sock[$sn]->peerhost() . '[:' . $sock[$sn]->peerport().']';
                    }
                    &main::mlog(0,"sending DNS(TXT)-query to $regsock{$sock[$sn]} on $list for $type checks on $target") if $main::RBLLog>=2 && $type eq "RBL" || $main::URIBLLog>=2 && $type eq "URIBL" || $main::RWLLog>=2 && $type eq "RWL" || $main::BacksctrLog>=2 && $type eq "BACKSCATTER";
                } else {
                    close($sock[$sn]);
                    splice(@sock,$sn,1);
                    $redo = 1;
                }
                $sn = 0 if ++$sn >= scalar @sock;
                last unless scalar @sock;
                redo if $redo;
            }
            return "send: $!" if (! scalar @availsock && ! scalar @sock);
        }
      }
    } else {
        foreach my $list(@{ $self->{ lists } }) {
          if (length($qtarget.$list) > 62 && $type ne 'URIBL' && $isip != 2) {
            eval{close($_) if $_;} for (@sock);
            return "domain name too long";
          }
          if ($list && !($type eq 'URIBL' && lc $list eq 'dbl.spamhaus.org' && $isip)) {
              my $msg = mk_packet($qtarget, $list);
              $list =~ s/.*?\$DATA\$\.?//io;
              foreach ($msg,0) {
                  last unless $_;
                  my $redo;
                  if ($sock[$sn]->send($_)) {
                      if (! exists $regsock{$sock[$sn]} ) {
                          push @availsock , $sock[$sn];
                          $regsock{$sock[$sn]} = $sock[$sn]->peerhost() . '[:' . $sock[$sn]->peerport().']';
                      }
                      &main::mlog(0,"sending DNS-query to $regsock{$sock[$sn]} on $list for $type checks on $target") if $main::RBLLog>=2 && $type eq "RBL" || $main::URIBLLog>=2 && $type eq "URIBL" || $main::RWLLog>=2 && $type eq "RWL" || $main::BacksctrLog>=2 && $type eq "BACKSCATTER";
                  } else {
                      close($sock[$sn]);
                      splice(@sock,$sn,1);
                      $redo = 1;
                  }
                  $sn = 0 if ++$sn >= scalar @sock;
                  last unless scalar @sock;
                  redo if $redo;
              }
              return "send: $!" if (! scalar @availsock && ! scalar @sock);
          }
        }
    }
    @sock = @availsock;

    $self->{ results } = {};
    $self->{ txt } = {};

    my $needed = 0;
    if ($self->{ max_replies} > @{ $self->{ lists } }) {
      $needed = @{ $self->{ lists } };
    } else {
      $needed = $self->{ max_replies };
    }

    my $hits = my $replies = 0;

    my $select = IO::Select->new();
    $select->add($_) for @sock;
    my $numsock = scalar @sock;
    # Keep receiving packets until one of the exit conditions is met:
    &main::mlog(0,"Commencing $type checks on '$target'") if $main::RBLLog>=2 && $type eq "RBL" || $main::URIBLLog>=2 && $type eq "URIBL" || $main::RWLLog>=2 && $type eq "RWL" || $main::BacksctrLog>=2 && $type eq "BACKSCATTER";
    my $countansw = 0;
    while ($needed && time < $deadline) {
      my @msg = ();
      my $st = Time::HiRes::time();
      if ($numsock && (my @ready = $select->can_read( $self->{timeout} )) ) {
        map {
            if ($_->recv(my $msg, $self->{udp_maxlen} )) {
                push @msg, $msg;
            } else {
                $select->remove($_);
                close($_);
                $numsock--;
            }
        } @ready;
        return "recv: $!" if (! @msg && ! $numsock);
        next unless @msg;
      } elsif (! $numsock) {
        return "recv: $!";
      } else {
        next; # there are no data on socket -> next loop
      }
      $dur = time - $start_time;
      while (my $msg = shift @msg) {
        $countansw++;
        my ($domain, $res, $type) = decode_packet($self,$msg);
        unless ($domain) {
            $needed --;
            next ;
        }
        next if exists $self->{ results }{ $domain };
        $replies ++;
        if ($res) {
          my $ret = $domain;
          $ret =~ s/^$qtarget\.//;
          push @failed, $ret unless grep(/\Q$ret\E/,@failed);

          $hits ++;
          $self->{ results }{ $domain } = $res;

          if (! $main::Showmaxreplies &&
              ($hits >= $self->{ max_hits } || $replies >= $self->{ max_replies })
             ) {

              $dur = time - $start_time;
              &main::mlog(0,"got $countansw answers, $replies replies and $hits hits after $dur seconds for $type checks on '$target'") if $main::RBLLog>=2 && $type eq "RBL" || $main::URIBLLog>=2 && $type eq "URIBL" || $main::RWLLog>=2 && $type eq "RWL" || $main::BacksctrLog>=2 && $type eq "BACKSCATTER";
              &main::mlog(0,"got OK replies from (@ok) - NOTOK replies from (@failed) for $type on '$target'") if $main::RBLLog>2 && $type eq "RBL" || $main::URIBLLog>2 && $type eq "URIBL" || $main::RWLLog>2 && $type eq "RWL" || $main::BacksctrLog>2 && $type eq "BACKSCATTER";
              eval{close($_) if $_;} for (@sock);
              return 1;
          }
        } else {
            my $ret = $domain;
            $ret =~ s/^$qtarget\.//;
            push @ok, $ret unless grep(/\Q$ret\E/,@ok);
        }
        $needed --;
      }
    }
    $dur = time - $start_time;
    &main::mlog(0,"got $countansw answers, $replies replies and $hits hits after $dur seconds for $type checks on '$target'") if $main::RBLLog>=2 && $type eq "RBL" || $main::URIBLLog>=2 && $type eq "URIBL" || $main::RWLLog>=2 && $type eq "RWL" || $main::BacksctrLog>=2 && $type eq "BACKSCATTER";
    &main::mlog(0,"got OK replies from (@ok) - NOTOK replies from (@failed) for $type on '$target'") if $main::RBLLog>2 && $type eq "RBL" || $main::URIBLLog>2 && $type eq "URIBL" || $main::RWLLog>2 && $type eq "RWL" || $main::BacksctrLog>2 && $type eq "BACKSCATTER";
    &main::mlog(0,"Completed $type checks on '$target'") if $main::RBLLog>=2 && $type eq "RBL" || $main::URIBLLog>=2 && $type eq "URIBL" || $main::RWLLog>=2 && $type eq "RWL" || $main::BacksctrLog>=2 && $type eq "BACKSCATTER";
    eval{close($_) if $_;} for (@sock);
    return 1;
}

sub listed_by {
    my $self = shift;
    sort keys %{ $self->{ results } };
}

sub listed_hash {
    my $self = shift;
    %{ $self->{ results } };
}

sub txt_hash {
    my $self = shift;
    warn <<_ unless $self->{ query_txt };
Without query_txt turned on, you won't get any results from ->txt_hash().
_
    if (wantarray) { %{ $self->{ txt } } }
    else { $self->{ txt } }
}

# End methods - begin internal functions

sub mk_packet {
    # pass me a REVERSED dotted quad ip (qip) and a blocklist domain
    my($qip, $list) = @_;
    my ($packet, $txt_packet, $error);
    my $fqdn;
    if ($list =~ s/\$DATA\$/$qip/io) {     # if a key is required it is in $list
        $fqdn = $list;                    # like key.$DATA$.serviceProvider
    } else {
        $fqdn = "$qip.$list";
    }
    ($packet, $error) = new Net::DNS::Packet( $fqdn , 'A');
    return "Cannot build DNS query for $fqdn, type A: $error" unless $packet;
    return $packet->data unless wantarray;
    ($txt_packet, $error) = new Net::DNS::Packet($fqdn, 'TXT', 'IN');
    return "Cannot build DNS query for $fqdn, type TXT: $error" unless $txt_packet;
    $packet->data, $txt_packet->data;
}

sub decode_packet {
    # takes a raw DNS response packet
    # returns domain, response
    my ($self,$data) = @_;
    my $packet = Net::DNS::Packet->new(\$data);
    my @answer = $packet->answer;
    my @question = $packet->question;
    my $domain = $question[0]->qname;
    $domain =~ s/^.*?\d+\.\d+\.\d+\.\d+\.//o;
    {
        my(%res, $res, $type);
        foreach my $answer (@answer) {
            $type = $answer->type;
            $res{$type} = $type eq 'A'     ? inet_ntoa($answer->rdata)  :
                          $type eq 'CNAME' ? cleanup($answer->rdata)    :
                          $type eq 'TXT'   ? (exists $res{'TXT'} && $res{'TXT'}.'; ')
                                             . $answer->txtdata         :
                          '?';
        }
        $res = $res{'A'} || $res{'CNAME'} || $res{'TXT'};
        $self->{ txt }{ $domain } .= $res{'TXT'} if $res{'TXT'};
        ($res) = $res =~ /(127\.\d+\.\d+\.\d+)/os;
        return $domain, $res, $type if $res;
    }

    # OK, there were no answers -
    # need to determine which domain
    # sent the packet.

    return $domain;
}

sub cleanup {
    # remove control chars and stuff
    $_[ 0 ] =~ tr/a-zA-Z0-9./ /cs;
    $_[ 0 ];
}

sub lists {
    qw(
       bl.spamcop.net
       list.dsbl.org
       zen.spamhaus.org
    );
}
1;
#              end rbl
################################################################################
