New port numbers
[electrum-nvc.git] / lib / interface.py
index 91bcf91..4424a0c 100644 (file)
@@ -27,36 +27,12 @@ from version import ELECTRUM_VERSION, PROTOCOL_VERSION
 from util import print_error, print_msg
 from simple_config import SimpleConfig
 
+import x509
 
 DEFAULT_TIMEOUT = 5
 proxy_modes = ['socks4', 'socks5', 'http']
 
 
-def check_cert(host, cert):
-    from OpenSSL import crypto as c
-    _cert = c.load_certificate(c.FILETYPE_PEM, cert)
-
-    m = "host: %s\n"%host
-    m += "has_expired: %s\n"% _cert.has_expired()
-    m += "pubkey: %s bits\n" % _cert.get_pubkey().bits()
-    m += "serial number: %s\n"% _cert.get_serial_number() 
-    #m += "issuer: %s\n"% _cert.get_issuer()
-    #m += "algo: %s\n"% _cert.get_signature_algorithm() 
-    m += "version: %s\n"% _cert.get_version()
-    print_msg(m)
-
-
-
-def check_certificates():
-    config = SimpleConfig()
-    mydir = os.path.join(config.path, "certs")
-    certs = os.listdir(mydir)
-    for c in certs:
-        print c
-        p = os.path.join(mydir,c)
-        with open(p) as f:
-            cert = f.read()
-        check_cert(c, cert)
     
 
 def cert_verify_hostname(s):
@@ -73,14 +49,11 @@ def cert_verify_hostname(s):
 class Interface(threading.Thread):
 
 
-    def __init__(self, config=None):
-
-        if config is None:
-            config = SimpleConfig()
+    def __init__(self, server, config = None):
 
         threading.Thread.__init__(self)
         self.daemon = True
-        self.config = config
+        self.config = config if config is not None else SimpleConfig()
         self.connect_event = threading.Event()
 
         self.subscriptions = {}
@@ -91,27 +64,31 @@ class Interface(threading.Thread):
         self.is_connected = False
         self.poll_interval = 1
 
+        self.debug = False # dump network messages. can be changed at runtime using the console
+
         #json
         self.message_id = 0
         self.unanswered_requests = {}
-        self.pending_transactions_for_notifications= []
 
         # parse server
-        s = config.get('server')
-        host, port, protocol = s.split(':')
-        port = int(port)
-            
+        self.server = server
+        try:
+            host, port, protocol = self.server.split(':')
+            port = int(port)
+        except Exception:
+            self.server = None
+            return
+
         if protocol not in 'ghst':
-            raise BaseException('Unknown protocol: %s'%protocol)
+            raise Exception('Unknown protocol: %s'%protocol)
 
         self.host = host
         self.port = port
         self.protocol = protocol
         self.use_ssl = ( protocol in 'sg' )
-        self.proxy = self.parse_proxy_options(config.get('proxy'))
+        self.proxy = self.parse_proxy_options(self.config.get('proxy'))
         if self.proxy:
             self.proxy_mode = proxy_modes.index(self.proxy["mode"]) + 1
-        self.server = host + ':%d:%s'%(port, protocol)
 
 
 
@@ -120,7 +97,8 @@ class Interface(threading.Thread):
     def queue_json_response(self, c):
 
         # uncomment to debug
-        # print_error( "<--",c )
+        if self.debug:
+            print_error( "<--",c )
 
         msg_id = c.get('id')
         error = c.get('error')
@@ -181,7 +159,7 @@ class Interface(threading.Thread):
         self.connection_msg = ('https' if self.use_ssl else 'http') + '://%s:%d'%( self.host, self.port )
         try:
             self.poll()
-        except:
+        except Exception:
             print_error("http init session failed")
             self.is_connected = False
             return
@@ -203,7 +181,7 @@ class Interface(threading.Thread):
                 break
             except socket.error:
                 break
-            except:
+            except Exception:
                 traceback.print_exc(file=sys.stdout)
                 break
             
@@ -211,7 +189,7 @@ class Interface(threading.Thread):
 
                 
     def poll(self):
-        self.send([])
+        self.send([], None)
 
 
     def send_http(self, messages, callback):
@@ -229,11 +207,13 @@ class Interface(threading.Thread):
         t1 = time.time()
 
         data = []
