Handle hostnames with upper-case letters
[webmin.git] / mailboxes / mailboxes-lib.pl
1 # mailboxes-lib.pl
2 # Common functions for reading user mailboxes
3
4 BEGIN { push(@INC, ".."); };
5 use WebminCore;
6 &init_config();
7 do "$module_root_directory/boxes-lib.pl";
8 do "$module_root_directory/folders-lib.pl";
9 %access = &get_module_acl();
10 $config{'perpage'} ||= 20;
11 $config{'column_count'} ||= 4;
12 $no_log_file_changes = 1;       # Turn off file change logging for this module
13
14 @mail_system_modules = (
15                          [ undef, 4, \&check_qmail_ldap, \&test_qmail_ldap ],
16                          [ undef, 5, \&check_vpopmail ],
17                          [ "qmailadmin", 2 ],
18                          [ "postfix", 0 ],
19                          [ "sendmail", 1 ],
20                          [undef, 6, \&check_exim ]
21                         );
22
23 @ignore_users_list = &split_quoted_string($config{'ignore_users'});
24
25 # Always detect the mail system if not set
26 if ($config{'mail_system'} == 3) {
27         $config{'mail_system'} = &detect_mail_system();
28         &save_module_config() if ($config{'mail_system'} != 3);
29         }
30
31 # send_mail_program(from, &dests)
32 # Returns the command for injecting email, based on the mail system in use
33 sub send_mail_program
34 {
35 my ($from, $dests) = @_;
36 my $qdests = join(" ", map { quotemeta($_) } @$dests);
37
38 if ($config{'mail_system'} == 1 || $config{'mail_system'} == 0) {
39         # Use sendmail executable, or postfix wrapper
40         local %sconfig = &foreign_check("sendmail") &&
41                          $config{'mail_system'} == 1 ?
42                                 &foreign_config("sendmail") : ( );
43         local $cmd = &has_command($sconfig{'sendmail_path'} || "sendmail");
44         return "$cmd -f".quotemeta($_[0])." ".$qdests if ($cmd);
45         }
46 elsif ($config{'mail_system'} == 2) {
47         # Use qmail injector
48         local %qconfig = &foreign_check("qmailadmin") ?
49                                 &foreign_config("qmailadmin") : ( );
50         local $cmd = ($qconfig{'qmail_dir'} || "/var/qmail").
51                      "/bin/qmail-inject";
52         return $cmd if (-x $cmd);
53         }
54 else {
55         # Fallback - use sendmail command
56         local $cmd = &has_command("sendmail");
57         return "$cmd -f".quotemeta($_[0])." ".$qdests if ($cmd);
58         }
59 return undef;
60 }
61
62 # list_folders()
63 # Returns a list of all mailboxes for all users
64 sub list_folders
65 {
66 local (@rv, $uinfo);
67 foreach $uinfo (&list_mail_users()) {
68         push(@rv, &list_user_folders(@$uinfo));
69         }
70 return @rv;
71 }
72
73 # list_user_folders(user, [other info])
74 # Returns a list of folders for mailboxes belonging to some user
75 sub list_user_folders
76 {
77 if ($_[0] =~ /^\//) {
78         # A path .. return a folder just for it
79         return ( { 'name' => $_[0],
80                    'file' => $_[0],
81                    'type' => &folder_type($_[0]),
82                    'mode' => 1,
83                    'user' => $_[0],
84                    'index' => 0 } );
85         }
86 else {
87         # Getting folders for a user
88         local @uinfo = @_ > 1 ? @_ : &get_mail_user($_[0]);
89         return ( ) if (!@uinfo);
90         local ($dir, $style, $mailbox, $maildir) = &get_mail_style();
91         local @rv;
92
93         # Check for user-specified mail store
94         if ($uinfo[10]) {
95                 push(@rv, { 'name' => $uinfo[10],
96                             'file' => $uinfo[10],
97                             'type' => &folder_type($uinfo[10]),
98                             'mode' => 0,
99                             'index' => scalar(@rv) } );
100                 }
101
102         # Check for /var/mail/USERNAME file
103         if ($dir) {
104                 local $mf = &mail_file_style($uinfo[0], $dir, $style);
105                 push(@rv, { 'type' => 0,
106                             'name' => $mf,
107                             'file' => $mf,
108                             'user' => $uinfo[0],
109                             'index' => scalar(@rv) });
110                 }
111
112         # Check for file in home dir
113         if ($mailbox) {
114                 local $mf = "$uinfo[7]/$mailbox";
115                 if (-r $mf || !@rv) {
116                         push(@rv, { 'type' => 0,
117                                     'name' => "~$uinfo[0]/$mailbox",
118                                     'file' => $mf,
119                                     'user' => $uinfo[0],
120                                     'index' => scalar(@rv) });
121                         }
122                 }
123
124         # Check for directory in home dir
125         if ($maildir) {
126                 local $mf = "$uinfo[7]/$maildir";
127                 if (-d $mf || !@rv) {
128                         push(@rv, { 'type' => 1,
129                                     'name' => "~$uinfo[0]/$maildir/",
130                                     'file' => $mf,
131                                     'user' => $uinfo[0],
132                                     'index' => scalar(@rv) });
133                         }
134                 }
135
136         # Add any ~/mail files
137         if ($config{'mail_usermin'}) {
138                 local $folders_dir = "$uinfo[7]/$config{'mail_usermin'}";
139                 foreach $p (&recursive_files($folders_dir, 1)) {
140                         local $f = $p;
141                         $f =~ s/^\Q$folders_dir\E\///;
142                         push(@rv, { 'file' => $p,
143                                     'name' => "~$uinfo[0]/$config{'mail_usermin'}/$f",
144                                     'type' => &folder_type($p),
145                                     'mode' => 0,
146                                     'sent' => $f eq "sentmail",
147                                     'index' => scalar(@rv) } );
148
149                         # Work out if this is a spam folder
150                         if (lc($f) eq "spam" || lc($f) eq ".spam") {
151                                 # Has spam in the name
152                                 $rv[$#rv]->{'spam'} = 1;
153                                 }
154                         elsif (&foreign_check("virtual-server")) {
155                                 # Check if Virtualmin is defaulting to it
156                                 local %vconfig = &foreign_config(
157                                         "virtual-server");
158                                 local $sf = $vconfig{'spam_delivery'};
159                                 if ($sf) {
160                                         $sf =~ s/\$HOME/$uinfo[7]/g;
161                                         $sf =~ s/\~/$uinfo[7]/g;
162                                         if ($sf !~ /^\//) {
163                                                 $sf = $uinfo[7]."/".$sf;
164                                                 }
165                                         $sf =~ s/\/$//;
166                                         if ($p eq $sf) {
167                                                 $rv[$#rv]->{'spam'} = 1;
168                                                 }
169                                         }
170                                 }
171                         }
172                 }
173
174         # Add any Usermin external mail files
175         if ($config{'mailbox_user'}) {
176                 local %userconfig;
177                 &read_file_cached("$uinfo[7]/$config{'mailbox_user'}/config",
178                                   \%userconfig);
179                 local $o;
180                 foreach $o (split(/\t+/, $userconfig{'mailboxes'})) {
181                         $o =~ /\/([^\/]+)$/ || next;
182                         push(@rv, { 'name' => $o,
183                                     'file' => $o,
184                                     'type' => &folder_type($o),
185                                     'mode' => 1,
186                                     'index' => scalar(@rv) } );
187                         }
188                 }
189
190         foreach my $f (@rv) {
191                 $f->{'user'} = $_[0];
192                 }
193         return @rv;
194         }
195 }
196
197 sub list_user_folders_sorted
198 {
199 return &list_user_folders(@_);
200 }
201
202 # get_mail_style()
203 # Returns a list containing the mail base directory, directory style,
204 # mail file in home dir, and maildir in home dir
205 sub get_mail_style
206 {
207 if (!scalar(@mail_style_cache)) {
208         if ($config{'auto'}) {
209                 # Based on mail server
210                 if ($config{'mail_system'} == 1) {
211                         # Can get paths from Sendmail module config
212                         local %sconfig = &foreign_config("sendmail");
213                         if ($sconfig{'mail_dir'}) {
214                                 # File under /var/mail
215                                 return ($sconfig{'mail_dir'},
216                                         $sconfig{'mail_style'}, undef, undef);
217                                 }
218                         elsif ($sconfig{'mail_type'}) {
219                                 # Maildir under home directory
220                                 return (undef, $sconfig{'mail_style'},
221                                         undef, $sconfig{'mail_file'});
222                                 }
223                         else {
224                                 # mbox under home directory
225                                 return (undef, $sconfig{'mail_style'},
226                                         $sconfig{'mail_file'}, undef);
227                                 }
228                         }
229                 elsif ($config{'mail_system'} == 0) {
230                         # Need to query Postfix module for paths
231                         &foreign_require("postfix", "postfix-lib.pl");
232                         local @s = &postfix::postfix_mail_system();
233                         if ($s[0] == 0) {
234                                 return ($s[1], 0, undef, undef);
235                                 }
236                         elsif ($s[0] == 1) {
237                                 return (undef, 0, $s[1], undef);
238                                 }
239                         elsif ($s[0] == 2) {
240                                 return (undef, 0, undef, $s[1]);
241                                 }
242                         }
243                 elsif ($config{'mail_system'} == 2 ||
244                        $config{'mail_system'} == 4) {
245                         # Need to check qmail module config for paths
246                         local %qconfig = &foreign_config("qmailadmin");
247                         if ($qconfig{'mail_system'} == 1) {
248                                 return (undef, 0, undef,
249                                         $qconfig{'mail_dir_qmail'});
250                                 }
251                         elsif ($qconfig{'mail_dir'}) {
252                                 return ($qconfig{'mail_dir'}, $qconfig{'mail_style'}, undef, undef);
253                                 }
254                         else {
255                                 return (undef, $qconfig{'mail_style'}, $qconfig{'mail_file'}, undef);
256                                 }
257                         }
258                 elsif ($config{'mail_system'} == 5) {
259                         # vpopmail always uses ~/Maildir
260                         return ( undef, 0, undef, "Maildir" );
261                         }
262                 elsif ($config{'mail_system'} == 6) {
263                         # localmail using smail or exim
264                         return ( "/var/spool/mail", 0, undef, undef );
265                         }
266                 else {
267                         # No mail server set yet!
268                         return (undef, undef, undef, undef);
269                         }
270                 }
271         else {
272                 # Use config settings
273                 @mail_style_cache = ($config{'mail_dir'}, $config{'mail_style'},
274                                      $config{'mail_file'}, $config{'mail_sub'});
275                 }
276         }
277 return @mail_style_cache;
278 }
279
280 # can_user(username, [other details])
281 sub can_user
282 {
283 if (!&is_user($_[0])) {
284         # For external files, check if the file is under an allowed
285         # directory, or owned by an allowed user.
286         local @st = stat($_[0]);
287         local @uinfo = &get_mail_uid($st[4]);
288         return 1 if (@uinfo && &can_user(@uinfo));
289         local $dir = &allowed_directory();
290         return defined($dir) && &is_under_directory($dir, $_[0]);
291         }
292 local @u = @_ > 1 ? @_ : &get_mail_user($_[0]);
293 return 1 if ($_[0] && $access{'sent'} eq $_[0]);
294 return 1 if ($access{'mmode'} == 1);
295 return 0 if (!@u);
296 return 0 if ($_[0] =~ /\.\./);
297 return 0 if ($access{'mmode'} == 0);
298 local $u;
299 if ($access{'mmode'} == 2) {
300         # Is user in list of users?
301         foreach $u (split(/\s+/, $access{'musers'})) {
302                 return 1 if ($u eq $_[0]);
303                 }
304         return 0;
305         }
306 elsif ($access{'mmode'} == 4) {
307         # Is user the current Webmin user?
308         return 1 if ($_[0] eq $remote_user);
309         }
310 elsif ($access{'mmode'} == 5) {
311         # Is the user's gid in the list of groups?
312         local $gid;
313         foreach $gid (split(/\s+/, $access{'musers'})) {
314                 return 1 if ($u[3] == $gid);
315                 if ($access{'msec'}) {
316                         # Check user's secondary groups too
317                         local @ginfo = getgrgid($gid);
318                         local @m = split(/\s+/, $ginfo[3]);
319                         return 1 if (&indexof($_[0], @m) >= 0);
320                         }
321                 }
322         }
323 elsif ($access{'mmode'} == 3) {
324         # Is the user not in the list of denied users
325         foreach $u (split(/\s+/, $access{'musers'})) {
326                 return 0 if ($u eq $_[0]);
327                 }
328         return 1;
329         }
330 elsif ($access{'mmode'} == 6) {
331         # Does the user match a regexp?
332         return ($_[0] =~ /^$access{'musers'}$/);
333         }
334 elsif ($access{'mmode'} == 7) {
335         # Is the user's UID within the allowed range?
336         return (!$access{'musers'} || $u[2] >= $access{'musers'}) &&
337                (!$access{'musers2'} || $u[2] <= $access{'musers2'});
338         }
339 return 0;       # can't happen!
340 }
341
342 # movecopy_user_select(number, folders, folder, form-no)
343 # Returns HTML for entering a username to copy mail to
344 sub movecopy_user_select
345 {
346 local $rv;
347 $rv .= "<input type=submit name=move$_[0] value=\"$text{'mail_move'}\" ".
348        "onClick='return check_clicks(form)'>";
349 $rv .= "<input type=submit name=copy$_[0] value=\"$text{'mail_copy'}\" ".
350        "onClick='return check_clicks(form)'>";
351 $rv .= &ui_user_textbox("mfolder$_[0]", undef, $_[3]);
352 return $rv;
353 }
354
355 # need_delete_warn(&folder)
356 sub need_delete_warn
357 {
358 return 1 if ($config{'delete_warn'} eq 'y');
359 return 0 if ($config{'delete_warn'} eq 'n');
360 local $mf;
361 return $_[0]->{'type'} == 0 &&
362        ($mf = &folder_file($_[0])) &&
363        &disk_usage_kb($mf)*1024 > $config{'delete_warn'};
364 }
365
366 # detect_mail_system()
367 # Works out which mail server is installed
368 sub detect_mail_system
369 {
370 foreach my $m (@mail_system_modules) {
371         return $m->[1] if (&check_mail_system($m));
372         }
373 return 3;
374 }
375
376 # check_mail_system(&mailsystem)
377 sub check_mail_system
378 {
379 if ($_[0]->[0]) {
380         # Just check module
381         return &foreign_installed($_[0]->[0]);
382         }
383 else {
384         # Call function
385         local $func = $_[0]->[2];
386         return &$func($_[0]);
387         }
388 }
389
390 # test_mail_system([&mailsystem])
391 # Returns an error message if the mail system is invalid
392 sub test_mail_system
393 {
394 local $ms;
395 if (!$ms) {
396         ($ms) = grep { $_->[1] == $config{'mail_system'} } @mail_system_modules;
397         }
398 if ($ms->[3]) {
399         local $func = $ms->[3];
400         return &$func();
401         }
402 return undef;
403 }
404
405 # check_qmail_ldap()
406 # Make sure Qmail with LDAP extensions is installed
407 sub check_qmail_ldap
408 {
409 return 0 if (&foreign_installed("qmailadmin", 1) != 2);
410 local %qconfig = &foreign_config("qmailadmin");
411 return 0 if (!-r "$qconfig{'qmail_dir'}/control/ldapserver");
412 return 1;
413 }
414
415 # check_vpopmail()
416 # Make sure Qmail with VPopMail extensions is installed
417 sub check_vpopmail
418 {
419 return 0 if (&foreign_installed("qmailadmin", 1) != 2);
420 return -x "$config{'vpopmail_dir'}/bin/vadddomain";
421 }
422
423 # check_exim()
424 # Make sure Exim is installed
425 sub check_exim
426 {
427 return &has_command('exim');
428 }
429
430
431 # test_qmail_ldap()
432 # Returns undef the Qmail+LDAP database can be contacted OK, or an error message
433 sub test_qmail_ldap
434 {
435 $config{'ldap_host'} || return $text{'ldap_ehost'};
436 $config{'ldap_port'} =~ /^\d+$/ || return $text{'ldap_eport'};
437 $config{'ldap_login'} || return $text{'ldap_euser'};
438 $config{'ldap_base'} || return $text{'ldap_ebase'};
439 local $err = &connect_qmail_ldap(1);
440 return ref($err) ? undef : $err;
441 }
442
443 # show_users_table(&users-list, [only-with-mail])
444 # Outputs HTML for a table of users, with the appropriate sorting and mode
445 sub show_users_table
446 {
447 my @users = @{$_[0]};
448 my ($u, %size, %incount, %sentcount, %foldercount);
449 if ($config{'sort_mode'} == 2 || $config{'show_size'} > 0 || $_[1] ||
450     $config{'show_count'} || $config{'show_sent'}) {
451         # Need to check folders
452         foreach $u (@users) {
453                 next if ($config{'ignore_users_enabled'} == 1 &&
454                          &indexof($u->[0], @ignore_users_list) >= 0);
455                 local @folders = &list_user_folders(@$u);
456                 $foldercount{$u->[0]} = scalar(@folders);
457                 if ($config{'sort_mode'} == 2 ||
458                     $config{'show_size'} > 0 || $_[1]) {
459                         # Compute size of folders
460                         $size{$u->[0]} = $config{'size_mode'} ?
461                                                 &folder_size(@folders) :
462                                                 &folder_size($folders[0]);
463                         }
464                 if ($config{'show_count'}) {
465                         # Get number of mails in inbox
466                         $incount{$u->[0]} = &mailbox_folder_size($folders[0]);
467                         }
468                 if ($config{'show_sent'}) {
469                         # Count number of messages in sent mail
470                         local ($sent) = grep { $_->{'sent'} } @folders;
471                         $sentcount{$u->[0]} = &mailbox_folder_size($sent)
472                                 if ($sent);
473                         }
474                 }
475         }
476
477 # Sort by chosen mode
478 if ($config{'sort_mode'} == 2) {
479         @users = sort { $size{$b->[0]} <=> $size{$a->[0]} } @users;
480         }
481 elsif ($config{'sort_mode'} == 1) {
482         @users = sort { lc($a->[0]) cmp lc($b->[0]) } @users;
483         }
484
485 local @allusers = @users;
486 if ($_[1]) {
487         # Limit to those with mail
488         @users = grep { $size{$_->[0]} } @users;
489         }
490
491 # Show table of users
492 if (!@allusers) {
493         print "<b>$text{'index_nousers'}</b><p>\n";
494         }
495 elsif (!@users) {
496         print "<b>$text{'index_nousersmail'}</b><p>\n";
497         }
498 elsif ($config{'show_size'} == 2) {
499         # Show full user details
500         local %uconfig = &foreign_config("useradmin");
501         local @ccols;
502         push(@ccols, $text{'find_incount'}) if ($config{'show_count'});
503         push(@ccols, $text{'find_sentcount'}) if ($config{'show_sent'});
504         push(@ccols, $text{'find_fcount'}) if (%foldercount);
505         print &ui_columns_start( [ $text{'find_user'}, $text{'find_real'},
506                                    $text{'find_group'},
507                                    $text{'find_size'}, @ccols ], 100);
508         foreach $u (@users) {
509                 local $g = getgrgid($u->[3]);
510                 next if ($config{'ignore_users_enabled'} == 1 &&
511                          &indexof($u->[0], @ignore_users_list) >= 0);
512                 $u->[6] =~ s/,.*$// if ($uconfig{'extra_real'} ||
513                                         $u->[6] =~ /,$/);
514                 local $home = $u->[7];
515                 if (length($home) > 30) {
516                         $home = "...".substr($home, -30);
517                         }
518                 local @ccols;
519                 if ($config{'show_count'}) {
520                         push(@ccols, int($incount{$u->[0]}))
521                         }
522                 if ($config{'show_sent'}) {
523                         push(@ccols, int($sentcount{$u->[0]}))
524                         }
525                 if (%foldercount) {
526                         push(@ccols, int($foldercount{$u->[0]}))
527                         }
528                 print &ui_columns_row(
529                         [ "<a href='list_mail.cgi?user=$u->[0]'>$u->[0]</a>",
530                           $u->[6], $g,
531                           $size{$u->[0]} == 0 ? $text{'index_empty'} :
532                                 &nice_size($size{$u->[0]}),
533                           @ccols ],
534                         [ undef, undef, undef, undef, "nowrap" ]);
535                 }
536         print &ui_columns_end();
537         }
538 else {
539         # Just showing username (and maybe size)
540         my @grid;
541         foreach $u (@users) {
542                 next if ($config{'ignore_users_enabled'} == 1 &&
543                          &indexof($u->[0], @ignore_users_list) >= 0);
544                 my $g = "<a href='list_mail.cgi?user=".&urlize($u->[0])."'>";
545                 $g .= &html_escape($u->[0]);
546                 if ($config{'show_size'} == 1) {
547                         local @folders = &list_user_folders(@$u);
548                         local $total = &folder_size(@folders);
549                         if ($size{$u->[0]} > 0) {
550                                 $g .= $config{'show_size_below'} ? '<br>' : ' ';
551                                 $g .= "(";
552                                 if (%foldercount) {
553                                         $g .= &text('find_in',
554                                                 &nice_size($size{$u->[0]}),
555                                                 $foldercount{$u->[0]});
556                                         }
557                                 else {
558                                         $g .= &nice_size($size{$u->[0]});
559                                         }
560                                 $g .= ")";
561                                 }
562                         }
563                 $g .= "</a>";
564                 push(@grid, $g);
565                 }
566         my $w = int(100/$config{'column_count'});
567         my @tds = map { "width=".int($w)."%" } (1..$config{'column_count'});
568         print &ui_grid_table(\@grid, $config{'column_count'}, 100, \@tds, undef,
569                              $text{'index_header'});
570         }
571 }
572
573 # switch_to_user(user)
574 # Switch to the Unix user that files are accessed as.
575 sub switch_to_user
576 {
577 if (!defined($old_uid)) {
578         local @uinfo = &get_mail_user($_[0]);
579         $old_uid = $>;
580         $old_gid = $);
581         $) = "$uinfo[3] $uinfo[3]";
582         $> = $uinfo[2];
583         }
584 }
585
586 sub switch_user_back
587 {
588 if (defined($old_uid)) {
589         $> = $old_uid;
590         $) = $old_gid;
591         $old_uid = $old_gid = undef;
592         }
593 }
594
595 sub folder_link
596 {
597 return "<a href='list_mail.cgi?user=$_[0]&folder=$_[1]->{'index'}'>$text{'mail_return2'}</a>";
598 }
599
600 # get_from_address()
601 # Returns the address to use when sending email from a script
602 sub get_from_address
603 {
604 local $host = &get_from_domain();
605 if ($config{'webmin_from'} =~ /\@/) {
606         return $config{'webmin_from'};
607         }
608 elsif (!$config{'webmin_from'}) {
609         return "webmin\@$host";
610         }
611 else {
612         return "$config{'webmin_from'}\@$host";
613         }
614 }
615
616 # get_from_domain()
617 # Returns the default domain for From: addresses
618 sub get_from_domain
619 {
620 return $config{'from_dom'} || &get_display_hostname();
621 }
622
623 # get_user_from_address(&uinfo)
624 # Returns the default From: address for mail sent from some user's mailbox
625 sub get_user_from_address
626 {
627 local $uinfo = $_[0];
628 if ($config{'from_addr'}) {
629         return $config{'from_addr'};
630         }
631 elsif ($uinfo->[11]) {
632         return $uinfo->[11];
633         }
634 elsif ($config{'from_virtualmin'} && &foreign_check("virtual-server")) {
635         # Does Virtualmin manage this user?
636         &foreign_require("virtual-server", "virtual-server-lib.pl");
637         local $d;
638         foreach $d (&virtual_server::list_domains()) {
639                 local @users = &virtual_server::list_domain_users($d, 0, 0, 1);
640                 local $u;
641                 foreach $u (@users) {
642                         if ($u->{'user'} eq $uinfo->[0] && $u->{'email'}) {
643                                 # Found him!
644                                 return $u->{'email'};
645                                 }
646                         }
647                 }
648         }
649 if ($uinfo->[0] =~ /\@/) {
650         return $uinfo->[0];
651         }
652 else {
653         return $uinfo->[0].'@'.&get_from_domain();
654         }
655 }
656
657 # check_modification(&folder)
658 # Display an error message if a folder has been modified since the time
659 # in $in{'mod'}
660 sub check_modification
661 {
662 local $newmod = &modification_time($_[0]);
663 if ($in{'mod'} && $in{'mod'} != $newmod && $config{'check_mod'}) {
664         # Changed!
665         &error(&text('emodified', "list_mail.cgi?user=$in{'user'}&folder=$_[0]->{'index'}"));
666         }
667 }
668
669 # spam_report_cmd(user)
670 # Returns a command for reporting spam, or undef if none
671 sub spam_report_cmd
672 {
673 local ($user) = @_;
674 local %sconfig = &foreign_config("spam");
675 local $cmd;
676 if ($config{'spam_report'} eq 'sa_learn') {
677         $cmd = &has_command($sconfig{'sa_learn'}) ? "$sconfig{'sa_learn'} --spam --mbox" : undef;
678         }
679 elsif ($config{'spam_report'} eq 'spamassassin') {
680         $cmd = &has_command($sconfig{'spamassassin'}) ? "$sconfig{'spamassassin'} --r" : undef;
681         }
682 else {
683         $cmd = &has_command($sconfig{'sa_learn'}) ?
684                 "$sconfig{'sa_learn'} --spam --mbox" :
685                &has_command($sconfig{'spamassassin'}) ?
686                 "$sconfig{'spamassassin'} --r" : undef;
687         }
688 return $user eq "root" ? $cmd :
689        $cmd ? &command_as_user($user, 0, $cmd) : undef;
690 }
691
692 # ham_report_cmd()
693 # Returns a command for reporting ham, or undef if none
694 sub ham_report_cmd
695 {
696 local %sconfig = &foreign_config("spam");
697 return &has_command($sconfig{'sa_learn'}) ? "$sconfig{'sa_learn'} --ham --mbox" : undef;
698 }
699
700 # allowed_directory()
701 # Returns base directory for mail files, or undef if not allowed
702 sub allowed_directory
703 {
704 if ($access{'dir'}) {
705         return $access{'dir'};
706         }
707 else {
708         return $access{'mmode'} == 1 ? "/" : undef;
709         }
710 }
711
712 # is_user(user)
713 sub is_user
714 {
715 return $_[0] !~ /^\//;
716 }
717
718 # list_mail_users([max], [filterfunc])
719 # Returns getpw* style structures for all users who can recieve mail. Those with
720 # duplicate info are skipped.
721 sub list_mail_users
722 {
723 local ($max, $filter) = @_;
724 local @rv;
725
726 if ($config{'mail_system'} < 3 or $config{'mail_system'} == 6 ) {
727         # Postfix, Sendmail, smail, exim and Qmail all use Unix users
728         local %found;
729         local %hfound;
730         local ($dir, $style, $mailbox, $maildir) = &get_mail_style();
731         setpwent();
732         while(my (@uinfo) = getpwent()) {
733                 if ($found{$uinfo[0]}++) {
734                         # done this username already
735                         next;
736                         }
737                 if (!$dir && $hfound{$uinfo[7]}++) {
738                         # done this home directory (and thus inbox) already
739                         next;
740                         }
741                 if ($dir && $uinfo[0] =~ /\@/) {
742                         # If this is a user@domain username, mark user-domain
743                         # as done already, to avoid showing dupes
744                         local $noat = $uinfo[0];
745                         $noat =~ s/\@/-/g;
746                         $found{$noat}++;
747                         }
748                 next if ($filter && !&$filter(@uinfo));
749                 push(@rv, \@uinfo);
750                 last if ($max && @rv > $max);
751                 }
752         endpwent();
753         }
754 elsif ($config{'mail_system'} == 4) {
755         # Qmail+LDAP uses an LDAP db
756         local $ldap = &connect_qmail_ldap();
757         local $rv = $ldap->search(base => $config{'ldap_base'},
758                           filter => "(objectClass=qmailUser)");
759         &error($rv->error) if ($rv->code);
760         local $u;
761         foreach $u ($rv->all_entries) {
762                 local @uinfo = &qmail_dn_to_user($u);
763                 next if (!$uinfo[10]);  # alias only
764                 next if ($filter && !&$filter(@uinfo));
765                 push(@rv, \@uinfo);
766                 last if ($max && @rv > $max);
767                 }
768         $ldap->unbind();
769         }
770 elsif ($config{'mail_system'} == 5) {
771         # Get vpopmail user list for all domains
772         opendir(DOMS, "$config{'vpopmail_dir'}/domains");
773         foreach my $d (readdir(DOMS)) {
774                 next if ($d =~ /^\./);
775                 local @uinfos = &parse_vpopmail_users("-D $d", $d);
776                 if ($filter) {
777                         @uinfos = grep { &$filter(@$_) } @uinfos;
778                         }
779                 push(@rv, @uinfos);
780                 last if ($max && @rv > $max);
781                 }
782         closedir(DOMS);
783         }
784 return @rv;
785 }
786
787 # parse_vpopmail_users(command, domain)
788 sub parse_vpopmail_users
789 {
790 local %attr_map = ( "passwd" => 1,
791                     "gecos" => 5,
792                     "dir" => 7 );
793 local (@rv, $user);
794 open(UINFO, "$config{'vpopmail_dir'}/bin/vuserinfo $_[0] |");
795 while(<UINFO>) {
796         s/\r|\n//g;
797         if (/^([^:]+):\s+(.*)$/) {
798                 local ($attr, $value) = ($1, $2);
799                 if ($attr eq "name") {
800                         # Start of a new user
801                         $user = [ "$value\@$_[1]" ];
802                         $user->[11] = $user->[0];
803                         push(@rv, $user);
804                         }
805                 local $amapped = $attr_map{$attr};
806                 $user->[$amapped] = $value if ($amapped);
807                 }
808         }
809 close(UINFO);
810 return @rv;
811 }
812
813 # get_mail_user(name)
814 # Looks up a user by name
815 sub get_mail_user
816 {
817 if ($config{'mail_system'} < 3 || $config{'mail_system'} == 6) {
818         # Just find Unix user for Sendmail, Postfix, Qmail and Exim
819         return getpwnam($_[0]);
820         }
821 elsif ($config{'mail_system'} == 4) {
822         # Lookup in LDAP DB
823         local $ldap = &connect_qmail_ldap();
824         local $rv = $ldap->search(base => $config{'ldap_base'},
825                           filter => "(&(objectClass=qmailUser)(uid=$_[0]))");
826         &error($rv->error) if ($rv->code);
827         local ($u) = $rv->all_entries;
828         if ($u) {
829                 # Found in LDAP
830                 local @uinfo = &qmail_dn_to_user($u);
831                 $ldap->unbind();
832                 return @uinfo;
833                 }
834         else {
835                 # Fall back to Unix user
836                 return getpwnam($_[0]);
837                 }
838         }
839 elsif ($config{'mail_system'} == 5) {
840         # Find in vpopmail
841         local ($box, $dom) = split(/\@/, $_[0]);
842         local @users = &parse_vpopmail_users($_[0], $dom);
843         return @{$users[0]};
844         }
845 }
846
847 # get_mail_uid(name)
848 # Looks up a user by UID
849 sub get_mail_uid
850 {
851 if ($config{'mail_system'} < 3) {
852         # Just find Unix user
853         return getpwuid($_[0]);
854         }
855 elsif ($config{'mail_system'} == 4) {
856         # Lookup in LDAP DB
857         local $ldap = &connect_qmail_ldap();
858         local $rv = $ldap->search(base => $config{'ldap_base'},
859                   filter => "(&(objectClass=qmailUser)(uidNumber=$_[0]))");
860         &error($rv->error) if ($rv->code);
861         local ($u) = $rv->all_entries;
862         local @uinfo = &qmail_dn_to_user($u);
863         $ldap->unbind();
864         return @uinfo;
865         }
866 elsif ($config{'mail_system'} == 5) {
867         # Find in vpopmail
868         return ( );     # not possible, since UIDs aren't used!
869         }
870 }
871
872 # connect_qmail_ldap([return-error])
873 # Connect to the LDAP server used for Qmail. Returns an LDAP handle on success,
874 # or an error message on failure.
875 sub connect_qmail_ldap
876 {
877 eval "use Net::LDAP";
878 if ($@) {
879         local $err = &text('ldap_emod', "<tt>Net::LDAP</tt>");
880         if ($_[0]) { return $err; }
881         else { &error($err); }
882         }
883
884 # Connect to server
885 local $port = $config{'ldap_port'} || 389;
886 local $ldap = Net::LDAP->new($config{'ldap_host'}, port => $port);
887 if (!$ldap) {
888         local $err = &text('ldap_econn',
889                            "<tt>$config{'ldap_host'}</tt>","<tt>$port</tt>");
890         if ($_[0]) { return $err; }
891         else { &error($err); }
892         }
893
894 # Start TLS if configured
895 if ($config{'ldap_tls'}) {
896         $ldap->start_tls();
897         }
898
899 # Login
900 local $mesg;
901 if ($config{'ldap_login'}) {
902         $mesg = $ldap->bind(dn => $config{'ldap_login'},
903                             password => $config{'ldap_pass'});
904         }
905 else {
906         $mesg = $ldap->bind(anonymous => 1);
907         }
908 if (!$mesg || $mesg->code) {
909         local $err = &text('ldap_elogin', "<tt>$config{'ldap_host'}</tt>",
910                      $dn, $mesg ? $mesg->error : "Unknown error");
911         if ($_[0]) { return $err; }
912         else { &error($err); }
913         }
914 return $ldap;
915 }
916
917 # qmail_dn_to_user(&dn)
918 sub qmail_dn_to_user
919 {
920 local $mms = &add_ldapmessagestore(
921         scalar($_[0]->get_value("mailMessageStore")));
922 if (-d "$mms/Maildir") {
923         $mms .= "/" if ($mms !~ /\/$/);
924         $mms .= "Maildir";
925         }
926 return ( scalar($_[0]->get_value("uid")),
927          scalar($_[0]->get_value("userPassword")),
928          scalar($_[0]->get_value("uidNumber")),
929          scalar($_[0]->get_value("gidNumber")),
930          scalar($_[0]->get_value("mailQuotaSize")),
931          scalar($_[0]->get_value("cn")),
932          scalar($_[0]->get_value("cn")),
933          scalar($_[0]->get_value("homeDirectory")),
934          scalar($_[0]->get_value("loginShell")),
935          undef,
936          $mms,
937          scalar($_[0]->get_value("mail")),
938          );
939 }
940
941 # add_ldapmessagestore(path)
942 sub add_ldapmessagestore
943 {
944 if (!$_[0]) {
945         return $_[0];
946         }
947 elsif ($_[0] =~ /^\//) {
948         return $_[0];
949         }
950 else {
951         &foreign_require("qmailadmin", "qmail-lib.pl");
952         local $pfx = &qmailadmin::get_control_file("ldapmessagestore");
953         return $pfx."/".$_[0];
954         }
955 }
956
957 # show_buttons(number, &folders, current-folder, &mail, user, search-mode)
958 sub show_buttons
959 {
960 local ($num, $folders, $folder, $mail, $user, $search) = @_;
961 local $uuser = &urlize($user);
962 local $spacer = "&nbsp;\n";
963 if (@$mail) {
964         # Delete
965         print "<input type=submit name=delete value=\"$text{'mail_delete'}\" ",
966               "onClick='return check_clicks(form)'>";
967         if ($config{'show_delall'} && !$search) {
968                 print "<input type=submit name=deleteall value=\"$text{'mail_deleteall'}\">";
969                 }
970         print $spacer;
971
972         # Mark as
973         print "<input type=submit name=mark$_[0] value=\"$text{'mail_mark'}\">";
974         print "<select name=mode$_[0]>\n";
975         print "<option value=1 checked>$text{'mail_mark1'}\n";
976         print "<option value=0>$text{'mail_mark0'}\n";
977         print "<option value=2>$text{'mail_mark2'}\n";
978         print "</select>";
979         print $spacer;
980
981         if (&is_user($user)) {
982                 # Forward
983                 if ($config{'open_mode'}) {
984                         # Forward messages in a separate window
985                         print "<input type=submit name=forward value=\"$text{'mail_forward'}\" onClick='args = \"user=$uuser&folder=$folder->{'index'}\"; for(i=0; i<form.d.length; i++) { if (form.d[i].checked) { args += \"&mailforward=\"+form.d[i].value; } } window.open(\"reply_mail.cgi?\"+args, \"compose\", \"toolbar=no,menubar=no,scrollbars=yes,width=1024,height=768\"); return false'>";
986
987                         }
988                 else {
989                         print "<input type=submit name=forward value=\"$text{'mail_forward'}\">";
990                         }
991                 print $spacer;
992                 }
993
994         # Move/copy
995         print &movecopy_user_select($_[0], $folders, $folder, 1);
996         print $spacer;
997
998         # Show spam report buttons
999         if (!$folder->{'spam'} &&
1000             &foreign_installed("spam") &&
1001             $config{'spam_buttons'} =~ /list/ &&
1002             &spam_report_cmd($user)) {
1003                 if (&foreign_available("spam")) {
1004                         print "<input type=submit value=\"$text{'mail_black'}\" name=black>";
1005                         }
1006                 if ($config{'spam_del'}) {
1007                         print "<input type=submit value=\"$text{'view_razordel'}\" name=razor>\n";
1008                         }
1009                 else {
1010                         print "<input type=submit value=\"$text{'view_razor'}\" name=razor>";
1011                         }
1012                 print $spacer;
1013                 }
1014         elsif ($folder->{'spam'} &&
1015                &foreign_installed("spam") &&
1016                $config{'spam_buttons'} =~ /list/ &&
1017                &ham_report_cmd($user)) {
1018                 if (&foreign_available("spam")) {
1019                         print "<input type=submit value=\"$text{'mail_white'}\" name=white>";
1020                         }
1021                 print "<input type=submit value=\"$text{'view_ham'}\" name=ham>";
1022                 print $spacer;
1023                 }
1024         }
1025
1026 if ($config{'open_mode'}) {
1027         # Show mass open button
1028         print "<input type=submit name=new value=\"$text{'mail_open'}\" onClick='for(i=0; i<form.d.length; i++) { if (form.d[i].checked) { window.open(\"view_mail.cgi?user=$uuser&folder=$folder->{'index'}&idx=\"+form.d[i].value, \"view\"+i, \"toolbar=no,menubar=no,scrollbars=yes,width=1024,height=768\"); } }
1029  return false'>";
1030         print $spacer;
1031         }
1032
1033 # Compose
1034 if (&is_user($user)) {
1035         if ($config{'open_mode'}) {
1036                 # In a separate window
1037                 print "<input type=submit name=new value=\"$text{'mail_compose'}\" onClick='window.open(\"reply_mail.cgi?user=$user&new=1\", \"compose\", \"toolbar=no,menubar=no,scrollbars=yes,width=1024,height=768\"); return false'>";
1038                 }
1039         else {
1040                 print "<input type=submit name=new value=\"$text{'mail_compose'}\">";
1041                 }
1042         }
1043 print "<br>\n";
1044 }
1045
1046 # get_signature(user)
1047 # Returns the users signature, if any
1048 sub get_signature
1049 {
1050 local $sf = &get_signature_file($_[0]);
1051 $sf || return undef;
1052 local $sig;
1053 open(SIG, $sf) || return undef;
1054 while(<SIG>) {
1055         $sig .= $_;
1056         }
1057 close(SIG);
1058 return $sig;
1059 }
1060
1061 # get_signature_file(user)
1062 # Returns the full path to the file that should contain the user's signature,
1063 # or undef if none is defined
1064 sub get_signature_file
1065 {
1066 return undef if ($config{'sig_file'} eq '*' || $config{'sig_file'} eq '');
1067 local $sf = $config{'sig_file'};
1068 if ($sf !~ /^\//) {
1069         local @uinfo = getpwnam($_[0]);
1070         $sf = "$uinfo[7]/$sf";
1071         }
1072 return $sf;
1073 }
1074
1075 # view_mail_link(user, &folder, index, from-to-text)
1076 sub view_mail_link
1077 {
1078 local ($user, $folder, $idx, $txt) = @_;
1079 local $uuser = &urlize($user);
1080 local $url = "view_mail.cgi?user=$uuser&idx=$idx&folder=$folder->{'index'}";
1081 if ($config{'open_mode'}) {
1082         return "<a href='' onClick='window.open(\"$url\", \"viewmail\", \"toolbar=no,menubar=no,scrollbars=yes,width=1024,height=768\"); return false'>".
1083                &simplify_from($txt)."</a>";
1084         }
1085 else {
1086         return "<a href='$url'>".&simplify_from($txt)."</a>";
1087         }
1088 }
1089
1090 # mail_page_header(title, headstuff, bodystuff, rightstuff)
1091 sub mail_page_header
1092 {
1093 if ($config{'open_mode'}) {
1094         &popup_header($_[0], $_[1], $_[2]);
1095         }
1096 else {
1097         &ui_print_header(undef, $_[0], "", undef, 0, 0, 0, $_[3], $_[1], $_[2]);
1098         }
1099 }
1100
1101 # mail_page_footer(link, text, ...)
1102 sub mail_page_footer
1103 {
1104 if ($config{'open_mode'}) {
1105         &popup_footer();
1106         }
1107 else {
1108         &ui_print_footer(@_);
1109         }
1110 }
1111
1112 # delete_user_index_files(name)
1113 # Delete all files associated with some user's mail, such as index and maildir
1114 # cache files
1115 sub delete_user_index_files
1116 {
1117 local ($user) = @_;
1118 foreach my $folder (&list_user_folders($user)) {
1119         if ($folder->{'type'} == 0) {
1120                 # Remove mbox index
1121                 local $ifile = &user_index_file($folder->{'file'});
1122                 if (-r $ifile) {
1123                         &unlink_logged($ifile);
1124                         }
1125                 else {
1126                         &unlink_logged(glob("$ifile.{dir,pag,db}"));
1127                         }
1128                 &unlink_logged("$ifile.ids");
1129                 }
1130         elsif ($folder->{'type'} == 1) {
1131                 # Remove Maildir files file
1132                 &unlink_logged(&get_maildir_cachefile($folder->{'file'}));
1133                 }
1134         # Remove sort index
1135         local $ifile = &folder_new_sort_index_file($folder);
1136         if (-r $ifile) {
1137                 &unlink_logged($ifile);
1138                 }
1139         else {
1140                 &unlink_logged(glob("$ifile.{dir,pag,db}"));
1141                 }
1142         }
1143 # Remove read file
1144 local $read = "$module_config_directory/$user.read";
1145 if (-r $read) {
1146         &unlink_logged($read);
1147         }
1148 else {
1149         &unlink_logged(glob("$read.{dir,pag,db}"));
1150         }
1151 }
1152
1153 # get_mail_read(&folder, &mail)
1154 # Returns the read flag for some message, from the DBM
1155 sub get_mail_read
1156 {
1157 local ($folder, $mail) = @_;
1158 if (!$done_dbmopen_read++) {
1159         &open_dbm_db(\%read, "$module_config_directory/$folder->{'user'}.read", 0600);
1160         }
1161 return $read{$mail->{'header'}->{'message-id'}};
1162 }
1163
1164 # set_mail_read(&folder, &mail, read)
1165 # Sets the read flag for some message in the DBM
1166 sub set_mail_read
1167 {
1168 local ($folder, $mail, $read) = @_;
1169 if (!$done_dbmopen_read++) {
1170         &open_dbm_db(\%read, "$module_config_directory/$folder->{'user'}.read", 0600);
1171         }
1172 $read{$mail->{'header'}->{'message-id'}} = $read;
1173 }
1174
1175 # show_mail_table(&mails, &folder, formno)
1176 # Output a full table of messages
1177 sub show_mail_table
1178 {
1179 local @mail = @{$_[0]};
1180 local (undef, $folder, $formno) = @_;
1181
1182 my $showto = !$folder ? 0 :
1183              $folder->{'sent'} || $folder->{'drafts'} ? 1 : 0;
1184 my @tds = ( "nowrap", "nowrap", "nowrap", "nowrap" );
1185
1186 # Show mailbox headers
1187 local @hcols;
1188 if ($folder) {
1189         push(@hcols, "");
1190         splice(@tds, "width=5", 0, 0);
1191         }
1192 push(@hcols, $showto ? $text{'mail_to'} : $text{'mail_from'});
1193 push(@hcols, $config{'show_to'} ? $showto ? ( $text{'mail_from'} ) :
1194                                             ( $text{'mail_to'} ) : ());
1195 push(@hcols, $text{'mail_date'});
1196 push(@hcols, $text{'mail_size'});
1197 push(@hcols, $text{'mail_subject'});
1198 my @links = ( &select_all_link("d", $formno),
1199               &select_invert_link("d", $formno) );
1200 if ($folder) {
1201         print &ui_links_row(\@links);
1202         }
1203 print &ui_columns_start(\@hcols, 100, 0, \@tds);
1204
1205 # Show rows for actual mail messages
1206 my $i = 0;
1207 foreach my $mail (@mail) {
1208         local $idx = $mail->{'idx'};
1209         local $cols = 0;
1210         local @cols;
1211
1212         # From and To columns, with links
1213         local $from = $mail->{'header'}->{$showto ? 'to' : 'from'};
1214         $from = $text{'mail_unknown'} if ($from !~ /\S/);
1215         local $mfolder = $mail->{'folder'} || $folder;
1216         push(@cols, &view_mail_link($in{'user'}, $mfolder, $idx, $from));
1217         if ($config{'show_to'}) {
1218                 push(@cols, &simplify_from(
1219                         $mail->{'header'}->{$showto ? 'from' : 'to'}));
1220                 }
1221
1222         # Date and size columns
1223         push(@cols, &simplify_date($mail->{'header'}->{'date'}, "ymd"));
1224         push(@cols, &nice_size($mail->{'size'}, 1024));
1225
1226         # Subject with icons
1227         local @icons = &message_icons($mail, $mfolder->{'sent'}, $mfolder);
1228         push(@cols, &simplify_subject($mail->{'header'}->{'subject'}).
1229                     join("&nbsp;", @icons));
1230
1231         # Generate the row
1232         if (!$folder) {
1233                 print &ui_columns_row(\@cols, \@tds);
1234                 }
1235         elsif (&editable_mail($mail)) {
1236                 print &ui_checked_columns_row(\@cols, \@tds, "d", $idx);
1237                 }
1238         else {
1239                 print &ui_columns_row([ "", @cols ], \@tds);
1240                 }
1241
1242         if ($config{'show_body'}) {
1243                 # Show part of the body too
1244                 &parse_mail($mail);
1245                 local $data = &mail_preview($mail);
1246                 if ($data) {
1247                         print "<tr $cb> <td colspan=",(scalar(@cols)+1),"><tt>",
1248                                 &html_escape($data),"</tt></td> </tr>\n";
1249                         }
1250                 }
1251         $i++;
1252         }
1253 print &ui_columns_end();
1254 if ($folder) {
1255         print &ui_links_row(\@links);
1256         }
1257 }
1258
1259 1;
1260