package openwebmail::storage_mbox;
use strict;

use Fcntl qw(:DEFAULT :flock);

#
# storage_mbox.pl - store mail in mbox format
# from original openwebmail 2.01
# 2003/06/07 Laurent Frigault - lfrigault@agneau.org
#

################## STOREAGE_GETFOLDERS ####################
# return list of valid folders and calc the total folder usage(0..100%)
sub storage_getfolders($$$$$$)
{
   my ($folderdir,$r_folders, $r_usage, $user, $uuid, $ugid)=@_;
   my @delfiles=();
   my @userfolders;
   my $totalsize = 0;
   my $filename;

   my @fdirs=($folderdir);		# start with root folderdir
   while (my $fdir=pop(@fdirs)) {
      opendir (FOLDERDIR, "$fdir") or
    	 &main::openwebmailerror("$main::lang_err{'couldnt_open'} $fdir! ($!)");

      while (defined($filename = readdir(FOLDERDIR))) {
         ($filename =~ /^(.+)$/) && ($filename = $1);   # untaint data from readdir
         next if ( $filename eq "." || $filename eq ".." );
         if (-d "$fdir/$filename" && $filename!~/^\./) { # recursive into non dot dir
            push(@fdirs,"$fdir/$filename");
            next;
         }

         # find internal file that are stale
         if ( $filename=~/^\.(.*)\.db$/ ||
              $filename=~/^\.(.*)\.dir$/ ||
              $filename=~/^\.(.*)\.pag$/ ||
              $filename=~/^(.*)\.lock$/ ||
              ($filename=~/^\.(.*)\.cache$/ &&
               $filename ne ".search.cache" &&
               $filename ne ".webdisk.cache")
            ) {
            if ($1 ne $user &&
                $1 ne 'address.book' &&
                $1 ne 'filter.book' &&
                ! -f "$folderdir/$1" ) {
               # dbm or cache whose folder doesn't exist
               push (@delfiles, "$folderdir/$filename");
               next;
            }
         }

         # summary file size
         $totalsize += ( -s "$folderdir/$filename" ) || 0;

         # skip openwebmail internal files (conf, dbm, lock, search caches...)
         next if ( $filename=~/^\./ || $filename =~ /\.lock$/);

         # find all user folders
         if ( $filename ne 'saved-messages' &&
              $filename ne 'sent-mail' &&
              $filename ne 'saved-drafts' &&
              $filename ne 'mail-trash' ) {
            push(@userfolders, substr("$fdir/$filename",length($folderdir)+1));
         }
      }

      closedir (FOLDERDIR) or
         &main::openwebmailerror("$main::lang_err{'couldnt_close'} $folderdir! ($!)");
   }
   unlink(@delfiles) if ($#delfiles>=0);

   @{$r_folders}=();
   push (@{$r_folders},
         'INBOX', 'saved-messages', 'sent-mail', 'saved-drafts', 'mail-trash',
         sort(@userfolders));

   # add INBOX size to totalsize
   my ($spoolfile,$headerdb)=&main::get_folderfile_headerdb($user, 'INBOX');
   if ( -f $spoolfile ) {
      $totalsize += ( -s "$spoolfile" ) || 0;
   } else {
      # create spool file with user uid, gid if it doesn't exist
      ($spoolfile =~ /^(.+)$/) && ($spoolfile = $1); # untaint ...
      open (F, ">>$spoolfile"); close(F);
      chown ($uuid, $ugid, $spoolfile);
   }

   ${$r_usage}=$totalsize/1024;	# unit=k
   return;
}
################ END STORAGE_GETFOLDERS ##################

################## STORAGE_NEEDFOLDERLOCK ####################
sub storage_needfolderlock()
{
	return 1;
}
################## END STORAGE_NEEDFOLDERLOCK ####################

################## STORAGE_METAINFO ####################
sub storage_metainfo($)
{
	my $name = shift;
	if ( -e $name)
	{
		# dev, ino, mode, nlink, uid, gid, rdev, size, atime, mtime, ctime, blksize, blocks
		my @a=stat(_);
		return("mtime=$a[9] size=$a[7]");
	}
	else
	{
		return("");
	};
}
################## END STORAGE_METAINFO ####################

############### STORAGE_GET_SPOOLFILE_FOLDERDB ################
sub storage_get_folderfile_headerdb($$$$)
{
   my ($username, $foldername, $folderdir, $homedir)=@_;
   my ($folderfile, $headerdb);

   if ($foldername eq 'INBOX') {
      if ($main::config{'use_homedirspools'}) {
         $folderfile = "$homedir/$main::config{'homedirspoolname'}";
      } elsif ($main::config{'use_hashedmailspools'}) {
         $username =~ /^(.)(.)/;
         my $firstchar = $1;
         my $secondchar = $2;
         $folderfile = "$main::config{'mailspooldir'}/$firstchar/$secondchar/$username";
      } else {
         $folderfile = "$main::config{'mailspooldir'}/$username";
      }
      $headerdb="$folderdir/.$username";

   } elsif ($foldername eq 'DELETE') {
      $folderfile = $headerdb ='';

   } else {
      $folderfile = "$folderdir/$foldername";
      $headerdb=$folderfile;
      ($headerdb =~ /^(.+)\/(.*)$/) && ($headerdb = "$1/.$2");
   }

   ($folderfile =~ /^(.+)$/) && ($folderfile = $1); # untaint ...
   ($headerdb =~ /^(.+)$/) && ($headerdb = $1);

   return($folderfile, $headerdb);
}
############### END STORAGE_GET_SPOOLFILE_FOLDERDB ################

######################### STORAGER_UPDATE_HEADERDB ############################
# this routine indexes the messages in a mailfolder
# and remove those with duplicated messageids
sub storage_update_headerdb($$)
{
	my ($headerdb, $folderfile) = @_;
	my (%HDB, %OLDHDB);
	my @oldmessageids=();
	my $dberr=0;

	($headerdb =~ /^(.+)$/) && ($headerdb = $1);		# untaint ...
	if ( -e "$headerdb$main::config{'dbm_ext'}" )
	{
		my ($metainfo, $allmessages, $internalmessages, $newmessages);

		if (!$main::config{'dbmopen_haslock'})
		{
			&main::filelock("$headerdb$main::config{'dbm_ext'}", LOCK_SH) or return -1;
		}
		dbmopen (%HDB, "$headerdb$main::config{'dbmopen_ext'}", undef);
		$metainfo=$HDB{'METAINFO'};
		$allmessages=$HDB{'ALLMESSAGES'};
		$internalmessages=$HDB{'INTERNALMESSAGES'};
		$newmessages=$HDB{'NEWMESSAGES'};
		dbmclose(%HDB);
		&main::filelock("$headerdb$main::config{'dbm_ext'}", LOCK_UN) if (!$main::config{'dbmopen_haslock'});

		if ( $metainfo eq &storage_metainfo($folderfile) && $allmessages >=0
			&& $internalmessages >=0 && $newmessages >=0 )
		{
			return 0;
		}

		if ($main::config{'dbm_ext'} eq 'dir')
		{
			rename("$headerdb.dir", "$headerdb.old.dir");
			rename("$headerdb.pag", "$headerdb.old.pag");
		}
		else
		{
			rename("$headerdb$main::config{'dbm_ext'}", "$headerdb.old$main::config{'dbm_ext'}");
		}

		# we will try to reference records in old headerdb if possible
		@oldmessageids=&main::get_messageids_sorted_by_offset("$headerdb.old");
		if (!$main::config{'dbmopen_haslock'})
		{
			&main::filelock("$headerdb.old$main::config{'dbm_ext'}", LOCK_SH) or return -1;
		}
		dbmopen(%OLDHDB, "$headerdb.old$main::config{'dbmopen_ext'}", undef);
	}

	my ($messagenumber, $newmessages, $internalmessages) = (-1, 0, 0);
	my ($inheader, $offset, $totalsize)=(0, 0, 0);
	my @duplicateids=();

	my ($line, $lastheader, $delimiter, $has_att, $verified);
	my ($_message_id);
	my ($_from, $_to, $_date, $_subject);
	my ($_content_type, $_status, $_messagesize, $_references, $_inreplyto, $_charset);

	dbmopen(%HDB, "$headerdb$main::config{'dbmopen_ext'}", 0600);
	if (!$main::config{'dbmopen_haslock'})
	{
		&main::filelock("$headerdb$main::config{'dbm_ext'}", LOCK_EX) or return -1;
	}
	%HDB=();	# ensure the headerdb is empty

	my $folderhandle=do { local *FH };

	open ($folderhandle , $folderfile);

	# copy records from oldhdb as more as possible
	my $foldersize=(stat($folderhandle))[7];
	foreach my $id (@oldmessageids)
	{
		my @attr=split(/\@\@\@/, $OLDHDB{$id});
		my $buff;

		last if ($attr[$main::_OFFSET] != $totalsize);
		$totalsize+=$attr[$main::_SIZE];

		if ($totalsize!=$foldersize)
		{
			seek($folderhandle, $totalsize, 0);
			read($folderhandle, $buff, 5);	# file ptr is 5 byte after totalsize now!
		}
		else
		{
			$buff="From ";
		}

		if ( $buff=~/^From /)
		{	# ya, msg end is found!
			$HDB{$id}=join('@@@', @attr) || $dberr++;
			if ($dberr)
			{
				dbmclose(%OLDHDB);
				&main::filelock("$headerdb.old$main::config{'dbm_ext'}", LOCK_UN) if (!$main::config{'dbmopen_haslock'});
				unlink("$headerdb.old$main::config{'dbm_ext'}", "$headerdb.old.dir", "$headerdb.old.pag");
				$dberr=0;
				$totalsize-=$attr[$main::_SIZE];
				last;
			}
			$internalmessages++ if ( &main::is_internal_subject($attr[$main::_SUBJECT]) );
			$newmessages++ if ($attr[$main::_STATUS] !~ /r/i);
			$messagenumber++;
		}
		else
		{
			$totalsize-=$attr[$main::_SIZE];
			last;
		}
	}

	my $eof=0;
	while ( !$dberr )
	{
		$offset=$totalsize;
		seek($folderhandle, $totalsize, 0);     # set file ptr back to totalsize

		($_message_id, $_from, $_to, $_date, $_subject, $_content_type, $_status, $_messagesize, $_references, $_charset)
			= &main::parse_message($folderhandle,\$newmessages,\$internalmessages);
		last if !defined $_message_id;
		$messagenumber++;
		$totalsize+=$_messagesize;

		if (! defined($HDB{$_message_id}) )
		{
			$HDB{$_message_id}=&main::make_msgrecord($_message_id, $offset, $_from, $_to,
				$_date, $_subject, $_content_type, $_status, $_messagesize, $_references, $_charset)
				or $dberr++;
			if ($dberr && defined(%OLDHDB))
			{
				dbmclose(%OLDHDB);
				&main::filelock("$headerdb.old$main::config{'dbm_ext'}", LOCK_UN) if (!$main::config{'dbmopen_haslock'});
				unlink("$headerdb.old$main::config{'dbm_ext'}", "$headerdb.old.dir", "$headerdb.old.pag");
				$dberr=0;
				$HDB{$_message_id}=&main::make_msgrecord($_message_id, $offset, $_from, $_to,
					$_date, $_subject, $_content_type, $_status, $_messagesize, $_references, $_charset)
				or $dberr++;
			}
		}
		else
		{
			my $dup=$#duplicateids+1;
			$HDB{"dup$dup-$_message_id"}=&main::make_msgrecord("dup$dup-$_message_id", $offset, $_from, $_to,
				$_date, $_subject, $_content_type, $_status, $_messagesize, $_references, $_charset)
				or $dberr++;
			if ($dberr && defined(%OLDHDB))
			{
				dbmclose(%OLDHDB);
				&main::filelock("$headerdb.old$main::config{'dbm_ext'}", LOCK_UN) if (!$main::config{'dbmopen_haslock'});
				unlink("$headerdb.old$main::config{'dbm_ext'}", "$headerdb.old.dir", "$headerdb.old.pag");
				$dberr=0;
				$HDB{"dup$dup-$_message_id"}=&main::make_msgrecord("dup$dup-$_message_id", $offset, $_from, $_to,
					$_date, $_subject, $_content_type, $_status, $_messagesize, $_references, $_charset)
					or $dberr++;
			}
			push(@duplicateids, "dup$dup-$_message_id") if (!$dberr);
		}

	} # end while( !$dberr )

	close ($folderhandle);

	if ( !$dberr )
	{
		$HDB{'ALLMESSAGES'}=$messagenumber+1;
		$HDB{'INTERNALMESSAGES'}=$internalmessages;
		$HDB{'NEWMESSAGES'}=$newmessages;
		$HDB{'METAINFO'}=&storage_metainfo($folderfile) || $dberr++;
	}

	&main::filelock("$headerdb$main::config{'dbm_ext'}", LOCK_UN) if (!$main::config{'dbmopen_haslock'});
	dbmclose(%HDB);

	# remove old headerdb
	if (defined(%OLDHDB))
	{
		dbmclose(%OLDHDB);
		&main::filelock("$headerdb.old$main::config{'dbm_ext'}", LOCK_UN) if (!$main::config{'dbmopen_haslock'});
		unlink("$headerdb.old$main::config{'dbm_ext'}", "$headerdb.old.dir", "$headerdb.old.pag");
	}

	if ($dberr)
	{
		return -1;
	}
	else
	{
		# remove if any duplicates
		&storage_operate_message_with_ids("delete", \@duplicateids, $folderfile, $headerdb) if ($#duplicateids>=0);
		return 1;
	}
}
##################### END STORAGE_UPDATE_HEADERDB #########################
##################### STORAGE_GET_MESSAGE_BLOCK #########################
sub storage_get_message_block ($$$)
{
	my ($messageid, $headerdb, $folderfile)=@_;
	my (@attr, $buff);
	my $folderhandle=do { local *FH };

	open($folderhandle, "$folderfile");

	@attr=&main::get_message_attributes($messageid, $headerdb);
	if ($#attr>=7 && $attr[$main::_SIZE]>=0)
	{
		seek($folderhandle, $attr[$main::_OFFSET], 0);
		read($folderhandle, $buff, $attr[$main::_SIZE]);
	}
	else
	{
		$buff="";
	}
	close($folderhandle);

	return(\$buff);
}
##################### END STORAGE_GET_MESSAGE_BLOCK #########################
##################### STORAGE_GET_MESSAGE_BLOCK_OFFSET ######################
sub storage_get_message_block_offset($$$)
{
	my ($offset, $size, $folderfile)=@_;
	my $buff='';
	my @lines=();
	my $folderhandle=do { local *FH };

	open($folderhandle, "$folderfile");

	seek($folderhandle, $offset, 0);

	if (wantarray)
	{
		my $left=$size;
		while ($left>0)
		{
			my $line = <$folderhandle>;
			$left-=length($line);
			push @lines,$line;
		};
	}
	else
	{
		read($folderhandle, $buff, $size);
	};

	close($folderhandle);

	return wantarray?@lines:\$buff;
}
##################### END STORAGE_GET_MESSAGE_BLOCK_OFFSET ###################


##################### STORAGE_GET_MESSAGE_HEADER #########################
sub storage_get_message_header($$$)
{
	my ($messageid, $headerdb, $folderfile)=@_;
	my (@attr, $header);

	my $folderhandle=do { local *FH };

	open($folderhandle, "$folderfile")
		|| return "";

	@attr=&main::get_message_attributes($messageid, $headerdb);
	if ($#attr>=0)
	{
		seek($folderhandle, $attr[$main::_OFFSET], 0);
		$header="";
		while(<$folderhandle>)
		{
			$header.=$_;
			last if ($_ eq "\n");
		}
	}
	else
	{
		$header="";
	}
	close($folderhandle);

	return(\$header);
}
##################### END STORAGE_GET_MESSAGE_HEADER #########################
##################### STORAGE_GET_MESSAGE_HEADER_OFFSET ######################
sub storage_get_message_header_offset($$)
{
	my ($offset, $folderfile)=@_;

	my $folderhandle=do { local *FH };

	open($folderhandle, "$folderfile")
		|| return "";

	if (!seek($folderhandle, $offset, 0))
	{
		close($folderhandle);
		return "";
	};
	my $header="";
	while(<$folderhandle>)
	{
		$header.=$_;
		last if ($_ eq "\n");
	}
	close($folderhandle);

	return(\$header);
}
##################### END STORAGE_GET_MESSAGE_HEADER_OFFSET ##################


###################### STORAGE_UPDATE_MESSAGE_STATUS ########################
sub storage_update_message_status($$$$)
{
   my ($messageid, $status, $headerdb, $folderfile) = @_;
   my $messageoldstatus='';
   my $folderhandle=do { local *FH };
   my %HDB;
   my $ioerr=0;

   if (&torage_pdate_headerdb($headerdb, $folderfile)<0) {
      &main::folderfilelock($folderfile, LOCK_UN);
      &main::writelog("db error - Couldn't update index db $headerdb$main::config{'dbm_ext'}");
      &main::writehistory("db error - Couldn't update index db $headerdb$main::config{'dbm_ext'}");
      return -1;
   }

   my @messageids=&main::get_messageids_sorted_by_offset($headerdb);
   my $movement=0;
   my @attr;
   my $i;

   if (!$main::config{'dbmopen_haslock'}) {
      &main::filelock("$headerdb$main::config{'dbm_ext'}", LOCK_EX) or return -1;
   }
   dbmopen (%HDB, "$headerdb$main::config{'dbmopen_ext'}", 0600);

   for ($i=0; $i<=$#messageids; $i++) {
      if ($messageids[$i] eq $messageid) {
         @attr=split(/@@@/, $HDB{$messageid});

         $messageoldstatus=$attr[$main::_STATUS];
         last if ($messageoldstatus eq $status);

         my $messagestart=$attr[$main::_OFFSET];
         my $messagesize=$attr[$main::_SIZE];
         my ($header, $headerend, $headerlen, $newheaderlen);
         my $buff;

         open ($folderhandle, "+<$folderfile");
         seek ($folderhandle, $messagestart, 0);
         $header="";
         while (<$folderhandle>) {
            last if ($_ eq "\n" && $header=~/\n$/);
            $header.=$_;
         }
         if ($header !~ /^From /) { # index not consistent with folder content
            close ($folderhandle);

            &main::writelog("db warning - msg $messageid in $folderfile index inconsistence");
            &main::writehistory("db warning - msg $messageid in $folderfile index inconsistence");

            $HDB{'METAINFO'}="ERR";
            dbmclose(%HDB);
            &main::filelock("$headerdb$main::config{'dbm_ext'}", LOCK_UN) if (!$main::config{'dbmopen_haslock'});
            # forced reindex since metainfo = ERR
            return -3 if (&storage_update_headerdb($headerdb, $folderfile)<0);
            return -2;
         }
         $headerlen=length($header);
         $headerend=$messagestart+$headerlen;

         # update status, flags from rfc2076
         my $status_update = "";
         $status_update .= "R" if ($status =~ m/r/i); # Read
         $status_update .= "U" if ($status =~ m/u/i); # undownloaded & not deleted
         $status_update .= "N" if ($status =~ m/n/i); # New
         $status_update .= "D" if ($status =~ m/d/i); # to be Deleted
         $status_update .= "O" if ($status =~ m/o/i); # Old
         if ($status_update ne "") {
            if (!($header =~ s/^status:.*\n/Status: $status_update\n/im)) {
               $header .= "Status: $status_update\n";
            }
         } else {
            $header =~ s/^status:.*\n//im;
         }

	 # update x-status
         $status_update = "";
         $status_update .= "A" if ($status =~ m/a/i); # Answered
         $status_update .= "I" if ($status =~ m/i/i); # Important
         $status_update .= "D" if ($status =~ m/d/i); # to be Deleted
         if ($status_update ne "") {
            if (!($header =~ s/^x-status:.*\n/X-Status: $status_update\n/im)) {
               $header .= "X-Status: $status_update\n";
            }
         } else {
            $header =~ s/^x-status:.*\n//im;
         }

         $newheaderlen=length($header);
         $movement=$newheaderlen-$headerlen;

         my $foldersize=(stat($folderhandle))[7];
         if (&main::shiftblock($folderhandle, $headerend, $foldersize-$headerend, $movement)<0) {
            &main::writelog("data error - msg $messageids[$i] in $folderfile shiftblock failed");
            &main::writehistory("data error - msg $messageids[$i] in $folderfile shiftblock failed");
            $ioerr++;
         }

         if (!$ioerr) {
            seek($folderhandle, $messagestart, 0);
            print $folderhandle $header || $ioerr++;
         }
         if (!$ioerr) {
            seek($folderhandle, $foldersize+$movement, 0);
            truncate($folderhandle, tell($folderhandle));
         }
         close ($folderhandle);

         if (!$ioerr) {
            # set attributes in headerdb for this status changed message
            if ($messageoldstatus!~/r/i && $status=~/r/i) {
               $HDB{'NEWMESSAGES'}--;
               $HDB{'NEWMESSAGES'}=0 if ($HDB{'NEWMESSAGES'}<0); # should not happen
            } elsif ($messageoldstatus=~/r/i && $status!~/r/i) {
               $HDB{'NEWMESSAGES'}++;
            }
            $attr[$main::_SIZE]=$messagesize+$movement;
            $attr[$main::_STATUS]=$status;
            $HDB{$messageid}=join('@@@', @attr);
         }

         last;
      }
   }
   $i++;

   # if size of this message is changed
   if ($movement!=0 && !$ioerr) {
      #  change offset attr for messages after the above one
      for (;$i<=$#messageids; $i++) {
         @attr=split(/@@@/, $HDB{$messageids[$i]});
         $attr[$main::_OFFSET]+=$movement;
         $HDB{$messageids[$i]}=join('@@@', @attr);
      }
   }

   # update folder metainfo
   $HDB{'METAINFO'}=&storage_metainfo($folderfile) if (!$ioerr);

   dbmclose(%HDB);
   &main::filelock("$headerdb$main::config{'dbm_ext'}", LOCK_UN) if (!$main::config{'dbmopen_haslock'});

   if (!$ioerr) {
      return 0;
   } else {
      return -3;
   }
}
#################### END STORAGE_UPDATE_MESSAGE_STATUS ######################

###################### STORAGE_OP_MESSAGE_WITH_IDS #########################
# operate messages with @messageids from src folderfile to dst folderfile
# available $op: "move", "copy", "delete"
sub storage_operate_message_with_ids {
   my ($op, $r_messageids, $srcfile, $srcdb, $dstfile, $dstdb)=@_;
   my $folderhandle=do { local *FH };
   my (%HDB, %HDB2);
   my $messageids = join("\n", @{$r_messageids});
   my $ioerr=0;

   # $lang_err{'inv_msg_op'}
   return -1 if ($op ne "move" && $op ne "copy" && $op ne "delete");
   return 0 if ($srcfile eq $dstfile || $#{$r_messageids} < 0);

   if (&storage_update_headerdb($srcdb, $srcfile)<0) {
      &main::folderfilelock($srcfile, LOCK_UN);
      &main::writelog("db error - Couldn't update index db $srcdb$main::config{'dbm_ext'}");
      &main::writehistory("db error - Couldn't update index db $srcdb$main::config{'dbm_ext'}");
      return -2;
   }

   open ($folderhandle, "+<$srcfile") or
      return -3;	# $lang_err{'couldnt_open'} $srcfile!

   if ($op eq "move" || $op eq "copy") {
      open (DEST, ">>$dstfile") or
         return -5;	# $lang_err{'couldnt_open'} $destination!
      seek(DEST, 0, 2);	# seek end explicitly to cover tell() bug in perl 5.8
      if (&storage_update_headerdb("$dstdb", $dstfile)<0) {
         &main::folderfilelock($dstfile, LOCK_UN);
         &main::writelog("db error - Couldn't update index db $dstdb$main::config{'dbm_ext'}");
         &main::writehistory("db error - Couldn't update index db $dstdb$main::config{'dbm_ext'}");
         return -4;
      }
   }

   my @allmessageids=&main::get_messageids_sorted_by_offset($srcdb);
   my ($blockstart, $blockend, $writepointer);
   my ($messagestart, $messagesize, $messagevalid, @attr, $buff);
   my $counted=0;

   if (!$main::config{'dbmopen_haslock'}) {
      &main::filelock("$srcdb$main::config{'dbm_ext'}", LOCK_EX) or return -6;
   }
   dbmopen (%HDB, "$srcdb$main::config{'dbmopen_ext'}", 0600);

   if ($op eq "move" || $op eq "copy") {
      if (!$main::config{'dbmopen_haslock'}) {
         &main::filelock("$dstdb$main::config{'dbm_ext'}", LOCK_EX) or return -7;
      }
      dbmopen (%HDB2, "$dstdb$main::config{'dbmopen_ext'}", 0600);
   }

   $blockstart=$blockend=$writepointer=0;

   for (my $i=0; $i<=$#allmessageids; $i++) {
      @attr=split(/@@@/, $HDB{$allmessageids[$i]});
      $messagestart=$attr[$main::_OFFSET];
      $messagesize=$attr[$main::_SIZE];
      $messagevalid=1;

      seek($folderhandle, $attr[$main::_OFFSET], 0);
      if ($attr[$main::_OFFSET] == 0) {
         $messagevalid=1;
      } elsif ($attr[$main::_SIZE]<=5) {
         $messagevalid=0;
      } else {
         read($folderhandle, $buff, 5);
         $messagevalid=0 if ($buff!~/^From /);
      }

      if ($messageids =~ /^\Q$allmessageids[$i]\E$/m && $messagevalid) { # msg to be operated
         $counted++;

         if ($op eq 'move' || $op eq 'delete') {
            if (&main::shiftblock($folderhandle, $blockstart, $blockend-$blockstart, $writepointer-$blockstart)<0) {
               &main::writelog("data error - msg $allmessageids[$i] in $srcfile shiftblock failed");
               &main::writehistory("data error - msg $allmessageids[$i] in $srcfile shiftblock failed");
               $ioerr++;
            } else {
               $writepointer=$writepointer+($blockend-$blockstart);
               $blockstart=$blockend=$messagestart+$messagesize;
            }
         } else {
            $blockend=$messagestart+$messagesize;
         }

         # append msg to dst folder only if
         # op=move/copy and msg doesn't exist in dstfile
         if (($op eq "move" || $op eq "copy") &&
             !defined($HDB2{$allmessageids[$i]}) && !$ioerr ) {

            seek($folderhandle, $attr[$main::_OFFSET], 0);
            $attr[$main::_OFFSET]=tell(DEST);

            # copy message from $folderhandle to DEST
            my $left=$attr[$main::_SIZE];
            while ($left>0) {
               if ($left>=32768) {
                   read($folderhandle, $buff, 32768);
                   print DEST $buff || $ioerr++;
                   $left=$left-32768;
               } else {
                   read($folderhandle, $buff, $left);
                   print DEST $buff || $ioerr++;
                   $left=0;
               }
            }

            if (!$ioerr) {
               $HDB2{'NEWMESSAGES'}++ if ($attr[$main::_STATUS]!~/r/i);
               $HDB2{'INTERNALMESSAGES'}++ if (&main::is_internal_subject($attr[$main::_SUBJECT]));
               $HDB2{'ALLMESSAGES'}++;
               $HDB2{$allmessageids[$i]}=join('@@@', @attr);
            }
         }
         if (($op eq 'move' || $op eq 'delete') && !$ioerr) {
            $HDB{'NEWMESSAGES'}-- if ($attr[$main::_STATUS]!~/r/i);
            $HDB{'NEWMESSAGES'}=0 if ($HDB{'NEWMESSAGES'}<0); # should not happen
            $HDB{'INTERNALMESSAGES'}-- if (&main::is_internal_subject($attr[$main::_SUBJECT]));
            $HDB{'ALLMESSAGES'}--;
            delete $HDB{$allmessageids[$i]};
         }

      } else {						# msg to be kept in same folder
         $blockend=$messagestart+$messagesize;

         if ($op eq 'move' || $op eq 'delete') {
            my $movement=$writepointer-$blockstart;
            if ($movement<0) {
               $attr[$main::_OFFSET]+=$movement;
               $HDB{$allmessageids[$i]}=join('@@@', @attr);
            }
         }
      }

      last if ($ioerr);
   }

   if ( ($op eq 'move' || $op eq 'delete') && $counted>0 && !$ioerr) {
      if (&main::shiftblock($folderhandle, $blockstart, $blockend-$blockstart, $writepointer-$blockstart)<0) {
         &main::writelog("data error - msgs in $srcfile shiftblock failed");
         &main::writehistory("data error - msgs in $srcfile shiftblock failed");
         $ioerr++;
      } else {
         seek($folderhandle, $writepointer+($blockend-$blockstart), 0);
         truncate($folderhandle, tell($folderhandle));
      }
   }

   if ($op eq "move" || $op eq "copy") {
      close (DEST);
      $HDB2{'METAINFO'}=&storage_metainfo($dstfile) if (!$ioerr);
      dbmclose(%HDB2);
      &main::filelock("$dstdb$main::config{'dbm_ext'}", LOCK_UN) if (!$main::config{'dbmopen_haslock'});
   }

   close ($folderhandle);
   $HDB{'METAINFO'}=&storage_metainfo($srcfile) if (!$ioerr);
   dbmclose(%HDB);
   &main::filelock("$srcdb$main::config{'dbm_ext'}", LOCK_UN) if (!$main::config{'dbmopen_haslock'});

   if (!$ioerr) {
      return($counted);
   } else {
      return -8;
   }
}
#################### END STORAGE_OP_MESSAGE_WITH_IDS #######################

##################### STORAGE_REBUILD_MESSAGE_WITH_PARTIALID #################
# rebuild orig msg with partial msgs in the same folder
sub storage_rebuild_message_with_partialid($$$)
{
   my ($folderfile, $headerdb, $partialid)=@_;
   my (%HDB, @messageids);
   my ($partialtotal, @partialmsgids, @offset, @size);

   if (&storage_update_headerdb($headerdb, $folderfile)<0) {
      &main::folderfilelock($folderfile, LOCK_UN);
      &main::writelog("db error - Couldn't update index db $headerdb$main::config{'dbm_ext'}");
      &main::writehistory("db error - Couldn't update index db $headerdb$main::config{'dbm_ext'}");
      return -1;
   }

   # find all partial msgids
   if (!$main::config{'dbmopen_haslock'}) {
      &main::filelock("$headerdb$main::config{'dbm_ext'}", LOCK_SH) or return -2;
   }
   dbmopen (%HDB, "$headerdb$main::config{'dbmopen_ext'}", undef);
   @messageids=keys %HDB;
   foreach my $id (@messageids) {
      next if ( $id eq 'METAINFO'
             || $id eq 'NEWMESSAGES'
             || $id eq 'INTERNALMESSAGES'
             || $id eq 'ALLMESSAGES'
             || $id eq "" );

      my @attr=split( /@@@/, $HDB{$id} );
      next if ($attr[$main::_CONTENT_TYPE] !~ /^message\/partial/i );

      $attr[$main::_CONTENT_TYPE] =~ /;\s*id="(.+?)";?/i;
      next if ($partialid ne $1);

      if ($attr[$main::_CONTENT_TYPE] =~ /;\s*number="?(.+?)"?;?/i) {
         my $n=$1;
         $partialmsgids[$n]=$id;
         $offset[$n]=$attr[$main::_OFFSET];
         $size[$n]=$attr[$main::_SIZE];
         $partialtotal=$1 if ($attr[$main::_CONTENT_TYPE] =~ /;\s*total="?(.+?)"?;?/i);
      }
   }
   dbmclose(%HDB);
   &main::filelock("$headerdb$main::config{'dbm_ext'}", LOCK_UN) if (!$main::config{'dbmopen_haslock'});

   # check completeness
   if ($partialtotal<1) {	# last part not found
      return -3;
   }
   for (my $i=1; $i<=$partialtotal; $i++) {
      if ($partialmsgids[$i] eq "") {	# some part missing
         return -4;
      }
   }

   my $tmpfile="/tmp/rebuild_tmp_$$";
   my $tmpdb="/tmp/.rebuild_tmp_$$";
   ($tmpfile =~ /^(.+)$/) && ($tmpfile = $1);
   ($tmpdb =~ /^(.+)$/) && ($tmpdb = $1);

   &main::folderfilelock("$tmpfile", LOCK_EX) or return -5;
   open (TMP,  ">$tmpfile");
   open (FOLDER, "$folderfile");

   seek(FOLDER, $offset[1], 0);
   my $line = <FOLDER>;
   my $writtensize = length($line);
   print TMP $line;	# copy delimiter line from 1st partial message

   for (my $i=1; $i<=$partialtotal; $i++) {
      my $currsize=0;
      seek(FOLDER, $offset[$i], 0);

      # skip header of the partial message
      while (defined($line = <FOLDER>)) {
         $currsize += length($line);
         last if ( $line =~ /^\r*$/ );
      }

      # read body of the partial message and copy to tmpfile
      while (defined($line = <FOLDER>)) {
         $currsize += length($line);
         $writtensize += length($line);
         print TMP $line;
         last if ( $currsize>=$size[$i] );
      }
   }

   close(TMP);
   close(FOLDER);
   &main::folderfilelock("$tmpfile", LOCK_EX) or return -6;

   # index tmpfile, get the msgid
   if (&storage_update_headerdb($tmpdb, $tmpfile)<0) {
      &main::folderfilelock($tmpfile, LOCK_UN);
      &main::writelog("db error - Couldn't update index db $tmpdb$main::config{'dbm_ext'}");
      &main::writehistory("db error - Couldn't update index db $tmpdb$main::config{'dbm_ext'}");
      return -7;
   }

   # check the rebuild integrity
   my @rebuildmsgids=&main::get_messageids_sorted_by_offset($tmpdb);
   if ($#rebuildmsgids!=0) {
      unlink("$tmpdb$main::config{'dbm_ext'}", $tmpfile);
      return -8;
   }
   my $rebuildsize=(&main::get_message_attributes($rebuildmsgids[0], $tmpdb))[$main::_SIZE];
   if ($writtensize!=$rebuildsize) {
      unlink("$tmpdb$main::config{'dbm_ext'}", $tmpfile);
      return -9;
   }

   my $moved=&storage_operate_message_with_ids("move", \@rebuildmsgids,
				$tmpfile, $tmpdb, $folderfile, $headerdb);

   unlink("$tmpdb$main::config{'dbm_ext'}", $tmpfile);

   return(0, $rebuildmsgids[0], @partialmsgids);
}
################ END STORAGE_REBUILD_MESSAGE_WITH_PARTIALID ################

################ STORAGE_ADD_MESSAGE ################
sub storage_add_message($$$)
{
	my ($folderfile,$r_message,$r_attr) = @_;

	open(DEST, ">>$folderfile")
		|| return -1;
	seek(DEST, 0, 2); # seek end explicitly to cover tell() bug in perl 5.8
	my $oldpos=tell(DEST);
	if(!print DEST ${$r_message})
	{
		seek(DEST,$oldpos,0);
		truncate(DEST,$oldpos);
		close(DEST);
		return -2;
	};
	${$r_attr}[$main::_OFFSET]=$oldpos;
	${$r_attr}[$main::_SIZE]=length(${$r_message});
	close(DEST);

	return 0;
}
################ END STORAGE_ADD_MESSAGE ################

################ STORAGE_REMOVE_MESSAGE_OFFSET ################
sub storage_remove_message_offset($$$)
{
	my ($offset, $size, $folderfile)=@_;
	my $folderhandle=do { local *FH };

	open ($folderhandle,"+<$folderfile")
		|| return -1;
	my $foldersize = (stat($folderfile))[7];
	if (&main::shiftblock($folderhandle,$offset+$size,$foldersize-$size,-$size)<0)
	{
		close($folderhandle);
		return -2
	};
	seek($folderhandle,$foldersize-$size,0);
	truncate($folderhandle,$foldersize-$size);
	close($folderhandle);
	return 0;
}
################ END STORAGE_REMOVE_MESSAGE_OFFSET ################

################ STORAGE_FOLDER_EXIST ################
sub storage_folder_exist($)
{
	my $filename = shift;

	return (-f $filename);
}
################ END STORAGE_FOLDER_EXIST ################

################ STORAGE_FOLDER_SIZE ################
sub storage_folder_size($)
{
	my $filename = shift;

	return 0 if ! -f $filename;
	return (stat($filename))[7];
}
################ END STORAGE_FOLDER_SIZE ################


################ STORAGE_CREATE_FOLDER ################
sub storage_create_folder($)
{
	my $filename = shift;
	my $folderhandle=do { local *FH };

	open ($folderhandle, ">$filename")
		|| return -1;
	close($folderhandle);
	return 0;
}
################ END STORAGE_CREATE_FOLDER ################

################ STORAGE_DELETE_FOLDER ################
sub storage_delete_folder($)
{
	my $filename = shift;
	unlink $filename
		|| return -1;
	return 0;
}
################ END STORAGE_CREATE_FOLDER ################

################ STORAGE_EMPTY_FOLDER ################
sub storage_empty_folder($)
{
	my $filename = shift;
	my $folderhandle=do { local *FH };
	open ($folderhandle, ">$filename")
		|| return -1;
      
	close ($folderhandle)
		|| return -1;

	return 0;
}
################ END STORAGE_CREATE_FOLDER ################

################ STORAGE_FOLDER_RENAME ################
sub storage_folder_rename($$)
{
    my ($old,$new) = @_;
    return rename ($old,$new);
}
################ END STORAGE_FOLDER_RENAME ################
################ STORAGE_COMPARE_OFFSET  ################
sub storage_compare_offset($$)
{
	my ($a,$b) = @_;
	return $a <=> $b;
}
################ END STORAGE_COMPARE_OFFSET ################


1;