+        ids = []
         for m in messages:
             method, params = m
             if type(params) != type([]): params = [params]
             data.append( { 'method':method, 'id':self.message_id, 'params':params } )
             self.unanswered_requests[self.message_id] = method, params, callback
+            ids.append(self.message_id)
             self.message_id += 1
 
         if data:
@@ -250,7 +230,7 @@ class Interface(threading.Thread):
         try:
             req = urllib2.Request(self.connection_msg, data_json, headers)
             response_stream = urllib2.urlopen(req, timeout=DEFAULT_TIMEOUT)
-        except:
+        except Exception:
             return
 
         for index, cookie in enumerate(cj):
@@ -276,6 +256,7 @@ class Interface(threading.Thread):
 
         self.rtime = time.time() - t1
         self.is_connected = True
+        return ids
 
 
 
@@ -294,23 +275,42 @@ class Interface(threading.Thread):
             socket.getaddrinfo = getaddrinfo
 
         if self.use_ssl:
-            cert_path = os.path.join( self.config.get('path'), 'certs', self.host)
+            cert_path = os.path.join( self.config.path, 'certs', self.host)
 
             if not os.path.exists(cert_path):
                 is_new = True
                 # get server certificate.
                 # Do not use ssl.get_server_certificate because it does not work with proxy
-                s = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
                 try:
-                    s.connect((self.host, self.port))
-                except:
-                    # print_error("failed to connect", self.host, self.port)
+                    l = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM)
+                except socket.gaierror:
+                    print_error("error: cannot resolve", self.host)
+                    return
+
+                for res in l:
+                    try:
+                        s = socket.socket( res[0], socket.SOCK_STREAM )
+                        s.connect(res[4])
+                    except:
+                        s = None
+                        continue
+
+                    try:
+                        s = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_SSLv3, cert_reqs=ssl.CERT_NONE, ca_certs=None)
+                    except ssl.SSLError, e:
+                        print_error("SSL error retrieving SSL certificate:", self.host, e)
+                        s = None
+
+                    break
+
+                if s is None:
                     return
 
-                s = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_SSLv3, cert_reqs=ssl.CERT_NONE, ca_certs=None)
                 dercert = s.getpeercert(True)
                 s.close()
                 cert = ssl.DER_cert_to_PEM_cert(dercert)
+                # workaround android bug
+                cert = re.sub("([^\n])-----END CERTIFICATE-----","\\1\n-----END CERTIFICATE-----",cert)
                 temporary_path = cert_path + '.temp'
                 with open(temporary_path,"w") as f:
                     f.write(cert)
@@ -318,14 +318,24 @@ class Interface(threading.Thread):
             else:
                 is_new = False
 
+        try:
+            addrinfo = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM)
+        except socket.gaierror:
+            print_error("error: cannot resolve", self.host)
+            return
 
-        s = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
-        s.settimeout(2)
-        s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+        for res in addrinfo:
+            try:
+                s = socket.socket( res[0], socket.SOCK_STREAM )
+                s.settimeout(2)
+                s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+                s.connect(res[4])
+            except:
+                s = None
+                continue
+            break
 
-        try:
-            s.connect(( self.host.encode('ascii'), int(self.port)))
-        except:
+        if s is None:
             print_error("failed to connect", self.host, self.port)
             return
 
@@ -338,21 +348,33 @@ class Interface(threading.Thread):
                                     do_handshake_on_connect=True)
             except ssl.SSLError, e:
                 print_error("SSL error:", self.host, e)
+                if e.errno != 1:
+                    return
                 if is_new:
-                    os.rename(temporary_path, cert_path + '.rej')
+                    rej = cert_path + '.rej'
+                    if os.path.exists(rej):
+                        os.unlink(rej)
+                    os.rename(temporary_path, rej)
                 else:
-                    from OpenSSL import crypto as c
                     with open(cert_path) as f:
                         cert = f.read()
-                    _cert = c.load_certificate(c.FILETYPE_PEM, cert)
-                    if _cert.has_expired():
+                    try:
+                        x = x509.X509()
+                        x.parse(cert)
+                        x.slow_parse()
+                    except:
+                        traceback.print_exc(file=sys.stdout)
+                        print_error("wrong certificate", self.host)
+                        return
+                    try:
+                        x.check_date()
+                    except:
                         print_error("certificate has expired:", cert_path)
                         os.unlink(cert_path)
-                    else:
-                        print_msg("wrong certificate", self.host)
-
+                        return
+                    print_error("wrong certificate", self.host)
                 return
