#!/usr/local/bin/perl -w

#
# Description: 
#
# This program, given an OID reference as an argument, creates some
# template mib module files to be used with the ucd-snmp agent.  It is
# far from perfect and will not generate working modules, but it
# significantly shortens development time by outlining the basic
# structure.
#
# Its up to you to verify what it does and change the default values
# it returns.
#

require 5;

use SNMP;
#use strict 'vars';
$SNMP::save_descriptions=1;
$SNMP::use_long_names=1;
$SNMP::use_enums=1;
SNMP::initMib();

$configfile="mib2c.conf";
$debug=0;

sub usage {
    print "$0 [-h] [-c configfile] [-f prefix] mibNode\n\n";
    print "  -h\t\tThis message.\n\n";
    print "  -c configfile\tSpecifies the configuration file to use\n\t\tthat dictates what the output of mib2c will look like.\n\n";
    print "  -f prefix\tSpecifies the output prefix to use.  All code\n\t\twill be put into prefix.c and prefix.h\n\n";
    print "  mibNode\tThe name of the top level mib node you want to\n\t\tgenerate code for.  By default, the code will be stored in\n\t\tmibNode.c and mibNode.h (use the -f flag to change this)\n\n";
    1;
}	

while($#ARGV >= 0) {
    $_ = shift;
    $configfile = shift if (/-c/);
    usage && exit(1) if (/-h/);
    $outputName = shift if (/-f/);
    $oid = $_ if (/^[^-]/);
}
 
if ( open(I,"$configfile") ) {
while(<I>) {
    next if /^\s*#/;
    if (/type:\s*(.*)/) {
	$type = $1;
	chomp($type);
    } else {
	if (/\s*([^:]*):\s*(.*)/) {
	    $variableTypes{$type}{$1} = $2;
	    chomp($variableTypes{$type}{$1});
	    $lasttoken = $1;
	} else {
	    # continuation line
	    $_ =~ s/^\s*//;
	    $variableTypes{$type}{$lasttoken} .= "\n" . $_;
	    chomp($variableTypes{$type}{$lasttoken});
	}
    }
}
close(I);
}
else
{
  warn "Config file ($configfile) not found.
Note that variable writing routines will not be generated\n";
}

#
# internal conversion tables
#
$varInits =
"  /* variables we may use later */
  static long long_ret;
  static unsigned char string[SPRINT_MAX_LEN];
  static oid objid[MAX_OID_LEN];
  static struct counter64 c64;";

%accessToUCD = qw(ReadOnly RONLY ReadWrite RWRITE 
		  WriteOnly RWRITE Create RWRITE);

#  The lengths of the defined 'variableN' structures
@varLengths = (2,4,7,8,13);

if (!defined($oid)) {
    print STDERR "You didn\'t specify a mib oid to convert!\n";
    usage();
    exit(1);
}

$mib = $SNMP::MIB{$oid};
$_ = $commaoid = $fulloid = $mib->{'objectID'};
if (!defined ($fulloid)) {
    print STDERR "Couldn\'t find mib reference: $oid\n";
    exit(1);
}
s/[^.]//g;
$commaoid =~ s/\./,/g;
$commaoid =~ s/^,//g;

$outputName = $mib->{'label'} if (!defined($outputName));
$OUTPUTNAME = uc($outputName);
$vroutine="$outputName";
print "outputting to $outputName.c and $outputName.h ...\n";

#============================================
#
#   Walk the MIB tree, and construct strings
#     holding the various fragments of code needed.
#
#   'loadMib' returns the length of the longest OID suffix
#     encountered.
#
#   The variables constructed and used are:
#
#   (in the header file)
#	functionInfo :	A list of definitions for the table-handling functions,
#			and routines for SETtable variables.
#			(The main scalar handling routine is handled implicitly)
#
#   (in the code file)
#	structinfo :	The contents of the variableN structure listing
#			the variables handled, including type, access level,
#			OID suffix and 'magic number'
#
#	caseStatements:	A hash array (indexed by variable routine name)
#			containing the body of the switch statement
#			used for returning the appropriate values.
#			At a minimum, this consists of the various 'case' labels
#			If full type information is available (from mib2c.conf)
#			then this will also include a default initialiser,
#			and setting of a 'write_method' (if appropriate).
#
#	writeFuncs:	A list of function skeletons for setting variables
#			(for variables with suitable access levels).
#			Note that this list will not include functions
#			for variables which don't provide type information
#			in the mib2c.conf file (even if such variables are
#			defined as writeable in the variableN structure).
#
#============================================
$count = 0;
$depth = loadMib($mib,0)-1;

#  Determine which 'variableN' structure is needed
for($varlen = 0; $varlen <= $#varLengths; $varlen++) {
  last if ($depth <= $varLengths[$varlen]);
}
$varlen = $varLengths[$varlen];


#============================================
#
#   Output the header file
#
#============================================
open(DOTH,">$outputName.h");
print DOTH "
/* This file was generated by mib2c and is intended for use as a mib module
   for the ucd-snmp snmpd agent. */

