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 subsitution
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