From 0db16978980b0832b515af2198cccf83088a042c Mon Sep 17 00:00:00 2001 From: Fabian Faessler Date: Sun, 29 Sep 2013 20:39:07 +0200 Subject: [PATCH 01/11] added ns query call to get the IP from hosts --- nsupdate/main/models.py | 6 ++++++ nsupdate/main/templates/main/host.html | 12 ++++++++++++ nsupdate/main/templates/main/overview.html | 6 ++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/nsupdate/main/models.py b/nsupdate/main/models.py index 0d6aec4..a9492c7 100644 --- a/nsupdate/main/models.py +++ b/nsupdate/main/models.py @@ -78,6 +78,12 @@ class Host(models.Model): return Host.objects.filter( subdomain=splitted[0], domain__domain=splitted[1], **kwargs) + def getIPv4(self): + return dnstools.query(self.get_fqdn(), 'A') + + def getIPv6(self): + return dnstools.query(self.get_fqdn(), 'AAAA') + def generate_secret(self): # note: we use a quick hasher for the update_secret as expensive # more modern hashes might put too much load on the servers. also diff --git a/nsupdate/main/templates/main/host.html b/nsupdate/main/templates/main/host.html index 0952e77..6f94a50 100644 --- a/nsupdate/main/templates/main/host.html +++ b/nsupdate/main/templates/main/host.html @@ -34,6 +34,18 @@ +
+
+

General Information

+ your IPv4: {{ request.session.ipv4 }}
+ your IPv6: {{ request.session.ipv6 }} +
+
+

Host Information

+ Host IPv4: {{ host.getIPv4 }}
+ Host IPv6: {{ host.getIPv6 }} +
+
{% endblock %} diff --git a/nsupdate/main/templates/main/overview.html b/nsupdate/main/templates/main/overview.html index 3d7249f..8351f02 100644 --- a/nsupdate/main/templates/main/overview.html +++ b/nsupdate/main/templates/main/overview.html @@ -6,11 +6,13 @@

Host List

- + {% for host in hosts %} - + + +
domainlast updatecommentaction
domainlast updateIPv4IPv6commentaction
{{ host.subdomain }}.{{ host.domain.domain }}{{ host.last_update|date }}{{ host.last_update|timesince }}{{ host.getIPv4 }}{{ host.getIPv6 }} {{ host.comment }} edit | From a4e3ab4e588d456ff4bdaa0b09c50339c3397b1b Mon Sep 17 00:00:00 2001 From: Fabian Faessler Date: Sun, 29 Sep 2013 20:43:48 +0200 Subject: [PATCH 02/11] fixed uncaptured exceptions NXDOMAIN. increased table size in the overview --- nsupdate/main/models.py | 11 +++++++++-- nsupdate/main/templates/main/overview.html | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/nsupdate/main/models.py b/nsupdate/main/models.py index a9492c7..6adc8d7 100644 --- a/nsupdate/main/models.py +++ b/nsupdate/main/models.py @@ -6,6 +6,7 @@ from django.conf import settings from django.db.models.signals import post_delete from django.contrib.auth.hashers import make_password from main import dnstools +import dns.resolver import re @@ -79,10 +80,16 @@ class Host(models.Model): subdomain=splitted[0], domain__domain=splitted[1], **kwargs) def getIPv4(self): - return dnstools.query(self.get_fqdn(), 'A') + try: + return dnstools.query_ns(self.get_fqdn(), 'A') + except dns.resolver.NXDOMAIN: + return '' def getIPv6(self): - return dnstools.query(self.get_fqdn(), 'AAAA') + try: + return dnstools.query_ns(self.get_fqdn(), 'A') + except dns.resolver.NXDOMAIN: + return '' def generate_secret(self): # note: we use a quick hasher for the update_secret as expensive diff --git a/nsupdate/main/templates/main/overview.html b/nsupdate/main/templates/main/overview.html index 8351f02..8deccbb 100644 --- a/nsupdate/main/templates/main/overview.html +++ b/nsupdate/main/templates/main/overview.html @@ -3,7 +3,7 @@ {% block content %}
-
+

Host List

@@ -24,7 +24,7 @@ {% endfor %}
domainlast updateIPv4IPv6commentaction
-
+

New Host