#ifndef _MIBGROUP_${OUTPUTNAME}_H
#define _MIBGROUP_${OUTPUTNAME}_H

/* we may use header_generic and header_simple_table from the util_funcs module */

config_require(util_funcs)

/* function prototypes */

void   init_$outputName(void);
unsigned char *var_$outputName(struct variable *, oid *, int *, int, int *, WriteMethod **write_method);
$functionInfo

#endif /* _MIBGROUP_${OUTPUTNAME}_H */\n";
close(DOTH);



#============================================
#
#   Output the code file:
#	Initialisation and main variable routine.
#
#============================================

open(DOTC,">$outputName.c");
print DOTC "
/* This file was generated by mib2c and is intended for use as a mib module
   for the ucd-snmp snmpd agent. */

/* This should always be included first before anything else */
#include <config.h>

/* minimal include directives */
#include \"mibincl.h\"
#include \"util_funcs.h\"
#include \"$outputName.h\"

/* 
 * ${outputName}_variables_oid:
 *   this is the top level oid that we want to register under.  This
 *   is essentially a prefix, with the suffix appearing in the
 *   variable below.
 */

oid ${outputName}_variables_oid[] = { $commaoid };

/* 
 * variable$varlen ${outputName}_variables:
 *   this variable defines function callbacks and type return information 
 *   for the $outputName mib section 
 */

struct variable$varlen ${outputName}_variables[] = {
/*  magic number        , variable type , ro/rw , callback fn  , L, oidsuffix */
$structinfo
};
/*    (L = length of the oidsuffix) */

/*
 * init_$outputName():
 *   Initialization routine.  This is called when the agent starts up.
 *   At a minimum, registration of your variables should take place here.
 */
