# # MailScanner - SMTP E-Mail Virus Scanner # Copyright (C) 2002 Julian Field # # $Id: Lock.pm 3638 2006-06-17 20:28:07Z sysjkf $ # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # The author, Julian Field, can be contacted by email at # Jules@JulianField.net # or by paper mail at # Julian Field # Dept of Electronics & Computer Science # University of Southampton # Southampton # SO17 1BJ # United Kingdom # # Provide functions to deal with opening + locking spool files package MailScanner::Lock; use strict; use Fcntl qw(:DEFAULT :flock); use POSIX qw(:unistd_h :errno_h); #use MailScanner::Log; use vars qw($FLOCK_STRUCT); my $have_module; my $LockType; sub ReportLockType { return $LockType; } # Run-time initialisation sub initialise { eval { require MailScanner::Fcntl; import MailScanner::Fcntl (@MailScanner::Fcntl::EXPORT, @MailScanner::Fcntl::EXPORT_OK); 1; }; $have_module = ($@ eq ""?1:0); # Determine locktype to use $LockType = (MailScanner::Config::Value('locktype'))? MailScanner::Config::Value('locktype') : $global::MS->{mta}->{LockType}; #print STDERR "Debug = " . MailScanner::Config::Value('debug') . "\n"; #print STDERR "Config Value = " . MailScanner::Config::Value('locktype') . "\n"; #print STDERR "Global = " . $global::MS->{mta}->{LockType} . "\n"; #print STDERR "Lock Type = $LockType\n"; MailScanner::Log::DebugLog("lock.pl sees Config LockType = " . $LockType); #MailScanner::Log::DebugLog("lock.pl sees MTA::LockType = ".$MTA::LockType); MailScanner::Log::DebugLog("lock.pl sees have_module = ".$have_module); # module has bugs $LockType =~ /posix/ and $have_module and $LockType = "module"; MailScanner::Log::InfoLog("Using locktype = " . $LockType); # Note that in IEEE Std 1003.1-2001, # "The interaction between fcntl() and lockf() locks is unspecified." # # (bother) # # And we shouldn't really call these "posix" locks, as although they are # specified in POSIX, there are two possible types, which may or may not # be the same. DOH! # Determine correct struct_flock to use at include time # HORRIBLY HARDWIRED # would like to "use File::lockf" but that would make # installation harder. And lockf isn't guaranteed to # do the same thing as fcntl :( # # CPAN File::Lock also appears to be broken (doesn't build, then when # built, doesn't pass it's own tests - including segfaulting) # # So I'll do it myself. if ($LockType =~ /posix/i) { for ($^O) { # $^O returns: # Linux: "linux" # OpenBSD: "openbsd" # Solaris: "solaris" # SunOS4: "sunos" # AIX: "aix" # IRIX: "irix" # if (/bsd/) { MailScanner::Log::InfoLog("Creating hardcoded struct_flock subroutine for $^O (BSD-type)"); # from "man fcntl" and /usr/include/sys/fcntl.h on OBSD 2.7: # struct flock { # off_t l_start; /* starting offset */ # off_t l_len; /* len = 0 means until end of file */ # pid_t l_pid; /* lock owner */ # short l_type; /* lock type: read/write, etc. */ # short l_whence; /* type of l_start */ # }; # # FreeBSD exim.tulsaconnect.com 4.5-RELEASE FreeBSD 4.5-RELEASE #0: Sun May # 19 23:53:40 CDT 2002 # # from /usr/include/sys/fcntl.h: # # /* # * Advisory file segment locking data type - # * information passed to system by user # */ # struct flock { # off_t l_start; /* starting offset */ # off_t l_len; /* len = 0 means until end of file */ # pid_t l_pid; /* lock owner */ # short l_type; /* lock type: read/write, etc. */ # short l_whence; /* type of l_start */ # }; # # FreeBSD off_t is typedef'd to _BSD_OFF_T_ which is in turn __int64_t # eval <<'__EOD'; # XXX: should be Q not LL but # "Quads are available only if your system supports 64-bit # integer values _and_ if Perl has been compiled to support those. # Causes a fatal error otherwise." # $FLOCK_STRUCT = 'LL LL L l s'; sub struct_flock { my ($xxstart, $start, $xxlen, $len, $pid, $type, $whence); if (wantarray) { ($xxstart, $start, $xxlen, $len, $pid, $type, $whence) = unpack($FLOCK_STRUCT, $_[0]); return ($type, $whence, $start, $len, $pid); } else { ($type, $whence, $start, $len, $pid) = @_; ($xxstart, $xxlen) = (0,0); return pack($FLOCK_STRUCT, $xxstart, $start, $xxlen, $len, $pid, $type, $whence); } } __EOD if ($@ ne "") { MailScanner::Log::DieLog("Unable to create struct_flock subroutine: $@"); } next; } if ($_ eq 'linux') { MailScanner::Log::InfoLog("Creating hardcoded struct_flock subroutine for $^O (Linux-type)"); # from linux 2.2 /usr/include/asm/fcntl.h: # struct flock { # short l_type; # short l_whence; # off_t l_start; # off_t l_len; # pid_t l_pid; # }; # # size of off_t appears to depend on whether we've got large file support etc. ugh. # # was previously using ssx32 to pack and sslls to unpack # eval <<'__EOD'; $FLOCK_STRUCT = 's s LL LL I'; sub struct_flock { my ($start, $len, $pid, $type, $whence); if (wantarray) { # Interpreting a returned struct ($type, $whence, $start, $len, $pid) = unpack($FLOCK_STRUCT, $_[0]); return ($type, $whence, $start, $len, $pid); } else { # Building a struct ($type, $whence, $start, $len, $pid) = @_; return pack($FLOCK_STRUCT, $type, $whence, $start, $len, $pid); } } __EOD if ($@ ne "") { MailScanner::Log::DieLog("Unable to create struct_flock subroutine: $@"); } next; } if (/solaris|irix|aix/) { MailScanner::Log::InfoLog("Creating hardcoded struct_flock subroutine for $^O (misc-type)"); # from solaris 2.7 /usr/include/sys/fcntl.h: # /* regular version, for both small and large file compilation environment */ # typedef struct flock { # short l_type; # short l_whence; # off_t l_start; # off_t l_len; /* len == 0 means until end of file */ # int l_sysid; # pid_t l_pid; # long l_pad[4]; /* reserve area */ # } flock_t; # # and: # /* transitional large file interface version */ # # #if defined(_LARGEFILE64_SOURCE) # # typedef struct flock64 { # short l_type; # short l_whence; # off64_t l_start; # off64_t l_len; /* len == 0 means until end of file */ # int l_sysid; # pid_t l_pid; # long l_pad[4]; /* reserve area */ # } flock64_t; # # and: # /* SVr3 flock type; needed for rfs across the wire compatibility */ # typedef struct o_flock { # int16_t l_type; # int16_t l_whence; # int32_t l_start; # int32_t l_len; /* len == 0 means until end of file */ # int16_t l_sysid; # int16_t l_pid; # } o_flock_t; # # so even thought that one's not used in solaris any more, I guess there'll # be systems "out there" that use it. # # # From IRIX 5.3 man pages: # The structure flock describes a file lock. It includes the following # members: # # short l_type; /* Type of lock */ # short l_whence; /* Flag for starting offset */ # off_t l_start; /* Relative offset in bytes */ # off_t l_len; /* Size; if 0 then until EOF */ # long l_sysid; /* Returned with F_GETLK */ # pid_t l_pid; /* Returned with F_GETLK */ # # # The structure flock64 describes a file lock for use on large files. It # includes the following members: # # short l_type; /* Type of lock */ # short l_whence; /* Flag for starting offset */ # off64_t l_start; /* Relative offset in bytes */ # off64_t l_len; /* Size; if 0 then until EOF */ # long l_sysid; /* Returned with F_GETLK */ # pid_t l_pid; /* Returned with F_GETLK */ # # Apparently the 64-bit version is used with a different fcntl command # (F_SETLK64 as opposed to F_SETLK). # # # It seems that under AIX, a struct flock is: # l_type, l_whence, l_start, l_len, l_sysid, l_pid, l_vfs # Again, things vary depending on whether large file support is being # used. # eval <<'__EOD'; # TEST THIS! $FLOCK_STRUCT = 's s L L I I'; # ignore solaris' pad on the end sub struct_flock { my ($type, $whence, $start, $len, $sysid, $pid); if (wantarray) { ($type, $whence, $start, $len, $sysid, $pid) = unpack($FLOCK_STRUCT, $_[0]); return ($type, $whence, $start, $len, $pid); } else { ($type, $whence, $start, $len, $pid) = @_; $sysid = 0; return pack($FLOCK_STRUCT, $type, $whence, $start, $len, $sysid, $pid); } } __EOD if ($@ ne "") { MailScanner::Log::DieLog("Unable to create struct_flock subroutine: $@"); } next; } MailScanner::Log::DieLog("1\n2\n3\n4\n5\nDon't know how to do fcntl locking on '$^O'\nPlease contact mailscanner authors.5\n4\n3\n2\n1"); } } } # Open and lock a file. # # Pass in a filehandle, a filespec (including ">", "<", or # whatever on the front), and (optionally) the type of lock # you want - "r" or "s" for shared/read lock, or pretty much # anything else (but "w" or "x" really) for exclusive/write # lock. # # Lock type used (flock or fcntl/lockf/posix) depends on # config. If you're using posix locks, then don't try asking # for a write-lock on a file opened for reading - it'll fail # with EBADF (Bad file descriptor). # # If $quiet is true, then don't print any warning. # sub openlock { my ($fh, $fn, $rw, $quiet) = @_; my ($struct_flock); defined $rw or $rw = ((substr($fn,0,1) eq '>')?"w":"r"); $rw =~ /^[rs]/i or $rw = 'w'; # Set umask every time as SpamAssassin might have reset it #umask 0077; # Now cleared up after SpamAssassin runs unless (open($fh, $fn)) { MailScanner::Log::NoticeLog("Could not open file $fn: %s", $!) unless $quiet; return 0; } if ($LockType =~ /module/i) { #MailScanner::Log::DebugLog("Using module to lock $fn"); MailScanner::Fcntl::setlk($fh, ($rw eq 'w' ? F_WRLCK : F_RDLCK)) == 0 and return 1; } elsif ($LockType =~ /posix/i) { # Added 3 zeroes for 'start, length, + pid', # otherwise pack was being called with undefined values -- nwp #MailScanner::Log::DebugLog("Using fcntl() to lock $fn"); $struct_flock = struct_flock(($rw eq 'w' ? F_WRLCK : F_RDLCK),0,0,0,0); fcntl($fh, F_SETLK, $struct_flock) and return 1; } elsif ($LockType =~ /flock/i) { #MailScanner::Log::DebugLog("Using flock() to lock $fn"); flock($fh, ($rw eq 'w' ? LOCK_EX : LOCK_SH) + LOCK_NB) and return 1; } else { MailScanner::Log::DebugLog("Not locking spool file $fn"); return 1; } close ($fh); if (($! == POSIX::EAGAIN) || ($! == POSIX::EACCES)) { MailScanner::Log::DebugLog("Failed to lock $fn: %s", $!) unless $quiet; } else { MailScanner::Log::NoticeLog("Failed to lock $fn with unexpected error: %s", $!); } return 0; } sub unlockclose { my ($fh) = @_; if ($LockType =~ /module/i) { MailScanner::Fcntl::setlk($fh, F_UNLCK); } elsif ($LockType =~ /posix/i) { fcntl($fh, F_SETLK, struct_flock(F_UNLCK,0,0,0,0)); } elsif ($LockType =~ /flock/i) { flock($fh, LOCK_UN); } # else { # default - do nothing, as we didn't lock it in the first place # } close ($fh); return 1; } 1;