1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
#!/usr/bin/env python
# $Id$
#
# Copyright (C) 2014 Dragon Research Labs ("DRL")
# Portions copyright (C) 2009-2013 Internet Systems Consortium ("ISC")
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notices and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND DRL AND ISC DISCLAIM ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL OR
# ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
import os
import glob
import shutil
import sqlite3
import rpki.rtr.client
import rpki.rtr.channels
def find_free_name(fmt, n = 10):
"""
Handle SKI hash collisions by allowing a small number of distinct
filenames for each ASN/SKI pair.
"""
for i in xrange(n):
fn = fmt % i
if not os.path.exists(fn):
return fn
raise RuntimeError("Couldn't find a free filename for key %s after %d tries" % (fmt, n))
class ClientChannel(rpki.rtr.client.ClientChannel):
"""
Subclass ClientChannel to extend .end_of_data() method.
"""
def end_of_data(self, version, serial, nonce, refresh, retry, expire):
"""
Call base method to do all the normal EndOfData processing, then
dump out the key database using the symlink-to-directory hack so
we can rename() the result to perform an atomic installation.
"""
# Run the base method
super(ClientChannel, self).end_of_data(version, serial, nonce, refresh, retry, expire)
# Set up our new output directory
dn = "%s.%s" % (self.args.bgpsec_key_directory, rpki.rtr.channels.Timestamp.now())
ln = "%s.%s" % (self.args.bgpsec_key_directory, ".tmp")
if os.path.exists(ln):
os.unlink(ln)
os.makedirs(dn)
# Write all the keys
for asn, gski, key in self.sql.execute("SELECT asn, ski, key FROM routerkey"):
with open(find_free_name("%s/%s.%s.%%d.key" % (dn, asn, gski)), "wb") as f:
f.write(key.decode("base64"))
# Install the new directory
os.symlink(os.path.basename(dn), ln)
os.rename(ln, self.args.bgpsec_key_directory)
# Clean up old output directories
pattern = ".[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z"
for gn in glob.iglob(self.args.bgpsec_key_directory + pattern):
if gn != dn:
shutil.rmtree(gn)
def SymlinkToDirectory(path):
"""
argparse "type" function to clean up and sanity check --bgpsec-key-directory.
"""
path = os.path.abspath(path).rstrip("/")
if os.path.exists(path) and not os.path.islink(path):
raise ValueError
return path
# Grab handle on the normal client argparse setup function
client_argparse_setup = rpki.rtr.client.argparse_setup
# Extend argparse setup to add our own (required) parameter
def argparse_setup(subparsers):
subparser = client_argparse_setup(subparsers)
subparser.add_argument("--bgpsec-key-directory", required = True, type = SymlinkToDirectory,
help = "where to write BGPSEC router keys")
return subparser
# Splice our extensions into client
rpki.rtr.client.argparse_setup = argparse_setup
rpki.rtr.client.ClientChannelClass = ClientChannel
# And run the program
import rpki.rtr.main
rpki.rtr.main.main()
|