#!/usr/bin/perl -w # # SCCS information: # @(#) File: ftpuploads.pl # @(#) Version: 1.18 # @(#) SCCS Base: ./SCCS/s.ftpuploads.pl # @(#) Updated on: 06/04/22 at 11:08:44 # @(#) Obtained on: 06/04/22 at 11:14:55 # # Purpose: # Monitor the incoming ftp traffic and transmit the files of interest # to the relevant production server. Direct access to production # servers is not given to outsiders. # # Description: # Monitor the ftplog which is in wuftp format. Ignore files that # are outgoing, done by anonymous login, incomplete transfers, or # in directories not of interest. For the directories of interest, # transmit the file to the mapped production server using ftp and # on successful ftp, move the file to an archive directory. # Log the actions in a log file per purpose. # The log files and archive directories are created per day, so # that their cleanup is easy. # This program replaces a host of cron jobs running earlier, # improves the reaction time, and processes only completely # transferred files. # # How to Use: # Define parameters in /opt/TouchBase/config/ftptargets.txt and # /opt/TouchBase/config/ftpdirmap.txt files. # Run the program as a background job, redirecting its output. # This job is a daemon. It does not exit. It must always be # running. # use strict ; use Env ; # To access environment variables use File::Path ; # To run 'mkpath' use Getopt::EvaP; # For processing command line parameters use Data::Dumper ; # Used only for debugging statements use Net::FTP; # Perl Ftp Module for transferring file use Shell qw(mv) ; # Invoke shell command seamlessly use sigtrap qw(die normal-signals) ; # Signal handling use XML::Simple ; # XML parsing sub main() ; # Main sub funcCommand($$) ; # Run specified commands sub funcLHOpush($$$) ; # Process files at the LHO servers sub funcTransmit($$$) ; # Ftp files from one server to another sub getLine($$) ; # Get line number to read from ftp log sub initialize() ; # Initialization sub logError($) ; # Log error sub mainloop() ; # The main loop. sub move($$$) ; # To archive the process files sub openConnection($) ; # Open an ftp connection sub processFile($$) ; # Process one incoming file sub processArgv() ; # To process the command line arguments sub readDirMap($$) ; # Gead file which holds directory mapping sub readNoProcess($) ; # Read file that hold patterns sub readTargets($$) ; # Read file which holds targets information sub readUserDetails($) ; # To determine 'ftparea' of user sub sigHandler() ; # Interrupt handler sub timestamp() ; # Nicely formatted date and time stamp sub today() ; # Today's date in YYYYMMDD format sub validConnection($) ; # Checks that a ftp connection is still valid sub windup() ; # Clean up routine sub writeRestart() ; # write in restart file sub ERROR { -1 } ; sub FALSE { 0 } ; # This is like #define of C sub TRUE { 1 } ; # TRUE and FALSE are used for booleans sub RESTARTMAIN { 2 } ; sub REREADLOG { 3 } ; my %cmdarg ; # Hash which stores the command line arguments my $currPos ; # Current line number in logfile my $debug = 1 ; # Debug level my $debugFile ; # Debug file name my %dirinfo = () ; # Information about directory mapping my $error = FALSE ; # Global flag that error has occurred my $errorFile = "PROGRAM.error.txt" ; my $ftpLog ; # Path of the ftp log file to monitor my $ftpDirMap ; # Holds path of file which maps targets to directories my $ftpTargets ; # Holds path of file which has info on target my $host ; # Holds hostname of user machine my $lastReadDatetime ; # Date of last incoming file processed by program my $lastReadDatetimeSno ; # Serial numbser of file with the same last date my %log ; # Information about log files my $noProcess ; # File with patterns my @MM ; # Holds info on command line parameters my %month ; # Translates month name to month number my @months = qw( jan feb mar apr may jun jul aug sep oct nov dec) ; my @PDT ; # Holds default values of command line parameters my $osType ; # The operating system on which the program is run my $openLog ; # Flag set when log is to be opened my @pattern = () ; # Hash with patterns of directories not to processed my $prodMode ; # Indicates mode of running of program my $resetInterval ; # Idle interval my $restartFile = "PROGRAM.restart" ; my $runMode ; # Holds the program execution mode my %target = () ; # Information about ftp target machines my $tempPath ; # Holds the path of the temporary files my $user ; # Holds logname of user my %failed = () ; # Hash for holding info abt targets with which ftp # connection is failed. my $failedtarget=''; # target name with which ftp connection is failed my @weekdays # Name for weekday. Used for logs = qw( Sun Mon Tue Wed Thu Fri Sat ) ; my %notArchived ; # Hash to store inforamation on files that # have been processed but not archived @PDT = split /\n/, <$debugFile") || die "Unable to open debug file.\n" ; select DEBUGFILE ; $| = 1 ; select STDOUT ; } $errorFile = "$tempPath/$errorFile" ; open (ERRORFILE, ">$errorFile") || die "Unable to open error file.\n" ; select ERRORFILE; $| = 1 ; select STDOUT ; ($prodMode == 0) ? ($runMode = "TEST") : ($runMode = "PROD") ; print DEBUGFILE "Program is running in $runMode mode.\n" if $debug ; print "Program running in $runMode mode.\n" ; print DEBUGFILE "Restart file = $restartFile\n" if $debug ; print DEBUGFILE "Ftp DirMap file = $ftpDirMap\n" if $debug ; print DEBUGFILE "Ftp Targets file = $ftpTargets\n" if $debug ; print DEBUGFILE "No Process file = $noProcess\n" if $debug ; # Checking the OS type to decide # the ftp log file to use $osType = $^O ; chomp $osType ; if ( $osType =~ /^hpux$/i ) { $ftpLog = "/var/adm/syslog/xferlog" ; } elsif ( $osType =~ /^aix$/i ) { $ftpLog = "/var/adm/syslog/ftp.log" ; } else { print DEBUGFILE "OS type ($osType) no recognized. Quitting.\n" if $debug ; print "OS type ($osType) not recognized\n" ; exit 1 ; } print DEBUGFILE "OS Type = $osType\n" if $debug ; print DEBUGFILE "FTP Log = $ftpLog\n" if $debug ; my $mm = 1 ; for my $mon (@months) { # Populate hash to get month number $month{lc $mon} = sprintf "%02d", $mm ; # from 3-letter month name $mm++ ; } # Set log open flag to false $openLog = TRUE ; # Set reset interval to 10 min $resetInterval = 600 ; # Read data file and populate %target readTargets($ftpTargets, "all") ; print DEBUGFILE "Targets ($ftpTargets) read.\n" if $debug > 1 ; # Read data file and populate %dirinfo readDirmap($ftpDirMap, "all") ; print DEBUGFILE "Dirmap ($ftpDirMap) read.\n" if $debug > 1 ; # Read no process file readNoProcess($noProcess) ; print DEBUGFILE "File with directories ($noProcess), that are not to be processed, read.\n" if $debug > 1 ; # Resetting notArchived flag $notArchived{'FLAG'} = FALSE ; # Check the restart point if ( ! -e $restartFile ) { # No last restart point print STDERR "Restart file, $restartFile, does not exist\n" ; exit 1 ; } elsif ( ! -r $restartFile ) { print STDERR "Can't read $restartFile\n" ; } # Entry in the restart file is in the format # # YYYYMMDD_HHMMSS dd # '--'\/\/ \/\/\/ \/ # | ' ' ' ' ' '-----> Count of files with same timestamp # ' ' ' ' ' '-----------> Seconds # ' ' ' ' '-------------> Minutes # ' ' ' '---------------> Hours (24 Hrs) # ' ' '------------------> Day # ' '--------------------> Month # '----------------------> Year # open( RESTART, "$restartFile" ) or die "Can't open $restartFile" ; my $restartPoint = ; close RESTART ; if ( ! defined $restartPoint || $restartPoint !~ /\S/ ) { $restartPoint = '' ; $lastReadDatetime = '' ; $lastReadDatetimeSno = '' ; } if ( $restartPoint !~ /^(\d{8}_\d{6})\s+(\d+)/ ) { print STDERR "Restart file is not in proper format\n" ; exit 1 ; } else { $lastReadDatetime = $1 ; $lastReadDatetimeSno = $2 ; } print DEBUGFILE "From restart file [$lastReadDatetime][$lastReadDatetimeSno]\n" if $debug ; open( RESTART, ">$restartFile" ) or die "Can't open $restartFile" ; select RESTART ; $| = 1 ; select STDOUT ; seek RESTART, 0, 0 ; # Rewrite the file. printf RESTART "$lastReadDatetime\t%5d\n", $lastReadDatetimeSno ; # Write the last read date and time. The length must be constant. } #-------------------------------------------------------------------- # Trnsmit the required file. # Input: # Output: None # sub funcTransmit($$$) { my $file = $_[0] ; my $filesize = $_[1] ; my $d = $_[2] ; my $result = FALSE ; my $dir = $d->{DIR} ; my $tname = $d->{TARGET} ; # Target name, e.g. sbib24 my $t = $target{$tname} if defined $target{$tname} ; # Hash reference for target information my $s = $t->{SESSION} if defined $t ; # Reference to ftp session created for target my $logfile = $d->{LOGFILE} ; if ( ! defined $t ) { print DEBUGFILE "\tTarget not defined for $dir/$file .\n" if $debug ; logError "Target not defined" ; return ERROR ; } $s = openConnection($t) unless ( $s && validConnection($s) ) ; if ( $failed{$tname} && $s ) { $failedtarget = $tname ; return RESTARTMAIN ; } unless ( $s ) { logError( "Can't open ftp session with $tname this time" ) ; print {$log{$logfile}{FH}} timestamp() . "\t$runMode\tFailed\t$file\tCould not open ftp session for $tname\n" if ( $log{$logfile}{FH} ) ; if( ! defined $failed{$tname} ) { $failedtarget = $tname ; $failed{uc $failedtarget} = $failedtarget ; $failed{$failedtarget}{TIME} = $lastReadDatetime ; $failed{$failedtarget}{SNO} = ($lastReadDatetimeSno-1) ; $failedtarget = undef ; } return ERROR ; } print DEBUGFILE "\tHave ftp connection to $tname\n" if $debug ; # Check that target directory exists and is reachable my $targetDir = $d->{TARGET_DIR} ; if ( ! $s->cwd( $targetDir ) ) { logError "Target directory $targetDir is not reachable" ; print {$log{$logfile}{FH}} timestamp() . "\t$runMode\tFailed\t$file\tCan't go to $targetDir on $tname\n" if ( $log{$logfile}{FH} ) ; return ERROR ; } print DEBUGFILE "\tChanged dir to $targetDir\n" if $debug > 1; # Moving file to required target. if ( ! $s->put( "$dir/$file" ) ) { logError "Error in putting file $dir/$file to $tname at $targetDir" ; print {$log{$logfile}{FH}} timestamp() . "\t$runMode\tFailed\t$file\tFTP transfer failed midway\n" if ( $log{$logfile}{FH} ) ; return ERROR ; } print DEBUGFILE "\tTransferred $file to $targetDir\n" if $debug ; print {$log{$logfile}{FH}} timestamp() . "\t$runMode\tSent\t$file\t$filesize\n" if ( $log{$logfile}{FH} ) ; # Passed all error checks. Must be successful. $result = TRUE ; writeRestart() ; if ( $t->{CHMOD} ) { print DEBUGFILE "\tGoing to execute chmod $t->{CHMOD} $file\n" if $debug > 1 ; my $retval = $s->site("chmod $t->{CHMOD} $file") ; print DEBUGFILE "\tchmod returned $retval\n" if $debug > 1 ; # Return value is not predictable here. Hope for the best. } return $result ; } #------------------------------------------------------------------ # Run the command in the file # Input: # Output: None # sub funcCommand($$) { my $file = $_[0] ; my $d = $_[1] ; my $result = FALSE ; my $arg1 = '' ; my $arg2 = '' ; print DEBUGFILE "Processing command file [$file]\n" if $debug ; my $dir = $d->{DIR} ; my $logfile = $d->{LOGFILE} ; my $today = today() ; open( CMDFILE, "$dir/$file" ) || die "Unable to open $file for reading.\n" ; my $cmd = ; chomp $cmd ; close CMDFILE ; if ( $cmd !~ /^\s*(\S+)\s+(.+)\s*$/ ) { print STDERR "Command file is not in proper format.\n" ; return ERROR ; } else { $arg1 = $1 ; $arg2 = $2 ; } print DEBUGFILE "Command arguments are [$arg1][$arg2]\n" if $debug > 1 ; if ( $arg1 =~ /^READTARGETS$/i ) { print "Re-reading Target file [$ftpTargets]\n" ; readTargets($ftpTargets,$arg2) ; writeRestart() ; $result = TRUE ; } elsif ( $arg1 =~ /^READDIRMAP$/i ) { print "Re-reading Dirmap file [$ftpDirMap]\n" ; readDirmap($ftpDirMap,$arg2) ; writeRestart() ; $result = TRUE ; } elsif ( $arg1 =~ /^RESTART$/i ) { if ( $arg2 =~ /^(\d{8}_\d{6})$/ ) { print "Restart command valiadated. Restarting..\n" ; $lastReadDatetime = $1 ; $lastReadDatetimeSno = 1 ; my $archiveDir = $d->{ARCHIVE_DIR} ; $archiveDir =~ s/YYYYMMDD/$today/ ; mkdir $archiveDir unless -d $archiveDir ; move( $file, $archiveDir, $d ) ; # status not important. restart mandatory. return RESTARTMAIN ; } else { print DEBUGFILE "\tArguments specified are incorrect.\n" if $debug ; logError "Second argument is not correct. Cannot execute [$cmd]" ; return ERROR ; } } elsif ( $arg1 =~ /^READNOPROCESS$/i ) { if ( $arg2 =~ /^reset$/i ) { print "Resetting patterns.Re-reading NoProcess file [$noProcess]\n" ; @pattern = () ; print DEBUGFILE "\tPatterns (\@pattern) reset.\n" if $debug > 1 ; readNoProcess($noProcess) ; } elsif ( (-e $arg2) and (-r $arg2) ) { print DEBUGFILE "\t[$arg2] is the noProcess file.\n" if $debug > 1 ; print "Re-reading NoProcess file [$arg2]\n" ; readNoProcess($arg2) ; } else { print DEBUGFILE "\tArgument [$arg2] is invalid.\n" if $debug ; logError "Second argument is incorrect. Cannot execute [$cmd]" ; return ERROR ; } writeRestart() ; $result = TRUE ; } return $result ; } #------------------------------------------------------------------ # Process file (at the LHO) and push to branch # Input: # Output: None # sub funcLHOpush($$$) { my $dir = $_[0] ; my $file = $_[1] ; my $d = $_[2] ; my $sendLine ; print DEBUGFILE "\tProcessing file for LHO [$dir/$file].\n" if $debug > 1 ; my $logfile = $d->{LOGFILE} ; my $spool = $d->{SPOOL_DIR} ; my $spoolFile = "$spool/ftplist.due" ; my $ftparea = $d->{FTPAREA} ; my ($branchCode, $relPath) ; if ( $dir =~ /^($ftparea)\/locations\/(b[^\/]+)\/(.*)$/ ) { $branchCode = $2 ; $relPath = $3 ; } else { print DEBUGFILE "\tDir pattern [$dir] is not in the correct format.\n" if $debug ; return FALSE ; } my $source = "$relPath/$file" ; my $target = "$relPath/$file" ; $sendLine = "$branchCode\t$source\t$target" ; print DEBUGFILE "\tOpening spool file [$spoolFile].\n" if $debug > 1 ; if ( !(open (FTPLIST, ">>$spoolFile")) ) { print DEBUGFILE "\tUnable to open spoolfile [$spoolFile].\n" if $debug ; logError "Unable to open spoolfile \'$spoolFile\'" ; print {$log{$logfile}{FH}} timestamp() . "\t$runMode\tError\t$file\t$spoolFile not accessible for $branchCode\n" if ( $log{$logfile}{FH} ) ; return ERROR ; } select FTPLIST ; $| = 1 ; select STDOUT ; print DEBUGFILE "\tEntry in spoolFile [$sendLine].\n" if $debug > 1 ; if ( ! print FTPLIST "$sendLine\n" ) { print DEBUGFILE "\tUnable to write into file [$spoolFile].\n" if $debug > 1 ; logError "Unable to write into '\$spoolFile\'" ; print {$log{$logfile}{FH}} timestamp() . "\t$runMode\tError\t$file\t$spoolFile not writable for $branchCode\n" if ( $log{$logfile}{FH} ) ; close FTPLIST ; return ERROR ; } close FTPLIST ; writeRestart() ; print {$log{$logfile}{FH}} timestamp() . "\t$runMode\tDone\t$file\tReport processed for $branchCode\n" if ( $log{$logfile}{FH} ) ; return TRUE ; } #-------------------------------------------------------------------- # Read line fron ftp log file # Input : Date-Time, Count # Output : Position in bytes # sub getLine($$) { my $datetime = $_[0] ; my $datetimeSno = $_[1] ; # For the format of the entry made in the log file, look at the # example in 'mainloop' # datetime is in the format 'yyyymmdd_hhmmss' $datetime =~ /(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})/ ; # preDate = 'Month' # date = date, single or double digit # postDate = 'Time Year' my $preDate = $months[($2)-1] ; my $date = 0 + $3 ; my $postDate = "$4:$5:$6 $1" ; print DEBUGFILE "Comparing in log file for [$preDate $date $postDate]\n" if $debug ; my $lineCount = 0 ; my $byteCount = 0 ; seek (FTPLOG, 0, 0) ; while () { $lineCount++ ; $byteCount += length($_) ; if ( grep / $preDate ($date|0$date| $date) $postDate /i, $_ ) { my $offset = 1 ; my $logEntry ; while ( $offset < $datetimeSno ) { $logEntry = ; if ( grep / $preDate ($date|0$date| $date) $postDate /i, $logEntry ) { $byteCount += length($logEntry) ; $offset++ ; $lineCount++ ; } else { $datetimeSno = $offset ; last ; } } last ; } else { next ; } } my $lineToRead = $lineCount ; print DEBUGFILE "Line number from which program is reading [$lineToRead]\n" if $debug ; print DEBUGFILE "Position in file is [$byteCount].\n" if $debug > 1 ; return $byteCount ; } #-------------------------------------------------------------------- # Log error in the log file # Input: Error message # Output: None # sub logError($) { my $msg = $_[0] ; if ( ! $error ) { print ERRORFILE timestamp()."\tProgram is running in $runMode mode.\n" ; $error = TRUE ; } print ERRORFILE timestamp()."\t$msg\n" ; } #-------------------------------------------------------------------- # This is the main processing loop. # It waits for files to come into the server through ftp and immediately # moves them to the target server. # It does not return from this function. # sub mainloop() { # Open file and use the filehandle in getline my $logInode = (stat($ftpLog))[1] ; print DEBUGFILE "Inode of ftplog ($ftpLog) is [$logInode]\n" if $debug > 1 ; if ( $openLog ) { print DEBUGFILE "Opening log ($ftpLog).\n" if $debug > 2 ; open( FTPLOG, "$ftpLog") or die "Can not open ftplog" ; $openLog = FALSE ; } if ( ! defined $currPos ) { $currPos = getLine($lastReadDatetime, $lastReadDatetimeSno) ; } seek (FTPLOG, $currPos, 0) ; my $retVal ; my $lastProcessed = time ; print DEBUGFILE "Reading log (from $currPos) for incoming files via ftp\n" if $debug ; while ( (time - $lastProcessed) < $resetInterval ) { while ( ) { $currPos += length($_) ; $lastProcessed = time ; my ($weekday, $mon, $dd, $hms, $yyyy, $duration, $fromIp, $filesize, $dirfile, $xferMode, $splActionFlag, $direction, $realAnon, $user, $service, $authMethod, $authUserId, $lastField ) = split ; # Sample line read from the log file # Mon Apr 18 17:10:47 2005 1 10.0.20.23 1068 /INTERFACES/STEPS/STEPS-OUTGOING-ENCRYPTED/001921000000249210000000024 b _ i r winftpuser ftp 0 * c/60 # Explanation of fields in the log line # Fields unused by ftpd, hence can be ignored # $splActionFlag (always '_') # $service (always 'ftp' ) # $authMethod (always '0' ) # $authUserId (always '*' ) # Obvious fields - One can figure out by looking at sample line # $weekday = Day of the week, 3 character # $mon = Month, 3 character # $dd = Day of the month # $hms = Time, in hour:minute:seconds # $yyyy = Year, 4 digit # $fromIp = IP address of client machine # $filesize = Size of the file in bytes # $dirfile = Full name of the file with absolute path # $user = ftp user id # Other fields # $duration = Duration of transfer in seconds, minimum 1 # $xferMode = 'a' for Ascii, 'b' for Binary # $direction = 'i' for incoming file, 'o' for outgoing file # $realAnon = 'r' for real user, 'a' for anonymous user # $lastField varies depending on OS. # on hpux : $lastFiled indicates the current time in seconds # in linux : 'c' for complete transfer, 'i' for incomplete transfer # However, it is found to be 'c' even when ^C is # pressed in ftp client session and transfer aborted. # May not be reliable. # next unless $direction eq 'i' ; # Consider only incoming files next unless $realAnon eq 'r' ; # Consider only real users if ( $osType =~ /linux/i ) { # Consider only complete transfers # Check valid while running on Linux next unless $lastField eq 'c' ; } $hms =~ s/://g ; # Change from HH:MM:SS to HHMMSS my $mm = $month{lc $mon} ; my $day = sprintf "%02d", $dd ; my $datetime = "$yyyy$mm$day" . "_$hms" ; if ( $datetime eq $lastReadDatetime ) { $lastReadDatetimeSno++ ; } else { $lastReadDatetime = $datetime ; $lastReadDatetimeSno = 1 ; } print DEBUGFILE "Read \'$_" if $debug > 1 ; print DEBUGFILE "Values reset : BytesRead [$currPos] Time [$lastProcessed].\n" if $debug > 2 ; $retVal = processFile($dirfile, $filesize) ; if ( $retVal == ERROR ) { print DEBUGFILE "Error while processing $dirfile\n" if $debug ; logError "Error while processing $dirfile" ; } elsif ( $retVal == FALSE ) { print DEBUGFILE "Not processing $dirfile\n" if $debug ; } elsif ( $retVal == RESTARTMAIN ) { if ( defined $failed{$failedtarget} ) { $lastReadDatetime=$failed{$failedtarget}->{TIME} ; $lastReadDatetimeSno=$failed{$failedtarget}->{SNO} ; $failed{$failedtarget} = undef ; $failedtarget = undef ; } print DEBUGFILE "RESTARTING with [$lastReadDatetime][$lastReadDatetimeSno].\n" if $debug ; return $retVal ; } print DEBUGFILE "Waiting for incoming file via FTP\n" if $debug ; } sleep 1 ; print DEBUGFILE "Checking program idle time.\n" if $debug > 2 ; } print DEBUGFILE "Checking if \'$ftpLog\' is valid.\n" if $debug > 2 ; my $inodeNow = (stat($ftpLog))[1] ; if ( $logInode != $inodeNow ) { $currPos = 0 ; $openLog = TRUE ; print DEBUGFILE "Changed inode value = [$inodeNow]\n" if $debug > 1 ; print DEBUGFILE "Rereading ftp log ($ftpLog) from [$currPos]. Inode value changed.\n" if $debug ; } else { $openLog = FALSE ; } return REREADLOG ; } #-------------------------------------------------------------------- # To archive the processed files # sub move($$$) { my $file = $_[0] ; my $destination = $_[1] ; my $d = $_[2] ; my $result = FALSE ; my $dir = $d->{DIR} ; my $logfile = $d->{LOGFILE} ; mv ( "$dir/$file" , $destination ) ; if ( $? != 0 ) { # Error in move logError "Failed to move $dir/$file to $destination" ; print {$log{$logfile}{FH}} timestamp() . "\t$runMode\tNoArch\t$file\tCould not archive to $destination\n" if ( $log{$logfile}{FH} ) ; print DEBUGFILE "Unable to archive $dir/$file to $destination\n" if $debug ; $result = ERROR ; $notArchived{'FLAG'} = TRUE ; push @{$notArchived{$dir}}, $file ; print DEBUGFILE " Files not archived : " . Dumper(%notArchived) ."\n" if $debug > 1 ; } else { print DEBUGFILE "\tArchived $file to $destination\n" if $debug ; $result=TRUE ; } return $result ; } #-------------------------------------------------------------------- # Open ftp connection for the given target. # Fill the session id in the target structure. # Return session id. # Input: Reference to target hash # Output: Reference to session if opened # undef in cases of any error # sub openConnection($) { my $t = $_[0] ; print DEBUGFILE "\tOpening ftp connection with $t->{IPADDR}:$t->{PORT}\n" if $debug ; #open the Ftp connection to HOST my $ftpSession = Net::FTP->new( $t->{IPADDR}, Port => $t->{PORT}) ; $t->{SESSION} = $ftpSession ; if ( ! $ftpSession ) { logError "Can not open ftp session with $t->{IPADDR}" ; return FALSE ; } # Login as ftp user/password unless ( $ftpSession->login( $t->{USER}, $t->{PWD} ) ) { logError "FTP login failed for $t->{IPADDR} with user = $t->{USER} and password = $t->{PWD}\n" ; return FALSE ; } $ftpSession->binary() ; # Set transfer mode to binary return $ftpSession ; # Return the session } #-------------------------------------------------------------------- # Function to process the command line arguments. # Input: None # Output: None # sub processArgv() { &EvaP( \@PDT, \@MM, \%cmdarg ) ; $debug = $cmdarg{'debug'} ; $debugFile = $cmdarg{'debugfile'} ; $ftpDirMap = $cmdarg{'ftpdirmap'} ; $ftpTargets = $cmdarg{'ftptargets'} ; $noProcess = $cmdarg{'noprocess'} ; $prodMode = $cmdarg{'prodmode'} ; } #-------------------------------------------------------------------- # Process one file that has arrived into the ftp server # Transfer the file to its target ftp server into appropriate directory # Log the action # Input: Incoming filename with absolute path # Size of the file in bytes # Output: TRUE for success # FALSE for failure # sub processFile($$) { my $dirfile = $_[0] ; my $filesize = $_[1] ; my $result = FALSE ; my @actions = () ; # Separate the directory name and the file name $dirfile =~ /(.*)\/(.*)/ ; # Search is greedy. First pattern gets till last slash my $dir = $1 ; my $file = $2 ; my $ext = '' ; $ext = $1 if ( $file =~ /.*\.(.*)/ ) ; # Extract extension from file name my $dirkey = $dir . ":" . $ext ; # Check if dir matches with patterns of directories if ( $#pattern >= 0 ) { foreach ( @pattern ) { if ( $dirkey =~ /.*$_.*/ ) { print DEBUGFILE "Directory [$dir] matches [$_] in \'noProcess\' file.\n" if $debug ; return FALSE ; } } } # Check if dirkey is present. If not, then # check if dirkey without extension exists. # # Also if BASE_DIR is set, check if the directory # specified in the dirmap matches the incoming directory. my $ignoreDir = TRUE ; if (! $dirinfo{$dirkey} ) { # Remove 'ext' $dirkey =~ s/(.*)\:(.*)/$1\:/ ; if (! $dirinfo{$dirkey} ) { # If the BASE_DIR flag is set for any of the valid # directories, check with and without 'ext' for my $dirs ( keys %dirinfo ) { if ( $dirinfo{$dirs}{BASE_DIR} eq "Y" ) { if ( $dir =~ /^$dirinfo{$dirs}{DIR}\/.*$/ ) { $dirkey = $dirinfo{$dirs}{DIR}.":".$ext ; if (! $dirinfo{$dirkey} ) { $dirkey =~ s/(.*)\:(.*)/$1\:/ ; if ( $dirinfo{$dirkey} ) { $ignoreDir = FALSE ; last ; } } else { $ignoreDir = FALSE ; } } } } } else { $ignoreDir = FALSE ; } } else { $ignoreDir = FALSE ; } if ($ignoreDir == TRUE) { print DEBUGFILE "Unexpected incoming ftp $dir/$file\n" if $debug ; return FALSE ; } print DEBUGFILE "DirKey used is [$dirkey].\n" if $debug > 1 ; my $d = $dirinfo{$dirkey} ; # Hash reference for directory informtion print DEBUGFILE "Noticed $dir/$file\n" if $debug ; if ( $d->{ACTION} eq 'IGNORE' ) { print DEBUGFILE "\t$dir/$file to be ignored.\n" if $debug > 1 ; return FALSE ; } print DEBUGFILE "\tProcessing $dir/$file\n" if $debug ; if ( ($notArchived{'FLAG'} == TRUE) && (defined $notArchived{$dir}) ) { if ( grep /$file/, @{$notArchived{$dir}} ) { print DEBUGFILE "\t\'$dir/$file\' processed earlier but not archived.\n" if $debug > 1 ; print DEBUGFILE "\tNot processing in current run.\n" if $debug ; logError "File [$dir/$file] processed earlier but not archived." ; return FALSE ; } } my $today = today() ; # Open a new log file every day my $logfile = $d->{LOGFILE} ; $log{$logfile}{DATE} = "00000000" if (! defined $log{$logfile}{DATE}) ; if ( $log{$logfile}{DATE} ne $today ) { # Date has changedi close $log{$logfile}{FH} if defined $log{$logfile}{FH} ; $log{$logfile}{FILENAME} = $logfile ; $log{$logfile}{FILENAME} =~ s/YYYYMMDD/$today/ ; # Fill today's date at appropriate place # Open the log file for writing if ( ! open( $log{$logfile}{FH}, ">>$log{$logfile}{FILENAME}" ) ) { print STDERR "Can not open log file $log{$logfile}{FILENAME}\n" ; $log{$logfile}{FH} = undef ; $log{$logfile}{DATE} = undef ; } else { select $log{$logfile}{FH} ; $| = 1 ; select STDOUT ; $log{$logfile}{DATE} = $today ; } } push @actions, $d->{ACTION} ; # Checking for existence of file before carrying on. if ( ! -e "$dir/$file" ) { logError "Incoming file not found in directory ($dir/$file)" ; print {$log{$logfile}{FH}} timestamp() . "\t$runMode\tFailed\t$file\tIncoming file not present in directory\n" if ( $log{$logfile}{FH} ) ; return ERROR ; } while (@actions) { my $todo = pop @actions ; if ( $todo =~ /^TRANSMIT$/i) { $result = funcTransmit($file, $filesize, $d) ; return $result if ( $result != TRUE ) ; } elsif ( $todo =~ /^COMMAND$/i) { $result = funcCommand($file, $d) ; return $result if ( $result != TRUE ) ; } elsif ( $todo =~ /^LHOPUSH$/i ) { $result = funcLHOpush($dir, $file, $d) ; return $result if ( $result != TRUE ) ; } else { print DEBUGFILE "Action \'$todo\' not recognized.\n" if $debug ; logError "Action \'$todo\' not recognized" ; return ERROR ; } } if ( ($prodMode == 1) && (defined $d->{ARCHIVE_DIR}) ) { # 1 indicates PROD my $archiveDir = $d->{ARCHIVE_DIR} ; $archiveDir =~ s/YYYYMMDD/$today/ ; # Fill today's date in directory name mkdir $archiveDir unless -d $archiveDir ; # Check if file is alreading present in archive directory. # If file is present, delete file, because 'mv' fails # due to permission conflict. if ( -e "$archiveDir/$file" ) { print DEBUGFILE "\tFile ($file) present in archive directory. Removing old file.\n" if $debug > 1; unlink ("$archiveDir/$file") ; } # Transfer is successful. Now move the file to archive folder $result = move( $file, $archiveDir, $d ) ; } return $result ; } #-------------------------------------------------------------------- # Read the dirmap file # sub readDirmap($) { my $fileDirMap = $_[0] ; my $dirToRead = $_[1] ; # Check if file exists print DEBUGFILE "Checking for file [$fileDirMap]\n" if $debug > 1 ; if ( ! -e $fileDirMap ) { print DEBUGFILE "File \'$fileDirMap\' does not exist.\n" if $debug ; return ; } print DEBUGFILE "Reading directory information from $fileDirMap for \'$dirToRead\'.\n" if $debug > 1 ; unless ( open( DIRMAP, $fileDirMap ) ) { print DEBUGFILE "Can not open $fileDirMap for reading\n" if $debug ; return ; } # Checks for specified action are against the vales # specified below. All actions must have atleast one # value to check for. my %allowedActions = ( transmit => [ "target", "target_dir", "archive_dir" ] , tbpush => [ "client", "appl", "archive_dir" ] , transfer => [ "client", "appl", "archive_dir" ] , command => [ "archive_dir" ] , lhopush => [ "spool_dir", "user_details" ] , ) ; my @validParams = qw( dir ext action target target_dir client appl archive_dir logfile spool_dir user_details base_dir ) ; print DEBUGFILE "Allowed actions are : " . Dumper(%allowedActions) ."\n" if $debug > 1 ; print DEBUGFILE "Valid parameters are : [ @validParams ]\n" if $debug > 1 ; while ( ) { next if /^\s*#/ ; # Skip comment lines next unless /\S/ ; # Skip blank lines chomp ; my $dirError = FALSE ; my @paramLine = split(/\s*\|\s*/); # Separated by pipe character my @checkDir = () ; my %p = () ; # Value of parameters read from a line for my $param (@paramLine) { $param =~ s/\s*$// ; if ( $param =~ /^\s*([A-Za-z0-9_]+)\s*=\s*(.*)/ ) { if ( grep /^$1$/i, @validParams ) { eval '$p{' . uc $1 . '} =' . '$2;' ; print DEBUGFILE "Parameter $1 = $2\n" if $debug > 1 ; } else { print DEBUGFILE "Unrecognised Parameter \'$1\'\n" if $debug ; } } else { logError "Input syntax error in Parameter \'$param\'. It must be in the form key=value" ; die ("Terminating due to syntax error in parameter file"); } } if ( defined $p{DIR} ) { # Continue if expected to read present target next unless ( ($dirToRead =~ /^all$/i) || ($p{DIR} =~ /^$dirToRead$/i) ) ; if ( (defined $p{ACTION}) && (grep /$p{ACTION}/i, keys (%allowedActions)) ) { $p{ACTION} = lc $p{ACTION} ; if (! defined $allowedActions{$p{ACTION}} ) { print DEBUGFILE "\tNo checks specified for action \'$p{ACTION}\'\n." if $debug ; print DEBUGFILE "\tNot reading other values for $p{DIR}.\n" if $debug > 1 ; next ; } @checkDir = @{$allowedActions{$p{ACTION}}} ; print DEBUGFILE "\tChecks for \'$p{ACTION}\' are [@checkDir].\n" if $debug > 1 ; } else { $p{ACTION} = 'IGNORE' ; print DEBUGFILE "\tAction set to \'IGNORE\' for $p{DIR}.\n" if $debug ; print DEBUGFILE "\tNot reading other values for $p{DIR}.\n" if $debug > 1 ; next ; } my $tempVal ; foreach (@checkDir) { $tempVal = uc $_ ; if (! defined $p{$tempVal}) { print DEBUGFILE "\t\'$tempVal\' entry for $p{DIR} not available. Ignoring $p{DIR}\n" if $debug ; $dirError = TRUE ; } } } else { $dirError = TRUE ; } if ( $dirError == TRUE ) { logError "\'$p{DIR}\' did not clear initial checks" ; next ; } unless ($p{LOGFILE}) { # Setting default logfile if not defined print DEBUGFILE "\t Setting default logfile for $p{DIR}\n" if $debug ; $p{LOGFILE} = "$tempPath/default.YYYYMMDD" unless $p{LOGFILE} ; } $p{EXT} = '' unless $p{EXT} ; # Set default value $p{TARGET} = lc $p{TARGET} if defined $p{TARGET} ; $p{BASE_DIR} = "N" if ( ! defined $p{BASE_DIR} ) ; my $dirkey = ( $p{DIR} . ":" . $p{EXT} ) ; # Ignore case in input file # TBD this is not required. Bad case is error anyway $dirinfo{$dirkey} = {} ; $dirinfo{$dirkey}{DIR} = $p{DIR} ; $dirinfo{$dirkey}{EXT} = $p{EXT} ; $dirinfo{$dirkey}{ACTION} = $p{ACTION} ; $dirinfo{$dirkey}{TARGET} = $p{TARGET} ; $dirinfo{$dirkey}{TARGET_DIR} = $p{TARGET_DIR} ; $dirinfo{$dirkey}{ARCHIVE_DIR} = $p{ARCHIVE_DIR} ; $dirinfo{$dirkey}{LOGFILE} = $p{LOGFILE} ; $dirinfo{$dirkey}{SPOOL_DIR} = $p{SPOOL_DIR} ; $dirinfo{$dirkey}{USER_DETAILS} = $p{USER_DETAILS} ; $dirinfo{$dirkey}{BASE_DIR} = $p{BASE_DIR} ; print DEBUGFILE " Values for $dirkey are " . Dumper($dirinfo{$dirkey}) ."\n" if $debug > 1 ; # Cheking if directory information is accurate # Incoming and archive directory are necessary for any action, hence # we dont have to check if they are defined. my $dirCheck = TRUE ; print DEBUGFILE " Checking directory ($dirkey).\n" if $debug ; if ( -d $dirinfo{$dirkey}{DIR} ) { # Check if archive-dir is defined. # And then check for presence if ( defined $dirinfo{$dirkey}{ARCHIVE_DIR} ) { print DEBUGFILE " Checking archive directory \'$dirinfo{$dirkey}{ARCHIVE_DIR}\'\n" if $debug > 1 ; my $archiveDir = $dirinfo{$dirkey}{ARCHIVE_DIR} ; $archiveDir =~ s/\/YYYYMMDD$// ; # Remove the variable date part if (! -d $archiveDir ) { print DEBUGFILE "\tArchive directory \'$dirinfo{$dirkey}{ARCHIVE_DIR}\' not present.\n" if $debug ; mkpath ($archiveDir, 0, 0755) ; print DEBUGFILE "\t Created $archiveDir\n" if $debug > 1 ; } } # Similar check for spool-dir if ( defined $dirinfo{$dirkey}{SPOOL_DIR} ) { print DEBUGFILE " Checking spool directory \'$dirinfo{$dirkey}{SPOOL_DIR}\'\n" if $debug > 1 ; my $spoolDir = $dirinfo{$dirkey}{SPOOL_DIR} ; if (! -d $spoolDir ) { print DEBUGFILE "\tSpool directory \'$spoolDir' not present.\n" if $debug ; logError "Directory $spoolDir not present" ; $dirCheck = FALSE ; } } } else { print DEBUGFILE "\tIncoming directory \'$dirinfo{$dirkey}{DIR}\' not present.\n" if $debug ; logError "Directory $dirinfo{$dirkey}{DIR} not present" ; $dirCheck = FALSE ; } # Reading userdetails, required for TBpush if ( ($dirCheck != FALSE) && (defined $dirinfo{$dirkey}{USER_DETAILS}) ) { print DEBUGFILE " Reading '\$dirinfo{$dirkey}{USER_DETAILS}\'\n" if $debug > 1 ; ($dirCheck, $dirinfo{$dirkey}{FTPAREA}) = readUserDetails($dirinfo{$dirkey}{USER_DETAILS}) ; # Check if the directory is present if ( $dirCheck != FALSE ) { print DEBUGFILE " Checking ftparea \'$dirinfo{$dirkey}{FTPAREA}\'\n" if $debug > 1 ; if ( ! -d $dirinfo{$dirkey}{FTPAREA} ) { print DEBUGFILE "\tFtparea specified \'$dirinfo{$dirkey}{FTPAREA}\' does not exist.\n" if $debug ; logError "Directory $dirinfo{$dirkey}{FTPAREA} not present." ; $dirCheck = FALSE ; } } else { print DEBUGFILE "\tError while reading $dirinfo{$dirkey}{USER_DETAILS}.\n" if $debug ; logError "Error while reading config file \'$dirinfo{$dirkey}{USER_DETAILS}\'" ; $dirCheck = FALSE ; } } if ( $dirCheck == FALSE ) { print DEBUGFILE "REMOVING information relating to $dirkey.\n" if $debug ; delete $dirinfo{$dirkey} ; } else { print DEBUGFILE " Directory \'$dirkey\' validated.\n" if $debug ; } } close DIRMAP ; } #-------------------------------------------------------------------- # Read the noProcess file. # sub readNoProcess($) { my $fileNoProcess = $_[0] ; # Check if file exists. print DEBUGFILE "Checking for file [$fileNoProcess]\n" if $debug > 1 ; if ( ! -e $noProcess ) { print DEBUGFILE " File \'$fileNoProcess\' does not exist.\n" if $debug ; return ; } unless ( open( NOPROCESS, "$fileNoProcess" ) ) { print DEBUGFILE "Can not open $fileNoProcess for reading\n" if $debug ; return ; } print DEBUGFILE "Reading patterns from $fileNoProcess.\n" if $debug > 1 ; unless ( open ( NOPROCESS, "$fileNoProcess" ) ) { print DEBUGFILE "Unable to read $fileNoProcess\n" if $debug ; return ; } while ( ) { next if /^\s*#/ ; # Skip comment lines next unless /\S/ ; # Skip blank lines my $inParamLine = $_ ; s/\s*$// ; # Remove trailing blanks chomp ; $_ =~ /^\s*(.*)\s*$/ ; push @pattern, $1 ; } close NOPROCESS ; print DEBUGFILE "Patterns specified are : [ @pattern ]\n" if $debug ; } #-------------------------------------------------------------------- # Read the ftp targets file. # sub readTargets($$) { my $fileTargets = $_[0] ; my $targetToRead = $_[1] ; # Check if file exists print DEBUGFILE "Checking for file [$fileTargets]\n" if $debug > 1 ; if ( ! -e $fileTargets ) { print DEBUGFILE "File \'$fileTargets\' does not exist.\n" if $debug ; return ; } print DEBUGFILE "Reading target information from $fileTargets for \'$targetToRead\'.\n" if $debug > 1 ; unless ( open( TARGETS, $fileTargets ) ) { print DEBUGFILE "Can not open $fileTargets for reading\n" if $debug ; return ; } my @validParams = qw( target ipaddr port user pwd chmod chown ) ; my @checkTarget = qw ( ipaddr user pwd ) ; print DEBUGFILE "Valid parameters are : [ @validParams ]\n" if $debug > 1 ; print DEBUGFILE "Checks are : [ @checkTarget ]\n" if $debug > 1 ; while ( ) { next if /^\s*#/ ; # Skip comment lines next unless /\S/ ; # Skip blank lines my $inParamLine = $_ ; s/\s*$// ; # Remove trailing blanks chomp ; my $targetError = FALSE ; my @paramLine = split(/\s*\|\s*/); # Separated by pipe character my %p = () ; # Value of parameters read from a line for my $param (@paramLine) { $param =~ s/\s*$// ; if ( $param =~ /^\s*([A-Za-z0-9_]+)\s*=\s*(.*)/ ) { if ( grep /^$1$/i, @validParams ) { eval '$p{' . uc $1 . '} =' . '$2;' ; print DEBUGFILE "Parameter $1 = $2\n" if $debug > 1 ; } else { print DEBUGFILE "Unrecognised Parameter \'$1\'\n" if $debug ; } } else { logError "Input syntax error in Parameter \'$param\'. It must be in the form key=value" ; die ("Terminating due to syntax error in parameter file"); } } if ( defined $p{TARGET} ) { # Continue if expected to read present target next unless ( ($targetToRead =~ /^all$/i) || ($p{TARGET} =~ /^$targetToRead$/i) ) ; # Checking if required fields are # specified. my $tempVal ; foreach (@checkTarget) { $tempVal = uc $_ ; if (! defined $p{$tempVal} ) { print DEBUGFILE "\t\'$tempVal\' entry for $p{TARGET} not available. Ignoring $p{TARGET}\n" if $debug ; $targetError = TRUE ; last ; } } } else { $targetError = TRUE ; } if ( $targetError == TRUE ) { next ; } $p{PORT} = 21 unless $p{PORT} ; # Set default value $p{TARGET} = lc $p{TARGET} ; # Ignore case in input if ( defined $target{$p{TARGET}} ) { print DEBUGFILE "\tResetting the values for \'$p{TARGET}\'.\n" if $debug ; } $target{$p{TARGET}} = {} ; $target{$p{TARGET}}{IPADDR} = $p{IPADDR} ; $target{$p{TARGET}}{PORT} = $p{PORT} ; $target{$p{TARGET}}{USER} = $p{USER} ; $target{$p{TARGET}}{PWD} = $p{PWD} ; $target{$p{TARGET}}{CHMOD} = $p{CHMOD} if $p{CHMOD} ; $target{$p{TARGET}}{CHOWN} = $p{CHOWN} if $p{CHOWN} ; print DEBUGFILE " Values for $p{TARGET} are " . Dumper($target{$p{TARGET}}) ."\n" if $debug > 1 ; # Checking if target information is accurate. my $tname = $p{TARGET} ; print DEBUGFILE " Checking connection for $tname.\n" ; if ( (defined $target{$tname}->{SESSION}) && (validConnection($target{$tname}->{SESSION})) ) { print DEBUGFILE "\tConnection exists for $tname.\n" if $debug ; } else { unless ( openConnection($target{$tname}) ) { print DEBUGFILE "\tUnable to open connection with $tname.\n" if $debug ; print DEBUGFILE "REMOVING information on $tname.\n" if $debug ; delete $target{$tname} ; } } print DEBUGFILE " Target \'$tname\' validated.\n" if $debug ; } close TARGETS ; } #-------------------------------------------------------------------- # sub sigHandler() { print DEBUGFILE "Ctrl+C or Kill caught.\n" if $debug ; exit ; } #-------------------------------------------------------------------- # Read user details. # sub readUserDetails($) { my $detailsFile = $_[0] ; print DEBUGFILE "\tReading config file \'$detailsFile\' for \'$host\' .\n" if $debug > 2 ; if ( (! -e $detailsFile) || (! -r $detailsFile) ) { print DEBUGFILE "\tUnable to read [$detailsFile].\n" if $debug ; return (FALSE, " ") ; } my $config = XMLin($detailsFile) ; my $hostInfo = $config->{$host} ; if (! $hostInfo) { print DEBUGFILE "\tHostInfo for \'$host\' no available in [$detailsFile].\n" if $debug ; return (FALSE, " ") ; } my $ftparea = undef ; # Parse file for ftparea details my $regions = $hostInfo->{REGION} ; foreach my $rgnInfo (@{$regions}) { if ( $rgnInfo->{USER} eq $user ) { $ftparea = $rgnInfo->{FTPAREA} ; print DEBUGFILE " Ftparea read is '\$ftparea\'\n" if $debug > 1 ; last ; } } return (TRUE, $ftparea) ; } #-------------------------------------------------------------------- # Form time stamp for log file # Return timestamp as a text sting. # sub timestamp() { my ( $sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime ; $year += 1900 ; $mon += 1 ; my $timeStamp = sprintf "%04d-%02d-%02d %3s %02d:%02d:%02d", $year, $mon, $mday, $weekdays[$wday], $hour, $min, $sec ; return $timeStamp ; } #-------------------------------------------------------------------- # Return today's date as YYYYMMDD # sub today() { my ( $sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime ; $year += 1900 ; $mon += 1 ; return sprintf "%04d%02d%02d", $year, $mon, $mday ; } #-------------------------------------------------------------------- # Check if a given ftp session is valid or not. The session times out # if not used for a long time. # Input: Reference to ftp session # Output: TRUE if ftp session is valid # FALSE if it is not valid # sub validConnection($) { my $s = $_[0] ; print DEBUGFILE "\tChecking if ftp connection is still valid\n" if $debug > 1 ; # Carry out a simple command to test whether session is valid or not # Change session mode to binary return $s->binary() ; } #-------------------------------------------------------------------- # Builds the restart file. Writes the date and time of the last # successfully process file. (Before chmod and archive.) # sub writeRestart() { seek RESTART, 0, 0 ; # Rewrite the file. printf RESTART "$lastReadDatetime\t%5d\n", $lastReadDatetimeSno ; print DEBUGFILE "\tEntry made into restart file: [$lastReadDatetime $lastReadDatetimeSno]\n" if $debug ; } #-------------------------------------------------------------------- # Executed when the program winds up. # sub windup() { print DEBUGFILE "Winding up.\n" if $debug ; close RESTART ; close FTPLOG ; close DEBUGFILE if $debug ; close ERRORFILE ; unlink($errorFile) if (! $error) ; } #-------------------------------------------------------------------- END { windup() ; } #-------------------------------------------------------------------- main ; # Don't forget to invoke the main