#!/usr/bin/env python3 # # dyndns.py # # a simple ssh based dyndns updater for bind zone files # # Copyright (C) 2013 Christian Pointner # # This file is part of dyndns.py. # # dyndns.py is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # any later version. # # dyndns.py 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 dyndns.py. If not, see . # import subprocess import fcntl import shutil import hashlib import tempfile import sys import os if len(sys.argv) != 2: print("ERROR: the base-domain-name must be the sole parameter") sys.exit(1) domain = sys.argv[1] base_d = '/var/lib/dyndns' rndc = '/usr/sbin/rndc' digest_fn = os.path.join(base_d, 'digest.'+domain) header_fn = os.path.join(base_d, 'header.'+domain) records_fn = os.path.join(base_d, 'records.'+domain) tmpl_fn = os.path.join(base_d, 'tmpl.'+domain) serial_fn = os.path.join(base_d, 'serial.'+domain) db_fn = os.path.join(base_d, 'db.'+domain) digest_fd = open(digest_fn, 'a+') fcntl.flock(digest_fd, fcntl.LOCK_EX) digest_fd.seek(0) h = hashlib.blake2b() with open(tmpl_fn, 'wb+') as tmpl_fd: with open(header_fn, 'rb') as header_fd: shutil.copyfileobj(header_fd, tmpl_fd) try: with open(records_fn, 'rb') as records_fd: shutil.copyfileobj(records_fd, tmpl_fd) except FileNotFoundError: pass tmpl_fd.seek(0) while True: data = tmpl_fd.read(64*1024) if not data: break h.update(data) old_digest = digest_fd.readline().strip() new_digest = h.hexdigest() if old_digest == new_digest: print("OK: already up to date.") sys.exit(0) serial = 0 with open(serial_fn, 'a+') as serial_fd: serial_fd.seek(0) serial = int(serial_fd.readline()) + 1 serial_fd.truncate(0) serial_fd.write(str(serial)+'\n') with tempfile.NamedTemporaryFile(dir=base_d, delete=False, mode='w') as out_fd: with open(tmpl_fn, 'r') as tmpl_fd: while True: line = tmpl_fd.readline() if not line: break line = line.replace('__SERIAL__', str(serial)) out_fd.write(line) os.chmod(out_fd.name, 0o644) os.rename(out_fd.name, db_fn) cmd = [rndc, 'reload', domain] p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) rndc_output = '' for line in p.stdout.readlines(): rndc_output = rndc_output + line.decode('utf-8') r = p.wait() if r != 0: print("ERROR: rndc returned %d\n\n%s" % (r, rndc_output)) sys.exit(1) digest_fd.truncate(0) digest_fd.write(new_digest + '\n') print("OK: " + rndc_output)