Update README and remove utils/web that the wireless-regdb.git sucked in.
authorLuis R. Rodriguez <lrodriguez@atheros.com>
Mon, 17 Nov 2008 21:56:08 +0000 (13:56 -0800)
committerLuis R. Rodriguez <lrodriguez@atheros.com>
Mon, 17 Nov 2008 21:56:08 +0000 (13:56 -0800)
Signed-off-by: Luis R. Rodriguez <lrodriguez@atheros.com>
README
utils/db2bin.py [deleted file]
utils/dbparse.py [deleted file]
web/Regulatory.py [deleted file]

diff --git a/README b/README
index 7270fbe..db67d6a 100644 (file)
--- a/README
+++ b/README
@@ -8,21 +8,25 @@ purpose: tell Linux kernel what to enforce.
 ===================
 
 CRDA is provided as a binary file so all the host needs is libc/uclibc.
+You will also need udev.
 
  BUILD REQUIREMENTS
 ====================
 
 The package build requirements currently are:
 
- * wget to be able to download the public key and regulatory.bin file
  * python and the m2crypto package (python-m2crypto)
  * libgcrypt or libssl (openssl) header files
  * nl library and header files (libnl1 and libnl-dev)
    available at git://git.kernel.org/pub/scm/libs/netlink/libnl.git
+ * regulatory database, clone this tree:
 
-If you want to put up a web site with a database viewer using MoinMoin:
+   git://git.kernel.org/pub/scm/linux/kernel/git/linville/wireless-regdb.git
 
