Esempio n. 1
0
def migration_miabldap_1(env):
	# This migration step moves users from sqlite3 to openldap

	# users table:
	# for each row create an ldap entry of the form:
	#   dn: uid=[uuid],ou=Users,dc=mailinabox
	#   objectClass: inetOrgPerson, mailUser, shadowAccount
	#   mail: [email]
	#   maildrop: [email]
	#   userPassword: [password]
	#   mailaccess: [privilege]   # multi-valued
	# 
	# aliases table:
	# for each row create an ldap entry of the form:
	#   dn: cn=[uuid],ou=aliases,ou=Users,dc=mailinabox
	#   objectClass: mailGroup
	#   mail: [source]
	#   member: [destination-dn]   # multi-valued
	#   rfc822MailMember: [email]  # multi-values
	#
	# if the alias has permitted_senders, create:
	#   dn: cn=[uuid],ou=permitted-senders,ou=Config,dc=mailinabox
	#   objectClass: mailGroup
	#   mail: [source]
	#   member: [user-dn]         # multi-valued
	
	print("Migrating users and aliases from sqlite to ldap")
	
	# Get the ldap server up and running
	shell("check_call", ["setup/ldap.sh", "-v"])
	
	import sqlite3, ldap3
	import migration_13 as m13
				
	# 2. get ldap site details (miab_ldap.conf was created by ldap.sh)
	ldapvars = load_env_vars_from_file(os.path.join(env["STORAGE_ROOT"], "ldap/miab_ldap.conf"), strip_quotes=True)
	ldap_base = ldapvars.LDAP_BASE
	ldap_domains_base = ldapvars.LDAP_DOMAINS_BASE
	ldap_permitted_senders_base = ldapvars.LDAP_PERMITTED_SENDERS_BASE
	ldap_users_base = ldapvars.LDAP_USERS_BASE
	ldap_aliases_base = ldapvars.LDAP_ALIASES_BASE
	ldap_services_base = ldapvars.LDAP_SERVICES_BASE
	ldap_admin_dn = ldapvars.LDAP_ADMIN_DN
	ldap_admin_pass = ldapvars.LDAP_ADMIN_PASSWORD

	# 3. connect
	conn = sqlite3.connect(os.path.join(env["STORAGE_ROOT"], "mail/users.sqlite"))
	ldap = ldap3.Connection('127.0.0.1', ldap_admin_dn, ldap_admin_pass, raise_exceptions=True)
	ldap.bind()
	
	# 4. perform the migration
	users=m13.create_users(env, conn, ldap, ldap_base, ldap_users_base, ldap_domains_base)
	aliases=m13.create_aliases(env, conn, ldap, ldap_aliases_base)
	permitted=m13.create_permitted_senders(conn, ldap, ldap_users_base, ldap_permitted_senders_base)
	m13.populate_aliases(conn, ldap, users, aliases)
		
	ldap.unbind()
	conn.close()
