#!/usr/bin/perl -w #------------------------------------------------------------------------ # # $Sendmail: update_mk,v @sm_version@ @sm_date@ @sm_time@ cowboy Exp $ # # Create /etc/mail/Makefile for Debian Sendmail databases # # Copyright 1998-@SM_CPYRT@ 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,'/')), "@datadir@/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 = '@sm_version@'; $main::program_date = '@sm_date@ @sm_time@ 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 = "@sysconfdir@/mail/Makefile"; my $Conffile = "@sysconfdir@/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 = "@localstatedir@/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-@SM_CPYRT@ 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 @sysconfdir@/mail && make)\` ##### B) \`make -f ${Makefile}\` ##### C) \`${Makefile}\` ##### 2) Force update of - Add to one of the ##### prior commands ie, \`${Makefile} access\`. Most ##### any reasonable value is accepted. ##### #################################################################### SHELL=/bin/sh # # targets that will be routed to the @sysconfdir@/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 @sysconfdir@/init.d/sendmail script # .PHONY: \$(INIT) \$(INIT): FORCE @sysconfdir@/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 = "@sysconfdir@/mail/$entry"; } elsif ($class eq 'update_conf') { $dbname = "@sysconfdir@/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 \@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 ", "\`@sysconfdir@/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, "@sysconfdir@/mail/$entry"; } elsif ($class eq 'update_conf') { push @smdbs, "@sysconfdir@/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 @sbindir@/makemap ]; then \\ @sbindir@/makemap hash \$\@.new.db < \$<; \\ chown root:smmsp \$\@.new.db; \\ chmod 0640 \$\@.new.db; \\ mv -f \$\@.new.db \$\@; \\ fi; EOT # Spew primary target of 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 = "@sysconfdir@/mail/$entry"; } elsif ($class eq 'update_conf') { $dbname = "@sysconfdir@/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 @sbindir@/sendmail ]; then \\\n"; print $ofh "\t\t@sbindir@/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 @datadir@/sendmail/${class} ]; then \\\n"; print $ofh "\t\t@datadir@/sendmail/${class} || true; \\\n" } elsif ($class eq 'btree' or $class eq 'hash') { print $ofh "\t\@if [ -x @sbindir@/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@sbindir@/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@sbindir@/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 = "@sysconfdir@/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 @datadir@/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\@@sbindir@/sendmailconfig", " --no-reload;\n"; } elsif ($entry eq 'submit.cf') { print $ofh "\t\@cp @datadir@/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 @datadir@/sendmail/", "examples/db/access \\\n", "\t\t$file;\n", "\t\@chown $uid:smmsp $file;\n", "\t\@chmod $mode $file;\n"; } elsif ($file eq '@sysconfdir@/mail/aliases') { print $ofh "\t\@ln -s ../aliases @sysconfdir@/mail/aliases\n"; } elsif ($class eq 'update_conf' or $class eq 'update_auth' or $class eq 'update_tls') { print $ofh "\t\@@datadir@/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 };