{% csrf_token %} From e1cad04b1e83f5ab89cbc4fbfe410490c69bdaee Mon Sep 17 00:00:00 2001 From: Fabian Faessler Date: Sun, 29 Sep 2013 20:45:55 +0200 Subject: [PATCH 03/11] fixed IPv6 display in the overview table --- nsupdate/main/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nsupdate/main/models.py b/nsupdate/main/models.py index 6adc8d7..caeeefc 100644 --- a/nsupdate/main/models.py +++ b/nsupdate/main/models.py @@ -87,7 +87,7 @@ class Host(models.Model): def getIPv6(self): try: - return dnstools.query_ns(self.get_fqdn(), 'A') + return dnstools.query_ns(self.get_fqdn(), 'AAAA') except dns.resolver.NXDOMAIN: return '' From 33d5475965219591f222ba1813885cdd3b751a16 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 29 Sep 2013 20:39:11 +0200 Subject: [PATCH 04/11] create docs/examples/ directory, add configuration for bind9 --- docs/examples/bind9-snippet | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/examples/bind9-snippet diff --git a/docs/examples/bind9-snippet b/docs/examples/bind9-snippet new file mode 100644 index 0000000..f73c270 --- /dev/null +++ b/docs/examples/bind9-snippet @@ -0,0 +1,18 @@ +// configuration snippet for bind 9 nameserver (put it into /etc/bind9/named.conf ) + +key "nsupdate.info." { + // must be same algorithm as in the nsupdate/nsupdate/settings.py + algorithm hmac-sha512; + // the secret is just a shared secret in base64-encoding, you don't need + // to use a special tool to create it. Some random in base64 encoding should + // be OK. + secret "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYQ=="; +}; + +zone nsupdate.info { + type master; + // bind9 needs write permissions into that directory and into that file: + file "/etc/bind/zones/nsupdate.info"; + // everyone who has that key may update this zone: + allow-update { key "nsupdate.info."; }; +}; From 3fa09acc23640626d086d2a72754831f225d6d91 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 29 Sep 2013 20:43:21 +0200 Subject: [PATCH 05/11] add nsupdate.info zonefile for bind9 --- docs/examples/bind9-nsupdate.info-zonefile | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 docs/examples/bind9-nsupdate.info-zonefile diff --git a/docs/examples/bind9-nsupdate.info-zonefile b/docs/examples/bind9-nsupdate.info-zonefile new file mode 100644 index 0000000..189db3f --- /dev/null +++ b/docs/examples/bind9-nsupdate.info-zonefile @@ -0,0 +1,20 @@ +$ORIGIN . +$TTL 3600 ; 1 hour +nsupdate.info IN SOA ns1.thinkmo.de. root.thinkmo.de. ( + 2013093117 ; serial + 7200 ; refresh (2 hours) + 1800 ; retry (30 minutes) + 604800 ; expire (1 week) + 60 ; minimum (1 minute) + ) + NS ns1.thinkmo.de. + NS ns3.thinkmo.de. + A 178.32.221.14 + AAAA 2001:41d0:8:e00e::1 + MX 10 mx.thinkmo.de. +$ORIGIN nsupdate.info. +$TTL 3600 ; 1 hour +www A 178.32.221.14 + AAAA 2001:41d0:8:e00e::1 +ipv4 A 178.32.221.14 +ipv6 AAAA 2001:41d0:8:e00e::1 From 053f2f9a65361a7c2111f40c295ed5a7f26f7840 Mon Sep 17 00:00:00 2001 From: Fabian Faessler Date: Sun, 29 Sep 2013 20:50:33 +0200 Subject: [PATCH 06/11] added last_api_update field and migrated db --- .../0009_added_last_api_update_field.py | 89 +++++++++++++++++++ nsupdate/main/models.py | 1 + 2 files changed, 90 insertions(+) create mode 100644 nsupdate/main/migrations/0009_added_last_api_update_field.py diff --git a/nsupdate/main/migrations/0009_added_last_api_update_field.py b/nsupdate/main/migrations/0009_added_last_api_update_field.py new file mode 100644 index 0000000..87e7c3c --- /dev/null +++ b/nsupdate/main/migrations/0009_added_last_api_update_field.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Host.last_api_update' + db.add_column(u'main_host', 'last_api_update', + self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Host.last_api_update' + db.delete_column(u'main_host', 'last_api_update') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'main.blacklisteddomain': { + 'Meta': {'object_name': 'BlacklistedDomain'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + u'main.domain': { + 'Meta': {'object_name': 'Domain'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + u'main.host': { + 'Meta': {'unique_together': "(('subdomain', 'domain'),)", 'object_name': 'Host'}, + 'comment': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': u"orm['auth.User']"}), + 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['main.Domain']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_api_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'subdomain': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'update_secret': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + } + } + + complete_apps = ['main'] \ No newline at end of file diff --git a/nsupdate/main/models.py b/nsupdate/main/models.py index caeeefc..fde541b 100644 --- a/nsupdate/main/models.py +++ b/nsupdate/main/models.py @@ -56,6 +56,7 @@ class Host(models.Model): max_length=256, default='', blank=True, null=True) last_update = models.DateTimeField(auto_now=True) + last_api_update = models.DateTimeField(blank=True, null=True) created = models.DateTimeField(auto_now_add=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, related_name='hosts') From beafe06852dc49d1cbadd65dfb9ac85faac0f4ad Mon Sep 17 00:00:00 2001 From: Fabian Faessler Date: Sun, 29 Sep 2013 21:00:08 +0200 Subject: [PATCH 07/11] added logic for last_api_update. field updated with Hosts.poke() method --- nsupdate/api/views.py | 8 ++++++++ nsupdate/main/models.py | 13 +++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/nsupdate/api/views.py b/nsupdate/api/views.py index 59bcac0..2ea1d70 100644 --- a/nsupdate/api/views.py +++ b/nsupdate/api/views.py @@ -198,6 +198,14 @@ def AuthorizedNicUpdateView(request): def _update(hostname, ipaddr): ipaddr = str(ipaddr) # XXX bug in dnspython: crashes if ipaddr is unicode, wants a str! + hosts = Host.filter_by_fqdn(hostname) + num_hosts = len(hosts) + if num_hosts == 0: + return False + if num_hosts > 1: + logging.error("fqdn %s has multiple entries" % hostname) + return False + hosts[0].poke() try: update(hostname, ipaddr) logger.info('%s - received good update -> ip: %s' % (hostname, ipaddr, )) diff --git a/nsupdate/main/models.py b/nsupdate/main/models.py index fde541b..25e80b9 100644 --- a/nsupdate/main/models.py +++ b/nsupdate/main/models.py @@ -7,6 +7,7 @@ from django.db.models.signals import post_delete from django.contrib.auth.hashers import make_password from main import dnstools import dns.resolver +from datetime import datetime import re @@ -83,14 +84,18 @@ class Host(models.Model): def getIPv4(self): try: return dnstools.query_ns(self.get_fqdn(), 'A') - except dns.resolver.NXDOMAIN: - return '' + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + return '-' def getIPv6(self): try: return dnstools.query_ns(self.get_fqdn(), 'AAAA') - except dns.resolver.NXDOMAIN: - return '' + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + return '-' + + def poke(self): + self.last_api_update = datetime.now() + self.save() def generate_secret(self): # note: we use a quick hasher for the update_secret as expensive From 17d4aade4dd7a6074394df4cc8b1cd9bcfb64bcf Mon Sep 17 00:00:00 2001 From: Fabian Faessler Date: Sun, 29 Sep 2013 21:05:46 +0200 Subject: [PATCH 08/11] some UI changes --- nsupdate/main/templates/main/host.html | 7 +++++-- nsupdate/main/templates/main/overview.html | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/nsupdate/main/templates/main/host.html b/nsupdate/main/templates/main/host.html index 6f94a50..7da0035 100644 --- a/nsupdate/main/templates/main/host.html +++ b/nsupdate/main/templates/main/host.html @@ -34,14 +34,17 @@
+
-
+