Esempio n. 2
0
def check_dns_zone(domain, env, dns_zonefiles):
	# We provide a DNS zone for the domain. It should have NS records set up
	# at the domain name's registrar pointing to this box.
	existing_ns = query_dns(domain, "NS")
	correct_ns = "ns1.BOX; ns2.BOX".replace("BOX", env['PRIMARY_HOSTNAME'])
	if existing_ns.lower() == correct_ns.lower():
		env['out'].print_ok("Nameservers are set correctly at registrar. [%s]" % correct_ns)
	else:
		env['out'].print_error("""The nameservers set on this domain are incorrect. They are currently %s. Use your domain name registar's
			control panel to set the nameservers to %s."""
				% (existing_ns, correct_ns) )

	# See if the domain has a DS record set at the registrar. The DS record may have
	# several forms. We have to be prepared to check for any valid record. We've
	# pre-generated all of the valid digests --- read them in.
	ds_correct = open('/etc/nsd/zones/' + dns_zonefiles[domain] + '.ds').read().strip().split("\n")
	digests = { }
	for rr_ds in ds_correct:
		ds_keytag, ds_alg, ds_digalg, ds_digest = rr_ds.split("\t")[4].split(" ")
		digests[ds_digalg] = ds_digest

	# Some registrars may want the public key so they can compute the digest. The DS
	# record that we suggest using is for the KSK (and that's how the DS records were generated).
	dnssec_keys = load_env_vars_from_file(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/keys.conf'))
	dnsssec_pubkey = open(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys['KSK'] + '.key')).read().split("\t")[3].split(" ")[3]

	# Query public DNS for the DS record at the registrar.
	ds = query_dns(domain, "DS", nxdomain=None)
	ds_looks_valid = ds and len(ds.split(" ")) == 4
	if ds_looks_valid: ds = ds.split(" ")
	if ds_looks_valid and ds[0] == ds_keytag and ds[1] == '7' and ds[3] == digests.get(ds[2]):
		env['out'].print_ok("DNS 'DS' record is set correctly at registrar.")
	else:
		if ds == None:
			env['out'].print_error("""This domain's DNS DS record is not set. The DS record is optional. The DS record activates DNSSEC.
				To set a DS record, you must follow the instructions provided by your domain name registrar and provide to them this information:""")
		else:
			env['out'].print_error("""This domain's DNS DS record is incorrect. The chain of trust is broken between the public DNS system
				and this machine's DNS server. It may take several hours for public DNS to update after a change. If you did not recently
				make a change, you must resolve this immediately by following the instructions provided by your domain name registrar and
				provide to them this information:""")
		env['out'].print_line("")
		env['out'].print_line("Key Tag: " + ds_keytag + ("" if not ds_looks_valid or ds[0] == ds_keytag else " (Got '%s')" % ds[0]))
		env['out'].print_line("Key Flags: KSK")
		env['out'].print_line("Algorithm: 7 / RSASHA1-NSEC3-SHA1" + ("" if not ds_looks_valid or ds[1] == '7' else " (Got '%s')" % ds[1]))
			# see http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
		env['out'].print_line("Digest Type: 2 / SHA-256")
			# http://www.ietf.org/assignments/ds-rr-types/ds-rr-types.xml
		env['out'].print_line("Digest: " + digests['2'])
		if ds_looks_valid and ds[3] != digests.get(ds[2]):
			env['out'].print_line("(Got digest type %s and digest %s which do not match.)" % (ds[2], ds[3]))
		env['out'].print_line("Public Key: ")
		env['out'].print_line(dnsssec_pubkey, monospace=True)
		env['out'].print_line("")
		env['out'].print_line("Bulk/Record Format:")
		env['out'].print_line("" + ds_correct[0])
		env['out'].print_line("")
