- #!/usr/bin/perl -wT
- #
- # /usr/local/sbin/localmkpostfixvirtual
- # Copyright 2001-2006 Jonas Smedegaard <dr@jones.dk>
- #
- # $Id: localmkpostfixvirtual,v 1.34 2006-10-30 13:36:49 jonas Exp $
- #
- # Generate virtual file for postfix
- #
- # This script provides a poor-man's email ISP tool using the standard
- # Unix users/groups database (with generic getent lookups, supporting
- # smooth upgrades to LDAP or other enterprise systems supporting the
- # libc Name Service Switch.
- #
- #
- # Email domains
- # -------------
- #
- # A system group is needed to locate email domains:
- #
- # # addgroup --system maildomains
- #
- #
- # Each domain (or group of syncronously maintained domains) needs a
- # virtual user, added to that central group:
- #
- # # adduser --system --no-create-home --group --disabled-password abcde
- #
- # # adduser abcde maildomains
- #
- # In the "Other" field of the GECOS info, the virtual user accounts has
- # hint(s) on the email domains tied to that domain:
- #
- # # usermod -c "@abc.com @cde.org" abcde
- #
- # The GECOS info has limited space. A domain group can span multiple
- # virtual accounts by mentioning the secondary account names in the
- # group field of the main virtual account.
- # "Office" or "roomnumber" field of the primary mailgroup (include the
- # primary mailgroup itself too, if using this filed at all!).
- #
- #
- # Email addresses
- # ---------------
- #
- # In the "Other" field of the GECOS info, real user accounts has hint(s)
- # on the email userparts tied to that user:
- #
- # # usermod -c "db@ dbowie@abcde +@xyz" david
- #
- #
- # Suggestion: Add postmaster-specific hints to the root account
- # (default: "postmaster@"):
- #
- # # usermod -c "postmaster@ abuse@ support@abcde" root
- #
- #
- # Postfix lookup tables
- # ---------------------
- #
- # Lookup table for postfix is generated like this:
- #
- # # localmaildomaincreate
- #
- # ..and if satisified, apply the new file to the running system:
- #
- # # localmaildomainupdate
- #
- #
- # The above example entries should generate the following in
- # /etc/postfix/virtual:
- #
- # abc.com VIRTUAL
- # postmaster@abc.com root
- # abuse@abc.com root
- # support@abc.com root
- #
- # db@abc.com david
- # dbowie@abc.com david
- #
- # cde.org VIRTUAL
- # postmaster@cde.org root
- # abuse@cde.org root
- # support@cde.org root
- #
- # db@cde.org david
- # dbowie@cde.org david
- #
- # If another virtual domain account "xyz" is added with @xyz.net
- # in comment field, the following will be included as well:
- #
- # xyz.net VIRTUAL
- # postmaster@xyz.net
- # abuse@xyz.net
- #
- # db@xyz.net
- # @xyz.net
- #
- # (notice how support is only tied to the abcde domains, and the
- # specific dbowie userpart is substituted with a wildcard)
- use strict;
- use User::pwent;
- use User::grent;
- 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};
- }
- }
- 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 $member 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 '';
- }
- my (%username_by_localpart_by_maildomain);
- sub print_accounts($$$$) {
- my ($username, $mailgroup, $maildomain, $pre_text, $post_fallback_text) = @_;
- ($pre_text) && print $pre_text . "\n";
- my $joker_seen;
- if (&warnonce(defined(@{$addresshints{$username}}), "addresshints_$username", "Skipping non-hinted username \"$username\".")) {
- my @localparthints = @{$addresshints{$username}};
- my @localparts = grep {s/(.+)@($mailgroup|$maildomain)?$/$1/} @localparthints;
- foreach my $localpart (@localparts) {
- for ($localpart) {
- # FIXME: the below doesn't work as intended?!?
- # if (&warnonce(! defined(@{$username_by_localpart_by_maildomain{$_}}), "address_$_", "Skipping duplicate address \"$_\" for \"$username\").")) {
- # next;
- # }
- 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";
- }
- }
- }
- } else {
- print $post_fallback_text . "\n" if ($post_fallback_text);
- }
- 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.")) {
- my @groups_sorted = sort @{$groups{$user}};
- push @s, '(' . join(' ', @groups_sorted) . ')';
- }
- 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;
- 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, &usercomment($mailgroupowner));
- }
- # Do secondary mailgroup members
- foreach my $mailuser (@mailusers_sorted) {
- &print_accounts($mailuser, $mailgroup, $maildomain, &usercomment($mailuser), "#WARNING: No addresses for $mailuser");
- }
- }
- }
|