# -*-Perl-*-
################################################################
###
### LocalMbox.pm
###
### Author: Internet Message Group
### Created: Apr 23, 1997
### Revised: Jul 4, 2004
###
my $PM_VERSION = "IM::LocalMbox.pm version 20031028(IM146)";
package IM::LocalMbox;
require 5.003;
require Exporter;
use Fcntl;
use IM::Config;
use IM::Util;
use IM::MsgStore qw(store_message exec_getsbrfile fsync);
use integer;
use strict;
use vars qw(@ISA @EXPORT $getchk_hook);
@ISA = qw(Exporter);
@EXPORT = qw(local_get_msg);
use vars qw($locked_by_file $locked_by_flock);
#################################
# local mailbox access routines #
#################################
##### LOCAL SPOOL MANAGEMENT #####
#
# local_get_msg(src, dst, how)
# check, from, get
#
sub local_get_msg($$$$$) {
my($src, $dst, $how, $lock_type, $noscan) = @_;
my($need_lock, $qmail_ok, $msgs, $l, $file, $p);
my(@MailDrops);
if ($how eq 'get') {
$need_lock = 1;
} else {
$need_lock = 0;
}
(my $mbox = $src) =~ s/local:?//i;
if (&mbox_style() =~ /qmail/i) {
$qmail_ok = 1;
im_notice("qmail access enabled.\n");
} else {
$qmail_ok = 0;
im_notice("qmail access disabled.\n");
}
# my $user = $ENV{'USER'} || $ENV{'LOGNAME'} || im_getlogin();
my $user = im_getlogin();
my $home = $ENV{'HOME'};
if ($user eq '' || $home eq '') {
my @pw = getpwuid($<);
$user = $pw[0] unless ($user);
$home = $pw[7] unless ($home);
}
# set default
unless ($mbox) {
if ($qmail_ok) {
push(@MailDrops, $ENV{'MAILDIR'}) if ($ENV{'MAILDIR'});
push(@MailDrops, $ENV{'MAILDROP'}) if ($ENV{'MAILDROP'});
push(@MailDrops, $ENV{'MAIL'}) if ($ENV{'MAIL'});
push(@MailDrops, "$home/Maildir");
foreach $p (@MailDrops) {
if ((-d $p && -d "$p/new" && -d "$p/cur") || -f $p) {
$mbox = $p;
last;
}
}
}
unless ($mbox) {
@MailDrops = (
"/var/mail/$user",
"/var/spool/mail/$user",
"/usr/mail/$user",
"/usr/spool/mail/$user"
);
unshift(@MailDrops, "$home/Mailbox") if ($qmail_ok);
foreach $p (@MailDrops) {
if (-f $p) {
$mbox = $p;
last;
}
}
}
unless ($mbox) {
im_warn("mailbox for $user not found\n");
return -1;
}
}
im_notice("mailbox for $user is $mbox\n");
$getchk_hook = getchksbr_file();
if ($getchk_hook) {
if ($getchk_hook =~ /(.+)/) {
if ($main::INSECURE) {
im_warn("Sorry, GetChkSbr is ignored for SUID root script.\n");
} else {
if ($> != 0) {
$getchk_hook = $1; # to pass through taint check
}
if (-f $getchk_hook) {
require $getchk_hook;
} else {
im_err("scan subroutine file $getchk_hook not found.\n");
}
}
}
}
if (-d $mbox) {
# DIRECTORY
im_info("Getting new messages from maildir into $dst...\n")
if ($how eq 'get');
my $msgs = 0;
if ($qmail_ok && -d "$mbox/new" && -d "$mbox/cur") {
$msgs = process_maildir($mbox, $dst, $how, $noscan);
} else {
unless (opendir(FLDR, $mbox)) {
im_warn("can't open directory: $mbox\n");
return -1;
}
my $f;
foreach $f (sort {$a <=> $b} readdir(FLDR)) {
if ($f =~ /^\d+$/ && -s "$mbox/$f") {
if (process_file("$mbox/$f", $dst, $how, $noscan) < 0) {
return -1;
}
if ($how eq 'get' && $main::opt_keep == 0) {
if ($> != 0) {
$f =~ /(.+)/; # may be tainted
$f = $1; # clean up
}
unlink("$mbox/$f");
}
$msgs++;
}
}
closedir(FLDR);
}
if ($msgs == 0) {
if ($how eq 'check') {
im_msg("no message in local maildir.\n");
} elsif ($how eq 'from') {
im_info("no message in local maildir.\n");
} else {
im_info("no messages in local maildir.\n");
}
return 0;
}
if ($how eq 'check') {
im_msg("$msgs message(s) in local maildir.\n");
} elsif ($how eq 'from') {
im_info("$msgs message(s) in local maildir.\n");
} elsif ($how eq 'get') {
flush('STDOUT');
im_info("$msgs message(s).\n");
}
return $msgs;
} elsif (-s $mbox) {
# FILE and not ZERO
if ($need_lock) {
if (&local_lockmbox($mbox, $lock_type) < 0) {
&local_unlockmbox($mbox);
return -1;
}
}
if ($how eq 'get' && $getchk_hook ne '' && !$main::opt_keep) {
my $tmpmbox = expand_path('tmp_mbox');
if (local_copymbox($mbox, $tmpmbox) < 0) {
return -1;
}
unless (im_open(\*SAVE, "+>$mbox")) {
im_err("can't open $mbox ($!).\n");
close(SAVE);
return -1;
}
if (($msgs = process_mbox($tmpmbox, $dst, $how, $mbox, $noscan)) < 0) {
close(SAVE);
if (local_copymbox($tmpmbox, $mbox) < 0) {
im_err("write back to $mbox failed. $tmpmbox preserved ($!).\n");
} else {
unlink($tmpmbox);
}
&local_unlockmbox($mbox) if ($need_lock);
return -1;
}
if (&unixp() && !&no_sync()) {
if (fsync(fileno(SAVE)) < 0) {
im_err("write back to $mbox failed ($!).\n");
close(SAVE);
unlink($tmpmbox) if (-z $tmpmbox);
return -1;
}
}
truncate(SAVE, tell(SAVE));
unlink($tmpmbox);
} else {
if (($msgs = process_mbox($mbox, $dst, $how, '', $noscan)) < 0) {
&local_unlockmbox($mbox) if ($need_lock);
return -1;
}
if ($how eq 'get') {
&local_empty($mbox) unless ($main::opt_keep);
}
}
&local_unlockmbox($mbox) if ($need_lock);
return $msgs;
} else {
if ($how eq 'check') {
im_msg("no message in local mailbox.\n");
} elsif ($how eq 'from') {
im_info("no message in local mailbox.\n");
} else {
im_info("no messages in local mailbox.\n");
}
return 0;
}
}
sub local_copymbox($$) {
my($src, $dst) = @_;
im_debug("copy from $src to $dst\n") if (&debug('local'));
unless (im_open(\*SRC, "<$src")) {
return -1;
}
unless (im_open(\*DST, "+>$dst")) {
return -1;
}
while () {
unless (print DST) {
im_err("writing to $dst failed ($!).\n");
close(DST);
close(SRC);
unlink($dst) if (-z $dst);
return -1;
}
}
if (&unixp() && !&no_sync()) {
if (fsync(fileno(DST)) < 0) {
im_err("writing to $dst failed ($!).\n");
close(DST);
close(SRC);
unlink($dst) if (-z $dst);
return -1;
}
}
truncate(DST, -s SRC);
close(DST);
close(SRC);
return 0;
}
sub process_maildir($$$$) {
my($maildir, $dst, $how, $noscan) = @_;
my($msgs, $f, $dir);
unless (-d "$maildir/new" && -r "$maildir/new" && -x "$maildir/new"
&& -d "$maildir/cur" && -r "$maildir/cur" && -x "$maildir/cur") {
im_warn("can't open maildir: $dir\n");
return -1;
}
$msgs = 0;
foreach $dir ("$maildir/cur", "$maildir/new") {
unless (opendir(FLDR, $dir)) {
im_warn("can't open directory: $dir\n");
return -1;
}
foreach $f (sort {(-M $b) <=> (-M $a) || $a cmp $b} readdir(FLDR)) {
if ($f =~ /^\d+\.[^.:\/]+\./ && -s "$dir/$f") {
my $ret = process_file("$dir/$f", $dst, $how, $noscan);
next if ($ret > 0); # skip by rule
if ($ret < 0) {
closedir(FLDR);
return -1;
}
if ($how eq 'get' && $main::opt_keep == 0) {
if ($> != 0) {
$dir =~ /(.+)/; # may be tainted
$dir = $1; # clean up
$f =~ /(.+)/; # may be tainted
$f = $1; # clean up
}
unlink("$dir/$f");
}
$msgs++;
}
}
closedir(FLDR);
}
return $msgs;
}
sub process_file($$$$) {
my($mbox, $dst, $how, $noscan) = @_;
my($format, $msgs, $rp, $length, $inheader, @Message);
local(*MBOX);
im_notice("opening file ($mbox)\n");
unless (im_open(\*MBOX, "<$mbox")) {
# XXX not found or unreadable...
return -1;
}
while () {
push (@Message, $_);
}
if ($getchk_hook ne '') {
my %head;
lcl_store_header(\%head, \@Message);
unless (eval { &getchk_sub(\%head); }) {
close(MBOX);
return 1
}
}
if ($how eq 'get') {
if (store_message(\@Message, $dst, $noscan) < 0) {
close(MBOX);
return -1;
}
}
close(MBOX);
&exec_getsbrfile($dst);
return 0;
}
sub process_mbox($$$$$) {
my($mbox, $dst, $how, $save, $noscan) = @_;
my($format, $msgs, $length, $inheader, @Message);
local(*MBOX);
my($first_line, $FIRST_LINE);
my($mbox_filter);
if ($how eq 'get') {
im_info("Getting new messages from local mailbox into $dst...\n");
}
$mbox_filter = &mbox_filter();
if ($mbox_filter ne '') {
if ($mbox_filter =~ /(.+)/) {
if ($main::INSECURE) {
im_warn("Sorry, MboxFilter is ignored for SUID root script.\n");
$mbox_filter = '';
} else {
if ($> != 0) {
$mbox_filter = $1; # to pass through taint check
}
im_warn("opening MBOX ($mbox_filter $mbox)\n") if (&verbose);
unless (im_open(\*MBOX, "$mbox_filter $mbox |")) {
im_err("MboxFilter failed ($!).\n");
return -1;
}
}
} else {
$mbox_filter = '';
}
}
if ($mbox_filter eq '') {
im_warn("opening MBOX ($mbox)\n") if (&verbose);
unless (im_open(\*MBOX, "<$mbox")) {
# XXX not found or unreadable...
return -1;
}
}
chomp($first_line = );
if ($first_line =~ /^From /) {
$format = 'UNIX';
$FIRST_LINE = $first_line;
} elsif ($first_line =~ /^\001\001\001\001$/) {
$format = 'MMDF';
} elsif ($first_line =~ /^BABYL OPTIONS:/) {
$format = 'RMAIL';
} else {
im_warn("invalid mbox format: $mbox\n");
return -1;
}
$msgs = 0;
while ($first_line ne '') {
im_notice("reading a message ($first_line)\n");
if ($msgs > 0 && $format eq 'MMDF') {
$first_line = ;
if ($first_line !~ /^\001\001\001\001$/) {
last;
}
}
if ($format eq 'RMAIL') {
while () {
last if /^\*\*\* EOOH \*\*\*$/;
}
}
if ($how eq 'from' && $format eq 'UNIX') {
print "$first_line\n";
}
if ($format eq 'UNIX' && $main::opt_rpath ne 'ignore') {
# convert UNIX From_ into Return-Path
my $rp = $first_line;
$rp =~ s/^From +//;
$rp =~ s/ +[A-Z][a-z][a-z] [A-Z][a-z][a-z] [\d ]\d \d\d:\d\d.*//;
$rp = "<$rp>" if ($rp !~ /^<.*>$/);
@Message = ("Return-Path: $rp\n");
} else {
@Message = ();
}
$first_line = '';
$inheader = 1;
$length = -1;
while () {
if ($format eq 'MMDF' && $_ =~ /^\001\001\001\001$/) {
$first_line = 'MMDF';
last;
} elsif ($format eq 'UNIX' && $length <= 0
&& /^From / && $Message[$#Message] eq "\n") {
chomp($first_line = $_);
last;
} elsif ($format eq 'RMAIL' && /^\x1f/) {
chomp($first_line = );
last;
} elsif ($inheader) {
if ($format eq 'MMDF' && $how eq 'from') {
print "$_" if (/^From:/i);
}
# XXX continuous line processing needed
push(@Message, $_)
unless (/^Return-Path:/i && $main::opt_rpath eq 'replace');
# for Solaris 2.x or ...
# XXX option
if ($main::Obey_CL && /^Content-Length:(.*)/i) {
chomp($length = $1);
}
$inheader = 0 if (/^\n$/);
} else {
push(@Message, $_);
$length -= length($_) if ($length > 0);
}
}
if ($Message[$#Message] eq "\n") {
pop(@Message);
}
if ($getchk_hook) {
my %head;
lcl_store_header(\%head, \@Message);
unless (eval { &getchk_sub(\%head); }) {
if (save_message(\@Message, $save, $format, $FIRST_LINE) < 0) {
close(MBOX);
return -1;
}
next;
}
}
$msgs++ if ($#Message >= 0);
if ($how eq 'get') {
if (store_message(\@Message, $dst, $noscan) < 0) {
close(MBOX);
return -1;
}
}
}
close(MBOX);
if ($how eq 'check') {
im_msg("$msgs message(s) in local mailbox.\n");
} elsif ($how eq 'from') {
im_info("$msgs message(s) in local mailbox.\n");
} elsif ($how eq 'get') {
flush('STDOUT');
im_info("$msgs message(s).\n");
&exec_getsbrfile($dst);
}
return $msgs;
}
sub save_message($$$$) {
my($msg, $save, $mode, $fline) = @_;
im_debug("saving to $save\n") if (&debug('local'));
if ($mode eq 'UNIX') {
shift(@$msg);
unless (print SAVE "$fline\n") {
im_err("writing to $save failed ($!).\n");
close(SAVE);
return -1;
}
} elsif ($mode eq 'RMAIL') {
if (tell(SAVE) == 0) {
unless (print SAVE "BABYL OPTIONS:\n") {
im_err("writing to $save failed ($!).\n");
close(SAVE);
return -1;
}
}
} elsif ($mode eq 'MMDF') {
if (tell(SAVE) == 0) {
unless (print SAVE "\001\001\001\001\n") {
im_err("writing to $save failed ($!).\n");
close(SAVE);
return -1;
}
}
}
foreach (@$msg) {
unless (print SAVE) {
im_err("writing to $save failed ($!).\n");
close(SAVE);
return -1;
}
}
if ($mode eq 'UNIX') {
unless (print SAVE "\n") {
im_err("writing to $save failed ($!).\n");
close(SAVE);
return -1;
}
} elsif ($mode eq 'RMAIL') {
unless (print SAVE "*** EOOH ***\n") {
im_err("writing to $save failed ($!).\n");
close(SAVE);
return -1;
}
} elsif ($mode eq 'MMDF') {
}
return 0;
}
sub local_empty($) {
my $mbox = shift;
unless (truncate($mbox, 0)) {
unless (im_open(\*MBOX, ">$mbox")) {
im_warn("mailbox can not be zeroed.\n");
return;
}
close(MBOX);
}
im_notice("local mailbox has been zeroed.\n");
}
sub LOCK_SH { 1 }
sub LOCK_EX { 2 }
sub LOCK_NB { 4 }
sub LOCK_UN { 8 }
sub local_lockmbox($$) {
my($base, $type) = @_;
my $retry = 0;
im_warn("creating lock file with uid=$> gid=$)\n") if (&debug('local'));
$locked_by_file = 0;
$locked_by_flock = 0;
if ($type =~ /file/) {
# while (!sysopen(LOCK, "$base.lock", O_RDWR()|O_CREAT()|O_EXCL())) {
# if ($retry >= 10) {
# im_warn("can't create $base.lock ($!).\n");
# return -1;
# }
# im_warn("mailbox is processed by another process, waiting...\n")
# if ($retry == 0);
# $retry++;
# sleep(5);
# }
unless (im_open(\*LOCKFILE, ">$base.$$")) {
im_warn("can't create lock file $base.$$ ($!).\n");
im_warn("use 'flock' instead of 'file' if possible.\n");
return -1;
}
print LOCKFILE "$$\n";
close(LOCKFILE);
while (!link("$base.$$", "$base.lock")) {
if ($retry >= 10) {
im_warn("can't create $base.lock ($!).\n");
unlink("$base.$$");
return -1;
}
im_warn("mailbox is owned by another process, waiting...\n")
if ($retry == 0);
$retry++;
sleep(5);
}
unlink("$base.$$");
$locked_by_file = 1;
}
if ($type =~ /flock/) {
unless (im_open(\*LOCK_FH, "+<$base")) {
im_err "can't open $base ($!).\n";
return -1;
}
if (! &win95p) {
unless (flock (LOCK_FH, LOCK_EX|LOCK_NB)) {
im_warn "can't flock $base ($!).\n";
return -1;
}
}
$locked_by_flock = 1;
}
return 0;
}
sub local_unlockmbox($) {
my $base = shift;
my $rcode = 0;
im_debug("removing lock file with uid=$> gid=$)\n") if (&debug('local'));
if ($locked_by_file) {
if (-f "$base.lock" && unlink("$base.lock") <= 0) {
im_warn("can't unlink lock file $base.lock ($!).\n");
$rcode = -1;
}
$locked_by_file = 0;
}
if ($locked_by_flock) {
if (! &win95p) {
flock(LOCK_FH, LOCK_UN);
}
$locked_by_flock = 0;
}
return $rcode;
}
sub lcl_store_header($$) {
my($href, $msg) = @_;
my($line);
foreach (@$msg) {
my $l = $_;
chomp($l);
last if ($l =~ /^$/);
if ($l =~ /^\s/) {
$l =~ s/\s+/ /;
$line .= $l;
next;
} else {
lcl_set_line($href, $line);
$line = $l;
}
}
lcl_set_line($href, $line);
}
sub lcl_set_line($$) {
my($href, $line) = @_;
return unless ($line =~ /^([^:]*):\s*(.*)$/);
my $label = lc($1);
return if ($label eq 'received');
if (defined($href->{$label})) {
# if ($STRUCTURED_HASH{$label}) {
# $href->{$label} .= ", ";
# } else {
$href->{$label} .= "\n\t";
# }
$href->{$label} .= $2;
} else {
$href->{$label} = $2;
}
}
1;
__END__
=head1 NAME
IM::LocalMbox - local mailbox managing
=head1 SYNOPSIS
use IM::LocalMbox;
$num_msgs = &local_get_msg(source_mailbox, destination_folder, access_mode);
=head1 DESCRIPTION
The I module handles local mailbox.
MH folder, MMDF file, mbox, and Maildir are supported.
This modules is provided by IM (Internet Message).
=head1 EXAMPLES
$mbox = 'local:/var/mail/motonori';
$folder = '+inbox'
$num_msgs = &local_get_msg($mbox, $folder, 'get');
=head1 COPYRIGHT
IM (Internet Message) is copyrighted by IM developing team.
You can redistribute it and/or modify it under the modified BSD
license. See the copyright file for more details.
=cut
### Copyright (C) 1997, 1998, 1999 IM developing team
### All rights reserved.
###
### Redistribution and use in source and binary forms, with or without
### modification, are permitted provided that the following conditions
### are met:
###
### 1. Redistributions of source code must retain the above copyright
### notice, this list of conditions and the following disclaimer.
### 2. Redistributions in binary form must reproduce the above copyright
### notice, this list of conditions and the following disclaimer in the
### documentation and/or other materials provided with the distribution.
### 3. Neither the name of the team nor the names of its contributors
### may be used to endorse or promote products derived from this software
### without specific prior written permission.
###
### THIS SOFTWARE IS PROVIDED BY THE TEAM AND CONTRIBUTORS ``AS IS'' AND
### ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
### IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
### PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE TEAM OR CONTRIBUTORS BE
### LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
### CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
### SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
### BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
### WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
### OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
### IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.