Vendor Galaxy Roles and Collections
Some checks failed
/ Ansible Lint (push) Failing after 5m45s
/ Ansible Lint (pull_request) Failing after 4m59s

This commit is contained in:
Stefan Bethke 2026-02-06 22:07:16 +01:00
commit 2aed20393f
3553 changed files with 387444 additions and 2 deletions

View file

@ -0,0 +1,19 @@
debops.dhcp_probe - Configure DHCP Probe using Ansible
Copyright (C) 2014-2018 Maciej Delmanowski <drybjed@gmail.com>
Copyright (C) 2014-2018 DebOps <https://debops.org/>
SPDX-License-Identifier: GPL-3.0-only
This Ansible role is part of DebOps.
DebOps is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3, as
published by the Free Software Foundation.
DebOps 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.
You should have received a copy of the GNU General Public License
along with DebOps. If not, see https://www.gnu.org/licenses/.

View file

@ -0,0 +1,166 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# .. Copyright (C) 2014-2018 Maciej Delmanowski <drybjed@gmail.com>
# .. Copyright (C) 2014-2018 DebOps <https://debops.org/>
# .. SPDX-License-Identifier: GPL-3.0-only
# .. _dhcp_probe__ref_defaults:
# debops.dhcp_probe default variables
# ===================================
# .. contents:: Sections
# :local:
#
# .. include:: ../../../../includes/global.rst
# APT packages [[[
# ----------------
# .. envvar:: dhcp_probe__base_packages [[[
#
# List of APT packages required for DHCP Probe service.
dhcp_probe__base_packages: [ 'dhcp-probe' ]
# ]]]
# .. envvar:: dhcp_probe__packages [[[
#
# List of additional APT packages to install with DHCP Probe.
dhcp_probe__packages: []
# ]]]
# ]]]
# Custom directories, scripts [[[
# -------------------------------
# .. envvar:: dhcp_probe__cache [[[
#
# Absolute path of the cache directory where :command:`mail-throttled` script
# state is stored.
dhcp_probe__cache: '{{ (ansible_local.fhs.cache | d("/var/cache"))
+ "/dhcp-probe" }}'
# ]]]
# .. envvar:: dhcp_probe__lib [[[
#
# Absolute path where the custom scripts used by the DHCP Probe service are
# stored.
dhcp_probe__lib: '{{ (ansible_local.fhs.lib | d("/usr/local/lib"))
+ "/dhcp-probe" }}'
# ]]]
# ]]]
# Monitored network interfaces [[[
# --------------------------------
# These variables define on which network interfaces DHCP Probe will listen for
# rogue DHCP servers. By default Ethernet interfaces, bridges, VLAN interfaces,
# bonds and various uplink interfaces with IPv4 addresses will be monitored.
# See :ref:`dhcp_probe__ref_interfaces` for more details.
# .. envvar:: dhcp_probe__default_interfaces [[[
#
# List of default network interfaces to listen on for rogue DHCP servers.
dhcp_probe__default_interfaces: '{{ lookup("template", "lookup/dhcp_probe__default_interfaces.j2",
convert_data=False) | from_yaml }}'
# ]]]
# .. envvar:: dhcp_probe__interfaces [[[
#
# Additional list of network interfaces to listen on for rogue DHCP servers.
dhcp_probe__interfaces: []
# ]]]
# .. envvar:: dhcp_probe__combined_interfaces [[[
#
# The variable that combines the interface lists and is used in the role tasks.
dhcp_probe__combined_interfaces: '{{ dhcp_probe__default_interfaces
+ dhcp_probe__interfaces }}'
# ]]]
# ]]]
# DHCP Probe configuration [[[
# ----------------------------
# .. envvar:: dhcp_probe__alert_program [[[
#
# Absolute path of the script executed by DHCP Probe when new rogue DHCP server
# is detected. By default an e-mail message will be generated and sent to the
# system administrator. Set to an empty string to disable.
dhcp_probe__alert_program: '{{ dhcp_probe__lib + "/dhcp_probe_notify2" }}'
# ]]]
# .. envvar:: dhcp_probe__legal_servers [[[
#
# List of IPv4 addresses of the DHCP servers that are allowed on the network.
# If both an IP address list and a hardware address list are specified, both
# elements must match.
dhcp_probe__legal_servers: []
# ]]]
# .. envvar:: dhcp_probe__legal_servers_ethersrc [[[
#
# List of hardware (MAC) addresses of the DHCP servers that are allowed on the
# network. If both an IP address list and a hardware address list are
# specified, both elements must match.
dhcp_probe__legal_servers_ethersrc: []
# ]]]
# .. envvar:: dhcp_probe__options [[[
#
# A string or YAML text block with custom :command:`dhcp_probe` options.
# See :man:`dhcp_probe.cf(5)` for more details about available options.
dhcp_probe__options: ''
# ]]]
# ]]]
# Mail and pager notification [[[
# -------------------------------
# These variables are used to configure the custom mail notification script
# used by DHCP Probe to notify the system administrator about rogue DHCP
# servers found on the network.
# .. envvar:: dhcp_probe__domain [[[
#
# The DNS domain used to send e-mail notifications about rogue DHCP servers.
dhcp_probe__domain: '{{ ansible_domain }}'
# ]]]
# .. envvar:: dhcp_probe__mail_from [[[
#
# The sender of the notification mail. The default ``root`` will be expanded to
# a full e-mail address by the SMTP server.
dhcp_probe__mail_from: 'root'
# ]]]
# .. envvar:: dhcp_probe__mail_to [[[
#
# The list of the e-mail addresses of recipients which should get the
# notification mail.
dhcp_probe__mail_to: [ 'root@{{ dhcp_probe__domain }}' ]
# ]]]
# .. envvar:: dhcp_probe__mail_subject [[[
#
# The prefix of the notification mail subject.
dhcp_probe__mail_subject: 'Unexpected BOOTP/DHCP server'
# ]]]
# .. envvar:: dhcp_probe__mail_timeout [[[
#
# Time in seconds between successive notification mail messages.
dhcp_probe__mail_timeout: '{{ 20 * 60 }}'
# ]]]
# .. envvar:: dhcp_probe__page_to [[[
#
# The list of e-mail addresses of recipients which will receive short e-mail
# messages, suitable for pagers.
dhcp_probe__page_to: []
# ]]]
# .. envvar:: dhcp_probe__page_timeout [[[
#
# Time in seconds between successive notification pager messages.
dhcp_probe__page_timeout: '{{ 20 * 60 }}'
# ]]]
# ]]]

