2 import ipaddress, subprocess, sys, getopt,dbm
3 from configparser import ConfigParser
4 import urllib.request,urllib.parse
5 class DyndnsNetwork(object):
7 This class represents dynamic DNS server and network.
8 It has attributes hostname network server user and password
10 def __init__(self,config_section):
11 self.hostname = config_section['hostname']
12 self.network = ipaddress.ip_network(config_section['network'])
13 self.server = config_section['server']
14 if user in config_section:
15 self.user=config_section["user"]
16 self.password=config_section["password"]
19 def contains(self,address):
20 if self.network.prefixlen==0 and address.is_private:
22 if self.network.version != address.version:
24 return address in self.network
25 def nsupdate(self, address):
27 Sends a get query to specified server.
28 Raises HTTPError if something goes wrong
31 if hasattr(self,user):
32 password_mgr =urllib.request.HTTPPasswordMgrWithDefaultRealm()
33 password_mgr.add_password(None,self.server,self.user,self.password)
34 handler=urllib.request.HTTPBasicAuthHandler(password_mgr)
35 opener = urllib.request.build_opener(handler)
37 opener = urllib.request.build_opener()
38 with opener.open('%s?%s'%(self.server,
39 urllib.parse.urlencode({'hostname':self.hostname,
40 'myip':str(address)}))) as req:
41 req.read().decode("utf-8")
45 def get_current_addresses():
47 for line in subprocess.run(['ip','-o','addr','show'],check=True,stdout=subprocess.PIPE).stdout.decode('utf-8').split("\n"):
50 (no,iface,family,addr,rest)=line.split(maxsplit=4)
51 address=ipaddress.ip_address(addr.split('/')[0])
52 if address.is_loopback or address.is_link_local:
54 result.append(address)
56 def check_for_update():
57 addrlist=get_current_addresses()
58 for name,net in networks.items():
64 old_addr=ipaddress.ip_address(database[name].decode("utf-8"))
66 # Nothing changed go, to next net
72 except urllib.error.HTTPError as e:
82 config['dyngo']={'interval':'60','database':'/var/lib/dyngo/dyngo.db','ca':'/etc/ssl/certs'}
83 options=dict(getopt.getopt(sys.argv,"f:")[0])
84 if not '-f' in options:
85 options["-f"]="/etc/dyngo.conf"
86 if len(config.read(options["-f"]))!=1:
87 print("Cannot read config %s"%options["-f"],file=sys.stderr)
91 interval=int(conf['interval'])
92 database=dbm.open(conf['database'],"c")
94 if 'ca' in conf and len(conf['ca']):
96 if os.path.isdir(path):
97 https_params['capath']=path
99 http_params['cafile']=path
100 # Convert all other config sections to DyndnsNetwork objects
102 for sect in config.sections():
103 if sect == 'dyngo' or sect == 'DEFAULT':
105 networks[sect]=DyndnsNetwork(config[sect])
106 # Remove stale items from persistent database, which are no more
107 # mentioned in the config file
108 for i in set([x.decode("utf-8") for x in database.keys()])-set(network.keys()):