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