1# -*- mode: Perl -*- 2package MRTG_lib; 3################################################################### 4# MRTG 2.17.4 Support library MRTG_lib.pm 5################################################################### 6# Created by Tobias Oetiker <tobi@oetiker.ch> 7# and Dave Rand <dlr@bungi.com> 8# 9# For individual Contributers check the CHANGES file 10# 11################################################################### 12# 13# Distributed under the GNU General Public License 14# 15################################################################### 16 17require 5.005; 18use strict; 19use vars qw($OS $SL $PS @EXPORT @ISA $VERSION %timestrpospattern); 20 21 22my %mrtgrules; 23 24BEGIN { 25 # Automatic OS detection ... do NOT touch 26 if ( $^O =~ /^(?:(ms)?(dos|win(32|nt)?))/i ) { 27 $OS = 'NT'; 28 $SL = '\\'; 29 $PS = ';'; 30 } elsif ( $^O =~ /^NetWare$/i ) { 31 $OS = 'NW'; 32 $SL = '/'; 33 $PS = ';'; 34 } elsif ( $^O =~ /^VMS$/i ) { 35 $OS = 'VMS'; 36 $SL = '.'; 37 $PS = ':'; 38 } elsif ( $^O =~ /^os2$/i ) { 39 $OS = 'OS2'; 40 $SL = '/'; 41 $PS = ';'; 42 } else { 43 $OS = 'UNIX'; 44 $SL = '/'; 45 $PS = ':'; 46 } 47} 48 49require Exporter; 50@ISA = qw(Exporter); 51@EXPORT = qw(readcfg cfgcheck setup_loghandlers 52 datestr expistr ensureSL timestamp 53 create_pid demonize_me debug log2rrd storeincache readfromcache clearfromcache cleanhostkey 54 populateconfcache readconfcache writeconfcache 55 v4onlyifnecessary); 56 57$VERSION = 2.100016; 58 59%timestrpospattern = 60 ( 61 'NO' => 0, 62 'LU' => 1, 63 'RU' => 2, 64 'LL' => 3, 65 'RL' => 4 66 ); 67 68%mrtgrules = 69 ( # General CFG 70 'workdir' => 71 [sub{$_[0] && (-d $_[0])}, sub{"Working directory $_[0] does not exist"}], 72 73 'htmldir' => 74 [sub{$_[0] && (-d $_[0])}, sub{"Html directory $_[0] does not exist"}], 75 76 'imagedir' => 77 [sub{$_[0] && (-d $_[0])}, sub{"Image directory $_[0] does not exist"}], 78 79 'logdir' => 80 [sub{$_[0] && (-d $_[0] )}, sub{"Log directory $_[0] does not exist"}], 81 82 'forks' => 83 [sub{$_[0] && (int($_[0]) > 0 and $MRTG_lib::OS eq 'UNIX')}, 84 sub{"Less than 1 fork or not running on Unix/Linux"}], 85 86 'refresh' => 87 [sub{int($_[0]) >= 300}, sub{"$_[0] should be 300 seconds or more"}], 88 89 'enablesnmpv3' => 90 [sub{((lc($_[0])) eq 'yes' or (lc($_[0])) eq 'no')}, sub{"$_[0] must be yes or no"}], 91 92 'enableipv6' => 93 [sub{((lc($_[0])) eq 'yes' or (lc($_[0])) eq 'no')}, sub{"$_[0] must be yes or no"}], 94 95 'interval' => 96 [sub{$_[0] =~ /(\d+)(?::(\d+))?/ ; 97 my $int = $1*60; $int += $2 if $2; 98 $int >= 1 and $int <= 60*60}, sub{"$_[0] should be at least 1 Second (0:01) and no more than 60 Minutes (60)"}], 99 100 'writeexpires' => 101 [sub{1}, sub{"Internal Error"}], 102 103 'nomib2' => 104 [sub{1}, sub{"Internal Error"}], 105 106 'singlerequest' => 107 [sub{1}, sub{"Internal Error"}], 108 109 'icondir' => 110 [sub{$_[0]}, sub{"Directory argument missing"}], 111 112 'language' => 113 [sub{1}, sub{"Mrtg not localized for $_[0] - defaulting to english"}], 114 115 'loadmibs' => 116 [sub{$_[0]}, sub{"No MIB Files specified"}], 117 118 'userrdtool' => 119 [sub{0}, sub{"UseRRDtool is not valid any more. Use LogFormat, PathAdd and LibAdd instead"}], 120 121 'userrdtool[]' => 122 [sub{0}, sub{"UseRRDtool[] is not valid any more. Check the new xyz*bla[] syntax for passing parameters to tool xyz who reads the mrtg.cfg"}], 123 124 'logformat' => 125 [sub{$_[0] =~ /^(rateup|rrdtool)$/}, sub{"Invalid Logformat '$_[0]'"}], 126 127 'pathadd' => 128 [sub{-d $_[0]}, sub{"$_[0] is not the name of a directory"}], 129 130 'libadd' => 131 [sub{-d $_[0]}, sub{"$_[0] is not the name of a directory"}], 132 133 'runasdaemon' => 134 [sub{1}, sub{"Internal Error"}], 135 136 'nodetach' => 137 [sub{1}, sub{"Internal Error"}], 138 139 'maxage' => 140 [sub{(($_[0] =~ /^[0-9]+$/) and ($_[0] > 0)) }, 141 sub{"$_[0] must be a Number bigger than 0"}], 142 143 'nospacechar' => 144 [sub{length($_[0]) == 1}, sub{"$_[0] must be one character long"}], 145 146 'snmpoptions' => 147 [sub{ debug('eval',"snmpotions $_[0]");local $SIG{__DIE__}; eval( '{'.$_[0].'}' ); return not $@}, 148 sub{"Must have the format \"OptA => Number, OptB => 'String', ... \""}], 149 150 'conversioncode' => 151 [sub{-r $_[0]}, sub{"Cannot read conversion code file $_[0]"}], 152 153 # Check for an environment setting for RRDCACHED_ADDRESS 154 # Steve Shipway, Sep 2010 155 'rrdcached' => 156# [sub{(($_[0] =~ /^unix:(\S+)/)and(-w $1))}, sub{"Currently, only UNIX domain sockets are supported for RRDCached, and must exist and be writeable."}], 157 [sub{1},sub{"Internal Error"}], 158 159 # Per Router CFG 160 'target[]' => 161 [sub{1}, sub{"Internal Error"}], #will test this later 162 163 'snmpoptions[]' => 164 [sub{ debug('eval',"snmpotions[] $_[0]");local $SIG{__DIE__}; eval('{'.$_[0].'}' ); return not $@}, 165 sub{"Must have the format \"OptA => Number, OptB => 'String', ... \""}], 166 167 'routeruptime[]' => 168 [sub{1}, sub{"Internal Error"}], #will test this later 169 170 'routername[]' => 171 [sub{1}, sub{"Internal Error"}], #will test this later 172 173 'nohc[]' => 174 [sub{((lc($_[0])) eq 'yes' or (lc($_[0])) eq 'no')}, sub{"$_[0] must be yes or no"}], 175 176 'maxbytes[]' => 177 [sub{(($_[0] =~ /^[0-9]+$/) && ($_[0] > 0)) }, 178 sub{"$_[0] must be a Number bigger than 0"}], 179 180 'maxbytes1[]' => 181 [sub{(($_[0] =~ /^[0-9]+$/) && ($_[0] > 0))}, 182 sub{"$_[0] must be numerical and larger than 0"}], 183 184 'maxbytes2[]' => 185 [sub{(($_[0] =~ /^[0-9]+$/) && ($_[0] > 0))}, 186 sub{"$_[0] must a number bigger than 0"}], 187 188 'ipv4only[]' => 189 [sub{((lc($_[0])) eq 'yes' or (lc($_[0])) eq 'no')}, sub{"$_[0] must be yes or no"}], 190 191 'absmax[]' => 192 [sub{($_[0] =~ /^[0-9]+$/)}, sub{"$_[0] must be a Number"}], 193 194 'title[]' => 195 [sub{1}, sub{"Internal Error"}], #what ever the user chooses. 196 197 'directory[]' => 198 [sub{1}, sub{"Internal Error"}], #what ever the user chooses. 199 200 'clonedirectory[]' => 201 [sub{($_[0] =~ /[^,]\s*$/)}, sub{"$_[0] with comma must have the second parameter"}], 202 203 'pagetop[]' => 204 [sub{1}, sub{"Internal Error"}], #what ever the user chooses. 205 206 'bodytag[]' => 207 [sub{1}, sub{"Internal Error"}], #what ever the user chooses. 208 209 'pagefoot[]' => 210 [sub{1}, sub{"Internal Error"}], #what ever the user chooses. 211 212 'addhead[]' => 213 [sub{1}, sub{"Internal Error"}], #what ever the user chooses. 214 215 'rrdrowcount[]' => 216 [sub{1}, sub{"Internal Error"}], #what ever the user chooses. 217 218 'rrdrowcount30m[]' => 219 [sub{1}, sub{"Internal Error"}], #what ever the user chooses. 220 221 'rrdrowcount2h[]' => 222 [sub{1}, sub{"Internal Error"}], #what ever the user chooses. 223 224 'rrdrowcount1d[]' => 225 [sub{1}, sub{"Internal Error"}], #what ever the user chooses. 226 227 'rrdhwrras[]' => 228 [sub{$_[0] =~ /^RRA:(HWPREDICT|SEASONAL|DEVPREDICT|DEVSEASONAL|FAILURES):\S+(\s+RRA:(HWPREDICT|SEASONAL|DEVPREDICT|DEVSEASONAL|FAILURES):\S+)*$/}, 229 sub{"This does not look like rrdtool HW RRAs. Check the rrdcreate manual page for inspiration. ($_[0])"}], 230 231 'extension[]' => 232 [sub{1}, sub{"Internal Error"}], #what ever the user chooses. 233 234 'unscaled[]' => 235 [sub{$_[0] =~ /[ndwmy]+/i}, sub{"Must be a string of [n]one, [d]ay, [w]eek, [m]onth, [y]ear"}], 236 237 'weekformat[]' => 238 [sub{$_[0] =~ /[UVW]/}, sub{"Must be either W, V, or U"}], 239 240 'withpeak[]' => 241 [sub{$_[0] =~ /[ndwmy]+/i}, sub{"Must be a string of [n]one, [d]ay, [w]eek, [m]onth, [y]ear"}], 242 243 'suppress[]' => 244 [sub{$_[0] =~ /[ndwmy]+/i}, sub{"Must be a string of [n]one, [d]ay, [w]eek, [m]onth, [y]ear"}], 245 246 'xsize[]' => 247 [sub{((int($_[0]) >= 30) && (int($_[0]) <= 600))}, sub{"$_[0] must be between 30 and 600 pixels"}], 248 249 'ysize[]' => 250 [sub{(int($_[0]) >= 30)}, sub{"Must be >= 30 pixels"}], 251 252 'ytics[]' => 253 [sub{(int($_[0]) >= 1) }, sub{"Must be >= 1"}], 254 255 'yticsfactor[]' => 256 [sub{$_[0] =~ /[-+0-9.efg]+/}, sub{"Should be a numerical value"}], 257 258 'factor[]' => 259 [sub{$_[0] =~ /[-+0-9.efg]+/}, sub{"Should be a numerical value"}], 260 261 'step[]' => 262 [sub{(int($_[0]) >= 0)}, sub{"$_[0] must be > 0"}], 263 264 'timezone[]' => 265 [sub{1}, sub{"Internal Error"}], 266 267 'options[]' => 268 [sub{1}, sub{"Internal Error"}], 269 270 'colours[]' => 271 [sub{1}, sub{"Internal Error"}], 272 273 'background[]' => 274 [sub{1}, sub{"Internal Error"}], 275 276 'kilo[]' => 277 [sub{($_[0] =~ /^[0-9]+$/)}, sub{"$_[0] must be a Integer Number"}], 278 #define whatever k should be (1000, 1024, ???) 279 280 'kmg[]' => 281 [sub{1}, sub{"Internal Error"}], 282 283 'pngtitle[]' => 284 [sub{1}, sub{"Internal Error"}], 285 286 'ylegend[]' => 287 [sub{1}, sub{"Internal Error"}], 288 289 'shortlegend[]' => 290 [sub{1}, sub{"Internal Error"}], 291 292 'legend1[]' => 293 [sub{1}, sub{"Internal Error"}], 294 295 'legend2[]' => 296 [sub{1}, sub{"Internal Error"}], 297 298 'legend3[]' => 299 [sub{1}, sub{"Internal Error"}], 300 301 'legend4[]' => 302 [sub{1}, sub{"Internal Error"}], 303 304 'legend5[]' => 305 [sub{1}, sub{"Internal Error"}], 306 307 'legendi[]' => 308 [sub{1}, sub{"Internal Error"}], 309 310 'legendo[]' => 311 [sub{1}, sub{"Internal Error"}], 312 313 'setenv[]' => 314 [sub{$_[0] =~ /^(?:[-\w]+=\"[^"]*"(?:\s+|$))+$/}, 315 sub{"$_[0] must be XY=\"dddd\" AASD=\"kjlkj\" ... "}], 316 317 318 'xzoom[]' => 319 [sub{($_[0] =~ /^[0-9]+(?:\.[0-9]+)?$/)}, 320 sub{"$_[0] must be a Number xxx.xxx"}], 321 322 'yzoom[]' => 323 [sub{($_[0] =~ /^[0-9]+(?:\.[0-9]+)?$/)}, 324 sub{"$_[0] must be a Number xxx.xxx"}], 325 326 'xscale[]' => 327 [sub{($_[0] =~ /^[0-9]+(?:\.[0-9]+)?$/)}, 328 sub{"$_[0] must be a Number xxx.xxx"}], 329 330 'yscale[]' => 331 [sub{($_[0] =~ /^[0-9]+(?:\.[0-9]+)?$/)}, 332 sub{"$_[0] must be a Number xxx.xxx"}], 333 334 'threshdir' => 335 [sub{$_[0] && (-d $_[0])}, sub{"Threshold directory $_[0] does not exist"}], 336 337 'threshhyst' => 338 [sub{($_[0] =~ /^[0-9]+(?:\.[0-9]+)?$/)}, 339 sub{"$_[0] must be a Number xxx.xxx"}], 340 341 'hwthreshhyst' => 342 [sub{($_[0] =~ /^[0-9]+(?:\.[0-9]+)?$/)}, 343 sub{"$_[0] must be a Number xxx.xxx"}], 344 345 'threshmailserver' => 346 [sub{$_[0] && gethostbyname($_[0])}, sub{"Unknown mailserver hostname $_[0]"}], 347 348 'threshmailsender' => 349 [sub{$_[0] && ($_[0] =~ /\S+\@\S+/)}, sub{"ThreshMailAddress $_[0] does not look like an email address at all"}], 350 351 'threshmini[]' => 352 [sub{1}, sub{"Internal Threshold Config Error"}], 353 354 'threshmino[]' => 355 [sub{1}, sub{"Internal Threshold Config Error"}], 356 357 'threshmaxi[]' => 358 [sub{1}, sub{"Internal Threshold Config Error"}], 359 360 'threshmaxo[]' => 361 [sub{1}, sub{"Internal Threshold Config Error"}], 362 363 'threshdesc[]' => 364 [sub{1}, sub{"Internal Threshold Config Error"}], 365 366 'threshprogi[]' => 367 [sub{$_[0] && (-e $_[0])}, sub{"Threshold program $_[0] cannot be executed"}], 368 369 'threshprogo[]' => 370 [sub{$_[0] && (-e $_[0])}, sub{"Threshold program $_[0] cannot be executed"}], 371 372 'threshprogoki[]' => 373 [sub{$_[0] && (-e $_[0])}, sub{"Threshold program $_[0] cannot be executed"}], 374 375 'threshprogoko[]' => 376 [sub{$_[0] && (-e $_[0])}, sub{"Threshold program $_[0] cannot be executed"}], 377 378 'threshmailaddress[]' => 379 [sub{$_[0] && ($_[0] =~ /\S+\@\S+/)}, sub{"ThreshMailAddress $_[0] does not look like an email address at all"}], 380 381 'hwthreshmini[]' => 382 [sub{1}, sub{"Internal Threshold Config Error"}], 383 384 'hwthreshmino[]' => 385 [sub{1}, sub{"Internal Threshold Config Error"}], 386 387 'hwthreshmaxi[]' => 388 [sub{1}, sub{"Internal Threshold Config Error"}], 389 390 'hwthreshmaxo[]' => 391 [sub{1}, sub{"Internal Threshold Config Error"}], 392 393 'hwthreshdesc[]' => 394 [sub{1}, sub{"Internal Threshold Config Error"}], 395 396 'hwthreshprogi[]' => 397 [sub{$_[0] && (-e $_[0])}, sub{"Threshold program $_[0] cannot be executed"}], 398 399 'hwthreshprogo[]' => 400 [sub{$_[0] && (-e $_[0])}, sub{"Threshold program $_[0] cannot be executed"}], 401 402 'hwthreshprogoki[]' => 403 [sub{$_[0] && (-e $_[0])}, sub{"Threshold program $_[0] cannot be executed"}], 404 405 'hwthreshprogoko[]' => 406 [sub{$_[0] && (-e $_[0])}, sub{"Threshold program $_[0] cannot be executed"}], 407 408 'hwthreshmailaddress[]' => 409 [sub{$_[0] && ($_[0] =~ /\S+\@\S+/)}, sub{"ThreshMailAddress $_[0] does not look like an email address at all"}], 410 411 'timestrpos[]' => 412 [sub{$_[0] =~ /^(no|[lr][ul])$/i}, sub{"Must be a string of NO, LU, RU, LL, RL"}], 413 414 'timestrfmt[]' => 415 [sub{1}, sub{"Internal Error"}] #what ever the user chooses. 416); 417 418 419# config file reading 420 421sub readcfg ($$$$;$$) { 422 my $cfgfile = shift; 423 my $routers = shift; 424 my $cfg = shift; 425 my $rcfg = shift; 426 my $extprefix = shift || ''; 427 my $extrules = shift; 428 my ($first,$second,$key,$userules); 429 my (%seen); 430 my (%pre,%post,%deflt,%defaulted); 431 unless ($cfgfile) { 432 die "ERROR: readfg: no configfile specified\n"; 433 } 434 unless (ref($routers) eq 'ARRAY' and ref($cfg) eq 'HASH' 435 and ref($rcfg) eq 'HASH') { 436 die "ERROR: readcfg called with wrong arguments\n"; 437 } 438 if ($extprefix and ref($extrules) ne 'HASH') { 439 die "ERROR: readcfg called with wrong args for mrtg extension\n"; 440 } 441 my $hand; 442 my $file; 443 my @filestack; 444 local *CFG; 445 if ($cfgfile eq '-'){$cfgfile = '<&STDIN'}; 446 open (CFG, $cfgfile) || die "ERROR: unable to open config file: $cfgfile\n"; 447 $hand = *CFG; 448 my @handstack; 449 my $nextfile = $cfgfile; 450 my %routerhash; 451 while (1) { 452 if (eof $hand || not defined ($_ = <$hand>) ) { 453 close $hand; 454 if (scalar @handstack){ 455 $hand = pop @handstack; 456 $nextfile = pop @filestack; 457 next; 458 } else { 459 last; 460 } 461 } 462 $file=$nextfile; 463 chomp; 464 my $line = $.; 465 if (/^include:\s*(.*?\S)\s*$/i){ 466 my $newhandle; 467 my @nextfiles; 468 $nextfile = $1; 469 if( $nextfile =~ /\*/ ) { 470 @nextfiles = glob( $nextfile ); 471 @nextfiles = glob( ($cfgfile =~ m#(.+)${MRTG_lib::SL}[^${MRTG_lib::SL}]+$#)[0] . ${MRTG_lib::SL} . $nextfile ) 472 if(!@nextfiles); 473 } else { 474 $nextfile = ($cfgfile =~ m#(.+)${MRTG_lib::SL}[^${MRTG_lib::SL}]+$#)[0] . ${MRTG_lib::SL} . $nextfile 475 if(!-r $nextfile); 476 @nextfiles = ( $nextfile ); 477 } 478 foreach $nextfile ( @nextfiles ) { 479 open my $newhandle, '<', $nextfile or die "ERROR: unable to open include file: $nextfile\n"; 480 push @handstack, $hand; 481 push @filestack, $file; 482 $hand = $newhandle; 483 $file = $nextfile; 484 } 485 next; 486 } 487 488 debug('cfg',"$file\[$.\]: $_"); 489 490 s/\t/ /g; #replace tab by space 491 s/\r$//; # kill dos newlines ... 492 s/ +$//g; #remove space at the end of the line 493 next if /^ *\#/; #ignore comment lines 494 next if /^ *$/; #ignore empty lines 495 # oops spelling error 496 s/^supress/suppress/gi; 497 498 499 # the line we got starts with white space so it is to be appended to what ever 500 # was on the previous line. 501 502 if (defined $first && /^\s+(.*\S)\s*$/) { 503 if (defined $second) { 504 $second eq '^' && do { $pre{$first} .= "\n".$1; next}; 505 $second eq '$' && do { $post{$first} .= "\n".$1; next}; 506 $second eq '_' && do { $deflt{$first} .= "\n".$1; next}; 507 $$rcfg{$first}{$second} .= " ".$1; 508 } else { 509 $$cfg{$first} .= "\n".$1; 510 } 511 next; 512 } 513 514 if (defined $first && defined $second && defined $post{$first} && ($second !~ /^[\$^_]$/)) { 515 if (defined $defaulted{$first}{$second}) { 516 $$rcfg{$first}{$second} = $post{$first}; 517 delete $defaulted{$first}{$second}; 518 } else { 519 $$rcfg{$first}{$second} .= ( defined $$cfg{nospacechar} and $post{$first} =~ /(.*)\Q$$cfg{nospacechar}\E$/) ? $1 : " ".$post{$first} ; 520 } 521 } 522 523 if (defined $first and $first =~ m/^([^*]+)\*(.+)$/) { 524 $userules = ($1 eq $extprefix ? $extrules : ''); 525 } else { 526 $userules = \%mrtgrules; 527 } 528 529 if ($first && defined $deflt{$first} && ($second eq '_')) { 530 quickcheck($first,$second,$deflt{$first},$file,$line,$userules) 531 } elsif ($first && $second && ($second !~ /^[\$^_]$/)) { 532 quickcheck($first,$second,$$rcfg{$first}{$second},$file,$line,$userules) 533 } elsif ($first && not $second) { 534 quickcheck($first,0,$$cfg{$first},$file, $line,$userules) 535 } 536 537 if (/^([A-Za-z0-9*]+)\[(\S+)\]\s*:\s*(.*\S?)\s*$/) { 538 $first = lc($1); 539 $second = lc($2); 540 # For us spelling-handicapped Americans. ;) 541 # James Overbeck, grendel@gmo.jp, 2003/01/19 542 if ($first eq 'colors') { $first = 'colours' }; 543 if ($second eq '^') { 544 if ($3 ne '') { 545 $pre{$first}=$3; 546 } else { 547 delete $pre{$first}; 548 } 549 next; 550 } 551 if ($second eq '$') { 552 if ($3 ne '') { 553 $post{$first}=$3; 554 } else { 555 delete $post{$first}; 556 } 557 next; 558 } 559 if ($second eq '_') { 560 if ($3 ne '') { 561 $deflt{$first}=$3; 562 } else { 563 delete $deflt{$first}; 564 } 565 next; 566 } 567 568 if (not defined $routerhash{$second}) { 569 push (@{$routers}, $second); 570 $routerhash{$second} = 1; 571 } 572 573 # make sure that default tags spring into existance upon first 574 # call of a router 575 576 foreach $key (keys %deflt) { 577 if (! defined $$rcfg{$key}{$second}) { 578 $$rcfg{$key}{$second} = $deflt{$key}; 579 $defaulted{$key}{$second} = 1; 580 } 581 } 582 583 # make sure that prefix-only tags spring into existance upon first 584 # call of a router 585 586 foreach $key (keys %pre) { 587 if (! defined $$rcfg{$key}{$second}) { 588 delete $defaulted{$key}{$second} if $defaulted{$key}{$second}; 589 $$rcfg{$key}{$second} = ( defined $$cfg{nospacechar} && $pre{$key} =~ m/(.*)\Q$$cfg{nospacechar}\E$/ ) ? $1 : $pre{$key}." "; 590 } 591 } 592 593 if ($seen{$first}{$second}) { 594 die ("ERROR: Line $line ($_) in CFG file ($file)\n". 595 "contains a duplicate definition for $first\[$second].\n". 596 "First definition is on line $seen{$first}{$second}\n") 597 } else { 598 $seen{$first}{$second} = $line; 599 } 600 601 if ($defaulted{$first}{$second}) { 602 $$rcfg{$first}{$second} = ''; 603 delete $defaulted{$first}{$second}; 604 } 605 $$rcfg{$first}{$second} .= $3; 606 607 next; 608 609 } 610 if (/^(\S+):\s*(.*\S)\s*$/) { 611 $first = lc($1); 612 $$cfg{$first} = $2; 613 $second = ''; 614 next; 615 } 616 die "ERROR: Line $line ($_) in CFG file ($file) does not make sense\n"; 617 } 618 619 # append $ stuff to the very last tag in cfg file if necessary 620 if (defined $first && defined $second && defined $post{$first} && ($second !~ /^[\$^_]$/)) { 621 if ($defaulted{$first}{$second}) { 622 $$rcfg{$first}{$second} = $post{$first}; 623 delete $defaulted{$first}{$second}; 624 } else { 625 $$rcfg{$first}{$second} .= 626 ( defined $$cfg{'nospacechar'} && $post{$first} =~ /(.*)\Q$$cfg{nospacechar}\E$/ ) ? $1 : " ".$post{$first} ; 627 } 628 } 629 630 #check the last input line 631 if ($first =~ m/^([^*]+)\*(.+)$/) { 632 $userules = ($1 eq $extprefix ? $extrules : ''); 633 } else { 634 $userules = \%mrtgrules; 635 } 636 if ($first && defined $deflt{$first} && ($second eq '_')) { 637 quickcheck($first,$second,$deflt{$first},$file,$.,$userules) 638 } elsif ($first && $second && ($second !~ /^[\$^_]$/)) { 639 quickcheck($first,$second,$$rcfg{$first}{$second},$file,$.,$userules) 640 } elsif ($first && not $second) { 641 quickcheck($first,0,$$cfg{$first},$file,$.,$userules) 642 } 643 644 close (CFG); 645 646 # Check for an environment setting for RRDCACHED_ADDRESS 647 # Steve Shipway, Sep 2010 648 if( $ENV{RRDCACHED_ADDRESS} and not exists $cfg->{ rrdcached } ) { 649 warn("WARNING: Using environment variable RRDCACHED_ADDRESS\n"); 650 $cfg->{ rrdcached } = $ENV{RRDCACHED_ADDRESS}; 651 quickcheck('rrdcached',0,$ENV{RRDCACHED_ADDRESS},'Environment variable RRDCACHED_ADDRESS','n/a',\%mrtgrules); 652 } 653 if( exists $cfg->{ rrdcached } ) { 654 warn ("WARNING: You are running with RRDCached enabled (".$cfg->{ rrdcached }."). This will disable all Threshold checking, since RRDCached does not support updatev and an update/fetch will cancel out the caching benefits.\n"); 655 if( $cfg->{ rrdcached } !~ /^unix:/ ) { 656 warn("WARNING: You are running RRDCached in TCP mode. This means that it will use its own Base Directory instead of WorkDir for storing the RRD files. Also, changes to MaxBytes and DS Type will not be actioned after the RRD file has been created.\n"); 657 } 658 } 659 if ($cfg->{enablesnmpv3} and $cfg->{enablesnmpv3} eq 'yes' and eval {local $SIG{__DIE__}; require Net_SNMP_util} ) { 660 import Net_SNMP_util; 661 } else { 662 require SNMP_util; 663 import SNMP_util; 664 } 665} 666 667# quick checks 668 669sub quickcheck ($$$$$$) { 670 my ($first,$second,$arg,$file,$line,$rules) = @_; 671 return unless ref($rules) eq 'HASH'; 672 my $braces = $second ? '[]':''; 673 if (exists $rules->{$first.$braces}) { 674 if (&{$rules->{$first.$braces}[0]}($arg)) { 675 return 1; 676 } else { 677 if ($second) { 678 die "ERROR: CFG Error in \"$first\[$second\]\", file $file line $line: ". 679 &{$rules->{$first.$braces}[1]}($arg)."\n\n"; 680 } else { 681 die "ERROR: CFG Error in \"$first\", file $file line $line: ". 682 &{$rules->{$first.$braces}[1]}($arg)."\n\n"; 683 } 684 } 685 } 686 die "ERROR: CFG Error Unknown Option \"$first\" in file $file on line $line or above.\n". 687 " Check doc/mrtg-reference.txt for Help\n\n"; 688} 689 690# complex config checks 691 692sub mkdirhier ($){ 693 my @dirs = split /\Q${MRTG_lib::SL}\E+/, shift; 694 my $path = ""; 695 while (@dirs){ 696 $path .= shift @dirs; 697 $path .= ${MRTG_lib::SL}; 698 if (! -d $path){ 699 warn ("WARNING: $path did not exist I will create it now\n"); 700 mkdir $path, 0777 or die ("ERROR: mkdir $path: $!\n"); 701 } 702 } 703} 704 705sub cfgcheck ($$$$;$) { 706 my $routers = shift; 707 my $cfg = shift; 708 my $rcfg = shift; 709 my $target = shift; 710 my $opts = shift || {}; 711 my ($rou, $confname, $one_option); 712 # Target index hash. Keys are "int:community@router" target definition 713 # strings and values are indices of the @$target array. Used to avoid 714 # duplicate entries in @$target. 715 my $targIndex = { }; 716 my $error="no"; 717 my(@known_options) = qw(growright bits noinfo absolute gauge nopercent avgpeak derive 718 integer perhour perminute transparent dorelpercent 719 unknaszero withzeroes noborder noarrow noi noo 720 nobanner nolegend logscale secondmean pngdate printrouter expscale); 721 722 snmpmapOID('hrSystemUptime' => '1.3.6.1.2.1.25.1.1'); 723 724 if (defined $$cfg{workdir}) { 725 die ("ERROR: WorkDir must not contain spaces when running on Windows. (Yeat another reason to get Linux)\n") 726 if ($OS eq 'NT' or $OS eq 'OS2') and $$cfg{workdir} =~ /\s/; 727 ensureSL(\$$cfg{workdir}); 728 $$cfg{logdir}=$$cfg{htmldir}=$$cfg{imagedir}=$$cfg{workdir}; 729 mkdirhier "$$cfg{workdir}" unless $opts->{check}; 730 731 } elsif ( not (defined $$cfg{logdir} or defined $$cfg{htmldir} or defined $$cfg{imagedir})) { 732 die ("ERROR: \"WorkDir\" not specified in mrtg config file\n"); 733 $error = "yes"; 734 } else { 735 if (! defined $$cfg{logdir}) { 736 warn ("WARNING: \"LogDir\" not specified\n"); 737 $error = "yes"; 738 } else { 739 ensureSL(\$$cfg{logdir}); 740 mkdirhier $$cfg{logdir} unless $opts->{check}; 741 } 742 if (! defined $$cfg{htmldir}) { 743 warn ("WARNING: \"HtmlDir\" not specified\n"); 744 $error = "yes"; 745 } else { 746 ensureSL(\$$cfg{htmldir}); 747 mkdirhier $$cfg{htmldir} unless $opts->{check}; 748 } 749 if (! defined $$cfg{imagedir}) { 750 warn ("WARNING: \"ImageDir\" not specified\n"); 751 $error = "yes"; 752 } else { 753 ensureSL(\$$cfg{imagedir}); 754 mkdirhier $$cfg{imagedir} unless $opts->{check}; 755 } 756 } 757 if ($cfg->{threshmailserver} and not $cfg->{threshmailsender}){ 758 warn ("WARNING: If \"ThreshMailServer\" is defined, then \"ThreshMailSender\" must be defined too.\n"); 759 $error = "yes"; 760 } 761 if ($cfg->{threshmailsender} and not $cfg->{threshmailserver}){ 762 warn ("WARNING: If \"ThreshMailSender\" is defined, then \"ThreshMailServer\" must be defined too.\n"); 763 $error = "yes"; 764 } 765 # default ThreshHyst to 0.1 if ThreshDir is defined 766 if ($cfg->{threshdir}){ 767 $cfg->{threshhyst} = 0.1 unless $cfg->{threshhyst}; 768 } 769 # build relativ path from htmldir to image dir. 770 my @htmldir = split /\Q${MRTG_lib::SL}\E+/, $$cfg{htmldir}; 771 my @imagedir = split /\Q${MRTG_lib::SL}\E+/, $$cfg{imagedir}; 772 while (scalar @htmldir > 0 and $htmldir[0] eq $imagedir[0]) { 773 shift @htmldir; shift @imagedir; 774 } 775 # this is for the webpages so we use / path separator always 776 $$cfg{imagehtml} = ""; 777 foreach my $dir ( @htmldir ) { 778 $$cfg{imagehtml} .= "../" if $dir; 779 } 780 map {$$cfg{imagehtml} .= "$_/" } @imagedir; 781 # relative path is built 782 debug('dir', "imagehtml = $$cfg{imagehtml}"); 783 784 $SNMP_util::CacheFile = "$$cfg{'logdir'}oid-mib-cache.txt"; 785 $Net_SNMP_util::CacheFile = "$$cfg{'logdir'}oid-mib-cache.txt"; 786 787 if (defined $$cfg{loadmibs}) { 788 my($mibFile); 789 foreach $mibFile (split /[,\s]+/, $$cfg{loadmibs}) { 790 snmpQueue_MIB_File($mibFile); 791 } 792 } 793 if(defined $$cfg{pathadd}){ 794 ensureSL(\$$cfg{pathadd}); 795 $ENV{PATH} = "$$cfg{pathadd}${MRTG_lib::PS}$ENV{PATH}"; 796 } 797 if(defined $$cfg{libadd}){ 798 ensureSL(\$$cfg{libadd}); 799 debug('eval',"libadd $$cfg{libadd}\n"); 800 local $SIG{__DIE__}; 801 eval "use lib qw( $$cfg{libadd} )"; 802 my @match; 803 foreach my $dir (@INC){ 804 push @match, $dir if -f "$dir/RRDs.pm"; 805 } 806 warn "WARN: found several copies of RRDs.pm in your path: ". 807 (join ", ", @match)." I will be using $match[0]. This could ". 808 "be a problem if this is an old copy and you think I would be using a newer one!\n" 809 if $#match > 0; 810 } 811 $$cfg{logformat} = 'rateup' unless defined $$cfg{logformat}; 812 813 if($$cfg{logformat} eq 'rrdtool') { 814 my ($name); 815 if ($MRTG_lib::OS eq 'NT' or $MRTG_lib::OS eq 'OS2'){ 816 $name = "rrdtool.exe"; 817 } elsif ($MRTG_lib::OS eq 'NW'){ 818 $name = "rrdtool.nlm"; 819 } else { 820 $name = "rrdtool"; 821 } 822 foreach my $path (split /\Q${MRTG_lib::PS}\E/, $ENV{PATH}) { 823 ensureSL(\$path); 824 -f "$path$name" && do { 825 $$cfg{'rrdtool'} = "$path$name"; 826 last;} 827 }; 828 die "ERROR: could not find $name. Use PathAdd: in mrtg.cfg to help mrtg find rrdtool\n" 829 unless defined $$cfg{rrdtool}; 830 debug ('rrd',"found rrdtool in $$cfg{rrdtool}"); 831 my $found; 832 foreach my $path (@INC) { 833 ensureSL(\$path); 834 -f "${path}RRDs.pm" && do { 835 $found=1; 836 last;} 837 }; 838 die "ERROR: could not find RRDs.pm. Use LibAdd: in mrtg.cfg to help mrtg find RRDs.pm\n" 839 unless defined $found; 840 } 841 if (defined $$cfg{snmpoptions}) { 842 debug('eval',"redef snmpotions $cfg->{snmpoptions}"); 843 local $SIG{__DIE__}; 844 $cfg->{snmpoptions} = eval('{'.$cfg->{snmpoptions}.'}'); 845 } 846 847 # default interval is 5 minutes 848 if ($cfg->{interval} and $cfg->{interval} =~ /(\d+)(?::(\d+))?/){ 849 $cfg->{interval} = $1; 850 $cfg->{interval} += $2/60.0 if $2; 851 } else { 852 $cfg->{interval} = 5; 853 } 854 unless ($$cfg{logformat} eq 'rrdtool') { 855 # interval has to be 5 minutes at least without userrdtool 856 if ($$cfg{interval} < 5.0) { 857 die "ERROR: CFG Error in \"Interval\": should be at least 5 Minutes (unless you use rrdtool)"; 858 } 859 } 860 861 # Check for a Conversion Code file and evaluate its contents, which 862 # should consist of one or more subroutine definitions. The code goes 863 # into the MRTGConversion name space. 864 if( exists $cfg->{ conversioncode } ) { 865 open CONV, $cfg->{ conversioncode } 866 or die "ERROR: Can't open file $cfg->{ conversioncode }\n"; 867 my $code = "local \$SIG{__DIE__};package MRTGConversion;\n". join( '', <CONV> ) . "1;\n"; 868 close CONV; 869 debug('eval',"covnversioncode $cfg->{ conversioncode }"); 870 die "ERROR: File $cfg->{ conversioncode } conversion code evaluation failed\n$@\n" 871 unless eval $code; 872 } 873 874 my $thresh_error; 875 876 foreach $rou (@$routers) { 877 # and now for the testing 878 if (defined $rcfg->{threshmailaddress}{$rou}){ 879 if (not defined $cfg->{threshmailserver} and not $thresh_error){ 880 warn (qq{ERROR: ThreshMailAddress[$rou]: specified without "ThreshMailServer:"}); 881 $error = "yes"; 882 $thresh_error = "yes"; 883 } 884 # the dependency between sender and server is taken care of already 885 } 886 if (! defined $rcfg->{snmpoptions}{$rou}) { 887 $rcfg->{snmpoptions}{$rou} = {%{$cfg->{snmpoptions}}} 888 if defined $cfg->{snmpoptions}; 889 } else { 890 debug('eval',"redef snmpoptions[$rou] $rcfg->{snmpoptions}{$rou}"); 891 local $SIG{__DIE__}; 892 $rcfg->{snmpoptions}{$rou} = eval('{'.$rcfg->{snmpoptions}{$rou}.'}'); 893 } 894 $rcfg->{snmpoptions}{$rou}{avoid_negative_request_ids} = 1; 895 # $rcfg->{snmpoptions}{$rou}{domain} = 'udp'; 896 897 if (! defined $$rcfg{"title"}{$rou}) { 898 warn ("WARNING: \"Title[$rou]\" not specified\n"); 899 $error = "yes"; 900 } 901 if (defined $$rcfg{'directory'}{$rou} and $$rcfg{'directory'}{$rou} ne "") { 902 # They specified a directory for this router. Append the 903 # pathname seperator to it (so that it can either be present or 904 # absent, and the rules for including it are the same). 905 ensureSL(\$$rcfg{'directory'}{$rou}); 906 for my $x (qw(imagedir logdir htmldir)) { 907 mkdirhier $$cfg{$x}.$$rcfg{directory}{$rou} unless $opts->{check}; 908 } 909 $$rcfg{'directory_web'}{$rou} = $$rcfg{'directory'}{$rou}; 910 $$rcfg{'directory_web'}{$rou} =~ s/\Q${MRTG_lib::SL}\E+/\//g; 911 debug('dir', "directory for $rou '$$rcfg{'directory_web'}{$rou}'"); 912 } else { 913 $$rcfg{'directory'}{$rou}=""; 914 $$rcfg{'directory_web'}{$rou}=""; 915 } 916 917 if (defined $$rcfg{"pagetop"}{$rou}) { 918 $$rcfg{"pagetop"}{$rou} =~ s/\\n/\n/g; 919 } 920 921 922 if (defined $$rcfg{"pagefoot"}{$rou}) { 923 # allow for linebreaks 924 $$rcfg{"pagefoot"}{$rou} =~ s/\\n/\n/g; 925 } 926 927 $$rcfg{"maxbytes1"}{$rou} = $$rcfg{"maxbytes"}{$rou} unless defined $$rcfg{"maxbytes1"}{$rou}; 928 $$rcfg{"maxbytes2"}{$rou} = $$rcfg{"maxbytes"}{$rou} unless defined $$rcfg{"maxbytes2"}{$rou}; 929 930 if ( not defined $$rcfg{"maxbytes"}{$rou} 931 and not defined $$rcfg{"maxbytes1"}{$rou} 932 and not defined $$rcfg{"maxbytes2"}{$rou}) { 933 warn ("WARNING: \"MaxBytes[$rou]\" not specified\n"); 934 $error = "yes"; 935 } else { 936 937 if (not defined $$rcfg{"maxbytes1"}{$rou}) { 938 warn ("WARNING: \"MaxBytes1[$rou]\" not specified\n"); 939 $error = "yes"; 940 } 941 if (not defined $$rcfg{"maxbytes2"}{$rou}) { 942 warn ("WARNING: \"MaxBytes2[$rou]\" not specified\n"); 943 $error = "yes"; 944 } 945 } 946 # set default extension 947 if (! defined $$rcfg{"extension"}{$rou}) { 948 $$rcfg{"extension"}{$rou}="html"; 949 } 950 951 # set default size 952 if (! defined $$rcfg{"xsize"}{$rou}) { 953 $$rcfg{"xsize"}{$rou}=400; 954 } 955 if (! defined $$rcfg{"ysize"}{$rou}) { 956 $$rcfg{"ysize"}{$rou}=100; 957 } 958 if (! defined $$rcfg{"ytics"}{$rou}) { 959 $$rcfg{"ytics"}{$rou}=4; 960 } 961 if (! defined $$rcfg{"yticsfactor"}{$rou}) { 962 $$rcfg{"yticsfactor"}{$rou}=1; 963 } 964 if (! defined $$rcfg{"factor"}{$rou}) { 965 $$rcfg{"factor"}{$rou}=1; 966 } 967 968 if (defined $$rcfg{"options"}{$rou}) { 969 my $opttemp = lc($$rcfg{"options"}{$rou}); 970 delete $$rcfg{"options"}{$rou}; 971 foreach $one_option (split /[,\s]+/, $opttemp) { 972 if (grep {$one_option eq $_} @known_options) { 973 $$rcfg{'options'}{$one_option}{$rou} = 1; 974 } else { 975 warn ("WARNING: Option[$rou]: \"$one_option\" is unknown\n"); 976 $error="yes"; 977 } 978 } 979 if ($rcfg->{'options'}{derive}{$rou} and not $cfg->{logformat} eq 'rrdtool'){ 980 warn ("WARNING: Option[$rou]: \"derive\" works only with rrdtool logformat\n"); 981 $error="yes"; 982 } 983 } 984 # 985 # Check out routeruptime definition 986 # 987 if (defined $$rcfg{"routeruptime"}{$rou}) { 988 ($$rcfg{"community"}{$rou},$$rcfg{"router"}{$rou}) = 989 split(/@/,$$rcfg{"routeruptime"}{$rou}); 990 } 991 # 992 # Check out target definition 993 # 994 if (defined $$rcfg{"target"}{$rou}) { 995 $$rcfg{targorig}{$rou} = $$rcfg{target}{$rou}; 996 debug ('tarp',"Starting $rou -> $$rcfg{target}{$rou}"); 997 # Decide whether to turn on IPv6 support for this target. 998 # IPv6 support is turned on only if the EnableIPv6 global 999 # setting is yes and the IPv4Only per-target setting is no. 1000 # If IPv6 is disabled, we set IPv4Only to true for all 1001 # targets, thus disabling all IPv6-related code. 1002 my $ipv4only = 1; 1003 if ($$cfg{enableipv6} and $$cfg{enableipv6} eq 'yes') { 1004 # IPv4Only is off by default 1005 $ipv4only = 0 1006 unless (defined $$rcfg{ipv4only}{$rou}) && (lc($$rcfg{ipv4only}{$rou}) eq 'yes'); 1007 } 1008 # Check if nohc has been set, designating a low-speed interface 1009 # without working HC counters. Default is that high-speed 1010 # counters exist. 1011 my $nohc = 0; 1012 $nohc = 1 if (defined $$rcfg{nohc}{$rou}) && (lc($$rcfg{nohc}{$rou}) eq 'yes'); 1013 1014 ( $$rcfg{target}{$rou}, $$rcfg{uniqueTarget}{$rou} ) = 1015 targparser( $$rcfg{target}{$rou}, $target, $targIndex, $ipv4only, $rcfg->{snmpoptions}{$rou}, $nohc ); 1016 } else { 1017 warn ("WARNING: I can't find a \"target[$rou]\" definition\n"); 1018 $error = "yes"; 1019 } 1020 1021 # colors format: name#hexcol, 1022 if (defined $$rcfg{"colours"}{$rou}) { 1023 if ($$rcfg{'options'}{'dorelpercent'}{$rou}) { 1024 if ($$rcfg{"colours"}{$rou} =~ 1025 /^([^\#]+)(\#[0-9a-f]{6})\s*,\s* 1026 ([^\#]+)(\#[0-9a-f]{6})\s*,\s* 1027 ([^\#]+)(\#[0-9a-f]{6})\s*,\s* 1028 ([^\#]+)(\#[0-9a-f]{6})\s*,\s* 1029 ([^\#]+)(\#[0-9a-f]{6})/ix) { 1030 ($$rcfg{'col1'}{$rou}, $$rcfg{'rgb1'}{$rou}, 1031 $$rcfg{'col2'}{$rou}, $$rcfg{'rgb2'}{$rou}, 1032 $$rcfg{'col3'}{$rou}, $$rcfg{'rgb3'}{$rou}, 1033 $$rcfg{'col4'}{$rou}, $$rcfg{'rgb4'}{$rou}, 1034 $$rcfg{'col5'}{$rou}, $$rcfg{'rgb5'}{$rou}) = 1035 ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10); 1036 } else { 1037 warn ("WARNING: \"colours[$rou]\" for colour definition\n". 1038 " use the format: Name#hexcolour, Name#Hexcolour,...\n", 1039 " note, that dorelpercent requires 5 colours"); 1040 $error="yes"; 1041 } 1042 } else { 1043 if ($$rcfg{"colours"}{$rou} =~ 1044 /^([^\#]+)(\#[0-9a-f]{6})\s*,\s* 1045 ([^\#]+)(\#[0-9a-f]{6})\s*,\s* 1046 ([^\#]+)(\#[0-9a-f]{6})\s*,\s* 1047 ([^\#]+)(\#[0-9a-f]{6})/ix) { 1048 ($$rcfg{'col1'}{$rou}, $$rcfg{'rgb1'}{$rou}, 1049 $$rcfg{'col2'}{$rou}, $$rcfg{'rgb2'}{$rou}, 1050 $$rcfg{'col3'}{$rou}, $$rcfg{'rgb3'}{$rou}, 1051 $$rcfg{'col4'}{$rou}, $$rcfg{'rgb4'}{$rou}) = 1052 ($1, $2, $3, $4, $5, $6, $7, $8); 1053 } else { 1054 warn "WARNING: \"colours[$rou]\" for colour definition\n". 1055 " use the format: Name#hexcolour, Name#Hexcolour,...\n"; 1056 $error="yes"; 1057 } 1058 } 1059 } else { 1060 if (defined $$rcfg{'options'}{'dorelpercent'}{$rou}) { 1061 ($$rcfg{'col1'}{$rou}, $$rcfg{'rgb1'}{$rou}, 1062 $$rcfg{'col2'}{$rou}, $$rcfg{'rgb2'}{$rou}, 1063 $$rcfg{'col3'}{$rou}, $$rcfg{'rgb3'}{$rou}, 1064 $$rcfg{'col4'}{$rou}, $$rcfg{'rgb4'}{$rou}, 1065 $$rcfg{'col5'}{$rou}, $$rcfg{'rgb5'}{$rou}) = 1066 ("GREEN","#00cc00", 1067 "BLUE","#0000ff", 1068 "DARK GREEN","#006600", 1069 "MAGENTA","#ff00ff", 1070 "AMBER","#ef9f4f"); 1071 } else { 1072 ($$rcfg{'col1'}{$rou}, $$rcfg{'rgb1'}{$rou}, 1073 $$rcfg{'col2'}{$rou}, $$rcfg{'rgb2'}{$rou}, 1074 $$rcfg{'col3'}{$rou}, $$rcfg{'rgb3'}{$rou}, 1075 $$rcfg{'col4'}{$rou}, $$rcfg{'rgb4'}{$rou}) = 1076 ("GREEN","#00cc00", 1077 "BLUE","#0000ff", 1078 "DARK GREEN","#006600", 1079 "MAGENTA","#ff00ff"); 1080 } 1081 } 1082 # Background color, format: #rrggbb 1083 if (! defined $$rcfg{'background'}{$rou}) { 1084 $$rcfg{'background'}{$rou} = "#ffffff"; 1085 } 1086 if ($$rcfg{'background'}{$rou} =~ /^(\#[0-9a-f]{6})/i) { 1087 $$rcfg{'backgc'}{$rou} = "$1"; 1088 } else { 1089 warn "WARNING: \"background[$rou]: ". 1090 "$$rcfg{'background'}{$rou}\" for colour definition\n". 1091 " use the format: #rrggbb\n"; 1092 $error="yes"; 1093 } 1094 1095 if (! defined $$rcfg{'kilo'}{$rou}) { 1096 $$rcfg{'kilo'}{$rou} = 1000; 1097 } 1098 if (defined $$rcfg{'kmg'}{$rou}) { 1099 $$rcfg{'kmg'}{$rou} =~ s/\s+//g; 1100 } 1101 1102 if (! defined $$rcfg{'xzoom'}{$rou}) { 1103 $$rcfg{'xzoom'}{$rou} = 1.0; 1104 } 1105 if (! defined $$rcfg{'yzoom'}{$rou}) { 1106 $$rcfg{'yzoom'}{$rou} = 1.0; 1107 } 1108 if (! defined $$rcfg{'xscale'}{$rou}) { 1109 $$rcfg{'xscale'}{$rou} = 1.0; 1110 } 1111 if (! defined $$rcfg{'yscale'}{$rou}) { 1112 $$rcfg{'yscale'}{$rou} = 1.0; 1113 } 1114 if (defined $$rcfg{'options'}{'pngdate'}{$rou}) { 1115 $$rcfg{'timestrpos'}{$rou} = 'RU'; 1116 $$rcfg{'timestrfmt'}{$rou} = $$rcfg{'timezone'}{$rou} ? "%Y-%m-%d %H:%M %Z" : "%Y-%m-%d %H:%M"; 1117 delete $$rcfg{'options'}{'pntdate'}{$rou} 1118 } 1119 if (! defined $$rcfg{'timestrpos'}{$rou}) { 1120 $$rcfg{'timestrpos'}{$rou} = 'NO'; 1121 } 1122 if (! defined $$rcfg{'timestrfmt'}{$rou}) { 1123 $$rcfg{'timestrfmt'}{$rou} = "%Y-%m-%d %H:%M"; 1124 } 1125 if ($error eq "yes") { 1126 die "ERROR: Please fix the error(s) in your config file\n"; 1127 } 1128 } 1129} 1130 1131# make sure string ends with a slash. 1132sub ensureSL($) { 1133# return; 1134 my $ref = shift; 1135 return if not $$ref; 1136 debug('dir',"ensure path IN: '$$ref'"); 1137 if (${MRTG_lib::SL} eq '\\'){ 1138 # two slashes at the start of the string are OK 1139 $$ref =~ s/(.)\Q${MRTG_lib::SL}\E+/$1${MRTG_lib::SL}/g; 1140 } else { 1141 $$ref =~ s/\Q${MRTG_lib::SL}\E+/${MRTG_lib::SL}/g; 1142 } 1143 $$ref =~ s/\Q${MRTG_lib::SL}\E*$/${MRTG_lib::SL}/; 1144 debug('dir',"ensure path OUT: '$$ref'"); 1145} 1146 1147# convert current supplied time into a nice date string 1148 1149sub datestr ($) { 1150 my ($time) = shift || return 0; 1151 my ($wday) = ('Sunday','Monday','Tuesday','Wednesday', 1152 'Thursday','Friday','Saturday')[(localtime($time))[6]]; 1153 my ($month) = ('January','February' ,'March' ,'April' , 1154 'May' , 'June' , 'July' , 'August' , 'September' , 1155 'October' , 1156 'November' , 'December' )[(localtime($time))[4]]; 1157 my ($mday,$year,$hour,$min) = (localtime($time))[3,5,2,1]; 1158 if ($min<10) { 1159 $min = "0$min"; 1160 } 1161 return "$wday, $mday $month ".($year+1900)." at $hour:$min"; 1162} 1163 1164 1165# create expire date for expiery in ARG Minutes 1166 1167sub expistr ($) { 1168 my ($time) = time+int($_[0]*60)+5; 1169 my ($wday) = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat')[(gmtime($time))[6]]; 1170 my ($month) = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep', 1171 'Oct','Nov','Dec')[(gmtime($time))[4]]; 1172 my ($mday,$year,$hour,$min,$sec) = (gmtime($time))[3,5,2,1,0]; 1173 if ($mday<10) { 1174 $mday = "0$mday"; 1175 } 1176 ; 1177 if ($hour<10) { 1178 $hour = "0$hour"; 1179 } 1180 ; 1181 if ($min<10) { 1182 $min = "0$min"; 1183 } 1184 if ($sec<10) { 1185 $sec = "0$sec"; 1186 } 1187 return "$wday, $mday $month ".($year+1900)." $hour:$min:$sec GMT"; 1188} 1189 1190sub create_pid ($) { 1191 my $pidfile = shift; 1192 return if ($OS eq 'NT' ); 1193 return if -e $pidfile; 1194 if ( open(PIDFILE,">$pidfile")) { 1195 close PIDFILE; 1196 } else { 1197 warn "cannot write to $pidfile: $!\n"; 1198 } 1199} 1200 1201sub demonize_me ($) { 1202 my $pidfile = shift; 1203 my $cfgfile = shift; 1204 print "Daemonizing MRTG ...\n"; 1205 if ( $OS eq 'NT' ) { 1206 print "Do Not close this window. Or MRTG will die\n"; 1207# require Win32::Console; 1208# my $CONSOLE = new Win32::Console; 1209 # detach process from Console 1210# $CONSOLE->Flush(); 1211# $CONSOLE->Free(); 1212# $CONSOLE->Alloc(); 1213# $CONSOLE->Mode() 1214 } 1215 elsif( $OS eq 'OS2') 1216 { 1217 require OS2::Process; 1218 if (my_type() eq 'VIO'){ 1219 $main::Cleanfile3 = $pidfile; 1220 1221 print "MRTG detached. PID=".system(P_DETACH(),$^X." ".$0." ".$cfgfile); 1222 exit; 1223 } 1224 } else { 1225 # Check out if there is another mrtg running before forking 1226 if (defined $pidfile && open(READPID, "<$pidfile")){ 1227 if (not eof READPID) { 1228 chomp(my $input = <READPID>); # read process id in pidfile 1229 my ($pid) = $input =~ /^(\d+)$/; # to improve taint-safe code 1230 if ($pid && kill 0 => $pid) {# oops - the pid actually exists 1231 die "ERROR: I Quit! Another copy of mrtg seems to be running. Check $pidfile\n"; 1232 } 1233 } 1234 close READPID; 1235 } 1236 1237 defined (my $pid = fork) or die "Can't fork: $!"; 1238 if ($pid) { 1239 exit; 1240 } else { 1241 if (defined $pidfile){ 1242 $main::Cleanfile3 = $pidfile; 1243 if (open(PIDFILE,">$pidfile")) { 1244 print PIDFILE "$$\n"; 1245 close PIDFILE; 1246 } else { 1247 warn "cannot write to $pidfile: $!\n"; 1248 } 1249 } 1250 require 'POSIX.pm'; 1251 POSIX::setsid() or die "Can't start a new session: $!"; 1252 open STDOUT,'>/dev/null' or die "ERROR: Redirecting STDOUT to /dev/null: $!"; 1253 open STDERR,'>/dev/null' or die "ERROR: Redirecting STDERR to /dev/null: $!"; 1254 open STDIN, '</dev/null' or die "ERROR: Redirecting STDIN from /dev/null: $!"; 1255 } 1256 } 1257} 1258 1259# Create a new SNMP target entry for the @$target array and return a 1260# reference to it 1261sub newSnmpTarg( $$ ) { 1262 my $t = shift; # target string 1263 my $if = shift; # interface match strings 1264 my $targ = { }; # New target closure 1265 $targ->{ Methode } = 'SNMP'; 1266 $targ->{ Community } = $if->{ComStr}; 1267 $targ->{ Host } = ( defined $if->{HostIPv6} ) ? $if->{HostIPv6} : $if->{HostName}; 1268 $targ->{ SnmpOpt } = $if->{SnmpInfo}; 1269 $targ->{ snmpoptions} = $if->{snmpoptions}; 1270 $targ->{ Conversion } = ( defined $if->{ConvSub} ) ? $if->{ConvSub} : ''; 1271 for my $i( 0..1 ) { 1272 die 'ERROR: Malformed ', $i ? 'output ' : 'input ', "ifSpec in '$t'\n" 1273 if not defined $if->{OID}[$i] and not defined $if->{Alt}[$i]; 1274 $targ->{OID}[$i] = $if->{OID}[$i]; 1275 if( defined $if->{Alt}[$i] ) { 1276 if( defined $if->{Num}[$i] ) { 1277 $targ->{IfSel}[$i] = 'If'; 1278 $targ->{Key}[$i] = $if->{Num}[$i]; 1279 } elsif( defined $if->{IP}[$i] ) { 1280 $targ->{IfSel}[$i] = 'Ip'; 1281 $targ->{Key}[$i] = $if->{IP}[$i]; 1282 } elsif( defined $if->{Desc}[$i] ) { 1283 $targ->{IfSel}[$i] = 'Descr'; 1284 $targ->{Key}[$i] = $if->{Desc}[$i]; 1285 } elsif( defined $if->{Name}[$i] ) { 1286 $targ->{IfSel}[$i] = 'Name'; 1287 $targ->{Key}[$i] = $if->{Name}[$i]; 1288 } elsif( defined $if->{Eth}[$i] ) { 1289 $targ->{IfSel}[$i] = 'Eth'; 1290 $targ->{Key}[$i] = join( '-', map( { sprintf '%02x', hex $_ } split( /-/, $if->{Eth}[$i] ) ) ); 1291 } elsif( defined $if->{Type}[$i] ) { 1292 $targ->{IfSel}[$i] = 'Type'; 1293 $targ->{Key}[$i] = $if->{Type}[$i]; 1294 } else { 1295 die "ERROR: Internal error parsing ifSpec in '$t'\n"; 1296 } 1297 } else { 1298 $targ->{IfSel}[$i] = 'None'; 1299 $targ->{Key}[$i] = ''; 1300 } 1301 # Remove escaped characters and trailing space from Descr or Name Key 1302 $targ->{Key}[$i] =~ s/\\([\s:&@])/$1/g 1303 if $targ->{IfSel}[$i] eq 'Descr' or $targ->{IfSel}[$i] eq 'Name'; 1304 $targ->{Key}[$i] =~ s/[\0- ]+$//; 1305 } 1306 # Remove escaped characters from community 1307 $targ->{ Community } =~ s/\\([ @])/$1/g; 1308 return $targ; # Return new target closure 1309} 1310 1311# ( $string, $unique ) = targparser( $string, $target, $targIndex, $ipv4only ) 1312# Walk amd analyze the target string $string. $target is a reference to the 1313# array of targets being built. $targIndex is a reference to a hash of targets 1314# previously encountered indexed by target string. When $ipv4only is nonzero, 1315# only IPv4 is in use. Returns the modifed target string and the index of the 1316# @$target array to which the target refers if that index is unique. If the 1317# index is not unique, i.e. the target definition is a calculation involving 1318# two or more different targets, then the value -1 is returned for $unique. 1319# Targparser updates the target array avoiding duplicate targets. The goal is 1320# to substitute all target definitions with strings of the form 1321# "$t1$thisTarg$t2", where $thisTarg is the target index, and $t1 and $t2 are 1322# as defined below. The intended result is a target string that can be eval'ed 1323# in its entirety later on when monitoring data has been collected. This 1324# evaluation occurs in sub getcurrent in the main mrtg script. 1325 1326# Note: In the regular expressions in &targparser, we have avoided m/.../i 1327# and the variables &`, $&, and $'. Use of these makes regex processing less 1328# efficient. See Friedl, J.E.F. Mastering Regular Expressions. O'Reilly. 1329# p. 273 1330 1331sub targparser( $$$$$$ ) { 1332 # Target string (int:community@router, etc.) 1333 my $string = shift; 1334 # Reference to target array 1335 my $target = shift; 1336 # Reference to target index hash 1337 my $targIndex = shift; 1338 # Nonzero if only IPv4 is in use 1339 my $ipv4only = shift; 1340 # options passed per target. 1341 my $snmpoptions = shift; 1342 # Highspeed Counter test 1343 my $nohc = shift; 1344 1345 # Next available index in the @$target array 1346 my $idx = @$target; 1347 # Common match strings: pre-target, target, post-target 1348 my( $pre, $t, $post ); 1349 # Portion of string already parsed 1350 my $parsed = ''; 1351 # Initialize $unique to undefined. It will take on the $targIndex value 1352 # of the first target encountered. $otherTargCount will count the 1353 # number of other targets (targets with different values of $targIndex) 1354 # encountered during the parse. $unique will be returned as undef 1355 # unless $otherTargCount remains 0. 1356 my $unique = -1; 1357 my $otherTargCount = 0; 1358 1359 # Components of the target expression that are substituted into the 1360 # target string each time a target is identified. The substitution 1361 # string is the interpolated value of "$t1$targIndex$t2". At present 1362 # $t1 and $t2 are set to create a new BigFloat object. 1363# my $t1 = ' Math::BigFloat->new($target->['; 1364# my $t2 = ']{$mode}) '; 1365 # this gives problems with perl 5.005 so bigfloat is introduces in mrtg itself 1366 my $t1 = ' $target->['; 1367 my $t2 = ']{$mode} '; 1368 1369 # Find and substitute all external program targets 1370 while( ( $pre, $t, $post ) = $string =~ m< 1371 ^(.*?) # capture pre-target string 1372 ` # beginning of program target 1373 ((?:\\`|[^`])+) # capture target contents (\` allowed) 1374 ` # end of program target 1375 (.*)$ # capture post-target string 1376 >x ) { # Total of 3 captures 1377 my $thisTarg; 1378 if( exists $targIndex->{ $t } ) { 1379 # This program target has been encountered previously 1380 $thisTarg = $targIndex->{ $t }; 1381 debug( 'tarp', "Existing program target [$thisTarg]" ); 1382 } else { 1383 # A new program target is needed 1384 my $targ = { }; 1385 $targ->{ Methode } = 'EXEC'; 1386 $targ->{ Command } = $t; 1387 # Remove escaped backticks 1388 $targ->{ Command } =~ s/\\\`/\`/g; 1389 $target->[ $idx ] = $targ; 1390 $thisTarg = $idx++; 1391 $targIndex->{ $t } = $thisTarg; 1392 debug( 'tarp', "New program target [$thisTarg] '$t'" ); 1393 } 1394 $parsed .= "$pre$t1$thisTarg$t2"; 1395 $string = $post; 1396 if( $unique < 0 ) { 1397 $unique = $thisTarg; 1398 } else { 1399 $otherTargCount++ unless $thisTarg == $unique; 1400 } 1401 }; 1402 # Reset $string for new target type search 1403 $string = $parsed . $string; 1404 $parsed = ''; 1405 debug( 'tarp', "&targparser external done: '$string'" ); 1406 1407 # Common interface specification regex components 1408 1409 # Simple interface specification regex component. Matches interface 1410 # specification by IPv4 address, description, name, Ethernet address, or 1411 # type. 1412 my $ifSimple = 1413 ' (\d+)|' . # by number ($if->{Num}) 1414 ' / (\d+(?:\.\d+)+)|' . # by IPv4 address ($if->{IP}) 1415 ' \\\\ ((?:\\\\[\s:&@]|[^\s:&@])+)|' . # by description (allow \ \: \& \@) ($if->{Desc}) 1416 ' \# ((?:\\\\[\s:&@]|[^\s:&@])+)|' . # by name (allow \ \: \& \@) ($if->{Name}) 1417 ' ! ([a-fA-F0-9]+(?:-[a-fA-F0-9]+)+)|' . # by Ethernet address ($if->{Eth}) 1418 ' % (\d+)'; # by type ($if->{Type}) 1419 1420 # Complex interface specification regex component. Note that a null string 1421 # will match. Therefore the match must be postprocessed to check that 1422 # $ifOID and $ifAlt are not both null. 1423 my $ifComplex = 1424 '((?:\.\d+)*?\.?[-a-zA-Z0-9]*(?:\.\d+)*?)' . # OID possibly starting with a MIB name ($if->{OID}) 1425 '(' . # Interface specification alternatives: ($if->{Alt}) 1426 '\.' . # separator 1427 $ifSimple . # simple alternatives (6 variables) 1428 ')?'; # maybe none of the above 1429 1430 # Community-host interface specification regex component. 1431 my $ifComHost = 1432 '((?:\\\\[@ ]|[^\s@])+)' . # community string ('\@' and '\ ' allowed) ($if->{ComStr}) 1433 '@' . # separator 1434 '(?:(\[[a-fA-F0-9:]*\])|' . # hostname as IPv6 address ($if->{HostIPv6}) 1435 '([-\w]+(?:\.[-\w]+)*))' . # or DNS name ($if->{HostName}) 1436 '((?::[\d.!]*)*)' . # SNMP session configuration ($if->{SnmpInfo}) 1437 '(?:\|([a-zA-Z_][\w]*))?'; # numeric conversion subroutine ($if->{ConvSub}) 1438 1439 # Match strings for simple and complex interface specifications. Entries 1440 # are of the form $if->{k1}[i], where k1 is OID, Alt, Num, IP, Desc, 1441 # Name, Eth, or Type, and i is 0 or 1 (input or output). Entries may also 1442 # have the form $if->{k1}, where k1 is Rev, ComStr, HostIPv6, HostName, 1443 # SnmpInfo, or ConvSub, with no [i] in these cases. 1444 my $if; 1445 1446 # Find and substitute all complex OID targets 1447 1448 while( ( $pre, $t, $if->{OID}[0], $if->{Alt}[0], $if->{Num}[0], 1449 $if->{IP}[0], $if->{Desc}[0], $if->{Name}[0], $if->{Eth}[0], 1450 $if->{Type}[0], $if->{OID}[1], $if->{Alt}[1], $if->{Num}[1], 1451 $if->{IP}[1], $if->{Desc}[1], $if->{Name}[1], $if->{Eth}[1], 1452 $if->{Type}[1], $if->{ComStr}, $if->{HostIPv6}, $if->{HostName}, 1453 $if->{SnmpInfo}, $if->{ConvSub}, $post ) = $string =~ m< 1454 ^(.*?) # capture pre-target string 1455 ( # capture entire target 1456 ${ifComplex} # input interface specification (8 captures) 1457 & # separator 1458 ${ifComplex} # output interface specification (8 captures) 1459 : # separator 1460 ${ifComHost} # community-host specification (5 captures) 1461 ) # end of entire target capture 1462 (.*)$ # capture post-target string 1463 >x ) { # Total of 24 captures 1464 my $thisTarg; 1465 # Exception: skip and try to parse later as a simple target if 1466 # $if->{Desc}[0], $if->{Name}[0], $if->{Desc}[1], or $if->{Name}[1] 1467 # ends with a backslash character 1468 if( ( defined $if->{Desc}[0] and $if->{Desc}[0] =~ m<\\$> ) or 1469 ( defined $if->{Name}[0] and $if->{Name}[0] =~ m<\\$> ) or 1470 ( defined $if->{Desc}[1] and $if->{Desc}[1] =~ m<\\$> ) or 1471 ( defined $if->{Name}[1] and $if->{Name}[1] =~ m<\\$> ) ) { 1472 $parsed .= "$pre$t"; 1473 $string = $post; 1474 next; 1475 } 1476 if( exists $targIndex->{ $t } ) { 1477 # This complex target has been encountered previously 1478 $thisTarg = $targIndex->{ $t }; 1479 debug( 'tarp', "Existing complex target [$thisTarg]" ); 1480 } else { 1481 # A new complex target is needed 1482 my $targ = newSnmpTarg( $t, $if ); 1483 $targ->{ ipv4only } = $ipv4only; 1484 $targ->{ snmpoptions } = $snmpoptions; 1485 $target->[ $idx ] = $targ; 1486 $thisTarg = $idx++; 1487 $targIndex->{ $t } = $thisTarg; 1488 debug( 'tarp', "New complex target [$thisTarg] '$t':\n" . 1489 " Comu: $targ->{Community}, Host: $targ->{Host}\n" . 1490 " Opt: $targ->{SnmpOpt}, IPv4: $targ->{ipv4only}\n" . 1491 " Conv: $targ->{Conversion}\n" . 1492 " OID: $targ->{OID}[0], $targ->{OID}[1]\n" . 1493 " IfSel: $targ->{IfSel}[0], $targ->{IfSel}[1]\n" . 1494 " Key: $targ->{Key}[0], $targ->{Key}[1]" ); 1495 } 1496 $parsed .= "$pre$t1$thisTarg$t2"; 1497 $string = $post; 1498 if( $unique < 0 ) { 1499 $unique = $thisTarg; 1500 } else { 1501 $otherTargCount++ unless $thisTarg == $unique; 1502 } 1503 } 1504 # Reset $string and $parsedfor new target type search 1505 $string = $parsed . $string; 1506 $parsed = ''; 1507 debug( 'tarp', "&targparser complex done: '$string'" ); 1508 1509 # Find and substitute all simple targets 1510 1511 while( ( $pre, $t, $if->{Rev}, $if->{Num}[0], $if->{IP}[0], 1512 $if->{Desc}[0], $if->{Name}[0], $if->{Eth}[0], $if->{Type}[0], 1513 $if->{ComStr}, $if->{HostIPv6}, $if->{HostName}, $if->{SnmpInfo}, 1514 $if->{ConvSub}, $post ) = $string =~ m< 1515 ^(.*?) # capture pre-target string 1516 ( # capture entire target 1517 (-)? # capture direction reversal 1518 (?: ${ifSimple} ) # simple interface specification (6 captures) 1519 : # separator 1520 ${ifComHost} # community-host specification (5 captures) 1521 ) # end of entire target capture 1522 (.*)$ # capture post-target string 1523 >x ) { # Total of 15 captures 1524 my $thisTarg; 1525 if( exists $targIndex->{ $t } ) { 1526 # This simple target has been encountered previously 1527 $thisTarg = $targIndex->{ $t }; 1528 debug( 'tarp', "Existing simple target [$thisTarg]" ); 1529 } else { 1530 # A new simple target is needed 1531 # Reverse interface directions if indicated by $if->{Rev}. 1532 # The sense of $d1 and $d2 is 0 for input and 1 for output 1533 my $d1 = ( defined $if->{Rev} and $if->{Rev} eq '-' ) ? 1 : 0; 1534 my $d2 = 1 - $d1; 1535 # Set the OIDs depending on whether SNMPv2 has been specified 1536 # and on the direction 1537 if( $if->{SnmpInfo} =~ m/(?::[^:]*){4}:[32][Cc]?/ and $nohc == 0 ) { 1538 $if->{OID}[$d1] = 'ifHCInOctets'; 1539 $if->{OID}[$d2] = 'ifHCOutOctets'; 1540 } else { 1541 $if->{OID}[$d1] = 'ifInOctets'; 1542 $if->{OID}[$d2] = 'ifOutOctets'; 1543 } 1544 # Give $if->{Alt}[i] an arbitrary defined value so that 1545 # &newSnmpTarg works correctly 1546 $if->{Alt}[0] = 1; 1547 $if->{Alt}[1] = 1; 1548 # Copy input specification to output 1549 $if->{Num}[1] = $if->{Num}[0]; 1550 $if->{IP}[1] = $if->{IP}[0]; 1551 $if->{Desc}[1] = $if->{Desc}[0]; 1552 $if->{Name}[1] = $if->{Name}[0]; 1553 $if->{Eth}[1] = $if->{Eth}[0]; 1554 $if->{Type}[1] = $if->{Type}[0]; 1555 my $targ = newSnmpTarg( $t, $if ); 1556 $targ->{ snmpoptions} = $snmpoptions; 1557 $targ->{ ipv4only } = $ipv4only; 1558 $target->[ $idx ] = $targ; 1559 $thisTarg = $idx++; 1560 $targIndex->{ $t } = $thisTarg; 1561 debug( 'tarp', "New simple target [$thisTarg] '$t':\n" . 1562 " Comu: $targ->{Community}, Host: $targ->{Host}\n" . 1563 " Opt: $targ->{SnmpOpt}, IPv4: $targ->{ipv4only}\n" . 1564 " Conv: $targ->{Conversion}\n" . 1565 " OID: $targ->{OID}[0], $targ->{OID}[1]\n" . 1566 " IfSel: $targ->{IfSel}[0], $targ->{IfSel}[1]\n" . 1567 " Key: $targ->{Key}[0], $targ->{Key}[1]" ); 1568 } 1569 $parsed .= "$pre$t1$thisTarg$t2"; 1570 $string = $post; 1571 if( $unique < 0 ) { 1572 $unique = $thisTarg; 1573 } else { 1574 $otherTargCount++ unless $thisTarg == $unique; 1575 } 1576 } 1577 # Assemble string to be returned 1578 $string = $parsed . $string; 1579 # Set $unique undefined if more than one target is referred to in the 1580 # target string 1581 $unique = -1 if $otherTargCount; 1582 debug( 'tarp', "&targparser simple done: '$string'" ); 1583 debug( 'tarp', "&targparser returning: unique = $unique" ); 1584 return ( $string, $unique ); 1585} 1586 1587# Display of &targparser intermediate values for debugging purposes. Call as 1588# showMatch( $string, $pre, $t, $post, $if ) from within &targparser. 1589sub showMatch( $$$$$ ) { 1590 my( $string, $pre, $t, $post, $if ) = @_; 1591 warn "# Matching on string '$string'\n"; 1592 warn "# Prematch: '$pre'\n"; 1593 warn "# Target: '$t'\n"; 1594 warn "# Postmatch: '$post'\n"; 1595 warn "# Captured:\n"; 1596 foreach my $k( keys %$if ) { 1597 if( ref( $if->{$k} ) eq 'ARRAY' ) { 1598 warn "# \$if->{$k}[0,1]: '", 1599 ( defined $if->{$k}[0] ) ? $if->{$k}[0] : 'undef', "', '", 1600 ( defined $if->{$k}[1] ) ? $if->{$k}[1] : 'undef', "'\n"; 1601 } else { 1602 warn "# \$if->{$k}: '", 1603 ( defined $if->{$k} ) ? $if->{$k} : 'undef', "'\n"; 1604 } 1605 } 1606} 1607 1608sub readconfcache ($) { 1609 my $cfgfile = shift; 1610 my %confcache; 1611 if (open (CFGOK,"<$cfgfile")) { 1612 while (<CFGOK>) { 1613 chomp; 1614 next unless /\t/; #ignore odd lines 1615 next if /^\S+:/; #ignore legacy lines 1616 my ($host,$method,$key,$if) = split (/\t/, $_); 1617 $key =~ s/[\0- ]+$//; # no trailing whitespace in keys realy ! 1618 $key =~ s/[\0- ]/ /g; # all else becomes a normal space ... get a life 1619 $confcache{$host}{$method}{$key} = $if; 1620 } 1621 close CFGOK; 1622 } 1623 return \%confcache; 1624} 1625 1626sub writeconfcache ($$) { 1627 my $confcache = shift; 1628 my $cfgfile = shift; 1629 if ($cfgfile ne '&STDOUT'){ 1630 open (CFGOK,">$cfgfile") or die "ERROR: writing $cfgfile.ok: $!"; 1631 } 1632 my @hosts; 1633 if (defined $$confcache{___updated}) { 1634 @hosts = @{$$confcache{___updated}} ; 1635 delete $$confcache{___updated}; 1636 } else { 1637 @hosts = grep !/^___/, keys %{$confcache} 1638 } 1639 foreach my $host (sort @hosts) { 1640 foreach my $method (sort keys %{$$confcache{$host}}) { 1641 foreach my $key (sort keys %{$$confcache{$host}{$method}}) { 1642 if ($cfgfile ne '&STDOUT'){ 1643 print CFGOK "$host\t$method\t$key\t". 1644 $$confcache{$host}{$method}{$key},"\n"; 1645 } else { 1646 print "$host\t$method\t$key\t". 1647 $$confcache{$host}{$method}{$key},"\n"; 1648 } 1649 } 1650 } 1651 } 1652 close CFGOK; 1653} 1654 1655sub cleanhostkey ($){ 1656 my $host = shift; 1657 return undef unless defined $host; 1658 $host =~ s/(:\d*)(?:(:\d*)(?:(:\d*)(?:(:\d*)(?:(:\d*)))))$/$1$5/ 1659 or 1660 $host =~ s/(:\d*)(?:(:\d*)(?:(:\d*)(?:(:\d*)?)?)?)$/$1/; 1661 $host =~ s/:/_/g; # make sure that double invocations do not kill us 1662 return $host; 1663} 1664 1665sub storeincache ($$$$$){ 1666 my($confcache,$host,$method,$key,$value) = @_; 1667 $host = cleanhostkey $host; 1668 if (not defined $value ){ 1669 $$confcache{$host}{$method}{$key} = undef; 1670 return; 1671 } 1672 $value =~ s/[\0- ]/ /g; # all else becomes a normal space ... get a life 1673 $value =~ s/ +$//; # no trailing spaces 1674 if (defined $$confcache{$host}{$method}{$key} and 1675 $$confcache{$host}{$method}{$key} ne $value) { 1676 $$confcache{$host}{$method}{$key} = "Dup"; 1677 debug('coca',"store in confcache $host $method $key --> $value (duplicate)"); 1678 } else { 1679 $$confcache{$host}{$method}{$key} = $value; 1680 debug('coca',"store in confcache $host $method $key --> $value"); 1681 } 1682 1683} 1684 1685sub readfromcache ($$$$){ 1686 my($confcache,$host,$method,$key) = @_; 1687 $host = cleanhostkey $host; 1688 return $$confcache{$host}{$method}{$key}; 1689} 1690 1691 1692sub clearfromcache ($$){ 1693 my($confcache,$host) = @_; 1694 $host = cleanhostkey $host; 1695 delete $$confcache{$host}; 1696 debug('coca',"clear confcache $host"); 1697} 1698 1699 1700sub populateconfcache ($$$$$) { 1701 my $confcache = shift; 1702 my $host = shift; 1703 my $ipv4only = shift; 1704 my $reread = shift; 1705 my $snmpoptions = shift || {}; 1706 my $hostkey = cleanhostkey $host; 1707 return if defined $$confcache{$hostkey} and not $reread; 1708 my $snmp_errlevel = $SNMP_Session::suppress_warnings; 1709 my $net_snmp_errlevel = $Net_SNMP_util::suppress_warnings; 1710 $SNMP_Session::suppress_warnings = 3; 1711 $Net_SNMP_util::suppress_warnings = 3; 1712 debug('coca',"populate confcache $host"); 1713 1714 # clear confcache for host; 1715 delete $$confcache{$hostkey}; 1716 1717 my @ret; 1718 my %tables = ( ifDescr => 'Descr', 1719 ifName => 'Name', 1720 ifType => 'Type', 1721 ipAdEntIfIndex => 'Ip' ); 1722 my @nodes = qw (ifName ifDescr ifType ipAdEntIfIndex); 1723 # it seems that some devices only give back sensible data if their tables 1724 # are walked in the right ordere .... 1725 foreach my $node (@nodes) { 1726 next if $confcache->{___deadhosts}{$hostkey} and time - $confcache->{___deadhosts}{$hostkey} < 300; 1727 $SNMP_Session::errmsg = undef; 1728 $Net_SNMP_util::ErrorMessage = undef; 1729 @ret = &main::snmpwalk(v4onlyifnecessary($host, $ipv4only), $snmpoptions, $node); 1730 unless ( $SNMP_Session::errmsg or $Net_SNMP_util::ErrorMessage){ 1731 foreach my $ret (@ret) 1732 { 1733 my ($oid, $desc) = split(':', $ret, 2); 1734 if ($tables{$node} eq 'Ip') { 1735 storeincache($confcache,$host,$tables{$node},$oid,$desc); 1736 } else { 1737 $desc =~ s/[\0- ]+$//; #trailing whitespace is too sick for us 1738 $desc =~ s/[\0- ]/ /g; #whitespace is just whitespace 1739 storeincache($confcache,$host,$tables{$node},$desc,$oid); 1740 } 1741 }; 1742 } else { 1743 $confcache->{___deadhosts}{$hostkey} = time 1744 if defined($SNMP_Session::errmsg) and $SNMP_Session::errmsg =~ /no response received/; 1745 $confcache->{___deadhosts}{$hostkey} = time 1746 if defined($Net_SNMP_util::ErrorMessage) and $Net_SNMP_util::ErrorMessage =~ /No response from remote/; 1747 debug('coca',"Skipping $node scanning because $host does not seem to support it"); 1748 } 1749 } 1750 if ($confcache->{___deadhosts}{$hostkey} and time - $confcache->{___deadhosts}{$hostkey} < 300){ 1751 $SNMP_Session::suppress_warnings = $snmp_errlevel; 1752 $Net_SNMP_util::suppress_warnings = $snmp_errlevel; 1753 return; 1754 } 1755 $SNMP_Session::errmsg = undef; 1756 $Net_SNMP_util::ErrorMessage = undef; 1757 @ret = &main::snmpwalk(v4onlyifnecessary($host, $ipv4only), $snmpoptions, "ifPhysAddress"); 1758 unless ( $SNMP_Session::errmsg or $Net_SNMP_util::ErrorMessage){ 1759 foreach my $ret (@ret) 1760 { 1761 my ($oid, $bin) = split(':', $ret, 2); 1762 my $eth = unpack 'H*', $bin; 1763 my @eth; 1764 while ($eth =~ s/^..//){ 1765 push @eth, $&; 1766 } 1767 my $phys=join '-', @eth; 1768 storeincache($confcache,$host,"Eth",$phys,$oid); 1769 } 1770 } else { 1771 debug('coca',"Skipping ifPhysAddress scanning because $host does not seem to support it"); 1772 } 1773 1774 if (ref $$confcache{___updated} ne 'ARRAY') { 1775 $$confcache{___updated} = []; #init to empty array 1776 } 1777 push @{$$confcache{___updated}}, $hostkey; 1778 1779 $SNMP_Session::suppress_warnings = $snmp_errlevel; 1780 $Net_SNMP_util::supress_warnings = $net_snmp_errlevel; 1781} 1782 1783sub log2rrd ($$$) { 1784 my $router = shift; 1785 my $cfg = shift; 1786 my $rcfg = shift; 1787 my %mark; 1788 my %incomp; 1789 my %elapsed_time; 1790 my %rate; 1791 my %store; 1792 my %first_step; 1793 my %cur; 1794 my %next; 1795 my $rrd; 1796 my @steps = qw(300 1800 7200 86400); 1797 my %sizes = ( 300 => 600, 1800 => 700, 7200 => 775, 86400 => 797); 1798 1799 open R, "<$$cfg{logdir}$$rcfg{'directory'}{$router}$router.log" or 1800 die "ERROR: opening $$cfg{logdir}$$rcfg{'directory'}{$router}$router.log: $!"; 1801 debug('rrd',"converting $$cfg{logdir}$$rcfg{'directory'}{$router}$router.log"); 1802 my $latest_timestamp; 1803 my %latest_counter; 1804 chomp($_ = <R>); 1805 my $time; 1806 my $next_time; 1807 ($latest_timestamp,$latest_counter{in},$latest_counter{out}) = split /\s+/; 1808 chomp($_ = <R>); 1809 ($time,$cur{in},$cur{out},$cur{maxin},$cur{maxout}) = split /\s+/; 1810 1811 foreach my $s (@steps) { 1812 $mark{$s} = $latest_timestamp - ($latest_timestamp % $s) + $s; 1813 $first_step{$s} = $latest_timestamp - ($mark{$s} - $s); 1814 $elapsed_time{$s} = $s - $first_step{$s}; 1815 $rate{in}{$s}=$cur{in}; 1816 $rate{out}{$s}=$cur{out}; 1817 $rate{maxin}{$s}=$cur{maxin}; 1818 $rate{maxout}{$s}=$cur{maxout}; 1819 } 1820 1821 while(<R>){ 1822 chomp; 1823 ($next_time,$next{in},$next{out},$next{maxin},$next{maxout}) = 1824 split /\s+/; 1825 foreach my $s (@steps) { 1826 # bail if we have enough entries 1827 next if ref $store{in}{$s} and 1828 scalar @{$store{in}{$s}} > $sizes{$s}; 1829 1830 # ok we are still here. If next mark is before the next time 1831 # we take a short step, else we gobble up 1832 my $next_stop; 1833 do { 1834 if ($elapsed_time{$s} + $time - $next_time > $s) { 1835 $next_stop = $mark{$s}-$s; 1836 } else { 1837 $next_stop = $next_time; 1838 } 1839 my $time_diff = $time-$next_stop; 1840 foreach my $d (qw(in out)) { 1841 $rate{$d}{$s} = ($rate{$d}{$s} * $elapsed_time{$s} 1842 + $cur{$d} * $time_diff) / 1843 ($elapsed_time{$s} + $time_diff); 1844 } 1845 foreach my $d (qw(maxin maxout)){ 1846 $rate{$d}{$s} = $cur{$d} if $rate{$d}{$s} < $cur{$d}; 1847 } 1848 $elapsed_time{$s} += $time_diff; 1849# print "$time $next_stop\n" if $s == 300; 1850 if ($next_stop == $mark{$s}-$s) { 1851 foreach my $t (qw(in out maxin maxout)){ 1852 $rate{$t}{$s}/=3600 1853 if (defined $$rcfg{'options'}{'perhour'}{$router}); 1854 $rate{$t}{$s}/=60 1855 if (defined $$rcfg{'options'}{'perminute'}{$router}); 1856 push @{$store{$t}{$s}}, $rate{$t}{$s}; 1857 } 1858 $mark{$s} -= $s; 1859 $rate{maxin}{$s} = 0; 1860 $rate{maxout}{$s} = 0; 1861 $elapsed_time{$s} = 0; 1862 } 1863 } while ($next_stop > $next_time ); 1864 } 1865 ($time,$cur{in},$cur{out},$cur{maxin},$cur{maxout}) = 1866 ($next_time,$next{in},$next{out},$next{maxin},$next{maxout}); 1867 } 1868 close R; 1869 # lets see if we have rrdtool 1.2 at our hands 1870 my $VERSION = '0001'; 1871 if ($RRDs::VERSION >= 1.2){ 1872 $VERSION = '0003'; 1873 } 1874 my $DST; 1875 my $pdprepin = (shift @{$store{in}{300}})*($first_step{300}); 1876 my $pdprepout = (shift @{$store{out}{300}})*($first_step{300}); 1877 1878 if (defined $$rcfg{'options'}{'absolute'}{$router}) { 1879 $DST = 'ABSOLUTE' 1880 } elsif (defined $$rcfg{'options'}{'gauge'}{$router}) { 1881 $DST = 'GAUGE' 1882 } else { 1883 $DST = 'COUNTER' 1884 } 1885 1886 my $MHB = int($$cfg{interval} * 60 * 2); 1887 1888 my $MAX1 = 1889 $$rcfg{'absmax'}{$router} 1890 || $$rcfg{'maxbytes1'}{$router} 1891 || 'U'; 1892 1893 my $MAX2 = 1894 $$rcfg{'absmax'}{$router} 1895 || $$rcfg{'maxbytes2'}{$router} 1896 || 'U'; 1897 1898 $rrd = <<RRD; 1899<!-- MRTG Log converted to RRD --> 1900<rrd> 1901 <version> $VERSION </version> 1902 <step> 300 </step> 1903 <lastupdate> $latest_timestamp </lastupdate> 1904 1905 <ds> 1906 <name> ds0 </name> 1907 <type> $DST </type> 1908 <minimal_heartbeat> $MHB </minimal_heartbeat> 1909 <min> 0 </min> 1910 <max> $MAX1 </max> 1911 1912 <!-- PDP Status --> 1913 <last_ds> $latest_counter{in} </last_ds> 1914 <value> $pdprepin </value> 1915 <unknown_sec> 0 </unknown_sec> 1916 </ds> 1917 1918 <ds> 1919 <name> ds1 </name> 1920 <type> $DST </type> 1921 <minimal_heartbeat> $MHB </minimal_heartbeat> 1922 <min> 0 </min> 1923 <max> $MAX2 </max> 1924 1925 <!-- PDP Status --> 1926 <last_ds> $latest_counter{out} </last_ds> 1927 <value> $pdprepout </value> 1928 <unknown_sec> 0 </unknown_sec> 1929 </ds> 1930RRD 1931 $first_step{300} = 0; # invalidate 1932 addarch(1,'AVERAGE','in','out',\%store,\%first_step,\$rrd); 1933 addarch(6,'AVERAGE','in','out',\%store,\%first_step,\$rrd); 1934 addarch(24,'AVERAGE','in','out',\%store,\%first_step,\$rrd); 1935 addarch(288,'AVERAGE','in','out',\%store,\%first_step,\$rrd); 1936 addarch(1,'MAX','maxin','maxout',\%store,\%first_step,\$rrd); 1937 addarch(6,'MAX','maxin','maxout',\%store,\%first_step,\$rrd); 1938 addarch(24,'MAX','maxin','maxout',\%store,\%first_step,\$rrd); 1939 addarch(288,'MAX','maxin','maxout',\%store,\%first_step,\$rrd); 1940 $rrd .= <<RRD; 1941</rrd> 1942RRD 1943 1944 if ( $OS eq 'NT' or $OS eq 'OS2') { 1945 open (R, "|$$cfg{rrdtool} restore - $$cfg{logdir}$$rcfg{'directory'}{$router}$router.rrd"); 1946 } else { 1947 open (R, "|-") or exec "$$cfg{rrdtool}","restore","-","$$cfg{logdir}$$rcfg{'directory'}{$router}$router.rrd"; 1948 } 1949 print R $rrd; 1950 close R; 1951} 1952 1953 1954sub addarch($$$$$$$){ 1955 my $steps = shift; 1956 my $cons = shift; 1957 my $in = shift; 1958 my $out = shift; 1959 my $store = shift; 1960 my $first_step = shift; 1961 my $rrd = shift; 1962 my $cdpin = 'NaN'; 1963 my $cdpout = 'NaN'; 1964 1965 my $param_start = ''; 1966 my $param_end = ''; 1967 my $extra_ds = ''; 1968 if ($RRDs::VERSION >= 1.2){ 1969 $param_start = '<params>'; 1970 $param_end = '</params>'; 1971 $extra_ds = '<primary_value> 0.0000000000e+00 </primary_value> <secondary_value> 0.0000000000e+00 </secondary_value>'; 1972 } 1973 1974 if ($steps != 300) { 1975 $cdpin = shift @{$$store{$in}{300*$steps}}; 1976 $cdpout = shift @{$$store{$out}{300*$steps}}; 1977 }; 1978 $$rrd .= <<RRD; 1979<!-- Round Robin Archive --> 1980 <rra> 1981 <cf> $cons </cf> 1982 <pdp_per_row> $steps </pdp_per_row> 1983 $param_start <xff> 0.5 </xff> $param_end 1984 <cdp_prep> 1985 <ds>$extra_ds <value> $cdpin </value> <unknown_datapoints> 0 </unknown_datapoints></ds> 1986 <ds>$extra_ds <value> $cdpout </value> <unknown_datapoints> 0 </unknown_datapoints></ds> 1987 </cdp_prep> 1988 1989 <database> 1990RRD 1991 while (@{$$store{$in}{$steps*300}}){ 1992 # we take zero as UNKNOWN 1993 my $inr = pop @{$$store{$in}{$steps*300}} || 'NaN'; 1994 my $outr = pop @{$$store{$out}{$steps*300}} || 'NaN'; 1995 $$rrd .= <<RRD; 1996 <row><v> $inr </v><v> $outr </v></row> 1997RRD 1998 } 1999 $$rrd .= <<RRD; 2000 </database> 2001 </rra> 2002RRD 2003} 2004 2005 2006 2007 2008# debug if the relevant debug tag is active print the debug message 2009sub debug ($$) { 2010 return unless scalar @main::DEBUG; 2011 my $tag = shift; 2012 my $msg = shift; 2013 return unless grep {$_ eq $tag} @main::DEBUG; 2014 warn "--".$tag.": ".$msg."\n"; 2015 return; 2016} 2017 2018# timestamp 2019sub timestamp () { 2020 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = 2021 localtime(time); 2022 $year += 1900; 2023 $mon += 1; 2024 return sprintf "%4d-%02d-%02d %02d:%02d:%02d", $year,$mon,$mday,$hour,$min,$sec; 2025} 2026 2027# configure __DIE__ and __WARN__ 2028 2029sub setup_loghandlers ($){ 2030 $::global_logfile = $_[0]; 2031 for($_[0]){ 2032 /^eventlog$/i && do { 2033 require Win32::EventLog; 2034 $SIG{__WARN__} = sub { 2035 my $EventLog = Win32::EventLog->new('MRTG'); 2036 my $Type = ($_[0] =~ /warning/) ? 2037 &Win32::EventLog::EVENTLOG_WARNING_TYPE : 2038 &Win32::EventLog::EVENTLOG_INFORMATION_TYPE; 2039 my $Msg = $_[0]; 2040 $Msg =~ s/\n/\r\n/g; 2041 $Msg =~ s/[\n\r]$//g; 2042 $EventLog->Report({ 2043 EventID => 1000, 2044 Category => "WARN", 2045 EventType => $Type, 2046 Data => '', 2047 Strings => $Msg }); 2048 $EventLog->Close; 2049 }; 2050 $SIG{__DIE__} = sub { 2051 return if $^S ; # no handler in eval 2052 my $EventLog = Win32::EventLog->new('MRTG'); 2053 my $Msg = $_[0]; 2054 $Msg =~ s/\n/\r\n/g; 2055 $Msg =~ s/[\n\r]$//g; 2056 $EventLog->Report({ 2057 EventID => 1000, 2058 Category => "ERROR", 2059 EventType => &Win32::EventLog::EVENTLOG_ERROR_TYPE, 2060 Data => '', 2061 Strings => $Msg }); 2062 $EventLog->Close; 2063 exit 1; 2064 }; 2065 last; 2066 }; 2067 $SIG{__WARN__} = sub { 2068 if (open DEB, ">>$::global_logfile") { 2069 print DEB timestamp." -- $_[0]"; 2070 close DEB; 2071 } else { 2072 print STDERR timestamp." -- $_[0]" 2073 } 2074 }; 2075 2076 2077 $SIG{__DIE__} = sub { 2078 return if $^S ; # no handler in eval 2079 if ( open DEB, ">>$::global_logfile") { 2080 print DEB timestamp." -- $_[0]"; 2081 close DEB; 2082 } else { 2083 print STDERR timestamp." -- $_[0]" 2084 } 2085 exit 1 2086 }; 2087 2088 } 2089} 2090 2091# Adds the v4only attribute to a target if the caller requests it. 2092# (this includes targets specified using numeric IPv6 addresses...) 2093sub v4onlyifnecessary ($$) { 2094 my $target = shift; 2095 my $add = shift; 2096 my ($v6addr, $temptarget); 2097 2098 if($add) { 2099 # Catch numeric IPv6 addresses 2100 if ( $target =~ /(\[[\w:]*\])(.*)/) { 2101 ($v6addr, $temptarget) = ($1,$2); 2102 } else { 2103 $temptarget = $target; 2104 } 2105 return $target.(":" x (5 - ($temptarget =~ tr/://))).":v4only"; 2106 } else { 2107 return $target; 2108 } 2109} 2110__END__ 2111 2112=pod 2113 2114=head1 NAME 2115 2116MRTG_lib.pm - Library for MRTG and support scripts 2117 2118=head1 SYNOPSIS 2119 2120 use MRTG_lib; 2121 my ($configfile, @target_names, %globalcfg, %targetcfg); 2122 readcfg($configfile, \@target_names, \%globalcfg, \%targetcfg); 2123 my (@parsed_targets); 2124 cfgcheck(\@target_names, \%globalcfg, \%targetcfg, \@parsed_targets); 2125 2126=head1 DESCRIPTION 2127 2128MRTG_lib is part of MRTG, the Multi Router Traffic Grapher. It was separated 2129from MRTG to allow other programs to easily use the same config files. The 2130main part of MRTG_lib is the config file parser but some other funcions are 2131there too. 2132 2133=over 4 2134 2135=item C<$MRTG_lib::OS> 2136 2137Type of OS: WIN, UNIX, VMS 2138 2139=item C<$MRTG_lib::SL> 2140 2141I<Slash> in the current OS. 2142 2143=item C<$MRTG_lib::PS> 2144 2145Path separator in PATH variable 2146 2147=item C<readcfg> 2148 2149C<readcfg($file, \@targets, \%globalcfg, \%targetcfg [, $prefix, \%extrules])> 2150 2151Reads a config file, parses it and fills some arrays and hashes. The 2152mandatory arguments are: the name of the config file, a ref to an array which 2153will be filled with a list of the target names, a hashref for the global 2154configuration, a hashref for the target configuration. 2155 2156The configuration file syntax is: 2157 2158 globaloption: value 2159 targetoption[targetname]: value 2160 aprefix*extglobal: value 2161 aprefix*exttarget[target2]: value 2162 2163E.g. 2164 2165 workdir: /var/stat/mrtg 2166 target[router1]: 2:public@router1.local.net 2167 14all*columns: 2 2168 2169The global config hash has the structure 2170 2171 $globalcfg{configoption} = 'value' 2172 2173The target config hash has the structure 2174 2175 $targetcfg{configoption}{targetname} = 'value' 2176 2177See L<mrtg-reference> for more information about the MRTG configuration syntax. 2178 2179C<readcfg> can take two additional arguments to extend the config file 2180syntax. This allows programs to put their configuration into the mrtg config 2181file. The fifth argument is the prefix of the extension, the sixth argument 2182is a hash with the checkrules for these extension settings. E.g. if the 2183prefix is "14all" C<readcfg> will check config lines that begin with 2184"14all*", i.e. all lines like 2185 2186 14all*columns: 2 2187 14all*graphsize[target3]: 500 200 2188 2189against the rules in %extrules. The format of this hash is: 2190 2191 $extrules{option} = [sub{$_[0] =~ m/^\d+$/}, sub{"Error message for $_[0]"}] 2192 i.e. 2193 $extrules{option}[0] -> a test expression 2194 $extrules{option}[1] -> error message if test fails 2195 2196The first part of the array is a perl expression to test the value of the 2197option. The test can access this value in the variable "$arg". The second 2198part of the array is an error message to display when the test fails. The 2199failed value can be integrated by using the variable "$arg". 2200 2201Config settings with an different prefix than the one given in the C<readcfg> 2202call are not checked but inserted into I<%globalcfg> and I<%targetcfg>. 2203Prefixed settings keep their prefix in the config hashes: 2204 2205 $targetcfg{'14all*graphsize'}{'target3'} = '500 200' 2206 2207=item C<cfgcheck> 2208 2209C<cfgcheck(\@target_names, \%globalcfg, \%targetcfg, \@parsed_targets)> 2210 2211Checks the configuration read by C<readcfg>. Checks the values in the config 2212for syntactical and/or semantical errors. Sets defaults for some options. 2213Parses the "target[...]" options and filles the array @parsed_targets ready 2214for mrtg functions. 2215 2216The first three arguments are the same as for C<readcfg>. The fourth argument 2217is an arrayref which will be filled with the parsed target defs. 2218 2219C<cfgcheck> converts the values of target settings I<options>, e.g. 2220 2221 options[router1]: bits, growright 2222 2223to a hash: 2224 2225 $targetcfg{'option'}{'bits'}{'router1'} = 1 2226 $targetcfg{'option'}{'growright'}{'router1'} = 1 2227 2228This is not done by C<readcfg> so if you don't use C<cfgcheck> you have to 2229check the scalar variable I<$targetcfg{'option'}{'router1'}> (MRTG allows 2230options to be separated by space or ','). 2231 2232=item C<ensureSL> 2233 2234C<ensureSL(\$pathname)> 2235 2236Checks that the I<pathname> does not contain double path separators and ends 2237with a path separator. It uses $MRTG_lib::SL as path separator which will be / 2238or \ depending on the OS. 2239 2240=item C<log2rrd> 2241 2242C<log2rrd ($router,\%globalcfg,\%targetcfg)> 2243 2244Convert log file to rrd format. Needs rrdtool. 2245 2246=item C<datestr> 2247 2248C<datestr(time)> 2249 2250Returns the time given in the argument as a nicely formated date string. 2251The argument has to be in UNIX time format (seconds since 1970-1-1). 2252 2253=item C<timestamp> 2254 2255C<timestamp()> 2256 2257Return a string representing the current time. 2258 2259=item C<setup_loghandlers> 2260 2261C<setup_loghandlers(filename)> 2262 2263Install signalhandlers for __DIE__ and __WARN__ making the errors 2264go the the specified destination. If filename is 'eventlog' 2265mrtg will log to the windows event logger. 2266 2267=item C<expistr> 2268 2269C<expistr(time)> 2270 2271Returns the time given in the argument formatted suitable for HTTP 2272Expire-Headers. 2273 2274=item C<create_pid> 2275 2276C<create_pid()> 2277 2278Creates a pid file for the mrtg daemon 2279 2280=item C<demonize_me> 2281 2282C<demonize_me()> 2283 2284Puts the running program into background, detaching it from the terminal. 2285 2286=item C<populatecache> 2287 2288C<populatecache(\%confcache, $host, $reread, $snmpoptshash)> 2289 2290Reads the SNMP variables I<ifDescr>, I<ipAdEntIfIndex>, I<ifPhysAddress>, I<ifName> from 2291the I<host> and stores the values in I<%confcache> as follows: 2292 2293 $confcache{$host}{'Descr'}{ifDescr}{oid} = (ifDescr or 'Dup') 2294 $confcache{$host}{'IP'}{ipAdEntIfIndex}{oid} = (ipAdEntIfIndex or 'Dup') 2295 $confcache{$host}{'Eth'}{ifPhysAddress}{oid} = (ifPhysAddress or 'Dup') 2296 $confcache{$host}{'Name'}{ifName}{oid} = (ifName or 'Dup') 2297 $confcache{$host}{'Type'}{ifType}{oid} = (ifType or 'Dup') 2298 2299The value (at the right side of =) is 'Dup' if a value was retrieved 2300muliple times, the retrieved value else. 2301 2302=item C<readconfcache> 2303 2304C<my $confcache = readconfcache($file)> 2305 2306Preload the confcache from a file. 2307 2308=item C<readfromconfcache> 2309 2310C<writeconfcache($confcache,$file)> 2311 2312Store the current confcache into a file. 2313 2314=item C<writeconfcache> 2315 2316C<writeconfcache($confcache,$file)> 2317 2318Store the current confcache into a file. 2319 2320=item C<storeincache> 2321 2322C<storeincache($confcache,$host,$method,$key,$value)> 2323 2324=item C<readfromcache> 2325 2326C<readfromcache($confcache,$host,$method,$key)> 2327 2328=item C<clearfromcache> 2329 2330C<clearfromcache($confcache,$host)> 2331 2332=item C<debug> 2333 2334C<debug($type, $message)> 2335 2336Prints the I<message> on STDERR if debugging is enabled for type I<type>. 2337A debug type is enabled if I<type> is in array @main::DEBUG. 2338 2339=back 2340 2341=head1 AUTHORS 2342 2343Rainer Bawidamann E<lt>Rainer.Bawidamann@rz.uni-ulm.deE<gt> 2344 2345(This Manpage) 2346 2347=cut 2348