]> www.wagner.pp.ru Git - oss/dyngo.git/blob - dyngo
ab21fde053fe2a99868da3814a5b687dc765ce69
[oss/dyngo.git] / dyngo
1 #!/usr/bin/env python3
2 import ipaddress, subprocess, sys, getopt,dbm
3 from configparser import ConfigParser
4 import urllib.request,urllib.parse
5 class DyndnsNetwork(object):
6     """
7     This class represents dynamic DNS server and network.
8     It has attributes hostname network server user and password
9     """
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"]
17
18
19     def contains(self,address):
20         if self.network.prefixlen==0 and address.is_private:
21             return False
22         if self.network.version != address.version:
23             return False
24         return address in self.network
25     def nsupdate(self, address):
26         """
27         Sends a get query to specified server.
28         Raises HTTPError if something goes wrong
29         """
30         #construct opener
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)
36         else:
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")
42                
43            
44
45 def get_current_addresses():
46     result=[]    
47     for line in subprocess.run(['ip','-o','addr','show'],check=True,stdout=subprocess.PIPE).stdout.decode('utf-8').split("\n"):
48         if not len(line): 
49             continue
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:
53             continue
54         result.append(address)
55     return result
56 def check_for_update():
57     addrlist=get_current_addresses()
58     for name,net in networks.items():
59         found=False
60         for a in addrlist:
61             if net.contains(a):
62                 found =True
63                 if name in database:
64                     old_addr=ipaddress.ip_address(database[name].decode("utf-8"))
65                     if old_addr==a:
66                         # Nothing changed go, to next net
67                         break
68                 # address changed
69                 try:
70                      net.nsupdate(a)
71                      database[name]=str(a)
72                 except urllib.error.HTTPError as e:
73                     pass
74                 break     
75         if not found:
76             del data[name]
77
78             
79
80
81 config=ConfigParser()
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)
88     sys.exit(1)
89     
90 conf=config['dyngo']
91 interval=int(conf['interval'])
92 database=dbm.open(conf['database'],"c")
93 https_params={}
94 if 'ca' in conf and len(conf['ca']):
95     path=conf['ca']
96     if os.path.isdir(path):
97         https_params['capath']=path
98     else:
99         http_params['cafile']=path
100 # Convert all other config sections to DyndnsNetwork objects
101 networks={}
102 for sect in config.sections():
103     if sect == 'dyngo' or sect == 'DEFAULT':
104         continue
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()):
109     del database[i]
110
111
112 while True:
113     check_for_update()
114     time.sleep(interval)
115