aboutsummaryrefslogtreecommitdiff
path: root/rpki/gui
diff options
context:
space:
mode:
Diffstat (limited to 'rpki/gui')
-rw-r--r--rpki/gui/app/check_expired.py43
-rw-r--r--rpki/gui/app/forms.py2
-rw-r--r--rpki/gui/app/glue.py75
-rw-r--r--rpki/gui/app/models.py37
-rwxr-xr-xrpki/gui/app/range_list.py2
-rw-r--r--rpki/gui/app/templates/app/roarequest_confirm_multi_form.html37
-rw-r--r--rpki/gui/app/templates/app/roarequest_multi_form.html11
-rw-r--r--rpki/gui/app/templates/app/route_detail.html8
-rw-r--r--rpki/gui/app/urls.py10
-rw-r--r--rpki/gui/app/views.py87
-rw-r--r--rpki/gui/cacheview/models.py8
-rw-r--r--rpki/gui/cacheview/tests.py1
-rw-r--r--rpki/gui/cacheview/util.py3
-rw-r--r--rpki/gui/cacheview/views.py1
-rw-r--r--rpki/gui/decorators.py24
-rw-r--r--rpki/gui/default_settings.py182
-rw-r--r--rpki/gui/models.py4
-rw-r--r--rpki/gui/routeview/api.py2
-rw-r--r--rpki/gui/routeview/util.py288
-rw-r--r--rpki/gui/script_util.py61
20 files changed, 310 insertions, 576 deletions
diff --git a/rpki/gui/app/check_expired.py b/rpki/gui/app/check_expired.py
index a084af79..62292e66 100644
--- a/rpki/gui/app/check_expired.py
+++ b/rpki/gui/app/check_expired.py
@@ -25,9 +25,10 @@ from rpki.gui.cacheview.models import Cert
from rpki.gui.app.models import Conf, ResourceCert, Timestamp, Alert
from rpki.gui.app.glue import list_received_resources
from rpki.irdb import Zookeeper
-from rpki.left_right import report_error_elt, list_published_objects_elt
from rpki.x509 import X509
+from rpki.left_right import version, nsmap, tag_msg, tag_list_published_objects
+from lxml.etree import Element, SubElement
from django.core.mail import send_mail
logger = logging.getLogger(__name__)
@@ -41,8 +42,8 @@ def check_cert(handle, p, errs):
The displayed object name defaults to the class name, but can be overridden
using the `object_name` argument.
-
"""
+
t = p.certificate.getNotAfter()
if t <= expire_time:
e = 'expired' if t <= now else 'will expire'
@@ -102,30 +103,26 @@ def check_expire(conf, errs):
def check_child_certs(conf, errs):
"""Fetch the list of published objects from rpkid, and inspect the issued
resource certs (uri ending in .cer).
-
"""
+
z = Zookeeper(handle=conf.handle)
- req = list_published_objects_elt.make_pdu(action="list",
- tag="list_published_objects",
- self_handle=conf.handle)
+ req = Element(tag_msg, nsmap=nsmap, type="query", version=version)
+ SubElement(req, tag_list_published_objects,
+ tag="list_published_objects", self_handle=conf.handle)
pdus = z.call_rpkid(req)
for pdu in pdus:
- if isinstance(pdu, report_error_elt):
- logger.error("rpkid reported an error: %s", pdu.error_code)
- elif isinstance(pdu, list_published_objects_elt):
- if pdu.uri.endswith('.cer'):
- cert = X509()
- cert.set(Base64=pdu.obj)
- t = cert.getNotAfter()
- if t <= expire_time:
- e = 'expired' if t <= now else 'will expire'
- errs.write("%(handle)s's rescert for Child %(child)s %(expire)s on %(date)s uri=%(uri)s subject=%(subject)s\n" % {
- 'handle': conf.handle,
- 'child': pdu.child_handle,
- 'uri': pdu.uri,
- 'subject': cert.getSubject(),
- 'expire': e,
- 'date': t})
+ if pdu.get("uri").endswith('.cer'):
+ cert = X509(Base64=pdu.text)
+ t = cert.getNotAfter()
+ if t <= expire_time:
+ e = 'expired' if t <= now else 'will expire'
+ errs.write("%(handle)s's rescert for Child %(child)s %(expire)s on %(date)s uri=%(uri)s subject=%(subject)s\n" % {
+ 'handle': conf.handle,
+ 'child': pdu.get("child_handle"),
+ 'uri': pdu.get("uri"),
+ 'subject': cert.getSubject(),
+ 'expire': e,
+ 'date': t})
class NetworkError(Exception):
@@ -139,8 +136,8 @@ def notify_expired(expire_days=14, from_email=None):
expire_days: the number of days ahead of today to warn
from_email: set the From: address for the email
-
"""
+
global expire_time # so i don't have to pass it around
global now
diff --git a/rpki/gui/app/forms.py b/rpki/gui/app/forms.py
index a1214297..f173c15d 100644
--- a/rpki/gui/app/forms.py
+++ b/rpki/gui/app/forms.py
@@ -193,7 +193,7 @@ def ROARequestFormFactory(conf):
'class': 'span1'
})
)
- protect_children = forms.BooleanField(required=False)
+ confirmed = forms.BooleanField(widget=forms.HiddenInput, required=False)
def __init__(self, *args, **kwargs):
kwargs['auto_id'] = False
diff --git a/rpki/gui/app/glue.py b/rpki/gui/app/glue.py
index a2dddb51..bfade6d8 100644
--- a/rpki/gui/app/glue.py
+++ b/rpki/gui/app/glue.py
@@ -16,7 +16,6 @@
"""
This file contains code that interfaces between the django views implementing
the portal gui and the rpki.* modules.
-
"""
from __future__ import with_statement
@@ -28,17 +27,19 @@ from datetime import datetime
from rpki.resource_set import (resource_set_as, resource_set_ipv4,
resource_set_ipv6, resource_range_ipv4,
resource_range_ipv6)
-from rpki.left_right import list_received_resources_elt, report_error_elt
from rpki.irdb.zookeeper import Zookeeper
from rpki.gui.app import models
from rpki.exceptions import BadIPResource
+from rpki.left_right import nsmap, version, tag_msg, tag_list_received_resources
+from lxml.etree import Element, SubElement
from django.contrib.auth.models import User
from django.db.transaction import commit_on_success
def ghostbuster_to_vcard(gbr):
"""Convert a GhostbusterRequest object into a vCard object."""
+
import vobject
vcard = vobject.vCard()
@@ -66,18 +67,6 @@ def ghostbuster_to_vcard(gbr):
return vcard.serialize()
-class LeftRightError(Exception):
- """Class for wrapping report_error_elt errors from Zookeeper.call_rpkid().
-
- It expects a single argument, which is the associated report_error_elt instance."""
-
- def __str__(self):
- return 'Error occurred while communicating with rpkid: handle=%s code=%s text=%s' % (
- self.args[0].self_handle,
- self.args[0].error_code,
- self.args[0].error_text)
-
-
@commit_on_success
def list_received_resources(log, conf):
"""
@@ -86,11 +75,12 @@ def list_received_resources(log, conf):
The semantics are to clear the entire table and populate with the list of
certs received. Other models should not reference the table directly with
foreign keys.
-
"""
z = Zookeeper(handle=conf.handle, disable_signal_handlers=True)
- pdus = z.call_rpkid(list_received_resources_elt.make_pdu(self_handle=conf.handle))
+ req = Element(tag_msg, nsmap=nsmap, type="query", version=version)
+ SubElement(req, tag_list_received_resources, self_handle=conf.handle)
+ pdus = z.call_rpkid(req)
# pdus is sometimes None (see https://trac.rpki.net/ticket/681)
if pdus is None:
print >>log, 'error: call_rpkid() returned None for handle %s when fetching received resources' % conf.handle
@@ -99,34 +89,27 @@ def list_received_resources(log, conf):
models.ResourceCert.objects.filter(conf=conf).delete()
for pdu in pdus:
- if isinstance(pdu, report_error_elt):
- # this will cause the db to be rolled back so the above delete()
- # won't clobber existing resources
- raise LeftRightError(pdu)
- elif isinstance(pdu, list_received_resources_elt):
- if pdu.parent_handle != conf.handle:
- parent = models.Parent.objects.get(issuer=conf,
- handle=pdu.parent_handle)
- else:
- # root cert, self-signed
- parent = None
-
- not_before = datetime.strptime(pdu.notBefore, "%Y-%m-%dT%H:%M:%SZ")
- not_after = datetime.strptime(pdu.notAfter, "%Y-%m-%dT%H:%M:%SZ")
-
- cert = models.ResourceCert.objects.create(
- conf=conf, parent=parent, not_before=not_before,
- not_after=not_after, uri=pdu.uri)
-
- for asn in resource_set_as(pdu.asn):
- cert.asn_ranges.create(min=asn.min, max=asn.max)
-
- for rng in resource_set_ipv4(pdu.ipv4):
- cert.address_ranges.create(prefix_min=rng.min,
- prefix_max=rng.max)
-
- for rng in resource_set_ipv6(pdu.ipv6):
- cert.address_ranges_v6.create(prefix_min=rng.min,
- prefix_max=rng.max)
+ if pdu.get("parent_handle") != conf.handle:
+ parent = models.Parent.objects.get(issuer=conf,
+ handle=pdu.get("parent_handle"))
else:
- print >>log, "error: unexpected pdu from rpkid type=%s" % type(pdu)
+ # root cert, self-signed
+ parent = None
+
+ not_before = datetime.strptime(pdu.get("notBefore"), "%Y-%m-%dT%H:%M:%SZ")
+ not_after = datetime.strptime(pdu.get("notAfter"), "%Y-%m-%dT%H:%M:%SZ")
+
+ cert = models.ResourceCert.objects.create(
+ conf=conf, parent=parent, not_before=not_before,
+ not_after=not_after, uri=pdu.get("uri"))
+
+ for asn in resource_set_as(pdu.get("asn")):
+ cert.asn_ranges.create(min=asn.min, max=asn.max)
+
+ for rng in resource_set_ipv4(pdu.get("ipv4")):
+ cert.address_ranges.create(prefix_min=rng.min,
+ prefix_max=rng.max)
+
+ for rng in resource_set_ipv6(pdu.get("ipv6")):
+ cert.address_ranges_v6.create(prefix_min=rng.min,
+ prefix_max=rng.max)
diff --git a/rpki/gui/app/models.py b/rpki/gui/app/models.py
index 40bdbe2c..ed32e9d2 100644
--- a/rpki/gui/app/models.py
+++ b/rpki/gui/app/models.py
@@ -18,7 +18,6 @@ __version__ = '$Id$'
from django.db import models
from django.contrib.auth.models import User
from django.core.mail import send_mail
-from django.db.models import Q
import rpki.resource_set
import rpki.exceptions
@@ -66,15 +65,6 @@ class Child(rpki.irdb.models.Child):
proxy = True
verbose_name_plural = 'children'
- @property
- def routes(self):
- "Return a list of RouteOrigin objects (potentially) originated by this child."
- query = Q()
- for r in self.address_ranges.filter(version='IPv4'):
- rng = r.as_resource_range()
- query |= Q(prefix_min__gte=rng.min, prefix_max__lte=rng.max)
- return RouteOrigin.objects.filter(query)
-
class ChildASN(rpki.irdb.models.ChildASN):
"""Proxy model for irdb ChildASN."""
@@ -131,30 +121,17 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
def parents(self):
"""Simulates irdb.models.Parent.objects, but returns app.models.Parent
proxy objects.
-
"""
+
return Parent.objects.filter(issuer=self)
@property
def children(self):
"""Simulates irdb.models.Child.objects, but returns app.models.Child
proxy objects.
-
- When running rootd, we need to exclude the Child object for self.
-
"""
- return Child.objects.filter(issuer=self).exclude(handle=self.handle)
- @property
- def child_routes(self):
- """Return currently announced routes for prefixes covered by child
- sub-allocations.
- """
- query = Q()
- for pfx in ChildNet.objects.filter(child__issuer=self, version='IPv4'):
- rng = pfx.as_resource_range()
- query |= Q(prefix_min__gte=rng.min, prefix_max__lte=rng.max)
- return RouteOrigin.objects.filter(query)
+ return Child.objects.filter(issuer=self)
@property
def ghostbusters(self):
@@ -172,8 +149,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
def routes(self):
"""Return all IPv4 routes covered by RPKI certs issued to this resource
holder.
-
"""
+
# build a Q filter to select all RouteOrigin objects covered by
# prefixes in the resource holder's certificates
prefixes = ResourceRangeAddressV4.objects.filter(cert__conf=self)
@@ -190,8 +167,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
def routes_v6(self):
"""Return all IPv6 routes covered by RPKI certs issued to this resource
holder.
-
"""
+
# build a Q filter to select all RouteOrigin objects covered by
# prefixes in the resource holder's certificates
prefixes = ResourceRangeAddressV6.objects.filter(cert__conf=self)
@@ -206,6 +183,7 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
def send_alert(self, subject, message, from_email, severity=Alert.INFO):
"""Store an alert for this resource holder."""
+
self.alerts.create(subject=subject, text=message, severity=severity)
send_mail(
@@ -221,8 +199,8 @@ class Conf(rpki.irdb.models.ResourceHolderCA):
Contact emails are extract from any ghostbuster requests, and any
linked user accounts.
-
"""
+
notify_emails = [gbr.email_address for gbr in self.ghostbusters if gbr.email_address]
notify_emails.extend(
[acl.user.email for acl in ConfACL.objects.filter(conf=self) if acl.user.email]
@@ -247,7 +225,6 @@ class ResourceCert(models.Model):
"""Represents a resource certificate.
This model is used to cache the output of <list_received_resources/>.
-
"""
# Handle to which this cert was issued
@@ -275,6 +252,7 @@ class ResourceCert(models.Model):
def get_cert_chain(self):
"""Return a list containing the complete certificate chain for this
certificate."""
+
cert = self
x = [cert]
while cert.issuer:
@@ -448,7 +426,6 @@ class RouteOriginV6(rpki.gui.routeview.models.RouteOriginV6):
class ConfACL(models.Model):
"""Stores access control for which users are allowed to manage a given
resource handle.
-
"""
conf = models.ForeignKey(Conf)
diff --git a/rpki/gui/app/range_list.py b/rpki/gui/app/range_list.py
index 21fd1f29..5cb4f5e4 100755
--- a/rpki/gui/app/range_list.py
+++ b/rpki/gui/app/range_list.py
@@ -70,6 +70,7 @@ class RangeList(list):
def difference(self, other):
"""Return a RangeList object which contains ranges in this object which
are not in "other"."""
+
it = iter(other)
try:
@@ -85,6 +86,7 @@ class RangeList(list):
def V(v):
"""convert the integer value to the appropriate type for this
range"""
+
return x.__class__.datum_type(v)
try:
diff --git a/rpki/gui/app/templates/app/roarequest_confirm_multi_form.html b/rpki/gui/app/templates/app/roarequest_confirm_multi_form.html
index d1d8171f..4a06a4aa 100644
--- a/rpki/gui/app/templates/app/roarequest_confirm_multi_form.html
+++ b/rpki/gui/app/templates/app/roarequest_confirm_multi_form.html
@@ -1,4 +1,5 @@
{% extends "app/app_base.html" %}
+{% load url from future %}
{% load app_extras %}
{% block content %}
@@ -8,20 +9,17 @@
<div class='row-fluid'>
<div class='span6'>
- <div class='alert'>
- <strong>Please confirm</strong> that you would like to create the following ROA(s).
+ <div class='alert alert-block-message alert-warning'>
+ <p><strong>Please confirm</strong> that you would like to create the following ROA(s).
The accompanying table indicates how the validation status may change as a result.
</div>
<table class='table table-condensed table-striped'>
- <thead>
- <tr>
- <th>Prefix</th>
- <th>Max Length</th>
- <th>AS</th>
- </tr>
- </thead>
- <tbody>
+ <tr>
+ <th>Prefix</th>
+ <th>Max Length</th>
+ <th>AS</th>
+ </tr>
{% for roa in roas %}
<tr>
<td>{{ roa.prefix }}</td>
@@ -29,7 +27,6 @@
<td>{{ roa.asn }}</td>
</tr>
{% endfor %}
- </tbody>
</table>
<form method='POST' action='{% url "rpki.gui.app.views.roa_create_multi_confirm" %}'>
@@ -50,22 +47,18 @@
<h2>Matched Routes</h2>
<table class='table table-striped table-condensed'>
- <thead>
- <tr>
- <th>Prefix</th>
- <th>Origin AS</th>
- <th>Validation Status</th>
- </tr>
- </thead>
- <tbody>
+ <tr>
+ <th>Prefix</th>
+ <th>Origin AS</th>
+ <th>Validation Status</th>
+ </tr>
{% for r in routes %}
<tr>
- <td>{{ r.get_prefix_display }}</td>
- <td>{{ r.asn }}</td>
+ <td>{{ r.get_prefix_display }}</td>
+ <td>{{ r.asn }}</td>
<td>{% validity_label r.newstatus %}</td>
</tr>
{% endfor %}
- </tbody>
</table>
</div>
diff --git a/rpki/gui/app/templates/app/roarequest_multi_form.html b/rpki/gui/app/templates/app/roarequest_multi_form.html
index ed2135ed..0fbc49ae 100644
--- a/rpki/gui/app/templates/app/roarequest_multi_form.html
+++ b/rpki/gui/app/templates/app/roarequest_multi_form.html
@@ -1,28 +1,27 @@
{% extends "app/app_base.html" %}
+{% load url from future %}
{% block content %}
<div class='page-title'>
<h1>Create ROAs</h1>
</div>
-<form class='form-inline' method='POST' action='{{ request.get_full_path }}'>
+<form method='POST' action='{{ request.get_full_path }}'>
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
-
+ <div class="controls controls-row">
{{ form.prefix }}
{{ form.max_prefixlen }}
{{ form.asn }}
- <label class="checkbox" title='create additional ROAs for child routes'>{{ form.protect_children }} Protect children</label>
-
{# <label class="checkbox inline span1">{{ form.DELETE }} Delete</label> #}
{% if form.errors %}<span class="help-inline">{{ form.errors }}</span>{% endif %}
{% if form.non_field_errors %}<span class="help-inline">{{ form.non_field_errors }}</span>{% endif %}
-
+ </div>
{% endfor %}
<div class="form-actions">
- <button class='btn btn-primary' type='submit'>Preview</button>
+ <input class="btn" type="submit" value="Preview">
<a class="btn" href="{% url "rpki.gui.app.views.dashboard" %}">Cancel</a>
</div>
</form>
diff --git a/rpki/gui/app/templates/app/route_detail.html b/rpki/gui/app/templates/app/route_detail.html
index 8bd744df..84add4a8 100644
--- a/rpki/gui/app/templates/app/route_detail.html
+++ b/rpki/gui/app/templates/app/route_detail.html
@@ -42,15 +42,13 @@
</thead>
<tbody>
{% for pfx in roa_prefixes %}
- {% for roa in pfx.roas.all %}
<tr>
<td>{{ pfx.as_resource_range }}</td>
<td>{{ pfx.max_length }}</td>
- <td>{{ roa.asid }}</td>
- <td>{{ roa.not_after }}</td>
- <td>{{ roa.repo.uri }}</td>
+ <td>{{ pfx.roas.all.0.asid }}</td>
+ <td>{{ pfx.roas.all.0.not_after }}</td>
+ <td>{{ pfx.roas.all.0.repo.uri }}</td>
</tr>
- {% endfor %}
{% endfor %}
</tbody>
</table>
diff --git a/rpki/gui/app/urls.py b/rpki/gui/app/urls.py
index 81c9b127..f595ea8f 100644
--- a/rpki/gui/app/urls.py
+++ b/rpki/gui/app/urls.py
@@ -88,14 +88,12 @@ urlpatterns = patterns(
#{'post_reset_redirect' : '/user/password/reset/done/'},
{'extra_context': {'form_title': 'Password Reset'}},
name="password_reset"),
- url(r'^user/password/reset/done/$',
- 'django.contrib.auth.views.password_reset_done',
- name='password_reset_done'),
+ (r'^user/password/reset/done/$',
+ 'django.contrib.auth.views.password_reset_done'),
url(r'^user/password/reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
'django.contrib.auth.views.password_reset_confirm',
#{'post_reset_redirect' : '/user/password/done/'},
name="password_reset_confirm"),
- url(r'^user/password/done/$',
- 'django.contrib.auth.views.password_reset_complete',
- name='password_reset_complete'),
+ (r'^user/password/done/$',
+ 'django.contrib.auth.views.password_reset_complete'),
)
diff --git a/rpki/gui/app/views.py b/rpki/gui/app/views.py
index bf152f8e..dfd36dbb 100644
--- a/rpki/gui/app/views.py
+++ b/rpki/gui/app/views.py
@@ -27,7 +27,6 @@ from tempfile import NamedTemporaryFile
import cStringIO
import csv
import logging
-import lxml.etree
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
@@ -39,8 +38,8 @@ from django.contrib.auth.models import User
from django.views.generic import DetailView, ListView, DeleteView, FormView
from django.core.paginator import Paginator, InvalidPage
from django.forms.formsets import formset_factory, BaseFormSet
+import django.db.models
from django.contrib import messages
-from django.db.models import Q
from rpki.irdb import Zookeeper, ChildASN, ChildNet, ROARequestPrefix
from rpki.gui.app import models, forms, glue, range_list
@@ -147,28 +146,19 @@ def generic_import(request, queryset, configure, form_class=None,
# expects it.
if handle == '':
handle = None
- try:
- # configure_repository returns None, so can't use tuple expansion
- # here. Unpack the tuple below if post_import_redirect is None.
- r = configure(z, tmpf.name, handle)
- except lxml.etree.XMLSyntaxError as e:
- logger.exception('caught XMLSyntaxError while parsing uploaded file')
- messages.error(
- request,
- 'The uploaded file has an invalid XML syntax'
- )
+ # configure_repository returns None, so can't use tuple expansion
+ # here. Unpack the tuple below if post_import_redirect is None.
+ r = configure(z, tmpf.name, handle)
+ # force rpkid run now
+ z.synchronize_ca(poke=True)
+ os.remove(tmpf.name)
+ if post_import_redirect:
+ url = post_import_redirect
else:
- # force rpkid run now
- z.synchronize_ca(poke=True)
- if post_import_redirect:
- url = post_import_redirect
- else:
- _, handle = r
- url = queryset.get(issuer=conf,
- handle=handle).get_absolute_url()
- return http.HttpResponseRedirect(url)
- finally:
- os.remove(tmpf.name)
+ _, handle = r
+ url = queryset.get(issuer=conf,
+ handle=handle).get_absolute_url()
+ return http.HttpResponseRedirect(url)
else:
form = form_class()
@@ -632,6 +622,7 @@ def get_covered_routes(rng, max_prefixlen, asn):
return routes
+
@handle_required
def roa_create(request):
"""Present the user with a form to create a ROA.
@@ -717,58 +708,20 @@ def roa_create_multi(request):
formset = formset_factory(forms.ROARequestFormFactory(conf), extra=extra)(initial=init)
elif request.method == 'POST':
formset = formset_factory(forms.ROARequestFormFactory(conf), extra=0)(request.POST, request.FILES)
- # We need to check .has_changed() because .is_valid() will return true
- # if the user clicks the Preview button without filling in the blanks
- # in the ROA form, leaving the form invalid from this view's POV.
- if formset.has_changed() and formset.is_valid():
+ if formset.is_valid():
routes = []
v = []
- query = Q() # for matching routes
- roas = []
for form in formset:
asn = form.cleaned_data['asn']
rng = resource_range_ip.parse_str(form.cleaned_data['prefix'])
max_prefixlen = int(form.cleaned_data['max_prefixlen'])
- protect_children = form.cleaned_data['protect_children']
-
- roas.append((rng, max_prefixlen, asn, protect_children))
+ # FIXME: This won't do the right thing in the event that a
+ # route is covered by multiple ROAs created in the form.
+ # You will see duplicate entries, each with a potentially
+ # different validation status.
+ routes.extend(get_covered_routes(rng, max_prefixlen, asn))
v.append({'prefix': str(rng), 'max_prefixlen': max_prefixlen,
'asn': asn})
-
- query |= Q(prefix_min__gte=rng.min, prefix_max__lte=rng.max)
-
- for rt in RouteOrigin.objects.filter(query):
- status = rt.status # cache the value
- newstatus = status
- if status == 'unknown':
- # possible change to valid or invalid
- for rng, max_prefixlen, asn, protect in roas:
- if rng.min <= rt.prefix_min and rng.max >= rt.prefix_max:
- # this route is covered
- if asn == rt.asn and rt.prefixlen <= max_prefixlen:
- newstatus = 'valid'
- break # no need to continue for this route
- else:
- newstatus = 'invalid'
- elif status == 'invalid':
- # possible change to valid
- for rng, max_prefixlen, asn, protect in roas:
- if rng.min <= rt.prefix_min and rng.max >= rt.prefix_max:
- # this route is covered
- if asn == rt.asn and rt.prefixlen <= max_prefixlen:
- newstatus = 'valid'
- break # no need to continue for this route
-
- if status != newstatus:
- if protect_children and newstatus == 'invalid' and conf.child_routes.filter(pk=rt.pk).exists():
- rng = rt.as_resource_range()
- v.append({'prefix': str(rng),
- 'max_prefixlen': rng.prefixlen,
- 'asn': rt.asn})
- newstatus = 'valid'
- rt.newstatus = newstatus # I"M A MUHNKAY!!!
- routes.append(rt)
-
# if there were no rows, skip the confirmation step
if v:
formset = formset_factory(forms.ROARequestConfirm, extra=0)(initial=v)
diff --git a/rpki/gui/cacheview/models.py b/rpki/gui/cacheview/models.py
index c3ee8421..08acfa2d 100644
--- a/rpki/gui/cacheview/models.py
+++ b/rpki/gui/cacheview/models.py
@@ -58,6 +58,7 @@ class ValidationLabel(models.Model):
Represents a specific error condition defined in the rcynic XML
output file.
"""
+
label = models.CharField(max_length=79, db_index=True, unique=True)
status = models.CharField(max_length=255)
kind = models.PositiveSmallIntegerField(choices=kinds)
@@ -70,6 +71,7 @@ class RepositoryObject(models.Model):
"""
Represents a globally unique RPKI repository object, specified by its URI.
"""
+
uri = models.URLField(unique=True, db_index=True)
generations = list(enumerate(('current', 'backup')))
@@ -89,6 +91,7 @@ class SignedObject(models.Model):
The signing certificate is ommitted here in order to give a proper
value for the 'related_name' attribute.
"""
+
repo = models.ForeignKey(RepositoryObject, related_name='cert', unique=True)
# on-disk file modification time
@@ -108,6 +111,7 @@ class SignedObject(models.Model):
"""
convert the local timestamp to UTC and convert to a datetime object
"""
+
return datetime.utcfromtimestamp(self.mtime + time.timezone)
def status_id(self):
@@ -116,6 +120,7 @@ class SignedObject(models.Model):
The selector is chosen based on the current generation only. If there is any bad status,
return bad, else if there are any warn status, return warn, else return good.
"""
+
for x in reversed(kinds):
if self.repo.statuses.filter(generation=generations_dict['current'], status__kind=x[0]):
return x[1]
@@ -129,6 +134,7 @@ class Cert(SignedObject):
"""
Object representing a resource certificate.
"""
+
addresses = models.ManyToManyField(AddressRange, related_name='certs')
addresses_v6 = models.ManyToManyField(AddressRangeV6, related_name='certs')
asns = models.ManyToManyField(ASRange, related_name='certs')
@@ -141,6 +147,7 @@ class Cert(SignedObject):
def get_cert_chain(self):
"""Return a list containing the complete certificate chain for this
certificate."""
+
cert = self
x = [cert]
while cert != cert.issuer:
@@ -180,6 +187,7 @@ class ROAPrefixV4(ROAPrefix, rpki.gui.models.PrefixV4):
@property
def routes(self):
"""return all routes covered by this roa prefix"""
+
return RouteOrigin.objects.filter(prefix_min__gte=self.prefix_min,
prefix_max__lte=self.prefix_max)
diff --git a/rpki/gui/cacheview/tests.py b/rpki/gui/cacheview/tests.py
index 2247054b..daca07bf 100644
--- a/rpki/gui/cacheview/tests.py
+++ b/rpki/gui/cacheview/tests.py
@@ -12,6 +12,7 @@ class SimpleTest(TestCase):
"""
Tests that 1 + 1 always equals 2.
"""
+
self.failUnlessEqual(1 + 1, 2)
__test__ = {"doctest": """
diff --git a/rpki/gui/cacheview/util.py b/rpki/gui/cacheview/util.py
index 9e8748bf..31ad8b8b 100644
--- a/rpki/gui/cacheview/util.py
+++ b/rpki/gui/cacheview/util.py
@@ -310,8 +310,8 @@ def fetch_published_objects():
"""Query rpkid for all objects published by local users, and look up the
current validation status of each object. The validation status is used
later to send alerts for objects which have transitioned to invalid.
-
"""
+
logger.info('querying for published objects')
handles = [conf.handle for conf in Conf.objects.all()]
@@ -353,7 +353,6 @@ class Handle(object):
def notify_invalid():
"""Send email alerts to the addresses registered in ghostbuster records for
any invalid objects that were published by users of this system.
-
"""
logger.info('sending notifications for invalid objects')
diff --git a/rpki/gui/cacheview/views.py b/rpki/gui/cacheview/views.py
index 94870eb2..451c0d1e 100644
--- a/rpki/gui/cacheview/views.py
+++ b/rpki/gui/cacheview/views.py
@@ -29,6 +29,7 @@ def cert_chain(obj):
"""
returns an iterator covering all certs from the root cert down to the EE.
"""
+
chain = [obj]
while obj != obj.issuer:
obj = obj.issuer
diff --git a/rpki/gui/decorators.py b/rpki/gui/decorators.py
index b5c52afb..75efeae0 100644
--- a/rpki/gui/decorators.py
+++ b/rpki/gui/decorators.py
@@ -15,18 +15,24 @@
__version__ = '$Id$'
from django import http
-from django.conf import settings
+from os import getenv
-def tls_required(f):
- """Decorator which returns a 500 error if the connection is not secured
- with TLS (https).
+# Don't set this in production, ever. Really. You have been warned.
+#
+_allow_plain_http_for_testing = getenv("ALLOW_PLAIN_HTTP_FOR_TESTING") == "I solemnly swear that I am not running this in production"
+
+def tls_required(f):
+ """
+ Decorator which returns a 500 error if the connection is not
+ secured with TLS (https).
"""
+
def _tls_required(request, *args, **kwargs):
- if settings.DEBUG or request.is_secure():
- return f(request, *args, **kwargs)
- return http.HttpResponseServerError(
- 'This resource may only be accessed securely via https',
- content_type='text/plain')
+ if not request.is_secure() and not _allow_plain_http_for_testing:
+ return http.HttpResponseServerError(
+ 'This resource may only be accessed securely via https',
+ content_type='text/plain')
+ return f(request, *args, **kwargs)
return _tls_required
diff --git a/rpki/gui/default_settings.py b/rpki/gui/default_settings.py
deleted file mode 100644
index 02987bb8..00000000
--- a/rpki/gui/default_settings.py
+++ /dev/null
@@ -1,182 +0,0 @@
-"""
-This module contains static configuration settings for the web portal.
-"""
-
-__version__ = '$Id$'
-
-import os
-import random
-import string
-import socket
-
-import rpki.config
-import rpki.autoconf
-
-# Where to put static files.
-STATIC_ROOT = rpki.autoconf.datarootdir + '/rpki/media'
-
-# Must end with a slash!
-STATIC_URL = '/media/'
-
-# Where to email server errors.
-ADMINS = (('Administrator', 'root@localhost'),)
-
-LOGGING = {
- 'version': 1,
- 'formatters': {
- 'verbose': {
- # see http://docs.python.org/2.7/library/logging.html#logging.LogRecord
- 'format': '%(levelname)s %(asctime)s %(name)s %(message)s'
- },
- },
- 'handlers': {
- 'stderr': {
- 'class': 'logging.StreamHandler',
- 'level': 'DEBUG',
- 'formatter': 'verbose',
- },
- 'mail_admins': {
- 'level': 'ERROR',
- 'class': 'django.utils.log.AdminEmailHandler',
- },
- },
- 'loggers': {
- 'rpki.async': {
- # enabled for tracking https://trac.rpki.net/ticket/681
- # need to change this to WARNING once ticket is closed
- 'level': 'DEBUG',
- },
- # The Django default LOGGING configuration disables propagate on these
- # two loggers. Re-enable propagate so they will hit our root logger.
- 'django.request': {
- 'propagate': True,
- },
- 'django.security': {
- 'propagate': True,
- },
- },
- 'root': {
- 'level': 'WARNING',
- 'handlers': ['stderr', 'mail_admins'],
- },
-}
-
-# Load the SQL authentication bits from the system rpki.conf.
-rpki_config = rpki.config.parser(section='web_portal')
-
-DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.mysql',
- 'NAME': rpki_config.get('sql-database'),
- 'USER': rpki_config.get('sql-username'),
- 'PASSWORD': rpki_config.get('sql-password'),
-
- # Ensure the default storage engine is InnoDB since we need
- # foreign key support. The Django documentation suggests
- # removing this after the syncdb is performed as an optimization,
- # but there isn't an easy way to do this automatically.
-
- 'OPTIONS': {
- 'init_command': 'SET storage_engine=INNODB',
- }
- }
-}
-
-
-def select_tz():
- "Find a supported timezone that looks like UTC"
- for tz in ('UTC', 'GMT', 'Etc/UTC', 'Etc/GMT'):
- if os.path.exists('/usr/share/zoneinfo/' + tz):
- return tz
- # Can't determine the proper timezone, fall back to UTC and let Django
- # report the error to the user.
- return 'UTC'
-
-# Local time zone for this installation. Choices can be found here:
-# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
-# although not all choices may be available on all operating systems.
-# If running in a Windows environment this must be set to the same as your
-# system time zone.
-TIME_ZONE = select_tz()
-
-def get_secret_key():
- """Retrieve the secret-key value from rpki.conf or generate a random value
- if it is not present."""
- d = string.letters + string.digits
- val = ''.join([random.choice(d) for _ in range(50)])
- return rpki_config.get('secret-key', val)
-
-# Make this unique, and don't share it with anybody.
-SECRET_KEY = get_secret_key()
-
-# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
-# for details on why you might need this.
-def get_allowed_hosts():
- allowed_hosts = set(rpki_config.multiget("allowed-hosts"))
- allowed_hosts.add(socket.getfqdn())
- try:
- import netifaces
- for interface in netifaces.interfaces():
- addresses = netifaces.ifaddresses(interface)
- for af in (netifaces.AF_INET, netifaces.AF_INET6):
- if af in addresses:
- for address in addresses[af]:
- if "addr" in address:
- allowed_hosts.add(address["addr"])
- except ImportError:
- pass
- return list(allowed_hosts)
-
-ALLOWED_HOSTS = get_allowed_hosts()
-
-DOWNLOAD_DIRECTORY = rpki_config.get('download-directory', '/var/tmp')
-
-# List of callables that know how to import templates from various sources.
-TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.Loader',
- 'django.template.loaders.app_directories.Loader',
- 'django.template.loaders.eggs.Loader'
-)
-
-MIDDLEWARE_CLASSES = (
- 'django.middleware.common.CommonMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware'
-)
-
-ROOT_URLCONF = 'rpki.gui.urls'
-
-INSTALLED_APPS = (
- 'django.contrib.auth',
- #'django.contrib.admin',
- #'django.contrib.admindocs',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.staticfiles',
- 'rpki.irdb',
- 'rpki.gui.app',
- 'rpki.gui.cacheview',
- 'rpki.gui.routeview',
- 'south',
-)
-
-TEMPLATE_CONTEXT_PROCESSORS = (
- "django.contrib.auth.context_processors.auth",
- "django.core.context_processors.debug",
- "django.core.context_processors.i18n",
- "django.core.context_processors.media",
- "django.contrib.messages.context_processors.messages",
- "django.core.context_processors.request",
- "django.core.context_processors.static"
-)
-
-# Allow local site to override any setting above -- but if there's
-# anything that local sites routinely need to modify, please consider
-# putting that configuration into rpki.conf and just adding code here
-# to read that configuration.
-try:
- from local_settings import *
-except:
- pass
diff --git a/rpki/gui/models.py b/rpki/gui/models.py
index 184383c0..62400d2a 100644
--- a/rpki/gui/models.py
+++ b/rpki/gui/models.py
@@ -42,8 +42,8 @@ class IPv6AddressField(models.Field):
"""
Note that we add a custom conversion to encode long values as hex
strings in SQL statements. See settings.get_conv() for details.
-
"""
+
return value.toBytes()
@@ -82,6 +82,7 @@ class Prefix(models.Model):
"""
Returns the prefix as a rpki.resource_set.resource_range_ip object.
"""
+
return self.range_cls(self.prefix_min, self.prefix_max)
@property
@@ -96,6 +97,7 @@ class Prefix(models.Model):
def __unicode__(self):
"""This method may be overridden by subclasses. The default
implementation calls get_prefix_display(). """
+
return self.get_prefix_display()
class Meta:
diff --git a/rpki/gui/routeview/api.py b/rpki/gui/routeview/api.py
index cf699c9a..b4ff297a 100644
--- a/rpki/gui/routeview/api.py
+++ b/rpki/gui/routeview/api.py
@@ -29,8 +29,8 @@ def route_list(request):
By default, only returns up to 10 matching routes, but the client may
request a different limit with the 'count=' query string parameter.
-
"""
+
hard_limit = 100
if request.method == 'GET' and 'prefix__in' in request.GET:
diff --git a/rpki/gui/routeview/util.py b/rpki/gui/routeview/util.py
index 1340e9fa..a2b515c8 100644
--- a/rpki/gui/routeview/util.py
+++ b/rpki/gui/routeview/util.py
@@ -22,11 +22,9 @@ import subprocess
import time
import logging
import urlparse
-import bz2
from urllib import urlretrieve, unquote
from django.db import transaction, connection
-from django.conf import settings
from rpki.resource_set import resource_range_ipv4, resource_range_ipv6
from rpki.exceptions import BadIPResource
@@ -38,118 +36,34 @@ logger = logging.getLogger(__name__)
# Eventually this can be retrived from rpki.conf
DEFAULT_URL = 'http://archive.routeviews.org/oix-route-views/oix-full-snapshot-latest.dat.bz2'
-class ParseError(Exception): pass
-
-class RouteDumpParser(object):
- """Base class for parsing various route dump formats."""
-
+def parse_text(f):
+ last_prefix = None
+ cursor = connection.cursor()
+ range_class = resource_range_ipv4
table = 'routeview_routeorigin'
sql = "INSERT INTO %s_new SET asn=%%s, prefix_min=%%s, prefix_max=%%s" % table
- range_class = resource_range_ipv4
-
- def __init__(self, path, *args, **kwargs):
- self.path = path
- self.cursor = connection.cursor()
- self.last_prefix = None
- self.asns = set()
-
- def parse(self):
- try:
- logger.info('Dropping existing staging table...')
- self.cursor.execute('DROP TABLE IF EXISTS %s_new' % self.table)
- except _mysql_exceptions.Warning:
- pass
-
- logger.info('Creating staging table...')
- self.cursor.execute('CREATE TABLE %(table)s_new LIKE %(table)s' % {'table': self.table})
-
- logger.info('Disabling autocommit...')
- self.cursor.execute('SET autocommit=0')
-
- logger.info('Adding rows to table...')
- for line in self.input:
- try:
- prefix, origin_as = self.parse_line(line)
- except ParseError as e:
- logger.warning('error while parsing line: {} ({})'.format(line, str(e)))
- continue
-
- # the output may contain multiple paths to the same origin.
- # if this is the same prefix as the last entry, we don't need
- # to validate it again.
- #
- # prefixes are sorted, but the origin_as is not, so we keep a set to
- # avoid duplicates, and insert into the db once we've seen all the
- # origin_as values for a given prefix
- if prefix != self.last_prefix:
- self.ins_routes()
- self.last_prefix = prefix
- self.asns.add(origin_as)
-
- self.ins_routes() # process data from last line
-
- logger.info('Committing...')
- self.cursor.execute('COMMIT')
-
- try:
- logger.info('Dropping old table...')
- self.cursor.execute('DROP TABLE IF EXISTS %s_old' % self.table)
- except _mysql_exceptions.Warning:
- pass
- logger.info('Swapping staging table with live table...')
- self.cursor.execute('RENAME TABLE %(table)s TO %(table)s_old, %(table)s_new TO %(table)s' % {'table': self.table})
-
- self.cleanup() # allow cleanup function to throw prior to COMMIT
-
- transaction.commit_unless_managed()
-
- logger.info('Updating timestamp metadata...')
- rpki.gui.app.timestamp.update('bgp_v4_import')
-
- def parse_line(self, row):
- "Parse one line of input. Return a (prefix, origin_as) tuple."
- return None
-
- def cleanup(self):
+ try:
+ logger.info('Dropping existing staging table...')
+ cursor.execute('DROP TABLE IF EXISTS %s_new' % table)
+ except _mysql_exceptions.Warning:
pass
- def ins_routes(self):
- # output routes for previous prefix
- if self.last_prefix is not None:
- try:
- rng = self.range_class.parse_str(self.last_prefix)
- rmin = long(rng.min)
- rmax = long(rng.max)
- self.cursor.executemany(self.sql, [(asn, rmin, rmax) for asn in self.asns])
- except BadIPResource:
- logger.warning('skipping bad prefix: ' + self.last_prefix)
- self.asns = set() # reset
-
-
-class TextDumpParser(RouteDumpParser):
- """Parses the RouteViews.org text dump."""
-
- def __init__(self, *args, **kwargs):
- super(TextDumpParser, self).__init__(*args, **kwargs)
- if self.path.endswith('.bz2'):
- logger.info('decompressing bz2 file')
- self.file = bz2.BZ2File(self.path, buffering=4096)
- else:
- self.file = open(self.path, buffering=-1)
- self.input = itertools.islice(self.file, 5, None) # skip first 5 lines
+ logger.info('Creating staging table...')
+ cursor.execute('CREATE TABLE %(table)s_new LIKE %(table)s' % {'table': table})
+
+ logger.info('Disabling autocommit...')
+ cursor.execute('SET autocommit=0')
- def parse_line(self, row):
- "Parse one line of input"
+ logger.info('Adding rows to table...')
+ for row in itertools.islice(f, 5, None):
cols = row.split()
# index -1 is i/e/? for igp/egp
- try:
- origin_as = int(cols[-2])
- except IndexError:
- raise ParseError('unexpected format')
- except ValueError:
- raise ParseError('bad AS value')
+ origin_as = cols[-2]
+ # FIXME: skip AS_SETs
+ if origin_as[0] == '{':
+ continue
prefix = cols[1]
@@ -163,35 +77,85 @@ class TextDumpParser(RouteDumpParser):
s.append('assuming it should be %s' % prefix)
logger.warning(' '.join(s))
- return prefix, origin_as
+ # the output may contain multiple paths to the same origin.
+ # if this is the same prefix as the last entry, we don't need
+ # to validate it again.
+ #
+ # prefixes are sorted, but the origin_as is not, so we keep a set to
+ # avoid duplicates, and insert into the db once we've seen all the
+ # origin_as values for a given prefix
+ if prefix != last_prefix:
+ # output routes for previous prefix
+ if last_prefix is not None:
+ try:
+ rng = range_class.parse_str(last_prefix)
+ rmin = long(rng.min)
+ rmax = long(rng.max)
+ cursor.executemany(sql, [(asn, rmin, rmax) for asn in asns])
+ except BadIPResource:
+ logger.warning('skipping bad prefix: ' + last_prefix)
+
+ asns = set()
+ last_prefix = prefix
+
+ try:
+ asns.add(int(origin_as))
+ except ValueError as err:
+ logger.warning('\n'.join(
+ ['unable to parse origin AS: ' + origin_as],
+ ['ValueError: ' + str(err)]
+ ['route entry was: ' + row],
+ ))
+
+ logger.info('Committing...')
+ cursor.execute('COMMIT')
+
+ try:
+ logger.info('Dropping old table...')
+ cursor.execute('DROP TABLE IF EXISTS %s_old' % table)
+ except _mysql_exceptions.Warning:
+ pass
+
+ logger.info('Swapping staging table with live table...')
+ cursor.execute('RENAME TABLE %(table)s TO %(table)s_old, %(table)s_new TO %(table)s' % {'table': table})
- def cleanup(self):
- self.file.close()
+ transaction.commit_unless_managed()
+ logger.info('Updating timestamp metadata...')
+ rpki.gui.app.timestamp.update('bgp_v4_import')
-class MrtDumpParser(RouteDumpParser):
- def __init__(self, *args, **kwargs):
- super(MrtDumpParser, self).__init__(*args, **kwargs)
- # filter input through bgpdump
- # bgpdump can decompress bz2 files directly, no need to do it here
- self.pipe = subprocess.Popen(['bgpdump', '-m', '-v', self.path], stdout=subprocess.PIPE, bufsize=-1)
- self.input = self.pipe.stdout
- def parse_line(self, row):
- a = row.split('|')
+def parse_mrt(f):
+ # filter input through bgpdump
+ pipe = subprocess.Popen(['bgpdump', '-m', '-v', '-'], stdin=f,
+ stdout=subprocess.PIPE)
+
+ last_prefix = None
+ last_as = None
+ for e in pipe.stdout.readlines():
+ a = e.split('|')
prefix = a[5]
try:
origin_as = int(a[6].split()[-1])
except ValueError:
- raise ParseError('bad AS value')
+ # skip AS_SETs
+ continue
+
+ if prefix != last_prefix:
+ last_prefix = prefix
+ elif last_as == origin_as:
+ continue
+ last_as = origin_as
- return prefix, origin_as
+ asns = PREFIXES.get(prefix)
+ if not asns:
+ asns = set()
+ PREFIXES[prefix] = asns
+ asns.add(origin_as)
- def cleanup(self):
- logger.info('waiting for child process to terminate')
- self.pipe.wait()
- if self.pipe.returncode:
- raise PipeFailed('bgpdump exited with code %d' % self.pipe.returncode)
+ pipe.wait()
+ if pipe.returncode:
+ raise ProgException('bgpdump exited with code %d' % pipe.returncode)
class ProgException(Exception):
@@ -206,7 +170,7 @@ class PipeFailed(ProgException):
pass
-def import_routeviews_dump(filename=DEFAULT_URL, filetype='text'):
+def import_routeviews_dump(filename=DEFAULT_URL, filetype='auto'):
"""Load the oix-full-snapshot-latest.bz2 from routeview.org into the
rpki.gui.routeview database.
@@ -215,34 +179,58 @@ def import_routeviews_dump(filename=DEFAULT_URL, filetype='text'):
filename [optional]: the full path to the downloaded file to parse
filetype [optional]: 'text' or 'mrt'
-
"""
+
start_time = time.time()
- tmpname = None
- try:
- if filename.startswith('http://'):
- #get filename from the basename of the URL
- u = urlparse.urlparse(filename)
- bname = os.path.basename(unquote(u.path))
- tmpname = os.path.join(settings.DOWNLOAD_DIRECTORY, bname)
-
- logger.info("Downloading %s to %s", filename, tmpname)
- if os.path.exists(tmpname):
- os.remove(tmpname)
- # filename is replaced with a local filename containing cached copy of
- # URL
- filename, headers = urlretrieve(filename, tmpname)
+ if filename.startswith('http://'):
+ #get filename from the basename of the URL
+ u = urlparse.urlparse(filename)
+ bname = os.path.basename(unquote(u.path))
+ tmpname = os.path.join('/tmp', bname)
+
+ logger.info("Downloading %s to %s", filename, tmpname)
+ if os.path.exists(tmpname):
+ os.remove(tmpname)
+ # filename is replaced with a local filename containing cached copy of
+ # URL
+ filename, headers = urlretrieve(filename, tmpname)
+
+ if filetype == 'auto':
+ # try to determine input type from filename, based on the default
+ # filenames from archive.routeviews.org
+ bname = os.path.basename(filename)
+ if bname.startswith('oix-full-snapshot-latest'):
+ filetype = 'text'
+ elif bname.startswith('rib.'):
+ filetype = 'mrt'
+ else:
+ raise UnknownInputType('unable to automatically determine input file type')
+ logging.info('Detected import format as "%s"', filetype)
+
+ pipe = None
+ if filename.endswith('.bz2'):
+ bunzip = 'bunzip2'
+ logging.info('Decompressing input file on the fly...')
+ pipe = subprocess.Popen([bunzip, '--stdout', filename],
+ stdout=subprocess.PIPE)
+ input_file = pipe.stdout
+ else:
+ input_file = open(filename)
- try:
- dispatch = {'text': TextDumpParser, 'mrt': MrtDumpParser}
- dispatch[filetype](filename).parse()
- except KeyError:
- raise UnknownInputType('"%s" is an unknown input file type' % filetype)
-
- finally:
- # make sure to always clean up the temp download file
- if tmpname is not None:
- os.unlink(tmpname)
+ try:
+ dispatch = {'text': parse_text, 'mrt': parse_mrt}
+ dispatch[filetype](input_file)
+ except KeyError:
+ raise UnknownInputType('"%s" is an unknown input file type' % filetype)
+
+ if pipe:
+ logging.debug('Waiting for child to exit...')
+ pipe.wait()
+ if pipe.returncode:
+ raise PipeFailed('Child exited code %d' % pipe.returncode)
+ pipe = None
+ else:
+ input_file.close()
logger.info('Elapsed time %d secs', (time.time() - start_time))
diff --git a/rpki/gui/script_util.py b/rpki/gui/script_util.py
index 43a53bc6..1941f6f7 100644
--- a/rpki/gui/script_util.py
+++ b/rpki/gui/script_util.py
@@ -16,13 +16,6 @@
This module contains utility functions for use in standalone scripts.
"""
-import django
-
-from django.conf import settings
-
-from rpki import config
-from rpki import autoconf
-
__version__ = '$Id$'
@@ -30,21 +23,39 @@ def setup():
"""
Configure Django enough to use the ORM.
"""
- cfg = config.parser(section='web_portal')
- # INSTALLED_APPS doesn't seem necessary so long as you are only accessing
- # existing tables.
- settings.configure(
- DATABASES={
- 'default': {
- 'ENGINE': 'django.db.backends.mysql',
- 'NAME': cfg.get('sql-database'),
- 'USER': cfg.get('sql-username'),
- 'PASSWORD': cfg.get('sql-password'),
- }
- },
- MIDDLEWARE_CLASSES = (),
- DOWNLOAD_DIRECTORY = cfg.get('download-directory', '/var/tmp'),
- )
- if django.VERSION >= (1, 7):
- from django.apps import apps
- apps.populate(settings.INSTALLED_APPS)
+
+ # In theory we no longer need to call settings.configure, which
+ # probably means this whole module can go away soon, but leave
+ # breadcrumbs for now.
+
+ if True:
+ import os
+ os.environ.update(DJANGO_SETTINGS_MODULE = "rpki.django_settings")
+
+ else:
+ import django
+ from rpki import config
+ from rpki import autoconf
+ from django.conf import settings
+
+ cfg = config.parser(section='web_portal')
+ # INSTALLED_APPS doesn't seem necessary so long as you are only accessing
+ # existing tables.
+ settings.configure(
+ DATABASES={
+ 'default': {
+ 'ENGINE': 'django.db.backends.mysql',
+ 'NAME': cfg.get('sql-database'),
+ 'USER': cfg.get('sql-username'),
+ 'PASSWORD': cfg.get('sql-password'),
+ },
+ },
+ MIDDLEWARE_CLASSES = (),
+ )
+ # Can't populate apps if we don't know what they are. If this
+ # explodes with an AppRegistryNotReady exception, the above comment
+ # about not needing to set INSTALLED_APPS is no longer true and
+ # you'll need to fix that here.
+ if False and django.VERSION >= (1, 7):
+ from django.apps import apps
+ apps.populate(settings.INSTALLED_APPS)