- * MoinMoin (http://moinmo.in) for the web viewer
+  and then stuff regulatory.bin (no need to build) provided there in
+  REG_BIN location specified in this Makefile. This regulatory.bin file
+  is only required to build CRDA here to verify it was built using
+  the RSA private key the public key agrees to.
 
  CALLING CRDA -- UDEV
 ======================
@@ -35,42 +39,28 @@ with this package as regulatory.rules
  OVERVIEW
 ==========
 
-The regulatory information is collected in a text file, `db.txt'. This
-text file is then compiled into a binary database `regulatory.bin' and
-digitally signed with the key in `key.priv.pem'. The binary database
-is then used by the regulatory agent to update the in-kernel enforcement
-table.
+The database is maintained on the wireless-regdb.git tree. This git
+tree maintains a binary regulatory database file which is produced
+using its own ASCII db.txt into binary form for size efficiency. The
+contents of the binary database are then signed using the private key.
 
- TECHNICAL INFORMATION
-=======================
+CRDA will use regulatory.bin if its signature checks out with the public
+key provided. This will prevent us from using corrupted data (in case
+of hard drive failure) in the running kernel. This separation between
+CRDA and the regulatory database also allows us to provide regulatory
+updates on distributions without having to require an update on CRDA.
 
-The regulatory information in `db.txt' is stored in a human-readable
-format which can be read using the `dbparse.py' python module. This
-python module is used by the web viewer (web/Regulatory.py) which is
-implemented as a MoinMoin macro (and used on http://wireless.kernel.org)
-to allow viewing the database for verification.
-
-The dbparse module is also used by db2bin.py, the `compiler', which
-compiles and signs the binary database.
-
-The binary database file format is described in `regdb.h' (which has
-to be kept in sync with the compiler.
-
-The key file, key.priv.pem, has to be an RSA key, for example created
-with openssl using `openssl genrsa -out key.priv.pem 1024'. Building
-without such a key file causes the test-key to be used to allow the
-build to succeed without generating a key first. This key is not meant
-to be used for deployments, however.
+Note that upon updating the regulatory database it is advised the
+user reboots or all the wireless modules get unloaded and reloaded.
 
 Under certain circumstances it may be desirable to have the regulatory
-agent accept multiple keys, this can be achieved by compiling it when
-more than one key is present in the source directory (named *.pem). In
-this case, the agent will accept a signature of any of those keys.
+agent accept multiple keys, this can be achieved by stuffing all the keys
+desired into pubkeys. Right now we only use John Linville's public key.
 
  REGDB AUTHORS
 ===============
 
-Authors of regulatory.bin first need a private key, which can
+Authors of regulatory.bin (John Linville) first need a private key, which can
 be generated with something like this:
 
        openssl genrsa -out your.key.priv.pem 2048
@@ -84,13 +74,6 @@ Then with this key you can generate regulatory.bin files like this:
 
        ./utils/db2bin.py regulatory.bin db.txt your.key.priv.pem
 
-You can find the source of the regulatory.bin used at:
-
-git://git.kernel.org/pub/scm/linux/kernel/git/linville/wireless-regdb.git
-
-We pull John's RSA key form that tree into this tree. If it changes
-we will update it here.
-
  MAGIC PATTERN
 ===============
 
diff --git a/utils/db2bin.py b/utils/db2bin.py
deleted file mode 100755 (executable)
index e783b3a..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-#!/usr/bin/env python
-
-from cStringIO import StringIO
-import struct
-import sha
-from dbparse import DBParser
-import sys
-
-MAGIC = 0x52474442
-VERSION = 19
-
-if len(sys.argv) < 3:
-    print 'Usage: %s output-file input-file [key-file]' % sys.argv[0]
-    sys.exit(2)
-
-def create_rules(countries):
-    result = {}
-    for c in countries.itervalues():
-        for rule in c.permissions:
-            result[rule] = 1
-    return result.keys()
-
-def create_collections(countries):
-    result = {}
-    for c in countries.itervalues():
-        result[c.permissions] = 1
-    return result.keys()
-
-
-def be32(output, val):
-    output.write(struct.pack('>I', val))
-
-class PTR(object):
-    def __init__(self, output):
-        self._output = output
-        self._pos = output.tell()
-        be32(output, 0xFFFFFFFF)
-
-    def set(self, val=None):
-        if val is None:
-            val = self._output.tell()
-        self._offset = val
-        pos = self._output.tell()
-        self._output.seek(self._pos)
-        be32(self._output, val)
-        self._output.seek(pos)
-
-    def get(self):
-        return self._offset
-
-p = DBParser()
-countries = p.parse(file(sys.argv[2]))
-power = []
-bands = []
-for c in countries.itervalues():
-    for perm in c.permissions:
-        if not perm.freqband in bands:
-            bands.append(perm.freqband)
-        if not perm.power in power:
-            power.append(perm.power)
-rules = create_rules(countries)
-rules.sort(cmp=lambda x, y: cmp(x.freqband, y.freqband))
-collections = create_collections(countries)
-collections.sort(cmp=lambda x, y: cmp(x[0].freqband, y[0].freqband))
-
-output = StringIO()
-
-# struct regdb_file_header
-be32(output, MAGIC)
-be32(output, VERSION)
-reg_country_ptr = PTR(output)
-# add number of countries
-be32(output, len(countries))
-siglen = PTR(output)
-
-power_rules = {}
-for pr in power:
-    power_rules[pr] = output.tell()
-    pr = [int(v * 100.0) for v in (pr.max_ant_gain, pr.max_eirp)]
-    # struct regdb_file_power_rule
-    output.write(struct.pack('>II', *pr))
-
-freq_ranges = {}
-for fr in bands:
-    freq_ranges[fr] = output.tell()
-    fr = [int(f * 1000.0) for f in (fr.start, fr.end, fr.maxbw)]
-    # struct regdb_file_freq_range
-    output.write(struct.pack('>III', *fr))
-
-
-reg_rules = {}
-for reg_rule in rules:
-    freq_range, power_rule = reg_rule.freqband, reg_rule.power
-    reg_rules[reg_rule] = output.tell()
-    # struct regdb_file_reg_rule
-    output.write(struct.pack('>III', freq_ranges[freq_range], power_rules[power_rule],
-                             reg_rule.flags))
-
-
-reg_rules_collections = {}
-
-for coll in collections:
-    reg_rules_collections[coll] = output.tell()
-    # struct regdb_file_reg_rules_collection
-    coll = list(coll)
-    be32(output, len(coll))
-    coll.sort(cmp=lambda x, y: cmp(x.freqband, y.freqband))
-    for regrule in coll:
-        be32(output, reg_rules[regrule])
-
-# update country pointer now!
-reg_country_ptr.set()
-
-countrynames = countries.keys()
-countrynames.sort()
-for alpha2 in countrynames:
-    coll = countries[alpha2]
-    # struct regdb_file_reg_country
-    output.write(struct.pack('>ccxxI', str(alpha2[0]), str(alpha2[1]), reg_rules_collections[coll.permissions]))
-
-
-if len(sys.argv) > 3:
-    # Load RSA only now so people can use this script
-    # without having those libraries installed to verify
-    # their SQL changes
-    from M2Crypto import RSA
-
-    # determine signature length
-    key = RSA.load_key(sys.argv[3])
-    hash = sha.new()
-    hash.update(output.getvalue())
-    sig = key.sign(hash.digest())
-    # write it to file
-    siglen.set(len(sig))
-    # sign again
-    hash = sha.new()
-    hash.update(output.getvalue())
-    sig = key.sign(hash.digest())
-
-    output.write(sig)
-else:
-    siglen.set(0)
-
-outfile = open(sys.argv[1], 'w')
-outfile.write(output.getvalue())
diff --git a/utils/dbparse.py b/utils/dbparse.py
deleted file mode 100755 (executable)
index 55615bd..0000000
+++ /dev/null
@@ -1,357 +0,0 @@
-#!/usr/bin/env python
-
-import sys, math
-
-# must match <linux/nl80211.h> enum nl80211_reg_rule_flags
-
-flag_definitions = {
-    'NO-OFDM':         1<<0,
-    'NO-CCK':          1<<1,
-    'NO-INDOOR':       1<<2,
-    'NO-OUTDOOR':      1<<3,
-    'DFS':             1<<4,
-    'PTP-ONLY':                1<<5,
-    'PTMP-ONLY':       1<<6,
-    'PASSIVE-SCAN':    1<<7,
-    'NO-IBSS':         1<<8,
-    # hole at bit 9. FIXME: Where is NO-HT40 defined?
-    'NO-HT40':         1<<10,
-}
-
-class FreqBand(object):
-    def __init__(self, start, end, bw, comments=None):
-        self.start = start
-        self.end = end
-        self.maxbw = bw
-        self.comments = comments or []
-
-    def __cmp__(self, other):
-        s = self
-        o = other
-        if not isinstance(o, FreqBand):
-            return False
-        return cmp((s.start, s.end, s.maxbw), (o.start, o.end, o.maxbw))
-
-    def __hash__(self):
-        s = self
-        return hash((s.start, s.end, s.maxbw))
-
-    def __str__(self):
-        return '<FreqBand %.3f - %.3f @ %.3f>' % (
-                  self.start, self.end, self.maxbw)
-
-class PowerRestriction(object):
-    def __init__(self, max_ant_gain, max_eirp, comments = None):
-        self.max_ant_gain = max_ant_gain
-        self.max_eirp = max_eirp
-        self.comments = comments or []
-
-    def __cmp__(self, other):
-        s = self
-        o = other
-        if not isinstance(o, PowerRestriction):
-            return False
-        return cmp((s.max_ant_gain, s.max_eirp),
-                   (o.max_ant_gain, o.max_eirp))
-
-    def __str__(self):
-        return '<PowerRestriction ...>'
-
-    def __hash__(self):
-        s = self
-        return hash((s.max_ant_gain, s.max_eirp))
-
-class FlagError(Exception):
-    def __init__(self, flag):
-        self.flag = flag
-
-class Permission(object):
-    def __init__(self, freqband, power, flags):
-        assert isinstance(freqband, FreqBand)
-        assert isinstance(power, PowerRestriction)
-        self.freqband = freqband
-        self.power = power
-        self.flags = 0
-        for flag in flags:
-            if not flag in flag_definitions:
-                raise FlagError(flag)
-            self.flags |= flag_definitions[flag]
-        self.textflags = flags
-
-    def _as_tuple(self):
-        return (self.freqband, self.power, self.flags)
-
-    def __cmp__(self, other):
-        if not isinstance(other, Permission):
-            return False
-        return cmp(self._as_tuple(), other._as_tuple())
-
-    def __hash__(self):
-        return hash(self._as_tuple())
-
-class Country(object):
-    def __init__(self, permissions=None, comments=None):
-        self._permissions = permissions or []
-        self.comments = comments or []
-
-    def add(self, perm):
-        assert isinstance(perm, Permission)
-        self._permissions.append(perm)
-        self._permissions.sort()
-
-    def __contains__(self, perm):
-        assert isinstance(perm, Permission)
-        return perm in self._permissions
-
-    def __str__(self):
-        r = ['(%s, %s)' % (str(b), str(p)) for b, p in self._permissions]
-        return '<Country (%s)>' % (', '.join(r))
-
-    def _get_permissions_tuple(self):
-        return tuple(self._permissions)
-    permissions = property(_get_permissions_tuple)
-
-class SyntaxError(Exception):
-    pass
-
-class DBParser(object):
-    def __init__(self, warn=None):
-        self._warn_callout = warn or sys.stderr.write
-
-    def _syntax_error(self, txt=None):
-        txt = txt and ' (%s)' % txt or ''
-        raise SyntaxError("Syntax error in line %d%s" % (self._lineno, txt))
-
-    def _warn(self, txt):
-        self._warn_callout("Warning (line %d): %s\n" % (self._lineno, txt))
-
-    def _parse_band_def(self, bname, banddef, dupwarn=True):
-        try:
-            freqs, bw = banddef.split('@')
-            bw = float(bw)
-        except ValueError:
-            bw = 20.0
-
-        try:
-            start, end = freqs.split('-')
-            start = float(start)
-            end = float(end)
-        except ValueError:
-            self._syntax_error("band must have frequency range")
-
-        b = FreqBand(start, end, bw, comments=self._comments)
-        self._comments = []
-        self._banddup[bname] = bname
-        if b in self._bandrev:
-            if dupwarn:
-                self._warn('Duplicate band definition ("%s" and "%s")' % (
-                              bname, self._bandrev[b]))
-            self._banddup[bname] = self._bandrev[b]
-        self._bands[bname] = b
-        self._bandrev[b] = bname
-        self._bandline[bname] = self._lineno
-
-    def _parse_band(self, line):
-        try:
-            bname, line = line.split(':', 1)
-            if not bname:
-                self._syntax_error("'band' keyword must be followed by name")
-        except ValueError:
-            self._syntax_error("band name must be followed by colon")
-
-        if bname in flag_definitions:
-            self._syntax_error("Invalid band name")
-
-        self._parse_band_def(bname, line)
-
-    def _parse_power(self, line):
-        try:
-            pname, line = line.split(':', 1)
-            if not pname:
-                self._syntax_error("'power' keyword must be followed by name")
-        except ValueError:
-            self._syntax_error("power name must be followed by colon")
-
-        if pname in flag_definitions:
-            self._syntax_error("Invalid power name")
-
-        self._parse_power_def(pname, line)
-
-    def _parse_power_def(self, pname, line, dupwarn=True):
-        try:
-            (max_ant_gain,
-             max_eirp) = line.split(',')
-            if max_ant_gain == 'N/A':
-                max_ant_gain = '0'
-            if max_eirp == 'N/A':
-                max_eirp = '0'
-            max_ant_gain = float(max_ant_gain)
-            def conv_pwr(pwr):
-                if pwr.endswith('mW'):
-                    pwr = float(pwr[:-2])
-                    return 10.0 * math.log10(pwr)
-                else:
-                    return float(pwr)
-            max_eirp = conv_pwr(max_eirp)
-        except ValueError:
-            self._syntax_error("invalid power data")
-
-        p = PowerRestriction(max_ant_gain, max_eirp,
-                             comments=self._comments)
-        self._comments = []
-        self._powerdup[pname] = pname
-        if p in self._powerrev:
-            if dupwarn:
-                self._warn('Duplicate power definition ("%s" and "%s")' % (
-                              pname, self._powerrev[p]))
-            self._powerdup[pname] = self._powerrev[p]
-        self._power[pname] = p
-        self._powerrev[p] = pname
-        self._powerline[pname] = self._lineno
-
-    def _parse_country(self, line):
-        try:
-            cname, line = line.split(':', 1)
-            if not cname:
-                self._syntax_error("'country' keyword must be followed by name")
-            if line:
-                self._syntax_error("extra data at end of country line")
-        except ValueError:
-            self._syntax_error("country name must be followed by colon")
-
-        cnames = cname.split(',')
-
-        self._current_countries = {}
-        for cname in cnames:
-            if len(cname) != 2:
-                self._warn("country '%s' not alpha2" % cname)
-            if not cname in self._countries:
-                self._countries[cname] = Country(comments=self._comments)
-            self._current_countries[cname] = self._countries[cname]
-        self._comments = []
-
-    def _parse_country_item(self, line):
-        if line[0] == '(':
-            try:
-                band, line = line[1:].split('),', 1)
-                bname = 'UNNAMED %d' % self._lineno
-                self._parse_band_def(bname, band, dupwarn=False)
-            except:
-                self._syntax_error("Badly parenthesised band definition")
-        else:
-            try:
-                bname, line = line.split(',', 1)
-                if not bname:
-                    self._syntax_error("country definition must have band")
-                if not line:
-                    self._syntax_error("country definition must have power")
-            except ValueError:
-                self._syntax_error("country definition must have band and power")
-
-        if line[0] == '(':
-            items = line.split('),', 1)
-            if len(items) == 1:
-                pname = items[0]
-                line = ''
-                if not pname[-1] == ')':
-                    self._syntax_error("Badly parenthesised power definition")
-                pname = pname[:-1]
-                flags = []
-            else:
-                pname = items[0]
-                flags = items[1].split(',')
-            power = pname[1:]
-            pname = 'UNNAMED %d' % self._lineno
-            self._parse_power_def(pname, power, dupwarn=False)
-        else:
-            line = line.split(',')
-            pname = line[0]
-            flags = line[1:]
-
-        if not bname in self._bands:
-            self._syntax_error("band does not exist")
-        if not pname in self._power:
-            self._syntax_error("power does not exist")
-        self._bands_used[bname] = True
-        self._power_used[pname] = True
-        # de-duplicate so binary database is more compact
-        bname = self._banddup[bname]
-        pname = self._powerdup[pname]
-        b = self._bands[bname]
-        p = self._power[pname]
-        try:
-            perm = Permission(b, p, flags)
-        except FlagError, e:
-            self._syntax_error("Invalid flag '%s'" % e.flag)
-        for cname, c in self._current_countries.iteritems():
-            if perm in c:
-                self._warn('Rule "%s, %s" added to "%s" twice' % (
-                              bname, pname, cname))
-            else:
-                c.add(perm)
-
-    def parse(self, f):
-        self._current_countries = None
-        self._bands = {}
-        self._power = {}
-        self._countries = {}
-        self._bands_used = {}
-        self._power_used = {}
-        self._bandrev = {}
-        self._powerrev = {}
-        self._banddup = {}
-        self._powerdup = {}
-        self._bandline = {}
-        self._powerline = {}
-
-        self._comments = []
-
-        self._lineno = 0
-        for line in f:
-            self._lineno += 1
-            line = line.strip()
-            if line[0:1] == '#':
-                self._comments.append(line[1:].strip())
-            line = line.replace(' ', '').replace('\t', '')
-            if not line:
-                self._comments = []
-            line = line.split('#')[0]
-            if not line:
-                continue
-            if line[0:4] == 'band':
-                self._parse_band(line[4:])
-                self._current_countries = None
-                self._comments = []
-            elif line[0:5] == 'power':
-                self._parse_power(line[5:])
-                self._current_countries = None
-                self._comments = []
-            elif line[0:7] == 'country':
-                self._parse_country(line[7:])
-                self._comments = []
-            elif self._current_countries is not None:
-                self._parse_country_item(line)
-                self._comments = []
-            else:
-                self._syntax_error("Expected band, power or country definition")
-
-        countries = self._countries
-        bands = {}
-        for k, v in self._bands.iteritems():
-            if k in self._bands_used:
-                bands[self._banddup[k]] = v
-                continue
-            # we de-duplicated, but don't warn again about the dupes
-            if self._banddup[k] == k:
-                self._lineno = self._bandline[k]
-                self._warn('Unused band definition "%s"' % k)
-        power = {}
-        for k, v in self._power.iteritems():
-            if k in self._power_used:
-                power[self._powerdup[k]] = v
-                continue
-            # we de-duplicated, but don't warn again about the dupes
-            if self._powerdup[k] == k:
-                self._lineno = self._powerline[k]
-                self._warn('Unused power definition "%s"' % k)
-        return countries
diff --git a/web/Regulatory.py b/web/Regulatory.py
deleted file mode 100644 (file)
index f56fddd..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    Regulatory Database
-
-    @copyright: 2008 Johannes Berg
-    @license: GNU GPL, see COPYING for details.
-"""
-
-import codecs, math
-from dbparse import DBParser, flag_definitions
-
-Dependencies = ["time"]
-
-def _country(macro, countries, code):
-    result = []
-
-    f = macro.formatter
-
-    result.extend([
-        f.heading(1, 1),
-        f.text('Regulatory definition for %s' % _get_iso_code(code)),
-        f.heading(0, 1),
-    ])
-
-    try:
-        country = countries[code]
-    except:
-        result.append(f.text('No information available'))
-        return ''.join(result)
-    
-
-    if country.comments:
-        result.extend([
-            f.preformatted(1),
-            f.text('\n'.join(country.comments)),
-            f.preformatted(0),
-        ])
-
-    result.append(f.table(1))
-    result.extend([
-        f.table_row(1),
-          f.table_cell(1), f.strong(1),
-            f.text('Band [MHz]'),
-          f.strong(0), f.table_cell(0),
-          f.table_cell(1), f.strong(1),
-            f.text('Max BW [MHz]'),
-          f.strong(0), f.table_cell(0),
-          f.table_cell(1), f.strong(1),
-            f.text('Flags'),
-          f.strong(0), f.table_cell(0),
-          f.table_cell(1), f.strong(1),
-            f.text('Max antenna gain [dBi]'),
-          f.strong(0), f.table_cell(0),
-          f.table_cell(1), f.strong(1),
-            f.text('Max EIRP [dBm'),
-            f.hardspace,
-            f.text('(mW)]'),
-          f.strong(0), f.table_cell(0),
-        f.table_row(0),
-    ])
-
-    for perm in country.permissions:
-        def str_or_na(val, dBm=False):
-            if val and not dBm:
-                return '%.2f' % val
-            elif val:
-                return '%.2f (%.2f)' % (val, math.pow(10, val/10.0))
-            return 'N/A'
-        result.extend([
-            f.table_row(1),
-              f.table_cell(1),
-                f.text('%.3f - %.3f' % (perm.freqband.start, perm.freqband.end)),
-              f.table_cell(0),
-              f.table_cell(1),
-                f.text('%.3f' % (perm.freqband.maxbw,)),
-              f.table_cell(0),
-              f.table_cell(1),
-                f.text(', '.join(perm.textflags)),
-              f.table_cell(0),
-              f.table_cell(1),
-                f.text(str_or_na(perm.power.max_ant_gain)),
-              f.table_cell(0),
-              f.table_cell(1),
-                f.text(str_or_na(perm.power.max_eirp, dBm=True)),
-              f.table_cell(0),
-            f.table_row(0),
-        ])
-    
-    result.append(f.table(0))
-
-    result.append(f.linebreak(0))
-    result.append(f.linebreak(0))
-    result.append(macro.request.page.link_to(macro.request, 'return to country list'))
-    return ''.join(result)
-
-_iso_list = {}
-
-def _get_iso_code(code):
-    if not _iso_list:
-        for line in codecs.open('/usr/share/iso-codes/iso_3166.tab', encoding='utf-8'):
-            line = line.strip()
-            c, name = line.split('\t')
-            _iso_list[c] = name
-    return _iso_list.get(code, 'Unknown (%s)' % code)
-
-def macro_Regulatory(macro):
-    _ = macro.request.getText
-    request = macro.request
-    f = macro.formatter
-
-    country = request.form.get('alpha2', [None])[0]
-
-    dbpath = '/tmp/db.txt'
-    if hasattr(request.cfg, 'regdb_path'):
-        dbpath = request.cfg.regdb_path
-
-    result = []
-
-    if request.form.get('raw', [None])[0]:
-        result.append(f.code_area(1, 'db-raw', show=1, start=1, step=1))
-        for line in open(dbpath):
-            result.extend([
-                f.code_line(1),
-                f.text(line.rstrip()),
-                f.code_line(0),
-            ])
-        result.append(f.code_area(0, 'db-raw'))
-        result.append(macro.request.page.link_to(macro.request, 'return to country list'))
-        return ''.join(result)
-
-    warnings = []
-    countries = DBParser(warn=lambda x: warnings.append(x)).parse(open(dbpath))
-
-    if country:
-        return _country(macro, countries, country)
-
-    countries = countries.keys()
-    countries = [(_get_iso_code(code), code) for code in countries]
-    countries.sort()
-
-    result.extend([
-        f.heading(1, 1),
-        f.text('Countries'),
-        f.heading(0, 1),
-    ])
-
-    result.append(f.bullet_list(1))
-    for name, code in countries:
-        result.extend([
-          f.listitem(1),
-          request.page.link_to(request, name, querystr={'alpha2': code}),
-          f.listitem(0),
-        ])
-    result.append(f.bullet_list(0))
-
-    if warnings:
-        result.append(f.heading(1, 2))
-        result.append(f.text("Warnings"))
-        result.append(f.heading(0, 2))
-        result.append(f.preformatted(1))
-        result.extend(warnings)
-        result.append(f.preformatted(0))
-
-    result.append(request.page.link_to(request, 'view raw database', querystr={'raw': 1}))
-
-    return ''.join(result)