#! /usr/sepp/bin/perl
# -*- mode: Perl -*-
##################################################################
# MRTG 2.9.10  -- Config file creator
##################################################################
# Created by Tobias Oetiker <oetiker@ee.ethz.ch>
# this produces an mrtg config file for one router or more routers
# by pulling info off the router via snmp
##################################################################
# Distributed under the GNU copyleft
# Copyright 2000 by Tobias Oetiker
##################################################################

# DEBUG TARGETS
# base - basic program flow
@main::DEBUG=qw(base);

require 5.005;
use strict;

BEGIN {
    # Automatic OS detection ... do NOT touch
    if ( $^O =~ /^(?:(ms)?(dos|win(32|nt)?))/i ) {
        $main::OS = 'NT';
        $main::SL = '\\';
        $main::PS = ';';
    } elsif ( $^O =~ /^VMS$/i ) {
        $main::OS = 'VMS';
        $main::SL = '.';
        $main::PS = ':';
    } else {
        $main::OS = 'UNIX';
        $main::SL = '/';
        $main::PS = ':';
    }
}

use FindBin;
use lib "${FindBin::Bin}";
use lib "${FindBin::Bin}${main::SL}..${main::SL}lib${main::SL}mrtg2";

use MRTG_lib "2.090009";
use SNMP_util "0.77";
use SNMP_Session "0.83";
use Getopt::Long;
use Pod::Usage;
use Socket;


sub main {

    my %opt;
    my %routers;
    my %confcache;
    init();
    $opt{fullcmd} = "$0 ".(join " ", map {$_ =~ /[ \[\]\*\{\}\;\>\<\&]/ ? "'$_'" : $_ } @ARGV);
    $opt{community}="public";
    options(\%opt,\%routers);
    foreach my $router (keys %routers) {
#        pod2usage(-verbose=>1,-message=>"ERROR: Could not Parse $router\n")
#                unless  $router =~ /.*\@.*/;
	debug('base',"Get Device Info on $router");
	$routers{$router}{deviceinfo} = DeviceInfo($router);
	debug('base',"Populating confcache");
	populateconfcache(\%confcache,$router,1);
	debug('base',"Get Interface Info");
	InterfaceInfo(\%confcache,\%routers,$router,\%opt)
    }
    GenConf(\%opt,\%routers,\%confcache);
}

main;
exit 0;

sub InterfaceInfo($$$$) {
    my $confcache = shift;
    my $routers = shift;
    my $router = shift;
    my $opt = shift;
    my @Variables = qw (ifIndex ifType ifSpeed
			ifAdminStatus ifOperStatus);
    if ($routers->{$router}{deviceinfo}{Vendor} eq 'cisco' &&
	$routers->{$router}{deviceinfo}{sysDescr} =~ m/Version\s+(\d\d\.\d+)/) {
	push @Variables,  ($1 > 11.0) ? "ifAlias" : "CiscolocIfDescr";
    }

    my $descr = $routers->{$router}{deviceinfo}{sysDescr};                                   
    if ($routers->{$router}{deviceinfo}{Vendor} eq 'cisco' &&                                
        $descr =~ m/Catalyst\sOperating\sSystem/ ) {                             
        push @Variables,  "CiscoCatalystPortName";                                 
    }                                                                            

    foreach my $var (@Variables) {
	debug('base',"Walking $var");
	foreach my $tuple (snmpwalk($router, $var)){
	    my($if,$value) = split /:/, $tuple, 2;
	    $routers->{$router}{$if}{$var} = $value;
	}
    }
    # magic speed determination for portmaster IFs

    if ($routers->{$router}{deviceinfo}{Vendor} eq 'portmaster') {
	# We can only approximate speeds
	# 
	# so we think that ppp can do 76800 bit/s, and slip 38400.
	# (actualy, slip is a bit faster, but usualy users with newer modems
	# use ppp). Alternatively, you can set async speed to 115200 or
	# 230400 (the maximum speed supported by portmaster).
	# 
	# But if the interface is ptpW (sync), max speed is 128000
	# change it to your needs. On various Portmasters there are
	# various numbers of sync interfaces, so modify it.
	# 
	#  The most commonly used PM-2ER has only one sync.
	# 
	#  Paul Makeev (mac@redline.ru)
	# 
	foreach my $if (keys %{$routers->{$router}}) {
	    next unless $if =~ /^\d+$/;
	    my $ift = $routers->{$router}{$if}{ifType};
	    my $ifd = $routers->{$router}{$if}{RDescr};
	    if ($ift ==  23) {
		if ($ifd eq 'ptpW1') {
		    $routers->{$router}{$if}{ifSpeed} = 128000;
		} else {
		  $routers->{$router}{$if}{ifSpeed} = 76800;
	      }
	    } elsif ($ift == 28) {
		$routers->{$router}{$if}{ifSpeed} = 38400;
	    } elsif ($ift == 6) {
		$routers->{$router}{$if}{ifSpeed} = 10000000;
	    }
	}
    }

    # match confcache info into tree
        foreach my $method (keys %{$$confcache{$router}}) {
            foreach my $key (keys %{$$confcache{$router}{$method}}) {
		my $if = $$confcache{$router}{$method}{$key};
                next unless $if =~ /^\d+$/;
		$routers->{$router}{$if}{$method} = $key;
		for ($method) {
		    #fix special chars in ifdescr
		    /^Descr|Name$/ && do {
                        $routers->{$router}{$if}{"R$method"} = $routers->{$router}{$if}{$method};
			$routers->{$router}{$if}{$method} =~ s/([ :])/\\$1/g;
			next;
		    };
		    #find hostname of IF
                    
		    !$$opt{noreversedns} && /^Ip$/ and do {
			my $name =
			  gethostbyaddr(
					pack('C4',
					     split(/\./,
						   $routers->{$router}{$if}{$method})),
					AF_INET);
         		$routers->{$router}{$if}{DNSName} = ($name or "");
			next;
		    };
		}
            }
        
    }
}

