# mailbox-lib.pl
# XXX don't connect when viewing cached mail?
# XXX can we cache IMAP messages?
# XXX don't reply-to our address

do '../web-lib.pl';
&init_config();
&switch_to_remote_user();
&create_user_config_dirs();
do 'boxes-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";
$folders_dir = "$remote_user_info[7]/$userconfig{'mailbox_dir'}";
%folder_types = map { $_, 1 } split(/,/, $config{'folder_types'});

# mailbox_list_mails(start, end, &folder, [headersonly], [&error])
sub mailbox_list_mails
{
if ($_[2]->{'type'} == 0) {
	# List a single mbox formatted file
	return &list_mails($_[2]->{'inbox'} ? $remote_user : $_[2]->{'file'},
			   $_[0], $_[1]);
	}
elsif ($_[2]->{'type'} == 1) {
	# List a qmail maildir
	local $md = $_[2]->{'inbox'} ? $qmail_maildir : $_[2]->{'file'};
	return &list_maildir($md, $_[0], $_[1]);
	}
elsif ($_[2]->{'type'} == 2) {
	# Get mail headers/body from a remote POP3 server

	# Login first
	local @rv = &pop3_login($_[2]);
	if ($rv[0] != 1) {
		# Failed to connect or login
		if ($_[4]) {
			@{$_[4]} = @rv;
			return ();
			}
		elsif ($rv[0] == 0) { &error($rv[1]); }
		else { &error(&text('save_elogin', $rv[1])); }
		}
	local $h = $rv[1];
	local @uidl = &pop3_uidl($h);
	local %onserver = map { &safe_uidl($_), 1 } @uidl;

	# Work out what range we want
	local ($start, $end);
	if (!defined($_[0])) {
		$start = 0; $end = @uidl-1;
		}
	elsif ($_[1] < 0) {
		$start = @uidl+$_[1]-1; $end = @uidl+$_[0]-1;
		$start = $start<0 ? 0 : $start;
		}
	else {
		$start = $_[0]; $end = $_[1];
		$end = @uidl-1 if ($end >= @uidl);
		}
	local @rv = map { undef } @uidl;

	# For each message in the range, get the headers or body
	local ($i, $f, %cached, %sizeneed);
	local $cd = "$user_module_config_directory/$_[2]->{'id'}.cache";
	if (opendir(CACHE, $cd)) {
		while($f = readdir(CACHE)) {
			if ($f =~ /^(\S+)\.body$/) {
				$cached{$1} = 2;
				}
			elsif ($f =~ /^(\S+)\.headers$/) {
				$cached{$1} = 1;
				}
			}
		closedir(CACHE);
		}
	else {
		mkdir($cd, 0700);
		}
	for($i=$start; $i<=$end; $i++) {
		local $u = &safe_uidl($uidl[$i]);
		if ($cached{$u} == 2 || $cached{$u} == 1 && $_[3]) {
			# We already have everything that we need
			}
		elsif ($cached{$u} == 1 || !$_[3]) {
			# We need to get the entire mail
			&pop3_command($h, "retr ".($i+1));
			open(CACHE, ">$cd/$u.body");
			while(<$h>) {
				s/\r//g;
				last if ($_ eq ".\n");
				print CACHE $_;
				}
			close(CACHE);
			unlink("$cd/$u.headers");
			$cached{$u} = 2;
			}
		else {
			# We just need the headers
			&pop3_command($h, "top ".($i+1)." 0");
			open(CACHE, ">$cd/$u.headers");
			while(<$h>) {
				s/\r//g;
				last if ($_ eq ".\n");
				print CACHE $_;
				}
			close(CACHE);
			$cached{$u} = 1;
			}
		local $mail = &read_mail_file($cached{$u} == 2 ?
				"$cd/$u.body" : "$cd/$u.headers");
		if ($cached{$u} == 1) {
			if ($mail->{'body'} ne "") {
				$mail->{'size'} = int($mail->{'body'});
				}
			else {
				$sizeneed{$i} = 1;
				}
			}
		$mail->{'uidl'} = $uidl[$i];
		$mail->{'idx'} = $i;
		$rv[$i] = $mail;
		}

	# Get sizes for mails if needed
	if (%sizeneed) {
		&pop3_command($h, "list");
		while(<$h>) {
			s/\r//g;
			last if ($_ eq ".\n");
			if (/^(\d+)\s+(\d+)/ && $sizeneed{$1-1}) {
				# Add size to the mail cache
				$rv[$1-1]->{'size'} = $2;
				local $u = &safe_uidl($uidl[$1-1]);
				open(CACHE, ">>$cd/$u.headers");
				print CACHE $2,"\n";
				close(CACHE);
				}
			}
		}

	# Clean up any cached mails that no longer exist on the server
	foreach $f (keys %cached) {
		if (!$onserver{$f}) {
			unlink($cached{$f} == 1 ? "$cd/$f.headers"
						: "$cd/$f.body");
			}
		}

	return @rv;
	}
elsif ($_[2]->{'type'} == 3) {
	# List an MH directory
	local $md = $_[2]->{'inbox'} ? $qmail_maildir : $_[2]->{'file'};
	return &list_mhdir($md, $_[0], $_[1]);
	}
elsif ($_[2]->{'type'} == 4) {
	# Get headers and possibly bodies from an IMAP server

	# Login and select the specified mailbox
	local @rv = &imap_login($_[2]);
	if ($rv[0] != 1) {
		# Something went wrong
		if ($_[4]) {
			@{$_[4]} = @rv;
			return ();
			}
		elsif ($rv[0] == 0) { &error($rv[1]); }
		elsif ($rv[0] == 3) { &error(&text('save_emailbox', $rv[1])); }
		elsif ($rv[0] == 2) { &error(&text('save_elogin2', $rv[1])); }
		}
	local $h = $rv[1];
	local $count = $rv[2];
	return () if (!$count);

	# Work out what range we want
	local ($start, $end);
	if (!defined($_[0])) {
		$start = 0; $end = $count-1;
		}
	elsif ($_[1] < 0) {
		$start = $count+$_[1]-1; $end = $count+$_[0]-1;
		$start = $start<0 ? 0 : $start;
		}
	else {
		$start = $_[0]; $end = $_[1];
		$end = $count-1 if ($end >= $count);
		}
	local @mail = map { undef } (0 .. $count-1);

	# Get the headers or body of messages in the specified range
	local @rv;
	if ($_[3]) {
		# Just the headers
		@rv = &imap_command($h,
			sprintf "FETCH %d:%d (RFC822.SIZE RFC822.HEADER)",
				$start+1, $end+1);
		}
	else {
		# Whole messages
		@rv = &imap_command($h,
			sprintf "FETCH %d:%d RFC822", $start+1, $end+1);
		}

	# Parse the headers or whole messages that came back
	local $i;
	for($i=0; $i<@{$rv[1]}; $i++) {
		# Extract the actual mail part
		local $mail = &parse_imap_mail($rv[1]->[$i]);
		if ($mail) {
			$mail->{'idx'} = $start+$i;
			$mail[$start+$i] = $mail;
			}
		}

	return @mail;
	}
}

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

# mailbox_search_mail(field, what, &folder)
sub mailbox_search_mail
{
if ($_[2]->{'type'} == 0) {
	return &search_mail($_[2]->{'inbox'} ? $remote_user : $_[2]->{'file'},
			    @_);
	}
elsif ($_[2]->{'type'} == 1) {
	local $md = $_[2]->{'inbox'} ? $qmail_maildir : $_[2]->{'file'};
	return &search_maildir($md, $_[0], $_[1]);
	}
elsif ($_[2]->{'type'} == 2) {
	# Get all of the mail from the server and search it
	local @mails = &mailbox_list_mails(undef, undef, $_[2],
					   $_[0] =~ /body$/ ? 0 : 1);
	local @rv;
	local $field = $_[0];
	local $neg = ($field =~ s/^\!//);
	foreach $m (@mails) {
		if ($field eq 'body') {
			push(@rv, $m)
				if (!$neg && $m->{'body'} =~ /\Q$_[1]\E/i ||
				     $neg && $m->{'body'} !~ /\Q$_[1]\E/i);
			}
		elsif ($field eq 'size') {
			push(@rv, $m)
				if (!$neg && $m->{'size'} > $_[1] ||
				     $neg && $m->{'size'} < $_[1]);
			}
		elsif ($field eq 'headers') {
			local $headers =
				join("", map { $_->[0].": ".$_->[1]."\n" }
					     @{$m->{'headers'}});
			push(@rv, $mail) if (!$neg && $headers =~ /\Q$_[1]\E/i ||
					      $neg && $headers !~ /\Q$_[1]\E/i);
			}
		else {
			push(@rv, $m)
			 if (!$neg && $m->{'header'}->{$field} =~ /\Q$_[1]\E/i||
			      $neg && $m->{'header'}->{$field} !~ /\Q$_[1]\E/i);
			}
		}
	return @rv;
	}
elsif ($_[2]->{'type'} == 3) {
	local $md = $_[2]->{'inbox'} ? $qmail_maildir : $_[2]->{'file'};
	return &search_mhdir($md, $_[0], $_[1]);
	}
elsif ($_[2]->{'type'} == 4) {
	# Use IMAP's remote search feature
	local @rv = &imap_login($_[2]);
	if ($rv[0] == 0) { &error($rv[1]); }
	elsif ($rv[0] == 3) { &error(&text('save_emailbox', $rv[1])); }
	elsif ($rv[0] == 2) { &error(&text('save_elogin2', $rv[1])); }
	local $h = $rv[1];

	# Do the search to get back a list of matching numbers
	local $field = $_[0];
	local $neg = ($field =~ s/^\!//);
	local $what = $_[1];
	$what = "\"$what\"" if ($field ne "size");
	$field = "LARGER" if ($field eq "size");
	local $search = uc($field)." ".$what."";
	$search = "NOT $search" if ($neg);
	@rv = &imap_command($h, "SEARCH $search");
	&error(&text('save_esearch', $rv[3])) if (!$rv[0]); 

	# Get and parse those specific messages
	local ($srch) = grep { $_ =~ /^\*\s+SEARCH/i } @{$rv[1]};
	local @ids = split(/\s+/, $srch);
	shift(@ids); shift(@ids);	# lose * SEARCH
	local (@mail, $idx);
	foreach $idx (@ids) {
		local @rv = &imap_command($h,
			"FETCH $idx (RFC822.SIZE RFC822.HEADER)");
		&error(&text('save_esearch', $rv[3])) if (!$rv[0]); 
		local $mail = &parse_imap_mail($rv[1]->[0]);
		if ($mail) {
			$mail->{'idx'} = $idx-1;
			push(@mail, $mail);
			}
		}
	return reverse(@mail);
	}
}

# mailbox_delete_mail(&folder, mail, ...)
sub mailbox_delete_mail
{
local $f = shift(@_);
if ($f->{'type'} == 0) {
	&delete_mail($f->{'inbox'} ? $remote_user : $f->{'file'}, @_);
	}
elsif ($f->{'type'} == 1) {
	&delete_maildir(@_);
	}
elsif ($f->{'type'} == 2) {
	# Login and delete from the POP3 server
	local @rv = &pop3_login($f);
	if ($rv[0] == 0) { &error($rv[1]); }
	elsif ($rv[0] == 2) { &error(&text('save_elogin', $rv[1])); }
	local $h = $rv[1];
	local @uidl = &pop3_uidl($h);
	local $m;
	local $cd = "$user_module_config_directory/$f->{'id'}.cache";
	foreach $m (@_) {
		local $idx = &indexof($m->{'uidl'}, @uidl);
		if ($idx >= 0) {
			&pop3_command($h, "dele ".($idx+1));
			local $u = &safe_uidl($m->{'uidl'});
			unlink("$cd/$u.headers",
			       "$cd/$u.body");
			}
		}
	}
elsif ($f->{'type'} == 3) {
	&delete_mhdir(@_);
	}
elsif ($f->{'type'} == 4) {
	# Delete from the IMAP server
	local @rv = &imap_login($f);
	if ($rv[0] == 0) { &error($rv[1]); }
	elsif ($rv[0] == 3) { &error(&text('save_emailbox', $rv[1])); }
	elsif ($rv[0] == 2) { &error(&text('save_elogin2', $rv[1])); }
	local $h = $rv[1];

	local $m;
	foreach $m (@_) {
		@rv = &imap_command($h, "STORE ".($m->{'idx'}+1).
					" +FLAGS (\\Deleted)");
		&error(&text('save_edelete', $rv[3])) if (!$rv[0]); 
		}
	@rv = &imap_command($h, "EXPUNGE");
	&error(&text('save_edelete', $rv[3])) if (!$rv[0]); 
	}
}

# mailbox_move_mail(&source, &dest, mail, ...)
# Move mail from one folder to another
sub mailbox_move_mail
{
local $src = shift(@_);
local $dst = shift(@_);
local $now = time();
local $hn = &get_system_hostname();
&create_folder_maildir($dst);
if (($src->{'type'} == 1 || $src->{'type'} == 3) && $dst->{'type'} == 1) {
	# Can just move mail files
	local $dd = $dst->{'inbox'} ? $qmail_maildir : $dst->{'file'};
	&create_folder_maildir($dst);
	foreach $m (@_) {
		rename($m->{'file'}, "$dd/cur/$now.$$.$hn");
		$now++;
		}
	}
elsif (($src->{'type'} == 1 || $src->{'type'} == 3) && $dst->{'type'} == 3) {
	# Can move and rename to MH numbering
	local $dd = $dst->{'inbox'} ? $qmail_maildir : $dst->{'file'};
	local $num = &max_mhdir($dst->{'file'}) + 1;
	foreach $m (@_) {
		rename($m->{'file'}, "$dd/$num");
		$num++;
		}
	}
else {
	# Append to new folder file, or create in folder directory
	foreach $m (@_) {
		&write_mail_folder($m, $dst);
		}
	&mailbox_delete_mail($src, @_);
	}
}

# 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;
foreach $a (@{$_[0]->{'attach'}}) {
	if ($a->{'data'} =~ /(-+BEGIN PGP MESSAGE-+\n([\000-\377]+)-+END PGP MESSAGE-+\n)/i) {
		local $enc = $1;
		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);
		$a->{'data'} = $plain;
		}
	}
return $cc ? ( 3 ) : ( 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 >= 2) {
		push(@rv, [ $sp[0], $sp[1], $i++, $sp[2] ]);
		}
	}
close(ADDRESS);
if ($config{'global_address'}) {
	local $gab = $config{'global_address'};
	local @ginfo = getgrgid($remote_user_info[3]);
	$gab =~ s/\$group/$ginfo[0]/g;
	if ($gab =~ /\$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 $gab2 = $gab;
				$gab2 =~ s/\$sgroup/$ginfo[0]/g;
				if (-r $gab2) {
					$gab = $gab2;
					last;
					}
				}
			}
		endgrent() if ($gconfig{'os_type'} ne 'hpux');
		}
	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])
# Returns HTML for an address-book popup button
sub address_button
{
local $form = @_ > 1 ? $_[1] : 0;
local $mode = @_ > 2 ? $_[2] : 0;
local ($rfield1, $rfield2);
if ($_[3]) {
	return "<input type=button onClick='ifield = document.forms[$form].$_[0]; rfield = document.forms[$form].$_[3]; chooser = window.open(\"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; chooser.rfield = rfield; window.rfield = rfield' value=\"...\">\n";
	}
else {
	return "<input type=button onClick='ifield = document.forms[$form].$_[0]; chooser = window.open(\"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
# folder modes: 0 = ~/mail, 1 = external folder, 2 = sent mail, 3 = inbox/drafts
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,
		    'index' => 0 });
	if ($config{'mail_system'} == 0) {
		$done{&user_mail_file(@remote_user_info)}++;
		}
	else {
		$done{$qmail_maildir}++;
		}
	}

# 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' => 1 });

# 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' => 2 });

# 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{'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{'index'} = scalar(@rv);
		push(@rv, \%imap);
		}
	}
closedir(DIR);
foreach $f (@rv) {
	if ($f->{'file'} && $userconfig{"notes_".$f->{'file'}}) {
		$f->{'notes_decode'} = 1;
		}
	}
return @rv;
}

# folder_type(file_or_dir)
sub folder_type
{
return -d "$_[0]/cur" ? 1 : -d $_[0] ? 3 : 0;
}

# create_folder_maildir(&folder)
# Ensure that a maildir folder has the needed new, cur and tmp directories
sub create_folder_maildir
{
mkdir($folders_dir, 0700);
if ($_[0]->{'type'} == 1) {
	local $id = $_[0]->{'inbox'} ? $qmail_maildir : $_[0]->{'file'};
	mkdir("$id/cur", 0700);
	mkdir("$id/new", 0700);
	mkdir("$id/tmp", 0700);
	}
}

# write_mail_folder(&mail, &folder, textonly)
# Writes some mail message to a folder
sub write_mail_folder
{
&create_folder_maildir($_[1]);
if ($_[1]->{'type'} == 1) {
	# Add to a maildir directory
	local $md = $_[1]->{'inbox'} ? $qmail_maildir : $_[1]->{'file'};
	&write_maildir($_[0], $md, $_[2]);
	}
elsif ($_[1]->{'type'} == 3) {
	# Create a new MH file
	local $num = &max_mhdir($_[1]->{'file'}) + 1;
	local $md = $_[1]->{'inbox'} ? $qmail_maildir : $_[1]->{'file'};
	&send_mail($_[0], "$md/$num", $_[2], 1);
	}
elsif ($_[1]->{'type'} == 0) {
	# Just append to the folder file
	&send_mail($_[0], $_[1]->{'inbox'} ? &user_mail_file($remote_user) :
			  		     $_[1]->{'file'}, $_[2], 1);
	}
elsif ($_[1]->{'type'} == 4) {
	# Upload to the IMAP server
	local @rv = &imap_login($_[1]);
	if ($rv[0] == 0) { &error($rv[1]); }
	elsif ($rv[0] == 3) { &error(&text('save_emailbox', $rv[1])); }
	elsif ($rv[0] == 2) { &error(&text('save_elogin2', $rv[1])); }
	local $h = $rv[1];

	# Create a temp file and use it to create the IMAP command
	local $temp = &tempname();
	&send_mail($_[0], $temp, $_[2], 1);
	open(TEMP, $temp);
	local $text;
	while(<TEMP>) { $text .= $_; }
	close(TEMP);
	unlink($temp);
	@rv = &imap_command($h, sprintf "APPEND %s {%d}\r\n%s",
			$_[1]->{'mailbox'} || "INBOX", length($text), $text);
	&error(&text('save_eappend', $rv[3])) if (!$rv[0]); 
	}
}

# mailbox_modify_mail(&oldmail, &newmail, &folder, textonly)
# Replaces some mail message with a new one
sub mailbox_modify_mail
{
if ($_[2]->{'type'} == 1) {
	# Just replace the existing file
	&modify_maildir($_[0], $_[1], $_[3]);
	}
elsif ($_[2]->{'type'} == 3) {
	# Just replace the existing file
	&modify_mhdir($_[0], $_[1], $_[3]);
	}
else {
	# Modify the mail file
	&modify_mail($_[2]->{'inbox'} ? $remote_user : $_[2]->{'file'},
		     $_[0], $_[1], $_[3]);
	}
}

# pop3_login(&folder)
# Logs into a POP3 server and returns a status (1=ok, 0=connect failed,
# 2=login failed) and handle or error message
sub pop3_login
{
local $h = $pop3_login_handle{$_[0]->{'id'}};
return (1, $h) if ($h);
$h = time().++$pop3_login_count;
&open_socket($_[0]->{'server'}, 110, $h, \$error);
return (0, $error) if ($error);
local $os = select($h); $| = 1; select($os);
local @rv = &pop3_command($h);
return (0, $rv[1]) if (!$rv[0]);
@rv = &pop3_command($h, "user $_[0]->{'user'}");
return (2, $rv[1]) if (!$rv[0]);
@rv = &pop3_command($h, "pass $_[0]->{'pass'}");
return (2, $rv[1]) if (!$rv[0]);
return (1, $pop3_login_handle{$_[0]->{'id'}} = $h);
}

