I've been told new OpenBSD stuff should use this license:
/*
* Copyright (c) 2025 Tomasz Kramkowski <tomasz@kramkow.ski>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
What follows is the output of the following command:
$ head -n10000 /etc/rc.d/zoneregend /usr/local/sbin/zoneregend /usr/local/sbin/zoneregen /usr/local/sbin/leases2zones /etc/zoneregen
==> /etc/rc.d/zoneregend <==
#!/bin/ksh
daemon="/usr/local/sbin/zoneregend"
daemon_logger="daemon.info"
. /etc/rc.d/rc.subr
pexp="bash $pexp"
rc_bg=YES
rc_cmd $1
==> /usr/local/sbin/zoneregend <==
#!/usr/bin/env bash
shopt -s lastpipe
LEASES=/var/db/dhcpd.leases
[[ -r /etc/zoneregen ]] && . /etc/zoneregen
do_regen() {
printf 'Running zoneregen\n'
/usr/local/sbin/zoneregen
}
trap 'do_regen' HUP
trap 'kill 0' EXIT
inotifywait -e modify -m "$LEASES" --format '' 2>&1 |
while read -r; do
case $REPLY in
'Watches established.'|'') do_regen ;;
esac
done
==> /usr/local/sbin/zoneregen <==
#!/usr/bin/env bash
LEASES=/var/db/dhcpd.leases
FWD_PRE=/dev/null
FWD_POST=/dev/null
REV_PRE=/dev/null
REV_POST=/dev/null
. /etc/zoneregen || exit
trap 'rm -f -- "$fwd_part" "$rev_part" "$full"' EXIT
fwd_part=$(mktemp) || exit
rev_part=$(mktemp) || exit
full=$(mktemp) || exit
leases2zones "$FWD_DOMAIN" "$fwd_part" "$rev_part" <"$LEASES" || exit
zone_serial() {
ldns-read-zone -c -E SOA -- "$1" | \
awk '{ serial = $7; exit } END { printf "%d\n", serial }'
}
regen_zone() {
local output=$1 domain=$2 pre=$3 post=$4 part=$5
local serial=$(zone_serial "$output")
ldns-read-zone -zS "$serial" -- \
<(cat "$pre" "$part" "$post") >"$full" || exit
wait "$!" || exit
cmp -s -- "$output" "$full" && return
ldns-read-zone -zS "$((serial + 1))" -- "$full" >"$output" || exit
nsd-control reload "$domain" || exit
}
regen_zone "$FWD" "$FWD_DOMAIN" "$FWD_PRE" "$FWD_POST" "$fwd_part"
regen_zone "$REV" "$REV_DOMAIN" "$REV_PRE" "$REV_POST" "$rev_part"
==> /usr/local/sbin/leases2zones <==
#!/usr/bin/env python3
from dataclasses import dataclass
from datetime import datetime
from ipaddress import IPv4Address, IPv6Address, ip_address
from sys import argv, stdin, stderr
from typing import Union, Optional, TextIO
IPAddress = Union[IPv4Address, IPv6Address]
@dataclass
class Lease:
ip: IPAddress
mac: str
hostname: Optional[str]
@dataclass
class Record:
name: str
type: str
target: str
def __str__(self):
return f'{self.name} {self.type} {self.target}'
def parse_leases(f: TextIO) -> list[Lease]:
now = datetime.now()
leases: dict[IPAddress, Lease] = {}
lease = None
for line in f:
line = line.strip()
if line.startswith('lease'):
lease = dict()
_, lease['ip'], _ = line.split(' ')
if not lease:
continue
if line.startswith('starts'):
_, dt = line[:-1].split(' ', maxsplit=1)
lease['starts'] = datetime.strptime(dt, '%w %Y/%m/%d %H:%M:%S %Z')
if line.startswith('ends'):
_, dt = line[:-1].split(' ', maxsplit=1)
lease['ends'] = datetime.strptime(dt, '%w %Y/%m/%d %H:%M:%S %Z')
if line.startswith('hardware ethernet'):
_, _, lease['mac'] = line[:-1].split(' ')
if line.startswith('client-hostname'):
_, lease['hostname'], _ = line.split('"')
if line.startswith('abandoned'):
lease['abandoned'] = True
if line == '}':
if lease and not lease.get('abandoned') and \
lease.get('mac') and \
lease.get('starts', now) <= now and \
lease.get('ends', now) > now:
ip = ip_address(lease['ip'])
leases[ip] = Lease(ip, lease['mac'],
lease.get('hostname'))
lease = None
return list(leases.values())
def to_zones(leases: list[Lease], domain: str) -> tuple[list[Record], list[Record]]:
forward: list[Record] = list()
reverse: list[Record] = list()
for l in leases:
typ = 'A' if isinstance(l.ip, IPv4Address) else 'AAAA'
rev = l.ip.reverse_pointer + '.' # 3.5
if l.hostname:
base, *_ = l.hostname.split('.')
hostname_fqdn = base + '.' + domain
forward.append(Record(hostname_fqdn, typ, str(l.ip)))
reverse.append(Record(rev, 'PTR', hostname_fqdn))
mac_fqdn = l.mac.replace(':', '_') + '.' + domain
forward.append(Record(mac_fqdn, typ, str(l.ip)))
if not l.hostname:
reverse.append(Record(rev, 'PTR', mac_fqdn))
return forward, reverse
def main() -> None:
if len(argv) != 4:
argv0 = argv[0] if len(argv) >= 1 else 'leases2zones'
print(f'Usage: {argv0} domain forward-filename reverse-filename',
file=stderr)
exit(1)
domain, forward_filename, reverse_filename = argv[1:]
domain = '.'.join(domain.split('.')) + '.'
forward, reverse = to_zones(parse_leases(stdin), domain)
with open(forward_filename, 'w') as f:
for record in forward:
print(str(record), file=f)
with open(reverse_filename, 'w') as f:
for record in reverse:
print(str(record), file=f)
if __name__ == '__main__':
main()
==> /etc/zoneregen <==
ZONE_DIR=/var/nsd/zones/master
FWD_DOMAIN=your.local.domain
FWD=$ZONE_DIR/$FWD_DOMAIN.zone
FWD_PRE=$FWD.pre
REV_DOMAIN=168.192.in-addr.arpa
REV=$ZONE_DIR/$REV_DOMAIN.zone
REV_PRE=$REV.pre