Created
November 21, 2011 15:59
-
-
Save mrmt/1383046 to your computer and use it in GitHub Desktop.
libkern.pl from fml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
# | |
# Copyright (C) 1993-2001 Ken'ichi Fukamachi | |
# All rights reserved. | |
# 1993-1996 fukachan@phys.titech.ac.jp | |
# 1996-2001 fukachan@sapporo.iij.ad.jp | |
# | |
# FML is free software; you can redistribute it and/or modify | |
# it under the terms of GNU General Public License. | |
# See the file COPYING for more details. | |
# | |
# $FML: fml.pl,v 2.124.2.6 2001/10/15 12:53:04 fukachan Exp $ | |
##### SubRoutines ##### | |
####### Section: Main Mode Bifurcation | |
# NOT $Envelope{'mode:ctladdr'} IS IMPORTANT; | |
# mode:ctladdr >> mode:distribute, mode:distribute* | |
# Changed a lot at 2.1 DELTA | |
sub ModeBifurcate | |
{ | |
local($command_mode, $member_p, $compat_hml); | |
# Do nothing. Tricky. Please ignore | |
if ($DO_NOTHING) { return 0;} | |
# DataBase Access Probe() | |
# return ASAP if database server is inaccessible. | |
if ($USE_DATABASE) { | |
&use('databases'); | |
# try to probe server | |
my (%mib, %result, %misc, $error); | |
&DataBaseMIBPrepare(\%mib, 'member_p', {'address' => $From_address}); | |
&DataBaseCtl(\%Envelope, \%mib, \%result, \%misc); | |
if ($mib{'error'}) { | |
&Mesg(*Envelope, 'database error occurs', 'configuration_error'); | |
return 0; # return ASAP | |
} | |
} | |
# member ot not? | |
&AdjustActiveAndMemberLists; | |
$member_p = &MailListMemberP($From_address); | |
$member_p = 1 if $Envelope{"trap:+"}; | |
$Envelope{'mode:stranger'} = 1 unless $member_p; | |
# chaddr is available from new address if old to "chaddr" is a member; | |
# hml compatible case (not default) | |
if (!$member_p && $Envelope{'mode:uip:chaddr'}) { | |
&use('utils'); | |
if (&ChAddrModeOK($Envelope{'mode:uip:chaddr'})) { | |
$Envelope{'mode:uip'} = $member_p = 1; | |
} | |
} | |
# hml compatible case (not default) | |
elsif ($CHADDR_AUTH_TYPE eq 'confirmation' && | |
$Envelope{'mode:uip:chaddr-confirm'}) { | |
$Envelope{'mode:uip'} = 1; | |
} | |
# fml 0.x - fml 2.1gamma compat (not default) | |
$compat_hml = &CompatFMLv1P; | |
# default | |
$REJECT_POST_HANDLER = $REJECT_POST_HANDLER || 'Reject'; | |
$REJECT_COMMAND_HANDLER = $REJECT_COMMAND_HANDLER || 'Reject'; | |
%RejectHandler = ("reject", "RejectHandler", | |
"auto_regist", "AutoRegistHandler", | |
"autoregist", "AutoRegistHandler", | |
"ignore", "IgnoreHandler", | |
"auto_subscribe", "AutoRegistHandler", | |
"auto_asymmetric_regist", "AutoRegistHandler", | |
); | |
if ($debug) { | |
&Log("ModeBifurcate: \$PERMIT_POST_FROM $PERMIT_POST_FROM"); | |
&Log("ModeBifurcate: \$PERMIT_COMMAND_FROM $PERMIT_COMMAND_FROM"); | |
&Log("ModeBifurcate: artype $AUTO_REGISTRATION_TYPE"); | |
&Log("ModeBifurcate: key $AUTO_REGISTRATION_KEYWORD"); | |
&Log("ModeBifurcate: member_p $member_p"); | |
} | |
### 00.01 Run Hooks | |
if ($MODE_BIFURCATE_HOOK) { | |
&eval($MODE_BIFURCATE_HOOK, "MODE_BIFURCATE_HOOK"); | |
} | |
### 01: compat_hml mode | |
if ($compat_hml) { | |
&Log("compat_hml mode") if $debug; | |
if (!$Envelope{"compat:cf2:post_directive"} && | |
($Envelope{'mode:req:guide'} || $Envelope{'req:guide'})) { | |
&GuideRequest(*Envelope); # Guide Request from anyone | |
return; # end; | |
} | |
local($ca) = &CutFQDN($CONTROL_ADDRESS); | |
# Default LOAD_LIBRARY SHOULD NOT BE OVERWRITTEN! | |
if ($Envelope{'mode:uip'} && | |
($Envelope{'trap:rcpt_fields'} =~ /$ca/i)) { | |
$command_mode = 1; | |
} | |
} | |
### 02: determine command mode or not | |
if ($Envelope{'mode:ctladdr'} || $COMMAND_ONLY_SERVER) { | |
&Log("\$command_mode = 1;") if $debug; | |
$command_mode = 1; | |
} | |
# BACKWARD COMPATIBLE | |
# when trap the mail body's "# command" syntax but without --ctladdr | |
# at this switch already "!$Envelope{'mode:ctladdr'}" is true | |
# but post=* is exception | |
elsif ($compat_hml && $Envelope{'mode:uip'}) { | |
&Log("backward && uip => command_mode on") if $debug; | |
$command_mode = 1; | |
} | |
# post=* mode and !"ctladdr mode" disables commands | |
if (!$Envelope{"mode:ctladdr"} && | |
$Envelope{"compat:cf2:post_directive"}) { | |
&Log("02 undef \$command_mode = $command_mode;") if $debug; | |
undef $command_mode; | |
} | |
&Log("03 \$command_mode = $command_mode;") if $debug; | |
# initialize Information | |
&GenInfo; | |
### 03: Bifurcate by Condition | |
# Do nothing. Tricky. Please ignore | |
if ($DO_NOTHING) { | |
return 0; | |
} | |
# command mode? | |
elsif ($command_mode) { | |
$Envelope{'pcb:mode'} = 'command'; # process control block | |
# NOT PERMIT COMMAND WHEN MAIL SIZE IS OVER LIMIT. | |
if ($Envelope{'trap:mail_size_overflow'}) { | |
&Log("ModeBifurcate: ignore too bit mail in command mode"); | |
return $NULL; | |
} | |
if ($PERMIT_COMMAND_FROM eq "anyone") { | |
require($LOAD_LIBRARY = $LOAD_LIBRARY || 'libfml.pl'); | |
&Command() if $ForceKickOffCommand; | |
} | |
elsif ($PERMIT_COMMAND_FROM eq "members_only" || | |
$PERMIT_COMMAND_FROM eq "members" | |
) { | |
if ($member_p) { | |
if ($Envelope{'mode:req:unsubscribe-confirm'}) { | |
undef $LOAD_LIBRARY; | |
require 'libfml.pl'; | |
&Command($Envelope{'buf:req:unsubscribe-confirm'}); | |
} | |
else { | |
require($LOAD_LIBRARY = $LOAD_LIBRARY || 'libfml.pl'); | |
&Command() if $ForceKickOffCommand; | |
} | |
} | |
# not member and ignore the mail | |
elsif ((! $member_p) && ($REJECT_COMMAND_HANDLER eq "ignore")) { | |
&Log("ignore request from not member"); | |
} | |
# chaddr-confirm | |
elsif ((! $member_p) && $Envelope{'mode:req:chaddr-confirm'}) { | |
&use('trap'); | |
&Trap__ChaddrConfirm(*Envelope); | |
} | |
# chaddr | |
elsif ((! $member_p) && $Envelope{'mode:req:chaddr'}) { | |
&use('trap'); | |
&Trap__ChaddrRequest(*Envelope); | |
} | |
# we should return reply for "guide" request from even "stranger"; | |
elsif ((! $member_p) && | |
($Envelope{'mode:req:guide'} || $Envelope{'req:guide'})) { | |
&GuideRequest(*Envelope); | |
} | |
# MANUAL REGISTRATION REQUEST WITH CONFIRMATION (subscribe) | |
elsif ((! $member_p) && $Envelope{'mode:req:subscribe'} && | |
&NonAutoRegistrableP) { | |
&Log("manual subscribe request"); | |
&use('confirm'); | |
&ManualRegistConfirm(*Envelope, 'subscribe', | |
$Envelope{'buf:req:subscribe'}); | |
} | |
# MANUAL REGISTRATION REQUEST WITH CONFIRMATION (confirm) | |
elsif ((! $member_p) && $Envelope{'mode:req:confirm'} && | |
&NonAutoRegistrableP) { | |
&Log("manual subscribe confirmed"); | |
&use('confirm'); | |
&ManualRegistConfirm(*Envelope, 'confirm', | |
$Envelope{'buf:req:confirm'}); | |
} | |
else { | |
$fp = $RejectHandler{$REJECT_COMMAND_HANDLER}||"RejectHandler"; | |
&$fp(*Envelope); | |
} | |
} | |
elsif ($PERMIT_COMMAND_FROM eq "moderator") { # dummay ? | |
&use('moderated'); | |
$Envelope{'mode:moderator:command'} = 1; | |
&ModeratedDelivery(*Envelope); # Moderated: check Approval; | |
} | |
else { | |
&Log("ERROR: \$PERMIT_COMMAND_FROM is unknown type."); | |
} | |
} | |
# distribute | |
else { | |
$Envelope{'pcb:mode'} = 'distribute'; # process control block | |
if ($PERMIT_POST_FROM eq "anyone") { | |
&Distribute(*Envelope, 'permit from anyone'); | |
} | |
elsif ($PERMIT_POST_FROM eq "members_only" || | |
$PERMIT_POST_FROM eq "members" | |
) { | |
if ($member_p) { | |
&Distribute(*Envelope, 'permit from members_only'); | |
} | |
else { | |
$fp = $RejectHandler{$REJECT_POST_HANDLER}||"RejectHandler"; | |
&$fp(*Envelope); | |
} | |
} | |
elsif ($PERMIT_POST_FROM eq "moderator") { | |
&use('moderated'); | |
&ModeratedDelivery(*Envelope); # Moderated: check Approval; | |
} | |
else { | |
&Log("ERROR: \$PERMIT_POST_FROM is unknown type."); | |
} | |
# to ensure the unique Date: (since the smallest unit is second). | |
if ($DATE_TYPE =~ /distribute-date/) { sleep 1;} | |
} | |
&LogFileNewSyslog if $LOGFILE_NEWSYSLOG_LIMIT; # log file turn over | |
} | |
####### Section: Main Functions | |
# | |
# Configuration Files: evalucation order | |
# | |
# 1 site_init site default | |
# 2 <ML>/config.ph each ML configurations | |
# 3 sitedef force site-own-rules to overwrite ML configrations | |
# | |
sub LoadConfig | |
{ | |
# configuration file for each ML | |
if (-e "$DIR/config.ph" && ((stat("$DIR/config.ph"))[4] != $<)) { | |
print STDERR "\nFYI: include's owner != config.ph's owner, O.K.?\n\n"; | |
} | |
# 3.0B new loading configuration | |
# XXX &__LoadConfiguration('__KERN__'); for main routine: fml.pl, libkern | |
# XXX &__LoadConfiguration; for other tools: bin/* ... | |
require 'libloadconfig.pl'; &__LoadConfiguration('__KERN__'); | |
require 'libsmtp.pl'; # a library using smtp | |
# load MIME handling functions for convenience | |
if ($LANGUAGE eq 'Japanese') { require 'libMIME.pl';} | |
# if mode:some is set, load the default configuration of the mode | |
for (keys %Envelope) { | |
/^mode:(\S+)/ && $Envelope{$_} && do { &DEFINE_MODE($1);}; | |
} | |
} | |
sub SetDefaults | |
{ | |
$NULL = ''; # useful constant :D | |
$Envelope{'mci:mailer'} = 'ipc'; # use IPC(default) | |
$Envelope{'mode:uip'} = ''; # default UserInterfaceProgram is nil.; | |
$Envelope{'mode:req:guide'} = 0; # not member && guide request only | |
$LOCKFILE = "$$ $DIR"; # (variable name is historical, not meaning) | |
{ # DNS AutoConfigure to set FQDN and DOMAINNAME; | |
local(@n, $hostname, $list); | |
chop($hostname = `hostname`); # beth or beth.domain may be possible | |
$FQDN = $hostname; | |
@n = (gethostbyname($hostname))[0,1]; $list .= " @n "; | |
@n = split(/\./, $hostname); $hostname = $n[0]; # beth.dom -> beth | |
@n = (gethostbyname($hostname))[0,1]; $list .= " @n "; | |
for (split(/\s+/, $list)) { /^$hostname\.\w+/ && ($FQDN = $_);} | |
$FQDN =~ s/\.$//; # for e.g. NWS3865 | |
$DOMAINNAME = $FQDN; | |
$DOMAINNAME =~ s/^$hostname\.//; | |
} | |
# Architecture Dependence; | |
$UNISTD = $HAS_ALARM = $HAS_GETPWUID = $HAS_GETPWGID = $HAS_GETGRENT = 1; | |
# REQUIRED AS DEFAULTS | |
%SEVERE_ADDR_CHECK_DOMAINS = ('or.jp', +1, 'ne.jp', +1); | |
$REJECT_ADDR = 'root|postmaster|MAILER-DAEMON|msgs|nobody'; | |
$REJECT_ADDR .= '|majordomo|listserv|listproc'; | |
$REJECT_ADDR .= '|\S+\-subscribe|\S+\-unsubscribe|\S+\-help'; | |
$SKIP_FIELDS = 'Received|Return-Receipt-To'; | |
$ADD_URL_INFO = $ML_MEMBER_CHECK = $CHECK_MESSAGE_ID = $USE_FLOCK = 1; | |
$NOTIFY_MAIL_SIZE_OVERFLOW = 1; | |
$CHADDR_CONFIRMATION_KEYWORD = 'chaddr-confirm'; | |
$UNSUBSCRIBE_CONFIRMATION_KEYWORD = 'unsubscribe-confirm'; | |
# Envelope Filter | |
$FILTER_ATTR_REJECT_NULL_BODY = $FILTER_ATTR_REJECT_ONE_LINE_BODY = 1; | |
$FILTER_ATTR_REJECT_INVALID_COMMAND = 1; | |
### default distribution and command mode | |
$PERMIT_POST_FROM = $PERMIT_COMMAND_FROM = "members_only"; | |
$REJECT_POST_HANDLER = $REJECT_COMMAND_HANDLER = "reject"; | |
### fmlserv compat code; (e.g. a bit for umask and permissions ctrl) | |
if (-d "$DIR/../fmlserv") { # tricky ;-) | |
$USE_FML_WITH_FMLSERV = 1; | |
$GID = (stat("$DIR/../fmlserv"))[5]; | |
} | |
### Security; default security level (mainly backward compat) | |
undef %Permit; | |
@DenyProcedure = ('library'); | |
@HdrFieldsOrder = # rfc822; fields = ...; Resent-* are ignored; | |
('Return-Path', 'Received', | |
'Delivered-To', # for postfix, qmail | |
'Date', 'Posted', 'X-Posted', 'X-Original-Date', | |
'From', 'Reply-To', 'Subject', 'Sender', | |
'To', 'Cc', 'Errors-To', 'Message-Id', 'In-Reply-To', | |
'References', 'Keywords', 'Comments', 'Encrypted', | |
':XMLNAME:', ':XMLCOUNT:', 'X-MLServer', | |
'XRef', 'X-Stardate', 'X-ML-Info', | |
'X-Mailer', 'X-Dispatcher', 'X-Newsreader', 'User-Agent', | |
'Mail-Followup-To', # I-D now? | |
':body:', ':any:', | |
'X-Authentication-Warning', | |
'Mime-Version', 'Content-Type', 'Content-Transfer-Encoding', | |
'Content-ID', 'Content-Description', # RFC2045 | |
'Precedence', 'Lines'); | |
# Content Filtering Handler for MIME | |
@MailContentHandler = (); # Default: No filter | |
} | |
sub GetTime | |
{ | |
@WDay = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'); | |
@Month = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', | |
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'); | |
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); | |
$Now = sprintf("%02d/%02d/%02d %02d:%02d:%02d", | |
($year % 100), $mon + 1, $mday, $hour, $min, $sec); | |
$MailDate = sprintf("%s, %d %s %d %02d:%02d:%02d %s", | |
$WDay[$wday], $mday, $Month[$mon], | |
1900 + $year, $hour, $min, $sec, | |
$isdst ? $TZONE_DST : $TZone); | |
# /usr/src/sendmail/src/envelop.c | |
# (void) sprintf(tbuf, "%04d%02d%02d%02d%02d", tm->tm_year + 1900, | |
# tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min); | |
# | |
$CurrentTime = sprintf("%04d%02d%02d%02d%02d", | |
1900 + $year, $mon + 1, $mday, $hour, $min); | |
$PCurrentTime = sprintf("%04d%02d%02d%02d%02d%02d", | |
1900 + $year, $mon + 1, $mday, $hour, $min, $sec); | |
} | |
sub InitConfig | |
{ | |
&SetDefaults; | |
&LoadConfig; | |
# XXX SetDefaults but works after &__LoadConfiguration, so defined here. | |
# 3.0.1 compatible with 3.0's "From: $MAIL_LIST" rejection | |
# disabled by fukachan (2000/06/19), PR8220 pointed out by MURASHITA Takuya | |
# &DEFINE_FIELD_LOOP_CHECKED('from'); | |
# $FML for process table readability | |
if ($0 =~ m%^(.*)/(.*)%) { $ExecDir = $1; $FML = $2;} | |
# fml 4.0 modules | |
push(@INC, "$ExecDir/module"); | |
push(@INC, "$ExecDir/module/CPAN"); | |
push(@INC, "$ExecDir/module/Japanese"); | |
push(@INC, "$ExecDir/module/fml-devel"); | |
# a little configuration before the action | |
if (defined $FML_UMASK) { | |
$UMASK = $FML_UMASK; | |
} | |
elsif (defined $UMASK) { | |
; | |
} | |
elsif ($USE_FML_WITH_FMLSERV) { | |
$UMASK = 007; # rw-rw---- | |
} | |
else { | |
$UMASK = 077; # rw------- | |
} | |
umask($UMASK); | |
defined $DEFAULT_DIR_MODE || ($DEFAULT_DIR_MODE = ($UMASK & 0777) ^ 0777); | |
### Against the future loop possibility | |
if (&AddressMatch($MAIL_LIST, $MAINTAINER)) { | |
&Log("DANGER! \$MAIL_LIST == \$MAINTAINER, STOP!"); | |
exit 0; | |
} | |
# set architechture if not defined | |
if (! $COMPA_ARCH) { | |
if ($CPU_TYPE_MANUFACTURER_OS =~ /(sysv4|solaris2)/i) { | |
$COMPAT_ARCH = "SOLARIS2"; | |
} | |
elsif ($CPU_TYPE_MANUFACTURER_OS =~ /windowsnt4$/i) { | |
$COMPAT_ARCH = "WINDOWS_NT4"; | |
} | |
} | |
### Options | |
&SetOpts; | |
# load architecture dependent default | |
# here for command line options --COMPAT_ARCH | |
if ($COMPAT_ARCH) { require "sys/$COMPAT_ARCH/depend.pl";} | |
if ($DUMPVAR) { require 'dumpvar.pl'; &dumpvar('main');} | |
if ($debug) { require 'libdebug.pl';} | |
if ($Opt{"opt:b"} eq 'd') { &use('utils'); &daemon;} # become daemon; | |
# COMPATIBILITY | |
if ($COMPAT_CF1 || ($CFVersion < 2)) { &use('compat_cf1');} | |
if ($CFVersion < 3) { &use('compat_cf2');} | |
if ($COMPAT_FML15) { &use('compat_cf1'); &use('compat_fml15');} | |
if (!$TZone) { $TZone = '+0900';} # TIME ZONE | |
&GetTime; # Time, (may be for compatible codes) | |
push(@MAIL_LIST_ALIASES, @PLAY_TO); | |
unshift(@ARCHIVE_DIR, $ARCHIVE_DIR); | |
### Initialize DIR's and FILE's of the ML server | |
# FullPath-ed (FP) | |
local($s); | |
for (SPOOL_DIR,TMP_DIR,VAR_DIR,VARLOG_DIR,VARRUN_DIR,VARDB_DIR) { | |
&eval("\$s = \$$_; \$s =~ s#\$DIR/##g; \$s =~ s#$DIR/##g;"); | |
&eval("\$FP_$_ = \"$DIR/\$s\";"); | |
&eval("\$$_ =~ s#\$DIR/##g; \$$_ =~ s#\$DIR/##g;"); | |
&eval("-d \$$_||&Mkdir(\$$_);"); | |
} | |
if ($USE_DATABASE) { | |
for ($LOGFILE, $MGET_LOGFILE, | |
$SEQUENCE_FILE, $SUMMARY_FILE, $LOG_MESSAGE_ID) { | |
-f $_ || &Touch($_); | |
} | |
} | |
else { | |
for ($LOGFILE, $MEMBER_LIST, $MGET_LOGFILE, | |
$SEQUENCE_FILE, $SUMMARY_FILE, $LOG_MESSAGE_ID) { | |
-f $_ || &Touch($_); | |
} | |
} | |
### CFVersion 3 | |
### DEFINE INTERNAL FLAG FOR THE USE $DIR/members or $DIR/actives ? | |
### $ML_MEMBER_CHECK is internal variable to indicate file relation | |
local($touch) = "${ACTIVE_LIST}_is_dummy_when_auto_regist"; | |
if (&AutoRegistrableP) { | |
$ML_MEMBER_CHECK = 0; # backward | |
if (&NotUseSeparateListP) { | |
&Touch($touch) if ! -f $touch; | |
} | |
else { | |
unlink $touch if -f $touch; | |
} | |
} | |
else { | |
-f $ACTIVE_LIST || &Touch($ACTIVE_LIST); | |
unlink $touch if -f $touch; | |
} | |
if ($SUBJECT_TAG_TYPE) { | |
&use("tagdef"); | |
&SubjectTagDef($SUBJECT_TAG_TYPE); | |
} | |
### END CFVersion 3 | |
### misc | |
$LOG_MESSAGE_ID = $LOG_MESSAGE_ID || "$VARRUN_DIR/msgidcache";#important; | |
$LOG_MAILBODY_CKSUM = $LOG_MAILBODY_CKSUM || "$VARRUN_DIR/bodycksumcache"; | |
$REJECT_ADDR_LIST = $REJECT_ADDR_LIST || "$DIR/spamlist"; | |
$FML .= "[".(split(/\@/, $MAIL_LIST))[0]."]"; # For tracing Process Table | |
# initialize some arrays; if auto-regist is clear here, we reset; | |
&AdjustActiveAndMemberLists; | |
# since variables are defined in config.ph; | |
@NEWSYSLOG_FILES = @NEWSYSLOG_FILES || | |
("$MSEND_RC.bak", "$MEMBER_LIST.bak", "$ACTIVE_LIST.bak"); | |
# struct sockaddr { ;} | |
$STRUCT_SOCKADDR = $STRUCT_SOCKADDR || "S n a4 x8"; | |
# &BackwardCompat; | |
&DEFINE_MODE('expire') if $USE_EXPIRE; # 2.1 release built-in expire | |
&DEFINE_MODE('archive') if $USE_ARCHIVE; # 2.1 release built-in archive; | |
&DEFINE_MODE('html') if $AUTO_HTML_GEN; | |
# command trap keywrod : '# ' ; in default, we not use it. | |
# XXX: "# command" is internal represention | |
# XXX: it is historical, so remove '# command' part if exist and possible. | |
$Envelope{'trap:ctk'} = &CompatFMLv1P ? '# ' : ''; | |
# signal handling | |
$SIG{'ALRM'} = 'Tick'; | |
$SIG{'INT'} = $SIG{'QUIT'} = $SIG{'TERM'} = 'SignalLog'; | |
# MIME Content Handler(include backword compatible) | |
if ($AGAINST_HTML_MAIL || | |
$HTML_MAIL_DEFAULT_HANDLER eq 'strip') { | |
&ADD_CONTENT_HANDLER('multipart/.*', 'text/plain', 'allow'); | |
&ADD_CONTENT_HANDLER('multipart/.*', '.*/.*', 'strip'); | |
&ADD_CONTENT_HANDLER('text/plain', '.*/.*', 'allow'); | |
&ADD_CONTENT_HANDLER('!MIME', '.*/.*', 'allow'); | |
} elsif ($HTML_MAIL_DEFAULT_HANDLER eq 'reject') { | |
&ADD_CONTENT_HANDLER('multipart/.*', 'text/plain', 'allow'); | |
&ADD_CONTENT_HANDLER('multipart/.*', '.*/.*', 'reject'); | |
&ADD_CONTENT_HANDLER('text/plain', '.*/.*', 'allow'); | |
&ADD_CONTENT_HANDLER('!MIME', '.*/.*', 'allow'); | |
} | |
# For example, $LOGFILE_SUFFIX = ".%C%y"; | |
if ($LOGFILE_SUFFIX) { | |
use POSIX; | |
$LOGFILE .= strftime($LOGFILE_SUFFIX, localtime(time)); | |
} | |
} | |
# one pass to cut out the header and the body | |
sub Parsing { &Parse;} | |
sub Parse | |
{ | |
$0 = "${FML}: Parsing header and body <$LOCKFILE>"; | |
local($bufsiz, $buf, $p, $maxbufsiz, $in_header); | |
$maxbufsiz = &ATOI($INCOMING_MAIL_SIZE_LIMIT) if $INCOMING_MAIL_SIZE_LIMIT; | |
undef $Envelope{'Body'}; | |
$in_header = 1; # firstly header comes. | |
while ($p = sysread(STDIN, $_, 1024)) { | |
$bufsiz += $p; | |
if ($INCOMING_MAIL_SIZE_LIMIT && ($bufsiz > $maxbufsiz)) { | |
$Envelope{'trap:mail_size_overflow'} = 1; | |
last; | |
} | |
$Envelope{'Body'} .= $_; | |
if ($in_header) { | |
# separator between header and body is found! | |
if (($p = index($Envelope{'Body'}, "\n\n", 0)) > 0) { | |
$Envelope{'Header'} = substr($Envelope{'Body'}, 0, $p + 1); | |
$Envelope{'Body'} = substr($Envelope{'Body'}, $p + 2); | |
$in_header = 0; | |
} | |
} | |
} | |
# oops, header only mail ?! (2000/05/31 by fukachan) | |
if ($in_header) { | |
$Envelope{'Header'} = $Envelope{'Body'}; | |
undef $Envelope{'Body'}; | |
} | |
# Really? but check "what happen if no input is given?". | |
if ($bufsiz == 0) { | |
&Log("read 0 bytes, stop"); | |
exit(0); | |
} | |
$Envelope{'tmp:bufsiz'} = $bufsiz; | |
} | |
# Phase 2 extract several fields | |
sub GetFieldsFromHeader | |
{ | |
local($field, $value, @hdr, %hf); | |
local($s); | |
$0 = "${FML}: GetFieldsFromHeader <$LOCKFILE>"; | |
# To ensure non exsistence | |
for (split(/\|/, $SKIP_FIELDS)) { &DELETE_FIELD($_);} | |
# pass all fields through | |
if ($SUPERFLUOUS_HEADERS || $PASS_ALL_FIELDS_IN_HEADER) { | |
$hdr_entry = join("|", @HdrFieldsOrder); | |
} | |
### Header Fields Extraction | |
$s = $Envelope{'Header'}."\n"; | |
# $* = 0; # match one line | |
if ($s =~ /^From\s+(\S+)/is) { | |
# XXX NO REACH HERE (1999/10/27 by fukachan) | |
# $Envelope{'UnixFrom'} = $UnixFrom = $1; | |
$s =~ s/^From\s+.*//i; | |
} | |
$s = "\n".$s; # tricky | |
$s =~ s/\n(\S+):/\n\n$1:\n\n/g; # trick for folding and unfolding. | |
$s =~ s/^\n*//; # remove the first null lines; | |
@hdr = split(/\n\n/, $s); | |
while (@hdr) { | |
$_ = $field = shift @hdr; | |
last if /^[\s\n]*$/; # null field must be end | |
$value = shift @hdr; # not cut the first spaces of $value | |
print STDERR "FIELD>$field<\n >$value<\n" if $debug; | |
# Save Entry anyway. '.=' for multiple 'Received:' | |
$field =~ tr/A-Z/a-z/; | |
$hf{$field} = 1; | |
$Envelope{$field} .= $Envelope{$field} ? "\n${_}$value" : $value; | |
# e.g. ONLY multiple Received: are possible. | |
# "Cc: ..\n Cc: ..\n" also may exist | |
$Envelope{"h:$field"} .= | |
$Envelope{"h:$field"} ? "\n${_}$value" : $value; | |
next if /^($SKIP_FIELDS):/i; | |
# hold fields without in use_fields if $SUPERFLUOUS_HEADERS is 1. | |
if ($SUPERFLUOUS_HEADERS || $PASS_ALL_FIELDS_IN_HEADER) { | |
next if /^($hdr_entry)/i; # :\w+: not match | |
$Envelope{'Hdr2add'} .= "${_}$value\n"; | |
} | |
} | |
### Anyway set all the fields (96/09/24) ### | |
local($f, $fc, $k, $v, $x); | |
while (($k, $v) = each %hf) { | |
$_ = &FieldCapitalize($k); | |
$Envelope{"h:$_"} = $Envelope{"h:$k"}; | |
} | |
### fix Unix From | |
if (! $Envelope{'UnixFrom'}) { # == !$UnixFrom | |
$UnixFrom = $Envelope{'h:return-path:'} || $Envelope{'h:From:'} || | |
"postmaster\@$FQDN"; | |
$UnixFrom = $Unix_From = $Envelope{'UnixFrom'} = | |
&Conv2mailbox($UnixFrom, *Envelope); | |
} | |
} | |
# LATTER PART is to fix extracts | |
# Set variables to need special effects | |
sub FixHeaderFields | |
{ | |
local(*e) = @_; | |
local($addr); | |
### MIME: IF USE_LIBMIME && MIME-detected; | |
if ($USE_MIME && $e{'Header'} =~ /=\?ISO\-2022\-JP\?/io) { | |
$e{'MIME'}= 1; | |
} | |
### $MAIL_LIST Aliases | |
$addr = "$e{'h:recent-to:'}, $e{'h:to:'}. $e{'h:cc:'}"; | |
for (@MAIL_LIST_ALIASES) { | |
next unless $_; | |
if ($addr =~ /$_/i) { $MAIL_LIST = $_;} | |
} | |
$e{'h:Return-Path:'} = "<$MAINTAINER>"; # needed? | |
$e{'h:Precedence:'} = $PRECEDENCE || 'list'; | |
# $e{'h:Lines:'} = $e{'nlines'}; now in CheckCurrentProc (97/12/07) | |
# Date: field type definition | |
if ($DATE_TYPE eq 'original-date') { | |
$e{'h:Date:'} = $e{'h:date:'}; | |
} | |
elsif ($DATE_TYPE eq 'received-date+x-posted') { | |
$e{'h:Date:'} = $MailDate; | |
$e{'h:X-Posted:'} = $e{'h:date:'} || $e{'h:Date:'}; | |
} | |
elsif ($DATE_TYPE eq 'received-date+x-original-date') { | |
$e{'h:Date:'} = $MailDate; | |
$e{'h:X-Original-Date:'} = $e{'h:date:'} || $e{'h:Date:'}; | |
} | |
elsif (($DATE_TYPE eq 'received-date') || | |
($DATE_TYPE eq 'received-date+posted') || | |
(!$DATE_TYPE)) { # default (backward) | |
$e{'h:Date:'} = $MailDate; | |
$e{'h:Posted:'} = $e{'h:date:'} || $e{'h:Date:'}; | |
} | |
# Some Fields need to "Extract the user@domain part" | |
# Addr2Reply: is used to pass to sendmail as the recipient | |
$From_address = &Conv2mailbox($e{'h:from:'}, *e); | |
$e{'macro:x'} = $e{'tmp:x'}; | |
&Log("Gecos [$e{'macro:x'}]") if $debug; | |
# XXX should we nuke $COMMAND_RETURN_ADDR_POLICY ? | |
if ($COMMAND_RETURN_ADDR_POLICY eq 'from' || | |
$MESSAGE_RETURN_ADDR_POLICY eq 'from') { | |
$e{'Addr2Reply:'} = $From_address; | |
} | |
else { # $MESSAGE_RETURN_ADDR_POLICY eq 'reply-to' | |
$e{'Addr2Reply:'} = &Conv2mailbox($e{'h:reply-to:'},*e)||$From_address; | |
} | |
# KAKUSHI(SECRET) OPTION :) (UNDER DEVELOPMENT) | |
# use Return-Path: as the sender for authentication | |
if ($SENDER_AUTH_TYPE eq 'strict-envelope-from') { | |
$_ = &Conv2mailbox($Envelope{'h:return-path:'}, *e); | |
if ($_) { | |
$From_address = $_; | |
} | |
else { | |
&Log("\$SENDER_AUTH_TYPE eq 'strict-envelope-from'"); | |
&Log("INVALID Return-Path:<$_>"); | |
&Mesg(*e, "YOU ARE NOT A MEMBER!"); | |
&Mesg(*e, $NULL, 'not_member'); | |
$DO_NOTHING = 1; | |
} | |
} | |
elsif ($SENDER_AUTH_TYPE eq 'envelope-from-or-from') { | |
for ($UnixFrom, $From_address) { | |
&MailListMemberP($_) && ($From_address = $_, last); | |
} | |
} | |
# To: $MAIL_LIST for readability; | |
# &RewriteField(*e, 'Cc') unless $NOT_REWRITE_CC; | |
if ($REWRITE_TO < 0) { | |
; # pass through for the use of this flag when $CFVersion < 3.; | |
} | |
elsif ($CFVersion < 3.1) { # 3.1 is 2.1A#8 (1997/10/14) | |
$REWRITE_TO = $NOT_REWRITE_TO ? 0 : 1; # 2.1 release default; | |
} | |
&Log("REWRITE_TO $REWRITE_TO") if $debug; | |
if ($REWRITE_TO == 2) { | |
$e{'h:To:'} = "$MAIL_LIST $ML_FN"; # force the original To: to pass | |
} | |
elsif ($REWRITE_TO == 1) { | |
# To: $MALI_LIST, plus original To: ... | |
$e{'h:To:'} = "$MAIL_LIST $ML_FN"; | |
&RewriteField(*e, 'To'); | |
} | |
elsif ((!$REWRITE_TO) || $REWRITE_TO < 0) { | |
; # do nothing, pass through | |
} | |
# Subject: | |
# 1. remove [Elena:id] | |
# 2. while ( Re: Re: -> Re: ) (THIS IS REQUIED ANY TIME, ISN'T IT? but...) | |
# Default: not remove multiple Re:'s), | |
# which actions may be out of my business | |
if ($_ = $e{'h:Subject:'}) { | |
if ($STRIP_BRACKETS || | |
$SUBJECT_FREE_FORM_REGEXP || $SUBJECT_HML_FORM) { | |
if ($e{'MIME'}) { # against cc:mail ;_; | |
&use('MIME'); | |
&StripMIMESubject(*e); | |
} | |
else { # e.g. Subject: [Elena:003] E.. U so ...; | |
print STDERR "IN: $_\n" if $debug; | |
$e{'h:Subject:'} = &StripBracket($_); | |
print STDERR "OUT: $e{'h:Subject:'}\n" if $debug; | |
} | |
} | |
# Even if pass through, always strip of Re:* | |
else { | |
$e{'h:Subject:'} = &CutOffRe($_); | |
} | |
} | |
# Obsolete Errors-to:, against e.g. BBS like a nifty | |
if ($USE_ERRORS_TO) { | |
$e{'h:Errors-To:'} = $ERRORS_TO || $MAINTAINER; | |
} | |
else { # delete obsolete fields; | |
&DELETE_FIELD('Errors-To'); | |
} | |
# Set Control-Address for reply, notify and message | |
$e{'CtlAddr:'} = &CtlAddr; | |
### USER MACROS: ©_FIELD(old, new); | |
local($old, $new); | |
while (($old,$new) = each %HdrFieldCopy) { | |
&Debug("COPY_FIELD: \$e{\"h: $old => $new\"}") if $debug; | |
$e{"h:$new:"} = $e{"h:$old:"}; | |
$e{"h:$new:"} =~ s/\n$old:/\n$new:/gi; | |
} | |
} | |
sub FieldCapitalize | |
{ | |
local($_) = @_; | |
s/^(\w)/ $x = $1, $x =~ tr%a-z%A-Z%, $x/e; | |
s/(\-\w)/$x = $1, $x =~ tr%a-z%A-Z%, $x/eg; | |
$_ =~ s/^X-ML-/X-ML-/i; # X-ML- is an exception. to avoid dup of X-M{L,;} | |
$_; | |
} | |
sub StripBracket | |
{ | |
local($_) = @_; | |
local($pat); | |
if ($SUBJECT_FREE_FORM_REGEXP) { | |
$pat = $SUBJECT_FREE_FORM_REGEXP; | |
} | |
else { # default; | |
# pessimistic ? | |
if (! $BRACKET) { ($BRACKET) = split(/\@/, $MAIL_LIST);} | |
$pat = "\\[$BRACKET:\\d+\\]"; | |
} | |
# cut out all the e.g. [BRACKET:\d] form; | |
s/$pat//g; | |
$_ = &CutOffRe($_); | |
} | |
sub CutOffRe | |
{ | |
local($_) = @_; | |
local($pattern); | |
# Re: Re2: Re[2]: Re(2): Re^2: Re*2: | |
$pattern = 'Re:|Re\d+:|Re\[\d+\]:|Re\(\d+\):|Re\^\d+:|Re\*\d+:'; | |
s/^(\s*($pattern)\s*)+/Re: /oi; | |
if ($LANGUAGE eq 'Japanese') { | |
require("module/$LANGUAGE/liblangdep.pl"); | |
$_ = &Japanese'CutOffReReRe($_); | |
} | |
$_; | |
} | |
sub CheckCurrentProc | |
{ | |
local(*e, $ccp_mode) = @_; | |
# log | |
&Log("read $e{'tmp:bufsiz'} bytes") if $debug_fml_org; | |
undef $e{'tmp:bufsiz'}; | |
### SubSection: Log socket connection info | |
&eval("&use('kernsubr2'); &GetPeerInfo;") if $LOG_CONNECTION; | |
### SubSection: Check Body Contents (For Command Mode) | |
local($limit, $p, $buf, $boundary, $nclines, $cc); | |
### SubSection: MIME info | |
# MIME skip mode; against automatic-MIME-encapsulated fool MUA | |
if ($e{'h:content-type:'} =~ /boundary=\"(.*)\"/i || | |
$e{'h:content-type:'} =~ /boundary=\s*(\S+)/i) { | |
$boundary = $1; | |
$boundary = "--$boundary"; | |
$e{'MIME:boundary'} = $boundary; | |
} | |
elsif ($e{'h:content-type:'} =~ /multipart/i) { | |
&Log("cannot get boundary string of Content-Type"); | |
&Log("Content-Type: $e{'h:content-type:'}"); | |
} | |
### SubSection: Check command in mail body | |
# the range to scan | |
$limit = $GUIDE_CHECK_LIMIT > $COMMAND_CHECK_LIMIT ? | |
$GUIDE_CHECK_LIMIT : $COMMAND_CHECK_LIMIT; | |
## QMAIL command hook | |
## dot-qmail(5) ~alias/.uja-default emulates uja-help@domain ("-> #help") | |
if ($USE_DOT_QMAIL_EXT && | |
(!&AddressMatch($MAIL_LIST, $ENV{'RECIPIENT'}))) { | |
&Log("sets in dot-qmail-ext") if $debug_qmail; | |
&use('qmail'); | |
&DotQmailExt(*Envelope); | |
} | |
# search the location of $limit's "\n"; | |
$limit += 10; # against MIME | |
$p = 0; | |
while ($limit-- > 0) { | |
if (index($e{'Body'}, "\n", $p + 1) > 0) { | |
$p = index($e{'Body'}, "\n", $p + 1); | |
} | |
else { | |
last; | |
} | |
} | |
# +1 for the last "\n"; | |
$buf = substr($e{'Body'}, 0, $p > 0 ? $p+1 : 1024); | |
# check only the first $limit lines. | |
local($found, $mime_skip); | |
for (split(/\n/, $buf)) { | |
print STDERR "INPUT BUF> $_\n" if $debug; | |
# subscribe trap | |
# XXX: "# command" is internal represention | |
# XXX: remove '# command' part if exist | |
if (/^(\s*|\#\s*)$CONFIRMATION_SUBSCRIBE\s+/i) { | |
$e{'mode:req:subscribe'} = 1; | |
$e{'buf:req:subscribe'} .= $_."\n"; | |
} | |
if ($CHADDR_AUTH_TYPE eq 'confirmation' && | |
(/^(\s*|\#\s*)$CHADDR_KEYWORD\s+/i)) { | |
$e{'mode:req:chaddr'} = 1; | |
$e{'buf:req:chaddr'} .= $_."\n"; | |
} | |
# chaddr-confirm trap (may be with citatin e.g. ">") | |
if ($CHADDR_AUTH_TYPE eq 'confirmation' && | |
/$CHADDR_CONFIRMATION_KEYWORD\s+\S+/i) { | |
$e{'mode:req:chaddr-confirm'} = 1; | |
$e{'buf:req:chaddr-confirm'} .= $_."\n"; | |
} | |
# confirm trap (may be with citatin e.g. ">") | |
elsif (/$CONFIRMATION_KEYWORD\s+\S+/i) { | |
$e{'mode:req:confirm'} = 1; | |
$e{'buf:req:confirm'} .= $_."\n"; | |
} | |
# unsubscribe-confirm ID | |
if (/($UNSUBSCRIBE_CONFIRMATION_KEYWORD\s+\S+.*)/i) { | |
$e{'buf:req:unsubscribe-confirm'} .= $1."\n"; | |
$e{'mode:req:unsubscribe-confirm'} = 1; | |
} | |
if ($boundary) { # if MIME skip mode; | |
if ($_ eq $boundary) { $found++; $mime_skip++; next;} | |
if (/^Content-Type:/i && $mime_skip) { next;} | |
# skip the null line after the first MIME separator | |
if ($mime_skip) { $mime_skip = 0; next;} | |
} | |
# skip before the first MIME boundary | |
next if $boundary && !$found; | |
$cc++; | |
print STDERR " SCAN BUF> $_ ($cc line)\n\n" if $debug; | |
# DO NOT "skip useless checks (2.23?)" | |
# which we uses in the "guide" request check from a stranger. | |
# if (! $e{'trap:ctk'}) { | |
# print STDERR " -skip fml rel. 1 compatible scan\n" if $debug; | |
# next; | |
# } | |
# Guide Request from the unknown | |
if ($GUIDE_CHECK_LIMIT-- > 0) { | |
$e{'mode:req:guide'} = 1 if /^\#\s*$GUIDE_KEYWORD\s*$/i; | |
# accept 'guide' under --ctladdr; | |
$e{'mode:req:guide'} = 1 | |
if $e{'mode:ctladdr'} && /^\s*$GUIDE_KEYWORD\s*$/i; | |
} | |
# Command or not is checked within the first 3 lines. | |
# '# help\s*' is OK. '# guide"JAPANESE"' & '# JAPANESE' is NOT! | |
# BUT CANNOT JUDGE '# guide "JAPANESE CHARS"' SYNTAX;-); | |
if ($COMMAND_CHECK_LIMIT-- > 0) { | |
$e{'mode:uip'} = 'on' if /^\#\s*[\w\-]+\s|^\#\s*[\w\-]+$/; | |
$e{'mode:uip:chaddr'} = $_ | |
if /^\#\s*($CHADDR_KEYWORD)\s+/i; | |
$e{'mode:uip:chaddr-confirm'} = $_ | |
if /^\#\s*($CHADDR_CONFIRMATION_KEYWORD)\s+/i; | |
} | |
$nclines++ if /^\#/o; # the number of command lines | |
} | |
### close(STDIN); # close(STDIN) prevents open2, really? | |
$e{'nlines'} = ($e{'Body'} =~ tr/\n/\n/); | |
$e{'nclines'} = $nclines; | |
$e{'size'} = $bufsiz; | |
$e{'h:Lines:'} = $e{'nlines'}; | |
### SubSection: special trap | |
return 0 if $CheckCurrentProcUpperPartOnly; | |
return 0 if $ccp_mode eq 'upper_part_only'; | |
### SubSection: MailBody Size | |
if ($e{'trap:mail_size_overflow'}) { | |
&use('error'); | |
&NotifyMailSizeOverFlow(*e); | |
if ($ANNOUNCE_MAIL_SIZE_OVERFLOW) { | |
&AnnounceMailSizeOver(*e); # call &Distribute; | |
} | |
else { | |
$DO_NOTHING = 1; | |
return $NULL; | |
} | |
} | |
# Against a lot of mails for MIME partial, e.g. Outlook | |
# Content-Type: message/partial; number=1; total=6; ... | |
if ($e{'h:content-type:'} =~ /\/partial\s*;/ && | |
$INCOMING_MAIL_SIZE_LIMIT) { | |
local($n, $total, $bufsiz); | |
$e{'h:content-type:'} =~ s/number=(\d+)/$n = $1/e; | |
$e{'h:content-type:'} =~ s/total=(\d+)/$total = $1/e; | |
$bufsiz = length($Envelope{'Body'}) * $total; | |
if ($bufsiz > &ATOI($INCOMING_MAIL_SIZE_LIMIT)) { | |
&Log("reject for too large size mail"); | |
&Log("partial message's <$n/$total> total mail size seems too large"); | |
&Log("evaluated whole size $bufsiz > \$INCOMING_MAIL_SIZE_LIMIT[$INCOMING_MAIL_SIZE_LIMIT]"); | |
# WARNING in n ==1 partial case. | |
if ($n == 1 && $NOTIFY_MAIL_SIZE_OVERFLOW) { | |
&use('error'); | |
&NotifyMailSizeOver; | |
} | |
if ($n == 1 && $ANNOUNCE_MAIL_SIZE_OVERFLOW) { | |
&use('error'); | |
&AnnounceMailSizeOver(*e); # call &Distribute; | |
} | |
else { | |
$DO_NOTHING = 1; | |
} | |
} | |
else { | |
&Log("partial message but the whole size seems enough small") | |
if $debug; | |
} | |
} | |
### SubSection: Access Control based on address | |
## WE SHOULD REJCECT "CANNOT IDENTIFIED AS PERSONAL" ADDRESSES; | |
## In addition, we check another atack possibility; | |
## e.g. majorodmo,listproc,list-subscribe <-> fml-ctl | |
if ($REJECT_ADDR && $From_address =~ /^($REJECT_ADDR)\@(\S+)/i) { | |
local($addr, $domain) = ($1, $2); | |
&Log("reject mail from $addr\@$domain"); | |
&WarnE("reject mail from $addr\@$domain", | |
"reject mail from $addr\@$domain\n"); | |
$DO_NOTHING = 1; | |
return 0; | |
} | |
# XXX reject all "From: MAIL_LIST" mails (3.0) | |
# XXX fix for 3.0.1 | |
# XXX controllable by %LOOP_CHECKED_HDR_FIELD. | |
if (%LOOP_CHECKED_HDR_FIELD) { | |
my($f, $v); | |
for $f (keys %LOOP_CHECKED_HDR_FIELD) { | |
next unless $LOOP_CHECKED_HDR_FIELD{$f}; | |
if ($v = $e{"h:${f}:"}) { | |
$v = &Conv2mailbox($v); | |
if (&LoopBackWarn($v)) { | |
&Log("$f: <$v> may cause loop. rejected"); | |
&WarnE("reject mail $f:<$v>", | |
"rejected since '$f' header\n". | |
"may cause mail loop.\n". | |
"${f}: ". | |
$e{"h:${f}:"}. | |
"\n"); | |
$DO_NOTHING = 1; | |
return 0; | |
} | |
} | |
} | |
} | |
## security level (?) | |
while (($k, $v) = each %SEVERE_ADDR_CHECK_DOMAINS) { | |
print STDERR "/$k/ && ADDR_CHECK_MAX += $v\n" if $debug; | |
($From_address =~ /$k/) && ($ADDR_CHECK_MAX += $v); | |
} | |
# AGAINST SPAM MAILS | |
if (-f $REJECT_ADDR_LIST) { | |
if (&RejectAddrP($From_address) || | |
&RejectAddrP($UnixFrom)) { | |
$s="Reject spammers: UnixFrom=[$UnixFrom], From=[$From_address]"; | |
&WarnE("Spam mail from a spammer is rejected $ML_FN", | |
"Reject Spammers:\n". | |
" UnixFrom\t$UnixFrom\n From\t\t$From_address\n"); | |
&Log($s); | |
$DO_NOTHING = 1; | |
return 0; | |
} | |
} | |
### SubSection: misc | |
### For CommandMode Check(see the main routine in this flie) | |
$e{'trap:rcpt_fields'} = $e{'h:to:'} || $e{'h:apparently-to:'}; | |
$e{'trap:rcpt_fields'} .= ", $e{'h:Cc:'}, "; | |
$e{'trap:rcpt_fields'} =~ s/\n(\s+)/$1/g; | |
# SUBJECT: GUIDE SYNTAX | |
if ($USE_SUBJECT_AS_COMMANDS && $e{'h:Subject:'}) { | |
local($_) = $e{'h:Subject:'}; | |
s/^\s*//; | |
$e{'mode:req:guide'}++ if /^\#\s*$GUIDE_KEYWORD\s*$/i; | |
$e{'mode:uip'} = 'on' if /^\#\s*[\w\-]+\s|^\#\s*[\w\-]+$/; | |
$e{'mode:req:guide'}++ | |
if $COMMAND_ONLY_SERVER && /^\s*$GUIDE_KEYWORD\s*$/i; | |
$e{'mode:uip'} = 'on' | |
if $COMMAND_ONLY_SERVER && /^\s*[\w\-]+\s|^\s*[\w\-]+$/; | |
} | |
# ? for --distribute, here and again in &MLMemberCheck; | |
&AdjustActiveAndMemberLists; | |
### SubSection: debug info dump | |
if ($debug) { &eval(&FieldsDebug, 'FieldsDebug');} | |
### SubSection: Mail Loop Check | |
## LOOP CHECK PHASE 1: Message-Id | |
if ($CHECK_MESSAGE_ID && &DupMessageIdP) { exit 0;} | |
## LOOP CHECK PHASE 2 | |
# now before flock(); | |
if ((! $NOT_USE_UNIX_FROM_LOOP_CHECK) && | |
&AddressMatch($UnixFrom, $MAINTAINER)) { | |
&Log("WARNING: UNIX FROM Loop[$UnixFrom == $MAINTAINER]"); | |
&WarnE("WARNING: UNIX FROM Loop", | |
"UNIX FROM[$UnixFrom] == MAINTAINER[$MAINTAINER]\n\n"); | |
exit 0; | |
} | |
### SubSection: Address Test Mode; (Become Test Mode) | |
if ($Opt{"opt:b"} eq 't') { | |
$DO_NOTHING = 1; &Log("Address Test Mode:Do nothing"); | |
} | |
### SubSection: Crosspost emulation | |
# Check crosspost in To: and Cc: | |
if ($USE_CROSSPOST) { &use('crosspost');} | |
} | |
# We REWRITED To: to "To: MAIL_LIST" FOR MORE READABILITY; | |
# Check the To: and overwrite it; | |
# if To: has $MAIL_LIST, ok++; IF NOT, add $MAIL_LIST to To: | |
sub RewriteField | |
{ | |
local(*e, $ruleset) = @_; | |
local($f) = 'RuleSetTo' if $ruleset eq 'To'; | |
$f ? &$f(*e) : &Log("RewriteField: unknown ruleset $ruleset"); | |
} | |
# 2000/11/17 version is modified to use Mail::Address.pm | |
# based on the patch by OGAWA Kunihiko <kuni@edit.ne.jp> | |
sub RuleSetTo | |
{ | |
local(*e) = @_; | |
local($ok, $match, $addr, $x_addr, $ml, $buf, $to); | |
local(@ml) = ($MAIL_LIST, @MAIL_LIST_ALIASES); # PLAY_TO Trick; | |
eval "require Mail::Address; Mail::Address->import();"; | |
# if fails, call fml 3.0 compatible routine. | |
if ($@ ne '') { | |
&Log("WARN: Mail::Address.pm not works well"); | |
&Log("disable RuleSetTo function"); | |
return $NULL; | |
} | |
# search $MALI_LIST (+ aliases) contained in To: ? | |
$to = $e{'h:to:'}; | |
$to =~ s/\s*To: /, /gi; | |
for $addr (Mail::Address->parse($to)) { | |
$x_addr = $addr->format; | |
$addr = $addr->address; | |
$match = 0; | |
# match one of $MALI_LIST ? | |
for $ml (@ml) { | |
# ignore $MALI_LIST (or aliases) addresses to avoid noisy To;) | |
if (&AddressMatch($addr, $ml)) { $ok++; $match = 1;} | |
} | |
# IF NOT match one of $MAIL_LIST, | |
# gobble not $MALI_LIST address to $buf | |
if (! $match) { | |
$buf .= $buf ? ", ". $addr : $addr; | |
&Log("RuleSetTo: buf = {$buf}") if $debug; | |
} | |
} | |
if ($buf) { | |
$e{'h:To:'} .= $e{'h:To:'} ? "\n\t,".$buf : $buf; | |
} | |
} | |
# Expand mailbox in RFC822 | |
# From_address is user@domain syntax for e.g. member check, logging, commands | |
# return "1#mailbox" form ?(anyway return "1#1mailbox" 95/6/14) | |
# | |
# macro:x is moved to FixHeaderFields (97/05/07 fukui@sonic.nm.fujitsu.co.jp) | |
# | |
# 2000/11/17 version is modified to use Mail::Address.pm | |
# based on the patch by OGAWA Kunihiko <kuni@edit.ne.jp> | |
sub Conv2mailbox | |
{ | |
local($mb, *e) = @_; # original string | |
local($addr); | |
eval "require Mail::Address; Mail::Address->import();"; | |
# if fails, call fml 3.0 compatible routine. | |
if ($@ ne '') { | |
&Log($@); | |
&Log("WARN: Mail::Address.pm not works well"); | |
return &__Conv2mailbox30($mb, *e); | |
} | |
# NULL is given, return NULL | |
($mb =~ /^\s*$/) && (return $NULL); | |
($addr) = (Mail::Address->parse($mb))[0]; | |
return $NULL unless $addr; | |
$e{'tmp:x'} = $addr->phrase . $addr->comment; | |
return $addr->address; | |
} | |
sub __Conv2mailbox30 | |
{ | |
local($mb, *e) = @_; # original string | |
# return NULL if addr does not contain @. ? | |
# return $NULL unless $mb =~ /\@/; | |
# $mb = &Strip822Comments($mb); | |
# NULL is given, return NULL | |
($mb =~ /^\s*$/) && (return $NULL); | |
# RFC822 unfolding and cut the first SPACE|HTAB; | |
$mb =~ s/\n(\s+)/$1/g; | |
$mb =~ s/^\s*//; | |
# Hayakawa Aoi <Aoi@aoi.chan.panic> | |
if ($mb =~ /^\s*(.*)\s*<(\S+)>.*$/io) { $e{'tmp:x'} = $1; return $2;} | |
# Aoi@aoi.chan.panic (Chacha Mocha no cha nu-to no 1) | |
if ($mb =~ /^\s*(\S+)\s*\((.*)\)$/io || $mb =~ /^\s*(\S+)\s*(.*)$/io) { | |
$e{'tmp:x'} = $2, return $1; | |
} | |
# Aoi@aoi.chan.panic | |
return $mb; | |
} | |
# When just guide request from unknown person, return the guide only | |
# change reply-to: for convenience | |
sub GuideRequest | |
{ | |
local(*e) = @_; | |
local($ap); | |
if ($debug) { @c=caller; &Log("GuideRequest called from $c[2]");} | |
&Log("Guide request"); | |
$e{'GH:Reply-To:'} = $e{'CtlAddr:'}; | |
&SendFile($e{'Addr2Reply:'}, "Guide $ML_FN", $GUIDE_FILE); | |
} | |
# MAIL_LIST == CONTROL_ADDRESS or !CONTROL_ADDRESS ? | |
# ATTENTION! | |
# if cf == 3, !$CONTROL_ADDRESS IS JUST "distribute only" | |
# if cf < 2, !$CONTROL_ADDRESS => Command($MAIL_LIST==$CONTROL_ADDRESS) | |
sub CompatFMLv1P | |
{ | |
local($ml) = split(/\@/, $MAIL_LIST); | |
local($ca) = split(/\@/, $CONTROL_ADDRESS); | |
if ($CFVersion < 3) { return &CF2CompatFMLv1P($ca, $ml);} | |
# Version 3, criterion is only "MAIL_LIST == CONTROL_ADDRESS" | |
$ml eq $ca && return 1; | |
$MAIL_LIST eq $CONTROL_ADDRESS && return 1; | |
# Version 3, compat mode for before FML 2.1 | |
$MAIL_LIST_ACCEPT_COMMAND && return 1; | |
0; | |
} | |
# Distribute mail to members (fml.pl -> libdist.pl) | |
sub Distribute | |
{ | |
local(*e, $mode, $compat_hml) = @_; | |
local($ml) = (split(/\@/, $MAIL_LIST))[0]; | |
# Japanese specific: convert hankaku to zenkaku | |
if ($USE_HANKAKU_CONVERTER) { | |
if ($LANGUAGE eq 'Japanese') { | |
require("module/$LANGUAGE/libhankaku2zenkaku.pl"); | |
eval &FixJapaneseMDChars(*e); | |
&Log($@) if $@; | |
} | |
} | |
# Filtering mail body from members but not check other cases | |
# e.g. null body subscribe request in "no-keyword" case | |
if ($USE_DISTRIBUTE_FILTER) { | |
&EnvelopeFilter(*e, 'distribute'); | |
return $NULL if $DO_NOTHING; | |
} | |
# check duplication baesd on mailbody MD5 cksum cache | |
if ($CHECK_MAILBODY_CKSUM) { | |
&use('cksum'); | |
if (&CheckMailBodyCKSUM(*e)) { | |
# looped ! | |
$DO_NOTHING = 1; | |
return $NULL; | |
} | |
else { | |
# not looped ! O.K. now cache on for the future | |
&CacheMailBodyCksum(*e); | |
} | |
} | |
# Security: Mail Traffic Information | |
if ($USE_MTI) { | |
&use('mti'); | |
&MTICache(*e, 'distribute'); | |
return $NULL if &MTIError(*e); | |
} | |
if ($debug) { @c = caller; &Log("Distritute called from $c[2] ");} | |
if ($mode eq 'permit from members_only') { | |
$Rcsid .= "; post only (only members can post)"; | |
} | |
elsif ($mode eq 'permit from anyone') { | |
$Rcsid .= "; post only (anyone can post)"; | |
} | |
elsif ($mode eq 'permit from moderator') { | |
$Rcsid =~ s/^(.*)(\#\d+:\s+.*)/$1."(moderated mode)".$2/e; | |
undef $e{'h:approval:'}; # delete the passwd entry; | |
undef $e{'h:Approval:'}; # delete the passwd entry; | |
} | |
### NOT REMOVE FOR BACKWARD? | |
else { | |
$Rcsid .= "; post + commands (members only)"; # default ; | |
} | |
if ($MAIL_LIST eq $CONTROL_ADDRESS) { | |
$Rcsid =~ s/post only (from.*)/post $1 + commands/; | |
} | |
# Dabasase access: dump recipients | |
if ($USE_DATABASE) { | |
my (%mib, %result, %misc, $error); | |
&DataBaseMIBPrepare(\%mib, 'dump_active_list'); | |
&DataBaseCtl(\%Envelope, \%mib, \%result, \%misc); | |
&Log("fail to dump active list") if $mib{'error'}; | |
return $NULL if $mib{'error'}; | |
# tricky but effective | |
# we push cache file to recipient lists, so that | |
# @ACTIVE_LIST = ($ACTIVE_LIST, $mib{'_cache_file'}); | |
push(@ACTIVE_LIST, $mib{'_cache_file'}) if -f $mib{'_cache_file'}; | |
&Log("ACTIVE = ( @ACTIVE_LIST )") if $debug; | |
} | |
require 'libdist.pl'; | |
&DoDistribute(*e); | |
} | |
sub RunStartHooks | |
{ | |
$0 = "${FML}: RunStartHooks <$LOCKFILE>"; | |
# additional before action | |
$START_HOOK && &eval($START_HOOK, 'Start hook'); | |
for (keys %FmlStartHook) { | |
print STDERR "Run StartHook $_ -> $FmlStartHook{$_}\n" if $debug; | |
next unless $FmlStartHook{$_}; | |
$0 = "${FML}: Run FmlStartHook [$_] <$LOCKFILE>"; | |
&eval($FmlStartHook{$_}, "Run FmlStartHook [$_]"); | |
} | |
} | |
sub RunEndHooks | |
{ | |
$0 = "${FML}: RunEndHooks <$LOCKFILE>"; | |
# additional before action | |
$END_HOOK && &eval($END_HOOK, 'END_HOOK'); | |
for (keys %FmlEndHook) { | |
print STDERR "Run EndHook $_ -> $FmlEndHook{$_}\n" if $debug; | |
next unless $FmlEndHook{$_}; | |
$0 = "${FML}: Run FmlEndHook [$_] <$LOCKFILE>"; | |
&eval($FmlEndHook{$_}, "Run FmlEndHook [$_]"); | |
} | |
} | |
# Lastly exec to be exceptional process | |
sub ExExec { &RunHooks(@_);} | |
sub RunExitHooks | |
{ | |
local($s); | |
$0 = "${FML}: RunExitHooks <$LOCKFILE>"; | |
# FIX COMPATIBILITY | |
$FML_EXIT_HOOK .= $_PCB{'hook', 'str'}; | |
if ($s = $FML_EXIT_HOOK) { | |
print STDERR "\nmain::eval >$s<\n\n" if $debug; | |
$0 = "${FML}: Run Hooks(eval) <$LOCKFILE>"; | |
&eval($s, 'Run Hooks:'); | |
} | |
for (keys %FmlExitHook) { | |
print STDERR "Run hooks $_ -> $FmlExitHook{$_}\n" if $debug; | |
next unless $FmlExitHook{$_}; | |
$0 = "${FML}: Run FmlExitHook [$_] <$LOCKFILE>"; | |
&eval($FmlExitHook{$_}, "Run FmlExitHook [$_]"); | |
} | |
} | |
sub ExecNewProcess | |
{ | |
$0 = "${FML}: Run New Process <$LOCKFILE>"; | |
$FML_EXIT_PROG .= $_PCB{'hook', 'prog'}; | |
if ($FML_EXIT_PROG) { &use('kernsubr2'); &__ExecNewProcess;} | |
} | |
sub SpawnProcess | |
{ | |
local(@xargv) = @_; &use('kernsubr2'); &__SpawnProcess(@xargv); | |
} | |
####### Section: Member Check | |
# fix array list; | |
# | |
# files to check for the authentication 96/09/17 | |
# @MEMBER_LIST = ($MEMBER_LIST) unless @MEMBER_LIST; | |
sub AdjustActiveAndMemberLists | |
{ | |
local($f, $status); | |
if ($status = &AutoRegistrableP) { | |
# automatic asymmetric registration | |
# XXX: fml 2.x auto_asymmetric_regist fakes "only member list". | |
if ($status eq "auto_asymmetric_regist") { | |
$FILE_TO_REGIST = $FILE_TO_REGIST || $ACTIVE_LIST; | |
&Touch($FILE_TO_REGIST) unless -f $FILE_TO_REGIST; | |
} | |
# XXX: fml 2.x auto_regist uses only member file. | |
# XXX: fml 3.x auto_subscribe uses actives and members. | |
elsif (&NotUseSeparateListP) { | |
# XXX: this block is always true in 2.x but false in 3.x. | |
$ACTIVE_LIST = $MEMBER_LIST; | |
for (@MEMBER_LIST) { | |
grep(/$_/, @ACTIVE_LIST) || push(@ACTIVE_LIST, $_); | |
} | |
} | |
} | |
grep(/$MEMBER_LIST/, @MEMBER_LIST) || push(@MEMBER_LIST, $MEMBER_LIST); | |
grep(/$ACTIVE_LIST/, @ACTIVE_LIST) || push(@ACTIVE_LIST, $ACTIVE_LIST); | |
if (($f = $FILE_TO_REGIST) && -f $FILE_TO_REGIST) { | |
grep(/$f/, @MEMBER_LIST) || push(@MEMBER_LIST, $f); | |
grep(/$f/, @ACTIVE_LIST) || push(@ACTIVE_LIST, $f); | |
} | |
elsif (-f $FILE_TO_REGIST) { | |
&Log("ERROR: \$FILE_TO_REGIST NOT EXIST"); | |
} | |
# ONLY IF EXIST ALREADY, add the admin list (if not, noisy errors...;-) | |
if (($f = $ADMIN_MEMBER_LIST) && -f $ADMIN_MEMBER_LIST) { | |
grep(/$f/, @MEMBER_LIST) || push(@MEMBER_LIST, $f); | |
} | |
} | |
# if found, return the non-null file name; | |
sub DoMailListMemberP | |
{ | |
local($addr, $type) = @_; | |
local($file, @file, %file); | |
# DataBase Access | |
if ($USE_DATABASE) { | |
&use('databases'); | |
my ($action) = $type eq 'm' ? 'member_p' : 'active_p'; | |
# try to access database to verify the server is alive | |
# and try to verify the address is member or not. | |
my (%mib, %result, %misc, $error); | |
&DataBaseMIBPrepare(\%mib, $action, {'address' => $addr}); | |
&DataBaseCtl(\%Envelope, \%mib, \%result, \%misc); | |
# if search fails for $MEMBER_LIST, | |
if ( !$mib{'_result'} && ($type eq 'm')) { | |
# retry search in $ADMIN_MEMBER_LIST | |
my (%xmib, %result, %misc, $error); | |
&DataBaseMIBPrepare(\%xmib,'admin_member_p',{'address' => $addr}); | |
&DataBaseCtl(\%Envelope, \%xmib, \%result, \%misc); | |
# search fails for both $MEMBER_LIST and $ADMIN_MEMBER_LIST | |
if ($xmib{'error'}) { | |
return 0; | |
} | |
else { | |
$mib{ _result } = $xmib{ _result }; | |
} | |
} | |
# if search fails for $ACTIVE_LIST, return here ASAP. | |
elsif ($mib{'error'}) { | |
return 0; | |
} | |
$Envelope{'database:cache:$action'} = 1 if $mib{'_result'}; | |
return $mib{'_result'}; | |
} | |
# XXX not reach here when you use $USE_DATABASE | |
$SubstiteForMemberListP = 1; | |
@file = $type eq 'm' ? @MEMBER_LIST : @ACTIVE_LIST; | |
for $file (@file) { | |
next unless -f $file; | |
next if $file{$file}; $file{$file} = 1; # uniq | |
# prohibit ordinary people operations (but should permit probing only) | |
# NOT CHECK OUTSIDE "amctl" procedures in &Command; | |
# WITHIN "amctl" | |
# check also $ADMIN_MEMBER_LIST if IN ADMIN MODE | |
# ignore $ADMIN_MEMBER_LIST if NOT IN ADMIN MODE | |
if ($e{'mode:in_amctl'} && # in "amctl" library | |
($file eq $ADMIN_MEMBER_LIST) && | |
(! $e{'mode:admin'})) { # called NOT in ADMIN MODE | |
next; | |
} | |
if ($debug && -f $file) { | |
&Debug(" DoMailListMemberP(\n\t$addr\n\tin $file);\n"); | |
} | |
if (-f $file && &Lookup($addr, $file)) { | |
&Debug("+++Hit: $addr in $file") if $debug; | |
$SubstiteForMemberListP = 0; | |
return $file; | |
} | |
} | |
$SubstiteForMemberListP = 0; | |
if ($IDENTIFY_MIGRATING_DOMAIN) { | |
# avoid recursive call under libmgrdom.pl | |
return $NULL if $Envelope{'mode:in_mgrdom'}; | |
&use('mgrdom'); | |
&MgrdomConsider($addr, $type); | |
} | |
else { | |
$NULL; | |
} | |
} | |
sub MailListMemberP { return &DoMailListMemberP(@_, 'm');} | |
sub MailListActiveP { return &DoMailListMemberP(@_, 'a');} | |
sub MailListAdminMemberP | |
{ | |
my ($addr) = @_; | |
# DataBase Access | |
if ($USE_DATABASE) { | |
&use('databases'); | |
my (%mib, %result, %misc, $error); | |
&DataBaseMIBPrepare(\%mib, 'admin_member_p', {'address' => $addr}); | |
&DataBaseCtl(\%Envelope, \%mib, \%result, \%misc); | |
if ($mib{'error'}) { return 0;} | |
$Envelope{'database:cache:admin_member_p'} = 1 if $mib{'_result'}; | |
return $mib{'_result'}; | |
} | |
else { | |
&Lookup($addr, $ADMIN_MEMBER_LIST); | |
} | |
} | |
sub NonAutoRegistrableP { ! &AutoRegistrableP;} | |
sub AutoRegistrableP | |
{ | |
if ($REJECT_POST_HANDLER =~ /auto\S+regist/ && | |
$REJECT_COMMAND_HANDLER eq 'auto_asymmetric_regist') { | |
&Log("These HANDLER configuration may not work well"); | |
} | |
if ($Envelope{'mode:ctladdr'} && | |
($REJECT_POST_HANDLER eq 'auto_asymmetric_regist' || | |
$REJECT_COMMAND_HANDLER eq 'auto_asymmetric_regist')) { | |
"auto_asymmetric_regist"; | |
} | |
elsif ($Envelope{'mode:ctladdr'} && | |
($REJECT_POST_HANDLER eq 'auto_subscribe' || | |
$REJECT_COMMAND_HANDLER eq 'auto_subscribe')) { | |
"auto_subscribe"; | |
} | |
elsif ($REJECT_COMMAND_HANDLER =~ /auto_regist/i || | |
$REJECT_COMMAND_HANDLER =~ /auto_subscribe/i || | |
$REJECT_COMMAND_HANDLER =~ /autoregist/i) { | |
$REJECT_COMMAND_HANDLER; | |
} | |
elsif ($REJECT_POST_HANDLER =~ /auto_regist/i || | |
$REJECT_POST_HANDLER =~ /auto_subscribe/i || | |
$REJECT_POST_HANDLER =~ /autoregist/i) { | |
$REJECT_POST_HANDLER; | |
} | |
else { | |
0; | |
} | |
} | |
sub NotUseSeparateListP { ! &UseSeparateListP;} | |
sub UseSeparateListP | |
{ | |
local($x) = &AutoRegistrableP; | |
if ($debug_fml30 == 1) { | |
&Log("AutoRegistrableP = $x"); $debug_fml30++; | |
} | |
if ($x eq 'auto_subscribe' || (! $x)) { | |
1; | |
} | |
else { | |
0; | |
} | |
} | |
# canonicalize address for database storing | |
# since we search it in case sensitive mode. | |
# preserve case in localpart but not in domain part | |
sub TrivialRewrite | |
{ | |
my ($addr) = @_; | |
my ($local, $domain) = split(/\@/, $addr); | |
$domain =~ tr/A-Z/a-z/; | |
$local. '@'. $domain; | |
} | |
sub AutoRegistHandler | |
{ | |
if ($debug) { @c = caller; &Log("AutoRegistHandler called from $c[2]");} | |
&use('amctl'); | |
&AutoRegist(*Envelope); | |
} | |
sub RejectHandler | |
{ | |
if ($debug) { @c = caller; &Log("RejectHandler called from $c[2]");} | |
&Log("Rejected: \"From:\" field is not member"); | |
&WarnE("NOT MEMBER article from $From_address $ML_FN", | |
"NOT MEMBER article from $From_address\n\n"); | |
if (-f $DENY_FILE) { | |
&SendFile($From_address, | |
"You $From_address are not member $ML_FN", $DENY_FILE); | |
} | |
else { | |
&Mesg(*Envelope, 'you are not member.', 'info.reject'); | |
} | |
} | |
sub IgnoreHandler | |
{ | |
&Log("Ignored: \"From:\" field is not member"); | |
&WarnE("Ignored NOT MEMBER article from $From_address $ML_FN", | |
"Ignored NOT MEMBER article from $From_address"); | |
} | |
# Lookup(key, file); return 1 if the "key" is found in the "file". | |
# e.g. Lookup(addr, member-list-file) | |
# return 1 if a given address is authenticated as member's (found in the file). | |
# | |
# performance test example 1 (100 times for 158 entries == 15800) | |
# fastest case | |
# old 1.880u 0.160s 0:02.04 100.0% 74+34k 0+1io 0pf+0w | |
# new 1.160u 0.160s 0:01.39 94.9% 73+36k 0+1io 0pf+0w | |
# slowest case | |
# old 20.170u 1.520s 0:22.76 95.2% 74+34k 0+1io 0pf+0w | |
# new 9.050u 0.190s 0:09.90 93.3% 74+36k 0+1io 0pf+0w | |
# | |
# the actual performance is the average between values above | |
# but the new version provides stable performance. | |
# | |
sub CheckMember { &Lookup(@_);} | |
sub Lookup | |
{ | |
local($address, $file) = @_; | |
local($addr, $has_special_char, $auto_registrable); | |
# mode | |
$auto_registrable = &AutoRegistrableP; | |
# more severe check; | |
$address =~ s/^\s*//; | |
if ($address =~ /\@/) { # RFC822 addrspec | |
($addr) = split(/\@/, $address); | |
} | |
else { # not addrspec, arbitrary string | |
$addr = substr($address, 0, 8); | |
} | |
# MUST BE ONLY * ? () [] but we enhance the category -> shell sc | |
# add + (+ed user) 1998/11/08 | |
if ($addr =~ /[\+\$\&\*\(\)\{\}\[\]\'\\\"\;\\\\\|\?\<\>\~\`]/) { | |
$has_special_char = 1; | |
} | |
open(LOOKUP_TABLE, $file) || do { | |
&Log("LookUp: cannot open $file"); | |
return 0; | |
}; | |
getline: while (<LOOKUP_TABLE>) { | |
chop; | |
if ($auto_registrable || $SubstiteForMemberListP) { | |
/^\#\s*(.*)/ && ($_ = $1); | |
} | |
next getline if /^\#/o; # strip comments | |
next getline if /^\s*$/o; # skip null line | |
/^\s*(\S+)\s*.*$/o && ($_ = $1); # including .*#.* | |
# member nocheck(for nocheck but not add mode) | |
# fixed by yasushi@pier.fuji-ric.co.jp 95/03/10 | |
# $ENCOUNTER_PLUS by fukachan@phys 95/08 | |
# $Envelope{'mode:anyone:ok'} by fukachan@phys 95/10/04 | |
# $Envelope{'trap:+'} by fukachan@sapporo 97/06/28 | |
if (/^\+\s*$/o) { | |
&Debug("encounter + [$_]") if $debug; | |
$Envelope{'trap:+'} = 1; | |
close(LOOKUP_TABLE); | |
return 1; | |
} | |
# for high performance(Firstly special character check) | |
if (! $has_special_char) { next getline unless /^$addr/i;} | |
# This searching algorithm must require about N/2, not tuned, | |
if (1 == &AddressMatch($_, $address)) { | |
close(LOOKUP_TABLE); | |
return 1; | |
} | |
}# end of while loop; | |
close(LOOKUP_TABLE); | |
return 0; | |
} | |
# for convenience | |
sub ExactAddressMatch | |
{ | |
local($addr1, $addr2) = @_; | |
&SaveACL; | |
$ADDR_CHECK_MAX = 100; | |
local($r) = &AddressMatch($addr1, $addr2); | |
&RetACL; | |
$r; | |
} | |
# sub AddressMatching($addr1, $addr2) | |
# return 1 given addresses are matched at the accuracy of 4 fields | |
sub AddressMatching { &AddressMatch(@_);} | |
sub AddressMatch | |
{ | |
local($addr1, $addr2) = @_; | |
&Debug(" AddressMatch($addr1, $addr2)". | |
" [\$ADDR_CHECK_MAX=$ADDR_CHECK_MAX]\n") if $debug_addrmatch; | |
# canonicalize to lower case | |
$addr1 =~ y/A-Z/a-z/; | |
$addr2 =~ y/A-Z/a-z/; | |
# try exact match. must return here in a lot of cases. | |
if ($addr1 eq $addr2) { | |
if ($debug) { | |
&Debug(" AddressMatch($addr1, $addr2) => exact match"); | |
&Log("AddressMatch($addr1, $addr2) => exact match"); | |
} | |
return 1; | |
} | |
# for further investigation, parse account and host | |
local($acct1, $addr1) = split(/@/, $addr1); | |
local($acct2, $addr2) = split(/@/, $addr2); | |
# At first, account is the same or not?; | |
if ($acct1 ne $acct2) { return 0;} | |
# Get an array "jp.ac.titech.phys" for "fukachan@phys.titech.ac.jp" | |
local(@d1) = reverse split(/\./, $addr1); | |
local(@d2) = reverse split(/\./, $addr2); | |
# Check only "jp.ac.titech" part( = 3)(default) | |
# If you like to strict the address check, | |
# change $ADDR_CHECK_MAX = e.g. 4, 5 ... | |
local($i, $m) = (0, 0); | |
while ($d1[$i] && $d2[$i] && ($d1[$i] eq $d2[$i])) { $i++;} | |
$m = ($ADDR_CHECK_MAX > 0) ? $ADDR_CHECK_MAX : 3; | |
if ($debug) { | |
&Debug(" AddressMatch($acct1\@$addr1, $acct2\@$addr2) => ". | |
(($i >= $m) ? "match" : "not match"). | |
" [$i >= $m ? y : n]"); | |
&Log("AddressMatch($acct1\@$addr1, $acct2\@$addr2) => ". | |
(($i >= $m) ? "match" : "not match")); | |
&Log("AddressMatch: $i >= $m ? match : not match"); | |
} | |
($i >= $m) ? 1 : 0; | |
} | |
sub LowerDomain | |
{ | |
my ($addr) = @_; | |
my (@addr) = split(/\@/, $addr); | |
if ($DATABASE_DRIVER_ATTRIBUTES =~ /always_lower_domain/) { | |
$addr[1] =~ tr/A-Z/a-z/; | |
return $addr[0].'@'.$addr[1]; | |
} | |
else { | |
return $addr; | |
} | |
} | |
####### Section: Info | |
# Recreation of the whole mail for error infomation | |
sub WholeMail { | |
local(@xargv) = @_; &use('kernsubr2'); &__WholeMail(@xargv); | |
} | |
sub ForwMail { | |
local(@xargv) = @_; &use('kernsubr2'); &__ForwMail(@xargv); | |
} | |
sub Translate | |
{ | |
local(*e, $s, $mesgle_key, @mesgle_argv) = @_; | |
if ($MESSAGE_LANGUAGE && $mesgle_key) { | |
&use('mesgle'); | |
&MesgLE(*e, $mesgle_key, @mesgle_argv); | |
} | |
else { $NULL;} | |
} | |
# &Mesg(*e, ); | |
# $mesgle == Message Languae Extension | |
sub Mesg | |
{ | |
local(*e, $s, $mesgle_key, @mesgle_argv) = @_; | |
if ($MESSAGE_LANGUAGE && $mesgle_key) { | |
&Debug("MesgLE:try to translate key <$mesgle_key>") if $debug; | |
&use('mesgle'); | |
$s = &MesgLE(*e, $mesgle_key, @mesgle_argv) || $s; | |
} | |
# if $s is null, return just now! (DUMMY_OPS may be useful) | |
return unless $s; | |
$e{'message'} .= "$s\n"; | |
$MesgBuf .= "$s\n"; | |
# dup to admins | |
$e{'message:to:admin'} .= "$s\n" if $e{'mode:notify_to_admin_also'}; | |
} | |
# no real data copy but | |
# enable flag to mail body forwarding in Smtp() via Notify(). | |
sub MesgMailBodyCopyOn | |
{ | |
&Mesg(*e, "Original mail as follows:\n"); | |
$Envelope{'message:ebuf2socket'} = 1; | |
} | |
sub MesgSetBreakPoint { undef $MesgBuf;} | |
sub MesgGetABP { $MesgBuf;} # After Break Point | |
# Forwarded and Warned to Maintainer; | |
sub Warn { &Forw(@_);} | |
sub Forw { &Sendmail($MAINTAINER, $_[0], $_[1]);} | |
sub WarnFile | |
{ | |
local($subject, $file, $preamble, $trailor) = @_; | |
local($to, @file); | |
@file = $file; undef $file; | |
@to = ($MAINTAINER); | |
$Envelope{'preamble'} = $preamble; | |
&NeonSendFile(*to, *subject,*file); | |
undef $Envelope{'preamble'}; | |
} | |
# Warn() with direct buffer copy from %Envelope to Socket | |
# and with "mh forwarding" separators (added in smtp library). | |
sub WarnF | |
{ | |
$Envelope{'ctl:smtp:forw:ebuf2socket'} = 1; | |
&WarnE(@_); | |
$Envelope{'ctl:smtp:forw:ebuf2socket'} = 0; | |
} | |
# Extended Warn() with direct buffer copy from %Envelope to Socket | |
sub WarnE | |
{ | |
local($subject, $body, $preamble, $trailor) = @_; | |
local($title); | |
$Envelope{'preamble'} = $preamble; | |
$title = $Envelope{"tmp:ws"} || "Original mail as follows"; | |
$title = "\n$title:\n\n"; | |
$Envelope{'ctl:smtp:ebuf2socket'} = 1; | |
&Sendmail($MAINTAINER, $subject, $body.$title); | |
$Envelope{'ctl:smtp:ebuf2socket'} = 0; | |
undef $Envelope{'preamble'}; | |
} | |
sub Notify | |
{ | |
local(@xargv) = @_; &use('kernsubr'); &__Notify(@xargv); | |
} | |
sub EnableReportForw2Admin | |
{ | |
local(*e) = @_; $e{'mode:notify_to_admin_also'} = 1; | |
} | |
sub DisableReportForw2Admin | |
{ | |
local(*e) = @_; $e{'mode:notify_to_admin_also'} = 0; | |
} | |
## | |
## Negative Cache Wrapper()'s | |
## | |
# wrap &Mesg() with negative cache | |
sub CMesg | |
{ | |
local($msgkey, $howold, *e, $s, $mesgle_key, @mesgle_argv) = @_; | |
if (&OutOfNegativeCacheP($msgkey, $howold)) { | |
&Mesg(*e, $s, $mesgle_key, @mesgle_argv); | |
} | |
} | |
# wrap &Warn() with negative cache | |
sub CWarn | |
{ | |
local($msgkey, $howold, $subject, $body) = @_; | |
if (&OutOfNegativeCacheP($msgkey, $howold)) { | |
&Warn($subject, $body); | |
} | |
} | |
sub OutOfNegativeCacheP | |
{ | |
local($msgkey, $howold) = @_; | |
local($dir) = "$VAR_DIR/mesgcache"; | |
local($cf) = "$dir/$msgkey"; | |
local($x); | |
-d $dir || &Mkdir($dir); | |
if (-f $cf) { | |
$x = time - (stat($cf))[9]; # how lod | |
if ($x < $howold) { | |
print STDERR "ignore now ($count, $x $howold)\n" if $debug; | |
return 0; # in negative cache | |
} | |
} | |
&Touch($cf); | |
1; | |
} | |
# Generate additional information for command mail reply. | |
# return the STRING | |
sub GenInfo | |
{ | |
local($s, $c, $d, $del); | |
local($message, $has_ctladdr_p, $addr, $trap); | |
# initialize variables | |
$del = ('*' x 60); | |
# if has control-address | |
if ($CONTROL_ADDRESS) { | |
$addr = $Envelope{'CtlAddr:'}; | |
$has_ctladdr_p = 1; | |
} | |
# if !control-address but MAIL_LIST==CONTROL_ADDRESS | |
elsif ((! $CONTROL_ADDRESS) && &CompatFMLv1P) { | |
$addr = $MAIL_LIST; | |
$has_ctladdr_p = 1; | |
} | |
elsif ((! $CONTROL_ADDRESS) && $MAIL_LIST_ACCEPT_COMMAND) { | |
$addr = $MAIL_LIST; | |
$has_ctladdr_p = 1; | |
} | |
# help style; | |
$message = $Envelope{"mode:fmlserv"} ? "help": "$Envelope{'trap:ctk'}help"; | |
if ($MAIL_LIST =~ /^(fmlserv|majordomo|listserv)/i) { | |
$trap = ''; | |
} | |
else { | |
$trap = &CompatFMLv1P ? '#' : ''; | |
} | |
$s .= "\n$del\n"; | |
# URL Extentions | |
if ($ADD_URL_INFO) { | |
if ($Envelope{'mode:stranger'}) { | |
$URLInfo = ";\n\t<mailto:$MAINTAINER>"; | |
$URLComInfo = &GenXMLInfo; | |
} | |
# not stranger and has ctladdr (From: is a member). | |
elsif ($has_ctladdr_p) { | |
$s .= "\n"; | |
$s .= " Help: <mailto:$addr?body=${trap}help>\n"; | |
$s .= "Unsubscribe: <mailto:$addr?body=${trap}unsubscribe>\n"; | |
$s .= "\n"; | |
$URLInfo = ";\n\thelp=<mailto:$addr?body=${trap}help>"; | |
$URLComInfo = &GenXMLInfo; | |
} | |
# not stranger and has no ctladdr (From: is a member). | |
else { | |
$URLInfo = ";\n\t<mailto:$MAINTAINER>"; | |
} | |
} | |
# RFC2369; Proposed Standard (so fml optional) | |
if ($USE_RFC2369) { &use('kernsubr2'); &EmulRFC2369;} | |
$s .= "If you have any questions or problems,\n"; | |
$s .= " please contact $MAINTAINER\n"; | |
if (! $Envelope{'mode:stranger'} && $has_ctladdr_p) { # a member | |
$s .= " or \n"; | |
$s .= " send e-mail with the body \"$message\"(without quotes) to\n"; | |
$s .= " $addr\n"; | |
$s .= " (here is the automatic reply, so more preferable)\n\n"; | |
$s .= "e.g. on a Unix Machine\n"; | |
$s .= "(shell prompt)\% echo \"$message\" |Mail $addr"; | |
} | |
$s .= "\n\n$del\n"; | |
$s; | |
} | |
sub GenXMLInfo | |
{ | |
if ($X_ML_INFO_MESSAGE) { | |
$X_ML_INFO_MESSAGE; | |
} | |
elsif ($Envelope{'mode:stranger'} || | |
(!$CONTROL_ADDRESS && | |
$PERMIT_POST_FROM =~ /^(anyone|members_only)$/)) { | |
"If you have a question,\n\tplease contact $MAINTAINER". | |
";\n\t<mailto:$MAINTAINER>"; | |
} | |
else { | |
"If you have a question, send e-mail with the body\n". | |
"\t\"". $Envelope{'trap:ctk'}. | |
"help\" (without quotes) to the address ". &CtlAddr . | |
$URLInfo; | |
} | |
} | |
####### Section: IO | |
# Log: Logging function | |
# ALIAS:Logging(String as message) (OLD STYLE: Log is an alias) | |
# delete \015 and \012 for seedmail return values | |
# $s for ERROR which shows trace infomation | |
sub Logging { &Log(@_);} # BACKWARD COMPATIBILITY | |
sub LogWEnv { local($s, *e) = @_; &Log($s); $e{'message'} .= "$s\n";} | |
sub Log | |
{ | |
local($str, $s) = @_; | |
local($package, $filename, $line) = caller; # called from where? | |
local($from) = $PeerAddr ? "$From_address[$PeerAddr]" : $From_address; | |
local($error); | |
&GetTime; | |
$str =~ s/\015\012$//; # FIX for SMTP (cut \015(^M)); | |
if ($debug_smtp && ($str =~ /^5\d\d\s/)) { | |
$error .= "Sendmail ERROR:\n"; | |
$error .= "\t$Now $str $_\n\t($package, $filename, $line)\n\n"; | |
} | |
$str = "$filename:$line% $str" if $debug_caller; | |
# existence and append(open system call check) | |
if (-f $LOGFILE && open(APP, ">> $LOGFILE")) { | |
&Append2("$Now $str ($from)", $LOGFILE); | |
&Append2("$Now $filename:$line% $s", $LOGFILE) if $s; | |
} | |
else { | |
print STDERR "$Now ($package, $filename, $line) $LOGFILE\n"; | |
print STDERR "$Now $str ($from)\n\t$s\n"; | |
} | |
$Envelope{'error'} .= $error if $error; | |
print STDERR "*** $str; $s;\n" if $debug; | |
} | |
# $mode: see open(2) | |
sub __Write30 | |
{ | |
local(*e, *s, *f, $mode, $envelope_hash_key) = @_; | |
local($status); | |
if ($mode eq "O_APPEND") { | |
$status = open(WRITE2_OUT, ">> $f"); | |
} | |
else { | |
$status = open(WRITE2_OUT, "> $f"); | |
} | |
if ($status) { | |
select(WRITE2_OUT); $| = 1; select(STDOUT); | |
# XXX Caution: "\n" handling differs. | |
if ($envelope_hash_key) { | |
print WRITE2_OUT $e{$envelope_hash_key}; | |
} | |
else { | |
print WRITE2_OUT $s, "\n"; | |
} | |
close(WRITE2_OUT); | |
1; | |
} | |
else { | |
0; | |
} | |
} | |
sub HashValueAppend | |
{ | |
local(*e, $key, $f) = @_; | |
&__Write30(*e, *NULL, *f, "O_APPEND", $key) || do { | |
local(@caller) = caller; | |
print STDERR "HashValueAppend(@_)::Error caller=<@caller>\n"; | |
}; | |
} | |
# append $s >> $file | |
# if called from &Log and fails, must be occur an infinite loop. set $nor | |
# return NONE | |
sub Append2 | |
{ | |
local($s, $f, $o_append) = @_; | |
($s && &__Write30(*NULL, *s, *f, "O_APPEND")) || do { | |
local(@caller) = caller; | |
print STDERR "Append2(@_)::Error caller=<@caller>\n"; | |
}; | |
} | |
sub Write2 | |
{ | |
local($s, $f, $o_append) = @_; | |
if ($o_append) { | |
return &Append2(@_); | |
} | |
elsif ($s && &__Write30(*NULL, *s, *f, "O_RWONLY")) { | |
; | |
} | |
else { | |
local(@caller) = caller; | |
print STDERR "Write2(@_)::Error caller=<@caller>\n"; | |
return 0; | |
}; | |
1; | |
} | |
sub Touch { open(APP, ">>$_[0]"); close(APP); chown $<, $GID, $_[0] if $GID;} | |
sub Write3 | |
{ | |
local(@xargv) = @_; &use('kernsubr'); &__Write3(@xargv); | |
} | |
sub GetFirstLineFromFile | |
{ | |
if (open(GFLFF, $_[0])) { | |
chop($_ = <GFLFF>); | |
close(GFLFF); | |
$_; | |
} | |
else { | |
return $NULL; | |
} | |
} | |
# $id = IncrementCounter(file, modulus) | |
sub IncrementCounter | |
{ | |
my ($f, $modulus) = @_; | |
my ($id) = 0; | |
# return cached id (against duplicated calls within one thread). | |
$IncrementCounterCalled{$f}++; | |
return $IncrementCounter{$f} if $IncrementCounterCached{$f}; | |
&Touch($f) unless -f $f; | |
if (-f $f) { | |
$id = &GetFirstLineFromFile($f); | |
$id++; | |
if ($modulus > 0) { $id = $id % $modulus;} | |
if (open($f, "> ${f}.$$.new")) { | |
select($f); $| = 1; select(STDOUT); | |
print $f $id, "\n"; | |
close($f); | |
rename("${f}.$$.new", $f); | |
} | |
} | |
$IncrementCounter{$f} = $id; # (0 .. modulus-unit) | |
$IncrementCounterCached{$f} = 1; | |
$id; | |
} | |
# For Example, | |
# $pp = $p = 0; | |
# while (1) { | |
# $p = &GetLinePtrFromHash(*Envelope, "Body", $pp); | |
# print substr($Envelope{'Body'}, $pp, $p-$pp+1); | |
# last if $p < 0; | |
# $pp = $p + 1; | |
# } | |
sub GetLinePtrFromHash | |
{ | |
local(*e, $key, $ptr) = @_; | |
index($e{$key}, "\n", $ptr); | |
} | |
# For example, | |
# ($p, $pb, $pe) = &GetBlockPtrFromHash(*Envelope, 'Body', $b, $pp); | |
# last if $p < 0; | |
# print substr($Envelope{'Body'}, $pb, $pe - $pb); | |
sub GetBlockPtrFromHash | |
{ | |
local(*e, $key, $b, $ptr) = @_; | |
local($p, $pb, $pe); | |
$p = &GetPtrFromHash(*e, 'Body', $b, $ptr); | |
$pb = &GetPtrFromHash(*e, 'Body', "\n\n", $p + 1); | |
$pe = &GetPtrFromHash(*e, 'Body', $b, $pb + 1); | |
($p, $pb + 2, $pe) | |
} | |
sub GetPtrFromHash | |
{ | |
local(*e, $key, $pat, $ptr) = @_; | |
index($e{$key}, $pat, $ptr); | |
} | |
# useful for "Read Open" | |
sub Open | |
{ | |
if ((!-f $_[1]) || $_[1] eq '') { | |
local(@c) = caller; local($c) = "$c[1],$c[2]"; $c =~ s#^\S+/##; | |
if (! -f $_[1]) { &Log("${c}::Open $_[1] NOT FOUND");} | |
if ($_[1] eq '') { &Log("${c}::Open $_[1] IS NULL; NOT DEFINED");} | |
return 0; | |
} | |
open($_[0], $_[1]) || do { | |
local(@c) = caller; local($c) = "$c[1],$c[2]"; $c =~ s#^\S+/##; | |
&Log("$c::Open failed $_[1]"); return 0; | |
}; | |
} | |
sub Copy | |
{ | |
local($in, $out) = @_; | |
local($mode) = (stat($in))[2]; | |
open(COPYIN, $in) || (&Log("ERROR: Copy::In [$!]"), return 0); | |
open(COPYOUT, "> $out") || (&Log("ERROR: Copy::Out [$!]"), return 0); | |
select(COPYOUT); $| = 1; select(STDOUT); | |
chmod $mode, $out; | |
while (sysread(COPYIN, $_, 4096)) { print COPYOUT $_;} | |
close(COPYOUT); | |
close(COPYIN); | |
1; | |
} | |
# checks the executable "prog" in "prog option". | |
sub ProgExecuteP | |
{ | |
local($prog) = @_; | |
$prog || return 0; # no input | |
($prog) = (split(/\s+/, $prog))[0]; | |
-x $prog ? 1 : 0; | |
} | |
# check fundamental programs existence | |
sub DiagPrograms | |
{ | |
my (@prog) = @_; | |
my ($x, $bad); | |
if ($UNISTD) { | |
for $x (@prog) { | |
&ProgExecuteP( ${ $x } ) || do { | |
$bad++; | |
&Log("ERROR: program \$${x} is not defined"); | |
}; | |
} | |
} | |
$bad ? 0 : 1; # if bad, return 0; | |
} | |
# mainly search e.g. "sendmail" | |
sub SearchPath | |
{ | |
local($prog, @path) = @_; | |
for ("/usr/sbin", "/usr/lib", @path) { | |
if (-e "$_/$prog" && -x "$_/$prog") { return "$_/$prog";} | |
} | |
} | |
sub SearchFileInLIBDIR | |
{ | |
for (@LIBDIR) { | |
&Debug("SearchFileInLIBDIR: <$_>/$_[0]") if $debug; | |
if (-f "$_/$_[0]") { return "$_/$_[0]";} | |
} | |
$NULL; | |
} | |
sub SearchFileInINC | |
{ | |
for (@INC) { if (-f "$_/$_[0]") { return "$_/$_[0]";}} | |
$NULL; | |
} | |
sub GetFirstMultipartBlock | |
{ | |
local(*e) = @_; | |
if ($e{'MIME:boundary'}) { | |
($p, $pb, $pe) = | |
&GetBlockPtrFromHash(*e, 'Body', $e{'MIME:boundary'}, 0); | |
if ($pb > 0 && $pe > 0) { | |
substr($e{'Body'}, $pb, $pe - $pb); | |
} | |
else { | |
&Log("GetFirstMultipartBlock: invalid MIME/multipart message"); | |
$NULL; | |
} | |
} | |
else { | |
&Log("GetFirstMultipartBlock: invalid MIME/multipart message"); | |
$NULL; | |
} | |
} | |
####### Section: Utilities | |
# we suppose &Uniq(*array)'s "array" is enough small. | |
sub Uniq | |
{ | |
local(*q) = @_; | |
local(%p, @p); | |
for (@q) { next if $p{$_}; $p{$_} = $_; push(@p, $_);} | |
@q = @p; | |
} | |
# $pat is included in $list (A:B:C:... syntax) | |
sub ListIncludePatP | |
{ | |
local($pat, $list) = @_; | |
for (split(/:/, $list)) { return 1 if $pat eq $_;} | |
0; | |
} | |
sub DebugLog | |
{ | |
local($s) = @_; | |
local($f) = $DEBUG_LOGFILE || $LOGFILE.".debug"; | |
&GetTime; | |
&Append2("$Now $s", $f); | |
} | |
sub Debug | |
{ | |
print STDERR "$_[0]\n"; | |
&Mesg(*Envelope, "\nDEBUG $_[0]") if $debug_message; | |
&DebugLog($_[0]) if $debug > 1; | |
} | |
sub ABS { $_[0] < 0 ? - $_[0] : $_[0];} | |
sub ATOI | |
{ | |
if ($_[0] eq '') { | |
return $NULL; | |
} | |
elsif ($_[0] =~ /^(\d+)$/i) { | |
$_[0]; | |
} | |
elsif ($_[0] =~ /^(\d+)M$/i) { | |
$1 * 1024 * 1024; | |
} | |
elsif ($_[0] =~ /^(\d+)K$/i) { | |
$1 * 1024; | |
} | |
else { | |
&Log("ATOI: $_[0] is unknown type"); | |
} | |
} | |
# eval and print error if error occurs. | |
# which is best? but SHOULD STOP when require fails. | |
sub use { require "lib$_[0].pl";} | |
sub Use { require "lib$_[0].pl";} | |
# &UseModule("Japanese", "langdep"); | |
sub UseModule { require "module/$_[0]/lib$_[1].pl";} | |
sub MkDir { &Mkdir(@_);} | |
sub Mkdir | |
{ | |
if ($_[1] ne '') { return &MkDirHier($_[0], $_[1]);} | |
&MkDirHier($_[0], $DEFAULT_DIR_MODE || 0700); | |
if ($USE_FML_WITH_FMLSERV && $SPOOL_DIR eq $_[0]) { chmod 0750, $_[0];} | |
if ($USE_FML_WITH_FMLSERV && $GID) { chown $<, $GID, $_[0];} | |
} | |
sub MkDirHier | |
{ | |
local($pat) = $UNISTD ? '/|$' : '\\\\|/|$'; # on UNIX or NT4 | |
while ($_[0] =~ m:$pat:go) { | |
next if (!$UNISTD) && $` =~ /^[A-Za-z]:$/; # ignore drive letter on NT4 | |
if ($` ne "" && !-d $`) { | |
mkdir($`, $_[1] || 0777) || do { | |
&Log("cannot mkdir $`: $!"); | |
return 0; | |
}; | |
} | |
} | |
1; | |
} | |
# eval and print error if error occurs. | |
sub eval | |
{ | |
&CompatFML15_Pre if $COMPAT_FML15; | |
eval $_[0]; | |
$@ ? (&Log("$_[1]:$@"), 0) : 1; | |
&CompatFML15_Post if $COMPAT_FML15; | |
} | |
sub PerlModuleExistP | |
{ | |
local($pm) = @_; | |
if ($] !~ /^5\./) { &Log("ERROR: using $pm requires perl 5"); return 0;} | |
eval("use $pm"); | |
if ($@) { &Log("${pm}.pm NOT FOUND; Please install ${pm}.pm"); return 0;} | |
1; | |
} | |
# Getopt | |
sub Opt { push(@SetOpts, @_);} | |
# Setting CommandLineOptions after include config.ph | |
sub SetOpts | |
{ | |
# should pararelly define ... | |
for (@SetOpts) { | |
/^\-\-MLADDR=(\S+)/i && (&use("mladdr"), &MLAddr($1)); | |
if (/^\-\-([_a-z0-9]+)$/||/^\-\-([_a-z0-9]+=\S+)$/) {&DEFINE_MODE($1);} | |
} | |
for (@SetOpts) { | |
if (/^\-\-(force|fh):(\S+)=(\S+)/) { # "foreced header"; | |
&DEFINE_FIELD_FORCED($2, $3); next; | |
} | |
elsif (/^\-\-(original|org|oh):(\S+)/) { # "foreced header"; | |
&DEFINE_FIELD_ORIGINAL($2); next; | |
} | |
elsif (/^\-\-([_A-Z0-9]+)=(\S+)/) { # USER DEFINED VARIABLES | |
eval("\$$1 = '$2';"); next; | |
} | |
elsif (/^\-\-(\S+)/) { # backward mode definition is moved above | |
local($_) = $1; | |
/^[_a-z0-9]+$/ || eval("\$${_} = 1;"); | |
/^permit:([a-z0-9:]+)$/ && ($Permit{$1} = 1); # set %Permit; | |
next; | |
} | |
/^\-(\S)/ && ($Opt{"opt:$1"} = 1); | |
/^\-(\S)(\S+)/ && ($Opt{"opt:$1"} = $2); | |
/^\-d(\d+)/ && ($debug = $1) && next; | |
/^\-d|^\-bt/ && ($debug = 1) && next; | |
/^\-s(\S+)/ && &eval("\$$1 = 1;") && next; | |
/^\-u(\S+)/ && &eval("undef \$$1;") && next; | |
/^\-l(\S+)/ && ($LOAD_LIBRARY = $1) && next; | |
} | |
} | |
sub GenMessageId | |
{ | |
&GetTime; | |
$GenMessageId = $GenMessageId++ ? $GenMessageId : 'AAA'; | |
"<${CurrentTime}.FML${GenMessageId}". $$ .".$MAIL_LIST>"; | |
} | |
# which address to use a COMMAND control. | |
sub CtlAddr { &Addr2FQDN($CONTROL_ADDRESS);} | |
# Do FQDN of the given Address 1. $addr is set and has @, 2. MAIL_LIST | |
sub Addr2FQDN { $_[0]? ($_[0] =~ /\@/ ? $_[0]: $_[0]."\@$FQDN") : $MAIL_LIST;} | |
sub CutFQDN { $_[0] =~ /^(\S+)\@\S+/ ? $1 : $_[0];} | |
sub SRand | |
{ | |
local($i) = time; | |
$i = (($i & 0xff) << 8) | (($i >> 8) & 0xff) | 1; | |
srand($i + $$); | |
} | |
# Reference: NetBSD:/usr/src/usr.bin/cksum/sum2.c | |
# *** cksum utility is expected to conform to IEEE Std 1003.2-1992 *** | |
sub TraditionalATTUnixCheckSum | |
{ | |
my ($f) = @_; | |
my ($crc, $total, $nr); | |
$crc = $total = 0; | |
if (open($f, $f)) { | |
while (($nr = sysread($f, $buf, 1024)) > 0) { | |
my ($i) = 0; | |
$total += $nr; | |
for ($i = 0; $i < $nr; $i++) { | |
$r = substr($buf, $i, 1); | |
$crc += ord($r); | |
} | |
} | |
close($f); | |
$crc = ($crc & 0xffff) + ($crc >> 16); | |
$crc = ($crc & 0xffff) + ($crc >> 16); | |
} | |
else { | |
print STDERR "ERROR: no such file $f\n"; | |
} | |
($crc, $total); | |
} | |
sub LogFileNewSyslog | |
{ | |
$LOGFILE_NEWSYSLOG_LIMIT = &ATOI($LOGFILE_NEWSYSLOG_LIMIT); | |
if ($LOGFILE_NEWSYSLOG_LIMIT) { | |
if ((stat($LOGFILE))[7] > $LOGFILE_NEWSYSLOG_LIMIT) { | |
require 'libnewsyslog.pl'; | |
&NewSyslog($LOGFILE); | |
&Touch($LOGFILE); | |
} | |
} | |
} | |
sub CacheTurnOver | |
{ | |
local($file, $size_limit) = @_; | |
if ((stat($file))[7] > $size_limit) { | |
&use('newsyslog'); | |
&NewSyslog'TurnOverW0($file);#'; | |
&Touch($file); | |
} | |
} | |
sub DBCtl | |
{ | |
&use('db'); | |
&FML_SYS_DBCtl(@_); | |
} | |
####### Section: Security | |
# anyway alias now (1998/05/03) | |
# If sent back directly, X-ML-Info: exists and must contains e.g. $MAIL_LIST . | |
sub MailLoopP | |
{ | |
if ($Envelope{'h:x-ml-info:'}) { | |
if ($Envelope{'h:x-ml-info:'} =~ /contact $MAINTAINER/i || | |
$Envelope{'h:x-ml-info:'} =~ /(address\s+|mailto:)$MAIL_LIST/i || | |
$Envelope{'h:x-ml-info:'} =~ /(address\s+|mailto:)$CONTROL_ADDRESS/i) { | |
&Log("Loop Alert: dup X-ML-Info:"); | |
&WarnE("Loop Alert: dup X-ML-Info: $ML_FN", | |
"fml <$MAIL_LIST> has detected a loop condition so that\n" | |
."input mail has already our ML X-ML-Info: field.\n\n"); | |
return 1; | |
} | |
} | |
&DupMessageIdP; | |
} | |
sub SearchDupKey | |
{ | |
local($key, $file) = @_; | |
local($status, $i); | |
# 1. scan current and | |
if (-f $file) { | |
$status = &Lookup($key, $file); | |
} | |
return $status if $status; | |
# 2. scan all available caches | |
for $i (0 .. $NEWSYSLOG_MAX) { | |
if ($status) { | |
last; # end if non null $status is returned. | |
} | |
elsif (-f "$file.$i") { | |
$status = &Lookup($key, "$file.$i"); | |
} | |
} | |
$status; | |
} | |
# If O.K., record the Message-Id to the file $LOG_MESSAGE_ID); | |
# message-id cache should be done for mails in action | |
sub CacheMessageId | |
{ | |
local(*e, $msgid) = @_; | |
local($id); | |
# canonicalize | |
$id = $msgid || $e{'h:Message-Id:'}; | |
$id || (&Log("Invalid Message-Id:<$id>"), return $NULL); | |
$id =~ s/[\<\>]//g; | |
$id =~ s/^\s+//; | |
if ($CachedMessageID{$id}) { | |
&Log("CacheMessageId: warning: duplicated input") if $debug_loop; | |
return 0; | |
} | |
# Turn Over log file (against too big); | |
# The default value is evaluated as "once per about 100 mails". | |
&CacheTurnOver($LOG_MESSAGE_ID, | |
$MESSAGE_ID_CACHE_BUFSIZE || 60*100); | |
$CachedMessageID{$id} = 1; | |
&Append2($id." \# pid=$$", $LOG_MESSAGE_ID); | |
} | |
sub DupMessageIdP | |
{ | |
local($status, $mid); | |
# no check -> "return not looped" | |
$CHECK_MESSAGE_ID || return 0; | |
local($mid) = $Envelope{'h:Message-Id:'}; | |
$mid =~ s/[\<\>]//g; | |
$mid =~ s/^\s+//; | |
&Debug("DupMessageIdP::($mid, $LOG_MESSAGE_ID)") if $debug; | |
$status = &SearchDupKey($mid, $LOG_MESSAGE_ID); | |
if ($status) { | |
&Debug("\tDupMessageIdP::(DUPLICATED == LOOPED)") if $debug; | |
local($s) = "Duplicated Message-ID"; | |
&Log("Loop Alert: $s"); | |
&WarnE("Loop Alert: $s $ML_FN", "$s in <$MAIL_LIST>.\n\n"); | |
1; | |
} | |
else { | |
&Debug("\tDupMessageIdP::(OK NOT LOOPED)") if $debug; | |
0; | |
} | |
} | |
# if the addr to reply is O.K., return value is 1; | |
sub CheckAddr2Reply | |
{ | |
local(*e, @addr_list) = @_; | |
local($addr, $m); | |
my (@caller) = caller; | |
### 01: check recipients == myself? | |
for $addr (@addr_list) { | |
if (&LoopBackWarn($addr)) { | |
&Log("Notify: ERROR: the mail is not sent to $addr", | |
"since the addr to reply == ML or ML-Ctl-Addr"); | |
&Log(@caller); | |
$m .= "\nNotify: ERROR: the mail is not sent to [$addr]\n"; | |
$m .= "since the addr to reply == ML or ML-Ctl-Addr.\n"; | |
$m .= "-" x60; $m .= "\n"; | |
} | |
else { | |
print STDERR "CheckAddr2Reply 01: OK\t$addr\n" if $debug; | |
} | |
} | |
### 02: check the recipents | |
for $addr (@addr_list) { | |
if ($addr =~ /^($REJECT_ADDR)\@/i) { | |
$m .= "\nNotify: ERROR: the mail should not be sent to [$addr]\n"; | |
$m .= "since the addr is not-personal or other agent softwares\n"; | |
$m .= "-" x60; $m .= "\n"; | |
} | |
else { | |
print STDERR "CheckAddr2Reply 02: OK\t$addr\n" if $debug; | |
} | |
} | |
# if anything happens, append the information; | |
if ($m) { | |
# append the original message and forwarding to the maintainer; | |
$m .= "=" x60; $m .= "\n"; | |
$m .= "Original 'message' to send to the user:\n\n". $e{'message'}; | |
$m .= "=" x60; $m .= "\n"; | |
# message for the maintainer; | |
$e{'error'} .= $m; | |
} | |
$m ? 0 : 1; # if O.K., return 1; | |
} | |
# Check uid == euid && gid == egid | |
sub CheckUGID | |
{ | |
print STDERR "\nsetuid is not set $< != $>\n\n" if $< != $>; | |
print STDERR "\nsetgid is not set $( != $)\n\n" if $( ne $); | |
# die("YOU SHOULD NOT RUN fml AS ROOT NOR DAEMON\n") if $< == 0 || $< == 1; | |
} | |
sub GetGID { (getgrnam($_[0]))[2];} | |
sub InSecureP { (! &SecureP(@_));} | |
sub SecureP { | |
local(@xargv) = @_; &use('kernsubr'); &__SecureP(@xargv); | |
} | |
sub ValidAddrSpecP | |
{ | |
($_[0] !~ /\s|\033\$[\@B]|\033\([BJ]/ && | |
$_[0] =~ /^[\0-\177]+\@[\0-\177]+$/) ? 1 : 0; | |
} | |
# Check Looping | |
# return 1 if loopback | |
sub LoopBackWarning { &LoopBackWarn(@_);} | |
sub LoopBackWarn | |
{ | |
local($to) = @_; | |
local($a); | |
local(@c) = caller; | |
for $a ($MAIL_LIST, $CONTROL_ADDRESS, @MAIL_LIST_ALIASES, | |
"fmlserv\@$DOMAINNAME", "majordomo\@$DOMAINNAME", | |
"listserv\@$DOMAINNAME", "mead\@$DOMAINNAME") { | |
next if $a =~ /^\s*$/oi; # for null control addresses | |
if (&AddressMatch($to, $a)) { | |
&Debug("AddressMatch($to, $a)") if $debug; | |
&Log("Loop Back Warning: ", "$to eq $a"); | |
&Log("called from @c"); | |
&WarnE("Loop Back Warning: [$to eq $a] $ML_FN", | |
"Loop Back Warning: [$to eq $a]"); | |
return 1; | |
} | |
} | |
0; | |
} | |
sub RejectAddrP { | |
local(@xargv) = @_; &use('kernsubr'); &__RejectAddrP(@xargv); | |
} | |
sub EnvelopeFilter { | |
local(@xargv) = @_; &use('envf'); &__EnvelopeFilter(@xargv); | |
} | |
# QUOTA | |
sub CheckResourceLimit | |
{ | |
local(*e, $mode) = @_; | |
if ($mode eq 'member') { | |
&use('amctl'); return &MemberLimitP(*e); | |
} | |
elsif ($mode eq 'mti:distribute:max_traffic') { | |
&MTIProbe(*MTI, $From_address, 'distribute:max_traffic'); | |
} | |
elsif ($mode eq 'mti:command:max_traffic') { | |
&MTIProbe(*MTI, $From_address, 'command:max_traffic'); | |
} | |
} | |
####### Section: Macros for the use of user-side-definition (config.ph) | |
### moved to "libloadconfig.pl" | |
####### Section: misc | |
# "get ID by auto-increment" for user | |
sub GET_ID_AUTOINC | |
{ | |
local($f) = @_; | |
if (-f $f) { # Get the present ID | |
local($id) = &GetFirstLineFromFile($f); | |
$id++; | |
&Write2($id, $f); | |
$id; | |
} | |
else { | |
&Log("GET_ID_AUTOINC: cannot open $f"); | |
0; | |
} | |
} | |
# Get Next MIME Multipart Block | |
sub GetNextMPBPtr | |
{ | |
local(*e, $ptr) = @_; | |
local($pTop, $pEndHeader, $pBottom, $xbuf); | |
if ($e{'MIME:boundary'}) { | |
$pTop = index($e{'Body'}, $e{'MIME:boundary'}, $ptr); | |
$pEndHeader = index($e{'Body'}, "\n\n", $pTop); | |
$pBottom = index($e{'Body'}, $e{'MIME:boundary'}, $pEndHeader); | |
($pTop, $pEndHeader, $pBottom); | |
} else { | |
&Log("GetNextMPBPtr: no MIME boundary definition"); | |
(); | |
} | |
} | |
# Get Next MIME Multipart Block | |
sub GetNextMultipartBlock | |
{ | |
local(*e, $ptr) = @_; | |
local($pTop, $pEndHeader, $pBottom, $xbuf); | |
if ($e{'MIME:boundary'}) { | |
$pTop = index($e{'Body'}, $e{'MIME:boundary'}, $ptr); | |
$pEndHeader = index($e{'Body'}, "\n\n", $pTop); | |
$pBottom = index($e{'Body'}, $e{'MIME:boundary'}, $pEndHeader); | |
if ($pEndHeader > 0 && $pBottom > 0) { | |
$xhdr = substr($e{'Body'}, $pTop, $pEndHeader - $pTop); | |
$xbuf = substr($e{'Body'}, $pEndHeader, $pBottom - $pEndHeader); | |
($xhdr, $xbuf, $pBottom) | |
} else { | |
$NULL; | |
} | |
} else { | |
&Log("GetNextMultipartBlock: no MIME boundary definition"); | |
$NULL; | |
} | |
} | |
####### Section: Switch | |
sub SaveACL { $ProcCtlBlock{"main:ADDR_CHECK_MAX"} = $ADDR_CHECK_MAX;} | |
sub RetACL { $ADDR_CHECK_MAX = $ProcCtlBlock{"main:ADDR_CHECK_MAX"};} | |
####### Section: Event Handling Functions | |
sub SignalLog | |
{ | |
local($sig) = @_; | |
&Log("Caught SIG$sig, shutting down"); | |
sleep 1; | |
exit(1); | |
} | |
# Strange "Check flock() OK?" mechanism??? | |
# fml.pl exits under all cases after 12 hours (IT IS TOO LONG)! | |
sub Lock | |
{ | |
&SetEvent($TimeOut{'dead'} || 43200, 'TimeOut') if $HAS_ALARM; | |
# $LockQueueId is of mean under main locked phase | |
# "mget" runs after $LockQueueId is cleared. | |
$LockQueueId = &SetEvent($TimeOut{'lock'} || $TimeOut{'flock'} || 3600, | |
'TimeOut') if $HAS_ALARM; | |
$USE_FLOCK ? &Flock : (&use('lock'), &V7Lock); | |
} | |
# for installer ? | |
sub ReloadMySelf | |
{ | |
# If myself is changed after exec'ed, reload it again. | |
if ((-M __FILE__) < 0) { | |
&Log("FYI: reload myself against installation"); | |
for ("libkern.pl", keys %INC) { | |
next unless /^lib\S+\.pl$|\/lib\S+\.pl$/; | |
delete $INC{$_}; | |
&Log("reload $_ agasin against installation") if $debug; | |
require $_; | |
} | |
} | |
} | |
sub Unlock | |
{ | |
$USE_FLOCK ? &Funlock : &V7Unlock; | |
# $LockQueueId is of mean under main locked phase | |
if ($LockQueueId) { &ClearEvent($LockQueueId);} | |
} | |
# lock algorithm using flock system call | |
# if lock does not succeed, fml process should exit. | |
sub Flock | |
{ | |
local($min,$hour,$mday,$mon) = | |
(localtime(time + ($TimeOut{'flock'} || 3600)))[1..4]; | |
local($ut) = sprintf("%02d/%02d %02d:%02d", $mon + 1, $mday, $hour, $min); | |
$FlockFile = $FlockFile || | |
(open(LOCK,$FP_SPOOL_DIR) ? $FP_SPOOL_DIR : "$DIR/config.ph"); | |
$0 = "${FML}: Locked(flock) until $ut <$LOCKFILE>"; | |
# spool is also a file! | |
if (! open(LOCK, $FlockFile)) { | |
&Log("Flock:Cannot open FlockFile[$FlockFile]"); | |
die("Flock:Cannot open FlockFile[$FlockFile]"); | |
} | |
flock(LOCK, $LOCK_EX); | |
} | |
sub Funlock | |
{ | |
$0 = "${FML}: Unlock <$LOCKFILE>"; | |
flock(LOCK, $LOCK_UN); | |
close(LOCK); # unlock,close <kizu@ics.es.osaka-u.ac.jp> | |
} | |
# do not anything except for logging since now the fatal error case. | |
sub TimeOut | |
{ | |
&GetTime; | |
$0 = "${FML}: TimeOut $Now <$LOCKFILE>"; | |
# Now we may be not able to connect socket, isn't it? | |
# &WarnE("TimeOut: $MailDate ($From_address) $ML_FN", $NULL); | |
&Log("TimeOut[$$]: Caught SIGALRM, timeout"); | |
if ($TimeOutCalled++) { | |
kill 9, $$; | |
} | |
else { | |
$TimeOutCalled++; | |
exit(0); | |
} | |
} | |
sub SetEvent | |
{ | |
local($interval, $fp) = @_; | |
local($id, $now, $qp, $prev_qp); | |
$now = time; # the current time; | |
$id = $EventQueue++ + 1; # unique identifier | |
if ($interval < 60) { | |
&Log("SetEvent: input interval[$interval] is too short. reset to 60"); | |
$interval = $interval < 60 ? 60 : $interval; | |
} | |
# the first reference is a dummy (without $fp); | |
if ($id == 1) { | |
$EventQueue{"time:${id}"} = $now; | |
$EventQueue{"next:${id}"} = $id + 1; | |
$id = $EventQueue++ + 1; # unique identifier | |
} | |
# search the event queue for correct position; | |
# here search all entries; | |
for ($qp = $EventQueue{"next:1"}, $prev_qp = 1; | |
$qp ne ""; $qp = $EventQueue{"next:${qp}"}) { | |
if ($EventQueue{"time:$qp"} >= $now + $interval) { last;} | |
$prev_qp = $qp; | |
} | |
$EventQueue{"time:${id}"} = $now + $interval; | |
$EventQueue{"debug:${id}"} = $interval if $debug; | |
$EventQueue{"fp:${id}"} = $fp; | |
# "next:id = null" if the next link does not exist. | |
$EventQueue{"next:${prev_qp}"} = $id; # pointed to the current id; | |
$EventQueue{"next:${id}"} = $qp != $id ? $qp : ""; | |
&Tick; # tick(0); | |
$id; # return the identifier; | |
} | |
sub ClearEvent | |
{ | |
local($id) = @_; | |
local($now, $qp, $prev_qp); | |
# search the event queue for correct position; | |
# here search all entries; | |
for ($qp = $EventQueue{"next:1"}; | |
$qp ne ""; | |
$qp = $EventQueue{"next:${qp}"}) { | |
if ($qp == $id) { | |
$EventQueue{"next:$prev_qp"} = $EventQueue{"next:$qp"}; | |
&Debug("---ClearEvent: qp=$id fp=$EventQueue{\"fp:${id}\"}") | |
if $debug; | |
&Log("ClearEvent: qp=$EventQueue{\"fp:$id\"}") if $debug_tick; | |
undef $EventQueue{"fp:$id"}; | |
last; | |
} | |
$prev_qp = $qp; | |
} | |
} | |
# ### ATTENTION! alarm(3) may conflict sleep(3); ### | |
# alarm(3) do actions as long as if needed; | |
# Plural functions may be done at the same time; | |
# but it is responsible Tick(); | |
sub Tick | |
{ | |
local($cur, $fp, $qp); | |
&GetTime; $0 = "${FML}: Tick $Now <$LOCKFILE>"; | |
return unless $HAS_ALARM; | |
print STDERR "===Tick called\n" if $debug; | |
alarm(0); # before we sets in the routine, reset the current alarm; | |
$cur = time; | |
# scan all entries and do the function (if time < the current_time); | |
# so $qp (queue pointer) is set to the last action (< curret time) | |
for ($qp = $EventQueue{"next:1"}; | |
$EventQueue{"time:$qp"} <= $cur; | |
$qp = $EventQueue{"next:${qp}"}) { | |
$fp = $EventQueue{"fp:$qp"}; | |
next unless $fp; | |
# $EventQueue{time:$qp} and alarm(3) time may be at the same time! | |
undef $EventQueue{"fp:$qp"}; | |
&Log("Tick[$$]: run fp=$fp"); | |
eval("&$fp;"); | |
&Log($@) if $@; | |
alarm(0); | |
$cur = time; | |
} | |
$SIG{'ALRM'} = 'Tick'; | |
# info | |
&Debug("\tnow\tqp=$qp fp=$EventQueue{\"fp:${qp}\"}") if $debug; | |
# find the next $qp defined function pointer (time > cur_time) | |
# skip null functions since the functions has been expireed. | |
# *1 ignore $qp=0 case. | |
for (; $qp && !$EventQueue{"fp:${qp}"}; $qp = $EventQueue{"next:${qp}"}) { | |
; | |
} | |
&Debug("\tfinally\tqp=$qp fp=$EventQueue{\"fp:${qp}\"}") if $debug; | |
$cur = $EventQueue{"time:${qp}"} - time; | |
$cur = $cur > 0 ? $cur : 3; | |
alarm($cur); # considering context switching; | |
&Log("Tick[$$]::alarm($cur)") if $debug_tick; | |
if ($debug) { | |
&OutputEventQueue; | |
&Debug("\tnow set alarm($cur) for the queue id $qp"); | |
&Debug("\tfp = $qp->$EventQueue{\"fp:${qp}\"}") if $debug_tick; | |
} | |
if ($debug_tick) { | |
for ($qp = 1; $qp ne ""; $qp = $EventQueue{"next:${qp}"}) { | |
$cur = $EventQueue{"time:${qp}"} - time; | |
if ($cur >= 0) { # the future events list | |
&Log(sprintf(" when=%-5d qp=%-2d link->%-2d fp=%s", | |
$cur, | |
$qp, | |
$EventQueue{"next:$qp"}, | |
$EventQueue{"fp:$qp"})); | |
} | |
} | |
} | |
} | |
1; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment