1#! /usr/bin/env python3 2 3'''SMTP/ESMTP client class. 4 5This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP 6Authentication) and RFC 2487 (Secure SMTP over TLS). 7 8Notes: 9 10Please remember, when doing ESMTP, that the names of the SMTP service 11extensions are NOT the same thing as the option keywords for the RCPT 12and MAIL commands! 13 14Example: 15 16 >>> import smtplib 17 >>> s=smtplib.SMTP("localhost") 18 >>> print(s.help()) 19 This is Sendmail version 8.8.4 20 Topics: 21 HELO EHLO MAIL RCPT DATA 22 RSET NOOP QUIT HELP VRFY 23 EXPN VERB ETRN DSN 24 For more info use "HELP <topic>". 25 To report bugs in the implementation send email to 26 sendmail-bugs@sendmail.org. 27 For local information send email to Postmaster at your site. 28 End of HELP info 29 >>> s.putcmd("vrfy","someone@here") 30 >>> s.getreply() 31 (250, "Somebody OverHere <somebody@here.my.org>") 32 >>> s.quit() 33''' 34 35# Author: The Dragon De Monsyne <dragondm@integral.org> 36# ESMTP support, test code and doc fixes added by 37# Eric S. Raymond <esr@thyrsus.com> 38# Better RFC 821 compliance (MAIL and RCPT, and CRLF in data) 39# by Carey Evans <c.evans@clear.net.nz>, for picky mail servers. 40# RFC 2554 (authentication) support by Gerhard Haering <gerhard@bigfoot.de>. 41# 42# This was modified from the Python 1.5 library HTTP lib. 43 44import socket 45import io 46import re 47import email.utils 48import email.message 49import email.generator 50import base64 51import hmac 52import copy 53import datetime 54import sys 55from email.base64mime import body_encode as encode_base64 56 57__all__ = ["SMTPException", "SMTPServerDisconnected", "SMTPResponseException", 58 "SMTPSenderRefused", "SMTPRecipientsRefused", "SMTPDataError", 59 "SMTPConnectError", "SMTPHeloError", "SMTPAuthenticationError", 60 "quoteaddr", "quotedata", "SMTP"] 61 62SMTP_PORT = 25 63SMTP_SSL_PORT = 465 64CRLF = "\r\n" 65bCRLF = b"\r\n" 66_MAXLINE = 8192 # more than 8 times larger than RFC 821, 4.5.3 67 68OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I) 69 70# Exception classes used by this module. 71class SMTPException(OSError): 72 """Base class for all exceptions raised by this module.""" 73 74class SMTPNotSupportedError(SMTPException): 75 """The command or option is not supported by the SMTP server. 76 77 This exception is raised when an attempt is made to run a command or a 78 command with an option which is not supported by the server. 79 """ 80 81class SMTPServerDisconnected(SMTPException): 82 """Not connected to any SMTP server. 83 84 This exception is raised when the server unexpectedly disconnects, 85 or when an attempt is made to use the SMTP instance before 86 connecting it to a server. 87 """ 88 89class SMTPResponseException(SMTPException): 90 """Base class for all exceptions that include an SMTP error code. 91 92 These exceptions are generated in some instances when the SMTP 93 server returns an error code. The error code is stored in the 94 `smtp_code' attribute of the error, and the `smtp_error' attribute 95 is set to the error message. 96 """ 97 98 def __init__(self, code, msg): 99 self.smtp_code = code 100 self.smtp_error = msg 101 self.args = (code, msg) 102 103class SMTPSenderRefused(SMTPResponseException): 104 """Sender address refused. 105 106 In addition to the attributes set by on all SMTPResponseException 107 exceptions, this sets `sender' to the string that the SMTP refused. 108 """ 109 110 def __init__(self, code, msg, sender): 111 self.smtp_code = code 112 self.smtp_error = msg 113 self.sender = sender 114 self.args = (code, msg, sender) 115 116class SMTPRecipientsRefused(SMTPException): 117 """All recipient addresses refused. 118 119 The errors for each recipient are accessible through the attribute 120 'recipients', which is a dictionary of exactly the same sort as 121 SMTP.sendmail() returns. 122 """ 123 124 def __init__(self, recipients): 125 self.recipients = recipients 126 self.args = (recipients,) 127 128 129class SMTPDataError(SMTPResponseException): 130 """The SMTP server didn't accept the data.""" 131 132class SMTPConnectError(SMTPResponseException): 133 """Error during connection establishment.""" 134 135class SMTPHeloError(SMTPResponseException): 136 """The server refused our HELO reply.""" 137 138class SMTPAuthenticationError(SMTPResponseException): 139 """Authentication error. 140 141 Most probably the server didn't accept the username/password 142 combination provided. 143 """ 144 145def quoteaddr(addrstring): 146 """Quote a subset of the email addresses defined by RFC 821. 147 148 Should be able to handle anything email.utils.parseaddr can handle. 149 """ 150 displayname, addr = email.utils.parseaddr(addrstring) 151 if (displayname, addr) == ('', ''): 152 # parseaddr couldn't parse it, use it as is and hope for the best. 153 if addrstring.strip().startswith('<'): 154 return addrstring 155 return "<%s>" % addrstring 156 return "<%s>" % addr 157 158def _addr_only(addrstring): 159 displayname, addr = email.utils.parseaddr(addrstring) 160 if (displayname, addr) == ('', ''): 161 # parseaddr couldn't parse it, so use it as is. 162 return addrstring 163 return addr 164 165# Legacy method kept for backward compatibility. 166def quotedata(data): 167 """Quote data for email. 168 169 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into 170 Internet CRLF end-of-line. 171 """ 172 return re.sub(r'(?m)^\.', '..', 173 re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)) 174 175def _quote_periods(bindata): 176 return re.sub(br'(?m)^\.', b'..', bindata) 177 178def _fix_eols(data): 179 return re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data) 180 181try: 182 import ssl 183except ImportError: 184 _have_ssl = False 185else: 186 _have_ssl = True 187 188 189class SMTP: 190 """This class manages a connection to an SMTP or ESMTP server. 191 SMTP Objects: 192 SMTP objects have the following attributes: 193 helo_resp 194 This is the message given by the server in response to the 195 most recent HELO command. 196 197 ehlo_resp 198 This is the message given by the server in response to the 199 most recent EHLO command. This is usually multiline. 200 201 does_esmtp 202 This is a True value _after you do an EHLO command_, if the 203 server supports ESMTP. 204 205 esmtp_features 206 This is a dictionary, which, if the server supports ESMTP, 207 will _after you do an EHLO command_, contain the names of the 208 SMTP service extensions this server supports, and their 209 parameters (if any). 210 211 Note, all extension names are mapped to lower case in the 212 dictionary. 213 214 See each method's docstrings for details. In general, there is a 215 method of the same name to perform each SMTP command. There is also a 216 method called 'sendmail' that will do an entire mail transaction. 217 """ 218 debuglevel = 0 219 file = None 220 helo_resp = None 221 ehlo_msg = "ehlo" 222 ehlo_resp = None 223 does_esmtp = 0 224 default_port = SMTP_PORT 225 226 def __init__(self, host='', port=0, local_hostname=None, 227 timeout=socket._GLOBAL_DEFAULT_TIMEOUT, 228 source_address=None): 229 """Initialize a new instance. 230 231 If specified, `host' is the name of the remote host to which to 232 connect. If specified, `port' specifies the port to which to connect. 233 By default, smtplib.SMTP_PORT is used. If a host is specified the 234 connect method is called, and if it returns anything other than a 235 success code an SMTPConnectError is raised. If specified, 236 `local_hostname` is used as the FQDN of the local host in the HELO/EHLO 237 command. Otherwise, the local hostname is found using 238 socket.getfqdn(). The `source_address` parameter takes a 2-tuple (host, 239 port) for the socket to bind to as its source address before 240 connecting. If the host is '' and port is 0, the OS default behavior 241 will be used. 242 243 """ 244 self._host = host 245 self.timeout = timeout 246 self.esmtp_features = {} 247 self.command_encoding = 'ascii' 248 self.source_address = source_address 249 250 if host: 251 (code, msg) = self.connect(host, port) 252 if code != 220: 253 raise SMTPConnectError(code, msg) 254 if local_hostname is not None: 255 self.local_hostname = local_hostname 256 else: 257 # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and 258 # if that can't be calculated, that we should use a domain literal 259 # instead (essentially an encoded IP address like [A.B.C.D]). 260 fqdn = socket.getfqdn() 261 if '.' in fqdn: 262 self.local_hostname = fqdn 263 else: 264 # We can't find an fqdn hostname, so use a domain literal 265 addr = '127.0.0.1' 266 try: 267 addr = socket.gethostbyname(socket.gethostname()) 268 except socket.gaierror: 269 pass 270 self.local_hostname = '[%s]' % addr 271 272 def __enter__(self): 273 return self 274 275 def __exit__(self, *args): 276 try: 277 code, message = self.docmd("QUIT") 278 if code != 221: 279 raise SMTPResponseException(code, message) 280 except SMTPServerDisconnected: 281 pass 282 finally: 283 self.close() 284 285 def set_debuglevel(self, debuglevel): 286 """Set the debug output level. 287 288 A non-false value results in debug messages for connection and for all 289 messages sent to and received from the server. 290 291 """ 292 self.debuglevel = debuglevel 293 294 def _print_debug(self, *args): 295 if self.debuglevel > 1: 296 print(datetime.datetime.now().time(), *args, file=sys.stderr) 297 else: 298 print(*args, file=sys.stderr) 299 300 def _get_socket(self, host, port, timeout): 301 # This makes it simpler for SMTP_SSL to use the SMTP connect code 302 # and just alter the socket connection bit. 303 if self.debuglevel > 0: 304 self._print_debug('connect: to', (host, port), self.source_address) 305 return socket.create_connection((host, port), timeout, 306 self.source_address) 307 308 def connect(self, host='localhost', port=0, source_address=None): 309 """Connect to a host on a given port. 310 311 If the hostname ends with a colon (`:') followed by a number, and 312 there is no port specified, that suffix will be stripped off and the 313 number interpreted as the port number to use. 314 315 Note: This method is automatically invoked by __init__, if a host is 316 specified during instantiation. 317 318 """ 319 320 if source_address: 321 self.source_address = source_address 322 323 if not port and (host.find(':') == host.rfind(':')): 324 i = host.rfind(':') 325 if i >= 0: 326 host, port = host[:i], host[i + 1:] 327 try: 328 port = int(port) 329 except ValueError: 330 raise OSError("nonnumeric port") 331 if not port: 332 port = self.default_port 333 if self.debuglevel > 0: 334 self._print_debug('connect:', (host, port)) 335 self.sock = self._get_socket(host, port, self.timeout) 336 self.file = None 337 (code, msg) = self.getreply() 338 if self.debuglevel > 0: 339 self._print_debug('connect:', repr(msg)) 340 return (code, msg) 341 342 def send(self, s): 343 """Send `s' to the server.""" 344 if self.debuglevel > 0: 345 self._print_debug('send:', repr(s)) 346 if hasattr(self, 'sock') and self.sock: 347 if isinstance(s, str): 348 # send is used by the 'data' command, where command_encoding 349 # should not be used, but 'data' needs to convert the string to 350 # binary itself anyway, so that's not a problem. 351 s = s.encode(self.command_encoding) 352 try: 353 self.sock.sendall(s) 354 except OSError: 355 self.close() 356 raise SMTPServerDisconnected('Server not connected') 357 else: 358 raise SMTPServerDisconnected('please run connect() first') 359 360 def putcmd(self, cmd, args=""): 361 """Send a command to the server.""" 362 if args == "": 363 str = '%s%s' % (cmd, CRLF) 364 else: 365 str = '%s %s%s' % (cmd, args, CRLF) 366 self.send(str) 367 368 def getreply(self): 369 """Get a reply from the server. 370 371 Returns a tuple consisting of: 372 373 - server response code (e.g. '250', or such, if all goes well) 374 Note: returns -1 if it can't read response code. 375 376 - server response string corresponding to response code (multiline 377 responses are converted to a single, multiline string). 378 379 Raises SMTPServerDisconnected if end-of-file is reached. 380 """ 381 resp = [] 382 if self.file is None: 383 self.file = self.sock.makefile('rb') 384 while 1: 385 try: 386 line = self.file.readline(_MAXLINE + 1) 387 except OSError as e: 388 self.close() 389 raise SMTPServerDisconnected("Connection unexpectedly closed: " 390 + str(e)) 391 if not line: 392 self.close() 393 raise SMTPServerDisconnected("Connection unexpectedly closed") 394 if self.debuglevel > 0: 395 self._print_debug('reply:', repr(line)) 396 if len(line) > _MAXLINE: 397 self.close() 398 raise SMTPResponseException(500, "Line too long.") 399 resp.append(line[4:].strip(b' \t\r\n')) 400 code = line[:3] 401 # Check that the error code is syntactically correct. 402 # Don't attempt to read a continuation line if it is broken. 403 try: 404 errcode = int(code) 405 except ValueError: 406 errcode = -1 407 break 408 # Check if multiline response. 409 if line[3:4] != b"-": 410 break 411 412 errmsg = b"\n".join(resp) 413 if self.debuglevel > 0: 414 self._print_debug('reply: retcode (%s); Msg: %a' % (errcode, errmsg)) 415 return errcode, errmsg 416 417 def docmd(self, cmd, args=""): 418 """Send a command, and return its response code.""" 419 self.putcmd(cmd, args) 420 return self.getreply() 421 422 # std smtp commands 423 def helo(self, name=''): 424 """SMTP 'helo' command. 425 Hostname to send for this command defaults to the FQDN of the local 426 host. 427 """ 428 self.putcmd("helo", name or self.local_hostname) 429 (code, msg) = self.getreply() 430 self.helo_resp = msg 431 return (code, msg) 432 433 def ehlo(self, name=''): 434 """ SMTP 'ehlo' command. 435 Hostname to send for this command defaults to the FQDN of the local 436 host. 437 """ 438 self.esmtp_features = {} 439 self.putcmd(self.ehlo_msg, name or self.local_hostname) 440 (code, msg) = self.getreply() 441 # According to RFC1869 some (badly written) 442 # MTA's will disconnect on an ehlo. Toss an exception if 443 # that happens -ddm 444 if code == -1 and len(msg) == 0: 445 self.close() 446 raise SMTPServerDisconnected("Server not connected") 447 self.ehlo_resp = msg 448 if code != 250: 449 return (code, msg) 450 self.does_esmtp = 1 451 #parse the ehlo response -ddm 452 assert isinstance(self.ehlo_resp, bytes), repr(self.ehlo_resp) 453 resp = self.ehlo_resp.decode("latin-1").split('\n') 454 del resp[0] 455 for each in resp: 456 # To be able to communicate with as many SMTP servers as possible, 457 # we have to take the old-style auth advertisement into account, 458 # because: 459 # 1) Else our SMTP feature parser gets confused. 460 # 2) There are some servers that only advertise the auth methods we 461 # support using the old style. 462 auth_match = OLDSTYLE_AUTH.match(each) 463 if auth_match: 464 # This doesn't remove duplicates, but that's no problem 465 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \ 466 + " " + auth_match.groups(0)[0] 467 continue 468 469 # RFC 1869 requires a space between ehlo keyword and parameters. 470 # It's actually stricter, in that only spaces are allowed between 471 # parameters, but were not going to check for that here. Note 472 # that the space isn't present if there are no parameters. 473 m = re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?', each) 474 if m: 475 feature = m.group("feature").lower() 476 params = m.string[m.end("feature"):].strip() 477 if feature == "auth": 478 self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \ 479 + " " + params 480 else: 481 self.esmtp_features[feature] = params 482 return (code, msg) 483 484 def has_extn(self, opt): 485 """Does the server support a given SMTP service extension?""" 486 return opt.lower() in self.esmtp_features 487 488 def help(self, args=''): 489 """SMTP 'help' command. 490 Returns help text from server.""" 491 self.putcmd("help", args) 492 return self.getreply()[1] 493 494 def rset(self): 495 """SMTP 'rset' command -- resets session.""" 496 self.command_encoding = 'ascii' 497 return self.docmd("rset") 498 499 def _rset(self): 500 """Internal 'rset' command which ignores any SMTPServerDisconnected error. 501 502 Used internally in the library, since the server disconnected error 503 should appear to the application when the *next* command is issued, if 504 we are doing an internal "safety" reset. 505 """ 506 try: 507 self.rset() 508 except SMTPServerDisconnected: 509 pass 510 511 def noop(self): 512 """SMTP 'noop' command -- doesn't do anything :>""" 513 return self.docmd("noop") 514 515 def mail(self, sender, options=[]): 516 """SMTP 'mail' command -- begins mail xfer session. 517 518 This method may raise the following exceptions: 519 520 SMTPNotSupportedError The options parameter includes 'SMTPUTF8' 521 but the SMTPUTF8 extension is not supported by 522 the server. 523 """ 524 optionlist = '' 525 if options and self.does_esmtp: 526 if any(x.lower()=='smtputf8' for x in options): 527 if self.has_extn('smtputf8'): 528 self.command_encoding = 'utf-8' 529 else: 530 raise SMTPNotSupportedError( 531 'SMTPUTF8 not supported by server') 532 optionlist = ' ' + ' '.join(options) 533 self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist)) 534 return self.getreply() 535 536 def rcpt(self, recip, options=[]): 537 """SMTP 'rcpt' command -- indicates 1 recipient for this mail.""" 538 optionlist = '' 539 if options and self.does_esmtp: 540 optionlist = ' ' + ' '.join(options) 541 self.putcmd("rcpt", "TO:%s%s" % (quoteaddr(recip), optionlist)) 542 return self.getreply() 543 544 def data(self, msg): 545 """SMTP 'DATA' command -- sends message data to server. 546 547 Automatically quotes lines beginning with a period per rfc821. 548 Raises SMTPDataError if there is an unexpected reply to the 549 DATA command; the return value from this method is the final 550 response code received when the all data is sent. If msg 551 is a string, lone '\\r' and '\\n' characters are converted to 552 '\\r\\n' characters. If msg is bytes, it is transmitted as is. 553 """ 554 self.putcmd("data") 555 (code, repl) = self.getreply() 556 if self.debuglevel > 0: 557 self._print_debug('data:', (code, repl)) 558 if code != 354: 559 raise SMTPDataError(code, repl) 560 else: 561 if isinstance(msg, str): 562 msg = _fix_eols(msg).encode('ascii') 563 q = _quote_periods(msg) 564 if q[-2:] != bCRLF: 565 q = q + bCRLF 566 q = q + b"." + bCRLF 567 self.send(q) 568 (code, msg) = self.getreply() 569 if self.debuglevel > 0: 570 self._print_debug('data:', (code, msg)) 571 return (code, msg) 572 573 def verify(self, address): 574 """SMTP 'verify' command -- checks for address validity.""" 575 self.putcmd("vrfy", _addr_only(address)) 576 return self.getreply() 577 # a.k.a. 578 vrfy = verify 579 580 def expn(self, address): 581 """SMTP 'expn' command -- expands a mailing list.""" 582 self.putcmd("expn", _addr_only(address)) 583 return self.getreply() 584 585 # some useful methods 586 587 def ehlo_or_helo_if_needed(self): 588 """Call self.ehlo() and/or self.helo() if needed. 589 590 If there has been no previous EHLO or HELO command this session, this 591 method tries ESMTP EHLO first. 592 593 This method may raise the following exceptions: 594 595 SMTPHeloError The server didn't reply properly to 596 the helo greeting. 597 """ 598 if self.helo_resp is None and self.ehlo_resp is None: 599 if not (200 <= self.ehlo()[0] <= 299): 600 (code, resp) = self.helo() 601 if not (200 <= code <= 299): 602 raise SMTPHeloError(code, resp) 603 604 def auth(self, mechanism, authobject, *, initial_response_ok=True): 605 """Authentication command - requires response processing. 606 607 'mechanism' specifies which authentication mechanism is to 608 be used - the valid values are those listed in the 'auth' 609 element of 'esmtp_features'. 610 611 'authobject' must be a callable object taking a single argument: 612 613 data = authobject(challenge) 614 615 It will be called to process the server's challenge response; the 616 challenge argument it is passed will be a bytes. It should return 617 bytes data that will be base64 encoded and sent to the server. 618 619 Keyword arguments: 620 - initial_response_ok: Allow sending the RFC 4954 initial-response 621 to the AUTH command, if the authentication methods supports it. 622 """ 623 # RFC 4954 allows auth methods to provide an initial response. Not all 624 # methods support it. By definition, if they return something other 625 # than None when challenge is None, then they do. See issue #15014. 626 mechanism = mechanism.upper() 627 initial_response = (authobject() if initial_response_ok else None) 628 if initial_response is not None: 629 response = encode_base64(initial_response.encode('ascii'), eol='') 630 (code, resp) = self.docmd("AUTH", mechanism + " " + response) 631 else: 632 (code, resp) = self.docmd("AUTH", mechanism) 633 # If server responds with a challenge, send the response. 634 if code == 334: 635 challenge = base64.decodebytes(resp) 636 response = encode_base64( 637 authobject(challenge).encode('ascii'), eol='') 638 (code, resp) = self.docmd(response) 639 if code in (235, 503): 640 return (code, resp) 641 raise SMTPAuthenticationError(code, resp) 642 643 def auth_cram_md5(self, challenge=None): 644 """ Authobject to use with CRAM-MD5 authentication. Requires self.user 645 and self.password to be set.""" 646 # CRAM-MD5 does not support initial-response. 647 if challenge is None: 648 return None 649 return self.user + " " + hmac.HMAC( 650 self.password.encode('ascii'), challenge, 'md5').hexdigest() 651 652 def auth_plain(self, challenge=None): 653 """ Authobject to use with PLAIN authentication. Requires self.user and 654 self.password to be set.""" 655 return "\0%s\0%s" % (self.user, self.password) 656 657 def auth_login(self, challenge=None): 658 """ Authobject to use with LOGIN authentication. Requires self.user and 659 self.password to be set.""" 660 if challenge is None: 661 return self.user 662 else: 663 return self.password 664 665 def login(self, user, password, *, initial_response_ok=True): 666 """Log in on an SMTP server that requires authentication. 667 668 The arguments are: 669 - user: The user name to authenticate with. 670 - password: The password for the authentication. 671 672 Keyword arguments: 673 - initial_response_ok: Allow sending the RFC 4954 initial-response 674 to the AUTH command, if the authentication methods supports it. 675 676 If there has been no previous EHLO or HELO command this session, this 677 method tries ESMTP EHLO first. 678 679 This method will return normally if the authentication was successful. 680 681 This method may raise the following exceptions: 682 683 SMTPHeloError The server didn't reply properly to 684 the helo greeting. 685 SMTPAuthenticationError The server didn't accept the username/ 686 password combination. 687 SMTPNotSupportedError The AUTH command is not supported by the 688 server. 689 SMTPException No suitable authentication method was 690 found. 691 """ 692 693 self.ehlo_or_helo_if_needed() 694 if not self.has_extn("auth"): 695 raise SMTPNotSupportedError( 696 "SMTP AUTH extension not supported by server.") 697 698 # Authentication methods the server claims to support 699 advertised_authlist = self.esmtp_features["auth"].split() 700 701 # Authentication methods we can handle in our preferred order: 702 preferred_auths = ['CRAM-MD5', 'PLAIN', 'LOGIN'] 703 704 # We try the supported authentications in our preferred order, if 705 # the server supports them. 706 authlist = [auth for auth in preferred_auths 707 if auth in advertised_authlist] 708 if not authlist: 709 raise SMTPException("No suitable authentication method found.") 710 711 # Some servers advertise authentication methods they don't really 712 # support, so if authentication fails, we continue until we've tried 713 # all methods. 714 self.user, self.password = user, password 715 for authmethod in authlist: 716 method_name = 'auth_' + authmethod.lower().replace('-', '_') 717 try: 718 (code, resp) = self.auth( 719 authmethod, getattr(self, method_name), 720 initial_response_ok=initial_response_ok) 721 # 235 == 'Authentication successful' 722 # 503 == 'Error: already authenticated' 723 if code in (235, 503): 724 return (code, resp) 725 except SMTPAuthenticationError as e: 726 last_exception = e 727 728 # We could not login successfully. Return result of last attempt. 729 raise last_exception 730 731 def starttls(self, keyfile=None, certfile=None, context=None): 732 """Puts the connection to the SMTP server into TLS mode. 733 734 If there has been no previous EHLO or HELO command this session, this 735 method tries ESMTP EHLO first. 736 737 If the server supports TLS, this will encrypt the rest of the SMTP 738 session. If you provide the keyfile and certfile parameters, 739 the identity of the SMTP server and client can be checked. This, 740 however, depends on whether the socket module really checks the 741 certificates. 742 743 This method may raise the following exceptions: 744 745 SMTPHeloError The server didn't reply properly to 746 the helo greeting. 747 """ 748 self.ehlo_or_helo_if_needed() 749 if not self.has_extn("starttls"): 750 raise SMTPNotSupportedError( 751 "STARTTLS extension not supported by server.") 752 (resp, reply) = self.docmd("STARTTLS") 753 if resp == 220: 754 if not _have_ssl: 755 raise RuntimeError("No SSL support included in this Python") 756 if context is not None and keyfile is not None: 757 raise ValueError("context and keyfile arguments are mutually " 758 "exclusive") 759 if context is not None and certfile is not None: 760 raise ValueError("context and certfile arguments are mutually " 761 "exclusive") 762 if keyfile is not None or certfile is not None: 763 import warnings 764 warnings.warn("keyfile and certfile are deprecated, use a" 765 "custom context instead", DeprecationWarning, 2) 766 if context is None: 767 context = ssl._create_stdlib_context(certfile=certfile, 768 keyfile=keyfile) 769 self.sock = context.wrap_socket(self.sock, 770 server_hostname=self._host) 771 self.file = None 772 # RFC 3207: 773 # The client MUST discard any knowledge obtained from 774 # the server, such as the list of SMTP service extensions, 775 # which was not obtained from the TLS negotiation itself. 776 self.helo_resp = None 777 self.ehlo_resp = None 778 self.esmtp_features = {} 779 self.does_esmtp = 0 780 else: 781 # RFC 3207: 782 # 501 Syntax error (no parameters allowed) 783 # 454 TLS not available due to temporary reason 784 raise SMTPResponseException(resp, reply) 785 return (resp, reply) 786 787 def sendmail(self, from_addr, to_addrs, msg, mail_options=[], 788 rcpt_options=[]): 789 """This command performs an entire mail transaction. 790 791 The arguments are: 792 - from_addr : The address sending this mail. 793 - to_addrs : A list of addresses to send this mail to. A bare 794 string will be treated as a list with 1 address. 795 - msg : The message to send. 796 - mail_options : List of ESMTP options (such as 8bitmime) for the 797 mail command. 798 - rcpt_options : List of ESMTP options (such as DSN commands) for 799 all the rcpt commands. 800 801 msg may be a string containing characters in the ASCII range, or a byte 802 string. A string is encoded to bytes using the ascii codec, and lone 803 \\r and \\n characters are converted to \\r\\n characters. 804 805 If there has been no previous EHLO or HELO command this session, this 806 method tries ESMTP EHLO first. If the server does ESMTP, message size 807 and each of the specified options will be passed to it. If EHLO 808 fails, HELO will be tried and ESMTP options suppressed. 809 810 This method will return normally if the mail is accepted for at least 811 one recipient. It returns a dictionary, with one entry for each 812 recipient that was refused. Each entry contains a tuple of the SMTP 813 error code and the accompanying error message sent by the server. 814 815 This method may raise the following exceptions: 816 817 SMTPHeloError The server didn't reply properly to 818 the helo greeting. 819 SMTPRecipientsRefused The server rejected ALL recipients 820 (no mail was sent). 821 SMTPSenderRefused The server didn't accept the from_addr. 822 SMTPDataError The server replied with an unexpected 823 error code (other than a refusal of 824 a recipient). 825 SMTPNotSupportedError The mail_options parameter includes 'SMTPUTF8' 826 but the SMTPUTF8 extension is not supported by 827 the server. 828 829 Note: the connection will be open even after an exception is raised. 830 831 Example: 832 833 >>> import smtplib 834 >>> s=smtplib.SMTP("localhost") 835 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"] 836 >>> msg = '''\\ 837 ... From: Me@my.org 838 ... Subject: testin'... 839 ... 840 ... This is a test ''' 841 >>> s.sendmail("me@my.org",tolist,msg) 842 { "three@three.org" : ( 550 ,"User unknown" ) } 843 >>> s.quit() 844 845 In the above example, the message was accepted for delivery to three 846 of the four addresses, and one was rejected, with the error code 847 550. If all addresses are accepted, then the method will return an 848 empty dictionary. 849 850 """ 851 self.ehlo_or_helo_if_needed() 852 esmtp_opts = [] 853 if isinstance(msg, str): 854 msg = _fix_eols(msg).encode('ascii') 855 if self.does_esmtp: 856 if self.has_extn('size'): 857 esmtp_opts.append("size=%d" % len(msg)) 858 for option in mail_options: 859 esmtp_opts.append(option) 860 (code, resp) = self.mail(from_addr, esmtp_opts) 861 if code != 250: 862 if code == 421: 863 self.close() 864 else: 865 self._rset() 866 raise SMTPSenderRefused(code, resp, from_addr) 867 senderrs = {} 868 if isinstance(to_addrs, str): 869 to_addrs = [to_addrs] 870 for each in to_addrs: 871 (code, resp) = self.rcpt(each, rcpt_options) 872 if (code != 250) and (code != 251): 873 senderrs[each] = (code, resp) 874 if code == 421: 875 self.close() 876 raise SMTPRecipientsRefused(senderrs) 877 if len(senderrs) == len(to_addrs): 878 # the server refused all our recipients 879 self._rset() 880 raise SMTPRecipientsRefused(senderrs) 881 (code, resp) = self.data(msg) 882 if code != 250: 883 if code == 421: 884 self.close() 885 else: 886 self._rset() 887 raise SMTPDataError(code, resp) 888 #if we got here then somebody got our mail 889 return senderrs 890 891 def send_message(self, msg, from_addr=None, to_addrs=None, 892 mail_options=[], rcpt_options={}): 893 """Converts message to a bytestring and passes it to sendmail. 894 895 The arguments are as for sendmail, except that msg is an 896 email.message.Message object. If from_addr is None or to_addrs is 897 None, these arguments are taken from the headers of the Message as 898 described in RFC 2822 (a ValueError is raised if there is more than 899 one set of 'Resent-' headers). Regardless of the values of from_addr and 900 to_addr, any Bcc field (or Resent-Bcc field, when the Message is a 901 resent) of the Message object won't be transmitted. The Message 902 object is then serialized using email.generator.BytesGenerator and 903 sendmail is called to transmit the message. If the sender or any of 904 the recipient addresses contain non-ASCII and the server advertises the 905 SMTPUTF8 capability, the policy is cloned with utf8 set to True for the 906 serialization, and SMTPUTF8 and BODY=8BITMIME are asserted on the send. 907 If the server does not support SMTPUTF8, an SMTPNotSupported error is 908 raised. Otherwise the generator is called without modifying the 909 policy. 910 911 """ 912 # 'Resent-Date' is a mandatory field if the Message is resent (RFC 2822 913 # Section 3.6.6). In such a case, we use the 'Resent-*' fields. However, 914 # if there is more than one 'Resent-' block there's no way to 915 # unambiguously determine which one is the most recent in all cases, 916 # so rather than guess we raise a ValueError in that case. 917 # 918 # TODO implement heuristics to guess the correct Resent-* block with an 919 # option allowing the user to enable the heuristics. (It should be 920 # possible to guess correctly almost all of the time.) 921 922 self.ehlo_or_helo_if_needed() 923 resent = msg.get_all('Resent-Date') 924 if resent is None: 925 header_prefix = '' 926 elif len(resent) == 1: 927 header_prefix = 'Resent-' 928 else: 929 raise ValueError("message has more than one 'Resent-' header block") 930 if from_addr is None: 931 # Prefer the sender field per RFC 2822:3.6.2. 932 from_addr = (msg[header_prefix + 'Sender'] 933 if (header_prefix + 'Sender') in msg 934 else msg[header_prefix + 'From']) 935 if to_addrs is None: 936 addr_fields = [f for f in (msg[header_prefix + 'To'], 937 msg[header_prefix + 'Bcc'], 938 msg[header_prefix + 'Cc']) 939 if f is not None] 940 to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)] 941 # Make a local copy so we can delete the bcc headers. 942 msg_copy = copy.copy(msg) 943 del msg_copy['Bcc'] 944 del msg_copy['Resent-Bcc'] 945 international = False 946 try: 947 ''.join([from_addr, *to_addrs]).encode('ascii') 948 except UnicodeEncodeError: 949 if not self.has_extn('smtputf8'): 950 raise SMTPNotSupportedError( 951 "One or more source or delivery addresses require" 952 " internationalized email support, but the server" 953 " does not advertise the required SMTPUTF8 capability") 954 international = True 955 with io.BytesIO() as bytesmsg: 956 if international: 957 g = email.generator.BytesGenerator( 958 bytesmsg, policy=msg.policy.clone(utf8=True)) 959 mail_options += ['SMTPUTF8', 'BODY=8BITMIME'] 960 else: 961 g = email.generator.BytesGenerator(bytesmsg) 962 g.flatten(msg_copy, linesep='\r\n') 963 flatmsg = bytesmsg.getvalue() 964 return self.sendmail(from_addr, to_addrs, flatmsg, mail_options, 965 rcpt_options) 966 967 def close(self): 968 """Close the connection to the SMTP server.""" 969 try: 970 file = self.file 971 self.file = None 972 if file: 973 file.close() 974 finally: 975 sock = self.sock 976 self.sock = None 977 if sock: 978 sock.close() 979 980 def quit(self): 981 """Terminate the SMTP session.""" 982 res = self.docmd("quit") 983 # A new EHLO is required after reconnecting with connect() 984 self.ehlo_resp = self.helo_resp = None 985 self.esmtp_features = {} 986 self.does_esmtp = False 987 self.close() 988 return res 989 990if _have_ssl: 991 992 class SMTP_SSL(SMTP): 993 """ This is a subclass derived from SMTP that connects over an SSL 994 encrypted socket (to use this class you need a socket module that was 995 compiled with SSL support). If host is not specified, '' (the local 996 host) is used. If port is omitted, the standard SMTP-over-SSL port 997 (465) is used. local_hostname and source_address have the same meaning 998 as they do in the SMTP class. keyfile and certfile are also optional - 999 they can contain a PEM formatted private key and certificate chain file 1000 for the SSL connection. context also optional, can contain a 1001 SSLContext, and is an alternative to keyfile and certfile; If it is 1002 specified both keyfile and certfile must be None. 1003 1004 """ 1005 1006 default_port = SMTP_SSL_PORT 1007 1008 def __init__(self, host='', port=0, local_hostname=None, 1009 keyfile=None, certfile=None, 1010 timeout=socket._GLOBAL_DEFAULT_TIMEOUT, 1011 source_address=None, context=None): 1012 if context is not None and keyfile is not None: 1013 raise ValueError("context and keyfile arguments are mutually " 1014 "exclusive") 1015 if context is not None and certfile is not None: 1016 raise ValueError("context and certfile arguments are mutually " 1017 "exclusive") 1018 if keyfile is not None or certfile is not None: 1019 import warnings 1020 warnings.warn("keyfile and certfile are deprecated, use a" 1021 "custom context instead", DeprecationWarning, 2) 1022 self.keyfile = keyfile 1023 self.certfile = certfile 1024 if context is None: 1025 context = ssl._create_stdlib_context(certfile=certfile, 1026 keyfile=keyfile) 1027 self.context = context 1028 SMTP.__init__(self, host, port, local_hostname, timeout, 1029 source_address) 1030 1031 def _get_socket(self, host, port, timeout): 1032 if self.debuglevel > 0: 1033 self._print_debug('connect:', (host, port)) 1034 new_socket = socket.create_connection((host, port), timeout, 1035 self.source_address) 1036 new_socket = self.context.wrap_socket(new_socket, 1037 server_hostname=self._host) 1038 return new_socket 1039 1040 __all__.append("SMTP_SSL") 1041 1042# 1043# LMTP extension 1044# 1045LMTP_PORT = 2003 1046 1047class LMTP(SMTP): 1048 """LMTP - Local Mail Transfer Protocol 1049 1050 The LMTP protocol, which is very similar to ESMTP, is heavily based 1051 on the standard SMTP client. It's common to use Unix sockets for 1052 LMTP, so our connect() method must support that as well as a regular 1053 host:port server. local_hostname and source_address have the same 1054 meaning as they do in the SMTP class. To specify a Unix socket, 1055 you must use an absolute path as the host, starting with a '/'. 1056 1057 Authentication is supported, using the regular SMTP mechanism. When 1058 using a Unix socket, LMTP generally don't support or require any 1059 authentication, but your mileage might vary.""" 1060 1061 ehlo_msg = "lhlo" 1062 1063 def __init__(self, host='', port=LMTP_PORT, local_hostname=None, 1064 source_address=None): 1065 """Initialize a new instance.""" 1066 SMTP.__init__(self, host, port, local_hostname=local_hostname, 1067 source_address=source_address) 1068 1069 def connect(self, host='localhost', port=0, source_address=None): 1070 """Connect to the LMTP daemon, on either a Unix or a TCP socket.""" 1071 if host[0] != '/': 1072 return SMTP.connect(self, host, port, source_address=source_address) 1073 1074 # Handle Unix-domain sockets. 1075 try: 1076 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 1077 self.file = None 1078 self.sock.connect(host) 1079 except OSError: 1080 if self.debuglevel > 0: 1081 self._print_debug('connect fail:', host) 1082 if self.sock: 1083 self.sock.close() 1084 self.sock = None 1085 raise 1086 (code, msg) = self.getreply() 1087 if self.debuglevel > 0: 1088 self._print_debug('connect:', msg) 1089 return (code, msg) 1090 1091 1092# Test the sendmail method, which tests most of the others. 1093# Note: This always sends to localhost. 1094if __name__ == '__main__': 1095 def prompt(prompt): 1096 sys.stdout.write(prompt + ": ") 1097 sys.stdout.flush() 1098 return sys.stdin.readline().strip() 1099 1100 fromaddr = prompt("From") 1101 toaddrs = prompt("To").split(',') 1102 print("Enter message, end with ^D:") 1103 msg = '' 1104 while 1: 1105 line = sys.stdin.readline() 1106 if not line: 1107 break 1108 msg = msg + line 1109 print("Message length is %d" % len(msg)) 1110 1111 server = SMTP('localhost') 1112 server.set_debuglevel(1) 1113 server.sendmail(fromaddr, toaddrs, msg) 1114 server.quit() 1115