#!/usr/local/bin/perl
# Display a form for replying to or composing an email
require './mailbox-lib.pl';
&ReadParse();
&set_module_index($in{'folder'});
@folders = &list_folders();
$folder = $folders[$in{'folder'}];
if ($in{'new'}) {
# Composing a new email
if (defined($in{'html'})) {
$html_edit = $in{'html'};
}
else {
$html_edit = $userconfig{'html_edit'} == 2 ? 1 : 0;
}
$sig = &get_signature();
if ($html_edit) {
$sig =~ s/\n/
\n/g;
$quote = "
".$in{'quick'}."".
"\n".$quote; $textonly = 0; } else { # Text quoting $quick_type = "text/plain"; $in{'quick'} =~ s/\s*$//g; $quick_body = $in{'quick'}."\n\n".$quote; } } else { # Body is just text $quick_body = $in{'quick'}; $quick_type = "text/plain"; } # Work out From: address ($froms, $doms) = &list_from_addresses(); ($defaddr) = grep { $_->[3] == 2 } &list_addresses(); if ($defaddr) { # From address book $from = $defaddr->[1] ? "\"$defaddr->[1]\" <$defaddr->[0]>" : $defaddr->[0]; } else { # Account default $from = $froms->[0]; } # Work out the subject and recipients $subject = &decode_mimewords($mail->{'header'}->{'subject'}); $subject = "Re: ".$subject if ($subject !~ /^Re/i); $to = $mail->{'header'}->{'reply-to'} || $mail->{'header'}->{'from'}; $cc = ""; if ($in{'quick_all'}) { $cc = $mail->{'header'}->{'to'}; $cc .= ", ".$mail->{'header'}->{'cc'} if ($mail->{'header'}->{'cc'}); } # Construct the email $newmid = &generate_message_id($from); $qmail->{'headers'} = [ [ 'From', $from ], [ 'Subject', $subject ], [ 'To', $to ], [ 'Cc', $cc ], [ 'X-Originating-IP', $ENV{'REMOTE_ADDR'} ], [ 'X-Mailer',"Usermin ".&get_webmin_version() ], [ 'Message-Id', $newmid ] ]; $qmail->{'header'}->{'message-id'} = $newmid; $rid = $mail->{'header'}->{'message-id'}; push(@{$qmail->{'headers'}}, [ 'In-Reply-To', $rid ]) if ($rid); if ($userconfig{'req_dsn'} == 1) { push(@{$mail->{'headers'}}, [ 'Disposition-Notification-To', $from ]); push(@{$mail->{'headers'}}, [ 'Read-Receipt-To', $from ]); } # Add the body $mt = $quick_type; $mt .= "; charset=$userconfig{'charset'}"; if ($quick_body =~ /[\177-\377]/) { # Contains 8-bit characters .. need to make quoted-printable $textonly = 0; @attach = ( { 'headers' => [ [ 'Content-Type', $mt ], [ 'Content-Transfer-Encoding', 'quoted-printable' ] ], 'data' => quoted_encode($quick_body) } ); } else { # Plain 7-bit ascii text @attach = ( { 'headers' => [ [ 'Content-Type', $mt ], [ 'Content-Transfer-Encoding', '7bit' ] ], 'data' => $quick_body } ); } # Inline HTML images # XXX # Tell the user &mail_page_header($draft ? $text{'send_title2'} : $text{'send_title'}); @tos = ( split(/,/, $to), split(/,/, $cc) ); $tos = join(" , ", map { "".&html_escape($_)."" } @tos); print &text('send_sending', $tos || $text{'send_nobody'}),"
\n"; # Sent it off $qmail->{'attach'} = \@attach; local $sfolder; if ($userconfig{'save_sent'}) { ($sfolder) = grep { $_->{'sent'} } @folders; if ($sfolder) { $qerr = &would_exceed_quota($sfolder, $qmail); &error($qerr) if ($qerr); } } $notify = $userconfig{'req_del'} == 1; &send_mail($qmail, undef, $textonly, $config{'no_crlf'}, undef, undef, undef, undef, $notify); if ($sfolder) { &lock_folder($sfolder); &write_mail_folder($qmail, $sfolder, $textonly) if ($sfolder); &unlock_folder($sfolder); } &set_mail_read($sfolder, $qmail, 1); print "$text{'send_done'}
\n"; if ($userconfig{'send_return'}) { # Return to mail list print "\n"; } &mail_page_footer( "index.cgi?folder=$in{'folder'}&start=$in{'start'}", $text{'mail_return'}); return; } else { # Replying or forwarding if ($in{'mailforward'} ne '') { # Forwarding multiple .. get the messages @mailforwardids = split(/\0/, $in{'mailforward'}); @fwdmail = &mailbox_select_mails($folder, \@mailforwardids, 0); @fwdmail || &error($text{'reply_efwdnone'}); $mail = $fwdmail[0]; } else { # Replying or forwarding one .. get it $mail = &mailbox_get_mail($folder, $in{'id'}, 0); $mail || &error($text{'view_egone'}); &decode_and_sub(); } $viewlink = "view_mail.cgi?id=".&urlize($in{'id'}). "&folder=$in{'folder'}"; $mail || &error($text{'mail_eexists'}); if ($in{'delete'}) { # Just delete the email if (!$in{'confirm'} && &need_delete_warn($folder)) { # Need to ask for confirmation before deleting &mail_page_header($text{'confirm_title'}); print &check_clicks_function(); print "
\n"; &mail_page_footer( $viewlink, $text{'view_return'}, "index.cgi?folder=$in{'folder'}", $text{'index'}); exit; } &lock_folder($folder); &mailbox_delete_mail($folder, $mail); &unlock_folder($folder); &pop3_logout_all(); &redirect_to_previous(); exit; } elsif ($in{'print'}) { # Extract the mail body ($textbody, $htmlbody, $body) = &find_body($mail, $userconfig{'view_html'}); # Output HTML header &PrintHeader(); print "\n"; print "| $text{'view_headers'} | ||||||||||
|
\n"; # Just display the mail body for printing if ($body eq $textbody) { print "
";
foreach $l (&wrap_lines($body->{'data'},
$userconfig{'wrap_width'})) {
print &eucconv_and_escape($l),"\n";
}
print " |
| \n"; print &safe_html($body->{'data'}); print " |
",&text('detach_ok', "$paths[$i]", $sz),"
\n"; } &mail_page_footer( $viewlink, $text{'view_return'}, "index.cgi?folder=$in{'folder'}", $text{'mail_return'}); exit; } elsif ($in{'black'} || $in{'white'}) { # Add sender to SpamAssassin black/write list, and tell user $mode = $in{'black'} ? "black" : "white"; &mail_page_header($text{$mode.'_title'}); &foreign_require("spam", "spam-lib.pl"); local $conf = &spam::get_config(); local @from = map { @{$_->{'words'}} } &spam::find($mode."list_from", $conf); local %already = map { $_, 1 } @from; local ($spamfrom) = &address_parts($mail->{'header'}->{'from'}); if ($already{$spamfrom}) { print &text($mode.'_already', "$spamfrom"),"
\n"; } else { push(@from, $spamfrom); &spam::save_directives($conf, $mode.'list_from', \@from, 1); &flush_file_lines(); print &text($mode.'_done', "$spamfrom"),"
\n"; } # Also move message to inbox $inbox = &get_spam_inbox_folder(); if ($userconfig{'white_move'} && $folder->{'spam'} && $in{'white'}) { &mailbox_move_mail($folder, $inbox, $mail); &mail_page_footer( "index.cgi?folder=$in{'folder'}", $text{'mail_return'}); } else { &mail_page_footer( $viewlink, $text{'view_return'}, "index.cgi?folder=$in{'folder'}", $text{'mail_return'}); } exit; } elsif ($in{'razor'} || $in{'ham'}) { # Report message to Razor as spam/ham and tell user $mode = $in{'razor'} ? "razor" : "ham"; &mail_page_header($text{$mode.'_title'}); print "",$text{$mode.'_report'},"\n"; print "
";
local $temp = &transname();
&send_mail($mail, $temp, 0, 1);
if ($userconfig{'spam_del'} && $mode eq "razor") {
# Delete message too
&lock_folder($folder);
&mailbox_delete_mail($folder, $mail);
&unlock_folder($folder);
}
local $cmd = $mode eq "razor" ? &spam_report_cmd()
: &ham_report_cmd();
open(OUT, "$cmd <$temp 2>&1 |");
local $error;
while() {
print &html_escape($_);
$error++ if (/failed/i);
}
close(OUT);
unlink($temp);
print " \n";
$deleted = 0;
if ($? || $error) {
print "",$text{'razor_err'},"\n"; } else { if ($userconfig{'spam_del'} && $mode eq "razor") { # Delete message too print "$text{'razor_deleted'}
\n"; $deleted = 1; $loc = "index.cgi?folder=$in{'folder'}"; } elsif ($userconfig{'white_move'} && $folder->{'spam'} && $in{'ham'}) { # Move mail to inbox and tell user &mailbox_move_mail($folder, $inbox, $mail); print "",&text('razor_moved', $inbox->{'name'}),"
\n"; $deleted = 1; $loc = "index.cgi?folder=$in{'folder'}"; } else { # Tell user it was done print "",$text{'razor_done'},"
\n";
$loc = $viewlink;
}
print "\n";
}
&mail_page_footer(
$deleted ? ( ) :
( $viewlink, $text{'view_return'} ),
"index.cgi?folder=$in{'folder'}",
$text{'mail_return'});
exit;
}
elsif ($in{'dsn'}) {
# Send DSN to sender
dbmopen(%dsn, "$user_module_config_directory/dsn", 0600);
$dsnaddr = &send_delivery_notification($mail, undef, 1);
if ($dsnaddr) {
$mid = $mail->{'header'}->{'message-id'};
$dsn{$mid} = time()." ".$dsnaddr;
}
dbmclose(%dsn);
&redirect_to_previous();
exit;
}
# Get the forwarded message and its attachments
if (!@fwdmail) {
&parse_mail($mail);
&decrypt_attachments($mail);
@attach = @{$mail->{'attach'}};
}
if ($in{'enew'}) {
# Editing an existing message, so keep same fields
$to = $mail->{'header'}->{'to'};
$rto = $mail->{'header'}->{'reply-to'};
$from = $mail->{'header'}->{'from'};
$cc = $mail->{'header'}->{'cc'};
$bcc = $mail->{'header'}->{'bcc'};
$ouser = $1 if ($from =~ /^(\S+)\@/);
}
else {
if (!$in{'forward'} && !@fwdmail) {
# Replying to a message, so set To: field
$to = $mail->{'header'}->{'reply-to'};
$to = $mail->{'header'}->{'from'} if (!$to);
}
if ($in{'rall'}) {
# If replying to all, add any addresses in the original
# To: or Cc: to our new Cc: address.
$cc = $mail->{'header'}->{'to'};
$cc .= ", ".$mail->{'header'}->{'cc'}
if ($mail->{'header'}->{'cc'});
}
}
# Work out new subject, depending on whether we are replying
# our forwarding a message (or neither)
local $qu = !$in{'enew'} &&
(!$in{'forward'} || !$userconfig{'fwd_mode'});
$subject = &decode_mimewords($mail->{'header'}->{'subject'});
$subject = "Re: ".$subject if ($subject !~ /^Re/i && !$in{'forward'} &&
!@fwdmail && !$in{'enew'});
$subject = "Fwd: ".$subject if ($subject !~ /^Fwd/i &&
($in{'forward'} || @fwdmail));
# Construct the initial mail text
$sig = &get_signature();
($quote, $html_edit, $body) = "ed_message($mail, $qu, $sig,
$in{'body'});
# Don't include the original body as an attachment
@attach = &remove_body_attachments($mail, \@attach);
if (!$in{'forward'} && !$in{'enew'}) {
# When replying, lose non-cid attachments
@attach = grep { $_->{'header'}->{'content-id'} ||
$_->{'header'}->{'content-location'} } @attach;
}
# For a HTML reply or forward, fix up the cid: to refer to attachments
# in the original message.
if ($html_edit) {
$qmid = &urlize($mail->{'id'});
$quote = &fix_cids($quote, \@attach,
"detach.cgi?id=$qmid&folder=$in{'folder'}$subs");
}
&mail_page_header(
$in{'forward'} || @fwdmail ? $text{'forward_title'} :
$in{'enew'} ? $text{'enew_title'} :
$text{'reply_title'},
undef,
$html_edit ? "onload='initEditor()'" : "");
}
# Show form start, with upload progress tracker hook
$upid = time().$$;
print &ui_form_start("send_mail.cgi?id=$upid", "form-data", undef,
&read_parse_mime_javascript($upid, [ map { "attach$_" } (0..10) ]));
# Output various hidden fields
print "\n";
print "\n";
print "\n";
print "\n";
print "\n";
print "\n";
foreach $s (@sub) {
print "\n";
}
if ($in{'reply'}) {
print "{'message-id'})."'>\n";
}
# Start tabs for from / to / cc / bcc / signing / options
# Subject is separate
print &ui_table_start($text{'reply_headers'}, "width=100%", 2);
if (&has_command("gpg") && &foreign_check("gnupg")) {
&foreign_require("gnupg", "gnupg-lib.pl");
@keys = &foreign_call("gnupg", "list_keys");
$has_gpg = @keys ? 1 : 0;
}
@tds = ( "width=10%", "width=90% nowrap" );
@tabs = ( [ "from", $text{'reply_tabfrom'} ],
$userconfig{'reply_to'} ne 'x' ?
( [ "rto", $text{'reply_tabreplyto'} ] ) : ( ),
[ "to", $text{'reply_tabto'} ],
[ "cc", $text{'reply_tabcc'} ],
[ "bcc", $text{'reply_tabbcc'} ],
$has_gpg ? ( [ "signing", $text{'reply_tabsigning'} ] ) : ( ),
[ "options", $text{'reply_taboptions'} ] );
print &ui_table_row(undef, &ui_tabs_start(\@tabs, "tab", "to", 0), 2);
# From address tab
if ($from) {
# Got From address
@froms = ( $from );
}
else {
# Work out From: address
local ($froms, $doms) = &list_from_addresses();
@froms = @$froms;
}
@faddrs = grep { $_->[3] } &list_addresses();
($defaddr) = grep { $_->[3] == 2 } @faddrs;
if ($folder->{'fromaddr'}) {
# Folder has a specified From: address
($defaddr) = &split_addresses($folder->{'fromaddr'});
}
if ($config{'edit_from'} == 1) {
# User can enter any from address he wants
if ($defaddr) {
# Address book contains a default from address
$froms[0] = $defaddr->[1] ? "\"$defaddr->[1]\" <$defaddr->[0]>"
: $defaddr->[0];
}
$frominput = &ui_address_field("from", $froms[0], 1, 0);
}
elsif ($config{'edit_from'} == 2) {
# Only the real name and username part is editable
local ($real, $user, $dom);
local ($sp) = $defaddr || &split_addresses($froms[0]);
$real = $sp->[1];
if ($sp->[0] =~ /^(\S+)\@(\S+)$/) {
$user = $1; $dom = $2;
}
else {
$user = $sp->[0];
}
$frominput = &ui_textbox("real", $real, 15)."\n".
"<".&ui_textbox("user", $user, 10)."\@";
if (@$doms > 1) {
$frominput .= &ui_select("dom", undef,
[ map { [ $_ ] } @$doms ]).">";
}
else {
$frominput .= "$dom>".
&ui_hidden("dom", $dom);
}
$frominput .= &address_button("user", 0, 2, "real") if (@faddrs);
}
else {
# A fixed From address, or a choice of fixed options
if (@froms > 1) {
$frominput = &ui_select("from", undef,
[ map { [ $_, &html_escape($_) ] } @froms ]);
}
else {
$frominput = "".&html_escape($froms[0])."".
&ui_hidden("from", $froms[0]);
}
}
print &ui_tabs_start_tabletab("tab", "from");
print &ui_table_row($text{'mail_from'}, $frominput, 1, \@tds);
print &ui_tabs_end_tabletab();
# Show the Reply-To field
if ($userconfig{'reply_to'} ne 'x') {
$rto = $userconfig{'reply_to'} if ($userconfig{'reply_to'} ne '*');
print &ui_tabs_start_tabletab("tab", "rto");
print &ui_table_row($text{'mail_replyto'},
&ui_address_field("replyto", $rto, 1, 0), 1, \@tds);
print &ui_tabs_end_tabletab();
}
# Show To: field
print &ui_tabs_start_tabletab("tab", "to");
print &ui_table_row($text{'mail_to'}, &ui_address_field("to", $to, 0, 1),
1, \@tds);
print &ui_tabs_end_tabletab();
# Show Cc: field
print &ui_tabs_start_tabletab("tab", "cc");
print &ui_table_row($text{'mail_cc'}, &ui_address_field("cc", $cc, 0, 1),
1, \@tds);
print &ui_tabs_end_tabletab();
# Show Bcc: field
$bcc ||= $userconfig{'bcc_to'};
print &ui_tabs_start_tabletab("tab", "bcc");
print &ui_table_row($text{'mail_bcc'}, &ui_address_field("bcc", $bcc, 0, 1),
1, \@tds);
print &ui_tabs_end_tabletab();
# Ask for signing and encryption
if ($has_gpg) {
print &ui_tabs_start_tabletab("tab", "signing");
@signs = ( );
foreach $k (@keys) {
local $n = $k->{'name'}->[0];
$n = substr($n, 0, 40)."..." if (length($n) > 40);
if ($k->{'secret'}) {
push(@signs, [ $k->{'index'}, $n ]);
}
push(@crypts, [ $k->{'index'}, $n ]);
}
print &ui_table_row($text{'mail_sign'},
&ui_select("sign", "",
[ [ "", $text{'mail_nosign'} ], @signs ]), 1, \@tds);
print &ui_table_row($text{'mail_crypt'},
&ui_select("crypt", "",
[ [ "", $text{'mail_nocrypt'} ],
[ -1, $text{'mail_samecrypt'} ], @crypts ]), 1, \@tds);
print &ui_tabs_end_tabletab();
}
# Show tabs for options
print &ui_tabs_start_tabletab("tab", "options");
print &ui_table_row($text{'mail_pri'},
&ui_select("pri", "",
[ [ 1, $text{'mail_highest'} ],
[ 2, $text{'mail_high'} ],
[ "", $text{'mail_normal'} ],
[ 4, $text{'mail_low'} ],
[ 5, $text{'mail_lowest'} ] ]), 1, \@tds);
if ($userconfig{'req_dsn'} == 2) {
# Ask for a disposition (read) status
print &ui_table_row($text{'reply_dsn'},
&ui_radio("dsn", 0, [ [ 1, $text{'yes'} ],
[ 0, $text{'no'} ] ]), 1, \@tds);
}
if ($userconfig{'req_del'} == 2) {
# Ask for a delivery status
print &ui_table_row($text{'reply_del'},
&ui_radio("del", 0, [ [ 1, $text{'yes'} ],
[ 0, $text{'no'} ] ]), 1, \@tds);
}
# Ask if should add to address book
print &ui_table_row(" ",
&ui_checkbox("abook", 1, $text{'reply_aboot'}, $userconfig{'add_abook'}),
1, \@tds);
print &ui_tabs_end_tabletab();
print &ui_tabs_end();
# Field for subject is always at the bottom
if ($userconfig{'send_buttons'}) {
print &ui_table_row($text{'mail_subject'},
&ui_textbox("subject", $subject, 40, 0, undef,
"style='width:60%'").
&ui_submit($text{'reply_send'}).
&ui_submit($text{'reply_draft'}, "draft").
&ui_submit($text{'reply_save'}, "save"),
1, \@tds);
}
else {
# Subject only
print &ui_table_row($text{'mail_subject'},
&ui_textbox("subject", $subject, 40, 0, undef,
"style='width:95%'"), 1, \@tds);
}
print &ui_table_end();
# Create link for switching to HTML/text mode
if ($in{'new'}) {
if ($html_edit) {
$modelink = " $text{'reply_html0'}";
}
else {
$modelink = " $text{'reply_html1'}";
}
}
# Output message body input
print &ui_table_start(&left_right_align("$text{'reply_body'}",$modelink),
"width=100%", 2);
if ($html_edit) {
# Output HTML editor textarea
print <
\\n";
}
return false;
}
function add_ss_attachment()
{
var block = document.getElementById("ssattachblock");
var uploader = "$ssider";
if (block) {
var count = 0;
while(document.forms[0]["file"+count]) { count++; }
block.innerHTML += uploader.replace("NAME", "file"+count)+"
\\n";
}
return false;
}
EOF
# Show form for attachments (both uploaded and server-side)
print &ui_table_start($config{'server_attach'} ? $text{'reply_attach2'}
: $text{'reply_attach3'},
"width=100%", 2);
}
# Uploaded attachments
if (!$main::no_browser_uploads) {
$atable = "";
for($i=0; $i<$userconfig{'def_attach'}; $i++) {
$atable .= &ui_upload("attach$i", 80, 0,
"style='width:100%'")."
";
}
print &ui_hidden("attachcount", int($i)),"\n";
print &ui_table_row(undef, $atable, 2, [ undef, "id=attachblock" ]);
}
if ($config{'server_attach'}) {
$atable = "";
for($i=0; $i<$userconfig{'def_attach'}; $i++) {
$atable .= &ui_textbox("file$i", undef, 60, 0, undef,
"style='width:95%'").
&file_chooser_button("file$i"),"
\n";
}
print &ui_table_row(undef, $atable, 2, [ undef, "id=ssattachblock" ]);
print &ui_hidden("ssattachcount", int($i)),"\n";
}
# Links to add more fields
if (!$main::no_browser_uploads && &supports_javascript()) {
push(@addlinks, "".
"$text{'reply_addattach'}" );
}
if ($config{'server_attach'} && &supports_javascript()) {
push(@addlinks, "".
"$text{'reply_addssattach'}" );
}
if ($any_attach) {
print &ui_table_row(undef, &ui_links_row(\@addlinks), 2);
print &ui_table_end();
}
print &ui_form_end([ [ "send", $text{'reply_send'} ],
[ "draft", $text{'reply_draft'} ],
[ "save", $text{'reply_save'} ],
]);
&mail_page_footer("index.cgi?folder=$in{'folder'}&start=$in{'start'}",
$text{'mail_return'});
&pop3_logout_all();
sub decode_and_sub
{
return if (!$mail);
¬es_decode($mail, $folder);
&parse_mail($mail);
@sub = split(/\0/, $in{'sub'});
$subs = join("", map { "&sub=$_" } @sub);
foreach $s (@sub) {
# We are looking at a mail within a mail ..
&decrypt_attachments($mail);
local $amail = &extract_mail(
$mail->{'attach'}->[$s]->{'data'});
&parse_mail($amail);
$mail = $amail;
}
($deccode, $decmessage) = &decrypt_attachments($mail);
}
sub redirect_to_previous
{
local $perpage = $folder->{'perpage'} || $userconfig{'perpage'};
local $s = int($mail->{'sortidx'} / $perpage) * $perpage;
if ($userconfig{'open_mode'}) {
&redirect($viewlink);
}
else {
&redirect("index.cgi?folder=$in{'folder'}&start=$s");
}
}