replace BaseException with Exception
[electrum-nvc.git] / plugins / aliases.py
1 import re
2 import platform
3 from decimal import Decimal
4
5 from PyQt4.QtGui import *
6 from PyQt4.QtCore import *
7 import PyQt4.QtCore as QtCore
8 import PyQt4.QtGui as QtGui
9
10 from electrum import bmp, pyqrnative
11 from electrum.i18n import _
12
13 from electrum import util
14
15 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'    
16
17
18
19 from electrum.plugins import BasePlugin
20
21 class Plugin(BasePlugin):
22
23     def fullname(self): return 'Aliases'
24
25     def description(self): return _('Retrieve aliases using http.')
26
27     def init(self):
28         self.aliases      = self.config.get('aliases', {})            # aliases for addresses
29         self.authorities  = self.config.get('authorities', {})        # trusted addresses
30         self.receipts     = self.config.get('receipts',{})            # signed URIs
31
32     def is_available(self):
33         return False
34
35     def timer_actions(self):
36         if self.gui.payto_e.hasFocus():
37             return
38         r = unicode( self.gui.payto_e.text() )
39         if r != self.gui.previous_payto_e:
40             self.gui.previous_payto_e = r
41             r = r.strip()
42             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
43                 try:
44                     to_address = self.get_alias(r, True, self.gui.show_message, self.gui.question)
45                 except:
46                     return
47                 if to_address:
48                     s = r + '  <' + to_address + '>'
49                     self.gui.payto_e.setText(s)
50
51
52     def get_alias(self, alias, interactive = False, show_message=None, question = None):
53         try:
54             target, signing_address, auth_name = read_alias(self, alias)
55         except Exception as e:
56             # raise exception if verify fails (verify the chain)
57             if interactive:
58                 show_message("Alias error: " + str(e))
59             return
60
61         print target, signing_address, auth_name
62
63         if auth_name is None:
64             a = self.aliases.get(alias)
65             if not a:
66                 msg = "Warning: the alias '%s' is self-signed.\nThe signing address is %s.\n\nDo you want to add this alias to your list of contacts?"%(alias,signing_address)
67                 if interactive and question( msg ):
68                     self.aliases[alias] = (signing_address, target)
69                 else:
70                     target = None
71             else:
72                 if signing_address != a[0]:
73                     msg = "Warning: the key of alias '%s' has changed since your last visit! It is possible that someone is trying to do something nasty!!!\nDo you accept to change your trusted key?"%alias
74                     if interactive and question( msg ):
75                         self.aliases[alias] = (signing_address, target)
76                     else:
77                         target = None
78         else:
79             if signing_address not in self.authorities.keys():
80                 msg = "The alias: '%s' links to %s\n\nWarning: this alias was signed by an unknown key.\nSigning authority: %s\nSigning address: %s\n\nDo you want to add this key to your list of trusted keys?"%(alias,target,auth_name,signing_address)
81                 if interactive and question( msg ):
82                     self.authorities[signing_address] = auth_name
83                 else:
84                     target = None
85
86         if target:
87             self.aliases[alias] = (signing_address, target)
88             
89         return target
90
91
92
93     def read_alias(self, alias):
94         import urllib
95
96         m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
97         m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
98         if m1:
99             url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) 
100         elif m2:
101             url = 'https://' + alias + '/bitcoin.id'
102         else:
103             return ''
104         try:
105             lines = urllib.urlopen(url).readlines()
106         except:
107             return ''
108
109         # line 0
110         line = lines[0].strip().split(':')
111         if len(line) == 1:
112             auth_name = None
113             target = signing_addr = line[0]
114         else:
115             target, auth_name, signing_addr, signature = line
116             msg = "alias:%s:%s:%s"%(alias,target,auth_name)
117             print msg, signature
118             EC_KEY.verify_message(signing_addr, signature, msg)
119         
120         # other lines are signed updates
121         for line in lines[1:]:
122             line = line.strip()
123             if not line: continue
124             line = line.split(':')
125             previous = target
126             print repr(line)
127             target, signature = line
128             EC_KEY.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
129
130         if not is_valid(target):
131             raise ValueError("Invalid bitcoin address")
132
133         return target, signing_addr, auth_name
134
135
136     def set_url(self, url, show_message, question):
137         payto, amount, label, message, signature, identity, url = util.parse_url(url)
138         if signature:
139             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
140                 signing_address = get_alias(identity, True, show_message, question)
141             elif is_valid(identity):
142                 signing_address = identity
143             else:
144                 signing_address = None
145             if not signing_address:
146                 return
147             try:
148                 EC_KEY.verify_message(signing_address, signature, url )
149                 self.receipt = (signing_address, signature, url)
150             except:
151                 show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
152                 address = amount = label = identity = message = ''
153
154         if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', payto):
155             payto_address = get_alias(payto, True, show_message, question)
156             if payto_address:
157                 payto += ' <' + payto_address + '>'
158
159         return payto, amount, label, message, signature, identity, url
160
161
162
163     def update_contacts_tab(self, l):
164         alias_targets = []
165         for alias, v in self.aliases.items():
166             s, target = v
167             alias_targets.append(target)
168             item = QTreeWidgetItem( [ target, alias, '-'] )
169             item.setBackgroundColor(0, QColor('lightgray'))
170             item.setData(0,32,False)
171             item.setData(0,33,alias + ' <' + target + '>')
172             l.insertTopLevelItem(0,item)
173
174
175     def update_completions(self, l):
176         l[:] = l + self.aliases.keys()
177
178
179     def create_contact_menu(self, menu, item):
180         label = unicode(item.text(1))
181         if label in self.aliases.keys():
182             addr = unicode(item.text(0))
183             label = unicode(item.text(1))
184             menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
185             menu.addAction(_("Delete alias"), lambda: delete_alias(self, label))
186
187
188     def show_contact_details(self, m):
189         a = self.aliases.get(m)
190         if a:
191             if a[0] in self.authorities.keys():
192                 s = self.authorities.get(a[0])
193             else:
194                 s = "self-signed"
195             msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
196             QMessageBox.information(self.gui, 'Alias', msg, 'OK')
197
198
199     def delete_alias(self, x):
200         if self.gui.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
201             if x in self.aliases:
202                 self.aliases.pop(x)
203                 self.update_history_tab()
204                 self.update_contacts_tab()
205                 self.update_completions()