1"""A POP3 client class. 2 3Based on the J. Myers POP3 draft, Jan. 96 4""" 5 6# Author: David Ascher <david_ascher@brown.edu> 7# [heavily stealing from nntplib.py] 8# Updated: Piers Lauder <piers@cs.su.oz.au> [Jul '97] 9# String method conversion and test jig improvements by ESR, February 2001. 10# Added the POP3_SSL class. Methods loosely based on IMAP_SSL. Hector Urtubia <urtubia@mrbook.org> Aug 2003 11 12# Example (see the test function at the end of this file) 13 14# Imports 15 16import re, socket 17 18__all__ = ["POP3","error_proto"] 19 20# Exception raised when an error or invalid response is received: 21 22class error_proto(Exception): pass 23 24# Standard Port 25POP3_PORT = 110 26 27# POP SSL PORT 28POP3_SSL_PORT = 995 29 30# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF) 31CR = '\r' 32LF = '\n' 33CRLF = CR+LF 34 35# maximal line length when calling readline(). This is to prevent 36# reading arbitrary length lines. RFC 1939 limits POP3 line length to 37# 512 characters, including CRLF. We have selected 2048 just to be on 38# the safe side. 39_MAXLINE = 2048 40 41 42class POP3: 43 44 """This class supports both the minimal and optional command sets. 45 Arguments can be strings or integers (where appropriate) 46 (e.g.: retr(1) and retr('1') both work equally well. 47 48 Minimal Command Set: 49 USER name user(name) 50 PASS string pass_(string) 51 STAT stat() 52 LIST [msg] list(msg = None) 53 RETR msg retr(msg) 54 DELE msg dele(msg) 55 NOOP noop() 56 RSET rset() 57 QUIT quit() 58 59 Optional Commands (some servers support these): 60 RPOP name rpop(name) 61 APOP name digest apop(name, digest) 62 TOP msg n top(msg, n) 63 UIDL [msg] uidl(msg = None) 64 65 Raises one exception: 'error_proto'. 66 67 Instantiate with: 68 POP3(hostname, port=110) 69 70 NB: the POP protocol locks the mailbox from user 71 authorization until QUIT, so be sure to get in, suck 72 the messages, and quit, each time you access the 73 mailbox. 74 75 POP is a line-based protocol, which means large mail 76 messages consume lots of python cycles reading them 77 line-by-line. 78 79 If it's available on your mail server, use IMAP4 80 instead, it doesn't suffer from the two problems 81 above. 82 """ 83 84 85 def __init__(self, host, port=POP3_PORT, 86 timeout=socket._GLOBAL_DEFAULT_TIMEOUT): 87 self.host = host 88 self.port = port 89 self.sock = socket.create_connection((host, port), timeout) 90 self.file = self.sock.makefile('rb') 91 self._debugging = 0 92 self.welcome = self._getresp() 93 94 95 def _putline(self, line): 96 if self._debugging > 1: print '*put*', repr(line) 97 self.sock.sendall('%s%s' % (line, CRLF)) 98 99 100 # Internal: send one command to the server (through _putline()) 101 102 def _putcmd(self, line): 103 if self._debugging: print '*cmd*', repr(line) 104 self._putline(line) 105 106 107 # Internal: return one line from the server, stripping CRLF. 108 # This is where all the CPU time of this module is consumed. 109 # Raise error_proto('-ERR EOF') if the connection is closed. 110 111 def _getline(self): 112 line = self.file.readline(_MAXLINE + 1) 113 if len(line) > _MAXLINE: 114 raise error_proto('line too long') 115 if self._debugging > 1: print '*get*', repr(line) 116 if not line: raise error_proto('-ERR EOF') 117 octets = len(line) 118 # server can send any combination of CR & LF 119 # however, 'readline()' returns lines ending in LF 120 # so only possibilities are ...LF, ...CRLF, CR...LF 121 if line[-2:] == CRLF: 122 return line[:-2], octets 123 if line[0] == CR: 124 return line[1:-1], octets 125 return line[:-1], octets 126 127 128 # Internal: get a response from the server. 129 # Raise 'error_proto' if the response doesn't start with '+'. 130 131 def _getresp(self): 132 resp, o = self._getline() 133 if self._debugging > 1: print '*resp*', repr(resp) 134 c = resp[:1] 135 if c != '+': 136 raise error_proto(resp) 137 return resp 138 139 140 # Internal: get a response plus following text from the server. 141 142 def _getlongresp(self): 143 resp = self._getresp() 144 list = []; octets = 0 145 line, o = self._getline() 146 while line != '.': 147 if line[:2] == '..': 148 o = o-1 149 line = line[1:] 150 octets = octets + o 151 list.append(line) 152 line, o = self._getline() 153 return resp, list, octets 154 155 156 # Internal: send a command and get the response 157 158 def _shortcmd(self, line): 159 self._putcmd(line) 160 return self._getresp() 161 162 163 # Internal: send a command and get the response plus following text 164 165 def _longcmd(self, line): 166 self._putcmd(line) 167 return self._getlongresp() 168 169 170 # These can be useful: 171 172 def getwelcome(self): 173 return self.welcome 174 175 176 def set_debuglevel(self, level): 177 self._debugging = level 178 179 180 # Here are all the POP commands: 181 182 def user(self, user): 183 """Send user name, return response 184 185 (should indicate password required). 186 """ 187 return self._shortcmd('USER %s' % user) 188 189 190 def pass_(self, pswd): 191 """Send password, return response 192 193 (response includes message count, mailbox size). 194 195 NB: mailbox is locked by server from here to 'quit()' 196 """ 197 return self._shortcmd('PASS %s' % pswd) 198 199 200 def stat(self): 201 """Get mailbox status. 202 203 Result is tuple of 2 ints (message count, mailbox size) 204 """ 205 retval = self._shortcmd('STAT') 206 rets = retval.split() 207 if self._debugging: print '*stat*', repr(rets) 208 numMessages = int(rets[1]) 209 sizeMessages = int(rets[2]) 210 return (numMessages, sizeMessages) 211 212 213 def list(self, which=None): 214 """Request listing, return result. 215 216 Result without a message number argument is in form 217 ['response', ['mesg_num octets', ...], octets]. 218 219 Result when a message number argument is given is a 220 single response: the "scan listing" for that message. 221 """ 222 if which is not None: 223 return self._shortcmd('LIST %s' % which) 224 return self._longcmd('LIST') 225 226 227 def retr(self, which): 228 """Retrieve whole message number 'which'. 229 230 Result is in form ['response', ['line', ...], octets]. 231 """ 232 return self._longcmd('RETR %s' % which) 233 234 235 def dele(self, which): 236 """Delete message number 'which'. 237 238 Result is 'response'. 239 """ 240 return self._shortcmd('DELE %s' % which) 241 242 243 def noop(self): 244 """Does nothing. 245 246 One supposes the response indicates the server is alive. 247 """ 248 return self._shortcmd('NOOP') 249 250 251 def rset(self): 252 """Unmark all messages marked for deletion.""" 253 return self._shortcmd('RSET') 254 255 256 def quit(self): 257 """Signoff: commit changes on server, unlock mailbox, close connection.""" 258 try: 259 resp = self._shortcmd('QUIT') 260 except error_proto, val: 261 resp = val 262 self.file.close() 263 self.sock.close() 264 del self.file, self.sock 265 return resp 266 267 #__del__ = quit 268 269 270 # optional commands: 271 272 def rpop(self, user): 273 """Not sure what this does.""" 274 return self._shortcmd('RPOP %s' % user) 275 276 277 timestamp = re.compile(br'\+OK.[^<]*(<.*>)') 278 279 def apop(self, user, secret): 280 """Authorisation 281 282 - only possible if server has supplied a timestamp in initial greeting. 283 284 Args: 285 user - mailbox user; 286 secret - secret shared between client and server. 287 288 NB: mailbox is locked by server from here to 'quit()' 289 """ 290 m = self.timestamp.match(self.welcome) 291 if not m: 292 raise error_proto('-ERR APOP not supported by server') 293 import hashlib 294 digest = hashlib.md5(m.group(1)+secret).digest() 295 digest = ''.join(map(lambda x:'%02x'%ord(x), digest)) 296 return self._shortcmd('APOP %s %s' % (user, digest)) 297 298 299 def top(self, which, howmuch): 300 """Retrieve message header of message number 'which' 301 and first 'howmuch' lines of message body. 302 303 Result is in form ['response', ['line', ...], octets]. 304 """ 305 return self._longcmd('TOP %s %s' % (which, howmuch)) 306 307 308 def uidl(self, which=None): 309 """Return message digest (unique id) list. 310 311 If 'which', result contains unique id for that message 312 in the form 'response mesgnum uid', otherwise result is 313 the list ['response', ['mesgnum uid', ...], octets] 314 """ 315 if which is not None: 316 return self._shortcmd('UIDL %s' % which) 317 return self._longcmd('UIDL') 318 319try: 320 import ssl 321except ImportError: 322 pass 323else: 324 325 class POP3_SSL(POP3): 326 """POP3 client class over SSL connection 327 328 Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None) 329 330 hostname - the hostname of the pop3 over ssl server 331 port - port number 332 keyfile - PEM formatted file that contains your private key 333 certfile - PEM formatted certificate chain file 334 335 See the methods of the parent class POP3 for more documentation. 336 """ 337 338 def __init__(self, host, port = POP3_SSL_PORT, keyfile = None, certfile = None): 339 self.host = host 340 self.port = port 341 self.keyfile = keyfile 342 self.certfile = certfile 343 self.buffer = "" 344 msg = "getaddrinfo returns an empty list" 345 self.sock = None 346 for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM): 347 af, socktype, proto, canonname, sa = res 348 try: 349 self.sock = socket.socket(af, socktype, proto) 350 self.sock.connect(sa) 351 except socket.error, msg: 352 if self.sock: 353 self.sock.close() 354 self.sock = None 355 continue 356 break 357 if not self.sock: 358 raise socket.error, msg 359 self.file = self.sock.makefile('rb') 360 self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile) 361 self._debugging = 0 362 self.welcome = self._getresp() 363 364 def _fillBuffer(self): 365 localbuf = self.sslobj.read() 366 if len(localbuf) == 0: 367 raise error_proto('-ERR EOF') 368 self.buffer += localbuf 369 370 def _getline(self): 371 line = "" 372 renewline = re.compile(r'.*?\n') 373 match = renewline.match(self.buffer) 374 while not match: 375 self._fillBuffer() 376 if len(self.buffer) > _MAXLINE: 377 raise error_proto('line too long') 378 match = renewline.match(self.buffer) 379 line = match.group(0) 380 self.buffer = renewline.sub('' ,self.buffer, 1) 381 if self._debugging > 1: print '*get*', repr(line) 382 383 octets = len(line) 384 if line[-2:] == CRLF: 385 return line[:-2], octets 386 if line[0] == CR: 387 return line[1:-1], octets 388 return line[:-1], octets 389 390 def _putline(self, line): 391 if self._debugging > 1: print '*put*', repr(line) 392 line += CRLF 393 bytes = len(line) 394 while bytes > 0: 395 sent = self.sslobj.write(line) 396 if sent == bytes: 397 break # avoid copy 398 line = line[sent:] 399 bytes = bytes - sent 400 401 def quit(self): 402 """Signoff: commit changes on server, unlock mailbox, close connection.""" 403 try: 404 resp = self._shortcmd('QUIT') 405 except error_proto, val: 406 resp = val 407 self.sock.close() 408 del self.sslobj, self.sock 409 return resp 410 411 __all__.append("POP3_SSL") 412 413if __name__ == "__main__": 414 import sys 415 a = POP3(sys.argv[1]) 416 print a.getwelcome() 417 a.user(sys.argv[2]) 418 a.pass_(sys.argv[3]) 419 a.list() 420 (numMsgs, totalSize) = a.stat() 421 for i in range(1, numMsgs + 1): 422 (header, msg, octets) = a.retr(i) 423 print "Message %d:" % i 424 for line in msg: 425 print ' ' + line 426 print '-----------------------' 427 a.quit() 428