ansible-dhcpd/templates/usr/local/lib/dhcp-probe/dhcp_probe_notify2.j2
Maciej Delmanowski 5014dd8642 Add 'dhcp-probe' support
dhcp-probe script is used to scan the network for unauthorized DHCP
servers.
2015-03-30 13:34:53 +02:00

248 lines
9.9 KiB
Django/Jinja
Executable File

#!/usr/bin/perl -w
# {{ ansible_managed }}
# dhcp_probe_notify2 -p calling_prog_name -I interface_name -i IPaddress -m MACaddress [-y yiaddr]
#
# An external program called by dhcp_probe upon response of a response from an unexpected BootP/DHCP server.
#
# Called via specification of 'alert_program_name2' in /etc/dhcp_probe.cf file.
# This version obeys the syntax provided by the 'alert_program_name2' statement,
# not the syntax provided by the older 'alert_program_name' statement.
#
# Required options:
# -p calling_prog_name the name of the calling program (e.g. 'dhcp_probe')
# -I interface_name the name of the interface on which the unexpected response packet arrived (e.g. 'qfe0')
# -i IPaddress the IP source address of the unexpected response packet (e.g. '192.168.0.1')
# -m MACaddress the Ethernet source address of the unexpected response packet (e.g. '0:1:2:3:4:5')
#
# Optional options:
# -y yiaddr the response packet's non-zero yiaddr value, when it falls within a "Lease Network of Interest" (e.g. '172.16.1.2')
#
# May send email subject to throttling.
# May send page subject to throttling.
#
# You will need to edit the definitions below.
#
# Irwin Tillman
use Sys::Hostname;
use Sys::Syslog qw(:DEFAULT setlogsock);
use Time::HiRes qw(gettimeofday); # from CPAN
use Getopt::Std;
use strict;
#############################################################################
#
# Definitions you may need to edit
my $SYSLOG_FACILITY="daemon"; # name of facility to use if syslogging (e.g. 'daemon')
my $SYSLOG_OPT = 'pid,cons'; # comma-separated syslog options to use if syslogging (e.g. 'pid,cons') , ignored if not syslogging
use vars qw($VERBOSE);
$VERBOSE = 1; # set to true to produce more verbose messages
# We may send one piece of mail this way, subject to frequency throttling.
# Use this set of definitions for a piece of email that is delivered as regular email (not a page).
use vars qw($THROTTLE_MAIL_CMD $THROTTLE_MAIL_TIMEOUT $THROTTLE_MAIL_FROM $THROTTLE_MAIL_RECIPIENT $THROTTLE_MAIL_RECIPIENT_TEST $THROTTLE_MAIL_SUBJECT);
{% if dhcpd_probe_mail_to|d() and dhcpd_probe_mail_to %}
$THROTTLE_MAIL_CMD = "{{ ansible_local.root.lib + '/dhcp-probe/mail-throttled' }}"; # set to "" to disable
{% else %}
$THROTTLE_MAIL_CMD = ""; # set to "" to disable
{% endif %}
$THROTTLE_MAIL_TIMEOUT = {{ dhcpd_probe_mail_timeout | default('600') }}; # seconds
$THROTTLE_MAIL_FROM = "root"; # e.g. "root"
$THROTTLE_MAIL_RECIPIENT = "{{ dhcpd_probe_mail_to | join(' ') | replace('@','\@') }}"; # space-separated email addresses, remember to escape '@' characters
$THROTTLE_MAIL_SUBJECT = "Unexpected BOOTP/DHCP server";
# We may also send another piece of email this way, subject to frequency throttling.
# Use this set of definitions for a piece of email that is delivered to a pager (not as regular email).
use vars qw($THROTTLE_PAGE_CMD $THROTTLE_PAGE_TIMEOUT $THROTTLE_PAGE_RECIPIENT);
{% if dhcpd_probe_page_to|d() and dhcpd_probe_page_to %}
$THROTTLE_PAGE_CMD = "{{ ansible_local.root.lib + '/dhcp-probe/mail-throttled' }}"; # set to "" to disable
{% else %}
$THROTTLE_PAGE_CMD = ""; # set to "" to disable
{% endif %}
$THROTTLE_PAGE_TIMEOUT = {{ dhcpd_probe_page_timeout | default('600') }}; # seconds
$THROTTLE_PAGE_RECIPIENT = "{{ dhcpd_probe_page_to | join(' ') | replace('@','\@') }}"; # space-separated email addresses, remember to escape '@' characters
# End of definitions you may need to edit
#
#############################################################################
(my $prog = $0) =~ s/.*\///;
# init our use of syslog
# setlogsock('unix'); # talk to syslog with UNIX domain socket, not INET domain. XXX causes failure in Solaris 7
openlog($prog, $SYSLOG_OPT, $SYSLOG_FACILITY);
#############################################################################
#
# Parse options and arguments
# We must use getopt() instead of getopts() to avoid throwing an error
# if we are passed an unrecognized option.
# We must silently ignore unrecognized options to be forward compatible with enhancements to dhcp_probe.
use vars qw($opt_i $opt_I $opt_m $opt_p $opt_y);
# &getopt('p:i:I:m:y:');
&getopt('piImy');
# Required options
my $calling_program = $opt_p;
my $ifname = $opt_I;
my $ip_src = $opt_i;
my $ether_src = $opt_m;
#
# Optional options
my $yiaddr = $opt_y || "";
# Enforce presence of required options
unless ($calling_program) {
my_message('LOG_ERR', "${prog}: missing -p calling_program option");
exit 100;
}
unless ($ifname) {
my_message('LOG_ERR', "${prog}: missing -I interface_name option");
exit 101;
}
unless ($ip_src) {
my_message('LOG_ERR', "${prog}: missing -i ip_src_address option");
exit 102;
}
unless ($ether_src) {
my_message('LOG_ERR', "${prog}: missing -m ether_src_address option");
exit 103;
}
# Done parsing options and arguments
#
#############################################################################
# Miscellaneous Initialization
my $hostname = hostname();
my ($seconds, $microseconds) = gettimeofday; # from Time::HiRes on CPAN
my $timestamp = scalar(localtime($seconds));
$timestamp =~ s/(\d\d:\d\d:\d\d)/$1.$microseconds/; # glue microsends to end of seconds
# When we pass a key to THROTTLE_MAIL_CMD, we want the key to also include an indication
# of whether the yiaddr option was specified. (If we didn't include such an indication,
# the following could happen: The first response we detect from a rogue server doesn't
# distribute an "address of concern", so the yiaddr option wasn't specified. We alert
# based on that. The next response we detect from a rogue server does distribute an
# "address of concern", so the yiaddr is specified. Our alert gets throttled since
# the first alert we sent was "recent". But this means the administrator doesn't get
# notified that the rogue distributed an "address of interest". So we need to
# include the state of yiaddr option in the key we pass to THROTTLE_MAIL_CMD,
# to ensure the second response isn't throttled using the same key as the first response.)
#
# We can't just use the value of yiaddr itself as the string to incorporate into the key,
# as that would result in a unqiue key for each distributed IP address. Instead, we
# will use the state of yiaddr (set or unset).
#
# Note this means that when a rogue DHCP server is distributing IP addresses that fall into "Networks of Concern",
# we will very likely send more than one notification for it within each throttle period.
# That's because while some of the responses from the rogue will have a yiaddr within a "Networks of Concern",
# others (for example, DHCPNAK responses) will not. This is unfortunate, but is better than the
# alternative approach (of not taking into account the yiaddr state in the key), since that will
# sometimes cause you to not be alerted at all to the "yiaddr falls into a Network of Concern" situation.
#
# Create a string based on the state of yiaddr, for later incorporation into the key.
my $yiaddr_option_state = $yiaddr ? "yiaddr=set" : "yiaddr=unset";
#############################################################################
if ($THROTTLE_MAIL_CMD) {
# This command suppresses the message if it's sent a message to 'key' within 'throttle_seconds'
# I use the calling program's name and the offender's hardware address as the key.
my $subject_yiaddr_addendum = "";
$subject_yiaddr_addendum = ", YIADDR=$yiaddr" if $yiaddr;
unless (open(THROTTLE_MAIL, "| $THROTTLE_MAIL_CMD -l -k ${calling_program}_mail_${ether_src}_$yiaddr_option_state -t $THROTTLE_MAIL_TIMEOUT -f \"$THROTTLE_MAIL_FROM\" -r \"$THROTTLE_MAIL_RECIPIENT\" -s\"$THROTTLE_MAIL_SUBJECT (MAC=${ether_src}, IP=${ip_src}${subject_yiaddr_addendum})\"")) {
my_message('LOG_ERR', "${prog}: failure trying to send throttled email: can't execute '${THROTTLE_MAIL_CMD}': open(): $!");
exit 20;
}
print THROTTLE_MAIL
$timestamp, "\n",
"\n",
"$calling_program detected an unexpected BOOTP/DHCP server.\n",
"Host=$hostname, interface=${ifname}, IP source=${ip_src}, Ethernet source=${ether_src}\n";
print THROTTLE_MAIL "\nThis means that *there is* a rogue BOOTP/DHCP server operating.\n" if $VERBOSE;
if ($yiaddr) {
print THROTTLE_MAIL
"The server distributed IP address $yiaddr, which falls into a network of special concern.\n";
}
unless (close(THROTTLE_MAIL)) {
my_message('LOG_ERR',
"${prog}: failure trying to send throttled email: error executing '${THROTTLE_MAIL_CMD}': close(): " .
($! ?
"syserr closing pipe: $!"
:
"wait status $? from pipe"
) .
"\n"
);
exit 21;
}
}
if ($THROTTLE_PAGE_CMD) {
# This command suppresses the message if it's sent a message to 'key' within 'throttle_seconds'
# I use the calling program's name and the offender's hardware address as the key.
unless (open(THROTTLE_PAGE, "| $THROTTLE_PAGE_CMD -l -k ${calling_program}_page_${ether_src}_$yiaddr_option_state -t $THROTTLE_PAGE_TIMEOUT -r \"$THROTTLE_PAGE_RECIPIENT\"")) {
my_message('LOG_ERR', "${prog}: failure trying to send throttled page: can't execute '${THROTTLE_PAGE_CMD}': open(): $!\n");
exit 30;
}
print THROTTLE_PAGE "Rogue DHCP server IP=$ip_src MAC=$ether_src seen via host $hostname interface $ifname\n";
print THROTTLE_PAGE "This means *there is* a rogue BOOTP/DHCP server operating.\n" if $VERBOSE;
if ($yiaddr) {
print THROTTLE_PAGE
"Rogue server distributed yiaddr=$yiaddr, a special concern.\n";
}
unless (close(THROTTLE_PAGE)) {
my_message('LOG_ERR',
"${prog}: failure trying to send throttled page: error executing '${THROTTLE_PAGE_CMD}': close(): " .
($! ?
"syserr closing pipe: $!"
:
"wait status $? from pipe"
) .
"\n"
);
exit 31;
}
}
exit 0;
#############################################################################
sub my_message {
# Call with a syslog priority constant and a message string.
# We write the message to syslog, using the specified priority.
# Your message should not contain a newline.
my($priority, $msg) = @_;
syslog($priority, $msg);
return;
}