Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit 861dd7a1d491439a8f36ca7125d1803522e4e898 @atdt committed Dec 20, 2011
Showing with 195 additions and 0 deletions.
  1. +13 −0 LICENSE
  2. +173 −0 afraid.py
  3. +9 −0 setup.py
13 LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2011, Ori Livneh <ori.livneh@gmail.com>
+
+Permission to use, copy, modify, and/or 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.
173 afraid.py
@@ -0,0 +1,173 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ afraid
+
+ A simple client for freedns.afraid.org's dynamic DNS service.
+
+ :copyright: (c) 2011 by Ori Livneh <ori.livneh@gmail.com>
+ :license: ICT, see LICENSE for more details.
+"""
+
+# usage: afraid.py [-h] [--daemonize] [--log file] [--interval seconds]
+# user password [hosts [hosts ...]]
+#
+# afraid.org dyndns client
+#
+# positional arguments:
+# user
+# password
+# hosts (deafult: all associated hosts)
+#
+# optional arguments:
+# -h, --help show this help message and exit
+# --daemonize, -d run in background (default: no)
+# --log file log to file (default: log to stdout)
+# --interval seconds update interval, in seconds (default: 600)
+
+import argparse
+import collections
+import hashlib
+import json
+import logging
+import re
+import sys
+import time
+
+import daemon
+import requests
+
+
+http = requests.session(config={'verbose': sys.stderr}, timeout=2)
+ip_pattern = re.compile(r'[0-9]+(?:\.[0-9]+){3}')
+RequestException = requests.RequestException
+
+
+class ApiError(Exception):
+ pass
+
+
+class DnsRecord(object):
+
+ def __hash__(self):
+ return hash(self.hostname)
+
+ def __cmp__(self, other):
+ return self.hostname.__cmp__(getattr(other, 'hostname', None))
+
+ def __init__(self, hostname, ip, update_url):
+ self.hostname = hostname
+ self.ip = ip
+ self.update_url = update_url
+
+ def __repr__(self):
+ return '<DnsRecord: {.hostname}>'.format(self)
+
+ def update(self):
+ response = http.get(self.update_url)
+ match = ip_pattern.search(response.content)
+
+ # response must contain an ip address, or else we can't parse it
+ if not match:
+ raise ApiError("Couldn't parse the server's response",
+ response.content)
+
+ self.ip = match.group(0)
+
+
+def get_auth_key(*credentials):
+ auth_string = '|'.join(credentials)
+ return hashlib.sha1(auth_string).hexdigest()
+
+
+def get_dyndns_records(login, password):
+ params = dict(action='getdyndns', sha=get_auth_key(login, password))
+ response = http.get('http://freedns.afraid.org/api/', params=params)
+ raw_records = (line.split('|') for line in response.content.split())
+
+ try:
+ records = frozenset(DnsRecord(*record) for record in raw_records)
+ except TypeError:
+ raise ApiError("Couldn't parse the server's response",
+ response.content)
+
+ return records
+
+
+def update_continuously(records, update_interval=600):
+ while True:
+ for record in records:
+ try:
+ record.update()
+ except (ApiError, RequestException) as e:
+ pass
+ time.sleep(update_interval)
+
+
+def main():
+
+ # ------------------------------------------------- command-line arguments
+
+ parser = argparse.ArgumentParser(description='afraid.org dyndns client')
+
+ ## positional arguments
+
+ parser.add_argument('user')
+ parser.add_argument('password')
+ parser.add_argument('hosts',
+ nargs='*',
+ help='(deafult: all associated hosts)',
+ default=None
+ )
+
+ ## optional arguments
+
+ # should we fork?
+ parser.add_argument('--daemonize', '-d',
+ action='store_true',
+ default=False,
+ help='run in background (default: no)',
+ )
+
+ # log to a file or stdout
+ parser.add_argument('--log',
+ help='log to file (default: log to stdout)',
+ type=argparse.FileType('w'),
+ default=sys.stdout,
+ metavar='file'
+ )
+
+ # how long to sleep between updates
+ parser.add_argument('--interval',
+ help='update interval, in seconds (default: 600)',
+ metavar='seconds',
+ default=600,
+ type=int
+ )
+
+ options = parser.parse_args()
+
+ # configure logging
+ logging.basicConfig(
+ stream=options.log,
+ level=logging.INFO,
+ format='%(asctime)s %(message)s',
+ datefmt='%Y-%m-%dT%H:%M:%S'
+ )
+
+ records = get_dyndns_records(options.user, options.password)
+ if options.hosts:
+ records = frozenset(record for record in records
+ if record.hostname in options.hosts)
+
+ # fork & run in background
+ if options.daemonize:
+ with daemon.DaemonContext():
+ update_continuously(records, options.interval)
+
+ else:
+ update_continuously(records, options.interval)
+
+
+if __name__ == '__main__':
+ main()
@@ -0,0 +1,9 @@
+from distutils.core import setup
+
+setup(
+ name='afraid',
+ version='0.1-dev',
+ license='ISC',
+ packages=['afraid',],
+ description='A simple client for the afraid.org dynamic DNS service',
+)

0 comments on commit 861dd7a

Please sign in to comment.