blob: 7808ba01cba887f6359a8af488376d8abd41a046 [file] [log] [blame]
Olivier Deprezf4ef2d02021-04-20 13:36:24 +02001#! /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", "SMTPNotSupportedError", "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
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
227
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.
232
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.
244
245 """
246 self._host = host
247 self.timeout = timeout
248 self.esmtp_features = {}
249 self.command_encoding = 'ascii'
250 self.source_address = source_address
251
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
274
275 def __enter__(self):
276 return self
277
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()
287
288 def set_debuglevel(self, debuglevel):
289 """Set the debug output level.
290
291 A non-false value results in debug messages for connection and for all
292 messages sent to and received from the server.
293
294 """
295 self.debuglevel = debuglevel
296
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)
302
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 timeout is not None and not timeout:
307 raise ValueError('Non-blocking socket (timeout=0) is not supported')
308 if self.debuglevel > 0:
309 self._print_debug('connect: to', (host, port), self.source_address)
310 return socket.create_connection((host, port), timeout,
311 self.source_address)
312
313 def connect(self, host='localhost', port=0, source_address=None):
314 """Connect to a host on a given port.
315
316 If the hostname ends with a colon (`:') followed by a number, and
317 there is no port specified, that suffix will be stripped off and the
318 number interpreted as the port number to use.
319
320 Note: This method is automatically invoked by __init__, if a host is
321 specified during instantiation.
322
323 """
324
325 if source_address:
326 self.source_address = source_address
327
328 if not port and (host.find(':') == host.rfind(':')):
329 i = host.rfind(':')
330 if i >= 0:
331 host, port = host[:i], host[i + 1:]
332 try:
333 port = int(port)
334 except ValueError:
335 raise OSError("nonnumeric port")
336 if not port:
337 port = self.default_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)
345
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')
364
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)
372
373 def getreply(self):
374 """Get a reply from the server.
375
376 Returns a tuple consisting of:
377
378 - server response code (e.g. '250', or such, if all goes well)
379 Note: returns -1 if it can't read response code.
380
381 - server response string corresponding to response code (multiline
382 responses are converted to a single, multiline string).
383
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
416
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
421
422 def docmd(self, cmd, args=""):
423 """Send a command, and return its response code."""
424 self.putcmd(cmd, args)
425 return self.getreply()
426
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)
437
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
473
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)
488
489 def has_extn(self, opt):
490 """Does the server support a given SMTP service extension?"""
491 return opt.lower() in self.esmtp_features
492
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]
498
499 def rset(self):
500 """SMTP 'rset' command -- resets session."""
501 self.command_encoding = 'ascii'
502 return self.docmd("rset")
503
504 def _rset(self):
505 """Internal 'rset' command which ignores any SMTPServerDisconnected error.
506
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
515
516 def noop(self):
517 """SMTP 'noop' command -- doesn't do anything :>"""
518 return self.docmd("noop")
519
520 def mail(self, sender, options=()):
521 """SMTP 'mail' command -- begins mail xfer session.
522
523 This method may raise the following exceptions:
524
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()
540
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()
548
549 def data(self, msg):
550 """SMTP 'DATA' command -- sends message data to server.
551
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)
577
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
584
585 def expn(self, address):
586 """SMTP 'expn' command -- expands a mailing list."""
587 self.putcmd("expn", _addr_only(address))
588 return self.getreply()
589
590 # some useful methods
591
592 def ehlo_or_helo_if_needed(self):
593 """Call self.ehlo() and/or self.helo() if needed.
594
595 If there has been no previous EHLO or HELO command this session, this
596 method tries ESMTP EHLO first.
597
598 This method may raise the following exceptions:
599
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)
608
609 def auth(self, mechanism, authobject, *, initial_response_ok=True):
610 """Authentication command - requires response processing.
611
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'.
615
616 'authobject' must be a callable object taking a single argument:
617
618 data = authobject(challenge)
619
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.
623
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)
647
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()
656
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)
661
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
669
670 def login(self, user, password, *, initial_response_ok=True):
671 """Log in on an SMTP server that requires authentication.
672
673 The arguments are:
674 - user: The user name to authenticate with.
675 - password: The password for the authentication.
676
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.
680
681 If there has been no previous EHLO or HELO command this session, this
682 method tries ESMTP EHLO first.
683
684 This method will return normally if the authentication was successful.
685
686 This method may raise the following exceptions:
687
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 """
697
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.")
702
703 # Authentication methods the server claims to support
704 advertised_authlist = self.esmtp_features["auth"].split()
705
706 # Authentication methods we can handle in our preferred order:
707 preferred_auths = ['CRAM-MD5', 'PLAIN', 'LOGIN']
708
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.")
715
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
732
733 # We could not login successfully. Return result of last attempt.
734 raise last_exception
735
736 def starttls(self, keyfile=None, certfile=None, context=None):
737 """Puts the connection to the SMTP server into TLS mode.
738
739 If there has been no previous EHLO or HELO command this session, this
740 method tries ESMTP EHLO first.
741
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.
747
748 This method may raise the following exceptions:
749
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)
791
792 def sendmail(self, from_addr, to_addrs, msg, mail_options=(),
793 rcpt_options=()):
794 """This command performs an entire mail transaction.
795
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.
805
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.
809
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.
814
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.
819
820 This method may raise the following exceptions:
821
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.
833
834 Note: the connection will be open even after an exception is raised.
835
836 Example:
837
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()
849
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.
854
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
895
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.
899
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.
915
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.)
926
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)
972
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()
985
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
995
996if _have_ssl:
997
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.
1009
1010 """
1011
1012 default_port = SMTP_SSL_PORT
1013
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)
1036
1037 def _get_socket(self, host, port, timeout):
1038 if self.debuglevel > 0:
1039 self._print_debug('connect:', (host, port))
1040 new_socket = super()._get_socket(host, port, timeout)
1041 new_socket = self.context.wrap_socket(new_socket,
1042 server_hostname=self._host)
1043 return new_socket
1044
1045 __all__.append("SMTP_SSL")
1046
1047#
1048# LMTP extension
1049#
1050LMTP_PORT = 2003
1051
1052class LMTP(SMTP):
1053 """LMTP - Local Mail Transfer Protocol
1054
1055 The LMTP protocol, which is very similar to ESMTP, is heavily based
1056 on the standard SMTP client. It's common to use Unix sockets for
1057 LMTP, so our connect() method must support that as well as a regular
1058 host:port server. local_hostname and source_address have the same
1059 meaning as they do in the SMTP class. To specify a Unix socket,
1060 you must use an absolute path as the host, starting with a '/'.
1061
1062 Authentication is supported, using the regular SMTP mechanism. When
1063 using a Unix socket, LMTP generally don't support or require any
1064 authentication, but your mileage might vary."""
1065
1066 ehlo_msg = "lhlo"
1067
1068 def __init__(self, host='', port=LMTP_PORT, local_hostname=None,
1069 source_address=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
1070 """Initialize a new instance."""
1071 super().__init__(host, port, local_hostname=local_hostname,
1072 source_address=source_address, timeout=timeout)
1073
1074 def connect(self, host='localhost', port=0, source_address=None):
1075 """Connect to the LMTP daemon, on either a Unix or a TCP socket."""
1076 if host[0] != '/':
1077 return super().connect(host, port, source_address=source_address)
1078
1079 if self.timeout is not None and not self.timeout:
1080 raise ValueError('Non-blocking socket (timeout=0) is not supported')
1081
1082 # Handle Unix-domain sockets.
1083 try:
1084 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1085 self.sock.settimeout(self.timeout)
1086 self.file = None
1087 self.sock.connect(host)
1088 except OSError:
1089 if self.debuglevel > 0:
1090 self._print_debug('connect fail:', host)
1091 if self.sock:
1092 self.sock.close()
1093 self.sock = None
1094 raise
1095 (code, msg) = self.getreply()
1096 if self.debuglevel > 0:
1097 self._print_debug('connect:', msg)
1098 return (code, msg)
1099
1100
1101# Test the sendmail method, which tests most of the others.
1102# Note: This always sends to localhost.
1103if __name__ == '__main__':
1104 def prompt(prompt):
1105 sys.stdout.write(prompt + ": ")
1106 sys.stdout.flush()
1107 return sys.stdin.readline().strip()
1108
1109 fromaddr = prompt("From")
1110 toaddrs = prompt("To").split(',')
1111 print("Enter message, end with ^D:")
1112 msg = ''
1113 while 1:
1114 line = sys.stdin.readline()
1115 if not line:
1116 break
1117 msg = msg + line
1118 print("Message length is %d" % len(msg))
1119
1120 server = SMTP('localhost')
1121 server.set_debuglevel(1)
1122 server.sendmail(fromaddr, toaddrs, msg)
1123 server.quit()