1#!/usr/bin/perl -w 2 3# Copyright (c) 1999-2004, 2007 Gregory Neil Shapiro. All Rights Reserved. 4# 5# Redistribution and use in source and binary forms, with or without 6# modification, are permitted provided that the following conditions 7# are met: 8# 9# 1. Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# 2. Redistributions in binary form must reproduce the above copyright 12# notice, this list of conditions and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# 3. Neither the name of the author nor the names of its contributors 15# may be used to endorse or promote products derived from this software 16# without specific prior written permission. 17# 18# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28# SUCH DAMAGE. 29 30# $Id: buildvirtuser,v 1.8 2007-10-08 18:44:15 gshapiro Exp $ 31 32=head1 NAME 33 34buildvirtuser - Build virtusertable support from a directory of files 35 36=head1 SYNOPSIS 37 38 buildvirtuser [-f] [-t] 39 40=head1 DESCRIPTION 41 42buildvirtuser will build /etc/mail/virtusertable.db and /etc/mail/virthosts 43based on the contents of the directory /etc/mail/virtusers/. That 44directory should contain one file per virtual domain with the filename 45matching the virtual domain name and the contents containing a list of 46usernames on the left and the actual address for that username on the 47right. An empty left column translates to the default for that domain. 48Blank lines and lines beginning with '#' are ignored. Occurrences of 49$DOMAIN in the file are replaced by the current domain being processed. 50Occurrences of $LHS in the right hand side are replaced by the address on 51the left hand side. 52 53The -f option forces the database to be rebuilt regardless of whether 54any file changes were detected. 55 56The -t option instructs the program to build a text file instead of a 57database. The text file can then be used with makemap. 58 59=head1 CONFIGURATION 60 61In order to function properly, sendmail must be configured to use these 62files with: 63 64 FEATURE(`virtusertable')dnl 65 VIRTUSER_DOMAIN_FILE(`/etc/mail/virthosts')dnl 66 67If a new domain is added (i.e., by adding a new file to 68/etc/mail/virtusers/), the sendmail daemon must be restarted for the change 69to take affect. 70 71=head1 EXAMPLES 72 73Here is an example file from the /etc/mail/virtusers/ directory: 74 75=head2 /etc/mail/virtusers/example.org: 76 77 # Services 78 MAILER-DAEMON gshapiro+bounce.$DOMAIN@example.net 79 postmaster gshapiro+$LHS.$DOMAIN@example.net 80 webmaster gshapiro+$LHS.$DOMAIN@example.net 81 82 # Defaults 83 error:nouser No such user 84 85 # Users 86 gshapiro gshapiro+$DOMAIN@example.net 87 zoe zoe@example.com 88 89=head1 AUTHOR 90 91Gregory Neil Shapiro E<lt>F<gshapiro@gshapiro.net>E<gt> 92 93=cut 94 95use strict; 96use File::stat; 97use Getopt::Std; 98 99my $makemap = "/usr/sbin/makemap"; 100my $dbtype = "hash"; 101my $maildir = "/etc/mail"; 102my $virthosts = "$maildir/virthosts"; 103my $newvirthosts = "$maildir/virthosts.new"; 104my $virts = "$maildir/virtusers"; 105my $newvirt = "$maildir/virtusertable.new.db"; 106my $virt = "$maildir/virtusertable.db"; 107my %virt = (); 108my $newest = 0; 109my ($lhs, $domain, $key, $value); 110my $opts = {}; 111 112sub preserve_perms ($$) 113{ 114 my $old = shift; 115 my $new = shift; 116 my $st; 117 118 $st = stat($old); 119 return if (!defined($st)); 120 chmod($st->mode, $new) || warn "Could not chmod($st->mode, $new): $!\n"; 121 chown($st->uid, $st->gid, $new) || warn "Could not chmod($st->uid, $st->gid, $new): $!\n"; 122} 123 124getopts('ft', $opts) || die "Usage: $0 [-f] [-t]\n"; 125 126if ($opts->{t}) 127{ 128 $newvirt = "$maildir/virtusertable.new"; 129 $virt = "$maildir/virtusertable"; 130} 131 132opendir(VIRTS, $virts) || die "Could not open directory $virts: $!\n"; 133my @virts = grep { -f "$virts/$_" } readdir(VIRTS); 134closedir(VIRTS) || die "Could not close directory $virts: $!\n"; 135 136foreach $domain (@virts) 137{ 138 next if ($domain =~ m/^\./); 139 open(DOMAIN, "$virts/$domain") || die "Could not open file $virts/$domain: $!\n"; 140 my $line = 0; 141 my $mtime = 0; 142 my $st = stat("$virts/$domain"); 143 $mtime = $st->mtime if (defined($st)); 144 if ($mtime > $newest) 145 { 146 $newest = $mtime; 147 } 148LINE: while (<DOMAIN>) 149 { 150 chomp; 151 $line++; 152 next LINE if /^#/; 153 next LINE if /^$/; 154 if (m/^([^\t ]*)[\t ]+(.*)$/) 155 { 156 if (defined($1)) 157 { 158 $lhs = "$1"; 159 $key = "$1\@$domain"; 160 } 161 else 162 { 163 $lhs = ""; 164 $key = "\@$domain"; 165 } 166 $value = $2; 167 } 168 else 169 { 170 warn "Bogus line $line in $virts/$domain\n"; 171 } 172 173 # Variable substitution 174 $key =~ s/\$DOMAIN/$domain/g; 175 $value =~ s/\$DOMAIN/$domain/g; 176 $value =~ s/\$LHS/$lhs/g; 177 $virt{$key} = $value; 178 } 179 close(DOMAIN) || die "Could not close $virts/$domain: $!\n"; 180} 181 182my $virtmtime = 0; 183my $st = stat($virt); 184$virtmtime = $st->mtime if (defined($st)); 185if ($opts->{f} || $virtmtime < $newest) 186{ 187 print STDOUT "Rebuilding $virt\n"; 188# logger -s -t ${prog} -p mail.info "Rebuilding ${basedir}/virtusertable" 189 if ($opts->{t}) 190 { 191 open(MAKEMAP, ">$newvirt") || die "Could not open $newvirt: $!\n"; 192 } 193 else 194 { 195 open(MAKEMAP, "|$makemap $dbtype $newvirt") || die "Could not start makemap: $!\n"; 196 } 197 198 foreach $key (keys %virt) 199 { 200 print MAKEMAP "$key\t\t$virt{$key}\n"; 201 } 202 close(MAKEMAP) || die "Could not close makemap ($?): $!\n"; 203 preserve_perms($virt, $newvirt); 204 rename($newvirt, $virt) || die "Could not rename $newvirt to $virt: $!\n"; 205 206 open(VIRTHOST, ">$newvirthosts") || die "Could not open file $newvirthosts: $!\n"; 207 foreach $domain (sort @virts) 208 { 209 next if ($domain =~ m/^\./); 210 print VIRTHOST "$domain\n"; 211 } 212 close(VIRTHOST) || die "Could not close $newvirthosts: $!\n"; 213 preserve_perms($virthosts, $newvirthosts); 214 rename($newvirthosts, $virthosts) || die "Could not rename $newvirthosts to $virthosts: $!\n"; 215} 216exit 0; 217