Esempio n. 3
0
def check_dnssec(domain, env, output, dns_zonefiles, is_checking_primary=False):
	# See if the domain has a DS record set at the registrar. The DS record may have
	# several forms. We have to be prepared to check for any valid record. We've
	# pre-generated all of the valid digests --- read them in.
	ds_file = '/etc/nsd/zones/' + dns_zonefiles[domain] + '.ds'
	if not os.path.exists(ds_file): return # Domain is in our database but DNS has not yet been updated.
	ds_correct = open(ds_file).read().strip().split("\n")
	digests = { }
	for rr_ds in ds_correct:
		ds_keytag, ds_alg, ds_digalg, ds_digest = rr_ds.split("\t")[4].split(" ")
		digests[ds_digalg] = ds_digest

	# Some registrars may want the public key so they can compute the digest. The DS
	# record that we suggest using is for the KSK (and that's how the DS records were generated).
	alg_name_map = { '7': 'RSASHA1-NSEC3-SHA1', '8': 'RSASHA256' }
	dnssec_keys = load_env_vars_from_file(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/%s.conf' % alg_name_map[ds_alg]))
	dnsssec_pubkey = open(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys['KSK'] + '.key')).read().split("\t")[3].split(" ")[3]

	# Query public DNS for the DS record at the registrar.
	ds = query_dns(domain, "DS", nxdomain=None)
	ds_looks_valid = ds and len(ds.split(" ")) == 4
	if ds_looks_valid: ds = ds.split(" ")
	if ds_looks_valid and ds[0] == ds_keytag and ds[1] == ds_alg and ds[3] == digests.get(ds[2]):
		if is_checking_primary: return
		output.print_ok("DNSSEC 'DS' record is set correctly at registrar.")
	else:
		if ds == None:
			if is_checking_primary: return
			output.print_warning("""This domain's DNSSEC DS record is not set. The DS record is optional. The DS record activates DNSSEC.
				To set a DS record, you must follow the instructions provided by your domain name registrar and provide to them this information:""")
		else:
			if is_checking_primary:
				output.print_error("""The DNSSEC 'DS' record for %s is incorrect. See further details below.""" % domain)
				return
			output.print_error("""This domain's DNSSEC DS record is incorrect. The chain of trust is broken between the public DNS system
				and this machine's DNS server. It may take several hours for public DNS to update after a change. If you did not recently
				make a change, you must resolve this immediately by following the instructions provided by your domain name registrar and
				provide to them this information:""")
		output.print_line("")
		output.print_line("Key Tag: " + ds_keytag + ("" if not ds_looks_valid or ds[0] == ds_keytag else " (Got '%s')" % ds[0]))
		output.print_line("Key Flags: KSK")
		output.print_line(
			  ("Algorithm: %s / %s" % (ds_alg, alg_name_map[ds_alg]))
			+ ("" if not ds_looks_valid or ds[1] == ds_alg else " (Got '%s')" % ds[1]))
			# see http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
		output.print_line("Digest Type: 2 / SHA-256")
			# http://www.ietf.org/assignments/ds-rr-types/ds-rr-types.xml
		output.print_line("Digest: " + digests['2'])
		if ds_looks_valid and ds[3] != digests.get(ds[2]):
			output.print_line("(Got digest type %s and digest %s which do not match.)" % (ds[2], ds[3]))
		output.print_line("Public Key: ")
		output.print_line(dnsssec_pubkey, monospace=True)
		output.print_line("")
		output.print_line("Bulk/Record Format:")
		output.print_line("" + ds_correct[0])
		output.print_line("")
Esempio n. 4
0
def check_dnssec(domain, env, output, dns_zonefiles, is_checking_primary=False):
	# See if the domain has a DS record set at the registrar. The DS record may have
	# several forms. We have to be prepared to check for any valid record. We've
	# pre-generated all of the valid digests --- read them in.
	ds_file = '/etc/nsd/zones/' + dns_zonefiles[domain] + '.ds'
	if not os.path.exists(ds_file): return # Domain is in our database but DNS has not yet been updated.
	ds_correct = open(ds_file).read().strip().split("\n")
	digests = { }
	for rr_ds in ds_correct:
		ds_keytag, ds_alg, ds_digalg, ds_digest = rr_ds.split("\t")[4].split(" ")
		digests[ds_digalg] = ds_digest

	# Some registrars may want the public key so they can compute the digest. The DS
	# record that we suggest using is for the KSK (and that's how the DS records were generated).
	alg_name_map = { '7': 'RSASHA1-NSEC3-SHA1', '8': 'RSASHA256' }
	dnssec_keys = load_env_vars_from_file(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/%s.conf' % alg_name_map[ds_alg]))
	dnsssec_pubkey = open(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys['KSK'] + '.key')).read().split("\t")[3].split(" ")[3]

	# Query public DNS for the DS record at the registrar.
	ds = query_dns(domain, "DS", nxdomain=None)
	ds_looks_valid = ds and len(ds.split(" ")) == 4
	if ds_looks_valid: ds = ds.split(" ")
	if ds_looks_valid and ds[0] == ds_keytag and ds[1] == ds_alg and ds[3] == digests.get(ds[2]):
		if is_checking_primary: return
		output.print_ok("DNSSEC 'DS' record is set correctly at registrar.")
	else:
		if ds == None:
			if is_checking_primary: return
			output.print_warning("""This domain's DNSSEC DS record is not set. The DS record is optional. The DS record activates DNSSEC.
				To set a DS record, you must follow the instructions provided by your domain name registrar and provide to them this information:""")
		else:
			if is_checking_primary:
				output.print_error("""The DNSSEC 'DS' record for %s is incorrect. See further details below.""" % domain)
				return
			output.print_error("""This domain's DNSSEC DS record is incorrect. The chain of trust is broken between the public DNS system
				and this machine's DNS server. It may take several hours for public DNS to update after a change. If you did not recently
				make a change, you must resolve this immediately by following the instructions provided by your domain name registrar and
				provide to them this information:""")
		output.print_line("")
		output.print_line("Key Tag: " + ds_keytag + ("" if not ds_looks_valid or ds[0] == ds_keytag else " (Got '%s')" % ds[0]))
		output.print_line("Key Flags: KSK")
		output.print_line(
			  ("Algorithm: %s / %s" % (ds_alg, alg_name_map[ds_alg]))
			+ ("" if not ds_looks_valid or ds[1] == ds_alg else " (Got '%s')" % ds[1]))
			# see http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
		output.print_line("Digest Type: 2 / SHA-256")
			# http://www.ietf.org/assignments/ds-rr-types/ds-rr-types.xml
		output.print_line("Digest: " + digests['2'])
		if ds_looks_valid and ds[3] != digests.get(ds[2]):
			output.print_line("(Got digest type %s and digest %s which do not match.)" % (ds[2], ds[3]))
		output.print_line("Public Key: ")
		output.print_line(dnsssec_pubkey, monospace=True)
		output.print_line("")
		output.print_line("Bulk/Record Format:")
		output.print_line("" + ds_correct[0])
		output.print_line("")