General Information

+

We try to get your IP addresses through a trick. We host two fake images on a IPv4 and IPv6 domain and we can associate those with your session. If we find an IP address, we will update this information accordingly. This allows us to get your IPv4 address even when you are using your IPv6 and vice versa.

your IPv4: {{ request.session.ipv4 }}
your IPv6: {{ request.session.ipv6 }}
-
+

Host Information

+

This information comes directly from the name server and shows the current IPs set for your domain.

Host IPv4: {{ host.getIPv4 }}
Host IPv6: {{ host.getIPv6 }}
diff --git a/nsupdate/main/templates/main/overview.html b/nsupdate/main/templates/main/overview.html index 8deccbb..c96a84e 100644 --- a/nsupdate/main/templates/main/overview.html +++ b/nsupdate/main/templates/main/overview.html @@ -33,9 +33,11 @@
+
-
+

Information

+

We try to get your IP addresses through a trick. We host two fake images on a IPv4 and IPv6 domain and we can associate those with your session. If we find an IP address, we will update this information accordingly. This allows us to get your IPv4 address even when you are using your IPv6 and vice versa.

your IPv4: {{ request.session.ipv4 }}
your IPv6: {{ request.session.ipv6 }}
From 5f0266a211518155384c3148dd548eabba404aee Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 29 Sep 2013 21:06:11 +0200 Subject: [PATCH 09/11] serve /robots.txt --- nsupdate/main/urls.py | 4 +++- nsupdate/main/views.py | 20 +++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/nsupdate/main/urls.py b/nsupdate/main/urls.py index 2cf3e2a..5366158 100644 --- a/nsupdate/main/urls.py +++ b/nsupdate/main/urls.py @@ -1,7 +1,8 @@ from django.conf.urls import patterns, url from django.views.generic import TemplateView from main.views import ( - HomeView, OverviewView, HostView, DeleteHostView, AboutView, HelpView, GenerateSecretView) + HomeView, OverviewView, HostView, DeleteHostView, AboutView, HelpView, GenerateSecretView, + RobotsTxtView, ) from api.views import ( MyIpView, DetectIpView, NicUpdateView, AuthorizedNicUpdateView) @@ -23,4 +24,5 @@ urlpatterns = patterns( url(r'^nic/update$', NicUpdateView), url(r'^nic/update_authorized$', AuthorizedNicUpdateView, name='nic_update_authorized'), + url(r'^robots.txt$', RobotsTxtView), ) diff --git a/nsupdate/main/views.py b/nsupdate/main/views.py index ebe5710..b83f897 100644 --- a/nsupdate/main/views.py +++ b/nsupdate/main/views.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from django.views.generic import TemplateView, CreateView from django.views.generic.edit import UpdateView, DeleteView -from django.http import HttpResponseRedirect +from django.http import HttpResponse, HttpResponseRedirect from django.contrib.auth.decorators import login_required from django.contrib import messages from django.utils.decorators import method_decorator @@ -152,3 +152,21 @@ class DeleteHostView(DeleteView): context['nav_overview'] = True context['hosts'] = Host.objects.filter(created_by=self.request.user) return context + + +def RobotsTxtView(request): + """ + Dynamically serve robots.txt content. + If you like, you can optimize this by statically serving this by your web server. + + :param request: django request object + :return: HttpResponse object + """ + content = """\ +User-agent: * +Crawl-delay: 10 +Disallow: /accounts/ +Disallow: /myip/ +Disallow: /nic/update/ +""" + return HttpResponse(content, content_type="text/plain") From d2caeab8dfb27bed788d41457d6be4380294a03b Mon Sep 17 00:00:00 2001 From: Fabian Faessler Date: Sun, 29 Sep 2013 21:07:30 +0200 Subject: [PATCH 10/11] host overview table change --- nsupdate/main/templates/main/overview.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nsupdate/main/templates/main/overview.html b/nsupdate/main/templates/main/overview.html index c96a84e..506a7d7 100644 --- a/nsupdate/main/templates/main/overview.html +++ b/nsupdate/main/templates/main/overview.html @@ -6,7 +6,7 @@