# pop3_command(handle, command)
# Executes a command and returns the status (1 or 0 for OK or ERR) and message
sub pop3_command
{
local ($h, $c) = @_;
print $h "$c\r\n" if ($c);
local $rv = <$h>;
$rv =~ s/\r|\n//g;
return !$rv ? ( 0, "Connection closed" ) :
       $rv =~ /^\+OK\s*(.*)/ ? ( 1, $1 ) :
       $rv =~ /^\-ERR\s*(.*)/ ? ( 0, $1 ) : ( 0, $rv );
}

# pop3_logout(handle, doquit)
sub pop3_logout
{
local @rv = $_[1] ? &pop3_command($_[0], "quit") : (1, undef);
local $f;
foreach $f (keys %pop3_login_handle) {
	delete($pop3_login_handle{$f}) if ($pop3_login_handle{$f} eq $_[0]);
	}
close($_[0]);
return @rv;
}

# pop3_uidl(handle)
# Returns the uidl list
sub pop3_uidl
{
local @rv;
local $h = $_[0];
&pop3_command($h, "uidl");
while(<$h>) {
	s/\r//g;
	last if ($_ eq ".\n");
	if (/^(\d+)\s+(\S+)/) {
		push(@rv, $2);
		}
	}
return @rv;
}

# pop3_logout_all()
# Properly closes all open POP3 and IMAP sessions
sub pop3_logout_all
{
local $f;
foreach $f (keys %pop3_login_handle) {
	&pop3_logout($pop3_login_handle{$f}, 1);
	}
foreach $f (keys %imap_login_handle) {
	&imap_logout($imap_login_handle{$f}, 1);
	}
}

# imap_login(&folder)
# Logs into a POP3 server, selects a mailbox and returns a status
# (1=ok, 0=connect failed, 2=login failed, 3=mailbox error), a handle or error
# message, and the number of messages in the mailbox.
sub imap_login
{
local $h = $imap_login_handle{$_[0]->{'id'}};
return (1, $h) if ($h);
$h = time().++$imap_login_count;
local $error;
&open_socket($_[0]->{'server'}, 143, $h, \$error);
return (0, $error) if ($error);
local $os = select($h); $| = 1; select($os);

# Login normally
local @rv = &imap_command($h);
return (0, $rv[3]) if (!$rv[0]);
@rv = &imap_command($h, "login \"$_[0]->{'user'}\" \"$_[0]->{'pass'}\"");
return (2, $rv[3]) if (!$rv[0]);

# Select the right folder
@rv = &imap_command($h, "select ".($_[0]->{'mailbox'} || "INBOX"));
return (3, $rv[3]) if (!$rv[0]);
return (1, $imap_login_handle{$_[0]->{'id'}} = $h,
	$rv[2] =~ /\*\s+(\d+)\s+EXISTS/i ? $1 : undef);
}

