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