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