| #!/usr/bin/perl |
| #!/usr/bin/perl -w |
| |
| # |
| # Description: |
| # |
| # This program, given an OID reference as an argument, creates some |
| # template mib module files to be used with the net-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. |
| # |
| |
| # SNMP |
| my $havesnmp = eval {require SNMP;}; |
| |
| if (!$havesnmp) { |
| print " |
| ERROR: You don't have the SNMP perl module installed. Please obtain |
| this by getting the latest source release of the net-snmp toolkit from |
| http://www.net-snmp.org/download/ . Once you download the source and |
| unpack it, the perl module is contained in the perl/SNMP directory. |
| See the INSTALL file there for instructions. |
| |
| "; |
| exit; |
| } |
| |
| if ($havesnmp) { |
| eval { import SNMP; } |
| } |
| use FileHandle; |
| |
| #use strict 'vars'; |
| $SNMP::save_descriptions=1; |
| $SNMP::use_long_names=1; |
| $SNMP::use_enums=1; |
| SNMP::initMib(); |
| |
| $configfile="mib2c.conf"; |
| $debug=0; |
| $quiet=0; |
| $nostats = 0; |
| $noindent = 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"; |
| print " -d\t\tdebugging output (dont do it. trust me.)\n\n"; |
| print " -s\t\tDon't display statistics at the end\n\n"; |
| print " -S VAR=VAL\tSet $VAR variable to $VAL\n"; |
| print " -i Don't run indent on the resulting code\n"; |
| 1; |
| } |
| |
| while($#ARGV >= 0) { |
| $_ = shift; |
| $configfile = shift if (/^-c/); |
| $debug = 1 if (/^-d/); |
| if (/-S/) { |
| my $expr = shift; |
| my ($var, $val) = ($expr =~ /([^=]*)=(.*)/); |
| die "no variable specified for -S flag." if (!$var); |
| $vars{$var} = $val; |
| } |
| $quiet = 1 if (/^-q/); |
| $nostats = 1 if (/^-s/); |
| $noindent = 1 if (/^-i/); |
| usage && exit(1) if (/^-h/); |
| $outputName = shift if (/^-f/); |
| $oid = $_ if (/^[^-]/); |
| } |
| |
| # |
| # internal conversion tables |
| # |
| |
| %accessToIsWritable = qw(ReadOnly 0 ReadWrite 1 |
| WriteOnly 1 Create 1); |
| %perltoctypes = qw(OCTETSTR ASN_OCTET_STR |
| INTEGER ASN_INTEGER |
| INTEGER32 ASN_INTEGER |
| UNSIGNED32 ASN_UNSIGNED |
| OBJECTID ASN_OBJECT_ID |
| COUNTER64 ASN_COUNTER64 |
| COUNTER ASN_COUNTER |
| NETADDR ASN_COUNTER |
| UINTEGER ASN_UINTEGER |
| IPADDR ASN_IPADDRESS |
| BITS ASN_OCTET_STR |
| TICKS ASN_TIMETICKS |
| GAUGE ASN_GAUGE |
| OPAQUE ASN_OPAQUE); |
| %perltodecl = ("OCTETSTR", "char", |
| "INTEGER", "long", |
| "INTEGER32", "long", |
| "UNSIGNED32", "u_long", |
| "UINTEGER", "u_long", |
| "OBJECTID", "oid", |
| "COUNTER64", "counter64", |
| "COUNTER", "u_long", |
| "IPADDR", "u_long", |
| "BITS", "char", |
| "TICKS", "u_long", |
| "GAUGE", "u_long", |
| "OPAQUE", "char"); |
| |
| my $mibnode = $SNMP::MIB{$oid}; |
| die "you didn't give me a valid OID to start with" if (!$mibnode); |
| |
| # setup |
| $outputName = $mibnode->{'label'} if (!defined($outputName)); |
| $vars{'name'} = $outputName; |
| $vars{'oid'} = $oid; |
| |
| # loop through mib nodes, remembering stuff. |
| setup_data($mibnode); |
| |
| # process .conf file |
| $fh = new IO::File; |
| if (-f "$configfile") { |
| $fh->open("$configfile"); |
| } elsif(-f "/usr/local/share/snmp/$configfile") { |
| $fh->open("/usr/local/share/snmp/$configfile"); |
| } else { |
| print STDERR "Can't find a configuration file called $configfile\n"; |
| print STDERR "I looked in . and /usr/local/share/snmp\n"; |
| exit; |
| } |
| process(); |
| $fh->close; |
| |
| if (!$noindent) { |
| foreach $i (keys(%written)) { |
| next if ($i eq "-"); |
| print STDERR "running indent on $i\n" if (!$quiet); |
| system("indent -orig -nbc -bap -nut -nfca -T netsnmp_mib_handler -T netsnmp_handler_registration -T netsnmp_delegated_cache -T netsnmp_mib_handler_methods -T netsnmp_old_api_info -T netsnmp_old_api_cache -T netsnmp_set_info -T netsnmp_request_info -T netsnmp_set_info -T netsnmp_tree_cache -T netsnmp_agent_request_info -T netsnmp_cachemap -T netsnmp_agent_session -T netsnmp_array_group_item -T netsnmp_array_group -T netsnmp_table_array_callbacks -T netsnmp_table_row -T netsnmp_table_data -T netsnmp_table_data_set_storage -T netsnmp_table_data_set -T netsnmp_column_info -T netsnmp_table_registration_info -T netsnmp_table_request_info -T netsnmp_iterator_info -T netsnmp_data_list -T netsnmp_oid_array_header -T netsnmp_oid_array_header_wrapper -T netsnmp_oid_stash_node -T netsnmp_pdu -T netsnmp_request_list -T netsnmp_callback_pass -T netsnmp_callback_info -T netsnmp_transport -T netsnmp_transport_list -T netsnmp_tdomain $i"); |
| } |
| } |
| |
| sub tocommas { |
| my $oid = $_[0]; |
| $oid =~ s/\./,/g; |
| $oid =~ s/^\s*,//; |
| return $oid; |
| } |
| |
| sub oidlength { |
| return scalar ($_[0] =~ /\./); |
| } |
| |
| # replaces $VAR type expressions and $VAR.subcomponent expressions |
| # with data from the mib tree and loop variables. |
| # possible uses: |
| # |
| # $var -- as defined by loops, etc. |
| # ${var}otherstuff -- appending text to variable contents |
| # $var.uc -- all upper case version of $var |
| # |
| # Mib components, $var must first expand to a mib node name: |
| # |
| # $var.objectID -- dotted full OID |
| # $var.commaoid -- comma separated OID for array init |
| # $var.subid -- last number component of oid |
| # $var.oidlength -- length of the oid |
| # $var.type -- node's ASN_XXX type |
| # $var.settable -- 1 if it's writable, 0 if not |
| # $var.noaccess -- 1 if not-accessible, 0 if not |
| # $var.access -- node's access type |
| # $var.status -- node's status |
| # $var.syntax -- node's syntax |
| # $var.decl -- C data type |
| sub process_vars { |
| my $it = shift; |
| |
| # mib substitutions ($var.type -> $mibnode->{'type'}) |
| $it =~ s/\$(\w+)\.(uc)/uc($vars{$1})/eg; # make something uppercase |
| $it =~ s/\$(\w+)\.(commaoid)/tocommas($SNMP::MIB{$vars{$1}}{objectID})/eg; |
| $it =~ s/\$(\w+)\.(oidlength)/oidlength($SNMP::MIB{$vars{$1}}{objectID})/eg; |
| $it =~ s/\$(\w+)\.(perltype)/$SNMP::MIB{$vars{$1}}{type}/g; |
| $it =~ s/\$(\w+)\.(type)/$perltoctypes{$SNMP::MIB{$vars{$1}}{$2}}/g; |
| $it =~ s/\$(\w+)\.(subid)/$SNMP::MIB{$vars{$1}}{subID}/g; |
| $it =~ s/\$(\w+)\.(settable)/(($SNMP::MIB{$vars{$1}}{access} =~ \/(ReadWrite|Create|Writeonly)\/)?1:0)/eg; |
| $it =~ s/\$(\w+)\.(noaccess)/(($SNMP::MIB{$vars{$1}}{access} =~ \/(NoAccess)\/)?1:0)/eg; |
| $it =~ s/\$(\w+)\.(objectID|label|subID|access|status|syntax)/$SNMP::MIB{$vars{$1}}{$2}/g; |
| $it =~ s/\$(\w+)\.(decl)/$perltodecl{$SNMP::MIB{$vars{$1}}{type}}/g; |
| # normal variable substitions |
| $it =~ s/\$\{(\w+)\}/$vars{$1}/g; |
| $it =~ s/\$(\w+)/$vars{$1}/g; |
| return $it; |
| } |
| |
| # process various types of statements |
| # |
| # which include: |
| # @open FILE@ |
| # writes generated output to FILE |
| # @foreach $VAR scalar@ |
| # repeat iterate over code until @end@ setting $VAR to all known scalars |
| # @foreach $VAR table@ |
| # repeat iterate over code until @end@ setting $VAR to all known tables |
| # @foreach $VAR column@ |
| # repeat iterate over code until @end@ setting $VAR to all known |
| # columns within a given table. Obviously this must be called |
| # within a foreach-table clause. |
| # @foreach $VAR index@ |
| # repeat iterate over code until @end@ setting $VAR to all known |
| # indexes within a given table. Obviously this must be called |
| # within a foreach-table clause. |
| # @foreach $LABEL, $VALUE enum@ |
| # repeat iterate over code until @end@ setting $LABEL and $VALUE |
| # to the label and values from the enum list. |
| # @foreach $RANGE_START, $RANGE_END range NODE@ |
| # repeat iterate over code until @end@ setting $RANGE_START and $RANGE_END |
| # to the legal accepted range set for a given mib NODE. |
| # @eval $VAR = expression@ |
| # evaluates expression and assigns the results to $VAR |
| # @perleval STUFF@ |
| # evaluates STUFF directly in perl. Note that all mib2c variables |
| # interpereted within .conf files are in $vars{NAME}. |
| # @skip@ |
| # skips everything till the appropriately matched @end@ |
| # @if expression@ |
| # evaluates expression, and if expression is true processes |
| # contained part until appropriate @end@ is reached. |
| sub skippart { |
| my $endcount = 1; |
| while(<$fh>) { |
| if (/\@end\@/) { |
| return if ($endcount == 1); |
| $endcount--; |
| } |
| if (/\@else\@/) { |
| return if ($endcount == 1); |
| } |
| if (/\@(foreach|if)/) { |
| $endcount++; |
| } |
| } |
| } |
| |
| |
| sub process { |
| while(<$fh>) { |
| if (/^\#\#/) { |
| # noop, it's a comment |
| } elsif (/\@open\s+([^\@]+)\@/) { |
| my $spec = process_vars($1); |
| $out->close() if ($out); |
| $out = new IO::File; |
| $out->open(">$spec") || die "failed to open $spec"; |
| print STDERR "writing to $spec\n" if (!$quiet); |
| $written{$spec} = '1'; |
| } elsif (/\@end\@/) { |
| return; |
| } elsif (/\@if\s+([^@]*)\@/) { |
| if (eval(process_vars($1))) { |
| process(); |
| } else { |
| skippart(); |
| } |
| } elsif (/\@eval\s+\$(\w+)\s*=\s*([^\@]*)/) { |
| my ($v, $e) = ($1, $2); |
| my $e = process_vars($e); |
| $vars{$v} = eval($e); |
| } elsif (/\@perleval\s*(.*)\@/) { |
| eval($1); |
| } elsif (/\@skip\@/) { |
| skippart(); |
| } elsif (/\@\s*foreach\s+\$([^\@]+)\s+scalars*\s*\@/) { |
| my $var = $1; |
| my $startpos = $fh->tell(); |
| my $scalar; |
| my @thekeys = keys(%scalars); |
| if ($#thekeys == -1) { |
| skippart(); |
| } else { |
| foreach $scalar (@thekeys) { |
| $fh->seek($startpos, 0); # go to top of section. |
| my $oldvar = $vars{$var}; |
| $vars{$var} = $scalar; |
| my $oldscalar = $currentscalar; |
| $currentscalar = $scalar; |
| process(); |
| $vars{$var} = $oldvar; |
| $currentscalar = $oldscalar; |
| } |
| } |
| } elsif (/\@\s*foreach\s+\$([^\@]+)\s+tables*\s*\@/) { |
| my $var = $1; |
| my $startpos = $fh->tell(); |
| my $table; |
| my @thekeys = keys(%tables); |
| if ($#thekeys == -1) { |
| skippart(); |
| } else { |
| foreach $table (@thekeys) { |
| $fh->seek($startpos, 0); # go to top of section. |
| my $oldvar = $vars{$var}; |
| $vars{$var} = $table; |
| my $oldtable = $currenttable; |
| $currenttable = $table; |
| process(); |
| $vars{$var} = $oldvar; |
| $currenttable = $oldtable; |
| } |
| } |
| } elsif (/\@\s*foreach\s+\$([^\@]+)\s+(column|index)\s*\@/) { |
| my ($var, $type) = ($1, $2); |
| my $startpos = $fh->tell(); |
| my $column; |
| if ($#{$tables{$currenttable}{$type}} == -1) { |
| skippart(); |
| } else { |
| foreach $column (@{$tables{$currenttable}{$type}}) { |
| # print "looping on $var for $type -> $column\n"; |
| $fh->seek($startpos, 0); # go to top of section. |
| my $oldvar = $vars{$var}; |
| $vars{$var} = $column; |
| my $oldcolumn = $currentcolumn; |
| $currentcolumn = $column; |
| process(); |
| $vars{$var} = $oldvar; |
| $currentcolumn = $oldcolumn; |
| } |
| } |
| } elsif (/\@\s*foreach\s+\$([^\@]+)\s+\$([^\@]+)\s+range\s+([^\@]+)\@/) { |
| my ($svar, $evar, $node) = ($1, $2, $3); |
| my $startpos = $fh->tell(); |
| my $range; |
| $node = $currentcolumn if (!$node); |
| my $mibn = $SNMP::MIB{process_vars($node)}; |
| die "no such mib node: $node" if (!$mibn); |
| my @ranges = @{$mibn->{'ranges'}}; |
| if ($#ranges > -1) { |
| foreach $range (@ranges) { |
| # print "looping on $var for $type -> $column\n"; |
| $fh->seek($startpos, 0); # go to top of section. |
| my $oldvars = $vars{$svar}; |
| my $oldvare = $vars{$evar}; |
| $vars{$svar} = $range->{'low'}; |
| $vars{$evar} = $range->{'high'}; |
| process(); |
| $vars{$svar} = $oldvars; |
| $vars{$evar} = $oldvare; |
| } |
| } else { |
| skippart(); |
| } |
| } elsif (/\@\s*foreach\s+\$([^\@]+)\s*,*\s+\$([^\@]+)\s+(enums*)\s*\@/) { |
| my ($varvar, $varval, $type) = ($1, $2, $3); |
| my $startpos = $fh->tell(); |
| my $enum; |
| my @keys = sort { $SNMP::MIB{$currentcolumn}{'enums'}{$a} <=> |
| $SNMP::MIB{$currentcolumn}{'enums'}{$b} } (keys(%{$SNMP::MIB{$currentcolumn}{'enums'}})); |
| if ($#keys > -1) { |
| foreach $enum (@keys) { |
| $fh->seek($startpos, 0); # go to top of section. |
| my $oldvarvar = $vars{$varvar}; |
| my $oldvarval = $vars{$varval}; |
| $vars{$varvar} = $enum; |
| $vars{$varval} = $SNMP::MIB{$currentcolumn}{'enums'}{$enum}; |
| process(); |
| $vars{$varvar} = $oldvarvar; |
| $vars{$varval} = $oldvarval; |
| } |
| } else { |
| skippart(); |
| } |
| } else { |
| die "no output file specified" if (!$out); |
| print $out process_vars($_); |
| } |
| } |
| } |
| |
| sub setup_data { |
| my $mib = shift; |
| if ($mib->{label} =~ /Table$/) { |
| my $tablename = $mib->{label}; |
| my $entry = $mib->{children}; |
| my $columns = $entry->[0]{children}; |
| foreach my $col (sort { $a->{'subID'} <=> $b->{'subID'} } @$columns) { |
| # store by numeric key so we can sort them later |
| push @{$tables{$tablename}{'column'}}, $col->{'label'}; |
| } |
| foreach my $index (@{$entry->[0]{'indexes'}}) { |
| my $node = $SNMP::MIB{$index} || |
| die "can't find info about index $index in table $tablename\n"; |
| push @{$tables{$tablename}{'index'}}, $index; |
| } |
| } else { |
| my $children = $mib->{children}; |
| if ($#children == -1 && $mib->{type}) { |
| # scalar |
| $scalars{$mib->{label}} = 1; |
| } else { |
| my $i; |
| for($i = 0; $i <= $#$children; $i++) { |
| setup_data($children->[$i]); |
| } |
| } |
| } |
| } |
| |
| sub min { |
| return $_[0] if ($_[0] < $_[1]); |
| return $_[1]; |
| } |
| |
| sub max { |
| return $_[0] if ($_[0] > $_[1]); |
| return $_[1]; |
| } |