#!/usr/bin/env perl # -------------------------------------------------- # update-internaldomains - atomically update global # whitelist of internal # domains # # Author - Jesse D. Guardiani # Created - 03/12/03 # Modified - 03/18/03 # -------------------------------------------------- # See 'update-internaldomains -h' for Usage # instructions. # # ChangeLog # --------- # # 03/18/03 - JDG # -------------- # - Added Usage text above. # - Made a few clarifications to the Usage() text # - Added the -a option # - Added Copyright and GNU statement # # 03/12/03 - JDG # -------------- # - Created # -------------------------------------------------- # Copyright (C) 2003 Jesse D. Guardiani # # This file is part of TMDA. # # TMDA is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. A copy of this license should # be included in the file COPYING. # # TMDA is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # # You should have received a copy of the GNU General Public License # along with TMDA; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # -------------------------------------------------- use strict; # ----------------------------------- # GLOBALS # ----------------------------------- my $VERSION="0.001"; # Version number of this script my $debug=0; # We default to non-debug mode my $path_to_whitelist=""; # Path (relative or full) to whitelist my $do_combine=0; # boolean flag that specifies whether or not the output of $command should # be combined with the contents of the file specified by -a my $add_to_whitelist="addto-whitelist"; # Name of the file whos contents will be combined with $command's output # if the file "$base_whitelist_dir/$add_to_whitelist" exists. my $command=""; # Command to execute that will return a list of internal domains, one per line. my $base_whitelist_dir=""; # Path to the whitelist's base directory, without a trailing slash. my $tmp_file=""; # relative or full path to temporary file my $owner="vpopmail"; # file owner. This is a candidate for a command line option someday. my $last_error=""; # Last Error code returned by back-tick called shell command # ----------------------------------- # usage() function # ----------------------------------- sub usage() { print STDERR << "EOF"; ------------------------------------------------------------------------ usage: update-internaldomains [-dh] [-a combine-file] -f whitelist -s command ------------------------------------------------------------------------ Version: $VERSION Summary: update-internaldomains is intended to be run from cron every fifteen minutes or so. -f and -s are required options. This script was originally written to retrieve a list of domains from vpopmail's 'vdominfo -n' command. However, it can be used with ANY command that returns a list of domains in the correct format (See -s syntax below). Possible -s commands might include: Billing apps, database retrieval scripts, radius query scripts, etc... How it works: update-internaldomains executes the command specified by -s and saves it's output to a temporary file in the same directory as the file specified by -f. update-internaldomains then compares the whitelist specified by -f with the temp file it just created using 'diff -q'. If changes are found, update-internaldomains atomically replaces the whitelist specified by -f with the temporary file using 'mv'. NOTE: The 'mv' operation is only atomic if your system has a POSIX rename() function, and the 'mv' command makes use of it to overwrite single files. Command Line Options: -a combine-file If specified, combine-file is the full path to a flat file containing domain names, one per line, which you wish to COMBINE with the output of '-s command' BEFORE comparing and/or updating the whitelist specified by -f. -d debug mode. -f whitelist This is the path to the whitelist you wish to write your internal domain list to. -s command command you wish to run that will return a raw list of domains, one per line, in the following format: domain1.com domain2.com domain3.org etc... -h print this usage report. Example: update-internaldomains -f "/usr/local/vpopmail/.tmda/internaldomains" -s "/usr/local/vpopmail/bin/vdominfo -n" Return Codes: If update-internaldomains can not write to the directory in which the file specified by -f resides, it exits nonzero. If update-internaldomains can not execute the command specified by -s, it immediately exits nonzero. If the 'mv' fails, update-internaldomains immediately exits nonzero. If anything else that update-internaldomains tries to execute fails, update-internaldomains will immediately exit nonzero. ------------------------------------------------------------------------\n EOF } # ----------------------------------- # General Functions # ----------------------------------- # save the last exit status sub saveexitstatus() { # Get exit status from last command. $last_error = $? >> 8; } # Get the last exit status sub getexitstatus() { # return exit status from last command. return $last_error; } # Print error message if bad exit status sub dieifbadexit( $$ ) { my $badstatus=$_[0]; # the "bad" exit status my $message=$_[1]; # the message to print before exiting if the last exit status was "bad" # Check exit status. if ( getexitstatus() == $badstatus and getexitstatus() ne "") { print STDERR ($message); exit 1; } } # Print error message if bad exit status sub dieifnotgoodexit( $$ ) { my $goodstatus=$_[0]; # the "good" exit status my $message=$_[1]; # the message to print before exiting if the last exit status was not the "good" exit status # Check exit status. if ( getexitstatus() != $goodstatus and getexitstatus() ne "") { print STDERR ($message); exit 1; } } # If debugging is turned on, print the message sub ifdebugprint( $ ) { my $message=$_[0]; # the message to print # print debug info if ($debug) { print STDERR ($message); } } # ----------------------------------- # Handle command line arguments # ----------------------------------- # Import Getopt::Std module use Getopt::Std; # we only take one option: -f # -f has a single argument. my $opt_string = 'hda:f:s:'; # get command line flags. If we recieve none, then we attempt to use defaults. getopts( "$opt_string", \my %opt ); # print usage message if -h specified usage() and exit if $opt{h}; # setup debug mode $debug = 1 if $opt{d}; print STDERR "Debugging mode ON.\n" if $debug; # set appropriate variables associated with -a if ($opt{a}) { # if flat file does not exist, create it if ( ! -e $opt{a} ) { `touch "$opt{a}"`; # Get any bad exit status from 'rm'. saveexitstatus(); # Check exit status. dieifnotgoodexit(0, "\nError: 'touch \"$opt{a}\"' failed!\n"); # This code is only executed if the flat file was successfully created. ifdebugprint("whitelist created: $opt{a}\n"); } die "$opt{a} is not readable." unless (-r $opt{a}); $add_to_whitelist="$opt{a}"; $do_combine=1; } # print debug info ifdebugprint("\$add_to_whitelist=$add_to_whitelist\n"); ifdebugprint("\$do_combine=$do_combine\n"); # do checks on whitelist file and set $path_to_whitelist variable if ($opt{f}) { if ($opt{f} == 1) { die "\n -f requires an argument."; } # if whitelist does not exist, create it if ( ! -e $opt{f} ) { `touch "$opt{f}"`; # Get any bad exit status from 'rm'. saveexitstatus(); # Check exit status. dieifnotgoodexit(0, "\nError: 'touch \"$opt{f}\"' failed!\n"); # This code is only executed if the whitelist file was successfully created. ifdebugprint("whitelist created: $opt{f}\n"); } die "$opt{f} is not readable and writable." unless (-r $opt{f} and -w $opt{f}); $path_to_whitelist="$opt{f}"; # print debug info ifdebugprint("\$path_to_whitelist=$path_to_whitelist\n"); } else { usage(); exit 1; } # do checks on command file and set $command variable if ($opt{s}) { if ($opt{s} == 1) { die "\n -s requires an argument."; } # die "$opt{s} is not executable." unless (-x $opt{s}); $command="$opt{s}"; # print debug info ifdebugprint("\$command=$command\n"); } else { usage(); exit 1; } # ----------------------------------- # Parse $path_to_whitelist for # directory path. # ----------------------------------- # get directory that whitelist lives in, then set $tmp_file variable my $last_slash_loc = rindex ("$path_to_whitelist","/"); # if $path_to_whitelist contains JUST a filename and no path if ($last_slash_loc == -1) { $base_whitelist_dir = "."; } else { $base_whitelist_dir = substr ("$path_to_whitelist", 0, $last_slash_loc); } # build temp file path. $tmp_file = $base_whitelist_dir . "/tmp"; # print debug info ifdebugprint("\$tmp_file=$tmp_file\n"); # ----------------------------------- # Get command output # ----------------------------------- # remove temp file if it exists if ( -e "$tmp_file" ) { # print debug info ifdebugprint("'$tmp_file' exists.\n"); `rm "$tmp_file"`; saveexitstatus(); dieifnotgoodexit(0, "\"rm '$tmp_file'\" failed!"); # print debug info ifdebugprint("\"rm '$tmp_file'\" successful!\n"); } # Get any bad exit status from 'rm'. saveexitstatus(); # Check exit status. dieifnotgoodexit(0, "\nError: 'rm' failed to remove temp file!\n"); if ( -r "$add_to_whitelist" and $do_combine) { # define the string we wish to execute my $execstring="$command | cat - \"$add_to_whitelist\" | sort | uniq > \"$tmp_file\""; # execute our command pipe. `$execstring`; # Get any bad exit status from execution. saveexitstatus(); # Check exit status and print error and exit if bad exit status found. dieifnotgoodexit(0, "\nError: `$execstring` failed!\n"); } else { # define the string we wish to execute my $execstring="$command | sort | uniq > \"$tmp_file\""; # execute our command pipe. `$execstring`; # Get any bad exit status from execution. saveexitstatus(); # Check exit status. dieifnotgoodexit(0, "\nError: `$execstring` failed!\n"); } # ----------------------------------- # Use 'diff -q' to compare the # contents of our temp file with the # contents of $path_to_whitelist # ----------------------------------- `diff -q "$tmp_file" "$path_to_whitelist"`; # Get any bad exit status from execution. saveexitstatus(); #If 'diff -q' returns with an 'exit 2', then there has been an error. dieifbadexit(2, "\nError: \"diff -q '$tmp_file' '$path_to_whitelist'\" failed!\n"); # ----------------------------------- # If the contents differ, then # atomically update $path_to_whitelist # Otherwise, simply 'exit 0;' # ----------------------------------- #If 'diff -q' returns with an 'exit 1', the files do NOT match. if ( getexitstatus() == 1 and getexitstatus() ne "") { # atomically overwrite $path_to_whitelist with $tmp_file `mv '$tmp_file' '$path_to_whitelist'`; # Get any bad exit status from execution. saveexitstatus(); #If 'mv' returns nonzero, there was an error dieifnotgoodexit(0, "\nError: \"mv '$tmp_file' '$path_to_whitelist'\" failed!\n"); # This point is only reached if 'mv' succeeded ifdebugprint("\"mv '$tmp_file' '$path_to_whitelist'\" successful!\n"); } else { # print debug info ifdebugprint("\"diff -q '$tmp_file' '$path_to_whitelist'\" indicates no update is necessary!\n"); } # ----------------------------------- # Make sure the files we just touched # are owned by $owner # ----------------------------------- ifdebugprint("executing 'chown $owner \"$base_whitelist_dir\"/*'"); `chown $owner "$base_whitelist_dir"/*`; saveexitstatus(); dieifnotgoodexit(0,"'chown $owner \"$base_whitelist_dir\"/*' failed!"); ifdebugprint("exiting happy.\n"); # If execution reaches this point, then chances are that all is well. exit 0;