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