Host List

- + {% for host in hosts %} From 18770dad4f1232f80eb380af8d3f8a785414707a Mon Sep 17 00:00:00 2001 From: Fabian Faessler Date: Sun, 29 Sep 2013 21:13:20 +0200 Subject: [PATCH 11/11] call dnstools.add once at create and set to remoteaddr --- nsupdate/main/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nsupdate/main/views.py b/nsupdate/main/views.py index ebe5710..55a3b09 100644 --- a/nsupdate/main/views.py +++ b/nsupdate/main/views.py @@ -8,6 +8,7 @@ from django.utils.decorators import method_decorator from django.core.urlresolvers import reverse from django.core.exceptions import PermissionDenied import dns.inet +import dnstools from main.forms import CreateHostForm, EditHostForm from main.models import Host @@ -88,6 +89,7 @@ class OverviewView(CreateView): self.object = form.save(commit=False) self.object.created_by = self.request.user self.object.save() + dnstools.add(self.object.get_fqdn(), self.request.META['REMOTE_ADDR']) messages.add_message(self.request, messages.SUCCESS, 'Host added.') return HttpResponseRedirect(self.get_success_url())
domainlast updateIPv4IPv6commentaction
domainlast IP updateIPv4IPv6commentaction
{{ host.subdomain }}.{{ host.domain.domain }}