void init_$outputName(void) {

  /* register ourselves with the agent to handle our mib tree */
  REGISTER_MIB(\"$outputName\", ${outputName}_variables, variable$varlen,\
               ${outputName}_variables_oid);

  /* place any other initialization junk you need here */
}

/*
 * var_$outputName():
 *   This function is called every time the agent gets a request for
 *   a scalar variable that might be found within your mib section
 *   registered above.  It is up to you to do the right thing and
 *   return the correct value.
 *     You should also correct the value of \"var_len\" if necessary.
 *
 *   Please see the documentation for more information about writing
 *   module extensions, and check out the examples in the examples
 *   and mibII directories.
 */
unsigned char *
var_$outputName(struct variable *vp, 
                oid     *name, 
                int     *length, 
                int     exact, 
                int     *var_len, 
                WriteMethod **write_method)
{

$varInits

  if (header_generic(vp,name,length,exact,var_len,write_method)
					== MATCH_FAILED )
    return NULL;

  /* 
   * this is where we do the value assignments for the mib results.
   */
  switch(vp->magic) {\n\n
$caseStatements{$outputName}\n
    default:
      ERROR_MSG(\"\");
  }
  return NULL;
}


";

#============================================
#
#	Table-handling routines.
#
#============================================
foreach $vtable (@table_list) {
    print DOTC "
/*
 * var_$vtable():
 *   Handle this table separately from the scalar value case.
 *   The workings of this are basically the same as for var_$outputName above.
 */
unsigned char *
var_$vtable(struct variable *vp,
    	    oid     *name,
    	    int     *length,
    	    int     exact,
    	    int     *var_len,
    	    WriteMethod **write_method)
{

$varInits

  /* 
   * This assumes that the table is a \'simple\' table.
   *	See the implementation documentation for the meaning of this.
   *	You will need to provide the correct value for the TABLE_SIZE parameter
   *
   * If this table does not meet the requirements for a simple table,
   *	you will need to provide the replacement code yourself.
   *	Mib2c is not smart enough to write this for you.
   *    Again, see the implementation documentation for what is required.
   */
  if (header_simple_table(vp,name,length,exact,var_len,write_method, TABLE_SIZE)
						== MATCH_FAILED )
    return NULL;

  /* 
   * this is where we do the value assignments for the mib results.
   */
  switch(vp->magic) {\n\n
$caseStatements{$vtable}\n
    default:
      ERROR_MSG(\"\");
  }
  return NULL;
}


";


}

#============================================
#
#	Writing routines.
#	   (and reporting on the results)
#
#============================================
print DOTC "$writeFuncs";
close(DOTC);




print "  depth: $depth\n";
print "  Number of Lines Created:\n";
system("wc -l $outputName.c $outputName.h");
print "Done.\n";


#============================================
#
#  loadMib:
#	Recursive routine to walk the mib,
#	and construct the various code fragment strings.
#
#============================================
sub loadMib {
    my $mib = shift;
    my $depth = shift;
    $depth = $depth + 1;
    my $name = $mib->{'label'};
    my $cname = uc($name);
    print "doing $mib->{label} : $mib->{objectID}\n" if $debug;
    if (defined($mib->{'access'}) && 
	$mib->{'access'} =~ /ReadOnly|ReadWrite|WriteOnly|Create/) {
	$caseStatement = "    case $cname:\n";
	$count = $count + 1;
	$subid = $mib->{'objectID'};
	$subid =~ s/$fulloid\.//;
	$subid =~ s/\./,/g;
	$structinfo .= sprintf("#define   %-20s  $count\n", $cname);
	if (!defined($variableTypes{$mib->{'type'}})) {
	    $caseStatement .= "/* unknown type: $mib->{type}.  mib2c can not set up a default value for this mib value */\n";
	    # $defineStatements .= "/* unknown type: $mib->{type}.  mib2c can not handle this mib value: */\n";
	    $count = $count + 1;
	    print STDERR "unknown type:  $mib->{type} for $mib->{label}\n";
	    $structinfo .= 
		sprintf("/* unknown type: $mib->{type}.  mib2c can not handle this mib value: */\n{ %-20s, %-14s, %-6.6s, %s, %d, { %s } },\n",
			$cname, "UNKNOWN_TYPE_$mib->{type}",
			$accessToUCD{$mib->{'access'}},
			"var_$vroutine",
			$depth-1, $subid);
	} else {
	    if ($mib->{'access'} =~ /ReadWrite|WriteOnly|Create/) {
		createWriteFunction($mib->{'label'}, $mib->{'type'});
		$caseStatement .= "      *write_method = write_$mib->{label};\n";
	    }
	    $x = $variableTypes{$mib->{'type'}}{'defaultInit'};
	    $x =~ s/\n/\n        /g;
	    $x = "        " . $x;
	    $caseStatement .= $x . "\n";
	    $structinfo .= 
		sprintf("  { %-20s, %-14s, %-6.6s, %s, %d, { %s } },\n",
			$cname, $variableTypes{$mib->{'type'}}{'asnType'},
			$accessToUCD{$mib->{'access'}},
			"var_$vroutine",
			$depth-1, $subid);
	}
	$caseStatement .= "\n";
	$caseStatements{$vroutine} .= $caseStatement;
	# $defineStatements .= sprintf("#define   %-20s  $count\n", $cname);
    }
    my $children = $$mib{'children'}; 
    my $i;
    my $newdepth = $depth;
    foreach $i (@{$children}) {
	if ( $name =~ /Table$/ ) {
	    $vroutine="$name";
    	    $functionInfo .= "unsigned char *var_$name(struct variable *, oid *, int *, int, int *, WriteMethod **write_method);\n";
	    push @table_list, $name;
	    $newdepth = max(loadMib($i, $depth), $newdepth);
	    $vroutine="$outputName";
	}
	else {
	    $newdepth = max(loadMib($i, $depth), $newdepth);
	}
    }
    return $newdepth;
}

sub max {
    my $x = shift;
    my $y = shift;
    return ($x > $y) ? $x : $y;
}


#============================================
#
#  createWriteFunction:
#	Construct a write function for the current variable,
#	including a declaration for the header file.
#
#============================================
sub createWriteFunction {
    my $name = shift;
    my $type = shift;

    $writeFuncs .= "int
write_$name(int      action,
   	    u_char   *var_val,
   	    u_char   var_val_type,
   	    int      var_val_len,
   	    u_char   *statP,
   	    oid      *name,
   	    int      name_len)
{
  $variableTypes{$type}{writeInit}
  int size, bigsize=SNMP_MAX_LEN;

  switch ( action ) {
	case RESERVE1:
	  if (var_val_type != $variableTypes{$type}{asnType}){
	      fprintf(stderr, \"write to $name not $variableTypes{$type}{asnType}\\n\");
	      return SNMP_ERR_WRONGTYPE;
	  }
	  if (var_val_len > sizeof($variableTypes{$type}{variable})){
	      fprintf(stderr,\"write to $name: bad length\\n\");
	      return SNMP_ERR_WRONGLENGTH;
	  }
	  break;

	case RESERVE2:
	  size = sizeof($variableTypes{$type}{variable});
	  if ($variableTypes{$type}{parser}(var_val, &bigsize, &var_val_type, $variableTypes{$type}{variablePtr}, &size)
			== NULL )
	      return SNMP_ERR_WRONGENCODING;	/* Probably not the correct error */

	      /* Any other checks on the value being acceptable go here */
	  break;

	case FREE:
	     /* Release any resources that have been allocated */
	  break;

	case ACTION:
	     /* The variable has been stored in $variableTypes{$type}{variable} for
	     you to use, and you have just been asked to do something with
	     it.  Note that anything done here must be reversable in the UNDO case */
	  break;

	case UNDO:
	     /* Back out any changes made in the ACTION case */
	  break;

	case COMMIT:
	     /* Things are working well, so it's now safe to make the change
	     permanently.  Make sure that anything done here can't fail! */
	  break;
  }
  return SNMP_ERR_NOERROR;
}\n\n";
    $functionInfo .= "int write_$name(int, u_char *,u_char, int, u_char *,oid*, int);\n";
}
