# mailboxes-lib.pl # Common functions for reading user mailboxes BEGIN { push(@INC, ".."); }; use WebminCore; &init_config(); do "$module_root_directory/boxes-lib.pl"; do "$module_root_directory/folders-lib.pl"; %access = &get_module_acl(); $config{'perpage'} ||= 20; $config{'column_count'} ||= 4; $no_log_file_changes = 1; # Turn off file change logging for this module @mail_system_modules = ( [ undef, 4, \&check_qmail_ldap, \&test_qmail_ldap ], [ undef, 5, \&check_vpopmail ], [ "qmailadmin", 2 ], [ "postfix", 0 ], [ "sendmail", 1 ], [undef, 6, \&check_exim ] ); @ignore_users_list = &split_quoted_string($config{'ignore_users'}); # Always detect the mail system if not set if ($config{'mail_system'} == 3) { $config{'mail_system'} = &detect_mail_system(); &save_module_config() if ($config{'mail_system'} != 3); } # send_mail_program(from, &dests) # Returns the command for injecting email, based on the mail system in use sub send_mail_program { my ($from, $dests) = @_; my $qdests = join(" ", map { quotemeta($_) } @$dests); if ($config{'mail_system'} == 1 || $config{'mail_system'} == 0) { # Use sendmail executable, or postfix wrapper local %sconfig = &foreign_check("sendmail") && $config{'mail_system'} == 1 ? &foreign_config("sendmail") : ( ); local $cmd = &has_command($sconfig{'sendmail_path'} || "sendmail"); return "$cmd -f".quotemeta($_[0])." ".$qdests if ($cmd); } elsif ($config{'mail_system'} == 2) { # Use qmail injector local %qconfig = &foreign_check("qmailadmin") ? &foreign_config("qmailadmin") : ( ); local $cmd = ($qconfig{'qmail_dir'} || "/var/qmail"). "/bin/qmail-inject"; return $cmd if (-x $cmd); } else { # Fallback - use sendmail command local $cmd = &has_command("sendmail"); return "$cmd -f".quotemeta($_[0])." ".$qdests if ($cmd); } return undef; } # list_folders() # Returns a list of all mailboxes for all users sub list_folders { local (@rv, $uinfo); foreach $uinfo (&list_mail_users()) { push(@rv, &list_user_folders(@$uinfo)); } return @rv; } # list_user_folders(user, [other info]) # Returns a list of folders for mailboxes belonging to some user sub list_user_folders { if ($_[0] =~ /^\//) { # A path .. return a folder just for it return ( { 'name' => $_[0], 'file' => $_[0], 'type' => &folder_type($_[0]), 'mode' => 1, 'index' => 0 } ); } else { # Getting folders for a user local @uinfo = @_ > 1 ? @_ : &get_mail_user($_[0]); return ( ) if (!@uinfo); local ($dir, $style, $mailbox, $maildir) = &get_mail_style(); local @rv; # Check for user-specified mail store if ($uinfo[10]) { push(@rv, { 'name' => $uinfo[10], 'file' => $uinfo[10], 'type' => &folder_type($uinfo[10]), 'mode' => 0, 'index' => scalar(@rv) } ); } # Check for /var/mail/USERNAME file or directory if ($dir) { $dir =~ s/\/$//; # Trailing / means maildir format. # Postfix sometimes does this. local $mf = &mail_file_style($uinfo[0], $dir, $style); push(@rv, { 'type' => -d $mf ? 1 : 0, 'name' => $mf, 'file' => $mf, 'user' => $uinfo[0], 'index' => scalar(@rv) }); } # Check for file in home dir if ($mailbox) { local $mf = "$uinfo[7]/$mailbox"; if (-r $mf || !@rv) { push(@rv, { 'type' => 0, 'name' => "~$uinfo[0]/$mailbox", 'file' => $mf, 'user' => $uinfo[0], 'index' => scalar(@rv) }); } } # Check for directory in home dir if ($maildir) { local $mf = "$uinfo[7]/$maildir"; if (-d $mf || !@rv) { push(@rv, { 'type' => 1, 'name' => "~$uinfo[0]/$maildir/", 'file' => $mf, 'user' => $uinfo[0], 'index' => scalar(@rv) }); } } # Add any ~/mail files if ($config{'mail_usermin'}) { local $folders_dir = "$uinfo[7]/$config{'mail_usermin'}"; foreach $p (&recursive_files($folders_dir, 1)) { local $f = $p; $f =~ s/^\Q$folders_dir\E\///; push(@rv, { 'file' => $p, 'name' => "~$uinfo[0]/$config{'mail_usermin'}/$f", 'type' => &folder_type($p), 'mode' => 0, 'sent' => $f eq "sentmail", 'index' => scalar(@rv) } ); # Work out if this is a spam folder if (lc($f) eq "spam" || lc($f) eq ".spam") { # Has spam in the name $rv[$#rv]->{'spam'} = 1; } elsif (&foreign_check("virtual-server")) { # Check if Virtualmin is defaulting to it local %vconfig = &foreign_config( "virtual-server"); local $sf = $vconfig{'spam_delivery'}; if ($sf) { $sf =~ s/\$HOME/$uinfo[7]/g; $sf =~ s/\~/$uinfo[7]/g; if ($sf !~ /^\//) { $sf = $uinfo[7]."/".$sf; } $sf =~ s/\/$//; if ($p eq $sf) { $rv[$#rv]->{'spam'} = 1; } } } } } # Add any Usermin external mail files if ($config{'mailbox_user'}) { local %userconfig; &read_file_cached("$uinfo[7]/$config{'mailbox_user'}/config", \%userconfig); local $o; foreach $o (split(/\t+/, $userconfig{'mailboxes'})) { $o =~ /\/([^\/]+)$/ || next; push(@rv, { 'name' => $o, 'file' => $o, 'type' => &folder_type($o), 'mode' => 1, 'index' => scalar(@rv) } ); } } foreach my $f (@rv) { $f->{'user'} = $_[0]; } return @rv; } } sub list_user_folders_sorted { return &list_user_folders(@_); } # get_mail_style() # Returns a list containing the mail base directory, directory style, # mail file in home dir, and maildir in home dir sub get_mail_style { if (!scalar(@mail_style_cache)) { if ($config{'auto'}) { # Based on mail server if ($config{'mail_system'} == 1) { # Can get paths from Sendmail module config local %sconfig = &foreign_config("sendmail"); if ($sconfig{'mail_dir'}) { # File under /var/mail return ($sconfig{'mail_dir'}, $sconfig{'mail_style'}, undef, undef); } elsif ($sconfig{'mail_type'}) { # Maildir under home directory return (undef, $sconfig{'mail_style'}, undef, $sconfig{'mail_file'}); } else { # mbox under home directory return (undef, $sconfig{'mail_style'}, $sconfig{'mail_file'}, undef); } } elsif ($config{'mail_system'} == 0) { # Need to query Postfix module for paths &foreign_require("postfix", "postfix-lib.pl"); local @s = &postfix::postfix_mail_system(); if ($s[0] == 0) { return ($s[1], 0, undef, undef); } elsif ($s[0] == 1) { return (undef, 0, $s[1], undef); } elsif ($s[0] == 2) { return (undef, 0, undef, $s[1]); } } elsif ($config{'mail_system'} == 2 || $config{'mail_system'} == 4) { # Need to check qmail module config for paths local %qconfig = &foreign_config("qmailadmin"); if ($qconfig{'mail_system'} == 1) { return (undef, 0, undef, $qconfig{'mail_dir_qmail'}); } elsif ($qconfig{'mail_dir'}) { return ($qconfig{'mail_dir'}, $qconfig{'mail_style'}, undef, undef); } else { return (undef, $qconfig{'mail_style'}, $qconfig{'mail_file'}, undef); } } elsif ($config{'mail_system'} == 5) { # vpopmail always uses ~/Maildir return ( undef, 0, undef, "Maildir" ); } elsif ($config{'mail_system'} == 6) { # localmail using smail or exim return ( "/var/spool/mail", 0, undef, undef ); } else { # No mail server set yet! return (undef, undef, undef, undef); } } else { # Use config settings @mail_style_cache = ($config{'mail_dir'}, $config{'mail_style'}, $config{'mail_file'}, $config{'mail_sub'}); } } return @mail_style_cache; } # can_user(username, [other details]) sub can_user { if (!&is_user($_[0])) { # For external files, check if the file is under an allowed # directory, or owned by an allowed user. local @st = stat($_[0]); local @uinfo = &get_mail_uid($st[4]); return 1 if (@uinfo && &can_user(@uinfo)); local $dir = &allowed_directory(); return defined($dir) && &is_under_directory($dir, $_[0]); } local @u = @_ > 1 ? @_ : &get_mail_user($_[0]); return 1 if ($_[0] && $access{'sent'} eq $_[0]); return 1 if ($access{'mmode'} == 1); return 0 if (!@u); return 0 if ($_[0] =~ /\.\./); return 0 if ($access{'mmode'} == 0); local $u; if ($access{'mmode'} == 2) { # Is user in list of users? foreach $u (split(/\s+/, $access{'musers'})) { return 1 if ($u eq $_[0]); } return 0; } elsif ($access{'mmode'} == 4) { # Is user the current Webmin user? return 1 if ($_[0] eq $remote_user); } elsif ($access{'mmode'} == 5) { # Is the user's gid in the list of groups? local $gid; foreach $gid (split(/\s+/, $access{'musers'})) { return 1 if ($u[3] == $gid); if ($access{'msec'}) { # Check user's secondary groups too local @ginfo = getgrgid($gid); local @m = split(/\s+/, $ginfo[3]); return 1 if (&indexof($_[0], @m) >= 0); } } } elsif ($access{'mmode'} == 3) { # Is the user not in the list of denied users foreach $u (split(/\s+/, $access{'musers'})) { return 0 if ($u eq $_[0]); } return 1; } elsif ($access{'mmode'} == 6) { # Does the user match a regexp? return ($_[0] =~ /^$access{'musers'}$/); } elsif ($access{'mmode'} == 7) { # Is the user's UID within the allowed range? return (!$access{'musers'} || $u[2] >= $access{'musers'}) && (!$access{'musers2'} || $u[2] <= $access{'musers2'}); } return 0; # can't happen! } # movecopy_user_select(number, folders, folder, form-no) # Returns HTML for entering a username to copy mail to sub movecopy_user_select { local $rv; $rv .= ""; $rv .= ""; $rv .= &ui_user_textbox("mfolder$_[0]", undef, $_[3]); return $rv; } # need_delete_warn(&folder) sub need_delete_warn { return 1 if ($config{'delete_warn'} eq 'y'); return 0 if ($config{'delete_warn'} eq 'n'); local $mf; return $_[0]->{'type'} == 0 && ($mf = &folder_file($_[0])) && &disk_usage_kb($mf)*1024 > $config{'delete_warn'}; } # detect_mail_system() # Works out which mail server is installed sub detect_mail_system { foreach my $m (@mail_system_modules) { return $m->[1] if (&check_mail_system($m)); } return 3; } # check_mail_system(&mailsystem) sub check_mail_system { if ($_[0]->[0]) { # Just check module return &foreign_installed($_[0]->[0]); } else { # Call function local $func = $_[0]->[2]; return &$func($_[0]); } } # test_mail_system([&mailsystem]) # Returns an error message if the mail system is invalid sub test_mail_system { local $ms; if (!$ms) { ($ms) = grep { $_->[1] == $config{'mail_system'} } @mail_system_modules; } if ($ms->[3]) { local $func = $ms->[3]; return &$func(); } return undef; } # check_qmail_ldap() # Make sure Qmail with LDAP extensions is installed sub check_qmail_ldap { return 0 if (&foreign_installed("qmailadmin", 1) != 2); local %qconfig = &foreign_config("qmailadmin"); return 0 if (!-r "$qconfig{'qmail_dir'}/control/ldapserver"); return 1; } # check_vpopmail() # Make sure Qmail with VPopMail extensions is installed sub check_vpopmail { return 0 if (&foreign_installed("qmailadmin", 1) != 2); return -x "$config{'vpopmail_dir'}/bin/vadddomain"; } # check_exim() # Make sure Exim is installed sub check_exim { return &has_command('exim'); } # test_qmail_ldap() # Returns undef the Qmail+LDAP database can be contacted OK, or an error message sub test_qmail_ldap { $config{'ldap_host'} || return $text{'ldap_ehost'}; $config{'ldap_port'} =~ /^\d+$/ || return $text{'ldap_eport'}; $config{'ldap_login'} || return $text{'ldap_euser'}; $config{'ldap_base'} || return $text{'ldap_ebase'}; local $err = &connect_qmail_ldap(1); return ref($err) ? undef : $err; } # show_users_table(&users-list, [only-with-mail]) # Outputs HTML for a table of users, with the appropriate sorting and mode sub show_users_table { my @users = @{$_[0]}; my ($u, %size, %incount, %sentcount, %foldercount); if ($config{'sort_mode'} == 2 || $config{'show_size'} > 0 || $_[1] || $config{'show_count'} || $config{'show_sent'}) { # Need to check folders foreach $u (@users) { next if ($config{'ignore_users_enabled'} == 1 && &indexof($u->[0], @ignore_users_list) >= 0); local @folders = &list_user_folders(@$u); $foldercount{$u->[0]} = scalar(@folders); if ($config{'sort_mode'} == 2 || $config{'show_size'} > 0 || $_[1]) { # Compute size of folders $size{$u->[0]} = $config{'size_mode'} ? &folder_size(@folders) : &folder_size($folders[0]); } if ($config{'show_count'}) { # Get number of mails in inbox $incount{$u->[0]} = &mailbox_folder_size($folders[0]); } if ($config{'show_sent'}) { # Count number of messages in sent mail local ($sent) = grep { $_->{'sent'} } @folders; $sentcount{$u->[0]} = &mailbox_folder_size($sent) if ($sent); } } } # Sort by chosen mode if ($config{'sort_mode'} == 2) { @users = sort { $size{$b->[0]} <=> $size{$a->[0]} } @users; } elsif ($config{'sort_mode'} == 1) { @users = sort { lc($a->[0]) cmp lc($b->[0]) } @users; } local @allusers = @users; if ($_[1]) { # Limit to those with mail @users = grep { $size{$_->[0]} } @users; } # Show table of users if (!@allusers) { print "$text{'index_nousers'}
\n"; } elsif (!@users) { print "$text{'index_nousersmail'}
\n";
}
elsif ($config{'show_size'} == 2) {
# Show full user details
local %uconfig = &foreign_config("useradmin");
local @ccols;
push(@ccols, $text{'find_incount'}) if ($config{'show_count'});
push(@ccols, $text{'find_sentcount'}) if ($config{'show_sent'});
push(@ccols, $text{'find_fcount'}) if (%foldercount);
print &ui_columns_start( [ $text{'find_user'}, $text{'find_real'},
$text{'find_group'},
$text{'find_size'}, @ccols ], 100);
foreach $u (@users) {
local $g = getgrgid($u->[3]);
next if ($config{'ignore_users_enabled'} == 1 &&
&indexof($u->[0], @ignore_users_list) >= 0);
$u->[6] =~ s/,.*$// if ($uconfig{'extra_real'} ||
$u->[6] =~ /,$/);
local $home = $u->[7];
if (length($home) > 30) {
$home = "...".substr($home, -30);
}
local @ccols;
if ($config{'show_count'}) {
push(@ccols, int($incount{$u->[0]}))
}
if ($config{'show_sent'}) {
push(@ccols, int($sentcount{$u->[0]}))
}
if (%foldercount) {
push(@ccols, int($foldercount{$u->[0]}))
}
print &ui_columns_row(
[ &ui_link("list_mail.cgi?user=$u->[0]","$u->[0]"),
$u->[6], $g,
$size{$u->[0]} == 0 ? $text{'index_empty'} :
&nice_size($size{$u->[0]}),
@ccols ],
[ undef, undef, undef, undef, "nowrap" ]);
}
print &ui_columns_end();
}
else {
# Just showing username (and maybe size)
my @grid;
foreach $u (@users) {
next if ($config{'ignore_users_enabled'} == 1 &&
&indexof($u->[0], @ignore_users_list) >= 0);
my $g = "";
$g .= &html_escape($u->[0]);
if ($config{'show_size'} == 1) {
local @folders = &list_user_folders(@$u);
local $total = &folder_size(@folders);
if ($size{$u->[0]} > 0) {
$g .= $config{'show_size_below'} ? '
' : ' ';
$g .= "(";
if (%foldercount) {
$g .= &text('find_in',
&nice_size($size{$u->[0]}),
$foldercount{$u->[0]});
}
else {
$g .= &nice_size($size{$u->[0]});
}
$g .= ")";
}
}
$g .= "";
push(@grid, $g);
}
my $w = int(100/$config{'column_count'});
my @tds = map { "width=".int($w)."%" } (1..$config{'column_count'});
print &ui_grid_table(\@grid, $config{'column_count'}, 100, \@tds, undef,
$text{'index_header'});
}
}
# switch_to_user(user)
# Switch to the Unix user that files are accessed as.
sub switch_to_user
{
if (!defined($old_uid)) {
local @uinfo = &get_mail_user($_[0]);
$old_uid = $>;
$old_gid = $);
$) = "$uinfo[3] $uinfo[3]";
$> = $uinfo[2];
}
}
sub switch_user_back
{
if (defined($old_uid)) {
$> = $old_uid;
$) = $old_gid;
$old_uid = $old_gid = undef;
}
}
sub folder_link
{
return &ui_link("list_mail.cgi?user=$_[0]&folder=$_[1]->{'index'}",$text{'mail_return2'});
}
# get_from_address()
# Returns the address to use when sending email from a script
sub get_from_address
{
local $host = &get_from_domain();
if ($config{'webmin_from'} =~ /\@/) {
return $config{'webmin_from'};
}
elsif (!$config{'webmin_from'}) {
return "webmin-noreply\@$host";
}
else {
return "$config{'webmin_from'}\@$host";
}
}
# get_from_domain()
# Returns the default domain for From: addresses
sub get_from_domain
{
return $config{'from_dom'} || &get_display_hostname();
}
# get_user_from_address(&uinfo)
# Returns the default From: address for mail sent from some user's mailbox
sub get_user_from_address
{
local $uinfo = $_[0];
if ($config{'from_addr'}) {
return $config{'from_addr'};
}
elsif ($uinfo->[11]) {
return $uinfo->[11];
}
elsif ($config{'from_virtualmin'} && &foreign_check("virtual-server")) {
# Does Virtualmin manage this user?
&foreign_require("virtual-server", "virtual-server-lib.pl");
local $d;
foreach $d (&virtual_server::list_domains()) {
local @users = &virtual_server::list_domain_users($d, 0, 0, 1);
local $u;
foreach $u (@users) {
if ($u->{'user'} eq $uinfo->[0] && $u->{'email'}) {
# Found him!
return $u->{'email'};
}
}
}
}
if ($uinfo->[0] =~ /\@/) {
return $uinfo->[0];
}
else {
return $uinfo->[0].'@'.&get_from_domain();
}
}
# check_modification(&folder)
# Display an error message if a folder has been modified since the time
# in $in{'mod'}
sub check_modification
{
local $newmod = &modification_time($_[0]);
if ($in{'mod'} && $in{'mod'} != $newmod && $config{'check_mod'}) {
# Changed!
&error(&text('emodified', "list_mail.cgi?user=$in{'user'}&folder=$_[0]->{'index'}"));
}
}
# spam_report_cmd(user)
# Returns a command for reporting spam, or undef if none
sub spam_report_cmd
{
local ($user) = @_;
local %sconfig = &foreign_config("spam");
local $cmd;
if ($config{'spam_report'} eq 'sa_learn') {
$cmd = &has_command($sconfig{'sa_learn'}) ? "$sconfig{'sa_learn'} --spam --mbox" : undef;
}
elsif ($config{'spam_report'} eq 'spamassassin') {
$cmd = &has_command($sconfig{'spamassassin'}) ? "$sconfig{'spamassassin'} --r" : undef;
}
else {
$cmd = &has_command($sconfig{'sa_learn'}) ?
"$sconfig{'sa_learn'} --spam --mbox" :
&has_command($sconfig{'spamassassin'}) ?
"$sconfig{'spamassassin'} --r" : undef;
}
return $user eq "root" ? $cmd :
$cmd ? &command_as_user($user, 0, $cmd) : undef;
}
# ham_report_cmd()
# Returns a command for reporting ham, or undef if none
sub ham_report_cmd
{
local %sconfig = &foreign_config("spam");
return &has_command($sconfig{'sa_learn'}) ? "$sconfig{'sa_learn'} --ham --mbox" : undef;
}
# allowed_directory()
# Returns base directory for mail files, or undef if not allowed
sub allowed_directory
{
if ($access{'dir'}) {
return $access{'dir'};
}
else {
return $access{'mmode'} == 1 ? "/" : undef;
}
}
# is_user(user)
sub is_user
{
return $_[0] !~ /^\//;
}
# list_mail_users([max], [filterfunc])
# Returns getpw* style structures for all users who can receive mail. Those with
# duplicate info are skipped.
sub list_mail_users
{
local ($max, $filter) = @_;
local @rv;
if ($config{'mail_system'} < 3 or $config{'mail_system'} == 6 ) {
# Postfix, Sendmail, smail, exim and Qmail all use Unix users
local %found;
local %hfound;
local ($dir, $style, $mailbox, $maildir) = &get_mail_style();
setpwent();
while(my (@uinfo) = getpwent()) {
if ($found{$uinfo[0]}++) {
# done this username already
next;
}
if (!$dir && $hfound{$uinfo[7]}++) {
# done this home directory (and thus inbox) already
next;
}
if ($dir && $uinfo[0] =~ /\@/) {
# If this is a user@domain username, mark user-domain
# as done already, to avoid showing dupes
local $noat = $uinfo[0];
$noat =~ s/\@/-/g;
$found{$noat}++;
}
next if ($filter && !&$filter(@uinfo));
push(@rv, \@uinfo);
last if ($max && @rv > $max);
}
endpwent();
}
elsif ($config{'mail_system'} == 4) {
# Qmail+LDAP uses an LDAP db
local $ldap = &connect_qmail_ldap();
local $rv = $ldap->search(base => $config{'ldap_base'},
filter => "(objectClass=qmailUser)");
&error($rv->error) if ($rv->code);
local $u;
foreach $u ($rv->all_entries) {
local @uinfo = &qmail_dn_to_user($u);
next if (!$uinfo[10]); # alias only
next if ($filter && !&$filter(@uinfo));
push(@rv, \@uinfo);
last if ($max && @rv > $max);
}
$ldap->unbind();
}
elsif ($config{'mail_system'} == 5) {
# Get vpopmail user list for all domains
opendir(DOMS, "$config{'vpopmail_dir'}/domains");
foreach my $d (readdir(DOMS)) {
next if ($d =~ /^\./);
local @uinfos = &parse_vpopmail_users("-D $d", $d);
if ($filter) {
@uinfos = grep { &$filter(@$_) } @uinfos;
}
push(@rv, @uinfos);
last if ($max && @rv > $max);
}
closedir(DOMS);
}
return @rv;
}
# parse_vpopmail_users(command, domain)
sub parse_vpopmail_users
{
local %attr_map = ( "passwd" => 1,
"gecos" => 5,
"dir" => 7 );
local (@rv, $user);
open(UINFO, "$config{'vpopmail_dir'}/bin/vuserinfo $_[0] |");
while(
\n";
}
# get_signature(user)
# Returns the users signature, if any
sub get_signature
{
local $sf = &get_signature_file($_[0]);
$sf || return undef;
local $sig;
open(SIG, "<".$sf) || return undef;
while( \n";
}
}
$i++;
}
print &ui_columns_end();
if ($folder) {
print &ui_links_row(\@links);
}
}
sub user_list_link
{
if ($in{'dom'}) {
return "../virtual-server/list_users.cgi?dom=$in{'dom'}";
}
else {
return "";
}
}
# user_read_dbm_file(user)
# Returns the DBM base filename to track read status for messages to some user
sub user_read_dbm_file
{
my ($user) = @_;
my $rv = "$module_config_directory/$user.read";
if (!glob("\Q$rv\E.*")) {
$rv = "$module_var_directory/$user.read";
}
return $rv;
}
1;
",
&html_escape($data),"