Package qpid :: Package messaging :: Module transports
[frames] | no frames]

Source Code for Module qpid.messaging.transports

  1  # 
  2  # Licensed to the Apache Software Foundation (ASF) under one 
  3  # or more contributor license agreements.  See the NOTICE file 
  4  # distributed with this work for additional information 
  5  # regarding copyright ownership.  The ASF licenses this file 
  6  # to you under the Apache License, Version 2.0 (the 
  7  # "License"); you may not use this file except in compliance 
  8  # with the License.  You may obtain a copy of the License at 
  9  # 
 10  #   http://www.apache.org/licenses/LICENSE-2.0 
 11  # 
 12  # Unless required by applicable law or agreed to in writing, 
 13  # software distributed under the License is distributed on an 
 14  # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
 15  # KIND, either express or implied.  See the License for the 
 16  # specific language governing permissions and limitations 
 17  # under the License. 
 18  # 
 19   
 20  import socket 
 21  from qpid.util import connect 
 22   
 23  TRANSPORTS = {} 
 24   
25 -class SocketTransport:
26
27 - def __init__(self, conn, host, port):
28 self.socket = connect(host, port) 29 if conn.tcp_nodelay: 30 self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
31
32 - def fileno(self):
33 return self.socket.fileno()
34
35 -class tcp(SocketTransport):
36
37 - def reading(self, reading):
38 return reading
39
40 - def writing(self, writing):
41 return writing
42
43 - def send(self, bytes):
44 return self.socket.send(bytes)
45
46 - def recv(self, n):
47 return self.socket.recv(n)
48
49 - def close(self):
50 self.socket.close()
51 52 TRANSPORTS["tcp"] = tcp 53 54 try: 55 from ssl import wrap_socket, SSLError, SSL_ERROR_WANT_READ, \ 56 SSL_ERROR_WANT_WRITE, CERT_REQUIRED, CERT_NONE 57 except ImportError: 58 59 ## try the older python SSL api: 60 from socket import ssl 61
62 - class old_ssl(SocketTransport):
63 - def __init__(self, conn, host, port):
64 SocketTransport.__init__(self, conn, host, port) 65 # Bug (QPID-4337): this is the "old" version of python SSL. 66 # The private key is required. If a certificate is given, but no 67 # keyfile, assume the key is contained in the certificate 68 ssl_keyfile = conn.ssl_keyfile 69 ssl_certfile = conn.ssl_certfile 70 if ssl_certfile and not ssl_keyfile: 71 ssl_keyfile = ssl_certfile 72 73 # this version of SSL does NOT perform certificate validation. If the 74 # connection has been configured with CA certs (via ssl_trustfile), then 75 # the application expects the certificate to be validated against the 76 # supplied CA certs. Since this version cannot validate, the peer cannot 77 # be trusted. 78 if conn.ssl_trustfile: 79 raise socket.error("This version of Python does not support verification of the peer's certificate.") 80 81 self.ssl = ssl(self.socket, keyfile=ssl_keyfile, certfile=ssl_certfile) 82 self.socket.setblocking(1)
83
84 - def reading(self, reading):
85 return reading
86
87 - def writing(self, writing):
88 return writing
89
90 - def recv(self, n):
91 return self.ssl.read(n)
92
93 - def send(self, s):
94 return self.ssl.write(s)
95
96 - def close(self):
97 self.socket.close()
98 99 TRANSPORTS["ssl"] = old_ssl 100 TRANSPORTS["tcp+tls"] = old_ssl 101 102 else:
103 - class tls(SocketTransport):
104
105 - def __init__(self, conn, host, port):
106 SocketTransport.__init__(self, conn, host, port) 107 if conn.ssl_trustfile: 108 validate = CERT_REQUIRED 109 else: 110 validate = CERT_NONE 111 112 # if user manually set flag to false then require cert 113 actual = getattr(conn, "_ssl_skip_hostname_check_actual", None) 114 if actual is not None and conn.ssl_skip_hostname_check is False: 115 validate = CERT_REQUIRED 116 117 self.tls = wrap_socket(self.socket, keyfile=conn.ssl_keyfile, 118 certfile=conn.ssl_certfile, 119 ca_certs=conn.ssl_trustfile, 120 cert_reqs=validate) 121 122 if validate == CERT_REQUIRED and not conn.ssl_skip_hostname_check: 123 verify_hostname(self.tls.getpeercert(), host) 124 125 self.socket.setblocking(0) 126 self.state = None 127 # See qpid-4872: need to store the parameters last passed to tls.write() 128 # in case the calls fail with an SSL_ERROR_WANT_* error and we have to 129 # retry the call with the same parameters. 130 self.write_retry = None # buffer passed to last call of tls.write()
131
132 - def reading(self, reading):
133 if self.state is None: 134 return reading 135 else: 136 return self.state == SSL_ERROR_WANT_READ
137
138 - def writing(self, writing):
139 if self.state is None: 140 return writing 141 else: 142 return self.state == SSL_ERROR_WANT_WRITE
143
144 - def send(self, bytes):
145 if self.write_retry is None: 146 self.write_retry = bytes 147 self._clear_state() 148 try: 149 n = self.tls.write( self.write_retry ) 150 self.write_retry = None 151 return n 152 except SSLError, e: 153 if self._update_state(e.args[0]): 154 # will retry on next invokation 155 return 0 156 self.write_retry = None 157 raise 158 except: 159 self.write_retry = None 160 raise
161
162 - def recv(self, n):
163 self._clear_state() 164 try: 165 return self.tls.read(n) 166 except SSLError, e: 167 if self._update_state(e.args[0]): 168 # will retry later: 169 return None 170 else: 171 raise
172
173 - def _clear_state(self):
174 self.state = None
175
176 - def _update_state(self, code):
177 if code in (SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE): 178 self.state = code 179 return True 180 else: 181 return False
182
183 - def close(self):
184 self.socket.setblocking(1) 185 # this closes the underlying socket 186 self.tls.close()
187
188 - def verify_hostname(peer_certificate, hostname):
189 match_found = False 190 peer_names = [] 191 if peer_certificate: 192 if 'subjectAltName' in peer_certificate: 193 for san in peer_certificate['subjectAltName']: 194 if san[0] == 'DNS': 195 peer_names.append(san[1].lower()) 196 if 'subject' in peer_certificate: 197 for sub in peer_certificate['subject']: 198 while isinstance(sub, tuple) and isinstance(sub[0], tuple): 199 sub = sub[0] # why the extra level of indirection??? 200 if sub[0] == 'commonName': 201 peer_names.append(sub[1].lower()) 202 for pattern in peer_names: 203 if _match_dns_pattern(hostname.lower(), pattern): 204 match_found = True 205 break 206 if not match_found: 207 raise SSLError("Connection hostname '%s' does not match names from peer certificate: %s" % (hostname, peer_names))
208
209 - def _match_dns_pattern( hostname, pattern ):
210 """ For checking the hostnames provided by the peer's certificate 211 """ 212 if pattern.find("*") == -1: 213 return hostname == pattern 214 215 # DNS wildcarded pattern - see RFC2818 216 h_labels = hostname.split(".") 217 p_labels = pattern.split(".") 218 219 while h_labels and p_labels: 220 if p_labels[0].find("*") == -1: 221 if p_labels[0] != h_labels[0]: 222 return False 223 else: 224 p = p_labels[0].split("*") 225 if not h_labels[0].startswith(p[0]): 226 return False 227 if not h_labels[0].endswith(p[1]): 228 return False 229 h_labels.pop(0) 230 p_labels.pop(0) 231 232 return not h_labels and not p_labels
233 234 235 TRANSPORTS["ssl"] = tls 236 TRANSPORTS["tcp+tls"] = tls 237