# imap_command(handle, command)
# Executes an IMAP command and returns 1 for success or 0 for failure, and
# a reference to an array of results (some of which may be multiline), and
# all of the results joined together, and the stuff after OK/BAD
sub imap_command
{
local ($h, $c) = @_;
local @rv;

# Send the command, and read lines until a non-* one is found
local $id = $$."-".$imap_command_count++;
if ($c) {
	print $h "$id $c\r\n";
	}
while(1) {
	local $l = <$h>;
	last if (!$l);
	if ($l =~ /^(\*|\+)/) {
		# Another response, and possibly the only one if no command
		# was sent.
		push(@rv, $l);
		last if (!$c);
		if ($l =~ /\{(\d+)\}\s*$/) {
			# Start of multi-line text .. read the specified size
			local $size = $1;
			local $got;
			local $err = "Error reading email";
			while($got < $size) {
				local $buf;
				local $r = read($h, $buf, $size-$got);
				return (0, [ $err ], $err, $err) if ($r < 0);
				$rv[$#rv] .= $buf;
				$got += $r;
				}
			}
		}
	elsif ($l =~ /^(\S+)\s+/ && $1 eq $id) {
		# End of responses
		push(@rv, $l);
		last;
		}
	else {
		# Part of last response
		if (!@rv) {
			local $err = "Got unknown line $l";
			return (0, [ $err ], $err, $err);
			}
		$rv[$#rv] .= $l;
		}
	}
local $j = join("", @rv);
local $lline = $rv[$#rv];
if ($lline =~ /^(\S+)\s+OK\s*(.*)/) {
	# Looks like the command worked
	return (1, \@rv, $j, $2);
	}
else {
	# Command failed!
	return (0, \@rv, $j, $lline =~ /^(\S+)\s+(\S+)\s*(.*)/ ? $3 : undef);
	}
}

# imap_logout(handle, doquit)
sub imap_logout
{
local @rv = $_[1] ? &imap_command($_[0], "close") : (1, undef);
local $f;
foreach $f (keys %imap_login_handle) {
	delete($imap_login_handle{$f}) if ($imap_login_handle{$f} eq $_[0]);
	}
close($_[0]);
return @rv;
}

# recursive_files(dir)
sub recursive_files
{
local ($f, @rv);
opendir(DIR, $_[0]);
local @files = readdir(DIR);
closedir(DIR);
foreach $f (@files) {
	next if ($f =~ /^\./ || $f =~ /\.lock$/i);
	local $p = "$_[0]/$f";
	if ($userconfig{'mailbox_recur'} || !-d $p || -d "$p/cur") {
		push(@rv, $p);
		}
	else {
		push(@rv, &recursive_files($p));
		}
	}
return @rv;
}

# lock_folder(&folder)
sub lock_folder
{
return if ($_[0]->{'type'} == 2 || $_[0]->{'type'} == 4);
local $f = $_[0]->{'file'} ? $_[0]->{'file'} :
	   $_[0]->{'type'} == 0 ? &user_mail_file($remote_user) :
				  $qmail_maildir;
if (&lock_file($f)) {
	$_[0]->{'lock'} = $f;
	}
else {
	# Cannot lock if in /var/mail
	$f =~ s/\//_/g;
	$f = "/tmp/$f";
	$_[0]->{'lock'} = $f;
	&lock_file($f);
	}
}

# unlock_folder(&folder)
sub unlock_folder
{
return if ($_[0]->{'type'} == 2 || $_[0]->{'type'} == 4);
&unlock_file($_[0]->{'lock'});
}

# 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];
	}
}

# folder_file(&folder)
# Returns the full path to the file or directory containing the folder's mail,
# or undef if not appropriate (such as for POP3)
sub folder_file
{
return $_[0]->{'type'} == 2 ? undef :
       $_[0]->{'inbox'} && $_[0]->{'type'} == 1 ? $qmail_maildir :
       $_[0]->{'inbox'} && $_[0]->{'type'} == 3 ? $qmail_maildir :
       $_[0]->{'inbox'} && $_[0]->{'type'} == 0 ?
		&user_mail_file($remote_user) : $_[0]->{'file'};
}

# 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;
}

