move openalias from plugins to core
This commit is contained in:
parent
90d32038fa
commit
616becd9a8
|
@ -19,6 +19,10 @@
|
|||
import sys, time, threading
|
||||
import os.path, json, traceback
|
||||
import shutil
|
||||
import socket
|
||||
import webbrowser
|
||||
import csv
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
import PyQt4
|
||||
|
@ -26,11 +30,10 @@ from PyQt4.QtGui import *
|
|||
from PyQt4.QtCore import *
|
||||
import PyQt4.QtCore as QtCore
|
||||
|
||||
from electrum.bitcoin import MIN_RELAY_TX_FEE, COIN, is_valid
|
||||
from electrum.plugins import run_hook
|
||||
|
||||
import icons_rc
|
||||
|
||||
from electrum.bitcoin import MIN_RELAY_TX_FEE, COIN, is_valid
|
||||
from electrum.plugins import run_hook
|
||||
from electrum.i18n import _
|
||||
from electrum.util import block_explorer, block_explorer_info, block_explorer_URL
|
||||
from electrum.util import print_error, print_msg
|
||||
|
@ -41,6 +44,7 @@ from electrum import util, bitcoin, commands, Wallet
|
|||
from electrum import SimpleConfig, Wallet, WalletStorage
|
||||
from electrum import Imported_Wallet
|
||||
from electrum import paymentrequest
|
||||
from electrum.contacts import Contacts
|
||||
|
||||
from amountedit import AmountEdit, BTCAmountEdit, MyLineEdit
|
||||
from network_dialog import NetworkDialog
|
||||
|
@ -48,12 +52,6 @@ from qrcodewidget import QRCodeWidget, QRDialog
|
|||
from qrtextedit import ScanQRTextEdit, ShowQRTextEdit
|
||||
from transaction_dialog import show_transaction
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
import socket
|
||||
import webbrowser
|
||||
import csv
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -120,7 +118,7 @@ class ElectrumWindow(QMainWindow):
|
|||
self.app = gui_object.app
|
||||
|
||||
self.invoices = InvoiceStore(self.config)
|
||||
self.contacts = util.Contacts(self.config)
|
||||
self.contacts = Contacts(self.config)
|
||||
|
||||
self.create_status_bar()
|
||||
self.need_update = threading.Event()
|
||||
|
@ -482,17 +480,16 @@ class ElectrumWindow(QMainWindow):
|
|||
if self.need_update.is_set():
|
||||
self.update_wallet()
|
||||
self.need_update.clear()
|
||||
|
||||
# resolve aliases
|
||||
self.payto_e.resolve()
|
||||
run_hook('timer_actions')
|
||||
|
||||
def format_amount(self, x, is_diff=False, whitespaces=False):
|
||||
return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
|
||||
|
||||
|
||||
def get_decimal_point(self):
|
||||
return self.decimal_point
|
||||
|
||||
|
||||
def base_unit(self):
|
||||
assert self.decimal_point in [2, 5, 8]
|
||||
if self.decimal_point == 2:
|
||||
|
@ -1051,6 +1048,13 @@ class ElectrumWindow(QMainWindow):
|
|||
return
|
||||
outputs = self.payto_e.get_outputs()
|
||||
|
||||
if self.payto_e.is_alias and self.payto_e.validated is False:
|
||||
alias = self.payto_e.toPlainText()
|
||||
msg = _('WARNING: the alias "%s" could not be validated via an additional security check, DNSSEC, and thus may not be correct.'%alias) + '\n'
|
||||
msg += _('Do you wish to continue?')
|
||||
if not self.question(msg):
|
||||
return
|
||||
|
||||
if not outputs:
|
||||
QMessageBox.warning(self, _('Error'), _('No outputs'), _('OK'))
|
||||
return
|
||||
|
@ -1208,6 +1212,7 @@ class ElectrumWindow(QMainWindow):
|
|||
self.payment_request = None
|
||||
return
|
||||
|
||||
self.payto_e.is_pr = True
|
||||
if not pr.has_expired():
|
||||
self.payto_e.setGreen()
|
||||
else:
|
||||
|
|
|
@ -34,6 +34,7 @@ class PayToEdit(ScanQRTextEdit):
|
|||
|
||||
def __init__(self, win):
|
||||
ScanQRTextEdit.__init__(self)
|
||||
self.win = win
|
||||
self.amount_edit = win.amount_e
|
||||
self.document().contentsChanged.connect(self.update_size)
|
||||
self.heightMin = 0
|
||||
|
@ -43,10 +44,13 @@ class PayToEdit(ScanQRTextEdit):
|
|||
self.outputs = []
|
||||
self.errors = []
|
||||
self.is_pr = False
|
||||
self.is_alias = False
|
||||
self.scan_f = win.pay_from_URI
|
||||
self.update_size()
|
||||
self.payto_address = None
|
||||
|
||||
self.previous_payto = ''
|
||||
|
||||
def lock_amount(self):
|
||||
self.amount_edit.setFrozen(True)
|
||||
|
||||
|
@ -60,11 +64,9 @@ class PayToEdit(ScanQRTextEdit):
|
|||
button.setHidden(b)
|
||||
|
||||
def setGreen(self):
|
||||
self.is_pr = True
|
||||
self.setStyleSheet("QWidget { background-color:#80ff80;}")
|
||||
|
||||
def setExpired(self):
|
||||
self.is_pr = True
|
||||
self.setStyleSheet("QWidget { background-color:#ffcccc;}")
|
||||
|
||||
def parse_address_and_amount(self, line):
|
||||
|
@ -252,3 +254,45 @@ class PayToEdit(ScanQRTextEdit):
|
|||
if data.startswith("bitcoin:"):
|
||||
self.scan_f(data)
|
||||
# TODO: update fee
|
||||
|
||||
def resolve(self):
|
||||
self.is_alias = False
|
||||
if self.hasFocus():
|
||||
return
|
||||
if self.is_multiline(): # only supports single line entries atm
|
||||
return
|
||||
if self.is_pr:
|
||||
return
|
||||
key = str(self.toPlainText())
|
||||
if key == self.previous_payto:
|
||||
return
|
||||
self.previous_payto = key
|
||||
if not (('.' in key) and (not '<' in key) and (not ' ' in key)):
|
||||
return
|
||||
try:
|
||||
data = self.win.contacts.resolve(key)
|
||||
except:
|
||||
return
|
||||
if not data:
|
||||
return
|
||||
self.is_alias = True
|
||||
|
||||
address = data.get('address')
|
||||
name = data.get('name')
|
||||
new_url = key + ' <' + address + '>'
|
||||
self.setText(new_url)
|
||||
self.previous_payto = new_url
|
||||
|
||||
#if self.win.config.get('openalias_autoadd') == 'checked':
|
||||
self.win.contacts[key] = ('openalias', name)
|
||||
self.win.update_contacts_tab()
|
||||
|
||||
self.setFrozen(True)
|
||||
if data.get('type') == 'openalias':
|
||||
self.validated = data.get('validated')
|
||||
if self.validated:
|
||||
self.setGreen()
|
||||
else:
|
||||
self.setExpired()
|
||||
else:
|
||||
self.validated = None
|
||||
|
|
|
@ -34,6 +34,7 @@ from bitcoin import is_address, hash_160_to_bc_address, hash_160, COIN
|
|||
from transaction import Transaction
|
||||
import paymentrequest
|
||||
from paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
|
||||
import contacts
|
||||
|
||||
known_commands = {}
|
||||
|
||||
|
@ -78,7 +79,7 @@ class Commands:
|
|||
self.network = network
|
||||
self._callback = callback
|
||||
self.password = None
|
||||
self.contacts = util.Contacts(self.config)
|
||||
self.contacts = contacts.Contacts(self.config)
|
||||
|
||||
def _run(self, method, args, password_getter):
|
||||
cmd = known_commands[method]
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
import sys
|
||||
import re
|
||||
import dns
|
||||
import traceback
|
||||
|
||||
import bitcoin
|
||||
from util import StoreDict, print_error
|
||||
from i18n import _
|
||||
|
||||
# Import all of the rdtypes, as py2app and similar get confused with the dnspython
|
||||
# autoloader and won't include all the rdatatypes
|
||||
try:
|
||||
import dns.name
|
||||
import dns.query
|
||||
import dns.dnssec
|
||||
import dns.message
|
||||
import dns.resolver
|
||||
import dns.rdatatype
|
||||
import dns.rdtypes.ANY.NS
|
||||
import dns.rdtypes.ANY.CNAME
|
||||
import dns.rdtypes.ANY.DLV
|
||||
import dns.rdtypes.ANY.DNSKEY
|
||||
import dns.rdtypes.ANY.DS
|
||||
import dns.rdtypes.ANY.NSEC
|
||||
import dns.rdtypes.ANY.NSEC3
|
||||
import dns.rdtypes.ANY.NSEC3PARAM
|
||||
import dns.rdtypes.ANY.RRSIG
|
||||
import dns.rdtypes.ANY.SOA
|
||||
import dns.rdtypes.ANY.TXT
|
||||
import dns.rdtypes.IN.A
|
||||
import dns.rdtypes.IN.AAAA
|
||||
from dns.exception import DNSException
|
||||
OA_READY = True
|
||||
except ImportError:
|
||||
OA_READY = False
|
||||
|
||||
|
||||
class Contacts(StoreDict):
|
||||
|
||||
def __init__(self, config):
|
||||
StoreDict.__init__(self, config, 'contacts')
|
||||
|
||||
def resolve(self, k):
|
||||
if bitcoin.is_address(k):
|
||||
return {
|
||||
'address': k,
|
||||
'type': 'address'
|
||||
}
|
||||
|
||||
if k in self.keys():
|
||||
_type, addr = self[k]
|
||||
if _type == 'address':
|
||||
return {
|
||||
'address': addr,
|
||||
'type': 'contact'
|
||||
}
|
||||
|
||||
out = self.resolve_openalias(k)
|
||||
if out:
|
||||
address, name = out
|
||||
try:
|
||||
validated = self.validate_dnssec(k)
|
||||
except:
|
||||
validated = False
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
return {
|
||||
'address': address,
|
||||
'name': name,
|
||||
'type': 'openalias',
|
||||
'validated': validated
|
||||
}
|
||||
|
||||
raise Exception("Invalid Bitcoin address or alias", k)
|
||||
|
||||
def resolve_openalias(self, url):
|
||||
'''Resolve OpenAlias address using url.'''
|
||||
print_error('[OA] Attempting to resolve OpenAlias data for ' + url)
|
||||
|
||||
url = url.replace('@', '.') # support email-style addresses, per the OA standard
|
||||
prefix = 'btc'
|
||||
retries = 3
|
||||
err = None
|
||||
for i in range(0, retries):
|
||||
try:
|
||||
resolver = dns.resolver.Resolver()
|
||||
resolver.timeout = 2.0
|
||||
resolver.lifetime = 4.0
|
||||
records = resolver.query(url, dns.rdatatype.TXT)
|
||||
for record in records:
|
||||
string = record.strings[0]
|
||||
if string.startswith('oa1:' + prefix):
|
||||
address = self.find_regex(string, r'recipient_address=([A-Za-z0-9]+)')
|
||||
name = self.find_regex(string, r'recipient_name=([^;]+)')
|
||||
if not name:
|
||||
name = address
|
||||
if not address:
|
||||
continue
|
||||
return (address, name)
|
||||
err = _('No OpenAlias record found.')
|
||||
break
|
||||
except dns.resolver.NXDOMAIN:
|
||||
err = _('No such domain.')
|
||||
continue
|
||||
except dns.resolver.Timeout:
|
||||
err = _('Timed out while resolving.')
|
||||
continue
|
||||
except DNSException:
|
||||
err = _('Unhandled exception.')
|
||||
continue
|
||||
except Exception, e:
|
||||
err = _('Unexpected error: ' + str(e))
|
||||
continue
|
||||
break
|
||||
if err:
|
||||
print_error(err)
|
||||
return 0
|
||||
|
||||
def find_regex(self, haystack, needle):
|
||||
regex = re.compile(needle)
|
||||
try:
|
||||
return regex.search(haystack).groups()[0]
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def validate_dnssec(self, url):
|
||||
print_error('Checking DNSSEC trust chain for ' + url)
|
||||
default = dns.resolver.get_default_resolver()
|
||||
ns = default.nameservers[0]
|
||||
parts = url.split('.')
|
||||
|
||||
for i in xrange(len(parts), 0, -1):
|
||||
sub = '.'.join(parts[i - 1:])
|
||||
query = dns.message.make_query(sub, dns.rdatatype.NS)
|
||||
response = dns.query.udp(query, ns, 3)
|
||||
if response.rcode() != dns.rcode.NOERROR:
|
||||
print_error("query error")
|
||||
return False
|
||||
|
||||
if len(response.authority) > 0:
|
||||
rrset = response.authority[0]
|
||||
else:
|
||||
rrset = response.answer[0]
|
||||
|
||||
rr = rrset[0]
|
||||
if rr.rdtype == dns.rdatatype.SOA:
|
||||
#Same server is authoritative, don't check again
|
||||
continue
|
||||
|
||||
query = dns.message.make_query(sub,
|
||||
dns.rdatatype.DNSKEY,
|
||||
want_dnssec=True)
|
||||
response = dns.query.udp(query, ns, 3)
|
||||
if response.rcode() != 0:
|
||||
self.print_error("query error")
|
||||
return False
|
||||
# HANDLE QUERY FAILED (SERVER ERROR OR NO DNSKEY RECORD)
|
||||
|
||||
# answer should contain two RRSET: DNSKEY and RRSIG(DNSKEY)
|
||||
answer = response.answer
|
||||
if len(answer) != 2:
|
||||
print_error("answer error", answer)
|
||||
return False
|
||||
|
||||
# the DNSKEY should be self signed, validate it
|
||||
name = dns.name.from_text(sub)
|
||||
try:
|
||||
dns.dnssec.validate(answer[0], answer[1], {name: answer[0]})
|
||||
except dns.dnssec.ValidationFailure:
|
||||
print_error("validation error")
|
||||
return False
|
||||
|
||||
return True
|
19
lib/util.py
19
lib/util.py
|
@ -469,22 +469,3 @@ class StoreDict(dict):
|
|||
self.save()
|
||||
|
||||
|
||||
import bitcoin
|
||||
from plugins import run_hook
|
||||
|
||||
class Contacts(StoreDict):
|
||||
|
||||
def __init__(self, config):
|
||||
StoreDict.__init__(self, config, 'contacts')
|
||||
|
||||
def resolve(self, k):
|
||||
if bitcoin.is_address(k):
|
||||
return {'address':k, 'type':'address'}
|
||||
if k in self.keys():
|
||||
_type, addr = self[k]
|
||||
if _type == 'address':
|
||||
return {'address':addr, 'type':'contact'}
|
||||
out = run_hook('resolve_address', k)
|
||||
if out:
|
||||
return out
|
||||
raise Exception("Invalid Bitcoin address or alias", k)
|
||||
|
|
|
@ -68,13 +68,6 @@ descriptions = [
|
|||
]),
|
||||
'available_for': ['qt']
|
||||
},
|
||||
{
|
||||
'name': 'openalias',
|
||||
'fullname': 'OpenAlias',
|
||||
'description': _('Allow for payments to OpenAlias addresses.'),
|
||||
'requires': [('dns', 'dnspython')],
|
||||
'available_for': ['qt', 'cmdline']
|
||||
},
|
||||
{
|
||||
'name': 'plot',
|
||||
'fullname': 'Plot History',
|
||||
|
|
|
@ -1,297 +0,0 @@
|
|||
# Copyright (c) 2014-2015, The Monero Project
|
||||
#
|
||||
# All rights reserved.
|
||||
|
||||
# This plugin is licensed under the GPL v3 license (see the LICENSE file in the base of
|
||||
# the project source code). The Monero Project reserves the right to change this license
|
||||
# in future to match or be compliant with any relicense of the Electrum project.
|
||||
|
||||
# This plugin implements the OpenAlias standard. For information on the standard please
|
||||
# see: https://openalias.org
|
||||
|
||||
# Donations for ongoing development of the standard and hosting resolvers can be sent to
|
||||
# openalias.org or donate.monero.cc
|
||||
|
||||
# Version: 0.1
|
||||
# Todo: optionally use OA resolvers; add DNSCrypt support
|
||||
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from PyQt4.QtGui import *
|
||||
from PyQt4.QtCore import *
|
||||
|
||||
from electrum_gui.qt.util import *
|
||||
from electrum.plugins import BasePlugin, hook
|
||||
from electrum.util import print_error
|
||||
from electrum.i18n import _
|
||||
|
||||
|
||||
# Import all of the rdtypes, as py2app and similar get confused with the dnspython
|
||||
# autoloader and won't include all the rdatatypes
|
||||
try:
|
||||
import dns.name
|
||||
import dns.query
|
||||
import dns.dnssec
|
||||
import dns.message
|
||||
import dns.resolver
|
||||
import dns.rdatatype
|
||||
import dns.rdtypes.ANY.NS
|
||||
import dns.rdtypes.ANY.CNAME
|
||||
import dns.rdtypes.ANY.DLV
|
||||
import dns.rdtypes.ANY.DNSKEY
|
||||
import dns.rdtypes.ANY.DS
|
||||
import dns.rdtypes.ANY.NSEC
|
||||
import dns.rdtypes.ANY.NSEC3
|
||||
import dns.rdtypes.ANY.NSEC3PARAM
|
||||
import dns.rdtypes.ANY.RRSIG
|
||||
import dns.rdtypes.ANY.SOA
|
||||
import dns.rdtypes.ANY.TXT
|
||||
import dns.rdtypes.IN.A
|
||||
import dns.rdtypes.IN.AAAA
|
||||
from dns.exception import DNSException
|
||||
OA_READY = True
|
||||
except ImportError:
|
||||
OA_READY = False
|
||||
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def is_available(self):
|
||||
return OA_READY
|
||||
|
||||
def __init__(self, gui, name):
|
||||
BasePlugin.__init__(self, gui, name)
|
||||
self._is_available = OA_READY
|
||||
self.print_error('OA_READY is ' + str(OA_READY))
|
||||
self.previous_payto = ''
|
||||
|
||||
@hook
|
||||
def init_qt(self, gui):
|
||||
self.gui = gui
|
||||
self.win = gui.main_window
|
||||
|
||||
def requires_settings(self):
|
||||
return True
|
||||
|
||||
def settings_widget(self, window):
|
||||
return EnterButton(_('Settings'), self.settings_dialog)
|
||||
|
||||
@hook
|
||||
def timer_actions(self):
|
||||
if self.win.payto_e.hasFocus():
|
||||
return
|
||||
if self.win.payto_e.is_multiline(): # only supports single line entries atm
|
||||
return
|
||||
if self.win.payto_e.is_pr:
|
||||
return
|
||||
|
||||
url = str(self.win.payto_e.toPlainText())
|
||||
|
||||
if url == self.previous_payto:
|
||||
return
|
||||
self.previous_payto = url
|
||||
|
||||
if not (('.' in url) and (not '<' in url) and (not ' ' in url)):
|
||||
return
|
||||
|
||||
data = self.resolve(url)
|
||||
|
||||
if not data:
|
||||
self.previous_payto = url
|
||||
return True
|
||||
|
||||
address, name = data
|
||||
new_url = url + ' <' + address + '>'
|
||||
self.win.payto_e.setText(new_url)
|
||||
self.previous_payto = new_url
|
||||
|
||||
if self.config.get('openalias_autoadd') == 'checked':
|
||||
self.win.contacts[url] = ('openalias', name)
|
||||
self.win.update_contacts_tab()
|
||||
|
||||
self.win.payto_e.setFrozen(True)
|
||||
try:
|
||||
self.validated = self.validate_dnssec(url)
|
||||
except:
|
||||
self.validated = False
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
if self.validated:
|
||||
self.win.payto_e.setGreen()
|
||||
else:
|
||||
self.win.payto_e.setExpired()
|
||||
|
||||
@hook
|
||||
def before_send(self):
|
||||
'''
|
||||
Change URL to address before making a send.
|
||||
IMPORTANT:
|
||||
return False to continue execution of the send
|
||||
return True to stop execution of the send
|
||||
'''
|
||||
|
||||
if self.win.payto_e.is_multiline(): # only supports single line entries atm
|
||||
return False
|
||||
if self.win.payto_e.is_pr:
|
||||
return
|
||||
payto_e = str(self.win.payto_e.toPlainText())
|
||||
regex = re.compile(r'^([^\s]+) <([A-Za-z0-9]+)>') # only do that for converted addresses
|
||||
try:
|
||||
(url, address) = regex.search(payto_e).groups()
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
if not self.validated:
|
||||
msgBox = QMessageBox()
|
||||
msgBox.setText(_('WARNING: the address ' + address + ' could not be validated via an additional security check, DNSSEC, and thus may not be correct.'))
|
||||
msgBox.setInformativeText(_('Do you wish to continue?'))
|
||||
msgBox.setStandardButtons(QMessageBox.Cancel | QMessageBox.Ok)
|
||||
msgBox.setDefaultButton(QMessageBox.Cancel)
|
||||
reply = msgBox.exec_()
|
||||
if reply != QMessageBox.Ok:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def settings_dialog(self):
|
||||
'''Settings dialog.'''
|
||||
d = QDialog()
|
||||
d.setWindowTitle("Settings")
|
||||
layout = QGridLayout(d)
|
||||
layout.addWidget(QLabel(_('Automatically add to contacts')), 0, 0)
|
||||
autoadd_checkbox = QCheckBox()
|
||||
autoadd_checkbox.setEnabled(True)
|
||||
autoadd_checkbox.setChecked(self.config.get('openalias_autoadd', 'unchecked') != 'unchecked')
|
||||
layout.addWidget(autoadd_checkbox, 0, 1)
|
||||
ok_button = QPushButton(_("OK"))
|
||||
ok_button.clicked.connect(d.accept)
|
||||
layout.addWidget(ok_button, 1, 1)
|
||||
|
||||
def on_change_autoadd(checked):
|
||||
if checked:
|
||||
self.config.set_key('openalias_autoadd', 'checked')
|
||||
else:
|
||||
self.config.set_key('openalias_autoadd', 'unchecked')
|
||||
|
||||
autoadd_checkbox.stateChanged.connect(on_change_autoadd)
|
||||
|
||||
return bool(d.exec_())
|
||||
|
||||
|
||||
@hook
|
||||
def resolve_address(self, url):
|
||||
data = self.resolve(url)
|
||||
if not data:
|
||||
return
|
||||
address, name = data
|
||||
try:
|
||||
validated = self.validate_dnssec(url)
|
||||
except:
|
||||
validated = False
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
return {
|
||||
'address': address,
|
||||
'name': name,
|
||||
'type': 'openalias',
|
||||
'validated': validated
|
||||
}
|
||||
|
||||
|
||||
def resolve(self, url):
|
||||
'''Resolve OpenAlias address using url.'''
|
||||
self.print_error('[OA] Attempting to resolve OpenAlias data for ' + url)
|
||||
|
||||
url = url.replace('@', '.') # support email-style addresses, per the OA standard
|
||||
prefix = 'btc'
|
||||
retries = 3
|
||||
err = None
|
||||
for i in range(0, retries):
|
||||
try:
|
||||
resolver = dns.resolver.Resolver()
|
||||
resolver.timeout = 2.0
|
||||
resolver.lifetime = 4.0
|
||||
records = resolver.query(url, dns.rdatatype.TXT)
|
||||
for record in records:
|
||||
string = record.strings[0]
|
||||
if string.startswith('oa1:' + prefix):
|
||||
address = self.find_regex(string, r'recipient_address=([A-Za-z0-9]+)')
|
||||
name = self.find_regex(string, r'recipient_name=([^;]+)')
|
||||
if not name:
|
||||
name = address
|
||||
if not address:
|
||||
continue
|
||||
return (address, name)
|
||||
QMessageBox.warning(self.win, _('Error'), _('No OpenAlias record found.'), _('OK'))
|
||||
return 0
|
||||
except dns.resolver.NXDOMAIN:
|
||||
err = _('No such domain.')
|
||||
continue
|
||||
except dns.resolver.Timeout:
|
||||
err = _('Timed out while resolving.')
|
||||
continue
|
||||
except DNSException:
|
||||
err = _('Unhandled exception.')
|
||||
continue
|
||||
except Exception, e:
|
||||
err = _('Unexpected error: ' + str(e))
|
||||
continue
|
||||
break
|
||||
if err:
|
||||
QMessageBox.warning(self.win, _('Error'), err, _('OK'))
|
||||
return 0
|
||||
|
||||
def find_regex(self, haystack, needle):
|
||||
regex = re.compile(needle)
|
||||
try:
|
||||
return regex.search(haystack).groups()[0]
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def validate_dnssec(self, url):
|
||||
self.print_error('Checking DNSSEC trust chain for ' + url)
|
||||
default = dns.resolver.get_default_resolver()
|
||||
ns = default.nameservers[0]
|
||||
parts = url.split('.')
|
||||
|
||||
for i in xrange(len(parts), 0, -1):
|
||||
sub = '.'.join(parts[i - 1:])
|
||||
query = dns.message.make_query(sub, dns.rdatatype.NS)
|
||||
response = dns.query.udp(query, ns, 3)
|
||||
if response.rcode() != dns.rcode.NOERROR:
|
||||
self.print_error("query error")
|
||||
return False
|
||||
|
||||
if len(response.authority) > 0:
|
||||
rrset = response.authority[0]
|
||||
else:
|
||||
rrset = response.answer[0]
|
||||
|
||||
rr = rrset[0]
|
||||
if rr.rdtype == dns.rdatatype.SOA:
|
||||
#Same server is authoritative, don't check again
|
||||
continue
|
||||
|
||||
query = dns.message.make_query(sub,
|
||||
dns.rdatatype.DNSKEY,
|
||||
want_dnssec=True)
|
||||
response = dns.query.udp(query, ns, 3)
|
||||
if response.rcode() != 0:
|
||||
self.print_error("query error")
|
||||
return False
|
||||
# HANDLE QUERY FAILED (SERVER ERROR OR NO DNSKEY RECORD)
|
||||
|
||||
# answer should contain two RRSET: DNSKEY and RRSIG(DNSKEY)
|
||||
answer = response.answer
|
||||
if len(answer) != 2:
|
||||
self.print_error("answer error", answer)
|
||||
return False
|
||||
|
||||
# the DNSKEY should be self signed, validate it
|
||||
name = dns.name.from_text(sub)
|
||||
try:
|
||||
dns.dnssec.validate(answer[0], answer[1], {name: answer[0]})
|
||||
except dns.dnssec.ValidationFailure:
|
||||
self.print_error("validation error")
|
||||
return False
|
||||
|
||||
return True
|
Loading…
Reference in New Issue