1#!/usr/local/bin/perl 2## 3## @PACKAGE@ @VERSION@ 4## Copyright (c) @COPYYEARS@ by Henry Kilmer and John Heasley 5## All rights reserved. 6## 7## This code is derived from software contributed to and maintained by 8## Henry Kilmer, John Heasley, Andrew Partan, 9## Pete Whiting, Austin Schutz, and Andrew Fort. 10## 11## Redistribution and use in source and binary forms, with or without 12## modification, are permitted provided that the following conditions 13## are met: 14## 1. Redistributions of source code must retain the above copyright 15## notice, this list of conditions and the following disclaimer. 16## 2. Redistributions in binary form must reproduce the above copyright 17## notice, this list of conditions and the following disclaimer in the 18## documentation and/or other materials provided with the distribution. 19## 3. Neither the name of RANCID nor the names of its 20## contributors may be used to endorse or promote products derived from 21## this software without specific prior written permission. 22## 23## THIS SOFTWARE IS PROVIDED BY Henry Kilmer, John Heasley AND CONTRIBUTORS 24## ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 25## TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26## PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COMPANY OR CONTRIBUTORS 27## BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 28## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 30## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 31## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 32## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33## POSSIBILITY OF SUCH DAMAGE. 34## 35## It is the request of the authors, but not a condition of license, that 36## parties packaging or redistributing RANCID NOT distribute altered versions 37## of the etc/rancid.types.base file nor alter how this file is processed nor 38## when in relation to etc/rancid.types.conf. The goal of this is to help 39## suppress our support costs. If it becomes a problem, this could become a 40## condition of license. 41# 42# The expect login scripts were based on Erik Sherk's gwtn, by permission. 43# 44# The original looking glass software was written by Ed Kern, provided by 45# permission and modified beyond recognition. 46# 47# This version of rancid tries to deal with F5 BigIPs. 48# 49# RANCID - Really Awesome New Cisco confIg Differ 50# 51# usage: f5rancid [-dltCV] [-f filename | hostname] 52# 53use Getopt::Std; 54getopts('dflt:CV'); 55if ($opt_V) { 56 print "@PACKAGE@ @VERSION@\n"; 57 exit(0); 58} 59$log = $opt_l; 60$debug = $opt_d; 61$file = $opt_f; 62$host = $ARGV[0]; 63$clean_run = 0; 64$found_end = 0; 65$timeo = 90; # clogin timeout in seconds 66 67# force a terminal type so as not to confuse the POS 68$ENV{'TERM'} = "vt100-w"; 69 70my(@commandtable, %commands, @commands);# command lists 71my($aclsort) = ("ipsort"); # ACL sorting mode 72my($filter_commstr); # SNMP community string filtering 73my($filter_osc); # oscillating data filtering mode 74my($filter_pwds); # password filtering mode 75 76# This routine is used to print out the router configuration 77sub ProcessHistory { 78 my($new_hist_tag,$new_command,$command_string,@string) = (@_); 79 if ((($new_hist_tag ne $hist_tag) || ($new_command ne $command)) 80 && scalar(%history)) { 81 print eval "$command \%history"; 82 undef %history; 83 } 84 if (($new_hist_tag) && ($new_command) && ($command_string)) { 85 if ($history{$command_string}) { 86 $history{$command_string} = "$history{$command_string}@string"; 87 } else { 88 $history{$command_string} = "@string"; 89 } 90 } elsif (($new_hist_tag) && ($new_command)) { 91 $history{++$#history} = "@string"; 92 } else { 93 print "@string"; 94 } 95 $hist_tag = $new_hist_tag; 96 $command = $new_command; 97 1; 98} 99 100sub numerically { $a <=> $b; } 101 102# This is a sort routine that will sort numerically on the 103# keys of a hash as if it were a normal array. 104sub keynsort { 105 local(%lines) = @_; 106 local($i) = 0; 107 local(@sorted_lines); 108 foreach $key (sort numerically keys(%lines)) { 109 $sorted_lines[$i] = $lines{$key}; 110 $i++; 111 } 112 @sorted_lines; 113} 114 115# This is a sort routine that will sort on the 116# keys of a hash as if it were a normal array. 117sub keysort { 118 local(%lines) = @_; 119 local($i) = 0; 120 local(@sorted_lines); 121 foreach $key (sort keys(%lines)) { 122 $sorted_lines[$i] = $lines{$key}; 123 $i++; 124 } 125 @sorted_lines; 126} 127 128# This is a sort routine that will sort on the 129# values of a hash as if it were a normal array. 130sub valsort{ 131 local(%lines) = @_; 132 local($i) = 0; 133 local(@sorted_lines); 134 foreach $key (sort values %lines) { 135 $sorted_lines[$i] = $key; 136 $i++; 137 } 138 @sorted_lines; 139} 140 141# This is a numerical sort routine (ascending). 142sub numsort { 143 local(%lines) = @_; 144 local($i) = 0; 145 local(@sorted_lines); 146 foreach $num (sort {$a <=> $b} keys %lines) { 147 $sorted_lines[$i] = $lines{$num}; 148 $i++; 149 } 150 @sorted_lines; 151} 152 153# This is a sort routine that will sort on the 154# ip address when the ip address is anywhere in 155# the strings. 156sub ipsort { 157 local(%lines) = @_; 158 local($i) = 0; 159 local(@sorted_lines); 160 foreach $addr (sort sortbyipaddr keys %lines) { 161 $sorted_lines[$i] = $lines{$addr}; 162 $i++; 163 } 164 @sorted_lines; 165} 166 167# These two routines will sort based upon IP addresses 168sub ipaddrval { 169 my(@a) = ($_[0] =~ m#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#); 170 $a[3] + 256 * ($a[2] + 256 * ($a[1] +256 * $a[0])); 171} 172sub sortbyipaddr { 173 &ipaddrval($a) <=> &ipaddrval($b); 174} 175 176# This routine parses "bigpipe base list" 177sub ShowBaseRun { 178 my($line) = (0); 179 print STDERR " In ShowBaseRun: $_" if ($debug); 180 181 while (<INPUT>) { 182 tr/\015//d; 183 last if (/^$prompt/); 184 next if (/^(\s*|\s*$cmd\s*)$/); 185 return(1) if /^\s*\^\s*$/; 186 return(1) if /(Invalid input detected|Type help or )/; 187 return(-1) if (/command authorization failed/i); 188 189 if (!$line++) { 190 ProcessHistory("SHOWBASE","","","#\n#base:\n"); 191 } 192 # filter oscillating "password crypt", "auth password crypt", 193 # "privacy password crypt" 194 if (/(password crypt) / && $filter_pwds >= 1) { 195 ProcessHistory("LINE-PASS","","","# $`$1 <removed>\n"); 196 next; 197 } 198 if (/(auth-password-encrypted )\S+/) { 199 ProcessHistory("SHOWBASE","","","# $1 <removed>"); 200 next; 201 } 202 ProcessHistory("SHOWBASE","","","# $_") && next; 203 } 204 return(0); 205} 206 207# This routine parses "bigpipe db show" 208sub ShowDb { 209 my($line) = (0); 210 print STDERR " In ShowDb: $_" if ($debug); 211 212 while (<INPUT>) { 213 tr/\015//d; 214 last if (/^$prompt/); 215 next if (/^(\s*|\s*$cmd\s*)$/); 216 return(1) if /^\s*\^\s*$/; 217 return(1) if /(Invalid input detected|Type help or )/; 218 return(-1) if (/command authorization failed/i); 219 220 if (!$line++) { 221 ProcessHistory("SHOWDB","","","#\n#database:\n"); 222 } 223 /UCS.LoadTime/ && next; 224 /Configsync\..*Time/ && next; 225 /Configsync.peerupdatedstatus/ && next; 226 /Failover\..*Time/ && next; 227 /LTM.ConfigTime/ && next; 228 229 if (/^(snmp\..*\.community\..* =) (.+)/i) { 230 if ($filter_commstr) { 231 ProcessHistory("SHOWDB","","","# $1 <removed>\n") && next; 232 } else { 233 ProcessHistory("SHOWDB","","","# $1 $2\n") && next; 234 } 235 } 236 237 ProcessHistory("SHOWDB","","","# $_") && next; 238 } 239 return(0); 240} 241 242# This routine parses "cat /config/bigip.license" 243sub ShowLicense { 244 my($line) = (0); 245 print STDERR " In ShowLicense: $_" if ($debug); 246 247 while (<INPUT>) { 248 tr/\015//d; 249 # v9 software license does not have CR at EOF 250 s/^#-+($prompt.*)/$1/; 251 last if (/^$prompt/); 252 next if (/^(\s*|\s*$cmd\s*)$/); 253 return(1) if /^\s*\^\s*$/; 254 return(1) if /(Invalid input detected|Type help or )/; 255 return(-1) if (/command authorization failed/i); 256 257 if (!$line++) { 258 ProcessHistory("LICENSE","","","#\n#/config/bigip.license:\n"); 259 } 260 ProcessHistory("LICENSE","","","# $_") && next; 261 } 262 return(0); 263} 264 265# This routine parses "bigpipe monitor list all" 266sub ShowMonitor { 267 my($line) = (0); 268 print STDERR " In ShowMonitor: $_" if ($debug); 269 270 while (<INPUT>) { 271 tr/\015//d; 272 last if (/^$prompt/); 273 next if (/^(\s*|\s*$cmd\s*)$/); 274 return(1) if /^\s*\^\s*$/; 275 return(1) if /(Invalid input detected|Type help or )/; 276 return(-1) if (/command authorization failed/i); 277 278 if (!$line++) { 279 ProcessHistory("MONITOR","","","#\n"); 280 } 281 if (/^(snmp\.[^ ]+\.community) = (.+)/i) { 282 if ($filter_commstr) { 283 ProcessHistory("SHOWDB","","","# $1 <removed>\n") && next; 284 } else { 285 ProcessHistory("SHOWDB","","","# $1 $2\n") && next; 286 } 287 } 288 if (/^(\s*)password / && $filter_pwds >= 1) { 289 ProcessHistory("LINE-PASS","","","# $1password <removed>\n"); 290 next; 291 } 292 293 ProcessHistory("MONITOR","","","# $_") && next; 294 } 295 return(0); 296} 297 298# This routine parses "bigpipe platform" 299sub ShowPlatform { 300 print STDERR " In ShowPlatform: $_" if ($debug); 301 302 while (<INPUT>) { 303 tr/\015//d; 304 last if (/^$prompt/); 305 next if (/^(\s*|\s*$cmd\s*)$/); 306 return(1) if /^\s*\^\s*$/; 307 return(1) if /(Invalid input detected|Type help or )/; 308 return(-1) if (/command authorization failed/i); 309 310 /fan speed/i && next; 311 /chassis temperature/i && next; 312 /degC/ && next; 313 s/\d+rpm//ig; 314 s/^\|//; 315 /Type: / && ProcessHistory("COMMENTS","keysort","A0", 316 "#Chassis type: $'"); 317 318 ProcessHistory("COMMENTS","keysort","B1","#$_") && next; 319 } 320 return(0); 321} 322 323# This routine parses "bigpipe profile list" 324sub ShowProfile { 325 print STDERR " In ShowProfile: $_" if ($debug); 326 327 while (<INPUT>) { 328 tr/\015//d; 329 last if (/^$prompt/); 330 next if (/^(\s*|\s*$cmd\s*)$/); 331 return(1) if /^\s*\^\s*$/; 332 return(1) if /(Invalid input detected|Type help or )/; 333 return(-1) if (/command authorization failed/i); 334 335 ProcessHistory("PROFILE","",""," $_") && next; 336 } 337 return(0); 338} 339 340# This routine parses "ls --full-time --color=never /config/ssl/ssl.key" 341sub ShowSslKey { 342 print STDERR " In ShowSslKey: $_" if ($debug); 343 344 while (<INPUT>) { 345 tr/\015//d; 346 # v9 software license does not have CR at EOF 347 s/^#-+($prompt.*)/$1/; 348 last if (/^$prompt/); 349 next if (/^(\s*|\s*$cmd\s*)$/); 350 return(1) if /^\s*\^\s*$/; 351 return(1) if /(Invalid input detected|Type help or )/; 352 return(-1) if (/command authorization failed/i); 353 354 ProcessHistory("SSLKEY","","","# $_") && next; 355 } 356 return(0); 357} 358 359# This routine parses "ls --full-time --color=never /config/ssl/ssl.crt" 360sub ShowSslCrt { 361 print STDERR " In ShowSslCrt: $_" if ($debug); 362 363 while (<INPUT>) { 364 tr/\015//d; 365 # v9 software license does not have CR at EOF 366 s/^#-+($prompt.*)/$1/; 367 last if (/^$prompt/); 368 next if (/^(\s*|\s*$cmd\s*)$/); 369 return(1) if /^\s*\^\s*$/; 370 return(1) if /(Invalid input detected|Type help or )/; 371 return(-1) if (/command authorization failed/i); 372 373 ProcessHistory("SSLCRT","","","# $_") && next; 374 } 375 return(0); 376} 377 378# This routine parses "bigpipe route static show" 379sub ShowRouteStatic { 380 print STDERR " In ShowRouteStatic: $_" if ($debug); 381 382 while (<INPUT>) { 383 tr/\015//d; 384 last if (/^$prompt/); 385 next if (/^(\s*|\s*$cmd\s*)$/); 386 return(1) if /^\s*\^\s*$/; 387 return(1) if /(Invalid input detected|Type help or )/; 388 return(-1) if (/command authorization failed/i); 389 390 ProcessHistory("ROUTE","",""," $_") && next; 391 } 392 return(0); 393} 394 395# This routine parses "bigpipe version" 396sub ShowVersion { 397 print STDERR " In ShowVersion: $_" if ($debug); 398 399 while (<INPUT>) { 400 tr/\015//d; 401 last if (/^$prompt/); 402 next if (/^(\s*|\s*$cmd\s*)$/); 403 return(-1) if (/command authorization failed/i); 404 405 /^kernel:/i && ($_ = <INPUT>) && 406 ProcessHistory("COMMENTS","keysort","A3","#Image: Kernel: $_") && 407 next; 408 if (/^package:/i) { 409 my($line); 410 411 while ($_ = <INPUT>) { 412 tr/\015//d; 413 last if (/:/); 414 last if (/^$prompt/); 415 chomp; 416 $line .= " $_"; 417 } 418 ProcessHistory("COMMENTS","keysort","A2", 419 "#Image: Package:$line\n"); 420 } 421 422 if (/:/) { 423 ProcessHistory("COMMENTS","keysort","C1","#$_"); 424 } else { 425 ProcessHistory("COMMENTS","keysort","C1","#\t$_"); 426 } 427 } 428 return(0); 429} 430 431# This routine processes a "bigpipe list" 432sub WriteTerm { 433 my($lines) = 0; 434 print STDERR " In WriteTerm: $_" if ($debug); 435 436 while (<INPUT>) { 437 tr/\015//d; 438 next if (/^\s*$/); 439 # end of config - hopefully. f5 does not have a reliable end-of-config 440 # tag. 441 if (/^$prompt/) { 442 $found_end++; 443 last; 444 } 445 return(-1) if (/command authorization failed/i); 446 # the pager can not be disabled per-session on the PIX 447 s/^<-+ More -+>\s*//; 448 /Non-Volatile memory is in use/ && return(-1); # NvRAM is locked 449 # filter out any RCS/CVS tags to avoid confusing local CVS storage 450 s/\$(Revision|Id):/ $1:/; 451 $lines++; 452 453 if (/^(enable )?(password|passwd) / && $filter_pwds >= 1) { 454 ProcessHistory("ENABLE","","","! $1$2 <removed>\n"); 455 next; 456 } 457 if (/^(enable secret) / && $filter_pwds >= 2) { 458 ProcessHistory("ENABLE","","","# $1 <removed>\n"); 459 next; 460 } 461 if (/^username (\S+)(\s.*)? secret /) { 462 if ($filter_pwds >= 2) { 463 ProcessHistory("USER","keysort","$1","# username $1$2 secret <removed>\n"); 464 } else { 465 ProcessHistory("USER","keysort","$1","$_"); 466 } 467 next; 468 } 469 if (/^username (\S+)(\s.*)? password ((\d) \S+|\S+)/) { 470 if ($filter_pwds == 2) { 471 ProcessHistory("USER","keysort","$1","# username $1$2 password <removed>\n"); 472 } elsif ($filter_pwds == 1 && $4 ne "5"){ 473 ProcessHistory("USER","keysort","$1","# username $1$2 password <removed>\n"); 474 } else { 475 ProcessHistory("USER","keysort","$1","$_"); 476 } 477 next; 478 } 479 if (/^(\s*)password / && $filter_pwds >= 1) { 480 ProcessHistory("LINE-PASS","","","# $1password <removed>\n"); 481 next; 482 } 483 if (/^\s*neighbor (\S*) password / && $filter_pwds >= 1) { 484 ProcessHistory("","","","# neighbor $1 password <removed>\n"); 485 next; 486 } 487 # order logging statements 488 /^logging (\d+\.\d+\.\d+\.\d+)/ && 489 ProcessHistory("LOGGING","ipsort","$1","$_") && next; 490 # order/prune tacacs/radius server statements 491 if (/^(tacacs-server|radius-server) key / && $filter_pwds >= 1) { 492 ProcessHistory("","","","# $1 key <removed>\n"); next; 493 } 494 # order clns host statements 495 /^clns host \S+ (\S+)/ && 496 ProcessHistory("CLNS","keysort","$1","$_") && next; 497 # order alias statements 498 /^alias / && ProcessHistory("ALIAS","keysort","$_","$_") && next; 499 # delete ntp auth password - this md5 is a reversable too 500 if (/^(ntp authentication-key \d+ md5) / && $filter_pwds >= 1) { 501 ProcessHistory("","","","# $1 <removed>\n"); next; 502 } 503 # order ntp peers/servers 504 if (/^ntp (server|peer) (\d+)\.(\d+)\.(\d+)\.(\d+)/) { 505 $sortkey = sprintf("$1 %03d%03d%03d%03d",$2,$3,$4,$5); 506 ProcessHistory("NTP","keysort",$sortkey,"$_"); 507 next; 508 } 509 # order ip host line statements 510 /^ip host line(\d+)/ && 511 ProcessHistory("IPHOST","numsort","$1","$_") && next; 512 # order ip nat source static statements 513 /^ip nat (\S+) source static (\S+)/ && 514 ProcessHistory("IP NAT $1","ipsort","$2","$_") && next; 515 516 # monitor state 517 /^\s+monitor state (\S+)/ && next; 518 519 # catch anything that wasnt matched above. 520 ProcessHistory("","","","$_"); 521 } 522 523 if ($lines < 3) { 524 printf(STDERR "ERROR: $host configuration appears truncated.\n"); 525 $found_end = 0; 526 return(-1); 527 } 528 529 return(0); 530} 531 532# Main 533@commandtable = ( 534 {'bigpipe version' => 'ShowVersion'}, 535 {'bigpipe platform' => 'ShowPlatform'}, 536 {'cat /config/bigip.license' => 'ShowLicense'}, 537 {'bigpipe monitor list all' => 'ShowMonitor'}, 538 {'bigpipe profile list' => 'ShowProfile'}, 539 {'bigpipe base list' => 'ShowBaseRun'}, 540 {'bigpipe db show' => 'ShowDb'}, 541 {'bigpipe route static show' => 'ShowRouteStatic'}, 542 {'ls --full-time --color=never /config/ssl/ssl.crt' => 'ShowSslCrt'}, 543 {'ls --full-time --color=never /config/ssl/ssl.key' => 'ShowSslKey'}, 544 {'bigpipe list' => 'WriteTerm'} 545); 546# Use an array to preserve the order of the commands and a hash for mapping 547# commands to the subroutine and track commands that have been completed. 548@commands = map(keys(%$_), @commandtable); 549%commands = map(%$_, @commandtable); 550$commandcnt = scalar(keys %commands); 551 552$commandstr=join(";",@commands); 553$cmds_regexp = join("|", map quotemeta($_), @commands); 554 555if (length($host) == 0) { 556 if ($file) { 557 print(STDERR "Too few arguments: file name required\n"); 558 exit(1); 559 } else { 560 print(STDERR "Too few arguments: host name required\n"); 561 exit(1); 562 } 563} 564if ($opt_C) { 565 print "clogin -t $timeo -c\'$commandstr\' $host\n"; 566 exit(0); 567} 568open(OUTPUT,">$host.new") || die "Can't open $host.new for writing: $!\n"; 569select(OUTPUT); 570# make OUTPUT unbuffered if debugging 571if ($debug) { $| = 1; } 572 573if ($file) { 574 print(STDERR "opening file $host\n") if ($debug || $log); 575 open(INPUT,"<$host") || die "open failed for $host: $!\n"; 576} else { 577 print(STDERR "executing clogin -t $timeo -c\"$commandstr\" $host\n") if ($debug || $log); 578 system "clogin -t $timeo -c \"$commandstr\" $host </dev/null > $host.raw 2>&1" || die "clogin failed for $host: $!\n"; 579 open(INPUT, "< $host.raw") || die "clogin failed for $host: $!\n"; 580} 581 582# determine ACL sorting mode 583if ($ENV{"ACLSORT"} =~ /no/i) { 584 $aclsort = ""; 585} 586# determine community string filtering mode 587if (defined($ENV{"NOCOMMSTR"}) && 588 ($ENV{"NOCOMMSTR"} =~ /yes/i || $ENV{"NOCOMMSTR"} =~ /^$/)) { 589 $filter_commstr = 1; 590} else { 591 $filter_commstr = 0; 592} 593# determine oscillating data filtering mode 594if (defined($ENV{"FILTER_OSC"}) && $ENV{"FILTER_OSC"} =~ /no/i) { 595 $filter_osc = 0; 596} else { 597 $filter_osc = 1; 598} 599# determine password filtering mode 600if ($ENV{"FILTER_PWDS"} =~ /no/i) { 601 $filter_pwds = 0; 602} elsif ($ENV{"FILTER_PWDS"} =~ /all/i) { 603 $filter_pwds = 2; 604} else { 605 $filter_pwds = 1; 606} 607 608ProcessHistory("","","","#RANCID-CONTENT-TYPE: bigip\n#\n"); 609ProcessHistory("COMMENTS","keysort","A1","#\n"); 610ProcessHistory("COMMENTS","keysort","B0","#\n"); 611ProcessHistory("COMMENTS","keysort","C0","#\n"); 612TOP: while(<INPUT>) { 613 tr/\015//d; 614 if (/^Error:/) { 615 print STDOUT ("$host clogin error: $_"); 616 print STDERR ("$host clogin error: $_") if ($debug); 617 $clean_run=0; 618 last; 619 } 620 while (/#\s*($cmds_regexp)\s*$/) { 621 $cmd = $1; 622 if (!defined($prompt)) { 623 $prompt = ($_ =~ /^([^#]+#)/)[0]; 624 $prompt =~ s/([][}{)(\\])/\\$1/g; 625 print STDERR ("PROMPT MATCH: $prompt\n") if ($debug); 626 } 627 print STDERR ("HIT COMMAND:$_") if ($debug); 628 if (! defined($commands{$cmd})) { 629 print STDERR "$host: found unexpected command - \"$cmd\"\n"; 630 $clean_run = 0; 631 last TOP; 632 } 633 $rval = &{$commands{$cmd}}(*INPUT, *OUTPUT, $cmd); 634 delete($commands{$cmd}); 635 if ($rval == -1) { 636 $clean_run = 0; 637 last TOP; 638 } 639 } 640 if (/\#\s?exit$/) { 641 $clean_run=1; 642 last; 643 } 644} 645print STDOUT "Done $logincmd: $_\n" if ($log); 646# Flush History 647ProcessHistory("","","",""); 648# Cleanup 649close(INPUT); 650close(OUTPUT); 651 652unlink("$host.raw") if (! $debug); 653 654# check for completeness 655if (scalar(%commands) || !$clean_run || !$found_end) { 656 if (scalar(keys %commands) eq $commandcnt) { 657 printf(STDERR "$host: missed cmd(s): all commands\n"); 658 } elsif (scalar(%commands)) { 659 my($count, $i) = 0; 660 for ($i = 0; $i < $#commands; $i++) { 661 if ($commands{$commands[$i]}) { 662 if (!$count) { 663 printf(STDERR "$host: missed cmd(s): %s", $commands[$i]); 664 } else { 665 printf(STDERR ", %s", $commands[$i]); 666 } 667 $count++; 668 } 669 } 670 if ($count) { 671 printf(STDERR "\n"); 672 } 673 } 674 if (!$clean_run || !$found_end) { 675 print(STDERR "$host: End of run not found\n"); 676 if ($debug) { 677 print(STDERR "$host: clean_run is false\n") if (!$clean_run); 678 print(STDERR "$host: found_end is false\n") if (!$found_end); 679 } 680 system("/usr/bin/tail -1 $host.new"); 681 } 682 unlink "$host.new" if (! $debug); 683} 684