#! /usr/bin/perl
# -*- mode: Perl -*-
##################################################################
# MRTG 2.8.12  -- Config file creator
##################################################################
# Created by Tobias Oetiker <oetiker@ee.ethz.ch>
# this produces a config file for one router, by pulling info
# off the router via snmp
#################################################################
#
# Distributed under the GNU copyleft

use BER "0.57";
use SNMP_Session "0.59";
use SNMP_util "0.57";

use Getopt::Long;
use Socket;
use strict;
use vars '$DEBUG';
my $DEBUG = 0;

my (%optctl) = ();
my ($session, %ipaddr, %iphost);

sub main {

    snmpmapOID('ipAdEntAddr' => '1.3.6.1.2.1.4.20.1.1',
	       'ipAdEntIfIndex' => '1.3.6.1.2.1.4.20.1.2',
	       'sysObjectID' => '1.3.6.1.2.1.1.2.0',
	       'CiscolocIfDescr' => '1.3.6.1.4.1.9.2.2.1.1.28',
	       'ifAlias' => '1.3.6.1.2.1.31.1.1.1.18');

  my(%ifType_d)=('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',
                 #### New IF Types added 9/24/98 by Russ Carleton (roccor@livenetworking.com)
                 #### based on the IANA file updated at ftp://ftp.isi.edu/mib/ianaiftype.mib
                 '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'
                 );

  my($vendor)=0;
  my($iponly)=0;
  my($workdir)=0;
  my($community,$router,%defaults);
  my($res);

  # Just need to make sure that all perl installations have the GetOpt module
  #   - fwo@obsidian.co.za
  #
  $res = GetOptions(\%optctl,"vendor","iponly","workdir=s","options=s");
  if($res == 0 ) {
      print "\nError in arguments. Please run cfgmaker with no arguments for help!\n\n";
      exit 1;
  }

  if($optctl{"vendor"})   { $vendor = 1; }
  if($optctl{"iponly"})   { $iponly = 1; }
  if($optctl{"workdir"})  { $workdir = $optctl{"workdir"}; }
  if($optctl{"options"}) { 
      foreach $res(split(',',$optctl{"options"})) {
	  chomp($res);
	  next if($res eq "growright");
	  next if($res eq "bits");
	  next if($res eq "perminute");
	  next if($res eq "perhour");
	  next if($res eq "noinfo");
	  next if($res eq "nopercent");
	  next if($res eq "transparent");
	  next if($res eq "integer");
	  next if($res eq "dorelpercent");
	  next if($res eq "gauge");
	  next if($res eq "absolute");
	  next if($res eq "unknaszero");
	  print "\nERROR:$res is not a valid option for --options, exiting!\n";
	  exit 1;
      }
  }
  $res = 0;
#TODO: check for conflicting info in options - l->r and r->l
  print STDERR "iponly=$iponly\n" if $DEBUG;
  print STDERR "vendor=$vendor\n" if $DEBUG;
  print STDERR "workdir=$workdir\n" if $DEBUG;

  ($community,$router) = ($1,$2) if $ARGV[0] =~ /(.*)\@(.*)/;
  if(!($community) && !($router)) {
  print <<USAGE;

USAGE: cfgmaker [--vendor] [other options] 'community'\@'router'

use the --vendor option to try and wrestle some better information
    from willing livingston and cisco routers ... (may not work)

[other options] can be one of the following:
use the --iponly option to force the targets to the ip adress of the router 
    being queried. Also note that the 'router' option must be the ip address
    and not the symbolic name.

use the --workdir '/some/path' option to add the WorkDir option to the cfg file 
    being generated.

use the --options 'defaults' to set default options for each target. defaults 
    is a comma seperated list and can contain the following :
        growright bits perminute perhour noinfo nopercent transparent
	integer dorelpercent gauge absolute unknaszero

    See the mrtg.cfg doccumentation for descriptions of the above options.
    NOTE: no checking for conflicting options are done at the moment.

EXAMPLE:  cfgmaker public\@ezwf7.ethz.ch >>mrtg.cfg

USAGE
    exit 1;
  }
  if($iponly) { 
      # We only want to work with ip numbers and not names
      # Check to see whether the name is a valid ip address
      # If not bail - we do not want to deal with reverse dns
      #    issues - fwo@obsidian.co.za 2000/01/17
      chomp($router);
      if(!($router =~ /^[0-9]{1,3}.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/)){
	  print "\nERROR: when specifying --iponly, please use a valid\n";
	  print "ip address for the router. Do not use a symbolic name.\n\n";
	  exit 1;
      }
  }
  if($workdir) {
  	print "WorkDir: $workdir\n\n";
  } else {
  	print "# Add a WorkDir: /some/path line to this file\n\n";
  }

  $session = SNMP_Session->open ($router, $community, 161)
	|| die "Error opening SNMP session to $router";
  
  my($sysDescr,$sysContact,$sysName,$sysLocation,$ifNumber,$sysObjectID) =
    snmpget("$community\@$router",
	    'sysDescr','sysContact','sysName',	'sysLocation', 'ifNumber', 'sysObjectID');
   my $sysDescr_orig=$sysDescr;
   $sysDescr_orig =~ s/[\r\n ]+/ /g;
   $sysDescr =~ s/[\r\n]+/<BR>/g;  # Change returns to <BR>
   my($cisco_router_sysid) = '1\.3\.6\.1\.4\.1\.9';
   my($livingston_router_sysid) = '1\.3\.6\.1\.4\.1\.307';
   my($ciscobox) = ($sysObjectID =~ /^$cisco_router_sysid/);
   my($portmaster) = ($sysObjectID =~ /^$livingston_router_sysid/);

    print <<ECHO;


######################################################################
# Description: $sysDescr_orig
#     Contact: $sysContact
# System Name: $sysName
#    Location: $sysLocation
#.....................................................................
ECHO

  $session->map_table ([[1,3,6,1,2,1,4,20,1,1],	# ipAdEntAddr
			[1,3,6,1,2,1,4,20,1,2]], # ipAdEntIf
		       sub ($@) {
			   my ($index, $ipadentaddr, $ipadentif) = @_;
			   grep (defined $_ && ($_=pretty_print $_),
				 ($ipadentif, $ipadentaddr));
			   $ipaddr{$ipadentif} = $ipadentaddr;
			   $iphost{$ipadentif} = 
			       gethostbyaddr(pack('C4',split(/\./,$ipaddr{$ipadentif})), AF_INET);
			   if (!defined $iphost{$ipadentif} || ($iphost{$ipadentif} eq '')){
			       $iphost{$ipadentif} = 'No hostname defined for IP address';
			   }
		       });

  print STDERR "Got Addresses\n" if $DEBUG;
  print STDERR "Got IfTable\n" if $DEBUG;

  my(%sifdesc,%siftype,%sifspeed,%sifadminstatus,%sifoperstatus,%sciscodescr);

  ### May need the cisco IOS version number so we know which oid to use
  ###   to get the cisco description.
  ###
  ### - mjd 2/5/98 (Mike Diehn) (mdiehn@mindspring.net)
  ###
  my ($cisco_ver, $cisco_descr_oid, @ciscodescr);
  if ( $ciscobox ) {
    ($cisco_ver) = ($sysDescr =~ m/Version\s+([\d\.]+)\(\d/o);
    $cisco_descr_oid = ($cisco_ver ge "11.2") ? "ifAlias" : "CiscolocIfDescr";
  }

$session->map_table ([[1,3,6,1,2,1,2,2,1,1], # ifIndex
		      [1,3,6,1,2,1,2,2,1,2], # ifDescr
		      [1,3,6,1,2,1,2,2,1,3], # ifType
		      [1,3,6,1,2,1,2,2,1,5], # ifSpeed
		      [1,3,6,1,2,1,2,2,1,7], # ifAdminStatus
		      [1,3,6,1,2,1,2,2,1,8]], # ifOperStatus
sub ($@) {
    my ($rowindex,$index,$ifdescr,$iftype,$ifspeed,$ifadminstatus,$ifoperstatus) = @_;
    grep (defined $_ && ($_=pretty_print $_),
	  ($index,$ifdescr,$iftype,$ifspeed,$ifadminstatus,$ifoperstatus));
    $sifdesc{$index} = $ifdescr;
    $siftype{$index} = $iftype;
    $sifspeed{$index} = $ifspeed;
    $sifadminstatus{$index} = $ifadminstatus;
    $sifoperstatus{$index} = $ifoperstatus;
    if ($portmaster && $vendor) {
      
      # 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)
      # 

      if ($siftype{$index} eq '23') {
              if ($sifdesc{$index} eq 'ptpW1') {
                      $sifspeed{$index} = 128000;
              } else {
                      $sifspeed{$index} = 76800;
              }
      } elsif ($siftype{$index} eq '28') {
              $sifspeed{$index} = 38400;
      } elsif ($siftype{$index} eq '6') {
              $sifspeed{$index} = 10000000;
      }
    }

    ### Move this section south so we know what type of
    ###  circuit we're looking at before we retrieve
    ###  the cisco interface alias.
    ###
    ### This whole cicso thing should be re-written, but since
    ###   this script doesn't need to run quickly...
    ###
    ###  - mjd 2/5/98
    ###
    # Get the user configurable interface description entered in the config 
    # if it's a cisco-box
    #
    if ($ciscobox && $vendor) {

	my ($enoid, @descr);

	$enoid = $cisco_descr_oid . "." . $index;

	if ( $cisco_ver ge "11.2" or $siftype{$index} != '32' ) {

	  ### This is either not a frame-relay sub-interface or
	  ###  this router is running IOS 11.2+ and interface
	  ###  type won't matter. In either of these cases, it's
	  ###  ok to try getting the ifAlias or ciscoLocIfDesc.
	  ###
	  @descr = snmpget("$community\@$router", $enoid);

	} else {

	  ### This is a frame-relay sub-interface *and* the router
	  ###  is running an IOS older than 11.2. Therefore, we can
	  ###  get neither ifAlias nor ciscoLocIfDesc. Do something
	  ###  useful.
	  ###
	  @descr = ("Cisco PVCs descriptions require IOS 11.2+.");

	} # end if else

	### Put whatever I got into the array we'll use later to append the result
	###   of this operation onto the results from the ifDescr fetch.
	###
	push @ciscodescr, shift @descr;

    } # end if ($cisco_box && $vendor)

    # especially since cisco does not return a if
    # descr for each interface it has ...
    ## JB 2/8/97 - sometimes IOS inserts E1 controllers in the standard-MIB
    ## interface table, but not in the local interface table. This puts the
    ## local interface description table out-of-sync. the following 
    ## modification skips over E1 cards as interfaces.
    #
    ### I suspect that the mod I made above, to use the ifAlias
    ###   oid if possible, may cause problems here. If it seems
    ###   that your descriptions are out of sync, try commenting
    ###   out the "if ( condition )" and it's closing right brace
    ###   so that the "shift @ciscodescr" get executed for *all*
    ###   iterations of this loop.
    ###
    ### - mjd 2/5/95
    ###
    if ($ciscobox && $siftype{$index} != 18) {
          $sciscodescr{$index} = "<BR>" . (shift @ciscodescr) if @ciscodescr;
    }
});
  my $index;
  foreach $index ( sort { $a <=> $b } keys %sifdesc) {
    my $c;
    my $speed = int($sifspeed{$index} / 8); # bits to byte
    my $speed_str=&fmi($speed);
    my $name="$iphost{$index}";

    $name = "$router.$index" if not $name or $name =~ /\s/ or $iponly;

  my $err_msg;
    
  $err_msg .= "######## - administratively not UP\n" if ($sifadminstatus{$index} != 1);
# this check added by Josh - don't query E1-stack controllers
  $err_msg .= "######## - DS1 type\n" 
	  if ($siftype{$index} == 18);
# i might be mistaken on this one - fwo@obsidian.co.za
  $err_msg .= "######## - E1 type\n" 
	  if ($siftype{$index} == 19);
  $err_msg .= "######## - it is a softwareLoopback interface\n" 
	  if ($siftype{$index} == 24);
  $err_msg .= "######## - has an unrealistic speed setting\n" 
	  if (($speed == 0 ) || ($speed > 400 * 10**6));  
  $err_msg .= "######## - administratively not UP\n" 
	  if ($sifoperstatus{$index} == 3) ;
  if($err_msg ne "") {
    print <<ECHO;
########
######## This Interface not configured :
$err_msg######## It is commented out for this reason.
########
ECHO
    $c="# ";
  }else {
    $c = '';
  }
    
  print <<ECHO;
${c}
${c}Target[$name]: $index:$community\@$router
${c}MaxBytes[$name]: $speed
${c}Title[$name]: $sysName ($iphost{$index}): $sifdesc{$index}
ECHO
if($optctl{"options"}) {
    print "${c}Options[$name]: $optctl{'options'}\n";
}
   print <<ECHO;
${c}PageTop[$name]: <H1>Traffic Analysis for $sifdesc{$index}
${c} $sciscodescr{$index}</H1>
${c} <TABLE>
${c}   <TR><TD>System:</TD><TD>$sysName in $sysLocation</TD></TR>
${c}   <TR><TD>Maintainer:</TD><TD>$sysContact</TD></TR>
${c}   <TR><TD>Interface:</TD><TD>$sifdesc{$index} ($index)</TD></TR>
${c}   <TR><TD>IP:</TD><TD>$iphost{$index} ($ipaddr{$index})</TD></TR>
${c}   <TR><TD>Max Speed:</TD>
${c}       <TD>$speed_str ($ifType_d{$siftype{$index}})</TD></TR>
${c}  </TABLE>
${c}
#---------------------------------------------------------------
ECHO
  }
}
  
main;
exit(0);

sub fmi {
  my($number) = $_[0];
  my(@short);
  @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]);
}