sub GenConf ($$$) {
    my $opt = shift;
    my $routers = shift;
    my $confcache = shift;
    my $conf = "# Created by \n# $$opt{fullcmd}\n\n";
    
    # print global options
    if (not defined $$opt{global}) {
	$conf .= <<ECHO;

### Global Config Options

#  for UNIX
# WorkDir: /home/http/mrtg

#  or for NT
# WorkDir: c:\\mrtgdata

### Global Defaults

#  to get bits instead of bytes and graphs growing to the right
# Options[_]: growright, bits

ECHO
    }
    foreach my $router
	(sort
	 {($$routers{$a}{noofrouter}) <=> ($$routers{$b}{noofrouter})}
	 keys %$routers )
    {
	
	my $router_ref = $$routers{$router};
	my $router_opt = $$router_ref{opt};

	# Did any global options appear on the command line
	# before this router?  If so, include them into the
	# configuration file.

	if (defined $$router_opt{global}) {
	    foreach my $key (@{$$router_opt{global}}) {
		$conf .= "$key\n";
	    }
	}

	my $s = $routers->{$router}{deviceinfo};
	$conf .= <<ECHO;

######################################################################
# System: $$s{sysName}
# Description: $$s{sysDescr}
# Contact: $$s{sysContact}
# Location: $$s{sysLocation}
######################################################################


ECHO

	foreach my $key (sort {int($a) <=> int($b)} keys %$router_ref) {
	    next unless $key =~ /^\d+$/;
	    my $i = $$router_ref{$key};
            
       	    $conf .= "### Interface $key >> Descr: '$$i{RDescr}' | Name: '$$i{RName}' | Ip: '$$i{Ip}' | Eth: '$$i{Eth}' ###\n";
	    # does it make sense to look at the interface ?
	    my @prob;
	    my $c = "";
            if (not defined $$router_opt{'no-down'}) {
	     push @prob, "it is administratively DOWN" 
	      if $$i{ifAdminStatus} == 2;

	     push @prob, "it is in administrative TEST mode" 
	      if $$i{ifAdminStatus} == 3;

             if (not defined $$router_opt{'show-op-down'}) {

  	       push @prob, "it is operationally DOWN" 
	        if $$i{ifOperStatus} == 2 ;

  	       push @prob, "it is in operational TEST mode" 
	        if $$i{ifOperStatus} == 3;

             }
            }

	    push @prob, "it is a DS1 controllers"
	      if $$i{ifType} == 18; # by fwo@obsidian.co.za

	    push @prob, "it is a E1 controllers"
	      if $$i{ifType} == 19; # by fwo@obsidian.co.za

	    push @prob, "it is a Software Loopback interface" 
	      if $$i{ifType} == 24;

            push @prob, "it is a DS3 contorller"
              if $$i{ifType} == 30; # by blube@floridadigital.net

#            push @prob, "agregate cisco interface interface does not count traffic"
#              if $$s{Vendor} eq 'cisco' and
#                 $$i{ifType} == 53;

            push @prob, "it is a CEF Sub-interface" # John Begley <maslow@mediaone.net>
              if $$i{ifType} == 162;

	    push @prob, "has a speed of $$i{ifSpeed} which makes no sense" 
	      if $$i{ifSpeed} == 0;

	    push @prob, "it is a cisco Null0 interface"
	      if $$s{Vendor} eq 'cisco' and
		 $$i{Descr} eq 'Null0';

	    push @prob, "it is a cisco VLAN interface"
	      if $$s{Vendor} eq 'cisco' and
		 $$i{Descr} =~ /^VLAN\d+$/;

            my $message;
            push @prob, "got '$message' from interface when trying to query"
               if ($message = IsCounterBroken($key, $router));
                    

	    # determine interface reference
	    my $ifref;
	    if (defined $$router_opt{ifref}) {
		for ($$router_opt{ifref}) {
		    /^ip$/ && do { $ifref = "/".$$i{Ip} if $$i{Ip}  ; last};
		    /^eth$/ && do { $ifref = "!".$$i{Eth} if $$i{Eth}; last};
		    /^descr$/ && do {$ifref = "\\".$$i{Descr} if $$i{Descr}; last};
		    /^name$/ && do {$ifref = "#".$$i{Name} if $$i{Name}; last};
		    /^type$/ && do {$ifref = "%".$$i{Type} if $$i{Type}; last};
		    /^nr$/ && do {$ifref = $key; last};
		    die "ERROR: Invalid value for --ifref: '$$router_opt{ifref}'\n";
		}
		if (not defined $ifref) {
		    push @prob, "--ifref=$$router_opt{ifref} is not unique for this interface";
		    $ifref = $key;
		}
	    } else {
		$ifref = $key;
	    }

	    # issue problem report

	    if (@prob) {
		$c = "# ";
		$conf .= "### The following interface is commented out because:\n";
		map {$conf .= "### * $_\n"} @prob;
	    }

	    # generate Target name
	    my $reftrim = $ifref;
	    $reftrim =~ s/[#!\/\\:\s\@%]+/_/g;
	    $reftrim =~ s/^_*(.+?)_*$/$1/;
	    $router =~ /\@([^:]+)/;
            my $router_name = $1;
	    my $name = "$1_$reftrim";
            my $refsimp = $ifref;
            $refsimp =~ s/^[^\d]//;
	    my $speed = int($$i{ifSpeed} / 8);
	    my $speed_str = fmi($speed,$$router_ref{flags});
	    my $iftype = IfType($$i{ifType});
            my $refdesc = 'Traffic Analysis for ';
            if (defined $$router_opt{ifdesc}) {
                $refdesc = '';
		for ($$router_opt{ifdesc}) {
		    /^ip$/ && do { $refsimp = $$i{Ip} if $$i{Ip}  ; last};
		    /^eth$/ && do { $refsimp = $$i{Eth} if $$i{Eth}; last};
		    /^descr$/ && do {$refsimp = $$i{Descr} if $$i{Descr}; last};
		    /^alias$/ && do {$refsimp =
					 "$$i{RDescr} $$i{ifAlias} $$i{CiscolocIfDescr}";
				     last};
		    /^name$/ && do {$refsimp = "#".$$i{Name} if $$i{Name}; last};
		    /^type$/ && do {$refsimp = "%".$$i{Type} if $$i{Type}; last};
		    /^nr$/ && do {$refsimp = "Interface $key"; last};
		    /^$/ && do {$refsimp = $iftype; 
				$refsimp =~ s/^$/$$i{RDescr}/;
				last};
		    die "ERROR: Invalid value for --ifdesc: '$$router_opt{descint}'\n";
		}
            }

            my $port_dot = $$i{RName};                                             
            $port_dot =~ s/\//./g;                                                 
            my $portname = $$router_ref{$port_dot}{CiscoCatalystPortName};  
            $refsimp =~ s/\\([:@\\\/# ])/$1/g; # unescape
 
             $conf .= <<ECHO;
${c}
${c}Target[$name]: $ifref:$router
${c}SetEnv[$name]: MRTG_INT_IP="$$i{Ip}" MRTG_INT_DESCR="$$i{RDescr}"
ECHO
             if (defined $$router_opt{subdirs}) {
		 my $directory_name = $$router_opt{subdirs};
		 $directory_name =~ s/HOSTNAME/$router_name/g;
		 $directory_name =~ s/SNMPNAME/$$s{sysName}/g;

                 $conf .= "${c}Directory[$name]: $directory_name\n";
             }
             $conf .= <<ECHO;
${c}MaxBytes[$name]: $speed
${c}Title[$name]: $refdesc$refsimp -- $$s{sysName}
${c}PageTop[$name]: <H1>$refdesc$refsimp -- $$s{sysName}</H1>
${c} <TABLE>
${c}   <TR><TD>System:</TD>     <TD>$$s{sysName} in $$s{sysLocation}</TD></TR>
${c}   <TR><TD>Maintainer:</TD> <TD>$$s{sysContact}</TD></TR>
${c}   <TR><TD>Description:</TD><TD>$$i{RDescr} $$i{ifAlias} $$i{CiscolocIfDescr}</TD></TR>
${c}   <TR><TD>ifType:</TD>     <TD>$iftype ($$i{ifType})</TD></TR>
${c}   <TR><TD>ifName:</TD>     <TD>$$i{RName}</TD></TR>
ECHO
             $conf .= <<ECHO if defined $portname;
${c}   <TR><TD>Port Name:</TD>  <TD>$portname</TD></TR>
ECHO
             $conf .= <<ECHO;
${c}   <TR><TD>Max Speed:</TD>  <TD>$speed_str</TD></TR>
ECHO

        $conf .= <<ECHO if defined $$i{Ip};
${c}   <TR><TD>Ip:</TD>         <TD>$$i{Ip} ($$i{DNSName})</TD></TR>
ECHO
        $conf .= <<ECHO;
${c} </TABLE>


ECHO

	}
    }

    # print any global options which might have
    # appeared on the command line after the last
    # router.
    if (defined $$opt{global}) {
	foreach my $key (@{$$opt{global}}) {
	    $conf .= "$key\n";
	}
    }

    if ($$opt{output}) {
	debug ('base', "Writing $$opt{output}");
	open X, ">$$opt{output}" or die "ERROR: creating $$opt{output}: $!\n";
	print X $conf;
	close X;
    } else {
	print $conf;
    }
}

sub IsCounterBroken ($$) {
    my $if = shift;
    my $router = shift;
    $SNMP_Session::suppress_warnings = 3;
    snmpget($router, 'ifInOctets.'.$if);
    if (defined $SNMP_Session::errmsg) {
        my $error = $SNMP_Session::errmsg;
        $SNMP_Session::errmsg = undef;
        $error =~ s/\n/\n###     /g;
        return $error;
    }
    return 0;
}

sub DeviceInfo ($) {
    my $router=shift;
    my @variables = qw(sysDescr sysContact
		       sysName sysLocation
		       sysObjectID);
    my %DevInfo;
    if (@DevInfo{@variables} = snmpget($router, @variables)) {
	# remove \r from sysDescr
	$DevInfo{sysDescr} =~ s/[\n\r]+/\n#          /g;
        # vendor identification
	for ($DevInfo{sysObjectID}) {
	    $DevInfo{Vendor} = 'cisco' if /^\Q1.3.6.1.4.1.9.\E/;
	    $DevInfo{Vendor} = 'portmaster' if /^\Q1.3.6.1.4.1.307.\E/;
            debug('base',"Vendor Id: $DevInfo{Vendor}");
	};
	return \%DevInfo;
    } else {
	# we just die because the snmp module has already complained
	exit 1;
    }
}


sub fmi ($$) {
    my $number = shift;
    my $flags = shift;
    my(@short);
    if ($$flags{bits} eq "set"){
	$number*=8;
	@short = ("bits/s","kbits/s","Mbits/s","Gbits/s");
    } else {
	@short = ("Bytes/s","kBytes/s","MBytes/s","GBytes/s");
    }
    my $digits=length("".$number);
    my $divm=0;
    while ($digits-$divm*3 > 4) { $divm++; }
    my $divnum = $number/10**($divm*3);
    return sprintf("%1.1f %s",$divnum,$short[$divm]);
}


sub IfType ($) {
  return {'1'=>'Other',
	  '2'=>'regular1822',
	  '3'=>'hdh1822',
	  '4'=>'ddnX25',
	  '5'=>'rfc877x25',
	  '6'=>'ethernetCsmacd',
	  '7'=>'iso88023Csmacd',
	  '8'=>'iso88024TokenBus',
	  '9'=>'iso88025TokenRing',
	  '10'=>'iso88026Man',
	  '11'=>'starLan',
	  '12'=>'proteon10Mbit',
	  '13'=>'proteon80Mbit',
	  '14'=>'hyperchannel',
	  '15'=>'fddi',
	  '16'=>'lapb',
	  '17'=>'sdlc',
	  '18'=>'ds1',
	  '19'=>'e1',
		 '20'=>'basicISDN',
	  '21'=>'primaryISDN',
	  '22'=>'propPointToPointSerial',
	  '23'=>'ppp',
	  '24'=>'softwareLoopback',
	  '25'=>'eon',
	  '26'=>'ethernet-3Mbit',
	  '27'=>'nsip',
	  '28'=>'slip',
	  '29'=>'ultra',
	  '30'=>'ds3',
	  '31'=>'sip',
	  '32'=>'frame-relay',
	  '33'=>'rs232',
	  '34'=>'para',
	  '35'=>'arcnet',
	  '36'=>'arcnetPlus',
	  '37'=>'atm',
	  '38'=>'miox25',
	  '39'=>'sonet',
	  '40'=>'x25ple',
	  '41'=>'iso88022llc',
	  '42'=>'localTalk',
	  '43'=>'smdsDxi',
	  '44'=>'frameRelayService',
	  '45'=>'v35',
	  '46'=>'hssi',
	  '47'=>'hippi',
	  '48'=>'modem',
	  '49'=>'aal5',
	  '50'=>'sonetPath',
	  '51'=>'sonetVT',
	  '52'=>'smdsIcip',
	  '53'=>'propVirtual',
	  '54'=>'propMultiplexor',
	  '55'=>'100BaseVG',
	  '56'=>'Fibre Channel',
	  '57'=>'HIPPI Interface',
	  '58'=>'Obsolete for FrameRelay',
	  '59'=>'ATM Emulation of 802.3 LAN',
	  '60'=>'ATM Emulation of 802.5 LAN',
	  '61'=>'ATM Emulation of a Circuit',
	  '62'=>'FastEthernet (100BaseT)',
	  '63'=>'ISDN & X.25',
	  '64'=>'CCITT V.11/X.21',
	  '65'=>'CCITT V.36',
	  '66'=>'CCITT G703 at 64Kbps',
	  '67'=>'Obsolete G702 see DS1-MIB',
	  '68'=>'SNA QLLC',
	  '69'=>'Full Duplex Fast Ethernet (100BaseFX)',
	  '70'=>'Channel',
	  '71'=>'Radio Spread Spectrum (802.11)',
	  '72'=>'IBM System 360/370 OEMI Channel',
	  '73'=>'IBM Enterprise Systems Connection',
	  '74'=>'Data Link Switching',
	  '75'=>'ISDN S/T Interface',
	  '76'=>'ISDN U Interface',
	  '77'=>'Link Access Protocol D (LAPD)',
	  '78'=>'IP Switching Opjects',
	  '79'=>'Remote Source Route Bridging',
	  '80'=>'ATM Logical Port',
	  '81'=>'AT&T DS0 Point (64 Kbps)',
	  '82'=>'AT&T Group of DS0 on a single DS1',
	  '83'=>'BiSync Protocol (BSC)',
	  '84'=>'Asynchronous Protocol',
	  '85'=>'Combat Net Radio',
	  '86'=>'ISO 802.5r DTR',
	  '87'=>'Ext Pos Loc Report Sys',
	  '88'=>'Apple Talk Remote Access Protocol',
	  '89'=>'Proprietary Connectionless Protocol',
	  '90'=>'CCITT-ITU X.29 PAD Protocol',
	  '91'=>'CCITT-ITU X.3 PAD Facility',
	  '92'=>'MultiProtocol Connection over Frame/Relay',
	  '93'=>'CCITT-ITU X213',
	  '94'=>'Asymetric Digitial Subscriber Loop (ADSL)',
	  '95'=>'Rate-Adapt Digital Subscriber Loop (RDSL)',
	  '96'=>'Symetric Digitial Subscriber Loop (SDSL)',
	  '97'=>'Very High Speed Digitial Subscriber Loop (HDSL)',
	  '98'=>'ISO 802.5 CRFP',
	  '99'=>'Myricom Myrinet',
	  '100'=>'Voice recEive and transMit (voiceEM)',
	  '101'=>'Voice Foreign eXchange Office (voiceFXO)',
	  '102'=>'Voice Foreign eXchange Station (voiceFXS)',
	  '103'=>'Voice Encapulation',
	  '104'=>'Voice Over IP Encapulation',
	  '105'=>'ATM DXI',
	  '106'=>'ATM FUNI',
	  '107'=>'ATM IMA',
	  '108'=>'PPP Multilink Bundle',
	  '109'=>'IBM IP over CDLC',
	  '110'=>'IBM Common Link Access to Workstation',
	  '111'=>'IBM Stack to Stack',
	  '112'=>'IBM Virtual IP Address (VIPA)',
	  '113'=>'IBM Multi-Protocol Channel Support',
	  '114'=>'IBM IP over ATM',
	  '115'=>'ISO 802.5j Fiber Token Ring',
	  '116'=>'IBM Twinaxial Data Link Control (TDLC)',
	  '117'=>'Gigabit Ethernet',
	  '118'=>'Higher Data Link Control (HDLC)',
	  '119'=>'Link Access Protocol F (LAPF)',
	  '120'=>'CCITT V.37',
	  '121'=>'CCITT X.25 Multi-Link Protocol',
	  '122'=>'CCITT X.25 Hunt Group',
	  '123'=>'Transp HDLC',
	  '124'=>'Interleave Channel',
	  '125'=>'Fast Channel',
	  '126'=>'IP (for APPN HPR in IP Networks)',
	  '127'=>'CATV MAC Layer',
	  '128'=>'CATV Downstream Interface',
	  '129'=>'CATV Upstream Interface',
	  '130'=>'Avalon Parallel Processor',
	  '131'=>'Encapsulation Interface',
	  '132'=>'Coffee Pot',
	  '133'=>'Circuit Emulation Service',
	  '134'=>'ATM Sub Interface',
	  '135'=>'Layer 2 Virtual LAN using 802.1Q',
	  '136'=>'Layer 3 Virtual LAN using IP',
	  '137'=>'Layer 3 Virtual LAN using IPX',
	  '138'=>'IP Over Power Lines',
	  '139'=>'Multi-Media Mail over IP',
	  '140'=>'Dynamic synchronous Transfer Mode (DTM)',
	  '141'=>'Data Communications Network',
	  '142'=>'IP Forwarding Interface',
          '162'=>'Cisco Express Forwarding Interface',

	  }->{(shift)};
}

sub options () {
   my $opt = shift;
   my $routers = shift;

   my $noofrouter = 0; # How many routers we've seen on cmdline.
   
   # The $flags hash stores what we've seen in Options[_],
   # Options[^] and Options[$] so far.
   # A cmdline arg like --global 'Options[_]: bits' will insert
   # the element $$flags{default}{bits}="set".
   # Similarly --global 'Options[$]:' will delete all elements
   # in $$flags{append}
   #
   # This was originally created to manipulate the "bits" flag
   # so fmi should know when to use "bits" or "bytes".  It might
   # be overkill to use such a comples solution but it makes life
   # easier if cfgmaker in the future has to be extended to be
   # aware of other Options[] settings like gauge, growright etc.

   my %flags;
   {
       my $def = {};
       my $pre = {};
       my $app = {};
       %flags = (default => $def,
		 prepend => $pre,
		 append  => $app);
   }

   my $addrouter_ornf = addrouter($opt,
				  $routers,
				  \$noofrouter,
				  \%flags);

   Getopt::Long::Configure("permute");
   GetOptions( $opt,
   	'help|?',
	'man',
        'subdirs=s',
        'no-down',
        'show-op-down',
        'descint',
        'noreversedns',
	'ifref=s',
	'ifdesc=s',
	'community=s',
	'snmp-options=s',
	'dns-domain=s',
        'version',
	'output=s',
	'global=s@',
	'<>', $addrouter_ornf) or pod2usage(2);

   die("cfgmaker for mrtg-2.9.10\n") if $$opt{version};
   pod2usage(-exitval => 0, -verbose => 2) if $$opt{man};
   pod2usage(-verbose => 1) if not keys %$routers;


}

# The callback routine used by GetOptions to process "non-option
# strings" (routers) among the arguments is given only ONE argument.
# However, I want it to be able to specify both the %options hash
# (for read access) and the %routers hash (for modifying) as well
# as the router's name.  This makes for three arguments.
#
# The solution is to use a closure.  addrouter takes a opt hash, a
# routers hash, an index to the current number of routers and a flags
# hash and then returns a function which "remembers" these
# values (the closure) but also takes an argument (the router name).

sub addrouter() {
    my $opt = shift;
    my $routers = shift;
    my $noofrouter = shift;
    my $flags = shift;

    return sub {
	my $rawname = shift;

	$$noofrouter++;  # First increase the number of routers seen.

	my ($community,$routername,$routerkey,$snmpopt,$dnsdomain,$tmpname,@tmpsnmp);


	# Now make sure that the router is defined with the
	# proper community, domainname and SNMP options.
	# Dissect the rawname to find out what it contains.

	# First split the rawname on SNMP options:

	($tmpname,@tmpsnmp) = split ':',$rawname;
	
	# Then check for community:
	
	if ($tmpname =~ /(.+)\@(.+)/)
	{
	    # Community were given explicitly!
	    $community = $1;
	    $routername = $2
	} else {
	    $community = $$opt{community};
	    $routername = $tmpname;
	}

	# Now setup the SNMP options.
	if (not defined $$opt{'snmp-options'})
	{
	    $snmpopt = ':' . (join ':', @tmpsnmp);  # No merge needed.
	} else {
	    my ($t,$o,@s);
	    my @optsnmp = split ':',$$opt{'snmp-options'};

	    # Trim first element as the SNMP options start
	    # with a colon and thus the first element is a
	    # dummy "" string not corresponding to any SNMP option
	    # (or rather, corresponding to a router, if there had
	    # been one...)
	    shift @optsnmp;
	    

	    while ((scalar @tmpsnmp > 0)
		   or (scalar @optsnmp > 0)) {
		$t = shift @tmpsnmp;
		$o = shift @optsnmp;
		
		if(not defined $t) {$t = "";}
		if(not defined $o) {$o = "";}

		if($t ne "")
		{
		    push @s, $t;
		} else {
		    push @s, $o;
		}
	    }
	    
	    $snmpopt = ':' . (join ':', @s);
	}

	my $newopt={};  # Perhaps unecessary initialization but...

	foreach my $o (keys %$opt) {
	    my $ovalue = $$opt{$o};

	    $$newopt{$o} = $ovalue
		unless
		    ($o =~ /^fullcmd$/ or
		     $o =~ /^community$/ or
		     $o =~ /^snmp-options$/ or
		     $o =~ /^global$/ or
		     $o =~ /^output$/
		     );

	    # Ok, copy the --globals array from $$opt so we know
	    # that which global(s) to print out into the config.
	    push @{$$newopt{$o}}, @{$$opt{$o}} if ($o =~ /^global$/);

	    # Go through these --global statements one by one.
	    # If anyone of them contains Options[] for any of the
	    # targets [_], [^] or [_], process those statements
	    # tenderly and populate the $$flags{}{} hashes accordingly.
	    for my $g (@{$$opt{"global"}})
	    {
		my ($t,$fs);
		$g =~ /^options\[([_^\$])\]:\s*(.*)$/i;
		$t = $1;
		$fs = $2;
		$t =~ s/_/default/;
		$t =~ s/\^/prepend/;
		$t =~ s/\$/append/;

		# If a line like "options[X]:" is found clear
		# all flags for that category and then go to next
		# --global 'Options[..' line if any.
		if ($fs =~ /^\s*$/)
		{
		    $$flags{$t} = {};
		    next;
		} else {
		    for my $f (split ',',$fs)
		    {
			$$flags{$t}{$f} = "set";
		    }
		}
	    }
	    

	    $$opt{$o} = [] if ($o =~ /^global$/);
	}
	
	# Now let this router get it's own copy of
	# the "currently effective" flags.
	# Note, Options[_] should only be considered
	# if Options[^] and Options[$] both are absent.
	
	my $newflags = {};
	
	if((0 == keys %{$$flags{prepend}})
	   and (0== keys %{$$flags{append}}))
	{
	    for my $f (keys %{$$flags{default}})
	    {
		$$newflags{$f}="set";
	    }
	} else {
	    for my $f (keys %{$$flags{prepend}},
		       keys %{$$flags{append}})
	    {
		$$newflags{$f}="set";
	    }
	}

	if(defined $$opt{'dns-domain'}) {
	    $dnsdomain=$$opt{'dns-domain'};
	}else{
	    $dnsdomain="";
	}

	$routerkey =
	    "${community}\@${routername}" 
		. (($dnsdomain eq "")?"":".")
		    . "${dnsdomain}${snmpopt}";

	$$routers{$routerkey}=
	{ 
	    # rawname is the unprocessed string from the
	    # command line.
	    rawname     => $rawname,

	    # opt is the commandline options which are
	    # in effect for THIS particular router.
	    opt         => $newopt,

	    # noofrouter is the unique number for the
	    # router.  The first router on the command
	    # line is given number 1, the second number 2
	    # and so on.
	    noofrouter  => $$noofrouter,

	    # flags contains which --global 'Options[^_$]: flags'
	    # are effective for THIS particular router.
	    flags       => $newflags,

	    # community is the SNMP community used for the router
	    community   => $community,

	    # snmpopt is the SNMP options on the form
	    # [port[:timeout[:retries[:backoff[:version]]]]]
	    # The empty string simply means that no
	    # specific SNMP options has been given.
	    'snmp-options' => $snmpopt,

	    # dns-domain is a domain which should be added
	    # to the routers hostname.
	    # e.g if dns-domain is place.xyz and host is router
	    # the host "router.place.xyz" will be polled.
	    # If host is "router.dept" the poll will be against
	    # "router.dept.place.xyz".

	    'dns-domain' => $dnsdomain,

	    # routername is the routers name as given on the
	    # command line but with SNMP community (if given)
	    # and SNMP options (if given) stripped.
	    #
	    # (Yes, routername COULD be on the form
	    # "host.domain" or "host.subdomain.domain")
	    #
	    routername  => $routername};
	
    }
}

sub init () {
  snmpmapOID('sysObjectID' => '1.3.6.1.2.1.1.2.0',
             'CiscolocIfDescr' => '1.3.6.1.4.1.9.2.2.1.1.28',
             'CiscoCatalystPortName' => '1.3.6.1.4.1.9.5.1.4.1.1.4',  
	     'ifAlias' => '1.3.6.1.2.1.31.1.1.1.18');
}

__END__

=head1 NAME

cfgmaker - Creates mrtg.cfg files (for mrtg-2.9.10)

=head1 SYNOPSIS

cfgmaker [options] [community@]router [[options] [community@]router ...]

=head1 OPTIONS

 --ifref=nr	   interface references by Interface Number (default)
 --ifref=ip	                    ... by Ip Address
 --ifref=eth                        ... by Ethernet Number
 --ifref=descr                      ... by Interface Description
 --ifref=name                       ... by Interface Name
 --ifref=type                       ... by Interface Type

 --ifdesc=nr	   interface description uses Interface Number (default)
 --ifdesc=ip	                    ... uses Ip Address
 --ifdesc=eth                       ... uses Ethernet Number
 --ifdesc=descr                     ... uses Interface Description
 --ifdesc=name                      ... uses Interface Name
 --ifdesc=alias                     ... uses Interface Alias
 --ifdesc=type                      ... uses Interface Type

 --global "x: a"   add global config entries

 --no-down         do not look at admin or opr status of interfaces

 --show-op-down    show interfaces which are operatively down

 --descint         describe interface instead of just 'Traffic Analysis for'

 --subdirs=format  give each router its own subdirectory, naming each per 
                   "format", in which HOSTNAME and SNMPNAME will be 
                   replaced by the values of those items -- for instance,  
                   --subdirs=HOSTNAME or --subdirs="HOSTNAME (SNMPNAME)"

 --noreversedns    do not reverse lookup ip numbers

 --community=cmty  Set the default community string to "cmty" instead of
                   "public".

 --snmp-options=:[<port>][:[<timeout>][:[<retries>][:[<backoff>][:<version>]]]]

                   Specify default SNMP options to be appended to all
                   routers following.  Individual fields can be empty.
                   Routers following might override some or all of the
		   options given to --snmp-options.

 --dns-domain=domain
		   Specifies a domain to append to the name of all
		   routers following.

 --help            brief help message
 --man             full documentation
 --version         print the version of cfgmaker

 --output=file     output filename default is STDOUT

=head1 DESCRIPTION

B<Cfgmaker> creates MRTG configuration files based on information
pulled from a router or another SNMP manageable device.

[I<community>B<@>]I<router>

I<Community> is the community name of the device you want to create a
configuration for. If not specified, it defaults to 'B<public>'; you might
want to try this first if you do not know the community name of a
device. If you are using the wrong comunity name you will get no
response from the device.

I<Router> is the DNS name or the IP number of an SNMP-managable device.           
Following the name you can specify 6 further options separated by                 
colons.  The full syntax looks like this:                                      

B<router>[:[B<port>][:[B<timeout>][:[B<retries>][:[B<backoff>][:B<version>]]]]]

Of special interest may be the last parameter, B<version>.  If you set            
this to '2' then your device will be queried with SNMP version 2                  
requests. This allows to poll the 64 bit traffic counters in the device           
and will thus work much better with fast interfaces (no more counter              
overrun).                                                                         
Note that the order in which the routers are specified on the command line
do matter as the same order is used when the configuration file is
generated.  The first specified router has it's configuration lines
genrated first, followed by the lines belonging to the next router
and so on.

=head2 Configuration

Except for the B<--output> and B<--global> options, all options affect
only the routers following them on the command line.  If an option
specified earlier on the command line reappears later on the command
line with another value, the new value overrides the old value as far as
remaining routers are concerned.  This way options might be tailored for
groups of routers or for individual routers.

See B<--output> and B<--global> for how their behaviour is affected by
where or how many times they appear on the command line.

See the B<Examples> below on how to set an option differently for
multiple routers.

=item B<--help>

Print a brief help message and exit.

=item B<--man>

Prints the manual page and exits.

=item B<--version>

Print the version of cfgmaker.  This should match the version of MRTG
for which config files are being created.

=item B<--ifref> B<nr>|B<ip>|B<eth>|B<descr>|B<name>

Select the interface identification method.  Default is B<nr> which
identifies the router interfaces by their number.  Unfortunately the
interface numbering scheme in an SNMP tree can change. Some routers
change their numbering when new interfaces are added, others change
thier numbering every full moon just for fun.

To work around this sad problem MRTG can identify interfaces by 4
other properties. None of these works for all interfaces, but you
should be able to find one which does fine for you. Note that
especially ethernet addrsses can be problematic as some routers have
the same ethernet address on most of their interface cards.

Select B<ip> to identify the interface by its IP number. Use B<eth> to
use the ethernet address for identification. Use B<descr> to use
the Interface description. Or use B<name> to use the Interface name.

If your chosen method does not allow unique interface identification on
the device you are querying, B<cfgmaker> will tell you about it.

=item B<--ifdesc> B<nr>|B<ip>|B<eth>|B<descr>|B<name>|B<type>|B<alias>

Select what to use as the description of the interface.  The description
appears in the C<Title[]> property for the target as well as the text header
in the HTML code defined in the target's C<PageTop[]>.  Default is to use
B<nr> which is just the interface number which isn't always useful
to the viewer of the graphs.

There are 6 other properties which could be used.  Use B<ip> if you want
to use the interface's IP-address.  Use B<eth> if you want to use the
interface's ethernet address.  If you want a better description, you can
use either B<descr>, B<name> or B<alias>.  Exactly what each of these do
varies between different equipment so you might need to experiment.  For
instance, for a serial interface on a Cisco router running IOS using B<name>
might result in C<"S0"> being the interface description , B<descr> might result
in C<"Serial0"> and B<alias> might result in C<"Link to HQ"> (provided that is
what is used as the interface's C<description> in the router's configuration).

Finally, if you want to describe the interface by it's Btype
(i.e C<"ethernetCSMA">, C<"propPointtoPoint"> etc) you can use B<type>.  This is
roughly equivalent to the B<--descint> option above.

=item B<--community> B<community-string>

Use this to set the community for the routers following on the command
line to B<community-string>.  Individual routers might overrride this
community string by using the syntax B<community>B<@>B<router>.

=item B<--snmp-options>  :[B<port>][:[B<timeout>][:[B<retries>][:[B<backoff>][:B<version>]]]]

Use this to set the default SNMP options for all routers following on the
command line.  Individual values might be omitted as well as trailing
colons.  Note that routers might override individual (or all) values
specified by B<--snmp-options> by using the syntax

B<router>[:[B<port>][:[B<timeout>][:[B<retries>][:[B<backoff>][:B<version>]]]]]

=item B<--global> B<">I<bla: abc>B<">

Use this to add global options to the generated config file. 
You can call B<--global> several times to add multiple options.
The line will appear in the configuration just before the config for
the next router appearing on the command line.

 --global "workdir: /home/mrtg"

If you want some default Options you might want to put

 --global "options[_]: growright,bits"

Specifying B<--global> after the last router on the command line will
create a line in the configuration file which will appear after all the
routers.

=item B<--noreversedns>

Do not try to reverse lookup IP numbers ... a must for DNS free environments.

=item B<--no-down>

Normally cfgmaker will not include interfaces which are marked
anything but administratively and operationally UP. With this
switch you get them all.

=item B<--show-op-down>

Include interfaces which are operatively down.

=item B<--subdirs> I<format>

Give each router its own subdirectory for the HTML and graphics (or
.rrd) files.  The directory name is the given I<format> string with a
couple of pattern replacements.  The string "HOSTNAME" will be
replaced by the hostname of the router (however you specified it on
the B<cfgmaker> commandline -- it may be an actual hostname or just an
IP address), and "SNMPNAME" will be replaced with the device's idea of
its own name (the same name that appears on the right side of the
"Title" lines).  For instance, a call like:

 cfgmaker --subdirs=HOSTNAME__SNMPNAME public@10.10.0.18

would result in the generation of lines looking something like:

 Directory[10.10.0.18_1]: 10.10.0.18__fp2200-bothrip-1.3

=item B<--output> I<file>

Write the output from B<cfgmaker> into the file I<file>. The default
is to use C<STDOUT>. B<--output> is expected to appear only once on the
command line. If used multiple times, the file specified by the last
B<--output> will be used.

=back

=head1 EXAMPLES

The first example creates a config file for I<router.place.xyz>:  the router           
has the community name I<public>.  Interfaces get identified by their             
IP number.  Two global options get added to the config file.  The                 
config file gets redirected to I<mrtg.conf>.  The '\' signs at the end            
of the line mean that this command should be written on a single line.            

 cfgmaker --global "WorkDir: /home/tobi"           \
          --global "Options[_]: growright,bits"    \
          --ifref=ip                               \
          public@router.place.xyz > mrtg.cfg

The next example creates a config file for four devices:
I<router1.place.xyz>, I<router2.place.xyz>, I<switch1.place.xyz> and
I<switch2.place.xyz> all with the community I<public>.

The two routers will have B<--ifref> set to B<descr> whilst the two
switches will use B<--ifref> set to B<name>.  Further the routers will
use B<--ifdesc> set to B<alias> and I<switch1.place.xyz> will use
B<--ifdesc> set to B<descr> whilst I<switch2.place.xyz> use B<name> instead.

Finally, there will be two Options lines inserted in the configuration:
One will be in the beginning, whilst the other will be inserted after
the lines related to the two routers but before those lines related
to the switches.

 cfgmaker --global "WorkDir: /home/tobi"           \
          --global "Options[_]: growright,bits"    \
          --ifref=descr                            \
          --ifdesc=alias                           \
          public@router1.place.xyz                 \
          public@router2.place.xyz                 \
          --global "Options[_]: growright"         \
          --ifref=name                             \
          --ifdesc=descr                           \
          public@switch1.place.xyz                 \
          --ifdesc=name                            \
          public@switch2.place.xyz > mrtg.cfg


The next example demonstrates how to use the B<--community>,
B<--snmp-options> and B<--dns-domain> to make the command line
simpler.  All the equipment will use the community I<hidden>, except for
the ppp-server which use community I<access>.  All equipment uses these
SNMP options: B<1s timeout>, B<1 retry> and B<SNMP version 2> (B<backoff> and
B<port> is unspecified which means they use the default values).
The exception again is the ppp-server which uses B<SNMP version 1>.
Finally, all the equipment is part of the domain I<place.xyz>, except
for the ppp-server which is part of the domain I<remote.place.xyz>.
Note that the latter is achieved simply by specifying the name
of the ppp-server to be I<ppp-server.B<remote>> .

 cfgmaker --global "WorkDir: /home/tobi"           \
          --global "Options[_]: growright,bits"    \
          --dns-domain=place.xyz                   \
          --community=hidden                       \
          --snmp-options=::1:1::2                  \
          router1                                  \
          router2                                  \
          router3                                  \
          router4                                  \
          router5                                  \
          switch1                                  \
          switch2                                  \
          switch3                                  \
          switch4                                  \
          switch5                                  \
          switch6                                  \
          switch7                                  \
          access@ppp-server.remote:::::1 > mrtg.cfg


=head1 SEE ALSO

L<reference>

=head1 AUTHOR

Tobias Oetiker E<lt>tobi@oetiker.chE<gt> and
Jakob Ilves E<lt>jakob.ilves@oracle.comE<gt>


=head1 LICENSE

GNU General Public License

=head1 COPYRIGHT

Cfgmaker is Copyright 2000 by Tobias Oetiker E<lt>tobi@oetiker.chE<gt>

=cut
