summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am1
-rwxr-xr-xscripts/firewall/vyatta-firewall-trap.pl298
2 files changed, 299 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am
index e70c377..212b15a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -10,6 +10,7 @@ sbin_SCRIPTS =
curver_DATA = cfg-version/firewall@5
sbin_SCRIPTS += scripts/firewall/vyatta-firewall.pl
+sbin_SCRIPTS += scripts/firewall/vyatta-firewall-trap.pl
sbin_SCRIPTS += scripts/firewall/firewall.init
sbin_SCRIPTS += scripts/firewall/vyatta-ipset.pl
sbin_SCRIPTS += scripts/firewall/vyatta-fw-global-state-policy.pl
diff --git a/scripts/firewall/vyatta-firewall-trap.pl b/scripts/firewall/vyatta-firewall-trap.pl
new file mode 100755
index 0000000..5a19f7d
--- /dev/null
+++ b/scripts/firewall/vyatta-firewall-trap.pl
@@ -0,0 +1,298 @@
+#!/usr/bin/perl
+#
+# Module: vyatta-firewall-trap.pl
+# Description: Generate SNMP traps when firewall config changes
+#
+# **** License ****
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# This code was originally developed by Vyatta, Inc.
+# Portions created by Vyatta are Copyright (C) 2013 Vyatta, Inc.
+# All Rights Reserved.
+#
+# **** End License ****
+#
+
+use lib "/opt/vyatta/share/perl5";
+use warnings;
+use strict;
+use English;
+use Vyatta::Config;
+use Getopt::Long;
+use Sys::Syslog qw(:standard :macros);
+use SNMP;
+
+# Maps node status to OID value
+my %change_type_hash = ( 'unknown' => 0,
+ 'added' => 1,
+ 'deleted' => 2,
+ 'changed' => 3 );
+
+# Array of Net::SNMP::TrapSession to send traps
+my @snmp_sessions;
+
+# Enable printing debug output to stdout.
+my $debug_flag = 0;
+
+# Enable sending debug output to syslog.
+my $syslog_flag = 1;
+
+my $cfglevel;
+
+GetOptions("level=s" => \$cfglevel,
+ "debug" => \$debug_flag,
+ "syslog" => \$syslog_flag
+);
+
+openlog("firewall-trap", "pid", "user") if $syslog_flag;
+
+# If debugging, lets see more SNMP outpyt
+$SNMP::debugging = $debug_flag;
+
+sub log_msg {
+ my $message = shift;
+
+ chomp($message);
+ print "DEBUG: $message\n" if $debug_flag;
+ syslog(LOG_NOTICE, "%s", $message) if $syslog_flag;
+}
+
+sub log_err {
+ my $message = shift;
+
+ chomp($message);
+ print "DEBUG: $message\n" if $debug_flag;
+ syslog(LOG_ERR, "%s", $message) if $syslog_flag;
+}
+
+# Initializes an SNMP session for each configured trap-target
+#
+# Returns:
+# undef if no trap-targets
+# # of trap targets
+sub snmp_init {
+ my ( $config ) = @_;
+ my $trap_session;
+ my @trap_targets;
+ my $level_pfx = "service snmp trap-target";
+
+ @trap_targets = $config->listNodes($level_pfx);
+ return unless @trap_targets;
+
+ foreach my $trap_target (@trap_targets) {
+ my $port = $config->returnValue("$level_pfx $trap_target port");
+ my $community
+ = $config->returnValue("$level_pfx $trap_target community");
+
+ $trap_target .= ":$port" if $port;
+ $community = "public" unless $community;
+
+ my ($snmp_session, $error) = new SNMP::TrapSession(
+ DestHost => "$trap_target",
+ Community => $community,
+ Version => '2c'
+ );
+ if (!defined $snmp_session) {
+ log_err "Unable to open trap session for $trap_target community";
+ } else {
+ push(@snmp_sessions, $snmp_session);
+ }
+ }
+ return scalar (@trap_targets);
+}
+
+sub trap_send {
+ my ($change_type, $prev, $curr) = @_;
+ my $trap_name = 'VYATTA-TRAP-MIB::mgmtEventTrap';
+ my $mgmtEventUser = 'mgmtEventUser';
+ my $mgmtEventSource = 'mgmtEventSource';
+ my $mgmtEventType = 'mgmtEventType';
+ my $mgmtEventPrevCfg = 'mgmtEventPrevCfg';
+ my $mgmtEventCurrCfg = 'mgmtEventCurrCfg';
+ my $event_source = 1; # firewall
+ my $event_name = getpwuid($UID) . "($UID)";
+
+ $change_type = $change_type_hash{$change_type};
+ $change_type = 0 unless $change_type;
+ log_msg "trap_send: user = $event_name";
+ log_msg "trap_send: mgmtEventType = $change_type";
+ log_msg "trap_send: mgmtEventPrevCfg = $prev" if $prev;
+ log_msg "trap_send: mgmtEventCurrCfg = $curr" if $curr;
+
+ foreach my $snmp_session (@snmp_sessions) {
+ # uptime is auto-populated if not explicitly set
+ # trap does not like empty strings so only send oids with values
+ if ($change_type == $change_type_hash{'added'}) {
+ $snmp_session->trap(
+ oid => $trap_name,
+ [[$mgmtEventUser, 0, $event_name],
+ [$mgmtEventSource, 0, $event_source],
+ [$mgmtEventType, 0, $change_type],
+ [$mgmtEventCurrCfg, 0, $curr]]);
+ } elsif ($change_type == $change_type_hash{'deleted'}) {
+ $snmp_session->trap(
+ oid => $trap_name,
+ [[$mgmtEventUser, 0, $event_name],
+ [$mgmtEventSource, 0, $event_source],
+ [$mgmtEventType, 0, $change_type],
+ [$mgmtEventPrevCfg, 0, $prev]]);
+ } elsif ($change_type == $change_type_hash{'changed'}) {
+ $snmp_session->trap(
+ oid => $trap_name,
+ [[$mgmtEventUser, 0, $event_name],
+ [$mgmtEventSource, 0, $event_source],
+ [$mgmtEventType, 0, $change_type],
+ [$mgmtEventPrevCfg, 0, $prev],
+ [$mgmtEventCurrCfg, 0, $curr]]);
+ } else {
+ $snmp_session->trap(
+ oid => $trap_name,
+ [[$mgmtEventUser, 0, $event_name],
+ [$mgmtEventSource, 0, $event_source],
+ [$mgmtEventType, 0, $change_type]]);
+ }
+ }
+}
+
+sub leaf_trap {
+ my ($config, $change_type, $level) = @_;
+ my ($prev, $curr);
+
+ if (($change_type eq "deleted") || ($change_type eq "changed")) {
+ $prev = $config->returnOrigValue("$level");
+ chomp($prev);
+ $prev = "$level $prev" if length($prev);
+ }
+
+ if (($change_type eq "added") || ($change_type eq "changed")) {
+ $curr = $config->returnValue("$level");
+ chomp($curr);
+ $curr = "$level $curr" if length($curr);
+ }
+ trap_send($change_type, $prev, $curr);
+}
+
+sub leaf_multi_trap {
+ my ($config, $change_type, $level) = @_;
+ my (@prev, @curr);
+ my ($prevstr, $currstr);
+
+ if (($change_type eq "deleted") || ($change_type eq "changed")) {
+ @prev = $config->returnOrigValues("$level");
+ $prevstr = "$level " . join(' ', @prev);
+ chomp($prevstr);
+ }
+
+ if (($change_type eq "added") || ($change_type eq "changed")) {
+ @curr = $config->returnValues("$level");
+ $currstr = "$level " . join(' ', @curr);
+ chomp($currstr);
+ }
+ trap_send($change_type, $prevstr, $currstr);
+}
+
+sub leaf_valueless_trap {
+ my ($config, $change_type, $level) = @_;
+ my ($prev, $curr);
+
+ chomp($level);
+ $prev = "$level" if ($change_type eq "deleted");
+ $curr = "$level" if ($change_type eq "added");
+ trap_send($change_type, $prev, $curr);
+}
+
+sub firewall_cfg_trap {
+ my ($config, $level) = @_;
+ my %node_status = $config->listNodeStatus("$level");
+
+ foreach my $node (keys %node_status) {
+ next if $node_status{$node} eq 'static';
+ if ($config->isTagNode("$level $node")) {
+ firewall_cfg_trap($config, "$level $node");
+ } elsif ($config->isLeafNode("$level $node")) {
+ if ($config->isMultiNode("$level $node")) {
+ leaf_multi_trap($config, $node_status{$node}, "$level $node");
+ } else {
+ leaf_trap($config, $node_status{$node}, "$level $node");
+ }
+ } elsif ($config->hasTmplChildren("$level $node")) {
+ # Valueless node with children, descend tree recursively
+ firewall_cfg_trap($config, "$level $node");
+ } else {
+ # Valueless leaf node
+ leaf_valueless_trap($config, $node_status{$node}, "$level $node");
+ }
+ }
+}
+
+# Special handling for the top "firewall" config node.
+#
+# Only processes leaf nodes. Ideally the general purpose traversal
+# function (firewall_cfg_trap), could detect crossing priority groups
+# like the cstore code does. But this is easier.
+sub firewall_cfg_leaf_trap {
+ my ($config, $level) = @_;
+ my %node_status = $config->listNodeStatus("$level");
+
+ foreach my $node (keys %node_status) {
+ next if $node_status{$node} eq 'static';
+ if ($config->isLeafNode("$level $node")) {
+ if ($config->isMultiNode("$level $node")) {
+ leaf_multi_trap($config, $node_status{$node}, "$level $node");
+ } else {
+ leaf_trap($config, $node_status{$node}, "$level $node");
+ }
+ } elsif ($config->isTagNode("$level $node")) {
+ next; # Skip tag node
+ } elsif ($config->hasTmplChildren("$level $node")) {
+ next; # Valueless node with children, skip
+ } else {
+ # Valueless leaf node
+ leaf_valueless_trap($config, $node_status{$node}, "$level $node");
+ }
+ }
+}
+
+sub is_trap_enabled {
+ my ($config) = @_;
+ my $level = 'firewall config-trap';
+ my $value;
+
+ if ($config->existsOrig($level)) {
+ $value = $config->returnOrigValue($level);
+ } else {
+ my @tmpl = $config->parseTmpl($level);
+ $value = $tmpl[2]; # default value
+ }
+ return $value eq 'enable';
+}
+
+my $config = new Vyatta::Config;
+
+# Detect if firewall config traps are enabled
+exit 0 if ! is_trap_enabled($config);
+
+# Detect system startup (i.e., no snmpd running) and just exit.
+my $snmpd_service = `/usr/sbin/invoke-rc.d snmpd status 2> /dev/null`;
+exit 0 if (! $snmpd_service =~ m/snmpd is running/);
+
+# If no trap-targets configured just exit.
+exit 0 unless snmp_init($config);
+
+if (defined $cfglevel) {
+ if ($cfglevel eq 'firewall') {
+ # Special handling for the top "firewall" node.
+ firewall_cfg_leaf_trap($config, "$cfglevel");
+ } else {
+ firewall_cfg_trap($config, "$cfglevel");
+ }
+}
+
+exit 0;