-            except:
+            except Exception:
                 print_error("wrap_socket failed", self.host)
                 traceback.print_exc(file=sys.stdout)
                 return
@@ -361,7 +383,6 @@ class Interface(threading.Thread):
                 print_error("saving certificate for", self.host)
                 os.rename(temporary_path, cert_path)
 
-
         s.settimeout(60)
         self.s = s
         self.is_connected = True
@@ -381,7 +402,9 @@ class Interface(threading.Thread):
                 except ssl.SSLError:
                     timeout = True
                 except socket.error, err:
-                    if err.errno in [11, 10035]:
+                    if err.errno == 60:
+                        timeout = True
+                    elif err.errno in [11, 10035]:
                         print_error("socket errno", err.errno)
                         time.sleep(0.1)
                         continue
@@ -407,7 +430,7 @@ class Interface(threading.Thread):
                     c = json.loads(c)
                     self.queue_json_response(c)
 
-        except:
+        except Exception:
             traceback.print_exc(file=sys.stdout)
 
         self.is_connected = False
@@ -422,8 +445,8 @@ class Interface(threading.Thread):
             request = json.dumps( { 'id':self.message_id, 'method':method, 'params':params } )
             self.unanswered_requests[self.message_id] = method, params, callback
             ids.append(self.message_id)
-            # uncomment to debug
-            # print "-->", request
+            if self.debug:
+                print "-->", request
             self.message_id += 1
             out += request + '\n'
         while out:
@@ -480,6 +503,7 @@ class Interface(threading.Thread):
                         self.subscriptions[callback].append(message)
 
         if not self.is_connected: 
+            print_error("interface: trying to send while not connected")
             return
 
         if self.protocol in 'st':
@@ -518,13 +542,38 @@ class Interface(threading.Thread):
             self.s.shutdown(socket.SHUT_RDWR)
             self.s.close()
 
+        self.is_connected = False
+
 
     def is_up_to_date(self):
         return self.unanswered_requests == {}
 
 
+
+    def start(self, queue = None, wait = False):
+        if not self.server:
+            return
+        self.queue = queue if queue else Queue.Queue()
+        threading.Thread.start(self)
+        if wait:
+            self.connect_event.wait()
+
+
+    def run(self):
+        self.start_interface()
+        if self.is_connected:
+            self.send([('server.version', [ELECTRUM_VERSION, PROTOCOL_VERSION])], self.on_version)
+            self.change_status()
+            self.run_tcp() if self.protocol in 'st' else self.run_http()
+        self.change_status()
+        
+
+    def change_status(self):
+        #print "change status", self.server, self.is_connected
+        self.queue.put(self)
+
+
     def synchronous_get(self, requests, timeout=100000000):
-        # todo: use generators, unanswered_requests should be a list of arrays...
         queue = Queue.Queue()
         ids = self.send(requests, lambda i,r: queue.put(r))
         id2 = ids[:]
@@ -541,26 +590,38 @@ class Interface(threading.Thread):
         return out
 
 
-    def start(self, queue):
-        self.queue = queue
-        threading.Thread.start(self)
 
 
-    def run(self):
-        self.start_interface()
-        if self.is_connected:
-            self.send([('server.version', [ELECTRUM_VERSION, PROTOCOL_VERSION])], self.on_version)
-            self.change_status()
-            self.run_tcp() if self.protocol in 'st' else self.run_http()
-        self.change_status()
-        
+def check_cert(host, cert):
+    try:
+        x = x509.X509()
+        x.parse(cert)
+        x.slow_parse()
+    except:
+        traceback.print_exc(file=sys.stdout)
+        return
 
-    def change_status(self):
-        #print "change status", self.server, self.is_connected
-        self.queue.put(self)
+    try:
+        x.check_date()
+        expired = False
+    except:
+        expired = True
 
+    m = "host: %s\n"%host
+    m += "has_expired: %s\n"% expired
+    print_msg(m)
 
 
-if __name__ == "__main__":
+def test_certificates():
+    config = SimpleConfig()
+    mydir = os.path.join(config.path, "certs")
+    certs = os.listdir(mydir)
+    for c in certs:
+        print c
+        p = os.path.join(mydir,c)
+        with open(p) as f:
+            cert = f.read()
+        check_cert(c, cert)
 
-    check_certificates()
+if __name__ == "__main__":
+    test_certificates()