[PATCH 2/2] Adapt the code to work with the new imaplib2
Sebastian Spaeth
Sebastian at SSpaeth.de
Sun Aug 14 12:09:51 BST 2011
imaplib renamed self.sslobj to self.sock and our overriden open()
functions were failing for that reason when updating imaplib2 to
v2.28. It turns out that all of our custom initializations are being
done by stock imaplib2 now anyway, so there is no need to override them
anymore. This lets us simplify the code we have to worry about.
Move the verifycert() function to the imapserver.py file, it is now a
callback function that is being handed to imaplib from there, so it
makes sense to also define it in our imapserver function...
(this also lets us easily make use of the verifycert function in the
starttls case in the future)
TODO: we need to examine if and why we still need to override the
select() function, it is the only reason why we still wrap the IMAP4
classes.
Fix one typo in upstream imaplib2, a patch has been sent upstream too.
Signed-off-by: Sebastian Spaeth <Sebastian at SSpaeth.de>
---
Changelog.draft.rst | 2 +
offlineimap/imaplib2.py | 2 +-
offlineimap/imaplibutil.py | 156 ++------------------------------------------
offlineimap/imapserver.py | 51 ++++++++++++++-
4 files changed, 56 insertions(+), 155 deletions(-)
diff --git a/Changelog.draft.rst b/Changelog.draft.rst
index 6b5b18e..3b2243e 100644
--- a/Changelog.draft.rst
+++ b/Changelog.draft.rst
@@ -23,6 +23,8 @@ Changes
* Refactor our IMAPServer class. Background work without user-visible
changes.
+* Updated bundled imaplib2 to version 2.28
+
Bug Fixes
---------
diff --git a/offlineimap/imaplib2.py b/offlineimap/imaplib2.py
index 64f4353..8e6e4a0 100644
--- a/offlineimap/imaplib2.py
+++ b/offlineimap/imaplib2.py
@@ -471,7 +471,7 @@ class IMAP4(object):
if self.cert_verify_cb is not None:
cert_err = self.cert_verify_cb(self.sock.getpeercert(), self.host)
- if cert__err:
+ if cert_err:
raise ssl_exc(cert_err)
self.read_fd = self.sock.fileno()
diff --git a/offlineimap/imaplibutil.py b/offlineimap/imaplibutil.py
index 6492086..f7d103e 100644
--- a/offlineimap/imaplibutil.py
+++ b/offlineimap/imaplibutil.py
@@ -127,162 +127,16 @@ def new_mesg(self, s, tn=None, secs=None):
tm = time.strftime('%M:%S', time.localtime(secs))
getglobalui().debug('imap', ' %s.%02d %s %s' % (tm, (secs*100)%100, tn, s))
+
class WrappedIMAP4_SSL(UsefulIMAPMixIn, IMAP4_SSL):
- """Provides an improved version of the standard IMAP4_SSL
-
- It provides a better readline() implementation as impaplib's
- readline() is extremly inefficient. It can also connect to IPv6
- addresses."""
- def __init__(self, *args, **kwargs):
- self._readbuf = ''
- self._cacertfile = kwargs.get('cacertfile', None)
- if kwargs.has_key('cacertfile'):
- del kwargs['cacertfile']
- IMAP4_SSL.__init__(self, *args, **kwargs)
-
- def open(self, host=None, port=None):
- """Do whatever IMAP4_SSL would do in open, but call sslwrap
- with cert verification"""
- #IMAP4_SSL.open(self, host, port) uses the below 2 lines:
- self.host = host
- self.port = port
-
- #rather than just self.sock = socket.create_connection((host, port))
- #we use the below part to be able to connect to ipv6 addresses too
- #This connects to the first ip found ipv4/ipv6
- #Added by Adriaan Peeters <apeeters at lashout.net> based on a socket
- #example from the python documentation:
- #http://www.python.org/doc/lib/socket-example.html
- res = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
- socket.SOCK_STREAM)
- # Try all the addresses in turn until we connect()
- last_error = 0
- for remote in res:
- af, socktype, proto, canonname, sa = remote
- self.sock = socket.socket(af, socktype, proto)
- last_error = self.sock.connect_ex(sa)
- if last_error == 0:
- break
- else:
- self.sock.close()
- if last_error != 0:
- raise Exception("can't open socket; error: %s"\
- % socket.error(last_error))
-
- # Allow sending of keep-alive message seems to prevent some servers
- # from closing SSL on us leading to deadlocks
- self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
-
- #connected to socket, now wrap it in SSL
- try:
- if self._cacertfile:
- requirecert = ssl.CERT_REQUIRED
- else:
- requirecert = ssl.CERT_NONE
-
- self.sslobj = ssl.wrap_socket(self.sock, self.keyfile,
- self.certfile,
- ca_certs = self._cacertfile,
- cert_reqs = requirecert)
- except NameError:
- #Python 2.4/2.5 don't have the ssl module, we need to
- #socket.ssl() here but that doesn't allow cert
- #verification!!!
- if self._cacertfile:
- #user configured a CA certificate, but python 2.4/5 doesn't
- #allow us to easily check it. So bail out here.
- raise Exception("SSL CA Certificates cannot be checked with python <=2.6. Abort")
- self.sslobj = socket.ssl(self.sock, self.keyfile,
- self.certfile)
+ """Improved version of imaplib.IMAP4_SSL overriding select()"""
+ pass
- else:
- #ssl.wrap_socket worked and cert is verified (if configured),
- #now check that hostnames also match if we have a CA cert.
- if self._cacertfile:
- error = self._verifycert(self.sslobj.getpeercert(), host)
- if error:
- raise ssl.SSLError("SSL Certificate host name mismatch: %s" % error)
-
- # imaplib2 uses this to poll()
- self.read_fd = self.sock.fileno()
-
- #TODO: Done for now. We should implement a mutt-like behavior
- #that offers the users to accept a certificate (presenting a
- #fingerprint of it) (get via self.sslobj.getpeercert()), and
- #save that, and compare on future connects, rather than having
- #to trust what the CA certs say.
-
- def _verifycert(self, cert, hostname):
- '''Verify that cert (in socket.getpeercert() format) matches hostname.
- CRLs are not handled.
-
- Returns error message if any problems are found and None on success.
- '''
- if not cert:
- return ('no certificate received')
- dnsname = hostname.lower()
- certnames = []
-
- # cert expired?
- notafter = cert.get('notAfter')
- if notafter:
- if time.time() >= ssl.cert_time_to_seconds(notafter):
- return ('server certificate error: certificate expired %s'
- ) % notafter
-
- # First read commonName
- for s in cert.get('subject', []):
- key, value = s[0]
- if key == 'commonName':
- certnames.append(value.lower())
- if len(certnames) == 0:
- return ('no commonName found in certificate')
-
- # Then read subjectAltName
- for key, value in cert.get('subjectAltName', []):
- if key == 'DNS':
- certnames.append(value.lower())
-
- # And finally try to match hostname with one of these names
- for certname in certnames:
- if (certname == dnsname or
- '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
- return None
-
- return ('no matching domain name found in certificate')
class WrappedIMAP4(UsefulIMAPMixIn, IMAP4):
- """Improved version of imaplib.IMAP4 that can also connect to IPv6"""
+ """Improved version of imaplib.IMAP4 overriding select()"""
+ pass
- def open(self, host = '', port = IMAP4_PORT):
- """Setup connection to remote server on "host:port"
- (default: localhost:standard IMAP4 port).
- """
- #self.host and self.port are needed by the parent IMAP4 class
- self.host = host
- self.port = port
- res = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
- socket.SOCK_STREAM)
-
- # Try each address returned by getaddrinfo in turn until we
- # manage to connect to one.
- # Try all the addresses in turn until we connect()
- last_error = 0
- for remote in res:
- af, socktype, proto, canonname, sa = remote
- self.sock = socket.socket(af, socktype, proto)
- last_error = self.sock.connect_ex(sa)
- if last_error == 0:
- break
- else:
- self.sock.close()
- if last_error != 0:
- raise Exception("can't open socket; error: %s"\
- % socket.error(last_error))
- self.file = self.sock.makefile('rb')
-
- # imaplib2 uses this to poll()
- self.read_fd = self.sock.fileno()
mustquote = re.compile(r"[^\w!#$%&'+,.:;<=>?^`|~-]")
diff --git a/offlineimap/imapserver.py b/offlineimap/imapserver.py
index 6566ad5..a91cddb 100644
--- a/offlineimap/imapserver.py
+++ b/offlineimap/imapserver.py
@@ -24,10 +24,11 @@ import offlineimap.accounts
import hmac
import socket
import base64
+import time
from socket import gaierror
try:
- from ssl import SSLError
+ from ssl import SSLError, cert_time_to_seconds
except ImportError:
# Protect against python<2.6, use dummy and won't get SSL errors.
SSLError = None
@@ -204,9 +205,12 @@ class IMAPServer:
self.ui.connecting(self.hostname, self.port)
imapobj = imaplibutil.WrappedIMAP4_SSL(self.hostname,
self.port,
- self.sslclientkey, self.sslclientcert,
+ self.sslclientkey,
+ self.sslclientcert,
+ self.sslcacertfile,
+ self.verifycert,
timeout=socket.getdefaulttimeout(),
- cacertfile = self.sslcacertfile)
+ )
else:
self.ui.connecting(self.hostname, self.port)
imapobj = imaplibutil.WrappedIMAP4(self.hostname, self.port,
@@ -405,6 +409,47 @@ class IMAPServer:
self.ui.debug('imap', 'keepalive: bottom of loop')
+
+ def verifycert(self, cert, hostname):
+ '''Verify that cert (in socket.getpeercert() format) matches hostname.
+ CRLs are not handled.
+
+ Returns error message if any problems are found and None on success.
+ '''
+ errstr = "CA Cert verifying failed: "
+ if not cert:
+ return ('%s no certificate received' % errstr)
+ dnsname = hostname.lower()
+ certnames = []
+
+ # cert expired?
+ notafter = cert.get('notAfter')
+ if notafter:
+ if time.time() >= cert_time_to_seconds(notafter):
+ return '%s certificate expired %s' % (errstr, notafter)
+
+ # First read commonName
+ for s in cert.get('subject', []):
+ key, value = s[0]
+ if key == 'commonName':
+ certnames.append(value.lower())
+ if len(certnames) == 0:
+ return ('%s no commonName found in certificate' % errstr)
+
+ # Then read subjectAltName
+ for key, value in cert.get('subjectAltName', []):
+ if key == 'DNS':
+ certnames.append(value.lower())
+
+ # And finally try to match hostname with one of these names
+ for certname in certnames:
+ if (certname == dnsname or
+ '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
+ return None
+
+ return ('%s no matching domain name found in certificate' % errstr)
+
+
class IdleThread(object):
def __init__(self, parent, folder=None):
self.parent = parent
--
1.7.4.1
More information about the OfflineIMAP-project
mailing list