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