248 lines
9.9 KiB
Plaintext
248 lines
9.9 KiB
Plaintext
|
#!/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;
|
||
|
}
|