# mailbox-lib.pl
# XXX don't connect when viewing cached mail?
# XXX can we cache IMAP messages?
# XXX don't reply-to our address
# XXX dsns and multiple recipients
# 	XXX and delivery notifications
#	XXX put in separate hashes?
#	XXX existing code to detect bounces is wrong

do '../web-lib.pl';
&init_config();
require '../ui-lib.pl';
&switch_to_remote_user();
&create_user_config_dirs();
do 'boxes-lib.pl';
do 'folders-lib.pl';

if ($config{'mail_qmail'}) {
	$qmail_maildir = &mail_file_style($remote_user, $config{'mail_qmail'},
					  $config{'mail_style'});
	}
else {
	$qmail_maildir = "$remote_user_info[7]/$config{'mail_dir_qmail'}";
	}
$address_book = "$user_module_config_directory/address_book";
$address_group_book = "$user_module_config_directory/address_group_book";
$folders_dir = "$remote_user_info[7]/$userconfig{'mailbox_dir'}";
%folder_types = map { $_, 1 } split(/,/, $config{'folder_types'});

# mailbox_file()
sub mailbox_file
{
if ($config{'mail_system'} == 0) {
	return &user_mail_file(@remote_user_info);
	}
else {
	return "$qmail_maildir/";
	}
}

# decrypt_attachments(&mail)
# If the attachments on a mail are encrypted, converts them into unencrypted
# form. Returns a code and message, valid codes being: 0 = not encrypted,
# 1 = encrypted but cannot decrypt, 2 = failed to decrypt, 3 = decrypted OK
sub decrypt_attachments
{
# Check requirements for decryption
local $first = $_[0]->{'attach'}->[0];
local ($body) = grep { $_->{'type'} eq 'text/plain' || $_->{'type'} eq 'text' }
		     @{$_[0]->{'attach'}};
local $hasgpg = &has_command("gpg") && &foreign_check("gnupg");
if ($_[0]->{'header'}->{'content-type'} =~ /^multipart\/encrypted/ &&
    $first->{'type'} =~ /^application\/pgp-encrypted/ &&
    $first->{'data'} =~ /Version:\s+1/i) {
	# RFC 2015 PGP encryption of entire message
	return (1) if (!$hasgpg);
	&foreign_require("gnupg", "gnupg-lib.pl");
	local $plain;
	local $enc = $_[0]->{'attach'}->[1];
	local $rv = &foreign_call("gnupg", "decrypt_data", $enc->{'data'}, \$plain);
	return (2, $rv) if ($rv);
	$plain =~ s/\r//g;
	local $amail = &extract_mail($plain);
	&parse_mail($amail);
	$_[0]->{'attach'} = $amail->{'attach'};
	return (3);
	}

# Check individual attachments for text-only encryption
local $a;
local $cc = 0;
local $ok = 3;
foreach $a (@{$_[0]->{'attach'}}) {
	if ($a->{'data'} =~ /([\000-\377]+)(-+BEGIN PGP MESSAGE-+\n([\000-\377]+)-+END PGP MESSAGE-+\n)([\000-\377]+)/i) {
		local ($before, $enc, $after) = ($1, $2, $3);
		return (1) if (!$hasgpg);
		&foreign_require("gnupg", "gnupg-lib.pl");
		$cc++;
		local $pass = &foreign_call("gnupg", "get_passphrase");
		local $plain;
		local $rv = &foreign_call("gnupg", "decrypt_data", $enc, \$plain, $pass);
		return (2, $rv) if ($rv);
		$ok = 4 if ($before =~ /\S/ || $after =~ /\S/);
		$a->{'data'} = $before.$plain.$after;
		}
	}
return $cc ? ( $ok ) : ( 0 );
}

