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 errno 17import re 18import socket 19 20try: 21 import ssl 22 HAVE_SSL = True 23except ImportError: 24 HAVE_SSL = False 25 26__all__ = ["POP3","error_proto"] 27 28# Exception raised when an error or invalid response is received: 29 30class error_proto(Exception): pass 31 32# Standard Port 33POP3_PORT = 110 34 35# POP SSL PORT 36POP3_SSL_PORT = 995 37 38# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF) 39CR = b'\r' 40LF = b'\n' 41CRLF = CR+LF 42 43# maximal line length when calling readline(). This is to prevent 44# reading arbitrary length lines. RFC 1939 limits POP3 line length to 45# 512 characters, including CRLF. We have selected 2048 just to be on 46# the safe side. 47_MAXLINE = 2048 48 49 50class POP3: 51 52 """This class supports both the minimal and optional command sets. 53 Arguments can be strings or integers (where appropriate) 54 (e.g.: retr(1) and retr('1') both work equally well. 55 56 Minimal Command Set: 57 USER name user(name) 58 PASS string pass_(string) 59 STAT stat() 60 LIST [msg] list(msg = None) 61 RETR msg retr(msg) 62 DELE msg dele(msg) 63 NOOP noop() 64 RSET rset() 65 QUIT quit() 66 67 Optional Commands (some servers support these): 68 RPOP name rpop(name) 69 APOP name digest apop(name, digest) 70 TOP msg n top(msg, n) 71 UIDL [msg] uidl(msg = None) 72 CAPA capa() 73 STLS stls() 74 UTF8 utf8() 75 76 Raises one exception: 'error_proto'. 77 78 Instantiate with: 79 POP3(hostname, port=110) 80 81 NB: the POP protocol locks the mailbox from user 82 authorization until QUIT, so be sure to get in, suck 83 the messages, and quit, each time you access the 84 mailbox. 85 86 POP is a line-based protocol, which means large mail 87 messages consume lots of python cycles reading them 88 line-by-line. 89 90 If it's available on your mail server, use IMAP4 91 instead, it doesn't suffer from the two problems 92 above. 93 """ 94 95 encoding = 'UTF-8' 96 97 def __init__(self, host, port=POP3_PORT, 98 timeout=socket._GLOBAL_DEFAULT_TIMEOUT): 99 self.host = host 100 self.port = port 101 self._tls_established = False 102 self.sock = self._create_socket(timeout) 103 self.file = self.sock.makefile('rb') 104 self._debugging = 0 105 self.welcome = self._getresp() 106 107 def _create_socket(self, timeout): 108 return socket.create_connection((self.host, self.port), timeout) 109 110 def _putline(self, line): 111 if self._debugging > 1: print('*put*', repr(line)) 112 self.sock.sendall(line + CRLF) 113 114 115 # Internal: send one command to the server (through _putline()) 116 117 def _putcmd(self, line): 118 if self._debugging: print('*cmd*', repr(line)) 119 line = bytes(line, self.encoding) 120 self._putline(line) 121 122 123 # Internal: return one line from the server, stripping CRLF. 124 # This is where all the CPU time of this module is consumed. 125 # Raise error_proto('-ERR EOF') if the connection is closed. 126 127 def _getline(self): 128 line = self.file.readline(_MAXLINE + 1) 129 if len(line) > _MAXLINE: 130 raise error_proto('line too long') 131 132 if self._debugging > 1: print('*get*', repr(line)) 133 if not line: raise error_proto('-ERR EOF') 134 octets = len(line) 135 # server can send any combination of CR & LF 136 # however, 'readline()' returns lines ending in LF 137 # so only possibilities are ...LF, ...CRLF, CR...LF 138 if line[-2:] == CRLF: 139 return line[:-2], octets 140 if line[:1] == CR: 141 return line[1:-1], octets 142 return line[:-1], octets 143 144 145 # Internal: get a response from the server. 146 # Raise 'error_proto' if the response doesn't start with '+'. 147 148 def _getresp(self): 149 resp, o = self._getline() 150 if self._debugging > 1: print('*resp*', repr(resp)) 151 if not resp.startswith(b'+'): 152 raise error_proto(resp) 153 return resp 154 155 156 # Internal: get a response plus following text from the server. 157 158 def _getlongresp(self): 159 resp = self._getresp() 160 list = []; octets = 0 161 line, o = self._getline() 162 while line != b'.': 163 if line.startswith(b'..'): 164 o = o-1 165 line = line[1:] 166 octets = octets + o 167 list.append(line) 168 line, o = self._getline() 169 return resp, list, octets 170 171 172 # Internal: send a command and get the response 173 174 def _shortcmd(self, line): 175 self._putcmd(line) 176 return self._getresp() 177 178 179 # Internal: send a command and get the response plus following text 180 181 def _longcmd(self, line): 182 self._putcmd(line) 183 return self._getlongresp() 184 185 186 # These can be useful: 187 188 def getwelcome(self): 189 return self.welcome 190 191 192 def set_debuglevel(self, level): 193 self._debugging = level 194 195 196 # Here are all the POP commands: 197 198 def user(self, user): 199 """Send user name, return response 200 201 (should indicate password required). 202 """ 203 return self._shortcmd('USER %s' % user) 204 205 206 def pass_(self, pswd): 207 """Send password, return response 208 209 (response includes message count, mailbox size). 210 211 NB: mailbox is locked by server from here to 'quit()' 212 """ 213 return self._shortcmd('PASS %s' % pswd) 214 215 216 def stat(self): 217 """Get mailbox status. 218 219 Result is tuple of 2 ints (message count, mailbox size) 220 """ 221 retval = self._shortcmd('STAT') 222 rets = retval.split() 223 if self._debugging: print('*stat*', repr(rets)) 224 numMessages = int(rets[1]) 225 sizeMessages = int(rets[2]) 226 return (numMessages, sizeMessages) 227 228 229 def list(self, which=None): 230 """Request listing, return result. 231 232 Result without a message number argument is in form 233 ['response', ['mesg_num octets', ...], octets]. 234 235 Result when a message number argument is given is a 236 single response: the "scan listing" for that message. 237 """ 238 if which is not None: 239 return self._shortcmd('LIST %s' % which) 240 return self._longcmd('LIST') 241 242 243 def retr(self, which): 244 """Retrieve whole message number 'which'. 245 246 Result is in form ['response', ['line', ...], octets]. 247 """ 248 return self._longcmd('RETR %s' % which) 249 250 251 def dele(self, which): 252 """Delete message number 'which'. 253 254 Result is 'response'. 255 """ 256 return self._shortcmd('DELE %s' % which) 257 258 259 def noop(self): 260 """Does nothing. 261 262 One supposes the response indicates the server is alive. 263 """ 264 return self._shortcmd('NOOP') 265 266 267 def rset(self): 268 """Unmark all messages marked for deletion.""" 269 return self._shortcmd('RSET') 270 271 272 def quit(self): 273 """Signoff: commit changes on server, unlock mailbox, close connection.""" 274 resp = self._shortcmd('QUIT') 275 self.close() 276 return resp 277 278 def close(self): 279 """Close the connection without assuming anything about it.""" 280 try: 281 file = self.file 282 self.file = None 283 if file is not None: 284 file.close() 285 finally: 286 sock = self.sock 287 self.sock = None 288 if sock is not None: 289 try: 290 sock.shutdown(socket.SHUT_RDWR) 291 except OSError as exc: 292 # The server might already have closed the connection. 293 # On Windows, this may result in WSAEINVAL (error 10022): 294 # An invalid operation was attempted. 295 if (exc.errno != errno.ENOTCONN 296 and getattr(exc, 'winerror', 0) != 10022): 297 raise 298 finally: 299 sock.close() 300 301 #__del__ = quit 302 303 304 # optional commands: 305 306 def rpop(self, user): 307 """Not sure what this does.""" 308 return self._shortcmd('RPOP %s' % user) 309 310 311 timestamp = re.compile(br'\+OK.[^<]*(<.*>)') 312 313 def apop(self, user, password): 314 """Authorisation 315 316 - only possible if server has supplied a timestamp in initial greeting. 317 318 Args: 319 user - mailbox user; 320 password - mailbox password. 321 322 NB: mailbox is locked by server from here to 'quit()' 323 """ 324 secret = bytes(password, self.encoding) 325 m = self.timestamp.match(self.welcome) 326 if not m: 327 raise error_proto('-ERR APOP not supported by server') 328 import hashlib 329 digest = m.group(1)+secret 330 digest = hashlib.md5(digest).hexdigest() 331 return self._shortcmd('APOP %s %s' % (user, digest)) 332 333 334 def top(self, which, howmuch): 335 """Retrieve message header of message number 'which' 336 and first 'howmuch' lines of message body. 337 338 Result is in form ['response', ['line', ...], octets]. 339 """ 340 return self._longcmd('TOP %s %s' % (which, howmuch)) 341 342 343 def uidl(self, which=None): 344 """Return message digest (unique id) list. 345 346 If 'which', result contains unique id for that message 347 in the form 'response mesgnum uid', otherwise result is 348 the list ['response', ['mesgnum uid', ...], octets] 349 """ 350 if which is not None: 351 return self._shortcmd('UIDL %s' % which) 352 return self._longcmd('UIDL') 353 354 355 def utf8(self): 356 """Try to enter UTF-8 mode (see RFC 6856). Returns server response. 357 """ 358 return self._shortcmd('UTF8') 359 360 361 def capa(self): 362 """Return server capabilities (RFC 2449) as a dictionary 363 >>> c=poplib.POP3('localhost') 364 >>> c.capa() 365 {'IMPLEMENTATION': ['Cyrus', 'POP3', 'server', 'v2.2.12'], 366 'TOP': [], 'LOGIN-DELAY': ['0'], 'AUTH-RESP-CODE': [], 367 'EXPIRE': ['NEVER'], 'USER': [], 'STLS': [], 'PIPELINING': [], 368 'UIDL': [], 'RESP-CODES': []} 369 >>> 370 371 Really, according to RFC 2449, the cyrus folks should avoid 372 having the implementation split into multiple arguments... 373 """ 374 def _parsecap(line): 375 lst = line.decode('ascii').split() 376 return lst[0], lst[1:] 377 378 caps = {} 379 try: 380 resp = self._longcmd('CAPA') 381 rawcaps = resp[1] 382 for capline in rawcaps: 383 capnm, capargs = _parsecap(capline) 384 caps[capnm] = capargs 385 except error_proto as _err: 386 raise error_proto('-ERR CAPA not supported by server') 387 return caps 388 389 390 def stls(self, context=None): 391 """Start a TLS session on the active connection as specified in RFC 2595. 392 393 context - a ssl.SSLContext 394 """ 395 if not HAVE_SSL: 396 raise error_proto('-ERR TLS support missing') 397 if self._tls_established: 398 raise error_proto('-ERR TLS session already established') 399 caps = self.capa() 400 if not 'STLS' in caps: 401 raise error_proto('-ERR STLS not supported by server') 402 if context is None: 403 context = ssl._create_stdlib_context() 404 resp = self._shortcmd('STLS') 405 self.sock = context.wrap_socket(self.sock, 406 server_hostname=self.host) 407 self.file = self.sock.makefile('rb') 408 self._tls_established = True 409 return resp 410 411 412if HAVE_SSL: 413 414 class POP3_SSL(POP3): 415 """POP3 client class over SSL connection 416 417 Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None, 418 context=None) 419 420 hostname - the hostname of the pop3 over ssl server 421 port - port number 422 keyfile - PEM formatted file that contains your private key 423 certfile - PEM formatted certificate chain file 424 context - a ssl.SSLContext 425 426 See the methods of the parent class POP3 for more documentation. 427 """ 428 429 def __init__(self, host, port=POP3_SSL_PORT, keyfile=None, certfile=None, 430 timeout=socket._GLOBAL_DEFAULT_TIMEOUT, context=None): 431 if context is not None and keyfile is not None: 432 raise ValueError("context and keyfile arguments are mutually " 433 "exclusive") 434 if context is not None and certfile is not None: 435 raise ValueError("context and certfile arguments are mutually " 436 "exclusive") 437 if keyfile is not None or certfile is not None: 438 import warnings 439 warnings.warn("keyfile and certfile are deprecated, use a " 440 "custom context instead", DeprecationWarning, 2) 441 self.keyfile = keyfile 442 self.certfile = certfile 443 if context is None: 444 context = ssl._create_stdlib_context(certfile=certfile, 445 keyfile=keyfile) 446 self.context = context 447 POP3.__init__(self, host, port, timeout) 448 449 def _create_socket(self, timeout): 450 sock = POP3._create_socket(self, timeout) 451 sock = self.context.wrap_socket(sock, 452 server_hostname=self.host) 453 return sock 454 455 def stls(self, keyfile=None, certfile=None, context=None): 456 """The method unconditionally raises an exception since the 457 STLS command doesn't make any sense on an already established 458 SSL/TLS session. 459 """ 460 raise error_proto('-ERR TLS session already established') 461 462 __all__.append("POP3_SSL") 463 464if __name__ == "__main__": 465 import sys 466 a = POP3(sys.argv[1]) 467 print(a.getwelcome()) 468 a.user(sys.argv[2]) 469 a.pass_(sys.argv[3]) 470 a.list() 471 (numMsgs, totalSize) = a.stat() 472 for i in range(1, numMsgs + 1): 473 (header, msg, octets) = a.retr(i) 474 print("Message %d:" % i) 475 for line in msg: 476 print(' ' + line) 477 print('-----------------------') 478 a.quit() 479