commit 6d24aad64b45c082867dc990da0b867d84913098 Author: Ryan Armstrong Date: Wed Jun 1 15:33:08 2016 +0800 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..8dfe668 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# mib2zabbix + +This Perl script will generate a Zabbix Template in XML format for an OID tree +in an SNMP MIB file. + +### Usage + + mib2zabbix.pl [ -l | -t ] -o [OPTIONS]... + + Export loaded SNMP MIB OIDs to Zabbix Template XML + + --export-maps export value maps directly to Zabbix database + + -t, --template generate a Zabbix template + -f, --filename=PATH output filename (default: stdout) + + -N, --name=STRING template name (default: OID label) + -G, --group=STRING template group (default: 'Templates') + -e, --enable-items enable template items (default: disabled) + * enable with caution * + + -o, --oid=STRING OID tree root to export + + -v, --snmpver=1|2|3 SNMP version (default: 1) + -p, --port=PORT SNMP UDP port number (default: 161) + + SNMP Version 1 or 2c specific + + -c, --community=STRING SNMP community string (default: 'public') + + SNMP Version 3 specific + + -L, --level=LEVEL security level (noAuthNoPriv|authNoPriv|authPriv) + -n, --context=CONTEXT context name + -u, --username=USERNAME security name + -a, --auth=PROTOCOL authentication protocol (MD5|SHA) + -A, --authpass=PASSPHRASE authentication protocol passphrase + -x, --privacy=PROTOCOL privacy protocol (DES|AES) + -X, --privpass=PASSPHRASE privacy passphrase + + --check-delay=SECONDS check interval in seconds (default: 300) + --disc-delay=SECONDS discovery interval in seconds (default: 86400) + --history=DAYS history retention in days (default: 365) + --trends=DAYS trends retention in days (default: 3650) + + -h, --help print this message + +### Requirements + +* Perl v5+ +* Pod::Usage +* XML::Simple +* Net-SNMP +* Correctly configured [MIB files](http://net-snmp.sourceforge.net/tutorial/tutorial-5/commands/mib-options.html) + +### Translations + +* Scalar OID -> Zabbix SNMP Item +* Table OID -> Zabbix SNMP Discovery Rule +* Table Column OID -> Zabbix Discovery Prototype +* Trap/Notification OID -> Zabbix SNMP Trap Item +* OID Enums -> Zabbix Value Map diff --git a/mib2zabbix.pl b/mib2zabbix.pl new file mode 100755 index 0000000..37a247e --- /dev/null +++ b/mib2zabbix.pl @@ -0,0 +1,998 @@ +#!/usr/bin/perl -w +=pod + +=head1 NAME + +mib2zabbix.pl - SNMP MIB to Zabbix Template + +=head1 SYNOPSIS + +mib2zabbix.pl [ -l | -t ] -o [OPTIONS]... + +Export loaded SNMP MIB OIDs to Zabbix Template XML + + --export-maps export value maps directly to Zabbix database + + -t, --template generate a Zabbix template + -f, --filename=PATH output filename (default: stdout) + + -N, --name=STRING template name (default: OID label) + -G, --group=STRING template group (default: 'Templates') + -e, --enable-items enable template items (default: disabled) + * enable with caution * + + -o, --oid=STRING OID tree root to export + + -v, --snmpver=1|2|3 SNMP version (default: 1) + -p, --port=PORT SNMP UDP port number (default: 161) + +SNMP Version 1 or 2c specific + + -c, --community=STRING SNMP community string (default: 'public') + +SNMP Version 3 specific + + -L, --level=LEVEL security level (noAuthNoPriv|authNoPriv|authPriv) + -n, --context=CONTEXT context name + -u, --username=USERNAME security name + -a, --auth=PROTOCOL authentication protocol (MD5|SHA) + -A, --authpass=PASSPHRASE authentication protocol passphrase + -x, --privacy=PROTOCOL privacy protocol (DES|AES) + -X, --privpass=PASSPHRASE privacy passphrase + + --check-delay=SECONDS check interval in seconds (default: 300) + --disc-delay=SECONDS discovery interval in seconds (default: 86400) + --history=DAYS history retention in days (default: 365) + --trends=DAYS trends retention in days (default: 3650) + + -h, --help print this message + +=head1 DESCRIPTION + +B will export a loaded MIB tree into a Zabbix Template starting +from the OID root specified. + +Requires: Perl v5+, Pod::Usage, XML::Simple, Net-SNMP + +=head1 NOTES + +Value mappings query: +SELECT * FROM valuemaps JOIN mappings ON mappings.valuemapid = valuemaps.valuemapid + +Delete Value Maps and linked mappings: +DELETE FROM valuemaps USING mappings WHERE mappings.valuemapid = valuemaps.valuemapid AND valuemaps.name LIKE 'SEARCH%'; + +=head1 BUGS + +- Table indexes not found in iso.org.dod.internet.mgmt.mib-2.host (.1.3.6.1.2.1.25) + +=head1 AUTHOR + +Ryan Armstrong + +=head1 SEE ALSO + +Guidelines for Authors and Reviewers of MIB Documents +https://www.ietf.org/rfc/rfc4181.txt + +Next Generation Structure of Management Information (SMIng) Mappings to the Simple Network Management Protocol (SNMP) +https://tools.ietf.org/html/rfc3781 + +SNMP Table Basics +http://www.webnms.com/snmp/help/snmpapi/snmpv3/table_handling/snmptables_basics.html + +=head1 SUBROUTINES + +=cut + +use strict; +use warnings; + +use Cwd 'abs_path'; +use Data::Dumper; +use Date::Format; +use DBI; +use Encode qw(decode encode); +use File::Basename; +use Pod::Usage; +use Getopt::Long; +use SNMP; +use XML::Simple; + +# Get path info as constants +use constant SCRIPT_NAME => basename($0); +use constant BASE_PATH => dirname(abs_path($0)); + +use constant ZBX_SERVER_CONF => '/etc/zabbix/zabbix_server.conf'; +use constant ZBX_WEB_CONF => '/usr/share/zabbix/conf/zabbix.conf.php'; + +# For Zabbix type constants see: +# https://www.zabbix.com/documentation/2.2/manual/api/reference/item/object +# Zabbix Item status +use constant ZBX_ITEM_ENABLED => 0; +use constant ZBX_ITEM_DISABLED => 1; + +# Zabbix Item Type IDs +use constant ZBX_ITEM_TYPE_SNMPV1 => 1; +use constant ZBX_ITEM_TYPE_SNMPV2 => 4; +use constant ZBX_ITEM_TYPE_SNMPV3 => 6; +use constant ZBX_ITEM_TYPE_SNMPTRAP => 17; + +# Zabbix Item Value Type IDs +use constant ZBX_VAL_TYPE_FLOAT => 0; +use constant ZBX_VAL_TYPE_CHAR => 1; +use constant ZBX_VAL_TYPE_LOG => 2; +use constant ZBX_VAL_TYPE_UINT => 3; +use constant ZBX_VAL_TYPE_TEXT => 4; + +# Zabbix Item Storage types (delta) +use constant ZBX_ITEM_STORE_ASIS => 0; # Store value as is +use constant ZBX_ITEM_STORE_SPEED => 1; # Delta, speed per second +use constant ZBX_ITEM_STORE_CHANGE => 2; # Delta, simple change + +# Zabbix Item SNMPv3 constants +use constant ZBX_V3_PRIV_DES => 0; +use constant ZBX_V3_PRIV_AES => 1; +use constant ZBX_V3_AUTH_MD5 => 0; +use constant ZBX_V3_AUTH_SHA => 1; +use constant ZBX_V3_SEC_NOAUTHNOPRIV => 0; +use constant ZBX_V3_SEC_AUTHNOPRIV => 1; +use constant ZBX_V3_SEC_AUTHPRIV => 2; + +# SNMP Type -> Zabbix type mapping +my $type_map = { + BITS => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type + COUNTER => ZBX_VAL_TYPE_UINT, # Zabbix 'Numeric Unsigned' value type for an unsigned integer + COUNTER32 => ZBX_VAL_TYPE_UINT, # Zabbix 'Numeric Unsigned' value type for an unsigned integer + COUNTER64 => ZBX_VAL_TYPE_UINT, # Zabbix 'Numeric Unsigned' value type for an unsigned integer + GAUGE => ZBX_VAL_TYPE_UINT, # Zabbix 'Numeric Unsigned' value type for an unsigned integer + GAUGE32 => ZBX_VAL_TYPE_UINT, # Zabbix 'Numeric Unsigned' value type for an unsigned integer + INTEGER => ZBX_VAL_TYPE_FLOAT, # Zabbix 'Numeric Float' value type for an signed integer + INTEGER32 => ZBX_VAL_TYPE_FLOAT, # Zabbix 'Numeric Float' value type for an signed 32 bit integer + IPADDR => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type for an IP address + NETADDDR => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type for a network address + NOTIF => ZBX_ITEM_TYPE_SNMPTRAP, # Zabbix 'SNMP Trap' item type + 'TRAP' => ZBX_ITEM_TYPE_SNMPTRAP, # Zabbix 'SNMP Trap' item type + OBJECTID => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type for an OID + OCTETSTR => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type + OPAQUE => ZBX_VAL_TYPE_TEXT, # Zabbix 'Text' value type + TICKS => ZBX_VAL_TYPE_UINT, # Zabbix 'Numeric Unsigned' for a Module 232 timestamp + UNSIGNED32 => ZBX_VAL_TYPE_UINT # Zabbix 'Numeric Unsigned' value type for an unsigned 32bit integer +}; + +# SNMP Version -> Zabbix item type mapping +my $snmpver_map = { + 1 => ZBX_ITEM_TYPE_SNMPV1, # Zabbix SNMPv1 Agent type for SNMPv1 + 2 => ZBX_ITEM_TYPE_SNMPV2, # Zabbix SNMPv2 Agent type for SNMPv2 + 3 => ZBX_ITEM_TYPE_SNMPV3 # Zabbix SNMPv3 Agent type for SNMPv3 +}; + +# SNMP Auth config -> Zabbix item auth config +my $snmpv3_auth_level_map = { + 'noAuthNoPriv' => ZBX_V3_SEC_NOAUTHNOPRIV, + 'authNoPriv' => ZBX_V3_SEC_AUTHNOPRIV, + 'authPriv' => ZBX_V3_SEC_AUTHPRIV +}; + +my $snmpv3_auth_protocol_map = { + 'md5' => ZBX_V3_AUTH_MD5, + 'sha' => ZBX_V3_AUTH_SHA +}; + +my $snmpv3_sec_protocol_map = { + 'des' => ZBX_V3_PRIV_DES, + 'aes' => ZBX_V3_PRIV_AES +}; + +# Default command line options +my $opts = { + delay => 300, # 5 minute check interval + disc_delay => 3600, # Hourly discovery + enableitems => 0, # Disable items + group => 'Templates', + history => 365, + trends => 3650, + list => 0, + maxdepth => -1, + oid => '.1', + use_macros => 0, + snmpcomm => 'public', + snmpport => 161, + snmpver => 1, + v3auth_level => 'noAuthNoPriv', + v3context => '', + v3user => '', + v3auth_protocol => 'md5', + v3auth_pass => '', + v3sec_protocol => 'des', + v3sec_pass => '' +}; + +# Get command line options +Getopt::Long::Configure ("posix_default", "bundling"); +GetOptions( + 'export-maps' => \$opts->{ export_maps }, # Export value maps to database + + 't|template' => \$opts->{ template }, # Output an XML Template + 'f|filename=s' => \$opts->{ filename }, # Filename to output + + 'N|name=s' => \$opts->{ name }, # Template name + 'G|group=s' => \$opts->{ group }, # Template group + 'o|oid=s' => \$opts->{ oid }, # Root OID to export + + 'e|enable-items' => \$opts->{ enableitems }, # Enable template items + + 'v|snmpver=i' => \$opts->{ snmpver }, # SNMP Version + 'p|port=i' => \$opts->{ snmpport }, # SNMP Port + + 'c|community=s' => \$opts->{ snmpcomm }, # SNMP Community string + + 'L|level=s' => \$opts->{ v3auth_level }, # SNMPv3 Authentication level + 'n|context=s' => \$opts->{ v3context }, # SNMPv3 Security Context + 'u|username=s' => \$opts->{ v3user }, # SNMPv3 Authentication username + 'a|auth=s' => \$opts->{ v3auth_protocol }, # SNMPv3 Authentication protocol + 'A|authpass=s' => \$opts->{ v3auth_pass }, # SNMPv3 Authentication passphrase + 'x|privacy=s' => \$opts->{ v3sec_protocol }, # SNMPv3 Privacy protocol + 'X|privpass=s' => \$opts->{ v2sec_pass}, # SNMPv3 Privacy passphrase + + 'check-delay=i' => \$opts->{ delay }, # Update interval in seconds + 'disc-delay=i' => \$opts->{ disc_delay }, # Update interval in seconds + 'history=i' => \$opts->{ history }, # History retention in days + 'trends=i' => \$opts->{ trends }, # Trends retention in days + + 'h|help' => \$opts->{ help } +) || pod2usage(); + +# Print usage if requested +pod2usage({ -exitval => 0 }) if ($opts->{ help }); + +# Validate SNMPv3 settings +if ($opts->{ snmpver } == 3) { + $opts->{ snmpcomm } = ''; + + die("Unknown authentication level '$opts->{ v3auth_level }'") unless defined($snmpv3_auth_level_map->{ $opts->{ v3auth_level } }); + $opts->{ v3auth_level } = $snmpv3_auth_level_map->{ $opts->{ v3auth_level } }; + + die("Unknown authentical protocol '$opts->{ v3auth_protocol }'") unless defined($snmpv3_auth_protocol_map->{ $opts->{ v3auth_protocol } }); + $opts->{ v3auth_protocol } = $snmpv3_auth_protocol_map->{ $opts->{ v3auth_protocol } }; + + die("Unknown privacy protocol '$opts->{ v3sec_protocol }'") unless defined ($snmpv3_sec_protocol_map->{ $opts->{ v3sec_protocol } }); + $opts->{ v3sec_protocol } = $snmpv3_sec_protocol_map->{ $opts->{ v3sec_protocol } }; +} + +# Base template for Template Items, Discovery Rules and Item Prototypes +# See: https://www.zabbix.com/documentation/2.2/manual/api/reference/item/object +my %item_base_template = ( + allowed_hosts => '', + applications => [], + authtype => '0', + delay_flex => '', + ipmi_sensor => '', + params => '', + password => '', + port => '{$SNMP_PORT}', # Use macro for SNMP UDP Port + privatekey => '', + publickey => '', + snmp_community => $opts->{ snmpver } < 3 ? '{$SNMP_COMM}' : '', # Use macro for SNMP Community string + snmpv3_authpassphrase => $opts->{ snmpver } == 3 ? '{$SNMP_AUTHPASS}' : '', # Use macro for SNMPv3 Authentication passphrase + snmpv3_authprotocol => $opts->{ snmpver } == 3 ? $opts->{ v3auth_protocol } : '0', + snmpv3_contextname => $opts->{ snmpver } == 3 ? '{$SNMP_CONTEXT}' : '', # Use macro for SNMPv3 context name + snmpv3_privpassphrase => $opts->{ snmpver } == 3 ? '{$SNMP_PRIVPASS}' : '', # Use macro for SNMPv3 Privacy passphrase + snmpv3_privprotocol => $opts->{ snmpver } == 3 ? $opts->{ v3sec_protocol } : '0', + snmpv3_securitylevel => $opts->{ snmpver } == 3 ? $opts->{ v3auth_level } : '0', + snmpv3_securityname => $opts->{ snmpver } == 3 ? '{$SNMP_USER}' : '', # Use macro for SNMPv3 Username + status => ($opts->{ enableitems } ? ZBX_ITEM_ENABLED : ZBX_ITEM_DISABLED), # Enabled (0) | Disabled (1) + username => '', +); + +# Item template for standard Template items +my %item_template = ( + data_type => '0', + delay => $opts->{ delay }, # Update internal seconds + delta => '0', # Change delta + formula => '1', # Multiplier factor + history => $opts->{ history }, # History retention in days + inventory_link => '0', + multiplier => '0', # Enable multiplier + trends => $opts->{ trends }, # Trends retention in days + units => '', + valuemap => '' +); +%item_template = (%item_base_template, %item_template); + +# Discovery rule template +my %disc_rule_template = ( + delay => $opts->{ disc_delay }, + filter => ':', + lifetime => '30', + + # The following items must be created as unique refs for each item + host_prototypes => [], + item_prototypes => [], + graph_prototypes => [], + trigger_prototypes => [] +); +%disc_rule_template = (%item_base_template, %disc_rule_template); + +# SNMP Trap template +my %trap_template = ( + allowed_hosts => '', + applications => [], + authtype => 0, + data_type => 0, + delay => '0', + delay_flex => '', + delta => 0, + description => '', + formula => 1, + history => $opts->{ history }, + inventory_link => 0, + ipmi_sensor => '', + logtimefmt => 'hh:mm:ss dd/MM/yyyy', + multiplier => '0', + params => '', + password => '', + port => '', + privatekey => '', + publickey => '', + snmp_community => '', + snmp_oid => '', + snmpv3_authpassphrase => '', + snmpv3_authprotocol => 0, + snmpv3_contextname => '', + snmpv3_privpassphrase => '', + snmpv3_privprotocol => 0, + snmpv3_securitylevel => 0, + snmpv3_securityname => '', + status => ($opts->{ enableitems } ? ZBX_ITEM_ENABLED : ZBX_ITEM_DISABLED), + trends => $opts->{ trends }, + type => ZBX_ITEM_TYPE_SNMPTRAP, + units => '', + username => '', + value_type => ZBX_VAL_TYPE_LOG, + valuemap => '' +); + +# Item prototype template +my %item_proto_template = (); +%item_proto_template = (%item_template, %item_proto_template); + +# Global value maps array +my $valuemaps = {}; + +=head2 utf8_santize + +Parameters : (string) $malformed_utf8 +Returns : (string) $wellformed_utf8 +Description : Returns a sanitized UTF8 string, removing incompatable characters + +=cut +sub utf8_sanitize { + my ($malformed_utf8) = @_; + + my $octets = decode('UTF-8', $malformed_utf8, Encode::FB_DEFAULT); + return encode('UTF-8', $octets, Encode::FB_CROAK); +} + +=head2 oid_path + +Parameters : SNMP::MIB::Node $oid +Returns : (String) $oid_path +Description : Returns the fully qualified textual path of a MIB node by + traversing the node's parents. + +=cut +sub oid_path { + my ($oid) = @_; + + my $path = $oid->{ label }; + my $node = $oid; + while ($node = $node->{ parent }) { + $path = "$node->{ label }.$path"; + } + + return $path; +} + +=head2 get_child_by_label + +Parameters : SNMP::MIB::Node $node + (string) $child_label +Returns : SNMP::MIB::NODE $child +Description : Returns the direct descendant OID of the specified OID + if the label name matches, otherwise undef. + +=cut +sub get_child_by_label { + my ($node, $child_label) = @_; + + if ($node && $child_label) { + foreach my $child(@{ $node->{ children } }) { + if ($child->{ label } eq $child_label) { + return $child; + } + } + } + + return undef; +} + +=head2 node_to_item + +Parameters : SNMP::MIB::Node $node + (Hash) $template +Returns : (Hash) $item +Description : Returns a Zabbix Item hash derived from the specified MIB OID + +=cut +sub node_to_item { + my ($node, $template) = @_; + $template = $template || \%item_template; + + # Create item hash + my $item = { %{ $template } }; + + $item->{ name } = $node->{ label }; + $item->{ snmp_oid } = $node->{ objectID }; + if ($node->{ units }) { + $item->{ units } = $node->{ units }; + } + + # Merge in item defaults + %{ $item } = (%{ $template }, %{ $item } ); + + # Create SNMP Agent item + $item->{ type } = $snmpver_map->{ $opts->{ snmpver } }; + + # Item key + $item->{ key } = "$node->{ moduleID }.$node->{ label }"; + + # Map value type (Ignore for OID Table Entry Rows) + if ($node->{ type }) { + $item->{ value_type } = $type_map->{ $node->{ type } }; + if (!defined($item->{ value_type })) { + print STDERR "No type mapping found for type $node->{ type } in $node->{ objectID }\n"; + } + } + + # Set storage type + if ( $node->{ type } ~~ ['COUNTER', 'COUNTER32', 'COUNTER64']) { + $item->{ delta } = ZBX_ITEM_STORE_SPEED; + $item->{ units } .= '/sec'; + } + + # Translate SNMP Ticks + if ($node->{ type } eq 'TICKS') { + $item->{ multiplier } = '1'; + $item->{ formula } = '.01'; + $item->{ units } = 'uptime'; + } + + # Parse item desciption + $item->{ description } = utf8_sanitize($node->{ description }); + if ($item->{ description }) { + $item->{ description } =~ s/^\s+|\s+$|\n//g; # Trim left/right whitespace and newlines + $item->{ description } =~ s/\s{2,}/ /g; # Remove padding + } + + # Process value maps + if (scalar keys % {$node->{ enums } }) { + # Add valuemap to global list + my $map_name = "$node->{ moduleID }::$node->{ label }"; + + # If the map_name is longer than 64 characters... + if ( length($map_name) > 64 ) { + + # Truncate to 64 characters to match maximum database field length. + $map_name = substr($map_name,0,61) . "..."; + + } + + + $valuemaps->{ $map_name } = $node->{ enums }; + + # Assign value map to template + $item->{ valuemap } = { name => $map_name }; + } + + return $item; +} + +=head2 node_to_trapitem + +Parameters : SNMP::MIB::Node $node + (Hash) $template +Returns : (Hash) $item +Description : Returns a Zabbix SNMP Trap Item hash derived from the + specified MIB OID + +=cut +sub node_to_trapitem { + my ($node, $template) = @_; + $template = $template || \%trap_template; + + # Create item hash + my $item = { %{ $template } }; + + $item->{ name } = "SNMP Trap: $node->{ moduleID }::$node->{ label }"; + + # Merge in item defaults + %{ $item } = (%{ $template }, %{ $item } ); + + # Create trap key + my $oid = $node->{ objectID }; + $oid =~ s/\./\\./g; + $item->{ key } = "snmptrap[\"\\s$oid\\s\"]"; + + # Parse item desciption + my $desc = ''; + if ($node->{ description }) { + $desc = $node->{ description }; + $desc =~ s/^\s+|\s+$|\n//g; # Trim left/right whitespace and newlines + $desc =~ s/\s{2,}/ /g; # Remove padding + } + + # Append varbinds to description + if (defined($node->{ varbinds }) && scalar @{ $node->{ varbinds } }) { + my $varcount = scalar @{ $node->{ varbinds } }; + + if ($desc ne '') { + $desc .= "\n\n"; + } + + $desc .= "Varbinds:\n"; + + for(my $i = 0; $i < $varcount; $i++) { + my $varbind_label = $node->{ varbinds }[$i]; + $desc .= "$i. $varbind_label"; + + # Try to find OID for each varbind + my $varbind_path = "$node->{ moduleID }::$varbind_label"; + my $varbind = $SNMP::MIB{ $varbind_path }; + + if (defined($varbind)) { + $desc .= " ($varbind->{ type })\n" if $varbind->{ type }; + + if ($varbind->{ description }) { + my $vbdesc = $varbind->{ description }; + $vbdesc =~ s/[ \t]+/ /g; # Replace long whitespace with single space + $vbdesc =~ s/^ ?/ /mg; # Prepend indent to each description line + $desc .= "$vbdesc\n\n"; + } + } else { + $desc .= "\n"; + } + } + } + $item->{ description } = $desc; + + return $item; +} + +=head2 node_is_current + +Parameters : SNMP::MIB::Node $node +Returns : (int) 0|1 +Description : Returns true if the specified OID is not obsolete + +=cut +sub node_is_current { + my ($node) = @_; + + return ( + node_is_valid_trap($node) + || (defined($node->{ status }) && $node->{ status } ne 'Obsolete') + ); +} + +=head2 node_is_valid_scalar + +Parameters : SNMP::MIB::Node $node +Returns : (int) 0|1 +Description : Returns true if the specified OID is current, readable and + defines a valid value type. + +=cut +sub node_is_valid_scalar { + my ($node) = @_; + + return ( + node_is_current($node) + && $node->{ type } + && ( + $node->{ type } eq 'NOTIF' || $node->{ type } eq 'TRAP' + || ($node->{ access } eq 'ReadOnly' || $node->{ access } eq 'ReadWrite') + ) + + ); +} + +=head2 node_is_valid_trap + +Parameters : SNMP::MIB::Node $node +Returns : (int) 0|1 +Description : Returns true if the specified OID is an SNMP Trap + +=cut +sub node_is_valid_trap { + my ($node) = @_; + + return ( + defined($node->{ type }) && ($node->{ type } eq 'NOTIF' || $node->{ type } eq 'TRAP') + ); +} + +=head2 node_is_valid_table + +Parameters : SNMP::MIB::Node $node +Returns : (int) 0|1 +Description : Returns true if the specified OID is a valid table which is + current, readable and contains a single child (row + definition) + +=cut +sub node_is_valid_table { + my ($node) = @_; + + # The MIB will define a 'SEQUENCE OF' attribute for tables but + # SNMP::MIB::NODE does not expose this value. Instead, a table + # node must be 'NoAccess' and have a single 'NoAccess' child + return ( + node_is_current($node) + + # Table is NoAccess + && $node->{ access } eq 'NoAccess' + + # Table has one child (row definition) + && (scalar @{ $node->{ children } }) == 1 + + # Table row is NoAccess + && $node->{ children }[0]->{ access } eq 'NoAccess' + + # Table row defines atleast one index + && (scalar @{ $node->{ children }[0]->{ indexes } }) + ); +} + +=head2 build_template + +Parameters : (hash) $template + SNMP::MIB::NODE $node +Returns : (void) +Description : Traverses a loaded MIB tree from the specified OID node + a populates a Zabbix Template hash with items, discovery + rules, item prototypes, groups and macros. + +=cut +sub build_template { + my ($template, $node) = @_; + + # Ignore obsolete OIDs + if (node_is_current($node)) { + # Create an Item Application name for this node + my $appname = "$node->{ moduleID }::$node->{ parent }->{ label }"; + + # Is this a scalar value OID? + if (node_is_valid_trap($node)) { + # Convert the SNMP::MIB::Node to a Zabbix Template SNMP Trap Item + my $item = node_to_trapitem($node); + + # Add item applications to template application list + $item->{ applications } = [{ name => $appname }]; + $template->{ apptags }->{ $appname } = 1; + + # Add item to template + push(@{ $template->{ items } }, $item ); + + # If the snmptrap has children. + foreach(@{ $node->{ children } }) { + + # Convert the SNMP::MIB::Node to a Zabbix Template SNMP Trap Item + my $item = node_to_trapitem($_); + + # Add item applications to template application list + $item->{ applications } = [{ name => $appname }]; + $template->{ apptags }->{ $appname } = 1; + + # Add item to template + push(@{ $template->{ items } }, $item ); + + } + + } elsif (node_is_valid_scalar($node)) { + + # Convert the SNMP::MIB::Node to a Zabbix Template Item hash + my $item = node_to_item($node); + + # Append '.0' to normal SNMP OIDS + $item->{ snmp_oid } = "$item->{ snmp_oid }.0"; + + # Add item applications to template application list + $item->{ applications } = [{ name => $appname }]; + $template->{ apptags }->{ $appname } = 1; + + # Add item to template + push(@{ $template->{ items } }, $item ); + + } elsif (node_is_valid_table($node)) { + # Get row OID + my $table = $node; + my $row = $node->{ children }[0]; + + # Validate naming standard + if ($table->{ label } !~ /Table/) { + print STDERR "Warning: $table->{ moduleID }:: $table->{ label } appears to be a table but does not have the 'Table' suffix\n"; + } + + if ($row->{ label } !~ /Entry/) { + print STDERR "Warning: $row->{ moduleID }:: $row->{ label } appears to be a table entry but does not have the 'Entry; suffix\n"; + } + + # This is a table. Build a discovery rule + my $disc_rule = {}; + $disc_rule = node_to_item($row, \%disc_rule_template); + + # Update fields + $disc_rule->{ key } = "$table->{ moduleID }.$disc_rule->{ name }"; + $disc_rule->{ name } = "$disc_rule->{ name } Discovery"; + + # Fetch an arbitrary column OID for Zabbix to use for discovery + my $index_oid = $row->{ children }[0]; + if (!defined($index_oid)) { + print STDERR "No index found for table $table->{ moduleID}::$table->{ label } ($table->{ objectID })\n"; + } else { + # Append key ID to path + $disc_rule->{ snmp_oid } = "$index_oid->{ objectID }"; + + # Remove unrequired fields + delete($disc_rule->{ applications }); + delete($disc_rule->{ data_type }); + + # Create new array for prototypes + $disc_rule->{ item_prototypes } = []; + + # Add prototypes for each row column + foreach my $column(@{ $row->{ children } }) { + if (node_is_valid_scalar($column)) { + if (my $proto = node_to_item($column, \%item_proto_template)) { + $proto->{ name } = "$proto->{ name }.{#SNMPINDEX}"; + $proto->{ key } = "$disc_rule->{ key }.$column->{ label }\[{#SNMPINDEX}]"; + $proto->{ snmp_oid } = "$proto->{ snmp_oid }.{#SNMPINDEX}"; + + # Add item applications to template application list + $proto->{ applications } = [{ name => $appname }]; + $template->{ apptags }->{ $appname } = 1; + + push(@{ $disc_rule->{ item_prototypes } }, $proto); + } + } + } + + # Add discovery rule to template + push(@{ $template->{ discovery_rules } }, $disc_rule); + } + } + } else { + # Parse children + foreach(@{ $node->{ children } }) { + build_template($template, $_); + } + } +} + +sub export_valuemaps { + my ($oid_root) = @_; + + # Parse MIBs to get a hash of required value maps + my $template = {}; + build_template($template, $oid_root, 0); + + # Fetch DB connection details from Zabbix server config + my ($dbhost, $dbname, $dbschema, $dbport, $dbuser, $dbpassword); + $dbhost = 'localhost'; + $dbport = 5432; + $dbname = 'zabbix'; + $dbpassword = ''; + if ( -e ZBX_SERVER_CONF ) { + open (FH, "<${\ZBX_SERVER_CONF}") or die ("Failed to open ${\ZBX_SERVER_CONF} for reading."); + while (my $line = ) { + my ($key, $val) = ($line =~ m/^(\w*)=(.*)/); + if ($key ) { + if ($key eq 'DBHost') { + $dbhost = $val; + } elsif ($key eq 'DBName') { + $dbname = $val; + } elsif ($key eq 'DBSchema') { + $dbschema = $val; + } elsif ($key eq 'DBPort') { + $dbport = $val; + } elsif ($key eq 'DBUser') { + $dbuser = $val; + } elsif ($key eq 'DBPassword') { + $dbpassword = "$val"; + } + } + } + close(FH); + } elsif( -e ZBX_WEB_CONF ) { + + } else { + die("No database connection details could be obtained."); + } + + # Create a connection string and connect + my $dsn = "DBI:Pg:host=$dbhost;dbname=$dbname;port=$dbport;"; + my $dbh = DBI->connect($dsn, $dbuser, $dbpassword); + $dbh || die($DBI::errstr); + + my $results; + + # Get highest valuemap id + $results = query($dbh, 'SELECT MAX(valuemapid) AS valuemapid FROM valuemaps'); + my $valuemapid = @{ $results }[0]->{ valuemapid }; + + # Get highest map id + $results = query($dbh, 'SELECT MAX(mappingid) AS mappingid FROM mappings'); + my $mappingid = @{ $results }[0]->{ mappingid }; + + # Get existings valuemaps + $results = query($dbh, 'SELECT * FROM valuemaps'); + my $existing_maps = {}; + foreach my $valuemap(@{ $results }) { + $existing_maps->{ $valuemap->{ name } } = $valuemap->{ valuemapid }; + } + + # Process each map + foreach my $map_name (keys %{ $valuemaps }) { + + if (defined($existing_maps->{ $map_name })) { + print STDERR "Value map '$map_name' already exists\n"; + next; + } + + # Insert new map + $valuemapid++; + insert($dbh, "INSERT INTO valuemaps (valuemapid, name) VALUES ($valuemapid, '$map_name')"); + print ("Added Value Map '$map_name' with ID $valuemapid\n"); + + # Add mappings + foreach my $key (keys %{ $valuemaps->{ $map_name } }) { + my $val = $valuemaps->{ $map_name }->{ $key }; + + $mappingid++; + insert($dbh, "INSERT INTO mappings (mappingid, valuemapid, value, newvalue) VALUES ($mappingid, $valuemapid, $val, '$key');"); + print (" - Added mapping '$key' => '$val' with ID $mappingid\n"); + } + } + + # Increment ids for nextid. + $valuemapid++; + $mappingid++; + + # Now update the tables max ids in the ids table. + insert($dbh, "UPDATE ids SET nextid = $valuemapid WHERE table_name = 'valuemaps' AND field_name = 'valuemapid';"); + insert($dbh, "UPDATE ids SET nextid = $mappingid WHERE table_name = 'mappings' AND field_name = 'mappingid';"); + + + + # Clean up + $dbh->disconnect; +} + +sub query { + my ($dbh, $sql) = @_; + my @results; + my $sth = $dbh->prepare($sql); + $sth->execute(); + while(my $row = $sth->fetchrow_hashref()) { + push @results, $row; + } + + return \@results; +} + +sub insert { + my ($dbh, $sql) = @_; + my $sth = $dbh->prepare($sql); + $sth->execute(); +} + +# Initialize net-snmp +$SNMP::save_descriptions = 1; +SNMP::initMib(); + +# Verify the specified OID exists +my $oid_root = $SNMP::MIB{ $opts->{ oid } }; +if (!$oid_root || $oid_root->{ objectID } ne $opts->{ oid }) { + print STDERR "OID $opts->{ oid } not found in MIB tree.\n"; + exit 1; +} + +# Export value maps directly to Zabbix database +if($opts->{ export_maps }) { + export_valuemaps($oid_root); +} + +# Build a Zabbix template +elsif ($opts->{ template }) { + my $suffix = $opts->{ snmpver } > 2 ? " v$opts->{ snmpver }" : ''; + my $template_name = $opts->{ name } || "Template $oid_root->{ moduleID } - $oid_root->{ label }$suffix"; + my $template = { + name => $template_name, + template => $template_name, + apptags => {}, + applications => [], + discovery_rules => [], + groups => [{ + name => $opts->{ group } + }], + items => [], + macros => [ + { macro => '{$OID}', value => "$oid_root->{ objectID }" }, + { macro => '{$OID_PATH}', value => oid_path($oid_root) }, + { macro => '{$OID_MOD}', value => $oid_root->{ moduleID } }, + { macro => '{$SNMP_PORT}', value => $opts->{ snmpport } } + ] + }; + + # Add SNMP connection macros + if($opts->{ snmpver } < 3) { + push(@{ $template->{ macros } }, { macro => '{$SNMP_COMM}', value => $opts->{ snmpcomm } }); + } elsif($opts->{ snmpver } == 3) { + push(@{ $template->{ macros } }, { macro => '{$SNMP_USER}', value => $opts->{ v3user } }); + push(@{ $template->{ macros } }, { macro => '{$SNMP_CONTEXT}', value => $opts->{ v3context } }); + push(@{ $template->{ macros } }, { macro => '{$SNMP_AUTHPASS}', value => $opts->{ v3auth_pass } }); + push(@{ $template->{ macros } }, { macro => '{$SNMP_PRIVPASS}', value => $opts->{ v3sec_pass } }); + }; + build_template($template, $oid_root, 0); + + # Convert applications hash to array + @{ $template->{ applications } } = map { { name => $_ } } keys %{ $template->{ apptags } }; + delete($template->{ apptags }); + + # Build XML document + my $time = time(); + my $output = { + version => '2.0', + date => time2str("%Y-%m-%dT%H:%M:%SZ", $time), + groups => $template->{ groups }, + templates => [$template], + triggers => [], + graphs => [] + }; + + # Output stream + my $fh = *STDOUT; + if ($opts->{ filename }) { + open($fh, ">$opts->{ filename }") or die "$!"; + } + + # Output XML + XMLout($output, + OutputFile => \$fh, + XMLDecl => "", + RootName => 'zabbix_export', + NoAttr => 1, + SuppressEmpty => undef, + GroupTags => { + 'applications' => 'application', + 'groups' => 'group', + 'templates' => 'template', + 'items' => 'item', + 'macros' => 'macro', + 'discovery_rules' => 'discovery_rule', + 'item_prototypes' => 'item_prototype', + 'trigger_prototypes' => 'trigger_prototype', + 'graph_prototypes' => 'graph_prototype', + 'host_prototypes' => 'host_prototype' + } + ); + + if ($opts->{ filename }) { + close $fh; + } +} + +else { + pod2usage(); +}