#!/usr/bin/perl 
#
# alex pleiner - (c) 2003 zeitform Internet Dienste
# alex@zeitform.de
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# Get the GPL from: http://www.gnu.org/licenses/gpl.html
 
$VERSION="0.6";

#### config ##########################################################

$qmail_assign = "/var/qmail/users/assign";
$vpopmail_dir = "/home/vpopmail";

######################################################################

use POSIX;
use File::Find;

## options
use Getopt::Std;
getopts('hvpaPAxu:');

## some options
help()      if $opt_h;
$useronly = $opt_u; # look for this user only

## get match string and process virtualdomains
my $match = shift || "";
$useronly = $match, $match = "" if ! $useronly && $match =~ /\@/;
$useronly =~ s/\@(.*)$//; 
$match = $1 if $1;

## other options
$verbose  = 1 if $opt_v           || $opt_A;
$popusers = 1 if $opt_p || $opt_P || $opt_A || $useronly || $opt_x;
$aliases  = 1 if $opt_a           || $opt_A || $useronly;
$password = 1 if $opt_P           || $opt_A;
$extended = 1 if $opt_x           || $opt_A;

my %domain = collect_domains($match);
foreach (sort keys %domain) 
{
    report_domains($domain{$_});
}

exit;

#### sub land ########################################################