View file

@ -0,0 +1,33 @@
---
# Copyright (C) 2014-2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2014-2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# Ensure that custom Ansible plugins and modules included in the main DebOps
# collection are available to roles in other collections.
collections: [ 'debops.debops' ]
dependencies: []
galaxy_info:
author: 'Maciej Delmanowski'
description: 'Configure DHCP Probe service'
company: 'DebOps'
license: 'GPL-3.0-only'
min_ansible_version: '2.6.0'
platforms:
- name: 'Ubuntu'
versions:
- bionic
- name: 'Debian'
versions:
- stretch
- buster
galaxy_tags:
- dhcp
- networking

View file

@ -0,0 +1,111 @@
---
# Copyright (C) 2014-2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2014-2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Import custom Ansible plugins
ansible.builtin.import_role:
name: 'ansible_plugins'
- name: Import DebOps global handlers
ansible.builtin.import_role:
name: 'global_handlers'
- name: Install required packages
ansible.builtin.package:
name: '{{ q("flattened", (dhcp_probe__base_packages + dhcp_probe__packages)) }}'
state: 'present'
register: dhcp_probe__register_packages
until: dhcp_probe__register_packages is succeeded
- name: Ensure that sysvinit dhcp-probe service is stopped on install
ansible.builtin.systemd:
name: 'dhcp-probe.service'
state: 'stopped'
when: ((ansible_local is undefined or
ansible_local.dhcp_probe is undefined) and
ansible_service_mgr == 'systemd')
- name: Install custom systemd unit files
ansible.builtin.template:
src: 'etc/systemd/system/{{ item }}.j2'
dest: '/etc/systemd/system/{{ item }}'
owner: 'root'
group: 'root'
mode: '0644'
loop: [ 'dhcp-probe@.service', 'dhcp-probe.service' ]
notify: [ 'Reload service manager' ]
when: ansible_service_mgr == 'systemd'
- name: Reload systemd configuration when needed
ansible.builtin.meta: 'flush_handlers'
- name: Disable DHCP Probe instances if requested
ansible.builtin.systemd:
name: 'dhcp-probe@{{ item.name }}.service'
state: 'stopped'
enabled: False
loop: '{{ dhcp_probe__combined_interfaces | debops.debops.parse_kv_items }}'
loop_control:
label: '{{ {"name": item.name, "state": item.state} }}'
when: ansible_service_mgr == 'systemd' and item.name | d() and
item.state | d('present') == 'absent'
- name: Enable DHCP Probe instances if requested
ansible.builtin.systemd:
name: 'dhcp-probe@{{ item.name }}.service'
enabled: True
loop: '{{ dhcp_probe__combined_interfaces | debops.debops.parse_kv_items }}'
loop_control:
label: '{{ {"name": item.name, "state": item.state} }}'
when: ansible_service_mgr == 'systemd' and item.name | d() and
item.state | d('present') != 'absent'
notify: [ 'Restart dhcp-probe' ]
- name: Ensure that required directories exist
ansible.builtin.file:
path: '{{ item }}'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
loop: [ '{{ dhcp_probe__cache }}', '{{ dhcp_probe__lib }}' ]
- name: Install custom notification scripts
ansible.builtin.template:
src: 'usr/local/lib/dhcp-probe/{{ item }}.j2'
dest: '{{ dhcp_probe__lib + "/" + item }}'
owner: 'root'
group: 'root'
mode: '0755'
loop: [ 'dhcp_probe_notify2', 'mail-throttled' ]
- name: Generate dhcp_probe.cf configuration file
ansible.builtin.template:
src: 'etc/dhcp_probe.cf.j2'
dest: '/etc/dhcp_probe.cf'
owner: 'root'
group: 'root'
mode: '0644'
notify: [ 'Restart dhcp-probe' ]
- name: Make sure Ansible fact directory exists
ansible.builtin.file:
path: '/etc/ansible/facts.d'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
- name: Setup DHCP Probe local facts
ansible.builtin.template:
src: 'etc/ansible/facts.d/dhcp_probe.fact.j2'
dest: '/etc/ansible/facts.d/dhcp_probe.fact'
owner: 'root'
group: 'root'
mode: '0755'
notify: [ 'Refresh host facts' ]
tags: [ 'meta::facts' ]
- name: Reload facts if they were modified
ansible.builtin.meta: 'flush_handlers'

