#!/usr/bin/python

# openpgpkey: create OPENPGPKEY DNS record from a key in your keychain.

# Copyright Paul Wouters <paul@cypherpunks.ca>
#
# License: GNU GENERAL PUBLIC LICENSE Version 2 or later

VERSION="2.5"

import sys
import os
import gnupg
import unbound
import base64
from hashlib import sha224
import tempfile
import shutil

global openpgpkey_rrtype
global ctx

def asctohex(s):
    empty = '' # I use this construct because I find ''.join() too dense
    return empty.join(['%02x' % ord(c) for c in s]) # the %02 pads when needed

def getOPENPGPKEY(email,insecure_ok):
	"""This function queries for an OPENPGPKEY DNS Resource Record and compares it with the local gnupg keyring"""

	global ctx
	global openpgpkey_rrtype

	try:
		username, domainname = email.split("@")
	except:
		sys.exit("Invalid email syntax")

	keyname = "%s._openpgpkey.%s"%(sha224(username).hexdigest() ,domainname)

	status, result = ctx.resolve(keyname, rrtype=int(openpgpkey_rrtype))
	if status == 0 and result.havedata:
		if not result.secure:
			if not insecure_ok:
				# The data is insecure and a secure lookup was requested
				sys.exit("Error: query data is not secured by DNSSEC - use --insecure to override")
			else:
				print >> sys.stderr, 'Warning: query data was not secured by DNSSEC.'
		# If we are here the data was either secure or insecure data is accepted
		return result.data.raw
	else:
		sys.exit('Unsuccesful lookup or no data returned for rrtype %s.' %openpgpkey_rrtype)

if __name__ == '__main__':
	import argparse
	# create the parser
	parser = argparse.ArgumentParser(description='Create and verify OPENPGPKEY records.', epilog='For bugs. see paul@nohats.ca')

	parser.add_argument('--verify','-v', action='store_true', help='Verify an OPENPGPKEY record, exit 0 when all records are matched, exit 1 on error.')

	parser.add_argument('--create','-c', action='store_true', help='Create an OPENPGKEY record')
	parser.add_argument('--version', action='version', version='openpgpkey version: %s'%VERSION, help='show version and exit')

	parser.add_argument('--insecure', action='store_true', default=False, help='Allow use of non-dnssec secured answers')
	parser.add_argument('--resolvconf', action='store', default='', help='Use a recursive resolver listed in a resolv.conf file (default: /etc/resolv.conf)')
	parser.add_argument('--rootanchor', action='store', default='/var/lib/unbound/root.anchor', help='Location of the unbound compatible DNSSEC root.anchor (default: /var/lib/unbound/root.anchor)')
	parser.add_argument('--rrtype', metavar='rrtype', action='store', default=65280, help='Location of the unbound compatible DNSSEC root.anchor (default: /var/lib/unbound/root.anchor)')
	parser.add_argument('email', metavar="email")

	parser.add_argument('--debug', '-d', action='store_true', help='Print details plus the result of the validation')
	parser.add_argument('--quiet', '-q', action='store_true', help='Ignored for backwards compatibility')

	parser.add_argument('--output', '-o', action='store', default='generic', choices=['generic','rfc','both'], help='The type of output. Generic (RFC 3597, TYPE65280), RFC (OPENPGPKEY) or both (default: %(default)s).')

	args = parser.parse_args()

	global openpgpkey_rrtype

	# unbound setup
	global ctx
	ctx = unbound.ub_ctx()
	resolvconf = "/etc/resolv.conf"
	rootanchor = "/var/lib/unbound/root.anchor"
	dlvkey = "/etc/unbound/dlv.isc.org.key"

	if args.resolvconf:
		if os.path.isfile(args.resolvconf):
			resolvconf = args.resolvconf
		else:
			print >> sys.stdout, '%s is not a file. Unable to use it as resolv.conf' % args.resolvconf
			sys.exit(1)
	ctx.resolvconf(resolvconf)

	if os.path.isfile(dlvkey):
		ctx.set_option("dlv-anchor-file:", dlvkey)

	if args.rootanchor:
		if os.path.isfile(args.rootanchor):
			resolvconf = args.rootanchor
		else:
			print >> sys.stdout, '%s is not a file. Unable to use it as rootanchor' % args.rootanchor
			sys.exit(1)
	else:
		if not os.path.isfile(rootanchor):
			rootanchor = None
	if rootanchor:
		ctx.add_ta_file(rootanchor)
	else:
		# fallback to a root key if we can find it
		if os.path.isfile("/etc/unbound/root.key"):
			rootkey = "/etc/unbound/root.key"
		elif os.path.isfile("/var/lib/unbound/root.key"):
			rootkey = "/var/lib/unbound/root.key"
		else:
			rootkey = None
		if rootkey:
			unbound.ub_ctx_trustedkeys(ctx,rootkey)

	# unbound setup completed

	openpgpkey_rrtype = int(args.rrtype)

	if args.verify:
		openpgpkeys = getOPENPGPKEY(args.email, args.insecure)
		if len(openpgpkeys) == 0:
			print 'Received nothing?'
			sys.exit(1)
		fdir = tempfile.mkdtemp(".gpg","openpgpkey-","/tmp/")
		gpgnet = gnupg.GPG(gnupghome=fdir)
		gpgdisk = gnupg.GPG()
		for openpgpkey in openpgpkeys:
			import_result = gpgnet.import_keys(openpgpkey)
		received_keys = gpgnet.list_keys() 
		disk_keys = gpgdisk.list_keys()
		for pkey in received_keys:
			if args.debug:
				print >> sys.stderr, "Received from DNS: Key-ID:%s Fingerprint:%s"%(pkey["keyid"], pkey["fingerprint"])
			found = False
			for dkey in disk_keys:
				if args.debug:
					print >> sys.stderr, "Local disk: Key-ID:%s Fingerprint:%s"%(dkey["keyid"], dkey["fingerprint"])
				if pkey["keyid"] == dkey["keyid"] and pkey["fingerprint"] == dkey["fingerprint"]:
					found = True
			if found == False:
				shutil.rmtree(fdir)
				sys.exit("Received key with keyid %s was not found"%pkey["keyid"])
			else:
				if args.debug:
					print >> sys.stderr, "Received key with keyid %s was found"%pkey["keyid"]
		print "All OPENPGPKEY records matched with content from the local keyring"
		shutil.rmtree(fdir)
		sys.exit(0)
			
			
			


	else: # we want to create
		gpgdisk = gnupg.GPG()
		disk_keys = gpgdisk.list_keys()
		found = False
		for pgpkey in disk_keys:
			for uid in pgpkey["uids"]:
				if "<%s>"%args.email in uid:
					found = True
					if args.debug:
						print >> sys.stderr,  "Found matching KeyID: %s (%s) for %s"%(pgpkey["keyid"], pgpkey["fingerprint"], uid)
					ekey = gpgdisk.export_keys(pgpkey["keyid"],minimal=True, secret=False, armor=False)
					ekey64 = base64.b64encode(ekey)
					user,domain = args.email.split("@")
					euser = sha224(user).hexdigest()

					if args.output != "generic":
						print "%s._openpgpkey.%s. IN OPENPGPKEY %s"%(euser,domain,ekey64)
					if args.output !=  "rfc":
						if args.debug:
							print "Length for generic record is %s"%len(ekey)
						print "%s._openpgpkey.%s. IN TYPE65280 \# %s %s"%(euser,domain,len(ekey64),asctohex(ekey))
		if not found:
			sys.exit("No key found containing the email address '%s' in the keyid(s)"%args.email)
		


