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, keyfile=None, certfile=None, 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 keyfile and certfile parameters,
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 not None and keyfile is not None:
778 raise ValueError("context and keyfile arguments are mutually "
779 "exclusive")
780 if context is not None and certfile is not None:
781 raise ValueError("context and certfile arguments are mutually "
782 "exclusive")
783 if keyfile is not None or certfile is not None:
784 import warnings
785 warnings.warn("keyfile and certfile are deprecated, use a "
786 "custom context instead", DeprecationWarning, 2)
787 if context is None:
788 context = ssl._create_stdlib_context(certfile=certfile,
789 keyfile=keyfile)
790 self.sock = context.wrap_socket(self.sock,
791 server_hostname=self._host)
792 self.file = None
793 # RFC 3207:
794 # The client MUST discard any knowledge obtained from
795 # the server, such as the list of SMTP service extensions,
796 # which was not obtained from the TLS negotiation itself.
797 self.helo_resp = None
798 self.ehlo_resp = None
799 self.esmtp_features = {}
800 self.does_esmtp = False
801 else:
802 # RFC 3207:
803 # 501 Syntax error (no parameters allowed)
804 # 454 TLS not available due to temporary reason
805 raise SMTPResponseException(resp, reply)
806 return (resp, reply)
807
808 def sendmail(self, from_addr, to_addrs, msg, mail_options=(),
809 rcpt_options=()):
810 """This command performs an entire mail transaction.
811
812 The arguments are:
813 - from_addr : The address sending this mail.
814 - to_addrs : A list of addresses to send this mail to. A bare
815 string will be treated as a list with 1 address.
816 - msg : The message to send.
817 - mail_options : List of ESMTP options (such as 8bitmime) for the
818 mail command.
819 - rcpt_options : List of ESMTP options (such as DSN commands) for
820 all the rcpt commands.
821
822 msg may be a string containing characters in the ASCII range, or a byte
823 string. A string is encoded to bytes using the ascii codec, and lone
824 \\r and \\n characters are converted to \\r\\n characters.
825
826 If there has been no previous EHLO or HELO command this session, this
827 method tries ESMTP EHLO first. If the server does ESMTP, message size
828 and each of the specified options will be passed to it. If EHLO
829 fails, HELO will be tried and ESMTP options suppressed.
830
831 This method will return normally if the mail is accepted for at least
832 one recipient. It returns a dictionary, with one entry for each
833 recipient that was refused. Each entry contains a tuple of the SMTP
834 error code and the accompanying error message sent by the server.
835
836 This method may raise the following exceptions:
837
838 SMTPHeloError The server didn't reply properly to
839 the helo greeting.
840 SMTPRecipientsRefused The server rejected ALL recipients
841 (no mail was sent).
842 SMTPSenderRefused The server didn't accept the from_addr.
843 SMTPDataError The server replied with an unexpected
844 error code (other than a refusal of
845 a recipient).
846 SMTPNotSupportedError The mail_options parameter includes 'SMTPUTF8'
847 but the SMTPUTF8 extension is not supported by
848 the server.
849
850 Note: the connection will be open even after an exception is raised.
851
852 Example:
853
854 >>> import smtplib
855 >>> s=smtplib.SMTP("localhost")
856 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
857 >>> msg = '''\\
858 ... From: Me@my.org
859 ... Subject: testin'...
860 ...
861 ... This is a test '''
862 >>> s.sendmail("me@my.org",tolist,msg)
863 { "three@three.org" : ( 550 ,"User unknown" ) }
864 >>> s.quit()
865
866 In the above example, the message was accepted for delivery to three
867 of the four addresses, and one was rejected, with the error code
868 550. If all addresses are accepted, then the method will return an
869 empty dictionary.
870
871 """
872 self.ehlo_or_helo_if_needed()
873 esmtp_opts = []
874 if isinstance(msg, str):
875 msg = _fix_eols(msg).encode('ascii')
876 if self.does_esmtp:
877 if self.has_extn('size'):
878 esmtp_opts.append("size=%d" % len(msg))
879 for option in mail_options:
880 esmtp_opts.append(option)
881 (code, resp) = self.mail(from_addr, esmtp_opts)
882 if code != 250:
883 if code == 421:
884 self.close()
885 else:
886 self._rset()
887 raise SMTPSenderRefused(code, resp, from_addr)
888 senderrs = {}
889 if isinstance(to_addrs, str):
890 to_addrs = [to_addrs]
891 for each in to_addrs:
892 (code, resp) = self.rcpt(each, rcpt_options)
893 if (code != 250) and (code != 251):
894 senderrs[each] = (code, resp)
895 if code == 421:
896 self.close()
897 raise SMTPRecipientsRefused(senderrs)
898 if len(senderrs) == len(to_addrs):
899 # the server refused all our recipients
900 self._rset()
901 raise SMTPRecipientsRefused(senderrs)
902 (code, resp) = self.data(msg)
903 if code != 250:
904 if code == 421:
905 self.close()
906 else:
907 self._rset()
908 raise SMTPDataError(code, resp)
909 #if we got here then somebody got our mail
910 return senderrs
911
912 def send_message(self, msg, from_addr=None, to_addrs=None,
913 mail_options=(), rcpt_options=()):
914 """Converts message to a bytestring and passes it to sendmail.
915
916 The arguments are as for sendmail, except that msg is an
917 email.message.Message object. If from_addr is None or to_addrs is
918 None, these arguments are taken from the headers of the Message as
919 described in RFC 2822 (a ValueError is raised if there is more than
920 one set of 'Resent-' headers). Regardless of the values of from_addr and
921 to_addr, any Bcc field (or Resent-Bcc field, when the Message is a
922 resent) of the Message object won't be transmitted. The Message
923 object is then serialized using email.generator.BytesGenerator and
924 sendmail is called to transmit the message. If the sender or any of
925 the recipient addresses contain non-ASCII and the server advertises the
926 SMTPUTF8 capability, the policy is cloned with utf8 set to True for the
927 serialization, and SMTPUTF8 and BODY=8BITMIME are asserted on the send.
928 If the server does not support SMTPUTF8, an SMTPNotSupported error is
929 raised. Otherwise the generator is called without modifying the
930 policy.
931
932 """
933 # 'Resent-Date' is a mandatory field if the Message is resent (RFC 2822
934 # Section 3.6.6). In such a case, we use the 'Resent-*' fields. However,
935 # if there is more than one 'Resent-' block there's no way to
936 # unambiguously determine which one is the most recent in all cases,
937 # so rather than guess we raise a ValueError in that case.
938 #
939 # TODO implement heuristics to guess the correct Resent-* block with an
940 # option allowing the user to enable the heuristics. (It should be
941 # possible to guess correctly almost all of the time.)
942
943 self.ehlo_or_helo_if_needed()
944 resent = msg.get_all('Resent-Date')
945 if resent is None:
946 header_prefix = ''
947 elif len(resent) == 1:
948 header_prefix = 'Resent-'
949 else:
950 raise ValueError("message has more than one 'Resent-' header block")
951 if from_addr is None:
952 # Prefer the sender field per RFC 2822:3.6.2.
953 from_addr = (msg[header_prefix + 'Sender']
954 if (header_prefix + 'Sender') in msg
955 else msg[header_prefix + 'From'])
956 from_addr = email.utils.getaddresses([from_addr])[0][1]
957 if to_addrs is None:
958 addr_fields = [f for f in (msg[header_prefix + 'To'],
959 msg[header_prefix + 'Bcc'],
960 msg[header_prefix + 'Cc'])
961 if f is not None]
962 to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)]
963 # Make a local copy so we can delete the bcc headers.
964 msg_copy = copy.copy(msg)
965 del msg_copy['Bcc']
966 del msg_copy['Resent-Bcc']
967 international = False
968 try:
969 ''.join([from_addr, *to_addrs]).encode('ascii')
970 except UnicodeEncodeError:
971 if not self.has_extn('smtputf8'):
972 raise SMTPNotSupportedError(
973 "One or more source or delivery addresses require"
974 " internationalized email support, but the server"
975 " does not advertise the required SMTPUTF8 capability")
976 international = True
977 with io.BytesIO() as bytesmsg:
978 if international:
979 g = email.generator.BytesGenerator(
980 bytesmsg, policy=msg.policy.clone(utf8=True))
981 mail_options = (*mail_options, 'SMTPUTF8', 'BODY=8BITMIME')
982 else:
983 g = email.generator.BytesGenerator(bytesmsg)
984 g.flatten(msg_copy, linesep='\r\n')
985 flatmsg = bytesmsg.getvalue()
986 return self.sendmail(from_addr, to_addrs, flatmsg, mail_options,
987 rcpt_options)
988
989 def close(self):
990 """Close the connection to the SMTP server."""
991 try:
992 file = self.file
993 self.file = None
994 if file:
995 file.close()
996 finally:
997 sock = self.sock
998 self.sock = None
999 if sock:
1000 sock.close()
1001
1002 def quit(self):
1003 """Terminate the SMTP session."""
1004 res = self.docmd("quit")
1005 # A new EHLO is required after reconnecting with connect()
1006 self.ehlo_resp = self.helo_resp = None
1007 self.esmtp_features = {}
1008 self.does_esmtp = False
1009 self.close()
1010 return res
1011
1012 if _have_ssl:
1013
1014 class ESC[4;38;5;81mSMTP_SSL(ESC[4;38;5;149mSMTP):
1015 """ This is a subclass derived from SMTP that connects over an SSL
1016 encrypted socket (to use this class you need a socket module that was
1017 compiled with SSL support). If host is not specified, '' (the local
1018 host) is used. If port is omitted, the standard SMTP-over-SSL port
1019 (465) is used. local_hostname and source_address have the same meaning
1020 as they do in the SMTP class. keyfile and certfile are also optional -
1021 they can contain a PEM formatted private key and certificate chain file
1022 for the SSL connection. context also optional, can contain a
1023 SSLContext, and is an alternative to keyfile and certfile; If it is
1024 specified both keyfile and certfile must be None.
1025
1026 """
1027
1028 default_port = SMTP_SSL_PORT
1029
1030 def __init__(self, host='', port=0, local_hostname=None,
1031 keyfile=None, certfile=None,
1032 timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
1033 source_address=None, context=None):
1034 if context is not None and keyfile is not None:
1035 raise ValueError("context and keyfile arguments are mutually "
1036 "exclusive")
1037 if context is not None and certfile is not None:
1038 raise ValueError("context and certfile arguments are mutually "
1039 "exclusive")
1040 if keyfile is not None or certfile is not None:
1041 import warnings
1042 warnings.warn("keyfile and certfile are deprecated, use a "
1043 "custom context instead", DeprecationWarning, 2)
1044 self.keyfile = keyfile
1045 self.certfile = certfile
1046 if context is None:
1047 context = ssl._create_stdlib_context(certfile=certfile,
1048 keyfile=keyfile)
1049 self.context = context
1050 SMTP.__init__(self, host, port, local_hostname, timeout,
1051 source_address)
1052
1053 def _get_socket(self, host, port, timeout):
1054 if self.debuglevel > 0:
1055 self._print_debug('connect:', (host, port))
1056 new_socket = super()._get_socket(host, port, timeout)
1057 new_socket = self.context.wrap_socket(new_socket,
1058 server_hostname=self._host)
1059 return new_socket
1060
1061 __all__.append("SMTP_SSL")
1062
1063 #
1064 # LMTP extension
1065 #
1066 LMTP_PORT = 2003
1067
1068 class ESC[4;38;5;81mLMTP(ESC[4;38;5;149mSMTP):
1069 """LMTP - Local Mail Transfer Protocol
1070
1071 The LMTP protocol, which is very similar to ESMTP, is heavily based
1072 on the standard SMTP client. It's common to use Unix sockets for
1073 LMTP, so our connect() method must support that as well as a regular
1074 host:port server. local_hostname and source_address have the same
1075 meaning as they do in the SMTP class. To specify a Unix socket,
1076 you must use an absolute path as the host, starting with a '/'.
1077
1078 Authentication is supported, using the regular SMTP mechanism. When
1079 using a Unix socket, LMTP generally don't support or require any
1080 authentication, but your mileage might vary."""
1081
1082 ehlo_msg = "lhlo"
1083
1084 def __init__(self, host='', port=LMTP_PORT, local_hostname=None,
1085 source_address=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
1086 """Initialize a new instance."""
1087 super().__init__(host, port, local_hostname=local_hostname,
1088 source_address=source_address, timeout=timeout)
1089
1090 def connect(self, host='localhost', port=0, source_address=None):
1091 """Connect to the LMTP daemon, on either a Unix or a TCP socket."""
1092 if host[0] != '/':
1093 return super().connect(host, port, source_address=source_address)
1094
1095 if self.timeout is not None and not self.timeout:
1096 raise ValueError('Non-blocking socket (timeout=0) is not supported')
1097
1098 # Handle Unix-domain sockets.
1099 try:
1100 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1101 if self.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
1102 self.sock.settimeout(self.timeout)
1103 self.file = None
1104 self.sock.connect(host)
1105 except OSError:
1106 if self.debuglevel > 0:
1107 self._print_debug('connect fail:', host)
1108 if self.sock:
1109 self.sock.close()
1110 self.sock = None
1111 raise
1112 (code, msg) = self.getreply()
1113 if self.debuglevel > 0:
1114 self._print_debug('connect:', msg)
1115 return (code, msg)
1116
1117
1118 # Test the sendmail method, which tests most of the others.
1119 # Note: This always sends to localhost.
1120 if __name__ == '__main__':
1121 def prompt(prompt):
1122 sys.stdout.write(prompt + ": ")
1123 sys.stdout.flush()
1124 return sys.stdin.readline().strip()
1125
1126 fromaddr = prompt("From")
1127 toaddrs = prompt("To").split(',')
1128 print("Enter message, end with ^D:")
1129 msg = ''
1130 while 1:
1131 line = sys.stdin.readline()
1132 if not line:
1133 break
1134 msg = msg + line
1135 print("Message length is %d" % len(msg))
1136
1137 server = SMTP('localhost')
1138 server.set_debuglevel(1)
1139 server.sendmail(fromaddr, toaddrs, msg)
1140 server.quit()