View file

@ -0,0 +1,24 @@
#!{{ ansible_python['executable'] }}
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2014-2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# {{ ansible_managed }}
from __future__ import print_function
from json import dumps
import os
def cmd_exists(cmd):
return any(
os.access(os.path.join(path, cmd), os.X_OK)
for path in os.environ["PATH"].split(os.pathsep)
)
output = {'installed': cmd_exists('/usr/sbin/dhcp_probe')}
print(dumps(output, sort_keys=True, indent=4))

View file

@ -0,0 +1,29 @@
{# Copyright (C) 2014-2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2014-2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
{% if dhcp_probe__alert_program | d() %}
# Send mail messages about unauthorized DHCP servers
alert_program_name2 {{ dhcp_probe__alert_program }}
{% endif %}
{% if dhcp_probe__legal_servers | d() %}
# List of IP addresses of legal DHCP servers
{% for address in ([ dhcp_probe__legal_servers ] if dhcp_probe__legal_servers is string else dhcp_probe__legal_servers) %}
legal_server {{ address }}
{% endfor %}
{% endif %}
{% if dhcp_probe__legal_servers_ethersrc | d() %}
# List of MAC addresses of legal DHCP servers
{% for address in ([ dhcp_probe__legal_servers_ethersrc ] if dhcp_probe__legal_servers_ethersrc is string else dhcp_probe__legal_servers_ethersrc) %}
legal_server_ethersrc {{ address }}
{% endfor %}
{% endif %}
{% if dhcp_probe__options | d() %}
# Custom options
{{ dhcp_probe__options }}
{% endif %}

View file

@ -0,0 +1,19 @@
{# Copyright (C) 2014-2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2014-2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
[Unit]
Description=Daemon to survery DHCP/BOOTP servers on LAN
After=network-online.target
Documentation=man:dhcp_probe(8)
[Service]
Type=oneshot
ExecStart=/bin/true
ExecReload=/bin/true
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,38 @@
{# Copyright (C) 2014-2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2014-2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
[Unit]
Description=Daemon to survey DHCP/BOOTP servers on LAN
After=network-online.target
Documentation=man:dhcp_probe(8)
ConditionPathExists=/etc/dhcp_probe.cf
PartOf=dhcp-probe.service
ReloadPropagatedFrom=dhcp-probe.service
Before=dhcp-probe.service
[Service]
Type=forking
Slice=system-dhcp-probe.slice
ExecStart=/usr/sbin/dhcp_probe -T -p /var/run/dhcp-probe/dhcp_probe.%i.pid %i
PIDFile=/var/run/dhcp-probe/dhcp_probe.%i.pid
# dhcp_probe ignores SIGTERM
KillMode=control-group
KillSignal=SIGKILL
SuccessExitStatus=SIGKILL
RuntimeDirectory=dhcp-probe
RuntimeDirectoryMode=0755
PrivateTmp=yes
LimitNOFILE=65535
PrivateDevices=yes
ProtectHome=yes
[Install]
WantedBy=multi-user.target
WantedBy=dhcp-probe.service

View file

@ -0,0 +1,16 @@
{# Copyright (C) 2014-2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2014-2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
{% set dhcp_probe__tpl_interfaces = [] %}
{% for element in ansible_interfaces %}
{% if element.startswith(('bond', 'br', 'en', 'eth', 'mesh', 'sit', 'sl', 'tap', 'tun', 'vlan', 'wl', 'ww')) %}
{% if hostvars[inventory_hostname]['ansible_' + element]['ipv4'] is defined %}
{% set _ = dhcp_probe__tpl_interfaces.append({
'name': element,
'state': 'present'
}) %}
{% endif %}
{% endif %}
{% endfor %}
{{ dhcp_probe__tpl_interfaces | to_yaml }}

View file

@ -0,0 +1,299 @@
#!/usr/bin/perl -w
# {{ ansible_managed }}
# Copyright (C) 2008 Irwin Tillman <networking@princeton.edu>
# Copyright (C) 2014-2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2014-2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: BSD-4-Clause
# dhcp_probe was created by the Network Systems Group at Princeton University's
# Office of Information Technology, networking at princeton dot edu.
# It was written by Irwin Tillman.
#
# Portions of this software are copyrighted by other organizations or individuals;
# see below for details one which portions, and what legal notices apply
# to each of those portions.
#
# Those portions of the software which have not identified below as copyrighted
# by other organizations or individuals are themselves Copyright (©) 2000-2008,
# The Trustees of Princeton University, All Rights Reserved.
#
# Redistribution and use in source and binary form, with or without modification,
# are permitted provided that the following conditions are met:
#
# * The redistribution, use, or modification shall not violate any of the additional
# copyrights, patents, or other restrictions below.
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# * If redistribution includes any portion of the software that is copyrighted by other
# organizations or individuals (as detailed below), the copyright notice associated with
# that portion of the software also must be retained.
#
# * Neither the names of the individual authors or contributos, nor the
# name of "Princeton University" may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# This software is provided "as is" and any express or implied
# warranties, including, but not limited to, the implied warranties of
# mechantability and fitness for a particular purpose are disclaimed. In
# no event shall the authors or Princeton University be liable for any
# direct, indirect, incidental, special exemplary, or consequential
# damages (including, but not limited to, procurement of substitute goods
# or services; loss of use, data, or profits; or business interruption)
# however caused and on any theory of liability, whether in contract,
# strict liability, or tort (including negligence or otherwise) arising
# in any way out of the use of this software, even if advised of the
# possibility of such damage.
# 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 dhcp_probe__mail_to | d() %}
$THROTTLE_MAIL_CMD = "{{ dhcp_probe__lib + '/mail-throttled' }}"; # set to "" to disable
{% else %}
$THROTTLE_MAIL_CMD = ""; # set to "" to disable
{% endif %}
$THROTTLE_MAIL_TIMEOUT = {{ dhcp_probe__mail_timeout | default('600') }}; # seconds
$THROTTLE_MAIL_FROM = "{{ dhcp_probe__mail_from | replace('@', '\@') }}"; # e.g. "root"
$THROTTLE_MAIL_RECIPIENT = "{{ dhcp_probe__mail_to | join(' ') | replace('@','\@') }}"; # space-separated email addresses, remember to escape '@' characters
$THROTTLE_MAIL_SUBJECT = "{{ dhcp_probe__mail_subject }}";
# 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 dhcp_probe__page_to | d() %}
$THROTTLE_PAGE_CMD = "{{ dhcp_probe__lib + '/mail-throttled' }}"; # set to "" to disable
{% else %}
$THROTTLE_PAGE_CMD = ""; # set to "" to disable
{% endif %}
$THROTTLE_PAGE_TIMEOUT = {{ dhcp_probe__page_timeout | default('600') }}; # seconds
$THROTTLE_PAGE_RECIPIENT = "{{ dhcp_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 unique 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;
}

View file

@ -0,0 +1,225 @@
#!/usr/bin/perl -w
# {{ ansible_managed }}
# Copyright (C) 2008 Irwin Tillman <networking@princeton.edu>
# Copyright (C) 2014-2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2014-2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: BSD-4-Clause
# dhcp_probe was created by the Network Systems Group at Princeton University's
# Office of Information Technology, networking at princeton dot edu.
# It was written by Irwin Tillman.
#
# Portions of this software are copyrighted by other organizations or individuals;
# see below for details one which portions, and what legal notices apply
# to each of those portions.
#
# Those portions of the software which have not identified below as copyrighted
# by other organizations or individuals are themselves Copyright (©) 2000-2008,
# The Trustees of Princeton University, All Rights Reserved.
#
# Redistribution and use in source and binary form, with or without modification,
# are permitted provided that the following conditions are met:
#
# * The redistribution, use, or modification shall not violate any of the additional
# copyrights, patents, or other restrictions below.
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# * If redistribution includes any portion of the software that is copyrighted by other
# organizations or individuals (as detailed below), the copyright notice associated with
# that portion of the software also must be retained.
#
# * Neither the names of the individual authors or contributos, nor the
# name of "Princeton University" may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# This software is provided "as is" and any express or implied
# warranties, including, but not limited to, the implied warranties of
# mechantability and fitness for a particular purpose are disclaimed. In
# no event shall the authors or Princeton University be liable for any
# direct, indirect, incidental, special exemplary, or consequential
# damages (including, but not limited to, procurement of substitute goods
# or services; loss of use, data, or profits; or business interruption)
# however caused and on any theory of liability, whether in contract,
# strict liability, or tort (including negligence or otherwise) arising
# in any way out of the use of this software, even if advised of the
# possibility of such damage.
# $Header: /usr/local/etc/RCS/mail-throttled,v 1.11 2008/12/06 01:50:28 root Exp $
# mail-throttled [-l] [-D dbm_file] -k key -t throttle_seconds [-f from] -r recipient [-d] [-T tie_attempts_max] [-S tie_retry_sleep] [-s subject]
#
# Sends mail body (read from STDIN) to 'recipient', but avoids doing so "too frequently."
#
# You provide a 'key', which is an arbitrary string used to identify this notification.
# You also provide 'throttle_seconds', an integer. If we've sent anything that
# specified this 'key' within the last 'throttle_seconds', we do not send the message.
# Otherwise, we send the message, and the remember that we've sent a message for this 'key'
# at the current time.
#
# This key/timesent tuples are stored on-disk, in a dbm. As a result, the 'key'
# you supply must satisfy the syntactic requirements for dbm keys.
# The caller needs to have permission to read and write this DBM (and create it if
# it does not already exist). If you fail to specify a dbm_file, we'll use a default
# value, which may not be what you want (since the caller might not be able to r/w that
# particular DBM).
# We never clean this dbm. You can safely erase it entirely, if you don't mind losing
# the state, and you know the caller has permission to create a new instance of the DBM.
#
# The 'recipient' should be a valid email address. Naturally, it should not
# be one that will cause any ack or bounce mail to return to us!
# If there are several addresses (delimited by spaces), be sure to quote them as a single arg.
#
# If a subject is specified, be sure to quote it if it contains any spaces or other shell
# metachars.
#
# If -l is specified, then any errors, warnings, or debugging output is written to syslog
# in addition to its usual destination (STDERR). This is helpful if you call this from an
# environment where STDERR may get lost.
#
# Irwin Tillman
use Getopt::Std;
use GDBM_File;
use Errno qw(EAGAIN);
use Sys::Syslog qw(:DEFAULT setlogsock);
use strict;
use warnings;
use vars qw($DBM_FILE_DEFAULT $MAILCMD $MAILCMD_OPTS $FROM_DEFAULT);
$DBM_FILE_DEFAULT = '{{ dhcp_probe__cache + "/mail-throttled.gdbm" }}';
$MAILCMD = "/usr/lib/sendmail";
# $MAILCMD_OPTS = "-t -ODeliveryMode=queueonly";
$MAILCMD_OPTS = "-t";
$FROM_DEFAULT = "root";
my $SYSLOG_FACILITY="daemon"; # name of facility to use if syslogging
my $SYSLOG_OPT = 'pid,cons'; # syslog options to use if syslogging, ignored otherwise
my $SYSLOG_PRIORITY = 'LOG_ERR';
# The tie() call sometimes fails with EAGAIN.
# Perhaps that's due to some other process having the DBM open;
# in fact, that may be more likely if the process that calls us may calls us multiple times in quick succession.
# So when tie() fails with EAGAIN, we can sleep and retry some number of times before giving up entirely.
my $TIE_ATTEMPTS_MAX = 3; # number of times to try tie() before giving up
my $TIE_RETRY_SLEEP = 1; # seconds to sleep before retrying tie()
(my $prog = $0) =~ s/.*\///;
use vars qw($opt_f $opt_D $opt_d $opt_k $opt_l $opt_r $opt_s $opt_S $opt_t $opt_T);
&getopts('dD:f:k:lr:s:S:t:T:');
my $debug = $opt_d || "";
my $dbm_file = $opt_D || $DBM_FILE_DEFAULT;
my $key = $opt_k || "";
my $from = $opt_f || $FROM_DEFAULT;
my $recipient = $opt_r || "";
my $throttle_secs = $opt_t || 1;
my $subject = $opt_s || "";
my $also_syslog = $opt_l || "";
my $tie_attempts_max = $opt_T || $TIE_ATTEMPTS_MAX; # we deliberately override if CLI option specifies 0, as that makes no sense
my $tie_retry_sleep = $opt_S || $TIE_RETRY_SLEEP;
if ($also_syslog) {
# 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);
}
my_warn("${prog}:\nkey=$key\nthrottle_secs=$throttle_secs\nfrom=$from\nrecipient=$recipient\ntie_attempts_max=$tie_attempts_max\ntie_retry_sleep=$tie_retry_sleep\nsubject=$subject") if $debug;
# certain options and args are required
&Usage() unless ($key && $throttle_secs && $recipient);
my %last_sent = ();
my $tie_succeeded = 0;
my $tie_attempts_left = $TIE_ATTEMPTS_MAX;
while ($tie_attempts_left--) {
if (tie(%last_sent, 'GDBM_File', $dbm_file, &GDBM_WRCREAT, 0644)) {
$tie_succeeded = 1;
last;
}
# the tie() failed
if ($! == EAGAIN) {
# The failure may be due to a transient problem.
# Retrying may help.
sleep $TIE_RETRY_SLEEP;
next;
} else {
# Some other (presumably more serious) error.
last;
}
}
unless ($tie_succeeded) {
my_warn("${prog}: can't tie ${dbm_file}: $!");
exit 10;
}
my @mailbody = "";
@mailbody = <STDIN>; # read it even if we decide not to send it
my $now = time;
my_warn("now = $now") if $debug;
$last_sent{$key} = 0 unless defined($last_sent{$key}); # so it's defined before we use it in subtraction (placate use strict)
if ($now - $last_sent{$key} >= $throttle_secs) {
my_warn("last_sent = $last_sent{$key}, will send") if $debug;
unless (open(MAIL, "| $MAILCMD $MAILCMD_OPTS -f\"$from\"")) {
my_warn("${prog}: error executing '${MAILCMD}': open(): $!");
exit 20;
}
print MAIL "From: $from\n",
"To: $recipient\n",
($subject ? "Subject: $subject\n" : "") ,
"\n",
@mailbody;
unless (close(MAIL)) {
my_warn("${prog}: error executing '${MAILCMD}': close(): " .
($! ?
"syserror closing pipe: $!"
:
"wait status $? from pipe"
)
);
exit 21;
}
$last_sent{$key} = $now;
} else {
my_warn("last_sent = $last_sent{$key}, suppressing") if $debug;
}
untie %last_sent;
exit 0;
sub Usage {
my_warn("Usage: $prog [-l] [-D dbm_file] -k key -t throttle_seconds [-f from] -r recipient [-T tie_attempts_max] [-S tie_retry_sleep] [-s subject]");
exit 1;
}
sub my_warn {
# Just a wrapper for warn, but with a possible copy to syslog too.
my $msg = shift;
warn $msg, "\n";
syslog($SYSLOG_PRIORITY, $msg) if $also_syslog;
return;
}