#!/usr/bin/perl -wT # # /usr/local/sbin/localmkpostfixvirtual # Copyright 2001-2006 Jonas Smedegaard # # $Id: localmkpostfixvirtual,v 1.26 2006-08-22 22:59:25 jonas Exp $ # # Generate virtual file for postfix # # Hints are stored in the "Other" field (using chfn). # # Each user should have space-separated hints like this: # "mailname1@ mailname2@mailgroup1 mailname3@mailgroup2 mailname4@maildomain7". # # The user of each mailgroup should have hints like "@domain1 @domain2" # for each hosted maildomain. # # Generate virtual file like this: # # # ( cd /etc/postfix && localmkpostfixvirtual > virtual.new && diff virtual virtual.new ) # # ..and if the changes are correct, activate the new virtual file: # # # ( cd /etc/postfix && mv virtual.new virtual && postmap virtual ) # # Optional: Several mailgroups can be tied together (when amount of hints # exceeds the limit of the "Other" field: List them all in # "Office" or "roomnumber" field of primary mailgroup (include the # primary mailgroup itself!). # # Optional: root can have hints like "postmaster@ hostmaster@ support@" # for default accounts tied to the sysadmin (default: "postmaster@"). # # Suggestion: Add mailgroup users like this: # adduser --system --no-create-home --group --disabled-password # use strict; use User::pwent; use User::grent; #use Data::Dumper; my (%username, %fullname, %office, %workphone, %homephone, %other); my (%username_by_gid, %addresshints, %domains); while (my $pw = getpwent()) { $username_by_gid{$pw->gid} = $pw->name; ($fullname{$pw->name}, $office{$pw->name}, $workphone{$pw->name}, $homephone{$pw->name}, $other{$pw->name}) = split /\s*,\s*/, $pw->gecos; if (defined($other{$pw->name})) { @{$addresshints{$pw->name}} = grep {/^([\.[:alnum:]_-]+|\+)?@([\.[:alnum:]_-]+)?$/} split /\s+/, $other{$pw->name}; } #DEBUG: ($pw->name eq "annette") and print STDERR Dumper(@{$addresses{$pw->name}}); } my (%owner, %members, %groups); while (my $gr = getgrent()) { $owner{$gr->name} = $username_by_gid{$gr->gid}; $members{$gr->name} = $gr->members; foreach my $member (@{$members{$gr->name}}) { push @{$groups{$member}}, $gr->name unless (defined($owner{$gr->name}) and $gr->name eq $owner{$gr->name}); } } my (%warned); sub warnonce($$$) { my ($test, $id, $warning) = @_; if ($test) { return 1; } if ($warned{$id}) { return ''; } print STDERR 'W: ' . $warning . "\n"; $warned{$id} = 1; return ''; } sub print_accounts($$$$) { my ($username, $mailgroup, $maildomain, $pre_text, $post_fallback_text) = @_; ($pre_text) && print $pre_text . "\n"; my $user_seen; my $joker_seen; #DEBUG: print STDERR "$mailgroup $maildomain\n"; #DEBUG: ($username eq "annette") and print STDERR Dumper(@{$addresshints{$username}}); if (not &warnonce(defined(@{$addresshints{$username}}), "addresshints_$username", "Skipping non-hinted username \"$username\".")) { return ''; } my @localparthints = @{$addresshints{$username}}; my @localparts = grep {s/(.+)@($mailgroup|$maildomain)?$/$1/} @localparthints; foreach my $localpart (@localparts) { for ($localpart) { if (/^\+$/) { if (!$joker_seen) { print "\@$maildomain $username\n"; $joker_seen = $username; } else { print "#WARNING: Catch-all for $maildomain already set to $joker_seen!"; } } else { print "$localpart\@$maildomain $username\n"; } } $user_seen++; } print $post_fallback_text . "\n" if ($post_fallback_text && !$user_seen); print "\n"; } sub usercomment($) { my $user = shift; my @s = (); if (&warnonce(defined($fullname{$user}), "fullname_$user", "User \"$user\" lacks fullname.")) { push @s, $fullname{$user}; } if (&warnonce(defined(@{$groups{$user}}), "groups_$user", "User \"$user\" belongs to no (secondary) group.")) { push @s, '(' . join(' ', @{$groups{$user}}) . ')'; } if (@s) { unshift @s, '#'; } my $string = join(' ', @s); return "$string"; } my $loop; my @mailgroups = @ARGV ? @ARGV : @{$members{'maildomains'}}; foreach my $mailgroup (@mailgroups) { if (not &warnonce(defined(@{$addresshints{$mailgroup}}), "addresshints_$mailgroup", "Skipping empty mailgroup \"$mailgroup\".")) { next; } my @maildomainhints = @{$addresshints{$mailgroup}}; my @maildomains = grep {s/^@(.+)/$1/} @maildomainhints; foreach my $maildomain (@maildomains) { my (@mailgroupowners, @mailusers); my @mailgroupgroups = split / +/, $office{$mailgroup}; push @mailgroupgroups, $mailgroup unless (@mailgroupgroups); foreach my $mailgroupgroup (@mailgroupgroups) { push @mailgroupowners, $owner{$mailgroupgroup} if ($owner{$mailgroupgroup}); push @mailusers, @{$members{$mailgroupgroup}}; } my @mailgroupowners_sorted = sort @mailgroupowners; my @mailusers_sorted = sort @mailusers; #DEBUG: print STDERR Dumper(@mailusers); die; if ($loop) { print "\n##################################################################\n\n"; } $loop++; &print_accounts('root', $mailgroup, $maildomain, "$maildomain VIRTUAL", "postmaster\@$maildomain root"); # Do mailgroup owners (and don't warn if there's no addresses attached) foreach my $mailgroupowner (@mailgroupowners_sorted) { &print_accounts($mailgroupowner, $mailgroup, $maildomain, '# ' . $fullname{$mailgroupowner} . ' (' . join(' ', @{$groups{$mailgroupowner}}) . ')'); } # Do secondary mailgroup members foreach my $mailuser (@mailusers_sorted) { # &print_accounts($mailuser, $mailgroup, $maildomain, '# ' . $fullname{$mailuser} . ' (' . join(' ', @{$groups{$mailuser}}) . ')', "#WARNING: No addresses for $mailuser"); &print_accounts($mailuser, $mailgroup, $maildomain, &usercomment($mailuser), "#WARNING: No addresses for $mailuser"); } } }