RPKI Engine  1.0
myrpki.py (4015)
Go to the documentation of this file.
00001 """
00002 This (oversized) module used to be an (oversized) program.
00003 Refactoring in progress, some doc still needs updating.
00004 
00005 
00006 This program is now the merger of three different tools: the old
00007 myrpki.py script, the old myirbe.py script, and the newer setup.py CLI
00008 tool.  As such, it is still in need of some cleanup, but the need to
00009 provide a saner user interface is more urgent than internal code
00010 prettiness at the moment.  In the long run, 90% of the code in this
00011 file probably ought to move to well-designed library modules.
00012 
00013 Overall goal here is to build up the configuration necessary to run
00014 rpkid and friends, by reading a config file, a collection of .CSV
00015 files, and the results of a few out-of-band XML setup messages
00016 exchanged with one's parents, children, and so forth.
00017 
00018 The config file is in an OpenSSL-compatible format, the CSV files are
00019 simple tab-delimited text.  The XML files are all generated by this
00020 program, either the local instance or an instance being run by another
00021 player in the system; the mechanism used to exchange these setup
00022 messages is outside the scope of this program, feel free to use
00023 PGP-signed mail, a web interface (not provided), USB stick, carrier
00024 pigeons, whatever works.
00025 
00026 With one exception, the commands in this program avoid using any
00027 third-party Python code other than the rpki libraries themselves; with
00028 the same one exception, all OpenSSL work is done with the OpenSSL
00029 command line tool (the one built as a side effect of building rcynic
00030 will do, if your platform has no system copy or the system copy is too
00031 old).  This is all done in an attempt to make the code more portable,
00032 so one can run most of the RPKI back end software on a laptop or
00033 whatever.  The one exception is the configure_daemons command, which
00034 must, of necessity, use the same communication libraries as the
00035 daemons with which it is conversing.  So that one command will not
00036 work if the correct Python modules are not available.
00037 
00038 
00039 $Id: myrpki.py 4015 2011-10-05 17:45:34Z sra $
00040 
00041 Copyright (C) 2009--2011  Internet Systems Consortium ("ISC")
00042 
00043 Permission to use, copy, modify, and distribute this software for any
00044 purpose with or without fee is hereby granted, provided that the above
00045 copyright notice and this permission notice appear in all copies.
00046 
00047 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
00048 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00049 AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
00050 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00051 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00052 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00053 PERFORMANCE OF THIS SOFTWARE.
00054 """
00055 
00056 import subprocess, csv, re, os, getopt, sys, base64, time, glob, copy, warnings
00057 import rpki.config, rpki.cli, rpki.sundial, rpki.log, rpki.oids
00058 
00059 try:
00060   from lxml.etree            import (Element, SubElement, ElementTree,
00061                                      fromstring as ElementFromString,
00062                                      tostring   as ElementToString)
00063 except ImportError:
00064   from xml.etree.ElementTree import (Element, SubElement, ElementTree,
00065                                      fromstring as ElementFromString,
00066                                      tostring   as ElementToString)
00067 
00068 
00069 
00070 # Our XML namespace and protocol version.
00071 
00072 namespace      = "http://www.hactrn.net/uris/rpki/myrpki/"
00073 version        = "2"
00074 namespaceQName = "{" + namespace + "}"
00075 
00076 # Whether to include incomplete entries when rendering to XML.
00077 
00078 allow_incomplete = False
00079 
00080 # Whether to whine about incomplete entries while rendering to XML.
00081 
00082 whine = True
00083 
00084 class BadCommandSyntax(Exception):
00085   """
00086   Bad command line syntax.
00087   """
00088 
00089 class BadPrefixSyntax(Exception):
00090   """
00091   Bad prefix syntax.
00092   """
00093 
00094 class CouldntTalkToDaemon(Exception):
00095   """
00096   Couldn't talk to daemon.
00097   """
00098 
00099 class BadCSVSyntax(Exception):
00100   """
00101   Bad CSV syntax.
00102   """
00103 
00104 class BadXMLMessage(Exception):
00105   """
00106   Bad XML message.
00107   """
00108 
00109 class PastExpiration(Exception):
00110   """
00111   Expiration date has already passed.
00112   """
00113 
00114 class CantRunRootd(Exception):
00115   """
00116   Can't run rootd.
00117   """
00118 
00119 class comma_set(set):
00120   """
00121   Minor customization of set(), to provide a print syntax.
00122   """
00123 
00124   def __str__(self):
00125     return ",".join(self)
00126 
00127 class EntityDB(object):
00128   """
00129   Wrapper for entitydb path lookups and iterations.
00130   """
00131 
00132   def __init__(self, cfg):
00133     self.dir = cfg.get("entitydb_dir", "entitydb")
00134     self.identity = os.path.join(self.dir, "identity.xml")
00135 
00136   def __call__(self, dirname, filebase = None):
00137     if filebase is None:
00138       return os.path.join(self.dir, dirname)
00139     else:
00140       return os.path.join(self.dir, dirname, filebase + ".xml")
00141 
00142   def iterate(self, dir, base = "*"):
00143     return glob.iglob(os.path.join(self.dir, dir, base + ".xml"))
00144 
00145 class roa_request(object):
00146   """
00147   Representation of a ROA request.
00148   """
00149 
00150   v4re = re.compile("^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]+(-[0-9]+)?$", re.I)
00151   v6re = re.compile("^([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4}/[0-9]+(-[0-9]+)?$", re.I)
00152 
00153   def __init__(self, asn, group):
00154     self.asn = asn
00155     self.group = group
00156     self.v4 = comma_set()
00157     self.v6 = comma_set()
00158 
00159   def __repr__(self):
00160     s = "<%s asn %s group %s" % (self.__class__.__name__, self.asn, self.group)
00161     if self.v4:
00162       s += " v4 %s" % self.v4
00163     if self.v6:
00164       s += " v6 %s" % self.v6
00165     return s + ">"
00166 
00167   def add(self, prefix):
00168     """
00169     Add one prefix to this ROA request.
00170     """
00171     if self.v4re.match(prefix):
00172       self.v4.add(prefix)
00173     elif self.v6re.match(prefix):
00174       self.v6.add(prefix)
00175     else:
00176       raise BadPrefixSyntax, "Bad prefix syntax: %r" % (prefix,)
00177 
00178   def xml(self, e):
00179     """
00180     Generate XML element represeting representing this ROA request.
00181     """
00182     e = SubElement(e, "roa_request",
00183                    asn = self.asn,
00184                    v4 = str(self.v4),
00185                    v6 = str(self.v6))
00186     e.tail = "\n"
00187 
00188 class roa_requests(dict):
00189   """
00190   Database of ROA requests.
00191   """
00192 
00193   def add(self, asn, group, prefix):
00194     """
00195     Add one <ASN, group, prefix> set to ROA request database.
00196     """
00197     key = (asn, group)
00198     if key not in self:
00199       self[key] = roa_request(asn, group)
00200     self[key].add(prefix)
00201 
00202   def xml(self, e):
00203     """
00204     Render ROA requests as XML elements.
00205     """
00206     for r in self.itervalues():
00207       r.xml(e)
00208 
00209   @classmethod
00210   def from_csv(cls, roa_csv_file):
00211     """
00212     Parse ROA requests from CSV file.
00213     """
00214     self = cls()
00215     # format:  p/n-m asn group
00216     for pnm, asn, group in csv_reader(roa_csv_file, columns = 3):
00217       self.add(asn = asn, group = group, prefix = pnm)
00218     return self
00219 
00220 class child(object):
00221   """
00222   Representation of one child entity.
00223   """
00224 
00225   v4re = re.compile("^(([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]+)|(([0-9]{1,3}\.){3}[0-9]{1,3}-([0-9]{1,3}\.){3}[0-9]{1,3})$", re.I)
00226   v6re = re.compile("^(([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4}/[0-9]+)|(([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4}-([0-9a-f]{0,4}:){0,15}[0-9a-f]{0,4})$", re.I)
00227 
00228   def __init__(self, handle):
00229     self.handle = handle
00230     self.asns = comma_set()
00231     self.v4 = comma_set()
00232     self.v6 = comma_set()
00233     self.validity = None
00234     self.bpki_certificate = None
00235 
00236   def __repr__(self):
00237     s = "<%s %s" % (self.__class__.__name__, self.handle)
00238     if self.asns:
00239       s += " asn %s" % self.asns
00240     if self.v4:
00241       s += " v4 %s" % self.v4
00242     if self.v6:
00243       s += " v6 %s" % self.v6
00244     if self.validity:
00245       s += " valid %s" % self.validity
00246     if self.bpki_certificate:
00247       s += " cert %s" % self.bpki_certificate
00248     return s + ">"
00249 
00250   def add(self, prefix = None, asn = None, validity = None, bpki_certificate = None):
00251     """
00252     Add prefix, autonomous system number, validity date, or BPKI
00253     certificate for this child.
00254     """
00255     if prefix is not None:
00256       if self.v4re.match(prefix):
00257         self.v4.add(prefix)
00258       elif self.v6re.match(prefix):
00259         self.v6.add(prefix)
00260       else:
00261         raise BadPrefixSyntax, "Bad prefix syntax: %r" % (prefix,)
00262     if asn is not None:
00263       self.asns.add(asn)
00264     if validity is not None:
00265       self.validity = validity
00266     if bpki_certificate is not None:
00267       self.bpki_certificate = bpki_certificate
00268 
00269   def xml(self, e):
00270     """
00271     Render this child as an XML element.
00272     """
00273     complete = self.bpki_certificate and self.validity
00274     if whine and not complete:
00275       print "Incomplete child entry %s" % self
00276     if complete or allow_incomplete:
00277       e = SubElement(e, "child",
00278                      handle = self.handle,
00279                      valid_until = self.validity,
00280                      asns = str(self.asns),
00281                      v4 = str(self.v4),
00282                      v6 = str(self.v6))
00283       e.tail = "\n"
00284       if self.bpki_certificate:
00285         PEMElement(e, "bpki_certificate", self.bpki_certificate)
00286 
00287 class children(dict):
00288   """
00289   Database of children.
00290   """
00291 
00292   def add(self, handle, prefix = None, asn = None, validity = None, bpki_certificate = None):
00293     """
00294     Add resources to a child, creating the child object if necessary.
00295     """
00296     if handle not in self:
00297       self[handle] = child(handle)
00298     self[handle].add(prefix = prefix, asn = asn, validity = validity, bpki_certificate = bpki_certificate)
00299 
00300   def xml(self, e):
00301     """
00302     Render children database to XML.
00303     """
00304     for c in self.itervalues():
00305       c.xml(e)
00306 
00307   @classmethod
00308   def from_entitydb(cls, prefix_csv_file, asn_csv_file, fxcert, entitydb):
00309     """
00310     Parse child data from entitydb.
00311     """
00312     self = cls()
00313     for f in entitydb.iterate("children"):
00314       c = etree_read(f)
00315       self.add(handle = os.path.splitext(os.path.split(f)[-1])[0],
00316                validity = c.get("valid_until"),
00317                bpki_certificate = fxcert(c.findtext("bpki_child_ta")))
00318     # childname p/n
00319     for handle, pn in csv_reader(prefix_csv_file, columns = 2):
00320       self.add(handle = handle, prefix = pn)
00321     # childname asn
00322     for handle, asn in csv_reader(asn_csv_file, columns = 2):
00323       self.add(handle = handle, asn = asn)
00324     return self
00325 
00326 class parent(object):
00327   """
00328   Representation of one parent entity.
00329   """
00330 
00331   def __init__(self, handle):
00332     self.handle = handle
00333     self.service_uri = None
00334     self.bpki_cms_certificate = None
00335     self.myhandle = None
00336     self.sia_base = None
00337 
00338   def __repr__(self):
00339     s = "<%s %s" % (self.__class__.__name__, self.handle)
00340     if self.myhandle:
00341       s += " myhandle %s" % self.myhandle
00342     if self.service_uri:
00343       s += " uri %s" % self.service_uri
00344     if self.sia_base:
00345       s += " sia %s" % self.sia_base
00346     if self.bpki_cms_certificate:
00347       s += " cms %s" % self.bpki_cms_certificate
00348     return s + ">"
00349 
00350   def add(self, service_uri = None,
00351           bpki_cms_certificate = None,
00352           myhandle = None,
00353           sia_base = None):
00354     """
00355     Add service URI or BPKI certificates to this parent object.
00356     """
00357     if service_uri is not None:
00358       self.service_uri = service_uri
00359     if bpki_cms_certificate is not None:
00360       self.bpki_cms_certificate = bpki_cms_certificate
00361     if myhandle is not None:
00362       self.myhandle = myhandle
00363     if sia_base is not None:
00364       self.sia_base = sia_base
00365 
00366   def xml(self, e):
00367     """
00368     Render this parent object to XML.
00369     """
00370     complete = self.bpki_cms_certificate and self.myhandle and self.service_uri and self.sia_base
00371     if whine and not complete:
00372       print "Incomplete parent entry %s" % self
00373     if complete or allow_incomplete:
00374       e = SubElement(e, "parent",
00375                      handle = self.handle,
00376                      myhandle = self.myhandle,
00377                      service_uri = self.service_uri,
00378                      sia_base = self.sia_base)
00379       e.tail = "\n"
00380       if self.bpki_cms_certificate:
00381         PEMElement(e, "bpki_cms_certificate", self.bpki_cms_certificate)
00382 
00383 class parents(dict):
00384   """
00385   Database of parent objects.
00386   """
00387 
00388   def add(self, handle,
00389           service_uri = None,
00390           bpki_cms_certificate = None,
00391           myhandle = None,
00392           sia_base = None):
00393     """
00394     Add service URI or certificates to parent object, creating it if necessary.
00395     """
00396     if handle not in self:
00397       self[handle] = parent(handle)
00398     self[handle].add(service_uri = service_uri,
00399                      bpki_cms_certificate = bpki_cms_certificate,
00400                      myhandle = myhandle,
00401                      sia_base = sia_base)
00402 
00403   def xml(self, e):
00404     for c in self.itervalues():
00405       c.xml(e)
00406 
00407   @classmethod
00408   def from_entitydb(cls, fxcert, entitydb):
00409     """
00410     Parse parent data from entitydb.
00411     """
00412     self = cls()
00413     for f in entitydb.iterate("parents"):
00414       h = os.path.splitext(os.path.split(f)[-1])[0]
00415       p = etree_read(f)
00416       r = etree_read(f.replace(os.path.sep + "parents"      + os.path.sep,
00417                                os.path.sep + "repositories" + os.path.sep))
00418       if r.get("type") == "confirmed":
00419         self.add(handle = h,
00420                  service_uri = p.get("service_uri"),
00421                  bpki_cms_certificate = fxcert(p.findtext("bpki_resource_ta")),
00422                  myhandle = p.get("child_handle"),
00423                  sia_base = r.get("sia_base"))
00424       elif whine:
00425         print "Parent %s's repository entry in state %s, skipping this parent" % (h, r.get("type"))
00426     return self
00427 
00428 class repository(object):
00429   """
00430   Representation of one repository entity.
00431   """
00432 
00433   def __init__(self, handle):
00434     self.handle = handle
00435     self.service_uri = None
00436     self.bpki_certificate = None
00437 
00438   def __repr__(self):
00439     s = "<%s %s" % (self.__class__.__name__, self.handle)
00440     if self.service_uri:
00441       s += " uri %s" % self.service_uri
00442     if self.bpki_certificate:
00443       s += " cert %s" % self.bpki_certificate
00444     return s + ">"
00445 
00446   def add(self, service_uri = None, bpki_certificate = None):
00447     """
00448     Add service URI or BPKI certificates to this repository object.
00449     """
00450     if service_uri is not None:
00451       self.service_uri = service_uri
00452     if bpki_certificate is not None:
00453       self.bpki_certificate = bpki_certificate
00454 
00455   def xml(self, e):
00456     """
00457     Render this repository object to XML.
00458     """
00459     complete = self.bpki_certificate and self.service_uri
00460     if whine and not complete:
00461       print "Incomplete repository entry %s" % self
00462     if complete or allow_incomplete:
00463       e = SubElement(e, "repository",
00464                      handle = self.handle,
00465                      service_uri = self.service_uri)
00466       e.tail = "\n"
00467       if self.bpki_certificate:
00468         PEMElement(e, "bpki_certificate", self.bpki_certificate)
00469 
00470 class repositories(dict):
00471   """
00472   Database of repository objects.
00473   """
00474 
00475   def add(self, handle,
00476           service_uri = None,
00477           bpki_certificate = None):
00478     """
00479     Add service URI or certificate to repository object, creating it if necessary.
00480     """
00481     if handle not in self:
00482       self[handle] = repository(handle)
00483     self[handle].add(service_uri = service_uri,
00484                      bpki_certificate = bpki_certificate)
00485 
00486   def xml(self, e):
00487     for c in self.itervalues():
00488       c.xml(e)
00489 
00490   @classmethod
00491   def from_entitydb(cls, fxcert, entitydb):
00492     """
00493     Parse repository data from entitydb.
00494     """
00495     self = cls()
00496     for f in entitydb.iterate("repositories"):
00497       h = os.path.splitext(os.path.split(f)[-1])[0]
00498       r = etree_read(f)
00499       if r.get("type") == "confirmed":
00500         self.add(handle = h,
00501                  service_uri = r.get("service_uri"),
00502                  bpki_certificate = fxcert(r.findtext("bpki_server_ta")))
00503       elif whine:
00504         print "Repository %s in state %s, skipping this repository" % (h, r.get("type"))
00505 
00506     return self
00507 
00508 class csv_reader(object):
00509   """
00510   Reader for tab-delimited text that's (slightly) friendlier than the
00511   stock Python csv module (which isn't intended for direct use by
00512   humans anyway, and neither was this package originally, but that
00513   seems to be the way that it has evolved...).
00514 
00515   Columns parameter specifies how many columns users of the reader
00516   expect to see; lines with fewer columns will be padded with None
00517   values.
00518 
00519   Original API design for this class courtesy of Warren Kumari, but
00520   don't blame him if you don't like what I did with his ideas.
00521   """
00522 
00523   def __init__(self, filename, columns = None, min_columns = None, comment_characters = "#;"):
00524     assert columns is None or isinstance(columns, int)
00525     assert min_columns is None or isinstance(min_columns, int)
00526     if columns is not None and min_columns is None:
00527       min_columns = columns
00528     self.filename = filename
00529     self.columns = columns
00530     self.min_columns = min_columns
00531     self.comment_characters = comment_characters 
00532     self.file = open(filename, "r")
00533 
00534   def __iter__(self):
00535     line_number = 0
00536     for line in self.file:
00537       line_number += 1
00538       line = line.strip()
00539       if not line or line[0] in self.comment_characters:
00540         continue
00541       fields = line.split()
00542       if self.min_columns is not None and len(fields) < self.min_columns:
00543         raise BadCSVSyntax, "%s:%d: Not enough columns in line %r" % (self.filename, line_number, line)
00544       if self.columns is not None and len(fields) > self.columns:
00545         raise BadCSVSyntax, "%s:%d: Too many  columns in line %r" % (self.filename, line_number, line)
00546       if self.columns is not None and len(fields) < self.columns:
00547         fields += tuple(None for i in xrange(self.columns - len(fields)))
00548       yield fields
00549 
00550 class csv_writer(object):
00551   """
00552   Writer object for tab delimited text.  We just use the stock CSV
00553   module in excel-tab mode for this.
00554 
00555   If "renmwo" is set (default), the file will be written to
00556   a temporary name and renamed to the real filename after closing.
00557   """
00558 
00559   def __init__(self, filename, renmwo = True):
00560     self.filename = filename
00561     self.renmwo = "%s.~renmwo%d~" % (filename, os.getpid()) if renmwo else filename
00562     self.file = open(self.renmwo, "w")
00563     self.writer = csv.writer(self.file, dialect = csv.get_dialect("excel-tab"))
00564 
00565   def close(self):
00566     """
00567     Close this writer.
00568     """
00569     if self.file is not None:
00570       self.file.close()
00571       self.file = None
00572       if self.filename != self.renmwo:
00573         os.rename(self.renmwo, self.filename)
00574 
00575   def __getattr__(self, attr):
00576     """
00577     Fake inheritance from whatever object csv.writer deigns to give us.
00578     """
00579     return getattr(self.writer, attr)
00580 
00581 def PEMBase64(filename):
00582   """
00583   Extract Base64 encoded data from a PEM file.
00584   """
00585   lines = open(filename).readlines()
00586   while lines:
00587     if lines.pop(0).startswith("-----BEGIN "):
00588       break
00589   while lines:
00590     if lines.pop(-1).startswith("-----END "):
00591       break
00592   return "".join(lines)
00593 
00594 def PEMElement(e, tag, filename, **kwargs):
00595   """
00596   Create an XML element containing Base64 encoded data taken from a
00597   PEM file.
00598   """
00599   if e.text is None:
00600     e.text = "\n"
00601   se = SubElement(e, tag, **kwargs)
00602   se.text = "\n" + PEMBase64(filename)
00603   se.tail = "\n"
00604   return se
00605 
00606 class CA(object):
00607   """
00608   Representation of one certification authority.
00609   """
00610 
00611   # Mapping of path restriction values we use to OpenSSL config file
00612   # section names.
00613 
00614   path_restriction = { 0 : "ca_x509_ext_xcert0",
00615                        1 : "ca_x509_ext_xcert1" }
00616 
00617   def __init__(self, cfg_file, dir):
00618     self.cfg    = cfg_file
00619     self.dir    = dir
00620     self.cer    = dir + "/ca.cer"
00621     self.key    = dir + "/ca.key"
00622     self.req    = dir + "/ca.req"
00623     self.crl    = dir + "/ca.crl"
00624     self.index  = dir + "/index"
00625     self.serial = dir + "/serial"
00626     self.crlnum = dir + "/crl_number"
00627 
00628     cfg = rpki.config.parser(cfg_file, "myrpki")
00629     self.openssl = cfg.get("openssl", "openssl")
00630 
00631     self.env = { "PATH" : os.environ["PATH"],
00632                  "BPKI_DIRECTORY" : dir,
00633                  "RANDFILE" : ".OpenSSL.whines.unless.I.set.this",
00634                  "OPENSSL_CONF" : cfg_file }
00635 
00636   def run_openssl(self, *cmd, **kwargs):
00637     """
00638     Run an OpenSSL command, suppresses stderr unless OpenSSL returns
00639     failure, and returns stdout.
00640     """
00641     stdin = kwargs.pop("stdin", None)
00642     env = self.env.copy()
00643     env.update(kwargs)
00644     cmd = (self.openssl,) + cmd
00645     p = subprocess.Popen(cmd, env = env, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
00646     stdout, stderr = p.communicate(stdin)
00647     if p.wait() != 0:
00648       sys.stderr.write("OpenSSL command failed: " + stderr + "\n")
00649       raise subprocess.CalledProcessError(returncode = p.returncode, cmd = cmd)
00650     return stdout
00651 
00652   def run_ca(self, *args):
00653     """
00654     Run OpenSSL "ca" command with common initial arguments.
00655     """
00656     self.run_openssl("ca", "-batch", "-config",  self.cfg, *args)
00657 
00658   def run_req(self, key_file, req_file, log_key = sys.stdout):
00659     """
00660     Run OpenSSL "genrsa" and  "req" commands.
00661     """
00662     if not os.path.exists(key_file):
00663       if log_key:
00664         log_key.write("Generating 2048-bit RSA key %s\n" % os.path.realpath(key_file))
00665       self.run_openssl("genrsa", "-out", key_file, "2048")
00666     if not os.path.exists(req_file):
00667       self.run_openssl("req", "-new", "-sha256", "-config", self.cfg, "-key", key_file, "-out", req_file)
00668 
00669   def run_dgst(self, input, algorithm = "md5"):
00670     """
00671     Run OpenSSL "dgst" command, return cleaned-up result.
00672     """
00673     hash = self.run_openssl("dgst", "-" + algorithm, stdin = input)
00674     #
00675     # Twits just couldn't leave well enough alone, grr.
00676     hash = "".join(hash.split())
00677     if hash.startswith("(stdin)="):
00678       hash =  hash[len("(stdin)="):]
00679     return hash
00680 
00681   @staticmethod
00682   def touch_file(filename, content = None):
00683     """
00684     Create dumb little text files expected by OpenSSL "ca" utility.
00685     """
00686     if not os.path.exists(filename):
00687       f = open(filename, "w")
00688       if content is not None:
00689         f.write(content)
00690       f.close()
00691 
00692   def setup(self, ca_name):
00693     """
00694     Set up this CA.  ca_name is an X.509 distinguished name in
00695     /tag=val/tag=val format.
00696     """
00697 
00698     modified = False
00699 
00700     if not os.path.exists(self.dir):
00701       os.makedirs(self.dir)
00702       self.touch_file(self.index)
00703       self.touch_file(self.serial, "01\n")
00704       self.touch_file(self.crlnum, "01\n")
00705 
00706     self.run_req(key_file = self.key, req_file = self.req)
00707 
00708     if not os.path.exists(self.cer):
00709       modified = True
00710       self.run_ca("-selfsign", "-extensions", "ca_x509_ext_ca", "-subj", ca_name, "-in", self.req, "-out", self.cer)
00711 
00712     if not os.path.exists(self.crl):
00713       modified = True
00714       self.run_ca("-gencrl", "-out", self.crl)
00715 
00716     return modified
00717 
00718   def ee(self, ee_name, base_name):
00719     """
00720     Issue an end-enity certificate.
00721     """
00722     key_file = "%s/%s.key" % (self.dir, base_name)
00723     req_file = "%s/%s.req" % (self.dir, base_name)
00724     cer_file = "%s/%s.cer" % (self.dir, base_name)
00725     self.run_req(key_file = key_file, req_file = req_file)
00726     if not os.path.exists(cer_file):
00727       self.run_ca("-extensions", "ca_x509_ext_ee", "-subj", ee_name, "-in", req_file, "-out", cer_file)
00728       return True
00729     else:
00730       return False
00731 
00732   def cms_xml_sign(self, ee_name, base_name, elt):
00733     """
00734     Sign an XML object with CMS, return Base64 text.
00735     """
00736     self.ee(ee_name, base_name)
00737     return base64.b64encode(self.run_openssl(
00738       "cms", "-sign", "-binary", "-outform", "DER",
00739       "-keyid", "-md", "sha256", "-nodetach", "-nosmimecap",
00740       "-econtent_type", ".".join(str(i) for i in rpki.oids.name2oid["id-ct-xml"]),
00741       "-inkey",  "%s/%s.key" % (self.dir, base_name),
00742       "-signer", "%s/%s.cer" % (self.dir, base_name),
00743       stdin = ElementToString(etree_pre_write(elt))))
00744 
00745   def cms_xml_verify(self, b64, ca):
00746     """
00747     Attempt to verify and extract XML from a Base64-encoded signed CMS
00748     object.  CA is the filename of a certificate that we expect to be
00749     the issuer of the EE certificate bundled with the CMS, and must
00750     previously have been cross-certified under our trust anchor.
00751     """
00752     # In theory, we should be able to use the -certfile parameter to
00753     # pass in the CA certificate, but in practice, I have never gotten
00754     # this to work, either with the command line tool or in the
00755     # OpenSSL C API.  Dunno why.  Passing both TA and CA via -CAfile
00756     # does work, so we do that, using a temporary file, sigh.
00757     CAfile = os.path.join(self.dir, "temp.%s.pem" % os.getpid())
00758     try:
00759       f = open(CAfile, "w")
00760       f.write(open(self.cer).read())
00761       f.write(open(ca).read())
00762       f.close()
00763       return etree_post_read(ElementFromString(self.run_openssl(
00764         "cms", "-verify", "-inform", "DER", "-CAfile", CAfile,
00765         stdin = base64.b64decode(b64))))
00766     finally:
00767       if os.path.exists(CAfile):
00768         os.unlink(CAfile)
00769 
00770   def bsc(self, pkcs10):
00771     """
00772     Issue BSC certificate, if we have a PKCS #10 request for it.
00773     """
00774 
00775     if pkcs10 is None:
00776       return None, None
00777 
00778     pkcs10 = base64.b64decode(pkcs10)
00779 
00780     hash = self.run_dgst(pkcs10)
00781 
00782     req_file = "%s/bsc.%s.req" % (self.dir, hash)
00783     cer_file = "%s/bsc.%s.cer" % (self.dir, hash)
00784 
00785     if not os.path.exists(cer_file):
00786       self.run_openssl("req", "-inform", "DER", "-out", req_file, stdin = pkcs10)
00787       self.run_ca("-extensions", "ca_x509_ext_ee", "-in", req_file, "-out", cer_file)
00788 
00789     return req_file, cer_file
00790 
00791   def fxcert(self, b64, filename = None, path_restriction = 0):
00792     """
00793     Write PEM certificate to file, then cross-certify.
00794     """
00795     fn = os.path.join(self.dir, filename or "temp.%s.cer" % os.getpid())
00796     try:
00797       self.run_openssl("x509", "-inform", "DER", "-out", fn,
00798                        stdin = base64.b64decode(b64))
00799       return self.xcert(fn, path_restriction)
00800     finally:
00801       if not filename and os.path.exists(fn):
00802         os.unlink(fn)
00803 
00804   def xcert_filename(self, cert):
00805     """
00806     Generate filename for a cross-certification.
00807 
00808     Extracts public key and subject name from PEM file and hash it so
00809     we can use the result as a tag for cross-certifying this cert.
00810     """
00811 
00812     if cert and os.path.exists(cert):
00813       return "%s/xcert.%s.cer" % (self.dir, self.run_dgst(self.run_openssl(
00814         "x509", "-noout", "-pubkey", "-subject", "-in", cert)).strip())
00815     else:
00816       return None
00817 
00818   def xcert(self, cert, path_restriction = 0):
00819     """
00820     Cross-certify a certificate represented as a PEM file, if we
00821     haven't already.  This only works for self-signed certs, due to
00822     limitations of the OpenSSL command line tool, but that suffices
00823     for our purposes.
00824     """
00825 
00826     xcert = self.xcert_filename(cert)
00827     if not os.path.exists(xcert):
00828       self.run_ca("-ss_cert", cert, "-out", xcert, "-extensions", self.path_restriction[path_restriction])
00829     return xcert
00830 
00831   def xcert_revoke(self, cert):
00832     """
00833     Revoke a cross-certification and regenerate CRL.
00834     """
00835 
00836     xcert = self.xcert_filename(cert)
00837     if xcert:
00838       self.run_ca("-revoke", xcert)
00839       self.run_ca("-gencrl", "-out", self.crl)
00840 
00841 def etree_validate(e):
00842   # This is a kludge, schema should be loaded as module or configured
00843   # in .conf, but it will do as a temporary debugging hack.
00844   schema = os.getenv("MYRPKI_RNG")
00845   if schema:
00846     try:
00847       import lxml.etree
00848     except ImportError:
00849       return
00850     try:
00851       lxml.etree.RelaxNG(file = schema).assertValid(e)
00852     except lxml.etree.RelaxNGParseError:
00853       return
00854     except lxml.etree.DocumentInvalid:
00855       print lxml.etree.tostring(e, pretty_print = True)
00856       raise
00857 
00858 def etree_write(e, filename, verbose = False, validate = True, msg = None):
00859   """
00860   Write out an etree to a file, safely.
00861 
00862   I still miss SYSCAL(RENMWO).
00863   """
00864   filename = os.path.realpath(filename)
00865   tempname = filename
00866   if not filename.startswith("/dev/"):
00867     tempname += ".tmp"
00868   if verbose or msg:
00869     print "Writing", filename
00870   if msg:
00871     print msg
00872   e = etree_pre_write(e, validate)
00873   ElementTree(e).write(tempname)
00874   if tempname != filename:
00875     os.rename(tempname, filename)
00876 
00877 def etree_pre_write(e, validate = True):
00878   """
00879   Do the namespace frobbing needed on write; broken out of
00880   etree_write() because also needed with ElementToString().
00881   """
00882   e = copy.deepcopy(e)
00883   e.set("version", version)
00884   for i in e.getiterator():
00885     if i.tag[0] != "{":
00886       i.tag = namespaceQName + i.tag
00887     assert i.tag.startswith(namespaceQName)
00888   if validate:
00889     etree_validate(e)
00890   return e
00891 
00892 def etree_read(filename, verbose = False, validate = True):
00893   """
00894   Read an etree from a file, verifying then stripping XML namespace
00895   cruft.
00896   """
00897   if verbose:
00898     print "Reading", filename
00899   e = ElementTree(file = filename).getroot()
00900   return etree_post_read(e, validate)
00901 
00902 def etree_post_read(e, validate = True):
00903   """
00904   Do the namespace frobbing needed on read; broken out of etree_read()
00905   beause also needed by ElementFromString().
00906   """
00907   if validate:
00908     etree_validate(e)
00909   for i in e.getiterator():
00910     if i.tag.startswith(namespaceQName):
00911       i.tag = i.tag[len(namespaceQName):]
00912     else:
00913       raise BadXMLMessage, "XML tag %r is not in namespace %r" % (i.tag, namespace)
00914   return e
00915 
00916 def b64_equal(thing1, thing2):
00917   """
00918   Compare two Base64-encoded values for equality.
00919   """
00920   return "".join(thing1.split()) == "".join(thing2.split())
00921 
00922 
00923 
00924 class IRDB(object):
00925   """
00926   Front-end to the IRDB.  This is broken out from class main so
00927   that other applications (namely, the portal-gui) can reuse it.
00928   """
00929   def __init__(self, cfg):
00930     """
00931     Opens a new connection to the IRDB, using the configuration
00932     information from a rpki.config.parser object.
00933     """
00934 
00935     from rpki.mysql_import import MySQLdb
00936 
00937     irdbd_cfg = rpki.config.parser(cfg.get("irdbd_conf", cfg.filename), "irdbd")
00938 
00939     self.db = MySQLdb.connect(user   = irdbd_cfg.get("sql-username"),
00940                               db     = irdbd_cfg.get("sql-database"),
00941                               passwd = irdbd_cfg.get("sql-password"))
00942 
00943   def update(self, handle, roa_requests, children, ghostbusters=None):
00944     """
00945     Update the IRDB for a given resource handle.  Removes all
00946     existing data and replaces it with that specified in the
00947     argument list.
00948 
00949     The "roa_requests" argument is a sequence of tuples of the form
00950     (asID, v4_addresses, v6_addresses), where "v*_addresses" are
00951     instances of rpki.resource_set.roa_prefix_set_ipv*.
00952 
00953     The "children" argument is a sequence of tuples of the form
00954     (child_handle, asns, v4addrs, v6addrs, valid_until),
00955     where "asns" is an instance of rpki.resource_set.resource_set_asn,
00956     "v*addrs" are instances of rpki.resource_set.resource_set_ipv*,
00957     and "valid_until" is an instance of rpki.sundial.datetime.
00958 
00959     The "ghostbusters" argument is a sequence of tuples of the form
00960     (parent_handle, vcard_string).  "parent_handle" may be value None,
00961     in which case the specified vcard object will be used for all
00962     parents.
00963     """
00964 
00965     cur = self.db.cursor()
00966 
00967     cur.execute(
00968       """
00969       DELETE
00970       FROM  roa_request_prefix
00971       USING roa_request, roa_request_prefix
00972       WHERE roa_request.roa_request_id = roa_request_prefix.roa_request_id AND roa_request.roa_request_handle = %s
00973       """, (handle,))
00974 
00975     cur.execute("DELETE FROM roa_request WHERE roa_request.roa_request_handle = %s", (handle,))
00976 
00977     for asID, v4addrs, v6addrs in roa_requests:
00978       assert isinstance(v4addrs, rpki.resource_set.roa_prefix_set_ipv4)
00979       assert isinstance(v6addrs, rpki.resource_set.roa_prefix_set_ipv6)
00980       cur.execute("INSERT roa_request (roa_request_handle, asn) VALUES (%s, %s)", (handle, asID))
00981       roa_request_id = cur.lastrowid
00982       for version, prefix_set in ((4, v4addrs), (6, v6addrs)):
00983         if prefix_set:
00984           cur.executemany("INSERT roa_request_prefix (roa_request_id, prefix, prefixlen, max_prefixlen, version) VALUES (%s, %s, %s, %s, %s)",
00985                           ((roa_request_id, p.prefix, p.prefixlen, p.max_prefixlen, version) for p in prefix_set))
00986 
00987     cur.execute(
00988       """
00989       DELETE
00990       FROM   registrant_asn
00991       USING registrant, registrant_asn
00992       WHERE registrant.registrant_id = registrant_asn.registrant_id AND registrant.registry_handle = %s
00993       """ , (handle,))
00994 
00995     cur.execute(
00996       """
00997       DELETE FROM registrant_net USING registrant, registrant_net
00998       WHERE registrant.registrant_id = registrant_net.registrant_id AND registrant.registry_handle = %s
00999       """ , (handle,))
01000 
01001     cur.execute("DELETE FROM registrant WHERE registrant.registry_handle = %s" , (handle,))
01002 
01003     for child_handle, asns, ipv4, ipv6, valid_until in children:
01004       cur.execute("INSERT registrant (registrant_handle, registry_handle, registrant_name, valid_until) VALUES (%s, %s, %s, %s)",
01005                   (child_handle, handle, child_handle, valid_until.to_sql()))
01006       child_id = cur.lastrowid
01007       if asns:
01008         cur.executemany("INSERT registrant_asn (start_as, end_as, registrant_id) VALUES (%s, %s, %s)",
01009                         ((a.min, a.max, child_id) for a in asns))
01010       if ipv4:
01011         cur.executemany("INSERT registrant_net (start_ip, end_ip, version, registrant_id) VALUES (%s, %s, 4, %s)",
01012                         ((a.min, a.max, child_id) for a in ipv4))
01013       if ipv6:
01014         cur.executemany("INSERT registrant_net (start_ip, end_ip, version, registrant_id) VALUES (%s, %s, 6, %s)",
01015                         ((a.min, a.max, child_id) for a in ipv6))
01016 
01017     # don't munge the ghostbuster_request table when the arg is None.
01018     # this allows the cli to safely run configure_resources without
01019     # stomping on GBRs created by the portal gui.
01020     if ghostbusters is not None:
01021       cur.execute("DELETE FROM ghostbuster_request WHERE self_handle = %s", (handle,))
01022       if ghostbusters:
01023         cur.executemany("INSERT INTO ghostbuster_request (self_handle, parent_handle, vcard) VALUES (%s, %s, %s)",
01024                         ((handle, parent_handle, vcard) for parent_handle, vcard in ghostbusters))
01025 
01026     self.db.commit()
01027 
01028   def close(self):
01029     """
01030     Close the connection to the IRDB.
01031     """
01032     self.db.close()
01033     
01034 
01035 
01036 class main(rpki.cli.Cmd):
01037 
01038   prompt = "myrpki> "
01039 
01040   completedefault = rpki.cli.Cmd.filename_complete
01041 
01042   show_xml = False
01043 
01044   def __init__(self):
01045     os.environ["TZ"] = "UTC"
01046     time.tzset()
01047 
01048     rpki.log.use_syslog = False
01049 
01050     self.cfg_file = None
01051 
01052     opts, argv = getopt.getopt(sys.argv[1:], "c:h?", ["config=", "help"])
01053     for o, a in opts:
01054       if o in ("-c", "--config"):
01055         self.cfg_file = a
01056       elif o in ("-h", "--help", "-?"):
01057         argv = ["help"]
01058 
01059     if not argv or argv[0] != "help":
01060       rpki.log.init("myrpki")
01061       self.read_config()
01062 
01063     rpki.cli.Cmd.__init__(self, argv)
01064 
01065 
01066   def help_overview(self):
01067     """
01068     Show program __doc__ string.  Perhaps there's some clever way to
01069     do this using the textwrap module, but for now something simple
01070     and crude will suffice.
01071     """
01072     for line in __doc__.splitlines(True):
01073       self.stdout.write(" " * 4 + line)
01074     self.stdout.write("\n")
01075 
01076   def entitydb_complete(self, prefix, text, line, begidx, endidx):
01077     """
01078     Completion helper for entitydb filenames.
01079     """
01080     names = []
01081     for name in self.entitydb.iterate(prefix):
01082       name = os.path.splitext(os.path.basename(name))[0]
01083       if name.startswith(text):
01084         names.append(name)
01085     return names
01086 
01087   def read_config(self):
01088 
01089     self.cfg = rpki.config.parser(self.cfg_file, "myrpki")
01090 
01091     self.histfile  = self.cfg.get("history_file", ".myrpki_history")
01092     self.handle    = self.cfg.get("handle")
01093     self.run_rpkid = self.cfg.getboolean("run_rpkid")
01094     self.run_pubd  = self.cfg.getboolean("run_pubd")
01095     self.run_rootd = self.cfg.getboolean("run_rootd")
01096     self.entitydb  = EntityDB(self.cfg)
01097 
01098     if self.run_rootd and (not self.run_pubd or not self.run_rpkid):
01099       raise CantRunRootd, "Can't run rootd unless also running rpkid and pubd"
01100 
01101     self.bpki_resources = CA(self.cfg.filename, self.cfg.get("bpki_resources_directory"))
01102     if self.run_rpkid or self.run_pubd or self.run_rootd:
01103       self.bpki_servers = CA(self.cfg.filename, self.cfg.get("bpki_servers_directory"))
01104     else:
01105       self.bpki_servers = None
01106 
01107     self.default_repository = self.cfg.get("default_repository", "")
01108     self.pubd_contact_info = self.cfg.get("pubd_contact_info", "")
01109 
01110     self.rsync_module = self.cfg.get("publication_rsync_module")
01111     self.rsync_server = self.cfg.get("publication_rsync_server")
01112 
01113 
01114   def do_initialize(self, arg):
01115     """
01116     Initialize an RPKI installation.  This command reads the
01117     configuration file, creates the BPKI and EntityDB directories,
01118     generates the initial BPKI certificates, and creates an XML file
01119     describing the resource-holding aspect of this RPKI installation.
01120     """
01121 
01122     if arg:
01123       raise BadCommandSyntax, "This command takes no arguments"
01124 
01125     self.bpki_resources.setup(self.cfg.get("bpki_resources_ta_dn",
01126                                            "/CN=%s BPKI Resource Trust Anchor" % self.handle))
01127     if self.run_rpkid or self.run_pubd or self.run_rootd:
01128       self.bpki_servers.setup(self.cfg.get("bpki_servers_ta_dn",
01129                                            "/CN=%s BPKI Server Trust Anchor" % self.handle))
01130 
01131     # Create entitydb directories.
01132 
01133     for i in ("parents", "children", "repositories", "pubclients"):
01134       d = self.entitydb(i)
01135       if not os.path.exists(d):
01136         os.makedirs(d)
01137 
01138     if self.run_rpkid or self.run_pubd or self.run_rootd:
01139 
01140       if self.run_rpkid:
01141         self.bpki_servers.ee(self.cfg.get("bpki_rpkid_ee_dn",
01142                                           "/CN=%s rpkid server certificate" % self.handle), "rpkid")
01143         self.bpki_servers.ee(self.cfg.get("bpki_irdbd_ee_dn",
01144                                           "/CN=%s irdbd server certificate" % self.handle), "irdbd")
01145       if self.run_pubd:
01146         self.bpki_servers.ee(self.cfg.get("bpki_pubd_ee_dn",
01147                                           "/CN=%s pubd server certificate" % self.handle), "pubd")
01148       if self.run_rpkid or self.run_pubd:
01149         self.bpki_servers.ee(self.cfg.get("bpki_irbe_ee_dn",
01150                                           "/CN=%s irbe client certificate" % self.handle), "irbe")
01151       if self.run_rootd:
01152         self.bpki_servers.ee(self.cfg.get("bpki_rootd_ee_dn",
01153                                           "/CN=%s rootd server certificate" % self.handle), "rootd")
01154 
01155     # Build the identity.xml file.  Need to check for existing file so we don't
01156     # overwrite?  Worry about that later.
01157 
01158     e = Element("identity", handle = self.handle)
01159     PEMElement(e, "bpki_ta", self.bpki_resources.cer)
01160     etree_write(e, self.entitydb.identity,
01161                 msg = None if self.run_rootd else 'This is the "identity" file you will need to send to your parent')
01162 
01163     # If we're running rootd, construct a fake parent to go with it,
01164     # and cross-certify in both directions so we can talk to rootd.
01165 
01166     if self.run_rootd:
01167 
01168       e = Element("parent", parent_handle = self.handle, child_handle = self.handle,
01169                   service_uri = "http://localhost:%s/" % self.cfg.get("rootd_server_port"),
01170                   valid_until = str(rpki.sundial.now() + rpki.sundial.timedelta(days = 365)))
01171       PEMElement(e, "bpki_resource_ta", self.bpki_servers.cer)
01172       PEMElement(e, "bpki_child_ta", self.bpki_resources.cer)
01173       SubElement(e, "repository", type = "offer")
01174       etree_write(e, self.entitydb("parents", self.handle))
01175 
01176       self.bpki_resources.xcert(self.bpki_servers.cer)
01177 
01178       rootd_child_fn = self.cfg.get("child-bpki-cert", None, "rootd")
01179       if not os.path.exists(rootd_child_fn):
01180         os.link(self.bpki_servers.xcert(self.bpki_resources.cer), rootd_child_fn)
01181 
01182       repo_file_name = self.entitydb("repositories", self.handle)
01183 
01184       try:
01185         want_offer = etree_read(repo_file_name).get("type") != "confirmed"
01186       except IOError:
01187         want_offer = True
01188 
01189       if want_offer:
01190         e = Element("repository", type = "offer", handle = self.handle, parent_handle = self.handle)
01191         PEMElement(e, "bpki_client_ta", self.bpki_resources.cer)
01192         etree_write(e, repo_file_name,
01193                     msg = 'This is the "repository offer" file for you to use if you want to publish in your own repository')
01194 
01195 
01196   def do_update_bpki(self, arg):
01197     """
01198     Update BPKI certificates.  Assumes an existing RPKI installation.
01199 
01200     Basic plan here is to reissue all BPKI certificates we can, right
01201     now.  In the long run we might want to be more clever about only
01202     touching ones that need maintenance, but this will do for a start.
01203 
01204     Most likely this should be run under cron.
01205     """
01206 
01207     if self.bpki_servers:
01208       bpkis = (self.bpki_resources, self.bpki_servers)
01209     else:
01210       bpkis = (self.bpki_resources,)
01211 
01212     for bpki in bpkis:
01213       for cer in glob.iglob("%s/*.cer" % bpki.dir):
01214         key = cer[0:-4] + ".key"
01215         req = cer[0:-4] + ".req"
01216         if os.path.exists(key):
01217           print "Regenerating BPKI PKCS #10", req
01218           bpki.run_openssl("x509", "-x509toreq", "-in", cer, "-out", req, "-signkey", key)
01219         print "Clearing BPKI certificate", cer
01220         os.unlink(cer)
01221         if cer == bpki.cer:
01222           assert req == bpki.req
01223           print "Regenerating certificate", cer
01224           bpki.run_ca("-selfsign", "-extensions", "ca_x509_ext_ca", "-in", req, "-out", cer)
01225 
01226     print "Regenerating CRLs"
01227     for bpki in bpkis:
01228       bpki.run_ca("-gencrl", "-out", bpki.crl)
01229 
01230     self.do_initialize(None)
01231     if self.run_rpkid or self.run_pubd or self.run_rootd:
01232       self.do_configure_daemons(arg)
01233     else:
01234       self.do_configure_resources(None)
01235 
01236 
01237   def do_configure_child(self, arg):
01238     """
01239     Configure a new child of this RPKI entity, given the child's XML
01240     identity file as an input.  This command extracts the child's data
01241     from the XML, cross-certifies the child's resource-holding BPKI
01242     certificate, and generates an XML file describing the relationship
01243     between the child and this parent, including this parent's BPKI
01244     data and up-down protocol service URI.
01245     """
01246 
01247     child_handle = None
01248 
01249     opts, argv = getopt.getopt(arg.split(), "", ["child_handle="])
01250     for o, a in opts:
01251       if o == "--child_handle":
01252         child_handle = a
01253     
01254     if len(argv) != 1:
01255       raise BadCommandSyntax, "Need to specify filename for child.xml"
01256 
01257     c = etree_read(argv[0])
01258 
01259     if child_handle is None:
01260       child_handle = c.get("handle")
01261 
01262     try:
01263       e = etree_read(self.cfg.get("xml_filename"))
01264       service_uri_base = e.get("service_uri")
01265 
01266     except IOError:
01267       if self.run_rpkid:
01268         service_uri_base = "http://%s:%s/up-down/%s" % (self.cfg.get("rpkid_server_host"),
01269                                                         self.cfg.get("rpkid_server_port"),
01270                                                         self.handle)
01271       else:
01272         service_uri_base = None      
01273 
01274     if not service_uri_base:
01275       print "Sorry, you can't set up children of a hosted config that itself has not yet been set up"
01276       return
01277 
01278     print "Child calls itself %r, we call it %r" % (c.get("handle"), child_handle)
01279 
01280     if self.run_rpkid or self.run_pubd or self.run_rootd:
01281       self.bpki_servers.fxcert(c.findtext("bpki_ta"))
01282 
01283     e = Element("parent", parent_handle = self.handle, child_handle = child_handle,
01284                 service_uri = "%s/%s" % (service_uri_base, child_handle),
01285                 valid_until = str(rpki.sundial.now() + rpki.sundial.timedelta(days = 365)))
01286 
01287     PEMElement(e, "bpki_resource_ta", self.bpki_resources.cer)
01288     SubElement(e, "bpki_child_ta").text = c.findtext("bpki_ta")
01289 
01290     repo = None
01291     for f in self.entitydb.iterate("repositories"):
01292       r = etree_read(f)
01293       if r.get("type") == "confirmed":
01294         h = os.path.splitext(os.path.split(f)[-1])[0]
01295         if repo is None or h == self.default_repository:
01296           repo_handle = h
01297           repo = r
01298 
01299     if repo is None:
01300       print "Couldn't find any usable repositories, not giving referral"
01301 
01302     elif repo_handle == self.handle:
01303       SubElement(e, "repository", type = "offer")
01304 
01305     else:
01306       proposed_sia_base = repo.get("sia_base") + child_handle + "/"
01307       r = Element("referral", authorized_sia_base = proposed_sia_base)
01308       r.text = c.findtext("bpki_ta")
01309       auth = self.bpki_resources.cms_xml_sign(
01310         "/CN=%s Publication Referral" % self.handle, "referral", r)
01311 
01312       r = SubElement(e, "repository", type = "referral")
01313       SubElement(r, "authorization", referrer = repo.get("client_handle")).text = auth
01314       SubElement(r, "contact_info").text = repo.findtext("contact_info")
01315 
01316     etree_write(e, self.entitydb("children", child_handle),
01317                 msg = "Send this file back to the child you just configured")
01318 
01319 
01320   def do_delete_child(self, arg):
01321     """
01322     Delete a child of this RPKI entity.
01323 
01324     This should check that the XML file it's deleting really is a
01325     child, but doesn't, yet.
01326     """
01327 
01328     try:
01329       os.unlink(self.entitydb("children", arg))
01330     except OSError:
01331       print "No such child \"%s\"" % arg
01332 
01333   def complete_delete_child(self, *args):
01334     return self.entitydb_complete("children", *args)
01335 
01336 
01337   def do_configure_parent(self, arg):
01338     """
01339     Configure a new parent of this RPKI entity, given the output of
01340     the parent's configure_child command as input.  This command reads
01341     the parent's response XML, extracts the parent's BPKI and service
01342     URI information, cross-certifies the parent's BPKI data into this
01343     entity's BPKI, and checks for offers or referrals of publication
01344     service.  If a publication offer or referral is present, we
01345     generate a request-for-service message to that repository, in case
01346     the user wants to avail herself of the referral or offer.
01347     """
01348 
01349     parent_handle = None
01350 
01351     opts, argv = getopt.getopt(arg.split(), "", ["parent_handle="])
01352     for o, a in opts:
01353       if o == "--parent_handle":
01354         parent_handle = a
01355 
01356     if len(argv) != 1:
01357       raise BadCommandSyntax, "Need to specify filename for parent.xml on command line"
01358 
01359     p = etree_read(argv[0])
01360 
01361     if parent_handle is None:
01362       parent_handle = p.get("parent_handle")
01363 
01364     print "Parent calls itself %r, we call it %r" % (p.get("parent_handle"), parent_handle)
01365     print "Parent calls us %r" % p.get("child_handle")
01366 
01367     self.bpki_resources.fxcert(p.findtext("bpki_resource_ta"))
01368 
01369     etree_write(p, self.entitydb("parents", parent_handle))
01370 
01371     r = p.find("repository")
01372 
01373     if r is None or r.get("type") not in ("offer", "referral"):
01374       r = Element("repository", type = "none")
01375 
01376     r.set("handle", self.handle)
01377     r.set("parent_handle", parent_handle)
01378     PEMElement(r, "bpki_client_ta", self.bpki_resources.cer)
01379     etree_write(r, self.entitydb("repositories", parent_handle),
01380                 msg = "This is the file to send to the repository operator")
01381 
01382 
01383   def do_delete_parent(self, arg):
01384     """
01385     Delete a parent of this RPKI entity.
01386 
01387     This should check that the XML file it's deleting really is a
01388     parent, but doesn't, yet.
01389     """
01390 
01391     try:
01392       os.unlink(self.entitydb("parents", arg))
01393     except OSError:
01394       print "No such parent \"%s\"" % arg
01395 
01396   def complete_delete_parent(self, *args):
01397     return self.entitydb_complete("parents", *args)
01398 
01399 
01400   def do_configure_publication_client(self, arg):
01401     """
01402     Configure publication server to know about a new client, given the
01403     client's request-for-service message as input.  This command reads
01404     the client's request for service, cross-certifies the client's
01405     BPKI data, and generates a response message containing the
01406     repository's BPKI data and service URI.
01407     """
01408 
01409     sia_base = None
01410 
01411     opts, argv = getopt.getopt(arg.split(), "", ["sia_base="])
01412     for o, a in opts:
01413       if o == "--sia_base":
01414         sia_base = a
01415     
01416     if len(argv) != 1:
01417       raise BadCommandSyntax, "Need to specify filename for client.xml"
01418 
01419     client = etree_read(argv[0])
01420 
01421     if sia_base is None and client.get("handle") == self.handle and b64_equal(PEMBase64(self.bpki_resources.cer), client.findtext("bpki_client_ta")):
01422       print "This looks like self-hosted publication"
01423       sia_base = "rsync://%s/%s/%s/" % (self.rsync_server, self.rsync_module, self.handle)
01424 
01425     if sia_base is None and client.get("type") == "referral":
01426       print "This looks like a referral, checking"
01427       try:
01428         auth = client.find("authorization")
01429         if auth is None:
01430           raise BadXMLMessage, "Malformed referral, couldn't find <auth/> element"
01431         referrer = etree_read(self.entitydb("pubclients", auth.get("referrer").replace("/",".")))
01432         referrer = self.bpki_servers.fxcert(referrer.findtext("bpki_client_ta"))
01433         referral = self.bpki_servers.cms_xml_verify(auth.text, referrer)
01434         if not b64_equal(referral.text, client.findtext("bpki_client_ta")):
01435           raise BadXMLMessage, "Referral trust anchor does not match"
01436         sia_base = referral.get("authorized_sia_base")
01437       except IOError:
01438         print "We have no record of client (%s) alleged to have made this referral" % auth.get("referrer")
01439 
01440     if sia_base is None and client.get("type") == "offer" and client.get("parent_handle") == self.handle:
01441       print "This looks like an offer, client claims to be our child, checking"
01442       client_ta = client.findtext("bpki_client_ta")
01443       if not client_ta:
01444         raise BadXMLMessage, "Malformed offer, couldn't find <bpki_client_ta/> element"
01445       for child in self.entitydb.iterate("children"):
01446         c = etree_read(child)
01447         if b64_equal(c.findtext("bpki_child_ta"), client_ta):
01448           sia_base = "rsync://%s/%s/%s/%s/" % (self.rsync_server, self.rsync_module,
01449                                                self.handle, client.get("handle"))
01450           break
01451 
01452     # If we still haven't figured out what to do with this client, it
01453     # gets a top-level tree of its own, no attempt at nesting.
01454 
01455     if sia_base is None:
01456       print "Don't know where to nest this client, defaulting to top-level"
01457       sia_base = "rsync://%s/%s/%s/" % (self.rsync_server, self.rsync_module, client.get("handle"))
01458       
01459     if not sia_base.startswith("rsync://"):
01460       raise BadXMLMessage, "Malformed sia_base parameter %r, should start with 'rsync://'" % sia_base
01461 
01462     client_handle = "/".join(sia_base.rstrip("/").split("/")[4:])
01463 
01464     parent_handle = client.get("parent_handle")
01465 
01466     print "Client calls itself %r, we call it %r" % (client.get("handle"), client_handle)
01467     print "Client says its parent handle is %r" % parent_handle
01468 
01469     self.bpki_servers.fxcert(client.findtext("bpki_client_ta"))
01470 
01471     e = Element("repository", type = "confirmed",
01472                 client_handle = client_handle,
01473                 parent_handle = parent_handle,
01474                 sia_base = sia_base,
01475                 service_uri = "http://%s:%s/client/%s" % (self.cfg.get("pubd_server_host"),
01476                                                           self.cfg.get("pubd_server_port"),
01477                                                           client_handle))
01478 
01479     PEMElement(e, "bpki_server_ta", self.bpki_servers.cer)
01480     SubElement(e, "bpki_client_ta").text = client.findtext("bpki_client_ta")
01481     SubElement(e, "contact_info").text = self.pubd_contact_info
01482     etree_write(e, self.entitydb("pubclients", client_handle.replace("/", ".")),
01483                 msg = "Send this file back to the publication client you just configured")
01484 
01485 
01486   def do_delete_publication_client(self, arg):
01487     """
01488     Delete a publication client of this RPKI entity.
01489 
01490     This should check that the XML file it's deleting really is a
01491     client, but doesn't, yet.
01492     """
01493 
01494     try:
01495       os.unlink(self.entitydb("pubclients", arg))
01496     except OSError:
01497       print "No such client \"%s\"" % arg
01498 
01499   def complete_delete_publication_client(self, *args):
01500     return self.entitydb_complete("pubclients", *args)
01501 
01502 
01503   def do_configure_repository(self, arg):
01504     """
01505     Configure a publication repository for this RPKI entity, given the
01506     repository's response to our request-for-service message as input.
01507     This command reads the repository's response, extracts and
01508     cross-certifies the BPKI data and service URI, and links the
01509     repository data with the corresponding parent data in our local
01510     database.
01511     """
01512 
01513     parent_handle = None
01514 
01515     opts, argv = getopt.getopt(arg.split(), "", ["parent_handle="])
01516     for o, a in opts:
01517       if o == "--parent_handle":
01518         parent_handle = a
01519 
01520     if len(argv) != 1:
01521       raise BadCommandSyntax, "Need to specify filename for repository.xml on command line"
01522 
01523     r = etree_read(argv[0])
01524 
01525     if parent_handle is None:
01526       parent_handle = r.get("parent_handle")
01527 
01528     print "Repository calls us %r" % (r.get("client_handle"))
01529     print "Repository response associated with parent_handle %r" % parent_handle
01530 
01531     etree_write(r, self.entitydb("repositories", parent_handle))
01532 
01533 
01534   def do_delete_repository(self, arg):
01535     """
01536     Delete a repository of this RPKI entity.
01537 
01538     This should check that the XML file it's deleting really is a
01539     repository, but doesn't, yet.
01540     """
01541 
01542     try:
01543       os.unlink(self.entitydb("repositories", arg))
01544     except OSError:
01545       print "No such repository \"%s\"" % arg
01546 
01547   def complete_delete_repository(self, *args):
01548     return self.entitydb_complete("repositories", *args)
01549 
01550 
01551   def renew_children_common(self, arg, plural):
01552     """
01553     Common code for renew_child and renew_all_children commands.
01554     """
01555 
01556     valid_until = None
01557 
01558     opts, argv = getopt.getopt(arg.split(), "", ["valid_until"])
01559     for o, a in opts:
01560       if o == "--valid_until":
01561         valid_until = a
01562 
01563     if plural:
01564       if len(argv) != 0:
01565         raise BadCommandSyntax, "Unexpected arguments"
01566       children = "*"
01567     else:
01568       if len(argv) != 1:
01569         raise BadCommandSyntax, "Need to specify child handle"
01570       children = argv[0]
01571 
01572     if valid_until is None:
01573       valid_until = rpki.sundial.now() + rpki.sundial.timedelta(days = 365)
01574     else:
01575       valid_until = rpki.sundial.fromXMLtime(valid_until)
01576       if valid_until < rpki.sundial.now():
01577         raise PastExpiration, "Specified new expiration time %s has passed" % valid_until
01578 
01579     print "New validity date", valid_until
01580 
01581     for f in self.entitydb.iterate("children", children):
01582       c = etree_read(f)
01583       c.set("valid_until", str(valid_until))
01584       etree_write(c, f)
01585 
01586   def do_renew_child(self, arg):
01587     """
01588     Update validity period for one child entity.
01589     """
01590     return self.renew_children_common(arg, False)
01591 
01592   def complete_renew_child(self, *args):
01593     return self.entitydb_complete("children", *args)
01594 
01595   def do_renew_all_children(self, arg):
01596     """
01597     Update validity period for all child entities.
01598     """
01599     return self.renew_children_common(arg, True)
01600 
01601 
01602 
01603 
01604   def configure_resources_main(self, msg = None):
01605     """
01606     Main program of old myrpki.py script.  This remains separate
01607     because it's called from more than one place.
01608     """
01609 
01610     roa_csv_file                  = self.cfg.get("roa_csv")
01611     prefix_csv_file               = self.cfg.get("prefix_csv")
01612     asn_csv_file                  = self.cfg.get("asn_csv")
01613 
01614     # This probably should become an argument instead of (or in
01615     # addition to a default from?) a config file option.
01616     xml_filename                  = self.cfg.get("xml_filename")
01617 
01618     try:
01619       e = etree_read(xml_filename)
01620       bsc_req, bsc_cer = self.bpki_resources.bsc(e.findtext("bpki_bsc_pkcs10"))
01621       service_uri = e.get("service_uri")
01622     except IOError:
01623       bsc_req, bsc_cer = None, None
01624       service_uri = None
01625 
01626     e = Element("myrpki", handle = self.handle)
01627 
01628     if service_uri:
01629       e.set("service_uri", service_uri)
01630 
01631     roa_requests.from_csv(roa_csv_file).xml(e)
01632 
01633     children.from_entitydb(
01634       prefix_csv_file = prefix_csv_file,
01635       asn_csv_file = asn_csv_file,
01636       fxcert = self.bpki_resources.fxcert,
01637       entitydb = self.entitydb).xml(e)
01638 
01639     parents.from_entitydb(
01640       fxcert = self.bpki_resources.fxcert,
01641       entitydb = self.entitydb).xml(e)
01642 
01643     repositories.from_entitydb(
01644       fxcert = self.bpki_resources.fxcert,
01645       entitydb = self.entitydb).xml(e)
01646 
01647     PEMElement(e, "bpki_ca_certificate", self.bpki_resources.cer)
01648     PEMElement(e, "bpki_crl",            self.bpki_resources.crl)
01649 
01650     if bsc_cer:
01651       PEMElement(e, "bpki_bsc_certificate", bsc_cer)
01652 
01653     if bsc_req:
01654       PEMElement(e, "bpki_bsc_pkcs10", bsc_req)
01655 
01656     etree_write(e, xml_filename, msg = msg)
01657 
01658 
01659   def do_configure_resources(self, arg):
01660     """
01661     Read CSV files and all the descriptions of parents and children
01662     that we've built up, package the result up as a single XML file to
01663     be shipped to a hosting rpkid.
01664     """
01665 
01666     if arg:
01667       raise BadCommandSyntax, "Unexpected argument %r" % arg
01668     self.configure_resources_main(msg = "Send this file to the rpkid operator who is hosting you")
01669 
01670 
01671 
01672   def do_configure_daemons(self, arg):
01673     """
01674     Configure RPKI daemons with the data built up by the other
01675     commands in this program.
01676 
01677     The basic model here is that each entity with resources to certify
01678     runs the myrpki tool, but not all of them necessarily run their
01679     own RPKI engines.  The entities that do run RPKI engines get data
01680     from the entities they host via the XML files output by the
01681     configure_resources command.  Those XML files are the input to
01682     this command, which uses them to do all the work of configuring
01683     daemons, populating SQL databases, and so forth.  A few operations
01684     (eg, BSC construction) generate data which has to be shipped back
01685     to the resource holder, which we do by updating the same XML file.
01686 
01687     In essence, the XML files are a sneakernet (or email, or carrier
01688     pigeon) communication channel between the resource holders and the
01689     RPKI engine operators.
01690 
01691     As a convenience, for the normal case where the RPKI engine
01692     operator is itself a resource holder, this command in effect runs
01693     the configure_resources command automatically to process the RPKI
01694     engine operator's own resources.
01695 
01696     Note that, due to the back and forth nature of some of these
01697     operations, it may take several cycles for data structures to stablize
01698     and everything to reach a steady state.  This is normal.
01699     """
01700 
01701     argv = arg.split()
01702 
01703     try:
01704       import rpki.http, rpki.resource_set, rpki.relaxng, rpki.exceptions
01705       import rpki.left_right, rpki.x509, rpki.async
01706 
01707     except ImportError, e:
01708       print "Sorry, you appear to be missing some of the Python modules needed to run this command"
01709       print "[Error: %r]" % e
01710 
01711     def findbase64(tree, name, b64type = rpki.x509.X509):
01712       x = tree.findtext(name)
01713       return b64type(Base64 = x) if x else None
01714 
01715     # We can use a single BSC for everything -- except BSC key
01716     # rollovers.  Drive off that bridge when we get to it.
01717 
01718     bsc_handle = "bsc"
01719 
01720     self.cfg.set_global_flags()
01721 
01722     # Default values for CRL parameters are low, for testing.  Not
01723     # quite as low as they once were, too much expired CRL whining.
01724 
01725     self_crl_interval = self.cfg.getint("self_crl_interval", 2 * 60 * 60)
01726     self_regen_margin = self.cfg.getint("self_regen_margin", self_crl_interval / 4)
01727     pubd_base         = "http://%s:%s/" % (self.cfg.get("pubd_server_host"), self.cfg.get("pubd_server_port"))
01728     rpkid_base        = "http://%s:%s/" % (self.cfg.get("rpkid_server_host"), self.cfg.get("rpkid_server_port"))
01729 
01730     # Wrappers to simplify calling rpkid and pubd.
01731 
01732     call_rpkid = rpki.async.sync_wrapper(rpki.http.caller(
01733       proto       = rpki.left_right,
01734       client_key  = rpki.x509.RSA( PEM_file = self.bpki_servers.dir + "/irbe.key"),
01735       client_cert = rpki.x509.X509(PEM_file = self.bpki_servers.dir + "/irbe.cer"),
01736       server_ta   = rpki.x509.X509(PEM_file = self.bpki_servers.cer),
01737       server_cert = rpki.x509.X509(PEM_file = self.bpki_servers.dir + "/rpkid.cer"),
01738       url         = rpkid_base + "left-right",
01739       debug       = self.show_xml))
01740 
01741     if self.run_pubd:
01742 
01743       call_pubd = rpki.async.sync_wrapper(rpki.http.caller(
01744         proto       = rpki.publication,
01745         client_key  = rpki.x509.RSA( PEM_file = self.bpki_servers.dir + "/irbe.key"),
01746         client_cert = rpki.x509.X509(PEM_file = self.bpki_servers.dir + "/irbe.cer"),
01747         server_ta   = rpki.x509.X509(PEM_file = self.bpki_servers.cer),
01748         server_cert = rpki.x509.X509(PEM_file = self.bpki_servers.dir + "/pubd.cer"),
01749         url         = pubd_base + "control",
01750         debug       = self.show_xml))
01751 
01752       # Make sure that pubd's BPKI CRL is up to date.
01753 
01754       call_pubd(rpki.publication.config_elt.make_pdu(
01755         action = "set",
01756         bpki_crl = rpki.x509.CRL(PEM_file = self.bpki_servers.crl)))
01757 
01758     irdb = IRDB(self.cfg)
01759 
01760     xmlfiles = []
01761 
01762     # If [myrpki] section includes an "xml_filename" setting, run
01763     # myrpki.py internally, as a convenience, and include its output at
01764     # the head of our list of XML files to process.
01765 
01766     my_xmlfile = self.cfg.get("xml_filename", "")
01767     if my_xmlfile:
01768       self.configure_resources_main()
01769       xmlfiles.append(my_xmlfile)
01770     else:
01771       my_xmlfile = None
01772 
01773     # Add any other XML files specified on the command line
01774 
01775     xmlfiles.extend(argv)
01776 
01777     for xmlfile in xmlfiles:
01778 
01779       # Parse XML file and validate it against our scheme
01780 
01781       tree = etree_read(xmlfile, validate = True)
01782 
01783       handle = tree.get("handle")
01784 
01785       # Update IRDB with parsed resource and roa-request data.
01786 
01787       roa_requests = [(
01788         x.get('asn'),
01789         rpki.resource_set.roa_prefix_set_ipv4(x.get("v4")),
01790         rpki.resource_set.roa_prefix_set_ipv6(x.get("v6"))) for x in tree.getiterator("roa_request")]
01791 
01792       children = [(
01793         x.get("handle"),
01794         rpki.resource_set.resource_set_as(x.get("asns")),
01795         rpki.resource_set.resource_set_ipv4(x.get("v4")),
01796         rpki.resource_set.resource_set_ipv6(x.get("v6")),
01797         rpki.sundial.datetime.fromXMLtime(x.get("valid_until"))) for x in tree.getiterator("child")]
01798 
01799       # ghostbusters are ignored for now
01800       irdb.update(handle, roa_requests, children)
01801 
01802       # Check for certificates before attempting anything else
01803 
01804       hosted_cacert = findbase64(tree, "bpki_ca_certificate")
01805       if not hosted_cacert:
01806         print "Nothing else I can do without a trust anchor for the entity I'm hosting."
01807         continue
01808 
01809       rpkid_xcert = rpki.x509.X509(PEM_file = self.bpki_servers.fxcert(b64 = hosted_cacert.get_Base64(),
01810                                                                        #filename = handle + ".cacert.cer",
01811                                                                        path_restriction = 1))
01812 
01813       # See what rpkid and pubd already have on file for this entity.
01814 
01815       if self.run_pubd:
01816         client_pdus = dict((x.client_handle, x)
01817                            for x in call_pubd(rpki.publication.client_elt.make_pdu(action = "list"))
01818                            if isinstance(x, rpki.publication.client_elt))
01819 
01820       rpkid_reply = call_rpkid(
01821         rpki.left_right.self_elt.make_pdu(      action = "get",  tag = "self",       self_handle = handle),
01822         rpki.left_right.bsc_elt.make_pdu(       action = "list", tag = "bsc",        self_handle = handle),
01823         rpki.left_right.repository_elt.make_pdu(action = "list", tag = "repository", self_handle = handle),
01824         rpki.left_right.parent_elt.make_pdu(    action = "list", tag = "parent",     self_handle = handle),
01825         rpki.left_right.child_elt.make_pdu(     action = "list", tag = "child",      self_handle = handle))
01826 
01827       self_pdu        = rpkid_reply[0]
01828       bsc_pdus        = dict((x.bsc_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.bsc_elt))
01829       repository_pdus = dict((x.repository_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.repository_elt))
01830       parent_pdus     = dict((x.parent_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.parent_elt))
01831       child_pdus      = dict((x.child_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.child_elt))
01832 
01833       pubd_query = []
01834       rpkid_query = []
01835 
01836       # There should be exactly one <self/> object per hosted entity, by definition
01837 
01838       if (isinstance(self_pdu, rpki.left_right.report_error_elt) or
01839           self_pdu.crl_interval != self_crl_interval or
01840           self_pdu.regen_margin != self_regen_margin or
01841           self_pdu.bpki_cert != rpkid_xcert):
01842         rpkid_query.append(rpki.left_right.self_elt.make_pdu(
01843           action = "create" if isinstance(self_pdu, rpki.left_right.report_error_elt) else "set",
01844           tag = "self",
01845           self_handle = handle,
01846           bpki_cert = rpkid_xcert,
01847           crl_interval = self_crl_interval,
01848           regen_margin = self_regen_margin))
01849 
01850       # In general we only need one <bsc/> per <self/>.  BSC objects are a
01851       # little unusual in that the PKCS #10 subelement is generated by rpkid
01852       # in response to generate_keypair, so there's more of a separation
01853       # between create and set than with other objects.
01854 
01855       bsc_cert = findbase64(tree, "bpki_bsc_certificate")
01856       bsc_crl  = findbase64(tree, "bpki_crl", rpki.x509.CRL)
01857 
01858       bsc_pdu = bsc_pdus.pop(bsc_handle, None)
01859 
01860       if bsc_pdu is None:
01861         rpkid_query.append(rpki.left_right.bsc_elt.make_pdu(
01862           action = "create",
01863           tag = "bsc",
01864           self_handle = handle,
01865           bsc_handle = bsc_handle,
01866           generate_keypair = "yes"))
01867       elif bsc_pdu.signing_cert != bsc_cert or bsc_pdu.signing_cert_crl != bsc_crl:
01868         rpkid_query.append(rpki.left_right.bsc_elt.make_pdu(
01869           action = "set",
01870           tag = "bsc",
01871           self_handle = handle,
01872           bsc_handle = bsc_handle,
01873           signing_cert = bsc_cert,
01874           signing_cert_crl = bsc_crl))
01875 
01876       rpkid_query.extend(rpki.left_right.bsc_elt.make_pdu(
01877         action = "destroy", self_handle = handle, bsc_handle = b) for b in bsc_pdus)
01878 
01879       bsc_req = None
01880 
01881       if bsc_pdu and bsc_pdu.pkcs10_request:
01882         bsc_req = bsc_pdu.pkcs10_request
01883 
01884       # At present we need one <repository/> per <parent/>, not because
01885       # rpkid requires that, but because pubd does.  pubd probably should
01886       # be fixed to support a single client allowed to update multiple
01887       # trees, but for the moment the easiest way forward is just to
01888       # enforce a 1:1 mapping between <parent/> and <repository/> objects
01889 
01890       for repository in tree.getiterator("repository"):
01891 
01892         repository_handle = repository.get("handle")
01893         repository_pdu = repository_pdus.pop(repository_handle, None)
01894         repository_uri = repository.get("service_uri")
01895         repository_cert = findbase64(repository, "bpki_certificate")
01896 
01897         if (repository_pdu is None or
01898             repository_pdu.bsc_handle != bsc_handle or
01899             repository_pdu.peer_contact_uri != repository_uri or
01900             repository_pdu.bpki_cert != repository_cert):
01901           rpkid_query.append(rpki.left_right.repository_elt.make_pdu(
01902             action = "create" if repository_pdu is None else "set",
01903             tag = repository_handle,
01904             self_handle = handle,
01905             repository_handle = repository_handle,
01906             bsc_handle = bsc_handle,
01907             peer_contact_uri = repository_uri,
01908             bpki_cert = repository_cert))
01909 
01910       rpkid_query.extend(rpki.left_right.repository_elt.make_pdu(
01911         action = "destroy", self_handle = handle, repository_handle = r) for r in repository_pdus)
01912 
01913       # <parent/> setup code currently assumes 1:1 mapping between
01914       # <repository/> and <parent/>, and further assumes that the handles
01915       # for an associated pair are the identical (that is:
01916       # parent.repository_handle == parent.parent_handle).
01917 
01918       for parent in tree.getiterator("parent"):
01919 
01920         parent_handle = parent.get("handle")
01921         parent_pdu = parent_pdus.pop(parent_handle, None)
01922         parent_uri = parent.get("service_uri")
01923         parent_myhandle = parent.get("myhandle")
01924         parent_sia_base = parent.get("sia_base")
01925         parent_cms_cert = findbase64(parent, "bpki_cms_certificate")
01926 
01927         if (parent_pdu is None or
01928             parent_pdu.bsc_handle != bsc_handle or
01929             parent_pdu.repository_handle != parent_handle or
01930             parent_pdu.peer_contact_uri != parent_uri or
01931             parent_pdu.sia_base != parent_sia_base or
01932             parent_pdu.sender_name != parent_myhandle or
01933             parent_pdu.recipient_name != parent_handle or
01934             parent_pdu.bpki_cms_cert != parent_cms_cert):
01935           rpkid_query.append(rpki.left_right.parent_elt.make_pdu(
01936             action = "create" if parent_pdu is None else "set",
01937             tag = parent_handle,
01938             self_handle = handle,
01939             parent_handle = parent_handle,
01940             bsc_handle = bsc_handle,
01941             repository_handle = parent_handle,
01942             peer_contact_uri = parent_uri,
01943             sia_base = parent_sia_base,
01944             sender_name = parent_myhandle,
01945             recipient_name = parent_handle,
01946             bpki_cms_cert = parent_cms_cert))
01947 
01948       rpkid_query.extend(rpki.left_right.parent_elt.make_pdu(
01949         action = "destroy", self_handle = handle, parent_handle = p) for p in parent_pdus)
01950 
01951       # Children are simpler than parents, because they call us, so no URL
01952       # to construct and figuring out what certificate to use is their
01953       # problem, not ours.
01954 
01955       for child in tree.getiterator("child"):
01956 
01957         child_handle = child.get("handle")
01958         child_pdu = child_pdus.pop(child_handle, None)
01959         child_cert = findbase64(child, "bpki_certificate")
01960 
01961         if (child_pdu is None or
01962             child_pdu.bsc_handle != bsc_handle or
01963             child_pdu.bpki_cert != child_cert):
01964           rpkid_query.append(rpki.left_right.child_elt.make_pdu(
01965             action = "create" if child_pdu is None else "set",
01966             tag = child_handle,
01967             self_handle = handle,
01968             child_handle = child_handle,
01969             bsc_handle = bsc_handle,
01970             bpki_cert = child_cert))
01971 
01972       rpkid_query.extend(rpki.left_right.child_elt.make_pdu(
01973         action = "destroy", self_handle = handle, child_handle = c) for c in child_pdus)
01974 
01975       # Publication setup.
01976 
01977       if self.run_pubd:
01978 
01979         for f in self.entitydb.iterate("pubclients"):
01980           c = etree_read(f)
01981 
01982           client_handle = c.get("client_handle")
01983           client_base_uri = c.get("sia_base")
01984           client_bpki_cert = rpki.x509.X509(PEM_file = self.bpki_servers.fxcert(c.findtext("bpki_client_ta")))
01985           client_pdu = client_pdus.pop(client_handle, None)
01986 
01987           if (client_pdu is None or
01988               client_pdu.base_uri != client_base_uri or
01989               client_pdu.bpki_cert != client_bpki_cert):
01990             pubd_query.append(rpki.publication.client_elt.make_pdu(
01991               action = "create" if client_pdu is None else "set",
01992               client_handle = client_handle,
01993               bpki_cert = client_bpki_cert,
01994               base_uri = client_base_uri))
01995 
01996         pubd_query.extend(rpki.publication.client_elt.make_pdu(
01997             action = "destroy", client_handle = p) for p in client_pdus)
01998 
01999       # If we changed anything, ship updates off to daemons
02000 
02001       failed = False
02002 
02003       if rpkid_query:
02004         rpkid_reply = call_rpkid(*rpkid_query)
02005         bsc_pdus = dict((x.bsc_handle, x) for x in rpkid_reply if isinstance(x, rpki.left_right.bsc_elt))
02006         if bsc_handle in bsc_pdus and bsc_pdus[bsc_handle].pkcs10_request:
02007           bsc_req = bsc_pdus[bsc_handle].pkcs10_request
02008         for r in rpkid_reply:
02009           if isinstance(r, rpki.left_right.report_error_elt):
02010             failed = True
02011             print "rpkid reported failure:", r.error_code
02012             if r.error_text:
02013               print r.error_text
02014 
02015       if failed:
02016         raise CouldntTalkToDaemon
02017 
02018       if pubd_query:
02019         assert self.run_pubd
02020         pubd_reply = call_pubd(*pubd_query)
02021         for r in pubd_reply:
02022           if isinstance(r, rpki.publication.report_error_elt):
02023             failed = True
02024             print "pubd reported failure:", r.error_code
02025             if r.error_text:
02026               print r.error_text
02027             
02028       if failed:
02029         raise CouldntTalkToDaemon
02030 
02031       # Rewrite XML.
02032 
02033       e = tree.find("bpki_bsc_pkcs10")
02034       if e is not None:
02035         tree.remove(e)
02036       if bsc_req is not None:
02037         SubElement(tree, "bpki_bsc_pkcs10").text = bsc_req.get_Base64()
02038 
02039       tree.set("service_uri", rpkid_base + "up-down/" + handle)
02040 
02041       etree_write(tree, xmlfile, validate = True,
02042                   msg = None if xmlfile is my_xmlfile else 'Send this file back to the hosted entity ("%s")' % handle)
02043 
02044     irdb.close()
02045 
02046     # We used to run event loop again to give TLS connections a chance to shut down cleanly.
02047     # Seems not to be needed (and sometimes hangs forever, which is odd) with TLS out of the picture.
02048     #rpki.async.event_loop()
 All Classes Namespaces Files Functions Variables Properties