# parse_imap_mail(response)
# Parses a response from the IMAP server into a standard mail structure
sub parse_imap_mail
{
# Extract the actual mail part
local $mail = { };
local $realsize;
local $imap = $_[0];
if ($imap =~ /RFC822.SIZE\s+(\d+)/) {
	$realsize = $1;
	}
$imap =~ s/^\*\s+\d+\s+FETCH.*\{(\d+)\}\r?\n// || return undef;
local $size = $1;
local @lines = split(/\n/, substr($imap, 0, $size));

# Parse the headers
local $lnum = 0;
local @headers;
while(1) {
	local $line = $lines[$lnum++];
	$mail->{'size'} += length($line);
	$line =~ s/\r//g;
	last if ($line eq '');
	if ($line =~ /^(\S+):\s*(.*)/) {
		push(@headers, [ $1, $2 ]);
		}
	elsif ($line =~ /^(\s+.*)/) {
		$headers[$#headers]->[1] .= $1
			unless($#headers < 0);
		}
	}
$mail->{'headers'} = \@headers;
foreach $h (@headers) {
	$mail->{'header'}->{lc($h->[0])} = $h->[1];
	}

# Parse the body
while($lnum < @lines) {
	$mail->{'size'} += length($lines[$lnum]+1);
	$mail->{'body'} .= $lines[$lnum]."\n";
	$lnum++;
	}
$mail->{'size'} = $realsize if ($realsize);
return $mail;
}

# move_select(number, &folders, &folder-to-exclude)
# Returns HTML for selecting a folder to move to
sub move_select
{
local $rv;
$rv .= "<input type=submit name=move$_[0] value=\"$text{'mail_move'}\" ".
       "onClick='return check_clicks(form)'>\n";
$rv .= "<select name=mfolder$_[0]>\n";
foreach $f (@{$_[1]}) {
	if ($f ne $_[2] && $f->{'type'} != 2) {
		$rv .= "<option value=$f->{'index'}>$f->{'name'}\n";
		}
	}
$rv .= "</select>\n";
return $rv;
}

# find_body(&mail)
# Returns the plain text body, html body and the one to use
sub find_body
{
local ($a, $body, $textbody, $htmlbody);
foreach $a (@{$_[0]->{'attach'}}) {
	if ($a->{'type'} =~ /^text\/plain/i || $a->{'type'} eq 'text') {
		$textbody = $a if (!$textbody && $a->{'data'} =~ /\S/);
		}
	elsif ($a->{'type'} =~ /^text\/html/i) {
		$htmlbody = $a if (!$htmlbody && $a->{'data'} =~ /\S/);
		}
	}
if ($userconfig{'view_html'} == 0) {
	$body = $textbody;
	}
elsif ($userconfig{'view_html'} == 1) {
	$body = $textbody || $htmlbody;
	}
else {
	$body = $htmlbody || $textbody;
	}
return ($textbody, $htmlbody, $body);
}

# safe_html(html)
# Converts HTML to a form safe for inclusion in a page
sub safe_html
{
local $html = $_[0];
$html =~ s/^[\000-\377]*<BODY.*>//i;
$html =~ s/<\/BODY>[\000-\377]*$//i;
$html = &filter_javascript($html);
$html =~ s/(href\s*=\s*)[\/\.]+/$1/ig;
$html =~ s/(src\s*=\s*)[\/\.]+/$1/ig;
return $html;
}

# safe_uidl(string)
sub safe_uidl
{
local $rv = $_[0];
$rv =~ s/\/|\./_/g;
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);
}

# html_to_text(html)
# Attempts to convert some HTML to text form
sub html_to_text
{
local ($h2, $lynx);
if (($h2 = &has_command("html2text")) || ($lynx = &has_command("lynx"))) {
	# Can use a commonly available external program
	local $temp = &tempname().".html";
	open(TEMP, ">$temp");
	print TEMP $_[0];
	close(TEMP);
	open(OUT, ($lynx ? "$lynx -dump $temp" : "$h2 $temp")." 2>/dev/null |");
	while(<OUT>) {
		$text .= $_;
		}
	close(OUT);
	unlink($temp);
	return $text;
	}
else {
	# Do conversion manually :(
	local $html = $_[0];
	$html =~ s/\s+/ /g;
	$html =~ s/<p>/\n\n/gi;
	$html =~ s/<br>/\n/gi;
	$html =~ s/<[^>]+>//g;
	$html = &entities_to_ascii($html);
	return $html;
	}
}

1;