def find_dnssec_signing_keys(domain, env):
	# For key that we generated (one per algorithm)...
	d = os.path.join(env['STORAGE_ROOT'], 'dns/dnssec')
	keyconfs = [f for f in os.listdir(d) if f.endswith(".conf")]
	for keyconf in keyconfs:
		# Load the file holding the KSK and ZSK key filenames.
		keyconf_fn = os.path.join(d, keyconf)
		keyinfo = load_env_vars_from_file(keyconf_fn)

		# Skip this key if the conf file has a setting named DOMAINS,
		# holding a comma-separated list of domain names, and if this
		# domain is not in the list. This allows easily disabling a
		# key by setting "DOMAINS=" or "DOMAINS=none", other than
		# deleting the key's .conf file, which might result in the key
		# being regenerated next upgrade. Keys should be disabled if
		# they are not needed to reduce the DNSSEC query response size.
		if "DOMAINS" in keyinfo and domain not in [dd.strip() for dd in keyinfo["DOMAINS"].split(",")]:
			continue

		for keytype in ("KSK", "ZSK"):
			yield keytype, keyinfo[keytype]
Esempio n. 6
0
def sign_zone(domain, zonefile, env):
    algo = dnssec_choose_algo(domain, env)
    dnssec_keys = load_env_vars_from_file(
        os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/%s.conf' % algo))

    # In order to use the same keys for all domains, we have to generate
    # a new .key file with a DNSSEC record for the specific domain. We
    # can reuse the same key, but it won't validate without a DNSSEC
    # record specifically for the domain.
    #
    # Copy the .key and .private files to /tmp to patch them up.
    #
    # Use os.umask and open().write() to securely create a copy that only
    # we (root) can read.
    files_to_kill = []
    for key in ("KSK", "ZSK"):
        if dnssec_keys.get(key, "").strip() == "":
            raise Exception("DNSSEC is not properly set up.")
        oldkeyfn = os.path.join(env['STORAGE_ROOT'],
                                'dns/dnssec/' + dnssec_keys[key])
        newkeyfn = '/tmp/' + dnssec_keys[key].replace("_domain_", domain)
        dnssec_keys[key] = newkeyfn
        for ext in (".private", ".key"):
            if not os.path.exists(oldkeyfn + ext):
                raise Exception("DNSSEC is not properly set up.")
            with open(oldkeyfn + ext, "r") as fr:
                keydata = fr.read()
            keydata = keydata.replace(
                "_domain_", domain
            )  # trick ldns-signkey into letting our generic key be used by this zone
            fn = newkeyfn + ext
            prev_umask = os.umask(
                0o77)  # ensure written file is not world-readable
            try:
                with open(fn, "w") as fw:
                    fw.write(keydata)
            finally:
                os.umask(prev_umask
                         )  # other files we write should be world-readable
            files_to_kill.append(fn)

    # Do the signing.
    expiry_date = (datetime.datetime.now() +
                   datetime.timedelta(days=30)).strftime("%Y%m%d")
    shell(
        'check_call',
        [
            "/usr/bin/ldns-signzone",
            # expire the zone after 30 days
            "-e",
            expiry_date,

            # use NSEC3
            "-n",

            # zonefile to sign
            "/etc/nsd/zones/" + zonefile,

            # keys to sign with (order doesn't matter -- it'll figure it out)
            dnssec_keys["KSK"],
            dnssec_keys["ZSK"],
        ])

    # Create a DS record based on the patched-up key files. The DS record is specific to the
    # zone being signed, so we can't use the .ds files generated when we created the keys.
    # The DS record points to the KSK only. Write this next to the zone file so we can
    # get it later to give to the user with instructions on what to do with it.
    #
    # We want to be able to validate DS records too, but multiple forms may be valid depending
    # on the digest type. So we'll write all (both) valid records. Only one DS record should
    # actually be deployed. Preferebly the first.
    with open("/etc/nsd/zones/" + zonefile + ".ds", "w") as f:
        for digest_type in ('2', '1'):
            rr_ds = shell(
                'check_output',
                [
                    "/usr/bin/ldns-key2ds",
                    "-n",  # output to stdout
                    "-" + digest_type,  # 1=SHA1, 2=SHA256
                    dnssec_keys["KSK"] + ".key"
                ])
            f.write(rr_ds)

    # Remove our temporary file.
    for fn in files_to_kill:
        os.unlink(fn)
Esempio n. 7
0
def sign_zone(domain, zonefile, env):
	algo = dnssec_choose_algo(domain, env)
	dnssec_keys = load_env_vars_from_file(os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/%s.conf' % algo))

	# In order to use the same keys for all domains, we have to generate
	# a new .key file with a DNSSEC record for the specific domain. We
	# can reuse the same key, but it won't validate without a DNSSEC
	# record specifically for the domain.
	#
	# Copy the .key and .private files to /tmp to patch them up.
	#
	# Use os.umask and open().write() to securely create a copy that only
	# we (root) can read.
	files_to_kill = []
	for key in ("KSK", "ZSK"):
		if dnssec_keys.get(key, "").strip() == "": raise Exception("DNSSEC is not properly set up.")
		oldkeyfn = os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys[key])
		newkeyfn = '/tmp/' + dnssec_keys[key].replace("_domain_", domain)
		dnssec_keys[key] = newkeyfn
		for ext in (".private", ".key"):
			if not os.path.exists(oldkeyfn + ext): raise Exception("DNSSEC is not properly set up.")
			with open(oldkeyfn + ext, "r") as fr:
				keydata = fr.read()
			keydata = keydata.replace("_domain_", domain) # trick ldns-signkey into letting our generic key be used by this zone
			fn = newkeyfn + ext
			prev_umask = os.umask(0o77) # ensure written file is not world-readable
			try:
				with open(fn, "w") as fw:
					fw.write(keydata)
			finally:
				os.umask(prev_umask) # other files we write should be world-readable
			files_to_kill.append(fn)

	# Do the signing.
	expiry_date = (datetime.datetime.now() + datetime.timedelta(days=30)).strftime("%Y%m%d")
	shell('check_call', ["/usr/bin/ldns-signzone",
		# expire the zone after 30 days
		"-e", expiry_date,

		# use NSEC3
		"-n",

		# zonefile to sign
		"/etc/nsd/zones/" + zonefile,

		# keys to sign with (order doesn't matter -- it'll figure it out)
		dnssec_keys["KSK"],
		dnssec_keys["ZSK"],
	])

	# Create a DS record based on the patched-up key files. The DS record is specific to the
	# zone being signed, so we can't use the .ds files generated when we created the keys.
	# The DS record points to the KSK only. Write this next to the zone file so we can
	# get it later to give to the user with instructions on what to do with it.
	#
	# We want to be able to validate DS records too, but multiple forms may be valid depending
	# on the digest type. So we'll write all (both) valid records. Only one DS record should
	# actually be deployed. Preferebly the first.
	with open("/etc/nsd/zones/" + zonefile + ".ds", "w") as f:
		for digest_type in ('2', '1'):
			rr_ds = shell('check_output', ["/usr/bin/ldns-key2ds",
				"-n", # output to stdout
				"-" + digest_type, # 1=SHA1, 2=SHA256
				dnssec_keys["KSK"] + ".key"
			])
			f.write(rr_ds)

	# Remove our temporary file.
	for fn in files_to_kill:
		os.unlink(fn)
Esempio n. 8
0
def check_dns_zone(domain, env, dns_zonefiles):
    # We provide a DNS zone for the domain. It should have NS records set up
    # at the domain name's registrar pointing to this box.
    existing_ns = query_dns(domain, "NS")
    correct_ns = "ns1.BOX; ns2.BOX".replace("BOX", env['PRIMARY_HOSTNAME'])
    if existing_ns.lower() == correct_ns.lower():
        env['out'].print_ok(
            "Nameservers are set correctly at registrar. [%s]" % correct_ns)
    else:
        env['out'].print_error(
            """The nameservers set on this domain are incorrect. They are currently %s. Use your domain name registar's
			control panel to set the nameservers to %s.""" % (existing_ns, correct_ns))

    # See if the domain has a DS record set at the registrar. The DS record may have
    # several forms. We have to be prepared to check for any valid record. We've
    # pre-generated all of the valid digests --- read them in.
    ds_correct = open('/etc/nsd/zones/' + dns_zonefiles[domain] +
                      '.ds').read().strip().split("\n")
    digests = {}
    for rr_ds in ds_correct:
        ds_keytag, ds_alg, ds_digalg, ds_digest = rr_ds.split("\t")[4].split(
            " ")
        digests[ds_digalg] = ds_digest

    # Some registrars may want the public key so they can compute the digest. The DS
    # record that we suggest using is for the KSK (and that's how the DS records were generated).
    dnssec_keys = load_env_vars_from_file(
        os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/keys.conf'))
    dnsssec_pubkey = open(
        os.path.join(env['STORAGE_ROOT'], 'dns/dnssec/' + dnssec_keys['KSK'] +
                     '.key')).read().split("\t")[3].split(" ")[3]

    # Query public DNS for the DS record at the registrar.
    ds = query_dns(domain, "DS", nxdomain=None)
    ds_looks_valid = ds and len(ds.split(" ")) == 4
    if ds_looks_valid: ds = ds.split(" ")
    if ds_looks_valid and ds[0] == ds_keytag and ds[1] == '7' and ds[
            3] == digests.get(ds[2]):
        env['out'].print_ok("DNS 'DS' record is set correctly at registrar.")
    else:
        if ds == None:
            env['out'].print_error(
                """This domain's DNS DS record is not set. The DS record is optional. The DS record activates DNSSEC.
				To set a DS record, you must follow the instructions provided by your domain name registrar and provide to them this information:"""
            )
        else:
            env['out'].print_error(
                """This domain's DNS DS record is incorrect. The chain of trust is broken between the public DNS system
				and this machine's DNS server. It may take several hours for public DNS to update after a change. If you did not recently
				make a change, you must resolve this immediately by following the instructions provided by your domain name registrar and
				provide to them this information:""")
        env['out'].print_line("")
        env['out'].print_line("Key Tag: " + ds_keytag + (
            "" if not ds_looks_valid or ds[0] == ds_keytag else " (Got '%s')" %
            ds[0]))
        env['out'].print_line("Key Flags: KSK")
        env['out'].print_line("Algorithm: 7 / RSASHA1-NSEC3-SHA1" + (
            "" if not ds_looks_valid or ds[1] == '7' else " (Got '%s')" %
            ds[1]))
        # see http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
        env['out'].print_line("Digest Type: 2 / SHA-256")
        # http://www.ietf.org/assignments/ds-rr-types/ds-rr-types.xml
        env['out'].print_line("Digest: " + digests['2'])
        if ds_looks_valid and ds[3] != digests.get(ds[2]):
            env['out'].print_line(
                "(Got digest type %s and digest %s which do not match.)" %
                (ds[2], ds[3]))
        env['out'].print_line("Public Key: ")
        env['out'].print_line(dnsssec_pubkey, monospace=True)
        env['out'].print_line("")
        env['out'].print_line("Bulk/Record Format:")
        env['out'].print_line("" + ds_correct[0])
        env['out'].print_line("")