# list_addresses()
# Returns a list of address book entries, each an array reference containing
# the email address, real name, index (if editable) and From: flag
sub list_addresses
{
local @rv;
local $i = 0;
open(ADDRESS, $address_book);
while(<ADDRESS>) {
	s/\r|\n//g;
	local @sp = split(/\t+/, $_);
	if (@sp >= 1) {
		push(@rv, [ $sp[0], $sp[1], $i, $sp[2] ]);
		}
	$i++;
	}
close(ADDRESS);
if ($config{'global_address'}) {
	local $gab = &group_subs($config{'global_address'});
	open(ADDRESS, $gab);
	while(<ADDRESS>) {
		s/\r|\n//g;
		local @sp = split(/\t+/, $_);
		if (@sp >= 2) {
			push(@rv, [ $sp[0], $sp[1] ]);
			}
		}
	close(ADDRESS);
	}
if ($userconfig{'sort_addrs'} == 2) {
	return sort { lc($a->[0]) cmp lc($b->[0]) } @rv;
	}
elsif ($userconfig{'sort_addrs'} == 1) {
	return sort { lc($a->[1]) cmp lc($b->[1]) } @rv;
	}
else {
	return @rv;
	}
}

# create_address(email, real name, forfrom)
# Adds an entry to the address book
sub create_address
{
open(ADDRESS, ">>$address_book");
print ADDRESS "$_[0]\t$_[1]\t$_[2]\n";
close(ADDRESS);
}

# modify_address(index, email, real name, forfrom)
# Updates some entry in the address book
sub modify_address
{
&replace_file_line($address_book, $_[0], "$_[1]\t$_[2]\t$_[3]\n");
}

# delete_address(index)
# Deletes some entry from the address book
sub delete_address
{
&replace_file_line($address_book, $_[0]);
}

# address_button(field, [form], [frommode], [realfield], [nogroups])
# Returns HTML for an address-book popup button
sub address_button
{
local $form = @_ > 1 ? $_[1] : 0;
local $mode = @_ > 2 ? $_[2] : 0;
local $nogroups = @_ > 4 ? $_[4] : 0;
local ($rfield1, $rfield2);
if ($_[3]) {
	return "<input type=button onClick='ifield = document.forms[$form].$_[0]; rfield = document.forms[$form].$_[3]; chooser = window.open(\"../$module_name/address_chooser.cgi?addr=\"+escape(ifield.value)+\"&mode=$mode&nogroups=$nogroups\", \"chooser\", \"toolbar=no,menubar=no,scrollbars=yes,width=500,height=250\"); chooser.ifield = ifield; window.ifield = ifield; chooser.rfield = rfield; window.rfield = rfield' value=\"...\">\n";
	}
else {
	return "<input type=button onClick='ifield = document.forms[$form].$_[0]; chooser = window.open(\"../$module_name/address_chooser.cgi?addr=\"+escape(ifield.value)+\"&mode=$mode\", \"chooser\", \"toolbar=no,menubar=no,scrollbars=yes,width=500,height=250\"); chooser.ifield = ifield; window.ifield = ifield' value=\"...\">\n";
	}
}

