#!/usr/bin/env python2.3
'''Provide an SSL-capable POP3 class.
'''
__all__ = [
'POP3_ssl_port',
'sslsocket',
'POP3SSL',
]
import socket
from poplib import POP3, CR, LF, CRLF, error_proto
from getmailcore.exceptions import *
import getmailcore.logging
log = getmailcore.logging.Logger()
POP3_ssl_port = 995
class sslsocket(object):
'''The Python poplib.POP3() class mixes socket-like .sendall() and
file-like .readline() for communications. That would be okay, except that
the new socket.ssl objects provide only read() and write(), so they
don't act like a socket /or/ like a file. Argh.
This class takes a standard, connected socket.socket() object, sets it
to blocking mode (required for socket.ssl() to work correctly, though
apparently not documented), wraps .write() for .sendall() and implements
.readline().
The modified POP3 class below can then use this to provide POP3-over-SSL.
Thanks to Frank Benkstein for the inspiration.
'''
def __init__(self, sock, keyfile=None, certfile=None):
log.trace()
self.sock = sock
self.sock.setblocking(1)
if keyfile and certfile:
self.ssl = socket.ssl(self.sock, keyfile, certfile)
else:
self.ssl = socket.ssl(self.sock)
self.buf = ''
self.bufsize = 128
def _fillbuf(self):
'''Fill an internal buffer for .readline() to use.
'''
log.trace()
want = self.bufsize - len(self.buf)
log.trace('want %i bytes\n' % want)
if want <= 0:
return
s = self.ssl.read(want)
got = len(s)
log.trace('got %i bytes\n' % got)
self.buf += s
def close(self):
self.sock.close()
self.ssl = None
# self.sock.sendall
def sendall(self, s):
# Maybe only set blocking around this call?
self.ssl.write(s)
# self.file.readline
def readline(self):
'''Simple hack to implement .readline() on a non-file object that
only supports .read().
'''
log.trace()
line = ''
try:
if not self.buf:
self._fillbuf()
log.trace('checking self.buf\n')
if self.buf:
log.trace('self.buf = "%r", len %i\n'
% (self.buf, len(self.buf)))
while True:
log.trace('looking for EOL\n')
i = self.buf.find('\n')
if i != -1:
log.trace('EOL found at %d\n' % i)
line += self.buf[:i + 1]
self.buf = self.buf[i + 1:]
break
# else
log.trace('EOL not found, trying to fill self.buf\n')
line += self.buf
self.buf = ''
self._fillbuf()
if not self.buf:
log.trace('nothing read, exiting\n')
break
log.trace('end of loop\n')
log.trace('returning line "%r"\n' % line)
return line
except (socket.sslerror, socket.error), o:
raise getmailOperationError(
'socket/ssl error while reading from server (%s)' % o)
class POP3SSL(POP3):
'''Thin subclass to add SSL functionality to the built-in POP3 class.
Note that Python's socket module does not do certificate verification
for SSL connections.
This gets rid of the .file attribute from os.makefile(rawsock) and relies on
sslsocket() above to provide .readline() instead.
'''
def __init__(self, host, port=POP3_ssl_port, keyfile=None, certfile=None):
if not ((certfile and keyfile) or (keyfile == certfile == None)):
raise getmailConfigurationError('certfile requires keyfile')
self.host = host
self.port = port
msg = "getaddrinfo returns an empty list"
self.rawsock = None
self.sock = None
for res in socket.getaddrinfo(self.host, self.port, 0,
socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
try:
self.rawsock = socket.socket(af, socktype, proto)
self.rawsock.connect(sa)
if certfile and keyfile:
self.sock = sslsocket(self.rawsock, keyfile, certfile)
else:
self.sock = sslsocket(self.rawsock)
except socket.error, msg:
if self.rawsock:
self.rawsock.close()
self.rawsock = None
continue
break
if not self.sock:
raise socket.error, msg
self._debugging = 0
self.welcome = self._getresp()
# Internal: return one line from the server, stripping CRLF.
# This is where all the CPU time of this module is consumed.
# Raise error_proto('-ERR EOF') if the connection is closed.
def _getline(self):
line = self.sock.readline()
if self._debugging > 1:
print '*get*', `line`
if not line:
raise error_proto('-ERR EOF')
octets = len(line)
# server can send any combination of CR & LF
# however, 'readline()' returns lines ending in LF
# so only possibilities are ...LF, ...CRLF, CR...LF
if line[-2:] == CRLF:
return line[:-2], octets
if line[0] == CR:
return line[1:-1], octets
return line[:-1], octets
def quit(self):
"""Signoff: commit changes on server, unlock mailbox, close connection.
"""
try:
resp = self._shortcmd('QUIT')
except (error_proto, socket.error), val:
resp = val
self.sock.close()
del self.sock
return resp
syntax highlighted by Code2HTML, v. 0.9.1