File: //usr/share/sendmail/update_mk
#!/usr/bin/perl -w
#------------------------------------------------------------------------
#
# $Sendmail: update_mk,v 8.15.2 2021-03-16 16:04:16 cowboy Exp $
#
# Create /etc/mail/Makefile for Debian Sendmail databases
#
# Copyright 1998-2010 Richard Nelson. All Rights Reserved.
#
# Notes (to all):
# *
#
# Notes (to self):
# * Should actually put data in some files (local-host-names, etc)
#
#------------------------------------------------------------------------
#
# Initialization of the perl environment
use strict; # be kosher
use Cwd; # provide cwd()
use Env; # A few environmental references
use integer; # Peformance
use Sys::Hostname; # make sure we have a valid hostname
use Getopt::Long; # parameter handling
# Local libraries - for Debian Sendmail Perl helper functions
# BEGIN { $main::my_path = substr($0,$[,rindex($0,'/')) };
use lib ('.', substr($0,$[,rindex($0,'/')), "/usr/share/sendmail");
require Parse_mc;
require Parse_conf;
# Version of this program
#($main::MYNAME = $main::0) =~ s|.*/||;
#$main::Author = "Richard Nelson";
#$main::AuthorMail = "cowboy\@debian.org";
#$main::Version = '$Revision: 2.00 $ ';
$main::program_name = $0;
$main::program_version = '8.15.2';
$main::program_date = '2021-03-16 16:04:16 cowboy';
$main::debug = 0;
my $interp_pgm = "$^X";
my $interp_vrm = $];
$interp_vrm = ("$^V" | '000') if (defined $^V);
my $current_time = scalar localtime;
my $user = getlogin || (getpwuid($<))[0] || "Unknown!!";
my $hostname = hostname();
my $directory = getcwd();
my $Makefile_def = "/etc/mail/Makefile";
my $Conffile = "/etc/mail/sendmail.conf";
# List of database entries that will be created if not found
my %created_dbs;
my @created_dbs;
# A few files (like exposed-users) can be listed >1 times !
my %created_files = ();
my $reload_stamp = "/var/run/sendmail/stampdir/reload";
#
#------------------------------------------------------------------------------
# Finally, some code (almost)
#------------------------------------------------------------------------------
#
# Argument handling...
$main::opt_help='';
$main::opt_output_file='';
$main::opt_input_file='';
$main::opt_debug='';
my @options = qw(
help|h
output-file|output_file|o:s
input-file|input_file|i:s
debug!
);
my $result = GetOptions(@options);
if ( ! $result ) {
die "Terminating due to parameter error";
};
if ( $main::opt_help ) {
warn "$main::program_name $main::program_version $main::program_date\n";
warn "$0 \n";
warn " -help\n" if $main::opt_help;
warn " -debug\n" if $main::opt_debug;
warn " -o $main::opt_output_file\n" if $main::opt_output_file;
warn " -i $main::opt_input_file\n" if $main::opt_input_file;
exit 0;
};
my $Makefile = $main::opt_output_file || $Makefile_def;
${Parse_mc::database_file} = $main::opt_input_file
if $main::opt_input_file;
# $main::debug is used in Parse_mc !
$main::debug = $main::opt_debug || $main::debug;
# Pull in some configuration data
&Parse_conf::read_conf("$Conffile");
my ($ok, $value) = &Parse_conf::get_value('HANDS_OFF');
if ($value ne '0') {
exit;
};
# Let them know wtf is going on...
print STDOUT "Creating ${Makefile}...\n";
# Read the mc/m4 files
&Parse_mc::read_dbs($Parse_mc::database_file, '');
# Determine names with which we shall work
my @databases = &get_names();
# Write out the textual representation
&write_make;
#
#------------------------------------------------------------------------------
# Obtain list of candidate databases from sendmail.mc
#------------------------------------------------------------------------------
sub get_names {
my @names;
# Database types we know how to handle
my %make_types = (
'btree' => 1
,'dbm' => 1
,'hash' => 1
,'m4' => 1
,'newaliases' => 1
,'parse_mc' => 1
,'update_conf' => 1
,'update_mk' => 1
,'update_auth' => 1
,'update_tls' => 1
,'QUEUE_GROUP' => 1
,'include' => 1
);
# Add any qualified databases to the list
foreach my $entry (&Parse_mc::names_dbs()) {
my ($class, $flags, $files, $options) =
&Parse_mc::entry_dbs($entry);
if ( (exists($make_types{$entry}) or
exists($make_types{$class}))
and @{$files}[0] ne '-'
and ! exists($created_dbs{$entry})) {
push @names, $entry;
push @created_dbs, $entry;
$created_dbs{$entry} = '';
};
};
return @names;
};
#
#------------------------------------------------------------------------------
# Create Makefile
#------------------------------------------------------------------------------
sub write_make {
my $ofh = new FileHandle;
$Makefile = '&STDOUT' if ($Makefile eq '-');
unless ( open($ofh, ">$Makefile") ) {
warn("Could not open $Makefile($!), using STDOUT.\n");
open($ofh, ">&STDOUT");
};
$Makefile = '-' if ($Makefile eq '&STDOUT');
&write_header($ofh);
&write_target_clean($ofh);
&write_target_restart($ofh);
&write_target_sendmail($ofh);
&write_targets($ofh);
&write_files($ofh);
&write_footer($ofh);
close $ofh;
if ($Makefile eq $Makefile_def) {
my $gid = getgrnam('smmsp');
chown '0', $gid, $Makefile;
chmod 0754, $Makefile;
};
};
#
#------------------------------------------------------------------------------
# Write Makefile header
#------------------------------------------------------------------------------
sub write_header {
my ($ofh) = @_;
print $ofh <<"EOT";
#!/usr/bin/make -f
####################################################################
##### This file is automagically generated -- edit at your own risk
#####
##### Copyright (c) 1998-2010 Richard Nelson. All Rights Reserved.
#####
##### file: ${Makefile} Makefile for Sendmail databases
##### generated via: (${interp_pgm} ${interp_vrm})
##### ${main::program_name}
##### version: ${main::program_version} ${main::program_date}
##### by: ${user}\@${hostname}
##### on: ${current_time}
##### in: ${directory}
##### input files: ${Parse_mc::database_file}
#####
##### Usage:
##### 1) Make all targets upto date - use one of the following:
##### A) \`(cd /etc/mail && make)\`
##### B) \`make -f ${Makefile}\`
##### C) \`${Makefile}\`
##### 2) Force update of <target> - Add <target> to one of the
##### prior commands ie, \`${Makefile} access\`. Most
##### any reasonable <target> value is accepted.
#####
####################################################################
SHELL=/bin/sh
#
# targets that will be routed to the /etc/init.d/sendmail script
# NOTE: newaliases and clean removed due to extant rules
#
INIT = start stop restart restart-if-running \\
reload-if-running reload force-reload \\
hoststat purgestat mailstats mailq runq control \\
status debug
.SUFFIXES:
.PRECIOUS: $Makefile
#
# all, the default target, will update everything
#
.PHONY: all
all: sendmail $reload_stamp
#
# route to the /etc/init.d/sendmail script
#
.PHONY: \$(INIT)
\$(INIT): FORCE
/etc/init.d/sendmail \$\@
EOT
};
#
#------------------------------------------------------------------------------
# Write Makefile 'CLEAN' target
#------------------------------------------------------------------------------
sub write_target_clean {
my ($ofh) = @_;
my @names;
%created_files = ();
foreach my $entry (@databases) {
next if ($entry eq 'QUEUE_GROUP'
or $entry eq 'Makefile'
or $entry eq 'auth'
or $entry eq 'tls'
or $entry eq 'include'
);
my ($class, $flags, $files, $options) =
&Parse_mc::entry_dbs($entry);
foreach my $file (@{$files}) {
next if ( $file eq '-'
or exists($created_files{$file}) );
$created_files{$file} = '';
my $dbname;
if ($class eq 'parse_mc'
or $class eq 'update_mk'
or $class eq 'm4') {
$dbname = "/etc/mail/$entry";
}
elsif ($class eq 'update_conf') {
$dbname = "/etc/cron.d/sendmail";
}
elsif ($class eq 'btree'
or $class eq 'hash'
or $class eq 'newaliases') {
$dbname = "$file.db";
}
else {
$dbname = $file;
};
push @names, $dbname;
};
};
print $ofh <<"EOT";
#
# clean target, remove {sendmail,submit}.cf and generated databases
#
EOT
print $ofh
".PHONY: clean\n",
"clean: FORCE\n",
"\trm -f ",join(";\n\trm -f ", @names),";\n";
};
#
#------------------------------------------------------------------------------
# Write Makefile 'RESTART' target
#------------------------------------------------------------------------------
sub write_target_restart {
my ($ofh) = @_;
my $file;
print $ofh <<"EOT";
#
# restart target, check to see if sendmail needs to be restarted
#
.PHONY: noreload norestart
noreload norestart: FORCE
\@su smmsp -s /bin/sh -c "touch $reload_stamp"
.PHONY: should_reload should_restart
should_reload should_restart: FORCE
\@rm -f $reload_stamp;
\@\$(MAKE) -sf $Makefile $reload_stamp;
EOT
# Spew secondary target of actual restart
%created_files = ();
my @right;
foreach my $entry (sort &Parse_mc::restart_dbs()) {
if (! exists($created_dbs{$entry})) {
push @created_dbs, $entry;
$created_dbs{$entry} = '';
};
my ($class, $flags, $files, $options) =
&Parse_mc::entry_dbs($entry);
foreach my $file (@{$files}) {
next if ( $file eq '-'
or exists($created_files{$file}) );
$created_files{$file} = '';
my $dbname = $file;
$dbname =~ s/\.mc$/\.cf/;
push @right, "$dbname";
};
};
print $ofh "$reload_stamp: \\\n\t",
join(" \\\n\t", @right), "\n",
"\t\@if [ ! -f \$\@ ]; then \\\n",
"\t\techo 'A forced reload...'; \\\n",
"\telse \\\n",
"\t\techo 'The following file(s) have changed:'; \\\n",
"\t\techo ' \$?'; \\\n",
"\t\tfi;\n",
"\t\@echo '** ** You should issue ",
"\`/etc/init.d/sendmail reload\` ** **';\n";
};
#
#------------------------------------------------------------------------------
# Write Sendmail dependancies
#------------------------------------------------------------------------------
sub write_target_sendmail {
my ($ofh) = @_;
print $ofh <<"EOT";
#
# sendmail targets, depend upon *ALL* relevant files/databases
#
.PHONY: sendmail
sendmail: sendmail_files sendmail_dbs
EOT
my (@smfiles, @smdbs);
%created_files = ();
foreach my $entry (@created_dbs) {
my ($class, $flags, $files, $options) =
&Parse_mc::entry_dbs($entry);
foreach my $file (@{$files}) {
next if $file eq '-';
next if $file eq 'ldap'; # ALIAS hack
if ($class eq 'parse_mc' or $class eq 'update_mk'
or $class eq 'm4') {
push @smfiles, "$file"
if (!exists($created_files{$file}));
$created_files{$file} = '';
push @smdbs, "/etc/mail/$entry";
}
elsif ($class eq 'update_conf') {
push @smdbs, "/etc/cron.d/sendmail";
}
elsif ($class eq 'btree' or $class eq 'hash'
or $class eq 'newaliases') {
push @smfiles, "$file"
if (!exists($created_files{$file}));
$created_files{$file} = '';
push @smdbs, "$file.db";
}
else {
push @smfiles, "$file"
if (!exists($created_files{$file}));
$created_files{$file} = '';
};
};
};
print $ofh ".PHONY: sendmail_files\n",
"sendmail_files: \\\n\t",
join(" \\\n\t", @smfiles), "\n\n";
print $ofh ".PHONY: sendmail_dbs\n",
"sendmail_dbs: \\\n\t",
join(" \\\n\t", @smdbs), "\n";
};
#
#------------------------------------------------------------------------------
# Write Makefile individual targets
#------------------------------------------------------------------------------
sub write_targets {
my ($ofh) = @_;
my $file;
print $ofh <<"EOT";
#
# Individual database targets
#
#
# Default db type is hash (Must be in /etc/mail, or
# fully qualify the dataset for this target to work)
#
%.db: % FORCE
\@echo 'Updating \$\@...';
\@if [ -x /usr/sbin/makemap ]; then \\
/usr/sbin/makemap hash \$\@.new.db < \$<; \\
chown root:smmsp \$\@.new.db; \\
chmod 0640 \$\@.new.db; \\
mv -f \$\@.new.db \$\@; \\
fi;
EOT
# Spew primary target of <target>
foreach my $entry (@databases) {
my ($class, $flags, $files, $options) =
&Parse_mc::entry_dbs($entry);
my @names;
my $left = '';
my $right = '';
my $sleft = '';
my $dbname = '';
print $ofh "\n";
# Create short name(s) for database entries
$sleft .= "$entry ";
if ($class eq 'newaliases'
or $class eq 'parse_mc'
or $class eq 'update_mk'
or $class eq 'update_conf'
or $class eq 'update_auth'
or $class eq 'update_tls'
) {
$sleft = 'makefile make ' if ($class eq 'update_mk');
$sleft .= 'cron ' if ($class eq 'update_conf');
$sleft .= "$class ";
};
foreach my $file (@{$files}) {
next if $file eq '-';
next if $file eq 'ldap'; # ALIAS hack
if ($entry eq 'QUEUE_GROUP' or $entry eq 'include') {
$dbname = '';
$sleft = lc "${entry}s";
$left = lc "${entry}s";
}
elsif ($class eq 'parse_mc' or $class eq 'update_mk'
or $class eq 'm4') {
$dbname = "/etc/mail/$entry";
}
elsif ($class eq 'update_conf') {
$dbname = "/etc/cron.d/sendmail";
}
elsif ($class eq 'btree' or $class eq 'hash'
or $class eq 'newaliases') {
$dbname = "$file.db";
}
else {
$dbname = $file;
$file = ""
if ($class eq 'update_tls'
or $class eq 'update_auth');
};
$left .= "$dbname ";
$right .= " $file";
# Create short name(s) for database entries
if ($class ne 'parse_mc'
and $class ne 'update_mk'
and $class ne 'update_conf'
and $class ne 'update_auth'
and $class ne 'update_tls'
and $entry ne 'QUEUE_GROUP'
and $entry ne 'include'
) {
my $dbsname = "$dbname";
$dbsname =~ s/\/etc\/mail\///;
$sleft .= "$dbsname " if ($entry ne $dbsname);
if ($file ne $dbname) {
$dbsname =~ s/\.db//;
$sleft .= "$dbsname " if ($entry ne $dbsname);
};
};
};
# Spew out a phony entry suitable for FORCE
print $ofh '.PHONY: ', $sleft, "\n";
print $ofh $sleft,": FORCE\n"
if ($entry ne 'QUEUE_GROUP' and $entry ne 'include');
if ($class eq 'update_mk') {
print $ofh "\t\@touch $right;\n",
"\t\@\$(MAKE) -sf $Makefile $left;\n";
}
elsif ($entry ne 'QUEUE_GROUP' and $entry ne 'include') {
print $ofh "\t\@rm -f $left;\n",
"\t\@\$(MAKE) -sf $Makefile $left;\n";
};
#
# Spew out the whole enchilada for this database
if ($entry eq 'sendmail.cf' or $entry eq 'databases') {
my ($iclass, $iflags, $ifiles, $ioptions) =
&Parse_mc::entry_dbs('include');
$right .= ' ';
$right .= join(' ', @{$ifiles});
};
print $ofh $left, ':', $right, "\n";
next if ($entry eq 'QUEUE_GROUP' or $entry eq 'include');
print $ofh "\t\@echo 'Updating $entry ...';\n";
if ($class eq 'newaliases') {
print $ofh "\t\@if [ -x /usr/sbin/sendmail ]; then \\\n";
print $ofh "\t\t/usr/sbin/sendmail -bi || true; \\\n";
}
elsif ($class eq 'parse_mc'
or $class eq 'update_conf'
or $class eq 'update_mk'
or $class eq 'update_auth'
or $class eq 'update_tls'
) {
print $ofh "\t\@if [ -x /usr/share/sendmail/${class} ]; then \\\n";
print $ofh "\t\t/usr/share/sendmail/${class} || true; \\\n"
}
elsif ($class eq 'btree' or $class eq 'hash') {
print $ofh "\t\@if [ -x /usr/sbin/makemap ]; then \\\n";
};
foreach my $file (@{$files}) {
next if ( $file eq 'ldap' ); # ALIAS hack
my $dbname = $file;
my $newname = $file;
my $uid = 'root';
$uid = 'smmta' if ($entry eq 'authinfo'
or $entry eq 'access_db'
or $entry eq 'QUEUE_GROUP'
or $class eq 'newaliases');
my $mode = '0644';
$mode = '0640' if ($entry eq 'authinfo'
or $entry eq 'access_db');
$mode = '0640' if ($class eq 'btree' or $class eq 'hash');
$mode = '0644' if ($class eq 'm4');
$mode = '0754' if ($class eq 'update_mk');
$mode = '02750' if ($entry eq 'QUEUE_GROUP');
if ($class eq 'btree' or $class eq 'hash') {
$dbname .= '.db';
$newname .= '.new.db';
if (index($flags, '-o') == -1) {
print $ofh "\t\t/usr/sbin/makemap $class $newname \\\n",
"\t\t\t< $file; \\\n",
"\t\tchown $uid:smmsp $newname; \\\n",
"\t\tchmod $mode $newname; \\\n",
"\t\tmv -f $newname $dbname; \\\n";
}
else {
print $ofh
"\t\tif [ -s $file ]; then \\\n",
"\t\t/usr/sbin/makemap $class $newname \\\n",
"\t\t\t< $file; \\\n",
"\t\tchown $uid:smmsp $newname; \\\n",
"\t\tchmod $mode $newname; \\\n",
"\t\tmv -f $newname $dbname; \\\n",
"\t\tfi; \\\n";
};
}
elsif ($class eq 'newaliases') {
$newname .= '.db';
print $ofh "\t\tif [ -f $newname ]; then \\\n",
"\t\t\tchown $uid:smmsp $newname; \\\n",
"\t\t\tchmod $mode $newname; \\\n",
"\t\t\tfi; \\\n";
}
elsif ($class eq 'parse_mc' or $class eq 'update_mk') {
$newname = "/etc/mail/$entry";
print $ofh "\t\tchown $uid:smmsp $newname; \\\n",
"\t\tchmod $mode $newname; \\\n";
}
elsif ($class eq 'm4') {
my $oldname = $dbname;
$dbname =~ s/\.mc$/\.cf/;
$newname =~ s/\.mc$/\.cf\.new/;
my $lead = "\t";
my $leadh = "\t\@";
my $lead2 = "\t\t";
my $trail = "";
if ($entry eq 'submit.cf') {
$lead = "\t\t";
$leadh = "\t\t";
$lead2 = "\t\t\t";
$trail = "\\";
};
print $ofh
"\t\@rm -f ${dbname}.errors\n";
print $ofh
"\t\@if [ -f /usr/share/sendmail/cf/feature/msp.m4 ]; ",
"then \\\n" if ($entry eq 'submit.cf');
print $ofh
"${leadh}m4 $file > $newname \\\n",
"${lead2}2> ${dbname}.errors || true; $trail\n",
"${leadh}echo \"### ${oldname} ###\" \\\n",
"${lead2}>> $newname; $trail\n",
"${leadh}sed -e 's/^/# /' $file \\\n",
"${lead2}>> $newname; $trail\n",
"${leadh}chown $uid:smmsp $newname; $trail\n",
"${leadh}chmod $mode $newname; $trail\n",
"${leadh}mv -f $newname $dbname; $trail\n",
"${leadh}if [ -s ${dbname}.errors ]; then \\\n",
"${lead2}chown $uid:smmsp ${dbname}.errors; \\\n",
"${lead2}cat ${dbname}.errors; \\\n",
"${lead}else \\\n",
"${lead2}rm -f ${dbname}.errors; \\\n",
"${lead2}fi; $trail\n";
print $ofh
"${lead}fi;\n" if ($entry eq 'submit.cf');
};
};
if ($class eq 'newaliases'
or $class eq 'parse_mc'
or $class eq 'update_conf'
or $class eq 'update_mk'
or $class eq 'update_auth'
or $class eq 'update_tls'
or $class eq 'btree' or $class eq 'hash'
) {
print $ofh "\t\tfi;\n";
};
};
};
#
#------------------------------------------------------------------------------
# Write Makefile file targets (create any missing files)
#------------------------------------------------------------------------------
sub write_files {
my ($ofh) = @_;
print $ofh <<"EOT";
#
# Individual file targets - create any requisite files
#
EOT
%created_files = ();
foreach my $entry (@created_dbs) {
my ($class, $flags, $files, $options) =
&Parse_mc::entry_dbs($entry);
# These are done above...
next if ($entry eq 'databases'
or $entry eq 'auth'
or $entry eq 'tls'
or $entry eq 'Makefile'
or $entry eq 'include'
);
foreach my $file (@{$files}) {
next if ( $file eq '-'
or exists($created_files{$file}) );
$created_files{$file} = '';
my $uid = 'root';
$uid = 'smmta' if ($entry eq 'authinfo'
or $entry eq 'access_db'
or $entry eq 'QUEUE_GROUP'
or $class eq 'newaliases');
my $mode = '0644';
$mode = '0640' if ($entry eq 'authinfo'
or $entry eq 'access_db');
$mode = '0640' if ($class eq 'btree' or $class eq 'hash');
$mode = '0644' if ($class eq 'm4');
$mode = '0754' if ($class eq 'update_mk');
$mode = '02750' if ($entry eq 'QUEUE_GROUP');
print $ofh "\n$file:\n",
"\t\@echo 'Creating $file';\n";
if ($entry eq 'QUEUE_GROUP') {
print $ofh "\t\@install -d",
" -o $uid -g smmsp -m $mode $file;\n",
"\t\@chown $uid:smmsp $file;\n",
"\t\@chmod $mode $file;\n";
}
elsif ($entry eq 'sendmail.cf') {
print $ofh "\t\@/usr/sbin/sendmailconfig",
" --no-reload;\n";
}
elsif ($entry eq 'submit.cf') {
print $ofh "\t\@cp /usr/share/sendmail/",
"cf/debian/submit.mc \\\n",
"\t\t$file;\n",
"\t\@chown $uid:smmsp $file;\n",
"\t\@chmod $mode $file;\n";
}
elsif ($entry eq 'access_db') {
print $ofh "\t\@cp /usr/share/sendmail/",
"examples/db/access \\\n",
"\t\t$file;\n",
"\t\@chown $uid:smmsp $file;\n",
"\t\@chmod $mode $file;\n";
}
elsif ($file eq '/etc/mail/aliases') {
print $ofh "\t\@ln -s ../aliases /etc/mail/aliases\n";
}
elsif ($class eq 'update_conf'
or $class eq 'update_auth'
or $class eq 'update_tls') {
print $ofh "\t\@/usr/share/sendmail/${class};\n";
}
# FIXME: come back and create real data
# where needed (local-host-names, etc)
elsif (index($flags, '-o') == -1) {
print $ofh "\t\@touch $file;\n",
"\t\@chown $uid:smmsp $file;\n",
"\t\@chmod $mode $file;\n";
}
elsif (index($flags, '-o') != -1) {
print $ofh "\t# Optional file...\n";
};
};
};
};
#
#------------------------------------------------------------------------------
# Write Makefile footer
#------------------------------------------------------------------------------
sub write_footer {
my ($ofh) = @_;
print $ofh <<"EOT";
#
# FORCE target, allow one to override dependancies
#
.PHONY: FORCE
FORCE: ;
EOT
};