# list_folders()
# Returns a list of all folders for this user
# folder types: 0 = mbox, 1 = maildir, 2 = pop3, 3 = mh, 4 = imap, 5 = combined
# folder modes: 0 = ~/mail, 1 = external folder, 2 = sent mail,
#               3 = inbox/drafts/trash
sub list_folders
{
local (@rv, $f, $o, %done);
if ($config{'mail_system'} == 2) {
	# POP3 inbox
	push(@rv, { 'name' => $text{'folder_inbox'},
		    'type' => 2,
		    'server' => $config{'pop3_server'} || "localhost",
		    'mode' => 3,
		    'inbox' => 1,
		    'index' => 0 });
	&read_file("$user_module_config_directory/inbox.pop3", $rv[$#rv]);
	}
elsif ($config{'mail_system'} == 4) {
	# IMAP inbox
	push(@rv, { 'name' => $text{'folder_inbox'},
		    'type' => 4,
		    'server' => $config{'pop3_server'} || "localhost",
		    'mode' => 3,
		    'inbox' => 1,
		    'index' => 0 });
	&read_file("$user_module_config_directory/inbox.imap", $rv[$#rv]);
	}
else {
	# Local mail file inbox
	push(@rv, { 'name' => $text{'folder_inbox'},
		    'type' => $config{'mail_system'},
		    'mode' => 3,
		    'inbox' => 1,
		    'file' => $config{'mail_system'} == 0 ?
				&user_mail_file(@remote_user_info) :
				$qmail_maildir,
		    'index' => 0 });
	$done{$rv[$#rv]->{'file'}}++;
	}

# Add sent mail file
local $sf;
if ($folder_types{'ext'} && $userconfig{'sent_mail'}) {
	$sf = $userconfig{'sent_mail'};
	$done{$userconfig{'sent_mail'}}++;
	}
else {
	$sf = "$folders_dir/sentmail";
	}
$done{"$folders_dir/sentmail"}++;
push(@rv, { 'name' => $text{'folder_sent'},
	    'type' => &folder_type($sf),
	    'file' => $sf,
	    'perpage' => $userconfig{'perpage_sent_mail'},
	    'fromaddr' => $userconfig{'fromaddr_sent_mail'},
	    'mode' => 2,
	    'sent' => 1,
	    'index' => scalar(@rv) });

# Add drafts file
local $df = "$folders_dir/drafts";
$done{$df}++;
push(@rv, { 'name' => $text{'folder_drafts'},
	    'type' => &folder_type($df),
	    'file' => $df,
	    'mode' => 3,
	    'drafts' => 1,
	    'index' => scalar(@rv) });

# If using a trash folder, add it
if ($userconfig{'delete_mode'} == 1) {
	local $tf = "$folders_dir/trash";
	$done{$tf}++;
	push(@rv, { 'name' => $text{'folder_trash'},
		    'type' => &folder_type($df),
		    'file' => $tf,
		    'mode' => 3,
		    'trash' => 1,
		    'index' => scalar(@rv) });
	}

# Add local folders, usually under ~/mail
if ($folder_types{'local'}) {
	foreach $p (&recursive_files($folders_dir)) {
		local $f = $p;
		$f =~ s/^\Q$folders_dir\E\///;
		push(@rv, { 'name' => $f,
			    'file' => $p,
			    'type' => &folder_type($p),
			    'perpage' => $userconfig{"perpage_$f"},
			    'fromaddr' => $userconfig{"fromaddr_$f"},
			    'sent' => $userconfig{"sent_$f"},
			    'mode' => 0,
			    'index' => scalar(@rv) } ) if (!$done{$p});
		$done{$p}++;
		}
	}

# Add user-defined external mail file folders
if ($folder_types{'ext'}) {
	foreach $o (split(/\t+/, $userconfig{'mailboxes'})) {
		$o =~ /\/([^\/]+)$/ || next;
		push(@rv, { 'name' => $userconfig{"folder_$o"} || $1,
			    'file' => $o,
			    'perpage' => $userconfig{"perpage_$o"},
			    'fromaddr' => $userconfig{"fromaddr_$o"},
			    'sent' => $userconfig{"sent_$o"},
			    'type' => &folder_type($o),
			    'mode' => 1,
			    'index' => scalar(@rv) } ) if (!$done{$o});
		$done{$o}++;
		}
	}

# Add user-defined POP3	and IMAP folders
opendir(DIR, $user_module_config_directory);
foreach $f (readdir(DIR)) {
	if ($f =~ /^(\d+)\.pop3$/ && $folder_types{'pop3'}) {
		local %pop3;
		&read_file("$user_module_config_directory/$f", \%pop3);
		$pop3{'type'} = 2;
		$pop3{'mode'} = 0;
		$pop3{'remote'} = 1;
		$pop3{'nowrite'} = 1;
		$pop3{'index'} = scalar(@rv);
		push(@rv, \%pop3);
		}
	elsif ($f =~ /^(\d+)\.imap$/ && $folder_types{'imap'}) {
		local %imap;
		&read_file("$user_module_config_directory/$f", \%imap);
		$imap{'type'} = 4;
		$imap{'mode'} = 0;
		$imap{'remote'} = 1;
		$imap{'index'} = scalar(@rv);
		push(@rv, \%imap);
		}
	}
closedir(DIR);

# Add user-defined composite folders
opendir(DIR, $user_module_config_directory);
foreach $f (readdir(DIR)) {
	if ($f =~ /^(\d+)\.comp$/) {
		local %comp;
		&read_file("$user_module_config_directory/$f", \%comp);
		$comp{'type'} = 5;
		$comp{'mode'} = 0;
		$comp{'index'} = scalar(@rv);
		local $sfn;
		foreach $sfn (split(/\t+/, $comp{'subfoldernames'})) {
			local $sf = &find_named_folder($sfn, \@rv);
			push(@{$comp{'subfolders'}}, $sf) if ($sf);
			}
		push(@rv, \%comp);
		}
	}
closedir(DIR);

foreach $f (@rv) {
	if ($f->{'file'} && $userconfig{"notes_".$f->{'file'}}) {
		$f->{'notes_decode'} = 1;
		}
	}
return @rv;
}

# notes_decode(&mail, &folder)
# Given a message forwarded by lotus notes, extra the real from and subject
# lines from the body
sub notes_decode
{
return if (!$_[1]->{'notes_decode'});
local ($from, $subject, $h);
if ($_[0]->{'body'} =~ /(^|Content-type:.*)\n\s*\nFrom: +(.*)/) {
	$from = $2;
	}
elsif ($_[0]->{'body'} =~ /(^|Content-type:.*)\n\s*\n(\([^\)]+\)\s*)?(\S.*)/) {
	$from = $3;
	}
$from =~ s/\s+on.*//;
$from =~ s/\d+\/\d+\/\d+\s+\d+:\d+\s*//;
$from = undef if ($from =~ /:/);
if ($_[0]->{'body'} =~ /\nSubject: +(.*)/) {
	$subject = $1;
	}
local ($ofrom) = &address_parts($_[0]->{'header'}->{'from'});
if ($from && $from !~ /\@\S+\.\S+/) {
	$from = "\"$from\" <$ofrom>";
	}
foreach $h ([ 'From', $from ],
	    [ 'Subject', $subject ]) {
	next if (!$h->[1]);
	local ($eh) = grep { lc($_->[0]) eq lc($h->[0]) } @{$_[0]->{'headers'}};
	if ($eh) {
		$eh->[1] = $h->[1];
		}
	else {
		push(@{$_[0]->{'headers'}}, $h);
		}
	$_[0]->{'header'}->{lc($h->[0])} = $h->[1];
	}
}

# need_delete_warn(&folder)
sub need_delete_warn
{
return 1 if ($userconfig{'delete_warn'} eq 'y');
return 0 if ($userconfig{'delete_warn'} eq 'n');
local $mf;
return $_[0]->{'type'} == 0 &&
       ($mf = &folder_file($_[0])) &&
       &disk_usage_kb($mf)*1024 > $userconfig{'delete_warn'};
}

# get_signature()
# Returns the users signature, if any
sub get_signature
{
local $sf = &get_signature_file();
$sf || return undef;
local $sig;
open(SIG, $sf) || return undef;
while(<SIG>) {
	$sig .= $_;
	}
close(SIG);
return $sig;
}

# get_signature_file()
# Returns the full path to the file that should contain the user's signature,
# or undef if none is defined
sub get_signature_file
{
return undef if ($userconfig{'sig_file'} eq '*');
local $sf = $userconfig{'sig_file'};
$sf = "$remote_user_info[7]/$sf" if ($sf !~ /^\//);
return $sf;
}

# movecopy_select(number, &folders, &folder-to-exclude)
# Returns HTML for selecting a folder to move or copy to
sub movecopy_select
{
local $rv;
$rv .= "<input type=submit name=move$_[0] value=\"$text{'mail_move'}\" ".
       "onClick='return check_clicks(form)'>";
$rv .= "<input type=submit name=copy$_[0] value=\"$text{'mail_copy'}\" ".
       "onClick='return check_clicks(form)'>";
local @mfolders = grep { $_ ne $_[2] && !$_->{'nowrite'} } @{$_[1]};
$rv .= &folder_select(\@mfolders, undef, "mfolder$_[0]");
return $rv;
}

# show_folder_options(&folder, mode)
sub show_folder_options
{
print "<tr> <td><b>$text{'edit_perpage'}</b></td>\n";
printf "<td><input type=radio name=perpage_def value=1 %s> %s\n",
	$_[0]->{'perpage'} ? "" : "checked", $text{'default'};
printf "<input type=radio name=perpage_def value=0 %s> %s\n",
	$_[0]->{'perpage'} ? "checked" : "";
printf "<input name=perpage size=5 value='%s'></td> </tr>\n",
	$_[0]->{'perpage'};

if ($_[1] != 2) {
	print "<tr> <td><b>$text{'edit_sentview'}</b></td>\n";
	printf "<td><input type=radio name=sent value=1 %s> %s\n",
		$_[0]->{'sent'} ? "checked" : "", $text{'yes'};
	printf "<input type=radio name=sent value=0 %s> %s</td> </tr>\n",
		$_[0]->{'sent'} ? "" : "checked", $text{'no'};
	}

print "<tr> <td><b>$text{'edit_fromaddr'}</b></td>\n";
printf "<td><input type=radio name=fromaddr_def value=1 %s> %s\n",
	$_[0]->{'fromaddr'} ? "" : "checked", $text{'default'};
printf "<input type=radio name=fromaddr_def value=0 %s> %s\n",
	$_[0]->{'fromaddr'} ? "checked" : "";
printf "<input name=fromaddr size=30 value='%s'> %s</td> </tr>\n",
	$_[0]->{'fromaddr'}, &address_button("fromaddr", 0, 1);
}

# list_address_groups()
# Returns a list of address book entries, each an array reference containing
# the group name, members and index
sub list_address_groups
{
local @rv;
local $i = 0;
open(ADDRESS, $address_group_book);
while(<ADDRESS>) {
	s/\r|\n//g;
	local @sp = split(/\t+/, $_);
	if (@sp == 2) {
		push(@rv, [ $sp[0], $sp[1], $i ]);
		}
	$i++;
	}
close(ADDRESS);
if ($config{'global_address_group'}) {
	local $gab = &group_subs($config{'global_address_group'});
	open(ADDRESS, $gab);
	while(<ADDRESS>) {
		s/\r|\n//g;
		local @sp = split(/\t+/, $_);
		if (@sp == 2) {
			push(@rv, [ $sp[0], $sp[1] ]);
			}
		}
	close(ADDRESS);
	}
if ($userconfig{'sort_addrs'} == 1) {
	return sort { lc($a->[0]) cmp lc($b->[0]) } @rv;
	}
elsif ($userconfig{'sort_addrs'} == 2) {
	return sort { lc($a->[1]) cmp lc($b->[1]) } @rv;
	}
else {
	return @rv;
	}
}

# create_address_group(name, members)
# Adds an entry to the address group book
sub create_address_group
{
open(ADDRESS, ">>$address_group_book");
print ADDRESS "$_[0]\t$_[1]\n";
close(ADDRESS);
}

# modify_address_group(index, name, members)
# Updates some entry in the address group book
sub modify_address_group
{
&replace_file_line($address_group_book, $_[0], "$_[1]\t$_[2]\n");
}

# delete_address_group(index)
# Deletes some entry from the address group book
sub delete_address_group
{
&replace_file_line($address_group_book, $_[0]);
}

# list_folders_sorted()
# Like list_folders(), but applies the chosen sort
sub list_folders_sorted
{
local @folders = &list_folders();
if ($userconfig{'folder_sort'} == 0) {
	local @builtin = grep { $_->{'mode'} >= 2 } @folders;
	local @local = grep { $_->{'mode'} == 0 } @folders;
	local @external = grep { $_->{'mode'} == 1 } @folders;
	return (@builtin,
		(sort { lc($a->{'name'}) cmp lc($b->{'name'}) } @local),
		(sort { lc($a->{'name'}) cmp lc($b->{'name'}) } @external));
	}
elsif ($userconfig{'folder_sort'} == 1) {
	local @builtin = grep { $_->{'mode'} >= 2 } @folders;
	local @extra = grep { $_->{'mode'} < 2 } @folders;
	return (@builtin,
		sort { lc($a->{'name'}) cmp lc($b->{'name'}) } @extra);
	}
elsif ($userconfig{'folder_sort'} == 2) {
	return sort { lc($a->{'name'}) cmp lc($b->{'name'}) } @folders;
	}
}

# find_named_folder(name, &folders)
# Finds a folder by ID, filename, server name or displayed name
sub find_named_folder
{
local $rv;
($rv) = grep { $_->{'id'} eq $_[0] } @{$_[1]} if (!$rv);
($rv) = grep { $_->{'file'} eq $_[0] ||
		     $_->{'server'} eq $_[0] } @{$_[1]} if (!$rv);
($rv) = grep { $_->{'name'} eq $_[0] } @{$_[1]} if (!$rv);
return $rv;
}

# group_subs(filename)
# Replaces $group in a filename with the first valid primary or secondary
# that matches a file
sub group_subs
{
local @ginfo = getgrgid($remote_user_info[3]);
local $rv = $_[0];
$rv =~ s/\$group/$ginfo[0]/g;
if ($rv =~ /\$sgroup/) {
	# Try all secondary groups, and stop at the first one
	setgrent();
	while(@ginfo = getgrent()) {
		local @m = split(/\s+/, $ginfo[3]);
		if (&indexof($remote_user, @m) >= 0) {
			local $rv2 = $rv;
			$rv2 =~ s/\$sgroup/$ginfo[0]/g;
			if (-r $rv2) {
				$rv = $rv2;
				last;
				}
			}
		}
	endgrent() if ($gconfig{'os_type'} ne 'hpux');
	}
return $rv;
}

# set_module_index(folder-num)
sub set_module_index
{
$module_index_link = "/$module_name/index.cgi?folder=$_[0]";
$module_index_name = $text{'mail_indexlink'};
}

# check_modification(&folder)
# Display an error message if a folder has been modified since the time
# in $in{'mod'}
sub check_modification
{
local $newmod = &modification_time($_[0]);
if ($in{'mod'} && $in{'mod'} != $newmod && $userconfig{'check_mod'}) {
	# Changed!
	&error(&text('emodified', "index.cgi?folder=$_[0]->{'index'}"));
	}
}

# list_from_addresses()
# Returns a list of allowed From: addresses for the current user
sub list_from_addresses
{
local $http_host = $ENV{'HTTP_HOST'};
$http_host =~ s/:\d+$//;
if (&check_ipaddress($http_host)) {
	# Try to reverse-lookup IP
	local $rev = gethostbyaddr(inet_aton($acptip), AF_INET);
	$http_host = $rev if ($rev);
	}
$http_host =~ s/^(www|ftp|mail)\.//;
local $hostname = $config{'server_name'} eq '*' ? $http_host :
	    $config{'server_name'} eq '' ? &get_system_hostname() :
					   $config{'server_name'};
local @doms = split(/\s+/, $hostname);
local $ru = $remote_user;
$ru =~ s/\.\Q$http_host\E$//;
if ($http_host =~ /^([^\.]+)/) {
	$ru =~ s/\.\Q$1\E//;
	}
local @froms = map { $ru.'@'.$_ } @doms;
local @mfroms;
if ($config{'from_map'}) {
	open(MAP, $config{'from_map'});
	while(<MAP>) {
		s/\r|\n//g;
		s/#.*$//;
		if (/^\s*(\S+)\s+(\S+)/ &&
		    ($1 eq $remote_user || &indexof($1, @froms) >= 0) &&
		    $config{'from_format'} == 0) {
			# Username on LHS matches
			push(@mfroms, $2);
			}
		elsif (/^\s*(\S+)\s+(\S+)/ &&
		    ($2 eq $remote_user || &indexof($2, @froms) >= 0) &&
		    $config{'from_format'} == 1) {
			# Username on RHS matches
			push(@mfroms, $1);
			}
		}
	close(MAP);
	}
@froms = @mfroms if (@mfroms > 0);
local $ureal = $remote_user_info[6];
$ureal =~ s/,.*$//;
foreach $f (@froms) {
	$f = "\"$ureal\" <$f>"
		if ($ureal && $userconfig{'real_name'});
	}
return (\@froms, \@doms);
}

# update_delivery_notification(&mail, &folder)
# If the given mail is a DSN, update the original email so we know it has
# been read
sub update_delivery_notification
{
local ($mail, $folder) = @_;
return 0 if ($mail->{'header'}->{'content-type'} !~ /multipart\/report/i);
local $mid = $mail->{'header'}->{'message-id'};
&open_dsn_hash();
if ($dsnreplies{$mid} || $delreplies{$mid}) {
	# We have already done this DSN
	return 0;
	}
if (!defined($mail->{'body'}) && !$mail->{'parsed'} &&
    defined($mail->{'idx'})) {
	# This message has no body, perhaps because one wasn't fetched ..
	local @mail = &mailbox_list_mails($mail->{'idx'}, $mail->{'idx'},
					  $folder);
	$mail = $mail[$mail->{'idx'}];
	}
$dsnreplies{$mid} = $delreplies{$mid} = 1;

# Find the delivery or disposition status attachment
&parse_mail($mail);
local ($dsnattach) = grep { $_->{'header'}->{'content-type'} =~ /message\/disposition-notification/i } @{$mail->{'attach'}};
local ($delattach) = grep { $_->{'header'}->{'content-type'} =~ /message\/delivery-status/i } @{$mail->{'attach'}};

if ($dsnattach) {
	# Update the read status for the original message
	if ($dsnattach->{'data'} =~ /Original-Message-ID:\s*(.*)/) {
		$omid = $1;
		}
	else {
		return 0;
		}
	local ($faddr) = &split_addresses($mail->{'header'}->{'from'});
	&add_address_to_hash(\%dsnreplies, $omid, $faddr->[0]);
	return 1;
	}
elsif ($delattach) {
	# Update the delivery status for the original message, which will be
	# in a separate attachment
	local ($origattach) = grep { $_->{'header'}->{'content-type'} =~ /text\/rfc822-headers|message\/rfc822/i } @{$mail->{'attach'}};
	return 0 if (!$origattach);
	local $origmail = &extract_mail($origattach->{'data'});
	local $omid = $origmail->{'header'}->{'message-id'};
	return 0 if (!$omid);
	local ($faddr) = &split_addresses($origmail->{'header'}->{'from'});
	local $ds = &parse_delivery_status($delattach->{'data'});
	if ($ds->{'status'} =~ /^2\./) {
		&add_address_to_hash(\%delreplies, $omid, $faddr->[0]);
		}
	elsif ($ds->{'status'} =~ /^5\./) {
		&add_address_to_hash(\%delreplies, $omid, "!".$faddr->[0]);
		}
	}
else {
	return 0;
	}
}

# add_address_to_hash(&hash, messageid, address)
sub add_address_to_hash
{
local @cv = split(/\s+/, $_[0]->{$_[1]});
local $idx = &indexof($_[2], @cv);
if ($idx < 0) {
	$_[0]->{$_[1]} .= " " if (@cv);
	$_[0]->{$_[1]} .= time()." ".$_[2];
	}
}

# open_dsn_hash()
# Ensure the %dsnreplies and %delreplies hashes are tied
sub open_dsn_hash
{
if (!defined(%dsnreplies)) {
	dbmopen(%dsnreplies, "$user_module_config_directory/dsnreplies", 0600);
	}
if (!defined(%delreplies)) {
	dbmopen(%delreplies, "$user_module_config_directory/delreplies", 0600);
	}
}

# spam_report_cmd()
# Returns a command for reporting spam, or undef if none
sub spam_report_cmd
{
local %sconfig = &foreign_config("spam");
if ($config{'spam_report'} eq 'sa_learn') {
	return &has_command($sconfig{'sa_learn'}) ? "$sconfig{'sa_learn'} --spam" : undef;
	}
elsif ($config{'spam_report'} eq 'spamassassin') {
	return &has_command($sconfig{'spamassassin'}) ? "$sconfig{'spamassassin'} --r" : undef;
	}
else {
	return &has_command($sconfig{'sa_learn'}) ?
		"$sconfig{'sa_learn'} --spam" :
	       &has_command($sconfig{'spamassassin'}) ?
		"$sconfig{'spamassassin'} --r" : undef;
	}
}

1;