sub help
{
    print <<EOT;
$0
alex pleiner - (c) 2003 zeitform Internet Dienste
Version: $VERSION
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
(see http://www.gnu.org/licenses/gpl.html)

Syntax:
  $0 [-hvpPaxA] [-u user] domain

Options:
      -h : Display this help
      -v : Give more information (verbose)
      -p : include list of POP3 Accounts
      -P : include Account Passwords in Report (Security Violation!!!)
      -x : show extended information on POP3 Accounts (gecos, last auth, disk usage)
      -a : include list of Aliases/Forwarders/Mailinglists and Autoresponder
      -A : show everything thats possible (all)
 -u user : show only accounts that match "user"
  domain : Display only domains/users that match this "domain" (regex)
           if "domain" contains a "@" like in "user@domain" it will be
           treated as if you have written "-u user domain".

EOT
exit;
}

sub report_domains
{
    my $entry = shift or return;

    printf("%s\n", $entry->{alias});
    printf("     Domain Aliases:      %s\n", join("\n                          ",
						  sort @{$entry->{others}})) if $entry->{others};
    
    if ($verbose)
    {
	printf("     Directory:           %s\n", $entry->{path});
	printf("     UID/GID:             %s/%s\n", $entry->{uid}, $entry->{gid});
	printf("     Default Delivery:    %s\n", $entry->{delivery}) if $entry->{delivery};
        printf("     POP Users:           %s\n", $entry->{popcount} || 0);
        printf("     Auto Responders:     %s\n", $entry->{respondercount} || 0);
	printf("     Mailinglists:        %s\n", $entry->{mailinglistcount} || 0);

    }
    
    if ($verbose && $entry->{limits})
    {
        # max service limits
	printf("     Max. POP3 Accounts:  %s\n", $entry->{maxpopaccounts})   if $entry->{maxpopaccounts};
	printf("     Max. Aliases:        %s\n", $entry->{maxaliases})       if $entry->{maxaliases};
	printf("     Max. Forwards:       %s\n", $entry->{maxforwards})      if $entry->{maxforwards};
	printf("     Max. Mailinglists:   %s\n", $entry->{maxmailinglists})  if $entry->{maxmailinglists};
	printf("     Max. Autoresponders: %s\n", $entry->{maxautoresponders})if $entry->{maxautoresponders};

        # quota & message count limits
	printf("     Domain Quota:        %s (%0.2f MB)\n", $entry->{quota}, mb($entry->{quota})) if $entry->{quota};
	printf("     Def. User Quota:     %s (%0.2f MB)\n", $entry->{default_quota}, mb($entry->{default_quota})) if $entry->{default_quota};
        printf("     Domain Max Msg:      %s\n", $entry->{maxmsgcount})      if $entry->{maxmsgcount};
        printf("     Def. User Max Msg:   %s\n", $entry->{default_maxmsgcount})      if $entry->{default_maxmsgcount};

	# the following are 0 (false) or 1 (true)
        print("     Disabled SMTP:       yes\n") if $entry->{disable_smtp};
	print("     Disabled POP3:       yes\n") if $entry->{disable_pop};
	print("     Disabled IMAP:       yes\n") if $entry->{disable_imap};
	print("     Disabled Webmail:    yes\n") if $entry->{disable_webmail};
	print("     Disabled Relaying:   yes\n") if $entry->{disable_external_relay};
	print("     Disabled Dialup Use: yes\n") if $entry->{disable_dialup};
	print("     Disabled PWD Change: yes\n") if $entry->{disable_password_changing};

        # permissions of non-postmaster admins
        print("     Non-Postmaster") if $entry->{perm_account} || $entry->{perm_alias} || $entry->{perm_forward} || 
                                        $entry->{perm_maillist} || $entry->{perm_maillist_users} || 
                                        $entry->{perm_maillist_moderators} || $entry->{perm_quota} || 
                                        $entry->{perm_defaultquota};
        print("       POP3 Accounts:     %s\n", np_flags($entry->{perm_account}))             if $entry->{perm_account};
        print("       Aliases:           %s\n", np_flags($entry->{perm_alias}))               if $entry->{perm_alias};
        print("       Forwards:          %s\n", np_flags($entry->{perm_forward}))             if $entry->{perm_forward};
        print("       Auto Responders:   %s\n", np_flags($entry->{perm_autoresponder}))       if $entry->{perm_autoresponder};
        print("       Mailinglists:      %s\n", np_flags($entry->{perm_maillist}))            if $entry->{perm_maillist};
        print("       Mailinglist Users: %s\n", np_flags($entry->{perm_maillist_users}))      if $entry->{perm_maillist_users};
        print("       Mailinglist Mods:  %s\n", np_flags($entry->{perm_maillist_moderators})) if $entry->{perm_maillist_moderators};
        print("       Quota:             %s\n", np_flags($entry->{perm_quota}))               if $entry->{perm_quota};
        print("       Default Quota:     %s\n", np_flags($entry->{perm_defaultquota}))        if $entry->{perm_defaultquota};
    }
    
    print "\n";
    
    if ($popusers && $entry->{popuser})
    {
	print("     POP3 Accounts", $useronly ? " (matching \"$useronly\")" : "", ":\n");
	foreach (sort keys %{$entry->{popuser}})
	{
	    printf("       %-18s", $entry->{popuser}->{$_}->{user});
	    printf(" Pass: %s\n", $password ? $entry->{popuser}->{$_}->{passwd} : "*"x8);
            my $prop = stringify_attributes($entry->{popuser}->{$_}->{quota}, $entry->{popuser}->{$_}->{gid});
	    printf("                          Prop: %s\n", $prop) if $prop;
	    printf("                          Name: %s\n", $entry->{popuser}->{$_}->{name}) if $entry->{popuser}->{$_}->{name} && $extended;
	    printf("                          Last: %s from %s\n", $entry->{popuser}->{$_}->{lastauth}, 
                                                                   $entry->{popuser}->{$_}->{lastip} || "unknown") 
               if $entry->{popuser}->{$_}->{lastauth} && $extended;
	    my $size = $entry->{popuser}->{$_}->{maildirsize} || 0;
	    my $usage = "";
	    $usage = int(100 * $size / $entry->{popuser}->{$_}->{quota}) . "%" if $entry->{popuser}->{$_}->{quota} > 0;
	    printf("                          Size: %s (%s MB) %s\n", $size, mb($size), $usage) if $extended;

	}
	print "\n";
    }
    
    if ($aliases && $entry->{aliases})
    {
	print("     Forwarders/Aliases/Lists/Autoresponder", $useronly ? " (matching \"$useronly\")" : "", ":\n");
	foreach (sort keys %{$entry->{aliases}})
	{
	    printf("       %-18s", $_);
	    printf(" %s\n", join(", ", @{$entry->{aliases}->{$_}->{target}}));
	}
	print "\n";
    }
}

sub np_flags
{
    my $flags = shift;
    my $string = "";
    $string .= "NO_CREATE " if $flags & 0x01;
    $string .= "NO_MODIFY " if $flags & 0x02;
    $string .= "NO_DELETE " if $flags & 0x04;
    return $string;
}

sub mb
{
    my $bytes = shift || 0;
    return sprintf("%0.1f", $bytes / 1048576);
}

sub stringify_attributes
{
    my ($quota, $gid) = @_;
    my $string = ""; 
    $string .= "Quota=$quota (" . mb($quota) . " MB) "  if $quota > 0;
    $string .= "QA_ADMIN "       if $gid & 0x1000;
    $string .= "NO_POP "         if $gid & 0x02;
    $string .= "NO_IMAP "        if $gid & 0x08;
    $string .= "NO_WEBMAIL "     if $gid & 0x04;
    $string .= "NO_SMTP "        if $gid & 0x800;
    $string .= "NO_RELAY "       if $gid & 0x20;
    $string .= "NO_DIALUP "      if $gid & 0x40;
    $string .= "NO_PASSWD_CHNG " if $gid & 0x01;
    $string .= "BOUNCE_MAIL "    if $gid & 0x10;
    $string .= "V_USER0 "        if $gid & 0x080;
    $string .= "V_USER1 "        if $gid & 0x100;
    $string .= "V_USER2 "        if $gid & 0x200;
    $string .= "V_USER3 "        if $gid & 0x400;

    return $string;
}

sub collect_domain_limits
{
    my $entry = shift;
    my $path = $entry->{path} or return;

    ## we already get those
    return if $entry->{limits_done};

    return unless -r $path . "/.qmailadmin-limits";
    open LIMITS, $path . "/.qmailadmin-limits" or die "Sorry, can't open $path/.qmailadmin-limits for reading: $!\n";
    while (<LIMITS>)
    {
        chomp;
        my ($key, $value) = split(/:\s+/, $_);
	$entry->{$key}  = $value if $value;
        $entry->{$key}  = 1 if ! $value && $key =~ /^disable_/;
    }
    $entry->{limits_done} = 1;
    close LIMITS;
}

sub collect_domain_delivery
{
    my $entry = shift;
    my $path = $entry->{path} or return;

    ## we already get those
    return if $entry->{delivery};
   
    return unless -r $path . "/.qmail-default";
    open DEFAULT, $path . "/.qmail-default" or die "Sorry, can't open $path/.qmail-default for reading: $!\n";
    while (<DEFAULT>)
    {
        chomp;
        next if /^\s*$/; # empty
        next if /^\s*#/; # comment

	s/^\|\s*\S*vdelivermail\s+''\s+//; ## vdelivermail
        s/^&//;                            ## mail address
	s/^$path\///;                      ## path
 

        $entry->{delivery}  .= $_;
    }
    close DEFAULT;
}



sub collect_domain_aliases
{
    my $entry = shift;
    my $path = $entry->{path} or return;

    ## we already get those
    return if $entry->{aliases_done};

    opendir FWD, $path or die "Sorry, can't open $path for directory reading: $!\n";
    FILE: while (my $file = readdir FWD)
    {
	next unless $file =~ /^.qmail-(.+)$/;
	my $user = $1; $user =~ s/:/./g;
	next if $user eq "default"; ## we handle default delivery somewhere
	next if $useronly && $user !~ /$useronly/o;

	open DOT, $path . "/" . $file or die "Sorry, can't open $path/$file for reading: $!\n";
        my $value;
        while (<DOT>)
	{
	    chomp;
	    my $type;
	    next if /^\s*$/; # empty
            next if /^\s*#/; # comment

	    s/^&//;              # forwarder
            $_ = "AUTORESPONDER" if /^\|\s*\S*autorespond/;
	    $_ = "MAILINGLIST"   if /^\|\s*\S*ezmlm/;
	    s/\/.+$//            if s/^$path\///;  # alias (remove /Maildir/)
            $_ .= " (maildrop)"  if s/^\|\s*\S*maildrop\s+$path\/(.+)\/.+$/$1/;

	    push @{$entry->{aliases}->{$user}->{target}}, $_ if $aliases;
            $entry->{"respondercount"}++   if $_ eq "AUTORESPONDER";
	    $entry->{"mailinglistcount"}++ if $_ eq "MAILINGLIST" && $user !~ /-(default|digest|owner)$/;;
	    next FILE if $_ eq "MAILINGLIST";
	}
        close DOT;
    }
    $entry->{aliases_done} = 1;
    closedir FWD;
}


sub collect_domain_popusers
{
    my $entry = shift;
    my $path = $entry->{path} or return;

    ## we already get those
    return if $entry->{popusers_done};

    return unless -r $path . "/vpasswd";
    open VPASSWD, $path . "/vpasswd" or die "Sorry, can't open $path/vpasswd for reading: $!\n";
    while (<VPASSWD>)
    {
        chomp;
        my ($user, $crypt, $uid, $gid, $name, $dir, $quota, $passwd) = split(/:/, $_);
        $entry->{popcount}++;
        next unless $popusers;
        next if $useronly && $user !~ /$useronly/o;
        @{$entry->{popuser}->{$user}}{qw/user uid gid name dir quota passwd/} = ($user, $uid, $gid, $name, $dir, $quota, $passwd);
    
	if ($extended) # get last auth
	{
	    my $authfile = $dir . "/lastauth";
	    if (-r $authfile)
	    {
		my $mtime = (stat(_))[9]; 
		$entry->{popuser}->{$user}->{lastauth} = strftime("%Y-%m-%d %T",localtime($mtime));
		open AUTH, $authfile or die "cannot open $authfile for reading:$!\n";
		my $ip = <AUTH>;
		close AUTH;
		$entry->{popuser}->{$user}->{lastip} = $ip;
	    }
	    my $size;
	    finddepth (sub { $size += -s if -f; }, "$dir/Maildir");
	    $entry->{popuser}->{$user}->{maildirsize} = $size;
	}
    }
    close VPASSWD;
    $entry->{popusers_done} = 1;
}


sub collect_domains
{
    my $match = shift || "";
    my %domain;

    ## read file with virtual domain information
    open ASSIGN, $qmail_assign or die "Sorry, can't open $qmail_assign for reading: $!\n";
    my @assign = <ASSIGN>;
    close ASSIGN;

    ## collect real domains
    my $domains;
    foreach (@assign)
    {
	my ($address, $user, $uid, $gid, $directory, $dash, $extension) = split(/:/, $_);
        ## how can we better check which of those entries belong to vpopmail?
	next unless $directory =~ /^$vpopmail_dir/o;
        my ($virtual) = ($address =~ /^\+(.+)-/);
	$domains{$user} = 1  if ! $match || $virtual =~ /$match/io || $user =~ /$match/io;
    }

    ## collect info for real domains
    foreach (@assign)
    {
	chomp;
	my ($address, $user, $uid, $gid, $directory, $dash, $extension) = split(/:/, $_);
	if ($domains{$user}) ## real domain
	{
            my ($virtual) = ($address =~ /^\+(.+)-/);
	    push @{$domain{$user}->{others}}, $virtual if $user ne $virtual;
	    @{$domain{$user}}{qw/alias path domain uid gid/} = ($user, $directory, $user, $uid, $gid);
	    collect_domain_limits($domain{$user})   if $verbose;
            collect_domain_popusers($domain{$user}) if $verbose || $popusers;
            collect_domain_aliases($domain{$user})  if $verbose || $aliases ;
            collect_domain_delivery($domain{$user}) if $verbose;
	}
    }
	
   return %domain;
}
