]> www.wagner.pp.ru Git - oss/dyngo.git/commitdiff
Fix conflict which arises from editing example conf without updating working copy...
authorVictor Wagner <vitus@wagner.pp.ru>
Thu, 12 Sep 2019 06:17:50 +0000 (09:17 +0300)
committerVictor Wagner <vitus@wagner.pp.ru>
Thu, 12 Sep 2019 06:17:50 +0000 (09:17 +0300)
README.md
dyngo [changed mode: 0644->0755]
dyngo.md
dyngo.service [new file with mode: 0644]

index 9b5f951568721eb5e5b582de4efaa6a28565d634..721fe2fee741f9e502c638fe007983db22322937 100644 (file)
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ remembered name.
 But it is not always a case in the world of NATs and VPNs.
 
 If machine always have public IP address, than using some dynamic DNS
-service may help (short of neccessity to use full domain name. But 
+service may help (short of necessity to use full domain name. But 
 ${HOME}/.ssh/config may help). But what if you log into your office
 workstation via corporate VPN, and want ssh back home?
 
@@ -26,5 +26,40 @@ to specified URL.
 
 See [manual page](dyngo.md) for more info.
 
+PRROTOCOL
+=========
+
+**dyngo** uses protocol compatible with dyndns.org, as described 
+pn [help.dyn.com](https://help.dyn.com/remote-access-api/perform-update/
+)
+As different dynamic DNS servers use different path components of URL,
+you should specify URL with path component (up to question mark) in 
+the *server* parameter of configuraton section.
+
+INSTALLATION
+============
+
+**dyngo** is simple script which doesn't require anything but
+python3 with standard library and **ip** utility from iproute (or
+iproute2 on Debian) package, which presents on every modern Linux
+system.
+
+I've even preferred to use
+[urllib](https://docs.python.org/3/library/urllib.request.html#module-urllib.request)
+to [requests](http://docs.python-requests.org/) to eliminate extra
+external dependency.
+
+So, you can just drop **dyngo** somewhere in your filesystem, say in 
+**/usr/local/sbin** and put config into */etc* But **dyngo** should run
+as a service, so you might want to use provided service file.
+
+**dyngo** should run as unprivileged user. But its configuration file
+should, readable for this user, contains somewhat sensitive information
+—  your dyndns passwords. So, don't use common account many untrusted
+code run as such as **nobody** or **www-data**. Better to create special
+user dyngo.
+
+There should be writable directory, writable for this user for
+persistent state database. By default it is /var/lib/dyngo.
 
 
diff --git a/dyngo b/dyngo
old mode 100644 (file)
new mode 100755 (executable)
index 431e8cf..0b02240
--- a/dyngo
+++ b/dyngo
@@ -1,6 +1,8 @@
 #!/usr/bin/env python3
 import ipaddress, subprocess, sys, getopt,dbm
 from configparser import ConfigParser
+import urllib.request,urllib.parse
+import logging,os.path,time
 class DyndnsNetwork(object):
     """
     This class represents dynamic DNS server and network.
@@ -10,7 +12,7 @@ class DyndnsNetwork(object):
         self.hostname = config_section['hostname']
         self.network = ipaddress.ip_network(config_section['network'])
         self.server = config_section['server']
-        if user in config_section:
+        if 'user' in config_section:
             self.user=config_section["user"]
             self.password=config_section["password"]
 
@@ -21,11 +23,30 @@ class DyndnsNetwork(object):
         if self.network.version != address.version:
             return False
         return address in self.network
-   def nsupdate(self, address):
-        raise NotImplementedError
+    def nsupdate(self, address):
+        """
+        Sends a get query to specified server.
+        Raises HTTPError if something goes wrong
+        """
+        #construct opener
+        logging.debug("Going to send message to %s",self.server)
+        if hasattr(self,'user'):
+            password_mgr =urllib.request.HTTPPasswordMgrWithDefaultRealm()
+            password_mgr.add_password(None,self.server,self.user,self.password)
+            handler=urllib.request.HTTPBasicAuthHandler(password_mgr)
+            opener = urllib.request.build_opener(handler)
+        else:
+            opener = urllib.request.build_opener()
+        with opener.open('%s?%s'%(self.server,
+                     urllib.parse.urlencode({'hostname':self.hostname,
+                     'myip':str(address)}))) as req:
+               req.read().decode("utf-8")
+               
+           
 
 def get_current_addresses():
     result=[]    
+    logging.debug("Acquire current IPs")
     for line in subprocess.run(['ip','-o','addr','show'],check=True,stdout=subprocess.PIPE).stdout.decode('utf-8').split("\n"):
         if not len(line): 
             continue
@@ -34,6 +55,7 @@ def get_current_addresses():
         if address.is_loopback or address.is_link_local:
             continue
         result.append(address)
+    logging.debug("Got following addresses: %s",repr(result))
     return result
 def check_for_update():
     addrlist=get_current_addresses()
@@ -46,40 +68,64 @@ def check_for_update():
                     old_addr=ipaddress.ip_address(database[name].decode("utf-8"))
                     if old_addr==a:
                         # Nothing changed go, to next net
+                        logging.debug("Address for net %s not changed",name)
                         break
                 # address changed
-                net.nsupdate(a)
-                database[name]=str(a)
+                try:
+                     logging.info("Doing update for net %s with address %s",name, str(a))
+                     net.nsupdate(a)
+                     database[name]=str(a)
+                except urllib.error.HTTPError as e:
+                    logging.exception("Http error doing nsupdate code %s",e.code)
+                break     
         if not found:
+            logging.info("Address for net %s no more found",name)
             del data[name]
 
-            
-
-
-config=ConfigPaser()
-config['dyngo']={'interval':'60','database','/var/lib/dyngo/dyngo.db'}
+config=ConfigParser()
+config['dyngo']={'interval':'60','database':'/var/lib/dyngo/dyngo.db',
+                 'ca':'/etc/ssl/certs','loglevel':'WARNING'}
 options=dict(getopt.getopt(sys.argv,"f:")[0])
 if not '-f' in options:
     options["-f"]="/etc/dyngo.conf"
 if len(config.read(options["-f"]))!=1:
     print("Cannot read config %s"%options["-f"],file=sys.stderr)
     sys.exit(1)
-    
 conf=config['dyngo']
+if conf['loglevel'].isdigit():
+    level=int(conf['loglevel'])
+else:
+    try:
+        logging.NOTICE=25
+        level=getattr(logging,conf['loglevel'].upper())
+    except AttributeError:
+        print("Invalid logleevel '%s'"%conf('loglevel'))
+        sys.exit(1)
+
+logging.basicConfig(format="%(asctime)s %(message)s",
+      level=level, stream=sys.stderr)
 interval=int(conf['interval'])
 database=dbm.open(conf['database'],"c")
+https_params={}
+if 'ca' in conf and len(conf['ca']):
+    path=conf['ca']
+    if os.path.isdir(path):
+        https_params['capath']=path
+    else:
+        http_params['cafile']=path
 # Convert all other config sections to DyndnsNetwork objects
 networks={}
 for sect in config.sections():
-    if sect == 'dyngo' or sect= 'DEFAULT':
+    if sect == 'dyngo' or sect == 'DEFAULT':
         continue
+    logging.debug("Processing network %s",sect)    
     networks[sect]=DyndnsNetwork(config[sect])
 # Remove stale items from persistent database, which are no more
 # mentioned in the config file
-for i in set([x.decode("utf-8") for x database.keys()])-set(network.keys()):
+for i in set([x.decode("utf-8") for x in database.keys()])-set(networks.keys()):
+    logging.info("Removing from persistent state network %s which is no more  in config",i)
     del database[i]
 
-
 while True:
     check_for_update()
     time.sleep(interval)
index d7ed659c9e95361b84b4d6185d2537b0c0f0597f..94e58ffabe5e19a820ec21c5015237ceee2ccf7a 100644 (file)
--- a/dyngo.md
+++ b/dyngo.md
@@ -40,6 +40,25 @@ Configuration file **dyngo.conf** is ini-style file. It contains section
 Names of server-description sections are arbitrary, but should be
 unique, because they are used as keys into persistent database.
 
-
+Parameters of **dyngo** section
+-------------------------------
+
+* interval - number of seconds between rescans of network interfaces
+* database - path to persistent state database
+* ca - path to trusted certificate store in openssl compatible format.
+* loglevel - minimum log message level which shoud to console
+
+Parameters of network section
+-----------------------------
+
+* **hostname** - fully qualified domain name of your host to register in DNS
+* **network** - network in *address*/*bits* notation which this host
+should belong to. If we see address from this network on one of our
+interfaces, we would send request to  corresponding server. If net is
+not privatte, but include private ranges, i.e. 0.0.0.0/0 or ::/0 private
+addresses are not considered part of it.
+* **server** - full url (without query string) of the dyndns web  handler.
+* **user** - user name for HTTP basic authentication
+* **password** - password for HTTP basic authentcircation
 
 
diff --git a/dyngo.service b/dyngo.service
new file mode 100644 (file)
index 0000000..5177ad1
--- /dev/null
@@ -0,0 +1,14 @@
+[Unit]
+Description=Dyngo multiple dyndns client
+After=network.target
+Documentation=https://github.com/vbwagner/dyngo
+
+[Service]
+Type=simple
+ExecStart=/usr/local/sbin/dyngo
+KillSignal=SIGTERM
+WorkingDirectory=~
+User=dyngo
+StandardError=journal
+[Install]
+WantedBy=multi-user.target