Handle hostnames with upper-case letters
[webmin.git] / mailboxes / reply_mail.cgi
1 #!/usr/local/bin/perl
2 # Display a form for replying to or composing an email
3
4 require './mailboxes-lib.pl';
5 &ReadParse();
6 &can_user($in{'user'}) || &error($text{'mail_ecannot'});
7 @uinfo = &get_mail_user($in{'user'});
8 @uinfo || &error($text{'view_eugone'});
9 $euser = &urlize($in{'user'});
10
11 @folders = &list_user_folders($in{'user'});
12 $folder = $folders[$in{'folder'}];
13 if ($in{'new'}) {
14         # Composing a new email
15         if (defined($in{'html'})) {
16                 $html_edit = $in{'html'};
17                 }
18         else {
19                 $html_edit = $config{'html_edit'} == 2 ? 1 : 0;
20                 }
21         $sig = &get_signature($in{'user'});
22         if ($html_edit) {
23                 $sig =~ s/\n/<br>\n/g;
24                 $quote = "<html><body></body></html>";
25                 }
26         else {
27                 $quote = "\n\n$sig" if ($sig);
28                 }
29         $to = $in{'to'};
30         &mail_page_header($text{'compose_title'}, undef,
31                           $html_edit ? "onload='xinha_init()'" : "",
32                           &folder_link($in{'user'}, $folder));
33         }
34 else {
35         # Replying or forwarding
36         if ($in{'mailforward'} ne '') {
37                 # Forwarding multiple
38                 @mailforward = sort { $a <=> $b }
39                                     split(/\0/, $in{'mailforward'});
40                 @mails = &mailbox_list_mails(
41                              $mailforward[0], $mailforward[@mailforward-1],
42                              $folder);
43                 $mail = $mails[$mailforward[0]];
44                 @fwdmail = map { $mails[$_] } @mailforward;
45                 }
46         else {
47                 # Replying to one
48                 @mails = &mailbox_list_mails($in{'idx'}, $in{'idx'},
49                                              $folder);
50                 $mail = $mails[$in{'idx'}];
51                 &decode_and_sub();
52                 }
53         &check_modification($folder) if ($in{'delete'});
54         $mail || &error($text{'mail_eexists'});
55
56         # Find the body parts and set the character set
57         ($textbody, $htmlbody, $body) =
58                 &find_body($mail, $config{'view_html'});
59         $main::force_charset = &get_mail_charset($mail, $body);
60
61         if ($in{'delete'}) {
62                 # Just delete the email
63                 if (!$in{'confirm'} && &need_delete_warn($folder)) {
64                         # Need to ask for confirmation before deleting
65                         &mail_page_header($text{'confirm_title'}, undef, undef,
66                                           &folder_link($in{'user'}, $folder));
67                         print &ui_confirmation_form(
68                                 "reply_mail.cgi",
69                                 $text{'confirm_warn3'}."<br>".
70                                 ($config{'delete_warn'} ne 'y' ?
71                                         $text{'confirm_warn2'} :
72                                         $text{'confirm_warn4'}),
73                                 [ &inputs_to_hiddens(\%in) ],
74                                 [ [ 'confirm', $text{'confirm_ok'} ] ],
75                                 );
76                         &mail_page_footer("view_mail.cgi?idx=$in{'idx'}&folder=$in{'folder'}&user=$euser",
77                                 $text{'view_return'},
78                                 "list_mail.cgi?folder=$in{'folder'}&user=$euser",
79                                 $text{'mail_return'},
80                                 "", $text{'index_return'});
81                         exit;
82                         }
83                 &lock_folder($folder);
84                 &mailbox_delete_mail($folder, $mail);
85                 &unlock_folder($folder);
86                 &webmin_log("delmail", undef, undef,
87                             { 'from' => $folder->{'file'},
88                               'count' => 1 } );
89                 &redirect("list_mail.cgi?folder=$in{'folder'}&user=$euser");
90                 exit;
91                 }
92         elsif ($in{'print'}) {
93                 # Show email for printing
94                 &decode_and_sub();
95                 &ui_print_header(undef, &decode_mimewords(
96                                         $mail->{'header'}->{'subject'}));
97                 &show_mail_printable($mail, $body, $textbody, $htmlbody);
98                 print "<script>window.print();</script>\n";
99                 &ui_print_footer();
100                 exit;
101                 }
102         elsif ($in{'mark1'} || $in{'mark2'}) {
103                 # Just mark the message
104                 $mode = $in{'mark1'} ? $in{'mode1'} : $in{'mode2'};
105                 &set_mail_read($folder, $mail, $mode);
106                 $perpage = $folder->{'perpage'} || $config{'perpage'};
107                 $s = int((@mails - $in{'idx'} - 1) / $perpage) * $perpage;
108                 &redirect("list_mail.cgi?start=$s&folder=$in{'folder'}&user=$euser");
109                 exit;
110                 }
111         elsif ($in{'detach'}) {
112                 # Detach some attachment to a directory on the server
113                 &error_setup($text{'detach_err'});
114                 $in{'dir'} || &error($text{'detach_edir'});
115                 $in{'dir'} = "$uinfo[7]/$in{'dir'}"
116                         if ($in{'dir'} !~ /^\//);
117                 &decode_and_sub();
118
119                 if ($in{'attach'} eq '*') {
120                         # Detaching all attachments, under their filenames
121                         @dattach = grep { $_->{'idx'} ne $in{'bindex'} }
122                                         @{$mail->{'attach'}};
123                         }
124                 else {
125                         # Just one attachment
126                         @dattach = ( $mail->{'attach'}->[$in{'attach'}] );
127                         }
128
129                 local @paths;
130                 foreach $attach (@dattach) {
131                         local $path;
132                         if (-d $in{'dir'}) {
133                                 # Just write to the filename in the directory
134                                 local $fn;
135                                 if ($attach->{'filename'}) {
136                                         $fn = &decode_mimewords(
137                                                 $attach->{'filename'});
138                                         }
139                                 else {
140                                         $attach->{'type'} =~ /\/(\S+)$/;
141                                         $fn = "file.$1";
142                                         }
143                                 $path = "$in{'dir'}/$fn";
144                                 }
145                         else {
146                                 # Assume a full path was given
147                                 $path = $in{'dir'};
148                                 }
149                         push(@paths, $path);
150                         }
151
152                 &switch_to_user($in{'user'});
153                 for($i=0; $i<@dattach; $i++) {
154                         # Try to write the files
155                         &open_tempfile(FILE, ">$paths[$i]", 1, 1) ||
156                                 &error(&text('detach_eopen',
157                                              "<tt>$paths[$i]</tt>", $!));
158                         (print FILE $dattach[$i]->{'data'}) ||
159                                 &error(&text('detach_ewrite',
160                                              "<tt>$paths[$i]</tt>", $!));
161                         close(FILE) ||
162                                 &error(&text('detach_ewrite',
163                                              "<tt>$paths[$i]</tt>", $!));
164                         }
165                 &switch_user_back();
166
167                 # Show a message about the new files
168                 &mail_page_header($text{'detach_title'}, undef, undef,
169                                   &folder_link($in{'user'}, $folder));
170
171                 for($i=0; $i<@dattach; $i++) {
172                         local $sz = (int(length($dattach[$i]->{'data'}) /
173                                          1000)+1)." Kb";
174                         print "<p>",&text('detach_ok',
175                                           "<tt>$paths[$i]</tt>", $sz),"<p>\n";
176                         }
177
178                 &mail_page_footer("view_mail.cgi?idx=$in{'idx'}&folder=$in{'folder'}&user=$euser", $text{'view_return'},
179                         "list_mail.cgi?folder=$in{'folder'}&user=$euser", $text{'mail_return'},
180                         "", $text{'index_return'});
181                 exit;
182                 }
183         elsif ($in{'black'}) {
184                 # Add sender to global SpamAssassin blacklist, and tell user
185                 &mail_page_header($text{'black_title'});
186
187                 &foreign_require("spam", "spam-lib.pl");
188                 local $conf = &spam::get_config();
189                 local @from = map { @{$_->{'words'}} }
190                                   &spam::find("blacklist_from", $conf);
191                 local %already = map { $_, 1 } @from;
192                 local ($spamfrom) = &address_parts($mail->{'header'}->{'from'});
193                 if ($already{$spamfrom}) {
194                         print "<b>",&text('black_already',
195                                           "<tt>$spamfrom</tt>"),"</b><p>\n";
196                         }
197                 else {
198                         push(@from, $spamfrom);
199                         &spam::save_directives($conf, 'blacklist_from',
200                                                \@from, 1);
201                         &flush_file_lines();
202                         print "<b>",&text('black_done',
203                                           "<tt>$spamfrom</tt>"),"</b><p>\n";
204                         }
205
206                 &mail_page_footer("list_mail.cgi?folder=$in{'folder'}&user=$euser", $text{'mail_return'}, "", $text{'index_return'});
207                 exit;
208                 }
209         elsif ($in{'razor'}) {
210                 # Report message to Razor and tell user
211                 &mail_page_header($text{'razor_title'});
212
213                 print "<b>$text{'razor_report'}</b>\n";
214                 print "<pre>";
215                 local $cmd = &spam_report_cmd($in{'user'});
216                 local $temp = &transname();
217                 &send_mail($mail, $temp, 0, 1);
218                 &open_execute_command(OUT, "$cmd <$temp 2>&1", 1);
219                 local $error;
220                 while(<OUT>) {
221                         print &html_escape($_);
222                         $error++ if (/failed/i);
223                         }
224                 close(OUT);
225                 unlink($temp);
226                 print "</pre>\n";
227                 if ($? || $error) {
228                         print "<b>$text{'razor_err'}</b><p>\n";
229                         }
230                 else {
231                         if ($config{'spam_del'}) {
232                                 # Delete message too
233                                 &lock_folder($folder);
234                                 &mailbox_delete_mail($folder, $mail);
235                                 &unlock_folder($folder);
236                                 print "<b>$text{'razor_deleted'}</b><p>\n";
237                                 }
238                         else {
239                                 print "<b>$text{'razor_done'}</b><p>\n";
240                                 }
241                         }
242
243                 &mail_page_footer("list_mail.cgi?folder=$in{'folder'}&user=$euser", $text{'mail_return'}, "", $text{'index_return'});
244                 exit;
245                 }
246
247         if (!@mailforward) {
248                 &parse_mail($mail);
249                 @attach = @{$mail->{'attach'}};
250                 }
251
252         if ($in{'strip'}) {
253                 # Remove all non-body attachments
254                 local $newmail = { 'headers' => $mail->{'headers'},
255                                    'header' => $mail->{'header'},
256                                    'fromline' => $mail->{'fromline'} };
257                 foreach $a (@attach) {
258                         if ($a->{'type'} eq 'text/plain' ||
259                             $a->{'type'} eq 'text') {
260                                 $newmail->{'attach'} = [ $a ];
261                                 last;
262                                 }
263                         }
264                 &lock_folder($folder);
265                 &mailbox_modify_mail($mail, $newmail, $folder);
266                 &unlock_folder($folder);
267                 &redirect("list_mail.cgi?user=$euser&folder=$in{'folder'}");
268                 exit;
269                 }
270
271         if ($in{'enew'}) {
272                 # Editing an existing message, so keep same fields
273                 $to = $mail->{'header'}->{'to'};
274                 $rto = $mail->{'header'}->{'reply-to'};
275                 $from = $mail->{'header'}->{'from'};
276                 $cc = $mail->{'header'}->{'cc'};
277                 $ouser = $1 if ($from =~ /^(\S+)\@/);
278                 }
279         else {
280                 if (!$in{'forward'} && !@mailforward) {
281                         # Replying to a message, so set To: field
282                         $to = $mail->{'header'}->{'reply-to'};
283                         $to = $mail->{'header'}->{'from'} if (!$to);
284                         }
285                 if ($in{'rall'}) {
286                         # If replying to all, add any addresses in the original
287                         # To: or Cc: to our new Cc: address.
288                         $cc = $mail->{'header'}->{'to'};
289                         $cc .= ", ".$mail->{'header'}->{'cc'}
290                                 if ($mail->{'header'}->{'cc'});
291                         }
292                 }
293
294         # Convert MIMEwords in headers to 8 bit for display
295         $to = &decode_mimewords($to);
296         $rto = &decode_mimewords($rto);
297         $cc = &decode_mimewords($cc);
298         $bcc = &decode_mimewords($bcc);
299
300         # Work out new subject, depending on whether we are replying
301         # our forwarding a message (or neither)
302         local $qu = !$in{'enew'} &&
303                     (!$in{'forward'} || !$config{'fwd_mode'});
304         $subject = &html_escape(&decode_mimewords(
305                                 $mail->{'header'}->{'subject'}));
306         $subject = "Re: ".$subject if ($subject !~ /^Re/i && !$in{'forward'} &&
307                                        !@mailforward && !$in{'enew'});
308         $subject = "Fwd: ".$subject if ($subject !~ /^Fwd/i &&
309                                         ($in{'forward'} || @mailforward));
310
311         # Construct the initial mail text
312         $sig = &get_signature($in{'user'});
313         ($quote, $html_edit, $body) = &quoted_message($mail, $qu, $sig);
314         if ($in{'forward'} || $in{'enew'}) {
315                 @attach = grep { $_ ne $body } @attach;
316                 }
317         else {
318                 undef(@attach);
319                 }
320
321         # Show header
322         &mail_page_header(
323                 $in{'forward'} || @mailforward ? $text{'forward_title'} :
324                 $in{'enew'} ? $text{'enew_title'} :
325                               $text{'reply_title'}, undef,
326                 $html_edit ? "onload='xinha_init()'" : "",
327                 &folder_link($in{'user'}, $folder));
328         }
329
330 # Show form start, with upload progress tracker hook
331 $upid = time().$$;
332 $onsubmit = &read_parse_mime_javascript($upid, [ map { "attach$_" } (0..10) ]);
333 print &ui_form_start("send_mail.cgi?id=$upid", "form-data", undef, $onsubmit);
334
335 # Output various hidden fields
336 print &ui_hidden("user", $in{'user'});
337 print &ui_hidden("ouser", $ouser);
338 print &ui_hidden("idx", $in{'idx'});
339 print &ui_hidden("folder", $in{'folder'});
340 print &ui_hidden("new", $in{'new'});
341 print &ui_hidden("enew", $in{'enew'});
342 foreach $s (@sub) {
343         print &ui_hidden("sub", $s);
344         }
345 print &ui_hidden("charset", $main::force_charset);
346
347 # Start tabs for from / to / cc / bcc
348 # Subject is separate
349 print &ui_table_start($text{'reply_headers'}, "width=100%", 2);
350 @tds = ( "width=10%", "width=90% nowrap" );
351 @tabs = ( [ "from", $text{'reply_tabfrom'} ],
352           [ "to", $text{'reply_tabto'} ],
353           [ "cc", $text{'reply_tabcc'} ],
354           [ "bcc", $text{'reply_tabbcc'} ],
355           [ "options", $text{'reply_taboptions'} ] );
356 print &ui_table_row(undef, &ui_tabs_start(\@tabs, "tab", "to", 0), 2);
357
358 # From address tab
359 $from ||= &get_user_from_address(\@uinfo);
360 @froms = split(/\s+/, $access{'from'});
361 if ($access{'fmode'} == 0) {
362         # Any email addresss
363         $frominput = &ui_address_field("from", $from, 1, 0);
364         }
365 elsif ($access{'fmode'} == 1) {
366         # Current username at some domains
367         local $u = $from || $ouser || $in{'user'};
368         $u =~ s/\@.*$//;
369         $frominput = &ui_select("from", $from,
370                 $access{'fromname'} ?
371                         [ map { [ "$access{'fromname'} &lt;$u\@$_&gt;",
372                                   "$u\@$_" ] } @froms ] :
373                         [ map { "$u\@$_" } @froms ]);
374         }
375 elsif ($access{'fmode'} == 2) {
376         # Listed from addresses
377         $frominput = &ui_select("from", $from,
378                 $access{'fromname'} ?
379                         [ map { [ "$access{'fromname'} &lt;$_&gt;", $_ ] }
380                               @froms ] :
381                         \@froms);
382         }
383 elsif ($access{'fmode'} == 3) {
384         # Fixed address in fixed domain
385         $frominput = "<tt>$ouser\@$access{'from'}</tt>".
386                      &ui_hidden("from", "$ouser\@$access{'from'}");
387         }
388 print &ui_tabs_start_tabletab("tab", "from");
389 print &ui_table_row($text{'mail_from'}, $frominput, 1, \@tds);
390 print &ui_tabs_end_tabletab();
391
392 # Show To: field
393 print &ui_tabs_start_tabletab("tab", "to");
394 print &ui_table_row($text{'mail_to'}, &ui_address_field("to", $to, 0, 1),
395                     1, \@tds);
396 print &ui_tabs_end_tabletab();
397
398 # Show Cc: field
399 print &ui_tabs_start_tabletab("tab", "cc");
400 print &ui_table_row($text{'mail_cc'}, &ui_address_field("cc", $cc, 0, 1),
401                     1, \@tds);
402 print &ui_tabs_end_tabletab();
403
404 # Show Bcc: field
405 $bcc ||= $config{'bcc_to'};
406 print &ui_tabs_start_tabletab("tab", "bcc");
407 print &ui_table_row($text{'mail_bcc'}, &ui_address_field("bcc", $bcc, 0, 1),
408                     1, \@tds);
409 print &ui_tabs_end_tabletab();
410
411 # Show tab for options
412 print &ui_tabs_start_tabletab("tab", "options");
413 print &ui_table_row($text{'mail_pri'},
414                 &ui_select("pri", "",
415                         [ [ 1, $text{'mail_highest'} ],
416                           [ 2, $text{'mail_high'} ],
417                           [ "", $text{'mail_normal'} ],
418                           [ 4, $text{'mail_low'} ],
419                           [ 5, $text{'mail_lowest'} ] ]), 1, \@tds);
420 print &ui_tabs_end_tabletab();
421 print &ui_tabs_end();
422
423 # Subject field, outside tabs
424 print &ui_table_row($text{'mail_subject'},
425         &ui_textbox("subject", $subject, 40, 0, undef,
426                     "style='width:95%'"), 1, \@tds);
427 print &ui_table_end();
428
429 # Create link for switching to HTML/text mode for new mail
430 @bodylinks = ( );
431 if ($in{'new'}) {
432         if ($html_edit) {
433                 push(@bodylinks, "<a href='reply_mail.cgi?folder=$in{'folder'}&user=$euser&new=1&html=0'>$text{'reply_html0'}</a>");
434                 }
435         else {
436                 push(@bodylinks, "<a href='reply_mail.cgi?folder=$in{'folder'}&user=$euser&new=1&html=1'>$text{'reply_html1'}</a>");
437                 }
438         }
439
440 # Output message body input
441 print &ui_table_start($text{'reply_body'}, "width=100%", 2, undef,
442                       &ui_links_row(\@bodylinks));
443 if ($html_edit) {
444         # Output HTML editor textarea
445         print <<EOF;
446 <script type="text/javascript">
447   _editor_url = "$gconfig{'webprefix'}/$module_name/xinha/";
448   _editor_lang = "en";
449 </script>
450 <script type="text/javascript" src="xinha/XinhaCore.js"></script>
451
452 <script type="text/javascript">
453 xinha_init = function()
454 {
455 xinha_editors = [ "body" ];
456 xinha_plugins = [ ];
457 xinha_config = new Xinha.Config();
458 xinha_config.hideSomeButtons(" print showhelp about killword toggleborders ");
459 xinha_editors = Xinha.makeEditors(xinha_editors, xinha_config, xinha_plugins);
460 Xinha.startEditors(xinha_editors);
461 }
462 </script>
463 EOF
464         print &ui_table_row(undef,
465                 &ui_textarea("body", $quote, 40, 80, undef, 0,
466                              "style='width:99%' id=body"), 2);
467         }
468 else {
469         # Show text editing area
470         $wm = $config{'wrap_mode'};
471         $wm =~ s/^wrap=//g;
472         $wcols = $config{'wrap_compose'};
473         print &ui_table_row(undef,
474                 &ui_textarea("body", $quote, 20,
475                              $wcols || 80,
476                              $wcols ? "hard" : "",
477                              0,
478                              $wcols ? "" : "style='width:100%'"), 2);
479         }
480 if (&has_command("ispell")) {
481         print &ui_table_row(undef,
482               &ui_checkbox("spell", 1, $text{'reply_spell'}, 0), 2);
483         }
484 print &ui_table_end();
485 print &ui_hidden("html_edit", $html_edit);
486
487 # Display forwarded attachments
488 $viewurl = "view_mail.cgi?idx=$in{'idx'}&user=$euser&".
489            "&folder=$folder->{'index'}$subs";
490 $detachurl = "detach.cgi?idx=$in{'idx'}&user=$euser&".
491              "&folder=$folder->{'index'}$subs";
492 $mailurl = "view_mail.cgi?user=$euser&folder=$folder->{'index'}$subs";
493 if (@attach) {
494         &attachments_table(\@attach, $folder, $viewurl, $detachurl,
495                            $mailurl, 'idx', "forward");
496         }
497
498 # Display forwarded mails
499 if (@fwdmail) {
500         &attachments_table(\@fwdmail, $folder, $viewurl, $detachurl,
501                            $mailurl, 'idx');
502         foreach $f (@mailforward) {
503                 print &ui_hidden("mailforward", $f);
504                 }
505         }
506
507 # Display new attachment fields
508 &show_attachments_fields(3, $access{'canattach'});
509
510 print &ui_form_end([ [ undef, $text{'reply_send'} ] ]);
511
512 &mail_page_footer("list_mail.cgi?folder=$in{'folder'}&user=$in{'user'}",
513         $text{'mail_return'},
514         "", $text{'index_return'});
515
516 sub decode_and_sub
517 {
518 return if (!$mail);
519 &parse_mail($mail);
520 @sub = split(/\0/, $in{'sub'});
521 $subs = join("", map { "&sub=$_" } @sub);
522 foreach $s (@sub) {
523         # We are looking at a mail within a mail ..
524         local $amail = &extract_mail(
525                         $mail->{'attach'}->[$s]->{'data'});
526         &parse_mail($amail);
527         $mail = $amail;
528         }
529 }
530